522 lines
14 KiB
Markdown
522 lines
14 KiB
Markdown
|
|
# Story #76 — Sales Document Item Selection & Partial Billing
|
||
|
|
|
||
|
|
## Objetivo
|
||
|
|
|
||
|
|
Permitir que un **Sales Document** pueda construirse desde una selección parcial de ítems provenientes de uno o más **Delivery Notes**, desacoplando el concepto de:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Remito completo
|
||
|
|
```
|
||
|
|
|
||
|
|
del concepto de:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Contenido efectivamente facturable
|
||
|
|
```
|
||
|
|
|
||
|
|
La story debe permitir seleccionar líneas, excluir ítems completos y modificar cantidades facturables antes de generar el documento comercial.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Contexto funcional
|
||
|
|
|
||
|
|
El módulo **Sales Document** ya cuenta con persistencia, contratos de dominio, DTOs, Core Flow, repositorios, API oficial, UI BackOffice y Draft Review & Validation implementados en stories previas.
|
||
|
|
|
||
|
|
Actualmente el flujo funcional es:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Presupuesto
|
||
|
|
↓
|
||
|
|
Remito
|
||
|
|
↓
|
||
|
|
Sales Document
|
||
|
|
```
|
||
|
|
|
||
|
|
Cuando el usuario selecciona uno o más remitos, el sistema copia automáticamente todo el contenido del remito hacia `SalesDocumentDetails`.
|
||
|
|
|
||
|
|
Ese comportamiento es insuficiente para la operatoria real de ortopedias, financiadores y coberturas médicas, donde pueden existir escenarios como:
|
||
|
|
|
||
|
|
```text
|
||
|
|
- facturación parcial de un remito;
|
||
|
|
- facturación parcial de cantidades entregadas;
|
||
|
|
- exclusión de prestaciones rechazadas;
|
||
|
|
- facturación en etapas;
|
||
|
|
- saldos pendientes por línea;
|
||
|
|
- necesidad futura de notas de crédito sobre líneas específicas.
|
||
|
|
```
|
||
|
|
|
||
|
|
Por lo tanto, la evolución correcta del modelo es pasar de:
|
||
|
|
|
||
|
|
```text
|
||
|
|
DeliveryNote completo → SalesDocument
|
||
|
|
```
|
||
|
|
|
||
|
|
a:
|
||
|
|
|
||
|
|
```text
|
||
|
|
DeliveryNoteDetail → SalesDocumentDetail
|
||
|
|
```
|
||
|
|
|
||
|
|
sin rediseñar el módulo completo ni romper los contratos existentes que puedan seguir siendo útiles.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Diagnóstico técnico inicial
|
||
|
|
|
||
|
|
Antes de implementar, Codex debe revisar el repositorio actualizado y confirmar las rutas reales de los archivos. No asumir nombres si difieren del código actual.
|
||
|
|
|
||
|
|
El análisis previo detectó que hoy el flujo crea `SalesDocumentDetails` a partir de todos los ítems de los remitos seleccionados.
|
||
|
|
|
||
|
|
Flujo esperado actual aproximado:
|
||
|
|
|
||
|
|
```text
|
||
|
|
request.DeliveryNoteIds
|
||
|
|
↓
|
||
|
|
GetDeliveryNotesForSalesDocumentAsync
|
||
|
|
↓
|
||
|
|
foreach DeliveryNote
|
||
|
|
foreach DeliveryNoteItem
|
||
|
|
crear ESalesDocumentDetail
|
||
|
|
crear ESalesDocumentCoverage si corresponde
|
||
|
|
↓
|
||
|
|
CreateFromDeliveryNotesAsync
|
||
|
|
↓
|
||
|
|
marcar DeliveryNote.SalesinvoiceId = SalesDocument.Id
|
||
|
|
```
|
||
|
|
|
||
|
|
Restricción detectada:
|
||
|
|
|
||
|
|
```text
|
||
|
|
PhSDeliveryNote.SalesinvoiceId
|
||
|
|
```
|
||
|
|
|
||
|
|
parece operar como marca de remito ya facturado. Ese criterio sirve para facturación total, pero no sirve como criterio principal para facturación parcial, porque no permite distinguir saldos por línea.
|
||
|
|
|
||
|
|
La solución de esta story debe evitar que `SalesinvoiceId` siga siendo el único mecanismo de control de elegibilidad para facturar.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Alcance
|
||
|
|
|
||
|
|
Implementar soporte comercial para selección parcial de ítems de remitos al crear un Sales Document.
|
||
|
|
|
||
|
|
La implementación debe respetar estrictamente la arquitectura:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Data → Domain → Core → API → UI
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1. Data / Models
|
||
|
|
|
||
|
|
- No modificar manualmente modelos EF generados por scaffold.
|
||
|
|
- Revisar si las tablas existentes alcanzan para soportar la trazabilidad por línea.
|
||
|
|
- Preferir no crear tablas nuevas salvo necesidad técnica comprobada.
|
||
|
|
- Evaluar si conviene agregar índices SQL sobre `PhS_SalesDocumentDetails` para mejorar consultas por origen.
|
||
|
|
- Si se requiere SQL, entregarlo separado en archivo `.sql`.
|
||
|
|
- Si se actualiza scaffold, hacerlo mediante el procedimiento habitual del proyecto, no editando modelos EF a mano.
|
||
|
|
|
||
|
|
### 2. Domain
|
||
|
|
|
||
|
|
Agregar o ajustar contratos de dominio para representar candidatos facturables por línea.
|
||
|
|
|
||
|
|
DTOs sugeridos, ajustando nombres a convenciones reales del repo:
|
||
|
|
|
||
|
|
```text
|
||
|
|
SalesDocumentDeliveryNoteItemCandidateDto
|
||
|
|
SalesDocumentCreateFromDeliveryNoteItemsRequest
|
||
|
|
SalesDocumentCreateFromDeliveryNoteItemSelectionRequest
|
||
|
|
```
|
||
|
|
|
||
|
|
Cada candidato debe poder exponer como mínimo:
|
||
|
|
|
||
|
|
```text
|
||
|
|
DeliveryNoteId
|
||
|
|
DeliveryNoteNumber
|
||
|
|
DeliveryNoteDetailId
|
||
|
|
QuoteId
|
||
|
|
QuoteDetailId
|
||
|
|
Item description / product description
|
||
|
|
DeliveredQuantity
|
||
|
|
AlreadyBilledQuantity
|
||
|
|
PendingQuantity
|
||
|
|
SelectedQuantity
|
||
|
|
ApprovedUnitPrice
|
||
|
|
SelectedAmount
|
||
|
|
Coverage information when available
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Core
|
||
|
|
|
||
|
|
Agregar flujo de negocio para:
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. obtener ítems candidatos facturables desde uno o más remitos;
|
||
|
|
2. calcular cantidad ya facturada por línea;
|
||
|
|
3. calcular cantidad pendiente;
|
||
|
|
4. validar cantidades seleccionadas;
|
||
|
|
5. crear SalesDocumentDetails únicamente con las líneas seleccionadas;
|
||
|
|
6. recalcular importes comerciales;
|
||
|
|
7. mantener Coverage consistente con las líneas seleccionadas.
|
||
|
|
```
|
||
|
|
|
||
|
|
Métodos sugeridos, ajustando nombres al repo:
|
||
|
|
|
||
|
|
```text
|
||
|
|
GetDeliveryNoteItemCandidatesForSalesDocumentAsync
|
||
|
|
CreateFromDeliveryNoteItemsAsync
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas mínimas:
|
||
|
|
|
||
|
|
```text
|
||
|
|
SelectedQuantity > 0
|
||
|
|
SelectedQuantity <= PendingQuantity
|
||
|
|
PendingQuantity > 0
|
||
|
|
No mezclar clientes fiscales incompatibles
|
||
|
|
No crear SalesDocument sin líneas seleccionadas
|
||
|
|
No duplicar facturación por encima de la cantidad entregada
|
||
|
|
Preservar precios aprobados del presupuesto/remito cuando existan
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Repository
|
||
|
|
|
||
|
|
Agregar consultas para obtener saldos por línea.
|
||
|
|
|
||
|
|
El cálculo conceptual debe ser:
|
||
|
|
|
||
|
|
```text
|
||
|
|
PendingQuantity = DeliveredQuantity - AlreadyBilledQuantity
|
||
|
|
```
|
||
|
|
|
||
|
|
donde:
|
||
|
|
|
||
|
|
```text
|
||
|
|
AlreadyBilledQuantity = SUM(SalesDocumentDetails.Quantity)
|
||
|
|
```
|
||
|
|
|
||
|
|
filtrado por el origen correcto de la línea.
|
||
|
|
|
||
|
|
Decisión recomendada:
|
||
|
|
|
||
|
|
```text
|
||
|
|
SalesDocumentDetail.OriginType = DELIVERY_NOTE
|
||
|
|
SalesDocumentDetail.OriginId = DeliveryNoteDetail.Id
|
||
|
|
```
|
||
|
|
|
||
|
|
No usar `DeliveryNote.Id` como origen principal del detail para este nuevo flujo, porque impide trazabilidad granular por línea.
|
||
|
|
|
||
|
|
Si existen documentos anulados, cancelados o descartados, sus cantidades no deben descontar saldo pendiente. Codex debe revisar los estados reales disponibles antes de implementar el filtro definitivo.
|
||
|
|
|
||
|
|
### 5. API
|
||
|
|
|
||
|
|
Agregar endpoints nuevos sin romper los endpoints existentes.
|
||
|
|
|
||
|
|
Endpoints sugeridos, ajustando rutas y nombres al patrón actual:
|
||
|
|
|
||
|
|
```text
|
||
|
|
GET /api/SalesDocument/delivery-note-item-candidates
|
||
|
|
POST /api/SalesDocument/from-delivery-note-items
|
||
|
|
```
|
||
|
|
|
||
|
|
El endpoint de candidatos debe permitir consultar uno o más remitos y devolver líneas facturables con saldo pendiente.
|
||
|
|
|
||
|
|
El endpoint de creación debe recibir una selección explícita de líneas y cantidades.
|
||
|
|
|
||
|
|
### 6. UI BackOffice
|
||
|
|
|
||
|
|
Actualizar la creación de Sales Document para que el usuario pueda:
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. seleccionar uno o más remitos;
|
||
|
|
2. visualizar los ítems incluidos en esos remitos;
|
||
|
|
3. seleccionar o excluir líneas;
|
||
|
|
4. modificar cantidades facturables;
|
||
|
|
5. ver cantidad entregada, ya facturada y pendiente;
|
||
|
|
6. visualizar importes recalculados;
|
||
|
|
7. generar el Sales Document sólo con las líneas seleccionadas.
|
||
|
|
```
|
||
|
|
|
||
|
|
La UI debe preservar el estilo y patrones ya usados en BackOffice.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Fuera de alcance
|
||
|
|
|
||
|
|
Esta story NO debe implementar ni modificar:
|
||
|
|
|
||
|
|
```text
|
||
|
|
ARCA
|
||
|
|
AFIP
|
||
|
|
IVA
|
||
|
|
Factura A/B/C
|
||
|
|
Notas de crédito
|
||
|
|
Notas de débito
|
||
|
|
CAE
|
||
|
|
Sales Fiscal Document
|
||
|
|
Integración fiscal
|
||
|
|
PDF fiscal
|
||
|
|
Libro IVA
|
||
|
|
Reglas fiscales argentinas
|
||
|
|
```
|
||
|
|
|
||
|
|
Tampoco debe rediseñar por completo el módulo Sales Document ni eliminar contratos existentes si todavía son usados por UI/API.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Decisiones de diseño
|
||
|
|
|
||
|
|
### 1. Modelo conceptual correcto
|
||
|
|
|
||
|
|
La unidad facturable debe ser la línea del remito, no el remito completo.
|
||
|
|
|
||
|
|
Modelo objetivo:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Presupuesto
|
||
|
|
↓
|
||
|
|
Remito
|
||
|
|
↓
|
||
|
|
DeliveryNoteDetail seleccionado
|
||
|
|
↓
|
||
|
|
SalesDocumentDetail
|
||
|
|
↓
|
||
|
|
SalesDocumentCoverage
|
||
|
|
↓
|
||
|
|
SalesFiscalDocument futuro
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Trazabilidad
|
||
|
|
|
||
|
|
Para el nuevo flujo, `SalesDocumentDetail` debe poder trazar el origen granular:
|
||
|
|
|
||
|
|
```text
|
||
|
|
OriginType = DELIVERY_NOTE
|
||
|
|
OriginId = DeliveryNoteDetail.Id
|
||
|
|
```
|
||
|
|
|
||
|
|
El snapshot puede incluir información adicional para auditoría:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"deliveryNoteId": 123,
|
||
|
|
"deliveryNoteDetailId": 456,
|
||
|
|
"deliveryNoteNumber": "...",
|
||
|
|
"originalQuantity": 10,
|
||
|
|
"selectedQuantity": 4
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Control de doble facturación
|
||
|
|
|
||
|
|
No debe depender exclusivamente de `PhSDeliveryNote.SalesinvoiceId`.
|
||
|
|
|
||
|
|
La elegibilidad debe calcularse por línea mediante `SalesDocumentDetails` ya existentes y sus cantidades acumuladas.
|
||
|
|
|
||
|
|
### 4. Coverage
|
||
|
|
|
||
|
|
Coverage debe seguir siendo consistente con lo efectivamente facturado.
|
||
|
|
|
||
|
|
Para facturación parcial, el coverage debe reflejar la línea seleccionada y el importe seleccionado, no el total completo del remito.
|
||
|
|
|
||
|
|
Conceptualmente:
|
||
|
|
|
||
|
|
```text
|
||
|
|
CoverageAmount = importe de la línea seleccionada
|
||
|
|
CoveragePercentage = SelectedQuantity / DeliveredQuantity * 100
|
||
|
|
```
|
||
|
|
|
||
|
|
Ajustar el cálculo según los campos y reglas reales existentes en el repo.
|
||
|
|
|
||
|
|
### 5. Drafts y reserva de saldo
|
||
|
|
|
||
|
|
Decisión recomendada:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Los Drafts deben reservar saldo.
|
||
|
|
```
|
||
|
|
|
||
|
|
Motivo: evita que dos documentos comerciales en draft consuman la misma línea pendiente.
|
||
|
|
|
||
|
|
Si el repo tiene estados anulados/cancelados, esos documentos no deben reservar saldo.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Preguntas de negocio a validar si el código no permite inferirlas
|
||
|
|
|
||
|
|
Codex debe avanzar con la opción conservadora si no hay evidencia en el repo, pero dejar documentada la decisión.
|
||
|
|
|
||
|
|
1. ¿Un Sales Document en Draft debe reservar saldo?
|
||
|
|
- Recomendación: sí.
|
||
|
|
|
||
|
|
2. ¿Se permite facturar parcialmente una línea más de una vez hasta completar el total entregado?
|
||
|
|
- Recomendación: sí.
|
||
|
|
|
||
|
|
3. ¿Se permite facturar una línea sin `QuoteDetailId`?
|
||
|
|
- Recomendación: no, salvo que el flujo actual ya lo permita explícitamente.
|
||
|
|
|
||
|
|
4. ¿`SalesinvoiceId` debe mantenerse sólo como dato legacy/compatibilidad?
|
||
|
|
- Recomendación: sí, no usarlo como criterio principal para facturación parcial.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Criterios de aceptación
|
||
|
|
|
||
|
|
✔ El código compila sin errores.
|
||
|
|
|
||
|
|
✔ No se modifican manualmente modelos EF generados por scaffold.
|
||
|
|
|
||
|
|
✔ La arquitectura Data → Domain → Core → API → UI se respeta.
|
||
|
|
|
||
|
|
✔ El usuario puede consultar candidatos facturables por ítem desde uno o más remitos.
|
||
|
|
|
||
|
|
✔ La respuesta de candidatos muestra cantidad entregada, cantidad ya facturada y cantidad pendiente.
|
||
|
|
|
||
|
|
✔ El usuario puede seleccionar líneas completas o parciales para crear el Sales Document.
|
||
|
|
|
||
|
|
✔ No se permite crear un Sales Document sin líneas seleccionadas.
|
||
|
|
|
||
|
|
✔ No se permite facturar una cantidad mayor al saldo pendiente de una línea.
|
||
|
|
|
||
|
|
✔ No se permite duplicar facturación sobre una misma línea por encima de la cantidad entregada.
|
||
|
|
|
||
|
|
✔ `SalesDocumentDetails` se crean únicamente para los ítems seleccionados.
|
||
|
|
|
||
|
|
✔ `SalesDocumentCoverage` se genera de forma consistente con las líneas efectivamente facturadas.
|
||
|
|
|
||
|
|
✔ El flujo existente de Draft Review & Validation continúa funcionando.
|
||
|
|
|
||
|
|
✔ La UI BackOffice permite seleccionar/excluir ítems y modificar cantidades facturables.
|
||
|
|
|
||
|
|
✔ Los endpoints actuales no se rompen.
|
||
|
|
|
||
|
|
✔ Swagger/API queda usable para el nuevo flujo.
|
||
|
|
|
||
|
|
✔ Se incluyen pruebas manuales básicas documentadas.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Pruebas manuales sugeridas
|
||
|
|
|
||
|
|
### Caso 1 — Facturación total equivalente al flujo anterior
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Seleccionar un remito con 3 ítems.
|
||
|
|
2. Seleccionar los 3 ítems completos.
|
||
|
|
3. Crear Sales Document.
|
||
|
|
4. Verificar que los detalles coincidan con el total del remito.
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 2 — Exclusión de línea completa
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Seleccionar un remito con 3 ítems.
|
||
|
|
2. Seleccionar sólo 2 ítems.
|
||
|
|
3. Crear Sales Document.
|
||
|
|
4. Verificar que el tercer ítem no aparece en SalesDocumentDetails.
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 3 — Cantidad parcial
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Seleccionar una línea con DeliveredQuantity = 10.
|
||
|
|
2. Facturar SelectedQuantity = 4.
|
||
|
|
3. Crear Sales Document.
|
||
|
|
4. Volver a consultar candidatos.
|
||
|
|
5. Verificar PendingQuantity = 6.
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 4 — Evitar doble facturación
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Facturar parcialmente una línea.
|
||
|
|
2. Intentar facturar una cantidad mayor al pendiente.
|
||
|
|
3. Verificar que el Core/API rechaza la operación.
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 5 — Múltiples remitos
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Seleccionar dos remitos del mismo cliente fiscal.
|
||
|
|
2. Seleccionar líneas de ambos.
|
||
|
|
3. Crear Sales Document.
|
||
|
|
4. Verificar trazabilidad por DeliveryNoteDetail.
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 6 — Clientes incompatibles
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Intentar seleccionar remitos de clientes fiscales incompatibles.
|
||
|
|
2. Verificar que el sistema rechaza la creación.
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Entregable esperado
|
||
|
|
|
||
|
|
Codex debe entregar:
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Cambios de código por capas.
|
||
|
|
2. SQL separado si se requieren índices o estructura adicional.
|
||
|
|
3. Patch revisado.
|
||
|
|
4. Checklist de pruebas manuales ejecutadas.
|
||
|
|
5. Confirmación explícita de que no se modificaron modelos EF scaffold manualmente.
|
||
|
|
```
|
||
|
|
|
||
|
|
Archivos/rutas a revisar y posiblemente modificar, ajustando según el repo real:
|
||
|
|
|
||
|
|
```text
|
||
|
|
Domain/Entities/Sales/*
|
||
|
|
Domain/DTOs/Sales/*
|
||
|
|
Core/Interfaces/Sales/*
|
||
|
|
Core/Services/Sales/SalesDocumentService.cs
|
||
|
|
Data/Interfaces/Sales/*
|
||
|
|
Data/Repositories/Sales/*
|
||
|
|
API/Controllers/SalesDocumentController.cs
|
||
|
|
UI/BackOffice/Pages/SalesDocuments/*
|
||
|
|
UI/BackOffice/Services/*
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Branch sugerido
|
||
|
|
|
||
|
|
```text
|
||
|
|
feature/leandro/76-sales-document-item-selection-partial-billing
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Commit sugerido
|
||
|
|
|
||
|
|
```text
|
||
|
|
feat(sales-documents): support delivery note item selection for partial billing close #76
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Instrucciones específicas para Codex
|
||
|
|
|
||
|
|
Antes de escribir código:
|
||
|
|
|
||
|
|
```text
|
||
|
|
1. Analizar el repositorio completo.
|
||
|
|
2. Identificar rutas reales y patrones existentes.
|
||
|
|
3. Confirmar cómo se crean hoy SalesDocumentDetails.
|
||
|
|
4. Confirmar cómo se relacionan con Coverage.
|
||
|
|
5. Confirmar cómo se usa SalesinvoiceId.
|
||
|
|
6. Proponer el plan de cambios por capas.
|
||
|
|
7. Luego implementar en pasos pequeños.
|
||
|
|
```
|
||
|
|
|
||
|
|
Reglas obligatorias:
|
||
|
|
|
||
|
|
```text
|
||
|
|
- No modificar modelos EF generados por scaffold.
|
||
|
|
- No romper contratos existentes si todavía son consumidos.
|
||
|
|
- No rediseñar todo el módulo.
|
||
|
|
- Mantener separación Ph* / E*.
|
||
|
|
- Respetar nombres, convenciones y patrones existentes.
|
||
|
|
- Mantener el cambio acotado a facturación parcial comercial.
|
||
|
|
```
|