Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Decision records

Explanation

Party unification

Decision record for the rule that customers, suppliers, organisations, and employees in Cluster ERP are one party record with role facets, linked over the EventBus.

Status: Accepted, stated in the project's written engineering rules. This record covers the rule that a person or an organisation exists once in Cluster ERP, as a party (Party), and that customer, supplier, owned organisation, and employee are roles of that party, shown as facets on one card. The module is described on parties module (parties).

Context

Customers, suppliers, owned organisations, and employees live in different modules: crm, procurement, and owned_organisations. Without a common record the same company or person would be typed and maintained in several places. The project's written engineering rules state that everything is a party: one party card with role facets instead of separate cards, roles declared with FacetMeta and synchronised with PartyRoleSet, and no module importing the parties module. Further rationale is not determined from code.

Decision

  • The party is the canonical record. Its flags is_customer, is_supplier, and is_employee say which roles it plays (src/cluster_erp/modules/parties/parties/models.py:33-35).
  • Each role record holds a party_id. Its metadata declares IdentityMeta, so the web app opens the party card instead of a separate role card (src/cluster_erp/core/metadata.py:304-313). Seven entity metadata objects declare it (counted with grep -rn "identity=IdentityMeta").
  • The party card lists the roles as FacetMeta entries, each loaded by a plain query filter on party_id (src/cluster_erp/core/metadata.py:357-377, src/cluster_erp/modules/parties/parties/metadata.py:550-722).
  • A role module that links or unlinks a party emits PartyRoleSet; the parties module sets the matching flag (src/cluster_erp/core/contracts.py:47-55).

The sequence diagram shows a supplier being linked to a party.

sequenceDiagram
 participant Sup as procurement SupplierService
 participant Bus as EventBus
 participant Par as parties Module
 participant DB as Party row
 Sup->>Sup: create Supplier with party_id
 Sup->>Bus: emit PartyRoleSet(role supplier, active true)
 Bus->>Par: _handle_party_role_set
 Par->>DB: set is_supplier and updated_at

In prose: after the supplier row is flushed, the service emits PartyRoleSet with the party id, the role, and active=True (src/cluster_erp/modules/procurement/suppliers/services.py:130-145). The parties module subscribed to that event (src/cluster_erp/modules/parties/module.py:130). Its handler maps the role to the flag column and updates the party in its own session (src/cluster_erp/modules/parties/module.py:168-192).

Consequences

  • The party card shows five facets: customer (crm), supplier (procurement), owned organisation, employee, and assignment (owned_organisations). The assignment facet appears only when is_employee is set (src/cluster_erp/modules/parties/parties/metadata.py:551, src/cluster_erp/modules/parties/parties/metadata.py:629, src/cluster_erp/modules/parties/parties/metadata.py:680, src/cluster_erp/modules/parties/parties/metadata.py:700, src/cluster_erp/modules/parties/parties/metadata.py:718-722).
  • Creating a party with roles in one step is application-layer orchestration in server/routes/party_composite.py, declared to the client by composite_create (src/cluster_erp/server/routes/party_composite.py:1-10, src/cluster_erp/modules/parties/parties/metadata.py:319).
  • Adding a role to an existing party is also application-layer orchestration (src/cluster_erp/server/routes/party_roles.py:1-10).
  • A start-up pass reconciles the flags with the facets that exist, as a safety net for missed events (src/cluster_erp/server/app.py:1002-1008).
  • The flag update goes through the ORM: the handler loads the party, sets the flag and commits, so change history records the change and updated_at refreshes by onupdate (src/cluster_erp/modules/parties/module.py:184-193).

Where the code enforces it

Mechanism What it checks Source
test_guided_party_creation_is_declared_in_metadata Guided creation offers exactly the roles customer, supplier, and employee, each with its flag field and data groups tests/test_metadata_driven_rules.py:51-60
tests/test_party_form_contract.py The party form's metadata, schemas, error codes, and translations agree tests/test_party_form_contract.py:1-6
Module boundary test No role module imports the parties module tests/test_architecture_boundaries.py:46-57
_sync_party_role_flags Flags match existing facets after every start src/cluster_erp/server/app.py:1002-1008

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13