---
id: TST-ACC-TRX-046
title: Create draft 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/invoices/create-draft.integration.spec.js
implements:
  - ATS-ACC-TRX-001-03
  - ATS-ACC-TRX-001-04
  - ATS-ACC-TRX-001-05
implements_parts:
  - scenario: ATS-ACC-TRX-001-03
    assertion: it refuses an inactive product (product_not_sellable)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      refuses an inactive product (product_not_sellable)
  - scenario: ATS-ACC-TRX-001-03
    assertion: it refuses another unit’s product (business_unit_mismatch)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      refuses another unit’s product (business_unit_mismatch)
  - scenario: ATS-ACC-TRX-001-03
    assertion: it refuses a product of a type that is not sold (product_not_sellable)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      refuses a product of a type that is not sold (product_not_sellable)
  - scenario: ATS-ACC-TRX-001-03
    assertion: >-
      it refuses a line priced in another currency than the invoice’s
      (currency_mismatch)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      refuses a line priced in another currency than the invoice’s
      (currency_mismatch)
  - scenario: ATS-ACC-TRX-001-03
    assertion: it writes nothing for a refused draft
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      writes nothing for a refused draft
  - scenario: ATS-ACC-TRX-001-04
    assertion: it refuses a discount above the line’s extended amount (over_application)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      refuses a discount above the line’s extended amount (over_application)
  - scenario: ATS-ACC-TRX-001-05
    assertion: 'it takes the due date from the terms: thirty days after the invoice date'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      takes the due date from the terms: thirty days after the invoice date
  - scenario: ATS-ACC-TRX-001-05
    assertion: it keeps the due date the request states over the terms’
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      keeps the due date the request states over the terms’
  - scenario: ATS-ACC-TRX-001-05
    assertion: >-
      it REGRESSION — refuses a due date before the invoice date with a 400, not
      the database’s CHECK
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js#it
      REGRESSION — refuses a due date before the invoice date with a 400, not
      the database’s CHECK
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/invoices/create-draft.integration.spec.js
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/invoices/create-draft.integration.spec.js
    assertion_titles:
      - createDraft (integration)
      - a draft of two lines with net 30 terms and no due date
      - answers a draft with no number
      - >-
        answers the header totals in minor units: subtotal, discount, tax, total
        and balance
      - 'answers each line with a UUIDv7 id, its position and its amounts'
      - >-
        keeps the discount reason of a line, trimmed, and none for a line
        without one
      - freezes no accounts on a draft’s lines
      - snapshots the bill-to contact’s name from CRM
      - 'takes the due date from the terms: thirty days after the invoice date'
      - keeps the memo and the purchase order number
      - reads back what it answered
      - records one create_draft document event by the caller
      - the bill-to and the due date
      - snapshots an organization’s name
      - 'keeps a bill-to CRM does not have, with no name'
      - keeps the due date the request states over the terms’
      - >-
        takes the invoice date as the due date when neither a due date nor terms
        are given
      - >-
        REGRESSION — refuses a due date before the invoice date with a 400, not
        the database’s CHECK
      - the refusals
      - refuses a business unit the tenant has not (not_found)
      - refuses a product the tenant has not (not_found)
      - refuses payment terms the tenant has not (not_found)
      - refuses a tax class the tenant has not (not_found)
      - refuses a currency the unit does not support (currency_mismatch)
      - >-
        refuses a line priced in another currency than the invoice’s
        (currency_mismatch)
      - refuses another unit’s product (business_unit_mismatch)
      - refuses an inactive product (product_not_sellable)
      - >-
        refuses a product of another type than the line names
        (product_not_sellable)
      - refuses a product of a type that is not sold (product_not_sellable)
      - refuses a discount above the line’s extended amount (over_application)
      - writes nothing for a refused draft
    imports:
      - >-
        const { InvalidRequestError } =
        require('@rhythmsoftware/macroservice-errors/errors');
      - >-
        const { expectRefusal } =
        require('@rhythmsoftware/macroservice-testing/refusals');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { createProduct } =
        require('../../../../../products/services/persistence/products');
      - >-
        const { createDraft } =
        require('../../../../services/invoices/invoice-drafts');
      - >-
        const { getInvoice } =
        require('../../../../services/invoices/invoice-reads');
      - '} = require(''../../invoices-fixtures'');'
      - "\t\t\tworld.require();"
      - "\t\t\tconst draft = creating.require();"
      - "\t\t\tconst draft = creating.require();"
      - "\t\t\tconst { lines } = creating.require();"
      - "\t\t\tconst { lines, id } = creating.require();"
      - "\t\t\texpect(creating.require().lines.map((line) => [line.receivable_gl_account_id, line.revenue_gl_account_id])).to.deep.equal(["
      - "\t\t\texpect(creating.require().bill_to_name).to.equal('Ada Lovelace');"
      - "\t\t\texpect([creating.require().payment_terms_id, creating.require().due_date]).to.deep.equal(['net_30', '2026-02-09']);"
      - "\t\t\texpect([creating.require().memo, creating.require().purchase_order_number]).to.deep.equal(['Spring renewal', 'PO-77']);"
      - "\t\t\texpect(await getInvoice(ctx(), creating.require().id)).to.deep.equal(creating.require());"
      - "\t\t\texpect(await documentEventRowsOf(TENANT, creating.require().id)).to.deep.equal(["
      - "\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\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
      - "\t\t\tworld.require();"
    test_double_declarations:
      - ' * and totals in minor units, the bill-to''s name snapshot read from CRM''s stand-in, the due date from the terms, and every refusal'
      - ' * Real: the command through its one transaction, the idempotency ledger, the configuration and products reads, the CRM stand-in'
---

# Create draft 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 invalid product choices create no draft; rounded lines reconcile with their document; due-date precedence is explicit. 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.
