diff --git a/api/src/ProposalSystem.Application/DTOs/ProposalDtos.cs b/api/src/ProposalSystem.Application/DTOs/ProposalDtos.cs index 27f1aa6..4ac926d 100644 --- a/api/src/ProposalSystem.Application/DTOs/ProposalDtos.cs +++ b/api/src/ProposalSystem.Application/DTOs/ProposalDtos.cs @@ -67,6 +67,7 @@ public record ProposalFilterRequest( DateTime? FromDate, DateTime? ToDate, string? Search, + bool Mine = false, int Page = 1, int PageSize = 25 ); diff --git a/api/src/ProposalSystem.Infrastructure/Services/LineItemService.cs b/api/src/ProposalSystem.Infrastructure/Services/LineItemService.cs index cc2e5bf..e3f53d2 100644 --- a/api/src/ProposalSystem.Infrastructure/Services/LineItemService.cs +++ b/api/src/ProposalSystem.Infrastructure/Services/LineItemService.cs @@ -54,7 +54,11 @@ public class LineItemService : ILineItemService _db.LineItems.Add(lineItem); await _db.SaveChangesAsync(ct); - await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Added: {request.Description}", ct); + try + { + await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Added: {request.Description}", ct); + } + catch { /* audit failure should not roll back a successful save */ } return MapToResponse(lineItem); } @@ -97,7 +101,11 @@ public class LineItemService : ILineItemService await _db.SaveChangesAsync(ct); - await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Bulk update: {newItems.Count} items", ct); + try + { + await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Bulk update: {newItems.Count} items", ct); + } + catch { /* audit failure should not roll back a successful save */ } return newItems.OrderBy(li => li.SortOrder).Select(MapToResponse).ToList(); } diff --git a/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs b/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs index fa7ff4a..10ec344 100644 --- a/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs +++ b/api/src/ProposalSystem.Infrastructure/Services/ProposalService.cs @@ -43,7 +43,7 @@ public class ProposalService : IProposalService ScopeOfWork = request.ScopeOfWork, ServiceCategory = request.ServiceCategory, Priority = request.Priority, - Status = ProposalStatus.Draft, + Status = ProposalStatus.InReview, Notes = request.Notes ?? string.Empty, SubmittedById = _currentUser.UserId, SubmittedAt = now, @@ -79,7 +79,7 @@ public class ProposalService : IProposalService .Include(p => p.AssignedAdmin) .AsQueryable(); - if (_currentUser.Role == UserRole.Dispatcher) + if (filter.Mine) { query = query.Where(p => p.SubmittedById == _currentUser.UserId); } @@ -166,6 +166,9 @@ public class ProposalService : IProposalService .FirstOrDefaultAsync(p => p.Id == id, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); + if (proposal.Status == ProposalStatus.Approved) + return MapToResponse(proposal); + if (proposal.Status != ProposalStatus.InReview) throw new InvalidOperationException("Only proposals in review can be approved"); @@ -189,6 +192,9 @@ public class ProposalService : IProposalService var proposal = await _db.Proposals.FindAsync(new object[] { id }, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); + if (proposal.Status == ProposalStatus.Sent) + return MapToResponse(proposal); + if (proposal.Status != ProposalStatus.Approved) throw new InvalidOperationException("Only approved proposals can be marked as sent"); @@ -211,6 +217,14 @@ public class ProposalService : IProposalService .FirstOrDefaultAsync(p => p.Id == id, ct) ?? throw new KeyNotFoundException($"Proposal {id} not found"); + if (proposal.Status == ProposalStatus.Revised) + { + var existingRevision = await _db.Proposals + .FirstOrDefaultAsync(p => p.ParentProposalId == proposal.Id, ct); + if (existingRevision != null) + return MapToResponse(existingRevision); + } + if (proposal.Status != ProposalStatus.Sent) throw new InvalidOperationException("Only sent proposals can be revised"); diff --git a/web/src/lib/api/proposals.ts b/web/src/lib/api/proposals.ts index 09fdbb9..6afe5b5 100644 --- a/web/src/lib/api/proposals.ts +++ b/web/src/lib/api/proposals.ts @@ -67,6 +67,7 @@ export interface ProposalFilters { priority?: string; fromDate?: string; toDate?: string; + mine?: boolean; } export const proposalsApi = { @@ -83,6 +84,7 @@ export const proposalsApi = { if (filters.status) params.append('status', filters.status); if (filters.serviceCategory) params.append('serviceCategory', filters.serviceCategory); if (filters.priority) params.append('priority', filters.priority); + if (filters.mine) params.append('mine', 'true'); const res = await apiClient.get(`/proposals?${params.toString()}`); return res.data; diff --git a/web/src/pages/proposals/list/ProposalListPage.tsx b/web/src/pages/proposals/list/ProposalListPage.tsx index a2197d9..829a66a 100644 --- a/web/src/pages/proposals/list/ProposalListPage.tsx +++ b/web/src/pages/proposals/list/ProposalListPage.tsx @@ -40,6 +40,7 @@ function formatDate(iso: string): string { export default function ProposalListPage() { const navigate = useNavigate(); + const mineParams = { mine: true }; const { rows, search, @@ -51,7 +52,7 @@ export default function ProposalListPage() { totalCount, loading, err, - } = usePaginatedList(proposalsApi.getAll); + } = usePaginatedList(proposalsApi.getAll, mineParams); return (