Skip to main content

Companies Table — Access Analysis

Analysed: 2026-03-25 Web commit: a6f3a9d (pivotteam/pivot, branch development) Mobile commit: b6698b1 (pivotteam/Pivotmobile, branch development)


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

SourceFileRoleTypeContext
Webroutes/OwnerDashboard/index.tsx:375Pivot admin.onceCaches entire Companies node for client-side search
Webutils/db.js:9Dev utility.oncefindCompanyByName() — not used in production

Full object — Companies/$companyId

SourceFileRoleTypeContext
Webauth/hooks/useSessionSync.ts:77All.on listenerLive sync of connected company to Zustand store
Webcomponents/SuspendedAccount.tsx:44All.onceReads each company the user belongs to (multi-company)
Webroutes/OwnerDashboard/index.tsx:438Pivot admin.onceLoad active companies by ID
Webroutes/HeadOfficeDashboard/index.js:60Head office.onceLoad location companies on mount
Webroutes/HeadOfficeDashboard/export-payroll/hooks/useExportData.ts:166Head office.oncePayroll export — reads full company for jobs, settings
Webroutes/HeadOfficeDashboard/AddNewCompanyModal.js:75Head office.onceValidate company code exists
Webroutes/CompanySettings/index.tsx:173Admin, Manager.onceCompany switcher in settings
Webroutes/SchedulePage/.../LaborCostModalUI.js:228Admin, Manager.onceLabor cost init (fallback)
Webroutes/SchedulePage/.../LaborCostModalUI.js:1070Admin, Manager.onceProjected labor costs
Webroutes/CreateCompany/Office.js:42Admin/Owner.onceValidate company code during head office creation
Mobileindex.js:660All.onceBootstrap — hydrate all user companies at login
Mobileindex.js:844All.on listenerLive sync of current company to Redux

Roles/positions — Companies/$companyId/jobs

SourceFileRoleTypeContext
Webroutes/HeadOfficeDashboard/export-payroll/hooks/useHeadOfficeRoles.ts:41Head office.onceAggregate roles across locations
Webroutes/SchedulePage/.../CloseDayModalUI.js:391Admin, Manager.onceClose-day labor cost
Webroutes/CreateCompanyNew/CreateCompanyRoles/index.js:41Admin/Owner.onceLoad roles during company creation
Webroutes/CreateCompanyNew/CreateCompanyDepartment.js:43Admin/Owner.onceDepartment assignment step
Mobileroutes/Dashboard/services/onFloorService.ts:7Manager, Admin.onceOn-floor staff count by dept
Mobileroutes/StaffOnFloor/services/staffService.ts:7Manager, Admin.onceStaff On Floor screen

Default duration — Companies/$companyId/defaultDuration

SourceFileRoleTypeContext
Mobileroutes/SPLH/services/splhService.ts:32Manager, Admin.onceSPLH date range calculation
Mobileroutes/Dashboard/hooks/useSPLH.ts:110Manager, Admin.onceDashboard SPLH widget (periodic)

Writes (Client-Side — Web Only)

Full object — Companies/$companyId

FileRoleContext
routes/CreateCompany/Office.js:83Admin/OwnerCreate head office company during signup
routes/CreateCompanyNew/CreateCompanyInfo.js:60Admin/OwnerCreate standard company during signup
routes/CompanySettings/index.tsx:279Admin/OwnerSave company settings (name, address, etc.)

Roles — Companies/$companyId/jobs and sub-paths

FilePath DetailRoleContext
routes/CreateCompanyNew/CreateCompanyRoles/index.js:28.../jobs (full overwrite)Admin/OwnerRoles setup during company creation
routes/RolesPage/components/SingleRoleInfo/index.js:269.../jobs/$jobIdAdmin, ManagerEdit a role's details
routes/RolesPage/components/SingleRoleInfo/index.js:131.../jobs/$jobIdAdmin, ManagerArchive a role (archived: true)
routes/RolesPage/components/SingleRoleInfo/index.js:172.../jobs/$jid/subcategories/$sidAdmin, ManagerArchive a sub-role
routes/RolesPage/components/RolesList.js:61.../jobs/$jid/priorityAdmin, ManagerReorder roles (drag and drop)
routes/SchedulePage/.../PositionsSettingsModal/index.tsx:60.../jobs/$uidAdmin, ManagerUpdate position type (FOH/BOH/MNG)

Company preferences (individual fields)

FilePath DetailRoleContext
routes/AccountSettings/components/PreferenceSettings.tsx:143weekStartingDay, timezone, generationFormat, betweenShiftsAdmin/OwnerSave preference settings
routes/AccountSettings/components/EmployeeSettings.js:102emergency, exchanges, replacements, daysOff, timeRequired, defaultDuration, notifyBefor, excludeBreaks, breaks, maxDayHours, employeeInteraction*, employeesCanSeeShiftEndTimesAdmin/OwnerSave employee settings
routes/PayrollNew/.../HoursSettings.tsx:132payrollStartingDayAdmin/OwnerSync payroll starting day
routes/SchedulePage/DaysStructure/index.js:160daysStructureAdmin, ManagerSave day structure (shift templates)
routes/SchedulePage/.../EmployeeView.tsx:329employeesOrderAdmin, ManagerReorder employees
routes/SchedulePage/.../LaborCostModalUI.js:1057includeOnCallShiftsAdmin, ManagerToggle on-call shifts in labor cost
routes/AccountSettings/Integrations/AddPOSModal.tsx:504hasLibroIntegrationAdmin/OwnerEnable Libro integration flag

Head office / Pivot admin paths

FilePath DetailRoleContext
routes/HeadOfficeDashboard/AddNewCompanyModal.js:126locations/$locationIdHead officeAdd/remove location from HO group
routes/OwnerDashboard/index.tsx:942isSuspendedPivot adminToggle company suspension
routes/OwnerDashboard/index.tsx:1000hasAccessToAttendancePivot adminToggle attendance feature access
routes/OwnerDashboard/index.tsx:1032isGeolocationEnabledPivot adminToggle geolocation feature

Writes (Cloud Functions — Admin SDK, bypasses rules)

FunctionPath DetailTriggerContext
Stripe webhook: customer.updatedbillingInfos/*HTTP (Stripe)Sync billing contact info
Stripe webhook: subscription.*billingInfos/subscriptionStatus, isSuspendedHTTP (Stripe)Subscription status changes
Stripe webhook: subscription.createdbillingInfos/stripe*Id, isSuspendedHTTP (Stripe)Link Stripe subscription
onCallUpdateBillingInfobillingInfos/*CallableUser updates billing info
copyCompanyDataCompanies/$id (full)CallableCross-project data copy
DB trigger: onPositionUpdateWrites to Managers/, not CompaniesDB trigger on jobs/$jobIdClean up manager access
DB trigger: onTimezoneChangeWrites to CompanyNotificationSettings/DB trigger on timezoneSync timezone

Access Summary by Role

RoleReadsWrites
EmployeeOwn company (full object)None
ManagerOwn company, jobsjobs/*, daysStructure, employeesOrder, includeOnCallShifts
Admin/OwnerOwn company, jobsAll of the above + full company settings, preferences, integrations, new company creation
Head officeAll companies (root read), any location companylocations/* on their HO company
Pivot adminAll companies (root read)isSuspended, hasAccessToAttendance, isGeolocationEnabled (these users are also head office)

Edge Cases for Rules

  1. SuspendedAccount multi-company read: When suspended, the user reads Companies/$id for every company they belong to, not just currentCompanyId. Their token claims may only cover the current company.

  2. New company creation: User has no currentCompanyId claim yet. Rule must allow !data.exists() combined with newData.child('createdBy').val() === auth.uid.

  3. Manager writes: Managers write to jobs/*, daysStructure, employeesOrder, includeOnCallShifts but NOT to top-level company fields. If rules only allow isAdmin at $companyId level, managers would be blocked. Options: (a) allow isManager at $companyId level (permissive), or (b) add child-level rules for manager-writable paths.

  4. CompanySettings vs Companies split: Some settings like payrollStartingDay are written to both Companies/ (for mobile backwards compat) and CompanySettings/. Over time these should consolidate into CompanySettings/.

  5. OwnerDashboard pivot admin: These are internal Pivot employees with isHeadOffice claims. Their writes (isSuspended, etc.) are covered by the isHeadOffice rule.