# Brink POS PoC integration note

## Goal

Define the minimum viable approach for a Brink POS API pull PoC that:

- pulls Brink SOAP data for one tenant-location
- stores raw responses safely
- lands a small source-shaped Brink staging subset
- fills the first useful curated tables
- keeps enough metadata for debugging and reruns

This document is intentionally PoC-oriented. It is not the final production design.

## PoC decisions

- Use Brink as a read-only `API pull` source.
- Start with these Brink calls only:
  - `Sales2.GetOrders`
  - `Kitchen.GetOrderBumpTimes`
  - `Kitchen.GetOrderItemBumpTimes`
  - `Settings2.GetLastModifiedTime`
  - selected settings lookups for items, modifier groups, destinations, taxes, tenders, employees, and kitchen queues
- Store raw SOAP responses in S3.
- Reuse existing `kdsaidb` metadata for the PoC:
  - `TenantWarehouseMapping`
  - `IngestionBatch`
  - `IngestionCheckpoint`
- Do not require new control-plane metadata tables for the PoC.
- Use structured logs for per-call detail instead of a dedicated pull-attempt table.

## Brink constraints

- Brink is SOAP over HTTPS with WSDLs.
- Auth is header-based: `AccessToken`, `LocationToken`.
- TLS 1.2 or 1.3 is required.
- `ResultCode = 0` means success.
- `GetOrders` is heavy and Brink recommends conservative polling.
- Brink mixes business date, UTC timestamps, and location-local `DateTimeOffset` watermarks.

## Recommended implementation approach

- Try generated SOAP clients first for `Sales2`, `Settings2`, and `Kitchen`.
- If generated code is awkward, switch quickly to a thin hand-written SOAP client for the small PoC call set.
- Keep all Brink transport details behind one adapter boundary.

## PoC staging scope

The Brink PoC staging scope is limited to what can support the current curated contracts in `backend/Kds.Ai.Warehouse/Models/Curated/CuratedCanonicalModels.cs`.

Important simplification rule for the PoC:

- keep `staging` source-shaped, but keep it simple
- `Sales2.GetOrders` does not need to be fully exploded into many staging tables on day one
- prefer one primary order-centered staging landing plus only the child tables that clearly help `staging -> curated` transformations, replay, or debugging
- move canonical reshaping and business semantics into `curated`

### In scope staging tables

- `staging.brink_orders`
- optional normalized child tables from `Sales2.GetOrders` when they materially simplify curated publication:
  - `staging.brink_order_entries`
  - `staging.brink_order_entry_modifiers`
  - `staging.brink_order_taxes`
  - `staging.brink_order_entry_taxes`
  - `staging.brink_order_payments`
  - `staging.brink_order_payment_details`
- `staging.brink_items`
- `staging.brink_modifier_groups`
- `staging.brink_modifier_group_items`
- `staging.brink_destinations`
- `staging.brink_tables`
- `staging.brink_kitchen_queues`
- `staging.brink_taxes`
- `staging.brink_tenders`
- `staging.brink_employees`
- `staging.brink_order_bump_times`
- `staging.brink_order_item_bump_times`

### Optional PoC support tables

- `staging.brink_current_business_dates`
- `staging.brink_api_call_log`
- `staging.brink_raw_payload_manifest`

For the PoC, these optional tables can be skipped if the same information is available through `IngestionBatch`, `IngestionCheckpoint`, structured logs, and S3 raw object paths.

### Out of scope for the PoC

- full settings sync
- labor sync
- future-date orders and deposit flows
- inventory
- loyalty
- hourly sales
- production-grade replay/backfill control plane

## Staging source matrix

| Staging table | Brink source | API/service call | Brink contract reference | Endpoint and docs reference |
|---|---|---|---|---|
| `staging.brink_orders` | order headers | `Sales2.GetOrders` | `Order` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_entries` | order lines | `Sales2.GetOrders` | `OrderEntry`, `OrderItem`, `OrderGiftCard` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_entry_modifiers` | entry modifiers | `Sales2.GetOrders` | `OrderItemModifier` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_taxes` | order-level taxes | `Sales2.GetOrders` | `OrderTax` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_entry_taxes` | entry-level taxes | `Sales2.GetOrders` | `OrderEntryTax` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_payments` | payment headers | `Sales2.GetOrders` | `OrderPayment` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_order_payment_details` | payment detail events | `Sales2.GetOrders` | `OrderPaymentDetail` | Endpoint: `https://{{server}}.brinkpos.net/Sales2.svc`; docs: `https://brinkapiportal.parpos.com/sales2#GetOrders` |
| `staging.brink_items` | item master | `Settings2.GetSettings` or `Settings.GetItems` | `MenuItem` or `Item` | Endpoints: `https://{{server}}.brinkpos.net/Settings2.svc`, `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetSettings`, `https://brinkapiportal.parpos.com/settings#GetItems` |
| `staging.brink_modifier_groups` | modifier group headers | `Settings2.GetModifierGroups` or `Settings.GetModifierGroups` | `ModifierGroup` | Endpoints: `https://{{server}}.brinkpos.net/Settings2.svc`, `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetModifierGroups`, `https://brinkapiportal.parpos.com/settings#GetModifierGroups` |
| `staging.brink_modifier_group_items` | modifier group membership | `Settings2.GetModifierGroups` or `Settings.GetModifierGroups` | `ModifierGroup` child membership | Endpoints: `https://{{server}}.brinkpos.net/Settings2.svc`, `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetModifierGroups`, `https://brinkapiportal.parpos.com/settings#GetModifierGroups` |
| `staging.brink_destinations` | destination lookup | `Settings2.GetDestinations` or `Settings.GetDestinations` | `Destination` | Endpoints: `https://{{server}}.brinkpos.net/Settings2.svc`, `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetDestinations`, `https://brinkapiportal.parpos.com/settings#GetDestinations` |
| `staging.brink_tables` | table lookup | `Settings2.GetTables` | `Table` | Endpoint: `https://{{server}}.brinkpos.net/Settings2.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetTables` |
| `staging.brink_kitchen_queues` | kitchen queue lookup | `Settings2.GetKitchenQueues` | `KitchenQueue` | Endpoint: `https://{{server}}.brinkpos.net/Settings2.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetKitchenQueues` |
| `staging.brink_taxes` | tax lookup | `Settings2.GetTaxes` or `Settings.GetTaxes` | `Tax` | Endpoints: `https://{{server}}.brinkpos.net/Settings2.svc`, `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetTaxes`, `https://brinkapiportal.parpos.com/settings#GetTaxes` |
| `staging.brink_tenders` | tender lookup | `Settings.GetTenders` | `Tender` | Endpoint: `https://{{server}}.brinkpos.net/Settings.svc`; docs: `https://brinkapiportal.parpos.com/settings#GetTenders` |
| `staging.brink_employees` | employee lookup | `Settings2.GetEmployees` or `Settings2.GetSettings(Employee)` | `Employee` | Endpoint: `https://{{server}}.brinkpos.net/Settings2.svc`; docs: `https://brinkapiportal.parpos.com/settings2#GetEmployees`, `https://brinkapiportal.parpos.com/settings2#GetSettings` |
| `staging.brink_order_bump_times` | kitchen order bump events | `Kitchen.GetOrderBumpTimes` | `OrderBumpTime` | Endpoint: `https://{{server}}.brinkpos.net/Kitchen.svc`; docs: `https://brinkapiportal.parpos.com/kitchen#GetOrderBumpTimes` |
| `staging.brink_order_item_bump_times` | kitchen item bump events | `Kitchen.GetOrderItemBumpTimes` | `OrderItemBumpTime` | Endpoint: `https://{{server}}.brinkpos.net/Kitchen.svc`; docs: `https://brinkapiportal.parpos.com/kitchen#GetOrderItemBumpTimes` |

## Curated fill matrix

| Curated table | Input staging tables | Brink contract reference | Endpoint and docs reference | PoC fill strategy |
|---|---|---|---|---|
| `curated.kitchen_item_status_events` | `staging.brink_order_item_bump_times`, `staging.brink_kitchen_queues`, `staging.brink_orders`, `staging.brink_order_entries` | `OrderItemBumpTime`, `Order`, `OrderEntry`, `KitchenQueue` | `Kitchen.GetOrderItemBumpTimes`: `https://brinkapiportal.parpos.com/kitchen#GetOrderItemBumpTimes`; `Sales2.GetOrders`: `https://brinkapiportal.parpos.com/sales2#GetOrders` | Supported. Derive one row per item bump using synthetic status semantics where needed. |
| `curated.kitchen_order_items` | `staging.brink_order_entries`, `staging.brink_order_item_bump_times`, `staging.brink_orders`, `staging.brink_items` | `OrderEntry`, `OrderItemBumpTime`, `Order`, `MenuItem` or `Item` | `Sales2.GetOrders`, `Kitchen.GetOrderItemBumpTimes`, item docs | Supported. Map food/item lines into the current kitchen item contract. |
| `curated.kitchen_orders` | `staging.brink_orders`, `staging.brink_order_bump_times`, `staging.brink_destinations`, `staging.brink_kitchen_queues` | `Order`, `OrderBumpTime`, `Destination`, `KitchenQueue` | `Sales2.GetOrders`, `Kitchen.GetOrderBumpTimes`, destination docs | Supported. Map Brink orders into current kitchen order rows with canonical defaults where needed. |
| `curated.pos_transaction_items` | `staging.brink_order_entries`, `staging.brink_order_entry_modifiers`, `staging.brink_order_entry_taxes`, `staging.brink_orders`, `staging.brink_items` | `OrderEntry`, `OrderItemModifier`, `OrderEntryTax`, `Order` | `Sales2.GetOrders`, item docs | Supported. Use canonical projection into the current Aloha-shaped contract. |
| `curated.pos_transactions` | `staging.brink_orders`, `staging.brink_destinations`, `staging.brink_employees` | `Order`, `Destination`, `Employee` | `Sales2.GetOrders`, destination docs, employee docs | Supported. Use order header data as the source of the transaction row. |
| `curated.pos_transaction_tenders` | `staging.brink_order_payments`, `staging.brink_order_payment_details`, `staging.brink_tenders`, `staging.brink_orders`, `staging.brink_employees` | `OrderPayment`, `OrderPaymentDetail`, `Tender` | `Sales2.GetOrders`, `Settings.GetTenders` | Supported. Transform Brink payments into the current tender-style transaction contract. |
| `curated.pos_tables` | `staging.brink_tables` | `Table` | `Settings2.GetTables`: `https://brinkapiportal.parpos.com/settings2#GetTables` | Supported as a direct lookup mapping. |
| `curated.pos_tenders` | `staging.brink_tenders` | `Tender` | `Settings.GetTenders`: `https://brinkapiportal.parpos.com/settings#GetTenders` | Supported as a direct lookup mapping with many nullable fields. |
| `curated.pos_catalog_items` | `staging.brink_items`, `staging.brink_taxes` | `MenuItem` or `Item`, `Tax` | item docs, tax docs | Supported. Map only fields with safe Brink equivalents. |
| `curated.pos_modifiers` | `staging.brink_modifier_groups`, `staging.brink_modifier_group_items`, `staging.brink_items` | `ModifierGroup`, child membership | modifier docs, item docs | Supported. Canonical projection only, not one-to-one parity. |
| `curated.pos_orders` | `staging.brink_orders`, `staging.brink_order_payments`, `staging.brink_destinations` | `Order`, `OrderPayment`, `Destination` | `Sales2.GetOrders`, destination docs | Optional in the PoC. Add only if we agree on a simple canonical mapping. |
| `curated.pos_order_items` | `staging.brink_order_entries`, `staging.brink_orders`, `staging.brink_items`, `staging.brink_order_entry_modifiers` | `OrderEntry`, `Order`, `Item` | `Sales2.GetOrders`, item docs | Optional in the PoC. Add only if we agree on a simple canonical mapping. |
| `curated.pos_menu_items` | `staging.brink_items`, `staging.brink_destinations`, `staging.brink_kitchen_queues` | `MenuItem` or `Item`, `Destination`, `KitchenQueue` | item docs, destination docs, queue docs | Optional in the PoC. Only if the current contract can be filled without excessive source-specific hacks. |
| `curated.customers` | `staging.brink_orders` | customer-related fields on `Order` | `Sales2.GetOrders`: `https://brinkapiportal.parpos.com/sales2#GetOrders` | Out of scope unless Brink order payloads give stable customer data for the current contract. |
| `curated.pos_table_definitions` | none | none | none | Out of scope for the PoC. |
| `curated.loyalty_customers` | none | none | none | Out of scope for the PoC. |
| `curated.pos_hourly_sales` | none | none | none | Out of scope for the PoC. |
| `curated.inventory_items` | none | none | none | Out of scope for the PoC. |

## PoC metadata minimum

Use the existing control-plane metadata only:

- `TenantWarehouseMapping`
- `IngestionBatch`
- `IngestionCheckpoint`

For the PoC, this is enough if we also do these three things:

1. Save raw SOAP responses to S3.
2. Record the main raw response path on the batch when practical.
3. Emit structured logs for every Brink call.

### Minimum batch usage

- Use one `IngestionBatch` per Brink sync stream.
- Recommended first stream names:
  - orders
  - order-bumps
  - item-bumps
  - settings

### Minimum checkpoint usage

Use the existing `IngestionCheckpoint` table with these keys:

- `location:{location_key}:orders:modified-time`
- `location:{location_key}:order-bumps:bump-time`
- `location:{location_key}:item-bumps:bump-time`
- `location:{location_key}:settings:last-modified`

Rules:

- advance checkpoints only after successful end-to-end completion of the stream
- failed runs must not advance checkpoints
- preserve watermark offsets exactly for Brink `DateTimeOffset` values

### Minimum structured logging

For every Brink call, log at least:

- `tenant_id`
- `batch_id`
- `source_type`
- `service_name`
- `operation_name`
- `endpoint_url`
- `request_window_start`
- `request_window_end`
- `result_code`
- `status`
- `raw_response_s3_path`

## PoC minimum bar

The Brink PoC is good enough if:

1. One tenant can be linked to one Brink location and endpoint stack.
2. The adapter can pull orders, kitchen bumps, and one settings freshness check.
3. Raw SOAP responses are saved to S3.
4. One batch is recorded per sync stream.
5. Checkpoints advance only after stream success.
6. Failures do not advance checkpoints.
7. Logs are rich enough to diagnose which Brink call failed and for what time window.
8. The first agreed curated tables can be populated from the staging subset.

## References

- `docs/ETL.md`
- `docs/features/ETL/epics/06-source-adapter-brink-pos-api/`
- `backend/Kds.Ai.Warehouse/Sql/Bootstrap/Tenant/Staging/`
- `backend/Kds.Ai.Warehouse/Models/Curated/CuratedCanonicalModels.cs`
- `https://brinkapiportal.parpos.com/`
- `https://brinkapiportal.parpos.com/bestpracticeandfaqs`
- `https://brinkapiportal.parpos.com/sales2`
- `https://brinkapiportal.parpos.com/settings2`
- `https://brinkapiportal.parpos.com/settings`
- `https://brinkapiportal.parpos.com/kitchen`
