from decimal import Decimal import pytest from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType from api.v1.modules.a76.invoices.exports.process import main_process as export_main from api.v1.modules.a76.invoices.imports.process import main_process as import_main from core.exceptions import ValidationException from tests.fixtures.builders import ( EphemeralOrgIds, create_business_catalogs, create_export_invoice_with_line, create_import_invoice_with_line, ensure_reference_data, ) 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_import_pipeline_without_prevalidators(monkeypatch): 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 _prepare_inventory(client, db_session, monkeypatch, test_tenant: EphemeralOrgIds): tid, cid = test_tenant.tenant_id, test_tenant.company_id ensure_reference_data(db_session) catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) import_invoice, import_line = create_import_invoice_with_line( db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-EXP-01", qty=Decimal("10") ) _patch_import_pipeline(monkeypatch, import_line) resp = client.post(f"/api/v1/a76/invoices/{import_invoice.id}/process?company_id={cid}") assert resp.status_code == 200 return catalogs, import_invoice, import_line def test_process_export_endpoint_consumes_existing_balances(client, db_session, monkeypatch, test_tenant): catalogs, import_invoice, import_line = _prepare_inventory( client, db_session, monkeypatch, test_tenant ) tid, cid = test_tenant.tenant_id, test_tenant.company_id export_invoice, export_line = create_export_invoice_with_line( db_session, tid, cid, catalogs, import_invoice, import_line, qty=Decimal("3") ) _patch_export_pipeline(monkeypatch, export_line) resp = client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id={cid}") assert resp.status_code == 200 consumptions = ( db_session.query(BalanceMovement) .filter( BalanceMovement.source_invoice_id == export_invoice.id, BalanceMovement.movement_type == MovementType.CONSUMPTION, ) .all() ) assert consumptions def test_process_export_prevents_negative_balance(client, db_session, monkeypatch, test_tenant): catalogs, import_invoice, import_line = _prepare_inventory( client, db_session, monkeypatch, test_tenant ) tid, cid = test_tenant.tenant_id, test_tenant.company_id export_invoice, export_line = create_export_invoice_with_line( db_session, tid, cid, catalogs, import_invoice, import_line, qty=Decimal("99") ) _patch_export_pipeline(monkeypatch, export_line) with pytest.raises(ValidationException): client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id={cid}") # Debe no crear consumos al existir insuficiencia de saldo. consumptions = ( db_session.query(BalanceMovement) .filter( BalanceMovement.source_invoice_id == export_invoice.id, BalanceMovement.movement_type == MovementType.CONSUMPTION, ) .all() ) assert consumptions == [] def test_process_endpoint_prevents_double_processing_import(client, db_session, monkeypatch, test_tenant): tid, cid = test_tenant.tenant_id, test_tenant.company_id ensure_reference_data(db_session) catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) invoice, line = create_import_invoice_with_line( db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-DOUBLE-01", qty=Decimal("5") ) _patch_import_pipeline_without_prevalidators(monkeypatch) first = client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") assert first.status_code == 200 with pytest.raises(ValidationException): client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") # No debe duplicar entradas para la misma factura/lĂ­nea procesada. entries = ( db_session.query(BalanceMovement) .filter( BalanceMovement.import_invoice_id == invoice.id, BalanceMovement.import_item_line_id == line.id, BalanceMovement.movement_type == MovementType.ENTRY, ) .all() ) assert len(entries) == 1