Solicitud de servicio:
- Campos del documento maestro de cotización (ruta estructurada por país,
mercancía, dimensiones/bultos, FCL/LCL, servicios adicionales, notas).
- Origen/Destino seleccionables por catálogo de país (seed ya poblado).
- Validación de contacto asociado (422 si no existe).
Ciclo Oportunidad -> Solicitud -> Cotización -> Operación:
- Dirección impo/expo se captura en la Oportunidad y se hereda al ciclo.
- Conversión Oportunidad->Solicitud idempotente con back-link.
- Endpoint Solicitud->Cotización; "Ambas" genera 2 cotizaciones (FCL/LCL).
- Liberación a Operaciones confirma IMPO/EXPO (prefijado) y siembra los hitos.
- Fecha de la cotización (issue_date) por defecto hoy, editable y en el PDF.
Folios auto-generados {LETRA}{AAAA}-{MM}-{NNN}-{DIR} para Oportunidad (O),
Solicitud (S), Cotización (C) y Operación (OP); consecutivo mensual por
compañía y entidad (crm.folio_counters + helper next_folio con bloqueo de fila).
Catálogos: 9 nuevos (tipo_operacion, medio_transporte, tipo_servicio, prioridad,
tipo_mercancia, unidad_medida, tipo_embalaje, servicio_adicional, tipo_documento).
Migración b1c2d3e4f5a6 reversible (upgrade->downgrade->upgrade verificado en PG).
25 pruebas unitarias nuevas (folios, catálogos, solicitudes, cotizaciones,
embarques); suite completa en verde (101 pruebas).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.6 KiB
Python
50 lines
1.6 KiB
Python
"""Pruebas de la siembra idempotente de catálogos globales del CRM."""
|
|
|
|
from api.v1.modules.crm.catalogs.models import CatalogItem
|
|
from api.v1.modules.crm.catalogs.seed import seed_global_catalogs
|
|
|
|
# Catálogos nuevos del proceso comercial y una clave base que debe existir en cada uno.
|
|
NEW_CATALOGS = {
|
|
"tipo_operacion": "importacion",
|
|
"medio_transporte": "maritimo",
|
|
"tipo_servicio": "puerto_puerto",
|
|
"prioridad": "urgente",
|
|
"tipo_mercancia": "peligrosa",
|
|
"unidad_medida": "kg",
|
|
"tipo_embalaje": "pallet",
|
|
"servicio_adicional": "seguro",
|
|
"tipo_documento": "factura_comercial",
|
|
}
|
|
|
|
|
|
def _codes(db, catalog: str) -> set[str]:
|
|
return {
|
|
row.code
|
|
for row in db.query(CatalogItem.code).filter(
|
|
CatalogItem.catalog == catalog, CatalogItem.tenant_id.is_(None)
|
|
)
|
|
}
|
|
|
|
|
|
def test_seed_creates_new_catalogs(db):
|
|
seed_global_catalogs(db)
|
|
for catalog, base_code in NEW_CATALOGS.items():
|
|
codes = _codes(db, catalog)
|
|
assert codes, f"El catálogo {catalog} quedó vacío"
|
|
assert base_code in codes, f"Falta la clave base {base_code} en {catalog}"
|
|
|
|
|
|
def test_seed_is_idempotent(db):
|
|
first = seed_global_catalogs(db)
|
|
assert first, "La primera corrida debió sembrar filas"
|
|
second = seed_global_catalogs(db)
|
|
assert second == {}, "La segunda corrida no debe agregar filas nuevas"
|
|
|
|
|
|
def test_pais_catalog_populated(db):
|
|
"""El catálogo pais alimenta Origen/Destino de la solicitud (decisión 6)."""
|
|
seed_global_catalogs(db)
|
|
codes = _codes(db, "pais")
|
|
assert len(codes) > 100
|
|
assert "MEX" in codes
|