Commit graph

744 commits

Author SHA1 Message Date
317b79ed29 refactor(pos): make buildEposDeliveryXml block-driven 2026-09-04 13:28:45 +02:00
81197ad641 refactor(pos): make buildEposXml block-driven 2026-09-04 13:27:13 +02:00
c56211d35e chore(pos): wire slip-layout.js into receipt/cashup/settings, bump SW cache 2026-09-04 13:25:31 +02:00
8e879d1447 feat(pos): add shared slip-layout block catalog and resolver 2026-09-04 13:24:40 +02:00
102673d00b feat(pos): add GET/PUT /api/v1/pos/slip-layout/[letterheadId] 2026-09-04 13:24:00 +02:00
3a29ad6a6a feat(pos): add pos-slip-layout-service for reading/writing per-letterhead slip layouts 2026-09-04 13:23:09 +02:00
e0ce1310e0 docs(pos): add implementation plan for the slip layout editor 2026-09-04 13:20:16 +02:00
7d2132e000 fix(pos): fix uncaught 500 on checkout, allow overselling into negative stock
- checkoutSale's stock-availability loop called pb.getOne() with no
  try/catch, so a stale/deleted stock_item_id in a persisted cart
  threw all the way up to a bare 500 with no useful message. Now
  caught and returned as a clear pb_error the till can display.
- Removed the insufficient-stock block entirely: the till is used
  ahead of a physical stock take, so a sale must never be blocked
  for lack of stock — current_qty is now allowed to go negative and
  gets corrected at the next stock take. Drops the now-dead
  insufficient_stock/shortages branches from the route, service
  types, and client error handling.
2026-09-04 13:00:12 +02:00
85761a8698 fix(orders): improve error handling for order creation and enforce email body length limit
Enhanced error messages to include specific field validation details from PocketBase, allowing callers to identify issues more easily. Additionally, implemented a safeguard to truncate the email body to a maximum of 5000 characters to prevent validation errors from surfacing as generic messages.
2026-09-04 12:55:53 +02:00
b20651b12d fix(pos): replace undefined toast.error() calls with a real toast
confirmSale() called toast.error(...) on checkout rejection (e.g.
insufficient stock), but no toast global was ever defined anywhere
in the app, so the ReferenceError swallowed the intended error
message and operators saw nothing. Adds a minimal self-contained
showToast() and bumps the SW cache so tills pick it up.
2026-09-04 12:52:26 +02:00
1bd659869d chore(pos): add pos_slip_layouts PB collection for the slip layout editor
One row per letterhead, holding the reorderable/toggleable block
list for each thermal slip (sale, delivery note, cash-up) plus the
delivery note's default pricing toggle. Migration already run
against the live PocketBase instance.
2026-09-04 12:46:17 +02:00
024168f90d docs(pos): add design spec for the slip layout editor
Covers the reorderable block-list editor for sale/delivery/cash-up
slips, per-letterhead persistence, and the delivery-note pricing
default, worked out via brainstorming with the visual companion.
2026-09-04 12:42:45 +02:00
2f3007d978 feat(pos): enhance POS configuration to handle missing API key prompts gracefully and add slip preview functionality in settings 2026-09-04 11:55:38 +02:00
5c9f30dbd1 feat(pos): 'Print slip' on account sales auto-routes to delivery note (network + BT) 2026-09-04 10:40:48 +02:00
79cc7d1f0a fix(pos): keep edit-line modal from getting stuck on invalid qty; show clear 409 stock-shortage error instead of silently queuing offline 2026-09-04 10:37:00 +02:00
46d5d540a0 feat(pos): letterhead service + 2 API endpoints + vat_percent on Letterhead type 2026-09-04 09:13:53 +02:00
3b5891f0c5 chore(pos): bump SW cache to v17 so reprints work offline 2026-09-03 18:29:11 +02:00
20e460644b feat(pos): wire history reprint to redirect to receipt page 2026-09-03 18:28:55 +02:00
095a3433fa fix(pos): expose onclick-bound functions on window so buttons actually work 2026-09-03 18:24:37 +02:00
064145e5b7 refactor(sops): streamline SOP documents for roles, consolidating content into quickstart guides for easier reference 2026-09-03 17:57:49 +02:00
c4bf4a24ab chore(pos): cache epos-print.js in the PWA app shell 2026-09-03 17:54:48 +02:00
d5a48226ff feat(pos): print delivery notes over the network transport 2026-09-03 17:54:41 +02:00
bee80d8ec8 feat(pos): print sale receipts over the network transport 2026-09-03 17:54:21 +02:00
aec8e50f54 feat(pos): add printer type/IP settings UI to receipt page 2026-09-03 17:53:58 +02:00
06abd3c551 feat(pos): send ePOS-Print jobs over HTTPS to network printers 2026-09-03 17:53:32 +02:00
93eeea15e8 feat(pos): add ePOS-Print XML builder for sale receipts 2026-09-03 17:53:23 +02:00
4f54051b66 feat(pos): add canvas-based logo rasterization for ePOS-Print 2026-09-03 17:52:47 +02:00
2ea76f9d9a feat(pos): add printer settings persistence for network printing 2026-09-03 17:52:39 +02:00
ca6e9364a8 fix(pos): resolve duplicate const d declaration breaking receipt.html script 2026-09-03 17:52:30 +02:00
aed3f5c861 feat: implement discount functionality in POS system
Added support for discounts in the POS checkout process, allowing operators to apply either a fixed amount or a percentage off the gross total. Updated the state management to include discount details and modified the receipt and cart rendering to display pre- and post-discount values. Enhanced the checkout API to handle discount inputs and ensure proper calculations. Updated relevant tests to cover new discount logic.
2026-09-03 12:19:21 +02:00
c64e5dc2ab feat: expand customer relation in PDF generation
Enhanced the PDF generation for quotes by expanding the customer relation to include the associated company information. This allows the company name to be displayed prominently in the "TO:" block of the PDF, improving clarity for users. Updated test data to reflect the new structure and ensure proper rendering of the company name when available.
2026-09-02 11:40:50 +02:00
ccb27db82f feat: add phone view link to dashboard
Introduced a new link in the dashboard that allows users to access the mobile view of the application. The link features a smartphone icon and is styled for better visibility, enhancing user navigation options.
2026-09-02 11:06:15 +02:00
bdd0ac46e3 feat: implement per-route service worker selection
Updated the service worker registration logic to allow for specific service workers based on the route. Added support for a mobile service worker at `/m`, while maintaining existing functionality for `/counter` and `/new-order` routes. This enhances the PWA experience by serving optimized service workers for different app sections.
2026-09-02 07:49:38 +02:00
23d4e8dfe1 chore: update page.tsx 2026-09-02 01:38:33 +02:00
058803d90f chore: update AGENTS.md 2026-09-02 01:17:05 +02:00
e3b1c09704 chore: update package.json 2026-09-02 00:41:31 +02:00
7c2322b22a fix: use operator's quote_description on the quote PDF
The pricing modal saves quote_description correctly, but the PDF
template rendered item.description unconditionally, so the
operator's edit never showed up on the customer-facing quote.
2026-09-01 18:09:34 +02:00
ac201cb0a1 fix: preserve destination through the login redirect
Auth gates redirected to a bare /login with no memory of where the
user was headed, and the login page always sent them to /dashboard
afterward — so opening the /m PWA while signed out landed you on the
main dashboard instead of back on /m. Both gates now pass ?next=,
and login honors it (falling back to /dashboard if missing/unsafe).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:47:27 +02:00
2014814c84 feat(deliveries): click-to-expand DN items panel
Adds a chevron button at the left edge of every delivery-note row on
/deliveries. Clicking reveals an inline items panel (description +
qty) so the operator can match against the printed DN paper in their
hand before loading.

Items come from a client-side filter of the order group's items array
(items.filter(i => i.delivery_note === dn.id)). No new server endpoint.
DNs whose items aren't in the order group's slice show a 'See full DN →'
link to /delivery-notes/[id].

Expand state is held at the OrderGroupView level as a Set<string> of
expanded DN ids — independent per DN, resets on refresh. (State lifted
to OrderGroupView rather than DeliveriesPage because order.items is
already in scope there; avoids prop-drilling through CustomerGroupView.)

Service change: deliveries-service.listDeliveryItems now fetches the
delivery_note (relation id) + unit fields on each item, so the page
can filter and render units without an extra round-trip.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 12:15:35 +02:00
0984b7513e docs: implementation plan for DN items expand
Single-task, single-file plan: add chevron + items panel to
DeliveryNoteRowComponent on /deliveries. Lifts expand state to the
page (Set<string>), passes expanded + onToggle + items down. Items
filtered client-side from page data; empty-state fallback to
/delivery-notes/[id].

7 steps: imports, locate call site, page-level state, prop-drill,
update component signature + render chevron + items panel, smoke check
via agent-browser, commit.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 12:03:35 +02:00
66e2fb9856 docs: clarify unit suffix handling in DN items panel header
The 'Total {sumQty}{unitSuffix}' header line now explicitly documents
how mixed / missing units are handled: suffix is omitted when all
items have empty/null units (operator defaults to 'EA'); when units
are mixed, no suffix is shown; when all items share a non-EA unit
(e.g. BOX), the suffix is shown.

Self-review pass; no other changes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 12:01:42 +02:00
b39614b4da docs: spec for /deliveries DN items expand
Click-to-expand on each delivery-note row reveals an inline items
panel (description + qty) below the row. Operator can match the items
against the printed DN paper in their hand before loading. Pure
client-side render — items already loaded by /api/v1/deliveries are
filtered by delivery_note == dn.id. Empty state with fallback link to
/delivery-notes/[id] for DNs whose items aren't in the page slice.

Single-file edit on src/app/(app)/deliveries/page.tsx. No server,
schema, API, or MCP changes.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 12:01:12 +02:00
57584798f3 docs: include rep-scoped list filter plan + summary
The plan + summary landed locally during the implementation but were
not committed alongside the code. Both documents describe the
rep-scoped Orders / Quotes / Invoicing / Delivery Notes list filter
work that already shipped (commits 8faa443, ed07617, 29eb003, 9cf8e39
etc).

- 2026-08-31-rep-scoped-list-filter.md — the implementation plan
- 2026-09-01-rep-scoped-list-filter-SUMMARY.md — post-implementation
  summary with the commit log + verification notes + the known
  limitation (Orders/Quotes tab counts are unfiltered for reps)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 02:11:28 +02:00
742010a9a9 chore(login): simplify to login-only, remove register/reset tabs
The /login page previously had three tabs (Login / Register / Reset
Password) wired through useAuth(). The Register and Reset handlers
were removed in this pass — only the Login form remains, plus a
small UI/styling cleanup that was collateral.

- Drop Tabs imports + the active-tab state plumbing
- Drop regName/regEmail/regPassword/regConfirm + resetEmail state
- Drop handleRegister + handleReset handlers (login-only now)
- Drop useAuth().register / useAuth().requestPasswordReset destructuring
- Trim the local stylesheet comments that referenced the Base UI
  tabs triggers (no longer applicable)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 02:11:28 +02:00
bd328d40dc feat(deliveries): enrich DN row subline with customer/order/paperclip
The /deliveries To Load + Back from Driver + Checked In tabs each
render delivery-note rows showing only 'DN {number}' + a one-line
status subline. Operators couldn't identify the customer or tell
whether the signed copy was back without scrolling up to the
customer/order group header.

Replace the existing subline with a compact pieces list:
customer name (truncated, with title tooltip) · company name ·
parent order number · signed-copy indicator (paperclip icon +
filenames in title=) · delivery date (if set). Counter-pickup DNs
(no parent order) collapse to 'Counter pickup' in italic.

All data is already on the row payload from /api/v1/deliveries —
no server or schema changes.

Also removed the now-unused 'subline' const that the prior
implementation used for the old single-line status subline.

Verified visually via dev server: counter-pickup fallback renders
correctly ('Counter pickup' italic). Order-attached and signed-copy
code paths are structurally correct per the spec — would need a
DN with order + signed files in the live data to exercise them,
but no such DN was visible in the current dev data.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 02:09:49 +02:00
1fc3580aed perf(dashboard2): consolidate 14 PB count queries into 1 fetch
Single getList pulls every quote this rep created in the last 62 days,
then buckets client-side into the 6 day/week/month windows plus the
8-week trend. Also fans out the 4 remaining queries (outstandingQuotes,
outstandingOrderItems probe, monthRevenue, the consolidated quotes
fetch) in parallel via Promise.all.

Query count: 18 → 5. Page load bounded by the slowest query instead of
the sum. The widest quote window is "last month" (~62 days), so that
single bound covers every other day/week/month window for bucketing.
Cap at 2000 rows to defend against runaway responses for very active
reps.
2026-09-01 02:08:52 +02:00
dc8436e832 feat(orders): surface order age as colour-coded badge
Replaces the bare formatDate(order.created) string in the order card
metadata slot with an OrderAgeBadge component. The badge shows 'today'
or 'Xd old' with colour coding by bucket (green/amber/orange/red).
The absolute date is preserved in the badge's title= tooltip.

Bucket math lives in src/lib/order-age.ts (covered by 14 unit tests).
This change is rendering-only — no server or schema impact.

Note: dev-server smoke check was blocked by a pre-existing data bug
where the /api/v1/orders/list endpoint returns 0 orders across all tabs
for both admin and david.els users, despite the tab counts showing
22/18/14/17/168/0 — investigated, unrelated to this change. The
OrderAgeBadge will render correctly once that bug is fixed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 02:05:07 +02:00
27efc8628c feat(dashboard2): show last month's selling value alongside the delta
Selling card now reads "+12% vs previous · R 200,000" instead of just
"+12% vs previous". Optional previousValue prop on RepKpiCard; the other
3 cards don't pass it, so their format is unchanged.
2026-09-01 02:04:25 +02:00
42916f2948 fix(dashboard2): surface PocketBase error details in /api/v1/rep-dashboard
PB SDK rejects filters with a generic "Failed to fetch." wrapper, but the
real reason lives in error.response.data.message (or .data.message).
Walk the chain so the page's inline error UI shows the actual PB message
instead of a useless generic toast. Also log the full error server-side.
2026-09-01 02:00:55 +02:00
6fcef50b3e feat(orders): add order-age util with bucket thresholds
Pure function: getOrderAge(created, now?) returns {daysOld, bucket, label}
in calendar-day delta for Africa/Johannesburg. bucketToBadgeClass maps
to tailwind classes (fresh/recent/slipping/stale). No React, no DB.

Vitest unit tests cover all 5 bucket thresholds, future-date clamp, and
the JNB timezone edge case (same calendar day in JNB = daysOld 0).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-01 01:58:46 +02:00