Statement of Account
What it is
The screen that produces a statement of account for one client on one date: what they owe, split into age buckets, with every invoice, payment and credit note that makes up the figure. From here you email it, download it as Excel, or print it to PDF.
It reads the ledger_entries view, which already serves an invoice's balance gross of credits
and each credit note as its own full credit line. Nothing on this screen recomputes that.
Who can open it
Needs the finance.ledger feature grant while FEATURE_GRANULAR_RBAC is on. The owner bypasses
the grant unconditionally. While that flag is off, any signed-in account can open the screen by
typing the address — the sidebar hides it, but hiding is not a gate.
Before you start
The Print / PDF output has the company header written into the component: the GST number
33AUQPA5216Q1ZC, supply@a2zshipstores.com, purchase@a2zshipstores.com, two Chennai phone
numbers, www.a2zshipstores.com, "Chennai - 600001" and /logo.png. SOALedger is not given the
entity at all, so a statement for a Marella Marine or Marsea Ship Chandlers client prints
another company's name, tax number and bank contacts.
Do not send a printed statement to a client of any entity other than A2Z Ship Stores. The Download Excel and Send via Resend paths are built server-side from the selected client's own entity and are not affected.
Nothing appears until you choose a client. The screen loads with "Awaiting Instruction" and no
figures. Choosing a client and pressing Execute Generation reloads the page with ?client_id=
and ?date= in the address, so a statement is a link you can share or bookmark.
The statement is in the client's own currency and is never converted. A USD client's statement is in USD, an INR client's in INR. Conversion at the auditor's monthly rate exists for Tally and the accounting exports only — it is deliberately kept off customer-facing statements (owner decision, 2026-08-19). A2Z-F263 "Multi-currency SOA view" is recorded as PARTIALLY IMPLEMENTED because of this: the component still exists in the codebase but is not mounted on the page.
Age buckets are measured from the client's payment terms, not from the invoice date. A client on 60-day terms has an invoice 70 days old sitting in 1-30, not 61-90.
Every control on the page
The button strip, left to right:
| Control | What it does |
|---|---|
| + Issue Credit Note | Goes to New Credit Note |
| Manage Credit Notes | Goes to Credit Notes |
| Manage Wired Payments | Goes to Wired Payments |
| ⚡ Automated Import | Goes to /import, carrying ?client_id= when a client is selected |
| Batch Dispatch | Emails statements to several clients at once |
| Bulk Export | Downloads statements for several clients at once |
The generator form:
| Control | What it does |
|---|---|
| Target Entity | The client, alphabetically. Reads "— SELECT COUNTERPARTY —" until you pick one. Despite the label this is the client, not one of your own companies |
| Snapshot Date | The date the statement is drawn to. Defaults to today |
| Execute Generation | Submits the form as a GET, so the result is addressable |
Once a statement is generated:
- Escalation Protocol Active — a red banner, shown only when something has passed 90 days, naming the amount.
- Five bucket cards — Current, 1-30, 31-60, 61-90, 90+, each in the client's currency. A bucket with nothing in it is greyed.
- The ledger — every entry, with Ref # and Date sortable; click the heading, click again to reverse.
- Four actions at the foot: 📊 Download Excel, ✉️ Send via Resend, 🖨️ Print / PDF, and ✉️ Open in Mail App which appears only as a fallback.
⚠️ Send via Resend is disabled when the client has no email address, with the tooltip "Add client email first in Clients page". It reads ✓ Sent! afterwards and stays disabled, so a genuine resend needs a page reload.
What it writes
Generating a statement writes nothing. It is a read of ledger_entries.
🔴 The ledger deletes invoices, and it is the only thing on this screen that writes.
- The ✕ at the end of an invoice row deletes that invoice — "This will permanently remove the invoice and affect the SOA aging totals."
- Ticking rows shows Delete Selected (N), which deletes them all.
Both cascade to credit notes. The bulk prompt counts them first and says so: "Delete N invoice(s)? This will ALSO permanently delete N linked credit note(s)." There is no undo, and a deleted invoice is gone from every other screen too — this is not a statement-only action.
⚠️ Only invoice rows carry a ✕. Payments and credit notes on the statement cannot be removed from here.
Send via Resend sends through /api/send-soa; Download Excel builds the file in
/api/exports/soa, which recomputes the balances server-side from ledger_entries and takes only
ids from the browser.
When it goes wrong
"Clearance Status: Positive" when you expect a balance. The client has no rows in
ledger_entries at all. Check you picked the right client — the list is every client across every
entity, and two companies can have similar names.
The total does not match the invoice list you were expecting. The statement is drawn to the Snapshot Date. An invoice raised after that date is not on it.
A credit note appears to be applied twice. It should not — invoices come through gross of credits and each credit note as its own line, which is what the aging calculation expects. If you see a double deduction, report it: synthesising credit-note rows separately is a bug this page has had before.
Send via Resend is greyed out. The client has no email address. Add one on Clients.
The printed PDF names the wrong company. Expected — see the warning at the top. Use Excel.
Related screens
- Reconcile Payment — allocating a receipt across invoices.
- Wired Payments — receipts in transit.
- Credit Notes — issuing and cancelling credits.
- Invoices — where the debit side comes from.
- Clients — payment terms, currency and the email address used above.