← Files Zuora Coding AgentARCHIVED FILE
references/is-migration-dw-patterns.md
16.4 KB · Oct 2, 2026 · 00:30 UTC
# Invoice Settlement: Data Warehouse Migration Patterns
This reference covers how enabling Invoice Settlement (IS) changes the Zuora data model for downstream data warehouses and BI layers. Use it when customers report DW/BI query breakage or data gaps after IS go-live, or when generating IS-compatible rewrites of their warehouse queries.
---
## Background: Why IS Breaks DW Queries
IS replaces several legacy settlement objects with new ones. Warehouse pipelines that read the legacy objects will miss new IS transactions and may double-count migrated historical records if both old and new tables are read simultaneously.
### Legacy objects retired / replaced by IS
| Legacy object | Replaced by | Notes |
|---|---|---|
| `InvoicePayment` | `PaymentApplication` | IS migration creates a 1:1 `PaymentApplication` for every historical `InvoicePayment`. Legacy records are **not deleted**. |
| `RefundInvoicePayment` | `RefundApplication` | Same pattern — legacy records remain. |
| `CreditBalanceAdjustment` (type: Increase) | `CreditMemo` (unapplied) | Credit balance is migrated to an unapplied CreditMemo. CBA records themselves are not migrated. |
| `CreditBalanceAdjustment` (type: Decrease) | `CreditMemoApplication` | |
| `InvoiceAdjustment` | `CreditMemo` / `DebitMemo` | New adjustments are created as Memos post-IS. |
| `InvoiceItemAdjustment` | `CreditMemoItem` / `DebitMemoItem` | |
### New objects added by IS (no legacy equivalent)
| New object | Purpose |
|---|---|
| `CreditMemo` | Billing document that reduces customer AR (replaces credits/write-offs) |
| `CreditMemoItem` | Line items of a CreditMemo |
| `CreditMemoApplication` | How a CreditMemo is applied to an invoice |
| `DebitMemo` | Billing document that increases customer AR |
| `DebitMemoItem` | Line items of a DebitMemo |
| `PaymentApplication` | How a payment is applied to an invoice (replaces InvoicePayment) |
| `RefundApplication` | How a refund is applied (replaces RefundInvoicePayment) |
---
## Two Sync Modes
Ask the customer which sync mode their DW pipeline uses before producing rewrites.
### Mode A: Incremental Sync
**What it means:** The pipeline processes only new/changed records on each run. Historical data is kept as-is from prior loads.
**IS migration behavior:**
- Legacy records (pre-IS) were loaded historically and remain in the warehouse. Don't re-load them.
- After IS go-live, new records only appear in IS objects (`PaymentApplication`, `CreditMemo`, etc.). Legacy objects no longer receive new rows.
- Existing models that read legacy objects continue to work for historical data — they just stop growing.
- New models must be added for IS objects to capture post-IS activity.
- No deduplication is needed because the pipeline only ever wrote each record once.
**Rewrite strategy:**
- Keep existing models for historical data (they still produce correct rows for pre-IS records).
- Add new sibling models for IS objects.
- Optionally add new models for `CreditMemo` / `DebitMemo` (entirely new documents with no pre-IS equivalent).
### Mode B: Full Historical Sync
**What it means:** Each pipeline run rebuilds the full history from scratch — all records are re-read and re-written every time.
**IS migration behavior:**
- IS migration creates `PaymentApplication` records mirroring every historical `InvoicePayment` (1:1). If both `InvoicePayment` and `PaymentApplication` are UNION ALL'd without deduplication, **every pre-IS payment is counted twice**.
- Same double-count risk for `RefundInvoicePayment` / `RefundApplication`.
- `CreditBalanceAdjustment` records are not migrated — the credit balance is converted to an unapplied CreditMemo, but old CBA rows remain and should still be included (they're not duplicated).
**Rewrite strategy:**
- Use a UNION ALL of legacy + IS tables, with an **anti-join on the legacy table** to exclude IS-migrated records from the IS side.
- The anti-join pattern: include a `PaymentApplication` row only when there is no matching `InvoicePayment` row (i.e., the PA was created after IS go-live, not migrated from IP).
---
## Incremental Sync: SQL Patterns
These patterns follow the constructconnect reference rewrites. The DW tooling in the examples is dbt (using `{{ ref() }}`), but the logic applies to any SQL-based tool — substitute table names appropriate for the customer's environment.
### Pattern 1 — Payment model (InvoicePayment → PaymentApplication)
**Before IS (incremental):**
```sql
-- reads InvoicePayment only
with invoice_payment as (
select * from stg_zuora__invoice_payment -- or {{ ref('stg_zuora__invoice_payment') }}
),
-- ... joins to invoice, invoice_item, credit_balance_adjustment ...
select ... from invoice_payment
```
**After IS (incremental):**
```sql
-- replace invoice_payment with payment_application
with payment_application as (
select * from stg_zuora__payment_application
),
-- retain credit_balance_adjustment only for pre-IS CBA records (not migrated to IS)
credit_balance_adjustment as (
select * from stg_zuora__credit_balance_adjustment
),
-- ... joins to invoice, invoice_item ...
select ... from payment_application
```
**Key field changes:**
| Legacy field (InvoicePayment) | IS field (PaymentApplication) |
|---|---|
| `invoice_payment_id` | `payment_application_id` |
| `invoice_payment` (join key) | `payment_application` (join key) |
| *(implicit)* | `apply_amount` — amount applied to the invoice |
### Pattern 2 — Refund model (RefundInvoicePayment → RefundApplication)
**Before IS (incremental):**
```sql
with refund_invoice_payment as (
select * from stg_zuora__refund_invoice_payment
),
...
```
**After IS (incremental):**
```sql
-- IS: refund_application replaces refund_invoice_payment
with refund_application as (
select * from stg_zuora__refund_application
),
-- IS: credit_balance_adjustment retained for pre-IS CBA records.
-- CB Refund records are NOT migrated to RefundApplication.
credit_balance_adjustment as (
select * from stg_zuora__credit_balance_adjustment
),
...
```
**Key field changes:**
| Legacy field (RefundInvoicePayment) | IS field (RefundApplication) |
|---|---|
| `refund_invoice_payment_id` | `refund_application_id` |
### Pattern 3 — Invoice model adjustments
**Before IS (incremental):**
```sql
invoice_adjustment as (
select * from stg_zuora__invoice_adjustment
),
invoice_item_adjustment as (
select * from stg_zuora__invoice_item_adjustment
),
credit_balance_adjustment as (
select * from stg_zuora__credit_balance_adjustment
),
```
**After IS (incremental):**
```sql
-- IS: IIA/IA/CBA legacy objects remain unchanged — these records are not migrated to IS objects.
-- IS migration migrates CreditBalance (the balance, not CBAs) to an unapplied CreditMemo.
-- New IS activity (CreditMemo/DebitMemo) is tracked in separate dedicated models.
invoice_adjustment as (
select * from stg_zuora__invoice_adjustment -- keep for historical pre-IS records
),
invoice_item_adjustment as (
select * from stg_zuora__invoice_item_adjustment -- keep for historical pre-IS records
),
credit_balance_adjustment as (
select * from stg_zuora__credit_balance_adjustment -- keep; not migrated to IS
),
-- Add new models for IS activity:
-- credit_memo / credit_memo_item → in dim_transactions_creditmemo_is
-- debit_memo / debit_memo_item → in dim_transactions_debitmemo_is
```
### Pattern 4 — CreditMemo model (entirely new — incremental)
```sql
-- IS: CreditMemo is a new billing document with no legacy equivalent.
-- Sign convention: non-canceled rows have negative transaction_amount (reduces AR).
with credit_memo as (
select * from stg_zuora__credit_memo
),
credit_memo_item as (
select * from stg_zuora__credit_memo_item
),
-- join to subscription / product dimension for enrichment if needed
...
select
credit_memo_item_id as transaction_id,
credit_memo_id,
'CreditMemoItem' as transaction_type,
amount * -1 as transaction_amount, -- negative = reduces AR
...
from credit_memo_item
join credit_memo using (credit_memo_id)
```
### Pattern 5 — DebitMemo model (entirely new — incremental)
```sql
-- IS: DebitMemo increases customer AR (opposite of CreditMemo).
-- Sign convention: non-canceled rows have positive transaction_amount.
with debit_memo as (
select * from stg_zuora__debit_memo
),
debit_memo_item as (
select * from stg_zuora__debit_memo_item
),
...
select
debit_memo_item_id as transaction_id,
debit_memo_id,
'DebitMemoItem' as transaction_type,
amount as transaction_amount, -- positive = increases AR
...
from debit_memo_item
join debit_memo using (debit_memo_id)
```
---
## Full Historical Sync: SQL Patterns
For full-sync pipelines, the critical requirement is deduplication when unioning legacy and IS tables. IS migration creates IS records mirroring every legacy record — without deduplication you double-count.
### Pattern 6 — Payment union with deduplication (full sync)
```sql
-- IS migration creates a PaymentApplication for every historical InvoicePayment (1:1),
-- but does NOT delete the original InvoicePayment record. Union without dedup = double-count.
--
-- Fix: is_rows uses an anti-join on (payment_id, invoice_id) to include ONLY
-- PaymentApplication records that have no matching InvoicePayment —
-- i.e., genuinely new records created after IS go-live.
with invoice_payment as (
select * from stg_zuora__invoice_payment
),
payment_application as (
select * from stg_zuora__payment_application
),
legacy_rows as (
-- all pre-IS invoice payments
select
invoice_payment_id as payment_record_id,
payment_id,
invoice_id,
amount,
'InvoicePayment' as source_object,
created_date,
updated_date
from invoice_payment
),
is_rows as (
-- only PaymentApplications that have no matching InvoicePayment (post-IS)
select
pa.payment_application_id as payment_record_id,
pa.payment_id,
pa.invoice_id,
pa.apply_amount as amount,
'PaymentApplication' as source_object,
pa.created_date,
pa.updated_date
from payment_application pa
left join invoice_payment ip
on ip.payment_id = pa.payment_id
and ip.invoice_id = pa.invoice_id
where ip.invoice_payment_id is null -- exclude IS-migrated PAs
and pa.invoice_id is not null -- invoice-linked only
and pa.payment_application_status = 'Processed'
),
final as (
select * from legacy_rows
union all
select * from is_rows
)
select * from final
```
### Pattern 7 — Refund union with deduplication (full sync)
```sql
-- Same deduplication pattern for refunds.
-- RefundApplication has no status column per HBM — no status filter on RA side.
with refund_invoice_payment as (
select * from stg_zuora__refund_invoice_payment
),
refund_application as (
select * from stg_zuora__refund_application
),
legacy_rows as (
select
refund_invoice_payment_id as refund_record_id,
refund_id,
invoice_id,
amount,
'RefundInvoicePayment' as source_object,
created_date,
updated_date
from refund_invoice_payment
),
is_rows as (
-- only RefundApplications without a matching RefundInvoicePayment
select
ra.refund_application_id as refund_record_id,
ra.refund_id,
ra.invoice_id,
ra.apply_amount as amount,
'RefundApplication' as source_object,
ra.created_date,
ra.updated_date
from refund_application ra
left join refund_invoice_payment rip
on rip.refund_id = ra.refund_id
and rip.invoice_id = ra.invoice_id
where rip.refund_invoice_payment_id is null
and ra.invoice_id is not null
),
final as (
select * from legacy_rows
union all
select * from is_rows
)
select * from final
```
### Pattern 8 — CreditBalanceAdjustment (full sync — no IS equivalent)
```sql
-- CBA Increase ("Transfer Negative Invoice → Credit Balance") and CB Payment
-- (overpayment creating credit balance) have no direct IS row-level equivalent.
--
-- In IS, these are represented by:
-- - Payment.unapplied_amount (for CB Payment — overpayment)
-- - CreditMemo creation (for CB Increase — negative invoice transfer)
--
-- Since these are state fields rather than transaction rows, no union/dedup needed.
-- Keep reading CreditBalanceAdjustment as-is; it was NOT migrated to IS objects.
with credit_balance_adjustment as (
select * from stg_zuora__credit_balance_adjustment
),
...
-- no IS equivalent to union in — CBA records stand alone
select * from credit_balance_adjustment
```
---
## Staging Model Naming Conventions
The patterns above use the `stg_zuora__<object>` naming convention. Substitute the customer's actual table/view names:
| Conceptual object | dbt staging ref | Fivetran / raw SQL typical name |
|---|---|---|
| InvoicePayment | `stg_zuora__invoice_payment` | `zuora.invoice_payment` |
| PaymentApplication | `stg_zuora__payment_application` | `zuora.payment_application` |
| RefundInvoicePayment | `stg_zuora__refund_invoice_payment` | `zuora.refund_invoice_payment` |
| RefundApplication | `stg_zuora__refund_application` | `zuora.refund_application` |
| CreditBalanceAdjustment | `stg_zuora__credit_balance_adjustment` | `zuora.credit_balance_adjustment` |
| InvoiceAdjustment | `stg_zuora__invoice_adjustment` | `zuora.invoice_adjustment` |
| InvoiceItemAdjustment | `stg_zuora__invoice_item_adjustment` | `zuora.invoice_item_adjustment` |
| CreditMemo | `stg_zuora__credit_memo` | `zuora.credit_memo` |
| CreditMemoItem | `stg_zuora__credit_memo_item` | `zuora.credit_memo_item` |
| CreditMemoApplication | `stg_zuora__credit_memo_application` | `zuora.credit_memo_application` |
| DebitMemo | `stg_zuora__debit_memo` | `zuora.debit_memo` |
| DebitMemoItem | `stg_zuora__debit_memo_item` | `zuora.debit_memo_item` |
| PaymentApplicationItem | `stg_zuora__payment_application_item` | `zuora.payment_application_item` |
---
## Key Pitfalls
1. **Double-counting migrated records (full sync only):** IS migration creates PA/RA rows mirroring every legacy IP/RIP row. In full sync mode, union both tables without the anti-join dedup and every pre-IS payment is counted twice. Always use Pattern 6/7 when operating in full sync mode.
2. **CBA records are NOT migrated:** `CreditBalanceAdjustment` rows are left in place. The credit *balance* is converted to an unapplied CreditMemo, but the CBA transaction rows remain unchanged. Continue reading the CBA table for historical transactions.
3. **CreditMemo and DebitMemo are net-new:** There are no legacy rows for these objects. You must add new models — they are not covered by updating existing models.
4. **Sign convention:** CreditMemo items are typically recorded with a *negative* `amount` (reduces AR). DebitMemo items are positive. Confirm sign conventions with the customer's AR/finance team before finalizing.
5. **Staging model availability:** Before writing SQL, confirm the customer's DW pipeline actually syncs the new IS objects (`payment_application`, `credit_memo`, `debit_memo`, etc.) from Zuora. If using Fivetran/HVR, these tables may need to be explicitly enabled.
6. **RefundApplication has no `status` column:** Unlike `PaymentApplication` (which has `payment_application_status`), `RefundApplication` has no status field in the Zuora HBM. Do not add a status filter on the RA side.
---
## DW Rewrite Checklist
- [ ] Identify all DW models/queries that reference legacy settlement objects
- [ ] Confirm DW pipeline syncs IS objects (`payment_application`, `credit_memo`, `debit_memo`, etc.)
- [ ] Determine sync mode (incremental vs full historical)
- [ ] For **incremental**: add new IS models alongside existing legacy models
- [ ] For **full sync**: apply anti-join deduplication pattern on payment and refund unions
- [ ] Add new `dim_transactions_creditmemo` and `dim_transactions_debitmemo` models
- [ ] Validate staging model field names match customer's DW schema
- [ ] Run end-to-end reconciliation: compare IS-rewritten output vs legacy output for overlapping data
- [ ] Confirm no records are lost or double-counted for the historical period
---
## References
- [Invoice Settlement Overview](https://docs.zuora.com/en/zuora-billing/bill-your-customer/invoice-settlement/get-started-with-invoice-settlement/invoice-settlement-overview)
- [IS Migration Checklist and Guide](https://docs.zuora.com/en/zuora-billing/bill-your-customer/invoice-settlement/get-started-with-invoice-settlement/invoice-settlement-migration-checklist-and-guide)
- `${CLAUDE_PLUGIN_ROOT}/references/is-migration-api-reference.md` — legacy→IS field mapping
- `${CLAUDE_PLUGIN_ROOT}/references/is-migration-patterns.md` — API migration patterns
SHA-256: 42bbd8eb1c44907efb204d06853c60c7da3516f00886de8005425fbf344f32a4