- Add UpdateCurrentCompanyUseCase to handle company updates. - Create update-current-company.controller.ts to manage update requests. - Implement validation and mapping for update requests using Zod. - Add tests for the update use case and controller to ensure functionality. - Introduce error handling and feedback state management for company details settings. - Create utility functions for error normalization and feedback state resolution. - Implement tests for error handling and feedback state utilities. - Ensure nullable fields are handled correctly in DTOs and forms.
297 lines
5.2 KiB
Markdown
297 lines
5.2 KiB
Markdown
# `modules/companies`
|
|
|
|
`companies` es el módulo independiente y fuente de verdad de la ficha operativa de empresa.
|
|
|
|
## Responsabilidades
|
|
|
|
Gestiona la entidad `Company` funcional del ERP:
|
|
|
|
- datos legales
|
|
- datos operativos básicos
|
|
- `defaultPaymentTermId`
|
|
- `slug`
|
|
- `status`
|
|
|
|
Separación arquitectónica:
|
|
|
|
- `modules/identity`: autenticación, cuentas, memberships, acceso account-company
|
|
- `modules/companies`: ficha real de empresa y validación de company activa
|
|
|
|
## Tabla `companies`
|
|
|
|
Campos actuales:
|
|
|
|
```txt
|
|
id
|
|
legal_name
|
|
trade_name
|
|
tin
|
|
slug
|
|
email
|
|
phone
|
|
website
|
|
default_payment_term_id
|
|
status
|
|
created_at
|
|
updated_at
|
|
```
|
|
|
|
Índices esperados:
|
|
|
|
```txt
|
|
UNIQUE(slug)
|
|
INDEX(status)
|
|
INDEX(tin)
|
|
```
|
|
|
|
## Servicios públicos
|
|
|
|
El módulo expone:
|
|
|
|
```txt
|
|
companies:general
|
|
```
|
|
|
|
Responsabilidad:
|
|
|
|
- validar existencia y estado activo de la company
|
|
- devolver datos públicos/operativos de empresa
|
|
|
|
No debe asumir:
|
|
|
|
- memberships
|
|
- autenticación por cuenta
|
|
- roles/permisos de identity
|
|
|
|
## Dependencias
|
|
|
|
`modules/companies` depende de `identity` y `catalogs`.
|
|
|
|
Uso actual:
|
|
|
|
- sus endpoints usan `requireIdentityAuthenticated(params)`
|
|
- `GET /companies/available` no usa `requireIdentityTenant(params)`
|
|
- `GET /companies/current` y `PATCH /companies/current` usan auth de `identity` + `requireCompanyContext(...)` cableado con `companyAccessChecker` de `identity` y `publicFinder` propio de `companies`
|
|
- los endpoints legacy por `:company_id` siguen tenant-scoped y comparan `req.user.companyId` con `:company_id`
|
|
|
|
## Endpoints relevantes
|
|
|
|
```http
|
|
POST /companies
|
|
GET /companies/available
|
|
GET /companies/current
|
|
PATCH /companies/current
|
|
GET /companies/:company_id
|
|
PATCH /companies/:company_id
|
|
POST /companies/:company_id/enable
|
|
POST /companies/:company_id/disable
|
|
```
|
|
|
|
### Settings de empresa activa
|
|
|
|
`/settings/company/details` consume:
|
|
|
|
```http
|
|
GET /companies/current
|
|
PATCH /companies/current
|
|
```
|
|
|
|
Resolución real del target:
|
|
|
|
```txt
|
|
Authorization
|
|
+ X-Company-Id
|
|
+ requireCompanyContext(...)
|
|
=> req.user.companyId
|
|
```
|
|
|
|
El cliente no selecciona la empresa a editar mediante `companyId` en URL, body ni query.
|
|
|
|
## Política V1 de autorización
|
|
|
|
En la V1 cualquier miembro activo de la compañía puede editar sus datos generales mediante:
|
|
|
|
```http
|
|
PATCH /companies/current
|
|
```
|
|
|
|
Política real:
|
|
|
|
```txt
|
|
usuario autenticado
|
|
+ X-Company-Id válido
|
|
+ membership activa en esa company
|
|
+ company activa
|
|
```
|
|
|
|
No existe todavía enforcement real de RBAC ni permisos granulares en runtime.
|
|
|
|
`identity:companies:update` puede existir como código futuro de catálogo, pero no representa autorización aplicada hoy.
|
|
|
|
## Campos editables actuales
|
|
|
|
`PATCH /companies/current` admite solo:
|
|
|
|
```txt
|
|
legal_name
|
|
trade_name
|
|
tin
|
|
email
|
|
phone
|
|
website
|
|
default_payment_term_id
|
|
```
|
|
|
|
En frontend, la feature:
|
|
|
|
```txt
|
|
/settings/company/details
|
|
```
|
|
|
|
modela esos mismos campos como ficha editable de la company activa.
|
|
|
|
La UI actual agrupa la edición en tres secciones:
|
|
|
|
```txt
|
|
Datos generales
|
|
→ legalName
|
|
→ tradeName
|
|
→ tin
|
|
|
|
Datos de contacto
|
|
→ email
|
|
→ phone
|
|
→ website
|
|
|
|
Condiciones comerciales
|
|
→ defaultPaymentTermId
|
|
```
|
|
|
|
Semántica PATCH esperada en la pantalla:
|
|
|
|
```txt
|
|
propiedad omitida -> conservar valor actual
|
|
propiedad presente con valor -> actualizar
|
|
propiedad nullable presente con null -> limpiar
|
|
```
|
|
|
|
Flujo de guardado esperado en frontend:
|
|
|
|
```txt
|
|
GET /companies/current
|
|
→ hydrata formulario
|
|
|
|
editar campos dirty
|
|
→ construir PATCH parcial
|
|
|
|
PATCH /companies/current
|
|
→ respuesta servidor como SSOT
|
|
→ updateActiveCompany(...)
|
|
→ reset del formulario con la respuesta
|
|
```
|
|
|
|
Política de errores frontend en esta pantalla:
|
|
|
|
```txt
|
|
fallo inicial sin datos utilizables
|
|
→ PageErrorState
|
|
|
|
error de refetch con datos previos
|
|
→ mantener formulario
|
|
→ ErrorAlert inline
|
|
|
|
cancelled
|
|
→ sin pantalla bloqueante
|
|
|
|
422 con paths mapeables
|
|
→ errores de campo en RHF
|
|
|
|
error general de guardado
|
|
→ toast
|
|
→ sin reset del formulario
|
|
```
|
|
|
|
La UI no debe seleccionar el target mediante `companyId`.
|
|
|
|
Fuera de alcance de esta API:
|
|
|
|
```txt
|
|
slug
|
|
status
|
|
fiscalidad avanzada
|
|
VeriFactu
|
|
document series
|
|
payment methods
|
|
branding
|
|
bank accounts
|
|
defaultPaymentMethodId
|
|
```
|
|
|
|
## `defaultPaymentTermId`
|
|
|
|
Contrato actual:
|
|
|
|
```txt
|
|
omitido -> mantener
|
|
null -> desvincular
|
|
UUID -> validar dentro de la company activa
|
|
```
|
|
|
|
La referencia es:
|
|
|
|
```txt
|
|
nullable
|
|
company-scoped
|
|
validada mediante catalogs
|
|
```
|
|
|
|
Semántica HTTP actual:
|
|
|
|
```txt
|
|
UUID activo de la misma company -> 200
|
|
UUID de otra company -> 422
|
|
UUID inactivo -> 422
|
|
```
|
|
|
|
El error de asignación es deliberadamente opaco:
|
|
|
|
```txt
|
|
Default payment term is not assignable to the active company.
|
|
```
|
|
|
|
No expone si el problema concreto es:
|
|
|
|
- inexistente
|
|
- cross-company
|
|
- inactivo
|
|
|
|
El PATCH es transaccional:
|
|
|
|
- si falla `default_payment_term_id`
|
|
- no se persisten otros cambios del mismo request
|
|
|
|
## Limitaciones V1 de `Company Details`
|
|
|
|
Fuera del alcance funcional actual:
|
|
|
|
- RBAC granular
|
|
- address
|
|
- country
|
|
- language
|
|
- currency
|
|
- timezone
|
|
- branding
|
|
- logo
|
|
- bank accounts
|
|
- fiscalidad avanzada
|
|
- VeriFactu
|
|
- series documentales
|
|
- `defaultPaymentMethodId`
|
|
|
|
## Reglas
|
|
|
|
- `CompanyMembership` vive en `identity`.
|
|
- No mover memberships a `companies`.
|
|
- No usar `customers` para representar la empresa tenant.
|
|
- `companySlug` no debe venir de token, `X-Company-Slug` ni `req.user.companySlug`.
|