from datetime import date from decimal import Decimal from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType from api.v1.modules.a76.invoices.exports.process.sub_process.compare_balances import compare_balances from api.v1.modules.a76.invoices.exports.process.sub_process.discharge_types import ( AvailableLot, DownloadEntry, ) from api.v1.modules.a76.invoices.exports.process.sub_process.fill_available_balances import ( _net_balance_for_lot, ) from core.exceptions import ErrorCollector from tests.fixtures.builders import ( create_business_catalogs, create_import_invoice_with_line, ensure_reference_data, ) def test_net_balance_accounts_for_returns_and_entry_void(db_session, test_tenant): """ Balance neto esperado: entry 10 - consumption 4 + return 1 - entry_void 2 = 5 """ 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-UNIT-01", qty=Decimal("10"), ) line_id = import_line.id tenant_id = import_invoice.tenant_id company_id = import_invoice.company_id import_invoice_id = import_invoice.id for idx, (mtype, qty) in enumerate( [ (MovementType.ENTRY, Decimal("10")), (MovementType.CONSUMPTION, Decimal("4")), (MovementType.RETURN, Decimal("1")), (MovementType.ENTRY_VOID, Decimal("2")), ], start=1, ): db_session.add( BalanceMovement( id=10000 + idx, tenant_id=tenant_id, company_id=company_id, import_invoice_id=import_invoice_id, import_item_line_id=line_id, movement_type=mtype, quantity=qty, order_peps=10000 + idx, operation_date=date(2026, 3, 20), ) ) db_session.flush() balance = _net_balance_for_lot(db_session, line_id, date(2026, 3, 21)) assert balance == Decimal("5") def test_fifo_consumption_algorithm_uses_oldest_lots_first(db_session, monkeypatch): """ Verifica distribución FIFO (PEPS): - lote1 (order=1, qty=3), lote2 (order=2, qty=5), demanda=6 - resultado: lote1 consume 3, lote2 consume 3, faltante 0 """ monkeypatch.setattr( "api.v1.modules.a76.invoices.exports.process.sub_process.compare_balances._resolve_import_uom", lambda db, import_item_line_id: "PZA", ) entry = DownloadEntry( origin_procedure="TEM", export_line=1, part_number="PN-1", class_code="CLS", quantity=Decimal("6"), quantity_used=Decimal("0"), unit_of_measure="PZA", import_invoice="IMP-1", import_line=1, line_item_id=1, available_lots=[ AvailableLot( import_item_line_id=11, import_invoice_id=101, part_number_id=None, available_qty=Decimal("3"), value_me=Decimal("0"), value_mn=Decimal("0"), order_peps=1, ), AvailableLot( import_item_line_id=12, import_invoice_id=101, part_number_id=None, available_qty=Decimal("5"), value_me=Decimal("0"), value_mn=Decimal("0"), order_peps=2, ), ], ) errors = ErrorCollector() compare_balances(db_session, export_invoice=None, to_discharge=[entry], errors=errors) # type: ignore[arg-type] assert not errors.has_errors() assert entry.quantity_used == Decimal("6") assert entry.available_lots[0].consumed_qty == Decimal("3") assert entry.available_lots[1].consumed_qty == Decimal("3")