"""Export two GBP lists: per-stock-code summary + per-line items.""" import os, json, urllib.request from pathlib import Path from collections import defaultdict env = Path(__file__).resolve().parent.parent / ".env.local" PB_TOKEN = None for line in open(env): if line.startswith("PB_TOKEN="): PB_TOKEN = line.strip().split("=", 1)[1] break H = {"Authorization": PB_TOKEN} # Load stock codes sc = json.loads(urllib.request.urlopen(urllib.request.Request( "https://pb.agent.apphub.co.za/api/collections/JEI_stock_codes/records?perPage=500&sort=code", headers=H)).read()) codes = {c["id"]: c for c in sc["items"]} # Load invoices inv = json.loads(urllib.request.urlopen(urllib.request.Request( "https://pb.agent.apphub.co.za/api/collections/JEI_invoices/records?perPage=500&sort=-invoice_date", headers=H)).read()) invs = {i["id"]: i for i in inv["items"]} # Load lines lines = json.loads(urllib.request.urlopen(urllib.request.Request( "https://pb.agent.apphub.co.za/api/collections/JEI_invoice_lines/records?perPage=5000&sort=invoice", headers=H)).read()) # --- LIST 1: per-stock-code aggregated --- agg = defaultdict(lambda: {"qty": 0.0, "value": 0.0, "prices": []}) for l in lines["items"]: sc_id = l["stock_code"] if sc_id not in codes: continue agg[sc_id]["qty"] += l["quantity"] agg[sc_id]["value"] += l["net_amount"] if l["unit_price"]: agg[sc_id]["prices"].append(l["unit_price"]) # --- LIST 2: per-line items, sorted by invoice then line_no --- inv_lines = [] for l in lines["items"]: sc_id = l["stock_code"] if sc_id not in codes: continue inv_id = l["invoice"] inv_obj = invs.get(inv_id, {}) inv_lines.append({ "invoice_date": inv_obj.get("invoice_date", "")[:10], "invoice_no": inv_obj.get("invoice_no", ""), "line_no": l["line_no"], "stock_code": codes[sc_id]["code"], "description": codes[sc_id]["description_goods"], "qty": l["quantity"], "unit": l["unit_price"], "net": l["net_amount"], }) # Output print("=" * 130) print("LIST 1 — PER-STOCK-CODE SUMMARY (aggregated across all invoices)") print("=" * 130) print(f"{'Stock code':<22} {'Description':<55} {'Qty':>8} {'Avg £':>9} {'Min £':>9} {'Max £':>9} {'Total £':>11}") print("-" * 130) total_qty = 0 total_val = 0 for sc_id in sorted(agg.keys(), key=lambda i: codes[i]["code"]): c = codes[sc_id] s = agg[sc_id] avg = (sum(s["prices"]) / len(s["prices"])) if s["prices"] else 0 mn = min(s["prices"]) if s["prices"] else 0 mx = max(s["prices"]) if s["prices"] else 0 total_qty += s["qty"] total_val += s["value"] desc = (c["description_goods"] or "")[:54] print(f"{c['code']:<22} {desc:<55} {s['qty']:>8.1f} {avg:>9.2f} {mn:>9.2f} {mx:>9.2f} {s['value']:>11.2f}") print("-" * 130) print(f"{'TOTAL':<22} {'':<55} {total_qty:>8.1f} {'':>9} {'':>9} {'':>9} {total_val:>11.2f}") print() print("=" * 130) print("LIST 2 — PER-LINE ITEMS (every invoice line, sorted by invoice date then line number)") print("=" * 130) print(f"{'Date':<12} {'Invoice':<10} {'#':>4} {'Stock code':<18} {'Description':<45} {'Qty':>6} {'Unit £':>9} {'Net £':>10}") print("-" * 130) inv_lines.sort(key=lambda x: (x["invoice_date"], x["invoice_no"], x["line_no"])) for r in inv_lines: desc = (r["description"] or "")[:44] print(f"{r['invoice_date']:<12} {r['invoice_no']:<10} {r['line_no']:>4} {r['stock_code']:<18} {desc:<45} {r['qty']:>6.1f} {r['unit']:>9.2f} {r['net']:>10.2f}") print("-" * 130) print(f" Total lines: {len(inv_lines)}")