Compute sub-row text before emitting the main <tr> so has-note class
can suppress its border-bottom. The sub-row then carries the divider,
placing notes visually above it. Alternate rows get row-even class
(light #f7f8fa background) so each transaction is easy to track across
columns regardless of whether it has a note.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Statement page gains a period selector (same options as cashier widget)
in the print controls bar. Changing period reloads the page; custom
shows date pickers. Balance column reflects actual account balance at
each transaction by computing an opening balance before the period.
Period label shown in statement header.
Cashier stats widget gains: All time, Last week, Last month, Last
quarter, Last year options. _period_bounds extended with all last_*
variants and returns None for 'all' (callers skip the WHERE clause).
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Adds two widgets at the top of the Cashier view:
- Total outstanding credit across all member accounts
- Transaction summary (top-ups / withdrawals / charges / net) with
period selector: today, this week, month, quarter, year, or custom
date range. Backed by new GET /cashier/stats endpoint that respects
the configured display timezone for period boundaries.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Adds a Paper Size setting (A4/A5) to the General section of Admin
settings. Receipts and statements pre-select the configured size and
apply the correct @page margins; staff can still override per-print.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
- _server_timezone(): detects IANA timezone from /etc/timezone or
/etc/localtime symlink at startup; used as the CONFIG default
- CONFIG: new "timezone" key set to server's detected timezone
- AppSettingsUpdate: new optional timezone field
- _display_tz(), _fmt_dt(), _now_display() helpers: convert stored UTC
datetimes to the configured timezone for display; falls back to server
local if the setting is empty or the zone name is invalid
- receipt(): transaction timestamp uses _fmt_dt() instead of raw UTC slice
- statement(): row timestamps and "Generated" line use _fmt_dt()/_now_display()
- Admin settings: Timezone text input (IANA name) in General section
- app.js: loadAdminSettings/saveSettings handle timezone field
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Receipts:
- Font size raised to 11pt base (labels 9pt, amounts 13pt bold)
- Each field now shows LABEL (small, uppercase, gray) above VALUE —
two cells per row in a 1fr/1fr CSS grid, matching the provided samples
- Business header: left column = address lines, right column = Tel/Email/Web
- Charge receipt: STAFF+TRANSACTION / CHARGE+TIME / AMOUNT+BALANCE
- Top-up/Withdrawal receipt: STAFF+TXN / TRANSFER_TYPE+TIME /
AMOUNT+BALANCE / TRANSFER_TYPE+TRANSFER_REF
- Print button moved into the paper-size controls bar
Statement:
- Reduced from 9 to 7 columns: Date, Reference, Type, Venue, Staff,
Amount (+/-), Balance — removes the separate Charge/Credit split
- Amount shown as "+ £X.XX" (green) or "- £X.XX" (red)
- Sub-row shows "Transfer type: X — Ref" for top-ups/withdrawals,
or the note text for charges
Logo:
- New POST /admin/logo endpoint: accepts image upload, saves to
static/logo.{ext}, auto-updates logo_url setting
- New logo_max_width / logo_max_height config fields (default 200×80px)
- Admin branding section: file upload input + max-width/height fields
- python-multipart added to requirements.txt (needed for file upload)
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
- CONFIG: add business address/contact, logo URL+alignment, venue names,
transaction ref prefix, transfer types list, and 14 localizable receipt
labels plus per-receipt-type footer fields
- DB: add transfer_type and transfer_ref columns to ledger_entries
(init_db + migrate_db); fix duplicate return in pos_user
- Pydantic: extend AppSettingsUpdate with all new settings;
add transfer_type/transfer_ref to TopupRequest and WithdrawalRequest
- /topup and /withdrawal: persist transfer_type and transfer_ref
- /config: return transfer_types as parsed array for frontend dropdowns
- New helpers: _txn_ref(), _logo_html(), _biz_header_html()
- New RECEIPT_CSS with 2-column grid layout; receipt() fully redesigned
with business header, auto-generated TXN reference, separate charge vs
top-up/withdrawal layouts; statement() adds Reference column and
transfer-detail sub-rows
- index.html: Transfer Type + Transfer Reference fields on cashier/
withdrawal panels; admin settings expanded into organized sections
(General, Business Address, Branding, Transactions, Receipt Labels,
Receipt Footers)
- app.js: populateTransferTypes() called on startup and after settings
save; doTopup/doWithdrawal send transfer fields; clearCashierSelection
clears new fields; loadAdminSettings/saveSettings handle all new fields
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Instead of a per-transaction checkbox at point of sale, the overdraft
override is now a persistent flag on each member, set via the Edit Member
modal by the appropriate role.
Schema:
- members.overdraft_override INTEGER DEFAULT NULL
(NULL = follow global policy, 1 = explicitly allowed, 0 = explicitly blocked)
- migrate_db(): ALTER TABLE members ADD COLUMN ... for existing databases
Charge logic (combining global policy + member flag):
- never: always block (member flag ignored)
- always: always allow (member flag ignored)
- staff_override: allow only if member flag = 1
- admin_override: allow only if member flag = 1 (only admin can set it via UI)
- staff_block: allow unless member flag = 0 (explicitly blocked)
Edit Member modal:
- Shows "Allow overdraft for this member" for staff_override / admin_override
(admin_override hidden from non-admins)
- Shows "Block overdraft for this member" for staff_block
- Hidden for never / always policies
- Checkbox state pre-populated from current member.overdraft_override value
Removed the per-transaction barOverrideRow that was added in the previous
commit — it has been superseded by this per-member approach.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Policies:
- never – not allowed (default)
- always – allowed for all charges
- staff_override – default no; staff sees checkbox to allow per charge
- admin_override – default no; only admins see the allow-per-charge checkbox
- staff_block – default yes; staff sees checkbox to block per charge
Backend:
- CONFIG: allow_negative_balance → overdraft_policy: "never"
- migrate_db(): converts old allow_negative_balance setting in app_settings
to the equivalent overdraft_policy value on first startup after upgrade
- ChargeRequest: new optional overdraft_override: bool = False
- POST /charge: full policy logic; admin_override enforced server-side
so a non-admin can't bypass it by sending override=true
- POST /admin/settings: validates policy value before saving
Frontend:
- Admin settings: checkbox replaced by <select> with five options
- Bar form: barOverrideRow (hidden by default); selectBarMember() shows
it with the right label when policy is staff_override / admin_override
(admin only) / staff_block; hidden for never and always
- clearBarSelection() resets the override checkbox and hides the row
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Cashiers can process credit withdrawals (cash-back) for members:
- Requires member PIN to authorize
- Always checks sufficient balance (overdraft not allowed for withdrawals)
- Appears as 'withdrawal' type in ledger, statement, and on receipt
- Receipt opens automatically, same as top-up/charge
Backend:
- migrate_db() now also recreates ledger_entries with type constraint
extended to include 'withdrawal' (existing rows preserved)
- POST /withdrawal endpoint (cashier_user required)
- Receipt label map updated to include Withdrawal
Frontend:
- Cashier tab now shows two panels side-by-side: Top Up and Withdrawal
- Each panel has its own message area so they don't overwrite each other
- Withdrawal panel includes PIN field; top-up panel unchanged
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Roles:
- cashier: Members + Cashier (top-up) tabs; POST /topup enforced at API level
- pos-staff: Members + Bar (charge) tabs; POST /charge enforced at API level
- admin: all tabs including Admin panel
Changes:
- migrate_db() recreates staff_accounts with new CHECK constraint and
converts any existing 'staff' rows to 'pos-staff' on startup
- cashier_user / pos_user FastAPI dependencies added; applied to /topup and /charge
- Role dropdowns in admin panel updated to the three new values
- startApp() hides irrelevant tabs per role on login
- doLogout() resets all tab visibility so the next login starts clean
- fmtRole() formats role names ('pos-staff' → 'POS Staff') in the accounts table
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Auth system
- staff_accounts table: name, username, bcrypt password, role (staff|admin)
- Session tokens in memory (8-hour TTL), httpOnly cookie
- POST /auth/login, /auth/logout, GET /auth/me
- All API endpoints now require a valid session
- Default admin account seeded on first run (admin/admin), printed to console
- Staff name for transactions comes from the session, no more dropdown
Currency input fix
- Amount inputs are now decimal (step=0.01); users enter 1.00 not 100
- Frontend multiplies by cfg.currency_divisor before POSTing
- TopupRequest/ChargeRequest no longer include staff_name (from session)
Admin area (4th tab, admin role only)
- App Settings: club name, currency symbol, major/minor unit names,
divisor, min/max topup, max charge, receipt footer, allow overdraft
- Settings persisted in app_settings DB table; merged with CONFIG defaults
at startup and refreshed after each save
- Staff Accounts: list with edit modal (name, username, password, role,
active flag) and delete; Add Account inline form
- /admin/settings GET/POST, /admin/staff-accounts CRUD
- /config endpoint exposes live settings to frontend on every page load
receipt_footer field rendered on both receipt and statement print views
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
- / now serves index.html (three-view SPA: Members, Cashier, Bar)
- /cashier and /bar remain as standalone pages (unchanged)
- Members view: Edit button on every row opens a modal to update
name, member number, and optionally PIN. Delete button only appears
when balance is exactly 0; confirmation dialog before deletion removes
the member and their ledger entries.
- PUT /members/{id}: updates any combination of name/member_number/pin;
guards against duplicate member numbers.
- DELETE /members/{id}: rejects with 400 if balance != 0, otherwise
deletes ledger entries then member row.
- Modal styles added to style.css; app.js rebuilt as combined SPA script
(loads common.js for shared helpers).
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Fix 1: Replace free-text staff name with a dropdown populated from staff.json
via GET/POST/DELETE /staff endpoints. Staff management panel on cashier page
(type name, Add button, chip list with × remove). Dropdown remembers last
selection per session via sessionStorage.
Fix 2: Split single-page app into /cashier (register + top-up + member list +
staff management) and /bar (charge only). Each page is its own HTML file
with two plain <a> nav links; / redirects to /cashier. Shared helpers
extracted to common.js; page logic in cashier.js and bar.js.
Fix 3: Statement view gains an A4/A5 radio toggle that rewrites a dynamic
<style> @page rule before the browser print dialog opens. Defaults to A4.
Fix 4: POST /topup and POST /charge now return entry_id. Each successful
transaction opens /receipt/{entry_id} in a new tab — server-rendered HTML
showing member name/number, type, amount, balance-after (computed as running
sum up to that entry), staff, note, timestamp. Same A4/A5 print toggle.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7