# Exhibition rental management

> Screenshots use representative demo data captured from a live environment. Exhibition is a gated marketplace add-on and appears only when enabled. Never use real customer records, bank statements, secrets, or private packages in a public demo.

The rented-terminal marketplace: the organizer owns the booths and card machines, renters (exhibitors) sell from those booths swiping on the organizer's terminals, and the module attributes each card transaction to the right renter, takes commission and booth rent off the top, and settles the rest.

[← Public docs landing](README.md) · [Open HTML version](exhibition.html)

## How the Exhibition module fits together

The **organizer** owns the booths and the card-payment machines; **renters** sell from those booths on the organizer's terminals; the **bank** settles all card money into the organizer's account. The module attributes each card transaction to the right renter, takes the organizer's commission and any booth rent off the top, and settles the rest. The **terminal serial number** is the universal join key linking a bank line to a machine, its assignment, and the renter.

All pages live under **Exhibition** at `/exhibition/*`; the module is a federated web remote whose API is served at `/api/v1/exhibition`. The golden join key is the machine **Serial Number = the terminal ID** on the bank statement and the renter's report — if they do not match, nothing auto-matches. Every overview/dashboard money figure is computed from the same per-renter difference view used everywhere else (a single source of truth).

## Overview — the rented-terminal marketplace

An **organizer dashboard** (`/exhibition/dashboard`) composes the KPI overview and settlement report into one read-only command center: headline KPIs (commission income, payable owed to renters, overdue rent, reconciled machine sales, net position, and renter/contract/machine counts), an **Action-Needed** strip (overdue rent, open reconciliation sessions, unmatched recon lines), a renters-needing-attention list, and a settlement summary with PDF/CSV download and email.

| Screen | Purpose | Image |
|---|---|---|
| Organizer dashboard | KPIs + settlement summary | ![Dashboard](assets/screenshots/web/exhibition-dashboard.png) |

## Renters, machines, and assignments (setup)

A **renter** is a contact flagged *IsRenter*. A **payment machine** is a physical card terminal whose Serial Number must equal the bank's terminal ID. An **assignment** binds a machine to a renter over a date window and snapshots the commission percent in force, so later commission changes never rewrite recorded history. Overlapping active assignments for the same machine/period are rejected; commission percent (0–100, validated) is snapshotted onto the assignment and copied onto every statement created from it. **Booth contracts** (`/exhibition/contracts`) create an `EXH-` numbered rental contract for a renter against a booth asset with monthly rent + deposit and auto-generate the payment schedule.

| Screen | Purpose | Image |
|---|---|---|
| Booth renters | Contracts, balances, machine count | ![Renters](assets/screenshots/web/exhibition-renters.png) |
| Payment machines | Card terminals (serial = terminal ID) | ![Machines](assets/screenshots/web/exhibition-machines.png) |

**Wisely.** Set the Serial Number to the exact terminal ID the bank prints — it is what makes every auto-match work. Always create an assignment (with an Assigned From date) before transactions arrive; the window decides who owned the machine on each date.

## Reconciliation session (bank-confirmed money)

A **reconciliation session** is a batch of bank-confirmed card transactions for a bank account and period. It is created as Draft, populated by one of three import paths (bank feed / Network International PDF / bank CSV — each line is an Unmatched recon line), then auto/manually matched and completed; importing moves it to In Progress. On import, a cash-recognition JE posts *only when configured*: Dr bank GL (net) + Dr bank-fee expense (MSF+VAT) = Cr machine-settlements suspense (gross). **Auto-match** maps each line via terminal → machine serial → covering assignment (with a ±2-day tolerance pass); leftovers can be manually matched or excluded; **Complete Session** locks it.

| Screen | Purpose | Image |
|---|---|---|
| Reconciliation | Bank-to-renter session list | ![Reconcile](assets/screenshots/web/exhibition-reconcile.png) |

**Wisely.** Run auto-match first and reserve manual match/exclude for leftovers — an unmatched line is usually a missing assignment or a serial mismatch. Re-importing the same file is safe (lines de-dup).

## Renter sales statements + statement reconciliation (commission core)

A **renter sales statement** is what a renter declares they sold, as daily totals, with commission snapshotted from the assignment (commission = gross × rate, net = gross − commission, owed to the renter). **Reconcile a statement** to compare declared gross vs bank-confirmed gross for that machine/period (order-independent); on a match with bank gross > 0 the allocation JE posts once per statement (configured-or-skip): Dr machine-settlements suspense (bank gross) = Cr commission income (gross × rate) + Cr renter payable (net), and the statement is marked Reconciled.

**Wisely.** Reconcile every statement against the bank rather than paying out on a declaration alone — the discrepancy is your fraud/error check. The snapshotted rate means you can change a renter's go-forward rate without disturbing history.

## Settlement and payout (commission, overdue-rent offset, transfer)

Settlement nets each renter's bank-reconciled machine sales against commission and any overdue booth rent and pays out the rest. **Available payable** is the bank-confirmed net of each reconciled statement minus what is already offset or paid out. **Offset overdue rent** applies the available payable against overdue `EXH-` rent oldest-first (Dr renter payable / Cr booth rent income). **Transfer payout** pays the renter their available reconciled net (Dr renter payable / Cr bank) — **blocked while any overdue rent remains** (future-dated rent does not block). The per-renter difference view is the single source of truth.

**Wisely.** Run Check-Overdue before settling so the overdue figure (and the payout block) is current. The payout-blocked-while-rent-overdue rule is a feature: clear the rent, the payout button enables itself.

## Imports — PDF, bank feed, and the two CSVs

Four paths. Into a session: the **bank feed** (BankTransactions tagged with a machine ID, Draft-only, de-dup by bank txn id), a **Network International PDF** (terminal/date/gross/net/commission/VAT/acquirer ref/card number, de-dup by acquirer reference), and a **bank-statement CSV** (de-dup against already-imported lines). Separately, a **renter sales CSV** groups rows by terminal, buckets each under the covering assignment, sums per day, and creates one renter sales statement per (assignment, period). PDF and both CSVs are 10 MB max and extension-validated; both CSVs share one header-driven parser.

> **Availability:** Exhibition is a gated marketplace add-on. When not enabled, its routes are gated and redirect to the marketplace — expected behaviour. Screenshots were captured while it was enabled and are retained as a feature reference.

**Wisely.** Always include a Reference column in the bank CSV — it is the exact de-dup key, so re-imports stay clean and the cash JE never double-posts. Read the renter-import confirmation: malformed-rows-skipped and unmatched-terminals announce bad data and missing assignments.

## CSV import formats

Both imports use one shared parser (`ExhibitionCsvImportParser`): a header row is **required**; column names are case/space-insensitive; quoted fields and a UTF-8 BOM are handled; input is assumed UTF-8.

- **Bank-statement CSV** (`POST sessions/{id}/import-csv`). Required: **Terminal** (aliases: Terminal ID, Machine, Machine Serial, Serial, Serial Number) = the machine's Serial Number; **Date** (Transaction/Txn/Value/Posting Date); **Amount** (Gross, Gross Amount). Optional: **Net** (Net Amount); **Reference** (Ref, Acq Reference, Transaction ID, Txn ID) — recommended, it is the exact de-dup key.
- **Renter sales CSV** (`POST renter-statements/import-csv`). Same parser/aliases; in practice Terminal, Date, Amount — grouped by terminal, bucketed under the covering assignment, summed per day into one statement per (assignment, period).
- **Date formats** (tried in order, invariant/exact): `yyyy-MM-dd` (preferred), `yyyy/MM/dd`, `dd/MM/yyyy`, `dd-MM-yyyy`, and the same with `THH:mm:ss` / ` HH:mm:ss`. Ambiguous US `M/d/yyyy` is **not** accepted; time-of-day is dropped.
- **Amounts** are invariant-culture decimals, taken as absolute values (refunds/chargebacks are absolute-valued, not reversals). **Malformed rows** (blank terminal, unparseable date/amount) are skipped and counted.
- **De-dup / idempotent re-import.** Bank CSV: with a Reference, de-dup on `r:`reference; without, on natural key `n:`terminal|yyyyMMdd|gross|net — only against persisted lines (so two identical reference-less sales in ONE file are both kept; re-importing the same file skips dups and avoids double-posting the cash JE). Renter CSV: existing (MachineAssignment, PeriodFrom, PeriodTo) statements are skipped.
- **Statement period** = the min/max transaction date in the file (per assignment for the renter CSV).

> **Limitations:** positive-sales-only (no negative/reversal modelling); UTF-8 only; the renter import drops a row no assignment covers and reports terminals with no machine or no covering assignment as "unmatched".

## Reports and exports

Four surfaces: the **settlement & commission report** (per-renter rows + grand totals, optional date range) as JSON / RFC-4180 CSV (UTF-8 BOM) / printable PDF (QuestPDF), emailable; the **renter statement of account** PDF (combines booth contract, rent schedule, machine transactions, and settlement position) — downloadable or emailable; the **machine daily summary** (`/exhibition/machine-report`, per machine per day, client-side CSV); and the **renter statement report** (`/exhibition/renter-statement`, bank-matched transactions over a range).

| Screen | Purpose | Image |
|---|---|---|
| Machine daily summary | Per machine/day counts + totals | ![Machine report](assets/screenshots/web/exhibition-machine-report.png) |
| Renter statement report | Bank-matched transactions + total | ![Renter statement](assets/screenshots/web/exhibition-renter-statement.png) |

**Wisely.** Export the settlement report as CSV for spreadsheet checks and PDF for the renter/accountant — both come from the same difference view, so they always agree. Email the statement-of-account PDF straight from the renter profile.

## How the cycle works

1. Add the renter (`/exhibition/renters`) or pick an existing contact; flagged IsRenter.
2. Add the payment machine (`/exhibition/machines`) with Serial Number = the exact terminal ID the bank prints.
3. Assign the machine to the renter (`/exhibition/assignments`) with an Assigned From date; this snapshots the commission percent and defines the auto-match window.
4. (Optional) Create the booth contract (`/exhibition/contracts`) for rent + deposit; the rent schedule is auto-generated as an `EXH-` contract.
5. Bring in the money: create a reconciliation session (`/exhibition/reconcile`) and import bank-confirmed transactions (bank feed / NI PDF / bank CSV) — posts the cash JE if configured.
6. Bring in the claims: import the renter sales CSV (declared daily totals → one statement per renter/assignment with commission snapshotted), or create a statement by hand.
7. Reconcile the session: auto-match (terminal → machine → assignment, ±2-day), manual-match/exclude leftovers, then complete.
8. Reconcile each renter sales statement: declared vs bank-confirmed; on match it posts the allocation JE (suspense → commission income + renter payable) and marks it Reconciled.
9. Offset overdue rent oldest-first; a payout is blocked while overdue rent remains.
10. Transfer the payout (Dr renter payable / Cr bank); fully-drawn statements become Settled.
11. Report and share: settlement & commission report and machine daily summary; download/email the renter statement-of-account and settlement report PDFs.

## Permissions

- **Exhibition.Read** — renters, overview/dashboard, renter profiles, renter differences (single + all).
- **Exhibition.Recon.Read** — machines, assignments, reconciliation sessions/lines, machine daily summary.
- **Exhibition.Recon.Manage** — create/update/delete machines, create/end assignments, create sessions, import (bank feed / PDF / bank CSV), auto-reconcile, complete session, manual-match/exclude lines.
- **Exhibition.Statement.Read** — renter statement report, renter sales statements, settlement report (JSON/CSV/PDF), renter statement PDF.
- **Exhibition.Statement.Manage** — create renter sales statements, reconcile a statement, import renter sales CSV, record rent payment, check overdue payments.
- **Exhibition.Statement.Email** — email a renter their statement-of-account PDF; email the settlement & commission report PDF.
- **Exhibition.Payout.Run** — offset a renter's payable against overdue rent; transfer a renter's payout.
- **Exhibition.Contract.Read** — booth contracts, contract scenarios, contract bridges.
- **Exhibition.Contract.Manage** — create booth contracts; link a contract scenario.

## Related pages

- [Overview, login, and dashboard](overview.md)
- [Accounting, reports, banking, tax, and payments](finance.md)
- [Sales, purchase, contracts, and rental](commercial.md)
- [Inventory, manufacturing, marketplace, and add-ons](operations.md)
- [AI, ARIA assistant, document scan & draft, and AI setup](ai-automation.md)
- [Admin, settings, security, module access, audit, and approvals](admin-security.md)
- [Troubleshooting, browser requirements, and support handoff](troubleshooting.md)
