Files
clientflow_backend/tests/test_v4924_reconciliation_process_review.py
2026-06-09 22:55:58 +01:00

97 lines
3.6 KiB
Python

"""v4.9.24: reconciliation process review must be explainable and actionable."""
import sys
import types
from pathlib import Path
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
ROOT = Path(__file__).resolve().parents[1]
def read(path: str) -> str:
return (ROOT / path).read_text(encoding="utf-8")
def _item(id_, external_type, number, date, amount):
return {
"id": id_,
"source_system": "jasmin" if external_type.startswith("jasmin") else "odoo",
"external_type": external_type,
"title": f"{external_type} · {number}",
"customer_name": "ACZCO BRAGA ENERGY, LDA",
"customer_tax_id": "517249200",
"document_number": number,
"document_date": date,
"amount": amount,
"currency": "EUR",
"payload": {"record": {"name": number, "invoice_status": "to invoice"}} if external_type == "odoo_sale_order" else {},
"operation_suggestions": [],
}
def test_process_candidates_explain_reasons_and_risks(monkeypatch):
monkeypatch.setattr(
svc,
"list_reconciliation_items",
lambda *, status, limit, days: [
_item("quote-154", "jasmin_quotation", "ORC.ORC2026.154", "2026-06-03", "638.60"),
_item("sale-279", "odoo_sale_order", "S00279", "2026-06-05", "638.60"),
],
)
candidates = svc.list_reconciliation_process_candidates(status="open", days=7, limit=10)
assert len(candidates) == 1
candidate = candidates[0]
assert candidate["review_status"] == "needs_review"
assert "cliente fiscal por NIF exato" in candidate["reasons"]
assert "valor igual entre documentos" in candidate["reasons"]
assert "datas próximas" in candidate["reasons"]
assert "venda Odoo sem fatura Jasmin associada" in candidate["risks"]
def test_process_candidates_flag_conflict_when_amounts_differ(monkeypatch):
monkeypatch.setattr(
svc,
"list_reconciliation_items",
lambda *, status, limit, days: [
_item("quote-154", "jasmin_quotation", "ORC.ORC2026.154", "2026-06-03", "638.60"),
_item("invoice-80", "jasmin_invoice", "FA2026.80", "2026-06-05", "190.00"),
],
)
candidates = svc.list_reconciliation_process_candidates(status="open", days=7, limit=10)
assert len(candidates) == 2 # different amounts keep separate purchase anchors
assert all("reasons" in candidate and "risks" in candidate for candidate in candidates)
def test_v4924_schema_and_ui_have_decisions_review_and_history_actions():
service = read("app/reconciliation_service.py")
page = read("app/admin_ui/pages/reconciliation.py")
styles = read("app/admin_ui/styles.py")
assert "CREATE TABLE IF NOT EXISTS reconciliation_decisions" in service
assert "def _candidate_reasons_and_risks" in service
assert "reasons" in service and "risks" in service and "review_status" in service
assert "/reconciliation/processes/needs-review" in page
assert "/reconciliation/processes/historical" in page
assert "Motivos" in page and "Riscos" in page
assert "cf-reconcile-explain" in styles