Companies Table — Access Analysis
Analysed: 2026-03-25 Web commit:
a6f3a9d(pivotteam/pivot, branchdevelopment) Mobile commit:b6698b1(pivotteam/Pivotmobile, branchdevelopment)
Overview
The Companies node in Firebase Realtime Database stores company configuration: name, address, roles/positions, schedule preferences, billing info, integration flags, and more.
Mobile is read-only — zero client-side writes to Companies from the React Native app. All modifications happen via the web app or Cloud Functions.
Reads
Root-level — Companies
| Source | File | Role | Type | Context |
|---|---|---|---|---|
| Web | routes/OwnerDashboard/index.tsx:375 | Pivot admin | .once | Caches entire Companies node for client-side search |
| Web | utils/db.js:9 | Dev utility | .once | findCompanyByName() — not used in production |
Full object — Companies/$companyId
| Source | File | Role | Type | Context |
|---|---|---|---|---|
| Web | auth/hooks/useSessionSync.ts:77 | All | .on listener | Live sync of connected company to Zustand store |
| Web | components/SuspendedAccount.tsx:44 | All | .once | Reads each company the user belongs to (multi-company) |
| Web | routes/OwnerDashboard/index.tsx:438 | Pivot admin | .once | Load active companies by ID |
| Web | routes/HeadOfficeDashboard/index.js:60 | Head office | .once | Load location companies on mount |
| Web | routes/HeadOfficeDashboard/export-payroll/hooks/useExportData.ts:166 | Head office | .once | Payroll export — reads full company for jobs, settings |
| Web | routes/HeadOfficeDashboard/AddNewCompanyModal.js:75 | Head office | .once | Validate company code exists |
| Web | routes/CompanySettings/index.tsx:173 | Admin, Manager | .once | Company switcher in settings |
| Web | routes/SchedulePage/.../LaborCostModalUI.js:228 | Admin, Manager | .once | Labor cost init (fallback) |
| Web | routes/SchedulePage/.../LaborCostModalUI.js:1070 | Admin, Manager | .once | Projected labor costs |
| Web | routes/CreateCompany/Office.js:42 | Admin/Owner | .once | Validate company code during head office creation |
| Mobile | index.js:660 | All | .once | Bootstrap — hydrate all user companies at login |
| Mobile | index.js:844 | All | .on listener | Live sync of current company to Redux |
Roles/positions — Companies/$companyId/jobs
| Source | File | Role | Type | Context |
|---|---|---|---|---|
| Web | routes/HeadOfficeDashboard/export-payroll/hooks/useHeadOfficeRoles.ts:41 | Head office | .once | Aggregate roles across locations |
| Web | routes/SchedulePage/.../CloseDayModalUI.js:391 | Admin, Manager | .once | Close-day labor cost |
| Web | routes/CreateCompanyNew/CreateCompanyRoles/index.js:41 | Admin/Owner | .once | Load roles during company creation |
| Web | routes/CreateCompanyNew/CreateCompanyDepartment.js:43 | Admin/Owner | .once | Department assignment step |
| Mobile | routes/Dashboard/services/onFloorService.ts:7 | Manager, Admin | .once | On-floor staff count by dept |
| Mobile | routes/StaffOnFloor/services/staffService.ts:7 | Manager, Admin | .once | Staff On Floor screen |
Default duration — Companies/$companyId/defaultDuration
| Source | File | Role | Type | Context |
|---|---|---|---|---|
| Mobile | routes/SPLH/services/splhService.ts:32 | Manager, Admin | .once | SPLH date range calculation |
| Mobile | routes/Dashboard/hooks/useSPLH.ts:110 | Manager, Admin | .once | Dashboard SPLH widget (periodic) |
Writes (Client-Side — Web Only)
Full object — Companies/$companyId
| File | Role | Context |
|---|---|---|
routes/CreateCompany/Office.js:83 | Admin/Owner | Create head office company during signup |
routes/CreateCompanyNew/CreateCompanyInfo.js:60 | Admin/Owner | Create standard company during signup |
routes/CompanySettings/index.tsx:279 | Admin/Owner | Save company settings (name, address, etc.) |
Roles — Companies/$companyId/jobs and sub-paths
| File | Path Detail | Role | Context |
|---|---|---|---|
routes/CreateCompanyNew/CreateCompanyRoles/index.js:28 | .../jobs (full overwrite) | Admin/Owner | Roles setup during company creation |
routes/RolesPage/components/SingleRoleInfo/index.js:269 | .../jobs/$jobId | Admin, Manager | Edit a role's details |
routes/RolesPage/components/SingleRoleInfo/index.js:131 | .../jobs/$jobId | Admin, Manager | Archive a role (archived: true) |
routes/RolesPage/components/SingleRoleInfo/index.js:172 | .../jobs/$jid/subcategories/$sid | Admin, Manager | Archive a sub-role |
routes/RolesPage/components/RolesList.js:61 | .../jobs/$jid/priority | Admin, Manager | Reorder roles (drag and drop) |
routes/SchedulePage/.../PositionsSettingsModal/index.tsx:60 | .../jobs/$uid | Admin, Manager | Update position type (FOH/BOH/MNG) |
Company preferences (individual fields)
| File | Path Detail | Role | Context |
|---|---|---|---|
routes/AccountSettings/components/PreferenceSettings.tsx:143 | weekStartingDay, timezone, generationFormat, betweenShifts | Admin/Owner | Save preference settings |
routes/AccountSettings/components/EmployeeSettings.js:102 | emergency, exchanges, replacements, daysOff, timeRequired, defaultDuration, notifyBefor, excludeBreaks, breaks, maxDayHours, employeeInteraction*, employeesCanSeeShiftEndTimes | Admin/Owner | Save employee settings |
routes/PayrollNew/.../HoursSettings.tsx:132 | payrollStartingDay | Admin/Owner | Sync payroll starting day |
routes/SchedulePage/DaysStructure/index.js:160 | daysStructure | Admin, Manager | Save day structure (shift templates) |
routes/SchedulePage/.../EmployeeView.tsx:329 | employeesOrder | Admin, Manager | Reorder employees |
routes/SchedulePage/.../LaborCostModalUI.js:1057 | includeOnCallShifts | Admin, Manager | Toggle on-call shifts in labor cost |
routes/AccountSettings/Integrations/AddPOSModal.tsx:504 | hasLibroIntegration | Admin/Owner | Enable Libro integration flag |
Head office / Pivot admin paths
| File | Path Detail | Role | Context |
|---|---|---|---|
routes/HeadOfficeDashboard/AddNewCompanyModal.js:126 | locations/$locationId | Head office | Add/remove location from HO group |
routes/OwnerDashboard/index.tsx:942 | isSuspended | Pivot admin | Toggle company suspension |
routes/OwnerDashboard/index.tsx:1000 | hasAccessToAttendance | Pivot admin | Toggle attendance feature access |
routes/OwnerDashboard/index.tsx:1032 | isGeolocationEnabled | Pivot admin | Toggle geolocation feature |
Writes (Cloud Functions — Admin SDK, bypasses rules)
| Function | Path Detail | Trigger | Context |
|---|---|---|---|
Stripe webhook: customer.updated | billingInfos/* | HTTP (Stripe) | Sync billing contact info |
Stripe webhook: subscription.* | billingInfos/subscriptionStatus, isSuspended | HTTP (Stripe) | Subscription status changes |
Stripe webhook: subscription.created | billingInfos/stripe*Id, isSuspended | HTTP (Stripe) | Link Stripe subscription |
onCallUpdateBillingInfo | billingInfos/* | Callable | User updates billing info |
copyCompanyData | Companies/$id (full) | Callable | Cross-project data copy |
DB trigger: onPositionUpdate | Writes to Managers/, not Companies | DB trigger on jobs/$jobId | Clean up manager access |
DB trigger: onTimezoneChange | Writes to CompanyNotificationSettings/ | DB trigger on timezone | Sync timezone |
Access Summary by Role
| Role | Reads | Writes |
|---|---|---|
| Employee | Own company (full object) | None |
| Manager | Own company, jobs | jobs/*, daysStructure, employeesOrder, includeOnCallShifts |
| Admin/Owner | Own company, jobs | All of the above + full company settings, preferences, integrations, new company creation |
| Head office | All companies (root read), any location company | locations/* on their HO company |
| Pivot admin | All companies (root read) | isSuspended, hasAccessToAttendance, isGeolocationEnabled (these users are also head office) |
Edge Cases for Rules
-
SuspendedAccount multi-company read: When suspended, the user reads
Companies/$idfor every company they belong to, not justcurrentCompanyId. Their token claims may only cover the current company. -
New company creation: User has no
currentCompanyIdclaim yet. Rule must allow!data.exists()combined withnewData.child('createdBy').val() === auth.uid. -
Manager writes: Managers write to
jobs/*,daysStructure,employeesOrder,includeOnCallShiftsbut NOT to top-level company fields. If rules only allowisAdminat$companyIdlevel, managers would be blocked. Options: (a) allowisManagerat$companyIdlevel (permissive), or (b) add child-level rules for manager-writable paths. -
CompanySettings vs Companies split: Some settings like
payrollStartingDayare written to bothCompanies/(for mobile backwards compat) andCompanySettings/. Over time these should consolidate intoCompanySettings/. -
OwnerDashboard pivot admin: These are internal Pivot employees with
isHeadOfficeclaims. Their writes (isSuspended, etc.) are covered by theisHeadOfficerule.