- Changed references from Country to CountryCode in supplier input mappers, audit services, and snapshot builders. - Updated tests to reflect the changes in country handling. - Refactored postal address to utilize CountryCode instead of Country. - Added new company address mappers and tests to accommodate the updated structure. - Ensured all related schemas and DTOs are aligned with the new country code implementation.
413 lines
13 KiB
Markdown
413 lines
13 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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/create`
|
|
- `modules/customers/src/web/update`
|
|
- `modules/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`:
|
|
|
|
- `useCustomerSelectionFlow`
|
|
- `CustomerSelectionCriteria`
|
|
- `CustomerSelectionOption`
|
|
- `SelectCustomerDialog`
|
|
- `buildCustomerSelectionOption`
|
|
|
|
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`:
|
|
|
|
- `useQuickCreateCustomerFlow`
|
|
- `QuickCustomerCreateResult`
|
|
- `QuickCreateCustomerDialog`
|
|
|
|
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:
|
|
|
|
- `id`
|
|
- `status`
|
|
- `isCompany`
|
|
- `name`
|
|
- `tradeName`
|
|
- `tin`
|
|
- `street`
|
|
- `street2`
|
|
- `city`
|
|
- `province`
|
|
- `postalCode`
|
|
- `countryCode`
|
|
- `primaryEmail`
|
|
- `primaryPhone`
|
|
- `primaryMobile`
|
|
|
|
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:
|
|
|
|
```ts
|
|
type QuickCustomerCreateResult = CustomerSelectionOption;
|
|
```
|
|
|
|
Eso significa que, para el consumidor, no hay dos contratos distintos:
|
|
|
|
```text
|
|
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:
|
|
|
|
- `selectCtrl` para el selector;
|
|
- `createCtrl` para 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 cliente` de una pantalla consumidora.
|
|
|
|
Comportamiento:
|
|
|
|
```text
|
|
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 cliente` dentro de `SelectCustomerDialog`.
|
|
|
|
Comportamiento:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
Company.defaultPaymentTermId
|
|
```
|
|
|
|
No debe haber diferencias entre:
|
|
|
|
- create completo;
|
|
- quick create;
|
|
- API directa.
|
|
|
|
Fuente funcional principal:
|
|
|
|
- [Plazos de pago](../catalogs/payment-terms.md)
|
|
|
|
### 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:
|
|
|
|
- `isCompany`
|
|
- `name`
|
|
- `tin`
|
|
- `primaryEmail`
|
|
|
|
### Defaults internos y contexto opcional
|
|
|
|
La feature reutiliza `buildCustomerCreateDefault()` del área interna de create y resuelve por defecto:
|
|
|
|
- `countryCode = ES`
|
|
- `languageCode = es`
|
|
- `currencyCode = EUR`
|
|
|
|
Además, `useQuickCreateCustomerFlow` y `useQuickCreateCustomerDialogController` admiten contexto opcional:
|
|
|
|
- `defaultCountryCode?`
|
|
- `defaultLanguageCode?`
|
|
- `defaultCurrencyCode?`
|
|
|
|
Regla conceptual:
|
|
|
|
```text
|
|
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`:
|
|
|
|
- `QuickCustomerCreateFormSchema` valida el formulario;
|
|
- `useQuickCreateCustomerDialogController` aplica errores de validación al form;
|
|
- la feature muestra `showSuccessToast`, `showWarningToast` y `showErrorToast`;
|
|
- el diálogo mantiene `submitError` y el estado `isSubmitting`.
|
|
|
|
Por tanto, un consumidor normal no debería duplicar:
|
|
|
|
- `try/catch` del submit;
|
|
- toasts de éxito/error de creación;
|
|
- `setError` sobre 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.ts`
|
|
- `modules/customer-invoices/src/web/proformas/update/controllers/use-update-proforma-controller.ts`
|
|
- `modules/customer-invoices/src/web/proformas/update/ui/pages/proforma-update-page.tsx`
|
|
|
|
Integración conceptual actual:
|
|
|
|
```text
|
|
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.openQuickCreateDialog` para el botón principal `Nuevo cliente`;
|
|
- `SelectCustomerDialog` usa `selectCustomerCtrl.openQuickCreateFromSelection` para `Nuevo cliente` dentro 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 `CustomerSelectionOption` cuando el dato proviene del flujo público de `customers`;
|
|
- permitir que recursos externos reconstruyan su propio resumen cuando trabajan con snapshots parciales.
|
|
|
|
## Qué debe importar un consumidor
|
|
|
|
Imports recomendados:
|
|
|
|
```ts
|
|
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:
|
|
|
|
- `CreateCustomerParams`
|
|
- `CustomerCreateForm`
|
|
- `useCustomerCreateMutation`
|
|
- `buildCreateCustomerParams`
|
|
- componentes o hooks desde `modules/customers/src/web/create/*`
|
|
- detalles internos de `modules/customers/src/web/shared/*`
|
|
|
|
## Respuestas rápidas de contrato
|
|
|
|
1. `customer-selection` resuelve búsqueda, selección y entrega homogénea de cliente.
|
|
2. `quick-create-customer` resuelve alta mínima embebible sin exponer el CRUD interno.
|
|
3. La API pública real está definida por los barrels de cada feature, no por sus internals.
|
|
4. El consumidor debe importar desde `@erp/customers` o `@erp/customers/common`.
|
|
5. No debe importar internals de `web/create`, `web/update` o `web/shared`.
|
|
6. `CustomerSelectionOption` es la proyección pública reutilizable de cliente seleccionado.
|
|
7. `QuickCustomerCreateResult` converge en el mismo contrato para no bifurcar la integración.
|
|
8. `openQuickCreateDialog` es apertura directa; `openQuickCreateFromSelection` preserva el retorno al selector.
|
|
9. La cancelación y la reapertura del selector las gestiona el flow de `customers`.
|
|
10. La creación, validación, errores y caché pertenecen a `customers`.
|
|
11. El consumidor integra el resultado aplicando `CustomerSelectionOption` a su propio caso de uso.
|
|
12. `proformas/update` lo hace actualmente con `updateCtrl.setCustomer(customer)`.
|