fix: synchronize document links with reconciliation items
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user