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)}")