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

@@ -321,16 +321,17 @@ def _historical_stage_rates(conn: Any) -> dict[str, dict[str, Any]]:
def _realised_for_period(conn: Any, *, metric: str, period_start: date, period_end: date) -> dict[str, Any]:
from app.document_reconciliation_service import prepare_effective_document_links
prepare_effective_document_links(conn)
if metric == "cash_received":
rows = conn.execute(text("""
WITH latest_doc AS (
SELECT DISTINCT ON (opportunity_id) opportunity_id, COALESCE(total_amount, amount, 0) AS amount
FROM commercial_documents
WHERE opportunity_id IS NOT NULL AND COALESCE(is_active, TRUE) = TRUE
ORDER BY opportunity_id,
CASE WHEN document_kind = 'invoice' THEN 0 ELSE 1 END,
COALESCE(document_date, created_at::date) DESC,
created_at DESC
SELECT DISTINCT ON (l.opportunity_id) l.opportunity_id, COALESCE(d.total_amount, d.amount, 0) AS amount
FROM _effective_document_links l JOIN commercial_documents d ON d.id=l.document_id
WHERE l.ended_at IS NULL AND l.relationship='PRIMARY'
AND upper(COALESCE(d.status,'')) NOT IN ('CANCELLED','CANCELED')
ORDER BY l.opportunity_id, CASE WHEN d.document_kind = 'invoice' THEN 0 ELSE 1 END,
COALESCE(d.document_date, d.created_at::date) DESC, d.created_at DESC
)
SELECT ol.opportunity_id::text,
COALESCE(CASE WHEN COALESCE(ol.payload->>'amount','') ~ '^[0-9]+([.,][0-9]+)?$' THEN replace(ol.payload->>'amount', ',', '.')::numeric END, ld.amount, o.value_amount, 0) AS amount,
@@ -352,9 +353,10 @@ def _realised_for_period(conn: Any, *, metric: str, period_start: date, period_e
COALESCE(o.customer_name, o.title, 'Venda ganha') AS reference
FROM opportunities o
LEFT JOIN LATERAL (
SELECT COALESCE(total_amount, amount, 0) AS amount
FROM commercial_documents d
WHERE d.opportunity_id = o.id AND COALESCE(d.is_active, TRUE) = TRUE
SELECT COALESCE(d.total_amount, d.amount, 0) AS amount
FROM _effective_document_links l JOIN commercial_documents d ON d.id=l.document_id
WHERE l.opportunity_id = o.id AND l.ended_at IS NULL AND l.relationship='PRIMARY'
AND upper(COALESCE(d.status,'')) NOT IN ('CANCELLED','CANCELED')
ORDER BY CASE WHEN d.document_kind = 'invoice' THEN 0 ELSE 1 END,
COALESCE(d.document_date, d.created_at::date) DESC
LIMIT 1
@@ -364,14 +366,14 @@ def _realised_for_period(conn: Any, *, metric: str, period_start: date, period_e
"""), {"period_start": period_start, "period_end": period_end}).mappings().all()
else:
rows = conn.execute(text("""
SELECT d.opportunity_id::text,
SELECT l.opportunity_id::text,
COALESCE(d.total_amount, d.amount, 0) AS amount,
COALESCE(d.document_date::timestamp, d.created_at) AS realised_at,
COALESCE(d.document_number, d.external_id, 'Fatura') AS reference
FROM commercial_documents d
FROM _effective_document_links l JOIN commercial_documents d ON d.id=l.document_id
WHERE lower(d.document_kind) = 'invoice'
AND COALESCE(d.is_active, TRUE) = TRUE
AND COALESCE(d.role, 'current') IN ('current','accepted')
AND l.ended_at IS NULL AND l.relationship='PRIMARY'
AND upper(COALESCE(d.status,'')) NOT IN ('CANCELLED','CANCELED')
AND COALESCE(d.document_date, d.created_at::date) BETWEEN :period_start AND :period_end
"""), {"period_start": period_start, "period_end": period_end}).mappings().all()
items = [dict(r) for r in rows]
@@ -384,6 +386,8 @@ def _realised_for_period(conn: Any, *, metric: str, period_start: date, period_e
def _already_realised_ids(conn: Any, *, metric: str) -> set[str]:
from app.document_reconciliation_service import prepare_effective_document_links
prepare_effective_document_links(conn)
if metric == "cash_received":
sql = """
SELECT DISTINCT opportunity_id::text
@@ -398,12 +402,11 @@ def _already_realised_ids(conn: Any, *, metric: str) -> set[str]:
"""
else:
sql = """
SELECT DISTINCT opportunity_id::text
FROM commercial_documents
WHERE opportunity_id IS NOT NULL
AND lower(document_kind) = 'invoice'
AND COALESCE(is_active, TRUE) = TRUE
AND COALESCE(role, 'current') IN ('current','accepted')
SELECT DISTINCT l.opportunity_id::text
FROM _effective_document_links l JOIN commercial_documents d ON d.id=l.document_id
WHERE l.ended_at IS NULL AND l.relationship='PRIMARY'
AND lower(d.document_kind) = 'invoice'
AND upper(COALESCE(d.status,'')) NOT IN ('CANCELLED','CANCELED')
"""
return {str(r[0]) for r in conn.execute(text(sql)).all() if r[0]}
@@ -435,25 +438,21 @@ def get_revenue_forecast(*, limit: int = 1000, month: str | None = None, metric:
target = get_sales_target(month=period_start, metric=metric)
with engine.begin() as conn:
from app.document_reconciliation_service import prepare_effective_document_links
prepare_effective_document_links(conn)
historical = _historical_stage_rates(conn)
realised = _realised_for_period(conn, metric=metric, period_start=period_start, period_end=period_end)
already_realised_ids = _already_realised_ids(conn, metric=metric)
rows = conn.execute(text("""
WITH latest_doc AS (
SELECT DISTINCT ON (opportunity_id)
opportunity_id,
total_amount,
document_number,
document_kind,
document_date
FROM commercial_documents
WHERE COALESCE(is_active, TRUE) = TRUE
AND COALESCE(role, 'current') IN ('current','accepted','historical','history')
ORDER BY opportunity_id,
CASE WHEN COALESCE(is_primary, FALSE) THEN 0 ELSE 1 END,
CASE document_kind WHEN 'invoice' THEN 1 WHEN 'quotation' THEN 2 WHEN 'proforma' THEN 3 ELSE 4 END,
COALESCE(document_date, created_at::date) DESC,
created_at DESC
SELECT DISTINCT ON (l.opportunity_id)
l.opportunity_id, d.total_amount, d.document_number, d.document_kind, d.document_date
FROM _effective_document_links l JOIN commercial_documents d ON d.id=l.document_id
WHERE l.ended_at IS NULL AND l.relationship='PRIMARY'
AND upper(COALESCE(d.status,'')) NOT IN ('CANCELLED','CANCELED')
ORDER BY l.opportunity_id,
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
), item_totals AS (
SELECT opportunity_id, SUM(COALESCE(total_price, 0)) AS total
FROM opportunity_items