From 3d55913c314c4892110d78fcad1213f7559807ea Mon Sep 17 00:00:00 2001 From: leandro Date: Thu, 7 May 2026 20:47:34 -0300 Subject: [PATCH] feat(core): add sales document core flow with coverage closes #60 --- Core/Interfaces/ISalesDocumentDom.cs | 10 ++ Core/Services/SalesDocumentService.cs | 115 ++++++++++++++++++ .../Interfaces/IPhSSalesDocumentRepository.cs | 11 ++ .../PhSSalesDocumentRepository.cs | 105 ++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 Core/Interfaces/ISalesDocumentDom.cs create mode 100644 Core/Services/SalesDocumentService.cs create mode 100644 Models/Interfaces/IPhSSalesDocumentRepository.cs create mode 100644 Models/Repositories/PhSSalesDocumentRepository.cs diff --git a/Core/Interfaces/ISalesDocumentDom.cs b/Core/Interfaces/ISalesDocumentDom.cs new file mode 100644 index 0000000..7fd2971 --- /dev/null +++ b/Core/Interfaces/ISalesDocumentDom.cs @@ -0,0 +1,10 @@ +using Domain.Dtos.Sales; + +namespace Core.Interfaces +{ + public interface ISalesDocumentDom + { + Task CreateAsync(SalesDocumentCreateRequest request); + Task GetDtoByIdAsync(int id); + } +} diff --git a/Core/Services/SalesDocumentService.cs b/Core/Services/SalesDocumentService.cs new file mode 100644 index 0000000..8f16b4f --- /dev/null +++ b/Core/Services/SalesDocumentService.cs @@ -0,0 +1,115 @@ +using Core.Interfaces; +using Domain.Constants; +using Domain.Dtos.Sales; +using Domain.Entities; +using Models.Interfaces; + +namespace Core.Services +{ + public class SalesDocumentService(IPhSSalesDocumentRepository salesDocumentRepository) : ISalesDocumentDom + { + private readonly IPhSSalesDocumentRepository _salesDocumentRepository = salesDocumentRepository; + + public async Task CreateAsync(SalesDocumentCreateRequest request) + { + ArgumentNullException.ThrowIfNull(request); + + if (request.CustomerId <= 0) + throw new ArgumentException("Debe seleccionar un cliente.", nameof(request.CustomerId)); + + if (request.BillToCustomerId <= 0) + throw new ArgumentException("Debe seleccionar un cliente de facturación.", nameof(request.BillToCustomerId)); + + if (string.IsNullOrWhiteSpace(request.Currency)) + throw new ArgumentException("La moneda es obligatoria.", nameof(request.Currency)); + + if (request.Details is null || request.Details.Count == 0) + throw new InvalidOperationException("Debe incluir al menos un detail."); + + if (request.Coverage is null || request.Coverage.Count == 0) + throw new InvalidOperationException("Debe incluir coverage."); + + var netAmount = request.Details.Sum(x => x.NetAmount); + var taxAmount = request.Details.Sum(x => x.TaxAmount); + var totalAmount = request.Details.Sum(x => x.TotalAmount); + + if (totalAmount <= 0) + throw new InvalidOperationException("El total del documento debe ser mayor a cero."); + + var now = DateTime.Now; + + var entity = new ESalesDocument + { + FormseriesId = request.FormseriesId, + DocumentType = request.DocumentType, + FiscalVoucherType = request.FiscalVoucherType, + FiscalVoucherLetter = request.FiscalVoucherLetter, + Status = (int)SalesDocumentStatus.Draft, + QuoteId = request.QuoteId, + CustomerId = request.CustomerId, + BillToCustomerId = request.BillToCustomerId, + IssueDate = request.IssueDate ?? now, + Currency = request.Currency.Trim(), + ExchangeRate = request.ExchangeRate <= 0 ? 1 : request.ExchangeRate, + NetAmount = netAmount, + TaxAmount = taxAmount, + TotalAmount = totalAmount, + AssociatedDocumentType = request.AssociatedDocumentType, + AssociatedDocumentNumber = request.AssociatedDocumentNumber, + AssociatedDocumentDate = request.AssociatedDocumentDate, + Observations = request.Observations, + ExtraInfoJson = request.ExtraInfoJson, + PeriodFrom = request.PeriodFrom, + PeriodTo = request.PeriodTo, + Createdat = now, + PhSSalesDocumentDetails = request.Details.Select(x => new ESalesDocumentDetail + { + LineNumber = x.LineNumber, + OriginType = x.OriginType.ToString(), + OriginId = x.OriginId, + QuoteDetailId = x.QuoteDetailId, + ProductId = x.ProductId, + Description = x.Description.Trim(), + Quantity = x.Quantity, + AuthorizedUnitPrice = x.AuthorizedUnitPrice, + AuthorizedAmount = x.AuthorizedAmount, + BilledPercentage = x.BilledPercentage, + UnitPrice = x.UnitPrice, + NetAmount = x.NetAmount, + TaxAmount = x.TaxAmount, + TotalAmount = x.TotalAmount, + OriginSnapshotJson = x.OriginSnapshotJson, + Createdat = now + }).ToList(), + PhSSalesDocumentCoverages = request.Coverage.Select(x => new ESalesDocumentCoverage + { + QuoteId = x.QuoteId, + QuoteDetailId = x.QuoteDetailId, + CoverageType = x.CoverageType, + CoveragePercentage = x.CoveragePercentage, + CoverageAmount = x.CoverageAmount, + PeriodFrom = x.PeriodFrom, + PeriodTo = x.PeriodTo, + Notes = x.Notes, + Createdat = now + }).ToList() + }; + + var created = await _salesDocumentRepository.CreateAsync(entity); + + return new SalesDocumentCreateResponse + { + Id = created.Id, + InternalDocumentNumber = created.InternalDocumentNumber + }; + } + + public Task GetDtoByIdAsync(int id) + { + if (id <= 0) + throw new ArgumentOutOfRangeException(nameof(id)); + + return _salesDocumentRepository.GetDtoByIdAsync(id); + } + } +} diff --git a/Models/Interfaces/IPhSSalesDocumentRepository.cs b/Models/Interfaces/IPhSSalesDocumentRepository.cs new file mode 100644 index 0000000..49a0457 --- /dev/null +++ b/Models/Interfaces/IPhSSalesDocumentRepository.cs @@ -0,0 +1,11 @@ +using Domain.Dtos.Sales; +using Domain.Entities; + +namespace Models.Interfaces +{ + public interface IPhSSalesDocumentRepository + { + Task CreateAsync(ESalesDocument entity); + Task GetDtoByIdAsync(int id); + } +} diff --git a/Models/Repositories/PhSSalesDocumentRepository.cs b/Models/Repositories/PhSSalesDocumentRepository.cs new file mode 100644 index 0000000..c2e6299 --- /dev/null +++ b/Models/Repositories/PhSSalesDocumentRepository.cs @@ -0,0 +1,105 @@ +using Domain.Dtos.Sales; +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Models.Helpers; +using Models.Interfaces; +using Models.Models; + +namespace Models.Repositories +{ + public class PhSSalesDocumentRepository(PhronCareOperationsHubContext context) : IPhSSalesDocumentRepository + { + private readonly PhronCareOperationsHubContext _context = context; + + public async Task CreateAsync(ESalesDocument entity) + { + var mapped = EntityMapper.MapEntity(entity); + + await _context.PhSSalesDocuments.AddAsync(mapped); + await _context.SaveChangesAsync(); + + return EntityMapper.MapEntity(mapped); + } + + public async Task GetDtoByIdAsync(int id) + { + var entity = await _context.PhSSalesDocuments + .Include(x => x.Customer) + .Include(x => x.BillToCustomer) + .Include(x => x.PhSSalesDocumentDetails) + .Include(x => x.PhSSalesDocumentCoverages) + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == id); + + if (entity == null) + return null; + + return new SalesDocumentDto + { + Id = entity.Id, + FormseriesId = entity.FormseriesId, + InternalSequenceNumber = entity.InternalSequenceNumber, + InternalDocumentNumber = entity.InternalDocumentNumber, + DocumentType = entity.DocumentType, + FiscalVoucherType = entity.FiscalVoucherType, + FiscalVoucherLetter = entity.FiscalVoucherLetter, + Status = entity.Status, + QuoteId = entity.QuoteId, + CustomerId = entity.CustomerId, + CustomerName = entity.Customer?.Name ?? string.Empty, + BillToCustomerId = entity.BillToCustomerId, + BillToCustomerName = entity.BillToCustomer?.Name ?? string.Empty, + IssueDate = entity.IssueDate, + Currency = entity.Currency, + ExchangeRate = entity.ExchangeRate, + NetAmount = entity.NetAmount, + TaxAmount = entity.TaxAmount, + TotalAmount = entity.TotalAmount, + Observations = entity.Observations, + ExtraInfoJson = entity.ExtraInfoJson, + PeriodFrom = entity.PeriodFrom, + PeriodTo = entity.PeriodTo, + Createdat = entity.Createdat, + Modifiedat = entity.Modifiedat, + Details = entity.PhSSalesDocumentDetails.Select(x => new SalesDocumentDetailDto + { + Id = x.Id, + SalesDocumentId = x.SalesdocumentId, + LineNumber = x.LineNumber, + OriginType = x.OriginType, + OriginId = x.OriginId, + QuoteDetailId = x.QuoteDetailId, + ProductId = x.ProductId, + Description = x.Description, + Quantity = x.Quantity, + AuthorizedUnitPrice = x.AuthorizedUnitPrice, + AuthorizedAmount = x.AuthorizedAmount, + BilledPercentage = x.BilledPercentage, + UnitPrice = x.UnitPrice, + NetAmount = x.NetAmount, + TaxAmount = x.TaxAmount, + TotalAmount = x.TotalAmount, + OriginSnapshotJson = x.OriginSnapshotJson, + Createdat = x.Createdat, + Modifiedat = x.Modifiedat + }).ToList(), + Coverage = entity.PhSSalesDocumentCoverages.Select(x => new SalesDocumentCoverageDto + { + Id = x.Id, + SalesDocumentId = x.SalesdocumentId, + SalesDocumentDetailId = x.SalesdocumentdetailId, + QuoteId = x.QuoteId, + QuoteDetailId = x.QuoteDetailId, + CoverageType = x.CoverageType, + CoveragePercentage = x.CoveragePercentage, + CoverageAmount = x.CoverageAmount, + PeriodFrom = x.PeriodFrom, + PeriodTo = x.PeriodTo, + Notes = x.Notes, + Createdat = x.Createdat, + Modifiedat = x.Modifiedat + }).ToList() + }; + } + } +} -- 2.47.1