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 LineItemService : ILineItemService { private readonly ProposalDbContext _db; private readonly IAuditService _audit; public LineItemService(ProposalDbContext db, IAuditService audit) { _db = db; _audit = audit; } public async Task> GetByProposalIdAsync(Guid proposalId, CancellationToken ct = default) { return await _db.LineItems .Where(li => li.ProposalId == proposalId) .OrderBy(li => li.SortOrder) .Select(li => MapToResponse(li)) .ToListAsync(ct); } public async Task CreateAsync(Guid proposalId, CreateLineItemRequest request, CancellationToken ct = default) { var proposal = await _db.Proposals.FindAsync(new object[] { proposalId }, ct) ?? throw new KeyNotFoundException($"Proposal {proposalId} not found"); if (proposal.Status == ProposalStatus.Approved || proposal.Status == ProposalStatus.Sent) throw new InvalidOperationException("Cannot modify line items on approved/sent proposals"); var now = DateTime.UtcNow; var lineItem = new LineItem { Id = Guid.NewGuid(), ProposalId = proposalId, Description = request.Description, Quantity = request.Quantity, Unit = request.Unit, UnitPrice = request.UnitPrice, TotalPrice = request.TotalPrice, PricingMode = request.PricingMode, SortOrder = request.SortOrder, Source = request.Source, CreatedAt = now, UpdatedAt = now, }; _db.LineItems.Add(lineItem); await _db.SaveChangesAsync(ct); await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Added: {request.Description}", ct); return MapToResponse(lineItem); } public async Task> BulkUpdateAsync(Guid proposalId, BulkUpdateLineItemsRequest request, CancellationToken ct = default) { var proposal = await _db.Proposals.FindAsync(new object[] { proposalId }, ct) ?? throw new KeyNotFoundException($"Proposal {proposalId} not found"); if (proposal.Status == ProposalStatus.Approved || proposal.Status == ProposalStatus.Sent) throw new InvalidOperationException("Cannot modify line items on approved/sent proposals"); var existing = await _db.LineItems .Where(li => li.ProposalId == proposalId) .ToListAsync(ct); _db.LineItems.RemoveRange(existing); var now = DateTime.UtcNow; var newItems = request.LineItems.Select(entry => new LineItem { Id = entry.Id ?? Guid.NewGuid(), ProposalId = proposalId, Description = entry.Description, Quantity = entry.Quantity, Unit = entry.Unit, UnitPrice = entry.UnitPrice, TotalPrice = entry.TotalPrice, PricingMode = entry.PricingMode, SortOrder = entry.SortOrder, Source = entry.Source, CreatedAt = now, UpdatedAt = now, }).ToList(); _db.LineItems.AddRange(newItems); proposal.TotalBidAmount = newItems.Sum(li => li.TotalPrice); proposal.UpdatedAt = now; await _db.SaveChangesAsync(ct); await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Bulk update: {newItems.Count} items", ct); return newItems.OrderBy(li => li.SortOrder).Select(MapToResponse).ToList(); } public async Task DeleteAsync(Guid proposalId, Guid lineItemId, CancellationToken ct = default) { var lineItem = await _db.LineItems .FirstOrDefaultAsync(li => li.Id == lineItemId && li.ProposalId == proposalId, ct) ?? throw new KeyNotFoundException($"Line item {lineItemId} not found"); var proposal = await _db.Proposals.FindAsync(new object[] { proposalId }, ct)!; if (proposal!.Status == ProposalStatus.Approved || proposal.Status == ProposalStatus.Sent) throw new InvalidOperationException("Cannot modify line items on approved/sent proposals"); _db.LineItems.Remove(lineItem); await _db.SaveChangesAsync(ct); await _audit.LogAsync(AuditAction.EditLineItem, proposalId, $"Removed: {lineItem.Description}", ct); } private static LineItemResponse MapToResponse(LineItem li) => new( li.Id, li.ProposalId, li.Description, li.Quantity, li.Unit, li.UnitPrice, li.TotalPrice, li.PricingMode, li.SortOrder, li.Source, li.CreatedAt, li.UpdatedAt ); }