---
id: TST-ACC-TRX-003
title: Change memos suite
kind: api-lifecycle
product_area: accounting
module: transactions
revision: 1
status: draft
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
repository: services-accounting
files:
  - modules/transactions/tests/e2e/change-memos.e2e.spec.js
implements:
  - ATS-ACC-TRX-003-08
  - ATS-ACC-TRX-003-11
  - ATS-ACC-TRX-015-08
  - ATS-ACC-TRX-016-04
implements_parts:
  - scenario: ATS-ACC-TRX-003-08
    assertion: it is 400 without a reason
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      is 400 without a reason
  - scenario: ATS-ACC-TRX-003-11
    assertion: >-
      it is refused the create, the void, the details and the batch routes 403
      permission_missing
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      is refused the create, the void, the details and the batch routes 403
      permission_missing
  - scenario: ATS-ACC-TRX-003-11
    assertion: it lists no action
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      lists no action
  - scenario: ATS-ACC-TRX-003-11
    assertion: it is refused every credit memo route 403 permission_missing
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      is refused every credit memo route 403 permission_missing
  - scenario: ATS-ACC-TRX-015-08
    assertion: >-
      it is refused the create, the void, the details and the batch routes 403
      permission_missing
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      is refused the create, the void, the details and the batch routes 403
      permission_missing
  - scenario: ATS-ACC-TRX-016-04
    assertion: it reads A’s memo 404 not_found in tenant B
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js#it
      reads A’s memo 404 not_found in tenant B
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/e2e/change-memos.e2e.spec.js'
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/change-memos.e2e.spec.js
    assertion_titles:
      - credit and debit memos (end to end)
      - an administrator’s credit memo
      - 'answers 200 with the memo issued, numbered, its total in minor units'
      - reads the memo as the create answered it
      - >-
        changes the memo’s text (PUT .../details) and moves it to another open
        batch (PUT .../batch)
      - 'lists the invoice’s memos a page at a time, oldest first'
      - lists the void and reassign-batch actions for the administrator
      - >-
        voids the memo through the void action, the invoice owing what the memo
        took off again
      - an administrator’s debit memo
      - >-
        raises the invoice through the route, and the void route takes it back
        off
      - a body the boundary refuses
      - is 400 without a reason
      - is 400 with a field the request does not have
      - is 400 with an amount that is not minor units
      - >-
        is 400 over_application crediting more than the line owes, the invoice
        untouched
      - a principal holding only accounting.application.access
      - is refused every credit memo route 403 permission_missing
      - is refused every debit memo route 403 permission_missing
      - a principal holding only the memo kinds’ read
      - reads the memo
      - lists no action
      - >-
        is refused the create, the void, the details and the batch routes 403
        permission_missing
      - is refused performing the void action 403
      - tenant isolation
      - >-
        answers A’s memo in tenant B exactly as an id no memo has, but for the
        id
      - reads A’s memo 404 not_found in tenant B
      - refuses B’s void of A’s memo 404 and leaves it issued
      - lists none of A’s invoice’s memos in B
      - refuses B’s memo on A’s invoice 404
    imports:
      - 'const { expect } = require(''chai'');'
      - 'const { integrationDatabaseUrl } = require(''../../../../db/config'');'
      - >-
        const { registerLocalTenant, unregisterLocalTenant } =
        require('../../../../db/test-support/platform-tables');
      - >-
        const { testId } =
        require('@rhythmsoftware/macroservice-testing/integration-database');
      - >-
        const { uuidV7 } =
        require('@rhythmsoftware/macroservice-records/uuid-v7');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { accountingApi, accountingGrant } =
        require('../../../books/tests/e2e/e2e-security');
      - >-
        const { PERMISSION_MISSING, accessRefusalOf, configurationCaller } =
        require('../../../books/tests/e2e/configuration-e2e-support');
      - 'const { ready } = require(''../../../books/tests/integration/support'');'
      - >-
        const { balancesOf, createMemoWorld, ctxFor, issuedInvoice, openBatchOf,
        removeMemoWorld } = require('../integration/memos-fixtures');
      - "\tconst call = configurationCaller(handler, { tenant_id: () => TENANT_A, ctx: () => session.require().administrator });"
      - "\tconst world = () => session.require();"
      - "\t\t\tconst { first } = issued.require();"
      - "\t\t\tconst { first } = issued.require();"
      - "\t\t\tconst { second } = issued.require();"
      - "\t\t\tconst { first, second } = issued.require();"
      - "\t\t\tconst answer = await call('GET', ROUTES.credit_memo_actions, { id: issued.require().first.body.id });"
      - "\t\t\tconst { first, second } = issued.require();"
      - "\t\t\tconst answer = await call('GET', ROUTES.credit_memo, { ctx: world().reader, id: memo.require().id });"
      - "\t\t\texpect([answer.status, answer.body.id]).to.deep.equal([200, memo.require().id]);"
      - "\t\t\tconst answer = await call('GET', ROUTES.credit_memo_actions, { ctx: world().reader, id: memo.require().id });"
      - "\t\t\t\t\tid: memo.require().id,"
      - "\t\t\t\t\tid: memo.require().id,"
      - "\t\t\t\t\tid: memo.require().id,"
      - "\t\t\t\tid: memo.require().id,"
      - "\t\t\tconst foreign = await call('GET', ROUTES.credit_memo, { tenant_id: TENANT_B, id: memo.require().id });"
      - "\t\t\texpect(shape(foreign, memo.require().id)).to.deep.equal(shape(missing, absent));"
      - "\t\t\tconst answer = await call('GET', ROUTES.credit_memo, { tenant_id: TENANT_B, id: memo.require().id });"
      - "\t\t\t\tid: memo.require().id,"
      - "\t\t\texpect([codedRefusalOf(answer), (await call('GET', ROUTES.credit_memo, { id: memo.require().id })).body.status]).to.deep.equal(["
    test_double_declarations: []
---

# Change memos 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 memo void preserves opposing lines; memo write authority cannot be inferred from read; update access controls movement and details; workspace isolation treats foreign records as absent. 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.
