From d00c552497b8637ea7252f50e473ff76c333dce2 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Wed, 20 May 2026 18:08:50 -0400 Subject: [PATCH] Fix admin dashboard LINQ crash, revise unique constraint, and mutation response data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AdminController: Rewrite avgTurnaround query to fetch approved times to memory before computing TotalHours — EF Core/Npgsql cannot translate TimeSpan.TotalHours to SQL, causing a 409 on every dashboard load. ProposalService.ReviseAsync: Append -R{n} suffix to revision's ProposalNumber so it doesn't violate the unique index. Previously copied the parent's number verbatim, causing a DbUpdateException (500). ProposalService Update/Approve/MarkSent: Add .Include(p => p.SubmittedBy) (and ApprovedBy where relevant) so MapToResponse returns submittedByName instead of null. GetByIdAsync already had these includes. --- .../Controllers/AdminController.cs | 11 +++++++---- .../Services/ProposalService.cs | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/api/src/ProposalSystem.Api/Controllers/AdminController.cs b/api/src/ProposalSystem.Api/Controllers/AdminController.cs index 5cb7e0a..d5bd61c 100644 --- a/api/src/ProposalSystem.Api/Controllers/AdminController.cs +++ b/api/src/ProposalSystem.Api/Controllers/AdminController.cs @@ -29,11 +29,14 @@ public class AdminController : ControllerBase var approvedThisWeek = await _db.Proposals .CountAsync(p => p.ApprovedAt >= weekStart, ct); - var avgTurnaround = await _db.Proposals + var approvedTimes = await _db.Proposals .Where(p => p.ApprovedAt.HasValue) - .Select(p => (p.ApprovedAt!.Value - p.SubmittedAt).TotalHours) - .DefaultIfEmpty(0) - .AverageAsync(ct); + .Select(p => new { p.ApprovedAt, p.SubmittedAt }) + .ToListAsync(ct); + + var avgTurnaround = approvedTimes.Count > 0 + ? approvedTimes.Average(p => (p.ApprovedAt!.Value - p.SubmittedAt).TotalHours) + : 0; var totalProposals = await _db.Proposals.CountAsync(ct); diff --git a/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs b/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs index 10ec344..e807934 100644 --- a/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs +++ b/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs @@ -135,7 +135,11 @@ public class ProposalService : IProposalService public async Task UpdateAsync(Guid id, UpdateProposalRequest request, CancellationToken ct = default) { - var proposal = await _db.Proposals.FindAsync(new object[] { id }, ct) + var proposal = await _db.Proposals + .Include(p => p.SubmittedBy) + .Include(p => p.AssignedAdmin) + .Include(p => p.ApprovedBy) + .FirstOrDefaultAsync(p => p.Id == id, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); if (request.RefinedScope != null) @@ -163,6 +167,8 @@ public class ProposalService : IProposalService { var proposal = await _db.Proposals .Include(p => p.LineItems) + .Include(p => p.SubmittedBy) + .Include(p => p.ApprovedBy) .FirstOrDefaultAsync(p => p.Id == id, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); @@ -189,7 +195,10 @@ public class ProposalService : IProposalService public async Task MarkSentAsync(Guid id, CancellationToken ct = default) { - var proposal = await _db.Proposals.FindAsync(new object[] { id }, ct) + var proposal = await _db.Proposals + .Include(p => p.SubmittedBy) + .Include(p => p.ApprovedBy) + .FirstOrDefaultAsync(p => p.Id == id, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); if (proposal.Status == ProposalStatus.Sent) @@ -231,7 +240,7 @@ public class ProposalService : IProposalService var revision = new Proposal { Id = Guid.NewGuid(), - ProposalNumber = proposal.ProposalNumber, + ProposalNumber = $"{proposal.ProposalNumber}-R{proposal.CurrentRevision + 1}", WorkOrderNumber = proposal.WorkOrderNumber, CustomerName = proposal.CustomerName, CustomerAddress = proposal.CustomerAddress,