Implement document reconciliation v2

This commit is contained in:
plx
2026-08-05 23:55:33 +00:00
parent 4a60607503
commit 29382ec6c1
25 changed files with 2082 additions and 343 deletions

View File

@@ -110,6 +110,17 @@ def _missing_fields(customer: Dict[str, Any]) -> List[str]:
def _select_candidate_doc(conn: Any, opportunity_id: str) -> Optional[Dict[str, Any]]:
from app.document_reconciliation_service import resolve_document_links
effective = [row for row in resolve_document_links(opportunity_id, conn=conn)
if row.get("relationship") == "PRIMARY"
and row.get("system") == "jasmin"
and row.get("document_kind") in {"quotation", "invoice", "proforma"}]
effective.sort(key=lambda row: (
{"invoice": 3, "quotation": 2, "proforma": 1}.get(str(row.get("document_kind")), 0),
str(row.get("document_date") or ""), str(row.get("created_at") or "")), reverse=True)
if not effective:
return None
document_ids = [str(row["document_id"]) for row in effective[:5]]
rows = conn.execute(text("""
SELECT
d.id::text,
@@ -134,16 +145,14 @@ def _select_candidate_doc(conn: Any, opportunity_id: str) -> Optional[Dict[str,
c.jasmin_customer_id AS doc_customer_jasmin_id
FROM commercial_documents d
LEFT JOIN customers c ON c.id = d.customer_id
WHERE d.opportunity_id = CAST(:opportunity_id AS UUID)
AND d.system = 'jasmin'
AND d.document_kind IN ('quotation', 'invoice', 'proforma')
WHERE d.id = ANY(CAST(:document_ids AS UUID[]))
AND COALESCE(d.is_active, TRUE) = TRUE
ORDER BY
CASE d.document_kind WHEN 'invoice' THEN 1 WHEN 'quotation' THEN 2 WHEN 'proforma' THEN 3 ELSE 4 END,
COALESCE(d.document_date, d.created_at::date) DESC,
d.created_at DESC
LIMIT 5
"""), {"opportunity_id": opportunity_id}).mappings().all()
"""), {"document_ids": document_ids}).mappings().all()
best = None
best_score = -1
for row in rows:
@@ -324,12 +333,15 @@ def apply_jasmin_fiscal_sync(opportunity_id: str, *, actor: str = "operator") ->
WHERE id = CAST(:opportunity_id AS UUID)
AND (local_customer_id IS NULL OR local_customer_id = CAST(:customer_id AS UUID))
"""), {"customer_id": target_customer_id, "opportunity_id": opportunity_id})
conn.execute(text("""
UPDATE commercial_documents
SET customer_id = COALESCE(customer_id, CAST(:customer_id AS UUID)), updated_at = now()
WHERE opportunity_id = CAST(:opportunity_id AS UUID)
AND system = 'jasmin'
"""), {"customer_id": target_customer_id, "opportunity_id": opportunity_id})
from app.document_reconciliation_service import resolve_document_links
document_ids = [str(row["document_id"]) for row in resolve_document_links(opportunity_id, conn=conn)
if row.get("relationship") in {"PRIMARY", "SECONDARY", "HISTORICAL"}
and row.get("system") == "jasmin"]
if document_ids:
conn.execute(text("""UPDATE commercial_documents
SET customer_id=COALESCE(customer_id,CAST(:customer_id AS UUID)), updated_at=now()
WHERE id=ANY(CAST(:document_ids AS UUID[]))"""),
{"customer_id": target_customer_id, "document_ids": document_ids})
after_dict = dict(row or {})
filled = [key for key in ("tax_id", "email", "phone", "street_name", "postal_zone", "city_name", "country") if not _clean(before_dict.get(key)) and _clean(after_dict.get(key))]
conn.execute(text("""
@@ -349,10 +361,11 @@ def audit_jasmin_fiscal_gaps(limit: int = 200) -> List[Dict[str, Any]]:
ensure_commercial_schema()
findings: List[Dict[str, Any]] = []
with engine.begin() as conn:
# Candidate eligibility is resolved per opportunity below; this query
# deliberately contains no authoritative legacy/v2 relationship read.
rows = conn.execute(text("""
SELECT DISTINCT o.id::text, o.title, o.customer_name
SELECT o.id::text, o.title, o.customer_name
FROM opportunities o
JOIN commercial_documents d ON d.opportunity_id = o.id AND d.system = 'jasmin'
LEFT JOIN customers c ON c.id = o.local_customer_id
WHERE COALESCE(o.status, 'open') <> 'closed'
AND (