#!/usr/bin/env python3 import argparse import csv import json from datetime import datetime, timezone from decimal import Decimal from pathlib import Path OUTPUT_FIELDS = ( "invoice_id", "tenant_id", "status", "invoice_type", "invoice_date", "created_at", "sent_date", "cost_price", "raw_price", "payment_method_id", "payment_type", "payment_code", "binded_billing_config_id", "parent_invoice_id", ) def parse_timestamp(value: str) -> datetime: parsed = datetime.fromisoformat(value.replace(" ", "T")) return parsed.replace(tzinfo=timezone.utc) if parsed.tzinfo is None else parsed def iso_timestamp(value: str) -> str | None: if not value or value == "NULL": return None return parse_timestamp(value).isoformat() def decimal_string(value: str) -> str: return format(Decimal(value).normalize(), "f") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--input", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--tenant-id", required=True) parser.add_argument("--period-start", required=True) parser.add_argument("--period-end", required=True) args = parser.parse_args() period_start = parse_timestamp(args.period_start) period_end = parse_timestamp(args.period_end) records = [] with args.input.open(encoding="utf-8", newline="") as handle: for row in csv.DictReader(handle): created_at = parse_timestamp(row["created_at"]) if row["tenant_id"] != args.tenant_id: continue if not period_start <= created_at < period_end: continue if row.get("deleted_at") not in (None, "", "NULL"): continue records.append( { "invoice_id": row["id"], "tenant_id": row["tenant_id"], "status": row["status"], "invoice_type": row["type"], "invoice_date": iso_timestamp(row["invoice_date"]), "created_at": created_at.isoformat(), "sent_date": iso_timestamp(row["sent_date"]), "cost_price": decimal_string(row["cost_price"]), "raw_price": decimal_string(row["raw_price"]), "payment_method_id": row.get("payment_method_id") or None, "payment_type": row.get("payment_type") or None, "payment_code": row.get("payment_code") or None, "binded_billing_config_id": row.get("binded_billing_config_id") or None, "parent_invoice_id": row.get("parent_invoice_id") or None, } ) records.sort(key=lambda record: (record["created_at"], record["invoice_id"])) args.output.parent.mkdir(parents=True, exist_ok=True) with args.output.open("w", encoding="utf-8") as handle: for record in records: handle.write(json.dumps({field: record[field] for field in OUTPUT_FIELDS}) + "\n") return 0 if __name__ == "__main__": raise SystemExit(main())