Spaces:
Running
Running
File size: 5,222 Bytes
0ebef7d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | """
make_complex_pdf.py — generate a BIG, complex, multi-page bank statement PDF
that must still reconcile and pass the (unedited) converter.
Complexity: ~120 transactions across multiple pages, large comma-separated
amounts, long/messy descriptions, mixed debits & credits. Balances are computed
in integer cents so reconciliation is exact (no float drift). The header row is
repeated on every page so the unedited converter parses every page's table.
"""
from __future__ import annotations
import random
from pathlib import Path
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
)
HERE = Path(__file__).resolve().parent
OUT_PDF = HERE / "complex_statement.pdf"
random.seed(42)
MERCHANTS = [
"AMAZON MARKETPLACE EU", "WOOLWORTHS 1234 SYDNEY NSW", "UBER *TRIP HELP.UBER.COM",
"DIRECT DEBIT - AGL ENERGY LTD", "SALARY - ACME CORP PTY LTD PAYROLL",
"TFR TO 062-000 12345678 RENT", "PAYPAL *STEAMGAMES 35314369001",
"BPAY To ASIC INV 9920183", "STRIPE TRANSFER ST-X8K2Q9", "ATM WDL CBA GEORGE ST",
"INTEREST PAID - CMA ACCOUNT", "NETFLIX.COM SUBSCRIPTION", "COLES 0456 CHATSWOOD",
"INTL TXN GITHUB.COM USD FEE", "DIVIDEND - VAS ETF DISTRIBUTION",
"TFR FROM SAVINGS 062-111 99887766", "QANTAS AIRWAYS DOMESTIC FARE",
"MEDICARE BENEFIT DEPOSIT", "OPTUS MOBILE POSTPAID 0400111222",
"REFUND - APPLE STORE ONLINE",
]
REFS = ["REF", "INV", "TXN", "ORD", "PMT", "RCPT"]
def gen_transactions(n=120):
txns = []
day = 1
month = 1
year = 2025
for i in range(n):
# advance the date a little each row
day += random.randint(0, 3)
if day > 28:
day -= 28
month += 1
if month > 12:
month = 1
year += 1
merchant = random.choice(MERCHANTS)
ref = f"{random.choice(REFS)}{random.randint(10000, 99999)}"
desc = f"{merchant} {ref}"
is_credit = random.random() < 0.42
if "SALARY" in merchant or "DIVIDEND" in merchant or "INTEREST" in merchant or "REFUND" in merchant or "BENEFIT" in merchant:
is_credit = True
if "BPAY" in merchant or "ATM" in merchant or "DIRECT DEBIT" in merchant or "RENT" in merchant:
is_credit = False
cents = random.randint(150, 850000) # $1.50 .. $8,500.00
txns.append({
"date": f"{day:02d}/{month:02d}/{year}",
"description": desc,
"debit_c": 0 if is_credit else cents,
"credit_c": cents if is_credit else 0,
})
return txns
def money(cents: int) -> str:
return f"${cents / 100:,.2f}"
def build_pdf(txns, out_path: Path, opening_cents: int = 5_000_000):
styles = getSampleStyleSheet()
doc = SimpleDocTemplate(
str(out_path), pagesize=A4,
topMargin=16 * mm, bottomMargin=16 * mm,
leftMargin=14 * mm, rightMargin=14 * mm,
)
running = opening_cents
rows = [["Date", "Description", "Debit", "Credit", "Balance"]]
for t in txns:
running += t["credit_c"] - t["debit_c"]
tag = "CR" if running >= 0 else "DR"
rows.append([
t["date"],
t["description"],
money(t["debit_c"]) if t["debit_c"] else "",
money(t["credit_c"]) if t["credit_c"] else "",
f"{money(abs(running))} {tag}",
])
closing = running
period = f"{txns[0]['date']} to {txns[-1]['date']}"
story = [
Paragraph("<b>Macquarie Cash Management Account</b>", styles["Title"]),
Spacer(1, 6),
Paragraph("Account: 987654321 - BUSINESS CASH MANAGEMENT ACCOUNT", styles["Normal"]),
Paragraph("Account Holder: COMPLEXICORP HOLDINGS PTY LTD", styles["Normal"]),
Paragraph(f"Statement Period: {period}", styles["Normal"]),
Paragraph(f"Opening Balance: {money(opening_cents)}", styles["Normal"]),
Paragraph(f"Closing Balance: {money(closing)}", styles["Normal"]),
Spacer(1, 10),
]
tbl = Table(
rows,
colWidths=[22 * mm, 78 * mm, 26 * mm, 26 * mm, 30 * mm],
repeatRows=1, # repeat header on every page so each page's table parses
)
tbl.setStyle(TableStyle([
("GRID", (0, 0), (-1, -1), 0.4, colors.grey),
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#1f3a5f")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, -1), 7),
("ALIGN", (2, 0), (-1, -1), "RIGHT"),
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
]))
story.append(tbl)
doc.build(story)
credits = sum(t["credit_c"] for t in txns)
debits = sum(t["debit_c"] for t in txns)
return closing, credits, debits
if __name__ == "__main__":
txns = gen_transactions(120)
closing, credits, debits = build_pdf(txns, OUT_PDF)
print(f"Wrote {OUT_PDF}")
print(f"{len(txns)} transactions")
print(f"opening $50,000.00 + credits {money(credits)} - debits {money(debits)} = closing {money(closing)}")
|