- Replace flat settings form with 6 collapsible accordion sections
(General, Business Address, Branding, Transactions, Receipt Labels,
Receipt Footers), each with its own Save button and feedback message
- Add segmented mode-tab control to cashier form so staff pick
Top Up or Withdrawal before entering amounts; withdrawal panel
hidden by default, resets to Top Up on cancel/clear
- Add toggleAcc(), setCashierMode() JS helpers
- Add accordion + mode-tab CSS styles
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
Populates a <datalist> from Intl.supportedValuesOf('timeZone') on
startup, giving the timezone input native browser search/filter with
no extra dependencies. Degrades gracefully to plain text on browsers
that don't support the Intl API.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
On screens ≤640px the tab row collapses; a hamburger button (Material
Symbols menu/close icon) appears in the nav-right area. Tapping it
opens a fixed full-width dropdown below the header with full-height
tap targets and a left accent border on the active item. Tapping a tab
or anywhere outside closes the menu. Desktop layout is unchanged —
nav-tabs uses display:contents so the buttons remain direct flex
children of nav.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Replace button-border nav tabs with full-height underline tabs:
active tab gets a white 2px bottom border against the dark header.
Icons from Material Symbols Outlined (filled when active, outlined
when inactive): group→Members, universal_currency_alt→Cashier,
point_of_sale→Bar, settings_applications→Admin.
https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
Staff enter amount and note, click Charge; a full-screen patron-facing
screen appears showing the charge total, member name, and a large PIN
input that auto-focuses. PIN errors stay on the overlay; Cancel returns
to the amount form so staff can adjust before handing the device back.
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
Fixes garbled display ("â€"") on Windows where the UTF-8 bytes for U+2014
were being misread as Windows-1252. All em-dash occurrences in index.html,
app.js, and common.js are now expressed as HTML entities.
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
The login overlay lacked the `hidden` class by default, so on every page
load it was visible and blocking the app. On fresh load with a valid session,
boot() skips showLogin() (no submit handler added), leaving the overlay
covering the screen with a non-functional form. Adding `hidden` to the HTML
and explicitly hiding the overlay in startApp() fixes both paths.
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
Removed product search field, results list, barProductLookup(),
and selectProduct() from the bar view in index.html, bar.html,
app.js, and bar.js. Backend /products endpoints are unchanged.
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