---
id: WF-ACC-BKS-006
title: Replace Tax-Zone Rates and Payable Links
trigger: A caller saves all tax-zone details and rates using the loaded zone version.
product_area: accounting
module: books
revision: 1
status: draft
design_intent: proposed
implementation_evidence: inconsistent
inference: []
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-ACC-BKS-013
crosses: []
idempotency: >-
  The loaded version makes a successful retry stale. Every accepted replacement
  allocates fresh rate identities, including for rates with unchanged displayed
  values.
concurrency: >-
  The zone mutation takes its row lock. Posting reads payable links separately:
  its read timing determines whether it finds the old mapping or finds the old
  rate identity absent.
hops:
  - 'n': 1
    actor_or_service: Books service (tax rules)
    action: >-
      Normalize and validate criteria and bounded rate collections, including
      repeated class or unit and percentage checks.
    anchor: 'services-accounting:modules/books/services/rules/tax-zone-rules.ts'
    seam: sync
    on_failure: Return a positional validation refusal before replacing stored data.
    left_inconsistent: No stored setup is changed.
    observer: the caller
    provenance: test
    source: >-
      services-accounting:modules/books/tests/unit/rules/tax-zone-rules.spec.js#refuses
      more than twenty rates, a blank name, a class or a unit named twice, and a
      bad percentage, each by position
  - 'n': 2
    actor_or_service: Books service (transaction)
    action: >-
      Lock the zone, check update protection and replace it at the expected
      version.
    anchor: >-
      services-accounting:modules/books/services/persistence/tax-zones.ts:updateTaxZone
    seam: sync
    on_failure: Return locked or stale conflict and retain the earlier zone.
    left_inconsistent: The previous zone and rates remain available.
    observer: the caller
    provenance: test
    source: >-
      services-accounting:modules/books/tests/e2e/tax-zones.e2e.spec.js#answers
      200 with the version moved on and the rates rewritten whole, the first
      under a new id
  - 'n': 3
    actor_or_service: Books service and database
    action: >-
      Delete all old rates and insert the requested ordered rates with fresh
      identities, class percentages and payable links.
    anchor: >-
      services-accounting:modules/books/services/persistence/tax-zones.ts:writeRates
    seam: sync
    on_failure: Refuse invalid class or account references and roll back the entire save.
    left_inconsistent: No partial replacement commits.
    observer: the caller
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/tax-zones.integration.spec.js#refuses
      a rate naming a tax class the tenant has not, and one naming a GL account
      of another unit, by position
  - 'n': 4
    actor_or_service: 'Batch posting (later, another module)'
    action: >-
      During later posting, look up each issued tax line’s rate link, then the
      unit sales-tax default, or refuse when neither exists.
    anchor: >-
      services-accounting:modules/batches/services/engine/posting-documents.ts:TaxAccounts
    seam: sync
    on_failure: >-
      A removed old rate has no mapping; posting uses the default silently or
      reports missing configuration.
    left_inconsistent: >-
      Previously issued tax can post to a different liability account without an
      explicit change warning.
    observer: >-
      No direct warning; a missing fallback appears as a document-level posting
      error
    provenance: inferred
    source: >-
      services-accounting:modules/transactions/db/invoice-tax-lines.table.ts:tax_rate_id;
      services-accounting:modules/books/services/persistence/tax-zones.ts:taxRateAccountsOfUnit
    note: >-
      Issued lines retain a soft rate reference. Replacement removes that
      referenced rate and its payable mappings, so later lookup reaches the unit
      default rather than the original destination.
branches: []
waits:
  - An issued tax line can wait for posting while its zone is replaced.
evidence:
  - >-
    services-accounting at f47cb263:
    modules/books/services/persistence/tax-zones.ts;
    services/rules/tax-zone-rules.ts; db/tax-zones.table.ts,
    tax-zone-rates.table.ts
  - >-
    services-accounting at f47cb263:
    modules/batches/services/engine/posting-documents.ts:TaxAccounts;
    modules/transactions/db/invoice-tax-lines.table.ts (cited readers)
  - >-
    react-console at baf6dae3:
    components/configuration/resources/accounting-tax.tsx,
    components/configuration/tax/tax-zone-sections.tsx
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-DAT-001, TC-DAT-004, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003]
---

# Replace Tax-Zone Rates and Payable Links

## What It Does

A zone save replaces its rate collection atomically, including each rate’s percentages and per-unit payable links. Fresh identities distinguish the new rates. Issued documents retain their tax amounts and loose references to the earlier rate identities.

## The Flow

```mermaid
flowchart TD
  h0["Normalize and validate criteria and bounded rate collections, including repeated class or unit and percentage checks."]
  h1["Lock the zone, check update protection and replace it at the expected version."]
  h0 --> h1
  h2["Delete all old rates and insert the requested ordered rates with fresh identities, class percentages and payable links."]
  h1 --> h2
  h3["During later posting, look up each issued tax line’s rate link, then the unit sales-tax default, or refuse when neither exists."]
  h2 --> h3
```

## When Something Fails

The save rolls back completely if validation or a reference fails. The material risk follows a successful replacement: an issued line no longer finds its original rate’s payable link at posting. The batch then uses the unit default or refuses the document if no default exists. There is no compensation or screen warning for the changed destination.

## Open Design Questions

Product must choose stable rate identity, an issue-time payable snapshot or an accepted and warned fallback. That choice must remain consistent with the recorded posting-time account resolution and the Commerce tax transition.
