- 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.
13 KiB
Customer Selection y Quick Create
Objetivo
modules/customers/src/common/features es la superficie pública del módulo customers para colaboración entre módulos frontend del ERP.
Estas features permiten que un consumidor:
- seleccione un cliente existente;
- cree un cliente de alta rápida;
- reciba siempre un contrato público homogéneo para aplicar el resultado en su propio formulario.
La dependencia deseada es:
consumer module
│
▼
@erp/customers o @erp/customers/common
│
▼
customers/common/features
│
├── customer-selection
└── quick-create-customer
Un consumidor no debe resolver estos flujos importando implementación interna desde:
modules/customers/src/web/createmodules/customers/src/web/updatemodules/customers/src/web/shared
Esos directorios siguen siendo responsabilidad interna del módulo customers.
Features públicas
customer-selection
Resuelve el problema de buscar y seleccionar un cliente desde otro módulo, exponiendo un flujo reutilizable con diálogo de selección y un contrato público estable para devolver el cliente elegido.
API pública real exportada por modules/customers/src/common/features/customer-selection/index.ts:
useCustomerSelectionFlowCustomerSelectionCriteriaCustomerSelectionOptionSelectCustomerDialogbuildCustomerSelectionOption
Responsabilidades principales:
- búsqueda y paginación de clientes;
- apertura y cierre del selector;
- transformación del resultado de listado al contrato público
CustomerSelectionOption; - coordinación con alta rápida cuando el consumidor usa el flow público;
- entrega del cliente seleccionado al callback del consumidor.
No pertenece a esta feature:
- construir
CreateCustomerParams; - conocer el formulario completo de create/update de clientes;
- invalidar cachés externas del consumidor;
- decidir qué hace el consumidor con el cliente ya seleccionado.
quick-create-customer
Resuelve el problema de crear un cliente mínimo y embebible sin exponer al consumidor los detalles internos del CRUD de customers.
API pública real exportada por modules/customers/src/common/features/quick-create-customer/index.ts:
useQuickCreateCustomerFlowQuickCustomerCreateResultQuickCreateCustomerDialog
Responsabilidades principales:
- formulario reducido de alta rápida;
- validación del formulario;
- apertura y cierre del diálogo;
- creación mediante la mutation propia de
customers; - adaptación del resultado de creación a
CustomerSelectionOption; - gestión de toasts, errores y estado del diálogo;
- aplicación de defaults internos y contexto opcional del consumidor.
No pertenece a esta feature:
- que el consumidor construya
CreateCustomerParams; - que el consumidor importe
useCustomerCreateMutation; - que el consumidor gestione manualmente la caché de
customers.
Contrato público: CustomerSelectionOption
CustomerSelectionOption representa la proyección pública que customers entrega a otros módulos cuando el resultado del flujo es “ya tienes un cliente seleccionable y aplicable”.
No es la entity completa del CRUD de customers. Es un shape orientado a integración y UI.
Campos reales actuales:
idstatusisCompanynametradeNametinstreetstreet2cityprovincepostalCodecountryprimaryEmailprimaryPhoneprimaryMobile
Su objetivo es permitir que el consumidor:
- persista el identificador funcional (
customerId); - muestre un resumen útil del cliente;
- trate de la misma forma un cliente seleccionado y uno recién creado.
Convergencia entre selección y creación
La convergencia es intencional.
modules/customers/src/common/features/quick-create-customer/entities/quick-customer-create-result.entity.ts define:
type QuickCustomerCreateResult = CustomerSelectionOption;
Eso significa que, para el consumidor, no hay dos contratos distintos:
seleccionar cliente existente ───────┐
├──► CustomerSelectionOption
crear cliente rápidamente ───────────┘
Una vez terminada la operación, el consumidor puede usar el mismo callback para ambos casos. En el flow público actual, ese callback es onCustomerSelected(customer).
useCustomerSelectionFlow
useCustomerSelectionFlow es el punto de composición pública recomendado cuando una pantalla necesita:
- seleccionar un cliente existente;
- abrir alta rápida desde el selector;
- abrir alta rápida directamente desde la pantalla;
- recibir siempre un
CustomerSelectionOption.
Parámetros públicos actuales:
onCustomerSelected(customer: CustomerSelectionOption)defaultLanguageCode?defaultCurrencyCode?
Estado y artefactos que coordina:
selectCtrlpara el selector;createCtrlpara el diálogo de alta rápida;SelectCustomerDialog;QuickCreateCustomerDialog;- entrypoints de apertura para creación rápida en distintos contextos.
El consumidor no debería reproducir manualmente esta coordinación si ya consume este flow.
Dos entrypoints de creación rápida
La implementación actual diferencia dos contextos y su semántica importa.
openQuickCreateDialog
Uso típico:
- botón
Nuevo clientede una pantalla consumidora.
Comportamiento:
abre creación rápida directamente
→ cancelar
→ simplemente se cierra el diálogo
No intenta reabrir el selector porque no presupone que el usuario viniera de ahí.
openQuickCreateFromSelection
Uso típico:
- botón
Nuevo clientedentro deSelectCustomerDialog.
Comportamiento:
cierra selector
→ abre creación rápida
→ cancelar
→ vuelve a abrir selector
Esta diferencia evita que el consumidor tenga que gestionar manualmente el retorno al selector.
Diagrama del flujo
useCustomerSelectionFlow
│
├── selectCtrl.openDialog()
│ │
│ ▼
│ SelectCustomerDialog
│ │
│ ├── confirmar selección
│ │ │
│ │ ▼
│ │ onCustomerSelected(CustomerSelectionOption)
│ │
│ └── Nuevo cliente
│ │
│ ▼
│ openQuickCreateFromSelection()
│ │
│ ▼
│ QuickCreateCustomerDialog
│ │
│ ├── cancelar
│ │ │
│ │ ▼
│ │ reabrir selector
│ │
│ └── crear
│ │
│ ▼
│ onCustomerSelected(CustomerSelectionOption)
│
└── openQuickCreateDialog()
│
▼
QuickCreateCustomerDialog
│
├── cancelar
│ │
│ ▼
│ cerrar diálogo
│
└── crear
│
▼
onCustomerSelected(CustomerSelectionOption)
Detalle de quick-create-customer
Default de plazo de pago
La alta rápida no necesita pedir el plazo de pago al usuario.
Si no envía un valor explícito, el backend aplica la misma regla que en el alta completa:
Company.defaultPaymentTermId
No debe haber diferencias entre:
- create completo;
- quick create;
- API directa.
Fuente funcional principal:
Formulario reducido
La alta rápida no intenta sustituir al CRUD completo de customers.
El schema público de formulario rápido actual (QuickCustomerCreateFormSchema) expone solo:
isCompanynametinprimaryEmail
Defaults internos y contexto opcional
La feature reutiliza buildCustomerCreateDefault() del área interna de create y resuelve por defecto:
country = ESlanguageCode = escurrencyCode = EUR
Además, useQuickCreateCustomerFlow y useQuickCreateCustomerDialogController admiten contexto opcional:
defaultCountry?defaultLanguageCode?defaultCurrencyCode?
Regla conceptual:
consumidor
→ aporta contexto opcional
customers
→ sigue construyendo, validando y ejecutando la creación
El consumidor no debe construir CreateCustomerParams.
Validación, errores y toasts
La responsabilidad de validación y feedback está dentro de quick-create-customer:
QuickCustomerCreateFormSchemavalida el formulario;useQuickCreateCustomerDialogControlleraplica errores de validación al form;- la feature muestra
showSuccessToast,showWarningToastyshowErrorToast; - el diálogo mantiene
submitErrory el estadoisSubmitting.
Por tanto, un consumidor normal no debería duplicar:
try/catchdel submit;- toasts de éxito/error de creación;
setErrorsobre el formulario rápido.
Caché
La creación usa useCustomerCreateMutation() desde el módulo customers.
La sincronización de React Query y su estrategia de caché siguen siendo responsabilidad de customers. El consumidor no debe invalidar manualmente queries de customers tras una creación rápida salvo un caso excepcional y explícitamente justificado.
Ejemplo real: proformas/update
Caso real actual:
modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-page-controller.tsmodules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.tsmodules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx
Integración conceptual actual:
useCustomerSelectionFlow(...)
│
├── seleccionar cliente existente
└── crear cliente rápido
│
▼
onCustomerSelected(customer)
│
▼
updateCtrl.setCustomer(customer)
│
▼
form.setValue("customerId", customer.id)
En proformas/update:
- el page controller compone
useCustomerSelectionFlow; - la pantalla usa
selectCustomerCtrl.openQuickCreateDialogpara el botón principalNuevo cliente; SelectCustomerDialogusaselectCustomerCtrl.openQuickCreateFromSelectionparaNuevo clientedentro del selector;- ambos caminos convergen en
updateCtrl.setCustomer(customer).
Rehidratación y snapshots externos
CustomerSelectionOption es el contrato ideal que customers entrega a consumidores.
Eso no implica que cualquier otro recurso del ERP tenga que persistir exactamente el mismo shape. Por ejemplo, una proforma puede contener un snapshot suficiente para representar a su cliente, pero no necesariamente todos los campos disponibles en CustomerSelectionOption.
La integración correcta consiste en:
- usar
CustomerSelectionOptioncuando el dato proviene del flujo público decustomers; - permitir que recursos externos reconstruyan su propio resumen cuando trabajan con snapshots parciales.
Qué debe importar un consumidor
Imports recomendados:
import { useCustomerSelectionFlow } from "@erp/customers/common";
import type { CustomerSelectionOption } from "@erp/customers";
import { QuickCreateCustomerDialog, SelectCustomerDialog } from "@erp/customers";
La ruta concreta puede variar según el barrel elegido, pero debe salir de la superficie pública exportada por @erp/customers o @erp/customers/common.
Qué no debe importar un consumidor
No debe depender directamente de piezas internas del CRUD como:
CreateCustomerParamsCustomerCreateFormuseCustomerCreateMutationbuildCreateCustomerParams- componentes o hooks desde
modules/customers/src/web/create/* - detalles internos de
modules/customers/src/web/shared/*
Respuestas rápidas de contrato
customer-selectionresuelve búsqueda, selección y entrega homogénea de cliente.quick-create-customerresuelve alta mínima embebible sin exponer el CRUD interno.- La API pública real está definida por los barrels de cada feature, no por sus internals.
- El consumidor debe importar desde
@erp/customerso@erp/customers/common. - No debe importar internals de
web/create,web/updateoweb/shared. CustomerSelectionOptiones la proyección pública reutilizable de cliente seleccionado.QuickCustomerCreateResultconverge en el mismo contrato para no bifurcar la integración.openQuickCreateDialoges apertura directa;openQuickCreateFromSelectionpreserva el retorno al selector.- La cancelación y la reapertura del selector las gestiona el flow de
customers. - La creación, validación, errores y caché pertenecen a
customers. - El consumidor integra el resultado aplicando
CustomerSelectionOptiona su propio caso de uso. proformas/updatelo hace actualmente conupdateCtrl.setCustomer(customer).