> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Document Lifecycle

> From creation through formats and delivery to the customer portal — the path a sales document takes

# Document Lifecycle

Documents are the centre of the data model: 103 fields, 42 relations, reaching 6 of the 8
schema clusters. `documents` is also the largest core domain — 56 files, 11,682 lines.

## Creation paths

Four ways a document comes into existence, converging on the same accounting handoff.

```mermaid theme={null}
flowchart LR
    Sync["Integration sync<br/>batchCreateDocumentsActivity"]
    Draft["Manual draft<br/>create-draft-document.ts"]
    Direct["Direct posting<br/>create-direct-posting-document.ts"]
    Sammel["Sammelbeleg<br/>create-sammelbeleg-document.ts"]

    Doc[("documents<br/>+ line_items")]

    Numbers["number_ranges<br/>allocate document number"]
    Formats["document-format<br/>resolve format ids"]
    Connections["resolve-document-connections<br/>batch-connect-documents"]
    Periods["accounting_periods<br/>assign period"]

    Release["release-document-for-accounting.ts<br/>accounting_ready_at + version"]
    Queue[("accounting_work_queue")]

    Sync --> Doc
    Draft --> Doc
    Direct --> Doc
    Sammel --> Doc

    Numbers --> Doc
    Doc --> Formats
    Doc --> Connections
    Doc --> Periods
    Doc --> Release
    Release --> Queue

    classDef pkg fill:#e8eafd,stroke:#4967E6,color:#111827
    classDef data fill:#fde8e8,stroke:#c53b3b,color:#111827
    classDef async fill:#e3f7ea,stroke:#2f9e5c,color:#111827

    class Draft,Direct,Sammel,Numbers,Formats,Connections,Periods,Release pkg
    class Doc,Queue data
    class Sync async
```

Number allocation happens **before** creation — `batchAllocateNumbersActivity` runs as a
distinct pipeline step so numbers stay gapless under concurrency.

## Status model

`STATUS_TYPE` in the Prisma schema, with the documented meaning of each value:

```mermaid theme={null}
stateDiagram-v2
    [*] --> DRAFT: created, editable
    DRAFT --> PROCESSING: submitted
    PROCESSING --> UNAPPLIED: requires reconciliation
    PROCESSING --> COMPLETED: no reconciliation needed
    UNAPPLIED --> APPLIED: reconciliation completed
    APPLIED --> PARTIALLY_REFUNDED: partial refund
    APPLIED --> REFUNDED: full refund
    COMPLETED --> REFUNDED: full refund
    DRAFT --> CANCELLED: voided
    PROCESSING --> CANCELLED: voided
    UNAPPLIED --> CANCELLED: voided
```

`CUSTOM` is a ninth value — user-defined, and multiple are allowed per object type, so it
sits outside this graph.

Status resolution lives in the `statuses` domain (in-degree 4), not in `documents`.

## Formats and e-invoicing

```mermaid theme={null}
flowchart TB
    Doc[("documents")] --> Resolve["document-format<br/>resolve-format-ids.ts<br/>find-or-create-format.ts"]
    Resolve --> Formats[("document_formats<br/>document_format_defaults")]
    Resolve --> EInvoice["document-format/einvoice"]
    EInvoice --> Mustang["mustang<br/>ZUGFeRD via Mustang client"]
    Mustang --> Pdf["@cona/pdf<br/>render-zugferd"]
    Pdf --> Outputs[("document_format_outputs")]

    classDef pkg fill:#e8eafd,stroke:#4967E6,color:#111827
    classDef data fill:#fde8e8,stroke:#c53b3b,color:#111827
    class Resolve,EInvoice,Mustang,Pdf pkg
    class Doc,Formats,Outputs data
```

`document-format` is the third-largest core domain (24 files, 5,246 lines) — most of that
weight is the `einvoice` subtree. German e-invoicing (ZUGFeRD) goes through the `mustang`
domain, which wraps the Mustang toolchain and parses its validation report.

## Delivery

```mermaid theme={null}
flowchart TB
    Doc[("documents")] --> Profile["resolve-document-delivery-profile.ts"]
    Profile --> Channels[("document_delivery_profiles<br/>document_delivery_profile_channels")]
    Profile --> ResolveFmt["resolve-document-delivery-formats.ts"]
    ResolveFmt --> Snapshot["snapshot-document-format-outputs.ts"]
    Snapshot --> Send{"delivery channel"}

    Send -->|email| Email["@cona/emails<br/>Resend or org SMTP"]
    Send -->|portal| Token["portal token<br/>ensurePortalTokensForDocuments"]
    Send -->|marketplace| Mirakl["Mirakl OR74 upload<br/>submit-document-request"]

    Token --> Portal["@cona/portal"]

    classDef pkg fill:#e8eafd,stroke:#4967E6,color:#111827
    classDef data fill:#fde8e8,stroke:#c53b3b,color:#111827
    classDef app fill:#dbe2fb,stroke:#3B56C5,color:#111827
    classDef external fill:#fff4dd,stroke:#c98a12,color:#111827

    class Profile,ResolveFmt,Snapshot,Token,Mirakl pkg
    class Doc,Channels data
    class Portal app
    class Email external
```

Delivery **snapshots** format outputs rather than re-rendering on send — so what the
customer received stays reproducible even if templates change later.

Both portal token creation and auto-send run inside the ingestion pipeline as step 5.6
(`chunk-processor.ts:478`), so synced documents arrive delivery-ready.

## Document connections

`document_relations` links documents in parent/child pairs — an invoice to its credit
note, an order to its invoice. It carries **no `org_id`**; tenancy comes only through
`child_document_id` and `parent_document_id`.

Resolution is a distinct pipeline step: `resolveDocumentConnectionsActivity` then
`batchConnectDocumentsActivity` (`chunk-processor.ts:862,919`).

## Key files

| Concern                  | File                                             |
| ------------------------ | ------------------------------------------------ |
| Batch creation from sync | `documents/batch-create-documents.ts`            |
| Manual draft             | `documents/create-draft-document.ts`             |
| Direct posting           | `documents/create-direct-posting-document.ts`    |
| Collective document      | `documents/create-sammelbeleg-document.ts`       |
| Accounting handoff       | `documents/release-document-for-accounting.ts`   |
| Accounting read model    | `documents/get-document-for-accounting.ts`       |
| Which fields affect GL   | `documents/gl-relevant-fields.ts`                |
| Edit eligibility         | `documents/check-document-edit-eligibility.ts`   |
| Duplicate detection      | `documents/batch-duplication-check.ts`           |
| Refunds                  | `documents/add-refund-line-items-to-document.ts` |
| Finalisation             | `documents/finalize-invoice.ts`                  |

## Notes

**`gl-relevant-fields.ts` is the coupling contract.** It defines which document fields
force accounting re-derivation when changed — the reason `documents` and `accounting`
import each other. See [Core Domain Map](/architecture/core-domain-map).

**Edit eligibility is enforced in core, not the UI.**
`check-document-edit-eligibility.ts` gates whether a document can still be modified once
it has accounting impact.

**Sammelbeleg is a first-class creation path.** The German collective-document flow has
its own creation function and its own API route (`api/sammelbeleg`).
