"""v4.9.20: fiscal customer identity is not purchase identity.""" import sys import types if "sqlalchemy" not in sys.modules: sqlalchemy = types.ModuleType("sqlalchemy") sqlalchemy.text = lambda sql: sql sys.modules["sqlalchemy"] = sqlalchemy if "app.db" not in sys.modules: app_db = types.ModuleType("app.db") app_db.engine = object() sys.modules["app.db"] = app_db if "app.action_catalog" not in sys.modules: action_catalog = types.ModuleType("app.action_catalog") action_catalog.get_action_config = lambda code: {"route": "rever", "action": code, "action_required": True, "safe_to_post": False} sys.modules["app.action_catalog"] = action_catalog from app import reconciliation_service as svc def _jasmin_doc(id_, external_type, number, date, amount): return { "id": id_, "source_system": "jasmin", "external_type": external_type, "title": f"Documento Jasmin · {number}", "customer_name": "ACZCO BRAGA ENERGY, LDA", "customer_tax_id": "517249200", "document_number": number, "document_date": date, "amount": amount, "currency": "EUR", "operation_suggestions": [], } def test_same_company_two_jasmin_quotes_stay_as_two_purchase_candidates(monkeypatch): monkeypatch.setattr( svc, "list_reconciliation_items", lambda *, status, limit, days: [ _jasmin_doc("quote-154", "jasmin_quotation", "ORC.ORC2026.154", "2026-06-03", "638.60"), _jasmin_doc("quote-160", "jasmin_quotation", "ORC.ORC2026.160", "2026-06-06", "190.00"), ], ) candidates = svc.list_reconciliation_process_candidates(status="open", days=7, limit=10) assert len(candidates) == 2 keys = {candidate["operation_key"] for candidate in candidates} assert "jasmin_quotation:ORC.ORC2026.154" in keys assert "jasmin_quotation:ORC.ORC2026.160" in keys assert {tuple(candidate["item_ids"]) for candidate in candidates} == {("quote-154",), ("quote-160",)} def test_quote_and_invoice_with_same_amount_and_near_date_can_form_one_purchase(monkeypatch): monkeypatch.setattr( svc, "list_reconciliation_items", lambda *, status, limit, days: [ _jasmin_doc("quote-154", "jasmin_quotation", "ORC.ORC2026.154", "2026-06-03", "638.60"), _jasmin_doc("invoice-80", "jasmin_invoice", "FA2026.80", "2026-06-05", "638.60"), _jasmin_doc("quote-160", "jasmin_quotation", "ORC.ORC2026.160", "2026-06-20", "190.00"), ], ) candidates = svc.list_reconciliation_process_candidates(status="open", days=30, limit=10) grouped = {candidate["operation_key"]: set(candidate["item_ids"]) for candidate in candidates} assert grouped["jasmin_quotation:ORC.ORC2026.154"] == {"quote-154", "invoice-80"} assert grouped["jasmin_quotation:ORC.ORC2026.160"] == {"quote-160"} def test_payment_proof_assigns_only_when_order_match_is_unambiguous(monkeypatch): order_a = { "id": "sale-279", "source_system": "odoo", "external_type": "odoo_sale_order", "title": "Venda Odoo sem oportunidade · S00279", "customer_name": "ACZCO BRAGA ENERGY, LDA", "customer_tax_id": "517249200", "document_number": "S00279", "document_date": "2026-06-05", "amount": "638.60", "currency": "EUR", "payload": {"record": {"name": "S00279", "invoice_status": "to invoice"}}, "operation_suggestions": [], } order_b = {**order_a, "id": "sale-280", "document_number": "S00280", "amount": "190.00", "payload": {"record": {"name": "S00280", "invoice_status": "to invoice"}}} proof = { "id": "proof-279", "source_system": "email", "external_type": "payment_proof", "title": "Comprovativo recebido", "customer_name": "ACZCO BRAGA ENERGY, LDA", "customer_tax_id": "517249200", "document_number": "comprovativo-279", "document_date": "2026-06-06", "amount": "638.60", "currency": "EUR", "operation_suggestions": [], } monkeypatch.setattr(svc, "list_reconciliation_items", lambda *, status, limit, days: [order_a, order_b, proof]) candidates = svc.list_reconciliation_process_candidates(status="open", days=7, limit=10) grouped = {candidate["operation_key"]: set(candidate["item_ids"]) for candidate in candidates} assert grouped["odoo_sale_order:S00279"] == {"sale-279", "proof-279"} assert grouped["odoo_sale_order:S00280"] == {"sale-280"}