---
id: ATS-ACC-BKS-003
scope: CAP-ACC-BKS-003
product_area: accounting
module: books
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-ACC-BKS-003-01
    title: Protect the accounting method after current-period posting
    level: api
    proves:
      - BR-CAP-ACC-BKS-003-01
    preconditions: >-
      One unit has current-period posted work and another has posting only
      elsewhere.
    steps: Try method changes in both directions.
    expected: >-
      Current-period work blocks each change with period and count; other-period
      work permits it.
    negative: true
    data_needs: Isolated fixture records matching this scenario’s setup.
    bound_to:
      - TST-ACC-BKS-074
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      the change 409 naming the period and the count when a batch of the open
      period is posted, leaving the method
    category: transition
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
        the change 409 naming the period and the count when a batch of the open
        period is posted, leaving the method
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
        a change back as well, and still accepts a replacement that leaves the
        method as it is
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#does
        not count a posted batch of another period of the unit, or of another
        unit
  - id: ATS-ACC-BKS-003-02
    title: Use the policy effective at posting
    level: browser
    proves:
      - BR-CAP-ACC-BKS-003-02
    preconditions: A write-off is pending under bad-debt treatment.
    steps: Select revenue reversal and post the write-off batch.
    expected: The posting uses the revenue destination.
    negative: false
    data_needs: The setup needs no extra fixture records.
    bound_to:
      - TST-ACC-BKS-074
      - TST-ACC-BKS-003
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#takes
      bad_debt_expense on a create that leaves the treatment out
    category: rule
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#takes
        bad_debt_expense on a create that leaves the treatment out
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#replaces
        the stored treatment on a replacement that states it
      - >-
        react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0003.spec.ts#ACT-ACC-0003
  - id: ATS-ACC-BKS-003-03
    title: Validate a complete fee rule
    level: api
    proves:
      - BR-CAP-ACC-BKS-003-03
      - INV-DOM-ACC-BKS-001-06
    preconditions: A business unit is available.
    steps: >-
      Submit incomplete product/amount combinations, a standalone minimum, 101
      percent, a negative amount and five-place precision.
    expected: Every invalid rule is refused.
    negative: true
    data_needs: The setup needs no extra fixture records.
    bound_to:
      - TST-ACC-BKS-074
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a rule that is not whole (a percent without a product, a product without
      an amount, a minimum alone) by the database’s rule
    category: validation
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
        a rule that is not whole (a percent without a product, a product without
        an amount, a minimum alone) by the database’s rule
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
        a percent above 100, a negative amount, and a percent that is not a
        decimal of four places
  - id: ATS-ACC-BKS-003-04
    title: Require an eligible fee product
    level: api
    proves:
      - BR-CAP-ACC-BKS-003-04
      - INV-DOM-ACC-BKS-001-06
    preconditions: >-
      Products include a local active product, a foreign-unit product and an
      inactive product.
    steps: Choose each as the fee product.
    expected: Only the active same-unit product is accepted.
    negative: true
    data_needs: The setup needs no extra fixture records.
    bound_to:
      - TST-ACC-BKS-074
    provenance: test
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a fee product of another unit, an inactive one and one the tenant does not
      have, naming card_fee_product_id
    category: validation
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
        a fee product of another unit, an inactive one and one the tenant does
        not have, naming card_fee_product_id
  - id: ATS-ACC-BKS-003-05
    title: Calculate and distribute one tender fee
    level: unit
    proves:
      - BR-CAP-ACC-BKS-003-05
    preconditions: 'Fee rules contain percentage, fixed and minimum choices.'
    steps: Calculate base and foreign fees and allocate a tender across two invoices.
    expected: >-
      Half-up percentage rounding applies; base-only fixed and minimum values
      are shared once, with remainder on the last share.
    negative: false
    data_needs: The setup needs no extra fixture records.
    bound_to:
      - TST-ACC-BKS-171
    provenance: test
    source: >-
      services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#adds
      the fixed amount in the base currency: 2.9% + 30 on 10000 is 320
    category: rule
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#adds
        the fixed amount in the base currency: 2.9% + 30 on 10000 is 320
      - >-
        services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#applies
        the percent alone to a tender in another currency than the unit’s base:
        no fixed amount, no minimum
      - >-
        services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#charges
        the minimum once, not per invoice: two invoices of 100 under a minimum
        of 500 share one fee of 500
    supporting_configuration_evidence:
      - >-
        react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0048.spec.ts#ACT-ACC-0048
  - id: ATS-ACC-BKS-003-06
    title: Apply fees by payment behaviour
    level: unit
    proves:
      - BR-CAP-ACC-BKS-003-06
    preconditions: Electronic-check charging is alternately enabled and disabled.
    steps: 'Evaluate card, bank-debit and offline tenders.'
    expected: >-
      Cards attract the fee, bank debits follow the switch and offline money
      attracts none.
    negative: false
    data_needs: The setup needs no extra fixture records.
    bound_to:
      - TST-ACC-BKS-171
    provenance: test
    source: >-
      services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#charges
      a card behaviour always, a bank debit only when the rule says so, and
      never an offline method or without a rule
    category: rule
    binding_evidence:
      - >-
        services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#charges
        a card behaviour always, a bank debit only when the rule says so, and
        never an offline method or without a rule
  - id: ATS-ACC-BKS-003-07
    title: Keep an empty tender free of charges
    level: unit
    proves:
      - BR-CAP-ACC-BKS-003-09
    preconditions: The rule includes fixed and minimum charges.
    steps: Calculate zero and negative tender fees.
    expected: Each result is zero.
    negative: false
    data_needs: Isolated fixture records matching this scenario’s setup.
    bound_to: []
    provenance: proposed
    source: >-
      services-accounting:modules/books/services/rules/card-fee-rules.ts:cardFeeAmountOf
    category: edge
    binding_evidence: []
  - id: ATS-ACC-BKS-003-08
    title: Exercise the grants for set a unit’s accounting and tender-fee policies
    category: permission
    level: api
    proves:
      - BR-CAP-ACC-BKS-016-01
    preconditions: >-
      An application-authorized reader and administrator share the run-owned
      setup. A third principal lacks the relevant resource grant.
    steps: >-
      Read the relevant configuration as the reader, attempt each supported
      mutation, then repeat with the matching administrator grants. Repeat the
      reads without their resource grants.
    expected: >-
      The permission outcomes match the cited authorization rule, and refused
      mutations leave every configured value unchanged.
    negative: true
    data_needs: >-
      Run-owned records for the stated setup; retain the initial values for
      comparison.
    bound_to: []
    provenance: proposed
    source: >-
      services-accounting:modules/books/tests/e2e/configuration-security.e2e.spec.js#is
      refused the create 403 permission_missing
    permissions_exercised:
      - accounting.business-units.update
    binding_evidence: []
    supporting_tests: []
  - id: ATS-ACC-BKS-003-09
    title: Fee percentage endpoints
    category: boundary
    level: api
    proves:
      - BR-CAP-ACC-BKS-003-03
    preconditions: >-
      An isolated workspace has records needed for both eligible and ineligible
      examples.
    steps: >-
      Submit percentages zero and one hundred, then a value immediately above
      one hundred and a fifth decimal place.
    expected: >-
      Eligible endpoints save; each exceeded precision or range is refused
      without altering the previous rule.
    negative: true
    data_needs: >-
      Run-owned records for the stated setup; retain the initial values for
      comparison.
    bound_to: []
    provenance: proposed
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a rule that is not whole (a percent without a product, a product without
      an amount, a minimum alone) by the database’s rule
    binding_evidence: []
  - id: ATS-ACC-BKS-003-10
    title: Refuse the second replacement of a shared read version
    category: concurrency
    level: api
    proves:
      - BR-CAP-ACC-BKS-016-05
    preconditions: >-
      Two administrators read the same version of the relevant mutable record.
      Each prepares a different otherwise valid replacement.
    steps: >-
      Release both replacement requests from a barrier. Read the record again
      and retry the losing payload at its original version.
    expected: >-
      One replacement commits, the other is stale, and repeating the stale
      request preserves the winning values.
    negative: true
    data_needs: >-
      Run-owned records for the stated setup; retain the initial values for
      comparison.
    bound_to: []
    provenance: proposed
    source: >-
      services-accounting:modules/books/tests/e2e/payment-terms.e2e.spec.js#answers
      400 naming is_active to a replacement that leaves it out
    interaction_with:
      - CAP-ACC-BKS-016
    binding_evidence: []
  - id: ATS-ACC-BKS-003-11
    title: Changed write-off policy uses the selected destination
    category: interaction
    level: browser
    proves:
      - BR-CAP-ACC-BKS-003-02
      - BR-CAP-ACC-BKS-006-06
    preconditions: >-
      Run-owned records satisfy the preconditions of the referenced capability
      proofs.
    steps: >-
      Create a write-off, switch its treatment before posting and post;
      separately try an allowance write-off without its default and retry after
      assignment.
    expected: >-
      Posting follows the current policy and succeeds after the required
      destination is supplied.
    negative: false
    data_needs: >-
      Run-owned records for the stated setup; retain the initial values for
      comparison.
    bound_to:
      - TST-ACC-BKS-003
    provenance: test
    source:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#takes
        bad_debt_expense on a create that leaves the treatment out
      - 'services-accounting:docs/architecture/accounting-2.0/02-decisions.md#A-1'
    exercises:
      - CAP-ACC-BKS-003
      - CAP-ACC-BKS-006
    workflows: []
    binding_evidence:
      - >-
        react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0003.spec.ts#ACT-ACC-0003
  - id: ATS-ACC-BKS-003-12
    title: Invalid content selection preserves accounting policy
    category: interaction
    level: api
    proves:
      - BR-CAP-ACC-BKS-003-02
      - BR-CAP-ACC-BKS-015-04
    preconditions: >-
      Run-owned records satisfy the preconditions of the referenced capability
      proofs.
    steps: >-
      Change write-off treatment while selecting wrong-kind content in one
      replacement, then inspect both choices.
    expected: >-
      Neither the policy nor the document choice changes when validation refuses
      the replacement.
    negative: false
    data_needs: >-
      Run-owned records for the stated setup; retain the initial values for
      comparison.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#takes
        bad_debt_expense on a create that leaves the treatment out
      - >-
        services-accounting:modules/books/tests/e2e/document-templates.e2e.spec.js#answers
        400 to a business unit naming an inactive template
    exercises:
      - CAP-ACC-BKS-003
      - CAP-ACC-BKS-015
    workflows:
      - WF-ACC-BKS-002
    binding_evidence: []
evidence:
  - >-
    Verified source file:
    react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0003.spec.ts
  - >-
    Verified source file:
    react-console:acceptance/accounting/playwright/journeys/configuration/ACT-ACC-0048.spec.ts
  - >-
    Verified source file:
    services-accounting:modules/books/services/rules/card-fee-rules.ts
  - >-
    Verified source file:
    services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js
  - >-
    Verified source file:
    services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js
product_intent: proposed
coverage:
  rule: covered
  transition: covered
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-ACC-BKS-064'
  boundary: covered
  concurrency: 'gap: FND-ACC-BKS-064'
  invariant: 'gap: FND-ACC-BKS-064'
  interaction: covered
coverage_notes:
  rule:
    scenarios:
      - ATS-ACC-BKS-003-02
      - ATS-ACC-BKS-003-05
      - ATS-ACC-BKS-003-06
    provenance: documented
    source:
      - ATS-ACC-BKS-003-02
      - ATS-ACC-BKS-003-05
      - ATS-ACC-BKS-003-06
  transition:
    scenarios:
      - ATS-ACC-BKS-003-01
    provenance: documented
    source:
      - ATS-ACC-BKS-003-01
  validation:
    scenarios:
      - ATS-ACC-BKS-003-03
      - ATS-ACC-BKS-003-04
    provenance: documented
    source:
      - ATS-ACC-BKS-003-03
      - ATS-ACC-BKS-003-04
  permission:
    scenarios:
      - ATS-ACC-BKS-003-08
    provenance: documented
    source:
      - ATS-ACC-BKS-003-08
  edge:
    scenarios:
      - ATS-ACC-BKS-003-07
    provenance: documented
    source:
      - ATS-ACC-BKS-003-07
  failure:
    scenarios: []
    provenance: unknown
    source: FND-ACC-BKS-064
    reason: >-
      No scenario isolates this category for the capability at the pinned
      revision.
    gap: FND-ACC-BKS-064
  boundary:
    scenarios:
      - ATS-ACC-BKS-003-09
    provenance: documented
    source:
      - ATS-ACC-BKS-003-09
  concurrency:
    scenarios:
      - ATS-ACC-BKS-003-10
    provenance: unknown
    source: FND-ACC-BKS-064
    gap: FND-ACC-BKS-064
    reason: >-
      The examples isolate part of this category; the finding records its
      remaining specification or execution boundary.
  invariant:
    scenarios: []
    provenance: unknown
    source: FND-ACC-BKS-064
    reason: >-
      No scenario isolates this category for the capability at the pinned
      revision.
    gap: FND-ACC-BKS-064
  interaction:
    scenarios:
      - ATS-ACC-BKS-003-11
      - ATS-ACC-BKS-003-12
    provenance: documented
    source:
      - ATS-ACC-BKS-003-11
      - ATS-ACC-BKS-003-12
rule_coverage:
  - rule: BR-CAP-ACC-BKS-003-01
    scenarios:
      - ATS-ACC-BKS-003-01
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      the change 409 naming the period and the count when a batch of the open
      period is posted, leaving the method
  - rule: BR-CAP-ACC-BKS-003-02
    scenarios:
      - ATS-ACC-BKS-003-02
      - ATS-ACC-BKS-003-11
      - ATS-ACC-BKS-003-12
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#takes
      bad_debt_expense on a create that leaves the treatment out
  - rule: BR-CAP-ACC-BKS-003-03
    scenarios:
      - ATS-ACC-BKS-003-03
      - ATS-ACC-BKS-003-09
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a rule that is not whole (a percent without a product, a product without
      an amount, a minimum alone) by the database’s rule
  - rule: BR-CAP-ACC-BKS-003-04
    scenarios:
      - ATS-ACC-BKS-003-04
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a fee product of another unit, an inactive one and one the tenant does not
      have, naming card_fee_product_id
  - rule: BR-CAP-ACC-BKS-003-05
    scenarios:
      - ATS-ACC-BKS-003-05
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#adds
      the fixed amount in the base currency: 2.9% + 30 on 10000 is 320
  - rule: BR-CAP-ACC-BKS-003-06
    scenarios:
      - ATS-ACC-BKS-003-06
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/unit/rules/card-fee-rules.spec.js#charges
      a card behaviour always, a bank debit only when the rule says so, and
      never an offline method or without a rule
  - rule: BR-CAP-ACC-BKS-003-09
    scenarios:
      - ATS-ACC-BKS-003-07
    provenance: documented
    source: >-
      services-accounting:modules/books/services/rules/card-fee-rules.ts:cardFeeAmountOf
related_coverage:
  - capability: CAP-ACC-BKS-006
    scenarios:
      - ATS-ACC-BKS-003-11
    provenance: documented
    source: >-
      specs/accounting/books/planning/capabilities/CAP-ACC-BKS-003-set-how-a-business-unit-accounts-its-method-write-off-policy.md
workflow_coverage:
  - workflow: WF-ACC-BKS-001
    interactions: []
    provenance: documented
    source: >-
      specs/accounting/books/design/workflows/WF-ACC-BKS-001-open-a-business-unit-with-its-first-fiscal-years.md
    gap: FND-ACC-BKS-064
  - workflow: WF-ACC-BKS-002
    interactions:
      - ATS-ACC-BKS-003-12
    provenance: documented
    source: >-
      specs/accounting/books/design/workflows/WF-ACC-BKS-002-replace-a-business-units-settings.md
    gap: FND-ACC-BKS-064
coverage_interpretation: >-
  Covered means a specified scenario exists. Proposed scenarios and registered
  bindings do not establish a passed run or verified conformance.
invariant_coverage:
  - invariant: INV-DOM-ACC-BKS-001-01
    scenarios:
      - ATS-ACC-BKS-001-02
    provenance: documented
    source: >-
      services-accounting:modules/books/db/business-units.table.ts:business_units_freeze_trigger
  - invariant: INV-DOM-ACC-BKS-001-02
    scenarios:
      - ATS-ACC-BKS-001-03
      - ATS-ACC-BKS-001-11
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/fiscal-years.integration.spec.js#gives
      the new unit exactly its two years, the one containing today and the next
  - invariant: INV-DOM-ACC-BKS-001-03
    scenarios:
      - ATS-ACC-BKS-002-03
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/e2e/business-units.e2e.spec.js#answers
      400 to a supported currency named twice, in Rhythm 1.0’s words
  - invariant: INV-DOM-ACC-BKS-001-04
    scenarios:
      - ATS-ACC-BKS-002-05
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/e2e/business-units.e2e.spec.js#answers
      409 naming the currency, the unit and the kinds of account that use it
  - invariant: INV-DOM-ACC-BKS-001-05
    scenarios:
      - ATS-ACC-BKS-004-02
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/exchange-rates.integration.spec.js#refuses
      changing the unit, the currency or the day: a rate’s identity is
      write-once
  - invariant: INV-DOM-ACC-BKS-001-06
    scenarios:
      - ATS-ACC-BKS-003-03
      - ATS-ACC-BKS-003-04
    provenance: documented
    source: >-
      services-accounting:modules/books/services/persistence/business-units.ts:assertCardFeeRule;
      services-accounting:modules/books/db/business-units.table.ts:business_units_card_fee_rule_check
  - invariant: INV-DOM-ACC-BKS-001-07
    scenarios:
      - ATS-ACC-BKS-015-04
    provenance: documented
    source: >-
      services-accounting:modules/books/db/business-units.table.ts:business_units_template_references_trigger
  - invariant: INV-DOM-ACC-BKS-001-08
    scenarios:
      - ATS-ACC-BKS-001-06
    provenance: documented
    source: >-
      services-accounting:modules/books/tests/integration/services/persistence/business-units.integration.spec.js#refuses
      a unit with GL, bank and merchant accounts, naming all three
permission_coverage:
  - key: accounting.business-units.update
    where_checked: api
    scenarios:
      - ATS-ACC-BKS-003-08
    provenance: documented
    source: >-
      specs/accounting/books/planning/capabilities/CAP-ACC-BKS-003-set-how-a-business-unit-accounts-its-method-write-off-policy.md
    execution_gap: FND-ACC-BKS-064
reviewed_at: '2026-10-23'
---

# Set a unit’s accounting and tender-fee policies: Capability Test Specification

## Coverage

- Protect the accounting method after current-period posting.
- Use the policy effective at posting.
- Validate a complete fee rule.
- Require an eligible fee product.
- Calculate and distribute one tender fee.
- Apply fees by payment behaviour.
- Keep an empty tender free of charges.

The structured coverage map distinguishes specified examples from remaining findings. Proposed examples require executable proof before qualification.

## Interactions With Related Capabilities

- Changed write-off policy uses the selected destination.
- Invalid content selection preserves accounting policy.

Related-capability and workflow references identify the seams each scenario exercises. The lifecycle coverage record retains transitions that still need individual proof.

## Binding Review

The pinned service and browser assertions support the registered bindings. Original test references remain beside each scenario so partial evidence and unresolved conformance remain reviewable.
