feat(fin,ops): Facturación y Cobranza (Diag. 4), bitácora de embarque (Diag. 3) y subida a MinIO
- schema fin: fin.invoices + fin.invoice_items + fin.payments; totales con IVA, estados borrador→emitida→enviada→pagada, cobranza (pagos) y saldo automático - generar-factura-desde-embarque (toma conceptos de venta de la cotización) - ops.shipment_events: bitácora/hitos del embarque con secuencia por defecto según operación (importación/exportación) — cubre Diagrama 3 - subida de documentos a MinIO: POST /crm/uploads (multipart) + URL firmada - migración c4d5e6f7a8b9, routers/permisos (fin), seed del flujo hasta factura - 58 tests pytest en verde Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,10 @@ from api.v1.modules.crm.quotes.models import Quote, QuoteItem
|
||||
from api.v1.modules.crm.service_requests.models import ServiceRequest
|
||||
from api.v1.modules.crm.suppliers.models import Supplier
|
||||
from api.v1.modules.ops.shipments.models import Shipment, ShipmentDocument
|
||||
from api.v1.modules.ops.shipments import service as shipments_service
|
||||
from api.v1.modules.fin.invoices.models import Invoice
|
||||
from api.v1.modules.fin.invoices import service as invoices_service
|
||||
from api.v1.modules.fin.invoices.dto import PaymentCreate
|
||||
from core.database import CoreSessionLocal
|
||||
|
||||
# Deben coincidir con DEV_LOCAL_AUTH_TENANT_ID / DEV_LOCAL_AUTH_COMPANY_ID
|
||||
@@ -323,6 +327,39 @@ def seed_commercial_and_ops(db) -> None:
|
||||
print("✓ Flujo comercial: 1 solicitud, 1 cotización aceptada (3 conceptos), 1 embarque + documento MBL")
|
||||
|
||||
|
||||
def seed_invoicing_and_events(db) -> None:
|
||||
"""Factura desde el embarque (con pago parcial) + hitos de la bitácora."""
|
||||
from decimal import Decimal
|
||||
|
||||
if db.query(Invoice).filter(
|
||||
Invoice.tenant_id == TENANT_ID, Invoice.company_id == COMPANY_ID, Invoice.deleted_at.is_(None)
|
||||
).first():
|
||||
print("• Ya existe facturación; se omite")
|
||||
return
|
||||
|
||||
shipment = (
|
||||
db.query(Shipment)
|
||||
.filter(Shipment.tenant_id == TENANT_ID, Shipment.company_id == COMPANY_ID, Shipment.deleted_at.is_(None))
|
||||
.order_by(Shipment.id.asc())
|
||||
.first()
|
||||
)
|
||||
if not shipment:
|
||||
print("• Sin embarque; se omite facturación")
|
||||
return
|
||||
|
||||
# Bitácora de hitos (según tipo de operación)
|
||||
shipments_service.seed_default_milestones(db, shipment.id, TENANT_ID, COMPANY_ID)
|
||||
|
||||
# Factura generada desde el embarque (toma conceptos de la cotización)
|
||||
invoice = invoices_service.generate_from_shipment(db, shipment.id, TENANT_ID, COMPANY_ID)
|
||||
invoices_service.emit_invoice(db, invoice.id, TENANT_ID, COMPANY_ID)
|
||||
invoices_service.create_payment(
|
||||
db, PaymentCreate(invoice_id=invoice.id, amount=Decimal("1000"), method="transferencia", reference="SPEI-001"),
|
||||
TENANT_ID, COMPANY_ID,
|
||||
)
|
||||
print("✓ Factura emitida desde el embarque con pago parcial + hitos de la bitácora")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
db = CoreSessionLocal()
|
||||
try:
|
||||
@@ -331,6 +368,7 @@ def main() -> None:
|
||||
seed_sample_data(db, pipeline, stages)
|
||||
seed_suppliers_and_related(db)
|
||||
seed_commercial_and_ops(db)
|
||||
seed_invoicing_and_events(db)
|
||||
print("\nSeed CRM completado.")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user