The parties module in src/cluster_erp/modules/parties/ keeps one record, the party (Party, a person or an organisation the company deals with), for everyone the business knows. Customers, suppliers, employees, and owned organisations are roles of a party, not separate cards; that model is explained on party unification. The module also holds addresses, communication channels, spoken languages, and the country, address-level, and channel-type reference data. This page is the module overview; each sub-module has its own reference page.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/parties/ |
folder |
| Module class | Module |
src/cluster_erp/modules/parties/module.py:22 |
name / version |
parties / 0.1.0 |
src/cluster_erp/modules/parties/module.py:23-29 |
| API prefix | /api/v1/parties (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38 |
| Sub-modules | 5: parties, addresses, communication_channels, person_languages, settings |
folder listing |
| Tables | 11 (__tablename__ count in the package) |
src/cluster_erp/modules/parties/models.py:1-33 |
| Entities in metadata | 8: party, address, communication_channel, person_language, country, address_level, address_level_value, channel_type |
src/cluster_erp/modules/parties/module.py:642-651 |
| Routes | 53 route decorators in the package, plus 4 in server/routes/party_composite.py and server/routes/party_roles.py |
grep for @router. |
| Web app presence | Catalogue app parties, which covers the modules parties, owned_organisations, and crm |
src/cluster_erp/modules/app_store/catalog.json (entry parties) |
| Desktop presence | Generic widgets only; no folder in src/cluster_erp/desktop/modules/ |
folder |
What the module does¶
- Keeps one party card per person or organisation, with a yearly business code, a type, a display name, tax fields, notes, and an active flag (
src/cluster_erp/modules/parties/parties/models.py:18-55). - Records the roles a party plays as three flags,
is_customer,is_supplier, andis_employee, which other modules keep in step over the EventBus (src/cluster_erp/modules/parties/module.py:168-195). - Stores structured addresses whose levels come from country reference data; see addresses.
- Stores communication channels such as e-mail and phone; see communication channels.
- Stores the languages a person speaks; see person languages.
- Seeds the ISO country list and the default channel types at start-up (
src/cluster_erp/modules/parties/module.py:37-74); see settings and reference data. - Holds a profile photo per party in the shared attachment store (
src/cluster_erp/modules/parties/parties/routes.py:153-249).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
parties |
Party card, individual and organisation details, photo | Party, Individual, OrganisationInfo |
/api/v1/parties (module root) |
src/cluster_erp/modules/parties/parties/routes.py:28 |
addresses |
Structured addresses of a party | Address, AddressComponent |
/api/v1/parties/{party_id}/addresses, /api/v1/parties/addresses/... |
src/cluster_erp/modules/parties/addresses/routes.py:21 |
communication_channels |
Channel types and a party's channels | ChannelType, CommunicationChannel |
/api/v1/parties/{party_id}/channels, /api/v1/parties/settings/channel-types/ |
src/cluster_erp/modules/parties/communication_channels/routes.py:27 |
person_languages |
Languages a party speaks | PersonLanguage |
/api/v1/parties/{party_id}/languages |
src/cluster_erp/modules/parties/person_languages/routes.py:19 |
settings |
Countries, address levels, address-level values | Country, AddressLevel, AddressLevelValue |
/api/v1/parties/settings |
src/cluster_erp/modules/parties/settings/routes.py:30 |
All five routers are included under one module router, settings first, and the whole router depends on the parties app entitlement (src/cluster_erp/modules/parties/module.py:96-103). Two more routers with the same prefix live in the server layer: guided creation (POST /api/v1/parties/composite) and role enabling (POST /api/v1/parties/{party_id}/roles/...); both are described on party unification.
Data model¶
The diagram shows the eleven tables of the module and their foreign keys.
erDiagram
Party ||--o| Individual : "party_id"
Party ||--o| OrganisationInfo : "party_id"
Party ||--o{ Address : "party_id"
Party ||--o{ CommunicationChannel : "party_id"
Party ||--o{ PersonLanguage : "party_id"
Address ||--o{ AddressComponent : "address_id"
Country ||--o{ Address : "country_id"
Country ||--o{ AddressLevel : "country_id"
AddressLevel ||--o{ AddressLevelValue : "level_id"
AddressLevelValue ||--o{ AddressComponent : "level_value_id"
ChannelType ||--o{ CommunicationChannel : "channel_type_id"In prose: a Party has at most one live Individual row and at most one live OrganisationInfo row, and any number of addresses, channels, and spoken languages. An Address belongs to a Country and is made of AddressComponent rows, each pointing at an AddressLevel and an AddressLevelValue of that country. A CommunicationChannel has a ChannelType.
| Entity | Table | Key fields | Notes | Source |
|---|---|---|---|---|
Party |
parties_parties |
code, party_type, display_name, tax_id, tax_office, is_customer, is_supplier, is_employee, photo_attachment_id, extra |
code unique; extra holds tenant-defined fields |
src/cluster_erp/modules/parties/parties/models.py:18-55 |
Individual |
parties_individuals |
party_id, first_name, last_name, middle_name, title, date_of_birth, gender |
one live row per party | src/cluster_erp/modules/parties/parties/models.py:58-82 |
OrganisationInfo |
parties_organisation_info |
party_id, legal_name, trade_name, registration_number, industry |
one live row per party | src/cluster_erp/modules/parties/parties/models.py:85-105 |
Address |
parties_addresses |
code, party_id, country_id, address_type, full_text, is_primary |
see addresses | src/cluster_erp/modules/parties/addresses/models.py:16-41 |
AddressComponent |
parties_address_components |
address_id, address_level_id, level_value_id |
src/cluster_erp/modules/parties/addresses/models.py:44-65 |
|
ChannelType |
parties_channel_types |
code, name, translations, icon, validation_pattern |
code and name unique among live rows | src/cluster_erp/modules/parties/communication_channels/models.py:15-38 |
CommunicationChannel |
parties_communication_channels |
code, party_id, channel_type_id, value, is_primary |
src/cluster_erp/modules/parties/communication_channels/models.py:41-59 |
|
PersonLanguage |
parties_person_languages |
party_id, language, proficiency_level |
src/cluster_erp/modules/parties/person_languages/models.py:15-29 |
|
Country |
parties_countries |
code, code_alpha3, numeric_code, name, translations, phone_code |
three codes unique among live rows | src/cluster_erp/modules/parties/settings/models.py:12-49 |
AddressLevel |
parties_address_levels |
code, country_id, level_order, name, is_required |
level_order unique per country among live rows |
src/cluster_erp/modules/parties/settings/models.py:52-80 |
AddressLevelValue |
parties_address_level_values |
code, level_id, parent_id, value, postal_code |
unique per level, value, and parent among live rows | src/cluster_erp/modules/parties/settings/models.py:83-112 |
Every entity derives from BaseModel, so rows carry a ULID primary key, audit columns, and soft-delete columns; see base model and ids and soft delete and deactivation.
Screens and metadata¶
get_metadata declares one sidebar group Parties (Turkish Taraflar) with four sections (src/cluster_erp/modules/parties/module.py:557-641):
- Parties, a tabbed list with two entity tabs: the
partylist and theorganisationlist ofowned_organisations(Turkish Kuruluşlar) (src/cluster_erp/modules/parties/module.py:570-599). - Addresses and Communication Channels, two lists that use the
list_allendpoints and are hidden from the tab bar (src/cluster_erp/modules/parties/module.py:603-626). - Settings, with the tabs
country,address_level, andchannel_type(src/cluster_erp/modules/parties/module.py:627-637).
The party list has the columns code, name, type, tax id, active, and created, and two filters, Type and Role (src/cluster_erp/modules/parties/parties/metadata.py:440-508). The party detail has the tabs General, Addresses, Communication Channels, Spoken Languages, and Working Calendar; the last one is a custom widget of the calendars module (src/cluster_erp/modules/parties/parties/metadata.py:509-544). Role sections appear on the same card through five FacetMeta entries (src/cluster_erp/modules/parties/parties/metadata.py:550-763). The party card also declares guided creation, documents on create, tenant-field buckets, the quick actions call, message, and mail, and a photo (src/cluster_erp/modules/parties/parties/metadata.py:313-378). The web app renders these generically; see generic detail and generic form.
Status and actions¶
A party has no status field; it has an is_active flag (src/cluster_erp/modules/parties/parties/models.py:32). The module declares six ActionMeta entries (counted with grep "ActionMeta(" in the package).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
enable_customer_role |
Enable Customer Role | is_customer is false |
POST /{id}/roles/customer |
customer.create |
src/cluster_erp/modules/parties/parties/metadata.py:394-408, src/cluster_erp/server/routes/party_roles.py:58-77 |
enable_supplier_role |
Enable Supplier Role | is_supplier is false |
POST /{id}/roles/supplier |
supplier.create |
src/cluster_erp/modules/parties/parties/metadata.py:409-423, src/cluster_erp/server/routes/party_roles.py:80-99 |
enable_employee_role |
Enable Employee Role | is_employee is false |
POST /{id}/roles/employee |
employee.create |
src/cluster_erp/modules/parties/parties/metadata.py:424-438, src/cluster_erp/server/routes/party_roles.py:102-123 |
update_from_internet (country) |
Update from Internet | none (global) | POST /settings/countries/update-from-internet |
country.create |
src/cluster_erp/modules/parties/settings/metadata.py:34-43, src/cluster_erp/modules/parties/settings/routes.py:55-62 |
seed_address_levels (country) |
Seed Address Levels | none (single record) | POST /settings/countries/{id}/seed-address-levels |
country.seed |
src/cluster_erp/modules/parties/settings/metadata.py:44-53, src/cluster_erp/modules/parties/settings/routes.py:133-146 |
seed_defaults (channel type) |
Seed Defaults | none (global) | POST /settings/channel-types/seed-defaults |
channel_type.seed |
src/cluster_erp/modules/parties/settings/metadata.py:383-392, src/cluster_erp/modules/parties/communication_channels/routes.py:35-41 |
Business rules¶
- A new party gets its
codefrom the yearly sequenceparty(src/cluster_erp/modules/parties/parties/services.py:116); see base model and ids. - The list search matches the display name, the code, and searchable tenant fields (
src/cluster_erp/modules/parties/parties/services.py:64-73). - The role filter reads the three role flags (
src/cluster_erp/modules/parties/parties/services.py:55-61). - Create does not copy
is_customer,is_supplier, oris_employeefrom the payload into the row (src/cluster_erp/modules/parties/parties/services.py:117-126). The flags are set by the role routes, byPartyRoleSet, and by a start-up reconciliation (src/cluster_erp/server/app.py:1002-1055). - An update raises the row
versiononly when a field changed, and emitsPartyUpdatedwith the new display name and tax id (src/cluster_erp/modules/parties/parties/services.py:197-212). - Deleting a party sets
deleted_atand emitsPartyDeleted; bulk delete emits onePartyDeletedper id (src/cluster_erp/modules/parties/parties/services.py:216-233). - A photo must be JPEG, PNG, WebP, or GIF, not empty, and at most 5 MB (
src/cluster_erp/modules/parties/parties/routes.py:153-154,src/cluster_erp/modules/parties/parties/routes.py:196-203).
Events and queries¶
The module consumes 14 contracts and produces 3. Consumed means passed as the first argument of subscribe( or register_query_handler( in the package; produced means a contract class name followed by ( in the package. Producers and consumers across modules are listed on the contracts reference.
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | PartyCreated, PartyUpdated, PartyDeleted |
PartyService.create, .update, .delete, .bulk_delete |
src/cluster_erp/modules/parties/parties/services.py:156-163, src/cluster_erp/modules/parties/parties/services.py:201-212, src/cluster_erp/modules/parties/parties/services.py:222-232 |
| answers query | GetPartyById |
_handle_get_party |
src/cluster_erp/modules/parties/module.py:117, src/cluster_erp/modules/parties/module.py:359-402 |
| answers query | GetAddressById, GetPartyPrimaryAddress |
_handle_get_address, _handle_get_primary_address |
src/cluster_erp/modules/parties/module.py:118-121 |
| answers query | GetCountryByCode |
_handle_get_country_by_code |
src/cluster_erp/modules/parties/module.py:124 |
| answers query | SearchParties |
_handle_search_parties |
src/cluster_erp/modules/parties/module.py:125 |
| answers query | EnsurePartyForOrganisation |
_handle_ensure_party_for_org |
src/cluster_erp/modules/parties/module.py:126, src/cluster_erp/modules/parties/module.py:324-357 |
| answers query | CountPartyChannels |
_handle_count_party_channels |
src/cluster_erp/modules/parties/module.py:127 |
| answers query | GetPartyContactValues |
party_contact_values |
src/cluster_erp/modules/parties/module.py:154-157 |
| subscribes | ResolveEntityEmails |
_handle_resolve_emails |
src/cluster_erp/modules/parties/module.py:128, src/cluster_erp/modules/parties/module.py:489-536 |
| subscribes | CustomerCreated |
_handle_customer_created |
src/cluster_erp/modules/parties/module.py:129, src/cluster_erp/modules/parties/module.py:445-487 |
| subscribes | PartyRoleSet |
_handle_party_role_set |
src/cluster_erp/modules/parties/module.py:130 |
| subscribes | DescribeAnalyticsFacts |
describe_analytics_facts |
src/cluster_erp/modules/parties/module.py:134-139 |
| subscribes | CollectSubjectData, AnonymiseSubject |
collect_subject_data, anonymise_subject |
src/cluster_erp/modules/parties/module.py:159-166 |
Permissions¶
| Permission key | Used by | Source |
|---|---|---|
party.read, party.create, party.update, party.delete, party.bulk_delete |
party routes and photo routes | src/cluster_erp/modules/parties/parties/routes.py:46-237 |
address.read, .create, .update, .delete, .bulk_delete |
address routes | src/cluster_erp/modules/parties/addresses/routes.py:36-160 |
channel.read, .create, .update, .delete, .bulk_delete |
channel routes | src/cluster_erp/modules/parties/communication_channels/routes.py:118-228 |
channel_type.read, .create, .update, .delete, .bulk_delete, .seed |
channel-type routes | src/cluster_erp/modules/parties/communication_channels/routes.py:37-100 |
person_language.read, .create, .update, .delete, .bulk_delete |
language routes | src/cluster_erp/modules/parties/person_languages/routes.py:25-86 |
country.read, .create, .update, .delete, .bulk_delete, .seed |
country routes | src/cluster_erp/modules/parties/settings/routes.py:41-136 |
address_level.*, address_level_value.* |
address-level routes | src/cluster_erp/modules/parties/settings/routes.py:157-280 |
party.create; customer.create, supplier.create, employee.create |
guided creation; role enabling | src/cluster_erp/server/routes/party_composite.py:57, src/cluster_erp/server/routes/party_roles.py:62-106 |
How permission codes are seeded and checked is explained on roles and permissions.
Privacy, retention, and history¶
privacy_handlers.py answers the privacy module's two fan-out events for this module's tables (src/cluster_erp/modules/parties/privacy_handlers.py:1-10). collect_subject_data returns a copy of the party, its individual and organisation details, addresses, channels, and languages (src/cluster_erp/modules/parties/privacy_handlers.py:69-100). anonymise_subject overwrites identifying values with the request's marker, clears tax fields and notes, scrubs personal tenant fields, deactivates the rows with the request's reason, and reports the photo attachment so that the privacy module removes the file (src/cluster_erp/modules/parties/privacy_handlers.py:234-291). GetPartyContactValues returns the e-mail addresses and phone numbers of the party's live channels (src/cluster_erp/modules/parties/privacy_handlers.py:35-66). The overall flow is on privacy and data-subject requests. Changes are captured by change history.
Analytics facts¶
analytics_facts.py declares one fact set, party, over parties_parties, with the dimensions party type, the three role flags, active, and created date, and one measure, party_count (src/cluster_erp/modules/parties/analytics_facts.py:17-72). The module answers DescribeAnalyticsFacts with it (src/cluster_erp/modules/parties/analytics_facts.py:75-77).
Settings and reference data¶
Countries, address levels, address-level values, and channel types are reference data in the database. Countries come from the ISO 3166 list of the pycountry package and are seeded at start-up; existing rows are not overwritten (src/cluster_erp/modules/parties/settings/services.py:38-86). The details are on settings and reference data.
Tests¶
Found by file name in web-client/ and by a grep for modules.parties in tests/.
| Kind | File | Covers |
|---|---|---|
| pytest | tests/test_party_form_contract.py |
party form metadata, schemas, error catalogue, and module wiring (8 tests) |
| pytest | tests/test_tenant_fields.py, tests/test_tenant_field_catalog.py, tests/test_fresh_install_contract.py |
import party models and metadata as part of wider contracts |
| e2e | web-client/_e2e_parties.mjs |
the parties app: lists, CRUD, detail sections, organisation detail |
| e2e | web-client/_e2e_parties_supplier.mjs |
a supplier party created through the composite route and found by the role filter |
| e2e | web-client/_e2e_party_form_fixes.mjs |
the party form against the live server |
| e2e | web-client/_e2e_party_photo.mjs |
upload, change, and removal of a party photo |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| party | Party |
A person or an organisation the company deals with, kept as one card. | Taraf | src/cluster_erp/modules/parties/parties/models.py:18 |
| party type | party_type |
Whether a party is an individual or an organisation. |
Taraf türü | src/cluster_erp/modules/parties/parties/schemas.py:17-19 |
| role flag | is_customer, is_supplier, is_employee |
A boolean on the party that says it plays a role, kept in step with the role record. | Rol | src/cluster_erp/modules/parties/parties/models.py:33-35 |
Related pages¶
- Modules index
- Party unification
- Parties sub-module
- Addresses
- Communication channels
- Person languages
- Settings and reference data
- Owned organisations module
- API reference for this module
- Related material, historical, not verified: the in-tree note
src/cluster_erp/modules/parties/docs/how-to.md. It does not cover spoken languages, guided creation, or role enabling.
Verified against commit 92f8051 on 2026-11-13.