---
id: TST-ACC-TRX-007
title: Invoice documents 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/invoice-documents.e2e.spec.js
implements:
  - ATS-ACC-TRX-005-02
  - ATS-ACC-TRX-005-06
implements_parts:
  - scenario: ATS-ACC-TRX-005-02
    assertion: 'it renders the invoice with its template, and its e-mail'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js#it
      renders the invoice with its template, and its e-mail
  - scenario: ATS-ACC-TRX-005-02
    assertion: 'it refuses a format other than html: 400'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js#it
      refuses a format other than html: 400
  - scenario: ATS-ACC-TRX-005-06
    assertion: it renders for a principal holding only accounting.invoices.read
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js#it
      renders for a principal holding only accounting.invoices.read
  - scenario: ATS-ACC-TRX-005-06
    assertion: >-
      it refuses the e-mail to a principal holding only
      accounting.invoices.read: 403, submitting nothing
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js#it
      refuses the e-mail to a principal holding only accounting.invoices.read:
      403, submitting nothing
  - scenario: ATS-ACC-TRX-005-06
    assertion: it sends the e-mail for a principal holding accounting.invoices.email
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js#it
      sends the e-mail for a principal holding accounting.invoices.email
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/invoice-documents.e2e.spec.js
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/e2e/invoice-documents.e2e.spec.js
    assertion_titles:
      - the invoice document routes (end to end)
      - 'GET /invoices/{tenantId}/{id}/render'
      - 'renders the invoice with its template, and its e-mail'
      - renders html when asked for it by name
      - 'refuses a format other than html: 400'
      - 'refuses an inactive template in 1.0’s words: 409'
      - 'refuses an invoice that is not there: 404 not_found'
      - 'POST /invoices/{tenantId}/{id}/email'
      - >-
        submits the e-mail to the bill-to contact, answering it as submitted,
        the header its key
      - 'submits it to the addresses given, with the subject and message given'
      - 'refuses a body that is not an e-mail request: 400, submitting nothing'
      - 'refuses an invoice whose contact has no address: 409, submitting nothing'
      - >-
        writes Platform’s pointer row under the derived id it answers, with the
        production sender
      - permissions
      - renders for a principal holding only accounting.invoices.read
      - >-
        refuses the e-mail to a principal holding only accounting.invoices.read:
        403, submitting nothing
      - sends the e-mail for a principal holding accounting.invoices.email
      - tenant isolation
      - renders tenant A’s invoice in tenant A (premise)
      - >-
        answers 404 for tenant A’s invoice rendered in tenant B, exactly as one
        that does not exist
      - >-
        answers 404 for tenant A’s invoice e-mailed in tenant B, and sends
        nothing
    imports:
      - 'const { sql } = require(''drizzle-orm'');'
      - >-
        } =
        require('@rhythmsoftware/macroservice-communications/testing/in-memory-communication-object-store');
      - >-
        const { parseBody, proxyEvent } =
        require('@rhythmsoftware/macroservice-testing/signed-context');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { createDocumentTemplate } =
        require('../../../books/services/persistence/document-templates');
      - >-
        const { accountingApi, accountingGrant } =
        require('../../../books/tests/e2e/e2e-security');
      - >-
        const { accessRefusalOf, PERMISSION_MISSING, requireOk } =
        require('../../../books/tests/e2e/configuration-e2e-support');
      - >-
        const { communicationsInvoiceEmailSender, useInvoiceEmailSenderForTests
        } = require('../../services/ports/invoice-email-sender');
      - '} = require(''../integration/invoices-fixtures'');'
      - 'const { putContact } = require(''../integration/parties-fixtures'');'
      - "\t\t\tctx: ctx ?? session.require().administrator,"
      - "\t\t\tsession.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { old } = invoices.require();"
      - "\t\t\tinvoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { noAddress } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst answer = await call('GET', ROUTES.render, { ctx: session.require().reader, id: house.id });"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst answer = await call('POST', ROUTES.email, { key: 'e2e-docs-key-4', ctx: session.require().reader, id: house.id, body: {} });"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst answer = await call('POST', ROUTES.email, { key: 'e2e-docs-key-5', ctx: session.require().emailer, id: house.id, body: {} });"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
      - "\t\t\tconst { house } = invoices.require();"
    test_double_declarations:
      - ' * the stand-in declares them, the shared submission helper and Platform''s submissions table (the local stand-in), and PostgreSQL.'
---

# Invoice documents 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 invoice output is generated on request; viewing and delivery use separate authorities. 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.
