Gig::OutletSetting
Purpose
A Gig::OutletSetting holds the gig-specific configuration values for one outlet. Each outlet has exactly one row, created at outlet creation by copying the current values from the parent company's Gig::CompanySetting.
After creation, the outlet's row is independent. HR can edit any column. Later changes to Gig::CompanySetting do not retroactively propagate to existing outlets — this prevents silent behaviour changes at outlets that may have intentionally diverged.
Every column carries an explicit value at all times. There is no NULL-as-inheritance, no fallback walk at read time. Consumers (e.g. PayRate UI pre-fill, settlement scheduler) read directly from this row. The data team sees concrete values in every row.
See notes/2026-05-18-settings-cascade.md for the broader pattern and the rejected alternatives (lazy/sparse rows, NULL inheritance).
How initialisation works
gig_company_settings (current values)
│ copied at outlet creation, atomically with the outlet itself
▼
gig_outlet_settings (one row per outlet, all columns explicit, independent thereafter)
The copy happens once, in the same transaction as the outlet's creation. From that point on, the outlet's row is the source of truth for that outlet — no walk-up, no NULLs.
Model Context
| Context | Details |
|---|---|
| Entity | Gig::OutletSetting (independent) |
| Layer | Organisation Configuration |
| Upstream dependencies | Org::Outlet (the outlet this row belongs to); Gig::CompanySetting (source of seed values at creation) |
| Downstream dependents | Gig::PayRate UI (reads for pre-fill when the rate is outlet-specific); settlement scheduler (reads settlement_deadline_hour); auto-selection scheduling (reads auto_selection_enabled) |
Columns
Same column set as Gig::CompanySetting. Every column is NOT NULL. Initial values are copied from the parent company's row at outlet creation.
| Column | Type | What it controls |
|---|---|---|
night_shift_start_hour | integer | Hour (0–23) when this outlet's night band begins. UI pre-fill when HR creates an outlet-specific night Gig::PayRate row. |
night_shift_end_hour | integer | Hour (0–23) when this outlet's night band ends. Same UI-pre-fill role. |
auto_selection_enabled | boolean | Whether auto-selection runs at this outlet. Some outlets disable it because the manager wants to manually review every applicant. |
settlement_deadline_hour | integer | Hour (0–23) on the day after a shift when billable times at this outlet lock. Useful for outlets in different timezones where the company's deadline does not match local payroll cut-off times. |
Column set must stay in sync with Gig::CompanySetting. Adding a new gig-wide setting requires updating both tables together.
State Machine
Gig::OutletSetting has no status lifecycle. The row exists as long as the outlet exists; it is created with the outlet and updated as HR diverges from the company defaults.
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Create the settings row for a new outlet | Outlet onboarded | System |
| UC-2 | Edit an outlet-level setting | HR wants this outlet to differ from its company default | Admin / Employer (hq_manager or area_manager for assigned outlets) |
| UC-3 | View the current value of each setting at the outlet | HR reviewing the outlet's gig configuration | Admin / Employer (any role with outlet access) |
UC-1: Create the settings row for a new outlet
| Field | Details |
|---|---|
| Actor | System (called from the outlet-creation flow) |
| Trigger | A new Org::Outlet is created |
Preconditions:
Org::Outlethas just been created.- The parent
Org::Companyexists and has aGig::CompanySettingrow (always true — seeGig::CompanySettingUC-1).
System Behaviour:
- The outlet-creation Manager calls
Gig::OutletSettings::CreateService.execute(org_outlet:)inside the same transaction. - The service reads the parent company's
Gig::CompanySettingrow. - The service inserts one
gig_outlet_settingsrow, copying every column value from the company's row. - From this moment, any reader of the outlet's settings sees those explicit values.
Business Rules:
- Exactly one
Gig::OutletSettingperOrg::Outlet. Enforced by the unique index onorg_outlet_id. - Every column is populated at creation. Nothing is NULL.
- The Manager fails the outlet-creation transaction if the settings row cannot be created. An outlet cannot exist without its settings.
- The copy is a snapshot — later changes to
Gig::CompanySettingdo not retroactively change this row.
Postconditions:
- One
gig_outlet_settingsrow exists for the new outlet, carrying the company's values as of creation time.
UC-2: Edit an outlet-level setting
| Field | Details |
|---|---|
| Actor | Identities::Admin, Org::Membership with role hq_manager, or area_manager assigned to the outlet |
| Trigger | HR wants this outlet to differ from its company default |
Preconditions:
Gig::OutletSettingexists for the outlet (always true — see UC-1).- The actor has permission for the outlet.
System Behaviour:
- Actor opens the outlet-level settings page.
- The page displays each column with its current value and an edit input. The page also shows the parent company's current value for reference (so HR sees where their outlet diverges).
- Actor changes one or more columns and saves.
- System updates the row.
- Subsequent reads of the outlet's settings return the new values.
Business Rules:
- The outlet must belong to the actor's company (or, for
area_manager, must be in their outlet assignments). - Numeric columns must fall within sensible ranges (e.g.
night_shift_start_hourmust be 0–23). - The "Save" action does not propagate the change anywhere else. Editing one outlet does not change other outlets, even other outlets that currently share the same value.
Postconditions:
Gig::OutletSettingreflects the new value for the touched column(s).- Other outlets and the company-level row are unaffected.
UC-3: View the current value of each setting at the outlet
| Field | Details |
|---|---|
| Actor | Org::Membership (any role with access to the outlet) or Identities::Admin |
| Trigger | HR reviewing the outlet's gig configuration |
Preconditions:
- The actor has read access to the outlet.
System Behaviour:
- Actor opens the outlet-level settings page.
- For each column, the page shows the outlet's current value and (optionally) the parent company's current value for reference.
- Where the outlet's value differs from the company's, the page may highlight the divergence.
Business Rules:
- Read-only. No writes.
Postconditions:
- Read-only operation — no data changes.
Invariants
- Exactly one
Gig::OutletSettingrow perOrg::Outlet. Enforced by a unique index onorg_outlet_id. - Every column is NOT NULL. The row carries explicit values at all times.
- The row is created in the same transaction as the outlet. An outlet cannot exist without its settings.
- Initial values are copied from
Gig::CompanySettingat creation. After that, the row is independent —Gig::CompanySettingchanges do not flow through. - Editing the row does not affect any other outlet, even ones that currently share the same value.
- Editing the row does not affect existing
Gig::PayRaterows (UI pre-fill values are only seeds for new rows). - Removing an
Org::Outletcascades to deletion of itsGig::OutletSettingrow. - Column set must match
Gig::CompanySettingexactly. Adding or removing a column requires updating both tables together.
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Org::Outlet | Gig::OutletSetting belongs_to :org_outlet | Created with the outlet. One row per outlet, always. |
Gig::CompanySetting | Initialisation source | Values are copied from the parent company's row at outlet creation. After that, no relationship at read time. |
Gig::PayRate | Indirect — read for UI pre-fill | When HR creates an outlet-specific PayRate (org_outlet_id set), the rate-management form pre-fills starts_at / ends_at from Gig::OutletSetting for that outlet. Pre-fill only. |
Org::Membership | Permission gate | hq_manager can edit any outlet's settings. area_manager can edit only their assigned outlets. outlet_manager can edit only their single outlet. |
Open Questions
area_managerpermission scope. Shouldarea_managerbe allowed to edit every column, or only some (e.g.auto_selection_enabledbut notsettlement_deadline_hourbecause the latter affects payroll)? Recommendation: allow all columns in v1; tighten later if ops asks.- Audit table. Should
gig_outlet_settingshave a history table for compliance? Recommendation: defer; thecreated_at/updated_atplus PR history are enough until ops first needs to investigate.