feat(core): normalize sales document origin types
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 6m51s

closes #62
This commit is contained in:
Leandro Hernan Rojas 2026-05-08 00:15:11 -03:00
parent 4d5045718f
commit 3b4f664ae9
5 changed files with 74 additions and 6 deletions

View File

@ -29,6 +29,9 @@ namespace Core.Services
if (request.Coverage is null || request.Coverage.Count == 0) if (request.Coverage is null || request.Coverage.Count == 0)
throw new InvalidOperationException("Debe incluir coverage."); throw new InvalidOperationException("Debe incluir coverage.");
foreach (var detail in request.Details)
ValidateDetail(detail);
var netAmount = request.Details.Sum(x => x.NetAmount); var netAmount = request.Details.Sum(x => x.NetAmount);
var taxAmount = request.Details.Sum(x => x.TaxAmount); var taxAmount = request.Details.Sum(x => x.TaxAmount);
var totalAmount = request.Details.Sum(x => x.TotalAmount); var totalAmount = request.Details.Sum(x => x.TotalAmount);
@ -65,9 +68,9 @@ namespace Core.Services
PhSSalesDocumentDetails = request.Details.Select(x => new ESalesDocumentDetail PhSSalesDocumentDetails = request.Details.Select(x => new ESalesDocumentDetail
{ {
LineNumber = x.LineNumber, LineNumber = x.LineNumber,
OriginType = x.OriginType.ToString(), OriginType = x.OriginType.ToStorageCode(),
OriginId = x.OriginId, OriginId = ResolveOriginId(x),
QuoteDetailId = x.QuoteDetailId, QuoteDetailId = ResolveQuoteDetailId(x),
ProductId = x.ProductId, ProductId = x.ProductId,
Description = x.Description.Trim(), Description = x.Description.Trim(),
Quantity = x.Quantity, Quantity = x.Quantity,
@ -111,5 +114,49 @@ namespace Core.Services
return _salesDocumentRepository.GetDtoByIdAsync(id); 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;
}
} }
} }

View File

@ -5,6 +5,7 @@ namespace Domain.Constants
Manual = 1, Manual = 1,
QuoteDetail = 2, QuoteDetail = 2,
Adjustment = 3, Adjustment = 3,
Capita = 4 Capita = 4,
DeliveryNote = 5
} }
} }

View File

@ -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.")
};
}
}
}

View File

@ -1,9 +1,11 @@
using Domain.Constants;
namespace Domain.Dtos.Sales namespace Domain.Dtos.Sales
{ {
public class SalesDocumentCreateDetailRequest public class SalesDocumentCreateDetailRequest
{ {
public int LineNumber { get; set; } public int LineNumber { get; set; }
public int OriginType { get; set; } public SalesDocumentOriginType OriginType { get; set; }
public int? OriginId { get; set; } public int? OriginId { get; set; }
public int? QuoteDetailId { get; set; } public int? QuoteDetailId { get; set; }
public int? ProductId { get; set; } public int? ProductId { get; set; }

View File

@ -15,7 +15,7 @@ namespace Domain.Entities
public int LineNumber { get; set; } public int LineNumber { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public string OriginType { get; set; } = null!; public string OriginType { get; set; } = null!;