from decimal import Decimal from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader from api.v1.modules.a76.invoices.imports.process import main_process as import_main from api.v1.modules.a76.invoices.exports.process import main_process as export_main from api.v1.modules.a76.invoices.exports.process import task as export_task from tests.fixtures.builders import ( create_business_catalogs, create_export_invoice_with_line, create_import_invoice_with_line, ensure_reference_data, ensure_tenant_company, ) def _patch_import_pipeline(monkeypatch, import_line): monkeypatch.setattr(import_main, "pre_validators", lambda *args, **kwargs: [import_line]) monkeypatch.setattr(import_main, "review_classes", lambda *args, **kwargs: None) monkeypatch.setattr(import_main, "review_exchange_rate", lambda *args, **kwargs: None) monkeypatch.setattr(import_main, "review_weights_kgs", lambda *args, **kwargs: None) monkeypatch.setattr(import_main, "review_weights_lbs", lambda *args, **kwargs: None) monkeypatch.setattr(import_main, "_validate_sisimp_limits", lambda *args, **kwargs: None) monkeypatch.setattr(import_main, "_validate_lines", lambda *args, **kwargs: ([], {})) def _patch_export_pipeline(monkeypatch, export_line): monkeypatch.setattr(export_main, "pre_validators", lambda *args, **kwargs: [export_line]) monkeypatch.setattr(export_main, "review_class", lambda *args, **kwargs: None) monkeypatch.setattr(export_main, "review_exchange_rate", lambda *args, **kwargs: None) monkeypatch.setattr(export_main, "assign_values", lambda *args, **kwargs: None) monkeypatch.setattr(export_main, "review_qty_vs_weight", lambda *args, **kwargs: None) monkeypatch.setattr(export_main, "review_unit_cost", lambda *args, **kwargs: None) monkeypatch.setattr(export_main, "review_qty_series", lambda *args, **kwargs: None) def test_e2e_inventory_flow_import_then_export(client, db_session, monkeypatch): """ E2E principal: - Alta catálogos/base - Procesa factura IMP -> genera entry - Procesa factura EXP -> genera consumo/descarga - Valida decremento de saldo sin negativos """ ensure_reference_data(db_session) ensure_tenant_company(db_session, tenant_id=1, company_id=1) catalogs = create_business_catalogs(db_session, tenant_id=1, company_id=1) import_invoice, import_line = create_import_invoice_with_line( db_session, 1, 1, catalogs, invoice_type="TEM", invoice_number="IMP-E2E-01", qty=Decimal("10") ) _patch_import_pipeline(monkeypatch, import_line) response = client.post(f"/api/v1/a76/invoices/{import_invoice.id}/process?company_id=1") assert response.status_code == 200 entry_movements = ( db_session.query(BalanceMovement) .filter( BalanceMovement.import_invoice_id == import_invoice.id, BalanceMovement.import_item_line_id == import_line.id, BalanceMovement.movement_type == MovementType.ENTRY, ) .all() ) assert entry_movements assert sum(Decimal(str(m.quantity or 0)) for m in entry_movements) > 0 export_invoice, export_line = create_export_invoice_with_line( db_session, 1, 1, catalogs, import_invoice, import_line, qty=Decimal("4") ) _patch_export_pipeline(monkeypatch, export_line) response = client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id=1") assert response.status_code == 200 consumptions = ( db_session.query(BalanceMovement) .filter( BalanceMovement.source_invoice_id == export_invoice.id, BalanceMovement.movement_type == MovementType.CONSUMPTION, ) .all() ) assert consumptions, "El proceso de exportación debe generar consumos" discharge_headers = ( db_session.query(DischargeHeader) .filter(DischargeHeader.source_invoice_id == export_invoice.id) .all() ) assert discharge_headers discharge_details = ( db_session.query(DischargeDetail) .filter(DischargeDetail.export_item_line_id == export_line.id) .all() ) assert discharge_details assert all(detail.movement_id is not None for detail in discharge_details) net_balance = db_session.query(BalanceMovement).filter( BalanceMovement.import_item_line_id == import_line.id ).all() signed = Decimal("0") for mov in net_balance: qty = Decimal(str(mov.quantity or 0)) if mov.movement_type in {MovementType.CONSUMPTION, MovementType.WASTE, MovementType.SCRAP, MovementType.DESTRUCTION}: signed -= qty else: signed += qty assert signed >= 0 assert signed < Decimal("10")