fix: synchronize document links with reconciliation items

This commit is contained in:
plx
2026-08-14 13:44:37 +00:00
parent 7d138eccdc
commit 475ff15b9b
5 changed files with 256 additions and 1 deletions

View File

@@ -372,6 +372,55 @@ def _dual_write(conn: Any, document_id: str, opportunity_id: str, relationship:
"role": role, "primary": primary, "active": active})
def _sync_reconciliation_item_for_active_link(
conn: Any, document_id: str, opportunity_id: str, relationship: str,
) -> int:
"""Project an active document link onto its exact reconciliation item.
Document identity is intentionally limited to source system plus the stored
document number/external id. Customer and payload inference do not belong in
this consistency boundary.
"""
relationship = str(relationship or "").upper()
if relationship not in {"PRIMARY", "SECONDARY"}:
return 0
result = conn.execute(text("""
UPDATE reconciliation_items ri
SET status = 'linked',
opportunity_id = CAST(:opportunity_id AS UUID),
resolved_at = COALESCE(ri.resolved_at, now()),
updated_at = now()
FROM commercial_documents d
WHERE d.id = CAST(:document_id AS UUID)
AND ri.source_system = d.system
AND ri.status NOT IN ('ignored', 'historical')
AND (
(NULLIF(d.document_number, '') IS NOT NULL AND ri.document_number = d.document_number)
OR (NULLIF(d.external_id, '') IS NOT NULL AND ri.external_id = d.external_id)
)
"""), {"document_id": document_id, "opportunity_id": opportunity_id})
return int(result.rowcount or 0)
def active_document_link_exclusion_sql(item_alias: str = "ri") -> str:
"""SQL predicate hiding stale open items already owned by an active link."""
if not item_alias.replace("_", "").isalnum():
raise ValueError("invalid SQL alias")
return f"""NOT EXISTS (
SELECT 1
FROM commercial_documents linked_cd
JOIN opportunity_document_links linked_odl
ON linked_odl.document_id = linked_cd.id
AND linked_odl.ended_at IS NULL
AND linked_odl.relationship IN ('PRIMARY','SECONDARY')
WHERE linked_cd.system = {item_alias}.source_system
AND (
(NULLIF({item_alias}.document_number, '') IS NOT NULL AND linked_cd.document_number = {item_alias}.document_number)
OR (NULLIF({item_alias}.external_id, '') IS NOT NULL AND linked_cd.external_id = {item_alias}.external_id)
)
)"""
def _command_fingerprint(*, opportunity_id: str, document_id: str, target_relationship: str,
origin_opportunity_id: Optional[str] = None,
destination_opportunity_id: Optional[str] = None,
@@ -468,6 +517,9 @@ def set_document_relationship(opportunity_id: str, document_id: str, relationshi
existing = _validate_membership(conn, opportunity_id, document_id)
old = existing.get("relationship") if existing else None
if old == relationship:
_sync_reconciliation_item_for_active_link(
conn, document_id, opportunity_id, relationship,
)
result = dict(existing)
_complete_idempotency(conn, idempotency_key, result)
return result
@@ -505,6 +557,9 @@ def set_document_relationship(opportunity_id: str, document_id: str, relationshi
VALUES(CAST(:opportunity_id AS UUID),CAST(:document_id AS UUID),:kind,:relationship,:manual,
:reason,:actor,now(),:source,:correlation_id,:request_id,CAST(:metadata AS JSONB)) RETURNING *"""), params).mappings().first()
_dual_write(conn, document_id, opportunity_id, relationship)
_sync_reconciliation_item_for_active_link(
conn, document_id, opportunity_id, relationship,
)
conn.execute(text("UPDATE commercial_document_lines SET opportunity_document_link_id=:link_id WHERE commercial_document_id=CAST(:document_id AS UUID)"), {"link_id": link["id"], "document_id": document_id})
_event(conn, link_id=str(link["id"]), opportunity_id=opportunity_id, document_id=document_id,
event_type=event_type, actor=actor, reason=reason, old_relationship=old,
@@ -576,6 +631,10 @@ def reassign_document(origin_opportunity_id: str, destination_opportunity_id: st
existing_destination = conn.execute(text("""SELECT * FROM opportunity_document_links WHERE opportunity_id=CAST(:oid AS UUID)
AND document_id=CAST(:did AS UUID) AND ended_at IS NULL"""), {"oid": destination_opportunity_id, "did": document_id}).mappings().first()
if existing_destination:
_sync_reconciliation_item_for_active_link(
conn, document_id, destination_opportunity_id,
str(existing_destination.get("relationship") or ""),
)
result = dict(existing_destination)
_complete_idempotency(conn, idempotency_key, result)
return result
@@ -594,6 +653,9 @@ def reassign_document(origin_opportunity_id: str, destination_opportunity_id: st
"reason": reason, "actor": actor, "origin": origin_opportunity_id, "correlation_id": correlation_id,
"request_id": request_id, "old_link": str(old["id"])}).mappings().first()
_dual_write(conn, document_id, destination_opportunity_id, "SECONDARY")
_sync_reconciliation_item_for_active_link(
conn, document_id, destination_opportunity_id, "SECONDARY",
)
conn.execute(text("UPDATE commercial_document_lines SET opportunity_document_link_id=:link WHERE commercial_document_id=CAST(:doc AS UUID)"), {"link": link["id"], "doc": document_id})
_event(conn, link_id=str(old["id"]), opportunity_id=origin_opportunity_id, document_id=document_id,
event_type="REASSIGNED", actor=actor, reason=reason, old_relationship=old["relationship"],
@@ -644,6 +706,10 @@ def classify_sync_document(conn: Any, opportunity_id: str, document_id: str, doc
correlation_id=None, request_id=None,
idempotency_key=f"jasmin-primary-cancelled:{opportunity_id}:{document_id}")
result["relationship"] = "REVIEW_REQUIRED"
else:
_sync_reconciliation_item_for_active_link(
conn, document_id, opportunity_id, str(link.get("relationship") or ""),
)
return result
candidates = conn.execute(text("""SELECT * FROM opportunity_document_links
WHERE opportunity_id=CAST(:oid AS UUID) AND document_kind=:kind AND ended_at IS NULL FOR UPDATE"""),
@@ -671,6 +737,9 @@ def classify_sync_document(conn: Any, opportunity_id: str, document_id: str, doc
RETURNING *"""), {"oid": opportunity_id, "did": document_id, "kind": document_kind,
"relationship": relationship, "reason": reason, "actor": actor}).mappings().first()
_dual_write(conn, document_id, opportunity_id, relationship)
_sync_reconciliation_item_for_active_link(
conn, document_id, opportunity_id, relationship,
)
_event(conn, link_id=str(new_link["id"]), opportunity_id=opportunity_id, document_id=document_id,
event_type="SYNC_LINK_CLASSIFIED", actor=actor, reason=reason, old_relationship=None,
new_relationship=relationship, correlation_id=None, request_id=None,