The records module in src/cluster_erp/modules/records/ owns the lifecycle of records under the rule that nothing is deleted outright. It keeps the catalogue of deactivation reasons, offers generic endpoints to list deactivated records of any entity and to reactivate them, and keeps the retention window after which the daily purge removes deactivated rows for good (src/cluster_erp/modules/records/module.py:15-22). The deactivation mechanism itself is described on soft delete and deactivation.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/records/ |
folder |
| Module class | Module |
src/cluster_erp/modules/records/module.py:15 |
name / version |
records / 0.1.0 |
src/cluster_erp/modules/records/module.py:24-30 |
| API prefix | /api/v1/records (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/records/module.py:39-52 |
| Sub-modules | 2: deactivation_reasons, retention; plus lifecycle_routes.py and resolver.py |
folder listing |
| Tables | 3: core_deactivation_reasons, core_retention_config, core_retention_runs |
__tablename__ in the two models.py files |
| Entities in metadata | 1 key in entities |
src/cluster_erp/modules/records/module.py:73-75 |
| Routes | 10 route decorators in the package | grep for @...router.get/post/put/patch/delete |
| Actions | 0 ActionMeta entries |
grep "ActionMeta(" in the package |
| Migrations | 7 files | files in migrations/versions/ whose text contains one of the three table names |
| Contracts | consumes 0, produces 1 | see the section "Events and queries" |
| Web app presence | No sidebar section; surfaced from the web Settings screen | src/cluster_erp/modules/records/module.py:21-22, src/cluster_erp/modules/records/module.py:72 |
| Desktop presence | generic widgets only | src/cluster_erp/desktop/modules/ has no records folder |
What the module does¶
- Keeps the deactivation reasons an administrator can choose when a record is removed from use (
src/cluster_erp/modules/records/deactivation_reasons/models.py:9-32). - Lists the deactivated records of any deactivatable entity, resolves labels of records that may be inactive, and reactivates a record (
src/cluster_erp/modules/records/lifecycle_routes.py:1-10). - Keeps the retention window in years and the evidence rows of each retention pass (
src/cluster_erp/modules/records/retention/models.py:14-49). - Provides
purge_expired, the function that physically deletes deactivated rows and change history older than the window (src/cluster_erp/modules/records/retention/purge.py:1-7).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
deactivation_reasons |
Reason catalogue | DeactivationReason |
/deactivation-reasons |
src/cluster_erp/modules/records/deactivation_reasons/routes.py:21 |
retention |
Retention window, evidence rows, purge | RetentionConfig, RetentionRun |
/retention |
src/cluster_erp/modules/records/retention/routes.py:14 |
lifecycle_routes.py |
Passive lists, labels, reactivation | any deactivatable entity | /{module}/{entity}/... |
src/cluster_erp/modules/records/lifecycle_routes.py:32 |
Data model¶
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
DeactivationReason |
core_deactivation_reasons |
code, label_en, label_tr, requires_note, sort_order, is_active |
code unique among live rows; deactivated records point to it through their deactivation_reason column |
src/cluster_erp/modules/records/deactivation_reasons/models.py:9-32 |
RetentionConfig |
core_retention_config |
retention_years |
single row, default 10 | src/cluster_erp/modules/records/retention/models.py:19-30 |
RetentionRun |
core_retention_runs |
id (integer), ran_at, kind, summary |
evidence of each pass that removed data; plain Base without audit or soft-delete columns |
src/cluster_erp/modules/records/retention/models.py:33-49 |
Record lifecycle¶
flowchart TD L["live record"] -->|DELETE with reason| D["deleted_at, deleted_by, deactivation_reason set"] R["DeactivationReason catalogue"] --> D D --> H["core_change_history row"] D --> P["GET /records/module/entity/inactive"] P -->|POST .../record_id/activate| L D -->|older than retention_years| X["purge_expired: physical delete"] X --> E["RetentionRun evidence row"]
In prose:
- A delete route of any module sets
deleted_at. The central change-history hook stampsdeleted_byand the reason from the request context, so no module threads the reason itself (src/cluster_erp/core/change_history.py:245-259). The change is written to the change history; see change history. GET /records/{module}/{entity}/inactivelists the deactivated rows of an entity, newest first, after checking{entity}.readand the app entitlement of the module (src/cluster_erp/modules/records/lifecycle_routes.py:114-172).POST /records/{module}/{entity}/{record_id}/activatechecks{entity}.delete, clearsdeleted_atanddeactivation_reason, and emitsRecordActivated(src/cluster_erp/modules/records/lifecycle_routes.py:224-269).- Once a day the server runs the retention pass. It calls
purge_expiredwith the configured window for every database the node serves, and emitsRetentionSweepso modules drop their own time-window rows (src/cluster_erp/server/app.py:227-260,src/cluster_erp/server/app.py:404-460). purge_expireddeletes the files of expired attachments first, then every table with adeleted_atcolumn, each in its own savepoint, then change history and the legacyaudit_logolder than the window. A foreign-key conflict on one table is logged and skipped (src/cluster_erp/modules/records/retention/purge.py:33-176).- Each pass that removed data writes a
RetentionRunrow; a failure to write it does not block the purge (src/cluster_erp/modules/records/retention/services.py:20-32).
Screens and metadata¶
The module has no sidebar group; the code states that it is surfaced from the web Settings screen (src/cluster_erp/modules/records/module.py:15-22, src/cluster_erp/modules/records/module.py:72). Its one entity is deactivation_reason (src/cluster_erp/modules/records/module.py:73-75). The web client offers the reason list when a record is deactivated (webapp/js/a11y-ui.js:156). Passive lists and reactivation in the web app are described on records, documents, viewer, and share.
Status and actions¶
The module declares no ActionMeta. Its state is the deleted_at column of other modules' records.
Business rules¶
- Only entities whose metadata declares a
deleteendpoint are deactivatable.resolver.pybuilds the map from module and entity to model once at start, with hand-verified overrides for irregular table names, and logs every entity it cannot resolve as an error (src/cluster_erp/modules/records/resolver.py:1-19,src/cluster_erp/modules/records/resolver.py:35-40). - The retention window is clamped between 6 and 250 years (
src/cluster_erp/modules/records/retention/models.py:14-16,src/cluster_erp/modules/records/retention/services.py:49-54). - The purge skips
core_change_history,deleted_records,alembic_version, andcore_retention_configin the generic sweep (src/cluster_erp/modules/records/retention/purge.py:23-30). - A reason marked
requires_noteasks for a free-text note in the web client; a server-side check of the reason code against the catalogue was not found outside the web client (webapp/js/a11y-ui.js:156). - Deleting a reason sets
deleted_at(src/cluster_erp/modules/records/deactivation_reasons/services.py:73-79).
Events and queries¶
The module consumes no contract and produces 1, counted as on the fleet module page.
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | RecordActivated |
activate route |
src/cluster_erp/modules/records/lifecycle_routes.py:257-268 |
RetentionSweep is emitted by the server with source_module="records", not by the package itself (src/cluster_erp/server/app.py:240). The full list is on the contracts reference.
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
deactivation_reason.read, .create, .update, .delete |
reason catalogue | src/cluster_erp/modules/records/deactivation_reasons/routes.py:24-82 |
retention_config.read, .update |
retention window | src/cluster_erp/modules/records/retention/routes.py:17-35 |
{entity}.read |
passive lists and labels | src/cluster_erp/modules/records/lifecycle_routes.py:126, src/cluster_erp/modules/records/lifecycle_routes.py:191 |
{entity}.delete |
reactivation | src/cluster_erp/modules/records/lifecycle_routes.py:234 |
Privacy, retention, and history¶
The module has no privacy_handlers.py. It owns the retention window and the purge described above. The scheduling of the daily pass is on scheduled jobs and retention.
Analytics facts¶
This module has no analytics_facts.py.
Settings and reference data¶
GETandPUT /records/retention/read and set the window; a missing row is created with the default of 10 years (src/cluster_erp/modules/records/retention/routes.py:17-35,src/cluster_erp/modules/records/retention/services.py:39-47).- The deactivation reasons are the module's reference data. A seed of reasons was not found in the package.
Tests¶
Found by a grep for /records/ in web-client/_e2e_*.mjs and for modules.records, /records/, or retention in tests/*.py.
| Kind | File | Covers |
|---|---|---|
| e2e | web-client/_e2e_deactivate_reason.mjs, web-client/_e2e_deactivate_ui.mjs |
deactivation with a reason |
| e2e | web-client/_e2e_lifecycle.mjs |
passive lists and reactivation |
| e2e | web-client/_e2e_records_entitlement.mjs |
app entitlement on lifecycle routes |
| e2e | web-client/_e2e_retention.mjs |
retention window |
| pytest | tests/test_control_retention.py, tests/test_data_map_package1.py, tests/test_tenancy.py |
files that mention records or retention |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| deactivation reason | DeactivationReason |
A catalogue entry that says why a record was removed from use. | devre dışı bırakma nedeni | src/cluster_erp/modules/records/deactivation_reasons/models.py:9 |
| retention window | retention_years |
The years a deactivated record is kept before the purge removes it. | Saklama süresi | src/cluster_erp/modules/records/retention/models.py:19-30 |
| passive record | list_inactive |
A record with deleted_at set, listed for review and reactivation. |
Pasif kayıt | src/cluster_erp/modules/records/lifecycle_routes.py:114-115 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.