feat(domain): add sales document domain contracts with coverage
All checks were successful
CI/CD Pipeline / Build and Deploy with Docker Compose (pull_request) Successful in 7m39s

- add sales document domain entities
- add coverage-oriented DTO contracts
- add sales document enums/constants
- prepare domain model for mixed coverage scenarios
- align domain contracts with future ARCA integration

closes #57
This commit is contained in:
Leandro Hernan Rojas 2026-05-07 00:33:34 -03:00
parent 43f83c7252
commit 5e894ddca5
20 changed files with 720 additions and 0 deletions

View File

@ -0,0 +1,10 @@
namespace Domain.Constants
{
public enum SalesDocumentCoverageType : int
{
Direct = 1,
Capita = 2,
Adjustment = 3,
Manual = 4
}
}

View File

@ -0,0 +1,10 @@
namespace Domain.Constants
{
public enum SalesDocumentOriginType : int
{
Manual = 1,
QuoteDetail = 2,
Adjustment = 3,
Capita = 4
}
}

View File

@ -0,0 +1,10 @@
namespace Domain.Constants
{
public enum SalesDocumentStatus : int
{
Draft = 1,
Validated = 2,
Issued = 3,
Cancelled = 4
}
}

View File

@ -0,0 +1,12 @@
namespace Domain.Constants
{
public enum SalesDocumentType : int
{
Invoice = 1,
DebitNote = 2,
CreditNote = 3,
CreditInvoice = 4,
CreditDebitNote = 5,
CreditCreditNote = 6
}
}

View File

@ -0,0 +1,12 @@
namespace Domain.Constants
{
public enum SalesFiscalDocumentStatus : int
{
None = 0,
Pending = 1,
Authorized = 2,
Rejected = 3,
Error = 4,
PendingReconciliation = 5
}
}

View File

@ -0,0 +1,19 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentCoverageDto
{
public int Id { get; set; }
public int SalesDocumentId { get; set; }
public int? SalesDocumentDetailId { get; set; }
public int QuoteId { get; set; }
public int? QuoteDetailId { get; set; }
public int CoverageType { get; set; }
public decimal? CoveragePercentage { get; set; }
public decimal? CoverageAmount { get; set; }
public DateTime? PeriodFrom { get; set; }
public DateTime? PeriodTo { get; set; }
public string? Notes { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentCreateCoverageRequest
{
public int? SalesDocumentDetailId { get; set; }
public int QuoteId { get; set; }
public int? QuoteDetailId { get; set; }
public int CoverageType { get; set; }
public decimal? CoveragePercentage { get; set; }
public decimal? CoverageAmount { get; set; }
public DateTime? PeriodFrom { get; set; }
public DateTime? PeriodTo { get; set; }
public string? Notes { get; set; }
}
}

View File

@ -0,0 +1,21 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentCreateDetailRequest
{
public int LineNumber { get; set; }
public int OriginType { get; set; }
public int? OriginId { get; set; }
public int? QuoteDetailId { get; set; }
public int? ProductId { get; set; }
public string Description { get; set; } = string.Empty;
public decimal Quantity { get; set; }
public decimal? AuthorizedUnitPrice { get; set; }
public decimal? AuthorizedAmount { get; set; }
public decimal? BilledPercentage { get; set; }
public decimal UnitPrice { get; set; }
public decimal NetAmount { get; set; }
public decimal TaxAmount { get; set; }
public decimal TotalAmount { get; set; }
public string? OriginSnapshotJson { get; set; }
}
}

View File

@ -0,0 +1,25 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentCreateRequest
{
public int? FormseriesId { get; set; }
public int DocumentType { get; set; }
public int? FiscalVoucherType { get; set; }
public string? FiscalVoucherLetter { get; set; }
public int? QuoteId { get; set; }
public int CustomerId { get; set; }
public int BillToCustomerId { get; set; }
public DateTime? IssueDate { get; set; }
public string Currency { get; set; } = string.Empty;
public decimal ExchangeRate { get; set; }
public string? AssociatedDocumentType { get; set; }
public string? AssociatedDocumentNumber { get; set; }
public DateTime? AssociatedDocumentDate { get; set; }
public string? Observations { get; set; }
public string? ExtraInfoJson { get; set; }
public DateTime? PeriodFrom { get; set; }
public DateTime? PeriodTo { get; set; }
public List<SalesDocumentCreateDetailRequest> Details { get; set; } = new();
public List<SalesDocumentCreateCoverageRequest> Coverage { get; set; } = new();
}
}

View File

@ -0,0 +1,8 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentCreateResponse
{
public int Id { get; set; }
public string? InternalDocumentNumber { get; set; }
}
}

View File

@ -0,0 +1,25 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentDetailDto
{
public int Id { get; set; }
public int SalesDocumentId { get; set; }
public int LineNumber { get; set; }
public string OriginType { get; set; } = string.Empty;
public int? OriginId { get; set; }
public int? QuoteDetailId { get; set; }
public int? ProductId { get; set; }
public string Description { get; set; } = string.Empty;
public decimal Quantity { get; set; }
public decimal? AuthorizedUnitPrice { get; set; }
public decimal? AuthorizedAmount { get; set; }
public decimal? BilledPercentage { get; set; }
public decimal UnitPrice { get; set; }
public decimal NetAmount { get; set; }
public decimal TaxAmount { get; set; }
public decimal TotalAmount { get; set; }
public string? OriginSnapshotJson { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
}
}

View File

@ -0,0 +1,37 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentDto
{
public int Id { get; set; }
public int? FormseriesId { get; set; }
public int? InternalSequenceNumber { get; set; }
public string? InternalDocumentNumber { get; set; }
public int DocumentType { get; set; }
public int? FiscalVoucherType { get; set; }
public string? FiscalVoucherLetter { get; set; }
public int Status { get; set; }
public int? QuoteId { get; set; }
public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty;
public int BillToCustomerId { get; set; }
public string BillToCustomerName { get; set; } = string.Empty;
public DateTime? IssueDate { get; set; }
public string Currency { get; set; } = string.Empty;
public decimal ExchangeRate { get; set; }
public decimal NetAmount { get; set; }
public decimal TaxAmount { get; set; }
public decimal TotalAmount { get; set; }
public string? AssociatedDocumentType { get; set; }
public string? AssociatedDocumentNumber { get; set; }
public DateTime? AssociatedDocumentDate { get; set; }
public string? Observations { get; set; }
public string? ExtraInfoJson { get; set; }
public DateTime? PeriodFrom { get; set; }
public DateTime? PeriodTo { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public List<SalesDocumentDetailDto> Details { get; set; } = new();
public List<SalesDocumentCoverageDto> Coverage { get; set; } = new();
public SalesFiscalDocumentDto? FiscalDocument { get; set; }
}
}

View File

@ -0,0 +1,24 @@
namespace Domain.Dtos.Sales
{
public class SalesDocumentSummaryDto
{
public int Id { get; set; }
public string? InternalDocumentNumber { get; set; }
public int DocumentType { get; set; }
public int Status { get; set; }
public int? QuoteId { get; set; }
public int CustomerId { get; set; }
public string CustomerName { get; set; } = string.Empty;
public int BillToCustomerId { get; set; }
public string BillToCustomerName { get; set; } = string.Empty;
public DateTime? IssueDate { get; set; }
public string Currency { get; set; } = string.Empty;
public decimal NetAmount { get; set; }
public decimal TaxAmount { get; set; }
public decimal TotalAmount { get; set; }
public DateTime? PeriodFrom { get; set; }
public DateTime? PeriodTo { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
}
}

View File

@ -0,0 +1,15 @@
namespace Domain.Dtos.Sales
{
public class SalesFiscalDocumentAssociationDto
{
public int Id { get; set; }
public int SalesFiscalDocumentId { get; set; }
public int? AssociatedSalesDocumentId { get; set; }
public int AssociatedVoucherType { get; set; }
public short AssociatedPointOfSale { get; set; }
public int AssociatedVoucherNumber { get; set; }
public string? AssociatedVoucherCuit { get; set; }
public DateTime? AssociatedVoucherDate { get; set; }
public DateTime Createdat { get; set; }
}
}

View File

@ -0,0 +1,29 @@
namespace Domain.Dtos.Sales
{
public class SalesFiscalDocumentDto
{
public int Id { get; set; }
public int SalesDocumentId { get; set; }
public int FiscalStatus { get; set; }
public string Environment { get; set; } = string.Empty;
public short? PointOfSale { get; set; }
public int? VoucherType { get; set; }
public string? VoucherLetter { get; set; }
public int? VoucherNumber { get; set; }
public string? Cae { get; set; }
public DateTime? CaeExpirationDate { get; set; }
public string? RequestFingerprint { get; set; }
public bool IsFinal { get; set; }
public string? ArcaRequestPayloadJson { get; set; }
public string? ArcaResponsePayloadJson { get; set; }
public string? ErrorsJson { get; set; }
public string? EventsJson { get; set; }
public string? ObservationsJson { get; set; }
public DateTime? AttemptedAtUtc { get; set; }
public DateTime? CompletedAtUtc { get; set; }
public bool ReconciledAfterTimeout { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public List<SalesFiscalDocumentAssociationDto> Associations { get; set; } = new();
}
}

View File

@ -0,0 +1,135 @@
namespace Domain.Entities
{
public partial class ESalesDocument
{
/// <summary>
/// Identificador interno del documento comercial.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Talonario/serie interna existente en PhronCare. Reutiliza PhS_FormSeries para numeracion interna.
/// </summary>
public int? FormseriesId { get; set; }
/// <summary>
/// Numero secuencial interno asignado al emitir internamente el documento. No corresponde al numero fiscal ARCA.
/// </summary>
public int? InternalSequenceNumber { get; set; }
/// <summary>
/// Numero visible interno del documento, formado desde la serie/talonario interno. Puede diferir del numero fiscal.
/// </summary>
public string? InternalDocumentNumber { get; set; }
/// <summary>
/// Tipo comercial interno del documento. Ejemplos: Invoice, DebitNote, CreditNote, CreditInvoice, CreditDebitNote, CreditCreditNote.
/// </summary>
public int DocumentType { get; set; }
/// <summary>
/// Tipo de comprobante fiscal AFIP/ARCA previsto para autorizacion futura. Ejemplos: 1, 6, 11, 201, 202, 203.
/// </summary>
public int? FiscalVoucherType { get; set; }
/// <summary>
/// Letra fiscal prevista del comprobante: A, B, C u otras segun configuracion fiscal.
/// </summary>
public string? FiscalVoucherLetter { get; set; }
/// <summary>
/// Estado comercial interno. Ejemplos: Draft, Validated, Issued, Cancelled. Independiente del estado fiscal.
/// </summary>
public int Status { get; set; }
/// <summary>
/// Presupuesto origen opcional. Puede ser NULL para ventas manuales o de escritorio.
/// </summary>
public int? QuoteId { get; set; }
/// <summary>
/// Cliente origen de la operacion comercial.
/// </summary>
public int CustomerId { get; set; }
/// <summary>
/// Cliente al que se factura realmente. Permite escenarios obra social / particular u otros terceros pagadores.
/// </summary>
public int BillToCustomerId { get; set; }
/// <summary>
/// Fecha de emision interna del documento comercial.
/// </summary>
public DateTime? IssueDate { get; set; }
/// <summary>
/// Moneda del documento comercial.
/// </summary>
public string Currency { get; set; } = null!;
/// <summary>
/// Cotizacion utilizada para la moneda del documento.
/// </summary>
public decimal ExchangeRate { get; set; }
/// <summary>
/// Importe neto total del documento.
/// </summary>
public decimal NetAmount { get; set; }
/// <summary>
/// Importe total de impuestos del documento.
/// </summary>
public decimal TaxAmount { get; set; }
/// <summary>
/// Importe total del documento.
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// Tipo de documento interno asociado opcional, por ejemplo remito, orden de compra o autorizacion. No representa CbtesAsoc fiscal.
/// </summary>
public string? AssociatedDocumentType { get; set; }
/// <summary>
/// Numero del documento interno asociado opcional.
/// </summary>
public string? AssociatedDocumentNumber { get; set; }
/// <summary>
/// Fecha del documento interno asociado opcional.
/// </summary>
public DateTime? AssociatedDocumentDate { get; set; }
/// <summary>
/// Observaciones comerciales del documento.
/// </summary>
public string? Observations { get; set; }
/// <summary>
/// Snapshot JSON con informacion extra contextual del documento.
/// </summary>
public string? ExtraInfoJson { get; set; }
/// <summary>
/// Fecha inicial del periodo comercial facturado. Aplica especialmente a facturacion por capita o periodos mensuales.
/// </summary>
public DateTime? PeriodFrom { get; set; }
/// <summary>
/// Fecha final del periodo comercial facturado. Aplica especialmente a facturacion por capita o periodos mensuales.
/// </summary>
public DateTime? PeriodTo { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public virtual ICollection<ESalesDocumentCoverage> PhSSalesDocumentCoverages { get; set; } = new List<ESalesDocumentCoverage>();
public virtual ICollection<ESalesDocumentDetail> PhSSalesDocumentDetails { get; set; } = new List<ESalesDocumentDetail>();
public virtual ICollection<ESalesFiscalDocumentAssociation> PhSSalesFiscalDocumentAssociations { get; set; } = new List<ESalesFiscalDocumentAssociation>();
}
}

View File

@ -0,0 +1,65 @@
namespace Domain.Entities
{
public partial class ESalesDocumentCoverage
{
/// <summary>
/// Identificador interno de la cobertura.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Documento de venta que cubre el presupuesto/caso.
/// </summary>
public int SalesdocumentId { get; set; }
/// <summary>
/// Detalle del documento de venta asociado a esta cobertura, cuando aplique. En capita puede apuntar a la linea agregada mensual.
/// </summary>
public int? SalesdocumentdetailId { get; set; }
/// <summary>
/// Presupuesto/caso cubierto por el documento de venta. Se usa tanto para facturacion directa como para capita.
/// </summary>
public int QuoteId { get; set; }
/// <summary>
/// Detalle de presupuesto cubierto, cuando se requiera trazabilidad granular por item.
/// </summary>
public int? QuoteDetailId { get; set; }
/// <summary>
/// Tipo de cobertura. Valores esperados en Domain: Direct, Capita, Adjustment.
/// </summary>
public int CoverageType { get; set; }
/// <summary>
/// Porcentaje del presupuesto/caso cubierto por el documento. Permite 100% en facturacion directa o particiones 60/40.
/// </summary>
public decimal? CoveragePercentage { get; set; }
/// <summary>
/// Importe de referencia cubierto por el documento, cuando aplique.
/// </summary>
public decimal? CoverageAmount { get; set; }
/// <summary>
/// Fecha inicial del periodo de cobertura.
/// </summary>
public DateTime? PeriodFrom { get; set; }
/// <summary>
/// Fecha final del periodo de cobertura.
/// </summary>
public DateTime? PeriodTo { get; set; }
/// <summary>
/// Notas internas de cobertura.
/// </summary>
public string? Notes { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
}
}

View File

@ -0,0 +1,94 @@
namespace Domain.Entities
{
public partial class ESalesDocumentDetail
{
public int Id { get; set; }
/// <summary>
/// Documento comercial al que pertenece el detalle.
/// </summary>
public int SalesdocumentId { get; set; }
/// <summary>
/// Numero de linea dentro del documento.
/// </summary>
public int LineNumber { get; set; }
/// <summary>
/// Origen logico del item: Manual, QuoteDetail, Adjustment u otro valor definido por Domain/Core.
/// </summary>
public string OriginType { get; set; } = null!;
/// <summary>
/// Identificador generico del origen cuando aplique.
/// </summary>
public int? OriginId { get; set; }
/// <summary>
/// Detalle del presupuesto aprobado que origina la linea, cuando exista. Puede ser NULL en ventas manuales.
/// </summary>
public int? QuoteDetailId { get; set; }
/// <summary>
/// Producto asociado a la linea, si aplica.
/// </summary>
public int? ProductId { get; set; }
/// <summary>
/// Descripcion visible de la linea facturada.
/// </summary>
public string Description { get; set; } = null!;
/// <summary>
/// Cantidad facturada.
/// </summary>
public decimal Quantity { get; set; }
/// <summary>
/// Precio unitario autorizado o de referencia proveniente del origen comercial.
/// </summary>
public decimal? AuthorizedUnitPrice { get; set; }
/// <summary>
/// Importe autorizado o de referencia proveniente del origen comercial.
/// </summary>
public decimal? AuthorizedAmount { get; set; }
/// <summary>
/// Porcentaje facturado sobre el origen. Permite facturacion parcial obra social / particular.
/// </summary>
public decimal? BilledPercentage { get; set; }
/// <summary>
/// Precio unitario efectivo de la linea del documento.
/// </summary>
public decimal UnitPrice { get; set; }
/// <summary>
/// Importe neto de la linea.
/// </summary>
public decimal NetAmount { get; set; }
/// <summary>
/// Importe de impuestos de la linea.
/// </summary>
public decimal TaxAmount { get; set; }
/// <summary>
/// Importe total de la linea.
/// </summary>
public decimal TotalAmount { get; set; }
/// <summary>
/// Snapshot JSON del origen de la linea para trazabilidad historica.
/// </summary>
public string? OriginSnapshotJson { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public virtual ICollection<ESalesDocumentCoverage> PhSSalesDocumentCoverages { get; set; } = new List<ESalesDocumentCoverage>();
}
}

View File

@ -0,0 +1,108 @@
namespace Domain.Entities
{
public partial class ESalesFiscalDocument
{
public int Id { get; set; }
/// <summary>
/// Documento comercial interno vinculado al documento fiscal.
/// </summary>
public int SalesdocumentId { get; set; }
/// <summary>
/// Estado fiscal independiente del estado comercial. Ejemplos: None, Pending, Authorized, Rejected, Error, PendingReconciliation.
/// </summary>
public int FiscalStatus { get; set; }
/// <summary>
/// Ambiente fiscal usado para autorizacion: homologacion, produccion u otro valor definido por configuracion.
/// </summary>
public string Environment { get; set; } = null!;
/// <summary>
/// Punto de venta fiscal ARCA/AFIP.
/// </summary>
public short? PointOfSale { get; set; }
/// <summary>
/// Tipo de comprobante fiscal ARCA/AFIP utilizado en FECAESolicitar.
/// </summary>
public int? VoucherType { get; set; }
/// <summary>
/// Letra fiscal del comprobante autorizado o a autorizar.
/// </summary>
public string? VoucherLetter { get; set; }
/// <summary>
/// Numero fiscal del comprobante asignado para ARCA. Se mantiene separado del numero interno.
/// </summary>
public int? VoucherNumber { get; set; }
/// <summary>
/// Codigo de autorizacion electronico obtenido desde ARCA/AFIP.
/// </summary>
public string? Cae { get; set; }
/// <summary>
/// Fecha de vencimiento del CAE.
/// </summary>
public DateTime? CaeExpirationDate { get; set; }
/// <summary>
/// Huella de idempotencia fiscal para evitar duplicacion de solicitudes ante ARCA.
/// </summary>
public string? RequestFingerprint { get; set; }
/// <summary>
/// Indica si el resultado fiscal es final y no debe volver a mutar salvo procesos controlados de auditoria.
/// </summary>
public bool IsFinal { get; set; }
/// <summary>
/// Payload JSON enviado a ARCA/AFIP.
/// </summary>
public string? ArcaRequestPayloadJson { get; set; }
/// <summary>
/// Payload JSON recibido desde ARCA/AFIP.
/// </summary>
public string? ArcaResponsePayloadJson { get; set; }
/// <summary>
/// Errores devueltos por ARCA/AFIP serializados como JSON.
/// </summary>
public string? ErrorsJson { get; set; }
/// <summary>
/// Eventos devueltos por ARCA/AFIP serializados como JSON.
/// </summary>
public string? EventsJson { get; set; }
/// <summary>
/// Observaciones devueltas por ARCA/AFIP serializadas como JSON.
/// </summary>
public string? ObservationsJson { get; set; }
/// <summary>
/// Fecha/hora UTC del intento de autorizacion fiscal.
/// </summary>
public DateTime? AttemptedAtUtc { get; set; }
/// <summary>
/// Fecha/hora UTC de finalizacion del flujo fiscal.
/// </summary>
public DateTime? CompletedAtUtc { get; set; }
/// <summary>
/// Indica que el documento fiscal fue resuelto mediante reconciliacion posterior a timeout o resultado ambiguo.
/// </summary>
public bool ReconciledAfterTimeout { get; set; }
public DateTime Createdat { get; set; }
public DateTime? Modifiedat { get; set; }
public virtual ICollection<ESalesFiscalDocumentAssociation> PhSSalesFiscalDocumentAssociations { get; set; } = new List<ESalesFiscalDocumentAssociation>();
}
}

View File

@ -0,0 +1,46 @@
namespace Domain.Entities
{
public partial class ESalesFiscalDocumentAssociation
{
public int Id { get; set; }
/// <summary>
/// Documento fiscal que contiene esta asociacion.
/// </summary>
public int SalesfiscaldocumentId { get; set; }
/// <summary>
/// Documento comercial interno asociado, si existe dentro de PhronCare.
/// </summary>
public int? AssociatedSalesdocumentId { get; set; }
/// <summary>
/// Tipo fiscal ARCA/AFIP del comprobante asociado.
/// </summary>
public int AssociatedVoucherType { get; set; }
/// <summary>
/// Punto de venta fiscal del comprobante asociado.
/// </summary>
public short AssociatedPointOfSale { get; set; }
/// <summary>
/// Numero fiscal del comprobante asociado.
/// </summary>
public int AssociatedVoucherNumber { get; set; }
/// <summary>
/// CUIT emisor del comprobante asociado, cuando sea requerido por ARCA.
/// </summary>
public string? AssociatedVoucherCuit { get; set; }
/// <summary>
/// Fecha del comprobante fiscal asociado.
/// </summary>
public DateTime? AssociatedVoucherDate { get; set; }
public DateTime Createdat { get; set; }
public virtual ESalesDocument? AssociatedSalesdocument { get; set; }
}
}