Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)

Implementation Specs (157)

Detailed engineering and feature implementation specifications.
implementation-specs/specs/BrinkSettings2ToStaging/TASK.MD 36,837 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

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

Source Contract

Employee Type

Data NameData TypeData Description
Address1stringEmployee's address
Address2stringEmployee's address
AlternateIdstringAlternate external Id for this Employee
BirthDateDateTime?Employee's birth date
CanLoginWithCardboolWhether Employee can login with card
CanLoginWithFingerboolWhether Employee can login with fingerprint
CanLoginWithPinboolWhether Employee can login with PIN
CardNumberstringEmployee card number
CellPhonestringEmployee cell phone
CitystringEmployee city
ClockedInDiscountIdint?Clocked-in discount Id
ClockedOutDiscountIdint?Clocked-out discount Id
DisplayNamestringDisplay name
EmailAddressstringEmail
EmployeeUniqueIdGuidEmployee unique identifier
ExportToPayrollboolExport to payroll flag
FirstNamestringFirst name
HealthCardExpirationDateDateTime?Health card expiration
HireDateDateTime?Hire date
HomeLocationIdGuid?Home location
HomePhonestringHome phone
IdintEmployee Id
IdentificationVerifiedboolIdentity verified flag
IsExemptboolTax exempt flag
IsSalariedboolSalaried flag
JobsArray of EmployeeJobAssigned jobs
LastNamestringLast name
LimitLocationsboolWhether locations are limited
MaritalStatusbyteMarital status
MaximumDailyDiscountAmountdecimalMaximum daily discount amount
MaximumDailyDiscountCountintMaximum daily discount count
NotesstringNotes
PayrollIdstringPayroll Id
PermissionsArray of EmployeePermissionPermissions
PinstringAlways null when returned
SecondaryAuthPinstringSecondary auth PIN
StatestringState
TaxWitholdingAllowanceintWithholding allowance
TerminatedboolTerminated flag
TerminationDateDateTime?Termination date
ValidLocationsArray of EmployeeLocationValid locations
ZipstringZip

EmployeeJob Type

Data NameData TypeData Description
IdintEmployee-job relationship Id
JobIdintJob Id
JobUniqueIdGuidJob unique Id
PayRatedecimalPay rate
SecurityLevelIdintSecurity level Id
SecurityLevelUniqueIdGuidSecurity level unique Id

EmployeeLocation Type

Data NameData TypeData Description
IdintEmployee-location relationship Id
LocationIdGuidLocation Id

EmployeePermission Type

Data NameData TypeData Description
IdintEmployee-permission relationship Id
PermissionIdintPermission Id
PermissionUniqueIdGuidPermission unique Id

Job Type

Data NameData TypeData Description
AllowAddItemboolAllow add item
AlternateIdstringAlternate external Id
CannotCloseboolCannot close flag
CanOpenAnyOrderboolCan open any order flag
CashDrawerboolCash drawer flag
CheckoutRequiresApprovalboolCheckout approval required
ClockInRequiresApprovalboolClock-in approval required
ClockOutRequiresApprovalboolClock-out approval required
DeclareTipsboolCan declare tips
DefaultScreenIdint?Default screen Id
DefaultSecurityLevelIdint?Default security level Id
DefaultSecurityLevelUniqueIdGuid?Default security level unique Id
DisplayColorintARGB display color
ExcludeOnSalesAndLaborReportsboolExclude from reports
ExemptFromLaborScheduleboolExempt from labor schedule
ExportCodestringExport code
GroupItemsBySeatboolGroup items by seat
IdintJob Id
IndicateTrainingModeOnKitchenChitsboolIndicate training mode
IsBartenderboolBartender job
IsDeliveryDispatcherboolDelivery dispatcher job
IsDeliveryDriverboolDelivery driver job
IsHostessboolHostess job
ItemLookupboolItem lookup allowed
JobUniqueIdGuidJob unique Id
LaneIdint?Lane Id
LimitedDestinationIdint?Limited destination Id
LimitShiftBreakTypesboolLimit shift break types
LocationTypebyteLocation type
MenusArray of JobMenuAssigned menus
NamestringJob name
NoCashTransactionsboolNo cash transactions
OrderEntryboolOrder entry allowed
OrderScreenIdint?Order screen Id
RequireTipReportingboolRequire tip reporting
SectionIdint?Section Id
SelfBankingboolSelf-banking flag
SelfVoidboolSelf-void flag
ShiftBreakTypesArray of JobShiftBreakTypeAllowed break types
TabsboolTabs allowed
TrainingboolTraining job

JobMenu Type

Data NameData TypeData Description
Daysbyte?Days bitmask
MenuIdintMenu Id
StartTimeTimeSpan?Start time

JobShiftBreakType Type

Data NameData TypeData Description
IdintJob-break type relationship Id
ShiftBreakTypeIdintShift 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

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.
  • 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.
  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