Branches
Create branches with unique codes, list them with active filters, and understand how branch scoping isolates data.
You model every physical pharmacy as a branch row in branches, defined in packages/database/schema.sql. The HTTP surface lives in apps/server/src/features/branches/route.ts and every route sits behind authMiddleware.
What a branch holds
You work with id, code, name, city, vat_number, address, currency_code, is_active, and created_at. The code is your human key, like B1 or RIYADH-01, and it must be unique across the table. Each branch also trades in one currency: currency_code defaults to UGX and an unknown code is refused by resolveBranchCurrency in apps/server/src/features/branches/service.ts. Your JWT carries a branch_id claim that decides which rows you may touch.
Listing branches
Any authenticated user lists and reads branches. The list defaults to active branches and paginates with page and page_size.
curl "http://localhost:8000/branches?page=1&page_size=50" \
-H "Authorization: Bearer $TOKEN"
You filter with is_active=true, is_active=false, or is_active=all. You fetch one branch directly when you know its id:
curl http://localhost:8000/branches/BRANCH_ID \
-H "Authorization: Bearer $TOKEN"
A missing branch returns 404 with detail Branch not found.
Creating a branch
Only admins create branches with POST /branches. You must send name, code, and city, and you may add vat_number, address, and currency_code. A duplicate code returns 400 with Branch code already exists, and an unknown currency returns 400 with Unknown currency: CODE.
curl -X POST http://localhost:8000/branches \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"code":"B3","name":"Third Branch","city":"Dammam","vat_number":"300123456700003","address":"King Fahd Rd"}'
Creation returns 201 with the new row and emits a branches sync change marked global visibility, so every device pulls it. You classify visibility in apps/server/src/core/common/visibility.ts, and emitChange in apps/server/src/features/sync/protocol/emitter.ts tags each change with it as it lands in sync_changes.
Updating a branch
Only admins update branches with PUT /branches/:branchId. The allowlist is name, city, vat_number, address, is_active, and currency_code, and an empty update returns 400 with No valid fields to update. Every update emits another branches sync change.
curl -X PUT http://localhost:8000/branches/BRANCH_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"address":"New address","is_active":true}'
How branch scoping isolates data
You never see another branch sales, returns, sessions, or parked carts. Sales writes force currentUser.branch_id, returns reject rows from a different branch with 403, and the pull read in apps/server/src/features/sync/queries.ts only returns branch visibility rows for your device branch plus global rows. Users are global entities, so admins manage staff across branches while counter data stays local.
Deactivating a branch
You retire a branch by setting is_active to false with PUT /branches/:branchId. Deactivated branches drop out of the default list, disappear from sync snapshots in apps/server/src/features/sync/route.ts, and reject new user assignments because requireActiveBranch in apps/server/src/features/users/service.ts demands Branch does not exist or is inactive for an inactive branch.
curl -X PUT http://localhost:8000/branches/BRANCH_ID \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_active":false}'
You reactivate with the same endpoint and true. Historical sales, returns, and audit rows keep their original branch_id, so reporting still reconciles after deactivation.
Demo branches and users
Seeding with bun --cwd packages/database seed gives you two branches and three logins. Sign in as admin@demo.pharmaflow, pharm1@demo.pharmaflow, or pharm2@demo.pharmaflow, all with password Demo@1234. The two pharmacist accounts sit on different branches, so you can prove scoping by listing sales with each token.
Next steps
Lock down who can do what in /platform/authentication-and-users, trace changes per branch in /platform/audit-log, and fetch branch-filtered deltas in /platform/sync-protocol.