FactuGES_ERP/docs/catalogs/payment-terms.md

495 lines
10 KiB
Markdown
Raw Normal View History

feat: add proforma customer change evaluation utility - Implemented `evaluateProformaCustomerChange` function to assess changes in customer details for proforma invoices. - Introduced types for customer change details, blockers, field differences, and fiscal targets. - Added utility functions for normalizing text and formatting recipient information. test: add unit tests for customer payment resolver - Created tests for `CustomerPaymentResolver` to validate payment term retrieval and handling of inactive terms. - Ensured proper behavior when clearing payment terms and handling non-existent terms. test: add unit tests for customer default payment term resolver - Developed tests for `CustomerDefaultPaymentTermResolver` to verify default payment term resolution logic. - Included scenarios for creating customers with and without explicit payment term IDs. feat: implement customer default payment term resolver - Created `CustomerDefaultPaymentTermResolver` to manage default payment term resolution during customer creation. - Integrated with company finder and payment resolver services. test: add unit tests for create customer use case - Implemented tests for `CreateCustomerUseCase` to ensure correct handling of payment term IDs during customer creation. - Validated behavior when no explicit payment term ID is provided. test: add unit tests for update customer use case - Developed tests for `UpdateCustomerUseCase` to verify payment term handling during customer updates. - Ensured that inactive payment terms are not assigned and that explicit null values are respected. feat: add dependency injection for customer company services - Implemented DI for customer company dependencies to facilitate service resolution. test: add tests for customer create data utility - Created tests for `buildCustomerCreateData` to validate payment term handling based on user input.
2026-08-17 09:25:34 +00:00
# Plazos de pago
## Objetivo
Este documento resume cómo funciona `Payment Terms` en el ERP y qué reglas debe respetar cada módulo.
La cadena funcional final es:
```text
Company.defaultPaymentTermId
↓ valor inicial
Customer.paymentTermId
↓ valor inicial
Proforma.paymentTermId
↓ snapshot al emitir
IssuedInvoice.paymentTerm
```
## Modelo general
`PaymentTerm` define una plantilla de vencimientos.
Sus `dueRules` describen la distribución teórica del plazo, por ejemplo:
- 100% a 30 días
- 50% contado y 50% a 30 días
Estas reglas no crean todavía vencimientos reales, cuotas monetarias ni estado de cobro. Eso queda fuera de alcance hasta un futuro módulo de pagos y cobros.
## Referencia viva y snapshot
| Entidad | Modelo |
|---|---|
| Company | referencia viva |
| Customer | referencia viva |
| Proforma | referencia viva |
| Issued Invoice | snapshot histórico |
La diferencia es simple:
- Company, Customer y Proforma siguen usando configuración viva.
- Issued Invoice debe conservar exactamente lo que se emitió.
## Empresa
`Company` tiene:
```ts
defaultPaymentTermId: string | null
```
Es una referencia viva al catálogo de `Payment Terms`.
Reglas:
- puede ser `null`;
- puede apuntar a un `PaymentTerm` activo de la misma empresa;
- no puede apuntar a otra empresa;
- `isSystem` está permitido.
Cambiar el default de Company:
- no modifica Customers existentes;
- no modifica Proformas existentes.
Solo afecta a futuros valores iniciales.
### Desactivación del default
Si un `PaymentTerm` está configurado como `Company.defaultPaymentTermId`, no puede desactivarse.
Antes hay que:
- cambiar el default;
- o limpiarlo.
Esto evita que la empresa conserve un default que ya no podría reutilizar en nuevas asignaciones.
## Clientes
`Customer` tiene:
```ts
paymentTermId: string | null
```
Es una referencia viva propia.
### Create
Semántica:
- campo omitido o `undefined` -> aplicar `Company.defaultPaymentTermId`;
- `null` explícito -> crear el Customer sin plazo;
- `id` -> usar ese `PaymentTerm` si existe, pertenece a la misma empresa y está activo.
Una vez creado el Customer, su `paymentTermId` pasa a ser propio.
Cambiar después el default de Company no modifica ese Customer.
### Update
Semántica:
- `undefined` -> no cambiar;
- `null` -> limpiar;
- `id` -> asignar si es activo y de la misma empresa.
### Inactivos
Si un `PaymentTerm` ya asignado al Customer se desactiva después:
- el Customer puede conservarlo;
- no puede volver a seleccionarse como nueva asignación.
### Quick create
El quick create no necesita pedir plazo de pago al usuario.
Si no envía un valor explícito, el backend aplica la misma regla que en el alta completa:
```text
Company.defaultPaymentTermId
```
No debe haber reglas distintas entre:
- create completo;
- quick create;
- API directa.
## Proformas
`Proforma.paymentTermId` es una referencia viva.
No es un snapshot.
Mientras la Proforma no se haya emitido, cualquier cambio posterior en la definición del catálogo afecta a la lectura actual de ese plazo.
### Create
Prioridad de valor inicial:
1. `Customer.paymentTermId`
2. `Company.defaultPaymentTermId`
3. `null`
Reglas:
- si el usuario selecciona manualmente otro plazo, ese valor prevalece;
- `null` explícito significa “sin plazo de pago”;
- una selección manual de `null` no debe reactivar después un default automático.
### Update
Semántica:
- `undefined` -> no cambiar;
- `null` -> limpiar;
- `id` -> nueva asignación válida.
Si la Proforma ya tenía un `PaymentTerm` que luego quedó inactivo:
- puede conservarlo;
- puede emitirse;
- no pueden elegirse nuevos inactivos.
## Cambio de Customer en una Proforma
Cambiar el Customer de una Proforma existente siempre requiere confirmación.
No basta con cambiar `customerId`. Antes hay que cargar el Customer completo y evaluar compatibilidad.
### Moneda
Si:
```text
newCustomer.currencyCode !== proforma.currencyCode
```
el cambio se bloquea.
No se convierte moneda ni se reinterpretan importes.
### Idioma
Si:
```text
newCustomer.languageCode !== proforma.languageCode
```
el cambio también se bloquea.
Idioma y moneda se consideran características estructurales del documento.
### Estados del modal
Incompatible:
- no puede confirmarse;
- debe informarse del motivo.
Compatible:
- informa qué condiciones se adaptarán;
- informa qué valores se conservarán.
### Plazo de pago al cambiar Customer
Política:
```text
REFRESH_IF_NOT_OVERRIDDEN
```
Si el usuario no lo modificó manualmente:
```text
newCustomer.paymentTermId
-> Company.defaultPaymentTermId
-> null
```
Si lo modificó manualmente:
- se conserva el valor actual.
### Forma de pago al cambiar Customer
La forma de pago usa la misma política:
```text
REFRESH_IF_NOT_OVERRIDDEN
```
No se añade un default de Company para `PaymentMethod` porque esa regla no existe en este flujo.
### Dirty y override manual
Hay dos conceptos distintos:
- cambio pendiente de guardar;
- override manual.
Ejemplo:
- Customer A -> plazo 30 días
- cambio a Customer B -> se adapta automáticamente a 60 días
Ese cambio:
- debe guardarse;
- no cuenta como override manual.
Si luego cambia a Customer C, puede volver a adaptarse.
En cambio, si el usuario selecciona manualmente “Contado”:
- sí existe override manual;
- futuros cambios de Customer no deben sobrescribir ese valor.
La misma explicación aplica a:
- `paymentTermId`;
- `paymentMethodId`.
### Fiscalidad
La fiscalidad no se cambia silenciosamente.
Si el nuevo Customer tiene una configuración fiscal distinta:
- el modal debe advertirlo;
- el usuario debe confirmarlo;
- al confirmar, se adapta la configuración fiscal;
- se recalculan impuestos y totales con la infraestructura fiscal existente.
La existencia de líneas no bloquea por sí sola el cambio.
### Recipient
Si el cambio de Customer se confirma, el `recipient` snapshot de la Proforma siempre se refresca con el nuevo Customer.
Nunca debe quedar un documento con:
```text
customerId = Customer B
recipient = Customer A
```
El preview y el PDF usan ese snapshot documental.
## Duplicación de Proformas
Duplicar una Proforma crea un documento nuevo. Por eso no puede reutilizar una nueva asignación inactiva solo porque el original la conservaba.
### Original con Payment Term activo
- se copia el mismo `paymentTermId`.
### Original con Payment Term inactivo
No se copia como nueva asignación.
Se resuelve así:
1. `Customer.paymentTermId`, solo si está activo;
2. `Company.defaultPaymentTermId`, solo si está activo;
3. `null`.
Reglas adicionales:
- si el Customer tiene un término inactivo, se ignora;
- si la Company tiene por datos legacy un default inactivo, no se usa;
- la duplicación no falla por esa inconsistencia legacy.
### Feedback de duplicación
Si el plazo original estaba inactivo y hubo que sustituirlo o eliminarlo, el usuario recibe feedback después de duplicar.
La respuesta expone metadata funcional equivalente a:
```yaml
payment_term_adaptation:
null
o
{
source: "inactive_original",
resolution: "customer" | "company" | "none"
}
```
La intención de esa metadata es explicar si el sistema:
- conservó el mismo plazo;
- usó el del Customer;
- usó el default de Company;
- o creó la nueva Proforma sin plazo.
## Facturas emitidas
Issued Invoice no mantiene una referencia viva para interpretar el plazo.
Al emitir una Proforma, el sistema guarda un snapshot histórico.
Shape conceptual:
```ts
paymentTerm = {
sourceId,
name,
description,
dueRules
}
```
### Qué se conserva
- `payment_term_id` para trazabilidad;
- `payment_term_name` como snapshot;
- `payment_term_description` como snapshot;
- `payment_term_snapshot_json` con las reglas congeladas.
Los porcentajes conservan precisión escalada. No se convierten a `float` para persistir el histórico.
### Inmutabilidad histórica
Ejemplo:
- una factura se emite con “30 días” y 100% a 30 días;
- después el catálogo cambia a “30 / 60 días” con 50% / 50%.
La factura emitida sigue mostrando:
- “30 días”;
- 100% a 30 días.
El detail y el reporting de Issued Invoice no consultan el catálogo vivo para reconstruir ese plazo.
### Payment Term inactivo en emisión
Si una Proforma ya tenía asignado un `PaymentTerm` y después se desactiva:
- la Proforma puede emitirse.
La inactividad impide nuevas asignaciones. No invalida una referencia ya existente en la Proforma.
### Referencia perdida en emisión
Si la Proforma tiene `paymentTermId` pero al emitir no puede resolverse dentro de la misma empresa:
- la emisión falla.
No se crea una factura sin snapshot de forma silenciosa y tampoco se usa un fallback nuevo desde Customer o Company.
## Activos e inactivos
| Contexto | Puede conservar inactivo | Puede asignar inactivo |
|---|---:|---:|
| Company default | NO | NO |
| Customer | SÍ | NO |
| Proforma | SÍ | NO |
| Issued Invoice | snapshot histórico | N/A |
Nota:
`Company.defaultPaymentTermId` no puede quedar inactivo porque la desactivación se bloquea.
## Aislamiento por empresa
Toda nueva asignación o resolución debe quedar limitada a la empresa activa.
Aplica a:
- Company default;
- Customer create;
- Customer update;
- Proforma create;
- Proforma update;
- emisión;
- duplicación.
## isSystem
`isSystem` afecta a la gestión del catálogo.
No impide usar un `PaymentTerm` en:
- Company default;
- Customer;
- Proforma;
- emisión
si el término es válido para esa operación.
## Reporting
En reporting:
- Proforma usa el `PaymentTerm` vivo;
- Issued Invoice usa el snapshot histórico.
Además, para seleccionar templates FastReport:
```text
es-ES -> es
```
## Qué no está incluido todavía
Este feature no implementa todavía:
- `dueDate` real;
- calendario de cobros;
- cuotas monetarias;
- estado pagado o pendiente;
- pagos parciales;
- conciliación.
No debe confundirse `dueRules` de `PaymentTerm` con vencimientos reales de una factura.
## Ejemplos funcionales
### Ejemplo 1: Customer sin plazo
- Company default = 30 días
- Customer create no envía `paymentTermId`
Resultado:
- el Customer nace con 30 días.
### Ejemplo 2: Customer sin plazo explícito
- Company default = 30 días
- Customer create envía `paymentTermId = null`
Resultado:
- el Customer nace sin plazo.
### Ejemplo 3: Proforma con override manual
- Customer A = 30 días
- cambio a Customer B = 60 días
- el sistema adapta automáticamente a 60 días
- el usuario elige manualmente “Contado”
- cambio a Customer C
Resultado:
- la Proforma conserva “Contado”.