175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sqlite3
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from runtime_config import DB_PATH
|
|
|
|
|
|
DEFAULT_STATUSES = (
|
|
"voucher_matched",
|
|
"voucher_unmatched",
|
|
"voucher_recheck",
|
|
"voucher_excepted",
|
|
)
|
|
|
|
|
|
def latest_signature(conn: sqlite3.Connection, start_year: int, end_year: int) -> str:
|
|
row = conn.execute(
|
|
"""
|
|
SELECT signature
|
|
FROM wehago_compare_query_groups
|
|
WHERE start_year = ?
|
|
AND end_year = ?
|
|
GROUP BY signature
|
|
ORDER BY MAX(updated_at) DESC
|
|
LIMIT 1
|
|
""",
|
|
(start_year, end_year),
|
|
).fetchone()
|
|
return str(row[0] or "") if row else ""
|
|
|
|
|
|
def status_summary(
|
|
conn: sqlite3.Connection,
|
|
start_year: int,
|
|
end_year: int,
|
|
signature: str,
|
|
status_key: str,
|
|
) -> dict[str, int | float]:
|
|
row = conn.execute(
|
|
"""
|
|
WITH per_group AS (
|
|
SELECT
|
|
g.group_index,
|
|
COUNT(DISTINCT CASE
|
|
WHEN COALESCE(r.ledger_account_name, '') <> ''
|
|
OR COALESCE(r.ledger_desc, '') <> ''
|
|
OR ABS(COALESCE(r.ledger_debit, 0)) >= 0.5
|
|
OR ABS(COALESCE(r.ledger_credit, 0)) >= 0.5
|
|
THEN COALESCE(r.ledger_date, '') || '|' || COALESCE(r.voucher_no, '')
|
|
END) AS wehago_keys,
|
|
COUNT(DISTINCT CASE
|
|
WHEN COALESCE(r.voucher_account_name, '') <> ''
|
|
OR COALESCE(r.voucher_desc, '') <> ''
|
|
OR ABS(COALESCE(r.voucher_debit, 0)) >= 0.5
|
|
OR ABS(COALESCE(r.voucher_credit, 0)) >= 0.5
|
|
THEN COALESCE(r.draft_no, '')
|
|
END) AS erp_drafts,
|
|
COUNT(*) AS row_count
|
|
FROM wehago_compare_query_groups g
|
|
LEFT JOIN wehago_compare_query_rows r
|
|
ON r.start_year = g.start_year
|
|
AND r.end_year = g.end_year
|
|
AND r.signature = g.signature
|
|
AND r.status_key = g.status_key
|
|
AND r.group_index = g.group_index
|
|
WHERE g.start_year = ?
|
|
AND g.end_year = ?
|
|
AND g.signature = ?
|
|
AND g.status_key = ?
|
|
GROUP BY g.group_index
|
|
)
|
|
SELECT
|
|
COUNT(*) AS groups,
|
|
SUM(CASE WHEN wehago_keys > 1 THEN 1 ELSE 0 END) AS mixed_wehago_groups,
|
|
SUM(CASE WHEN erp_drafts > 1 THEN 1 ELSE 0 END) AS multi_erp_groups,
|
|
COALESCE(MAX(wehago_keys), 0) AS max_wehago_keys,
|
|
COALESCE(MAX(erp_drafts), 0) AS max_erp_drafts,
|
|
COALESCE(MAX(row_count), 0) AS max_rows,
|
|
COALESCE(AVG(row_count), 0) AS avg_rows
|
|
FROM per_group
|
|
""",
|
|
(start_year, end_year, signature, status_key),
|
|
).fetchone()
|
|
return {
|
|
"groups": int(row["groups"] or 0),
|
|
"mixed_wehago_groups": int(row["mixed_wehago_groups"] or 0),
|
|
"multi_erp_groups": int(row["multi_erp_groups"] or 0),
|
|
"max_wehago_keys": int(row["max_wehago_keys"] or 0),
|
|
"max_erp_drafts": int(row["max_erp_drafts"] or 0),
|
|
"max_rows": int(row["max_rows"] or 0),
|
|
"avg_rows": round(float(row["avg_rows"] or 0), 2),
|
|
}
|
|
|
|
|
|
def sample_mixed_groups(
|
|
conn: sqlite3.Connection,
|
|
start_year: int,
|
|
end_year: int,
|
|
signature: str,
|
|
status_key: str,
|
|
limit: int,
|
|
) -> list[dict[str, object]]:
|
|
rows = conn.execute(
|
|
"""
|
|
SELECT
|
|
g.group_index,
|
|
g.ledger_date,
|
|
g.voucher_no,
|
|
COUNT(DISTINCT CASE
|
|
WHEN COALESCE(r.ledger_account_name, '') <> ''
|
|
OR COALESCE(r.ledger_desc, '') <> ''
|
|
OR ABS(COALESCE(r.ledger_debit, 0)) >= 0.5
|
|
OR ABS(COALESCE(r.ledger_credit, 0)) >= 0.5
|
|
THEN COALESCE(r.ledger_date, '') || '|' || COALESCE(r.voucher_no, '')
|
|
END) AS wehago_keys,
|
|
COUNT(*) AS row_count
|
|
FROM wehago_compare_query_groups g
|
|
JOIN wehago_compare_query_rows r
|
|
ON r.start_year = g.start_year
|
|
AND r.end_year = g.end_year
|
|
AND r.signature = g.signature
|
|
AND r.status_key = g.status_key
|
|
AND r.group_index = g.group_index
|
|
WHERE g.start_year = ?
|
|
AND g.end_year = ?
|
|
AND g.signature = ?
|
|
AND g.status_key = ?
|
|
GROUP BY g.group_index
|
|
HAVING wehago_keys > 1
|
|
ORDER BY wehago_keys DESC, row_count DESC, g.group_index ASC
|
|
LIMIT ?
|
|
""",
|
|
(start_year, end_year, signature, status_key, limit),
|
|
).fetchall()
|
|
return [dict(row) for row in rows]
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Validate WEHAGO voucher projection anchoring.")
|
|
parser.add_argument("--start-year", type=int, default=2025)
|
|
parser.add_argument("--end-year", type=int, default=2025)
|
|
parser.add_argument("--signature", default="")
|
|
parser.add_argument("--sample", type=int, default=5)
|
|
parser.add_argument("--statuses", nargs="*", default=list(DEFAULT_STATUSES))
|
|
args = parser.parse_args()
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
signature = args.signature or latest_signature(conn, args.start_year, args.end_year)
|
|
if not signature:
|
|
raise SystemExit("No query projection signature found.")
|
|
print({"db": str(DB_PATH), "signature": signature})
|
|
for status_key in args.statuses:
|
|
summary = status_summary(conn, args.start_year, args.end_year, signature, status_key)
|
|
print({status_key: summary})
|
|
samples = sample_mixed_groups(
|
|
conn,
|
|
args.start_year,
|
|
args.end_year,
|
|
signature,
|
|
status_key,
|
|
args.sample,
|
|
)
|
|
if samples:
|
|
print({"mixed_samples": status_key, "rows": samples})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|