@contractspec/example.crm-pipeline 0.0.0-canary-20260113170453
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.
- package/.turbo/turbo-build$colon$bundle.log +172 -0
- package/.turbo/turbo-build.log +173 -0
- package/CHANGELOG.md +436 -0
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/crm-pipeline.feature.d.ts +12 -0
- package/dist/crm-pipeline.feature.d.ts.map +1 -0
- package/dist/crm-pipeline.feature.js +166 -0
- package/dist/crm-pipeline.feature.js.map +1 -0
- package/dist/deal/deal.enum.d.ts +14 -0
- package/dist/deal/deal.enum.d.ts.map +1 -0
- package/dist/deal/deal.enum.js +25 -0
- package/dist/deal/deal.enum.js.map +1 -0
- package/dist/deal/deal.operation.d.ts +513 -0
- package/dist/deal/deal.operation.d.ts.map +1 -0
- package/dist/deal/deal.operation.js +270 -0
- package/dist/deal/deal.operation.js.map +1 -0
- package/dist/deal/deal.schema.d.ts +300 -0
- package/dist/deal/deal.schema.d.ts.map +1 -0
- package/dist/deal/deal.schema.js +286 -0
- package/dist/deal/deal.schema.js.map +1 -0
- package/dist/deal/deal.test-spec.d.ts +8 -0
- package/dist/deal/deal.test-spec.d.ts.map +1 -0
- package/dist/deal/deal.test-spec.js +65 -0
- package/dist/deal/deal.test-spec.js.map +1 -0
- package/dist/deal/index.d.ts +4 -0
- package/dist/deal/index.js +5 -0
- package/dist/docs/crm-pipeline.docblock.d.ts +1 -0
- package/dist/docs/crm-pipeline.docblock.js +100 -0
- package/dist/docs/crm-pipeline.docblock.js.map +1 -0
- package/dist/docs/index.d.ts +1 -0
- package/dist/docs/index.js +1 -0
- package/dist/entities/company.entity.d.ts +40 -0
- package/dist/entities/company.entity.d.ts.map +1 -0
- package/dist/entities/company.entity.js +63 -0
- package/dist/entities/company.entity.js.map +1 -0
- package/dist/entities/contact.entity.d.ts +44 -0
- package/dist/entities/contact.entity.d.ts.map +1 -0
- package/dist/entities/contact.entity.js +78 -0
- package/dist/entities/contact.entity.js.map +1 -0
- package/dist/entities/deal.entity.d.ts +73 -0
- package/dist/entities/deal.entity.d.ts.map +1 -0
- package/dist/entities/deal.entity.js +120 -0
- package/dist/entities/deal.entity.js.map +1 -0
- package/dist/entities/index.d.ts +15 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +33 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/task.entity.d.ts +65 -0
- package/dist/entities/task.entity.d.ts.map +1 -0
- package/dist/entities/task.entity.js +129 -0
- package/dist/entities/task.entity.js.map +1 -0
- package/dist/events/contact.event.d.ts +29 -0
- package/dist/events/contact.event.d.ts.map +1 -0
- package/dist/events/contact.event.js +45 -0
- package/dist/events/contact.event.js.map +1 -0
- package/dist/events/deal.event.d.ts +111 -0
- package/dist/events/deal.event.d.ts.map +1 -0
- package/dist/events/deal.event.js +172 -0
- package/dist/events/deal.event.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.js +5 -0
- package/dist/events/task.event.d.ts +29 -0
- package/dist/events/task.event.d.ts.map +1 -0
- package/dist/events/task.event.js +45 -0
- package/dist/events/task.event.js.map +1 -0
- package/dist/example.d.ts +7 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +53 -0
- package/dist/example.js.map +1 -0
- package/dist/handlers/crm.handlers.d.ts +89 -0
- package/dist/handlers/crm.handlers.d.ts.map +1 -0
- package/dist/handlers/crm.handlers.js +172 -0
- package/dist/handlers/crm.handlers.js.map +1 -0
- package/dist/handlers/deal.handlers.d.ts +94 -0
- package/dist/handlers/deal.handlers.d.ts.map +1 -0
- package/dist/handlers/deal.handlers.js +120 -0
- package/dist/handlers/deal.handlers.js.map +1 -0
- package/dist/handlers/index.d.ts +4 -0
- package/dist/handlers/index.js +5 -0
- package/dist/handlers/mock-data.d.ts +49 -0
- package/dist/handlers/mock-data.d.ts.map +1 -0
- package/dist/handlers/mock-data.js +188 -0
- package/dist/handlers/mock-data.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/index.d.ts +5 -0
- package/dist/operations/index.js +6 -0
- package/dist/presentations/dashboard.presentation.d.ts +14 -0
- package/dist/presentations/dashboard.presentation.d.ts.map +1 -0
- package/dist/presentations/dashboard.presentation.js +62 -0
- package/dist/presentations/dashboard.presentation.js.map +1 -0
- package/dist/presentations/index.d.ts +3 -0
- package/dist/presentations/index.js +4 -0
- package/dist/presentations/pipeline.presentation.d.ts +22 -0
- package/dist/presentations/pipeline.presentation.d.ts.map +1 -0
- package/dist/presentations/pipeline.presentation.js +122 -0
- package/dist/presentations/pipeline.presentation.js.map +1 -0
- package/dist/seeders/index.d.ts +10 -0
- package/dist/seeders/index.d.ts.map +1 -0
- package/dist/seeders/index.js +47 -0
- package/dist/seeders/index.js.map +1 -0
- package/dist/shared/overlay-types.d.ts +34 -0
- package/dist/shared/overlay-types.d.ts.map +1 -0
- package/dist/shared/overlay-types.js +0 -0
- package/dist/ui/CrmDashboard.d.ts +7 -0
- package/dist/ui/CrmDashboard.d.ts.map +1 -0
- package/dist/ui/CrmDashboard.js +304 -0
- package/dist/ui/CrmDashboard.js.map +1 -0
- package/dist/ui/CrmDealCard.d.ts +15 -0
- package/dist/ui/CrmDealCard.d.ts.map +1 -0
- package/dist/ui/CrmDealCard.js +49 -0
- package/dist/ui/CrmDealCard.js.map +1 -0
- package/dist/ui/CrmPipelineBoard.d.ts +23 -0
- package/dist/ui/CrmPipelineBoard.d.ts.map +1 -0
- package/dist/ui/CrmPipelineBoard.js +98 -0
- package/dist/ui/CrmPipelineBoard.js.map +1 -0
- package/dist/ui/hooks/index.d.ts +3 -0
- package/dist/ui/hooks/index.js +6 -0
- package/dist/ui/hooks/useDealList.d.ts +35 -0
- package/dist/ui/hooks/useDealList.d.ts.map +1 -0
- package/dist/ui/hooks/useDealList.js +94 -0
- package/dist/ui/hooks/useDealList.js.map +1 -0
- package/dist/ui/hooks/useDealMutations.d.ts +26 -0
- package/dist/ui/hooks/useDealMutations.d.ts.map +1 -0
- package/dist/ui/hooks/useDealMutations.js +159 -0
- package/dist/ui/hooks/useDealMutations.js.map +1 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/modals/CreateDealModal.d.ts +33 -0
- package/dist/ui/modals/CreateDealModal.d.ts.map +1 -0
- package/dist/ui/modals/CreateDealModal.js +183 -0
- package/dist/ui/modals/CreateDealModal.js.map +1 -0
- package/dist/ui/modals/DealActionsModal.d.ts +51 -0
- package/dist/ui/modals/DealActionsModal.d.ts.map +1 -0
- package/dist/ui/modals/DealActionsModal.js +372 -0
- package/dist/ui/modals/DealActionsModal.js.map +1 -0
- package/dist/ui/modals/index.d.ts +3 -0
- package/dist/ui/modals/index.js +4 -0
- package/dist/ui/overlays/demo-overlays.d.ts +19 -0
- package/dist/ui/overlays/demo-overlays.d.ts.map +1 -0
- package/dist/ui/overlays/demo-overlays.js +68 -0
- package/dist/ui/overlays/demo-overlays.js.map +1 -0
- package/dist/ui/overlays/index.d.ts +2 -0
- package/dist/ui/overlays/index.js +3 -0
- package/dist/ui/renderers/index.d.ts +3 -0
- package/dist/ui/renderers/index.js +4 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts +23 -0
- package/dist/ui/renderers/pipeline.markdown.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.markdown.js +118 -0
- package/dist/ui/renderers/pipeline.markdown.js.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts +9 -0
- package/dist/ui/renderers/pipeline.renderer.d.ts.map +1 -0
- package/dist/ui/renderers/pipeline.renderer.js +28 -0
- package/dist/ui/renderers/pipeline.renderer.js.map +1 -0
- package/example.ts +1 -0
- package/package.json +127 -0
- package/src/crm-pipeline.feature.ts +100 -0
- package/src/deal/deal.enum.ts +21 -0
- package/src/deal/deal.operation.ts +291 -0
- package/src/deal/deal.schema.ts +154 -0
- package/src/deal/deal.test-spec.ts +55 -0
- package/src/deal/index.ts +26 -0
- package/src/docs/crm-pipeline.docblock.ts +98 -0
- package/src/docs/index.ts +1 -0
- package/src/entities/company.entity.ts +77 -0
- package/src/entities/contact.entity.ts +93 -0
- package/src/entities/deal.entity.ts +160 -0
- package/src/entities/index.ts +45 -0
- package/src/entities/task.entity.ts +137 -0
- package/src/events/contact.event.ts +31 -0
- package/src/events/deal.event.ts +104 -0
- package/src/events/index.ts +3 -0
- package/src/events/task.event.ts +28 -0
- package/src/example.ts +38 -0
- package/src/handlers/crm.handlers.ts +415 -0
- package/src/handlers/deal.handlers.ts +253 -0
- package/src/handlers/index.ts +30 -0
- package/src/handlers/mock-data.ts +198 -0
- package/src/index.ts +32 -0
- package/src/operations/index.ts +20 -0
- package/src/presentations/dashboard.presentation.ts +59 -0
- package/src/presentations/index.ts +2 -0
- package/src/presentations/pipeline.presentation.ts +117 -0
- package/src/seeders/index.ts +35 -0
- package/src/shared/overlay-types.ts +39 -0
- package/src/ui/CrmDashboard.tsx +311 -0
- package/src/ui/CrmDealCard.tsx +83 -0
- package/src/ui/CrmPipelineBoard.tsx +136 -0
- package/src/ui/hooks/index.ts +10 -0
- package/src/ui/hooks/useDealList.ts +113 -0
- package/src/ui/hooks/useDealMutations.ts +174 -0
- package/src/ui/index.ts +18 -0
- package/src/ui/modals/CreateDealModal.tsx +239 -0
- package/src/ui/modals/DealActionsModal.tsx +424 -0
- package/src/ui/modals/index.ts +2 -0
- package/src/ui/overlays/demo-overlays.ts +68 -0
- package/src/ui/overlays/index.ts +1 -0
- package/src/ui/renderers/index.ts +6 -0
- package/src/ui/renderers/pipeline.markdown.ts +198 -0
- package/src/ui/renderers/pipeline.renderer.tsx +35 -0
- package/tsconfig.json +10 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.js +7 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { web } from "@contractspec/lib.runtime-sandbox";
|
|
2
|
+
|
|
3
|
+
//#region src/handlers/crm.handlers.ts
|
|
4
|
+
const { generateId } = web;
|
|
5
|
+
function rowToDeal(row) {
|
|
6
|
+
return {
|
|
7
|
+
id: row.id,
|
|
8
|
+
projectId: row.projectId,
|
|
9
|
+
name: row.name,
|
|
10
|
+
value: row.value,
|
|
11
|
+
currency: row.currency,
|
|
12
|
+
pipelineId: row.pipelineId,
|
|
13
|
+
stageId: row.stageId,
|
|
14
|
+
status: row.status,
|
|
15
|
+
contactId: row.contactId ?? void 0,
|
|
16
|
+
companyId: row.companyId ?? void 0,
|
|
17
|
+
ownerId: row.ownerId,
|
|
18
|
+
expectedCloseDate: row.expectedCloseDate ? new Date(row.expectedCloseDate) : void 0,
|
|
19
|
+
wonSource: row.wonSource ?? void 0,
|
|
20
|
+
lostReason: row.lostReason ?? void 0,
|
|
21
|
+
notes: row.notes ?? void 0,
|
|
22
|
+
createdAt: new Date(row.createdAt),
|
|
23
|
+
updatedAt: new Date(row.updatedAt)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createCrmHandlers(db) {
|
|
27
|
+
/**
|
|
28
|
+
* List deals with filtering
|
|
29
|
+
*/
|
|
30
|
+
async function listDeals(input) {
|
|
31
|
+
const { projectId, pipelineId, stageId, status, ownerId, search, limit = 20, offset = 0 } = input;
|
|
32
|
+
let whereClause = "WHERE projectId = ?";
|
|
33
|
+
const params = [projectId];
|
|
34
|
+
if (pipelineId) {
|
|
35
|
+
whereClause += " AND pipelineId = ?";
|
|
36
|
+
params.push(pipelineId);
|
|
37
|
+
}
|
|
38
|
+
if (stageId) {
|
|
39
|
+
whereClause += " AND stageId = ?";
|
|
40
|
+
params.push(stageId);
|
|
41
|
+
}
|
|
42
|
+
if (status && status !== "all") {
|
|
43
|
+
whereClause += " AND status = ?";
|
|
44
|
+
params.push(status);
|
|
45
|
+
}
|
|
46
|
+
if (ownerId) {
|
|
47
|
+
whereClause += " AND ownerId = ?";
|
|
48
|
+
params.push(ownerId);
|
|
49
|
+
}
|
|
50
|
+
if (search) {
|
|
51
|
+
whereClause += " AND name LIKE ?";
|
|
52
|
+
params.push(`%${search}%`);
|
|
53
|
+
}
|
|
54
|
+
const total = (await db.query(`SELECT COUNT(*) as count FROM crm_deal ${whereClause}`, params)).rows[0]?.count ?? 0;
|
|
55
|
+
const totalValue = (await db.query(`SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`, params)).rows[0]?.total ?? 0;
|
|
56
|
+
return {
|
|
57
|
+
deals: (await db.query(`SELECT * FROM crm_deal ${whereClause} ORDER BY value DESC LIMIT ? OFFSET ?`, [
|
|
58
|
+
...params,
|
|
59
|
+
limit,
|
|
60
|
+
offset
|
|
61
|
+
])).rows.map(rowToDeal),
|
|
62
|
+
total,
|
|
63
|
+
totalValue
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a new deal
|
|
68
|
+
*/
|
|
69
|
+
async function createDeal(input, context) {
|
|
70
|
+
const id = generateId("deal");
|
|
71
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
72
|
+
await db.execute(`INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)
|
|
73
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
74
|
+
id,
|
|
75
|
+
context.projectId,
|
|
76
|
+
input.pipelineId,
|
|
77
|
+
input.stageId,
|
|
78
|
+
input.name,
|
|
79
|
+
input.value,
|
|
80
|
+
input.currency ?? "USD",
|
|
81
|
+
"OPEN",
|
|
82
|
+
input.contactId ?? null,
|
|
83
|
+
input.companyId ?? null,
|
|
84
|
+
context.ownerId,
|
|
85
|
+
input.expectedCloseDate?.toISOString() ?? null,
|
|
86
|
+
now,
|
|
87
|
+
now
|
|
88
|
+
]);
|
|
89
|
+
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [id])).rows;
|
|
90
|
+
if (!rows[0]) throw new Error("Failed to create deal");
|
|
91
|
+
return rowToDeal(rows[0]);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Move a deal to a different stage
|
|
95
|
+
*/
|
|
96
|
+
async function moveDeal(input) {
|
|
97
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
98
|
+
if (!(await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows[0]) throw new Error("NOT_FOUND");
|
|
99
|
+
if (!(await db.query(`SELECT * FROM crm_stage WHERE id = ?`, [input.stageId])).rows[0]) throw new Error("INVALID_STAGE");
|
|
100
|
+
await db.execute(`UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?`, [
|
|
101
|
+
input.stageId,
|
|
102
|
+
now,
|
|
103
|
+
input.dealId
|
|
104
|
+
]);
|
|
105
|
+
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
106
|
+
return rowToDeal(rows[0]);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Mark a deal as won
|
|
110
|
+
*/
|
|
111
|
+
async function winDeal(input) {
|
|
112
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
113
|
+
if (!(await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows[0]) throw new Error("NOT_FOUND");
|
|
114
|
+
await db.execute(`UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?`, [
|
|
115
|
+
input.wonSource ?? null,
|
|
116
|
+
input.notes ?? null,
|
|
117
|
+
now,
|
|
118
|
+
input.dealId
|
|
119
|
+
]);
|
|
120
|
+
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
121
|
+
return rowToDeal(rows[0]);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Mark a deal as lost
|
|
125
|
+
*/
|
|
126
|
+
async function loseDeal(input) {
|
|
127
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
128
|
+
if (!(await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows[0]) throw new Error("NOT_FOUND");
|
|
129
|
+
await db.execute(`UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?`, [
|
|
130
|
+
input.lostReason,
|
|
131
|
+
input.notes ?? null,
|
|
132
|
+
now,
|
|
133
|
+
input.dealId
|
|
134
|
+
]);
|
|
135
|
+
const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])).rows;
|
|
136
|
+
return rowToDeal(rows[0]);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get deals grouped by stage
|
|
140
|
+
*/
|
|
141
|
+
async function getDealsByStage(input) {
|
|
142
|
+
const deals = (await db.query(`SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC`, [input.projectId, input.pipelineId])).rows;
|
|
143
|
+
const stages = (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows;
|
|
144
|
+
const grouped = {};
|
|
145
|
+
for (const stage of stages) grouped[stage.id] = deals.filter((d) => d.stageId === stage.id).map(rowToDeal);
|
|
146
|
+
return grouped;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get pipeline stages
|
|
150
|
+
*/
|
|
151
|
+
async function getPipelineStages(input) {
|
|
152
|
+
return (await db.query(`SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`, [input.pipelineId])).rows.map((row) => ({
|
|
153
|
+
id: row.id,
|
|
154
|
+
pipelineId: row.pipelineId,
|
|
155
|
+
name: row.name,
|
|
156
|
+
position: row.position
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
listDeals,
|
|
161
|
+
createDeal,
|
|
162
|
+
moveDeal,
|
|
163
|
+
winDeal,
|
|
164
|
+
loseDeal,
|
|
165
|
+
getDealsByStage,
|
|
166
|
+
getPipelineStages
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
//#endregion
|
|
171
|
+
export { createCrmHandlers };
|
|
172
|
+
//# sourceMappingURL=crm.handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crm.handlers.js","names":[],"sources":["../../src/handlers/crm.handlers.ts"],"sourcesContent":["/**\n * Runtime-local CRM handlers\n *\n * These handlers work with the in-browser SQLite database\n * instead of in-memory mock arrays.\n */\nimport type { DatabasePort, DbRow } from '@contractspec/lib.runtime-sandbox';\n/* eslint-disable @typescript-eslint/no-non-null-assertion */\nimport { web } from '@contractspec/lib.runtime-sandbox';\nconst { generateId } = web;\n\n// ============ Types ============\n\nexport interface Deal {\n id: string;\n projectId: string;\n name: string;\n value: number;\n currency: string;\n pipelineId: string;\n stageId: string;\n status: 'OPEN' | 'WON' | 'LOST' | 'STALE';\n contactId?: string;\n companyId?: string;\n ownerId: string;\n expectedCloseDate?: Date;\n wonSource?: string;\n lostReason?: string;\n notes?: string;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface Stage {\n id: string;\n pipelineId: string;\n name: string;\n position: number;\n}\n\nexport interface CreateDealInput {\n name: string;\n value: number;\n currency?: string;\n pipelineId: string;\n stageId: string;\n contactId?: string;\n companyId?: string;\n expectedCloseDate?: Date;\n}\n\nexport interface MoveDealInput {\n dealId: string;\n stageId: string;\n}\n\nexport interface WinDealInput {\n dealId: string;\n wonSource?: string;\n notes?: string;\n}\n\nexport interface LoseDealInput {\n dealId: string;\n lostReason: string;\n notes?: string;\n}\n\nexport interface ListDealsInput {\n projectId: string;\n pipelineId?: string;\n stageId?: string;\n status?: 'OPEN' | 'WON' | 'LOST' | 'all';\n ownerId?: string;\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface ListDealsOutput {\n deals: Deal[];\n total: number;\n totalValue: number;\n}\n\n// ============ Row Type ============\n\n// Note: We don't use DbRow generics here - just cast the results\n// to the expected types since we know the schema\n\ninterface DealRow {\n id: string;\n projectId: string;\n name: string;\n value: number;\n currency: string;\n pipelineId: string;\n stageId: string;\n status: string;\n contactId: string | null;\n companyId: string | null;\n ownerId: string;\n expectedCloseDate: string | null;\n wonSource: string | null;\n lostReason: string | null;\n notes: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\ninterface StageRow {\n id: string;\n pipelineId: string;\n name: string;\n position: number;\n}\n\nfunction rowToDeal(row: DealRow): Deal {\n return {\n id: row.id,\n projectId: row.projectId,\n name: row.name,\n value: row.value,\n currency: row.currency,\n pipelineId: row.pipelineId,\n stageId: row.stageId,\n status: row.status as Deal['status'],\n contactId: row.contactId ?? undefined,\n companyId: row.companyId ?? undefined,\n ownerId: row.ownerId,\n expectedCloseDate: row.expectedCloseDate\n ? new Date(row.expectedCloseDate)\n : undefined,\n wonSource: row.wonSource ?? undefined,\n lostReason: row.lostReason ?? undefined,\n notes: row.notes ?? undefined,\n createdAt: new Date(row.createdAt),\n updatedAt: new Date(row.updatedAt),\n };\n}\n\n// ============ Handler Factory ============\n\nexport function createCrmHandlers(db: DatabasePort) {\n /**\n * List deals with filtering\n */\n async function listDeals(input: ListDealsInput): Promise<ListDealsOutput> {\n const {\n projectId,\n pipelineId,\n stageId,\n status,\n ownerId,\n search,\n limit = 20,\n offset = 0,\n } = input;\n\n let whereClause = 'WHERE projectId = ?';\n const params: (string | number)[] = [projectId];\n\n if (pipelineId) {\n whereClause += ' AND pipelineId = ?';\n params.push(pipelineId);\n }\n\n if (stageId) {\n whereClause += ' AND stageId = ?';\n params.push(stageId);\n }\n\n if (status && status !== 'all') {\n whereClause += ' AND status = ?';\n params.push(status);\n }\n\n if (ownerId) {\n whereClause += ' AND ownerId = ?';\n params.push(ownerId);\n }\n\n if (search) {\n whereClause += ' AND name LIKE ?';\n params.push(`%${search}%`);\n }\n\n // Get total count\n const countResult = (\n await db.query(\n `SELECT COUNT(*) as count FROM crm_deal ${whereClause}`,\n params\n )\n ).rows as DbRow[];\n const total = (countResult[0]?.count as number) ?? 0;\n\n // Get total value\n const valueResult = (\n await db.query(\n `SELECT COALESCE(SUM(value), 0) as total FROM crm_deal ${whereClause}`,\n params\n )\n ).rows as DbRow[];\n const totalValue = (valueResult[0]?.total as number) ?? 0;\n\n // Get paginated deals\n const dealRows = (\n await db.query(\n `SELECT * FROM crm_deal ${whereClause} ORDER BY value DESC LIMIT ? OFFSET ?`,\n [...params, limit, offset]\n )\n ).rows as unknown as DealRow[];\n\n return {\n deals: dealRows.map(rowToDeal),\n total,\n totalValue,\n };\n }\n\n /**\n * Create a new deal\n */\n async function createDeal(\n input: CreateDealInput,\n context: { projectId: string; ownerId: string }\n ): Promise<Deal> {\n const id = generateId('deal');\n const now = new Date().toISOString();\n\n await db.execute(\n `INSERT INTO crm_deal (id, projectId, pipelineId, stageId, name, value, currency, status, contactId, companyId, ownerId, expectedCloseDate, createdAt, updatedAt)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n id,\n context.projectId,\n input.pipelineId,\n input.stageId,\n input.name,\n input.value,\n input.currency ?? 'USD',\n 'OPEN',\n input.contactId ?? null,\n input.companyId ?? null,\n context.ownerId,\n input.expectedCloseDate?.toISOString() ?? null,\n now,\n now,\n ]\n );\n\n const rows = (await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [id]))\n .rows as unknown as DealRow[];\n\n if (!rows[0]) {\n throw new Error('Failed to create deal');\n }\n\n return rowToDeal(rows[0]);\n }\n\n /**\n * Move a deal to a different stage\n */\n async function moveDeal(input: MoveDealInput): Promise<Deal> {\n const now = new Date().toISOString();\n\n // Verify deal exists\n const existing = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n if (!existing[0]) {\n throw new Error('NOT_FOUND');\n }\n\n // Verify stage exists\n const stage = (\n await db.query(`SELECT * FROM crm_stage WHERE id = ?`, [input.stageId])\n ).rows as unknown as StageRow[];\n\n if (!stage[0]) {\n throw new Error('INVALID_STAGE');\n }\n\n await db.execute(\n `UPDATE crm_deal SET stageId = ?, updatedAt = ? WHERE id = ?`,\n [input.stageId, now, input.dealId]\n );\n\n const rows = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n return rowToDeal(rows[0]!);\n }\n\n /**\n * Mark a deal as won\n */\n async function winDeal(input: WinDealInput): Promise<Deal> {\n const now = new Date().toISOString();\n\n // Verify deal exists\n const existing = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n if (!existing[0]) {\n throw new Error('NOT_FOUND');\n }\n\n await db.execute(\n `UPDATE crm_deal SET status = 'WON', wonSource = ?, notes = ?, updatedAt = ? WHERE id = ?`,\n [input.wonSource ?? null, input.notes ?? null, now, input.dealId]\n );\n\n const rows = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n return rowToDeal(rows[0]!);\n }\n\n /**\n * Mark a deal as lost\n */\n async function loseDeal(input: LoseDealInput): Promise<Deal> {\n const now = new Date().toISOString();\n\n // Verify deal exists\n const existing = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n if (!existing[0]) {\n throw new Error('NOT_FOUND');\n }\n\n await db.execute(\n `UPDATE crm_deal SET status = 'LOST', lostReason = ?, notes = ?, updatedAt = ? WHERE id = ?`,\n [input.lostReason, input.notes ?? null, now, input.dealId]\n );\n\n const rows = (\n await db.query(`SELECT * FROM crm_deal WHERE id = ?`, [input.dealId])\n ).rows as unknown as DealRow[];\n\n return rowToDeal(rows[0]!);\n }\n\n /**\n * Get deals grouped by stage\n */\n async function getDealsByStage(input: {\n projectId: string;\n pipelineId: string;\n }): Promise<Record<string, Deal[]>> {\n const deals = (\n await db.query(\n `SELECT * FROM crm_deal WHERE projectId = ? AND pipelineId = ? AND status = 'OPEN' ORDER BY value DESC`,\n [input.projectId, input.pipelineId]\n )\n ).rows as unknown as DealRow[];\n\n const stages = (\n await db.query(\n `SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`,\n [input.pipelineId]\n )\n ).rows as unknown as StageRow[];\n\n const grouped: Record<string, Deal[]> = {};\n for (const stage of stages) {\n grouped[stage.id] = deals\n .filter((d) => d.stageId === stage.id)\n .map(rowToDeal);\n }\n\n return grouped;\n }\n\n /**\n * Get pipeline stages\n */\n async function getPipelineStages(input: {\n pipelineId: string;\n }): Promise<Stage[]> {\n const rows = (\n await db.query(\n `SELECT * FROM crm_stage WHERE pipelineId = ? ORDER BY position`,\n [input.pipelineId]\n )\n ).rows as unknown as StageRow[];\n\n return rows.map((row) => ({\n id: row.id,\n pipelineId: row.pipelineId,\n name: row.name,\n position: row.position,\n }));\n }\n\n return {\n listDeals,\n createDeal,\n moveDeal,\n winDeal,\n loseDeal,\n getDealsByStage,\n getPipelineStages,\n };\n}\n\nexport type CrmHandlers = ReturnType<typeof createCrmHandlers>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { MockStage } from "./mock-data.js";
|
|
2
|
+
|
|
3
|
+
//#region src/handlers/deal.handlers.d.ts
|
|
4
|
+
interface Deal {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
value: number;
|
|
8
|
+
currency: string;
|
|
9
|
+
pipelineId: string;
|
|
10
|
+
stageId: string;
|
|
11
|
+
status: 'OPEN' | 'WON' | 'LOST' | 'STALE';
|
|
12
|
+
contactId?: string;
|
|
13
|
+
companyId?: string;
|
|
14
|
+
ownerId: string;
|
|
15
|
+
expectedCloseDate?: Date;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
updatedAt: Date;
|
|
18
|
+
}
|
|
19
|
+
interface CreateDealInput {
|
|
20
|
+
name: string;
|
|
21
|
+
value: number;
|
|
22
|
+
currency?: string;
|
|
23
|
+
pipelineId: string;
|
|
24
|
+
stageId: string;
|
|
25
|
+
contactId?: string;
|
|
26
|
+
companyId?: string;
|
|
27
|
+
expectedCloseDate?: Date;
|
|
28
|
+
}
|
|
29
|
+
interface MoveDealInput {
|
|
30
|
+
dealId: string;
|
|
31
|
+
stageId: string;
|
|
32
|
+
position?: number;
|
|
33
|
+
}
|
|
34
|
+
interface WinDealInput {
|
|
35
|
+
dealId: string;
|
|
36
|
+
wonSource?: string;
|
|
37
|
+
notes?: string;
|
|
38
|
+
}
|
|
39
|
+
interface LoseDealInput {
|
|
40
|
+
dealId: string;
|
|
41
|
+
lostReason: string;
|
|
42
|
+
notes?: string;
|
|
43
|
+
}
|
|
44
|
+
interface ListDealsInput {
|
|
45
|
+
pipelineId?: string;
|
|
46
|
+
stageId?: string;
|
|
47
|
+
status?: 'OPEN' | 'WON' | 'LOST' | 'all';
|
|
48
|
+
ownerId?: string;
|
|
49
|
+
search?: string;
|
|
50
|
+
limit?: number;
|
|
51
|
+
offset?: number;
|
|
52
|
+
}
|
|
53
|
+
interface ListDealsOutput {
|
|
54
|
+
deals: Deal[];
|
|
55
|
+
total: number;
|
|
56
|
+
totalValue: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Mock handler for ListDealsContract
|
|
60
|
+
*/
|
|
61
|
+
declare function mockListDealsHandler(input: ListDealsInput): Promise<ListDealsOutput>;
|
|
62
|
+
/**
|
|
63
|
+
* Mock handler for CreateDealContract
|
|
64
|
+
*/
|
|
65
|
+
declare function mockCreateDealHandler(input: CreateDealInput, context: {
|
|
66
|
+
ownerId: string;
|
|
67
|
+
}): Promise<Deal>;
|
|
68
|
+
/**
|
|
69
|
+
* Mock handler for MoveDealContract
|
|
70
|
+
*/
|
|
71
|
+
declare function mockMoveDealHandler(input: MoveDealInput): Promise<Deal>;
|
|
72
|
+
/**
|
|
73
|
+
* Mock handler for WinDealContract
|
|
74
|
+
*/
|
|
75
|
+
declare function mockWinDealHandler(input: WinDealInput): Promise<Deal>;
|
|
76
|
+
/**
|
|
77
|
+
* Mock handler for LoseDealContract
|
|
78
|
+
*/
|
|
79
|
+
declare function mockLoseDealHandler(input: LoseDealInput): Promise<Deal>;
|
|
80
|
+
/**
|
|
81
|
+
* Get deals grouped by stage for Kanban view
|
|
82
|
+
*/
|
|
83
|
+
declare function mockGetDealsByStageHandler(input: {
|
|
84
|
+
pipelineId: string;
|
|
85
|
+
}): Promise<Record<string, Deal[]>>;
|
|
86
|
+
/**
|
|
87
|
+
* Get pipeline stages
|
|
88
|
+
*/
|
|
89
|
+
declare function mockGetPipelineStagesHandler(input: {
|
|
90
|
+
pipelineId: string;
|
|
91
|
+
}): Promise<MockStage[]>;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { CreateDealInput, Deal, ListDealsInput, ListDealsOutput, LoseDealInput, MoveDealInput, WinDealInput, mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler };
|
|
94
|
+
//# sourceMappingURL=deal.handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deal.handlers.d.ts","names":[],"sources":["../../src/handlers/deal.handlers.ts"],"sourcesContent":[],"mappings":";;;UAMiB,IAAA;;;EAAA,KAAA,EAAI,MAAA;EAWC,QAAA,EAAA,MAAA;EACT,UAAA,EAAA,MAAA;EACA,OAAA,EAAA,MAAA;EAAI,MAAA,EAAA,MAAA,GAAA,KAAA,GAAA,MAAA,GAAA,OAAA;EAGA,SAAA,CAAA,EAAA,MAAe;EAWf,SAAA,CAAA,EAAA,MAAa;EAMb,OAAA,EAAA,MAAY;EAMZ,iBAAa,CAAA,EA5BR,IA4BQ;EAMb,SAAA,EAjCJ,IAiCI;EAUA,SAAA,EA1CJ,IA0CI;AASjB;AACS,UAjDQ,eAAA,CAiDR;EACE,IAAA,EAAA,MAAA;EAAR,KAAA,EAAA,MAAA;EAAO,QAAA,CAAA,EAAA,MAAA;EAmDY,UAAA,EAAA,MAAA;EACb,OAAA,EAAA,MAAA;EAEE,SAAA,CAAA,EAAA,MAAA;EAAR,SAAA,CAAA,EAAA,MAAA;EAAO,iBAAA,CAAA,EAhGY,IAgGZ;AA2BV;AAAiD,UAxHhC,aAAA,CAwHgC;EAAwB,MAAA,EAAA,MAAA;EAAR,OAAA,EAAA,MAAA;EAAO,QAAA,CAAA,EAAA,MAAA;AA6BxE;AAAgD,UA/I/B,YAAA,CA+I+B;EAAuB,MAAA,EAAA,MAAA;EAAR,SAAA,CAAA,EAAA,MAAA;EAAO,KAAA,CAAA,EAAA,MAAA;AAwBtE;AAAiD,UAjKhC,aAAA,CAiKgC;EAAwB,MAAA,EAAA,MAAA;EAAR,UAAA,EAAA,MAAA;EAAO,KAAA,CAAA,EAAA,MAAA;AAwBxE;AAE2B,UArLV,cAAA,CAqLU;EAAf,UAAA,CAAA,EAAA,MAAA;EAAR,OAAA,CAAA,EAAA,MAAA;EAAO,MAAA,CAAA,EAAA,MAAA,GAAA,KAAA,GAAA,MAAA,GAAA,KAAA;EAgBW,OAAA,CAAA,EAAA,MAAA;;;;;UA3LL,eAAA;SACR;;;;;;;iBAQa,oBAAA,QACb,iBACN,QAAQ;;;;iBAmDW,qBAAA,QACb;;IAEN,QAAQ;;;;iBA2BW,mBAAA,QAA2B,gBAAgB,QAAQ;;;;iBA6BnD,kBAAA,QAA0B,eAAe,QAAQ;;;;iBAwBjD,mBAAA,QAA2B,gBAAgB,QAAQ;;;;iBAwBnD,0BAAA;;IAElB,QAAQ,eAAe;;;;iBAgBL,4BAAA;;IAErB,QAFiD,SAAA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MOCK_DEALS, MOCK_STAGES } from "./mock-data.js";
|
|
2
|
+
|
|
3
|
+
//#region src/handlers/deal.handlers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Mock handlers for Deal contracts
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Mock handler for ListDealsContract
|
|
9
|
+
*/
|
|
10
|
+
async function mockListDealsHandler(input) {
|
|
11
|
+
const { pipelineId, stageId, status, ownerId, search, limit = 20, offset = 0 } = input;
|
|
12
|
+
let filtered = [...MOCK_DEALS];
|
|
13
|
+
if (pipelineId) filtered = filtered.filter((d) => d.pipelineId === pipelineId);
|
|
14
|
+
if (stageId) filtered = filtered.filter((d) => d.stageId === stageId);
|
|
15
|
+
if (status && status !== "all") filtered = filtered.filter((d) => d.status === status);
|
|
16
|
+
if (ownerId) filtered = filtered.filter((d) => d.ownerId === ownerId);
|
|
17
|
+
if (search) {
|
|
18
|
+
const q = search.toLowerCase();
|
|
19
|
+
filtered = filtered.filter((d) => d.name.toLowerCase().includes(q));
|
|
20
|
+
}
|
|
21
|
+
filtered.sort((a, b) => b.value - a.value);
|
|
22
|
+
const total = filtered.length;
|
|
23
|
+
const totalValue = filtered.reduce((sum, d) => sum + d.value, 0);
|
|
24
|
+
return {
|
|
25
|
+
deals: filtered.slice(offset, offset + limit),
|
|
26
|
+
total,
|
|
27
|
+
totalValue
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Mock handler for CreateDealContract
|
|
32
|
+
*/
|
|
33
|
+
async function mockCreateDealHandler(input, context) {
|
|
34
|
+
const now = /* @__PURE__ */ new Date();
|
|
35
|
+
const deal = {
|
|
36
|
+
id: `deal-${Date.now()}`,
|
|
37
|
+
name: input.name,
|
|
38
|
+
value: input.value,
|
|
39
|
+
currency: input.currency ?? "USD",
|
|
40
|
+
pipelineId: input.pipelineId,
|
|
41
|
+
stageId: input.stageId,
|
|
42
|
+
status: "OPEN",
|
|
43
|
+
contactId: input.contactId,
|
|
44
|
+
companyId: input.companyId,
|
|
45
|
+
ownerId: context.ownerId,
|
|
46
|
+
expectedCloseDate: input.expectedCloseDate,
|
|
47
|
+
createdAt: now,
|
|
48
|
+
updatedAt: now
|
|
49
|
+
};
|
|
50
|
+
MOCK_DEALS.push(deal);
|
|
51
|
+
return deal;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mock handler for MoveDealContract
|
|
55
|
+
*/
|
|
56
|
+
async function mockMoveDealHandler(input) {
|
|
57
|
+
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
58
|
+
if (dealIndex === -1) throw new Error("NOT_FOUND");
|
|
59
|
+
const deal = MOCK_DEALS[dealIndex];
|
|
60
|
+
if (!deal) throw new Error("NOT_FOUND");
|
|
61
|
+
if (!MOCK_STAGES.find((s) => s.id === input.stageId)) throw new Error("INVALID_STAGE");
|
|
62
|
+
const updatedDeal = {
|
|
63
|
+
...deal,
|
|
64
|
+
stageId: input.stageId,
|
|
65
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
66
|
+
};
|
|
67
|
+
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
68
|
+
return updatedDeal;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Mock handler for WinDealContract
|
|
72
|
+
*/
|
|
73
|
+
async function mockWinDealHandler(input) {
|
|
74
|
+
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
75
|
+
if (dealIndex === -1) throw new Error("NOT_FOUND");
|
|
76
|
+
const deal = MOCK_DEALS[dealIndex];
|
|
77
|
+
if (!deal) throw new Error("NOT_FOUND");
|
|
78
|
+
const updatedDeal = {
|
|
79
|
+
...deal,
|
|
80
|
+
status: "WON",
|
|
81
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
82
|
+
};
|
|
83
|
+
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
84
|
+
return updatedDeal;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Mock handler for LoseDealContract
|
|
88
|
+
*/
|
|
89
|
+
async function mockLoseDealHandler(input) {
|
|
90
|
+
const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);
|
|
91
|
+
if (dealIndex === -1) throw new Error("NOT_FOUND");
|
|
92
|
+
const deal = MOCK_DEALS[dealIndex];
|
|
93
|
+
if (!deal) throw new Error("NOT_FOUND");
|
|
94
|
+
const updatedDeal = {
|
|
95
|
+
...deal,
|
|
96
|
+
status: "LOST",
|
|
97
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
98
|
+
};
|
|
99
|
+
MOCK_DEALS[dealIndex] = updatedDeal;
|
|
100
|
+
return updatedDeal;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get deals grouped by stage for Kanban view
|
|
104
|
+
*/
|
|
105
|
+
async function mockGetDealsByStageHandler(input) {
|
|
106
|
+
const deals = MOCK_DEALS.filter((d) => d.pipelineId === input.pipelineId && d.status === "OPEN");
|
|
107
|
+
const grouped = {};
|
|
108
|
+
for (const stage of MOCK_STAGES) grouped[stage.id] = deals.filter((d) => d.stageId === stage.id);
|
|
109
|
+
return grouped;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get pipeline stages
|
|
113
|
+
*/
|
|
114
|
+
async function mockGetPipelineStagesHandler(input) {
|
|
115
|
+
return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
export { mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler };
|
|
120
|
+
//# sourceMappingURL=deal.handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deal.handlers.js","names":[],"sources":["../../src/handlers/deal.handlers.ts"],"sourcesContent":["/**\n * Mock handlers for Deal contracts\n */\nimport { MOCK_DEALS, MOCK_STAGES } from './mock-data';\n\n// Types inferred from contract schemas\nexport interface Deal {\n id: string;\n name: string;\n value: number;\n currency: string;\n pipelineId: string;\n stageId: string;\n status: 'OPEN' | 'WON' | 'LOST' | 'STALE';\n contactId?: string;\n companyId?: string;\n ownerId: string;\n expectedCloseDate?: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface CreateDealInput {\n name: string;\n value: number;\n currency?: string;\n pipelineId: string;\n stageId: string;\n contactId?: string;\n companyId?: string;\n expectedCloseDate?: Date;\n}\n\nexport interface MoveDealInput {\n dealId: string;\n stageId: string;\n position?: number;\n}\n\nexport interface WinDealInput {\n dealId: string;\n wonSource?: string;\n notes?: string;\n}\n\nexport interface LoseDealInput {\n dealId: string;\n lostReason: string;\n notes?: string;\n}\n\nexport interface ListDealsInput {\n pipelineId?: string;\n stageId?: string;\n status?: 'OPEN' | 'WON' | 'LOST' | 'all';\n ownerId?: string;\n search?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface ListDealsOutput {\n deals: Deal[];\n total: number;\n totalValue: number;\n}\n\n/**\n * Mock handler for ListDealsContract\n */\nexport async function mockListDealsHandler(\n input: ListDealsInput\n): Promise<ListDealsOutput> {\n const {\n pipelineId,\n stageId,\n status,\n ownerId,\n search,\n limit = 20,\n offset = 0,\n } = input;\n\n let filtered = [...MOCK_DEALS];\n\n if (pipelineId) {\n filtered = filtered.filter((d) => d.pipelineId === pipelineId);\n }\n\n if (stageId) {\n filtered = filtered.filter((d) => d.stageId === stageId);\n }\n\n if (status && status !== 'all') {\n filtered = filtered.filter((d) => d.status === status);\n }\n\n if (ownerId) {\n filtered = filtered.filter((d) => d.ownerId === ownerId);\n }\n\n if (search) {\n const q = search.toLowerCase();\n filtered = filtered.filter((d) => d.name.toLowerCase().includes(q));\n }\n\n // Sort by value descending\n filtered.sort((a, b) => b.value - a.value);\n\n const total = filtered.length;\n const totalValue = filtered.reduce((sum, d) => sum + d.value, 0);\n const deals = filtered.slice(offset, offset + limit);\n\n return {\n deals,\n total,\n totalValue,\n };\n}\n\n/**\n * Mock handler for CreateDealContract\n */\nexport async function mockCreateDealHandler(\n input: CreateDealInput,\n context: { ownerId: string }\n): Promise<Deal> {\n const now = new Date();\n\n const deal: Deal = {\n id: `deal-${Date.now()}`,\n name: input.name,\n value: input.value,\n currency: input.currency ?? 'USD',\n pipelineId: input.pipelineId,\n stageId: input.stageId,\n status: 'OPEN',\n contactId: input.contactId,\n companyId: input.companyId,\n ownerId: context.ownerId,\n expectedCloseDate: input.expectedCloseDate,\n createdAt: now,\n updatedAt: now,\n };\n\n MOCK_DEALS.push(deal);\n\n return deal;\n}\n\n/**\n * Mock handler for MoveDealContract\n */\nexport async function mockMoveDealHandler(input: MoveDealInput): Promise<Deal> {\n const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);\n if (dealIndex === -1) {\n throw new Error('NOT_FOUND');\n }\n const deal = MOCK_DEALS[dealIndex];\n if (!deal) {\n throw new Error('NOT_FOUND');\n }\n\n const stage = MOCK_STAGES.find((s) => s.id === input.stageId);\n if (!stage) {\n throw new Error('INVALID_STAGE');\n }\n\n const updatedDeal: Deal = {\n ...deal,\n stageId: input.stageId,\n updatedAt: new Date(),\n };\n\n MOCK_DEALS[dealIndex] = updatedDeal;\n\n return updatedDeal;\n}\n\n/**\n * Mock handler for WinDealContract\n */\nexport async function mockWinDealHandler(input: WinDealInput): Promise<Deal> {\n const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);\n if (dealIndex === -1) {\n throw new Error('NOT_FOUND');\n }\n const deal = MOCK_DEALS[dealIndex];\n if (!deal) {\n throw new Error('NOT_FOUND');\n }\n\n const updatedDeal: Deal = {\n ...deal,\n status: 'WON' as const,\n updatedAt: new Date(),\n };\n\n MOCK_DEALS[dealIndex] = updatedDeal;\n\n return updatedDeal;\n}\n\n/**\n * Mock handler for LoseDealContract\n */\nexport async function mockLoseDealHandler(input: LoseDealInput): Promise<Deal> {\n const dealIndex = MOCK_DEALS.findIndex((d) => d.id === input.dealId);\n if (dealIndex === -1) {\n throw new Error('NOT_FOUND');\n }\n const deal = MOCK_DEALS[dealIndex];\n if (!deal) {\n throw new Error('NOT_FOUND');\n }\n\n const updatedDeal: Deal = {\n ...deal,\n status: 'LOST' as const,\n updatedAt: new Date(),\n };\n\n MOCK_DEALS[dealIndex] = updatedDeal;\n\n return updatedDeal;\n}\n\n/**\n * Get deals grouped by stage for Kanban view\n */\nexport async function mockGetDealsByStageHandler(input: {\n pipelineId: string;\n}): Promise<Record<string, Deal[]>> {\n const deals = MOCK_DEALS.filter(\n (d) => d.pipelineId === input.pipelineId && d.status === 'OPEN'\n );\n\n const grouped: Record<string, Deal[]> = {};\n for (const stage of MOCK_STAGES) {\n grouped[stage.id] = deals.filter((d) => d.stageId === stage.id);\n }\n\n return grouped;\n}\n\n/**\n * Get pipeline stages\n */\nexport async function mockGetPipelineStagesHandler(input: {\n pipelineId: string;\n}) {\n return MOCK_STAGES.filter((s) => s.pipelineId === input.pipelineId);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CrmHandlers, Stage, createCrmHandlers } from "./crm.handlers.js";
|
|
2
|
+
import { MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES, MockDeal, MockStage } from "./mock-data.js";
|
|
3
|
+
import { CreateDealInput, Deal, ListDealsInput, ListDealsOutput, LoseDealInput, MoveDealInput, WinDealInput, mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler } from "./deal.handlers.js";
|
|
4
|
+
export { type CreateDealInput, CrmHandlers, type Deal, type ListDealsInput, type ListDealsOutput, type LoseDealInput, MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES, MockDeal, MockStage, type MoveDealInput, Stage, type WinDealInput, createCrmHandlers, mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES } from "./mock-data.js";
|
|
2
|
+
import { mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler } from "./deal.handlers.js";
|
|
3
|
+
import { createCrmHandlers } from "./crm.handlers.js";
|
|
4
|
+
|
|
5
|
+
export { MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES, createCrmHandlers, mockCreateDealHandler, mockGetDealsByStageHandler, mockGetPipelineStagesHandler, mockListDealsHandler, mockLoseDealHandler, mockMoveDealHandler, mockWinDealHandler };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/handlers/mock-data.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Mock data for crm-pipeline handlers
|
|
4
|
+
*/
|
|
5
|
+
interface MockDeal {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
value: number;
|
|
9
|
+
currency: string;
|
|
10
|
+
pipelineId: string;
|
|
11
|
+
stageId: string;
|
|
12
|
+
status: 'OPEN' | 'WON' | 'LOST' | 'STALE';
|
|
13
|
+
contactId?: string;
|
|
14
|
+
companyId?: string;
|
|
15
|
+
ownerId: string;
|
|
16
|
+
expectedCloseDate?: Date;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
updatedAt: Date;
|
|
19
|
+
}
|
|
20
|
+
interface MockStage {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
position: number;
|
|
24
|
+
pipelineId: string;
|
|
25
|
+
}
|
|
26
|
+
declare const MOCK_STAGES: MockStage[];
|
|
27
|
+
declare const MOCK_DEALS: MockDeal[];
|
|
28
|
+
declare const MOCK_COMPANIES: {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
domain: string;
|
|
32
|
+
industry: string;
|
|
33
|
+
size: string;
|
|
34
|
+
website: string;
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
}[];
|
|
37
|
+
declare const MOCK_CONTACTS: {
|
|
38
|
+
id: string;
|
|
39
|
+
firstName: string;
|
|
40
|
+
lastName: string;
|
|
41
|
+
email: string;
|
|
42
|
+
phone: string;
|
|
43
|
+
title: string;
|
|
44
|
+
companyId: string;
|
|
45
|
+
createdAt: Date;
|
|
46
|
+
}[];
|
|
47
|
+
//#endregion
|
|
48
|
+
export { MOCK_COMPANIES, MOCK_CONTACTS, MOCK_DEALS, MOCK_STAGES, MockDeal, MockStage };
|
|
49
|
+
//# sourceMappingURL=mock-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-data.d.ts","names":[],"sources":["../../src/handlers/mock-data.ts"],"sourcesContent":[],"mappings":";;AAMA;;AAYa,UAZI,QAAA,CAYJ;EACA,EAAA,EAAA,MAAA;EAAI,IAAA,EAAA,MAAA;EAGA,KAAA,EAAA,MAAS;EASb,QAAA,EAAA,MAMZ;EAIY,UAAA,EAAA,MAyFZ;EAIY,OAAA,EAAA,MAAA;EAgCA,MAAA,EAAA,MAAA,GA+BZ,KAAA,GAAA,MAAA,GAAA,OAAA;;;;sBApLqB;aACT;aACA;;UAGI,SAAA;;;;;;cASJ,aAAa;cAUb,YAAY;cA6FZ;;;;;;;aA4BZ;;cAIY;;;;;;;;aA+BZ"}
|