Skip to content

Invoice creation

Invoice creation reads a field contract embedded in a Typst template and creates the ordinary user tables needed to hold that shape. Scalar fields become columns on a root table, while repeated collections become child tables linked to their parent.

Use invoice creation when a Typst template already declares the data paths it expects and you want the server to derive a matching relational table bundle.

Use ordinary table definition instead when precise types, required fields, indexes, money settings, accounting behavior, or a deliberately designed relational model matter at initial creation. The invoice creator intentionally produces only a basic starting structure.

The operation needs:

  • the existing target profile;
  • a name for the root invoice table;
  • the complete Typst source;
  • optional row-display columns for the root table.

The target profile must already exist. Invoice creation does not accept an accounting currency, which is required when creating a new profile.

The template must contain exactly one komp_ac_fields declaration with at least one field:

#let komp_ac_fields = (
"invoice.number",
"invoice.issued_at",
"customer.name",
"products[].name",
"products[].requested_quantity",
)

The rest of the Typst source is not compiled or semantically validated during table creation. The server parses this declaration to obtain paths; it does not prove that the rendering code uses those paths correctly.

Suppose the requested root name is invoice. The example above can create:

invoice
└── invoice_products
└── required link: invoice → invoice.id

A nested collection creates another level:

"products[].allocations[].location"
invoice
└── invoice_products
└── invoice_products_allocations
└── required link: invoice_products → invoice_products.id

Generated child names consist of the root name followed by every collection segment, with [] removed and segments joined by underscores. All normal identifier rules and length limits still apply.

Each child links to its immediate generated parent, not directly to the root. That parent link is required. The root and children are recorded as one table family, so data permissions are granted on the root and apply to every generated child.

When a path does not match an existing table reference, its final segment becomes a local column in the table for its collection scope:

Path Generated table Local column
invoice.number invoice number
invoice.issued_at invoice issued_at
products[].requested_quantity invoice_products requested_quantity
products[].allocations[].location invoice_products_allocations location

Every such column is created as optional TEXT. The server does not infer that issued_at is a date, requested_quantity is numeric, or unit_price is money. It creates no requested indexes. System columns and generated links retain their normal automatic indexes.

You may append ordinary columns later, attach validation and Steel scripts, and rename public aliases using the regular table APIs. Existing generated columns cannot have their types changed through the append-columns operation, so treat the initial all-text choice as a real limitation.

Before generating anything, the server catalogs the active tables and columns already present in the target profile. A path becomes a reference when:

  1. its segment immediately before the field names an existing table, after removing []; and
  2. its final segment names an existing column of that table, including id.

For example, if the profile already contains customer.name, then:

"customer.name"

does not create a local name column. It creates an optional customer link to the existing customer table.

Likewise, if products.name exists, products[].name creates one optional products link in invoice_products. Several matching paths such as products[].name and products[].sku still produce only that one link; referenced values remain owned by the existing product row.

Reference detection uses only the catalog that existed before invoice creation starts. Generated invoice tables are not treated as existing reference targets during the same request; their parent relationships are created explicitly from collection scopes.

Requested row-display columns apply only to the root invoice table. They must name columns actually generated there, such as number, or a generated reference alias such as customer.

Child tables receive no configured row-display columns and are identified by row ID.

Invoice creation changes table structure, so it requires table-management authority. The built-in admin and superadmin roles have that authority; ordinary data roles do not.

The server prepares the planned table definitions, then creates the complete family in one database transaction. If any generated name, column, link, display column, or table creation fails, the transaction is rolled back and none of the bundle remains.

On success, the result identifies every generated table, its collection path, and its immediate parent. The root has no collection path or parent.

After creation, these are ordinary tables served by the normal table-data operations:

  1. Insert the root invoice row and retain its ID.
  2. Insert each first-level collection row with the required root link.
  3. Insert nested collection rows with the required link to their immediate parent row.
  4. Supply IDs for references such as customer or products.

The server does not insert the hierarchy from one nested document automatically. Bulk import may be used per table, but parent tables must be inserted first so their IDs are available to child rows.

Continue with The field contract for path rules and common modeling mistakes.