The employer and broker API routes were never named in the path
classifier, so they fell through to its admin-only default and the
proxy rejected a site manager with a bare {"error":"Not allowed"}
before the route handler ran. The handler's own site scoping was
always correct — it just never got the chance to apply.
- classify /api/employers as dashboard and map it to the workforce
module, mirroring /api/employees
- let the site dropdown be chosen when adding an employer; only a
rename freezes the site, which preserves assignment history
- cover the rename path, which had no route test, and the proxy
gate for the site-manager role
109 lines
5.5 KiB
Markdown
109 lines
5.5 KiB
Markdown
# User access
|
||
|
||
Named PocketBase sign-in is required in every environment. Shared PIN sign-in
|
||
is disabled, even if an old environment file still contains `AUTH_MODE=pin`.
|
||
|
||
| Account type | App access | Site scope | User settings |
|
||
| --- | --- | --- | --- |
|
||
| Admin | Dashboard, Management, Ask, Clock | All sites | Manage users |
|
||
| Site manager | Operational dashboard and Clock | Assigned sites | Create and list clock accounts within assigned sites |
|
||
| Clock user | Clock only: worker/fleet scans and document uploads | Assigned sites | None |
|
||
| Office user | Selected dashboard sections with full actions | Assigned sites or All sites | None |
|
||
|
||
## Management planning
|
||
|
||
Management sits below Overview in the navigation. Its Turnover Overview,
|
||
Project Planning, Retention and Year Comparison pages require a named admin
|
||
account. Operational Site manager accounts and Office accounts have no access,
|
||
including those with all sites or every operational section enabled. Future
|
||
pages under `/dashboard/management/` inherit this restriction.
|
||
|
||
The initial data comes from `src/lib/management/workbook-snapshot.json`,
|
||
extracted from the supplied turnover workbook. `data.ts` checks access before
|
||
loading it; `model.ts` calculates results independently. Replace the provider's
|
||
source when connecting the planning database. Never import the snapshot into
|
||
a client component or put it in `public/`.
|
||
|
||
The overview preserves the workbook's reported monthly income and cost
|
||
summaries, including its manual values. Project detail remains separate and
|
||
the reconciliation shows the differences. All amounts are ZAR. The main plan
|
||
follows March–February headings from the workbook titled 2025, without
|
||
inventing exact reporting years. Retention releases have their own supplied
|
||
November 2025–October 2026 dates. Year Comparison follows the source's actual
|
||
January–December 2024 and 2025 columns, including its estimates. There is no
|
||
live actuals feed or planning editor yet.
|
||
|
||
## Office setup
|
||
|
||
In Settings · Users, choose Office user, then an access preset (Workforce,
|
||
Financial, or Plant) or select sections individually. At least one section is
|
||
required. Each section includes its overview, details, imports, corrections,
|
||
and future pages under the same section. Selecting several sections gives the
|
||
user all of those sections. Overview is the company dashboard overview;
|
||
Workforce's own overview is included with Workforce.
|
||
|
||
Choose All sites for head-office work spanning the company. This includes new
|
||
sites automatically. Turn it off to select specific sites. Site lookup names
|
||
and the site selector still work within enabled sections; this does not grant
|
||
access to the Sites management section. Ask and user administration remain
|
||
unavailable to Office users.
|
||
|
||
For Julius, after deploying this version, edit his existing account:
|
||
|
||
- Account type: Office user.
|
||
- Preset: Workforce (only Workforce selected).
|
||
- Site scope: All sites if his remit covers the company; otherwise select his sites.
|
||
|
||
He starts on Workforce overview and sees Workers, Timesheets, and On site now.
|
||
Worker details, rates, sick notes, calendars, timesheet imports/upload status,
|
||
and missed-clock-out corrections inherit Workforce access. Other section URLs
|
||
redirect him to his first enabled section, and other section APIs return 403.
|
||
|
||
## Schema rollout
|
||
|
||
Existing account roles, passwords, and sites are preserved. The additive
|
||
migration adds `office_user` to `users.role`, multi-select `users.modules`
|
||
(`overview`, `sites`, `workforce`, `plant`, `financial`), and Boolean
|
||
`users.all_sites`. Existing non-office accounts retain their role defaults;
|
||
office fields do not grant extra access to clock or site-manager accounts.
|
||
|
||
Run the dry run against the configured PocketBase environment:
|
||
|
||
```bash
|
||
python3 scripts/migrations/2026-10-07-office-access.py --env-file .env.local
|
||
```
|
||
|
||
Apply before deploying Office user settings, passing the exact URL printed by
|
||
the dry run as `--confirm-host`:
|
||
|
||
```bash
|
||
python3 scripts/migrations/2026-10-07-office-access.py --env-file .env.local --apply --confirm-host "$POCKETBASE_URL"
|
||
```
|
||
|
||
This changes only the users collection fields. It does not convert Julius or
|
||
any other existing account. Convert accounts after deploying, because the old
|
||
app rejects the new Office user role.
|
||
|
||
## Extending sections
|
||
|
||
`src/lib/auth/path-access.ts` is the shared page/API policy used by the proxy,
|
||
navigation, and operation guard. New nested pages inherit their section root.
|
||
Workforce employee, employer/broker and `/api/workforce/` routes inherit
|
||
Workforce access. Unknown API routes remain admin-only until assigned an
|
||
explicit category.
|
||
|
||
**A new `/api/...` route is invisible to site managers until it is named in
|
||
`src/lib/auth/proxy-path.ts`.** The proxy runs before the route handler, so an
|
||
unclassified API path falls through to the admin-only default and the handler's
|
||
own `requireApiUser` / `canAccessSite` checks never run. Symptom: an admin
|
||
succeeds, a site manager gets `{"error":"Not allowed"}` (403) from the proxy,
|
||
and the route's own tests still pass because they mock the guard. Name the
|
||
route in `classifyAuthPath` and map it to a module in `moduleForPath` in the
|
||
same change. `npx vitest run src/lib/auth` covers the policy.
|
||
|
||
Use `getSiteScope(searchParams, "workforce")` (or the relevant section) before
|
||
loading page data, and `requireApiUser("workforce")` in its handlers. Shared
|
||
operation routes use `authorizeOperation(request)` plus record/site checks.
|
||
The only Financial handlers permitting clock accounts are document uploads
|
||
and purchase-order upload status/retry; new dashboard operations must not use
|
||
`allowClock: true`.
|