Accounting-period profiles and the global profile
An ordinary profile is one accounting-period workspace: the period-specific tables, books, and business records that are eventually closed and approved together. Each ordinary profile maps to its own PostgreSQL schema — the profile name is the schema name.
Alongside them is one reserved global profile. It holds data that should survive across accounting periods, such as shared counterparties or product catalogs. It is implemented as the reserved global PostgreSQL schema rather than as an accounting-period workspace. Its global tables are stored once and visible while working in every ordinary profile.
Profiles are their own schemas
Section titled “Profiles are their own schemas”The schemas table is the catalog of profiles:
| Column | Meaning |
|---|---|
name |
The profile name, unique, equal to the PostgreSQL schema name |
accounting_currency |
The currency the profile’s books are kept in |
description |
Free text |
is_active |
Whether the profile is active |
Tables are created inside their profile’s schema. A link stays inside a profile: a table in one profile cannot link directly to a table in another.
The accounting controls may divide activity into reporting ranges inside the profile, but profile approval is the final boundary for the complete accounting-period workspace. A successor profile can name a period in the previous profile as its predecessor when balances are carried forward.
The global profile
Section titled “The global profile”Setting global = true when defining a table places it in the global profile, physically stored in the reserved global schema. The request’s profile_name and accounting_currency are ignored because the global profile is shared and does not keep its own accounting-period books. Every existing and future ordinary profile can resolve and operate on that same physical table through its own profile context.
The global profile is intended for shared master data. Its tables have several deliberate boundaries:
- a global table can link only to another global table;
- a profile table can link to a table in its own profile or to a global table;
- global tables cannot use
ACCOUNTING,ACCOUNTING_TRANSFER, or quantity-ledger columns because those effects belong to one profile’s books; - access is still checked through the requested profile’s table permission object;
- writes affect the single shared row seen from every profile, rather than making a copy per profile.
A live table name cannot normally exist in both scopes: creation rejects a global table when a profile table with that name exists, and rejects a profile table when the global name exists. If legacy catalog data contains both, the profile-local table wins when resolving that profile.
Because a global row can be referenced from many accounting-period profiles, changing it may affect records across all of them. Row versions and archiving explains how references preserve the exact version that a period used.
How a profile comes into existence
Section titled “How a profile comes into existence”Profiles are created implicitly: defining the first table of a new profile creates the schema, and the call that does so must state the accounting_currency, because opening a profile opens its books.
Creating a profile provisions its backend-owned system tables:
| System table | Purpose |
|---|---|
general_ledger |
The profile’s accounting journals |
accounts |
Account hierarchy and live projected balances |
custom_exchange_rates |
Hand-entered rates, maintained through ordinary table data |
quantity_ledger |
Running balances for quantity-ledger columns |
These are system definitions: their structure belongs to the backend, so general table-definition operations cannot modify or delete them. accounts and custom_exchange_rates are user-maintained — their rows are ordinary data for their owning feature.
Object naming
Section titled “Object naming”A client-facing column has an alias; the physical column is named by its ordinal (1, 2, 3, …). The mapping lives in table_definition_columns and is the reason a table’s public shape can change without rewriting its data.
- a rename changes the alias while preserving the physical column and its data; links, validation, and calculations keep pointing at the same underlying field;
- rename history is kept so old saved configuration can be reconciled;
- the server remaps physical columns to aliases at the public boundary and refuses to expose an unmapped column.
System columns — id, deleted, row_revision, created_at, and account_id — have no alias and keep their public names on every table.
Name budgets
Section titled “Name budgets”Postgres truncates identifiers longer than 63 bytes instead of rejecting them, so two names that first differ past the limit silently become the same name. Every derived object name — foreign keys, indexes — is built from the physical column name and stays inside the budget by construction; table names are limited so there is always room for the widest derived name.
Table kinds
Section titled “Table kinds”| Kind | Meaning |
|---|---|
dynamic |
Ordinary user-defined tables |
system |
Backend-owned tables such as the accounting bundle and the quantity ledger |
System tables cannot be changed or deleted through general table-definition operations. A table definition also records whether it is a template-family root; children of a family share the root’s permission object.
Copying a profile
Section titled “Copying a profile”CopyProfile creates the same structure without copying row data — columns, links, indexes, validation, accounting configuration, and attached scripts are copied, and relationships are resolved against the copied tables. This turns a known-good profile into a template for another accounting entity.
The global profile is not copied. The new ordinary profile resolves the same shared global definitions and rows.
Continue
Section titled “Continue”- Table definitions covers designing tables inside a profile.
- Server setup and API overview covers the catalog tables in the server database.