mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 13:58:56 +00:00
Some checks failed
Deploy / Deploy to AWS (push) Has been cancelled
## Summary Implements Phase 3 of the AUDIT-2026-05-20 findings: - 29 FIX-severity items across API, web, infra, and lambdas - 7 accessibility NITs (aria-labels, document titles) - 4 code quality NITs (deduplication, constants extraction) Key changes: - API: N+1 fix, pagination clamping, idempotent transitions, upload confirm endpoint, revision TotalBidAmount carry-forward - Web: confirmation dialogs, currency formatting, error states, date range filters, document titles - Infra: S3 CORS lockdown, API Gateway throttling, AOSS network policy fix, CI concurrency - Lambdas: skip empty suggestions, remove status side-effect - Scripts: post-deploy health check ## Test plan - [x] tsc --noEmit (web + infra) - [x] dotnet build (api) - [x] ruff check + format (lambdas) - [x] Cross-review via orchestrator (no blockers) [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);
|
|
}
|
|
}
|