Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/183558 Added page for providing extra information about project status change when status is cancelled or withdrawn #989

Merged
merged 8 commits into from
Jan 15, 2025
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project.Risk;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Common;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project.Risk;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project.Sites;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project.Tasks;

Expand All @@ -20,12 +21,22 @@ public record ProjectStatusResponse
{
public string CurrentFreeSchoolName { get; set; }
public ProjectStatus ProjectStatus { get; set; }


public ProjectCancelledReason ProjectCancelledReason { get; set; }

public ProjectWithdrawnReason ProjectWithdrawnReason { get; set; }

public DateTime? ProjectClosedDate { get; set; }

public DateTime? ProjectCancelledDate { get; set; }

public DateTime? ProjectWithdrawnDate { get; set; }

public YesNo? ProjectCancelledDueToNationalReviewOfPipelineProjects { get; set; }
public YesNo? ProjectWithdrawnDueToNationalReviewOfPipelineProjects { get; set; }
public string CommentaryForCancellation { get; set; }
public string CommentaryForWithdrawal { get; set; }

public string FreeSchoolsApplicationNumber { get; set; }
public string ProjectId { get; set; }
public string Urn { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.ComponentModel;

namespace Dfe.ManageFreeSchoolProjects.API.Contracts.Project
{
public enum ProjectCancelledReason
{
NotSet,

[Description("Educational")]
Educational,

[Description("Governance")]
Governance,

[Description("Planning")]
Planning,

[Description("Procurement / Construction")]
ProcurementConstruction,

[Description("Property")]
Property,

[Description("Pupil numbers / viability")]
PupilNumbersViability,

[Description("Trust not content with site option")]
TrustNotContentWithSiteOption,

[Description("Trust not willing to open in temporary accommodation")]
TrustNotWillingToOpenInTemporaryAccommodation


}

public enum ProjectWithdrawnReason
{
NotSet,

[Description("Educational")]
Educational,

[Description("Governance")]
Governance,

[Description("Planning")]
Planning,

[Description("Procurement / Construction")]
ProcurementConstruction,

[Description("Property")]
Property,

[Description("Pupil numbers / viability")]
PupilNumbersViability,

[Description("Trust not content with site option")]
TrustNotContentWithSiteOption,

[Description("Trust not willing to open in temporary accommodation")]
TrustNotWillingToOpenInTemporaryAccommodation


}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
using Dfe.ManageFreeSchoolProjects.API.Contracts.Common;

namespace Dfe.ManageFreeSchoolProjects.API.Contracts.Project;

public class UpdateProjectStatusRequest
{
public ProjectStatus ProjectStatus { get; set; }


public ProjectCancelledReason ProjectCancelledReason { get; set; }

public ProjectWithdrawnReason ProjectWithdrawnReason { get; set; }

public DateTime? CancelledDate { get; set; }

public DateTime? ClosedDate { get; set; }

public DateTime? WithdrawnDate { get; set; }

public YesNo? ProjectCancelledDueToNationalReviewOfPipelineProjects { get; set; }

public YesNo? ProjectWithdrawnDueToNationalReviewOfPipelineProjects { get; set; }

public string CommentaryForCancellation { get; set; }
public string CommentaryForWithdrawal { get; set; }

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ namespace Dfe.ManageFreeSchoolProjects.API.Contracts.Project;
public class UpdateProjectStatusResponse
{
public ProjectStatus ProjectStatus { get; set; }


public ProjectCancelledReason ProjectCancelledReason { get; set; }

public ProjectWithdrawnReason ProjectWithdrawnReason { get; set; }

public DateTime? CancelledDate { get; set; }

public DateTime? ClosedDate { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using Azure.Core;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Common;
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project;
using Dfe.ManageFreeSchoolProjects.API.Contracts.ResponseModels;
using Dfe.ManageFreeSchoolProjects.API.Tests.Fixtures;
using Dfe.ManageFreeSchoolProjects.API.Tests.Helpers;
using Dfe.ManageFreeSchoolProjects.API.UseCases.Project;
using Dfe.ManageFreeSchoolProjects.API.UseCases.ProjectOverview;
using System;
using System.Net;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace Dfe.ManageFreeSchoolProjects.API.Tests.Integration
{
[Collection(ApiTestCollection.ApiTestCollectionName)]
public class ProjectStatusApiTests : ApiTestsBase
{
public ProjectStatusApiTests(ApiTestFixture apiTestFixture) : base(apiTestFixture)
{
}

[Fact]
public async Task When_Post_ProjectDoesNotExist_Returns_404()
{
var projectId = Guid.NewGuid().ToString();
var updateStatusRequest = _autoFixture.Create<UpdateProjectStatusRequest>();

var updateStatusResponse = await _client.PostAsync($"/api/v1/client/updateprojectstatus?projectId={projectId}", updateStatusRequest.ConvertToJson());
updateStatusResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

[Fact]
public async Task When_Post_StatusValid_Returns_200()
{
var project = DatabaseModelBuilder.BuildProject();
var projectId = project.ProjectStatusProjectId;
var updateStatusRequest = new UpdateProjectStatusRequest()
{
ProjectStatus = ProjectStatus.Open,
ProjectCancelledReason = ProjectCancelledReason.NotSet,
ProjectWithdrawnReason = ProjectWithdrawnReason.NotSet,
CancelledDate = null,
ClosedDate = null,
WithdrawnDate = null,
ProjectCancelledDueToNationalReviewOfPipelineProjects = null,
ProjectWithdrawnDueToNationalReviewOfPipelineProjects = null,
CommentaryForCancellation = null,
CommentaryForWithdrawal = null

};


using var context = _testFixture.GetContext();
context.Kpi.Add(project);
await context.SaveChangesAsync();

var updateStatusResponse = await _client.PostAsync($"/api/v1/client/updateprojectstatus?projectId={projectId}", updateStatusRequest.ConvertToJson());
updateStatusResponse.StatusCode.Should().Be(HttpStatusCode.OK);

var overviewResponse = await _client.GetAsync($"/api/v1/client/projects/{project.ProjectStatusProjectId}/overview");
overviewResponse.StatusCode.Should().Be(HttpStatusCode.OK);

var result = await overviewResponse.Content.ReadFromJsonAsync<ApiSingleResponseV2<ProjectOverviewResponse>>();

// Project status
var projectStatus = result.Data.ProjectStatus;
projectStatus.ProjectStatus.Should().Be(ProjectStatus.Open);
projectStatus.ProjectCancelledReason.Should().Be(ProjectCancelledReason.NotSet);
projectStatus.ProjectWithdrawnReason.Should().Be(ProjectWithdrawnReason.NotSet);
projectStatus.ProjectCancelledDate.Should().BeNull();
projectStatus.ProjectClosedDate.Should().BeNull();
projectStatus.ProjectWithdrawnDate.Should().BeNull();
projectStatus.ProjectCancelledDueToNationalReviewOfPipelineProjects.Should().BeNull();
projectStatus.ProjectWithdrawnDueToNationalReviewOfPipelineProjects.Should().BeNull();
projectStatus.CommentaryForCancellation.Should().BeNull();
projectStatus.CommentaryForWithdrawal.Should().BeNull();


}

[Fact]
public async Task When_Post_StatusCancelledValid_Returns_200()
{
var project = DatabaseModelBuilder.BuildProject();
var projectId = project.ProjectStatusProjectId;

var cancelledDate = DateTime.UtcNow.Date;
var commentaryForCancellation = "Cancelled due to issues with planning";
var updateStatusRequest = new UpdateProjectStatusRequest()
{
ProjectStatus = ProjectStatus.Cancelled,
ProjectCancelledReason = ProjectCancelledReason.Planning,
ProjectWithdrawnReason = ProjectWithdrawnReason.NotSet,
CancelledDate = cancelledDate,
ClosedDate = null,
WithdrawnDate = null,
ProjectCancelledDueToNationalReviewOfPipelineProjects = YesNo.No,
ProjectWithdrawnDueToNationalReviewOfPipelineProjects = null,
CommentaryForCancellation = commentaryForCancellation,
CommentaryForWithdrawal = null

};


using var context = _testFixture.GetContext();
context.Kpi.Add(project);
await context.SaveChangesAsync();

var updateStatusResponse = await _client.PostAsync($"/api/v1/client/updateprojectstatus?projectId={projectId}", updateStatusRequest.ConvertToJson());
updateStatusResponse.StatusCode.Should().Be(HttpStatusCode.OK);

var overviewResponse = await _client.GetAsync($"/api/v1/client/projects/{project.ProjectStatusProjectId}/overview");
overviewResponse.StatusCode.Should().Be(HttpStatusCode.OK);

var result = await overviewResponse.Content.ReadFromJsonAsync<ApiSingleResponseV2<ProjectOverviewResponse>>();

// Project status
var projectStatus = result.Data.ProjectStatus;
projectStatus.ProjectStatus.Should().Be(ProjectStatus.Cancelled);
projectStatus.ProjectCancelledReason.Should().Be(ProjectCancelledReason.Planning);
projectStatus.ProjectWithdrawnReason.Should().Be(ProjectWithdrawnReason.NotSet);
projectStatus.ProjectCancelledDate.Should().Be(cancelledDate);
projectStatus.ProjectClosedDate.Should().BeNull();
projectStatus.ProjectWithdrawnDate.Should().BeNull();
projectStatus.ProjectCancelledDueToNationalReviewOfPipelineProjects.Should().Be(YesNo.No);
projectStatus.ProjectWithdrawnDueToNationalReviewOfPipelineProjects.Should().BeNull();
projectStatus.CommentaryForCancellation.Should().Be(commentaryForCancellation);
projectStatus.CommentaryForWithdrawal.Should().BeNull();


}

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Dfe.ManageFreeSchoolProjects.API.Contracts.Project.Tasks;
using SchoolType = Dfe.ManageFreeSchoolProjects.API.Contracts.Project.SchoolType;
using ProjectStatusType = Dfe.ManageFreeSchoolProjects.API.Contracts.Project.ProjectStatus;
using Dfe.ManageFreeSchoolProjects.Data.Entities.Existing;
using ProjectCancelledReasonType = Dfe.ManageFreeSchoolProjects.API.Contracts.Project.ProjectCancelledReason;
using ProjectWithdrawnReasonType = Dfe.ManageFreeSchoolProjects.API.Contracts.Project.ProjectWithdrawnReason;

namespace Dfe.ManageFreeSchoolProjects.API.UseCases.Project
{
Expand Down Expand Up @@ -121,5 +122,69 @@ public static string FromProjectStatusType(ProjectStatusType projectStatus)
_ => throw new ArgumentOutOfRangeException(nameof(projectStatus), projectStatus, null)
};
}

public static ProjectCancelledReasonType ToProjectCancelledReasonType(string projectCancelledReason)
{
return projectCancelledReason?.ToLower() switch
{
"educational" => ProjectCancelledReasonType.Educational,
"governance" => ProjectCancelledReasonType.Governance,
"planning" => ProjectCancelledReasonType.Planning,
"procurement / construction" => ProjectCancelledReasonType.ProcurementConstruction,
"property" => ProjectCancelledReasonType.Property,
"pupil numbers / viability" => ProjectCancelledReasonType.PupilNumbersViability,
"trust not content with site option" => ProjectCancelledReasonType.TrustNotContentWithSiteOption,
"trust not willing to open in temporary accommodation" => ProjectCancelledReasonType.TrustNotWillingToOpenInTemporaryAccommodation,
_ => ProjectCancelledReasonType.NotSet
};
}

public static string FromProjectCancelledReasonType(ProjectCancelledReasonType projectCancelledReason)
{
return projectCancelledReason switch
{
ProjectCancelledReasonType.Educational => "educational",
ProjectCancelledReasonType.Governance => "governance",
ProjectCancelledReasonType.Planning => "planning",
ProjectCancelledReasonType.ProcurementConstruction => "procurement / construction",
ProjectCancelledReasonType.Property => "property",
ProjectCancelledReasonType.PupilNumbersViability => "pupil numbers / viability",
ProjectCancelledReasonType.TrustNotContentWithSiteOption => "trust not content with site option",
ProjectCancelledReasonType.TrustNotWillingToOpenInTemporaryAccommodation => "trust not willing to open in temporary accommodation",
_ => ""
};
}

public static ProjectWithdrawnReasonType ToProjectWithdrawnReasonType(string projectWithdrawnReason)
{
return projectWithdrawnReason?.ToLower() switch
{
"educational" => ProjectWithdrawnReasonType.Educational,
"governance" => ProjectWithdrawnReasonType.Governance,
"planning" => ProjectWithdrawnReasonType.Planning,
"procurement / construction" => ProjectWithdrawnReasonType.ProcurementConstruction,
"property" => ProjectWithdrawnReasonType.Property,
"pupil numbers / viability" => ProjectWithdrawnReasonType.PupilNumbersViability,
"trust not content with site option" => ProjectWithdrawnReasonType.TrustNotContentWithSiteOption,
"trust not willing to open in temporary accommodation" => ProjectWithdrawnReasonType.TrustNotWillingToOpenInTemporaryAccommodation,
_ => ProjectWithdrawnReasonType.NotSet
};
}

public static string FromProjectWithdrawnReasonType(ProjectWithdrawnReasonType projectWithdrawnReason)
{
return projectWithdrawnReason switch
{
ProjectWithdrawnReasonType.Educational => "educational",
ProjectWithdrawnReasonType.Governance => "governance",
ProjectWithdrawnReasonType.Planning => "planning",
ProjectWithdrawnReasonType.ProcurementConstruction => "procurement / construction",
ProjectWithdrawnReasonType.Property => "property",
ProjectWithdrawnReasonType.PupilNumbersViability => "pupil numbers / viability",
ProjectWithdrawnReasonType.TrustNotContentWithSiteOption => "trust not content with site option",
ProjectWithdrawnReasonType.TrustNotWillingToOpenInTemporaryAccommodation => "trust not willing to open in temporary accommodation",
_ => ""
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Dfe.ManageFreeSchoolProjects.API.Exceptions;
using Dfe.ManageFreeSchoolProjects.API.Extensions;
using Dfe.ManageFreeSchoolProjects.Data;
using Dfe.ManageFreeSchoolProjects.Data.Migrations;
using Microsoft.EntityFrameworkCore;

namespace Dfe.ManageFreeSchoolProjects.API.UseCases.Project.ProjectStatus;
Expand Down Expand Up @@ -29,19 +30,19 @@ public async Task Execute(string projectId, UpdateProjectStatusRequest request)
throw new NotFoundException($"Project with id {projectId} not found");
}

var updateRequest = new UpdateProjectStatusRequest()
{
ProjectStatus = request.ProjectStatus,
WithdrawnDate = request.WithdrawnDate,
ClosedDate = request.ClosedDate,
CancelledDate = request.CancelledDate
};

dbProject.ProjectStatusProjectStatus = ProjectMapper.FromProjectStatusType(updateRequest.ProjectStatus);
dbProject.ProjectStatusDateClosed = updateRequest.ClosedDate;
dbProject.ProjectStatusDateCancelled = updateRequest.CancelledDate;
dbProject.ProjectStatusDateWithdrawn = updateRequest.WithdrawnDate;
dbProject.ProjectStatusProjectStatus = ProjectMapper.FromProjectStatusType(request.ProjectStatus);
dbProject.ProjectStatusDateClosed = request.ClosedDate;

dbProject.ProjectStatusDateCancelled = request.CancelledDate;
dbProject.ProjectStatusPrimaryReasonForCancellation = ProjectMapper.FromProjectCancelledReasonType(request.ProjectCancelledReason);
dbProject.ProjectStatusProjectCancelledDueToNationalReviewOfPipelineProjects = request.ProjectCancelledDueToNationalReviewOfPipelineProjects;
dbProject.ProjectStatusCommentaryForCancellation = request.CommentaryForCancellation;

dbProject.ProjectStatusDateWithdrawn = request.WithdrawnDate;
dbProject.ProjectStatusPrimaryReasonForWithdrawal = ProjectMapper.FromProjectWithdrawnReasonType(request.ProjectWithdrawnReason);
dbProject.ProjectStatusProjectWithdrawnDueToNationalReviewOfPipelineProjects = request.ProjectWithdrawnDueToNationalReviewOfPipelineProjects;
dbProject.ProjectStatusCommentaryForWithdrawal = request.CommentaryForWithdrawal;

await _context.SaveChangesAsync();
}

Expand Down
Loading
Loading