This page lists the columns that every business table inherits from BaseModel in core/models.py, how primary keys are generated in core/ulid.py, and how business codes are generated in core/sequence.py. Soft delete semantics are explained on soft delete and deactivation; the change history on change history.
Scope and source¶
- Defined in:
src/cluster_erp/core/models.py(Base,BaseModel),src/cluster_erp/core/ulid.py(new_ulid),src/cluster_erp/core/sequence.py(Sequence,next_code,next_value,seed_sequences) - Count: 11 columns on
BaseModel(read from the class body); 69 entity prefixes inENTITY_PREFIXESand 19 entity types inYEARLY_ENTITIES(counted from the dictionary and set literals)
BaseModel columns¶
Base is the SQLAlchemy declarative base (src/cluster_erp/core/models.py:13-14). BaseModel is an abstract subclass that adds the columns below (src/cluster_erp/core/models.py:17-78).
| Column | Type | Default | Meaning | Source |
|---|---|---|---|---|
id |
UUID |
new_ulid() |
Primary key; a ULID stored as a UUID | src/cluster_erp/core/models.py:20-24 |
tenant_id |
UUID, nullable, indexed |
none | Not set by any code in core/, server/, or modules/ (searched with grep -rn tenant_id); its use is not determined from code |
src/cluster_erp/core/models.py:25-29 |
created_at |
timestamp with time zone | now() on the server |
Creation time | src/cluster_erp/core/models.py:30-34 |
created_by |
UUID, nullable |
none | The acting user, stamped by the change-history hook on insert | src/cluster_erp/core/models.py:35-38, src/cluster_erp/core/change_history.py:216-224 |
updated_at |
timestamp with time zone | now(), refreshed on update |
Last change time | src/cluster_erp/core/models.py:39-44 |
updated_by |
UUID, nullable |
none | The acting user, stamped by the change-history hook on update | src/cluster_erp/core/models.py:45-48, src/cluster_erp/core/change_history.py:225-226 |
deleted_at |
timestamp with time zone, nullable | none | NULL means active; a value means deactivated |
src/cluster_erp/core/models.py:49-57 |
deleted_by |
UUID, nullable |
none | Who deactivated the record | src/cluster_erp/core/models.py:58-61, src/cluster_erp/core/change_history.py:249-250 |
deactivation_reason |
String(64), nullable |
none | A code from the deactivation reason catalogue | src/cluster_erp/core/models.py:62-68 |
deactivation_note |
Text, nullable |
none | Free text, required only when the reason demands it | src/cluster_erp/core/models.py:69-72 |
version |
Integer |
1 | Row version for optimistic concurrency checks | src/cluster_erp/core/models.py:73-78 |
version is not managed by SQLAlchemy's version counter: no __mapper_args__ declares one (searched with grep -rn __mapper_args__). Services raise it themselves; 140 lines in core/ and modules/ contain version + 1 (counted with grep -rn "version + 1"), for example src/cluster_erp/modules/bom/boms/services.py:152. check_version compares a client's version with the row and raises a 409 conflict on mismatch (src/cluster_erp/core/errors.py:411-430).
Some infrastructure tables subclass Base directly and do not carry these columns, for example core_change_history and audit_log (src/cluster_erp/core/change_history.py:78-98, src/cluster_erp/core/audit.py:15-69).
Primary keys¶
new_ulid() returns ULID().to_uuid() from the python-ulid package (src/cluster_erp/core/ulid.py:8-14, pyproject.toml:45). Its docstring states that the result is time-sortable and fits PostgreSQL's native uuid type (src/cluster_erp/core/ulid.py:9-13).
The change-history hook assigns the id of a new object before the flush, so the "create" history row carries the entity id (src/cluster_erp/core/change_history.py:273-297, src/cluster_erp/core/change_history.py:327).
Business codes¶
Business codes such as PTY-000001 come from the table core_sequences, one row per entity type (src/cluster_erp/core/sequence.py:112-120).
| Column | Type | Meaning | Source |
|---|---|---|---|
entity_type |
String(50), unique |
The key, for example party |
src/cluster_erp/core/sequence.py:115 |
prefix |
String(10) |
The code prefix, for example PTY |
src/cluster_erp/core/sequence.py:116 |
current_value |
BigInteger |
The last issued number | src/cluster_erp/core/sequence.py:117 |
padding |
Integer |
Zero-padding width | src/cluster_erp/core/sequence.py:118 |
yearly_reset |
Boolean |
Whether the counter restarts each calendar year | src/cluster_erp/core/sequence.py:119 |
last_reset_year |
Integer |
The year of the last reset | src/cluster_erp/core/sequence.py:120 |
The functions:
| Function | Behaviour | Source |
|---|---|---|
seed_sequences(session) |
Adds a row for every entity type in ENTITY_PREFIXES that has none. Yearly types get padding 5, others padding 6. Runs at server start-up |
src/cluster_erp/core/sequence.py:123-151, src/cluster_erp/server/app.py:1058-1066 |
next_code(session, entity_type) |
Locks the row with SELECT... FOR UPDATE, increments it, and returns PREFIX-NNNNNN or, for yearly types, PREFIX-YYYY-NNNNN |
src/cluster_erp/core/sequence.py:154-171, src/cluster_erp/core/sequence.py:182-199 |
next_value(session, entity_type) |
Same locking and reset, returns (year, value, padding) without the prefix |
src/cluster_erp/core/sequence.py:174-179 |
A yearly counter resets to zero when the current UTC year differs from last_reset_year (src/cluster_erp/core/sequence.py:194-197). An unknown entity type raises ValueError (src/cluster_erp/core/sequence.py:188-192).
The 19 yearly entity types are sales_order, purchase_order, request_for_quotation, quotation, work_order, shipment, non_conformity, production_order, goods_movement, usage_decision, costing_analysis, purchase_request, purchase_rfq, purchasing_quotation, purchase_contract, mrp_run, customer_subscription, packing_list, and delivery_note (src/cluster_erp/core/sequence.py:88-108). The other 50 prefixes are not yearly.
Constraints and validation¶
core_sequencesis excluded from the change history (src/cluster_erp/core/change_history.py:42-52).- The
FOR UPDATElock serialises concurrent callers on the same row, so codes have no gaps or collisions within a committed sequence of calls (src/cluster_erp/core/sequence.py:157-158,src/cluster_erp/core/sequence.py:185).
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| ULID | new_ulid |
A time-sortable unique identifier, stored as a PostgreSQL uuid. |
ULID | src/cluster_erp/core/ulid.py:8-14 |
| business code | next_code |
A human-readable record code such as PTY-000001 or SO-2026-00001. |
iş kodu | src/cluster_erp/core/sequence.py:154-171 |
| yearly sequence | YEARLY_ENTITIES |
A code counter that restarts every calendar year and includes the year. | yıllık sıra | src/cluster_erp/core/sequence.py:88-108 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.