"""Offline example: reconcile payments and shipments without losing exceptions. CSV columns: order_id, counterparty_id, amount, plus payment_id / shipment_id. Repeated event IDs are quarantined for review, not silently counted twice. Run: python3 reconcile_registers.py payments.csv shipments.csv result.csv """ import csv import sys from collections import Counter, defaultdict from decimal import Decimal KEYS = ("order_id", "counterparty_id") def read_register(filename): with open(filename, encoding="utf-8-sig", newline="") as source: return list(csv.DictReader(source)) def _totals(rows, event_id, keys): counts = Counter(row[event_id] for row in rows) amounts = defaultdict(Decimal) present, duplicates = set(), set() for row in rows: key = tuple(row[field].strip() for field in keys) if not all(key) or not row[event_id].strip(): raise ValueError("Missing reconciliation key or event ID") amount = Decimal(row["amount"]) if not amount.is_finite(): raise ValueError("Non-finite amount") present.add(key) if counts[row[event_id]] > 1: duplicates.add(key) else: amounts[key] += amount return amounts, present, duplicates def reconcile(payments, shipments, keys=KEYS, ids=("payment_id", "shipment_id")): paid, pkeys, pdups = _totals(payments, ids[0], keys) shipped, skeys, sdups = _totals(shipments, ids[1], keys) rows = [] for key in sorted(pkeys | skeys): duplicate = key in pdups or key in sdups delta = None if duplicate: status = "Проверить дубли" elif key not in pkeys: status = "Нет оплаты" elif key not in skeys: status = "Нет отгрузки" else: delta = paid[key] - shipped[key] status = ("Частичная оплата" if delta < 0 else "Оплата больше отгрузки" if delta > 0 else "Совпало") rows.append({ **dict(zip(keys, key)), "paid": "" if key not in pkeys or key in pdups else str(paid[key]), "shipped": "" if key not in skeys or key in sdups else str(shipped[key]), "delta": "" if delta is None else str(delta), "status": status, }) return rows def write_csv(filename, rows): with open(filename, "w", encoding="utf-8-sig", newline="") as target: writer = csv.DictWriter(target, fieldnames=[*KEYS, "paid", "shipped", "delta", "status"]) writer.writeheader() writer.writerows(rows) if __name__ == "__main__": if len(sys.argv) != 4: raise SystemExit("Usage: reconcile_registers.py payments.csv shipments.csv result.csv") write_csv(sys.argv[3], reconcile(read_register(sys.argv[1]), read_register(sys.argv[2])))