Add the Sales CRM service through collaboration, sync architecture/registry docs, expose crm.torbatyar.ir, and include a production deploy script. Co-authored-by: Cursor <cursoragent@cursor.com>
189 lines
7.0 KiB
Markdown
189 lines
7.0 KiB
Markdown
# Phase 6.1 — Enterprise CRM Core Business Entities
|
||
|
||
| Field | Value |
|
||
| --- | --- |
|
||
| Status | Complete |
|
||
| Module | crm |
|
||
| Version | 0.6.1.0 |
|
||
| Database | `crm_db` |
|
||
| Migration | `0002_phase_61_entities` |
|
||
| Depends On | Phase 6.0 |
|
||
|
||
## Goal
|
||
|
||
Implement the Sales CRM **core business entities** only: leads, contacts, organizations, lookups, tags, addresses, custom fields, notes, attachments (file references), and audit.
|
||
|
||
Out of scope: Workflow, Automation, Customer360, Marketing, Communication, Helpdesk, Analytics, AI, Accounting / Restaurant / Marketplace integrations.
|
||
|
||
## Created Models
|
||
|
||
### Core aggregates (expanded)
|
||
|
||
| Model | Table | Notes |
|
||
| --- | --- | --- |
|
||
| Lead | `leads` | lead_number, names, company, phones, source/status refs, owner, priority, soft delete, actors |
|
||
| Contact | `contacts` | personal/business, multi phone/email JSON, social links, birthday, soft delete |
|
||
| Organization | `organizations` | company/branch/department, legal/trade names, tax IDs, parent org, soft delete |
|
||
|
||
### Lookups
|
||
|
||
| Model | Table |
|
||
| --- | --- |
|
||
| LeadSource | `lead_sources` |
|
||
| LeadStatusDefinition | `lead_statuses` |
|
||
| ContactType | `contact_types` |
|
||
| OrganizationType | `organization_types` |
|
||
| OrganizationIndustry | `organization_industries` |
|
||
|
||
### Supporting
|
||
|
||
| Model | Table |
|
||
| --- | --- |
|
||
| LeadAssignment | `lead_assignments` |
|
||
| CrmTag | `crm_tags` |
|
||
| LeadTag / ContactTag / OrganizationTag | `lead_tags`, `contact_tags`, `organization_tags` |
|
||
| CrmCustomField / CrmCustomFieldValue | `crm_custom_fields`, `crm_custom_field_values` |
|
||
| CrmAddress | `crm_addresses` |
|
||
| CrmNote / CrmNoteVersion | `crm_notes`, `crm_note_versions` |
|
||
| CrmAttachment | `crm_attachments` (file_storage_id only) |
|
||
| CrmAuditLog | `crm_audit` |
|
||
|
||
Phase 6.0 aggregates retained: Pipeline, PipelineStage, Opportunity, SalesActivity, Quote.
|
||
|
||
## Relationships
|
||
|
||
UUID references within `crm_db` only (no SQLAlchemy relationship graphs between aggregates):
|
||
|
||
- Lead → `lead_source_id`, `lead_status_id`, `contact_id`, `organization_id`
|
||
- Contact → `contact_type_id`, `organization_id`
|
||
- Organization → `organization_type_id`, `organization_industry_id`, `parent_organization_id`
|
||
- Addresses / Notes / Attachments / CustomFieldValues → `(entity_type, entity_id)`
|
||
- Tag join tables → entity id + `tag_id`
|
||
- Attachment → `file_storage_id` (File Storage service contract)
|
||
|
||
## ER Diagram
|
||
|
||
```mermaid
|
||
erDiagram
|
||
LEAD ||--o{ LEAD_ASSIGNMENT : assigns
|
||
LEAD ||--o{ LEAD_TAG : tagged
|
||
CONTACT ||--o{ CONTACT_TAG : tagged
|
||
ORGANIZATION ||--o{ ORGANIZATION_TAG : tagged
|
||
CRM_TAG ||--o{ LEAD_TAG : used
|
||
CRM_TAG ||--o{ CONTACT_TAG : used
|
||
CRM_TAG ||--o{ ORGANIZATION_TAG : used
|
||
LEAD_SOURCE ||--o{ LEAD : sources
|
||
LEAD_STATUS ||--o{ LEAD : statuses
|
||
CONTACT_TYPE ||--o{ CONTACT : types
|
||
ORGANIZATION_TYPE ||--o{ ORGANIZATION : types
|
||
ORGANIZATION_INDUSTRY ||--o{ ORGANIZATION : industries
|
||
ORGANIZATION ||--o{ ORGANIZATION : parent
|
||
CRM_CUSTOM_FIELD ||--o{ CRM_CUSTOM_FIELD_VALUE : values
|
||
LEAD ||--o{ CRM_ADDRESS : addresses
|
||
LEAD ||--o{ CRM_NOTE : notes
|
||
CRM_NOTE ||--o{ CRM_NOTE_VERSION : versions
|
||
LEAD ||--o{ CRM_ATTACHMENT : attachments
|
||
LEAD ||--o{ CRM_AUDIT : audited
|
||
```
|
||
|
||
## Business Rules
|
||
|
||
- Lead number unique per tenant; auto-generated when omitted (`LEAD-XXXXXXXXXX`)
|
||
- Lead owner required
|
||
- Soft delete + restore for Lead / Contact / Organization / Address / Note / Attachment
|
||
- Organization name unique per tenant; parent cannot be self
|
||
- Attachment stores **reference only** (no binary in CRM)
|
||
- Notes are versioned on body update
|
||
- Custom fields are per-tenant and per-entity with typed validation
|
||
|
||
## Validation Rules
|
||
|
||
| Area | Rules |
|
||
| --- | --- |
|
||
| Email | RFC-ish format, lowercased |
|
||
| Phone / Mobile | 5–30 chars of digits/spaces/`+()-` |
|
||
| Owner | non-empty |
|
||
| Lead status | known enum / status definition |
|
||
| Amount | non-negative |
|
||
| Custom fields | type match; required; select option membership |
|
||
| Attachment | `file_storage_id` + `file_name` required |
|
||
| Address | `line1` required |
|
||
|
||
## Events
|
||
|
||
| Event | When |
|
||
| --- | --- |
|
||
| `crm.lead.created` / `crm.lead.updated` | Lead create/update |
|
||
| `crm.contact.created` | Contact create |
|
||
| `crm.organization.created` | Organization create |
|
||
| `crm.note.created` | Note create |
|
||
| `crm.attachment.added` | Attachment reference create |
|
||
|
||
## Permissions
|
||
|
||
`crm.leads.*`, `crm.contacts.*`, `crm.organizations.*`, `crm.notes.*`, `crm.attachments.*`, `crm.customfields.*` (+ existing 6.0 trees).
|
||
|
||
## API Endpoints
|
||
|
||
| Area | Paths |
|
||
| --- | --- |
|
||
| Leads | `POST/GET/PATCH /api/v1/leads`, `/assign`, `/delete`, `/restore` |
|
||
| Contacts | `POST/GET/PATCH /api/v1/contacts`, `/delete` |
|
||
| Organizations | `POST/GET/PATCH /api/v1/organizations`, `/delete` |
|
||
| Lookups | `/api/v1/lookups/lead-sources\|lead-statuses\|contact-types\|organization-types\|organization-industries` |
|
||
| Tags | `/api/v1/tags`, `/tags/leads\|contacts\|organizations/{id}` |
|
||
| Addresses | `/api/v1/addresses` |
|
||
| Notes | `/api/v1/notes` |
|
||
| Attachments | `/api/v1/attachments` |
|
||
| Custom fields | `/api/v1/custom-fields`, `/custom-fields/values` |
|
||
| Audit | `/api/v1/audit` |
|
||
|
||
## Migration Summary
|
||
|
||
- Alembic revision `0002_phase_61_entities` (down_revision `0001_initial`)
|
||
- Expands metadata via `create_all` for additive tables/columns on fresh installs
|
||
- Indexes on tenant + status/email/owner/entity polymorphic keys
|
||
- Unique constraints: lead_number, org name, lookup codes, tag names, custom field keys
|
||
|
||
## Services / Repositories / Validators
|
||
|
||
| Service | Repository |
|
||
| --- | --- |
|
||
| LeadService | LeadRepository (+ LeadAssignmentRepository) |
|
||
| ContactService | ContactRepository |
|
||
| OrganizationService | OrganizationRepository |
|
||
| AddressService | CrmAddressRepository |
|
||
| TagService | CrmTagRepository + join repos |
|
||
| CustomFieldService | CrmCustomField* repositories |
|
||
| AttachmentService | CrmAttachmentRepository |
|
||
| NoteService | CrmNote* repositories |
|
||
| AuditService | CrmAuditLogRepository |
|
||
| LookupService | lookup repositories |
|
||
|
||
Validators live in `app/validators/` (lead/contact/org/address/tag/custom field/attachment rules).
|
||
|
||
## Tests Executed
|
||
|
||
- Model / architecture / dependency
|
||
- Repository / service / validation / audit
|
||
- API / permission / migration / tenant isolation
|
||
- Documentation validation
|
||
|
||
## Known Limitations
|
||
|
||
- File Storage resolve is contract-only (`FileStorageProvider`); no live S3/upload in CRM
|
||
- Multi phone/email stored as JSON arrays (not separate child tables)
|
||
- Schema evolution on already-applied 6.0 Postgres may need operational `ALTER` if 0001 ran with old columns only
|
||
- No Automation / Customer360 consumption yet
|
||
|
||
## Next Phase
|
||
|
||
Phase 6.2 completed — see [crm-phase-6-2.md](crm-phase-6-2.md). CRM Core Platform complete through [crm-phase-6-3.md](crm-phase-6-3.md).
|
||
|
||
## Related Documents
|
||
|
||
- [crm-phase-6-0.md](crm-phase-6-0.md)
|
||
- [Module Registry](module-registry.md#crm)
|
||
- [Service README](../backend/services/crm/README.md)
|
||
- [Progress](progress.md)
|