---
id: TST-ACC-TRX-088
title: Customer terms suite
kind: integration
product_area: accounting
module: transactions
revision: 1
status: draft
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
repository: services-accounting
files:
  - >-
    modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js
implements:
  - ATS-ACC-TRX-019-01
  - ATS-ACC-TRX-019-02
  - ATS-ACC-TRX-019-03
  - ATS-ACC-TRX-019-04
  - ATS-ACC-TRX-019-05
  - ATS-ACC-TRX-019-07
implements_parts:
  - scenario: ATS-ACC-TRX-019-01
    assertion: it keeps one record per party
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      keeps one record per party
  - scenario: ATS-ACC-TRX-019-02
    assertion: 'it creates the record without a version, a UUIDv7 id, at version 1'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      creates the record without a version, a UUIDv7 id, at version 1
  - scenario: ATS-ACC-TRX-019-02
    assertion: >-
      it refuses a create over the existing record (no version sent), writing
      nothing
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a create over the existing record (no version sent), writing
      nothing
  - scenario: ATS-ACC-TRX-019-02
    assertion: 'it refuses a replacement at a stale version, writing nothing'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a replacement at a stale version, writing nothing
  - scenario: ATS-ACC-TRX-019-02
    assertion: >-
      it replaces the record whole: the settings left out take their defaults,
      the version moves to 2, the id stays
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      replaces the record whole: the settings left out take their defaults, the
      version moves to 2, the id stays
  - scenario: ATS-ACC-TRX-019-03
    assertion: 'it refuses inactive terms, naming the field'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses inactive terms, naming the field
  - scenario: ATS-ACC-TRX-019-03
    assertion: >-
      it refuses another tenant’s terms: the other tenant’s net 30 is not this
      one’s when this one has none
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses another tenant’s terms: the other tenant’s net 30 is not this
      one’s when this one has none
  - scenario: ATS-ACC-TRX-019-03
    assertion: >-
      it refuses a collection status and a remitting agreement outside their
      vocabularies
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a collection status and a remitting agreement outside their
      vocabularies
  - scenario: ATS-ACC-TRX-019-04
    assertion: 'it refuses a delete at a stale version, keeping the record'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a delete at a stale version, keeping the record
  - scenario: ATS-ACC-TRX-019-04
    assertion: it answers the defaults the party now has
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      answers the defaults the party now has
  - scenario: ATS-ACC-TRX-019-05
    assertion: 'it keeps terms a party names from being deleted (409, the key)'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      keeps terms a party names from being deleted (409, the key)
  - scenario: ATS-ACC-TRX-019-07
    assertion: it refuses a replacement (409)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a replacement (409)
  - scenario: ATS-ACC-TRX-019-07
    assertion: it refuses a delete (409)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js#it
      refuses a delete (409)
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: 3
isolation: >-
  Suite-marked records in the prepared local test database; fixture teardown
  removes owned records.
provenance: test
source: >-
  services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/persistence/customer-terms.integration.spec.js
    assertion_titles:
      - customer terms written (integration)
      - the upsert
      - 'creates the record without a version, a UUIDv7 id, at version 1'
      - >-
        answers every setting as written, and the system properties of the
        create
      - >-
        refuses a create over the existing record (no version sent), writing
        nothing
      - 'refuses a replacement at a stale version, writing nothing'
      - a replacement at the version read
      - >-
        replaces the record whole: the settings left out take their defaults,
        the version moves to 2, the id stays
      - the default payment terms
      - 'refuses terms the tenant does not have, naming the field'
      - 'refuses inactive terms, naming the field'
      - >-
        refuses another tenant’s terms: the other tenant’s net 30 is not this
        one’s when this one has none
      - 'keeps terms a party names from being deleted (409, the key)'
      - the database
      - >-
        trims the text and makes an empty note and an empty option id null or
        gone (the normalization trigger)
      - stores an empty allow-list as no allow-list
      - refuses a party kind outside contact and organization
      - >-
        refuses a collection status and a remitting agreement outside their
        vocabularies
      - refuses an empty allow-list and one of more than 50 ids
      - refuses an id that is not a UUIDv7
      - keeps one record per party
      - the delete
      - answers the defaults the party now has
      - leaves no record
      - answers the defaults when there is nothing to delete
      - refuses a replacement at a version of the deleted record
      - 'refuses a delete at a stale version, keeping the record'
      - a locked record
      - refuses a replacement (409)
      - refuses a delete (409)
      - tenant isolation
      - reads the record as absent in the other tenant
      - >-
        refuses the other tenant’s replacement at the record’s version: it has
        no such record
      - >-
        makes the other tenant’s write its own record, leaving the first
        tenant’s as it was
      - >-
        refuses a row for another tenant written under this tenant’s session
        (row-level security)
    imports:
      - 'const { sql } = require(''drizzle-orm'');'
      - >-
        const { IllegalOperationError, ObjectInvalidError, StaleObjectError } =
        require('@rhythmsoftware/macroservice-errors/errors');
      - >-
        const { expectConstraintViolation, expectDatabaseRefusal, expectRefusal
        } = require('@rhythmsoftware/macroservice-testing/refusals');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { TEST_ACTOR, TEST_NOW } =
        require('@rhythmsoftware/macroservice-testing/execution-context');
      - >-
        const { createPaymentTerms, deletePaymentTerms } =
        require('../../../../../books/services/persistence/payment-terms');
      - >-
        const { deleteCustomerTerms, putCustomerTerms, readCustomerTerms } =
        require('../../../../services/persistence/customer-terms');
      - '} = require(''../../invoices-fixtures'');'
      - "\t\t\tworld.require();"
      - "\t\t\tconst { terms, created: wasCreated } = created.require();"
      - "\t\t\tconst { terms } = created.require();"
      - "\t\t\tcreated.require();"
      - "\t\t\tcreated.require();"
      - "\t\t\t\tcreated.require();"
      - "\t\t\t\tconst { terms, created: wasCreated } = replaced.require();"
      - "\t\t\t\t\tterms.id === created.require().terms.id,"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\texpect(deleted.require()).to.deep.equal({"
      - "\t\t\tdeleted.require();"
      - "\t\t\tdeleted.require();"
      - "\t\t\tdeleted.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tawait expectRefusal(putCustomerTerms(ctx(), LOCKED, {}, locked.require().sys_version), IllegalOperationError, /locked/);"
      - "\t\t\tworld.require();"
      - "\t\t\twritten.require();"
      - "\t\t\tawait expectRefusal(putCustomerTerms(fixedIn(OTHER), ISOLATED, {}, written.require().sys_version), StaleObjectError);"
      - "\t\t\twritten.require();"
      - "\t\t\twritten.require();"
    test_double_declarations: []
---

# Customer terms suite

## Verification Boundary

The suite exercises transaction commands and database protections in the local prepared test environment. Registration records the existing executable file; it does not report a run or establish product approval.

## What This Suite Proves

Its cited assertions contribute evidence for terms defaults and uniqueness remain workspace-scoped; whole-record terms replacement resets omissions; invalid collection configuration is rejected; versioned removal restores defaults; referenced payment terms cannot be removed; customer-term protection blocks mutations. The structured assertion bindings identify which parts of combined scenarios it executes.

## What It Does Not Prove

Local commands and route invocations do not establish deployed routing, live processor guarantees or the complete staff browser experience. An unbound feature chain remains proposed, and this registration carries no execution result.

## Real Dependencies and Test Doubles

The suite uses the imported commands, route handlers or persistence code against the local test database. Fixture helpers supply accounting context; any stepped clock, dummy processor or substituted external client is retained in the evidence declarations.

## Must Not Be Doubled

The command or persistence behavior under assertion and its relevant database protections must remain real. Substituting the final financial result removes evidence of transactional effects.
