task-c.1
This commit is contained in:
78
docs/adr/0015-customer-ownership-contact-sharing.md
Normal file
78
docs/adr/0015-customer-ownership-contact-sharing.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# ADR 0015: Customer Ownership and Contact Sharing
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The CRM customer domain already supported customer masters, contacts, leads/enquiries, and quotations, but it still lacked two production behaviors:
|
||||
|
||||
- a first-class primary owner for each customer
|
||||
- a persistent sharing model for customer contacts
|
||||
|
||||
That gap caused two operational problems:
|
||||
|
||||
- Marketing could not reliably suggest the right sales owner when creating a lead.
|
||||
- Contact visibility depended on creator/admin fallbacks instead of explicit business governance.
|
||||
|
||||
## Decision
|
||||
|
||||
We introduce two persistent governance models.
|
||||
|
||||
### 1. Customer owner
|
||||
|
||||
`crm_customers` now stores:
|
||||
|
||||
- `owner_user_id`
|
||||
- `owner_assigned_at`
|
||||
- `owner_assigned_by`
|
||||
|
||||
Ownership changes are recorded in `crm_customer_owner_history`.
|
||||
|
||||
Rules:
|
||||
|
||||
- one primary owner at a time
|
||||
- owner is organization-scoped
|
||||
- owner changes are auditable
|
||||
- customer owner gains visibility to the customer and related CRM work, subject to CRM scope rules
|
||||
|
||||
### 2. Contact sharing
|
||||
|
||||
`crm_contact_shares` becomes the production source of truth for persistent contact sharing.
|
||||
|
||||
Rules:
|
||||
|
||||
- creator keeps access
|
||||
- explicitly shared users gain access
|
||||
- customer owner gains access
|
||||
- CRM admin and broader organization/team scopes keep access
|
||||
- removing a share revokes access by deactivating the share row
|
||||
|
||||
### 3. Lead assignment suggestion
|
||||
|
||||
Lead/enquiry creation now accepts an optional assignee suggestion.
|
||||
|
||||
When a selected customer has an owner:
|
||||
|
||||
- the form pre-selects that owner as the suggested sales owner
|
||||
- users may override before submit
|
||||
- the create route records whether the suggestion was used or overridden
|
||||
|
||||
## Consequences
|
||||
|
||||
Positive:
|
||||
|
||||
- customer responsibility is now explicit and historical
|
||||
- contact access is governed by durable business state instead of demo-only behavior
|
||||
- lead routing is more accurate at creation time
|
||||
|
||||
Tradeoffs:
|
||||
|
||||
- visibility logic across customer/contact/enquiry flows becomes more stateful
|
||||
- team-scope remains approximate until a first-class team graph exists
|
||||
|
||||
## Notes
|
||||
|
||||
- This ADR does not introduce multiple primary owners, temporary shares, or territory management.
|
||||
- Customer owner does not imply approval authority.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Task C.1: Customer Ownership and Contact Sharing Governance
|
||||
|
||||
## Summary
|
||||
|
||||
Task C.1 completes the missing customer-governance layer by adding:
|
||||
|
||||
- persistent customer owner fields and owner history
|
||||
- persistent contact sharing
|
||||
- owner/share-aware customer and contact visibility
|
||||
- lead owner suggestion during lead/enquiry creation
|
||||
|
||||
## Delivered
|
||||
|
||||
- Added `ownerUserId`, `ownerAssignedAt`, and `ownerAssignedBy` to `crm_customers`
|
||||
- Added `crm_customer_owner_history`
|
||||
- Added `crm_contact_shares`
|
||||
- Added customer owner management route:
|
||||
- `PATCH /api/crm/customers/[id]/owner`
|
||||
- `DELETE /api/crm/customers/[id]/owner`
|
||||
- Added contact sharing routes:
|
||||
- `GET /api/crm/customers/[id]/contacts/[contactId]/shares`
|
||||
- `POST /api/crm/customers/[id]/contacts/[contactId]/shares`
|
||||
- `DELETE /api/crm/customers/[id]/contacts/[contactId]/shares/[shareId]`
|
||||
- Expanded customer list/detail contracts with owner and share metadata
|
||||
- Added customer owner card with history on customer detail
|
||||
- Added contact sharing management UI on customer detail
|
||||
- Added owner columns and owner filter on customer list
|
||||
- Added new permissions:
|
||||
- `crm.customer.owner.read`
|
||||
- `crm.customer.owner.manage`
|
||||
- `crm.contact.share.read`
|
||||
- `crm.contact.share.create`
|
||||
- `crm.contact.share.delete`
|
||||
- `crm.contact.share.manage`
|
||||
- Added lead/enquiry assignee suggestion from selected customer owner
|
||||
- Added audit events for:
|
||||
- `assign_owner`
|
||||
- `change_owner`
|
||||
- `clear_owner`
|
||||
- `share`
|
||||
- `unshare`
|
||||
- `lead_owner_suggestion_used`
|
||||
- `lead_owner_suggestion_overridden`
|
||||
|
||||
## Verification
|
||||
|
||||
- `npx tsc --noEmit`
|
||||
|
||||
## Remaining limitation
|
||||
|
||||
- Team-scope visibility is still approximate because the production model still lacks an explicit CRM team hierarchy.
|
||||
@@ -434,9 +434,8 @@ Future:
|
||||
### Contact-sharing persistence gap
|
||||
|
||||
Current:
|
||||
Task L.3.1 hardens customer/contact visibility, but the production schema still has no persisted contact-sharing relation. Legacy shared-contact behavior exists only in demo/mock material.
|
||||
Task C.1 closes the contact-sharing persistence gap, but cross-feature regression coverage for owner/share visibility is still light and team-scope remains approximate.
|
||||
|
||||
Future:
|
||||
|
||||
- add a first-class contact-sharing table and audit trail if the business still needs shared-contact workflows
|
||||
- extend security verification from creator/customer/admin visibility to explicit share-based visibility once persistence exists
|
||||
- add deeper automated regression coverage for owner/share visibility across customer, lead, enquiry, and quotation flows
|
||||
- introduce a first-class CRM team hierarchy if the business still needs inherited team visibility
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
## High-Risk Findings
|
||||
|
||||
1. Team-scope semantics are still coarse because the current model does not yet carry a first-class subordinate/team graph.
|
||||
2. Production contact sharing is not yet backed by a persisted sharing table; current enforcement covers creator/customer/admin visibility only.
|
||||
2. Contact sharing is now persisted, but team-scope still remains approximate because there is no first-class CRM team hierarchy.
|
||||
3. Enquiry services already enforce meaningful scope, but several route files still pass legacy role-shaped context instead of a dedicated security context object.
|
||||
|
||||
## Follow-up Focus
|
||||
|
||||
- Normalize remaining enquiry routes onto the same security-context builder used by quotations and dashboard.
|
||||
- Add runtime seeded scenario tests once dedicated security fixtures are available.
|
||||
- Introduce a first-class team hierarchy and production contact-sharing model if the business requires them.
|
||||
- Introduce a first-class CRM team hierarchy if the business requires true team-based visibility.
|
||||
|
||||
@@ -72,7 +72,7 @@ Contact visibility is enforced through customer visibility plus contact ownershi
|
||||
- if a user can access the parent customer, they can access its contact list within their role boundary
|
||||
- contact creators retain access to their own contacts
|
||||
- admin/organization scope retains access
|
||||
- production shared-contact persistence does not exist yet, so legacy demo sharing is not part of the live authorization boundary
|
||||
- production contact sharing is persisted through `crm_contact_shares`, so explicit share grants are part of the live authorization boundary
|
||||
|
||||
## Approval Boundary
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ That means the system does **not** currently know:
|
||||
|
||||
Because of that, `team` currently behaves as an approximate operational scope rather than a strict org-chart scope.
|
||||
|
||||
## Contact Sharing Limitation
|
||||
## Contact Sharing Status
|
||||
|
||||
The production schema currently has no persisted contact-sharing table.
|
||||
The production schema now includes persisted contact sharing.
|
||||
|
||||
Result:
|
||||
|
||||
- contact visibility is enforced through customer ownership plus contact creator visibility
|
||||
- mock/demo shared-contact behavior from legacy demo data is not part of the production boundary yet
|
||||
- contact visibility is enforced through customer ownership, contact creator visibility, and explicit contact-share grants
|
||||
- cross-owner contact access can now be granted and revoked without relying on demo/mock behavior
|
||||
|
||||
## Future Team Hierarchy Model
|
||||
|
||||
@@ -37,5 +37,5 @@ Recommended future direction:
|
||||
|
||||
1. add a first-class manager/team relationship model
|
||||
2. resolve `team` scope from that relationship instead of permissive approximation
|
||||
3. add explicit contact-sharing persistence if the business still requires cross-owner contact access
|
||||
3. keep expanding security fixtures around owner/share behavior once a first-class team graph exists
|
||||
4. expand runtime security fixtures around manager-team boundaries after real hierarchy data exists
|
||||
|
||||
Reference in New Issue
Block a user