- Tabla crm.cases (expediente) con folio EXP2026-08-001 (next_folio entidad EXP,
sin dirección). Nace al crear la Oportunidad y se hereda vía case_id a
solicitud → cotización → operación → factura. advance_stage solo avanza.
- case_id (FK a crm.cases) en crm.opportunities/service_requests/quotes,
ops.shipments y fin.invoices; propagación en sus create_*. Migración
d4e5f6a7b8c9 reversible.
- Endpoints GET /v1/crm/cases, /cases/{id}, /cases/by-ref/{ref} con timeline
(historia completa para UI y otros sistemas).
- Frontend: casesAPI, ruta /dashboard/crm/expedientes (lista + timeline vertical),
chip "📁 Expediente" en solicitud/cotización, "Expedientes" en el sidebar.
- Consecutivo de folios sin tope (soporta >10,000,000/mes).
- 4 pruebas de expediente (minteo, propagación, timeline, no-retroceso). Suite en verde (113).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.3 KiB
Python
49 lines
2.3 KiB
Python
"""Pruebas del Expediente (crm.cases): minteo, propagación y timeline."""
|
|
|
|
from api.v1.modules.crm.cases import service as cases_service
|
|
from api.v1.modules.crm.opportunities import service as opp_service
|
|
from api.v1.modules.crm.opportunities.dto import OpportunityCreate
|
|
from api.v1.modules.crm.quotes import service as q_service
|
|
from api.v1.modules.crm.service_requests import service as sr_service
|
|
from api.v1.modules.crm.service_requests.dto import ServiceRequestCreate, ServiceRequestFromOpportunityInput
|
|
|
|
T, C = 1, 1
|
|
|
|
|
|
def test_opportunity_mints_expediente(db):
|
|
opp = opp_service.create_opportunity(db, OpportunityCreate(name="Negocio", operation_type="exportacion"), T, C)
|
|
assert opp.case_id is not None
|
|
case = cases_service.get_case(db, opp.case_id, T, C)
|
|
assert (case.reference or "").startswith("EXP")
|
|
assert case.stage == "oportunidad"
|
|
|
|
|
|
def test_case_propagates_and_advances(db):
|
|
opp = opp_service.create_opportunity(db, OpportunityCreate(name="Negocio", operation_type="importacion"), T, C)
|
|
sr = sr_service.create_from_opportunity(db, opp.id, ServiceRequestFromOpportunityInput(), T, C)
|
|
assert sr.case_id == opp.case_id
|
|
assert cases_service.get_case(db, opp.case_id, T, C).stage == "solicitud"
|
|
|
|
quotes = q_service.create_quotes_from_service_request(db, sr.id, T, C)
|
|
assert quotes[0].case_id == opp.case_id
|
|
case = cases_service.get_case(db, opp.case_id, T, C)
|
|
assert case.stage == "cotizacion"
|
|
|
|
# El timeline reúne toda la historia ligada al expediente
|
|
kinds = {e["kind"] for e in cases_service.build_timeline(db, case)}
|
|
assert {"oportunidad", "solicitud", "cotizacion"} <= kinds
|
|
|
|
|
|
def test_direct_service_request_mints_expediente(db):
|
|
# Solicitud directa (sin oportunidad) también obtiene expediente (fallback)
|
|
sr = sr_service.create_service_request(db, ServiceRequestCreate(operation_type="exportacion"), T, C)
|
|
assert sr.case_id is not None
|
|
assert cases_service.get_case(db, sr.case_id, T, C).stage == "solicitud"
|
|
|
|
|
|
def test_advance_stage_never_regresses(db):
|
|
opp = opp_service.create_opportunity(db, OpportunityCreate(name="N", operation_type="exportacion"), T, C)
|
|
cases_service.advance_stage(db, opp.case_id, "facturacion")
|
|
cases_service.advance_stage(db, opp.case_id, "solicitud") # no debe retroceder
|
|
assert cases_service.get_case(db, opp.case_id, T, C).stage == "facturacion"
|