Finance-Accounts/ar-aging-app/docs/system-guide.md

490 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Amazon A/R Aging Dashboard — System Guide
Complete reference for how the month-end Amazon Accounts Receivable system works: the flow,
every module, every screen, every calculation, and every rule.
> **Benchmark:** the engine reproduces the Finance team's manual workbook exactly —
> **USA January 2026 = 11,110,433** (`Detail!D11`), and all **13 marketplaces reconcile to the penny**.
---
## 1. What this system does
Finance downloads Amazon **Custom Unified Transaction** reports each month (one or more files per
marketplace). Previously they hand-built a 283 MB Excel workbook to answer one question:
> **How much does Amazon still owe us at month-end?**
This system automates that end to end: upload the files → it parses, classifies, calculates,
reconciles → you get a dashboard and a downloadable audit workbook.
**Scope:** Amazon only (no Walmart/TikTok/Temu/Shein/eBay/Shopify). 13 marketplaces supported.
---
## 2. Quick start
```bash
# both servers (from the project root)
python3 -m uvicorn app.api.main:app --app-dir ar-aging-app/backend --reload --port 8000
npm --prefix ar-aging-app/frontend run dev # http://localhost:5173
```
Or use the preview launcher, which starts both from `.claude/launch.json`.
```bash
# headless engine run
cd ar-aging-app/backend
python cli.py "/path/file1.xlsx" "/path/file2.xlsx" --month-end 2026-01-31 --lag 2
# tests
python -m pytest tests -m "not integration" # 64 fast tests
python -m pytest tests -m integration # 12 tests against the real sample files
```
---
## 3. End-to-end flow
```mermaid
flowchart TD
A["Amazon reports<br/>(.xlsx per marketplace)"] --> B[Upload & validate]
B --> C[Parse & normalize]
C --> D[Aggregate by settlement]
D --> E[Classify paid vs receivable]
E --> F[Compute receivable]
F --> G[Journal decomposition]
G --> H[(SQLite<br/>persisted results)]
H --> I[Dashboard tabs]
H --> J[Excel exports]
K[Finance inputs<br/>opening balance · FX · clearing-lag<br/>reserve · control amounts] --> E
K --> F
K --> I
style H fill:#ECEAFC,stroke:#6D5DE8
style I fill:#E7E5FB,stroke:#6D5DE8
style J fill:#E7E5FB,stroke:#6D5DE8
```
### The nine processing stages
Each stage reports live progress (rows done / total + ETA) to the dashboard.
```mermaid
flowchart LR
S1[1 Reading<br/>workbook] --> S2[2 Validating<br/>columns]
S2 --> S3[3 Combining<br/>transactions]
S3 --> S4[4 Detecting<br/>duplicates]
S4 --> S5[5 Calculating<br/>settlements]
S5 --> S6[6 Creating<br/>receivable]
S6 --> S7[7 Reconciling<br/>totals]
S7 --> S8[8 Persisting<br/>transactions]
S8 --> S9[9 Building<br/>journal entry]
```
| # | Stage | What happens |
|---|---|---|
| 1 | **Reading workbook** | Opens each file, auto-detects the raw data sheet (ignores user-made pivot tabs) |
| 2 | **Validating columns** | Maps localized headers to canonical fields; flags unmapped/missing |
| 3 | **Combining transactions** | Streams all files into one normalized record set |
| 4 | **Detecting duplicates** | Fingerprints rows; counts repeats (never deletes them) |
| 5 | **Calculating settlements** | Groups by settlement id; marks each paid or receivable |
| 6 | **Creating receivable** | Sums open settlements; applies reserve, rounding and FX |
| 7 | **Reconciling totals** | Checks the identity `uploaded = receivable + paid + transfers` |
| 8 | **Persisting transactions** | Bulk-inserts every row with full source traceability |
| 9 | **Building journal entry** | Decomposes activity into GL accounts, per marketplace |
---
## 4. The calculation — four rules
Everything the dashboard shows derives from these. **`total` (column AD) is the net amount of each
row** and is the figure that gets summed.
### Rule 1 — Settlement classification (which money is still owed)
Amazon pays in settlement batches. When a batch closes, the next opens and Amazon disburses the
prior one via a **`Transfer`** row *tagged with the next batch's id*.
> A settlement stays **receivable** until the payout that clears it has actually **reached the bank**
> by month-end.
```mermaid
flowchart TD
T["Transfer row<br/>(a bank payout)"] --> D{"payout date ≤<br/>month_end clearing_lag?"}
D -->|Yes| R["RECEIVED<br/>→ sets the paid boundary"]
D -->|No| I["IN TRANSIT<br/>→ its settlement stays receivable"]
R --> B["paid_boundary = max settlement_id<br/>over received payouts"]
B --> C{"settlement_id ≥ boundary?"}
C -->|Yes| RECV[RECEIVABLE]
C -->|No| PAID[PAID]
style RECV fill:#DCFCE7,stroke:#2FA875
style PAID fill:#F1F1F6,stroke:#868AA0
style I fill:#FAF0DA,stroke:#B37A1B
```
**Bank receipts override the heuristic.** Amazon's Transfer date is when the payout was
*initiated*; the bank credit lands 3-5 working days later. On the **AR Ledger** tab Finance can
record the actual bank date (and amount) per payout — then `received ⇔ bank date ≤ month-end`,
and the Movement-by-date ledger shows the payout on its bank date. Two modes per closing:
| `payout_mode` | Payout **with** a bank receipt | Payout **without** one |
|---|---|---|
| **auto** *(default)* | bank date decides | clearing-lag heuristic on Amazon's date |
| **manual** | bank date decides | **not received** — no heuristic at all |
`clearing_lag` defaults to **2 days** and is adjustable per closing. Receipt/mode changes apply
when the closing is re-processed (a banner prompts until then). A bank amount differing from
Amazon's payout raises a `bank_amount_variance` warning; an unknown settlement id raises
`unmatched_bank_receipt`.
### Rule 2 — The receivable base
```
additional_sales = Σ total
WHERE settlement is receivable
AND account_type ∈ {Standard Orders, Invoiced Orders, All Orders}
AND type ≠ Transfer
```
### Rule 3 — Closing receivable
Two independent methods, which must agree:
```
# Settlement method (the workbook's method — the benchmark)
receivable_local = ROUND(reserve + additional_sales, 0)
receivable_usd = receivable_local × fx_rate
# Roll-forward method (the AR Ledger)
closing = opening_balance + net_revenue payouts_received
```
#### Opening balance — three modes
Chosen when the closing is created (and changeable any time on the AR Ledger tab):
| Mode | Behaviour |
|---|---|
| **Zero** *(default)* | Every marketplace opens at 0 — correct for a first-ever closing |
| **Carry forward** | Copies each marketplace's closing receivable from a chosen prior closing. Falls back to zero if no processed prior exists |
| **Manual** | Opens at 0; you type each marketplace's balance on the AR Ledger tab after processing |
### Rule 4 — Bucket identity (**not** a control)
Every uploaded row lands in exactly one bucket, so this always holds:
```
uploaded_total = receivable_orders + paid_orders + transfers_total
```
> ⚠️ **This is a tautology and must never be read as assurance.** `uploaded_total` is accumulated
> from the same record stream that fills the three buckets, so it is one sum written twice — it
> cannot fail. It cannot detect a misclassified settlement, a wrong FX rate, an unmapped column, or
> an entire file that failed to parse (a file yielding no rows contributes zero to *both* sides).
> It reported "Reconciled" on the Jan-2026 close while the group receivable was understated by
> USD 444,658.44. It is kept only as a cheap self-consistency assert.
>
> **The month-end controls in §4a are what tell you whether a close can be trusted.**
---
## 4a. Month-end controls
Six controls run automatically at the end of every close and on demand (`app/core/controls.py`,
`services/controls_run.py`). Each compares the engine's output against **something the engine did
not produce** — that independence is what lets them fail.
| Control | Compares against | Fails when |
|---|---|---|
| **C1** Source row count | the worksheet's own `<dimension>` | rows read ≠ rows the file declares — separates "this market had no sales" from "this file failed to parse" |
| **C2** Column completeness | Amazon's own `total` column | Σ(GL lines) ≠ Σ(`total`): a column unmapped, double-mapped, or newly added by Amazon |
| **C3** Bucket completeness | the receivable filter | a money-carrying order row has no recognized account type or numeric settlement id |
| **C4** Dual-method agreement | settlement method vs roll-forward | the two closing methods diverge (**warning** — a first close with zero openings legitimately differs) |
| **C5** FX confirmed | a person, for this reporting month | a rate is a seeded default, or was confirmed for a different month |
| **C6** Currency integrity | the All-Markets roll-up | the two group totals disagree — someone added currencies without converting |
| **C7** Reader equivalence | the other parser | the two readers disagree (CI-time, `tests/test_reader_equivalence.py`) |
**Blocking.** An error-severity failure puts the closing in status `blocked`: `/summary`,
`/finance-summary`, `/aging` and both Excel exports withhold the figure and return the reason
instead. A number that cannot be trusted is never displayed — a missing number cannot be posted to
the ledger, a wrong one can.
Resolve a block on the **Controls** tab (e.g. confirm the FX rates for the month), then re-run.
Every control result travels with the workbook on its own *Month-End Controls* sheet.
See [`audit-2026-07-31-code-review.md`](audit-2026-07-31-code-review.md) for the audit these controls came out of.
---
## 5. Architecture
```mermaid
flowchart TB
subgraph FE["Frontend — React + TypeScript + Vite"]
P1[Dashboard] --- P2[New Closing] --- P3[Closing tabs] --- P4[Settings]
end
subgraph API["Backend — FastAPI"]
R1[sessions/files/processing] --- R2[results/ar/control] --- R3[analytics] --- R4[export/settings]
end
subgraph CORE["Engine — pure Python, no web deps"]
C1[readers] --> C2[column_map + i18n]
C2 --> C3[pipeline]
C3 --> C4[settlements]
C4 --> C5[receivable]
C5 --> C6[movement + journal]
C6 --> C7[reconciliation + validation]
C7 --> C8[excel_export + summary_export]
end
DB[(SQLite / Postgres)]
FE -->|"/api (Vite proxy)"| API
API --> CORE
API --> DB
CORE --> DB
style CORE fill:#ECEAFC,stroke:#6D5DE8
```
**Key principle:** the engine (`app/core/`) has no web or database dependencies. It can run headless
via `cli.py`, which is what the integration tests exercise.
---
## 6. Backend modules — each one defined
### `app/core/` — the engine
| Module | Purpose |
|---|---|
| **`readers.py`** | Factory choosing the parser: `python-calamine` (fast, Rust) by default, streaming fallback via `AR_USE_CALAMINE=0` |
| **`calamine_reader.py`** | Fast parser. Detects the data sheet, skips pivot/helper rows, yields normalized records |
| **`xlsx_reader.py`** | Low-memory streaming parser (`xml.etree.iterparse`) + `FileMeta`. Used for very large files |
| **`column_map.py`** | Canonical schema and header aliases for 8 languages. Maps by **normalized name, never position** |
| **`i18n.py`** | Marketplace config: transaction-type translations, currencies, default FX, storage keywords |
| **`dates.py`** | Multilingual date parsing (11 formats) with a prefix cache — turns millions of parses into dozens |
| **`regions.py`** | Maps `amazon.de``Germany` etc. |
| **`pipeline.py`** | Orchestrates parse → enrich → aggregate → classify → receivable → reconcile, with progress callbacks |
| **`settlements.py`** | Groups rows by settlement; owner attribution; the paid/receivable classification (Rule 1) |
| **`receivable.py`** | Rules 2 and 3: additional sales, reserve, rounding, FX, aging bands |
| **`movement.py`** | Roll-forward: opening + net revenue payouts, and the AR ledger rows |
| **`journal.py`** | GL decomposition into 9 journal lines + 17 granular components, per marketplace |
| **`reconciliation.py`** | Rule 4 identity check and status |
| **`validation.py`** | File coverage: wrong month, overlapping ranges, date gaps |
| **`excel_export.py`** | Full audit workbook (streaming `write_only`, live formulas) |
| **`summary_export.py`** | Compact Finance pack (5 sheets, generates in seconds) |
| **`compare.py`** | Diffs a generated workbook against the sample for the comparison test |
### `app/api/routes/` — the web layer
| Route module | Responsibility |
|---|---|
| **`sessions.py`** | Create/list/update/delete month-end closings; seeds opening balances from the prior month |
| **`files.py`** | Chunked upload, filename sanitization, per-file parse validation |
| **`processing.py`** | Starts the background job; reports status, stage, rows done, ETA |
| **`results.py`** | Summary, settlements, transactions (filterable), aging, exceptions, journal |
| **`ar.py`** | Opening balances, `ar-movement` (roll-forward), `finance-summary` |
| **`control.py`** | Reconciliation control: dashboard vs Finance amounts, sign-off, completion gate |
| **`analytics.py`** | Date-filtered ledger, daily FX, sign-explained reconciliation detail, all-markets roll-up |
| **`export.py`** | Starts Summary/Full exports; lists and downloads them |
| **`settings.py`** | Reserves, FX rates, and global header-mapping rules |
### `app/services/`
| Module | Responsibility |
|---|---|
| **`jobs.py`** | Background processing and export jobs; owns its DB session; writes progress + ETA |
| **`store.py`** | Bulk transaction inserts (batched raw DBAPI), persists aggregates, builds exceptions |
---
## 7. Database tables
| Table | What it holds |
|---|---|
| `sessions` | One month-end closing: month, month-end date, clearing-lag, tolerance, status, progress |
| `session_files` | Uploaded files: name, size, SHA-256, sheet, rows, date range, currency, status |
| `transactions` | Every parsed row with `source_file`/`source_row` traceability, canonical type, flags |
| `settlements` | Per (marketplace, account, settlement id): order total, transfers, dates, paid/receivable |
| `receivable_results` | Per marketplace and account: additional sales, reserve, local + USD receivable |
| `reconciliation` | Identity check figures and status |
| `market_payouts` | Per-marketplace received / total payouts, attributed to each settlement's **owner** |
| `opening_balances` | Opening AR per marketplace, with source (manual / carried-forward) and reason |
| `fx_rates` | Month FX rate + currency per marketplace |
| `fx_rates_daily` | Per-date FX rates — auto-fetched from the provider at processing (hand-editable); dated movements convert at the rate effective on their transaction date (exact fixing → previous banking day's fixing → month rate) |
| `reserves` | Net Closing Balance per marketplace and account |
| `journal_entries` | The GL decomposition JSON (primary + `per_marketplace`) and entry number |
| `finance_control` | Finance's control-sheet amounts, tolerance, sign-off and comments |
| `exceptions` | Every validation finding: category, severity, detail, source |
| `mapping_rules` | Admin header aliases (global, applied to all future parses) |
| `exports` | Generated workbooks: kind, path, SHA-256, size, timestamp |
---
## 8. The dashboard — each tab defined
```mermaid
flowchart LR
OV[Overview] --> CT[Controls]
CT --> OP[Opening Balances]
OP --> UP[Upload & Mapping]
UP --> VE[Validation & Exceptions]
VE --> FS[Finance Summary]
FS --> AL[AR Ledger]
AL --> SR[Settlement Reconciliation]
SR --> AG[A/R Aging]
AG --> TD[Transaction Details]
TD --> RE[Reconciliation]
RE --> JE[Journal Entry]
JE --> EX[Excel Export]
```
| Tab | What it shows | Key controls |
|---|---|---|
| **Overview** | KPI cards (closing receivable, month-end controls verdict, settlement counts, exceptions) and receivable by marketplace | **View details →** per marketplace; **All markets →** |
| **Controls** | The six month-end controls (§4a) with pass/fail, detail and evidence; blocking state and reason | **Re-run controls** · **Confirm FX rates** for the reporting month |
| **Opening Balances** | Every marketplace's opening AR balance on one worksheet, with the roll-forward vs settlement variance each produces and the implied opening that would close it | Inline edit per market · **Carry forward all** from a prior closing · **All to zero** — saving re-runs the controls |
| **Dashboard** (home) | All closings with status and receivable | **Delete** removes the closing and every row/file it owns |
| **Upload & Mapping** | Drag-drop upload; per-file rows, date coverage, marketplace, status | **Header mapping** panel to classify unknown columns and save the rule |
| **Validation & Exceptions** | Every finding grouped by severity | — |
| **Finance Summary** | The Finance report: opening → revenue components → gross → fees → net revenue → closing, plus the ledger | Market switcher · **All Markets** sub-tab · Summary Excel |
| **AR Ledger** | Opening balance for the selected market (**Adjust · Carry forward · Set to zero** — the Opening Balances tab edits all markets at once), roll-forward statement, ledger movement, **bank receipts per payout** (bank date + amount; drives received/in-transit and re-dates the payout in Movement-by-date), cross-check vs settlement method, **movement by date**, **daily FX table** | Bank-dates-only mode toggle · Daily/Weekly/Monthly + custom range · market switcher · All Markets |
| **Settlement Reconciliation** | Every settlement with orders, transfers, rows, period and status; the disbursement list | **Clearing-lag** control + re-process |
| **A/R Aging** | Aging matrix (Current / 1-30 / 31-60 / 61-90 / 91-Over) and chart | — |
| **Transaction Details** | Virtualized grid of every row with source file + row | Filters: settlement, type, marketplace, receivable, storage, search |
| **Reconciliation** | Sign-explained metrics (Revenue / Fees / Memo / Settlements), closing & variance, plus the **Reconciliation Control** | Finance amounts, tolerance, **Verify**, **Mark month complete** |
| **Journal Entry** | The month-end **accrual** entry in double-entry form (Debit/Credit columns, Σ Dr = Σ Cr): revenue & fees per GL account with **Advertising Cost** broken out, balanced by Dr A/R = net revenue. No Transfer line — bank receipts post separately. Per-marketplace GL names, market switcher + All Markets (USD) view | Journal Entry # · **Reviewed by / Approved by** two-step sign-off — approval publishes the month to the Accounts Summary; re-processing withdraws it |
| **Accounts Summary** *(sidebar)* | Approved journal entries across **all months × marketplaces** — per-market local currency or All Markets converted at each closing's confirmed FX | Market selector · links back to each closing |
| **Excel Export** | Generate/download Summary or Full workbook, with history | Two generate buttons |
### The All Markets sub-tab
Present on Finance Summary, AR Ledger and Reconciliation. Shows every marketplace in **its own
currency** with its FX rate, plus the **combined USD total**, and a **View details** button per row.
The selected market lives in the URL (`?market=Germany`, or `?market=__all__`), so links are shareable.
---
## 9. Data model — the source columns
Amazon's report has a 7-row preamble, **header on row 8**, data from row 9. Column counts vary by
marketplace (USA 30, Canada 29, UK 27, AU/IE 23, localized EU 28/24).
**The five columns that drive everything:**
| Col | Header | Role |
|---|---|---|
| A | `date/time` | Aging, date filters, payout clearing |
| B | `settlement id` | Grouping; the paid/receivable boundary (monotonic — higher = newer) |
| C | `type` | Order / Refund / Adjustment / fees / **Transfer** |
| I | `account type` | `Standard Orders` / `Invoiced Orders` (USA only; others use one `All Orders` stream) |
| **AD** | **`total`** | **The net amount summed for every figure** |
The other 25 columns (product sales, taxes, shipping, gift wrap, promos, selling fees, FBA fees,
storage, other) feed the **revenue/fee breakdown** in the Finance Summary and Journal Entry.
---
## 10. Multi-marketplace handling
| Aspect | How it works |
|---|---|
| **Languages** | Headers, transaction types and dates are localized in FR, DE, IT, ES, NL, PL, SV, TR. All normalized to canonical English |
| **Currencies** | USD, CAD, GBP, EUR, PLN, SEK, AUD, TRY — each with a default FX-to-USD rate, editable per closing |
| **Account streams** | Only the USA report has `account type`. Other marketplaces run a single `All Orders` stream |
| **Settlement owner** | A settlement belongs to the marketplace holding most of its rows. Payouts attach to their settlement's **owner**, not the file they appear in (Belgium's payouts sit inside the Germany report) |
| **Blank marketplace rows** | Inherit the file's **dominant** marketplace — never a filename guess (critical for combined files like "Belgium Germany") |
| **Cross-market rows** | Rows in a different marketplace than their settlement's owner are excluded and flagged |
| **Helper rows** | EU files carry a Finance-added translation row under the real header — detected and skipped |
---
## 11. Validation & exception categories
No amount is ever silently excluded.
| Category | Severity | Meaning |
|---|---|---|
| `missing_column` | error | A required field is absent — must be fixed |
| `unmapped_amounts` | **error** | An unmapped column contains money. **Caught Australia's renamed FBA-fee column (2,427.43)** |
| `unmapped_column` | warning | An unrecognized header — classify it in Upload & Mapping |
| `wrong_month` | error/warning | File dates fall outside the reporting month |
| `overlapping_files` | warning | Two files of the same marketplace cover the same dates |
| `missing_dates` | warning | A gap in coverage within the month |
| `duplicate_rows` | warning | Identical row fingerprints — surfaced, never deleted |
| `undated_rows` | warning | Rows with no usable date (e.g. a manually appended total row) |
| `potential_storage_fee` | warning | Storage-like description booked under another type |
| `missing_storage_fees` | warning | A marketplace with no storage fees at all this month |
| `cross_market_settlement` | warning | Rows belonging to another marketplace's settlement |
| `in_transit_disbursement` | info | A payout not yet cleared — explains why a settlement stays receivable |
| `helper_header_row` | info | Translation rows skipped |
| `empty_file` | warning | Header only, no data rows |
---
## 12. Excel exports
| | Contents | Size / speed |
|---|---|---|
| **Summary Excel** | Finance Summary · AR Ledger · Reconciliation Control · Category Totals · Audit Trail | ~11 KB, seconds |
| **Full Excel** | Summary · Detail · COA · AR Ledger · Journal Entry · per-marketplace transaction tabs · Reconciliation · Exceptions · Processing Audit Trail — with **live formulas** (`SUMIFS`, `ROUND`, cross-sheet refs) | ~240 MB, a few minutes |
The Full workbook mirrors the manual file's structure so an auditor can trace any total back to the
transaction rows. The **Processing Audit Trail** sheet records generation time, inputs, filenames with
**SHA-256 hashes**, row counts, FX rates, reserves and the app version.
---
## 13. Testing
| Suite | Covers |
|---|---|
| `test_engine_unit.py` | Dates, column mapping, aging bands, settlement classification |
| `test_multimarket.py` | 11 localized date formats, 22 type translations, storage detection, header mapping, **13-market benchmark** |
| `test_per_market.py` | Journal resolution, payout owner attribution, per-market API |
| `test_reconciliation_jan2026.py` | **USA = 11,110,433 to the penny**, settlement split, reconciliation |
| `test_excel_export.py` / `test_summary_export.py` | Workbook structure, formulas, sheet splitting |
| `test_workbook_comparison.py` | Generated workbook vs the sample |
| `test_api.py` / `test_robustness.py` | Full API flow; corrupt/empty/bad-header files |
**64 fast + 12 integration tests.** Run integration with the sample files present:
```bash
AR_SAMPLE_DIR="/path/to/samples" python -m pytest tests -m integration
```
---
## 14. Known data issues & assumptions
These came out of reviewing the real January and February files:
1. **Australia renamed a column** (Feb): `fba fees``fulfilment by amazon fees`. Aliased; the
`unmapped_amounts` check caught the 2,427.43 before it reached a report.
2. **Canada has a manual footer row** (Feb): "Cost of advertising", 362,715.55, with no date, type
or settlement id. Excluded from the receivable, flagged as `undated_rows`**needs a Finance decision**.
3. **New Amazon columns** (Feb): `Transaction Status`, `Transaction Release Date`, `low value goods`. All mapped.
4. **Duplicates are usually legitimate** — components sum to totals and the receivable reconciles
exactly with them included. Surfaced for review, never auto-removed.
5. **Liquidations are a memo line** — their amounts stay in the sales/fee buckets; re-bucketing would
change gross revenue.
6. **Settlement ids are assumed unique per Amazon account** — true across all real data.
7. **Month-boundary payouts**: a payout dated Jan 30 clears the bank in early February but lives in
the January file, so February never records receiving it. Explains USA/Canada roll-forward variances.
---
## 15. Where to look
| I want to… | Go to |
|---|---|
| Understand the receivable maths | [`accounts-receivable-logic.md`](accounts-receivable-logic.md) |
| See where each dashboard number comes from | `dashboard-guide.html` |
| Change the settlement cutoff | Settlement Reconciliation tab → clearing-lag |
| Map a new/renamed Amazon column | Upload & Mapping tab → Header mapping |
| Set the opening balance | AR Ledger tab → Adjust |
| Sign off the month | Reconciliation tab → Reconciliation Control → Verify → Mark month complete |
| Get the audit file | Excel Export tab → Generate full workbook |