Skip to main content

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

ContextDetails
EntityGig::OutletSetting (independent)
LayerOrganisation Configuration
Upstream dependenciesOrg::Outlet (the outlet this row belongs to); Gig::CompanySetting (source of seed values at creation)
Downstream dependentsGig::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.

ColumnTypeWhat it controls
night_shift_start_hourintegerHour (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_hourintegerHour (0–23) when this outlet's night band ends. Same UI-pre-fill role.
auto_selection_enabledbooleanWhether auto-selection runs at this outlet. Some outlets disable it because the manager wants to manually review every applicant.
settlement_deadline_hourintegerHour (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

IDUse CaseTriggerActor
UC-1Create the settings row for a new outletOutlet onboardedSystem
UC-2Edit an outlet-level settingHR wants this outlet to differ from its company defaultAdmin / Employer (hq_manager or area_manager for assigned outlets)
UC-3View the current value of each setting at the outletHR reviewing the outlet's gig configurationAdmin / Employer (any role with outlet access)

UC-1: Create the settings row for a new outlet

FieldDetails
ActorSystem (called from the outlet-creation flow)
TriggerA new Org::Outlet is created

Preconditions:

  • Org::Outlet has just been created.
  • The parent Org::Company exists and has a Gig::CompanySetting row (always true — see Gig::CompanySetting UC-1).

System Behaviour:

  1. The outlet-creation Manager calls Gig::OutletSettings::CreateService.execute(org_outlet:) inside the same transaction.
  2. The service reads the parent company's Gig::CompanySetting row.
  3. The service inserts one gig_outlet_settings row, copying every column value from the company's row.
  4. From this moment, any reader of the outlet's settings sees those explicit values.

Business Rules:

  • Exactly one Gig::OutletSetting per Org::Outlet. Enforced by the unique index on org_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::CompanySetting do not retroactively change this row.

Postconditions:

  • One gig_outlet_settings row exists for the new outlet, carrying the company's values as of creation time.

UC-2: Edit an outlet-level setting

FieldDetails
ActorIdentities::Admin, Org::Membership with role hq_manager, or area_manager assigned to the outlet
TriggerHR wants this outlet to differ from its company default

Preconditions:

  • Gig::OutletSetting exists for the outlet (always true — see UC-1).
  • The actor has permission for the outlet.

System Behaviour:

  1. Actor opens the outlet-level settings page.
  2. 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).
  3. Actor changes one or more columns and saves.
  4. System updates the row.
  5. 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_hour must 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::OutletSetting reflects 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

FieldDetails
ActorOrg::Membership (any role with access to the outlet) or Identities::Admin
TriggerHR reviewing the outlet's gig configuration

Preconditions:

  • The actor has read access to the outlet.

System Behaviour:

  1. Actor opens the outlet-level settings page.
  2. For each column, the page shows the outlet's current value and (optionally) the parent company's current value for reference.
  3. 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

  1. Exactly one Gig::OutletSetting row per Org::Outlet. Enforced by a unique index on org_outlet_id.
  2. Every column is NOT NULL. The row carries explicit values at all times.
  3. The row is created in the same transaction as the outlet. An outlet cannot exist without its settings.
  4. Initial values are copied from Gig::CompanySetting at creation. After that, the row is independent — Gig::CompanySetting changes do not flow through.
  5. Editing the row does not affect any other outlet, even ones that currently share the same value.
  6. Editing the row does not affect existing Gig::PayRate rows (UI pre-fill values are only seeds for new rows).
  7. Removing an Org::Outlet cascades to deletion of its Gig::OutletSetting row.
  8. Column set must match Gig::CompanySetting exactly. Adding or removing a column requires updating both tables together.

Model Interactions

Related ModelRelationshipInteraction
Org::OutletGig::OutletSetting belongs_to :org_outletCreated with the outlet. One row per outlet, always.
Gig::CompanySettingInitialisation sourceValues are copied from the parent company's row at outlet creation. After that, no relationship at read time.
Gig::PayRateIndirect — read for UI pre-fillWhen 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::MembershipPermission gatehq_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

  1. area_manager permission scope. Should area_manager be allowed to edit every column, or only some (e.g. auto_selection_enabled but not settlement_deadline_hour because the latter affects payroll)? Recommendation: allow all columns in v1; tighten later if ops asks.
  2. Audit table. Should gig_outlet_settings have a history table for compliance? Recommendation: defer; the created_at/updated_at plus PR history are enough until ops first needs to investigate.