mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 15:08:58 +00:00
Addresses 29 FIX-severity findings and accessibility/code-quality NITs from the 2026-05-20 audit. Key changes: - Add confirmation dialogs for Mark as Sent and Create Revision (FIX-17) - Restrict S3 CORS from wildcard to specific origins (FIX-38) - Add API Gateway throttling at 100 rps / 50 burst (FIX-39) - Separate vendor upload from SQS extraction trigger (FIX-04/05) - Copy TotalBidAmount on proposal revision (FIX-11) - Add CI paths-ignore and concurrency group (FIX-47) - Add post-deploy health check (FIX-46) - Fix N+1 query, Guid.Empty FK, pagination bounds, role sync (FIX-01/02/07/09) - Fix dashboard OOM, status transitions, audit error handling (FIX-03/06/12) - Fix frontend date filters, error display, currency formatting (FIX-14/16/18-23) - Remove AOSS dashboard public access, skip empty AI suggestions (FIX-41/45) - Add aria-labels, document.title management, deduplicate constants - Restrict CORS localhost to development, log invalid API key attempts [skip deploy]
88 lines
2.9 KiB
C#
88 lines
2.9 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using ProposalSystem.Application.DTOs;
|
|
using ProposalSystem.Application.Interfaces;
|
|
using ProposalSystem.Domain.Entities;
|
|
using ProposalSystem.Infrastructure.Data;
|
|
|
|
namespace ProposalSystem.Infrastructure.Services;
|
|
|
|
public class SimilarProposalService : ISimilarProposalService
|
|
{
|
|
private readonly ProposalDbContext _db;
|
|
private readonly ICurrentUserService _currentUser;
|
|
|
|
public SimilarProposalService(ProposalDbContext db, ICurrentUserService currentUser)
|
|
{
|
|
_db = db;
|
|
_currentUser = currentUser;
|
|
}
|
|
|
|
public async Task<IReadOnlyList<SimilarProposalResponse>> GetSimilarProposalsAsync(
|
|
Guid proposalId,
|
|
CancellationToken ct = default)
|
|
{
|
|
var references = await _db.SimilarProposalReferences
|
|
.Where(r => r.ProposalId == proposalId)
|
|
.OrderByDescending(r => r.SimilarityScore)
|
|
.Take(5)
|
|
.ToListAsync(ct);
|
|
|
|
if (references.Count == 0)
|
|
return [];
|
|
|
|
var libraryItemIds = references.Select(r => r.ReferencedLibraryItemId).ToList();
|
|
|
|
var proposals = await _db.Proposals
|
|
.Include(p => p.LineItems)
|
|
.Where(p => libraryItemIds.Contains(p.ProposalNumber))
|
|
.ToListAsync(ct);
|
|
|
|
var proposalsByNumber = proposals.ToDictionary(p => p.ProposalNumber);
|
|
|
|
var results = new List<SimilarProposalResponse>();
|
|
foreach (var reference in references)
|
|
{
|
|
if (!proposalsByNumber.TryGetValue(reference.ReferencedLibraryItemId, out var referencedProposal))
|
|
continue;
|
|
|
|
results.Add(new SimilarProposalResponse(
|
|
referencedProposal.ProposalNumber,
|
|
referencedProposal.CustomerName,
|
|
referencedProposal.ServiceCategory.ToString(),
|
|
reference.SimilarityScore,
|
|
referencedProposal.TotalBidAmount,
|
|
referencedProposal.LineItems
|
|
.OrderBy(li => li.SortOrder)
|
|
.Select(li => new SimilarLineItemResponse(
|
|
li.Description,
|
|
li.Quantity,
|
|
li.Unit,
|
|
li.UnitPrice,
|
|
li.TotalPrice
|
|
))
|
|
.ToList()
|
|
));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public async Task AddReferenceAsync(
|
|
Guid proposalId,
|
|
CreateSimilarReferenceRequest request,
|
|
CancellationToken ct = default)
|
|
{
|
|
var reference = new SimilarProposalReference
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ProposalId = proposalId,
|
|
ReferencedLibraryItemId = request.ReferencedLibraryItemId,
|
|
SimilarityScore = request.SimilarityScore,
|
|
ReferencedAt = DateTime.UtcNow,
|
|
ReferencedById = _currentUser.UserId,
|
|
};
|
|
|
|
_db.SimilarProposalReferences.Add(reference);
|
|
await _db.SaveChangesAsync(ct);
|
|
}
|
|
}
|