@contractspec/example.crm-pipeline 1.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.turbo/turbo-build$colon$bundle.log +97 -0
  2. package/.turbo/turbo-build.log +98 -0
  3. package/CHANGELOG.md +246 -0
  4. package/LICENSE +21 -0
  5. package/README.md +139 -0
  6. package/dist/crm-pipeline.feature.d.ts +12 -0
  7. package/dist/crm-pipeline.feature.d.ts.map +1 -0
  8. package/dist/crm-pipeline.feature.js +159 -0
  9. package/dist/crm-pipeline.feature.js.map +1 -0
  10. package/dist/deal/deal.enum.d.ts +14 -0
  11. package/dist/deal/deal.enum.d.ts.map +1 -0
  12. package/dist/deal/deal.enum.js +25 -0
  13. package/dist/deal/deal.enum.js.map +1 -0
  14. package/dist/deal/deal.operation.d.ts +513 -0
  15. package/dist/deal/deal.operation.d.ts.map +1 -0
  16. package/dist/deal/deal.operation.js +270 -0
  17. package/dist/deal/deal.operation.js.map +1 -0
  18. package/dist/deal/deal.schema.d.ts +300 -0
  19. package/dist/deal/deal.schema.d.ts.map +1 -0
  20. package/dist/deal/deal.schema.js +286 -0
  21. package/dist/deal/deal.schema.js.map +1 -0
  22. package/dist/deal/index.d.ts +4 -0
  23. package/dist/deal/index.js +5 -0
  24. package/dist/docs/crm-pipeline.docblock.d.ts +1 -0
  25. package/dist/docs/crm-pipeline.docblock.js +100 -0
  26. package/dist/docs/crm-pipeline.docblock.js.map +1 -0
  27. package/dist/docs/index.d.ts +1 -0
  28. package/dist/docs/index.js +1 -0
  29. package/dist/entities/company.entity.d.ts +40 -0
  30. package/dist/entities/company.entity.d.ts.map +1 -0
  31. package/dist/entities/company.entity.js +63 -0
  32. package/dist/entities/company.entity.js.map +1 -0
  33. package/dist/entities/contact.entity.d.ts +44 -0
  34. package/dist/entities/contact.entity.d.ts.map +1 -0
  35. package/dist/entities/contact.entity.js +78 -0
  36. package/dist/entities/contact.entity.js.map +1 -0
  37. package/dist/entities/deal.entity.d.ts +73 -0
  38. package/dist/entities/deal.entity.d.ts.map +1 -0
  39. package/dist/entities/deal.entity.js +120 -0
  40. package/dist/entities/deal.entity.js.map +1 -0
  41. package/dist/entities/index.d.ts +15 -0
  42. package/dist/entities/index.d.ts.map +1 -0
  43. package/dist/entities/index.js +33 -0
  44. package/dist/entities/index.js.map +1 -0
  45. package/dist/entities/task.entity.d.ts +65 -0
  46. package/dist/entities/task.entity.d.ts.map +1 -0
  47. package/dist/entities/task.entity.js +129 -0
  48. package/dist/entities/task.entity.js.map +1 -0
  49. package/dist/events/contact.event.d.ts +29 -0
  50. package/dist/events/contact.event.d.ts.map +1 -0
  51. package/dist/events/contact.event.js +45 -0
  52. package/dist/events/contact.event.js.map +1 -0
  53. package/dist/events/deal.event.d.ts +111 -0
  54. package/dist/events/deal.event.d.ts.map +1 -0
  55. package/dist/events/deal.event.js +172 -0
  56. package/dist/events/deal.event.js.map +1 -0
  57. package/dist/events/index.d.ts +4 -0
  58. package/dist/events/index.js +5 -0
  59. package/dist/events/task.event.d.ts +29 -0
  60. package/dist/events/task.event.d.ts.map +1 -0
  61. package/dist/events/task.event.js +45 -0
  62. package/dist/events/task.event.js.map +1 -0
  63. package/dist/example.d.ts +37 -0
  64. package/dist/example.d.ts.map +1 -0
  65. package/dist/example.js +46 -0
  66. package/dist/example.js.map +1 -0
  67. package/dist/handlers/deal.handlers.d.ts +94 -0
  68. package/dist/handlers/deal.handlers.d.ts.map +1 -0
  69. package/dist/handlers/deal.handlers.js +120 -0
  70. package/dist/handlers/deal.handlers.js.map +1 -0
  71. package/dist/handlers/index.d.ts +3 -0
  72. package/dist/handlers/index.js +4 -0
  73. package/dist/handlers/mock-data.d.ts +49 -0
  74. package/dist/handlers/mock-data.d.ts.map +1 -0
  75. package/dist/handlers/mock-data.js +188 -0
  76. package/dist/handlers/mock-data.js.map +1 -0
  77. package/dist/index.d.ts +34 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +43 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/operations/index.d.ts +5 -0
  82. package/dist/operations/index.js +6 -0
  83. package/dist/presentations/dashboard.presentation.d.ts +15 -0
  84. package/dist/presentations/dashboard.presentation.d.ts.map +1 -0
  85. package/dist/presentations/dashboard.presentation.js +59 -0
  86. package/dist/presentations/dashboard.presentation.js.map +1 -0
  87. package/dist/presentations/index.d.ts +3 -0
  88. package/dist/presentations/index.js +4 -0
  89. package/dist/presentations/pipeline.presentation.d.ts +23 -0
  90. package/dist/presentations/pipeline.presentation.d.ts.map +1 -0
  91. package/dist/presentations/pipeline.presentation.js +119 -0
  92. package/dist/presentations/pipeline.presentation.js.map +1 -0
  93. package/example.ts +1 -0
  94. package/package.json +105 -0
  95. package/src/crm-pipeline.feature.ts +96 -0
  96. package/src/deal/deal.enum.ts +21 -0
  97. package/src/deal/deal.operation.ts +291 -0
  98. package/src/deal/deal.schema.ts +154 -0
  99. package/src/deal/index.ts +26 -0
  100. package/src/docs/crm-pipeline.docblock.ts +98 -0
  101. package/src/docs/index.ts +1 -0
  102. package/src/entities/company.entity.ts +77 -0
  103. package/src/entities/contact.entity.ts +93 -0
  104. package/src/entities/deal.entity.ts +160 -0
  105. package/src/entities/index.ts +45 -0
  106. package/src/entities/task.entity.ts +137 -0
  107. package/src/events/contact.event.ts +31 -0
  108. package/src/events/deal.event.ts +104 -0
  109. package/src/events/index.ts +3 -0
  110. package/src/events/task.event.ts +28 -0
  111. package/src/example.ts +30 -0
  112. package/src/handlers/deal.handlers.ts +253 -0
  113. package/src/handlers/index.ts +27 -0
  114. package/src/handlers/mock-data.ts +198 -0
  115. package/src/index.ts +31 -0
  116. package/src/operations/index.ts +20 -0
  117. package/src/presentations/dashboard.presentation.ts +60 -0
  118. package/src/presentations/index.ts +2 -0
  119. package/src/presentations/pipeline.presentation.ts +118 -0
  120. package/tsconfig.json +10 -0
  121. package/tsconfig.tsbuildinfo +1 -0
  122. package/tsdown.config.js +7 -0
@@ -0,0 +1,154 @@
1
+ import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
2
+ import { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
3
+
4
+ /**
5
+ * A deal in the CRM pipeline.
6
+ */
7
+ export const DealModel = defineSchemaModel({
8
+ name: 'Deal',
9
+ description: 'A deal in the CRM pipeline',
10
+ fields: {
11
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
12
+ name: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
13
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
14
+ currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
15
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
16
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
17
+ status: { type: DealStatusEnum, isOptional: false },
18
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
19
+ companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
20
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
21
+ expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true },
22
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
23
+ updatedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
24
+ },
25
+ });
26
+
27
+ /**
28
+ * Input for creating a deal.
29
+ */
30
+ export const CreateDealInputModel = defineSchemaModel({
31
+ name: 'CreateDealInput',
32
+ description: 'Input for creating a deal',
33
+ fields: {
34
+ name: { type: ScalarTypeEnum.NonEmptyString(), isOptional: false },
35
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
36
+ currency: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
37
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
38
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
39
+ contactId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
40
+ companyId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
41
+ expectedCloseDate: { type: ScalarTypeEnum.DateTime(), isOptional: true },
42
+ },
43
+ });
44
+
45
+ /**
46
+ * Input for moving a deal to another stage.
47
+ */
48
+ export const MoveDealInputModel = defineSchemaModel({
49
+ name: 'MoveDealInput',
50
+ description: 'Input for moving a deal to another stage',
51
+ fields: {
52
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
53
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
54
+ position: { type: ScalarTypeEnum.Int_unsecure(), isOptional: true },
55
+ },
56
+ });
57
+
58
+ /**
59
+ * Payload for deal moved event.
60
+ */
61
+ export const DealMovedPayloadModel = defineSchemaModel({
62
+ name: 'DealMovedPayload',
63
+ fields: {
64
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
65
+ fromStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
66
+ toStage: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
67
+ },
68
+ });
69
+
70
+ /**
71
+ * Input for marking a deal as won.
72
+ */
73
+ export const WinDealInputModel = defineSchemaModel({
74
+ name: 'WinDealInput',
75
+ description: 'Input for marking a deal as won',
76
+ fields: {
77
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
78
+ wonSource: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
79
+ notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
80
+ },
81
+ });
82
+
83
+ /**
84
+ * Payload for deal won event.
85
+ */
86
+ export const DealWonPayloadModel = defineSchemaModel({
87
+ name: 'DealWonPayload',
88
+ fields: {
89
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
90
+ value: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
91
+ },
92
+ });
93
+
94
+ /**
95
+ * Input for marking a deal as lost.
96
+ */
97
+ export const LoseDealInputModel = defineSchemaModel({
98
+ name: 'LoseDealInput',
99
+ description: 'Input for marking a deal as lost',
100
+ fields: {
101
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
102
+ lostReason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
103
+ notes: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
104
+ },
105
+ });
106
+
107
+ /**
108
+ * Payload for deal lost event.
109
+ */
110
+ export const DealLostPayloadModel = defineSchemaModel({
111
+ name: 'DealLostPayload',
112
+ fields: {
113
+ dealId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
114
+ reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
115
+ },
116
+ });
117
+
118
+ /**
119
+ * Input for listing deals.
120
+ */
121
+ export const ListDealsInputModel = defineSchemaModel({
122
+ name: 'ListDealsInput',
123
+ description: 'Input for listing deals',
124
+ fields: {
125
+ pipelineId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
126
+ stageId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
127
+ status: { type: DealStatusFilterEnum, isOptional: true },
128
+ ownerId: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
129
+ search: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
130
+ limit: {
131
+ type: ScalarTypeEnum.Int_unsecure(),
132
+ isOptional: true,
133
+ defaultValue: 20,
134
+ },
135
+ offset: {
136
+ type: ScalarTypeEnum.Int_unsecure(),
137
+ isOptional: true,
138
+ defaultValue: 0,
139
+ },
140
+ },
141
+ });
142
+
143
+ /**
144
+ * Output for listing deals.
145
+ */
146
+ export const ListDealsOutputModel = defineSchemaModel({
147
+ name: 'ListDealsOutput',
148
+ description: 'Output for listing deals',
149
+ fields: {
150
+ deals: { type: DealModel, isArray: true, isOptional: false },
151
+ total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
152
+ totalValue: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
153
+ },
154
+ });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Deal domain - Deal management in CRM pipeline.
3
+ */
4
+
5
+ export { DealStatusEnum, DealStatusFilterEnum } from './deal.enum';
6
+
7
+ export {
8
+ DealModel,
9
+ CreateDealInputModel,
10
+ MoveDealInputModel,
11
+ DealMovedPayloadModel,
12
+ WinDealInputModel,
13
+ DealWonPayloadModel,
14
+ LoseDealInputModel,
15
+ DealLostPayloadModel,
16
+ ListDealsInputModel,
17
+ ListDealsOutputModel,
18
+ } from './deal.schema';
19
+
20
+ export {
21
+ CreateDealContract,
22
+ MoveDealContract,
23
+ WinDealContract,
24
+ LoseDealContract,
25
+ ListDealsContract,
26
+ } from './deal.operation';
@@ -0,0 +1,98 @@
1
+ import type { DocBlock } from '@contractspec/lib.contracts/docs';
2
+ import { registerDocBlocks } from '@contractspec/lib.contracts/docs';
3
+
4
+ const crmPipelineDocBlocks: DocBlock[] = [
5
+ {
6
+ id: 'docs.examples.crm-pipeline.goal',
7
+ title: 'CRM Pipeline — Goal',
8
+ summary:
9
+ 'Deals, stages, contacts, companies, and tasks with auditable stage movement.',
10
+ kind: 'goal',
11
+ visibility: 'public',
12
+ route: '/docs/examples/crm-pipeline/goal',
13
+ tags: ['crm', 'goal'],
14
+ body: `## Why it matters
15
+ - Regenerable CRM flow for deals/stages without code drift.
16
+ - Ensures stage movement, tasks, and contacts stay aligned across surfaces.
17
+
18
+ ## Business/Product goal
19
+ - Give sales teams a governed pipeline with auditable moves and notifications.
20
+ - Allow experimentation (feature flags) on stage definitions and task flows.
21
+
22
+ ## Success criteria
23
+ - Stage/state changes emit events and remain declarative in spec.
24
+ - PII (contacts) is scoped/redacted in presentations.`,
25
+ },
26
+ {
27
+ id: 'docs.examples.crm-pipeline.usage',
28
+ title: 'CRM Pipeline — Usage',
29
+ summary: 'How to seed, extend, and regenerate the CRM pipeline.',
30
+ kind: 'usage',
31
+ visibility: 'public',
32
+ route: '/docs/examples/crm-pipeline/usage',
33
+ tags: ['crm', 'usage'],
34
+ body: `## Setup
35
+ 1) Seed (if available) or create pipeline stages, deals, contacts, companies, tasks.
36
+ 2) Configure Notifications for stage changes/tasks; set policy.pii for contact data.
37
+
38
+ ## Extend & regenerate
39
+ 1) Adjust stage schema/order, deal fields, task fields in the spec.
40
+ 2) Regenerate to sync UI/API/events; ensure kanban/action buttons update.
41
+ 3) Use Feature Flags to trial new stages or SLA rules.
42
+
43
+ ## Guardrails
44
+ - Emit events for stage moves and task completions; log to Audit Trail.
45
+ - Keep required fields enforced in contracts; avoid freeform state.
46
+ - Redact contact PII in markdown/JSON outputs.`,
47
+ },
48
+ {
49
+ id: 'docs.examples.crm-pipeline.reference',
50
+ title: 'CRM Pipeline — Reference',
51
+ summary:
52
+ 'Entities, contracts, events, and presentations for the CRM template.',
53
+ kind: 'reference',
54
+ visibility: 'public',
55
+ route: '/docs/examples/crm-pipeline',
56
+ tags: ['crm', 'reference'],
57
+ body: `## Entities
58
+ - Contact, Company, Deal, Pipeline, Stage, Task.
59
+
60
+ ## Contracts
61
+ - deal/create, stage/move, contact/company CRUD, task create/complete.
62
+
63
+ ## Events
64
+ - deal.created, stage.moved, task.completed, contact.updated.
65
+
66
+ ## Presentations
67
+ - Pipelines/kanban, deal detail, contact/company profiles, task lists.
68
+
69
+ ## Notes
70
+ - Stage definitions should be declarative; enforce via spec and regeneration.
71
+ - Use Notifications for deal/task updates; Audit Trail for state changes.`,
72
+ },
73
+ {
74
+ id: 'docs.examples.crm-pipeline.constraints',
75
+ title: 'CRM Pipeline — Constraints & Safety',
76
+ summary:
77
+ 'Internal guardrails for stages, PII, and regeneration semantics in the CRM template.',
78
+ kind: 'reference',
79
+ visibility: 'internal',
80
+ route: '/docs/examples/crm-pipeline/constraints',
81
+ tags: ['crm', 'constraints', 'internal'],
82
+ body: `## Constraints
83
+ - Stage definitions/order must remain declarative; no imperative overrides in code.
84
+ - Events to emit: deal.created, stage.moved, task.completed, contact.updated (minimum).
85
+ - Regeneration should not alter stage semantics without explicit spec change.
86
+
87
+ ## PII
88
+ - Mark contact/company PII (emails, phones) for redaction in presentations.
89
+ - Ensure MCP/web outputs avoid raw PII when not needed.
90
+
91
+ ## Verification
92
+ - Add fixtures for stage move rules and SLA/task changes.
93
+ - Ensure Audit/Notifications remain wired for stage and task events.
94
+ - Use Feature Flags for experimental stages/SLAs; default safe/off.`,
95
+ },
96
+ ];
97
+
98
+ registerDocBlocks(crmPipelineDocBlocks);
@@ -0,0 +1 @@
1
+ import './crm-pipeline.docblock';
@@ -0,0 +1,77 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Company size enum.
10
+ */
11
+ export const CompanySizeEnum = defineEntityEnum({
12
+ name: 'CompanySize',
13
+ values: ['STARTUP', 'SMALL', 'MEDIUM', 'LARGE', 'ENTERPRISE'] as const,
14
+ schema: 'crm',
15
+ description: 'Size category of a company.',
16
+ });
17
+
18
+ /**
19
+ * Company entity - organization/account.
20
+ */
21
+ export const CompanyEntity = defineEntity({
22
+ name: 'Company',
23
+ description: 'A company/organization in the CRM.',
24
+ schema: 'crm',
25
+ map: 'company',
26
+ fields: {
27
+ id: field.id({ description: 'Unique company ID' }),
28
+
29
+ // Basic info
30
+ name: field.string({ description: 'Company name' }),
31
+ domain: field.string({ isOptional: true, description: 'Website domain' }),
32
+ website: field.url({ isOptional: true }),
33
+
34
+ // Industry
35
+ industry: field.string({ isOptional: true }),
36
+
37
+ // Size
38
+ size: field.enum('CompanySize', { isOptional: true }),
39
+ employeeCount: field.int({ isOptional: true }),
40
+ annualRevenue: field.decimal({ isOptional: true }),
41
+
42
+ // Ownership
43
+ organizationId: field.foreignKey(),
44
+ ownerId: field.foreignKey({ description: 'Account owner' }),
45
+
46
+ // Contact info
47
+ phone: field.string({ isOptional: true }),
48
+ email: field.email({ isOptional: true }),
49
+
50
+ // Address
51
+ address: field.string({ isOptional: true }),
52
+ city: field.string({ isOptional: true }),
53
+ state: field.string({ isOptional: true }),
54
+ country: field.string({ isOptional: true }),
55
+ postalCode: field.string({ isOptional: true }),
56
+
57
+ // Social
58
+ linkedInUrl: field.url({ isOptional: true }),
59
+
60
+ // Notes
61
+ description: field.string({ isOptional: true }),
62
+ tags: field.string({ isArray: true }),
63
+
64
+ // Custom fields
65
+ customFields: field.json({ isOptional: true }),
66
+
67
+ // Timestamps
68
+ createdAt: field.createdAt(),
69
+ updatedAt: field.updatedAt(),
70
+
71
+ // Relations
72
+ contacts: field.hasMany('Contact'),
73
+ deals: field.hasMany('Deal'),
74
+ },
75
+ indexes: [index.on(['organizationId', 'ownerId']), index.on(['domain'])],
76
+ enums: [CompanySizeEnum],
77
+ });
@@ -0,0 +1,93 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Contact status enum.
10
+ */
11
+ export const ContactStatusEnum = defineEntityEnum({
12
+ name: 'ContactStatus',
13
+ values: ['LEAD', 'PROSPECT', 'CUSTOMER', 'CHURNED', 'ARCHIVED'] as const,
14
+ schema: 'crm',
15
+ description: 'Status of a contact in the sales funnel.',
16
+ });
17
+
18
+ /**
19
+ * Contact entity - individual person.
20
+ */
21
+ export const ContactEntity = defineEntity({
22
+ name: 'Contact',
23
+ description: 'An individual person in the CRM.',
24
+ schema: 'crm',
25
+ map: 'contact',
26
+ fields: {
27
+ id: field.id({ description: 'Unique contact ID' }),
28
+
29
+ // Basic info
30
+ firstName: field.string({ description: 'First name' }),
31
+ lastName: field.string({ description: 'Last name' }),
32
+ email: field.email({ isOptional: true, isUnique: true }),
33
+ phone: field.string({ isOptional: true }),
34
+
35
+ // Company
36
+ companyId: field.string({
37
+ isOptional: true,
38
+ description: 'Associated company',
39
+ }),
40
+ jobTitle: field.string({ isOptional: true }),
41
+
42
+ // Status
43
+ status: field.enum('ContactStatus', { default: 'LEAD' }),
44
+
45
+ // Ownership
46
+ organizationId: field.foreignKey(),
47
+ ownerId: field.foreignKey({
48
+ description: 'Sales rep who owns this contact',
49
+ }),
50
+
51
+ // Source
52
+ source: field.string({ isOptional: true, description: 'Lead source' }),
53
+
54
+ // Social
55
+ linkedInUrl: field.url({ isOptional: true }),
56
+ twitterHandle: field.string({ isOptional: true }),
57
+
58
+ // Address
59
+ address: field.string({ isOptional: true }),
60
+ city: field.string({ isOptional: true }),
61
+ state: field.string({ isOptional: true }),
62
+ country: field.string({ isOptional: true }),
63
+ postalCode: field.string({ isOptional: true }),
64
+
65
+ // Notes
66
+ notes: field.string({ isOptional: true }),
67
+ tags: field.string({ isArray: true }),
68
+
69
+ // Custom fields
70
+ customFields: field.json({ isOptional: true }),
71
+
72
+ // Engagement
73
+ lastContactedAt: field.dateTime({ isOptional: true }),
74
+ nextFollowUpAt: field.dateTime({ isOptional: true }),
75
+
76
+ // Timestamps
77
+ createdAt: field.createdAt(),
78
+ updatedAt: field.updatedAt(),
79
+
80
+ // Relations
81
+ company: field.belongsTo('Company', ['companyId'], ['id']),
82
+ deals: field.hasMany('Deal'),
83
+ tasks: field.hasMany('Task'),
84
+ activities: field.hasMany('Activity'),
85
+ },
86
+ indexes: [
87
+ index.on(['organizationId', 'status']),
88
+ index.on(['organizationId', 'ownerId']),
89
+ index.on(['organizationId', 'companyId']),
90
+ index.on(['email']),
91
+ ],
92
+ enums: [ContactStatusEnum],
93
+ });
@@ -0,0 +1,160 @@
1
+ import {
2
+ defineEntity,
3
+ defineEntityEnum,
4
+ field,
5
+ index,
6
+ } from '@contractspec/lib.schema';
7
+
8
+ /**
9
+ * Deal status enum.
10
+ */
11
+ export const DealStatusEnum = defineEntityEnum({
12
+ name: 'DealStatus',
13
+ values: ['OPEN', 'WON', 'LOST', 'STALE'] as const,
14
+ schema: 'crm',
15
+ description: 'Status of a deal.',
16
+ });
17
+
18
+ /**
19
+ * Pipeline entity - sales pipeline definition.
20
+ */
21
+ export const PipelineEntity = defineEntity({
22
+ name: 'Pipeline',
23
+ description: 'A sales pipeline with stages.',
24
+ schema: 'crm',
25
+ map: 'pipeline',
26
+ fields: {
27
+ id: field.id(),
28
+ name: field.string({ description: 'Pipeline name' }),
29
+ description: field.string({ isOptional: true }),
30
+
31
+ // Ownership
32
+ organizationId: field.foreignKey(),
33
+
34
+ // Settings
35
+ isDefault: field.boolean({ default: false }),
36
+
37
+ // Timestamps
38
+ createdAt: field.createdAt(),
39
+ updatedAt: field.updatedAt(),
40
+
41
+ // Relations
42
+ stages: field.hasMany('Stage'),
43
+ deals: field.hasMany('Deal'),
44
+ },
45
+ });
46
+
47
+ /**
48
+ * Stage entity - pipeline stage.
49
+ */
50
+ export const StageEntity = defineEntity({
51
+ name: 'Stage',
52
+ description: 'A stage within a sales pipeline.',
53
+ schema: 'crm',
54
+ map: 'stage',
55
+ fields: {
56
+ id: field.id(),
57
+ name: field.string({ description: 'Stage name' }),
58
+ pipelineId: field.foreignKey(),
59
+
60
+ // Position
61
+ position: field.int({ description: 'Order in pipeline' }),
62
+
63
+ // Probability
64
+ probability: field.int({
65
+ default: 0,
66
+ description: 'Win probability (0-100)',
67
+ }),
68
+
69
+ // Type
70
+ isWonStage: field.boolean({ default: false }),
71
+ isLostStage: field.boolean({ default: false }),
72
+
73
+ // Settings
74
+ color: field.string({
75
+ isOptional: true,
76
+ description: 'Stage color for UI',
77
+ }),
78
+
79
+ // Timestamps
80
+ createdAt: field.createdAt(),
81
+ updatedAt: field.updatedAt(),
82
+
83
+ // Relations
84
+ pipeline: field.belongsTo('Pipeline', ['pipelineId'], ['id'], {
85
+ onDelete: 'Cascade',
86
+ }),
87
+ deals: field.hasMany('Deal'),
88
+ },
89
+ indexes: [index.on(['pipelineId', 'position'])],
90
+ });
91
+
92
+ /**
93
+ * Deal entity - sales opportunity.
94
+ */
95
+ export const DealEntity = defineEntity({
96
+ name: 'Deal',
97
+ description: 'A sales opportunity/deal.',
98
+ schema: 'crm',
99
+ map: 'deal',
100
+ fields: {
101
+ id: field.id({ description: 'Unique deal ID' }),
102
+ name: field.string({ description: 'Deal name' }),
103
+
104
+ // Value
105
+ value: field.decimal({ description: 'Deal value' }),
106
+ currency: field.string({ default: '"USD"' }),
107
+
108
+ // Pipeline
109
+ pipelineId: field.foreignKey(),
110
+ stageId: field.foreignKey(),
111
+
112
+ // Status
113
+ status: field.enum('DealStatus', { default: 'OPEN' }),
114
+
115
+ // Associations
116
+ contactId: field.string({ isOptional: true }),
117
+ companyId: field.string({ isOptional: true }),
118
+
119
+ // Ownership
120
+ organizationId: field.foreignKey(),
121
+ ownerId: field.foreignKey({ description: 'Deal owner' }),
122
+
123
+ // Timeline
124
+ expectedCloseDate: field.dateTime({ isOptional: true }),
125
+ closedAt: field.dateTime({ isOptional: true }),
126
+
127
+ // Tracking
128
+ lostReason: field.string({ isOptional: true }),
129
+ wonSource: field.string({ isOptional: true }),
130
+
131
+ // Notes
132
+ notes: field.string({ isOptional: true }),
133
+ tags: field.string({ isArray: true }),
134
+
135
+ // Custom fields
136
+ customFields: field.json({ isOptional: true }),
137
+
138
+ // Position in stage (for Kanban)
139
+ stagePosition: field.int({ default: 0 }),
140
+
141
+ // Timestamps
142
+ createdAt: field.createdAt(),
143
+ updatedAt: field.updatedAt(),
144
+
145
+ // Relations
146
+ pipeline: field.belongsTo('Pipeline', ['pipelineId'], ['id']),
147
+ stage: field.belongsTo('Stage', ['stageId'], ['id']),
148
+ contact: field.belongsTo('Contact', ['contactId'], ['id']),
149
+ company: field.belongsTo('Company', ['companyId'], ['id']),
150
+ tasks: field.hasMany('Task'),
151
+ activities: field.hasMany('Activity'),
152
+ },
153
+ indexes: [
154
+ index.on(['organizationId', 'status']),
155
+ index.on(['pipelineId', 'stageId', 'stagePosition']),
156
+ index.on(['ownerId', 'status']),
157
+ index.on(['expectedCloseDate']),
158
+ ],
159
+ enums: [DealStatusEnum],
160
+ });
@@ -0,0 +1,45 @@
1
+ export * from './company.entity';
2
+ export * from './contact.entity';
3
+ export * from './deal.entity';
4
+ export * from './task.entity';
5
+
6
+ import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
7
+ import { CompanyEntity, CompanySizeEnum } from './company.entity';
8
+ import { ContactEntity, ContactStatusEnum } from './contact.entity';
9
+ import {
10
+ DealEntity,
11
+ PipelineEntity,
12
+ StageEntity,
13
+ DealStatusEnum,
14
+ } from './deal.entity';
15
+ import {
16
+ TaskEntity,
17
+ ActivityEntity,
18
+ TaskTypeEnum,
19
+ TaskPriorityEnum,
20
+ TaskStatusEnum,
21
+ } from './task.entity';
22
+
23
+ /**
24
+ * CRM Pipeline schema contribution.
25
+ */
26
+ export const crmPipelineSchemaContribution: ModuleSchemaContribution = {
27
+ moduleId: '@contractspec/example.crm-pipeline',
28
+ entities: [
29
+ CompanyEntity,
30
+ ContactEntity,
31
+ DealEntity,
32
+ PipelineEntity,
33
+ StageEntity,
34
+ TaskEntity,
35
+ ActivityEntity,
36
+ ],
37
+ enums: [
38
+ CompanySizeEnum,
39
+ ContactStatusEnum,
40
+ DealStatusEnum,
41
+ TaskTypeEnum,
42
+ TaskPriorityEnum,
43
+ TaskStatusEnum,
44
+ ],
45
+ };