mirror of
https://github.com/kbenestad/ClubLedger.git
synced 2026-06-18 09:44:33 +00:00
docs: update all four guides to reflect new features
Covers timezone settings, business address/branding/logo upload, transfer types, transaction reference prefix, receipt label localisation, separate charge/cashier footers, three-role system (POS Staff / Cashier / Admin), five-option overdraft policy, per-member overdraft override, withdrawal transaction type, and manage.py CLI (reset-admin, reset-db). https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
This commit is contained in:
parent
ea03355743
commit
6aa4c45616
4 changed files with 391 additions and 83 deletions
|
|
@ -16,6 +16,18 @@ On first startup the system creates a default admin account:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
ClubLedger has three roles:
|
||||||
|
|
||||||
|
| Role | Tabs visible |
|
||||||
|
|---|---|
|
||||||
|
| **POS Staff** | Members, Bar |
|
||||||
|
| **Cashier** | Members, Cashier |
|
||||||
|
| **Admin** | Members, Cashier, Bar, Admin |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Admin Tab
|
## Admin Tab
|
||||||
|
|
||||||
The Admin tab contains two sections: **App Settings** and **Staff Accounts**.
|
The Admin tab contains two sections: **App Settings** and **Staff Accounts**.
|
||||||
|
|
@ -26,11 +38,12 @@ The Admin tab contains two sections: **App Settings** and **Staff Accounts**.
|
||||||
|
|
||||||
These settings control how ClubLedger looks and behaves. Changes take effect immediately without restarting the server.
|
These settings control how ClubLedger looks and behaves. Changes take effect immediately without restarting the server.
|
||||||
|
|
||||||
### Club Identity
|
### General
|
||||||
|
|
||||||
| Setting | Description |
|
| Setting | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Club Name** | Appears in the navigation bar, on receipts, and on statements |
|
| **Club Name** | Appears in the navigation bar, on receipts, and on statements |
|
||||||
|
| **Timezone** | IANA timezone name (e.g. `Europe/London`, `Asia/Bangkok`). All receipt and statement timestamps are shown in this timezone. Leave blank to use the server's local time. |
|
||||||
|
|
||||||
### Currency
|
### Currency
|
||||||
|
|
||||||
|
|
@ -55,18 +68,80 @@ All limits are entered in the **major unit** (e.g. pounds).
|
||||||
| **Maximum top-up** | Cashier cannot top up more than this in a single transaction |
|
| **Maximum top-up** | Cashier cannot top up more than this in a single transaction |
|
||||||
| **Maximum single charge** | Bar cannot charge more than this in a single transaction |
|
| **Maximum single charge** | Bar cannot charge more than this in a single transaction |
|
||||||
|
|
||||||
### Receipt Footer
|
### Business Address
|
||||||
|
|
||||||
Optional text printed at the bottom of every receipt and statement. Useful for:
|
These fields populate the business header printed at the top of every receipt and statement. All fields are optional — leave blank to omit.
|
||||||
- A thank-you message
|
|
||||||
- A refund or returns policy
|
|
||||||
- Contact details
|
|
||||||
|
|
||||||
Accepts plain text. Line breaks are preserved.
|
| Field | Description |
|
||||||
|
|---|---|
|
||||||
|
| **Address Line 1–4** | Street address, city, postcode, etc. |
|
||||||
|
| **Country** | Country name or code |
|
||||||
|
| **Phone** | Contact phone number |
|
||||||
|
| **Email** | Contact email address |
|
||||||
|
| **Website** | Contact website URL |
|
||||||
|
|
||||||
### Allow Negative Balance (Overdraft)
|
### Branding
|
||||||
|
|
||||||
When ticked, the bar can charge a member even if their balance would go below zero. When unticked (the default), charges are blocked if the member has insufficient funds.
|
| Setting | Description |
|
||||||
|
|---|---|
|
||||||
|
| **Logo** | Upload an image file (PNG, JPG, GIF, WebP, or SVG). The file is stored in the `static/` folder of the application. |
|
||||||
|
| **Logo URL** | The path used to display the logo — set automatically when you upload. Can also be set manually (e.g. `/static/yourlogo.png`) if you are copying a file directly to the server. |
|
||||||
|
| **Logo Alignment** | `Left`, `Centre`, or `Right` — controls where the logo appears in the receipt/statement header |
|
||||||
|
| **Logo Max Width** | Maximum display width in pixels (default 200) |
|
||||||
|
| **Logo Max Height** | Maximum display height in pixels (default 80) |
|
||||||
|
| **Bar Name** | Label used for the bar/POS venue on receipts (default `Bar`) |
|
||||||
|
| **Cashier Name** | Label used for the cashier venue on receipts (default `Cashier`) |
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
| Setting | Description |
|
||||||
|
|---|---|
|
||||||
|
| **Transaction Reference Prefix** | Prepended to the auto-generated transaction number. For example, `TXN` produces references like `TXN0000001` (default `TXN`). |
|
||||||
|
| **Transfer Types** | Comma-separated list of payment methods shown to cashiers in the Transfer Type dropdown on the Cashier tab (e.g. `Bank Transfer,Cash,QR`). |
|
||||||
|
|
||||||
|
### Overdraft Policy
|
||||||
|
|
||||||
|
Controls whether members are allowed to have a negative balance. The setting is a dropdown with five options:
|
||||||
|
|
||||||
|
| Policy | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| **Never allowed** | No member can ever go into overdraft |
|
||||||
|
| **Always allowed** | All members can always go into overdraft |
|
||||||
|
| **Staff override** | Staff can tick a per-member checkbox to allow overdraft for that specific member |
|
||||||
|
| **Admin override** | Only admins can tick the per-member overdraft checkbox |
|
||||||
|
| **Staff block** | Staff can tick a per-member checkbox to block overdraft for a specific member (all others are allowed) |
|
||||||
|
|
||||||
|
When the policy is **Staff override**, **Admin override**, or **Staff block**, an **Overdraft override** checkbox appears in the Edit Member modal.
|
||||||
|
|
||||||
|
### Receipt Labels
|
||||||
|
|
||||||
|
All fields in this section are optional and are provided for localisation. Each field overrides the default label printed on receipts and statements.
|
||||||
|
|
||||||
|
| Field | Default |
|
||||||
|
|---|---|
|
||||||
|
| Receipt title | `RECEIPT` |
|
||||||
|
| Top-up receipt title | `TOP-UP RECEIPT` |
|
||||||
|
| Withdrawal receipt title | `WITHDRAWAL RECEIPT` |
|
||||||
|
| Staff label | `STAFF` |
|
||||||
|
| Transaction label | `TRANSACTION` |
|
||||||
|
| Charge venue label | `CHARGE` |
|
||||||
|
| Transaction time label | `TRANSACTION TIME` |
|
||||||
|
| Amount charged label | `AMOUNT CHARGED` |
|
||||||
|
| Remaining balance label | `REMAINING BALANCE` |
|
||||||
|
| Balance transfer label | `BALANCE TRANSFER` |
|
||||||
|
| Amount topped-up label | `AMOUNT TOPPED-UP` |
|
||||||
|
| Amount withdrawn label | `AMOUNT WITHDRAWN` |
|
||||||
|
| Transfer type label | `TRANSFER TYPE` |
|
||||||
|
| Transfer reference label | `TRANSFER REFERENCE` |
|
||||||
|
|
||||||
|
### Receipt Footers
|
||||||
|
|
||||||
|
Optional text printed at the bottom of receipts. Useful for thank-you messages, refund policies, or contact details. Plain text; line breaks are preserved.
|
||||||
|
|
||||||
|
| Field | Appears on |
|
||||||
|
|---|---|
|
||||||
|
| **Footer — charge receipts** | Bar charge receipts |
|
||||||
|
| **Footer — cashier receipts** | Top-up and withdrawal receipts |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -78,17 +153,10 @@ Fill in the **Add Account** form at the bottom of the Staff Accounts panel:
|
||||||
|
|
||||||
| Field | Notes |
|
| Field | Notes |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Name | The person's real name — appears on receipts and transaction logs |
|
| **Name** | The person's real name — appears on receipts and transaction logs |
|
||||||
| Username | Used to sign in. Lowercase letters and numbers recommended. |
|
| **Username** | Used to sign in. Lowercase letters and numbers recommended. |
|
||||||
| Password | Minimum length enforced by the browser. Choose something strong. |
|
| **Password** | Choose something strong. |
|
||||||
| Role | **Staff** or **Admin** — see below |
|
| **Role** | **POS Staff**, **Cashier**, or **Admin** — see the Roles section above |
|
||||||
|
|
||||||
### Roles
|
|
||||||
|
|
||||||
| Role | Capabilities |
|
|
||||||
|---|---|
|
|
||||||
| **Staff** | Members, Cashier, Bar tabs |
|
|
||||||
| **Admin** | Everything above, plus the Admin tab (settings and account management) |
|
|
||||||
|
|
||||||
### Editing an Account
|
### Editing an Account
|
||||||
|
|
||||||
|
|
@ -115,9 +183,10 @@ Open the edit modal for their account, enter a new password, and save. The next
|
||||||
|
|
||||||
There is no transaction editing or deletion by design (audit trail). To correct a mistake:
|
There is no transaction editing or deletion by design (audit trail). To correct a mistake:
|
||||||
|
|
||||||
- **Overcharged:** Apply a top-up for the difference, with a note explaining the correction.
|
- **Overcharged at bar:** Apply a top-up for the difference, with a note explaining the correction.
|
||||||
- **Under-charged:** Apply a charge for the difference, with a note.
|
- **Under-charged at bar:** Apply a charge for the difference, with a note.
|
||||||
- **Wrong member charged:** Top up the affected member and charge the correct one, with matching notes on both.
|
- **Wrong member charged:** Top up the affected member and charge the correct one, with matching notes on both.
|
||||||
|
- **Incorrect top-up or withdrawal:** Apply an equal and opposite transaction (top-up to reverse a withdrawal, or withdrawal to reverse a top-up) with a note.
|
||||||
|
|
||||||
### Resetting a Member's PIN
|
### Resetting a Member's PIN
|
||||||
|
|
||||||
|
|
@ -135,16 +204,53 @@ Members tab → click **Statement** on any row. Statements open in a new browser
|
||||||
|
|
||||||
## Backing Up Data
|
## Backing Up Data
|
||||||
|
|
||||||
All data is stored in a single SQLite file: `clubledger.db` in the application folder. To back up, simply copy this file to another location.
|
All application data is stored in a SQLite database in the application folder. To take a full backup, copy the following files to another location:
|
||||||
|
|
||||||
|
**Database files:**
|
||||||
|
- `clubledger.db` — the main database
|
||||||
|
- `clubledger.db-wal` and `clubledger.db-shm` — write-ahead log files that may be present while the app is running
|
||||||
|
|
||||||
|
If the app is stopped, only `clubledger.db` needs to be copied (the WAL files will have been checkpointed). If the app is running, copy all three files.
|
||||||
|
|
||||||
|
**Logo file (if applicable):**
|
||||||
|
- `static/logo.png` (or `.jpg`, `.gif`, etc.) — the uploaded logo image. Copy this if you have uploaded a logo via the Branding settings.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Linux / Mac – copy to home directory
|
# Linux / Mac – back up the database to the home directory
|
||||||
cp /path/to/ClubLedger/clubledger.db ~/clubledger-backup-$(date +%Y%m%d).db
|
cp /path/to/ClubLedger/clubledger.db ~/clubledger-backup-$(date +%Y%m%d).db
|
||||||
```
|
```
|
||||||
|
|
||||||
The `staff.json` file stores the legacy staff name list (used only by the standalone `/cashier` and `/bar` pages, not the main app). Back this up too if you use those pages.
|
To restore, stop the server, replace `clubledger.db` with the backup copy (and the logo file if needed), and restart.
|
||||||
|
|
||||||
To restore, stop the server, replace `clubledger.db` with the backup copy, and restart.
|
---
|
||||||
|
|
||||||
|
## Command-Line Tools (`manage.py`)
|
||||||
|
|
||||||
|
Two administrative commands are available from the server terminal. They do not require using the web interface.
|
||||||
|
|
||||||
|
### Reset an Admin Password
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py reset-admin
|
||||||
|
```
|
||||||
|
|
||||||
|
- Interactively resets the password for an admin account.
|
||||||
|
- If there are multiple admin accounts, lists them and prompts you to select one.
|
||||||
|
- Prompts for a new password and a confirmation (minimum 4 characters). The password is not echoed to the screen.
|
||||||
|
- The app does **not** need to be stopped first — WAL mode allows concurrent access.
|
||||||
|
- Existing sessions for that account remain valid until they expire naturally (8 hours). To invalidate them immediately, restart the app after running this command.
|
||||||
|
|
||||||
|
### Reset the Database
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py reset-db
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Permanently deletes all data:** members, balances, transactions, staff accounts, and settings. This cannot be undone.
|
||||||
|
- You must type `RESET` to confirm. Anything else cancels the operation.
|
||||||
|
- The app **must be stopped** before running this command.
|
||||||
|
- After running: restart the app. It will create a fresh database with the default `admin` / `admin` credentials.
|
||||||
|
- Change the admin password immediately after the fresh start.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -384,7 +384,30 @@ To find your local network range: if your server's IP is `192.168.1.42`, your ra
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Part 8 – Security Notes
|
## Part 8 – Backing Up Data
|
||||||
|
|
||||||
|
All club data is stored in a single SQLite file: `clubledger.db`. To back up:
|
||||||
|
|
||||||
|
1. **If the app is stopped:** copy `clubledger.db` to a safe location.
|
||||||
|
2. **If the app is running:** SQLite may be in WAL (Write-Ahead Log) mode. Copy all three files if they exist:
|
||||||
|
- `clubledger.db`
|
||||||
|
- `clubledger.db-wal`
|
||||||
|
- `clubledger.db-shm`
|
||||||
|
|
||||||
|
Copy all three together in one operation so the backup is consistent.
|
||||||
|
|
||||||
|
3. **Logo file:** if an admin has uploaded a club logo, also copy `static/logo.*` (e.g. `static/logo.png`). The exact extension depends on the file that was uploaded.
|
||||||
|
|
||||||
|
**To restore from a backup:**
|
||||||
|
|
||||||
|
1. Stop the server.
|
||||||
|
2. Replace `clubledger.db` (and `clubledger.db-wal` / `clubledger.db-shm` if present) with the backed-up copies.
|
||||||
|
3. Copy any backed-up logo file back to the `static/` folder.
|
||||||
|
4. Restart the server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 9 – Security Notes
|
||||||
|
|
||||||
| Risk | Mitigation |
|
| Risk | Mitigation |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
|
@ -411,3 +434,5 @@ To find your local network range: if your server's IP is `192.168.1.42`, your ra
|
||||||
| View systemd logs (Linux) | `journalctl -u clubledger -f` |
|
| View systemd logs (Linux) | `journalctl -u clubledger -f` |
|
||||||
| Backup data | Copy `clubledger.db` to a safe location |
|
| Backup data | Copy `clubledger.db` to a safe location |
|
||||||
| Verify not internet-accessible | From mobile data: `http://<public-ip>:8000` should time out |
|
| Verify not internet-accessible | From mobile data: `http://<public-ip>:8000` should time out |
|
||||||
|
| Reset admin password | `python manage.py reset-admin` (app can be running) |
|
||||||
|
| Wipe database (start fresh) | Stop app first, then `python manage.py reset-db` |
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
| Backend | Python 3.11+ · FastAPI · SQLite (via stdlib `sqlite3`) |
|
| Backend | Python 3.11+ · FastAPI · SQLite (via stdlib `sqlite3`) |
|
||||||
| Auth | bcrypt password hashing · in-memory session tokens · httpOnly cookies |
|
| Auth | bcrypt password hashing · in-memory session tokens · httpOnly cookies |
|
||||||
| Frontend | Vanilla HTML/CSS/JS — no build step, no framework |
|
| Frontend | Vanilla HTML/CSS/JS — no build step, no framework |
|
||||||
| Dependencies | `fastapi`, `uvicorn[standard]`, `bcrypt` |
|
| Dependencies | `fastapi`, `uvicorn[standard]`, `bcrypt`, `python-multipart` |
|
||||||
|
|
||||||
|
`python-multipart` is required for file upload support via FastAPI's `UploadFile`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -16,6 +18,7 @@
|
||||||
```
|
```
|
||||||
ClubLedger/
|
ClubLedger/
|
||||||
├── main.py # Entire backend — one file
|
├── main.py # Entire backend — one file
|
||||||
|
├── manage.py # CLI for database/admin management (reset-admin, reset-db)
|
||||||
├── requirements.txt # pip dependencies
|
├── requirements.txt # pip dependencies
|
||||||
├── run.sh # Start script (creates venv, installs deps, runs server)
|
├── run.sh # Start script (creates venv, installs deps, runs server)
|
||||||
├── clubledger.db # SQLite database (created on first run, git-ignored)
|
├── clubledger.db # SQLite database (created on first run, git-ignored)
|
||||||
|
|
@ -33,10 +36,11 @@ ClubLedger/
|
||||||
├── cashier.html # Standalone cashier page (/cashier)
|
├── cashier.html # Standalone cashier page (/cashier)
|
||||||
├── cashier.js
|
├── cashier.js
|
||||||
├── bar.html # Standalone bar page (/bar)
|
├── bar.html # Standalone bar page (/bar)
|
||||||
└── bar.js
|
├── bar.js
|
||||||
|
└── logo.* # Uploaded logo file (created when admin uploads a logo; git-ignored)
|
||||||
```
|
```
|
||||||
|
|
||||||
`main.py` is intentionally a single file. It stays under ~450 lines because the domain is simple. Split it only if it grows substantially.
|
`main.py` is kept as a single file for simplicity. Split it only if it grows substantially.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -59,6 +63,20 @@ The default admin account (`admin` / `admin`) is printed to the console on first
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## manage.py
|
||||||
|
|
||||||
|
The `manage.py` script provides CLI commands for server-side administration. Run it from the project root with the virtual environment active.
|
||||||
|
|
||||||
|
### `python manage.py reset-admin`
|
||||||
|
|
||||||
|
Interactively resets an admin account password from the terminal. Safe to run while the app is running (SQLite WAL mode). Uses `getpass` so the password is not echoed.
|
||||||
|
|
||||||
|
### `python manage.py reset-db`
|
||||||
|
|
||||||
|
Wipes `clubledger.db`, `clubledger.db-wal`, and `clubledger.db-shm`. Requires typing `RESET` to confirm. The app must be stopped before running this command.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Database Schema
|
## Database Schema
|
||||||
|
|
||||||
### `members`
|
### `members`
|
||||||
|
|
@ -68,6 +86,7 @@ The default admin account (`admin` / `admin`) is printed to the console on first
|
||||||
| member_number | TEXT UNIQUE | Human-readable ID |
|
| member_number | TEXT UNIQUE | Human-readable ID |
|
||||||
| name | TEXT | |
|
| name | TEXT | |
|
||||||
| pin_hash | TEXT | bcrypt hash |
|
| pin_hash | TEXT | bcrypt hash |
|
||||||
|
| overdraft_override | INTEGER | NULL = use global policy; 1 = override allowed; 0 = override blocked |
|
||||||
| created_at | TEXT | `datetime('now')` UTC |
|
| created_at | TEXT | `datetime('now')` UTC |
|
||||||
|
|
||||||
### `ledger_entries`
|
### `ledger_entries`
|
||||||
|
|
@ -75,14 +94,16 @@ The default admin account (`admin` / `admin`) is printed to the console on first
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| id | INTEGER PK | |
|
| id | INTEGER PK | |
|
||||||
| member_id | INTEGER FK | → members.id |
|
| member_id | INTEGER FK | → members.id |
|
||||||
| amount | INTEGER | Minor currency units (e.g. pence) — always positive |
|
| amount | INTEGER | Minor currency units — always positive |
|
||||||
| type | TEXT | `topup` or `charge` |
|
| type | TEXT | `topup`, `charge`, or `withdrawal` |
|
||||||
| venue | TEXT | `cashier` or `bar` |
|
| venue | TEXT | `cashier` or `bar` |
|
||||||
| note | TEXT | Optional free text |
|
| note | TEXT | Optional free text |
|
||||||
| staff_name | TEXT | Name of logged-in staff at time of transaction |
|
| staff_name | TEXT | Name of logged-in staff at time of transaction |
|
||||||
|
| transfer_type | TEXT | Payment method for top-ups/withdrawals (e.g. "Cash") |
|
||||||
|
| transfer_ref | TEXT | Optional payment reference for top-ups/withdrawals |
|
||||||
| created_at | TEXT | UTC datetime |
|
| created_at | TEXT | UTC datetime |
|
||||||
|
|
||||||
Balance is computed on-the-fly: `SUM(topups) - SUM(charges)`. There is no stored balance column — this avoids drift and makes the audit trail self-consistent.
|
Balance is computed on-the-fly: `SUM(topups) - SUM(charges) - SUM(withdrawals)`. There is no stored balance column — this avoids drift and makes the audit trail self-consistent.
|
||||||
|
|
||||||
### `staff_accounts`
|
### `staff_accounts`
|
||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|
|
@ -91,10 +112,12 @@ Balance is computed on-the-fly: `SUM(topups) - SUM(charges)`. There is no stored
|
||||||
| name | TEXT | Display name, used as `staff_name` on transactions |
|
| name | TEXT | Display name, used as `staff_name` on transactions |
|
||||||
| username | TEXT UNIQUE | Login credential |
|
| username | TEXT UNIQUE | Login credential |
|
||||||
| password_hash | TEXT | bcrypt hash |
|
| password_hash | TEXT | bcrypt hash |
|
||||||
| role | TEXT | `staff` or `admin` |
|
| role | TEXT | `pos-staff`, `cashier`, or `admin` |
|
||||||
| active | INTEGER | 0 or 1 |
|
| active | INTEGER | 0 or 1 |
|
||||||
| created_at | TEXT | |
|
| created_at | TEXT | |
|
||||||
|
|
||||||
|
On startup, any existing rows with `role = 'staff'` are automatically migrated to `role = 'pos-staff'`.
|
||||||
|
|
||||||
### `products`
|
### `products`
|
||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
@ -131,6 +154,51 @@ format_amount() ← reads _settings at call time
|
||||||
/config endpoint ← returns _settings to the frontend on every page load
|
/config endpoint ← returns _settings to the frontend on every page load
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### CONFIG keys
|
||||||
|
|
||||||
|
| Key | Default / Notes |
|
||||||
|
|---|---|
|
||||||
|
| `club_name` | Club display name |
|
||||||
|
| `currency_symbol` | e.g. `£` |
|
||||||
|
| `currency_major` | e.g. `GBP` |
|
||||||
|
| `currency_minor` | e.g. `pence` |
|
||||||
|
| `currency_divisor` | e.g. `100` |
|
||||||
|
| `overdraft_policy` | `"never"` / `"always"` / `"staff-override"` / `"admin-override"` / `"staff-block"` |
|
||||||
|
| `min_topup` | Minimum top-up amount (minor units) |
|
||||||
|
| `max_topup` | Maximum top-up amount (minor units) |
|
||||||
|
| `max_charge` | Maximum single charge amount (minor units) |
|
||||||
|
| `biz_address1` – `biz_address4` | Business address lines |
|
||||||
|
| `biz_country` | |
|
||||||
|
| `biz_phone` | |
|
||||||
|
| `biz_email` | |
|
||||||
|
| `biz_website` | |
|
||||||
|
| `logo_url` | URL path to uploaded logo (set automatically on upload) |
|
||||||
|
| `logo_align` | |
|
||||||
|
| `logo_max_width` | Default `200` |
|
||||||
|
| `logo_max_height` | Default `80` |
|
||||||
|
| `bar_name` | Default `"Bar"` |
|
||||||
|
| `cashier_name` | Default `"Cashier"` |
|
||||||
|
| `txn_ref_prefix` | Default `"TXN"` |
|
||||||
|
| `transfer_types` | Comma-separated string (e.g. `"Bank Transfer,Cash,QR"`); returned as an array by `/config` |
|
||||||
|
| `lbl_receipt` | Receipt label keys (14 total — see source for full list) |
|
||||||
|
| `lbl_topup_receipt` | |
|
||||||
|
| `lbl_withdrawal_receipt` | |
|
||||||
|
| `lbl_staff` | |
|
||||||
|
| `lbl_transaction` | |
|
||||||
|
| `lbl_charge_venue` | |
|
||||||
|
| `lbl_txn_time` | |
|
||||||
|
| `lbl_amount_charged` | |
|
||||||
|
| `lbl_remaining_balance` | |
|
||||||
|
| `lbl_balance_transfer` | |
|
||||||
|
| `lbl_amount_topup` | |
|
||||||
|
| `lbl_amount_withdrawal` | |
|
||||||
|
| `lbl_transfer_type` | |
|
||||||
|
| `lbl_transfer_ref` | |
|
||||||
|
| `receipt_footer` | Footer text for all receipts |
|
||||||
|
| `receipt_footer_charge` | Override footer for charge receipts |
|
||||||
|
| `receipt_footer_cashier` | Override footer for cashier receipts |
|
||||||
|
| `timezone` | IANA timezone name; defaults to server local timezone via `_server_timezone()` |
|
||||||
|
|
||||||
To add a new configurable value:
|
To add a new configurable value:
|
||||||
1. Add a default to `CONFIG`
|
1. Add a default to `CONFIG`
|
||||||
2. Add the field to the `AppSettingsUpdate` Pydantic model
|
2. Add the field to the `AppSettingsUpdate` Pydantic model
|
||||||
|
|
@ -142,11 +210,13 @@ To add a new configurable value:
|
||||||
## Auth System
|
## Auth System
|
||||||
|
|
||||||
- `POST /auth/login` validates credentials against `staff_accounts`, creates a `secrets.token_hex(32)` token, stores it in the module-level `_sessions` dict, and sets it as an `httpOnly` cookie.
|
- `POST /auth/login` validates credentials against `staff_accounts`, creates a `secrets.token_hex(32)` token, stores it in the module-level `_sessions` dict, and sets it as an `httpOnly` cookie.
|
||||||
- All protected endpoints use `Depends(current_user)` which reads the cookie and looks up the session.
|
- All protected endpoints use a `Depends()` guard appropriate to the required role:
|
||||||
- Admin-only endpoints use `Depends(admin_user)` which calls `current_user` then checks `role == "admin"`.
|
- `Depends(pos_user)` — allows `pos-staff` and `admin`. Used by bar endpoints.
|
||||||
|
- `Depends(cashier_user)` — allows `cashier` and `admin`. Used by cashier endpoints.
|
||||||
|
- `Depends(admin_user)` — `admin` only.
|
||||||
- Sessions expire after 8 hours (configurable via `SESSION_TTL` in `main.py`).
|
- Sessions expire after 8 hours (configurable via `SESSION_TTL` in `main.py`).
|
||||||
- Sessions are lost on server restart (in-memory). This is intentional for simplicity; upgrade to a DB-backed session store if persistence is needed.
|
- Sessions are lost on server restart (in-memory). This is intentional for simplicity; upgrade to a DB-backed session store if persistence is needed.
|
||||||
- Print views (`/receipt/`, `/members/*/statement`) deliberately have **no auth** — they are opened as pop-up tabs from an authenticated page.
|
- Print views (`/receipt/`, `/members/*/statement`) have no auth — they are opened as pop-up tabs from an authenticated session.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -168,19 +238,20 @@ All endpoints except `/config`, `/auth/login`, and the print views require a val
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | `/members?q=` | List/search. Returns balance per member. |
|
| GET | `/members?q=` | List/search. Returns balance per member. |
|
||||||
| POST | `/members` | `{member_number, name, pin}` |
|
| POST | `/members` | `{member_number, name, pin}` |
|
||||||
| PUT | `/members/{id}` | `{member_number?, name?, pin?}` — all optional |
|
| PUT | `/members/{id}` | `{member_number?, name?, pin?, overdraft_override?}` — all optional |
|
||||||
| DELETE | `/members/{id}` | Blocked if balance ≠ 0 |
|
| DELETE | `/members/{id}` | Blocked if balance ≠ 0 |
|
||||||
| GET | `/members/{id}/transactions` | `?limit=50&offset=0` |
|
| GET | `/members/{id}/transactions` | `?limit=50&offset=0` |
|
||||||
| GET | `/members/{id}/statement` | Returns printable HTML |
|
| GET | `/members/{id}/statement` | Returns printable HTML. No auth. |
|
||||||
|
|
||||||
### Transactions
|
### Transactions
|
||||||
|
|
||||||
| Method | Path | Body |
|
| Method | Path | Body |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| POST | `/topup` | `{member_id, amount, note?}` — amount in minor units |
|
| POST | `/topup` | `{member_id, amount, transfer_type?, transfer_ref?, note?}` — amount in minor units |
|
||||||
| POST | `/charge` | `{member_id, amount, pin, note?}` |
|
| POST | `/charge` | `{member_id, amount, pin, note?}` |
|
||||||
|
| POST | `/withdrawal` | `{member_id, amount, pin, transfer_type?, transfer_ref?, note?}` |
|
||||||
|
|
||||||
Both return `{ok, entry_id, new_balance, new_balance_display}`.
|
All return `{ok, entry_id, new_balance, new_balance_display}`.
|
||||||
|
|
||||||
### Receipts
|
### Receipts
|
||||||
|
|
||||||
|
|
@ -201,6 +272,7 @@ Both return `{ok, entry_id, new_balance, new_balance_display}`.
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | `/admin/settings` | Admin only |
|
| GET | `/admin/settings` | Admin only |
|
||||||
| POST | `/admin/settings` | Admin only. Partial update — only sent fields are changed. |
|
| POST | `/admin/settings` | Admin only. Partial update — only sent fields are changed. |
|
||||||
|
| POST | `/admin/logo` | Admin only. Multipart file upload. Saves image to `static/logo.<ext>`, updates `logo_url` setting, returns `{url}`. Accepts PNG, JPG, GIF, WebP, SVG. |
|
||||||
| GET | `/admin/staff-accounts` | Admin only |
|
| GET | `/admin/staff-accounts` | Admin only |
|
||||||
| POST | `/admin/staff-accounts` | `{name, username, password, role}` |
|
| POST | `/admin/staff-accounts` | `{name, username, password, role}` |
|
||||||
| PUT | `/admin/staff-accounts/{id}` | All fields optional |
|
| PUT | `/admin/staff-accounts/{id}` | All fields optional |
|
||||||
|
|
@ -210,7 +282,7 @@ Both return `{ok, entry_id, new_balance, new_balance_display}`.
|
||||||
|
|
||||||
| Method | Path | Notes |
|
| Method | Path | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| GET | `/config` | Returns live `_settings`. Called by the frontend on every page load. |
|
| GET | `/config` | Returns live `_settings`. Called by the frontend on every page load. `transfer_types` is returned as an array. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -239,7 +311,7 @@ See the Settings System section above.
|
||||||
### Adding a new transaction venue
|
### Adding a new transaction venue
|
||||||
|
|
||||||
1. Add the new venue value to the `CHECK` constraint in the `ledger_entries` schema — requires a migration or database recreation.
|
1. Add the new venue value to the `CHECK` constraint in the `ledger_entries` schema — requires a migration or database recreation.
|
||||||
2. Add a new endpoint (or extend `/topup`/`/charge` with a `venue` parameter).
|
2. Add a new endpoint (or extend existing transaction endpoints with a `venue` parameter).
|
||||||
3. Add a new tab or form in `index.html` / `app.js`.
|
3. Add a new tab or form in `index.html` / `app.js`.
|
||||||
|
|
||||||
### Adding product management UI
|
### Adding product management UI
|
||||||
|
|
@ -266,5 +338,6 @@ Adjust `current_user()` to query the table instead of the dict.
|
||||||
|
|
||||||
- The database file `clubledger.db` is created automatically in the working directory on first run. Add it to `.gitignore`.
|
- The database file `clubledger.db` is created automatically in the working directory on first run. Add it to `.gitignore`.
|
||||||
- `staff.json` is also created in the working directory. Add to `.gitignore`.
|
- `staff.json` is also created in the working directory. Add to `.gitignore`.
|
||||||
|
- `static/logo.*` is created when an admin uploads a logo via the Admin panel. Add to `.gitignore`.
|
||||||
- No environment variables are required. All configuration is in `CONFIG` (code) or `app_settings` (database).
|
- No environment variables are required. All configuration is in `CONFIG` (code) or `app_settings` (database).
|
||||||
- The app binds to `0.0.0.0:8000` by default — accessible from any device on the network. Pass `--host 127.0.0.1` to restrict to localhost only.
|
- The app binds to `0.0.0.0:8000` by default — accessible from any device on the network. Pass `--host 127.0.0.1` to restrict to localhost only.
|
||||||
|
|
|
||||||
|
|
@ -2,114 +2,218 @@
|
||||||
|
|
||||||
## What is ClubLedger?
|
## What is ClubLedger?
|
||||||
|
|
||||||
ClubLedger is a store-credit system for clubs and venues. Members load credit onto their account at the cashier desk, then spend it at the bar or other service points. All transactions are tracked and receipts are printed automatically.
|
ClubLedger is a store-credit system for clubs and venues. Members load credit onto their account at the cashier desk, then spend it at the bar or other service points. All transactions are tracked and receipts are generated automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Signing In
|
## Signing In
|
||||||
|
|
||||||
Open the ClubLedger address in any web browser. You will see a sign-in screen. Enter the username and password given to you by your administrator, then click **Sign In**.
|
Open the ClubLedger address in any web browser. Enter the username and password provided by your administrator, then click **Sign In**.
|
||||||
|
|
||||||
Your name appears in the top-right corner of every screen while you are signed in. Click **Sign out** when you are done.
|
Your name appears in the top corner of every screen while you are signed in. Click **Sign out** when you are finished.
|
||||||
|
|
||||||
> Sessions expire after 8 hours. The sign-in screen will reappear automatically when your session ends.
|
> **Sessions expire after 8 hours.** The sign-in screen reappears automatically when your session ends. If the server is restarted, everyone is logged out regardless of how long they have been signed in.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The Three Tabs
|
## Tabs and Roles
|
||||||
|
|
||||||
The navigation bar at the top has three tabs: **Members**, **Cashier**, and **Bar**. Click a tab to switch between them. Administrators also see an **Admin** tab.
|
The navigation bar shows tabs depending on your role. You will only see the tabs listed for your role below.
|
||||||
|
|
||||||
|
| Role | Tabs visible |
|
||||||
|
|---|---|
|
||||||
|
| **POS Staff** | Members, Bar |
|
||||||
|
| **Cashier** | Members, Cashier |
|
||||||
|
| **Admin** | Members, Cashier, Bar, Admin |
|
||||||
|
|
||||||
|
Click any tab to switch to it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Members Tab
|
## Members Tab
|
||||||
|
|
||||||
Use this tab to register new members, look up existing members, and print account statements.
|
All roles can see this tab. Use it to register new members, search for existing members, and manage member records.
|
||||||
|
|
||||||
### Registering a New Member
|
### Registering a New Member
|
||||||
|
|
||||||
Fill in the **Register New Member** form:
|
Fill in the **Register New Member** form at the top of the tab:
|
||||||
|
|
||||||
| Field | Notes |
|
| Field | Notes |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Member Number | A unique ID for the member — a number, code, or anything you choose |
|
| Member Number | A unique identifier — a number, a code, or any text your venue uses |
|
||||||
| Full Name | The member's name as it should appear on receipts |
|
| Full Name | The member's name as it will appear on receipts and statements |
|
||||||
| PIN | A secret 4-digit (or longer) code the member uses at the bar. Tell the member their PIN privately. |
|
| PIN | A secret code (minimum 4 characters) the member uses to authorise charges. Tell the member their PIN privately. |
|
||||||
|
|
||||||
Click **Register**. The member appears in the table below.
|
Click **Register**. The new member appears in the table below.
|
||||||
|
|
||||||
### Searching for a Member
|
### Searching for a Member
|
||||||
|
|
||||||
Type part of a name or member number into the search box and click **Search** (or press Enter). Leave the box empty and search to list everyone.
|
Type part of a name or member number into the search box and click **Search** (or press **Enter**). Searching with an empty box lists all members.
|
||||||
|
|
||||||
### The Member Table
|
### The Member Table
|
||||||
|
|
||||||
Each row shows the member's number, name, current balance, and join date.
|
Search results appear in a table with these columns:
|
||||||
|
|
||||||
|
| Column | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| # | Member number |
|
||||||
|
| Name | Full name |
|
||||||
|
| Balance | Current account balance |
|
||||||
|
| Joined | Registration date |
|
||||||
|
| Actions | Buttons to act on this member |
|
||||||
|
|
||||||
|
### Actions
|
||||||
|
|
||||||
|
Each row has up to three action buttons:
|
||||||
|
|
||||||
| Button | What it does |
|
| Button | What it does |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Statement** | Opens a printable full transaction history in a new tab |
|
| **Statement** | Opens a printable full transaction history in a new tab |
|
||||||
| **Edit** | Change the member's name, number, or PIN |
|
| **Edit** | Opens a modal to change the member's details |
|
||||||
| **Delete** | Only appears when balance is exactly zero. Permanently removes the member. |
|
| **Delete** | Permanently removes the member. Only appears when the balance is exactly zero. |
|
||||||
|
|
||||||
### Editing a Member
|
### Editing a Member
|
||||||
|
|
||||||
Click **Edit** on any row. A panel appears with the current name and member number pre-filled. Change what you need. Leave the **New PIN** field blank to keep their current PIN. Click **Save**.
|
Click **Edit** on a row. A modal appears with the current details pre-filled.
|
||||||
|
|
||||||
|
| Field | Notes |
|
||||||
|
|---|---|
|
||||||
|
| Member Number | Change the member's unique ID if needed |
|
||||||
|
| Full Name | Update the name |
|
||||||
|
| New PIN | Enter a new PIN to change it. Leave blank to keep the existing PIN. |
|
||||||
|
| Overdraft override | May appear depending on the global overdraft policy and your role — see the Overdraft section below. |
|
||||||
|
|
||||||
|
Click **Save** to apply changes or close the modal to cancel.
|
||||||
|
|
||||||
### Printing a Statement
|
### Printing a Statement
|
||||||
|
|
||||||
Click **Statement** to open the statement in a new tab. Use the **A4 / A5** toggle to choose the paper size, then click **Print Statement**.
|
Click **Statement** on any member's row. The full transaction history opens in a new tab. Use the **A4 / A5** toggle to select paper size, then print from your browser.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Cashier Tab
|
## Cashier Tab
|
||||||
|
|
||||||
Use this tab to add credit to a member's account (top-up).
|
Cashiers and Admins can see this tab. Use it to add credit to a member's account (top-up) or withdraw credit from it.
|
||||||
|
|
||||||
### How to Top Up
|
### Selecting a Member
|
||||||
|
|
||||||
1. Search for the member by name or number and click their row.
|
Search for the member and click their row to select them. Their name and current balance appear at the top of the panels. Click **Cancel** at any time to deselect the member and clear all fields.
|
||||||
2. The selected member's name and current balance appear at the top of the form.
|
|
||||||
3. Enter the **Amount** — type it in the major currency unit (e.g. `10.00` for ten pounds).
|
|
||||||
4. Add an optional **Note** (e.g. "cash payment", "card payment").
|
|
||||||
5. Click **Top Up**.
|
|
||||||
|
|
||||||
A receipt opens automatically in a new tab. Print it or close it.
|
### Top Up Panel
|
||||||
|
|
||||||
If you need to start over, click **Cancel** to deselect the member.
|
Use this panel to add credit to the member's account.
|
||||||
|
|
||||||
### Receipts
|
| Field | Notes |
|
||||||
|
|---|---|
|
||||||
|
| Amount | The amount to add, in the major currency unit (e.g. `10.00`) |
|
||||||
|
| Transfer Type | How the payment was made — options are configured by your administrator (e.g. Bank Transfer, Cash, QR) |
|
||||||
|
| Transfer Reference | Optional. A reference for your records, such as a payment reference number |
|
||||||
|
| Note | Optional. Any additional note about this transaction |
|
||||||
|
|
||||||
Receipts show: member name and number, transaction type, amount, balance after, staff name, timestamp, and any footer text set by the administrator.
|
Click **Top Up**. A receipt opens automatically in a new tab.
|
||||||
|
|
||||||
Use the **A4 / A5** toggle at the top of the receipt page before printing.
|
### Withdrawal Panel
|
||||||
|
|
||||||
|
Use this panel to remove credit from the member's account.
|
||||||
|
|
||||||
|
| Field | Notes |
|
||||||
|
|---|---|
|
||||||
|
| Amount | The amount to withdraw |
|
||||||
|
| Member PIN | Required. The member must provide their PIN to authorise every withdrawal. |
|
||||||
|
| Transfer Type | How the funds are being returned — options configured by your administrator |
|
||||||
|
| Transfer Reference | Optional. A reference for your records |
|
||||||
|
| Note | Optional. Any additional note |
|
||||||
|
|
||||||
|
Click **Withdraw**. A receipt opens automatically in a new tab.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bar Tab
|
## Bar Tab
|
||||||
|
|
||||||
Use this tab to charge a member's account (debit). The member must enter their PIN.
|
POS Staff and Admins can see this tab. Use it to charge a member's account for purchases.
|
||||||
|
|
||||||
### How to Charge
|
### How to Charge
|
||||||
|
|
||||||
1. Search for the member and click their row.
|
1. Search for the member by name or number and click their row.
|
||||||
2. Enter the **Amount** to charge (e.g. `3.50`).
|
2. Enter the **Amount** to charge.
|
||||||
3. The member enters their **PIN** into the field.
|
3. Enter the member's **PIN** — this is always required.
|
||||||
4. Add an optional **Note** (e.g. the item name).
|
4. Optionally add a **Note** (for example, what was purchased).
|
||||||
5. Click **Charge**.
|
5. Click **Charge**.
|
||||||
|
|
||||||
If the PIN is wrong, an error appears and nothing is charged. If the balance is insufficient, the charge is also blocked (unless the administrator has enabled overdraft).
|
If the PIN is incorrect, an error appears and nothing is charged. If the balance is insufficient, the charge is blocked unless the member has overdraft permission (see the Overdraft section).
|
||||||
|
|
||||||
A receipt opens automatically in a new tab on a successful charge.
|
A receipt opens automatically in a new tab on a successful charge.
|
||||||
|
|
||||||
|
Click **Cancel** to deselect the member and clear all fields.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Receipts and Statements
|
||||||
|
|
||||||
|
### Receipts
|
||||||
|
|
||||||
|
A receipt opens in a new tab automatically after every successful transaction. Receipts include:
|
||||||
|
|
||||||
|
- Business header (logo, name, address, contact details)
|
||||||
|
- Receipt title and transaction reference (e.g. `TXN0000001`)
|
||||||
|
- Staff name who processed the transaction
|
||||||
|
- Venue and timestamp (in the configured timezone)
|
||||||
|
- Amount and remaining balance
|
||||||
|
- For top-ups and withdrawals: the transfer type and transfer reference
|
||||||
|
|
||||||
|
Use the **A4 / A5** toggle at the top of the receipt before printing.
|
||||||
|
|
||||||
|
> **Tip:** If the receipt tab does not open, your browser may be blocking pop-ups. Allow pop-ups for this site in your browser settings.
|
||||||
|
|
||||||
|
### Statements
|
||||||
|
|
||||||
|
Statements are accessed via the **Statement** button in the Members tab. They show the member's complete transaction history as a table:
|
||||||
|
|
||||||
|
| Column | Content |
|
||||||
|
|---|---|
|
||||||
|
| Date/Time | When the transaction occurred (configured timezone) |
|
||||||
|
| Reference | Transaction reference (e.g. `TXN0000001`) |
|
||||||
|
| Type | Top-up, Withdrawal, or Charge |
|
||||||
|
| Venue | Where the transaction was processed |
|
||||||
|
| Staff | Who processed the transaction |
|
||||||
|
| Amount | Positive for top-ups, negative for charges and withdrawals |
|
||||||
|
| Balance | Account balance after that transaction |
|
||||||
|
|
||||||
|
Each transaction also has a second row showing transfer details (for top-ups and withdrawals) or the note (for charges).
|
||||||
|
|
||||||
|
Use the **A4 / A5** toggle before printing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overdraft
|
||||||
|
|
||||||
|
By default, charges that would take a member's balance below zero are blocked. Your administrator can change this behaviour using a global overdraft policy. The policy affects what you see in the **Edit Member** modal:
|
||||||
|
|
||||||
|
| Policy | What you see in Edit Member | What it means |
|
||||||
|
|---|---|---|
|
||||||
|
| Never allowed | No checkbox | No member can go into overdraft, ever |
|
||||||
|
| Always allowed | No checkbox | All members can always go into overdraft |
|
||||||
|
| Staff override | Checkbox (staff can tick it) | Ticking the checkbox for a member allows them to go into overdraft |
|
||||||
|
| Admin override | Checkbox (only admins can tick it) | Same as above, but only admins can set it |
|
||||||
|
| Staff block | Checkbox (staff can tick it) | Ticking the checkbox for a member blocks them from overdraft |
|
||||||
|
|
||||||
|
If you are unsure whether a member should be allowed to go into overdraft, check with your administrator before changing any checkbox.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Common Questions
|
## Common Questions
|
||||||
|
|
||||||
**The member forgot their PIN.** An administrator can reset it: Members tab → Edit → enter a new PIN.
|
**The member forgot their PIN.**
|
||||||
|
An admin or cashier with edit access can reset it: Members tab → Edit → enter a new PIN in the **New PIN** field → Save. Leave the field blank if you do not want to change it.
|
||||||
|
|
||||||
**I topped up the wrong amount.** Contact an administrator. There is no undo button — a correcting charge or top-up must be applied manually and noted.
|
**I entered the wrong amount.**
|
||||||
|
There is no undo button. A correcting transaction must be applied manually. For a top-up error, process a withdrawal for the difference (or the full amount and re-top-up correctly). For a bar charge error, contact an administrator.
|
||||||
|
|
||||||
**The receipt tab didn't open.** Your browser may be blocking pop-ups. Allow pop-ups for this site in your browser settings, or navigate directly to the statement page via Members → Statement.
|
**The receipt tab did not open.**
|
||||||
|
Your browser is likely blocking pop-ups. Find the pop-up blocked notification in your browser's address bar and allow pop-ups for this site, then try the transaction again.
|
||||||
|
|
||||||
**The balance shows in the wrong currency.** Contact your administrator to update the currency settings in the Admin area.
|
**A member's balance is wrong.**
|
||||||
|
Use the **Statement** button on the member's row to view their full transaction history and identify any discrepancies. Contact an administrator if a correction is needed.
|
||||||
|
|
||||||
|
**I cannot see a tab I expect.**
|
||||||
|
Tab visibility depends on your role. If you believe your role is incorrect, contact your administrator.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue