- Added an audit trail feature that logs every create, update, and delete action on delivery notes and orders, capturing detailed changes per field. - Introduced the `AuditTimeline` component to display the history of changes for delivery notes and orders, enhancing traceability. - Updated the `page.tsx` files for delivery notes and orders to fetch and display audit logs, ensuring users can view the history of modifications. - Enhanced the `package.json` with a new migration script for setting up the audit log functionality. This feature improves accountability and transparency in the management of delivery notes and orders.
6.3 KiB
This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.
PocketBase select fields are not self-service
The order_items.item_status field is a select with a fixed list of
values. If you want to set a new value (e.g. delivery_note for the
new delivery-note flow), you must add it to the schema in the PB admin
first — the API will 500 with "Failed to update record." otherwise.
The local pb-exports/pb_schema-2.json is a snapshot for reference
only; the source of truth is the running PB instance.
If a new code path sets a select value that doesn't exist yet, ask the user to add it via the PB admin before debugging the API. They do this manually (it's a UI-only change in the PB admin).
Reuse existing multi-file fields for new attachment types
The order_items collection has a multi-file field order_item_attachements
(maxSelect: 10, accepts any MIME). When adding a new document type
to attach to an order item (e.g. delivery note PDF), upload the file
to this field rather than creating a new attachment collection. The
operator queries by some key field on the item (e.g.
delivery_note_number) to find the items, then reads the attachments
off each item.
Pattern in code:
- POST endpoint: generate the PDF, upload via multipart/form-data
using the
order_item_attachementsfield on each item in the group - GET endpoint: query
order_itemsby the key field, find a file whose name matches the expected pattern (e.g.DN26071401.pdf), stream the bytes back via PB's/api/files/...URL - Stable filename per document type so the retrieval side can disambiguate from other attachments on the same item
File URLs go through /api/v1/files/, never /api/files/
The WFDASH file-serving proxy lives at
/api/v1/files/{collection}/{recordId}/{filename} (route file:
src/app/api/v1/files/[collection]/[recordId]/[filename]/route.ts).
Every <a href={...}> and every fetch from a client component that
opens a repair/quote/order attachment must use the /api/v1/ prefix.
/api/files/{collection}/{recordId}/{filename} (no v1/) 404s —
that's the PocketBase convention, not a route in this app. The
PB instance serves files at that path, and the WFDASH proxy
forwards to it server-side, but a client that points directly at
/api/files/... gets the Next.js 404 page.
Gotcha: PocketBase record responses (e.g. record.delivery_note
as a string[] of filenames) sometimes come back as fully-qualified
PB URLs like http://pb-host/api/files/... — those should be
re-written to /api/v1/files/... on the client before rendering.
The existing detail page builds the path from scratch from the
record id + filename, which sidesteps the issue. Don't blindly
trust the URL PB hands back.
The src/app/api/invoicing/delivery-note-pdf/route.test.ts test
file intentionally matches /api/files/... in an expect — that's
asserting the raw PB response shape, not the URL the client
should use. Different concern.
Data access layer discipline
The app has two coexisting data layers:
- Legacy:
src/lib/pb-helpers.ts— used directly by Server Components undersrc/app/(app)/**/page.tsxand form components. - Current:
src/lib/server/*-service.ts— exposed via/api/v1/*route handlers and/api/mcp, used by the agent API and external integrations.
Both layers read the same PocketBase collections. Pick the right one depending on what you're doing.
Rules
For new endpoints / features: put business logic in
src/lib/server/<feature>-service.ts and expose it via a thin
src/app/api/v1/<feature>/<op>/route.ts. Do NOT extend pb-helpers.ts.
For new pages / form components: call /api/v1/* (or have the
Server Component call the corresponding lib/server/*-service.ts
function directly). Do NOT import from pb-helpers.ts.
When touching a page that already uses pb-helpers.ts: migrate its
data calls to the service layer in the same commit. One resource at a
time, no big-bang rewrites. Lowest-risk order: customers → orders →
quotes → suppliers → companies → everything else.
How to spot you're reaching for the legacy layer
pb-helpers.ts is the file in question. Grep for it:
grep -rn 'from "@/lib/pb-helpers"' src/ --include="*.ts" --include="*.tsx"
Each hit is one caller to migrate when you're in the area.
Migration cost indicator
A single page rewrite is typically: replace getX(...) import + call
with await fetch('/api/v1/x?...') (or a direct service-layer call),
then update the page to await. Half-day budget for 5 pages if no
weird cross-resource joins are involved.
Audit trail (audit_log) is written by a PB hook, not by app code
Every create / update / delete on quotes, quote_items,
quote_attachments, orders, order_items and delivery_notes is
recorded by pocketbase/pb_hooks/audit_log.pb.js (+ audit_log_utils.js)
into the audit_log collection. Don't add hand-written "audit" notes for
field changes — the hook already captures from → to per field.
Attribution: dashboard users writing straight to PB are credited
automatically. Server-side writes go through getAdminPocketBase(), which
attaches X-Audit-* headers from src/lib/server/audit-context.ts (the
browser forwards its PB token as x-wfdash-auth; PB verifies it). If a
server path knows the actor better than the request does (MCP actor
args), wrap it in runWithAuditActor(...). n8n writes appear as
"Automation" unless the workflow sends X-Audit-Actor-Name.
Read side: src/lib/server/audit-log-service.ts →
/api/v1/audit/{quote|order|delivery_note}/{id} → <AuditTimeline>.
Noisy fields (search/AI/timers) are skipped via IGNORED_FIELDS in the
hook utils — add new auto-updated fields there.
The hook files are deployed by hand into the PB server's pb_hooks/
(like select values, it's a manual step on the live instance).