Gig::CompanySetting
Purpose
A Gig::CompanySetting holds the gig-specific configuration values for one company. Each company has exactly one row, created at onboarding with the system defaults written explicitly into every column.
Unlike a NULL-fallback "inheritance" pattern, every column on every row carries an explicit value. HR can edit the values; the UI pre-fills with what the company chose previously, not a hidden default. The data team sees concrete values in every row — no walk-up cascade to reason about during analysis.
When a new outlet is created under the company, its Gig::OutletSetting row is initialised by copying the current values from Gig::CompanySetting. After creation, the outlet's row is independent — later edits to Gig::CompanySetting do not retroactively change existing outlet rows. This is deliberate: company-level changes should not silently alter the behaviour of existing outlets without HR's awareness.
See notes/2026-05-18-settings-cascade.md for the pattern (eager initialisation, independent rows, explicit values everywhere) and the alternatives that were rejected.
How initialisation works (no read-time fallback)
System defaults (constants in code)
│ seeded at onboarding
▼
gig_company_settings (one row per company, all columns explicit)
│ copied at outlet creation
▼
gig_outlet_settings (one row per outlet, all columns explicit, independent thereafter)
The "cascade" is a write-time concept: parent values seed child rows at creation. At read time, every consumer reads directly from the appropriate level's row — there is no fallback walk and no NULL to interpret.
Model Context
| Context | Details |
|---|---|
| Entity | Gig::CompanySetting (independent) |
| Layer | Organisation Configuration |
| Upstream dependencies | Org::Company (the company this setting row belongs to) |
| Downstream dependents | Gig::OutletSetting (copies values at outlet creation); Gig::PayRate UI (reads for pre-fill when the rate is company-wide) |
Columns
Every column is NOT NULL with a default matching the system constant. Rows are created at onboarding with these defaults; HR can edit any column afterwards.
| Column | Type | Default | What it controls |
|---|---|---|---|
night_shift_start_hour | integer | 18 | Hour (0–23) when the company "night band" begins. UI pre-fill when HR creates a company-wide night Gig::PayRate row. The PayRate stores the actual rule; this column only seeds the form. |
night_shift_end_hour | integer | 6 | Hour (0–23) when the night band ends. Same UI-pre-fill role. |
auto_selection_enabled | boolean | true | Whether the system auto-selects top-ranked applicants at the shift's selection deadline. Read at scheduling time. Outlets may set a different value via Gig::OutletSetting. |
settlement_deadline_hour | integer | 9 | Hour (0–23) on the day after a shift when billable times lock and payment processing can begin. Used by the settlement scheduler. |
The column set must stay in sync with Gig::OutletSetting. Adding a new gig-wide setting requires updating both tables, the system defaults file, and any consumer that reads the value.
State Machine
Gig::CompanySetting has no status lifecycle. The row exists as long as the company exists. Editing happens in place.
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Create the settings row for a new company | Company onboarded | System |
| UC-2 | Edit a company-level setting | HR wants to change the company default | Admin / Employer (hq_manager) |
| UC-3 | View the current value of each setting at the company | HR reviewing the company's gig configuration | Admin / Employer (any role) |
UC-1: Create the settings row for a new company
| Field | Details |
|---|---|
| Actor | System (called from the company-creation flow) |
| Trigger | A new Org::Company is created |
Preconditions:
Org::Companyhas just been created.
System Behaviour:
- The company-creation Manager calls
Gig::CompanySettings::CreateService.execute(org_company:)inside the same transaction. - The service inserts one
gig_company_settingsrow with every column set to the system default (night_shift_start_hour: 18,night_shift_end_hour: 6,auto_selection_enabled: true,settlement_deadline_hour: 9). - From this moment, any reader of the company's settings sees those explicit values.
Business Rules:
- Exactly one
Gig::CompanySettingperOrg::Company. Enforced by the unique index onorg_company_id. - Every column is populated at creation. Nothing is NULL.
- The Manager fails the company-creation transaction if the settings row cannot be created. A company cannot exist without its settings.
Postconditions:
- One
gig_company_settingsrow exists for the new company, with explicit default values for every column.
UC-2: Edit a company-level setting
| Field | Details |
|---|---|
| Actor | Identities::Admin or Org::Membership with role hq_manager |
| Trigger | HR wants to change a company-level value |
Preconditions:
Gig::CompanySettingexists for the company.
System Behaviour:
- Actor opens the company-level settings page.
- The page displays each column with its current value, an edit input, and (optionally) the system default for reference.
- Actor changes one or more columns and saves.
- System updates the row.
- Existing
Gig::OutletSettingrows are NOT modified. Outlets that were created before this edit keep their previously-seeded values. The change only affects:- New outlets created after this point (they will be seeded with the new company value).
- Company-wide
Gig::PayRatecreation forms (the UI pre-fill will use the new value).
Business Rules:
- Only
hq_managermembership can edit company-level settings.area_managerandoutlet_managerare restricted to outlet-level edits viaGig::OutletSetting. - Numeric columns must fall within sensible ranges (e.g.
night_shift_start_hourmust be 0–23). - The "Save" action does not offer to retroactively propagate the change to existing outlets. If HR wants to update outlets too, they edit each outlet's
Gig::OutletSettingseparately. This is deliberate — preventing silent behaviour changes at outlets that may have intentionally diverged.
Postconditions:
Gig::CompanySettingreflects the new value.- No existing
Gig::OutletSettingrows are touched. - New outlets and new company-wide PayRates use the new value.
UC-3: View the current value of each setting at the company
| Field | Details |
|---|---|
| Actor | Org::Membership (any role) or Identities::Admin |
| Trigger | HR reviewing the company's gig configuration |
Preconditions:
Gig::CompanySettingexists for the company.
System Behaviour:
- Actor opens the company-level settings page.
- For each column, the page shows the current value and (optionally) the system default for reference.
Business Rules:
- Read-only. No writes.
- The page does not show outlet-level values — those are visible on the per-outlet settings page (
Gig::OutletSettingUC-3).
Postconditions:
- Read-only operation — no data changes.
Invariants
- Exactly one
Gig::CompanySettingrow perOrg::Company. Enforced by a unique index onorg_company_id. - Every column is NOT NULL. The row carries explicit values at all times.
- The row is created in the same transaction as the company. A company cannot exist without its settings.
- Editing a column does NOT retroactively affect existing
Gig::OutletSettingrows. The cascade is a creation-time concept only. - Editing a column does NOT retroactively affect existing
Gig::PayRaterows — those store their ownstarts_at/ends_atdirectly. - Removing an
Org::Companycascades to deletion of itsGig::CompanySettingrow.
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Org::Company | Gig::CompanySetting belongs_to :org_company | Created with the company. One row per company. |
Gig::OutletSetting | Sibling at creation time | When a new Org::Outlet is created, the outlet's settings row copies the current values from the parent company's Gig::CompanySetting. After that, the rows are independent. |
Gig::PayRate | Indirect — read for UI pre-fill | When HR creates a company-wide PayRate (org_outlet_id NULL), the rate-management form pre-fills starts_at / ends_at from Gig::CompanySetting. Pre-fill only; no enforcement. |
Open Questions
- Audit table. Should
gig_company_settingshave agig_company_setting_historiestable to track every edit? Recommendation: add for money-adjacent columns (settlement_deadline_hour); defer for the rest until ops needs the audit trail to investigate an incident. - Retroactive propagation prompt. When HR edits a company-level setting, should the system offer "Apply this change to all outlets that currently match the old value"? Recommendation: defer to a later iteration; the current "no retroactive change" rule is the safer default.