From 74a68bfd51c57c447802c3348ce8c25463e2ee89 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:45:17 +0200 Subject: [PATCH 01/15] Init db tables --- .../Database/Models/DbProject.cs | 22 +++++++++ .../Models/DbWeeklyTaskOwnerReport.cs | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/Fusion.Summary.Api/Database/Models/DbProject.cs create mode 100644 src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs diff --git a/src/Fusion.Summary.Api/Database/Models/DbProject.cs b/src/Fusion.Summary.Api/Database/Models/DbProject.cs new file mode 100644 index 000000000..88fa368ee --- /dev/null +++ b/src/Fusion.Summary.Api/Database/Models/DbProject.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Database.Models; + +public class DbProject +{ + public required Guid Id { get; set; } + + public required string Name { get; set; } + public required Guid OrgProjectExternalId { get; set; } + + + internal static void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(project => + { + project.ToTable("Projects"); + project.HasKey(p => p.Id); + project.HasIndex(p => p.OrgProjectExternalId).IsUnique(); + }); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs new file mode 100644 index 000000000..47c743a0c --- /dev/null +++ b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Database.Models; + +public class DbWeeklyTaskOwnerReport +{ + public required Guid Id { get; set; } + public required Guid ProjectId { get; set; } + public DbProject? Project { get; set; } + + public required DateTime Period { get; set; } + + internal static void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(report => + { + report.ToTable("WeeklyTaskOwnerReports"); + report.HasKey(r => r.Id); + + report.HasOne(r => r.Project) + .WithMany() + .HasForeignKey(r => r.ProjectId) + .OnDelete(DeleteBehavior.Restrict); + }); + } +} + +public class DbAdminAccessExpiring +{ + public required Guid AzureUniqueId { get; set; } + public required string FullName { get; set; } + public required DateTime Expires { get; set; } +} + +public class DbActionsAwaitingTaskOwner +{ + // TODO: Implement +} + +public class DbPositionAllocationsEnding +{ + // TODO: Implement +} + +public class DbTBNPositionsStartingSoon +{ + // TODO: Implement +} \ No newline at end of file From 4ded63056e2eef6a8cb439ffe2aed34a33a36e40 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:15:35 +0200 Subject: [PATCH 02/15] feat: TaskOwnerReport code without KPIs and tests --- src/Fusion.Summary.Api/BaseController.cs | 3 + .../Controllers/ApiModels/ApiProject.cs | 28 ++++ .../ApiModels/ApiWeeklyTaskOwnerReport.cs | 23 ++++ .../Controllers/ProjectsController.cs | 121 ++++++++++++++++++ .../Controllers/Requests/PutProjectRequest.cs | 23 ++++ .../PutWeeklyTaskOwnerReportRequest.cs | 26 ++++ .../Controllers/TaskOwnerSummaryController.cs | 116 +++++++++++++++++ .../Database/Models/DbProject.cs | 5 + .../Models/DbWeeklyTaskOwnerReport.cs | 23 +++- .../Database/SummaryDbContext.cs | 6 + .../Domain/Commands/CreateProject.cs | 51 ++++++++ .../Commands/PutWeeklyTaskOwnerReport.cs | 61 +++++++++ .../Domain/Commands/UpdateProject.cs | 52 ++++++++ .../Domain/Models/Period.cs | 50 ++++++++ .../Domain/Models/PeriodType.cs | 6 + .../Domain/Models/QueryProject.cs | 38 ++++++ .../Models/QueryWeeklyTaskOwnerReport.cs | 32 +++++ .../Domain/Queries/GetProjects.cs | 41 ++++++ .../Queries/GetWeeklyTaskOwnerReports.cs | 61 +++++++++ 19 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 src/Fusion.Summary.Api/Controllers/ApiModels/ApiProject.cs create mode 100644 src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs create mode 100644 src/Fusion.Summary.Api/Controllers/ProjectsController.cs create mode 100644 src/Fusion.Summary.Api/Controllers/Requests/PutProjectRequest.cs create mode 100644 src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs create mode 100644 src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs create mode 100644 src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs create mode 100644 src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs create mode 100644 src/Fusion.Summary.Api/Domain/Commands/UpdateProject.cs create mode 100644 src/Fusion.Summary.Api/Domain/Models/Period.cs create mode 100644 src/Fusion.Summary.Api/Domain/Models/PeriodType.cs create mode 100644 src/Fusion.Summary.Api/Domain/Models/QueryProject.cs create mode 100644 src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs create mode 100644 src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs create mode 100644 src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs diff --git a/src/Fusion.Summary.Api/BaseController.cs b/src/Fusion.Summary.Api/BaseController.cs index e99ab82b1..b99f43c77 100644 --- a/src/Fusion.Summary.Api/BaseController.cs +++ b/src/Fusion.Summary.Api/BaseController.cs @@ -10,6 +10,9 @@ public class BaseController : ControllerBase protected ActionResult DepartmentNotFound(string sapDepartmentId) => FusionApiError.NotFound(sapDepartmentId, $"Department with sap id '{sapDepartmentId}' was not found"); + protected ActionResult ProjectNotFound(Guid projectId) => + FusionApiError.NotFound(projectId, $"Project with id '{projectId}' was not found"); + protected ActionResult SapDepartmentIdRequired() => FusionApiError.InvalidOperation("SapDepartmentIdRequired", "SapDepartmentId route parameter is required"); diff --git a/src/Fusion.Summary.Api/Controllers/ApiModels/ApiProject.cs b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiProject.cs new file mode 100644 index 000000000..b7c6a951a --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiProject.cs @@ -0,0 +1,28 @@ +using Fusion.Summary.Api.Domain.Models; + +namespace Fusion.Summary.Api.Controllers.ApiModels; + +public class ApiProject +{ + public required Guid Id { get; set; } + + public required string Name { get; set; } + public required Guid OrgProjectExternalId { get; set; } + + public Guid? DirectorAzureUniqueId { get; set; } + + public Guid[] AssignedAdminsAzureUniqueId { get; set; } = []; + + + public static ApiProject FromQueryProject(QueryProject queryProject) + { + return new ApiProject() + { + Id = queryProject.Id, + Name = queryProject.Name, + OrgProjectExternalId = queryProject.OrgProjectExternalId, + AssignedAdminsAzureUniqueId = queryProject.AssignedAdminsAzureUniqueId.ToArray(), + DirectorAzureUniqueId = queryProject.DirectorAzureUniqueId + }; + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs new file mode 100644 index 000000000..ca77baa65 --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs @@ -0,0 +1,23 @@ +using Fusion.Summary.Api.Domain.Models; + +namespace Fusion.Summary.Api.Controllers.ApiModels; + +public class ApiWeeklyTaskOwnerReport +{ + public required Guid Id { get; set; } + public required Guid ProjectId { get; set; } + public required DateTime PeriodStart { get; set; } + public required DateTime PeriodEnd { get; set; } + + + public static ApiWeeklyTaskOwnerReport FromQueryWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryWeeklyTaskOwnerReport) + { + return new ApiWeeklyTaskOwnerReport + { + Id = queryWeeklyTaskOwnerReport.Id, + ProjectId = queryWeeklyTaskOwnerReport.ProjectId, + PeriodStart = queryWeeklyTaskOwnerReport.Period.Start, + PeriodEnd = queryWeeklyTaskOwnerReport.Period.End + }; + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs new file mode 100644 index 000000000..637b330fb --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs @@ -0,0 +1,121 @@ +using System.Net.Mime; +using Fusion.AspNetCore.FluentAuthorization; +using Fusion.Authorization; +using Fusion.Integration.Profile; +using Fusion.Summary.Api.Authorization.Extensions; +using Fusion.Summary.Api.Controllers.ApiModels; +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Summary.Api.Domain.Commands; +using Fusion.Summary.Api.Domain.Queries; +using Microsoft.ApplicationInsights.AspNetCore.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Fusion.Summary.Api.Controllers; + +[Authorize] +[ApiController] +[Produces(MediaTypeNames.Application.Json)] +[ApiVersion("1.0")] +public class ProjectsController : BaseController +{ + [HttpGet("projects")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetProjectsV1() + { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + + var projects = await DispatchAsync(new GetProjects()); + + var apiProjects = projects.Select(ApiProject.FromQueryProject); + + return Ok(apiProjects); + } + + + [HttpGet("projects/{projectId:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetProjectsV1(Guid projectId) + { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + + var projects = await DispatchAsync(new GetProjects().WhereProjectId(projectId)); + + var apiProjects = projects.Select(ApiProject.FromQueryProject); + + return Ok(apiProjects.FirstOrDefault()); + } + + [HttpPut("projects/{projectId:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> PutProjectsV1(Guid projectId, PutProjectRequest request) + { + #region Authorization + + var authResult = await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion Authorization + + var personIdentifiers = request.AssignedAdminsAzureUniqueId + .Select(p => new PersonIdentifier(p)); + + if (request.DirectorAzureUniqueId.HasValue) + personIdentifiers = personIdentifiers.Append(new PersonIdentifier(request.DirectorAzureUniqueId.Value)); + + var unresolvedProfiles = (await ResolvePersonsAsync(personIdentifiers)) + .Where(r => !r.Success) + .ToList(); + + if (unresolvedProfiles.Count != 0) + return FusionApiError.NotFound(string.Join(',', unresolvedProfiles), "Profiles could not be resolved"); + + + var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); + + if (project == null) + { + await DispatchAsync(new CreateProject(request)); + + return Created(Request.GetUri(), null); + } + + await DispatchAsync(new UpdateProject(project.Id, request)); + + return NoContent(); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/Requests/PutProjectRequest.cs b/src/Fusion.Summary.Api/Controllers/Requests/PutProjectRequest.cs new file mode 100644 index 000000000..48fe9e8f5 --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/Requests/PutProjectRequest.cs @@ -0,0 +1,23 @@ +using FluentValidation; + +namespace Fusion.Summary.Api.Controllers.Requests; + +public class PutProjectRequest +{ + public required string Name { get; set; } + public required Guid OrgProjectExternalId { get; set; } + + public Guid? DirectorAzureUniqueId { get; set; } + + public Guid[] AssignedAdminsAzureUniqueId { get; set; } = []; + + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Name).NotEmpty(); + RuleFor(x => x.OrgProjectExternalId).NotEmpty(); + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs b/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs new file mode 100644 index 000000000..a195eabbe --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs @@ -0,0 +1,26 @@ +using FluentValidation; + +namespace Fusion.Summary.Api.Controllers.Requests; + +public class PutWeeklyTaskOwnerReportRequest +{ + public DateTime PeriodStart { get; set; } + public DateTime PeriodEnd { get; set; } + + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.PeriodStart).NotEmpty(); + RuleFor(x => x.PeriodEnd).NotEmpty(); + RuleFor(x => x.PeriodStart).LessThan(x => x.PeriodEnd); + RuleFor(x => x.PeriodStart).Must(x => x.DayOfWeek == DayOfWeek.Monday).WithMessage("Period start must be a Monday"); + RuleFor(x => x.PeriodEnd).Must(x => x.DayOfWeek == DayOfWeek.Monday).WithMessage("Period end must be a Monday"); + + RuleFor(x => x) + .Must(x => x.PeriodEnd.Date == x.PeriodStart.Date.AddDays(7)) + .WithMessage("Period must be exactly 7 days"); + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs new file mode 100644 index 000000000..3a6863cbe --- /dev/null +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs @@ -0,0 +1,116 @@ +using System.Net.Mime; +using Fusion.AspNetCore.FluentAuthorization; +using Fusion.AspNetCore.OData; +using Fusion.Authorization; +using Fusion.Summary.Api.Authorization.Extensions; +using Fusion.Summary.Api.Controllers.ApiModels; +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Summary.Api.Domain.Commands; +using Fusion.Summary.Api.Domain.Queries; +using Microsoft.ApplicationInsights.AspNetCore.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Fusion.Summary.Api.Controllers; + +[Authorize] +[ApiController] +[Produces(MediaTypeNames.Application.Json)] +[ApiVersion("1.0")] +public class TaskOwnerSummaryController : BaseController +{ + [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ODataTop(100), ODataSkip] + public async Task>> GetWeeklyTaskOwnerReportsV1(Guid projectId, ODataQueryParams query) + { + #region Authorization + + var authResult = + await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion + + if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null) + return ProjectNotFound(projectId); + + + var projects = await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, query)); + + return Ok(ApiCollection.FromQueryCollection(projects, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport)); + } + + [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly/{reportId:guid}")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetWeeklyTaskOwnerReportV1(Guid projectId, Guid reportId) + { + #region Authorization + + var authResult = + await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion + + if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null) + return ProjectNotFound(projectId); + + var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, new ODataQueryParams()).WhereReportId(reportId))).FirstOrDefault(); + + return report is null ? NotFound() : Ok(ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport(report)); + } + + /// + /// Summary report key is composed of the project id and the period start and end dates. + /// If a report already exists for the given period and project id, it will be replaced. + /// + [HttpPut("task-owners-summary-reports/{projectId:guid}/weekly")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task PutWeeklyTaskOwnerReportV1(Guid projectId, [FromBody] PutWeeklyTaskOwnerReportRequest request) + { + #region Authorization + + var authResult = + await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion + + var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); + if (project is null) + return ProjectNotFound(projectId); + + var command = new PutWeeklyTaskOwnerReport(project.Id, request); + + var newReportCreated = await DispatchAsync(command); + + return newReportCreated ? Created(Request.GetUri(), null) : NoContent(); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Database/Models/DbProject.cs b/src/Fusion.Summary.Api/Database/Models/DbProject.cs index 88fa368ee..16376a42d 100644 --- a/src/Fusion.Summary.Api/Database/Models/DbProject.cs +++ b/src/Fusion.Summary.Api/Database/Models/DbProject.cs @@ -9,6 +9,10 @@ public class DbProject public required string Name { get; set; } public required Guid OrgProjectExternalId { get; set; } + public Guid? DirectorAzureUniqueId { get; set; } + + public List AssignedAdminsAzureUniqueId { get; set; } = []; + internal static void OnModelCreating(ModelBuilder modelBuilder) { @@ -16,6 +20,7 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) { project.ToTable("Projects"); project.HasKey(p => p.Id); + project.Property(p => p.Name).HasMaxLength(500); project.HasIndex(p => p.OrgProjectExternalId).IsUnique(); }); } diff --git a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs index 47c743a0c..a924a6e21 100644 --- a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs @@ -8,7 +8,13 @@ public class DbWeeklyTaskOwnerReport public required Guid ProjectId { get; set; } public DbProject? Project { get; set; } - public required DateTime Period { get; set; } + public required DateTime PeriodStart { get; set; } + public required DateTime PeriodEnd { get; set; } + + // + // Add columns + // + internal static void OnModelCreating(ModelBuilder modelBuilder) { @@ -17,6 +23,17 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) report.ToTable("WeeklyTaskOwnerReports"); report.HasKey(r => r.Id); + report.HasIndex(r => new { r.ProjectId, Period = r.PeriodStart }) + .IsUnique(); + + report.Property(r => r.PeriodStart) + // Strip time from date and retrieve as UTC + .HasConversion(d => d.Date, d => DateTime.SpecifyKind(d, DateTimeKind.Utc)); + + report.Property(r => r.PeriodEnd) + // Strip time from date and retrieve as UTC + .HasConversion(d => d.Date, d => DateTime.SpecifyKind(d, DateTimeKind.Utc)); + report.HasOne(r => r.Project) .WithMany() .HasForeignKey(r => r.ProjectId) @@ -25,6 +42,8 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) } } +// TODO: Implement the following models + public class DbAdminAccessExpiring { public required Guid AzureUniqueId { get; set; } @@ -32,7 +51,7 @@ public class DbAdminAccessExpiring public required DateTime Expires { get; set; } } -public class DbActionsAwaitingTaskOwner +public class DbActionsAwaitingTaskOwners { // TODO: Implement } diff --git a/src/Fusion.Summary.Api/Database/SummaryDbContext.cs b/src/Fusion.Summary.Api/Database/SummaryDbContext.cs index d89df43e0..6ea3bba73 100644 --- a/src/Fusion.Summary.Api/Database/SummaryDbContext.cs +++ b/src/Fusion.Summary.Api/Database/SummaryDbContext.cs @@ -8,6 +8,10 @@ public class SummaryDbContext : DbContext public DbSet Departments { get; set; } public DbSet WeeklySummaryReports { get; set; } + public DbSet Projects { get; set; } + + public DbSet WeeklyTaskOwnerReports { get; set; } + public SummaryDbContext(DbContextOptions options) : base(options) { } @@ -16,6 +20,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); DbDepartment.OnModelCreating(modelBuilder); DbWeeklySummaryReport.OnModelCreating(modelBuilder); + DbProject.OnModelCreating(modelBuilder); + DbWeeklyTaskOwnerReport.OnModelCreating(modelBuilder); } } diff --git a/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs b/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs new file mode 100644 index 000000000..8728eab17 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs @@ -0,0 +1,51 @@ +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Summary.Api.Database; +using Fusion.Summary.Api.Database.Models; +using Fusion.Summary.Api.Domain.Models; +using MediatR; + +namespace Fusion.Summary.Api.Domain.Commands; + +public class CreateProject : IRequest +{ + public string Name { get; } + public Guid OrgProjectExternalId { get; } + public Guid? DirectorAzureUniqueId { get; } + public List AssignedAdminsAzureUniqueId { get; } + + public CreateProject(PutProjectRequest putRequest) + { + Name = putRequest.Name; + OrgProjectExternalId = putRequest.OrgProjectExternalId; + DirectorAzureUniqueId = putRequest.DirectorAzureUniqueId; + AssignedAdminsAzureUniqueId = putRequest.AssignedAdminsAzureUniqueId.ToList(); + } + + public class Handler : IRequestHandler + { + private readonly SummaryDbContext _dbContext; + + public Handler(SummaryDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(CreateProject request, CancellationToken cancellationToken) + { + var dbProject = new DbProject() + { + Id = Guid.NewGuid(), + Name = request.Name, + OrgProjectExternalId = request.OrgProjectExternalId, + DirectorAzureUniqueId = request.DirectorAzureUniqueId, + AssignedAdminsAzureUniqueId = request.AssignedAdminsAzureUniqueId + }; + + _dbContext.Projects.Add(dbProject); + + await _dbContext.SaveChangesAsync(cancellationToken); + + return QueryProject.FromDbProject(dbProject); + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs new file mode 100644 index 000000000..b2e3b6b94 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs @@ -0,0 +1,61 @@ +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Summary.Api.Database; +using Fusion.Summary.Api.Database.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Domain.Commands; + +public class PutWeeklyTaskOwnerReport : IRequest +{ + public Guid ProjectId { get; } + public PutWeeklyTaskOwnerReportRequest Report { get; } + + public PutWeeklyTaskOwnerReport(Guid projectId, PutWeeklyTaskOwnerReportRequest report) + { + ProjectId = projectId; + Report = report; + } + + + public class Handler : IRequestHandler + { + private readonly SummaryDbContext _dbContext; + + public Handler(SummaryDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationToken cancellationToken) + { + var project = await _dbContext.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId || p.OrgProjectExternalId == request.ProjectId, cancellationToken); + if (project == null) + throw new InvalidOperationException($"Project with id '{request.ProjectId}' was not found"); + + var existingReport = await _dbContext.WeeklyTaskOwnerReports + .FirstOrDefaultAsync(r => r.ProjectId == project.Id && + request.Report.PeriodStart.Date == r.PeriodStart.Date && + request.Report.PeriodEnd.Date == r.PeriodEnd.Date, cancellationToken); + + if (existingReport is not null) + _dbContext.WeeklyTaskOwnerReports.Remove(existingReport); + + var report = new DbWeeklyTaskOwnerReport() + { + Id = existingReport?.Id ?? Guid.NewGuid(), + PeriodStart = request.Report.PeriodStart, + PeriodEnd = request.Report.PeriodEnd, + ProjectId = project.Id + }; + + + _dbContext.WeeklyTaskOwnerReports.Add(report); + + await _dbContext.SaveChangesAsync(cancellationToken); + + // return true if a new report was created + return existingReport is null; + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Commands/UpdateProject.cs b/src/Fusion.Summary.Api/Domain/Commands/UpdateProject.cs new file mode 100644 index 000000000..1899282a8 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Commands/UpdateProject.cs @@ -0,0 +1,52 @@ +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Summary.Api.Database; +using Fusion.Summary.Api.Domain.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Domain.Commands; + +public class UpdateProject : IRequest +{ + public Guid Id { get; } + public string Name { get; } + public Guid OrgProjectExternalId { get; } + public Guid? DirectorAzureUniqueId { get; } + public List AssignedAdminsAzureUniqueId { get; } + + public UpdateProject(Guid id, PutProjectRequest putRequest) + { + Id = id; + Name = putRequest.Name; + OrgProjectExternalId = putRequest.OrgProjectExternalId; + DirectorAzureUniqueId = putRequest.DirectorAzureUniqueId; + AssignedAdminsAzureUniqueId = putRequest.AssignedAdminsAzureUniqueId.ToList(); + } + + + public class Handler : IRequestHandler + { + private readonly SummaryDbContext _dbContext; + + public Handler(SummaryDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task Handle(UpdateProject request, CancellationToken cancellationToken) + { + var project = await _dbContext.Projects.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken); + if (project == null) + throw new InvalidOperation($"Project with id {request.OrgProjectExternalId} not found"); + + project.Name = request.Name; + project.OrgProjectExternalId = request.OrgProjectExternalId; + project.DirectorAzureUniqueId = request.DirectorAzureUniqueId; + project.AssignedAdminsAzureUniqueId = request.AssignedAdminsAzureUniqueId; + + await _dbContext.SaveChangesAsync(cancellationToken); + + return QueryProject.FromDbProject(project); + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/Period.cs b/src/Fusion.Summary.Api/Domain/Models/Period.cs new file mode 100644 index 000000000..612a97386 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Models/Period.cs @@ -0,0 +1,50 @@ +namespace Fusion.Summary.Api.Domain.Models; + +public sealed class Period +{ + public PeriodType Type { get; init; } + public DateTime Start { get; init; } + public DateTime End { get; init; } + + public Period(PeriodType type, DateTime start, DateTime end) + { + switch (type) + { + case PeriodType.Weekly: + if (end - start != TimeSpan.FromDays(7)) + throw new ArgumentException("Weekly report period must be exactly 7 days", nameof(end)); + if (start.DayOfWeek != DayOfWeek.Monday) + throw new ArgumentException("Weekly report period must start on a Monday", nameof(start)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + Type = type; + Start = start.Date; + End = end.Date; + } + + public static Period FromStart(PeriodType type, DateTime start) + { + var end = type switch + { + PeriodType.Weekly => start.AddDays(7), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + + return new Period(type, start, end); + } + + + public static Period FromEnd(PeriodType type, DateTime end) + { + var start = type switch + { + PeriodType.Weekly => end.AddDays(-7), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + + return new Period(type, start, end); + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs b/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs new file mode 100644 index 000000000..af5e2c4fe --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs @@ -0,0 +1,6 @@ +namespace Fusion.Summary.Api.Domain.Models; + +public enum PeriodType +{ + Weekly +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/QueryProject.cs b/src/Fusion.Summary.Api/Domain/Models/QueryProject.cs new file mode 100644 index 000000000..fbd0bd21a --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Models/QueryProject.cs @@ -0,0 +1,38 @@ +using Fusion.Summary.Api.Database.Models; + +namespace Fusion.Summary.Api.Domain.Models; + +public class QueryProject +{ + public required Guid Id { get; set; } + + public required string Name { get; set; } + public required Guid OrgProjectExternalId { get; set; } + + public Guid? DirectorAzureUniqueId { get; set; } + + public List AssignedAdminsAzureUniqueId { get; set; } = []; + + public static QueryProject FromDbProject(DbProject dbProject) + { + return new QueryProject() + { + Id = dbProject.Id, + Name = dbProject.Name, + OrgProjectExternalId = dbProject.OrgProjectExternalId, + AssignedAdminsAzureUniqueId = dbProject.AssignedAdminsAzureUniqueId.ToList(), + DirectorAzureUniqueId = dbProject.DirectorAzureUniqueId + }; + } + + public DbProject ToDbProject() + { + return new DbProject() + { + Id = Id, + Name = Name, + OrgProjectExternalId = OrgProjectExternalId, + AssignedAdminsAzureUniqueId = AssignedAdminsAzureUniqueId.ToList() + }; + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs new file mode 100644 index 000000000..53b7a03c1 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs @@ -0,0 +1,32 @@ +using Fusion.Summary.Api.Database.Models; + +namespace Fusion.Summary.Api.Domain.Models; + +public class QueryWeeklyTaskOwnerReport +{ + public Guid Id { get; set; } + public Guid ProjectId { get; set; } + public required Period Period { get; set; } + + + public static DbWeeklyTaskOwnerReport ToDbWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryWeeklyTaskOwnerReport) + { + return new DbWeeklyTaskOwnerReport + { + Id = queryWeeklyTaskOwnerReport.Id, + ProjectId = queryWeeklyTaskOwnerReport.ProjectId, + PeriodStart = queryWeeklyTaskOwnerReport.Period.Start, + PeriodEnd = queryWeeklyTaskOwnerReport.Period.End + }; + } + + public static QueryWeeklyTaskOwnerReport FromDbWeeklyTaskOwnerReport(DbWeeklyTaskOwnerReport dbWeeklyTaskOwnerReport) + { + return new QueryWeeklyTaskOwnerReport + { + Id = dbWeeklyTaskOwnerReport.Id, + ProjectId = dbWeeklyTaskOwnerReport.ProjectId, + Period = new Period(PeriodType.Weekly, dbWeeklyTaskOwnerReport.PeriodStart, dbWeeklyTaskOwnerReport.PeriodEnd) + }; + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs b/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs new file mode 100644 index 000000000..d7acb8beb --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs @@ -0,0 +1,41 @@ +using Fusion.Summary.Api.Database; +using Fusion.Summary.Api.Domain.Models; +using Fusion.Summary.Api.Domain.Queries.Base; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Domain.Queries; + +public class GetProjects : IRequest> +{ + public Guid? ProjectId { get; private set; } + + public GetProjects WhereProjectId(Guid projectId) + { + ProjectId = projectId; + return this; + } + + + public class Handler : IRequestHandler> + { + private readonly SummaryDbContext _dbContext; + + public Handler(SummaryDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task> Handle(GetProjects request, CancellationToken cancellationToken) + { + var query = _dbContext.Projects.AsQueryable(); + + if (request.ProjectId.HasValue) + query = query.Where(p => p.Id == request.ProjectId || p.OrgProjectExternalId == request.ProjectId); + + var projects = await query.ToListAsync(cancellationToken); + + return new QueryCollection(projects.Select(QueryProject.FromDbProject)); + } + } +} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs b/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs new file mode 100644 index 000000000..bb8a478f5 --- /dev/null +++ b/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs @@ -0,0 +1,61 @@ +using Fusion.AspNetCore.OData; +using Fusion.Summary.Api.Database; +using Fusion.Summary.Api.Domain.Models; +using Fusion.Summary.Api.Domain.Queries.Base; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Fusion.Summary.Api.Domain.Queries; + +public class GetWeeklyTaskOwnerReports : IRequest> +{ + public Guid ProjectId { get; } + public ODataQueryParams Query { get; private set; } + public Guid? ReportId { get; private set; } + + public GetWeeklyTaskOwnerReports(Guid projectId, ODataQueryParams query) + { + ProjectId = projectId; + Query = query; + } + + public GetWeeklyTaskOwnerReports WhereReportId(Guid reportId) + { + ReportId = reportId; + return this; + } + + public class Handler : IRequestHandler> + { + private readonly SummaryDbContext _dbContext; + + public Handler(SummaryDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task> Handle(GetWeeklyTaskOwnerReports request, CancellationToken cancellationToken) + { + var query = _dbContext.WeeklyTaskOwnerReports + .Where(r => r.ProjectId == request.ProjectId) + .AsQueryable(); + + if (request.ReportId.HasValue) + query = query.Where(x => x.Id == request.ReportId); + + query = query.OrderByDescending(r => r.PeriodStart) + .ThenBy(r => r.Id); + + var totalCount = await query.CountAsync(cancellationToken: cancellationToken); + + var skip = request.Query.Skip.GetValueOrDefault(0); + var top = request.Query.Top.GetValueOrDefault(10); + var reports = await query + .Skip(skip) + .Take(top) + .ToListAsync(cancellationToken: cancellationToken); + + return new QueryCollection(reports.Select(QueryWeeklyTaskOwnerReport.FromDbWeeklyTaskOwnerReport), top, skip, totalCount); + } + } +} \ No newline at end of file From 7d2572cd2f405ea44536673e43824930d36aae6f Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:19:47 +0200 Subject: [PATCH 03/15] chore: Use project Id for taskowner endpoints --- .../Controllers/TaskOwnerSummaryController.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs index 3a6863cbe..0470b2e10 100644 --- a/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs @@ -41,13 +41,13 @@ await Request.RequireAuthorizationAsync(r => #endregion - if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null) + var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); + if (project is null) return ProjectNotFound(projectId); + var reports = await DispatchAsync(new GetWeeklyTaskOwnerReports(project.Id, query)); - var projects = await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, query)); - - return Ok(ApiCollection.FromQueryCollection(projects, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport)); + return Ok(ApiCollection.FromQueryCollection(reports, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport)); } [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly/{reportId:guid}")] @@ -70,10 +70,11 @@ await Request.RequireAuthorizationAsync(r => #endregion - if ((await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault() is null) + var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); + if (project is null) return ProjectNotFound(projectId); - var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports(projectId, new ODataQueryParams()).WhereReportId(reportId))).FirstOrDefault(); + var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports(project.Id, new ODataQueryParams()).WhereReportId(reportId))).FirstOrDefault(); return report is null ? NotFound() : Ok(ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport(report)); } From 11d99bfe78989cb8a033fc1fc387a374442f8659 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:40:07 +0200 Subject: [PATCH 04/15] Cleanup --- src/Fusion.Resources.sln.DotSettings | 2 ++ ...ller.cs => ResourceOwnerReportsController.cs} | 2 +- ...ntroller.cs => TaskOwnerReportsController.cs} | 4 ++-- .../Database/Models/DbWeeklyTaskOwnerReport.cs | 2 +- src/Fusion.Summary.Api/Domain/Models/Period.cs | 16 ++++++++++++---- .../Domain/Models/PeriodType.cs | 6 ------ .../Domain/Models/QueryWeeklyTaskOwnerReport.cs | 2 +- 7 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 src/Fusion.Resources.sln.DotSettings rename src/Fusion.Summary.Api/Controllers/{SummaryReportsController.cs => ResourceOwnerReportsController.cs} (98%) rename src/Fusion.Summary.Api/Controllers/{TaskOwnerSummaryController.cs => TaskOwnerReportsController.cs} (96%) delete mode 100644 src/Fusion.Summary.Api/Domain/Models/PeriodType.cs diff --git a/src/Fusion.Resources.sln.DotSettings b/src/Fusion.Resources.sln.DotSettings new file mode 100644 index 000000000..a6eec3876 --- /dev/null +++ b/src/Fusion.Resources.sln.DotSettings @@ -0,0 +1,2 @@ + + TBN \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/SummaryReportsController.cs b/src/Fusion.Summary.Api/Controllers/ResourceOwnerReportsController.cs similarity index 98% rename from src/Fusion.Summary.Api/Controllers/SummaryReportsController.cs rename to src/Fusion.Summary.Api/Controllers/ResourceOwnerReportsController.cs index 625cac547..94e9dfd82 100644 --- a/src/Fusion.Summary.Api/Controllers/SummaryReportsController.cs +++ b/src/Fusion.Summary.Api/Controllers/ResourceOwnerReportsController.cs @@ -17,7 +17,7 @@ namespace Fusion.Summary.Api.Controllers; [Authorize] [ApiController] [ApiVersion("1.0")] -public class SummaryReportsController : BaseController +public class ResourceOwnerReportsController : BaseController { [HttpGet("resource-owners-summary-reports/{sapDepartmentId}/weekly")] [MapToApiVersion("1.0")] diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs similarity index 96% rename from src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs rename to src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs index 0470b2e10..eac048f22 100644 --- a/src/Fusion.Summary.Api/Controllers/TaskOwnerSummaryController.cs +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs @@ -17,7 +17,7 @@ namespace Fusion.Summary.Api.Controllers; [ApiController] [Produces(MediaTypeNames.Application.Json)] [ApiVersion("1.0")] -public class TaskOwnerSummaryController : BaseController +public class TaskOwnerReportsController : BaseController { [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly")] [MapToApiVersion("1.0")] @@ -81,7 +81,7 @@ await Request.RequireAuthorizationAsync(r => /// /// Summary report key is composed of the project id and the period start and end dates. - /// If a report already exists for the given period and project id, it will be replaced. + /// If a report already exists for the given project id and period then it will be replaced. /// [HttpPut("task-owners-summary-reports/{projectId:guid}/weekly")] [MapToApiVersion("1.0")] diff --git a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs index a924a6e21..0a7e86eeb 100644 --- a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs @@ -23,7 +23,7 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) report.ToTable("WeeklyTaskOwnerReports"); report.HasKey(r => r.Id); - report.HasIndex(r => new { r.ProjectId, Period = r.PeriodStart }) + report.HasIndex(r => new { r.ProjectId, r.PeriodStart, r.PeriodEnd }) .IsUnique(); report.Property(r => r.PeriodStart) diff --git a/src/Fusion.Summary.Api/Domain/Models/Period.cs b/src/Fusion.Summary.Api/Domain/Models/Period.cs index 612a97386..f98ae6009 100644 --- a/src/Fusion.Summary.Api/Domain/Models/Period.cs +++ b/src/Fusion.Summary.Api/Domain/Models/Period.cs @@ -8,6 +8,9 @@ public sealed class Period public Period(PeriodType type, DateTime start, DateTime end) { + start = start.Date; + end = end.Date; + switch (type) { case PeriodType.Weekly: @@ -21,11 +24,11 @@ public Period(PeriodType type, DateTime start, DateTime end) } Type = type; - Start = start.Date; - End = end.Date; + Start = start; + End = end; } - public static Period FromStart(PeriodType type, DateTime start) + public static Period FromStartDate(PeriodType type, DateTime start) { var end = type switch { @@ -37,7 +40,7 @@ public static Period FromStart(PeriodType type, DateTime start) } - public static Period FromEnd(PeriodType type, DateTime end) + public static Period FromEndDate(PeriodType type, DateTime end) { var start = type switch { @@ -47,4 +50,9 @@ public static Period FromEnd(PeriodType type, DateTime end) return new Period(type, start, end); } + + public enum PeriodType + { + Weekly + } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs b/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs deleted file mode 100644 index af5e2c4fe..000000000 --- a/src/Fusion.Summary.Api/Domain/Models/PeriodType.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Fusion.Summary.Api.Domain.Models; - -public enum PeriodType -{ - Weekly -} \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs index 53b7a03c1..cbaa98fe9 100644 --- a/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs @@ -26,7 +26,7 @@ public static QueryWeeklyTaskOwnerReport FromDbWeeklyTaskOwnerReport(DbWeeklyTas { Id = dbWeeklyTaskOwnerReport.Id, ProjectId = dbWeeklyTaskOwnerReport.ProjectId, - Period = new Period(PeriodType.Weekly, dbWeeklyTaskOwnerReport.PeriodStart, dbWeeklyTaskOwnerReport.PeriodEnd) + Period = new Period(Period.PeriodType.Weekly, dbWeeklyTaskOwnerReport.PeriodStart, dbWeeklyTaskOwnerReport.PeriodEnd) }; } } \ No newline at end of file From f759fbb52b2d36d983dd33f4aa10b568840ce4b8 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:48:29 +0200 Subject: [PATCH 05/15] Removed personIdentifiers validation --- .../Controllers/DepartmentsController.cs | 12 ------------ .../Controllers/ProjectsController.cs | 14 -------------- 2 files changed, 26 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/DepartmentsController.cs b/src/Fusion.Summary.Api/Controllers/DepartmentsController.cs index 7a55f1c2f..dacf27a2a 100644 --- a/src/Fusion.Summary.Api/Controllers/DepartmentsController.cs +++ b/src/Fusion.Summary.Api/Controllers/DepartmentsController.cs @@ -100,18 +100,6 @@ public async Task PutV1(string sapDepartmentId, [FromBody] PutDep if (string.IsNullOrWhiteSpace(sapDepartmentId)) return SapDepartmentIdRequired(); - var personIdentifiers = request.ResourceOwnersAzureUniqueId - .Concat(request.DelegateResourceOwnersAzureUniqueId) - .Select(p => new PersonIdentifier(p)); - - var unresolvedProfiles = (await ResolvePersonsAsync(personIdentifiers)) - .Where(r => !r.Success) - .ToList(); - - if (unresolvedProfiles.Count != 0) - return FusionApiError.NotFound(string.Join(',', unresolvedProfiles), "Profiles could not be resolved"); - - var department = await DispatchAsync(new GetDepartment(sapDepartmentId)); // Check if department exist diff --git a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs index 637b330fb..db115a052 100644 --- a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs +++ b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs @@ -91,20 +91,6 @@ public async Task> GetProjectsV1() #endregion Authorization - var personIdentifiers = request.AssignedAdminsAzureUniqueId - .Select(p => new PersonIdentifier(p)); - - if (request.DirectorAzureUniqueId.HasValue) - personIdentifiers = personIdentifiers.Append(new PersonIdentifier(request.DirectorAzureUniqueId.Value)); - - var unresolvedProfiles = (await ResolvePersonsAsync(personIdentifiers)) - .Where(r => !r.Success) - .ToList(); - - if (unresolvedProfiles.Count != 0) - return FusionApiError.NotFound(string.Join(',', unresolvedProfiles), "Profiles could not be resolved"); - - var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); if (project == null) From 4ae1f9ac582a458f92f18a424ab3d9eaab7dba8c Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:04:26 +0200 Subject: [PATCH 06/15] Added report data --- .../ApiModels/ApiWeeklyTaskOwnerReport.cs | 65 ++++++++++++- .../PutWeeklyTaskOwnerReportRequest.cs | 6 ++ .../Database/Models/DbProject.cs | 2 +- .../Models/DbWeeklyTaskOwnerReport.cs | 49 +++++++--- .../Commands/PutWeeklyTaskOwnerReport.cs | 26 ++++- .../Models/QueryWeeklyTaskOwnerReport.cs | 97 +++++++++++++++++-- 6 files changed, 215 insertions(+), 30 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs index ca77baa65..b60fa033e 100644 --- a/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Controllers/ApiModels/ApiWeeklyTaskOwnerReport.cs @@ -9,15 +9,70 @@ public class ApiWeeklyTaskOwnerReport public required DateTime PeriodStart { get; set; } public required DateTime PeriodEnd { get; set; } + public required int ActionsAwaitingTaskOwnerAction { get; set; } + public required ApiAdminAccessExpiring[] AdminAccessExpiringInLessThanThreeMonths { get; set; } + public required ApiPositionAllocationEnding[] PositionAllocationsEndingInNextThreeMonths { get; set; } + public required ApiTBNPositionStartingSoon[] TBNPositionsStartingInLessThanThreeMonths { get; set; } - public static ApiWeeklyTaskOwnerReport FromQueryWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryWeeklyTaskOwnerReport) + + public static ApiWeeklyTaskOwnerReport FromQueryWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryReport) { return new ApiWeeklyTaskOwnerReport { - Id = queryWeeklyTaskOwnerReport.Id, - ProjectId = queryWeeklyTaskOwnerReport.ProjectId, - PeriodStart = queryWeeklyTaskOwnerReport.Period.Start, - PeriodEnd = queryWeeklyTaskOwnerReport.Period.End + Id = queryReport.Id, + ProjectId = queryReport.ProjectId, + PeriodStart = queryReport.Period.Start, + PeriodEnd = queryReport.Period.End, + ActionsAwaitingTaskOwnerAction = queryReport.ActionsAwaitingTaskOwnerAction, + AdminAccessExpiringInLessThanThreeMonths = queryReport.AdminAccessExpiringInLessThanThreeMonths.Select(x => + new ApiAdminAccessExpiring() + { + AzureUniqueId = x.AzureUniqueId, + FullName = x.FullName, + Expires = x.Expires + }).ToArray(), + PositionAllocationsEndingInNextThreeMonths = queryReport.PositionAllocationsEndingInNextThreeMonths.Select(x => + new ApiPositionAllocationEnding() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesTo = x.PositionAppliesTo, + PositionExternalId = x.PositionExternalId + }).ToArray(), + TBNPositionsStartingInLessThanThreeMonths = queryReport.TBNPositionsStartingInLessThanThreeMonths.Select(x => + new ApiTBNPositionStartingSoon() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesFrom = x.PositionAppliesFrom, + PositionExternalId = x.PositionExternalId + }).ToArray() }; } +} + +public class ApiAdminAccessExpiring +{ + public required Guid AzureUniqueId { get; set; } + public required string FullName { get; set; } + public required DateTime Expires { get; set; } +} + +public class ApiPositionAllocationEnding +{ + public required string PositionExternalId { get; set; } + + public required string PositionName { get; set; } + + public required string PositionNameDetailed { get; set; } + + public required DateTime PositionAppliesTo { get; set; } +} + +public class ApiTBNPositionStartingSoon +{ + public required string PositionExternalId { get; set; } + public required string PositionName { get; set; } + public required string PositionNameDetailed { get; set; } + public required DateTime PositionAppliesFrom { get; set; } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs b/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs index a195eabbe..8df669e8e 100644 --- a/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs +++ b/src/Fusion.Summary.Api/Controllers/Requests/PutWeeklyTaskOwnerReportRequest.cs @@ -1,4 +1,5 @@ using FluentValidation; +using Fusion.Summary.Api.Controllers.ApiModels; namespace Fusion.Summary.Api.Controllers.Requests; @@ -7,6 +8,11 @@ public class PutWeeklyTaskOwnerReportRequest public DateTime PeriodStart { get; set; } public DateTime PeriodEnd { get; set; } + public required int ActionsAwaitingTaskOwnerAction { get; set; } + public required ApiAdminAccessExpiring[] AdminAccessExpiringInLessThanThreeMonths { get; set; } + public required ApiPositionAllocationEnding[] PositionAllocationsEndingInNextThreeMonths { get; set; } + public required ApiTBNPositionStartingSoon[] TBNPositionsStartingInLessThanThreeMonths { get; set; } + public class Validator : AbstractValidator { diff --git a/src/Fusion.Summary.Api/Database/Models/DbProject.cs b/src/Fusion.Summary.Api/Database/Models/DbProject.cs index 16376a42d..0554c2a5f 100644 --- a/src/Fusion.Summary.Api/Database/Models/DbProject.cs +++ b/src/Fusion.Summary.Api/Database/Models/DbProject.cs @@ -20,7 +20,7 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) { project.ToTable("Projects"); project.HasKey(p => p.Id); - project.Property(p => p.Name).HasMaxLength(500); + project.Property(p => p.Name).HasMaxLength(200); project.HasIndex(p => p.OrgProjectExternalId).IsUnique(); }); } diff --git a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs index 0a7e86eeb..c6bc8e287 100644 --- a/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Database/Models/DbWeeklyTaskOwnerReport.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; namespace Fusion.Summary.Api.Database.Models; @@ -11,10 +12,12 @@ public class DbWeeklyTaskOwnerReport public required DateTime PeriodStart { get; set; } public required DateTime PeriodEnd { get; set; } - // - // Add columns - // + // Report data + public required int ActionsAwaitingTaskOwnerAction { get; set; } + public required List AdminAccessExpiringInLessThanThreeMonths { get; set; } + public required List PositionAllocationsEndingInNextThreeMonths { get; set; } + public required List TBNPositionsStartingInLessThanThreeMonths { get; set; } internal static void OnModelCreating(ModelBuilder modelBuilder) { @@ -34,6 +37,12 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) // Strip time from date and retrieve as UTC .HasConversion(d => d.Date, d => DateTime.SpecifyKind(d, DateTimeKind.Utc)); + report.OwnsMany(r => r.AdminAccessExpiringInLessThanThreeMonths, x => x.ToJson()); + + report.OwnsMany(r => r.PositionAllocationsEndingInNextThreeMonths, x => x.ToJson()); + + report.OwnsMany(r => r.TBNPositionsStartingInLessThanThreeMonths, x => x.ToJson()); + report.HasOne(r => r.Project) .WithMany() .HasForeignKey(r => r.ProjectId) @@ -42,26 +51,40 @@ internal static void OnModelCreating(ModelBuilder modelBuilder) } } -// TODO: Implement the following models public class DbAdminAccessExpiring { public required Guid AzureUniqueId { get; set; } + + [MaxLength(120)] public required string FullName { get; set; } public required DateTime Expires { get; set; } } -public class DbActionsAwaitingTaskOwners +public class DbPositionAllocationEnding { - // TODO: Implement -} + [MaxLength(120)] + public required string PositionExternalId { get; set; } -public class DbPositionAllocationsEnding -{ - // TODO: Implement + [MaxLength(120)] + public required string PositionName { get; set; } + + [MaxLength(120)] + public required string PositionNameDetailed { get; set; } + + public required DateTime PositionAppliesTo { get; set; } } -public class DbTBNPositionsStartingSoon +public class DbTBNPositionStartingSoon { - // TODO: Implement + [MaxLength(120)] + public required string PositionExternalId { get; set; } + + [MaxLength(120)] + public required string PositionName { get; set; } + + [MaxLength(120)] + public required string PositionNameDetailed { get; set; } + + public required DateTime PositionAppliesFrom { get; set; } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs index b2e3b6b94..3dfb58b9c 100644 --- a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs @@ -29,7 +29,7 @@ public Handler(SummaryDbContext dbContext) public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationToken cancellationToken) { - var project = await _dbContext.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId || p.OrgProjectExternalId == request.ProjectId, cancellationToken); + var project = await _dbContext.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken); if (project == null) throw new InvalidOperationException($"Project with id '{request.ProjectId}' was not found"); @@ -46,7 +46,29 @@ public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationTok Id = existingReport?.Id ?? Guid.NewGuid(), PeriodStart = request.Report.PeriodStart, PeriodEnd = request.Report.PeriodEnd, - ProjectId = project.Id + ProjectId = project.Id, + ActionsAwaitingTaskOwnerAction = request.Report.ActionsAwaitingTaskOwnerAction, + AdminAccessExpiringInLessThanThreeMonths = request.Report.AdminAccessExpiringInLessThanThreeMonths.Select(x => new DbAdminAccessExpiring() + { + AzureUniqueId = x.AzureUniqueId, + FullName = x.FullName, + Expires = x.Expires + }).ToList(), + PositionAllocationsEndingInNextThreeMonths = request.Report.PositionAllocationsEndingInNextThreeMonths.Select(x => new DbPositionAllocationEnding() + { + PositionExternalId = x.PositionExternalId, + PositionName = x.PositionName, + PositionAppliesTo = x.PositionAppliesTo, + PositionNameDetailed = x.PositionNameDetailed + }).ToList(), + TBNPositionsStartingInLessThanThreeMonths = request.Report.TBNPositionsStartingInLessThanThreeMonths.Select(x => new DbTBNPositionStartingSoon() + { + PositionExternalId = x.PositionExternalId, + PositionName = x.PositionName, + PositionAppliesFrom = x.PositionAppliesFrom, + PositionNameDetailed = x.PositionNameDetailed + }).ToList() + }; diff --git a/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs index cbaa98fe9..655209af7 100644 --- a/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Domain/Models/QueryWeeklyTaskOwnerReport.cs @@ -8,25 +8,104 @@ public class QueryWeeklyTaskOwnerReport public Guid ProjectId { get; set; } public required Period Period { get; set; } + public required int ActionsAwaitingTaskOwnerAction { get; set; } + public required List AdminAccessExpiringInLessThanThreeMonths { get; set; } + public required List PositionAllocationsEndingInNextThreeMonths { get; set; } + public required List TBNPositionsStartingInLessThanThreeMonths { get; set; } - public static DbWeeklyTaskOwnerReport ToDbWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryWeeklyTaskOwnerReport) + + public static DbWeeklyTaskOwnerReport ToDbWeeklyTaskOwnerReport(QueryWeeklyTaskOwnerReport queryReport) { return new DbWeeklyTaskOwnerReport { - Id = queryWeeklyTaskOwnerReport.Id, - ProjectId = queryWeeklyTaskOwnerReport.ProjectId, - PeriodStart = queryWeeklyTaskOwnerReport.Period.Start, - PeriodEnd = queryWeeklyTaskOwnerReport.Period.End + Id = queryReport.Id, + ProjectId = queryReport.ProjectId, + PeriodStart = queryReport.Period.Start, + PeriodEnd = queryReport.Period.End, + ActionsAwaitingTaskOwnerAction = queryReport.ActionsAwaitingTaskOwnerAction, + AdminAccessExpiringInLessThanThreeMonths = queryReport.AdminAccessExpiringInLessThanThreeMonths.Select(x => + new DbAdminAccessExpiring() + { + AzureUniqueId = x.AzureUniqueId, + FullName = x.FullName, + Expires = x.Expires + }).ToList(), + PositionAllocationsEndingInNextThreeMonths = queryReport.PositionAllocationsEndingInNextThreeMonths.Select(x => + new DbPositionAllocationEnding() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesTo = x.PositionAppliesTo, + PositionExternalId = x.PositionExternalId + }).ToList(), + TBNPositionsStartingInLessThanThreeMonths = queryReport.TBNPositionsStartingInLessThanThreeMonths.Select(x => + new DbTBNPositionStartingSoon() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesFrom = x.PositionAppliesFrom, + PositionExternalId = x.PositionExternalId + }).ToList() }; } - public static QueryWeeklyTaskOwnerReport FromDbWeeklyTaskOwnerReport(DbWeeklyTaskOwnerReport dbWeeklyTaskOwnerReport) + public static QueryWeeklyTaskOwnerReport FromDbWeeklyTaskOwnerReport(DbWeeklyTaskOwnerReport dbReport) { return new QueryWeeklyTaskOwnerReport { - Id = dbWeeklyTaskOwnerReport.Id, - ProjectId = dbWeeklyTaskOwnerReport.ProjectId, - Period = new Period(Period.PeriodType.Weekly, dbWeeklyTaskOwnerReport.PeriodStart, dbWeeklyTaskOwnerReport.PeriodEnd) + Id = dbReport.Id, + ProjectId = dbReport.ProjectId, + Period = new Period(Period.PeriodType.Weekly, dbReport.PeriodStart, dbReport.PeriodEnd), + ActionsAwaitingTaskOwnerAction = dbReport.ActionsAwaitingTaskOwnerAction, + AdminAccessExpiringInLessThanThreeMonths = dbReport.AdminAccessExpiringInLessThanThreeMonths.Select(x => + new AdminAccessExpiring() + { + AzureUniqueId = x.AzureUniqueId, + FullName = x.FullName, + Expires = x.Expires + }).ToList(), + PositionAllocationsEndingInNextThreeMonths = dbReport.PositionAllocationsEndingInNextThreeMonths.Select(x => + new PositionAllocationEnding() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesTo = x.PositionAppliesTo, + PositionExternalId = x.PositionExternalId + }).ToList(), + TBNPositionsStartingInLessThanThreeMonths = dbReport.TBNPositionsStartingInLessThanThreeMonths.Select(x => + new TBNPositionStartingSoon() + { + PositionName = x.PositionName, + PositionNameDetailed = x.PositionNameDetailed, + PositionAppliesFrom = x.PositionAppliesFrom, + PositionExternalId = x.PositionExternalId + }).ToList() }; } +} + +public class AdminAccessExpiring +{ + public required Guid AzureUniqueId { get; set; } + public required string FullName { get; set; } + public required DateTime Expires { get; set; } +} + +public class PositionAllocationEnding +{ + public required string PositionExternalId { get; set; } + + public required string PositionName { get; set; } + + public required string PositionNameDetailed { get; set; } + + public required DateTime PositionAppliesTo { get; set; } +} + +public class TBNPositionStartingSoon +{ + public required string PositionExternalId { get; set; } + public required string PositionName { get; set; } + public required string PositionNameDetailed { get; set; } + public required DateTime PositionAppliesFrom { get; set; } } \ No newline at end of file From 60278d8bf6f0753cb092aaf54fe8fb660b9205f5 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:18:10 +0200 Subject: [PATCH 07/15] Added migrations --- ...731_Add_TaskOwnerReports_Table.Designer.cs | 352 ++++++++++++++++++ ...241023141731_Add_TaskOwnerReports_Table.cs | 76 ++++ .../SummaryDbContextModelSnapshot.cs | 178 ++++++++- 3 files changed, 605 insertions(+), 1 deletion(-) create mode 100644 src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.Designer.cs create mode 100644 src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.cs diff --git a/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.Designer.cs b/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.Designer.cs new file mode 100644 index 000000000..fdc01a0de --- /dev/null +++ b/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.Designer.cs @@ -0,0 +1,352 @@ +// +using System; +using Fusion.Summary.Api.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Fusion.Summary.Api.Database.Migrations +{ + [DbContext(typeof(SummaryDbContext))] + [Migration("20241023141731_Add_TaskOwnerReports_Table")] + partial class Add_TaskOwnerReports_Table + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbDepartment", b => + { + b.Property("DepartmentSapId") + .HasColumnType("nvarchar(450)"); + + b.Property("DelegateResourceOwnersAzureUniqueId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FullDepartmentName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResourceOwnersAzureUniqueId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("DepartmentSapId"); + + b.ToTable("Departments", (string)null); + }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignedAdminsAzureUniqueId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DirectorAzureUniqueId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrgProjectExternalId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrgProjectExternalId") + .IsUnique(); + + b.ToTable("Projects", (string)null); + }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklySummaryReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AllocationChangesAwaitingTaskOwnerAction") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("AverageTimeToHandleRequests") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CapacityInUse") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DepartmentSapId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("NumberOfOpenRequests") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfPersonnel") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfRequestsLastPeriod") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfRequestsStartingInLessThanThreeMonths") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NumberOfRequestsStartingInMoreThanThreeMonths") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Period") + .HasColumnType("datetime2"); + + b.Property("ProjectChangesAffectingNextThreeMonths") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentSapId", "Period") + .IsUnique(); + + b.ToTable("WeeklySummaryReports", (string)null); + }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklyTaskOwnerReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActionsAwaitingTaskOwnerAction") + .HasColumnType("int"); + + b.Property("PeriodEnd") + .HasColumnType("datetime2"); + + b.Property("PeriodStart") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "PeriodStart", "PeriodEnd") + .IsUnique(); + + b.ToTable("WeeklyTaskOwnerReports", (string)null); + }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklySummaryReport", b => + { + b.HasOne("Fusion.Summary.Api.Database.Models.DbDepartment", "Department") + .WithMany() + .HasForeignKey("DepartmentSapId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbEndingPosition", "PositionsEnding", b1 => + { + b1.Property("DbWeeklySummaryReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("EndDate") + .HasColumnType("datetime2"); + + b1.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("DbWeeklySummaryReportId", "Id"); + + b1.ToTable("WeeklySummaryReports"); + + b1.ToJson("PositionsEnding"); + + b1.WithOwner() + .HasForeignKey("DbWeeklySummaryReportId"); + }); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbPersonnelMoreThan100PercentFTE", "PersonnelMoreThan100PercentFTE", b1 => + { + b1.Property("DbWeeklySummaryReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("FTE") + .HasColumnType("float"); + + b1.Property("FullName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b1.HasKey("DbWeeklySummaryReportId", "Id"); + + b1.ToTable("WeeklySummaryReports"); + + b1.ToJson("PersonnelMoreThan100PercentFTE"); + + b1.WithOwner() + .HasForeignKey("DbWeeklySummaryReportId"); + }); + + b.Navigation("Department"); + + b.Navigation("PersonnelMoreThan100PercentFTE"); + + b.Navigation("PositionsEnding"); + }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklyTaskOwnerReport", b => + { + b.HasOne("Fusion.Summary.Api.Database.Models.DbProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbAdminAccessExpiring", "AdminAccessExpiringInLessThanThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("AzureUniqueId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Expires") + .HasColumnType("datetime2"); + + b1.Property("FullName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("AdminAccessExpiringInLessThanThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbPositionAllocationEnding", "PositionAllocationsEndingInNextThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("PositionAppliesTo") + .HasColumnType("datetime2"); + + b1.Property("PositionExternalId") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionNameDetailed") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("PositionAllocationsEndingInNextThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbTBNPositionStartingSoon", "TBNPositionsStartingInLessThanThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("PositionAppliesFrom") + .HasColumnType("datetime2"); + + b1.Property("PositionExternalId") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionNameDetailed") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("TBNPositionsStartingInLessThanThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.Navigation("AdminAccessExpiringInLessThanThreeMonths"); + + b.Navigation("PositionAllocationsEndingInNextThreeMonths"); + + b.Navigation("Project"); + + b.Navigation("TBNPositionsStartingInLessThanThreeMonths"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.cs b/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.cs new file mode 100644 index 000000000..867870e05 --- /dev/null +++ b/src/Fusion.Summary.Api/Database/Migrations/20241023141731_Add_TaskOwnerReports_Table.cs @@ -0,0 +1,76 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Fusion.Summary.Api.Database.Migrations +{ + /// + public partial class Add_TaskOwnerReports_Table : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Projects", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + OrgProjectExternalId = table.Column(type: "uniqueidentifier", nullable: false), + DirectorAzureUniqueId = table.Column(type: "uniqueidentifier", nullable: true), + AssignedAdminsAzureUniqueId = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Projects", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WeeklyTaskOwnerReports", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ProjectId = table.Column(type: "uniqueidentifier", nullable: false), + PeriodStart = table.Column(type: "datetime2", nullable: false), + PeriodEnd = table.Column(type: "datetime2", nullable: false), + ActionsAwaitingTaskOwnerAction = table.Column(type: "int", nullable: false), + AdminAccessExpiringInLessThanThreeMonths = table.Column(type: "nvarchar(max)", nullable: true), + PositionAllocationsEndingInNextThreeMonths = table.Column(type: "nvarchar(max)", nullable: true), + TBNPositionsStartingInLessThanThreeMonths = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WeeklyTaskOwnerReports", x => x.Id); + table.ForeignKey( + name: "FK_WeeklyTaskOwnerReports_Projects_ProjectId", + column: x => x.ProjectId, + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Projects_OrgProjectExternalId", + table: "Projects", + column: "OrgProjectExternalId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_WeeklyTaskOwnerReports_ProjectId_PeriodStart_PeriodEnd", + table: "WeeklyTaskOwnerReports", + columns: new[] { "ProjectId", "PeriodStart", "PeriodEnd" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WeeklyTaskOwnerReports"); + + migrationBuilder.DropTable( + name: "Projects"); + } + } +} diff --git a/src/Fusion.Summary.Api/Database/Migrations/SummaryDbContextModelSnapshot.cs b/src/Fusion.Summary.Api/Database/Migrations/SummaryDbContextModelSnapshot.cs index 4f9c9f4d7..8af159fd8 100644 --- a/src/Fusion.Summary.Api/Database/Migrations/SummaryDbContextModelSnapshot.cs +++ b/src/Fusion.Summary.Api/Database/Migrations/SummaryDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.6") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -44,6 +44,35 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Departments", (string)null); }); + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AssignedAdminsAzureUniqueId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DirectorAzureUniqueId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("OrgProjectExternalId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OrgProjectExternalId") + .IsUnique(); + + b.ToTable("Projects", (string)null); + }); + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklySummaryReport", b => { b.Property("Id") @@ -101,6 +130,32 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("WeeklySummaryReports", (string)null); }); + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklyTaskOwnerReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ActionsAwaitingTaskOwnerAction") + .HasColumnType("int"); + + b.Property("PeriodEnd") + .HasColumnType("datetime2"); + + b.Property("PeriodStart") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "PeriodStart", "PeriodEnd") + .IsUnique(); + + b.ToTable("WeeklyTaskOwnerReports", (string)null); + }); + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklySummaryReport", b => { b.HasOne("Fusion.Summary.Api.Database.Models.DbDepartment", "Department") @@ -167,6 +222,127 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("PositionsEnding"); }); + + modelBuilder.Entity("Fusion.Summary.Api.Database.Models.DbWeeklyTaskOwnerReport", b => + { + b.HasOne("Fusion.Summary.Api.Database.Models.DbProject", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbAdminAccessExpiring", "AdminAccessExpiringInLessThanThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("AzureUniqueId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Expires") + .HasColumnType("datetime2"); + + b1.Property("FullName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("AdminAccessExpiringInLessThanThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbPositionAllocationEnding", "PositionAllocationsEndingInNextThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("PositionAppliesTo") + .HasColumnType("datetime2"); + + b1.Property("PositionExternalId") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionNameDetailed") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("PositionAllocationsEndingInNextThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.OwnsMany("Fusion.Summary.Api.Database.Models.DbTBNPositionStartingSoon", "TBNPositionsStartingInLessThanThreeMonths", b1 => + { + b1.Property("DbWeeklyTaskOwnerReportId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b1.Property("PositionAppliesFrom") + .HasColumnType("datetime2"); + + b1.Property("PositionExternalId") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.Property("PositionNameDetailed") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b1.HasKey("DbWeeklyTaskOwnerReportId", "Id"); + + b1.ToTable("WeeklyTaskOwnerReports"); + + b1.ToJson("TBNPositionsStartingInLessThanThreeMonths"); + + b1.WithOwner() + .HasForeignKey("DbWeeklyTaskOwnerReportId"); + }); + + b.Navigation("AdminAccessExpiringInLessThanThreeMonths"); + + b.Navigation("PositionAllocationsEndingInNextThreeMonths"); + + b.Navigation("Project"); + + b.Navigation("TBNPositionsStartingInLessThanThreeMonths"); + }); #pragma warning restore 612, 618 } } From bc90e4eae58b4375992b95a18e24624ed0fb6f1a Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:37:31 +0200 Subject: [PATCH 08/15] Refactored endpoints --- .../Controllers/TaskOwnerReportsController.cs | 42 ++++++++++++++++--- .../Queries/GetWeeklyTaskOwnerReports.cs | 26 ++++++++++-- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs index eac048f22..8373ca4a0 100644 --- a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs @@ -19,11 +19,40 @@ namespace Fusion.Summary.Api.Controllers; [ApiVersion("1.0")] public class TaskOwnerReportsController : BaseController { - [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly")] + [HttpGet("task-owners-summary-reports/weekly")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ODataFilter(nameof(ApiWeeklyTaskOwnerReport.PeriodStart), nameof(ApiWeeklyTaskOwnerReport.PeriodEnd))] + [ODataTop(100), ODataSkip] + public async Task>> GetWeeklyTaskOwnerReportsV1(ODataQueryParams query) + { + #region Authorization + + var authResult = + await Request.RequireAuthorizationAsync(r => + { + r.AlwaysAccessWhen().ResourcesFullControl(); + r.AnyOf(or => { or.BeTrustedApplication(); }); + }); + + if (authResult.Unauthorized) + return authResult.CreateForbiddenResponse(); + + #endregion + + var reports = await DispatchAsync(new GetWeeklyTaskOwnerReports(query)); + + return Ok(ApiCollection.FromQueryCollection(reports, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport)); + } + + [HttpGet("projects/{projectId:guid}/task-owners-summary-reports/weekly")] + [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ODataFilter(nameof(ApiWeeklyTaskOwnerReport.PeriodStart), nameof(ApiWeeklyTaskOwnerReport.PeriodEnd))] [ODataTop(100), ODataSkip] public async Task>> GetWeeklyTaskOwnerReportsV1(Guid projectId, ODataQueryParams query) { @@ -45,12 +74,12 @@ await Request.RequireAuthorizationAsync(r => if (project is null) return ProjectNotFound(projectId); - var reports = await DispatchAsync(new GetWeeklyTaskOwnerReports(project.Id, query)); + var reports = await DispatchAsync(new GetWeeklyTaskOwnerReports(query).WhereProjectId(project.Id)); return Ok(ApiCollection.FromQueryCollection(reports, ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport)); } - [HttpGet("task-owners-summary-reports/{projectId:guid}/weekly/{reportId:guid}")] + [HttpGet("projects/{projectId:guid}/task-owners-summary-reports/weekly/{reportId:guid}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -74,7 +103,10 @@ await Request.RequireAuthorizationAsync(r => if (project is null) return ProjectNotFound(projectId); - var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports(project.Id, new ODataQueryParams()).WhereReportId(reportId))).FirstOrDefault(); + var report = (await DispatchAsync(new GetWeeklyTaskOwnerReports() + .WhereProjectId(project.Id) + .WhereReportId(reportId))) + .FirstOrDefault(); return report is null ? NotFound() : Ok(ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport(report)); } @@ -83,7 +115,7 @@ await Request.RequireAuthorizationAsync(r => /// Summary report key is composed of the project id and the period start and end dates. /// If a report already exists for the given project id and period then it will be replaced. /// - [HttpPut("task-owners-summary-reports/{projectId:guid}/weekly")] + [HttpPut("projects/{projectId:guid}/task-owners-summary-reports/weekly")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status204NoContent)] diff --git a/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs b/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs index bb8a478f5..a2af9d3ac 100644 --- a/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs +++ b/src/Fusion.Summary.Api/Domain/Queries/GetWeeklyTaskOwnerReports.cs @@ -1,4 +1,5 @@ using Fusion.AspNetCore.OData; +using Fusion.Summary.Api.Controllers.ApiModels; using Fusion.Summary.Api.Database; using Fusion.Summary.Api.Domain.Models; using Fusion.Summary.Api.Domain.Queries.Base; @@ -9,14 +10,19 @@ namespace Fusion.Summary.Api.Domain.Queries; public class GetWeeklyTaskOwnerReports : IRequest> { - public Guid ProjectId { get; } public ODataQueryParams Query { get; private set; } + public Guid? ProjectId { get; private set; } public Guid? ReportId { get; private set; } - public GetWeeklyTaskOwnerReports(Guid projectId, ODataQueryParams query) + public GetWeeklyTaskOwnerReports(ODataQueryParams? query = null) + { + Query = query ?? new ODataQueryParams(); + } + + public GetWeeklyTaskOwnerReports WhereProjectId(Guid projectId) { ProjectId = projectId; - Query = query; + return this; } public GetWeeklyTaskOwnerReports WhereReportId(Guid reportId) @@ -37,15 +43,27 @@ public Handler(SummaryDbContext dbContext) public async Task> Handle(GetWeeklyTaskOwnerReports request, CancellationToken cancellationToken) { var query = _dbContext.WeeklyTaskOwnerReports - .Where(r => r.ProjectId == request.ProjectId) .AsQueryable(); + if (request.ProjectId.HasValue) + query = query.Where(x => x.ProjectId == request.ProjectId); + if (request.ReportId.HasValue) query = query.Where(x => x.Id == request.ReportId); query = query.OrderByDescending(r => r.PeriodStart) .ThenBy(r => r.Id); + if (request.Query.HasFilter) + { + query = query.ApplyODataFilters(request.Query, + m => + { + m.MapField(nameof(ApiWeeklyTaskOwnerReport.PeriodStart), r => r.PeriodStart); + m.MapField(nameof(ApiWeeklyTaskOwnerReport.PeriodEnd), r => r.PeriodEnd); + }); + } + var totalCount = await query.CountAsync(cancellationToken: cancellationToken); var skip = request.Query.Skip.GetValueOrDefault(0); From 19dea6b87b8cd4a9e0b66bdeae6f3e8809d56dec Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:36:00 +0100 Subject: [PATCH 09/15] Simplify PUT operation for project and weekly task owner report --- .../Controllers/ProjectsController.cs | 10 +++++----- .../Controllers/TaskOwnerReportsController.cs | 10 ++++------ .../Domain/Commands/PutWeeklyTaskOwnerReport.cs | 9 +++++---- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs index db115a052..e5776f0bc 100644 --- a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs +++ b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs @@ -73,8 +73,8 @@ public async Task> GetProjectsV1() [HttpPut("projects/{projectId:guid}")] [MapToApiVersion("1.0")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> PutProjectsV1(Guid projectId, PutProjectRequest request) { @@ -95,13 +95,13 @@ public async Task> GetProjectsV1() if (project == null) { - await DispatchAsync(new CreateProject(request)); + project = await DispatchAsync(new CreateProject(request)); - return Created(Request.GetUri(), null); + return Created(Request.GetUri(), project); } - await DispatchAsync(new UpdateProject(project.Id, request)); + project = await DispatchAsync(new UpdateProject(project.Id, request)); - return NoContent(); + return Ok(project); } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs index 8373ca4a0..e1ac6e560 100644 --- a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs @@ -7,7 +7,6 @@ using Fusion.Summary.Api.Controllers.Requests; using Fusion.Summary.Api.Domain.Commands; using Fusion.Summary.Api.Domain.Queries; -using Microsoft.ApplicationInsights.AspNetCore.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -117,10 +116,9 @@ await Request.RequireAuthorizationAsync(r => /// [HttpPut("projects/{projectId:guid}/task-owners-summary-reports/weekly")] [MapToApiVersion("1.0")] - [ProducesResponseType(StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task PutWeeklyTaskOwnerReportV1(Guid projectId, [FromBody] PutWeeklyTaskOwnerReportRequest request) + public async Task> PutWeeklyTaskOwnerReportV1(Guid projectId, [FromBody] PutWeeklyTaskOwnerReportRequest request) { #region Authorization @@ -142,8 +140,8 @@ await Request.RequireAuthorizationAsync(r => var command = new PutWeeklyTaskOwnerReport(project.Id, request); - var newReportCreated = await DispatchAsync(command); + var report = await DispatchAsync(command); - return newReportCreated ? Created(Request.GetUri(), null) : NoContent(); + return Ok(report); } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs index 3dfb58b9c..8ad44bd8d 100644 --- a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs @@ -1,12 +1,13 @@ using Fusion.Summary.Api.Controllers.Requests; using Fusion.Summary.Api.Database; using Fusion.Summary.Api.Database.Models; +using Fusion.Summary.Api.Domain.Models; using MediatR; using Microsoft.EntityFrameworkCore; namespace Fusion.Summary.Api.Domain.Commands; -public class PutWeeklyTaskOwnerReport : IRequest +public class PutWeeklyTaskOwnerReport : IRequest { public Guid ProjectId { get; } public PutWeeklyTaskOwnerReportRequest Report { get; } @@ -18,7 +19,7 @@ public PutWeeklyTaskOwnerReport(Guid projectId, PutWeeklyTaskOwnerReportRequest } - public class Handler : IRequestHandler + public class Handler : IRequestHandler { private readonly SummaryDbContext _dbContext; @@ -27,7 +28,7 @@ public Handler(SummaryDbContext dbContext) _dbContext = dbContext; } - public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationToken cancellationToken) + public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationToken cancellationToken) { var project = await _dbContext.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken); if (project == null) @@ -77,7 +78,7 @@ public async Task Handle(PutWeeklyTaskOwnerReport request, CancellationTok await _dbContext.SaveChangesAsync(cancellationToken); // return true if a new report was created - return existingReport is null; + return QueryWeeklyTaskOwnerReport.FromDbWeeklyTaskOwnerReport(report); } } } \ No newline at end of file From 58f67e95e12130af897f64a9e3ce509d25eef310 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:29:58 +0100 Subject: [PATCH 10/15] Added tests --- .../Helpers/ProjectHelpers.cs | 37 +++++++++++ .../Helpers/WeeklyTaskOwnerReportHelpers.cs | 65 +++++++++++++++++++ .../IntegrationTests/ProjectTests.cs | 42 ++++++++++++ .../WeeklyTaskOwnerReportTests.cs | 62 ++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 src/tests/Fusion.Summary.Api.Tests/Helpers/ProjectHelpers.cs create mode 100644 src/tests/Fusion.Summary.Api.Tests/Helpers/WeeklyTaskOwnerReportHelpers.cs create mode 100644 src/tests/Fusion.Summary.Api.Tests/IntegrationTests/ProjectTests.cs create mode 100644 src/tests/Fusion.Summary.Api.Tests/IntegrationTests/WeeklyTaskOwnerReportTests.cs diff --git a/src/tests/Fusion.Summary.Api.Tests/Helpers/ProjectHelpers.cs b/src/tests/Fusion.Summary.Api.Tests/Helpers/ProjectHelpers.cs new file mode 100644 index 000000000..a59b9b6b9 --- /dev/null +++ b/src/tests/Fusion.Summary.Api.Tests/Helpers/ProjectHelpers.cs @@ -0,0 +1,37 @@ +using FluentAssertions; +using Fusion.Summary.Api.Controllers.ApiModels; +using Fusion.Testing; + +namespace Fusion.Summary.Api.Tests.Helpers; + +public static class ProjectHelpers +{ + public static ApiProject GenerateProject(Guid? directorAzureUniqueId = null, Guid[]? additionalAdmins = null) + { + var id = Guid.NewGuid(); + return new ApiProject + { + Id = id, + Name = "Test Project - " + id, + OrgProjectExternalId = Guid.NewGuid(), + DirectorAzureUniqueId = directorAzureUniqueId, + AssignedAdminsAzureUniqueId = additionalAdmins ?? [] + }; + } + + public static async Task> PutProjectAsync(this HttpClient client, Action? setup = null) + { + var project = GenerateProject(); + setup?.Invoke(project); + + var response = await client.TestClientPutAsync($"projects/{project.OrgProjectExternalId}", project); + return response; + } + + public static async Task> GetProjectAsync(this HttpClient client, + string projectId) + { + var response = await client.TestClientGetAsync($"projects/{projectId}"); + return response; + } +} \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Api.Tests/Helpers/WeeklyTaskOwnerReportHelpers.cs b/src/tests/Fusion.Summary.Api.Tests/Helpers/WeeklyTaskOwnerReportHelpers.cs new file mode 100644 index 000000000..f84f8d4c9 --- /dev/null +++ b/src/tests/Fusion.Summary.Api.Tests/Helpers/WeeklyTaskOwnerReportHelpers.cs @@ -0,0 +1,65 @@ +using Fusion.Summary.Api.Controllers.ApiModels; +using Fusion.Summary.Api.Controllers.Requests; +using Fusion.Testing; + +namespace Fusion.Summary.Api.Tests.Helpers; + +public static class WeeklyTaskOwnerReportHelpers +{ + public static async Task>> + GetWeeklyTaskOwnerReportsAsync( + this HttpClient client, string projectId) + { + var response = + await client.TestClientGetAsync>( + $"projects/{projectId}/task-owners-summary-reports/weekly"); + + return response; + } + + public static async Task> PutWeeklyTaskOwnerReportAsync(this HttpClient client, + string projectId, Action? setup = null) + { + var now = CreateDayOfWeek(DayOfWeek.Monday); + var request = new PutWeeklyTaskOwnerReportRequest() + { + PeriodStart = now, + PeriodEnd = now.AddDays(7), + ActionsAwaitingTaskOwnerAction = 1, + PositionAllocationsEndingInNextThreeMonths = Enumerable.Range(1, 5).Select(i => new ApiPositionAllocationEnding + { + PositionName = i.ToString(), + PositionNameDetailed = i.ToString(), + PositionAppliesTo = now.AddDays(20), + PositionExternalId = i.ToString() + }).ToArray(), + AdminAccessExpiringInLessThanThreeMonths = Enumerable.Range(1, 5).Select(i => new ApiAdminAccessExpiring + { + AzureUniqueId = Guid.NewGuid(), + FullName = i.ToString(), + Expires = now.AddDays(20) + }).ToArray(), + TBNPositionsStartingInLessThanThreeMonths = Enumerable.Range(6, 5).Select(i => new ApiTBNPositionStartingSoon + { + PositionName = i.ToString(), + PositionNameDetailed = i.ToString(), + PositionAppliesFrom = now.AddDays(20), + PositionExternalId = i.ToString() + }).ToArray() + }; + + setup?.Invoke(request); + + return await client.TestClientPutAsync($"projects/{projectId}/task-owners-summary-reports/weekly", + request); + } + + private static DateTime CreateDayOfWeek(DayOfWeek dayOfWeek) + { + var newDate = DateTime.UtcNow; + + var daysUntil = ((int)dayOfWeek - (int)newDate.DayOfWeek + 7) % 7; + + return newDate.AddDays(daysUntil); + } +} \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/ProjectTests.cs b/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/ProjectTests.cs new file mode 100644 index 000000000..9df231b4a --- /dev/null +++ b/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/ProjectTests.cs @@ -0,0 +1,42 @@ +using FluentAssertions; +using Fusion.Summary.Api.Tests.Fixture; +using Fusion.Summary.Api.Tests.Helpers; +using Fusion.Summary.Api.Tests.IntegrationTests.Base; +using Fusion.Testing; +using Xunit.Abstractions; + +namespace Fusion.Summary.Api.Tests.IntegrationTests; + +[Collection(TestCollections.SUMMARY)] +public class ProjectTests : TestBase +{ + private readonly SummaryApiFixture _fixture; + private HttpClient _client; + + public ProjectTests(SummaryApiFixture fixture, ITestOutputHelper output) + { + _fixture = fixture; + _client = fixture.GetClient(); + SetOutput(output); + } + + [Fact] + public async Task PutProject_Then_GetProject_ShouldBeSuccess() + { + using var adminScope = _fixture.AdminScope(); + var testUser = _fixture.Fusion.CreateUser().AsEmployee().AzureUniqueId!.Value; + + var externalId = Guid.NewGuid(); + + var response = await _client.PutProjectAsync(s => + { + s.OrgProjectExternalId = externalId; + s.DirectorAzureUniqueId = testUser; + }); + response.Should().BeSuccessfull(); + + var getResponse = await _client.GetProjectAsync(externalId.ToString()); + getResponse.Should().BeSuccessfull(); + getResponse.Value!.OrgProjectExternalId.Should().Be(externalId); + } +} \ No newline at end of file diff --git a/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/WeeklyTaskOwnerReportTests.cs b/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/WeeklyTaskOwnerReportTests.cs new file mode 100644 index 000000000..3da45e9ff --- /dev/null +++ b/src/tests/Fusion.Summary.Api.Tests/IntegrationTests/WeeklyTaskOwnerReportTests.cs @@ -0,0 +1,62 @@ +using FluentAssertions; +using Fusion.Summary.Api.Tests.Fixture; +using Fusion.Summary.Api.Tests.Helpers; +using Fusion.Summary.Api.Tests.IntegrationTests.Base; +using Fusion.Testing; +using Xunit.Abstractions; + +namespace Fusion.Summary.Api.Tests.IntegrationTests; + +[Collection(TestCollections.SUMMARY)] +public class WeeklyTaskOwnerReportTests : TestBase +{ + private readonly SummaryApiFixture _fixture; + private HttpClient _client; + + public WeeklyTaskOwnerReportTests(SummaryApiFixture fixture, ITestOutputHelper output) + { + _fixture = fixture; + _client = _fixture.GetClient(); + SetOutput(output); + } + + + [Fact] + public async Task PutAndGetWeeklyTaskOwnerReport_ShouldReturnReport() + { + using var adminScope = _fixture.AdminScope(); + var testUser = _fixture.Fusion.CreateUser().AsEmployee().AzureUniqueId!.Value; + + var projectResponse = await _client.PutProjectAsync(s => { s.DirectorAzureUniqueId = testUser; }); + projectResponse.Should().BeSuccessfull(); + var projectExternalId = projectResponse.Value!.OrgProjectExternalId.ToString(); + + var response = await _client.PutWeeklyTaskOwnerReportAsync(projectExternalId); + response.Should().BeSuccessfull(); + + var getResponse = await _client.GetWeeklyTaskOwnerReportsAsync(projectExternalId); + getResponse.Should().BeSuccessfull(); + getResponse.Value!.Items.Should().HaveCount(1); + } + + [Fact] + public async Task PutWeeklyTaskOwnerReport_WithInvalidPeriodDate_ShouldReturnBadRequest() + { + using var adminScope = _fixture.AdminScope(); + var testUser = _fixture.Fusion.CreateUser().AsEmployee().AzureUniqueId!.Value; + + var projectResponse = await _client.PutProjectAsync(s => { s.DirectorAzureUniqueId = testUser; }); + projectResponse.Should().BeSuccessfull(); + var project = projectResponse.Value!; + + var reportResponse = await _client.PutWeeklyTaskOwnerReportAsync(project.OrgProjectExternalId.ToString(), (report) => + { + var nowDate = DateTime.UtcNow; + if (nowDate.DayOfWeek == DayOfWeek.Monday) + nowDate = nowDate.AddDays(1); + report.PeriodStart = nowDate; + }); + + reportResponse.Should().BeBadRequest(); + } +} \ No newline at end of file From ba9d0e40883b0e1ada40282addbad3f256aba12c Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:49:32 +0100 Subject: [PATCH 11/15] Added comment --- src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs b/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs index d7acb8beb..132ab4f5e 100644 --- a/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs +++ b/src/Fusion.Summary.Api/Domain/Queries/GetProjects.cs @@ -10,6 +10,7 @@ public class GetProjects : IRequest> { public Guid? ProjectId { get; private set; } + /// Checks against both the internal project id and the external id public GetProjects WhereProjectId(Guid projectId) { ProjectId = projectId; From e8a35bc8f084703c3e59a5874796bff32eb74b55 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:11:47 +0100 Subject: [PATCH 12/15] Use apimodel for returned project in PUT --- src/Fusion.Summary.Api/Controllers/ProjectsController.cs | 4 ++-- .../Controllers/TaskOwnerReportsController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs index e5776f0bc..92811667e 100644 --- a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs +++ b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs @@ -97,11 +97,11 @@ public async Task> GetProjectsV1() { project = await DispatchAsync(new CreateProject(request)); - return Created(Request.GetUri(), project); + return Created(Request.GetUri(), ApiProject.FromQueryProject(project)); } project = await DispatchAsync(new UpdateProject(project.Id, request)); - return Ok(project); + return Ok(ApiProject.FromQueryProject(project)); } } \ No newline at end of file diff --git a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs index e1ac6e560..9931a1c73 100644 --- a/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs +++ b/src/Fusion.Summary.Api/Controllers/TaskOwnerReportsController.cs @@ -142,6 +142,6 @@ await Request.RequireAuthorizationAsync(r => var report = await DispatchAsync(command); - return Ok(report); + return ApiWeeklyTaskOwnerReport.FromQueryWeeklyTaskOwnerReport(report); } } \ No newline at end of file From c89ff3b87430afa5d8e149001ef3b12061e3f1ae Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:30:52 +0100 Subject: [PATCH 13/15] Updated error message --- src/Fusion.Summary.Api/BaseController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fusion.Summary.Api/BaseController.cs b/src/Fusion.Summary.Api/BaseController.cs index b99f43c77..fa0484da2 100644 --- a/src/Fusion.Summary.Api/BaseController.cs +++ b/src/Fusion.Summary.Api/BaseController.cs @@ -11,7 +11,7 @@ protected ActionResult DepartmentNotFound(string sapDepartmentId) => FusionApiError.NotFound(sapDepartmentId, $"Department with sap id '{sapDepartmentId}' was not found"); protected ActionResult ProjectNotFound(Guid projectId) => - FusionApiError.NotFound(projectId, $"Project with id '{projectId}' was not found"); + FusionApiError.NotFound(projectId, $"Project with id or externalId '{projectId}' was not found"); protected ActionResult SapDepartmentIdRequired() => FusionApiError.InvalidOperation("SapDepartmentIdRequired", "SapDepartmentId route parameter is required"); From 137841de8b7d084dea7490c6f5cf509a36ea373f Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:27:54 +0100 Subject: [PATCH 14/15] Updated PUT project --- .../Controllers/ProjectsController.cs | 5 ++++- .../Domain/Commands/CreateProject.cs | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs index 92811667e..27769b030 100644 --- a/src/Fusion.Summary.Api/Controllers/ProjectsController.cs +++ b/src/Fusion.Summary.Api/Controllers/ProjectsController.cs @@ -91,11 +91,14 @@ public async Task> GetProjectsV1() #endregion Authorization + if (projectId == Guid.Empty) + return FusionApiError.InvalidOperation("ProjectIdRequired", "ProjectId route parameter is required and cannot be empty"); + var project = (await DispatchAsync(new GetProjects().WhereProjectId(projectId))).FirstOrDefault(); if (project == null) { - project = await DispatchAsync(new CreateProject(request)); + project = await DispatchAsync(new CreateProject(request).WithProjectId(projectId)); return Created(Request.GetUri(), ApiProject.FromQueryProject(project)); } diff --git a/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs b/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs index 8728eab17..4fed4f0ff 100644 --- a/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs +++ b/src/Fusion.Summary.Api/Domain/Commands/CreateProject.cs @@ -8,6 +8,8 @@ namespace Fusion.Summary.Api.Domain.Commands; public class CreateProject : IRequest { + public Guid? ProjectId { get; private set; } + public string Name { get; } public Guid OrgProjectExternalId { get; } public Guid? DirectorAzureUniqueId { get; } @@ -21,6 +23,13 @@ public CreateProject(PutProjectRequest putRequest) AssignedAdminsAzureUniqueId = putRequest.AssignedAdminsAzureUniqueId.ToList(); } + public CreateProject WithProjectId(Guid projectId) + { + ProjectId = projectId; + return this; + } + + public class Handler : IRequestHandler { private readonly SummaryDbContext _dbContext; @@ -34,7 +43,7 @@ public async Task Handle(CreateProject request, CancellationToken { var dbProject = new DbProject() { - Id = Guid.NewGuid(), + Id = request.ProjectId ?? Guid.NewGuid(), Name = request.Name, OrgProjectExternalId = request.OrgProjectExternalId, DirectorAzureUniqueId = request.DirectorAzureUniqueId, From 038f179448e6ee55846dff329c673695d01db451 Mon Sep 17 00:00:00 2001 From: Jonathan Idland Olsnes <73334350+Jonathanio123@users.noreply.github.com> Date: Wed, 30 Oct 2024 13:29:41 +0100 Subject: [PATCH 15/15] Removed old comment --- .../Domain/Commands/PutWeeklyTaskOwnerReport.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs index 8ad44bd8d..e82573428 100644 --- a/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs +++ b/src/Fusion.Summary.Api/Domain/Commands/PutWeeklyTaskOwnerReport.cs @@ -77,7 +77,6 @@ public async Task Handle(PutWeeklyTaskOwnerReport re await _dbContext.SaveChangesAsync(cancellationToken); - // return true if a new report was created return QueryWeeklyTaskOwnerReport.FromDbWeeklyTaskOwnerReport(report); } }