Setup · roles & access

Who can do what.

Opticops has ten roles. Each one matches a real job on an optical-retail team. Permissions are enforced at the database — not just hidden in the UI — so a curious staffer can't bypass them by guessing a URL.

The eight roles

From most powerful to most specialised:

RoleScopeWhat they typically do
Org ownerWhole organisationSees everything, configures everything. The first user when an org is onboarded.
Regional managerMultiple branchesWatches a cluster of branches. Reads margin & cost. Can dispatch transfers between their branches.
Branch managerSingle branchRuns the day-to-day at one shop. Can receive stock, transfer to other branches, approve refunds.
OptometristSingle branchCaptures prescriptions and runs eye-tests. Can also process sales.
Branch staffSingle branchFrontline counter — sells, takes payment, prints invoices. Cannot see cost.
AccountantWhole organisationReads everything finance: cost, margin, expenses, P&L, royalty. Cannot write transactions.
Lab technicianSingle branchRuns inbound + outbound QC on the production queue. No prices, no customer phone numbers.
Warehouse / Job CardSingle hubWorks the job-card queue — places lens orders with vendors, records tracking, dispatches finished lenses to branches. No prices, no customer PII.
FitterSingle branchEdges and fits lenses into frames from the fitting queue. No prices, no customer PII.
AuditorRead-only across orgExternal CA or franchisor auditor. Reads everything but writes nothing.
Rule of thumb: if you're not sure which role to give someone, start with the most restrictive that still lets them do their job. Roles can be changed in one click later — there's no migration pain.

Cost visibility (the big one)

Cost prices are revoked at the column level for branch staff and optometrists. This means a curious staff member can't even SELECT the cost column via the API — the database refuses. Only the org owner, regional manager, branch manager, and accountant see cost. Auditor and platform admin see it too.

This applies everywhere cost appears: inventory pages, P&L, margin reports, weighted-average-cost variance warnings on the receive form, the per-SKU history page. None of it leaks.

Multi-branch rules

A user is attached to one default branch. Whether they see other branches depends on their role:

RoleSees other branches?
Org ownerYes — all of them.
Regional managerYes — all branches in the org (cluster filter coming soon).
Branch managerOnly their own branch.
OptometristOnly their own branch.
Branch staffOnly their own branch.
AccountantYes — all branches, read-only writes.
Lab technicianOnly their own branch.
Warehouse / Job CardOnly their own warehouse / hub.
FitterOnly their own branch.
AuditorYes — read-only.

Franchisee data sovereignty

If you're a parent brand with FOFO franchisees, your view into a franchisee's data is intentionally limited. You see:

  • Aggregated brand metrics (footfall, total revenue volume)
  • Inventory state (you supplied the stock, you should know where it is)
  • Royalty-relevant sales totals (you need to bill them)

You do not see (without the franchisee's explicit consent):

  • Individual customer names, phones, or prescriptions
  • Sales broken down by customer
  • The franchisee's rent, salary, or operating expenses
  • Their staff records
This isn't a UI hide — it's enforced at the database with row-level security. Even an SQL console hooked up to your API key cannot read the protected rows.

How to add a user

Org owners and regional managers can invite team members from Settings → Team & access → Open team page → (or visit /settings/team directly).

  1. Click + Invite team member.
  2. Type the email, pick the role and (for branch-scoped roles) a default branch. Optionally type the full name so it pre-fills nicely.
  3. Pick Send email (default — Supabase emails a magic link) or Generate link (you get a URL to share via WhatsApp / SMS).
  4. The new user clicks the link, signs in (magic link or Continue with Google), and lands on the dashboard with their role + branch already attached.
Permissions matrix: org owners can grant any role; regional managers can grant branch_manager, branch_staff, optometrist, lab_technician, warehouse, and fitter. The form filters the role dropdown to what you can actually grant.

Managing existing members

Same page. Click Edit → on any row to change their role, change their default branch, or remove them from the team. Removed users lose sign-in access immediately (their next protected action 401s).

Two guardrails:

  • Last-owner protection. You can't remove or demote the only remaining org owner — the form blocks the action with “promote someone else to owner first”.
  • Self-edit limits. You can change your own default branch but not your own role (prevents accidental self-lockout).

Pending invites (people you've invited but who haven't accepted yet) show in a separate amber-bordered list with a Cancel action. Cancel pulls back the invite — if they later click the email link, sign-in fails politely.

Every change is logged

Invites, role changes, branch reassignments, and removals all write to audit_log with the actor user, the target user, the old + new values, and a timestamp. Visible to any org owner / regional manager / auditor in the org. We don't hide what we do.

Next up

Selling at the counter

Once your team is set up, this is the workflow they'll live in every day.