80 lines
3.4 KiB
Python
Executable File
80 lines
3.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Ignore reconciliation items outside the intended working window.
|
|
|
|
Dry-run by default. Use this after an external sync staged too much history.
|
|
|
|
Examples:
|
|
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --days 3
|
|
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --days 3 --apply
|
|
PYTHONPATH=. python scripts/cleanup_stale_reconciliation_items.py --source odoo --days 3 --apply
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
|
|
from sqlalchemy import text
|
|
|
|
from app.db import engine
|
|
from app.reconciliation_service import (
|
|
cleanup_reconciliation_outside_window,
|
|
ensure_reconciliation_schema,
|
|
)
|
|
|
|
|
|
def _preview(*, days: int, source_system: str | None, limit: int) -> dict:
|
|
ensure_reconciliation_schema()
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
days = max(int(days or 3), 1)
|
|
cutoff = (datetime.now(timezone.utc).date() - timedelta(days=days)).isoformat()
|
|
params = {"cutoff": cutoff, "limit": int(limit)}
|
|
source_sql = ""
|
|
if source_system:
|
|
source_sql = "AND source_system = :source_system"
|
|
params["source_system"] = source_system
|
|
|
|
with engine.begin() as conn:
|
|
rows = conn.execute(text(f"""
|
|
SELECT id::text, source_system, external_type, document_number,
|
|
customer_name, document_date, title, status
|
|
FROM reconciliation_items
|
|
WHERE status IN ('open', 'needs_review')
|
|
{source_sql}
|
|
AND document_date IS NOT NULL
|
|
AND document_date < CAST(:cutoff AS DATE)
|
|
ORDER BY document_date ASC, updated_at DESC
|
|
LIMIT :limit
|
|
"""), params).mappings().all()
|
|
return {"days": days, "cutoff": cutoff, "source_system": source_system or "all", "matched": len(rows), "items": [dict(r) for r in rows]}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Ignore stale reconciliation candidates outside a recent working window.")
|
|
parser.add_argument("--jasmin", action="store_true", help="same as --source jasmin")
|
|
parser.add_argument("--source", choices=["jasmin", "odoo", "packlink", "manual"], help="only clean one source system")
|
|
parser.add_argument("--days", type=int, default=3, help="keep open items from the last N days")
|
|
parser.add_argument("--limit", type=int, default=1000, help="maximum rows to inspect/update")
|
|
parser.add_argument("--apply", action="store_true", help="apply changes; otherwise dry-run")
|
|
args = parser.parse_args()
|
|
|
|
source = "jasmin" if args.jasmin else args.source
|
|
if args.apply:
|
|
result = cleanup_reconciliation_outside_window(days=args.days, source_system=source, limit=args.limit, actor="cleanup_stale_reconciliation_items")
|
|
else:
|
|
result = _preview(days=args.days, source_system=source, limit=args.limit)
|
|
|
|
print(f"Janela operacional: manter itens >= {result['cutoff']} ({result['days']} dias)")
|
|
print(f"Fonte: {result['source_system']}")
|
|
print(f"Encontrados para ignorar: {result['matched']}")
|
|
for row in result.get("items", [])[:50]:
|
|
print(f"- {row.get('document_date')} · {row.get('source_system')} · {row.get('external_type')} · {row.get('document_number')} · {row.get('customer_name') or ''}")
|
|
|
|
if args.apply:
|
|
print(f"Aplicado: {result['ignored']} itens marcados como ignored")
|
|
else:
|
|
print("Dry-run. Para aplicar, repetir com --apply")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|