# Brink Settings2 To Staging

## Goal
Add BrinkPOS `Settings2` reference data into the Brink ETL flow as raw staging data for Redshift.

This task covers three `Settings2` endpoints:

1. `GetEmployees`
2. `GetJobs`
3. `GetItems`

The target implementation must follow the newer Brink ETL contract-onboarding pattern already established by `BrinkLaborGetShiftsStaging`, not the older mapper-only `Settings2` path.

## Source
- Endpoint: `https://api2.brinkpos.net/Settings2.svc`
- SOAP Actions:
  - `http://www.brinksoftware.com/webservices/settings/v2/ISettingsWebService2/GetEmployees`
  - `http://www.brinksoftware.com/webservices/settings/v2/ISettingsWebService2/GetJobs`
  - `http://www.brinksoftware.com/webservices/settings/v2/ISettingsWebService2/GetItems`

## Scope
Onboard `Settings2` into the standard Brink ETL pipeline:

1. `extract_raw`
2. `normalize_load`
3. `load_to_staging`

This is not a one-off import path.

## Current Repo Findings

### Already implemented
- `GetEmployees` typed facade support exists.
- `GetItems` typed facade support exists.
- `Settings2Client` already supports `GetEmployeesAsync(...)` and `GetItemsAsync(...)`.
- `BrinkSoapEnvelopeBuilder` already supports `Settings2` SOAP envelopes.
- Legacy staging SQL already exists for some `Settings2` entities:
  - `staging.brink_settings2_get_employees`
  - `staging.brink_settings2_items`
  - `staging.brink_settings2_item_groups`
  - `staging.brink_settings2_item_modifier_groups`
  - `staging.brink_settings2_item_taxes`
- Legacy row models / mappers already exist for some `Settings2` endpoints.

### Missing or incomplete
- `GetJobs` transport / facade support is not onboarded.
- `IBrinkRawClient` / `BrinkRawClient` do not support `Settings2` raw capture.
- `BrinkPosEndpointCatalog` only includes:
  - `Sales2.GetOrders`
  - `Labor.GetShifts`
- `BrinkPosEndpointExecutor` only dispatches supported extract endpoints for orders and labor shifts.
- `BrinkPosOperationCatalog` has no `Settings2` ETL operation entries.
- `BrinkPosNormalizationCatalog` has no `Settings2` contract group for this task.
- `BrinkPosStagingTableCatalog` has no `Settings2` table group for this task.
- DI currently registers only the new-style staging profiles:
  - `Sales2GetOrdersStagingProfile`
  - `LaborGetShiftsStagingProfile`

### Important legacy mismatch
Existing `Settings2` employees / items artifacts do not consistently follow the newer Brink staging standard:

- some rows inherit `NormalizedRowBase` instead of `BrinkStagingRowBase`
- some tables are missing `_etl_contract_name`, `_etl_contract_row_type`, `_etl_row_ordinal`
- some nested collections are stored as `SUPER` JSON blobs instead of separate child artifacts
- naming is inconsistent between `get_*` and entity-based table names

This task should align the new implementation with the modern Brink staging pattern and only reuse legacy tables where their shape is already compatible or can be upgraded safely.

## Required Rules
- Staging is raw, not curated.
- Business columns use Brink field names flattened to `snake_case`.
- Shared staging/parquet row models should be used; do not introduce separate parquet-only DTOs.
- All new staging rows inherit from `BrinkStagingRowBase`.
- Include standard `_etl_*` metadata from `NormalizedRowBase` + `BrinkStagingRowBase`.
- Every new staging table, including every child table, must contain the full modern Brink ETL metadata set:
  - `_etl_batch_id`
  - `_etl_company_id`
  - `_etl_tenant_id`
  - `_etl_source_type`
  - `_etl_stream_name`
  - `_etl_operation_name`
  - `_etl_ingested_at_utc`
  - `_etl_raw_s3_path`
  - `_etl_raw_file_name`
  - `_etl_parser_version`
  - `_etl_schema_version`
  - `_etl_contract_name`
  - `_etl_contract_row_type`
  - `_etl_row_ordinal`
- Use `StageDiagnosticsEnvelope<T>` details payloads for extract / normalize / load diagnostics.
- Plug into the existing Brink ETL job chain, not a special settings-specific pipeline.
- Do not introduce legacy generic metadata columns like `payload_json`, `record_hash`, `source_endpoint`, or `loaded_at_utc` unless the framework requires them.
- Nested collections should be split into separate child artifacts when the structure is simple and relationally useful.
- For very deep `Item` subobjects, it is acceptable to keep selected substructures as JSON/SUPER if flattening them all would create excessive scope with little staging value.
- If `Settings2` is extracted as part of a date-scoped integration run, staging should preserve the observed snapshot for that run rather than only the latest tenant-wide state.

## Flow
1. Extract from Brink POS
   - `BrinkPosExtractOrchestrator` starts the job.
   - `BrinkPosEndpointCatalog` provides the list of `Settings2` endpoints to capture.
   - `BrinkPosEndpointExecutor` calls Brink SOAP endpoints through `IBrinkRawClient`.
2. Store raw response
   - Raw Brink XML is saved to S3 via `IEtlS3Storage.WriteRawAsync(...)`.
   - Extract details are persisted as `StageDiagnosticsEnvelope<BrinkPosExtractBatchDetails>`.
3. Normalize / map data
   - `BrinkPosNormalizeLoadJobs` reads endpoint-level extract details.
   - Dedicated staging profiles parse the SOAP payload through `IBrinkXmlEnvelopeParser`.
   - Profiles flatten parent rows and selected child collections into normalized staging row models.
4. Write Parquet
   - `ParquetBatchWriter<T>` writes staging row models directly into parquet.
   - Parent and child collections are emitted as separate artifacts.
5. Load to Redshift staging
   - `BrinkPosLoadToStagingJobs` resolves tables from the normalized contracts.
   - `RedshiftStagingLoadExecutor` loads parquet files into Redshift staging.
   - Existing rows are deleted first using `Settings2`-appropriate idempotency predicates.

## Source Contract

### Employee Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Address1 | string | Employee's address |
| Address2 | string | Employee's address |
| AlternateId | string | Alternate external Id for this Employee |
| BirthDate | DateTime? | Employee's birth date |
| CanLoginWithCard | bool | Whether Employee can login with card |
| CanLoginWithFinger | bool | Whether Employee can login with fingerprint |
| CanLoginWithPin | bool | Whether Employee can login with PIN |
| CardNumber | string | Employee card number |
| CellPhone | string | Employee cell phone |
| City | string | Employee city |
| ClockedInDiscountId | int? | Clocked-in discount Id |
| ClockedOutDiscountId | int? | Clocked-out discount Id |
| DisplayName | string | Display name |
| EmailAddress | string | Email |
| EmployeeUniqueId | Guid | Employee unique identifier |
| ExportToPayroll | bool | Export to payroll flag |
| FirstName | string | First name |
| HealthCardExpirationDate | DateTime? | Health card expiration |
| HireDate | DateTime? | Hire date |
| HomeLocationId | Guid? | Home location |
| HomePhone | string | Home phone |
| Id | int | Employee Id |
| IdentificationVerified | bool | Identity verified flag |
| IsExempt | bool | Tax exempt flag |
| IsSalaried | bool | Salaried flag |
| Jobs | Array of EmployeeJob | Assigned jobs |
| LastName | string | Last name |
| LimitLocations | bool | Whether locations are limited |
| MaritalStatus | byte | Marital status |
| MaximumDailyDiscountAmount | decimal | Maximum daily discount amount |
| MaximumDailyDiscountCount | int | Maximum daily discount count |
| Notes | string | Notes |
| PayrollId | string | Payroll Id |
| Permissions | Array of EmployeePermission | Permissions |
| Pin | string | Always null when returned |
| SecondaryAuthPin | string | Secondary auth PIN |
| State | string | State |
| TaxWitholdingAllowance | int | Withholding allowance |
| Terminated | bool | Terminated flag |
| TerminationDate | DateTime? | Termination date |
| ValidLocations | Array of EmployeeLocation | Valid locations |
| Zip | string | Zip |

### EmployeeJob Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Id | int | Employee-job relationship Id |
| JobId | int | Job Id |
| JobUniqueId | Guid | Job unique Id |
| PayRate | decimal | Pay rate |
| SecurityLevelId | int | Security level Id |
| SecurityLevelUniqueId | Guid | Security level unique Id |

### EmployeeLocation Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Id | int | Employee-location relationship Id |
| LocationId | Guid | Location Id |

### EmployeePermission Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Id | int | Employee-permission relationship Id |
| PermissionId | int | Permission Id |
| PermissionUniqueId | Guid | Permission unique Id |

### Job Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| AllowAddItem | bool | Allow add item |
| AlternateId | string | Alternate external Id |
| CannotClose | bool | Cannot close flag |
| CanOpenAnyOrder | bool | Can open any order flag |
| CashDrawer | bool | Cash drawer flag |
| CheckoutRequiresApproval | bool | Checkout approval required |
| ClockInRequiresApproval | bool | Clock-in approval required |
| ClockOutRequiresApproval | bool | Clock-out approval required |
| DeclareTips | bool | Can declare tips |
| DefaultScreenId | int? | Default screen Id |
| DefaultSecurityLevelId | int? | Default security level Id |
| DefaultSecurityLevelUniqueId | Guid? | Default security level unique Id |
| DisplayColor | int | ARGB display color |
| ExcludeOnSalesAndLaborReports | bool | Exclude from reports |
| ExemptFromLaborSchedule | bool | Exempt from labor schedule |
| ExportCode | string | Export code |
| GroupItemsBySeat | bool | Group items by seat |
| Id | int | Job Id |
| IndicateTrainingModeOnKitchenChits | bool | Indicate training mode |
| IsBartender | bool | Bartender job |
| IsDeliveryDispatcher | bool | Delivery dispatcher job |
| IsDeliveryDriver | bool | Delivery driver job |
| IsHostess | bool | Hostess job |
| ItemLookup | bool | Item lookup allowed |
| JobUniqueId | Guid | Job unique Id |
| LaneId | int? | Lane Id |
| LimitedDestinationId | int? | Limited destination Id |
| LimitShiftBreakTypes | bool | Limit shift break types |
| LocationType | byte | Location type |
| Menus | Array of JobMenu | Assigned menus |
| Name | string | Job name |
| NoCashTransactions | bool | No cash transactions |
| OrderEntry | bool | Order entry allowed |
| OrderScreenId | int? | Order screen Id |
| RequireTipReporting | bool | Require tip reporting |
| SectionId | int? | Section Id |
| SelfBanking | bool | Self-banking flag |
| SelfVoid | bool | Self-void flag |
| ShiftBreakTypes | Array of JobShiftBreakType | Allowed break types |
| Tabs | bool | Tabs allowed |
| Training | bool | Training job |

### JobMenu Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Days | byte? | Days bitmask |
| MenuId | int | Menu Id |
| StartTime | TimeSpan? | Start time |

### JobShiftBreakType Type
| Data Name | Data Type | Data Description |
|------------|-----------|------------------|
| Id | int | Job-break type relationship Id |
| ShiftBreakTypeId | int | Shift break type Id |

### Item Type
Use the full Brink `Item` contract from the source system. The main parent fields and nested child collections listed in the original task remain in scope. Implementation should flatten the parent row and selected child collections as described below.

## Proposed Staging Shape

This schema should be treated as a warehouse foundation layer, similar in spirit to how `curated.pos_orders` and `curated.pos_order_items` are modeled as separate but joinable tables.

The preferred pattern is:
- keep core entities in separate tables
- bind them through explicit source keys
- avoid embedding reusable child entities into parent JSON unless the child data is too deep, too volatile, or too low-value to justify relational staging

For `Settings2`, that means employees, jobs, and the simple child collections should stay relational.

### Employees contract group
Parent:
- `staging.brink_settings2_employees`

Child tables:
- `staging.brink_settings2_employee_jobs`
- `staging.brink_settings2_employee_permissions`
- `staging.brink_settings2_employee_valid_locations`

Binding strategy:
- `employee_jobs.employee_id -> employees.employee_id`
- `employee_permissions.employee_id -> employees.employee_id`
- `employee_valid_locations.employee_id -> employees.employee_id`
- `employee_jobs.job_id -> jobs.job_id`

Reasoning:
- `Jobs`, `Permissions`, and `ValidLocations` are not just display blobs inside an employee payload.
- They are reusable relationship surfaces that future curated models will likely join directly.
- Keeping them relational makes downstream dimensional modeling much easier.
- `employee_id` is not part of the native `EmployeeJob` child contract itself; it should be copied from the containing `Employee` node during flattening, just like other Brink staging child tables in this repo carry parent context keys such as `order_id` or `shift_id`.

### Jobs contract group
Parent:
- `staging.brink_settings2_jobs`

Child tables:
- `staging.brink_settings2_job_menus`
- `staging.brink_settings2_job_shift_break_types`

Binding strategy:
- `job_menus.job_id -> jobs.job_id`
- `job_shift_break_types.job_id -> jobs.job_id`

Reasoning:
- Jobs are a reference/master-data entity.
- Menus and allowed shift break types are repeating child relationships, not attributes that should be hidden inside one serialized column.

### Items contract group
Parent:
- `staging.brink_settings2_items`

Child tables to keep relational:
- `staging.brink_settings2_item_groups`
- `staging.brink_settings2_item_modifier_groups`
- `staging.brink_settings2_item_taxes`

Binding strategy:
- `item_groups.item_id -> items.item_id`
- `item_modifier_groups.item_id -> items.item_id`
- `item_taxes.item_id -> items.item_id`

Reasoning:
- These are simple repeatable relationship sets and are likely to be reused by downstream menu/catalog and transaction enrichment models.
- This is directly analogous to the warehouse pattern where `pos_orders` and `pos_order_items` stay separate and joinable rather than storing order items inside the order row.

Complex subobjects that may stay as JSON/SUPER on the parent row unless a later task requires deeper flattening:
- `brand_allocations`
- `components`
- `custom_fields`
- `destination_availabilities`
- `included_modifiers`
- `ingredients`
- `modifier_codes_pricing`
- `modify_panels`
- `selection_panels`
- `skus`

Reasoning for keeping these as JSON/SUPER for now:
- they are deeper structures with more nesting and lower immediate reuse value
- flattening all of them now would create many low-signal child tables before we know the curated access patterns
- they can still be parsed later if a concrete curated model needs them

## Binding And Warehouse Modeling Strategy

### Why not embed subtypes inside employee/job/item rows?
For a warehouse foundation schema, embedding reusable children into parent JSON makes downstream modeling harder:

- joins become harder and slower
- relationship semantics are hidden inside blobs
- curated tables cannot easily reuse child entities independently
- incremental validation and row-count reconciliation become weaker
- temporal comparison between snapshots becomes harder because changes are hidden inside serialized blobs

That is why the recommended pattern is the same one already visible in the warehouse for transactional data:

- parent surface in one table
- repeated child surface in another table
- explicit binding key between them

`curated.pos_orders` and `curated.pos_order_items` are a good precedent:
- they are separated because orders and order items serve different analytical purposes
- they are bound through `order_id`
- downstream models can join them when needed, but they are not forced into one physical shape

For time-varying reference data, the same principle still applies:
- keep the entity relational
- keep the child relationships relational
- carry snapshot context on every row
- let downstream models reconstruct the state "as observed" for a given integration run or snapshot date

The same logic applies here:
- employees are a master entity
- employee-jobs and employee-permissions are relationship tables
- jobs are a master entity
- items are a master entity
- simple repeating collections remain separate child tables

### Recommended keys for binding
Employees:
- primary source key: `employee_id`
- stable alternate source key: `employee_unique_id`

Jobs:
- primary source key: `job_id`
- stable alternate source key: `job_unique_id`

Items:
- primary source key: `item_id`

Child relations:
- employee child tables bind through `employee_id`, populated from the containing employee record during flattening rather than from the nested child payload itself
- employee-job also binds to jobs through `job_id`
- job child tables bind through `job_id`
- item child tables bind through `item_id`

### Raw contract fidelity rule
- Keep every native field that exists on the Brink child contract.
- It is acceptable to add parent binding columns during flattening when the API nests a child collection under a parent but does not repeat the parent key inside each child object.
- These added parent binding columns are part of the staging shape, not a claim that the child contract originally exposed them.
- For `EmployeeJob`, preserve native fields such as `id`, `job_id`, `job_unique_id`, `pay_rate`, `security_level_id`, and `security_level_unique_id`, and add `employee_id` from the containing employee record.
- The same rule applies to `EmployeePermission` and `EmployeeLocation` if their nested payloads do not repeat the employee key.

### Recommended rule
- Keep relational child tables for simple arrays of identifiers or small structured rows.
- Use JSON/SUPER only for deep nested structures that are not yet known warehouse join surfaces.
- Do not serialize `employee_jobs`, `employee_permissions`, `employee_valid_locations`, `job_menus`, `job_shift_break_types`, `item_groups`, `item_modifier_groups`, or `item_taxes` into parent rows.
- Nested JSON is acceptable as a convenience copy only for deep payload preservation, not as the primary analytical or historical model for reusable child relationships.

## Temporal Snapshot Strategy

`Settings2` should be treated as time-varying reference data captured by observation time, not as a timeless current-state lookup.

### Best-practice recommendation
- Keep the staging model relational.
- Preserve one logical snapshot per tenant per integration date/run.
- Put the snapshot context on every parent and child row.
- Build any "latest state" convenience surface later from the historical staging data.

### Why this is better than embedding more nested objects
- historical diffs are easier to compute row-by-row
- downstream curated models can reproduce the configuration state that existed when a dated integration ran
- joins stay explicit and stable
- shared reference entities like jobs remain reusable across employees and across snapshots
- child rows can be independently validated and reconciled across snapshots

### Practical guidance for this task
- Do not switch simple child collections to embedded JSON just because the settings can change over time.
- Instead, version the relational rows by snapshot context.
- If a later consumer wants one self-contained document per employee or item, that can be produced as a derived curated/export shape rather than being the foundation staging model.

### Snapshot columns to add
Because the current `Settings2` calls in this repo are not business-date parameterized, the historical meaning is "observed during this integration run".

Add snapshot business columns such as:
- `snapshot_date`: the date the integration is representing
- optionally `snapshot_captured_at_utc`: when the API response was actually observed, if separate from `_etl_ingested_at_utc`

Rules:
- the same snapshot column(s) must exist on every parent and child table for this task
- child rows bind to parent rows by both entity key and shared snapshot context
- snapshot context is part of the staging grain for `Settings2`

### Binding with temporal context
Recommended join shape for downstream use:
- `employee_jobs.employee_id + snapshot_date -> employees.employee_id + snapshot_date`
- `employee_permissions.employee_id + snapshot_date -> employees.employee_id + snapshot_date`
- `employee_valid_locations.employee_id + snapshot_date -> employees.employee_id + snapshot_date`
- `employee_jobs.job_id + snapshot_date -> jobs.job_id + snapshot_date`
- `job_menus.job_id + snapshot_date -> jobs.job_id + snapshot_date`
- `job_shift_break_types.job_id + snapshot_date -> jobs.job_id + snapshot_date`
- `item_groups.item_id + snapshot_date -> items.item_id + snapshot_date`
- `item_modifier_groups.item_id + snapshot_date -> items.item_id + snapshot_date`
- `item_taxes.item_id + snapshot_date -> items.item_id + snapshot_date`

Preferred operational bridge:
- use `staging.brink_labor_shifts` to connect employees to jobs for actual worked activity
- treat `employee_jobs` as assignment/eligibility history, not as the default actual-work attribution bridge
- for labor-based attribution, join:
  - `labor_shifts.employee_id + requested_business_date -> employees.employee_id + snapshot_date`
  - `labor_shifts.job_id + requested_business_date -> jobs.job_id + snapshot_date`
- if downstream curated logic uses `business_date` instead of `requested_business_date`, document that convention explicitly and keep it consistent across joins

### Important constraint
- If Brink does not expose a true "effective as-of date" for these endpoints, then `snapshot_date` represents warehouse observation/integration context, not a guaranteed source-system effective date.
- The spec and code should name/document that clearly to avoid overstating temporal precision.
- `snapshot_date` is the canonical business snapshot column name for this task.

## Table Grain

The implementation should treat each staging table as having an explicit row grain.

### `staging.brink_settings2_employees`
- Grain: one row per employee returned by `Settings2.GetEmployees`
- Native business key: `employee_id`
- Alternate source key to retain: `employee_unique_id`
- No duplicated `Jobs`, `Permissions`, or `ValidLocations` arrays stored as relational business columns on this row

### `staging.brink_settings2_employee_jobs`
- Grain: one row per `EmployeeJob` element nested under one employee
- Native child key from API: `id`
- Parent-derived binding key: `employee_id`
- Shared reference key: `job_id`
- Alternate shared reference key to retain: `job_unique_id`
- Important: `job_id` is not unique at the table level because many employees can reference the same job
- Practical row identity is the nested relationship row under a specific employee, not `job_id` alone

### `staging.brink_settings2_employee_permissions`
- Grain: one row per `EmployeePermission` element nested under one employee
- Native child key from API: `id`
- Parent-derived binding key: `employee_id`
- Shared reference key: `permission_id`
- Alternate shared reference key to retain: `permission_unique_id`

### `staging.brink_settings2_employee_valid_locations`
- Grain: one row per `EmployeeLocation` element nested under one employee
- Native child key from API: `id`
- Parent-derived binding key: `employee_id`
- Shared reference key: `location_id`

### `staging.brink_settings2_jobs`
- Grain: one row per job returned by `Settings2.GetJobs`
- Native business key: `job_id`
- Alternate source key to retain: `job_unique_id`

### `staging.brink_settings2_job_menus`
- Grain: one row per `JobMenu` element nested under one job
- Parent-derived binding key: `job_id`
- Shared reference key: `menu_id`
- Include `days` and `start_time` because they are part of the child row grain rather than attributes of the parent job

### `staging.brink_settings2_job_shift_break_types`
- Grain: one row per `JobShiftBreakType` element nested under one job
- Native child key from API: `id`
- Parent-derived binding key: `job_id`
- Shared reference key: `shift_break_type_id`

### `staging.brink_settings2_items`
- Grain: one row per item returned by `Settings2.GetItems`
- Native business key: `item_id`
- Keep deep nested structures only where they remain intentionally JSON/SUPER for now

### `staging.brink_settings2_item_groups`
- Grain: one row per item-group relation under one item
- Parent-derived binding key: `item_id`
- Shared reference key: `item_group_id`

### `staging.brink_settings2_item_modifier_groups`
- Grain: one row per modifier-group relation under one item
- Parent-derived binding key: `item_id`
- Native child key from API: `item_modifier_group_id`
- Shared reference key: `modifier_group_id`

### `staging.brink_settings2_item_taxes`
- Grain: one row per tax relation under one item
- Parent-derived binding key: `item_id`
- Shared reference key: `tax_id`

### Grain rule for nested collections
- If Brink returns a nested collection under a parent node, the child staging row grain is "one row per nested element under one parent".
- When the child payload does not repeat the parent key, copy the parent key into the child row during flattening.
- Do not treat shared reference keys like `job_id`, `permission_id`, `location_id`, `menu_id`, or `tax_id` as if they made the child row globally unique.
- Preserve the child's native `id` when Brink provides one, because it is part of the raw relationship payload.

## Downstream Join Guidance

Use the following downstream rules when building curated models from these staging tables:

- `orders -> employees` is a safe direct enrichment path when `employee_id` is present on the order and `snapshot_date = business_date` is the agreed warehouse convention.
- `order_items -> items` is a safe direct enrichment path when `item_id` is present on the transactional row and `snapshot_date = business_date` is the agreed warehouse convention.
- `labor_shifts -> employees` and `labor_shifts -> jobs` are the preferred paths for actual worked employee/job attribution.
- `employees -> employee_jobs` is a safe path for assignment history.
- `orders -> jobs through employee_jobs` is not a safe default join because an employee can have multiple assigned jobs on the same snapshot date.
- if order-to-job attribution is required, prefer a labor-based bridge: order -> employee -> labor shift by tenant + employee + date context + time overlap -> job.

## Reuse / Migration Guidance

### Existing tables that may be reused after review
- `staging.brink_settings2_items`
- `staging.brink_settings2_item_groups`
- `staging.brink_settings2_item_modifier_groups`
- `staging.brink_settings2_item_taxes`

These should only be reused if the final row model and metadata columns match the new contract-based implementation.

### Existing tables likely requiring upgrade or replacement
- `staging.brink_settings2_get_employees`

Reasons:
- table naming does not match the modern contract naming pattern
- metadata columns are incomplete compared with the current Brink staging standard
- legacy implementation stores child collections differently than the proposed contract group

### New tables required
- `staging.brink_settings2_jobs`
- `staging.brink_settings2_job_menus`
- `staging.brink_settings2_job_shift_break_types`
- `staging.brink_settings2_employee_jobs`
- `staging.brink_settings2_employee_permissions`
- `staging.brink_settings2_employee_valid_locations`

## Idempotency Strategy
Unlike orders and labor shifts, `Settings2` data is reference / snapshot data.

Recommended delete strategy:
- delete by `_etl_tenant_id` plus snapshot context for each target staging table before load
- do not wipe all historical snapshots for the tenant when loading one dated integration run

Parent-table example:

```sql
DELETE FROM staging.brink_settings2_employees
WHERE _etl_tenant_id = @tenant_id
  AND snapshot_date = CAST(@snapshot_date AS DATE);
```

Child-table example:

```sql
DELETE FROM staging.brink_settings2_employee_jobs
WHERE _etl_tenant_id = @tenant_id
  AND snapshot_date = CAST(@snapshot_date AS DATE);
```

This should be confirmed during implementation, but snapshot-scoped replacement is the expected model for `Settings2` when the integration is run for specific dates.

## Detailed Implementation Plan

### Module 1: Transport and facade support
- Verify existing `GetEmployees` and `GetItems` typed transport remains correct.
- Add missing `GetJobs` typed support:
  - `Settings2Client.GetJobsAsync(...)`
  - `GetJobsResponse`
  - `IBrinkPosFacade.GetJobsAsync(...)`
  - `BrinkPosFacade.GetJobsAsync(...)`
- Add raw capture support to:
  - `IBrinkRawClient`
  - `BrinkRawClient`
  - `BrinkOperationRequestBuilder`
- Add request builders for:
  - `CaptureEmployees`
  - `CaptureJobs`
  - `CaptureItems`
- Reuse `BrinkSoapEnvelopeBuilder.BuildSimpleSettings2Call(...)` where possible.

### Module 2: Extract onboarding
- Extend `BrinkPosEndpointCatalog` with three new `Settings2` endpoints.
- Extend `BrinkPosEndpointExecutor` to dispatch:
  - `Settings2.GetEmployees`
  - `Settings2.GetJobs`
  - `Settings2.GetItems`
- Keep the existing raw artifact persistence pattern through `IEtlS3Storage.WriteRawAsync(...)`.
- Ensure extract details capture endpoint-level metadata for each `Settings2` endpoint using `BrinkPosOperationCatalog`.
- Ensure extract details carry the snapshot/integration context that the normalize and load stages will use.

### Module 3: Operation metadata
- Add operation metadata entries in `BrinkPosOperationCatalog` for the three `Settings2` endpoints.
- For each entry define:
  - `stream_name`
  - full operation name
  - `target_contract_group`
  - parser version
   - request metadata, including snapshot/integration context
   - required contracts
   - required staging tables

Recommended stream names:
- `settings2_employees`
- `settings2_jobs`
- `settings2_items`

Recommended target contract groups:
- `brink_settings2_get_employees`
- `brink_settings2_get_jobs`
- `brink_settings2_get_items`

Snapshot metadata rule:
- standardize on `snapshot_date` as the canonical business snapshot column and request metadata key for this task

### Module 4: Row models
- Add new shared staging/parquet row models inheriting from `BrinkStagingRowBase`.
- Include the agreed snapshot business column(s) on every parent and child row model for this task.

Employees:
- `BrinkSettings2EmployeesRow`
- `BrinkSettings2EmployeeJobsRow`
- `BrinkSettings2EmployeePermissionsRow`
- `BrinkSettings2EmployeeValidLocationsRow`

Jobs:
- `BrinkSettings2JobsRow`
- `BrinkSettings2JobMenusRow`
- `BrinkSettings2JobShiftBreakTypesRow`

Items:
- keep or replace `BrinkSettings2GetItemsRow` based on final naming decision
- ensure child row models exist for:
  - item groups
  - item modifier groups
  - item taxes

Important:
- If an existing row model is kept, upgrade it to the current Brink staging conventions rather than layering compatibility code.
- Decimal fields should use the same parquet decimal annotations as the modern Brink staging rows.
- Snapshot context must be part of the row grain, not inferred only from batch-level metadata.

### Module 5: Staging profiles
- Add dedicated profile executors using `BrinkMultiArtifactProfileBase` and `IBrinkXmlEnvelopeParser`.

Recommended profiles:
- `Settings2GetEmployeesStagingProfile`
- `Settings2GetJobsStagingProfile`
- `Settings2GetItemsStagingProfile`

- Each profile should:
  - parse the SOAP payload root
  - read response `ResultCode` and `Message`
  - emit one parent artifact
  - emit child artifacts for selected collections
  - stamp every emitted parent and child row with the agreed snapshot/integration context
  - apply metadata using `BrinkStagingMetadataApplicator`

### Module 6: Normalization catalog
- Extend `BrinkPosNormalizationCatalog` with new `Settings2` contract groups.
- Register every emitted artifact contract explicitly.
- Ensure the contract list matches the staging profile output exactly.

### Module 7: DI registration
- Register the new `Settings2` staging profiles in `AddBrinkPosEtlIntegration()`.
- Keep the `GetShifts` pattern as the registration model.

### Module 8: Staging table catalog
- Extend `BrinkPosStagingTableCatalog` with table metadata for each new `Settings2` contract.
- Add idempotent delete SQL for parent and child tables.
- Use snapshot-scoped delete predicates (`_etl_tenant_id` + snapshot context), not tenant-wide delete predicates.

### Module 9: SQL bootstrap
- Review existing `Settings2` SQL scripts for compatibility.
- Add new SQL scripts for new tables.
- Alter or replace existing scripts where the schema does not match the modern row model.

Required SQL outcomes:
- all business columns first
- include the agreed snapshot business column(s) such as `snapshot_date`
- then the full 14-column `_etl_*` metadata set listed in `Required Rules`
- `_etl_ingested_at_utc TIMESTAMP NOT NULL`
- `_etl_row_ordinal BIGINT NOT NULL` for modern contract-based staging artifacts
- do not create any new table with the older reduced metadata shape used by some legacy `Settings2` scripts

### Module 10: Testing
- Update `BrinkPosEndpointCatalogTests` to assert `Settings2` endpoints are present.
- Update `BrinkPosEtlCatalogTests` to cover the new contract groups and staging tables.
- Add transport/request builder tests for:
  - `Settings2.GetEmployees`
  - `Settings2.GetJobs`
  - `Settings2.GetItems`
- Add staging profile tests for parent and child artifact emission.
- Add staging profile tests to verify snapshot context is populated on every emitted parent and child row.
- Add live facade tests for `GetJobs` if the facade surface is added.
- Add raw capture live tests if XML artifacts in `temp/` are part of the expected verification workflow.
- Add normalize/load tests for the new `Settings2` contract groups and snapshot-scoped delete predicates.
- Add downstream-spec tests or validation notes that mark `orders -> jobs through employee_jobs` as unsafe and `labor_shifts -> jobs` as the preferred actual-work bridge.

## Recommended Delivery Order
1. Add `GetJobs` transport + facade support.
2. Add raw capture support for `Settings2` endpoints.
3. Add endpoint catalog + endpoint executor support.
4. Add operation metadata + normalization contracts + staging table catalog entries.
5. Implement `Employees` staging profile and tables.
6. Implement `Jobs` staging profile and tables.
7. Review and modernize `Items` staging profile / tables, reusing existing SQL only where compatible.
8. Add tests.
9. Run full backend build and relevant test suites.

## Open Decisions To Confirm During Implementation
1. Whether to keep legacy table names like `brink_settings2_get_employees` or replace them with entity-based names like `brink_settings2_employees`.
2. Whether `Items` should keep selected deep subobjects as JSON/SUPER in this task or flatten more child collections now.
3. Whether any downstream curated flow already depends on the old `Settings2` table names.

Default recommendation:
- use modern entity-based contract/table names for newly onboarded artifacts
- keep `Items` partially relational and partially JSON/SUPER for deep subobjects
- only preserve old names if a concrete downstream dependency is discovered
- standardize on `snapshot_date` across all parent and child tables before implementation starts

## Code References
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosEndpointCatalog.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosEndpointExecutor.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosOperationCatalog.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosNormalizationCatalog.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosNormalizeLoadJobs.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosLoadToStagingJobs.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosStagingTableCatalog.cs`
- `backend/Kds.Ai.Integrations/BrinkPos/BrinkPosNormalizationContextFactory.cs`
- `backend/integrations/Kds.Ai.Integrations.BrinkPos/Services/Normalization/Staging/Profiles/LaborGetShiftsStagingProfile.cs`
- `backend/integrations/Kds.Ai.Integrations.BrinkPos/Services/Facade/Clients/Settings2Client.cs`
- `backend/integrations/Kds.Ai.Integrations.BrinkPos/Services/Facade/BrinkRawClient.cs`
- `backend/integrations/Kds.Ai.Integrations.BrinkPos/Abstractions/IBrinkRawClient.cs`
- `backend/Kds.Ai.Warehouse/Sql/Bootstrap/Tenant/Staging/BrinkPos/0611_create_staging_brink_settings2_get_employees.sql`
- `backend/Kds.Ai.Warehouse/Sql/Bootstrap/Tenant/Staging/BrinkPos/0612_create_staging_brink_settings2_get_items.sql`
- `specs/BrinkLaborGetShiftsStaging/TASK.md`
