#!/usr/bin/env bash # Monthly cash-receipt aggregation for KissMetrics payments. # # Usage: # payments-by-month.sh [--year YYYY] [--all-clients] # # By default only sums KissMetrics payments (socid=1). With --all-clients, # iterates every visible invoice. Useful as the foundation for the # cohort-review deferred-cycle dashboard (sum-by-month + count-by-month). set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOL_CURL="${SCRIPT_DIR}/../../dolibarr/scripts/dol-curl.sh" KM_SOCID=1 ALL=0 YEAR="" while [[ $# -gt 0 ]]; do case "$1" in --year) YEAR="$2"; shift 2 ;; --all-clients) ALL=1; shift ;; -h|--help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) echo "payments-by-month.sh: unknown arg: $1" >&2; exit 2 ;; esac done WORK="$(mktemp -d -t pmonth.XXXXXX)" trap 'rm -rf "${WORK}"' EXIT "${DOL_CURL}" '/invoices?limit=100&sortfield=t.datef&sortorder=DESC' > "${WORK}/inv.json" # Filter invoice ids if [[ "${ALL}" == "1" ]]; then IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1]))))" "${WORK}/inv.json") else IDS=$(python3 -c "import json,sys; print(' '.join(str(r['id']) for r in json.load(open(sys.argv[1])) if str(r.get('socid'))==sys.argv[2]))" "${WORK}/inv.json" "${KM_SOCID}") fi mkdir -p "${WORK}/pay" for id in ${IDS}; do "${DOL_CURL}" "/invoices/${id}/payments" > "${WORK}/pay/${id}.json" done python3 - "${WORK}" "${YEAR}" "${ALL}" "${KM_SOCID}" <<'PY' import json, sys, os, collections, datetime work, year, all_clients, km_socid = sys.argv[1], sys.argv[2], sys.argv[3]=="1", sys.argv[4] invoices = {str(r["id"]): r for r in json.load(open(os.path.join(work,"inv.json")))} monthly = collections.defaultdict(lambda: {"count":0, "amount":0.0}) for fn in sorted(os.listdir(os.path.join(work,"pay"))): iid = fn[:-len(".json")] inv = invoices.get(iid) if not inv: continue if not all_clients and str(inv.get("socid")) != km_socid: continue try: pays = json.load(open(os.path.join(work,"pay",fn))) except json.JSONDecodeError: continue for p in pays: d = datetime.datetime.strptime(p["date"], "%Y-%m-%d %H:%M:%S").date() if year and d.strftime("%Y") != year: continue key = d.strftime("%Y-%m") monthly[key]["count"] += 1 monthly[key]["amount"] += float(p.get("amount") or 0) scope = "all clients" if all_clients else f"KissMetrics (socid={km_socid})" print(f"# Monthly cash receipts — scope: {scope}" + (f" — year {year}" if year else "")) print() print(f"{'month':<8} {'count':>5} {'amount':>12} cumulative") print("-" * 50) cum = 0.0 for key in sorted(monthly): s = monthly[key] cum += s["amount"] print(f"{key:<8} {s['count']:>5} {s['amount']:>12.2f} {cum:>10.2f}") print("-" * 50) total_count = sum(v["count"] for v in monthly.values()) total_amount = sum(v["amount"] for v in monthly.values()) print(f"{'TOTAL':<8} {total_count:>5} {total_amount:>12.2f}") PY