The crm module in src/cluster_erp/modules/crm/ handles the customer side before a quote. It keeps the customer facet of a party, maps customer part numbers to the company's products with revisions, records customer requests for quotation (RFQs) with their line items and materials, and keeps an inbox of RFQ e-mails. Quotes that answer RFQs live in the quotations module. This page is the overview; the sub-topics have their own pages.
At a glance¶
| Item | Value | Source |
|---|---|---|
| Package | src/cluster_erp/modules/crm/ |
folder |
| Module class | Module |
src/cluster_erp/modules/crm/module.py:18 |
name / version |
crm / 0.1.0 |
src/cluster_erp/modules/crm/module.py:20-25 |
| API prefix | /api/v1/crm (ModuleBase.api_prefix) |
src/cluster_erp/core/module_base.py:37-38 |
| Sub-modules | 7: customers, customer_parts, customer_part_revisions, rfqs, rfq_line_items, rfq_line_materials, inbox |
folder listing |
| Tables | 8: crm_customers, crm_customer_parts, crm_customer_part_revisions, crm_rfqs, crm_rfq_packages, crm_rfq_line_items, crm_rfq_line_materials, crm_rfq_inbox_messages |
__tablename__ in the seven models.py files |
| Entities in metadata | 8 keys in entities |
src/cluster_erp/modules/crm/module.py:393-402 |
| Routes | 57 route decorators in the package | grep for @...router.get/post/put/patch/delete |
| Actions | 12 ActionMeta entries |
grep "ActionMeta(" in the package |
| Migrations | 16 files | files in migrations/versions/ whose text contains one of the eight table names |
| Web app presence | Catalogue apps customer-parts and rfqs, both list screens in the folder crm |
src/cluster_erp/modules/app_store/catalog.json:1002-1018, src/cluster_erp/modules/app_store/catalog.json:1052-1068 |
| Desktop presence | generic widgets only | src/cluster_erp/desktop/modules/ has no crm folder |
What the module does¶
- Keeps a customer facet per party, with identity fields derived from the party (
src/cluster_erp/modules/crm/customers/services.py:75-146). - Maps a customer's part number to one of the company's products and keeps revisions of that part (
src/cluster_erp/modules/crm/customer_parts/models.py:13-52,src/cluster_erp/modules/crm/customer_part_revisions/models.py:13-43). - Records customer RFQs with a status machine, line items, materials, and a document package (
src/cluster_erp/modules/crm/rfqs/services.py:64-80). - Keeps inbound RFQ e-mails and matches them to RFQs (
src/cluster_erp/modules/crm/inbox/services.py:67-131).
Sub-modules¶
| Sub-module | Purpose | Main entities | Router | Source |
|---|---|---|---|---|
customers |
Customer facet of a party | Customer |
/customers |
src/cluster_erp/modules/crm/customers/routes.py:28-37 |
customer_parts |
Customer part numbers | CustomerPart |
/customer-parts |
src/cluster_erp/modules/crm/customer_parts/routes.py:27-47 |
customer_part_revisions |
Revisions of a customer part | CustomerPartRevision |
/customer-parts/{id}/revisions, /customer-part-revisions |
src/cluster_erp/modules/crm/customer_part_revisions/routes.py:21-24 |
rfqs |
Customer RFQs and their document package | Rfq, RfqPackage |
/rfqs, /rfq-packages |
src/cluster_erp/modules/crm/rfqs/routes.py:36-51 |
rfq_line_items |
RFQ lines | RfqLineItem |
/rfqs/{rfq_id}/line-items, /line-items |
src/cluster_erp/modules/crm/rfq_line_items/routes.py:20-23 |
rfq_line_materials |
Materials per RFQ line | RfqLineMaterial |
/line-items/{id}/materials, /line-materials |
src/cluster_erp/modules/crm/rfq_line_materials/routes.py:26-29 |
inbox |
RFQ e-mails | RfqInboxMessage |
/inbox |
src/cluster_erp/modules/crm/inbox/routes.py:22-25 |
All routers are mounted under the module prefix with require_app("crm") (src/cluster_erp/modules/crm/module.py:66-74).
Data model¶
Fields are listed on customers and customer parts, RFQs, and inbox. CustomerPart.part_id names a product only; it carries no model or version (src/cluster_erp/modules/crm/customer_parts/models.py:35). The module stores no money column.
Screens and metadata¶
get_metadata declares the sidebar Customer Relations (Turkish Müşteri İlişkileri) with two groups: Customer Parts with Customer Part Numbers, and RFQ Management with RFQs and Inbox (src/cluster_erp/modules/crm/module.py:331-390). The customer facet itself is shown on the party card; see party unification.
Status and actions¶
RFQ statuses and actions are on RFQs; revision and inbox actions on customers and customer parts and inbox.
Business rules¶
Each sub-topic page lists its rules. Business keys come from the yearly sequences customer, request_for_quotation, and rfq_package (src/cluster_erp/modules/crm/customers/services.py:108, src/cluster_erp/modules/crm/rfqs/services.py:173, src/cluster_erp/modules/crm/rfqs/services.py:583).
Events and queries¶
The module consumes 9 contracts and produces 14, counted as on the parties module page. The full list is on the contracts reference.
| Direction | Contract class | Handler or emitter | Source |
|---|---|---|---|
| emits | CustomerCreated, CustomerUpdated, PartyRoleSet |
customer service | src/cluster_erp/modules/crm/customers/services.py:118-207 |
| emits | RfqReceived, RfqUpdated, RfqAssigned, RfqDeclined, RfqExpired, RfqWon, RfqLost, RfqPackageCompleted |
RFQ service | src/cluster_erp/modules/crm/rfqs/services.py:147-627 |
| queries | GetPartyById, CountPartyChannels, GetBomMaterials |
services | src/cluster_erp/modules/crm/rfqs/services.py:393-417, src/cluster_erp/modules/crm/rfq_line_materials/services.py:116 |
| answers query | GetCustomerById, GetRfqById, GetRfqIdsByCustomer, GetCustomerPartById, GetCustomerPartsByCustomer |
handlers in module.py |
src/cluster_erp/modules/crm/module.py:88-95 |
| subscribes | PartyDeleted, PartyUpdated |
customer facet sync | src/cluster_erp/modules/crm/module.py:96-99, src/cluster_erp/modules/crm/module.py:115-138, src/cluster_erp/modules/crm/module.py:271-297 |
| subscribes | CollectSubjectData, AnonymiseSubject |
privacy_handlers.py |
src/cluster_erp/modules/crm/module.py:110-113 |
Permissions¶
The module checks the families customer.*, customer_part.*, customer_part_revision.*, rfq.*, rfq_package.*, rfq_line_item.*, rfq_line_material.*, and inbox_message.*. Each sub-topic page lists the keys per route.
Privacy, retention, and history¶
privacy_handlers.py answers data-subject requests for customer facets of individual parties: CollectSubjectData returns the rows, and AnonymiseSubject overwrites the identifying values and deactivates the rows (src/cluster_erp/modules/crm/privacy_handlers.py:1-12). Inbox messages hold e-mail addresses and bodies; see inbox. Deletes set deleted_at (src/cluster_erp/modules/crm/customers/services.py:201). See privacy and data-subject requests.
Analytics facts¶
This module has no analytics_facts.py.
Settings and reference data¶
The module has no settings section. The RFQ priority threshold is the module constant PRIORITY_THRESHOLD_HOURS, 48 (src/cluster_erp/modules/crm/rfqs/services.py:82).
Tests¶
Found by a grep for modules.crm in tests/ and for the path /crm/ in web-client/_e2e_*.mjs.
| Kind | File | Covers |
|---|---|---|
| pytest | tests/test_party_form_contract.py |
the customer role on the composite party form |
| pytest | tests/test_tenant_field_catalog.py |
tenant fields on CRM entities |
| e2e | web-client/_e2e_status_actions.mjs |
status changes through actions |
| e2e | web-client/_e2e_order_shipment_chain.mjs, web-client/_e2e_quote_line_bom.mjs |
RFQ to quote to order |
| e2e | web-client/_e2e_party_form_fixes.mjs, web-client/_e2e_fk_labels.mjs |
customer facet and labels |
Discrepancies¶
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| customer facet | Customer |
The customer role record of a party, with commercial defaults. | Müşteri rolü | src/cluster_erp/modules/crm/customers/models.py:13 |
| customer RFQ | Rfq |
A customer's request for quotation, before a quote exists. | Teklif talebi | src/cluster_erp/modules/crm/rfqs/models.py:14 |
Related pages¶
- Modules index
- Customers and customer parts
- RFQs
- Inbox
- Quotations module
- Party unification
- API reference for this module
Verified against commit 92f8051 on 2026-11-13.