Staff were seeing false 'out of balance' readings when the Newbook
totals shown were fetched at page load (or last manual click) and
had gone stale during the count, then balanced once re-checked
later against fresh Newbook data. Surfacing when the data was last
fetched, with a warning past 3 minutes, lets staff catch this before
submitting instead of assuming a real shortfall.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
iOS Safari has no beforeinstallprompt API, so the portal's Install
button (?install=1) silently did nothing there. IosInstallHint now
shows Share -> Add to Home Screen steps on Safari, or a prompt to
switch to Safari first if opened in another iOS browser/in-app
webview (those can't install PWAs on iOS at all).
Also added apple-mobile-web-app-title + apple-touch-icon so the
home screen icon isn't a page screenshot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract the click/shift-click cell selection + sum/avg status bar
from the weekly report into a shared useExcelSelection hook, and wire
it up to the New Total column (and its footer total) on Safe Count.
Daily Cash Up initialised its date state from today() only, ignoring
the ?date= query param History links navigate with. Read it from the
URL on mount instead.
Adds POST /api/cashup/:id/unfinalise (admin-only) to revert a
finalised cash up back to draft so it can be corrected, plus an
Unfinalise button in the history table for admin users.
Scope was set to the app's own base path, which breaks the installed
PWA out of standalone mode into a regular browser tab on any
same-origin navigation outside that path (e.g. the auth redirect).
scope: "/" keeps navigation inside the installed app's window.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The /cashup/api/auth/ → central auth nginx block was missing, so the logout
POST was hitting the app backend and returning 404 — cookie never cleared,
user stayed logged in. Also removes the stale navigate('/login') call and
shows name + email in the sidebar footer instead of name in the header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking 'Start Cash Up' now POSTs an empty draft to the backend straight
away, then reloads the record to populate cashUp.id — so PDQ Z-report and
receipt evidence uploaders are available without having to save a draft first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetch reports_daily_audit_summary per day. Use cash_income total as the
reconciliation audit column (independent payment cross-check vs transaction
flow). Add a second Audit (DAS) column on the sales breakdown using
accrual_income totals alongside the earned revenue Audit (ER) column —
both highlight in amber if they diverge from the configured columns gross.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gross Total now sums only configured GL column gross amounts. Audit column
shows the all-groups earned revenue total (daily_gross_sales), with amber
variance highlight when they diverge — flags unconfigured GL groups the same
way the original WP plugin did.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch sales breakdown column matching from fuzzy name matching to exact
gl_group_id comparison. Add gl_account_groups_list endpoint for settings
sync so columns store real Newbook group IDs. Removes the name-based
fallback that was causing sundries VAT to bleed into sundries no-VAT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tables with multiple columns now scroll horizontally within their cards
instead of overflowing off-screen on mobile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hamburger menu with slide-in nav for viewports < 700px; auto-closes on route change
- PhotoUploader: separate camera trigger on mobile (capture="environment"), file input hidden on mobile; useCallback to fix stale-closure reset
- Add .gitignore to exclude compiled JS artefacts and package-lock
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vite-plugin-pwa with manifest scoped to /cashup/, purple £ icons matching
portal style, auto install prompt when opened with ?install=1 from the
portal dashboard, nginx no-cache rules for sw.js and manifest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Safe Count (new flow):
- Replaces simple re-use of FloatCountForm with a dedicated 3-column table:
Current in Safe | Adjustment | New Total
- Loads the most recent safe_cash float count on mount to pre-populate Current
- Adjustment inputs accept positive (deposit) or negative (uplift/exchange) values
- New Total = Current + Adjustment per denomination; minimum clamped to 0
- Saves a new safe_cash float_count record via existing /floats/save endpoint
Cash Summary (redesign):
- Backend adds a per-date denomination query (by_date_denom) to the
cash-summary endpoint in addition to the existing aggregate query
- Frontend rewritten as a cross-tab: denominations down rows, dates across
columns, row totals on the right, per-day cash totals in the footer
- Status badges (draft/final) shown per column in a header row
- "+ Add to Safe" button navigates to /safe passing the period's denomination
totals as React Router state, pre-filling the adjustment column
Float Management:
- Denomination entry form switched from CSS grid divs to a proper <table>
matching the style of the print view (header row, qty input right-aligned,
amount column, total in tfoot)
- DetailRecord type and FloatRecordPrint component exported so SafeCount can
reuse the existing print/view component for safe cash records
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applyLoaded now appends any DB machines whose name is no longer in the
current settings list, rather than silently dropping them. This prevents
their amounts being deleted from the database the next time the record
is saved (backend does DELETE + re-insert on every save).
Orphaned machines render with a red 'Removed from settings' badge, a
pink border, and read-only inputs so the data is visible but can't be
accidentally edited. They are still included in totalPdq and the save
payload, so nothing is lost.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings: add Card Machines section to configure number of PDQ terminals
and their names (1–6 machines, defaults to Front Desk + Restaurant / Bar).
Names are stored under the 'card_machines' settings key.
DailyCashUp: machine names now load from settings instead of hardcoded
const. Card Machines section changed from a 2-column grid to a vertical
list; each machine row shows the amount inputs on the left and its PDQ
Z-report photo uploader on the right. Separate PDQ Z-Reports card removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the float count variance pattern — once Newbook data is loaded,
shows the reported cash amount and the counted vs reported variance
directly beneath the Cash Takings header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue #1: Parse card machine amounts as floats in applyLoaded — pg returns
DECIMAL as strings which caused JS string concatenation in totalPdq and
reconciliation banked amounts. After draft save, reload cashUp to get the
persisted ID so PDQ Z-report uploads work on first save.
Issue #2: Guard empty gl_code in sales breakdown name-matching — an empty
column code caused gName.includes('') to always be true, making placeholder
columns (like WET 5%) match the first available GL group (dry dept).
Issue #3: Move Cash Summary above Safe Count in sidebar nav. Extend
cash-summary endpoint with per-day breakdown. Rewrite CashSummary.tsx
with a daily cash takings table above the denomination summary.
Issue #4: Add @media print CSS — hide sidebar nav, force colour printing,
style disabled inputs as plain text, and keep tables/cards intact on paper.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fetches receipts from the most recent petty cash count and populates the
current form — for when the tin hasn't been replenished and slips carry over.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was filtering out any denomination with target=0 before checking if it
was counted, so notes (default target £0) never appeared even when surplus.
Now: skip only shortfalls where no target is set; surpluses always show.
Also use denom.target from the saved record (backend already populates this)
instead of current-settings lookup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Qty entered for coins = number of bags, so total = BAG_VALUES[denom] × qty
not denom × qty. Fixes totalCounted, row totals, change order counted column,
and save payload. Also shows £X/bag hint under coin denomination labels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change tin count: remove bags/loose split — single qty per denomination
- After save: show saved record inline with Print button (clean new-window
printout with denomination table, receipts, totals/variance, signature lines)
- Change tin print: bank exchange order table (surplus=to bank, shortfall=in exchange)
- Petty cash history: Cash | Receipts | Total | Variance columns
- Fix uploads path: /app/src/../../uploads resolved to /uploads (container root,
outside Docker volume). Changed to /app/src/../uploads = /app/uploads so files
persist across rebuilds on the named volume.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Change tin targets:
- Add £5/£10/£20/£50 note rows (qty in individual notes, not bags)
- Fix label: (= £40.00 @ £20.00 each) instead of (= £40.00 each)
- Add per-denomination Notes text field (stored as change_tin_notes setting)
- Table layout replaces 2-col grid for all 10 denominations
Float count form:
- Notes denominations show a single Qty input; coins keep Bags + Loose
- Change order correctly shows "notes" vs "bags" needed per denomination
Sales breakdown columns:
- Add placeholder column button (gl_code='', labelled PLACEHOLDER)
- Add delete (×) button on every column row
- Fix key={i} to support multiple placeholders with same empty gl_code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
INSERT RETURNING * was succeeding but response threw TypeError trying to
access .rows[0] on the already-unwrapped rows array. File was saved, DB
record created, but 500 returned. Frontend now gets the record immediately
without needing a refresh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix history nav: type underscore → hyphen for route match
- Change tin: Bags + Loose inputs per denomination with UK bag values
- Change tin target: per-denomination bag count in Settings
- Change order sheet: inline table showing bags to order vs counted
- db: bag_quantity column on float_denominations
- api.ts: put text fields before file in FormData so busboy doesn't drain file stream early
- db.js: additive migration for uploaded_by column on cash_count_attachments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-save: every 60s while editing, silently saves draft if form has data.
Status "Auto-saved at HH:MM" appears next to the Save Draft button briefly.
- Float Count moved above Cash Takings and defaults to open so staff count
the float first before counting takings.
- Reconciliation table gains a fifth "Card Total" column: PDQ Visa/MC and
PDQ Amex rows share a rowspan=2 cell showing the combined PDQ variance.
If the per-category rows are red but the combined is green it means only
the Visa/Amex split was mis-allocated, not an actual cash discrepancy
(labelled "split only"). Gateway rows similarly grouped (always auto 0).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Persist transaction breakdown checked state in cash_ups.checked_transactions
(JSONB column); saved on each draft/final save and restored on page load.
Re-fetching Newbook no longer resets the checked items.
- Add till_float_target setting; Float Count card shows target and variance
so staff can see at a glance whether the till balances.
- Compress images client-side (max 1600px, 85% JPEG) before upload to avoid
504 gateway timeouts from NPM when uploading large mobile photos.
- File input accepts image/* only (was image/*+PDF which confuses mobile pickers).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds three new granular capabilities:
- history: gates /history page and GET /api/cashup/history
- cash_summary: gates /summary page and GET /api/reports/cash-summary
- safe_count: gates /safe page and safe_cash float routes
Updates legacy-token fallback to include all seven non-settings caps.
Route guards and nav items updated to use the split caps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DB:
- Add attachment_type ('pdq_z_report' | 'receipt_error' | 'other') and
label columns to cash_count_attachments; migrate existing rows via
ALTER TABLE IF NOT EXISTS (no data loss)
Backend:
- Upload route now reads attachment_type and label from multipart form
fields alongside the file, stores them in the DB
Frontend:
- PhotoUploader component: thumbnail grid, upload button (images + PDF),
spinner during upload, ✕ delete button on each photo, click opens in
new tab
- DailyCashUp: PDQ Z-Reports card (one section per machine — Front Desk /
Restaurant Bar) appears after Card Machines when a cash up exists
- DailyCashUp: Receipt Evidence card at bottom for any discrepancy photos;
stays editable even on final cash ups so receipts can be added later
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Variance = banked total - reported total across all payment categories.
Green badge = balanced, red = short (▼), amber = over (▲).
Days with no cash up show no variance (nothing was counted).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch from onClick to onMouseDown + e.preventDefault() so the
browser never starts its own text-selection on click or drag
- Add document-level mousemove/mouseup listeners for drag-to-select:
hold mouse button and sweep across cells to highlight a range
- Shift+click still extends the rectangle from the anchor cell
- userSelect: none on tables prevents any residual text highlighting
- Stats bar (Count/Sum/Avg) now appears after mouseup so it reads the
final selection, not mid-drag state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Backend: sales_breakdown column matching was always failing because
Newbook returns numeric gl_group_id but column settings use string codes
like 'ACCOMMODATION'. Added normalised name-fuzzy fallback matching and
a daily_gross_sales field that sums ALL earned revenue for the date,
bypassing column config entirely.
- Frontend: dayGrossSales() now uses daily_gross_sales from backend first
- Add floating Excel-style status bar (bottom-right): when cells are
selected it shows Count, Sum, and Average of the selected values;
disappears when ✕ clicked or selection cleared
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reconciliation now has two side-by-side tables (BANKED left, REPORTED right)
with dates on rows and payment categories on columns — matching original orientation
- BANKED table: green bg for manual cells (cash/PDQ), orange for auto (gateway/BACS),
red/amber bg + ▼/▲ indicator when variance vs reported
- REPORTED table: orange background, Audit column showing daily gross sales
- Gross Sales sub-table below reconciliation
- Sales Breakdown: dates now on rows, GL categories on columns (was transposed)
with Total Net / VAT / Gross Total / Audit columns
- Occupancy: dates on rows, metric columns, hover tooltips with category breakdown
- Copy/Paste Format section: spreadsheet-ready 2-row-per-date tables
- Excel-like cell selection: click to select, Shift+click for range,
Ctrl+C copies selected cells as TSV to clipboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Occupancy table now calculates from raw Newbook data:
- Rooms occupied (from per-category occupancy API)
- Occ % (rooms occupied / total rooms from sites_data)
- Guests (adults + children + infants from bookings_data)
- Net accommodation revenue (from earned_revenue, ACC GL group)
- Avg net per room, REVPAR
- GGR (average guest rate from tariffs_quoted)
- Avg lead time (arriving bookings only)
Sales breakdown now shows Net, VAT, Gross as separate total columns.
Debtors/Creditors adds Period Close row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- History: slice session_date to YYYY-MM-DD before Date parse (postgres
returns date columns as ISO timestamps in JSON)
- History: fix Edit/View navigate link passing full ISO string as date param
- Weekly report: add daily status row, reconciliation variance (banked vs
Newbook reported), sales with gross+net, debtors/creditors lazy-loaded
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Newbook payments now fetched automatically when a date loads (in
parallel with the cash up fetch), not just on button click
- Refresh button still available for manual re-fetch
- Transaction breakdown section below reconciliation table: three
collapsible groups (Reception Manual, Reception Gateway, Restaurant/Bar)
each showing individual transactions with click-to-tick checkboxes
for tracking down discrepancies — local state, clears on refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PostgreSQL NUMERIC columns return as strings in node-postgres. denomTotal
was doing 0 + "40.00" + "30.00" → "040.0030.00", and parseFloat in
fmtGBP read only the first portion, giving a wrong total (£40.00 instead
of £78.80 etc).
Fix: coerce total_amount and value_entered to numbers in applyLoaded;
make denomTotal's reducer defensively use Number() so strings can't
silently break the sum again.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cash summary: derive total_quantity from total_amount/denomination_value
so value_entered rows are counted (previously SUM(quantity) returned
NULL for those rows)
- Safe Count: moved safe_cash out of Float Management tabs into its own
/safe/* route with a dedicated sidebar entry (Vault icon)
- FloatManagement now only shows Petty Cash and Change Tin tabs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- On mount/date change: auto-fetch existing cash up
- Draft found → auto-load into editing state
- Finalised → auto-load into locked state (read-only + banner)
- Not found → show 'Start Cash Up' prompt
- Removed manual 'Load Existing' / 'New' buttons
- Submitting final transitions immediately to locked state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tab now moves down the Qty column first (£50→£20→...→1p), then down
the Value Override column, for both takings and float grids.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These were in the .env file but missing from docker-compose environment,
so process.env.SETTINGS_URL was undefined and newbook.js crashed on
URL construction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>