From 3b4f664ae960e96ca3b79525831f29bfb40b7c7e Mon Sep 17 00:00:00 2001 From: leandro Date: Fri, 8 May 2026 00:15:11 -0300 Subject: [PATCH] feat(core): normalize sales document origin types closes #62 --- Core/Services/SalesDocumentService.cs | 53 +++++++++++++++++-- Domain/Constants/SalesDocumentOriginType.cs | 3 +- .../SalesDocumentOriginTypeExtensions.cs | 18 +++++++ .../Sales/SalesDocumentCreateDetailRequest.cs | 4 +- Domain/Entities/ESalesDocumentDetail.cs | 2 +- 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 Domain/Constants/SalesDocumentOriginTypeExtensions.cs diff --git a/Core/Services/SalesDocumentService.cs b/Core/Services/SalesDocumentService.cs index 8f16b4f..044d708 100644 --- a/Core/Services/SalesDocumentService.cs +++ b/Core/Services/SalesDocumentService.cs @@ -29,6 +29,9 @@ namespace Core.Services if (request.Coverage is null || request.Coverage.Count == 0) throw new InvalidOperationException("Debe incluir coverage."); + foreach (var detail in request.Details) + ValidateDetail(detail); + var netAmount = request.Details.Sum(x => x.NetAmount); var taxAmount = request.Details.Sum(x => x.TaxAmount); var totalAmount = request.Details.Sum(x => x.TotalAmount); @@ -65,9 +68,9 @@ namespace Core.Services PhSSalesDocumentDetails = request.Details.Select(x => new ESalesDocumentDetail { LineNumber = x.LineNumber, - OriginType = x.OriginType.ToString(), - OriginId = x.OriginId, - QuoteDetailId = x.QuoteDetailId, + OriginType = x.OriginType.ToStorageCode(), + OriginId = ResolveOriginId(x), + QuoteDetailId = ResolveQuoteDetailId(x), ProductId = x.ProductId, Description = x.Description.Trim(), Quantity = x.Quantity, @@ -111,5 +114,49 @@ namespace Core.Services return _salesDocumentRepository.GetDtoByIdAsync(id); } + private static void ValidateDetail(SalesDocumentCreateDetailRequest detail) + { + if (detail.LineNumber <= 0) + throw new ArgumentException("El número de línea del detail debe ser mayor a cero.", nameof(detail.LineNumber)); + + if (!Enum.IsDefined(typeof(SalesDocumentOriginType), detail.OriginType)) + throw new ArgumentException("El tipo de origen del detail no es válido.", nameof(detail.OriginType)); + + if (string.IsNullOrWhiteSpace(detail.Description)) + throw new ArgumentException("La descripción del detail es obligatoria.", nameof(detail.Description)); + + if (detail.Quantity <= 0) + throw new ArgumentException("La cantidad del detail debe ser mayor a cero.", nameof(detail.Quantity)); + + var hasOriginId = detail.OriginId.HasValue && detail.OriginId.Value > 0; + var hasQuoteDetailId = detail.QuoteDetailId.HasValue && detail.QuoteDetailId.Value > 0; + + if (detail.OriginType != SalesDocumentOriginType.Manual && !hasOriginId && !hasQuoteDetailId) + throw new ArgumentException("Debe informar OriginId o QuoteDetailId para trazabilidad del origen.", nameof(detail.OriginId)); + + if (detail.OriginType == SalesDocumentOriginType.QuoteDetail && !hasQuoteDetailId && !hasOriginId) + throw new ArgumentException("Debe informar QuoteDetailId u OriginId para líneas originadas en presupuesto.", nameof(detail.QuoteDetailId)); + } + + private static int? ResolveOriginId(SalesDocumentCreateDetailRequest detail) + { + if (detail.OriginId.HasValue && detail.OriginId.Value > 0) + return detail.OriginId; + + return detail.OriginType == SalesDocumentOriginType.QuoteDetail + ? detail.QuoteDetailId + : null; + } + + private static int? ResolveQuoteDetailId(SalesDocumentCreateDetailRequest detail) + { + if (detail.QuoteDetailId.HasValue && detail.QuoteDetailId.Value > 0) + return detail.QuoteDetailId; + + return detail.OriginType == SalesDocumentOriginType.QuoteDetail + ? detail.OriginId + : null; + } + } } diff --git a/Domain/Constants/SalesDocumentOriginType.cs b/Domain/Constants/SalesDocumentOriginType.cs index ee0fb4b..9210ed7 100644 --- a/Domain/Constants/SalesDocumentOriginType.cs +++ b/Domain/Constants/SalesDocumentOriginType.cs @@ -5,6 +5,7 @@ namespace Domain.Constants Manual = 1, QuoteDetail = 2, Adjustment = 3, - Capita = 4 + Capita = 4, + DeliveryNote = 5 } } diff --git a/Domain/Constants/SalesDocumentOriginTypeExtensions.cs b/Domain/Constants/SalesDocumentOriginTypeExtensions.cs new file mode 100644 index 0000000..9711b66 --- /dev/null +++ b/Domain/Constants/SalesDocumentOriginTypeExtensions.cs @@ -0,0 +1,18 @@ +namespace Domain.Constants +{ + public static class SalesDocumentOriginTypeExtensions + { + public static string ToStorageCode(this SalesDocumentOriginType originType) + { + return originType switch + { + SalesDocumentOriginType.Manual => "MANUAL", + SalesDocumentOriginType.QuoteDetail => "QUOTE", + SalesDocumentOriginType.Adjustment => "ADJUSTMENT", + SalesDocumentOriginType.Capita => "CAPITA", + SalesDocumentOriginType.DeliveryNote => "DELIVERY_NOTE", + _ => throw new ArgumentOutOfRangeException(nameof(originType), originType, "Tipo de origen de documento de venta no soportado.") + }; + } + } +} diff --git a/Domain/Dtos/Sales/SalesDocumentCreateDetailRequest.cs b/Domain/Dtos/Sales/SalesDocumentCreateDetailRequest.cs index c5a4d53..778907d 100644 --- a/Domain/Dtos/Sales/SalesDocumentCreateDetailRequest.cs +++ b/Domain/Dtos/Sales/SalesDocumentCreateDetailRequest.cs @@ -1,9 +1,11 @@ +using Domain.Constants; + namespace Domain.Dtos.Sales { public class SalesDocumentCreateDetailRequest { public int LineNumber { get; set; } - public int OriginType { get; set; } + public SalesDocumentOriginType OriginType { get; set; } public int? OriginId { get; set; } public int? QuoteDetailId { get; set; } public int? ProductId { get; set; } diff --git a/Domain/Entities/ESalesDocumentDetail.cs b/Domain/Entities/ESalesDocumentDetail.cs index b0a4443..9711e11 100644 --- a/Domain/Entities/ESalesDocumentDetail.cs +++ b/Domain/Entities/ESalesDocumentDetail.cs @@ -15,7 +15,7 @@ namespace Domain.Entities public int LineNumber { get; set; } /// - /// Origen logico del item: Manual, QuoteDetail, Adjustment u otro valor definido por Domain/Core. + /// Origen logico del item. Persistir como codigo semantico: MANUAL, QUOTE, DELIVERY_NOTE, CAPITA o ADJUSTMENT. /// public string OriginType { get; set; } = null!;