Files
clientflow_backend/scripts/sync_external_reconciliation.py
2026-06-09 22:55:58 +01:00

78 lines
3.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Sync external systems into reconciliation candidates.
Examples:
PYTHONPATH=. python scripts/sync_external_reconciliation.py --all
PYTHONPATH=. python scripts/sync_external_reconciliation.py --jasmin --limit 50 --days 3
PYTHONPATH=. python scripts/sync_external_reconciliation.py --odoo --days 3
PYTHONPATH=. python scripts/sync_external_reconciliation.py --customers --limit 200
The full --all pipeline first creates/updates fiscal customers from Jasmin/Odoo
and then stages reconciliation_items. It never creates opportunities or confirms
payments, so the operator can link/create/ignore in the UI.
"""
from __future__ import annotations
import argparse
import asyncio
import json
from typing import Any, Dict, List
from app.external_reconciliation_sync import (
sync_all_external_reconciliation_candidates,
sync_external_fiscal_customers_for_reconciliation,
sync_jasmin_reconciliation_candidates,
sync_odoo_reconciliation_candidates,
sync_packlink_reconciliation_candidates,
)
def _print(result: Dict[str, Any]) -> None:
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
async def main() -> None:
parser = argparse.ArgumentParser(description="Sync external APIs into ClientFlow reconciliation candidates.")
parser.add_argument("--all", action="store_true", help="sync all enabled external systems")
parser.add_argument("--jasmin", action="store_true", help="sync Jasmin quotations/invoices")
parser.add_argument("--odoo", action="store_true", help="sync Odoo sale orders")
parser.add_argument("--packlink", action="store_true", help="sync Packlink shipments")
parser.add_argument("--customers", action="store_true", help="seed fiscal customers from Jasmin/Odoo before document reconciliation")
parser.add_argument("--limit", type=int, default=50, help="maximum records per source")
parser.add_argument("--days", type=int, default=3, help="lookback window for Jasmin/Odoo/Packlink syncs")
args = parser.parse_args()
if args.all or not (args.jasmin or args.odoo or args.packlink or args.customers):
_print(await sync_all_external_reconciliation_candidates(limit=args.limit, days=args.days))
return
if args.customers and not (args.jasmin or args.odoo or args.packlink):
_print(await sync_external_fiscal_customers_for_reconciliation(limit=max(args.limit, 200)))
return
results: List[Dict[str, Any]] = []
customer_result = None
if args.customers:
customer_result = await sync_external_fiscal_customers_for_reconciliation(limit=max(args.limit, 200))
if args.jasmin:
results.append(await sync_jasmin_reconciliation_candidates(limit=args.limit, days=args.days))
if args.odoo:
# Odoo XML-RPC client is sync; the service itself stays sync for easier reuse.
results.append(sync_odoo_reconciliation_candidates(limit=args.limit, days=args.days))
if args.packlink:
results.append(await sync_packlink_reconciliation_candidates(limit=args.limit, days=args.days))
output = {
"seen": sum(int(r.get("seen") or 0) for r in results),
"created_or_updated": sum(int(r.get("created_or_updated") or 0) for r in results),
"results": results,
}
if customer_result is not None:
output["customer_seen"] = customer_result.get("seen", 0)
output["customers_created_or_updated"] = customer_result.get("created_or_updated", 0)
output["customer_results"] = customer_result.get("results", [])
_print(output)
if __name__ == "__main__":
asyncio.run(main())