@classytic/crm 0.1.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.
- package/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/index.d.ts +1420 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1943 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1420 @@
|
|
|
1
|
+
import { DomainEvent, EventHandler, EventTransport } from "@classytic/primitives/events";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { OutboxStore } from "@classytic/primitives/outbox";
|
|
4
|
+
import { ContactInfo, PersonName } from "@classytic/primitives/person";
|
|
5
|
+
import { Money } from "@classytic/primitives/money";
|
|
6
|
+
|
|
7
|
+
//#region src/domain/entities/account.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* An account (company / organization). Contacts, Opportunities, and
|
|
10
|
+
* Activities can reference it.
|
|
11
|
+
*/
|
|
12
|
+
interface Account {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
/** Domain / website — used by host deduplication pipelines. */
|
|
16
|
+
domain?: string;
|
|
17
|
+
industry?: string;
|
|
18
|
+
/** Employee-count bucket, e.g. `1-10`, `11-50`. Free-form for flexibility. */
|
|
19
|
+
sizeBucket?: string;
|
|
20
|
+
annualRevenue?: {
|
|
21
|
+
amount: number;
|
|
22
|
+
currency: string;
|
|
23
|
+
};
|
|
24
|
+
ownerId?: string;
|
|
25
|
+
tags?: readonly string[];
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
createdAt: Date;
|
|
28
|
+
updatedAt: Date;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/domain/ports/account-repository.port.d.ts
|
|
32
|
+
interface AccountFilter {
|
|
33
|
+
ownerId?: string;
|
|
34
|
+
industry?: string;
|
|
35
|
+
domain?: string;
|
|
36
|
+
tags?: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
interface AccountRepository {
|
|
39
|
+
findById(id: string): Promise<Account | null>;
|
|
40
|
+
findByDomain(domain: string): Promise<Account | null>;
|
|
41
|
+
list(filter?: AccountFilter): Promise<Account[]>;
|
|
42
|
+
create(a: Omit<Account, 'createdAt' | 'updatedAt'>): Promise<Account>;
|
|
43
|
+
update(id: string, patch: Partial<Account>): Promise<Account>;
|
|
44
|
+
delete(id: string): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/domain/ports/clock.port.d.ts
|
|
48
|
+
interface Clock {
|
|
49
|
+
now(): Date;
|
|
50
|
+
}
|
|
51
|
+
declare const systemClock: Clock;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/domain/ports/id-generator.port.d.ts
|
|
54
|
+
interface IdGenerator {
|
|
55
|
+
next(): string;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/services/mutation-options.d.ts
|
|
59
|
+
/**
|
|
60
|
+
* Options passed to every service mutation. All fields optional — hosts
|
|
61
|
+
* opt into the behaviours they need.
|
|
62
|
+
*/
|
|
63
|
+
interface MutationOptions {
|
|
64
|
+
/**
|
|
65
|
+
* When `false`, skip event emission for this call. Useful for bulk seeds
|
|
66
|
+
* and when a higher-level orchestration verb composes several services
|
|
67
|
+
* and only wants the outer event to fire (see `LeadService.convert`).
|
|
68
|
+
*/
|
|
69
|
+
emitEvents?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Host-provided DB transaction handle (mongoose `ClientSession`,
|
|
72
|
+
* Prisma tx, Postgres client with `BEGIN`, etc.). Typed as `unknown`
|
|
73
|
+
* to keep crm framework-agnostic.
|
|
74
|
+
*
|
|
75
|
+
* Forwarded to:
|
|
76
|
+
* - The host's repository adapter (if it reads `options.session`) so
|
|
77
|
+
* the business write joins the transaction.
|
|
78
|
+
* - `outbox.save(event, { session })` so the outbox row commits
|
|
79
|
+
* atomically with the business write — PACKAGE_RULES §5.5 + P8's
|
|
80
|
+
* session-bound dispatch pattern.
|
|
81
|
+
*
|
|
82
|
+
* Wrap the CRM service call in your own transaction:
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* const session = await mongoose.connection.startSession();
|
|
86
|
+
* try {
|
|
87
|
+
* await session.withTransaction(async () => {
|
|
88
|
+
* await accountService.create({ name: 'Acme' }, { session });
|
|
89
|
+
* });
|
|
90
|
+
* } finally {
|
|
91
|
+
* await session.endSession();
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
session?: unknown;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/services/account.service.d.ts
|
|
99
|
+
interface AccountServiceConfig {
|
|
100
|
+
repo: AccountRepository;
|
|
101
|
+
events?: EventTransport | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Optional host-owned outbox. See `emitCrmEvent` for the dispatch contract:
|
|
104
|
+
* outbox save (session-bound when `options.session` is passed) is issued
|
|
105
|
+
* before the transport publish so durable at-least-once delivery works
|
|
106
|
+
* even when the transport is down.
|
|
107
|
+
*/
|
|
108
|
+
outbox?: OutboxStore | undefined;
|
|
109
|
+
clock?: Clock;
|
|
110
|
+
ids?: IdGenerator;
|
|
111
|
+
}
|
|
112
|
+
interface CreateAccountInput {
|
|
113
|
+
name: string;
|
|
114
|
+
domain?: string;
|
|
115
|
+
industry?: string;
|
|
116
|
+
sizeBucket?: string;
|
|
117
|
+
annualRevenue?: {
|
|
118
|
+
amount: number;
|
|
119
|
+
currency: string;
|
|
120
|
+
};
|
|
121
|
+
ownerId?: string;
|
|
122
|
+
tags?: readonly string[];
|
|
123
|
+
metadata?: Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
declare class AccountService {
|
|
126
|
+
private readonly repo;
|
|
127
|
+
private readonly events;
|
|
128
|
+
private readonly outbox;
|
|
129
|
+
private readonly clock;
|
|
130
|
+
private readonly ids;
|
|
131
|
+
constructor(config: AccountServiceConfig);
|
|
132
|
+
create(input: CreateAccountInput, options?: MutationOptions): Promise<Account>;
|
|
133
|
+
update(id: string, patch: Partial<Account>, options?: MutationOptions): Promise<Account>;
|
|
134
|
+
delete(id: string): Promise<void>;
|
|
135
|
+
findById(id: string): Promise<Account | null>;
|
|
136
|
+
findByDomain(domain: string): Promise<Account | null>;
|
|
137
|
+
list(filter?: Parameters<AccountRepository['list']>[0]): Promise<Account[]>;
|
|
138
|
+
private _require;
|
|
139
|
+
private _emit;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/domain/enums/activity.d.ts
|
|
143
|
+
/**
|
|
144
|
+
* Activity type vocabulary. The package stores `type` as free-form
|
|
145
|
+
* string so hosts can extend; these constants cover the common
|
|
146
|
+
* channels that automation tooling typically keys off.
|
|
147
|
+
*/
|
|
148
|
+
declare const ACTIVITY_TYPES: readonly ["call", "email", "meeting", "task", "note", "sms", "social", "demo", "other"];
|
|
149
|
+
type ActivityType = (typeof ACTIVITY_TYPES)[number];
|
|
150
|
+
declare const ACTIVITY_STATUSES: readonly ["planned", "in_progress", "completed", "cancelled"];
|
|
151
|
+
type ActivityStatus = (typeof ACTIVITY_STATUSES)[number];
|
|
152
|
+
declare const ACTIVITY_TRANSITIONS: Record<ActivityStatus, readonly ActivityStatus[]>;
|
|
153
|
+
declare function canTransitionActivity(from: ActivityStatus, to: ActivityStatus): boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Kinds of entity an Activity can reference. Polymorphic linking lets
|
|
156
|
+
* hosts log a call against a Lead, an Opportunity, a Contact, or a
|
|
157
|
+
* stand-alone Account.
|
|
158
|
+
*/
|
|
159
|
+
declare const SUBJECT_KINDS: readonly ["lead", "opportunity", "contact", "account"];
|
|
160
|
+
type SubjectKind = (typeof SUBJECT_KINDS)[number];
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/domain/entities/activity.d.ts
|
|
163
|
+
/**
|
|
164
|
+
* An activity is a polymorphic record of outreach / task / event
|
|
165
|
+
* attached to any CRM entity. `subjectKind` + `subjectId` define the
|
|
166
|
+
* link — this is how hosts build automation atop the package (subscribe
|
|
167
|
+
* to `crm:activity.*` events, filter by `subjectKind`, react).
|
|
168
|
+
*/
|
|
169
|
+
interface Activity {
|
|
170
|
+
id: string;
|
|
171
|
+
type: ActivityType | string;
|
|
172
|
+
status: ActivityStatus;
|
|
173
|
+
subjectKind: SubjectKind;
|
|
174
|
+
subjectId: string;
|
|
175
|
+
subject?: string;
|
|
176
|
+
body?: string;
|
|
177
|
+
/** When the activity is / was scheduled to happen. */
|
|
178
|
+
scheduledAt?: Date;
|
|
179
|
+
completedAt?: Date;
|
|
180
|
+
cancelledAt?: Date;
|
|
181
|
+
/** User-level ownership (call made by / task assigned to). */
|
|
182
|
+
ownerId?: string;
|
|
183
|
+
participantIds?: readonly string[];
|
|
184
|
+
/**
|
|
185
|
+
* Optional external ref — e.g. Calendar event id, email thread id,
|
|
186
|
+
* call-recording url. Host-owned semantics.
|
|
187
|
+
*/
|
|
188
|
+
externalRef?: string;
|
|
189
|
+
metadata?: Record<string, unknown>;
|
|
190
|
+
createdAt: Date;
|
|
191
|
+
updatedAt: Date;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/domain/entities/note.d.ts
|
|
195
|
+
/**
|
|
196
|
+
* A free-text note on a CRM entity. Separate from Activity because
|
|
197
|
+
* notes aren't status-tracked — they're typed once and stay; they're
|
|
198
|
+
* also the cheapest "activity log" entry to create.
|
|
199
|
+
*/
|
|
200
|
+
interface Note {
|
|
201
|
+
id: string;
|
|
202
|
+
subjectKind: SubjectKind;
|
|
203
|
+
subjectId: string;
|
|
204
|
+
body: string;
|
|
205
|
+
authorId?: string;
|
|
206
|
+
/** Supports @-mentions, Markdown, etc. — host-interpreted. */
|
|
207
|
+
format?: 'plain' | 'markdown';
|
|
208
|
+
metadata?: Record<string, unknown>;
|
|
209
|
+
createdAt: Date;
|
|
210
|
+
updatedAt: Date;
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/domain/ports/activity-repository.port.d.ts
|
|
214
|
+
interface ActivityFilter {
|
|
215
|
+
subjectKind?: SubjectKind;
|
|
216
|
+
subjectId?: string;
|
|
217
|
+
status?: ActivityStatus | readonly ActivityStatus[];
|
|
218
|
+
ownerId?: string;
|
|
219
|
+
scheduledBetween?: {
|
|
220
|
+
from: Date;
|
|
221
|
+
to: Date;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
interface ActivityRepository {
|
|
225
|
+
findById(id: string): Promise<Activity | null>;
|
|
226
|
+
list(filter?: ActivityFilter): Promise<Activity[]>;
|
|
227
|
+
create(a: Omit<Activity, 'createdAt' | 'updatedAt'>): Promise<Activity>;
|
|
228
|
+
update(id: string, patch: Partial<Activity>): Promise<Activity>;
|
|
229
|
+
casStatus(id: string, expected: ActivityStatus, next: ActivityStatus, patch?: Partial<Activity>): Promise<Activity | null>;
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/domain/ports/note-repository.port.d.ts
|
|
233
|
+
interface NoteRepository {
|
|
234
|
+
findById(id: string): Promise<Note | null>;
|
|
235
|
+
listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
|
|
236
|
+
create(n: Omit<Note, 'createdAt' | 'updatedAt'>): Promise<Note>;
|
|
237
|
+
update(id: string, patch: Partial<Note>): Promise<Note>;
|
|
238
|
+
delete(id: string): Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/services/activity.service.d.ts
|
|
242
|
+
interface ActivityServiceConfig {
|
|
243
|
+
repo: ActivityRepository;
|
|
244
|
+
notes: NoteRepository;
|
|
245
|
+
events?: EventTransport | undefined;
|
|
246
|
+
/** Optional host-owned outbox. See AccountServiceConfig.outbox. */
|
|
247
|
+
outbox?: OutboxStore | undefined;
|
|
248
|
+
clock?: Clock;
|
|
249
|
+
ids?: IdGenerator;
|
|
250
|
+
}
|
|
251
|
+
interface CreateActivityInput {
|
|
252
|
+
type: ActivityType | string;
|
|
253
|
+
subjectKind: SubjectKind;
|
|
254
|
+
subjectId: string;
|
|
255
|
+
subject?: string;
|
|
256
|
+
body?: string;
|
|
257
|
+
scheduledAt?: Date;
|
|
258
|
+
ownerId?: string;
|
|
259
|
+
participantIds?: readonly string[];
|
|
260
|
+
externalRef?: string;
|
|
261
|
+
metadata?: Record<string, unknown>;
|
|
262
|
+
}
|
|
263
|
+
interface CreateNoteInput {
|
|
264
|
+
subjectKind: SubjectKind;
|
|
265
|
+
subjectId: string;
|
|
266
|
+
body: string;
|
|
267
|
+
authorId?: string;
|
|
268
|
+
format?: 'plain' | 'markdown';
|
|
269
|
+
metadata?: Record<string, unknown>;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Polymorphic activity / note service. Activities have a lifecycle
|
|
273
|
+
* (`planned → in_progress → completed | cancelled`); notes are
|
|
274
|
+
* immutable-ish (create + update body, no status).
|
|
275
|
+
*/
|
|
276
|
+
declare class ActivityService {
|
|
277
|
+
private readonly repo;
|
|
278
|
+
private readonly noteRepo;
|
|
279
|
+
private readonly events;
|
|
280
|
+
private readonly outbox;
|
|
281
|
+
private readonly clock;
|
|
282
|
+
private readonly ids;
|
|
283
|
+
constructor(config: ActivityServiceConfig);
|
|
284
|
+
plan(input: CreateActivityInput, options?: MutationOptions): Promise<Activity>;
|
|
285
|
+
start(id: string, options?: MutationOptions): Promise<Activity>;
|
|
286
|
+
complete(id: string, completedAt?: Date, options?: MutationOptions): Promise<Activity>;
|
|
287
|
+
cancel(id: string, reason?: string, options?: MutationOptions): Promise<Activity>;
|
|
288
|
+
addNote(input: CreateNoteInput, options?: MutationOptions): Promise<Note>;
|
|
289
|
+
listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Activity[]>;
|
|
290
|
+
listNotesBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
|
|
291
|
+
findById(id: string): Promise<Activity | null>;
|
|
292
|
+
private _transition;
|
|
293
|
+
private _emit;
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/domain/entities/contact.d.ts
|
|
297
|
+
/**
|
|
298
|
+
* A person — the atom of a CRM. Contacts may belong to an Account,
|
|
299
|
+
* but can also exist stand-alone (independent consultants, consumers).
|
|
300
|
+
*
|
|
301
|
+
* Structured `name` + `contact` come from `@classytic/primitives/person`.
|
|
302
|
+
* Hosts projecting over a commerce Customer record embed the same shapes —
|
|
303
|
+
* no translation needed at the boundary.
|
|
304
|
+
*/
|
|
305
|
+
interface Contact {
|
|
306
|
+
id: string;
|
|
307
|
+
name: PersonName;
|
|
308
|
+
contact: ContactInfo;
|
|
309
|
+
jobTitle?: string;
|
|
310
|
+
accountId?: string;
|
|
311
|
+
ownerId?: string;
|
|
312
|
+
/** Free-form tags for segmentation. */
|
|
313
|
+
tags?: readonly string[];
|
|
314
|
+
/** Host-owned arbitrary payload (social handles, custom fields, etc.). */
|
|
315
|
+
metadata?: Record<string, unknown>;
|
|
316
|
+
createdAt: Date;
|
|
317
|
+
updatedAt: Date;
|
|
318
|
+
}
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/domain/ports/contact-repository.port.d.ts
|
|
321
|
+
interface ContactFilter {
|
|
322
|
+
accountId?: string;
|
|
323
|
+
ownerId?: string;
|
|
324
|
+
email?: string;
|
|
325
|
+
tags?: readonly string[];
|
|
326
|
+
}
|
|
327
|
+
interface ContactRepository {
|
|
328
|
+
findById(id: string): Promise<Contact | null>;
|
|
329
|
+
findByEmail(email: string): Promise<Contact | null>;
|
|
330
|
+
list(filter?: ContactFilter): Promise<Contact[]>;
|
|
331
|
+
create(c: Omit<Contact, 'createdAt' | 'updatedAt'>): Promise<Contact>;
|
|
332
|
+
update(id: string, patch: Partial<Contact>): Promise<Contact>;
|
|
333
|
+
delete(id: string): Promise<void>;
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/services/contact.service.d.ts
|
|
337
|
+
interface ContactServiceConfig {
|
|
338
|
+
repo: ContactRepository;
|
|
339
|
+
events?: EventTransport | undefined;
|
|
340
|
+
/** Optional host-owned outbox. See AccountServiceConfig.outbox. */
|
|
341
|
+
outbox?: OutboxStore | undefined;
|
|
342
|
+
clock?: Clock;
|
|
343
|
+
ids?: IdGenerator;
|
|
344
|
+
}
|
|
345
|
+
interface CreateContactInput {
|
|
346
|
+
name: PersonName;
|
|
347
|
+
contact?: ContactInfo;
|
|
348
|
+
jobTitle?: string;
|
|
349
|
+
accountId?: string;
|
|
350
|
+
ownerId?: string;
|
|
351
|
+
tags?: readonly string[];
|
|
352
|
+
metadata?: Record<string, unknown>;
|
|
353
|
+
}
|
|
354
|
+
declare class ContactService {
|
|
355
|
+
private readonly repo;
|
|
356
|
+
private readonly events;
|
|
357
|
+
private readonly outbox;
|
|
358
|
+
private readonly clock;
|
|
359
|
+
private readonly ids;
|
|
360
|
+
constructor(config: ContactServiceConfig);
|
|
361
|
+
create(input: CreateContactInput, options?: MutationOptions): Promise<Contact>;
|
|
362
|
+
update(id: string, patch: Partial<Contact>, options?: MutationOptions): Promise<Contact>;
|
|
363
|
+
delete(id: string): Promise<void>;
|
|
364
|
+
findById(id: string): Promise<Contact | null>;
|
|
365
|
+
findByEmail(email: string): Promise<Contact | null>;
|
|
366
|
+
list(filter?: Parameters<ContactRepository['list']>[0]): Promise<Contact[]>;
|
|
367
|
+
private _require;
|
|
368
|
+
private _emit;
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/domain/enums/lead-status.d.ts
|
|
372
|
+
/**
|
|
373
|
+
* Lead lifecycle — from initial inquiry through conversion.
|
|
374
|
+
*
|
|
375
|
+
* new — just captured (web form, import, referral)
|
|
376
|
+
* contacted — SDR has reached out
|
|
377
|
+
* qualified — meets ICP / BANT
|
|
378
|
+
* converted — turned into Contact + Account + Opportunity
|
|
379
|
+
* disqualified — rejected (bad fit, spam, duplicate)
|
|
380
|
+
* nurturing — not ready now; parked for later outreach
|
|
381
|
+
*/
|
|
382
|
+
declare const LEAD_STATUSES: readonly ["new", "contacted", "qualified", "converted", "disqualified", "nurturing"];
|
|
383
|
+
type LeadStatus = (typeof LEAD_STATUSES)[number];
|
|
384
|
+
declare const LEAD_TRANSITIONS: Record<LeadStatus, readonly LeadStatus[]>;
|
|
385
|
+
declare function canTransitionLead(from: LeadStatus, to: LeadStatus): boolean;
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/domain/entities/lead.d.ts
|
|
388
|
+
/**
|
|
389
|
+
* An unqualified inquiry. Leads are the top of the funnel — they
|
|
390
|
+
* don't yet have a dedicated Contact + Account + Opportunity in the
|
|
391
|
+
* system. `LeadService.convert(...)` creates all three and
|
|
392
|
+
* transitions the lead to `converted`.
|
|
393
|
+
*/
|
|
394
|
+
interface Lead {
|
|
395
|
+
id: string;
|
|
396
|
+
/** Name / email / phone / company captured at inquiry time. */
|
|
397
|
+
firstName?: string;
|
|
398
|
+
lastName?: string;
|
|
399
|
+
fullName: string;
|
|
400
|
+
email?: string;
|
|
401
|
+
phone?: string;
|
|
402
|
+
companyName?: string;
|
|
403
|
+
jobTitle?: string;
|
|
404
|
+
/** How the lead arrived (`web-form`, `referral`, `event`, `ads`, …). */
|
|
405
|
+
source?: string;
|
|
406
|
+
/** Free-form campaign identifier — the host's campaign system owns its semantics. */
|
|
407
|
+
campaignRef?: string;
|
|
408
|
+
/**
|
|
409
|
+
* Lead score — updated by `LeadService.recomputeScore(...)` whenever
|
|
410
|
+
* the score function decides. Stored on the record for indexing +
|
|
411
|
+
* routing. Raw score units; range is host-defined.
|
|
412
|
+
*/
|
|
413
|
+
score?: number;
|
|
414
|
+
status: LeadStatus;
|
|
415
|
+
statusHistory: readonly LeadStatusEntry[];
|
|
416
|
+
ownerId?: string;
|
|
417
|
+
tags?: readonly string[];
|
|
418
|
+
/** On successful `convert`, links to the resulting records. */
|
|
419
|
+
convertedContactId?: string;
|
|
420
|
+
convertedAccountId?: string;
|
|
421
|
+
convertedOpportunityId?: string;
|
|
422
|
+
convertedAt?: Date;
|
|
423
|
+
/** Disqualification detail. */
|
|
424
|
+
disqualifyReason?: string;
|
|
425
|
+
metadata?: Record<string, unknown>;
|
|
426
|
+
createdAt: Date;
|
|
427
|
+
updatedAt: Date;
|
|
428
|
+
}
|
|
429
|
+
interface LeadStatusEntry {
|
|
430
|
+
status: LeadStatus;
|
|
431
|
+
occurredAt: Date;
|
|
432
|
+
by?: string;
|
|
433
|
+
note?: string;
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/domain/enums/opportunity-status.d.ts
|
|
437
|
+
/**
|
|
438
|
+
* Top-level opportunity status. Movement through pipeline STAGES
|
|
439
|
+
* happens while status is `open`; `won` / `lost` are terminal
|
|
440
|
+
* outcomes. Re-opening a lost deal is a host-level decision —
|
|
441
|
+
* convention is to clone the opportunity rather than revive the
|
|
442
|
+
* terminal record.
|
|
443
|
+
*/
|
|
444
|
+
declare const OPPORTUNITY_STATUSES: readonly ["open", "won", "lost", "abandoned"];
|
|
445
|
+
type OpportunityStatus = (typeof OPPORTUNITY_STATUSES)[number];
|
|
446
|
+
declare const OPPORTUNITY_TRANSITIONS: Record<OpportunityStatus, readonly OpportunityStatus[]>;
|
|
447
|
+
declare function canTransitionOpportunity(from: OpportunityStatus, to: OpportunityStatus): boolean;
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/domain/entities/opportunity.d.ts
|
|
450
|
+
/**
|
|
451
|
+
* A qualified sales opportunity. Opportunities live inside a Pipeline
|
|
452
|
+
* and progress through Stages. Status captures the outcome
|
|
453
|
+
* (`open` / `won` / `lost` / `abandoned`); stage captures the current
|
|
454
|
+
* step.
|
|
455
|
+
*/
|
|
456
|
+
interface Opportunity {
|
|
457
|
+
id: string;
|
|
458
|
+
name: string;
|
|
459
|
+
accountId?: string;
|
|
460
|
+
primaryContactId?: string;
|
|
461
|
+
/** Which pipeline this opportunity belongs to (sales, partnerships, renewals). */
|
|
462
|
+
pipelineId: string;
|
|
463
|
+
/** Current stage — index INTO the pipeline's `stages` array. */
|
|
464
|
+
stageId: string;
|
|
465
|
+
status: OpportunityStatus;
|
|
466
|
+
statusHistory: readonly OpportunityStatusEntry[];
|
|
467
|
+
/** Host-expected deal value. */
|
|
468
|
+
amount?: Money;
|
|
469
|
+
/**
|
|
470
|
+
* Win probability (0-1). Defaults to the stage's probability at
|
|
471
|
+
* every stage move; host can override for hand-tuned forecasts.
|
|
472
|
+
*/
|
|
473
|
+
probability: number;
|
|
474
|
+
/** When the deal is expected to close. Drives forecast grouping. */
|
|
475
|
+
expectedCloseAt?: Date;
|
|
476
|
+
/** Populated on win/lose. */
|
|
477
|
+
closedAt?: Date;
|
|
478
|
+
lostReasonId?: string;
|
|
479
|
+
ownerId?: string;
|
|
480
|
+
sourceLeadId?: string;
|
|
481
|
+
tags?: readonly string[];
|
|
482
|
+
metadata?: Record<string, unknown>;
|
|
483
|
+
createdAt: Date;
|
|
484
|
+
updatedAt: Date;
|
|
485
|
+
}
|
|
486
|
+
interface OpportunityStatusEntry {
|
|
487
|
+
status: OpportunityStatus;
|
|
488
|
+
stageId?: string;
|
|
489
|
+
occurredAt: Date;
|
|
490
|
+
by?: string;
|
|
491
|
+
note?: string;
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
//#region src/domain/ports/lead-repository.port.d.ts
|
|
495
|
+
interface LeadFilter {
|
|
496
|
+
status?: LeadStatus | readonly LeadStatus[];
|
|
497
|
+
ownerId?: string;
|
|
498
|
+
source?: string;
|
|
499
|
+
minScore?: number;
|
|
500
|
+
}
|
|
501
|
+
interface LeadRepository {
|
|
502
|
+
findById(id: string): Promise<Lead | null>;
|
|
503
|
+
findByEmail(email: string): Promise<Lead | null>;
|
|
504
|
+
list(filter?: LeadFilter): Promise<Lead[]>;
|
|
505
|
+
create(l: Omit<Lead, 'createdAt' | 'updatedAt'>): Promise<Lead>;
|
|
506
|
+
update(id: string, patch: Partial<Lead>): Promise<Lead>;
|
|
507
|
+
casStatus(id: string, expected: LeadStatus, next: LeadStatus, patch?: Partial<Lead>): Promise<Lead | null>;
|
|
508
|
+
}
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/domain/ports/opportunity-repository.port.d.ts
|
|
511
|
+
interface OpportunityFilter {
|
|
512
|
+
status?: OpportunityStatus | readonly OpportunityStatus[];
|
|
513
|
+
pipelineId?: string;
|
|
514
|
+
stageId?: string;
|
|
515
|
+
ownerId?: string;
|
|
516
|
+
accountId?: string;
|
|
517
|
+
closingBetween?: {
|
|
518
|
+
from: Date;
|
|
519
|
+
to: Date;
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
interface OpportunityRepository {
|
|
523
|
+
findById(id: string): Promise<Opportunity | null>;
|
|
524
|
+
list(filter?: OpportunityFilter): Promise<Opportunity[]>;
|
|
525
|
+
create(o: Omit<Opportunity, 'createdAt' | 'updatedAt'>): Promise<Opportunity>;
|
|
526
|
+
update(id: string, patch: Partial<Opportunity>): Promise<Opportunity>;
|
|
527
|
+
delete(id: string): Promise<void>;
|
|
528
|
+
casStatus(id: string, expected: OpportunityStatus, next: OpportunityStatus, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
|
|
529
|
+
casStage(id: string, expectedStageId: string, nextStageId: string, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
|
|
530
|
+
}
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/services/opportunity.service.d.ts
|
|
533
|
+
interface OpportunityPipelinePort {
|
|
534
|
+
findById(id: string): Promise<{
|
|
535
|
+
id: string;
|
|
536
|
+
stages: readonly {
|
|
537
|
+
id: string;
|
|
538
|
+
}[];
|
|
539
|
+
} | null>;
|
|
540
|
+
getStage(pipelineId: string, stageId: string): Promise<{
|
|
541
|
+
id: string;
|
|
542
|
+
sequence: number;
|
|
543
|
+
defaultProbability: number;
|
|
544
|
+
}>;
|
|
545
|
+
getFirstStage(pipelineId: string): Promise<{
|
|
546
|
+
id: string;
|
|
547
|
+
sequence: number;
|
|
548
|
+
defaultProbability: number;
|
|
549
|
+
}>;
|
|
550
|
+
}
|
|
551
|
+
interface OpportunityServiceConfig {
|
|
552
|
+
repo: OpportunityRepository;
|
|
553
|
+
pipelines: OpportunityPipelinePort;
|
|
554
|
+
events?: EventTransport | undefined;
|
|
555
|
+
/** Optional host-owned outbox. See AccountServiceConfig.outbox. */
|
|
556
|
+
outbox?: OutboxStore | undefined;
|
|
557
|
+
clock?: Clock;
|
|
558
|
+
ids?: IdGenerator;
|
|
559
|
+
}
|
|
560
|
+
interface CreateOpportunityInput {
|
|
561
|
+
name: string;
|
|
562
|
+
pipelineId: string;
|
|
563
|
+
/** Starting stage — defaults to the pipeline's first stage. */
|
|
564
|
+
stageId?: string;
|
|
565
|
+
accountId?: string;
|
|
566
|
+
primaryContactId?: string;
|
|
567
|
+
sourceLeadId?: string;
|
|
568
|
+
amount?: Money;
|
|
569
|
+
/** Override the stage default. Defaults to the stage's `defaultProbability`. */
|
|
570
|
+
probability?: number;
|
|
571
|
+
expectedCloseAt?: Date;
|
|
572
|
+
ownerId?: string;
|
|
573
|
+
tags?: readonly string[];
|
|
574
|
+
metadata?: Record<string, unknown>;
|
|
575
|
+
}
|
|
576
|
+
interface MoveStageInput {
|
|
577
|
+
stageId: string;
|
|
578
|
+
/** Override probability. Defaults to the target stage's `defaultProbability`. */
|
|
579
|
+
probability?: number;
|
|
580
|
+
by?: string;
|
|
581
|
+
note?: string;
|
|
582
|
+
}
|
|
583
|
+
interface CloseInput {
|
|
584
|
+
amount?: Money;
|
|
585
|
+
closedAt?: Date;
|
|
586
|
+
by?: string;
|
|
587
|
+
note?: string;
|
|
588
|
+
}
|
|
589
|
+
interface LoseInput extends CloseInput {
|
|
590
|
+
lostReasonId?: string;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Opportunity lifecycle — stage movement + outcome resolution. Stage
|
|
594
|
+
* changes call the pipeline service to validate membership + default
|
|
595
|
+
* probability; status transitions (`won` / `lost` / `abandoned`) are
|
|
596
|
+
* terminal.
|
|
597
|
+
*/
|
|
598
|
+
declare class OpportunityService {
|
|
599
|
+
private readonly repo;
|
|
600
|
+
private readonly pipelines;
|
|
601
|
+
private readonly events;
|
|
602
|
+
private readonly outbox;
|
|
603
|
+
private readonly clock;
|
|
604
|
+
private readonly ids;
|
|
605
|
+
constructor(config: OpportunityServiceConfig);
|
|
606
|
+
create(input: CreateOpportunityInput, options?: MutationOptions): Promise<Opportunity>;
|
|
607
|
+
moveToStage(id: string, input: MoveStageInput, options?: MutationOptions): Promise<Opportunity>;
|
|
608
|
+
win(id: string, input?: CloseInput, options?: MutationOptions): Promise<Opportunity>;
|
|
609
|
+
lose(id: string, input?: LoseInput, options?: MutationOptions): Promise<Opportunity>;
|
|
610
|
+
abandon(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Opportunity>;
|
|
611
|
+
delete(id: string): Promise<void>;
|
|
612
|
+
findById(id: string): Promise<Opportunity | null>;
|
|
613
|
+
list(filter?: Parameters<OpportunityRepository['list']>[0]): Promise<Opportunity[]>;
|
|
614
|
+
private _require;
|
|
615
|
+
private _transitionStatus;
|
|
616
|
+
private _emit;
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
619
|
+
//#region src/services/scoring.d.ts
|
|
620
|
+
/**
|
|
621
|
+
* Pluggable lead scorer. Hosts inject whatever rubric they need —
|
|
622
|
+
* firmographic model, in-app signals, ML prediction, external enrichment.
|
|
623
|
+
*
|
|
624
|
+
* Stay pure: the scorer must not mutate anything, not hit I/O, and
|
|
625
|
+
* be deterministic given its input. Side-effects belong in the
|
|
626
|
+
* caller — `LeadService.recomputeScore` runs the scorer then persists.
|
|
627
|
+
*/
|
|
628
|
+
type LeadScorer = (lead: Lead) => number;
|
|
629
|
+
/**
|
|
630
|
+
* Reference implementation. Intentionally simple so hosts read it and
|
|
631
|
+
* understand the shape, then replace it.
|
|
632
|
+
*
|
|
633
|
+
* +20 if email is present
|
|
634
|
+
* +20 if phone is present
|
|
635
|
+
* +20 if companyName is present
|
|
636
|
+
* +10 per populated tag up to 40
|
|
637
|
+
* +10 if jobTitle looks director/manager/head (case-insensitive)
|
|
638
|
+
*/
|
|
639
|
+
declare function defaultLeadScorer(lead: Lead): number;
|
|
640
|
+
/**
|
|
641
|
+
* Compose a scoring pipeline — each rule returns an increment
|
|
642
|
+
* (positive or negative). Useful when host logic is a bunch of
|
|
643
|
+
* small, testable rules.
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```ts
|
|
647
|
+
* const scorer = composeScorers(
|
|
648
|
+
* (l) => (l.email ? 20 : 0),
|
|
649
|
+
* (l) => (l.source === 'referral' ? 30 : 0),
|
|
650
|
+
* (l) => (l.tags?.includes('enterprise') ? 50 : 0),
|
|
651
|
+
* );
|
|
652
|
+
* ```
|
|
653
|
+
*/
|
|
654
|
+
declare function composeScorers(...rules: readonly LeadScorer[]): LeadScorer;
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region src/services/lead.service.d.ts
|
|
657
|
+
interface LeadAccountWriter {
|
|
658
|
+
create(input: CreateAccountInput, options?: MutationOptions): Promise<Account>;
|
|
659
|
+
delete?(id: string): Promise<void>;
|
|
660
|
+
}
|
|
661
|
+
interface LeadContactWriter {
|
|
662
|
+
create(input: CreateContactInput, options?: MutationOptions): Promise<Contact>;
|
|
663
|
+
delete?(id: string): Promise<void>;
|
|
664
|
+
}
|
|
665
|
+
interface LeadOpportunityWriter {
|
|
666
|
+
create(input: CreateOpportunityInput, options?: MutationOptions): Promise<Opportunity>;
|
|
667
|
+
delete?(id: string): Promise<void>;
|
|
668
|
+
}
|
|
669
|
+
interface LeadServiceConfig {
|
|
670
|
+
repo: LeadRepository;
|
|
671
|
+
contacts: LeadContactWriter;
|
|
672
|
+
accounts: LeadAccountWriter;
|
|
673
|
+
opportunities: LeadOpportunityWriter;
|
|
674
|
+
events?: EventTransport | undefined;
|
|
675
|
+
/** Optional host-owned outbox. See AccountServiceConfig.outbox. */
|
|
676
|
+
outbox?: OutboxStore | undefined;
|
|
677
|
+
clock?: Clock;
|
|
678
|
+
ids?: IdGenerator;
|
|
679
|
+
/** Override the default scorer (`defaultLeadScorer`). */
|
|
680
|
+
scorer?: LeadScorer;
|
|
681
|
+
/** When true (default), `create()` rejects dup emails. */
|
|
682
|
+
rejectDuplicates?: boolean;
|
|
683
|
+
}
|
|
684
|
+
interface CreateLeadInput {
|
|
685
|
+
firstName?: string;
|
|
686
|
+
lastName?: string;
|
|
687
|
+
fullName?: string;
|
|
688
|
+
email?: string;
|
|
689
|
+
phone?: string;
|
|
690
|
+
companyName?: string;
|
|
691
|
+
jobTitle?: string;
|
|
692
|
+
source?: string;
|
|
693
|
+
campaignRef?: string;
|
|
694
|
+
ownerId?: string;
|
|
695
|
+
tags?: readonly string[];
|
|
696
|
+
metadata?: Record<string, unknown>;
|
|
697
|
+
}
|
|
698
|
+
interface ConvertLeadInput {
|
|
699
|
+
/** Pipeline the resulting opportunity enters. */
|
|
700
|
+
pipelineId: string;
|
|
701
|
+
/** Opportunity name. Defaults to `<lead.companyName> — <lead.fullName>`. */
|
|
702
|
+
opportunityName?: string;
|
|
703
|
+
/** Optional deal amount. */
|
|
704
|
+
amount?: Money;
|
|
705
|
+
expectedCloseAt?: Date;
|
|
706
|
+
by?: string;
|
|
707
|
+
}
|
|
708
|
+
interface ConvertResult {
|
|
709
|
+
lead: Lead;
|
|
710
|
+
contactId: string;
|
|
711
|
+
accountId?: string;
|
|
712
|
+
opportunityId: string;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Lead lifecycle + the crown-jewel `convert()` verb that atomically
|
|
716
|
+
* spawns a Contact + Account + Opportunity and transitions the lead
|
|
717
|
+
* to `converted`.
|
|
718
|
+
*/
|
|
719
|
+
declare class LeadService {
|
|
720
|
+
private readonly repo;
|
|
721
|
+
private readonly contacts;
|
|
722
|
+
private readonly accounts;
|
|
723
|
+
private readonly opportunities;
|
|
724
|
+
private readonly events;
|
|
725
|
+
private readonly outbox;
|
|
726
|
+
private readonly clock;
|
|
727
|
+
private readonly ids;
|
|
728
|
+
private readonly scorer;
|
|
729
|
+
private readonly rejectDuplicates;
|
|
730
|
+
constructor(config: LeadServiceConfig);
|
|
731
|
+
create(input: CreateLeadInput, options?: MutationOptions): Promise<Lead>;
|
|
732
|
+
markContacted(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Lead>;
|
|
733
|
+
qualify(id: string, by?: string, note?: string, options?: MutationOptions): Promise<Lead>;
|
|
734
|
+
disqualify(id: string, reason: string, by?: string, options?: MutationOptions): Promise<Lead>;
|
|
735
|
+
nurture(id: string, by?: string, options?: MutationOptions): Promise<Lead>;
|
|
736
|
+
convert(id: string, input: ConvertLeadInput, options?: MutationOptions): Promise<ConvertResult>;
|
|
737
|
+
/** Recompute score and persist. Hosts can call on update or on demand. */
|
|
738
|
+
recomputeScore(id: string, options?: MutationOptions): Promise<Lead>;
|
|
739
|
+
findById(id: string): Promise<Lead | null>;
|
|
740
|
+
list(filter?: Parameters<LeadRepository['list']>[0]): Promise<Lead[]>;
|
|
741
|
+
private _updateScore;
|
|
742
|
+
private _require;
|
|
743
|
+
private _transition;
|
|
744
|
+
private _emit;
|
|
745
|
+
}
|
|
746
|
+
//#endregion
|
|
747
|
+
//#region src/domain/entities/pipeline.d.ts
|
|
748
|
+
/**
|
|
749
|
+
* A Stage in a Pipeline. Stages are ordered (`sequence` ascending).
|
|
750
|
+
* Default win-probability applies at every opportunity-movement unless
|
|
751
|
+
* the caller overrides.
|
|
752
|
+
*/
|
|
753
|
+
interface Stage {
|
|
754
|
+
id: string;
|
|
755
|
+
name: string;
|
|
756
|
+
sequence: number;
|
|
757
|
+
/** Default win probability (0-1) assigned to opportunities entering this stage. */
|
|
758
|
+
defaultProbability: number;
|
|
759
|
+
/** Optional UX hint for color-coded kanbans. */
|
|
760
|
+
color?: string;
|
|
761
|
+
/** Describes what "reaching this stage" means. */
|
|
762
|
+
description?: string;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* A Pipeline groups ordered Stages for a specific sales motion. Hosts
|
|
766
|
+
* typically have 1-N pipelines (e.g. `new-business`, `expansion`,
|
|
767
|
+
* `partnerships`, `renewals`). Opportunities pin to exactly one
|
|
768
|
+
* pipeline at create time.
|
|
769
|
+
*/
|
|
770
|
+
interface Pipeline {
|
|
771
|
+
id: string;
|
|
772
|
+
name: string;
|
|
773
|
+
/** Deprecates this pipeline — opportunities can still move within it but no new ones are created. */
|
|
774
|
+
isArchived: boolean;
|
|
775
|
+
stages: readonly Stage[];
|
|
776
|
+
/** Free-form label for the sales team / motion that owns this pipeline. */
|
|
777
|
+
teamRef?: string;
|
|
778
|
+
metadata?: Record<string, unknown>;
|
|
779
|
+
createdAt: Date;
|
|
780
|
+
updatedAt: Date;
|
|
781
|
+
}
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/domain/ports/pipeline-repository.port.d.ts
|
|
784
|
+
interface PipelineRepository {
|
|
785
|
+
findById(id: string): Promise<Pipeline | null>;
|
|
786
|
+
list(includeArchived?: boolean): Promise<Pipeline[]>;
|
|
787
|
+
create(p: Omit<Pipeline, 'createdAt' | 'updatedAt'>): Promise<Pipeline>;
|
|
788
|
+
update(id: string, patch: Partial<Pipeline>): Promise<Pipeline>;
|
|
789
|
+
}
|
|
790
|
+
//#endregion
|
|
791
|
+
//#region src/services/pipeline.service.d.ts
|
|
792
|
+
interface PipelineServiceConfig {
|
|
793
|
+
repo: PipelineRepository;
|
|
794
|
+
events?: EventTransport | undefined;
|
|
795
|
+
/** Optional host-owned outbox. See AccountServiceConfig.outbox. */
|
|
796
|
+
outbox?: OutboxStore | undefined;
|
|
797
|
+
clock?: Clock;
|
|
798
|
+
ids?: IdGenerator;
|
|
799
|
+
}
|
|
800
|
+
interface CreatePipelineInput {
|
|
801
|
+
name: string;
|
|
802
|
+
stages: readonly Omit<Stage, 'id'>[];
|
|
803
|
+
teamRef?: string;
|
|
804
|
+
metadata?: Record<string, unknown>;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Pipeline lifecycle + stage helpers. The service owns stage id
|
|
808
|
+
* generation so callers don't have to — create a pipeline with an
|
|
809
|
+
* array of stage seeds and the service hands back the persisted
|
|
810
|
+
* pipeline with ids assigned.
|
|
811
|
+
*/
|
|
812
|
+
declare class PipelineService {
|
|
813
|
+
private readonly repo;
|
|
814
|
+
private readonly events;
|
|
815
|
+
private readonly outbox;
|
|
816
|
+
private readonly clock;
|
|
817
|
+
private readonly ids;
|
|
818
|
+
constructor(config: PipelineServiceConfig);
|
|
819
|
+
create(input: CreatePipelineInput, options?: MutationOptions): Promise<Pipeline>;
|
|
820
|
+
archive(id: string, options?: MutationOptions): Promise<Pipeline>;
|
|
821
|
+
getStage(pipelineId: string, stageId: string): Promise<Stage>;
|
|
822
|
+
/** First stage in the ordered pipeline (minimum `sequence`). */
|
|
823
|
+
getFirstStage(pipelineId: string): Promise<Stage>;
|
|
824
|
+
/** Next stage after `stageId` in the ordered pipeline, or `null` at the end. */
|
|
825
|
+
getNextStage(pipelineId: string, stageId: string): Promise<Stage | null>;
|
|
826
|
+
findById(id: string): Promise<Pipeline | null>;
|
|
827
|
+
list(includeArchived?: boolean): Promise<Pipeline[]>;
|
|
828
|
+
private _require;
|
|
829
|
+
private _emit;
|
|
830
|
+
}
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/services/forecast.d.ts
|
|
833
|
+
interface ForecastRow {
|
|
834
|
+
/** Bucket key — typically ISO month `YYYY-MM` derived from `expectedCloseAt`. */
|
|
835
|
+
bucket: string;
|
|
836
|
+
/** Sum of unweighted `amount`. */
|
|
837
|
+
committed: number;
|
|
838
|
+
/** Sum of `amount × probability`. */
|
|
839
|
+
weighted: number;
|
|
840
|
+
/** Count of opportunities contributing. */
|
|
841
|
+
count: number;
|
|
842
|
+
currency: string;
|
|
843
|
+
}
|
|
844
|
+
interface ForecastOptions {
|
|
845
|
+
/**
|
|
846
|
+
* Bucket function — given an opportunity, return a string bucket
|
|
847
|
+
* key. Default is ISO-month `YYYY-MM`. Custom buckets (quarter,
|
|
848
|
+
* fiscal-year, custom periods) plug in here.
|
|
849
|
+
*/
|
|
850
|
+
bucket?: (o: Opportunity) => string;
|
|
851
|
+
/**
|
|
852
|
+
* Filter opportunities before bucketing. Default includes only
|
|
853
|
+
* `open` opportunities with `amount` and `expectedCloseAt`.
|
|
854
|
+
*/
|
|
855
|
+
include?: (o: Opportunity) => boolean;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Aggregate opportunities into a forecast table — one row per
|
|
859
|
+
* bucket (month by default). Committed is `Σ amount`; weighted is
|
|
860
|
+
* `Σ amount × probability`.
|
|
861
|
+
*
|
|
862
|
+
* Integer-safe: sums are computed on minor-unit amounts (consistent
|
|
863
|
+
* with `@classytic/primitives/money`'s integer convention). Currency
|
|
864
|
+
* mismatch across a bucket throws — hosts dealing in multi-currency
|
|
865
|
+
* run this once per currency after partitioning.
|
|
866
|
+
*/
|
|
867
|
+
declare function forecast(opportunities: readonly Opportunity[], options?: ForecastOptions): ForecastRow[];
|
|
868
|
+
/**
|
|
869
|
+
* Pipeline conversion summary — how opportunities partitioned across
|
|
870
|
+
* stages and final outcome. Useful for "where do deals die?" analysis.
|
|
871
|
+
*/
|
|
872
|
+
interface ConversionRow {
|
|
873
|
+
stageId: string;
|
|
874
|
+
currentCount: number;
|
|
875
|
+
wonFromHere: number;
|
|
876
|
+
lostFromHere: number;
|
|
877
|
+
conversionRate: number;
|
|
878
|
+
}
|
|
879
|
+
declare function pipelineConversion(opportunities: readonly Opportunity[], stageIds: readonly string[]): ConversionRow[];
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/domain/entities/loss-reason.d.ts
|
|
882
|
+
/**
|
|
883
|
+
* Standardized reason for closing an opportunity as `lost`. Seeded
|
|
884
|
+
* once by the host; referenced by id on the opportunity. Keeping
|
|
885
|
+
* these as records (vs free-form strings) is what makes lost-reason
|
|
886
|
+
* analytics possible.
|
|
887
|
+
*/
|
|
888
|
+
interface LossReason {
|
|
889
|
+
id: string;
|
|
890
|
+
name: string;
|
|
891
|
+
/** Broad category — `price`, `feature-gap`, `timing`, `competitor`, `no-decision`. */
|
|
892
|
+
category?: string;
|
|
893
|
+
description?: string;
|
|
894
|
+
active: boolean;
|
|
895
|
+
metadata?: Record<string, unknown>;
|
|
896
|
+
createdAt: Date;
|
|
897
|
+
updatedAt: Date;
|
|
898
|
+
}
|
|
899
|
+
//#endregion
|
|
900
|
+
//#region src/domain/ports/loss-reason-repository.port.d.ts
|
|
901
|
+
interface LossReasonRepository {
|
|
902
|
+
findById(id: string): Promise<LossReason | null>;
|
|
903
|
+
list(activeOnly?: boolean): Promise<LossReason[]>;
|
|
904
|
+
create(r: Omit<LossReason, 'createdAt' | 'updatedAt'>): Promise<LossReason>;
|
|
905
|
+
update(id: string, patch: Partial<LossReason>): Promise<LossReason>;
|
|
906
|
+
}
|
|
907
|
+
//#endregion
|
|
908
|
+
//#region src/domain/ports/outreach.port.d.ts
|
|
909
|
+
/**
|
|
910
|
+
* Optional outreach bridge. When wired, activity transitions can send
|
|
911
|
+
* real emails / SMS / etc. via the host's pipeline. This package does
|
|
912
|
+
* NOT call it automatically — hosts subscribe to `crm:activity.*`
|
|
913
|
+
* events and call `outreach.send()` from their own handler so pacing,
|
|
914
|
+
* retry, and bounce handling live where they belong.
|
|
915
|
+
*
|
|
916
|
+
* The port intentionally stays minimal; implementations forward to
|
|
917
|
+
* SendGrid, Postmark, Twilio, Mailgun, or an in-house SMTP stack.
|
|
918
|
+
*/
|
|
919
|
+
interface OutreachSendRequest {
|
|
920
|
+
channel: 'email' | 'sms' | 'social' | string;
|
|
921
|
+
toAddress: string;
|
|
922
|
+
subject?: string;
|
|
923
|
+
body: string;
|
|
924
|
+
activityId?: string;
|
|
925
|
+
/** Host metadata — template id, campaign ref, personalization vars. */
|
|
926
|
+
metadata?: Record<string, unknown>;
|
|
927
|
+
}
|
|
928
|
+
interface OutreachSendResult {
|
|
929
|
+
/** Opaque ref — typically the provider's message id. */
|
|
930
|
+
providerMessageId?: string;
|
|
931
|
+
acceptedAt: Date;
|
|
932
|
+
}
|
|
933
|
+
interface OutreachPort {
|
|
934
|
+
send(request: OutreachSendRequest): Promise<OutreachSendResult>;
|
|
935
|
+
}
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/adapters/in-memory/account-repository.d.ts
|
|
938
|
+
declare class InMemoryAccountRepository implements AccountRepository {
|
|
939
|
+
private readonly clock;
|
|
940
|
+
private readonly items;
|
|
941
|
+
constructor(clock?: Clock);
|
|
942
|
+
findById(id: string): Promise<Account | null>;
|
|
943
|
+
findByDomain(domain: string): Promise<Account | null>;
|
|
944
|
+
list(filter?: AccountFilter): Promise<Account[]>;
|
|
945
|
+
create(input: Omit<Account, 'createdAt' | 'updatedAt'>): Promise<Account>;
|
|
946
|
+
update(id: string, patch: Partial<Account>): Promise<Account>;
|
|
947
|
+
delete(id: string): Promise<void>;
|
|
948
|
+
}
|
|
949
|
+
//#endregion
|
|
950
|
+
//#region src/adapters/in-memory/activity-repository.d.ts
|
|
951
|
+
declare class InMemoryActivityRepository implements ActivityRepository {
|
|
952
|
+
private readonly clock;
|
|
953
|
+
private readonly items;
|
|
954
|
+
constructor(clock?: Clock);
|
|
955
|
+
findById(id: string): Promise<Activity | null>;
|
|
956
|
+
list(filter?: ActivityFilter): Promise<Activity[]>;
|
|
957
|
+
create(input: Omit<Activity, 'createdAt' | 'updatedAt'>): Promise<Activity>;
|
|
958
|
+
update(id: string, patch: Partial<Activity>): Promise<Activity>;
|
|
959
|
+
casStatus(id: string, expected: ActivityStatus, next: ActivityStatus, patch?: Partial<Activity>): Promise<Activity | null>;
|
|
960
|
+
}
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/adapters/in-memory/contact-repository.d.ts
|
|
963
|
+
declare class InMemoryContactRepository implements ContactRepository {
|
|
964
|
+
private readonly clock;
|
|
965
|
+
private readonly items;
|
|
966
|
+
constructor(clock?: Clock);
|
|
967
|
+
findById(id: string): Promise<Contact | null>;
|
|
968
|
+
findByEmail(email: string): Promise<Contact | null>;
|
|
969
|
+
list(filter?: ContactFilter): Promise<Contact[]>;
|
|
970
|
+
create(input: Omit<Contact, 'createdAt' | 'updatedAt'>): Promise<Contact>;
|
|
971
|
+
update(id: string, patch: Partial<Contact>): Promise<Contact>;
|
|
972
|
+
delete(id: string): Promise<void>;
|
|
973
|
+
}
|
|
974
|
+
//#endregion
|
|
975
|
+
//#region src/adapters/in-memory/event-transport.d.ts
|
|
976
|
+
type RecordedEvent = DomainEvent;
|
|
977
|
+
declare class InMemoryEventTransport implements EventTransport {
|
|
978
|
+
readonly name = "crm-in-memory";
|
|
979
|
+
readonly events: RecordedEvent[];
|
|
980
|
+
private readonly subscribers;
|
|
981
|
+
publish(event: DomainEvent): Promise<void>;
|
|
982
|
+
subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
|
|
983
|
+
}
|
|
984
|
+
//#endregion
|
|
985
|
+
//#region src/adapters/in-memory/lead-repository.d.ts
|
|
986
|
+
declare class InMemoryLeadRepository implements LeadRepository {
|
|
987
|
+
private readonly clock;
|
|
988
|
+
private readonly items;
|
|
989
|
+
constructor(clock?: Clock);
|
|
990
|
+
findById(id: string): Promise<Lead | null>;
|
|
991
|
+
findByEmail(email: string): Promise<Lead | null>;
|
|
992
|
+
list(filter?: LeadFilter): Promise<Lead[]>;
|
|
993
|
+
create(input: Omit<Lead, 'createdAt' | 'updatedAt'>): Promise<Lead>;
|
|
994
|
+
update(id: string, patch: Partial<Lead>): Promise<Lead>;
|
|
995
|
+
casStatus(id: string, expected: LeadStatus, next: LeadStatus, patch?: Partial<Lead>): Promise<Lead | null>;
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/adapters/in-memory/loss-reason-repository.d.ts
|
|
999
|
+
declare class InMemoryLossReasonRepository implements LossReasonRepository {
|
|
1000
|
+
private readonly clock;
|
|
1001
|
+
private readonly items;
|
|
1002
|
+
constructor(clock?: Clock);
|
|
1003
|
+
findById(id: string): Promise<LossReason | null>;
|
|
1004
|
+
list(activeOnly?: boolean): Promise<LossReason[]>;
|
|
1005
|
+
create(input: Omit<LossReason, 'createdAt' | 'updatedAt'>): Promise<LossReason>;
|
|
1006
|
+
update(id: string, patch: Partial<LossReason>): Promise<LossReason>;
|
|
1007
|
+
}
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region src/adapters/in-memory/note-repository.d.ts
|
|
1010
|
+
declare class InMemoryNoteRepository implements NoteRepository {
|
|
1011
|
+
private readonly clock;
|
|
1012
|
+
private readonly items;
|
|
1013
|
+
constructor(clock?: Clock);
|
|
1014
|
+
findById(id: string): Promise<Note | null>;
|
|
1015
|
+
listBySubject(subjectKind: SubjectKind, subjectId: string): Promise<Note[]>;
|
|
1016
|
+
create(input: Omit<Note, 'createdAt' | 'updatedAt'>): Promise<Note>;
|
|
1017
|
+
update(id: string, patch: Partial<Note>): Promise<Note>;
|
|
1018
|
+
delete(id: string): Promise<void>;
|
|
1019
|
+
}
|
|
1020
|
+
//#endregion
|
|
1021
|
+
//#region src/adapters/in-memory/opportunity-repository.d.ts
|
|
1022
|
+
declare class InMemoryOpportunityRepository implements OpportunityRepository {
|
|
1023
|
+
private readonly clock;
|
|
1024
|
+
private readonly items;
|
|
1025
|
+
constructor(clock?: Clock);
|
|
1026
|
+
findById(id: string): Promise<Opportunity | null>;
|
|
1027
|
+
list(filter?: OpportunityFilter): Promise<Opportunity[]>;
|
|
1028
|
+
create(input: Omit<Opportunity, 'createdAt' | 'updatedAt'>): Promise<Opportunity>;
|
|
1029
|
+
update(id: string, patch: Partial<Opportunity>): Promise<Opportunity>;
|
|
1030
|
+
delete(id: string): Promise<void>;
|
|
1031
|
+
casStatus(id: string, expected: OpportunityStatus, next: OpportunityStatus, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
|
|
1032
|
+
casStage(id: string, expectedStageId: string, nextStageId: string, patch?: Partial<Opportunity>): Promise<Opportunity | null>;
|
|
1033
|
+
}
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region src/adapters/in-memory/pipeline-repository.d.ts
|
|
1036
|
+
declare class InMemoryPipelineRepository implements PipelineRepository {
|
|
1037
|
+
private readonly clock;
|
|
1038
|
+
private readonly items;
|
|
1039
|
+
constructor(clock?: Clock);
|
|
1040
|
+
findById(id: string): Promise<Pipeline | null>;
|
|
1041
|
+
list(includeArchived?: boolean): Promise<Pipeline[]>;
|
|
1042
|
+
create(input: Omit<Pipeline, 'createdAt' | 'updatedAt'>): Promise<Pipeline>;
|
|
1043
|
+
update(id: string, patch: Partial<Pipeline>): Promise<Pipeline>;
|
|
1044
|
+
}
|
|
1045
|
+
//#endregion
|
|
1046
|
+
//#region src/adapters/in-memory/outreach-port.d.ts
|
|
1047
|
+
declare class NullOutreachPort implements OutreachPort {
|
|
1048
|
+
private counter;
|
|
1049
|
+
send(_request: OutreachSendRequest): Promise<OutreachSendResult>;
|
|
1050
|
+
}
|
|
1051
|
+
interface RecordedOutreach extends OutreachSendRequest, OutreachSendResult {}
|
|
1052
|
+
declare class InMemoryOutreachPort implements OutreachPort {
|
|
1053
|
+
private readonly nowMs;
|
|
1054
|
+
readonly sent: RecordedOutreach[];
|
|
1055
|
+
private counter;
|
|
1056
|
+
constructor(nowMs?: () => number);
|
|
1057
|
+
send(request: OutreachSendRequest): Promise<OutreachSendResult>;
|
|
1058
|
+
}
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region src/adapters/in-memory/id-generator.d.ts
|
|
1061
|
+
declare const cryptoIdGenerator: IdGenerator;
|
|
1062
|
+
declare function createSequentialIdGenerator(prefix?: string): IdGenerator;
|
|
1063
|
+
//#endregion
|
|
1064
|
+
//#region src/domain/errors.d.ts
|
|
1065
|
+
/**
|
|
1066
|
+
* CRM error vocabulary. Hosts catch `CrmError` at API edges.
|
|
1067
|
+
*/
|
|
1068
|
+
declare class CrmError extends Error {
|
|
1069
|
+
readonly code: string;
|
|
1070
|
+
readonly details?: Record<string, unknown>;
|
|
1071
|
+
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
1072
|
+
}
|
|
1073
|
+
declare class NotFoundError extends CrmError {
|
|
1074
|
+
constructor(kind: string, id: string);
|
|
1075
|
+
}
|
|
1076
|
+
declare class InvalidLeadTransitionError extends CrmError {
|
|
1077
|
+
constructor(details: {
|
|
1078
|
+
from: string;
|
|
1079
|
+
to: string;
|
|
1080
|
+
leadId: string;
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
declare class InvalidOpportunityTransitionError extends CrmError {
|
|
1084
|
+
constructor(details: {
|
|
1085
|
+
from: string;
|
|
1086
|
+
to: string;
|
|
1087
|
+
opportunityId: string;
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
declare class InvalidActivityTransitionError extends CrmError {
|
|
1091
|
+
constructor(details: {
|
|
1092
|
+
from: string;
|
|
1093
|
+
to: string;
|
|
1094
|
+
activityId: string;
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
declare class StageNotInPipelineError extends CrmError {
|
|
1098
|
+
constructor(details: {
|
|
1099
|
+
pipelineId: string;
|
|
1100
|
+
stageId: string;
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
declare class LeadAlreadyConvertedError extends CrmError {
|
|
1104
|
+
constructor(leadId: string);
|
|
1105
|
+
}
|
|
1106
|
+
declare class DuplicateLeadError extends CrmError {
|
|
1107
|
+
constructor(details: {
|
|
1108
|
+
email?: string;
|
|
1109
|
+
phone?: string;
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
declare class EmptyPipelineError extends CrmError {
|
|
1113
|
+
constructor(pipelineId: string);
|
|
1114
|
+
}
|
|
1115
|
+
//#endregion
|
|
1116
|
+
//#region src/events/event-constants.d.ts
|
|
1117
|
+
/** PACKAGE_RULES rule 12: `package:resource.verb`. */
|
|
1118
|
+
declare const CRM_EVENTS: {
|
|
1119
|
+
readonly LEAD_CREATED: "crm:lead.created";
|
|
1120
|
+
readonly LEAD_STATUS_CHANGED: "crm:lead.status_changed";
|
|
1121
|
+
readonly LEAD_SCORED: "crm:lead.scored";
|
|
1122
|
+
readonly LEAD_CONVERTED: "crm:lead.converted";
|
|
1123
|
+
readonly LEAD_DISQUALIFIED: "crm:lead.disqualified";
|
|
1124
|
+
readonly ACCOUNT_CREATED: "crm:account.created";
|
|
1125
|
+
readonly ACCOUNT_UPDATED: "crm:account.updated";
|
|
1126
|
+
readonly CONTACT_CREATED: "crm:contact.created";
|
|
1127
|
+
readonly CONTACT_UPDATED: "crm:contact.updated";
|
|
1128
|
+
readonly OPPORTUNITY_CREATED: "crm:opportunity.created";
|
|
1129
|
+
readonly OPPORTUNITY_STAGE_ADVANCED: "crm:opportunity.stage_advanced";
|
|
1130
|
+
readonly OPPORTUNITY_STAGE_REGRESSED: "crm:opportunity.stage_regressed";
|
|
1131
|
+
readonly OPPORTUNITY_WON: "crm:opportunity.won";
|
|
1132
|
+
readonly OPPORTUNITY_LOST: "crm:opportunity.lost";
|
|
1133
|
+
readonly OPPORTUNITY_ABANDONED: "crm:opportunity.abandoned";
|
|
1134
|
+
readonly PIPELINE_CREATED: "crm:pipeline.created";
|
|
1135
|
+
readonly PIPELINE_ARCHIVED: "crm:pipeline.archived";
|
|
1136
|
+
readonly ACTIVITY_PLANNED: "crm:activity.planned";
|
|
1137
|
+
readonly ACTIVITY_COMPLETED: "crm:activity.completed";
|
|
1138
|
+
readonly ACTIVITY_CANCELLED: "crm:activity.cancelled";
|
|
1139
|
+
readonly NOTE_ADDED: "crm:note.added";
|
|
1140
|
+
};
|
|
1141
|
+
type CrmEventName = (typeof CRM_EVENTS)[keyof typeof CRM_EVENTS];
|
|
1142
|
+
//#endregion
|
|
1143
|
+
//#region src/events/event-catalog.d.ts
|
|
1144
|
+
interface CrmEventSchema {
|
|
1145
|
+
type: 'object';
|
|
1146
|
+
properties?: Record<string, {
|
|
1147
|
+
type?: string;
|
|
1148
|
+
format?: string;
|
|
1149
|
+
[key: string]: unknown;
|
|
1150
|
+
}>;
|
|
1151
|
+
required?: string[];
|
|
1152
|
+
[key: string]: unknown;
|
|
1153
|
+
}
|
|
1154
|
+
interface CrmEventDefinition<TSchema extends z.ZodType = z.ZodType> {
|
|
1155
|
+
readonly name: string;
|
|
1156
|
+
readonly version: number;
|
|
1157
|
+
readonly description?: string;
|
|
1158
|
+
readonly schema: CrmEventSchema;
|
|
1159
|
+
readonly zodSchema: TSchema;
|
|
1160
|
+
create(payload: z.infer<TSchema>, meta?: Partial<DomainEvent['meta']>): DomainEvent<z.infer<TSchema>>;
|
|
1161
|
+
readonly __payload?: z.infer<TSchema>;
|
|
1162
|
+
}
|
|
1163
|
+
type CrmEventPayloadOf<D> = D extends CrmEventDefinition<infer S> ? z.infer<S> : never;
|
|
1164
|
+
declare const leadCreatedSchema: z.ZodObject<{
|
|
1165
|
+
leadId: z.ZodString;
|
|
1166
|
+
email: z.ZodOptional<z.ZodString>;
|
|
1167
|
+
score: z.ZodOptional<z.ZodNumber>;
|
|
1168
|
+
}, z.core.$strip>;
|
|
1169
|
+
declare const leadStatusChangedSchema: z.ZodObject<{
|
|
1170
|
+
leadId: z.ZodString;
|
|
1171
|
+
from: z.ZodString;
|
|
1172
|
+
to: z.ZodString;
|
|
1173
|
+
}, z.core.$strip>;
|
|
1174
|
+
declare const leadScoredSchema: z.ZodObject<{
|
|
1175
|
+
leadId: z.ZodString;
|
|
1176
|
+
score: z.ZodNumber;
|
|
1177
|
+
}, z.core.$strip>;
|
|
1178
|
+
declare const leadConvertedSchema: z.ZodObject<{
|
|
1179
|
+
leadId: z.ZodString;
|
|
1180
|
+
contactId: z.ZodString;
|
|
1181
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
1182
|
+
opportunityId: z.ZodString;
|
|
1183
|
+
}, z.core.$strip>;
|
|
1184
|
+
declare const leadDisqualifiedSchema: z.ZodObject<{
|
|
1185
|
+
leadId: z.ZodString;
|
|
1186
|
+
reason: z.ZodString;
|
|
1187
|
+
}, z.core.$strip>;
|
|
1188
|
+
declare const accountCreatedSchema: z.ZodObject<{
|
|
1189
|
+
accountId: z.ZodString;
|
|
1190
|
+
}, z.core.$strip>;
|
|
1191
|
+
declare const accountUpdatedSchema: z.ZodObject<{
|
|
1192
|
+
accountId: z.ZodString;
|
|
1193
|
+
}, z.core.$strip>;
|
|
1194
|
+
declare const contactCreatedSchema: z.ZodObject<{
|
|
1195
|
+
contactId: z.ZodString;
|
|
1196
|
+
email: z.ZodOptional<z.ZodString>;
|
|
1197
|
+
}, z.core.$strip>;
|
|
1198
|
+
declare const contactUpdatedSchema: z.ZodObject<{
|
|
1199
|
+
contactId: z.ZodString;
|
|
1200
|
+
}, z.core.$strip>;
|
|
1201
|
+
declare const opportunityCreatedSchema: z.ZodObject<{
|
|
1202
|
+
opportunityId: z.ZodString;
|
|
1203
|
+
pipelineId: z.ZodString;
|
|
1204
|
+
stageId: z.ZodString;
|
|
1205
|
+
}, z.core.$strip>;
|
|
1206
|
+
declare const opportunityStageAdvancedSchema: z.ZodObject<{
|
|
1207
|
+
opportunityId: z.ZodString;
|
|
1208
|
+
from: z.ZodObject<{
|
|
1209
|
+
stageId: z.ZodString;
|
|
1210
|
+
sequence: z.ZodNumber;
|
|
1211
|
+
}, z.core.$strip>;
|
|
1212
|
+
to: z.ZodObject<{
|
|
1213
|
+
stageId: z.ZodString;
|
|
1214
|
+
sequence: z.ZodNumber;
|
|
1215
|
+
}, z.core.$strip>;
|
|
1216
|
+
}, z.core.$strip>;
|
|
1217
|
+
declare const opportunityStageRegressedSchema: z.ZodObject<{
|
|
1218
|
+
opportunityId: z.ZodString;
|
|
1219
|
+
from: z.ZodObject<{
|
|
1220
|
+
stageId: z.ZodString;
|
|
1221
|
+
sequence: z.ZodNumber;
|
|
1222
|
+
}, z.core.$strip>;
|
|
1223
|
+
to: z.ZodObject<{
|
|
1224
|
+
stageId: z.ZodString;
|
|
1225
|
+
sequence: z.ZodNumber;
|
|
1226
|
+
}, z.core.$strip>;
|
|
1227
|
+
}, z.core.$strip>;
|
|
1228
|
+
declare const opportunityWonSchema: z.ZodObject<{
|
|
1229
|
+
opportunityId: z.ZodString;
|
|
1230
|
+
amount: z.ZodOptional<z.ZodObject<{
|
|
1231
|
+
amount: z.ZodNumber;
|
|
1232
|
+
currency: z.ZodString;
|
|
1233
|
+
}, z.core.$strip>>;
|
|
1234
|
+
}, z.core.$strip>;
|
|
1235
|
+
declare const opportunityLostSchema: z.ZodObject<{
|
|
1236
|
+
opportunityId: z.ZodString;
|
|
1237
|
+
lostReasonId: z.ZodOptional<z.ZodString>;
|
|
1238
|
+
}, z.core.$strip>;
|
|
1239
|
+
declare const opportunityAbandonedSchema: z.ZodObject<{
|
|
1240
|
+
opportunityId: z.ZodString;
|
|
1241
|
+
}, z.core.$strip>;
|
|
1242
|
+
declare const pipelineCreatedSchema: z.ZodObject<{
|
|
1243
|
+
pipelineId: z.ZodString;
|
|
1244
|
+
}, z.core.$strip>;
|
|
1245
|
+
declare const pipelineArchivedSchema: z.ZodObject<{
|
|
1246
|
+
pipelineId: z.ZodString;
|
|
1247
|
+
}, z.core.$strip>;
|
|
1248
|
+
declare const activityPlannedSchema: z.ZodObject<{
|
|
1249
|
+
activityId: z.ZodString;
|
|
1250
|
+
type: z.ZodString;
|
|
1251
|
+
subjectKind: z.ZodEnum<{
|
|
1252
|
+
lead: "lead";
|
|
1253
|
+
opportunity: "opportunity";
|
|
1254
|
+
contact: "contact";
|
|
1255
|
+
account: "account";
|
|
1256
|
+
}>;
|
|
1257
|
+
subjectId: z.ZodString;
|
|
1258
|
+
}, z.core.$strip>;
|
|
1259
|
+
declare const activityCompletedSchema: z.ZodObject<{
|
|
1260
|
+
activityId: z.ZodString;
|
|
1261
|
+
completedAt: z.ZodISODateTime;
|
|
1262
|
+
}, z.core.$strip>;
|
|
1263
|
+
declare const activityCancelledSchema: z.ZodObject<{
|
|
1264
|
+
activityId: z.ZodString;
|
|
1265
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
1266
|
+
}, z.core.$strip>;
|
|
1267
|
+
declare const noteAddedSchema: z.ZodObject<{
|
|
1268
|
+
noteId: z.ZodString;
|
|
1269
|
+
subjectKind: z.ZodEnum<{
|
|
1270
|
+
lead: "lead";
|
|
1271
|
+
opportunity: "opportunity";
|
|
1272
|
+
contact: "contact";
|
|
1273
|
+
account: "account";
|
|
1274
|
+
}>;
|
|
1275
|
+
subjectId: z.ZodString;
|
|
1276
|
+
}, z.core.$strip>;
|
|
1277
|
+
type LeadCreatedPayload = z.infer<typeof leadCreatedSchema>;
|
|
1278
|
+
type LeadStatusChangedPayload = z.infer<typeof leadStatusChangedSchema>;
|
|
1279
|
+
type LeadScoredPayload = z.infer<typeof leadScoredSchema>;
|
|
1280
|
+
type LeadConvertedPayload = z.infer<typeof leadConvertedSchema>;
|
|
1281
|
+
type LeadDisqualifiedPayload = z.infer<typeof leadDisqualifiedSchema>;
|
|
1282
|
+
type AccountCreatedPayload = z.infer<typeof accountCreatedSchema>;
|
|
1283
|
+
type AccountUpdatedPayload = z.infer<typeof accountUpdatedSchema>;
|
|
1284
|
+
type ContactCreatedPayload = z.infer<typeof contactCreatedSchema>;
|
|
1285
|
+
type ContactUpdatedPayload = z.infer<typeof contactUpdatedSchema>;
|
|
1286
|
+
type OpportunityCreatedPayload = z.infer<typeof opportunityCreatedSchema>;
|
|
1287
|
+
type OpportunityStageAdvancedPayload = z.infer<typeof opportunityStageAdvancedSchema>;
|
|
1288
|
+
type OpportunityStageRegressedPayload = z.infer<typeof opportunityStageRegressedSchema>;
|
|
1289
|
+
type OpportunityWonPayload = z.infer<typeof opportunityWonSchema>;
|
|
1290
|
+
type OpportunityLostPayload = z.infer<typeof opportunityLostSchema>;
|
|
1291
|
+
type OpportunityAbandonedPayload = z.infer<typeof opportunityAbandonedSchema>;
|
|
1292
|
+
type PipelineCreatedPayload = z.infer<typeof pipelineCreatedSchema>;
|
|
1293
|
+
type PipelineArchivedPayload = z.infer<typeof pipelineArchivedSchema>;
|
|
1294
|
+
type ActivityPlannedPayload = z.infer<typeof activityPlannedSchema>;
|
|
1295
|
+
type ActivityCompletedPayload = z.infer<typeof activityCompletedSchema>;
|
|
1296
|
+
type ActivityCancelledPayload = z.infer<typeof activityCancelledSchema>;
|
|
1297
|
+
type NoteAddedPayload = z.infer<typeof noteAddedSchema>;
|
|
1298
|
+
declare const LeadCreated: CrmEventDefinition<z.ZodObject<{
|
|
1299
|
+
leadId: z.ZodString;
|
|
1300
|
+
email: z.ZodOptional<z.ZodString>;
|
|
1301
|
+
score: z.ZodOptional<z.ZodNumber>;
|
|
1302
|
+
}, z.core.$strip>>;
|
|
1303
|
+
declare const LeadStatusChanged: CrmEventDefinition<z.ZodObject<{
|
|
1304
|
+
leadId: z.ZodString;
|
|
1305
|
+
from: z.ZodString;
|
|
1306
|
+
to: z.ZodString;
|
|
1307
|
+
}, z.core.$strip>>;
|
|
1308
|
+
declare const LeadScored: CrmEventDefinition<z.ZodObject<{
|
|
1309
|
+
leadId: z.ZodString;
|
|
1310
|
+
score: z.ZodNumber;
|
|
1311
|
+
}, z.core.$strip>>;
|
|
1312
|
+
declare const LeadConverted: CrmEventDefinition<z.ZodObject<{
|
|
1313
|
+
leadId: z.ZodString;
|
|
1314
|
+
contactId: z.ZodString;
|
|
1315
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
1316
|
+
opportunityId: z.ZodString;
|
|
1317
|
+
}, z.core.$strip>>;
|
|
1318
|
+
declare const LeadDisqualified: CrmEventDefinition<z.ZodObject<{
|
|
1319
|
+
leadId: z.ZodString;
|
|
1320
|
+
reason: z.ZodString;
|
|
1321
|
+
}, z.core.$strip>>;
|
|
1322
|
+
declare const AccountCreated: CrmEventDefinition<z.ZodObject<{
|
|
1323
|
+
accountId: z.ZodString;
|
|
1324
|
+
}, z.core.$strip>>;
|
|
1325
|
+
declare const AccountUpdated: CrmEventDefinition<z.ZodObject<{
|
|
1326
|
+
accountId: z.ZodString;
|
|
1327
|
+
}, z.core.$strip>>;
|
|
1328
|
+
declare const ContactCreated: CrmEventDefinition<z.ZodObject<{
|
|
1329
|
+
contactId: z.ZodString;
|
|
1330
|
+
email: z.ZodOptional<z.ZodString>;
|
|
1331
|
+
}, z.core.$strip>>;
|
|
1332
|
+
declare const ContactUpdated: CrmEventDefinition<z.ZodObject<{
|
|
1333
|
+
contactId: z.ZodString;
|
|
1334
|
+
}, z.core.$strip>>;
|
|
1335
|
+
declare const OpportunityCreated: CrmEventDefinition<z.ZodObject<{
|
|
1336
|
+
opportunityId: z.ZodString;
|
|
1337
|
+
pipelineId: z.ZodString;
|
|
1338
|
+
stageId: z.ZodString;
|
|
1339
|
+
}, z.core.$strip>>;
|
|
1340
|
+
declare const OpportunityStageAdvanced: CrmEventDefinition<z.ZodObject<{
|
|
1341
|
+
opportunityId: z.ZodString;
|
|
1342
|
+
from: z.ZodObject<{
|
|
1343
|
+
stageId: z.ZodString;
|
|
1344
|
+
sequence: z.ZodNumber;
|
|
1345
|
+
}, z.core.$strip>;
|
|
1346
|
+
to: z.ZodObject<{
|
|
1347
|
+
stageId: z.ZodString;
|
|
1348
|
+
sequence: z.ZodNumber;
|
|
1349
|
+
}, z.core.$strip>;
|
|
1350
|
+
}, z.core.$strip>>;
|
|
1351
|
+
declare const OpportunityStageRegressed: CrmEventDefinition<z.ZodObject<{
|
|
1352
|
+
opportunityId: z.ZodString;
|
|
1353
|
+
from: z.ZodObject<{
|
|
1354
|
+
stageId: z.ZodString;
|
|
1355
|
+
sequence: z.ZodNumber;
|
|
1356
|
+
}, z.core.$strip>;
|
|
1357
|
+
to: z.ZodObject<{
|
|
1358
|
+
stageId: z.ZodString;
|
|
1359
|
+
sequence: z.ZodNumber;
|
|
1360
|
+
}, z.core.$strip>;
|
|
1361
|
+
}, z.core.$strip>>;
|
|
1362
|
+
declare const OpportunityWon: CrmEventDefinition<z.ZodObject<{
|
|
1363
|
+
opportunityId: z.ZodString;
|
|
1364
|
+
amount: z.ZodOptional<z.ZodObject<{
|
|
1365
|
+
amount: z.ZodNumber;
|
|
1366
|
+
currency: z.ZodString;
|
|
1367
|
+
}, z.core.$strip>>;
|
|
1368
|
+
}, z.core.$strip>>;
|
|
1369
|
+
declare const OpportunityLost: CrmEventDefinition<z.ZodObject<{
|
|
1370
|
+
opportunityId: z.ZodString;
|
|
1371
|
+
lostReasonId: z.ZodOptional<z.ZodString>;
|
|
1372
|
+
}, z.core.$strip>>;
|
|
1373
|
+
declare const OpportunityAbandoned: CrmEventDefinition<z.ZodObject<{
|
|
1374
|
+
opportunityId: z.ZodString;
|
|
1375
|
+
}, z.core.$strip>>;
|
|
1376
|
+
declare const PipelineCreated: CrmEventDefinition<z.ZodObject<{
|
|
1377
|
+
pipelineId: z.ZodString;
|
|
1378
|
+
}, z.core.$strip>>;
|
|
1379
|
+
declare const PipelineArchived: CrmEventDefinition<z.ZodObject<{
|
|
1380
|
+
pipelineId: z.ZodString;
|
|
1381
|
+
}, z.core.$strip>>;
|
|
1382
|
+
declare const ActivityPlanned: CrmEventDefinition<z.ZodObject<{
|
|
1383
|
+
activityId: z.ZodString;
|
|
1384
|
+
type: z.ZodString;
|
|
1385
|
+
subjectKind: z.ZodEnum<{
|
|
1386
|
+
lead: "lead";
|
|
1387
|
+
opportunity: "opportunity";
|
|
1388
|
+
contact: "contact";
|
|
1389
|
+
account: "account";
|
|
1390
|
+
}>;
|
|
1391
|
+
subjectId: z.ZodString;
|
|
1392
|
+
}, z.core.$strip>>;
|
|
1393
|
+
declare const ActivityCompleted: CrmEventDefinition<z.ZodObject<{
|
|
1394
|
+
activityId: z.ZodString;
|
|
1395
|
+
completedAt: z.ZodISODateTime;
|
|
1396
|
+
}, z.core.$strip>>;
|
|
1397
|
+
declare const ActivityCancelled: CrmEventDefinition<z.ZodObject<{
|
|
1398
|
+
activityId: z.ZodString;
|
|
1399
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
1400
|
+
}, z.core.$strip>>;
|
|
1401
|
+
declare const NoteAdded: CrmEventDefinition<z.ZodObject<{
|
|
1402
|
+
noteId: z.ZodString;
|
|
1403
|
+
subjectKind: z.ZodEnum<{
|
|
1404
|
+
lead: "lead";
|
|
1405
|
+
opportunity: "opportunity";
|
|
1406
|
+
contact: "contact";
|
|
1407
|
+
account: "account";
|
|
1408
|
+
}>;
|
|
1409
|
+
subjectId: z.ZodString;
|
|
1410
|
+
}, z.core.$strip>>;
|
|
1411
|
+
/**
|
|
1412
|
+
* Every CRM event defined in the package — pass to Arc's `EventRegistry`.
|
|
1413
|
+
* Hosts wire ONE array; the whole `crm:*` namespace becomes introspectable
|
|
1414
|
+
* via OpenAPI and auto-validated at publish time when
|
|
1415
|
+
* `eventPlugin({ validateMode: 'reject' })` is set.
|
|
1416
|
+
*/
|
|
1417
|
+
declare const crmEventDefinitions: ReadonlyArray<CrmEventDefinition>;
|
|
1418
|
+
//#endregion
|
|
1419
|
+
export { ACTIVITY_STATUSES, ACTIVITY_TRANSITIONS, ACTIVITY_TYPES, type Account, AccountCreated, type AccountCreatedPayload, type AccountFilter, type AccountRepository, AccountService, type AccountServiceConfig, AccountUpdated, type AccountUpdatedPayload, type Activity, ActivityCancelled, type ActivityCancelledPayload, ActivityCompleted, type ActivityCompletedPayload, type ActivityFilter, ActivityPlanned, type ActivityPlannedPayload, type ActivityRepository, ActivityService, type ActivityServiceConfig, type ActivityStatus, type ActivityType, CRM_EVENTS, type Clock, type CloseInput, type Contact, ContactCreated, type ContactCreatedPayload, type ContactFilter, type ContactRepository, ContactService, type ContactServiceConfig, ContactUpdated, type ContactUpdatedPayload, type ConversionRow, type ConvertLeadInput, type ConvertResult, type CreateAccountInput, type CreateActivityInput, type CreateContactInput, type CreateLeadInput, type CreateNoteInput, type CreateOpportunityInput, type CreatePipelineInput, CrmError, type CrmEventDefinition, type CrmEventName, type CrmEventPayloadOf, type CrmEventSchema, DuplicateLeadError, EmptyPipelineError, type ForecastOptions, type ForecastRow, type IdGenerator, InMemoryAccountRepository, InMemoryActivityRepository, InMemoryContactRepository, InMemoryEventTransport, InMemoryLeadRepository, InMemoryLossReasonRepository, InMemoryNoteRepository, InMemoryOpportunityRepository, InMemoryOutreachPort, InMemoryPipelineRepository, InvalidActivityTransitionError, InvalidLeadTransitionError, InvalidOpportunityTransitionError, LEAD_STATUSES, LEAD_TRANSITIONS, type Lead, LeadAlreadyConvertedError, LeadConverted, type LeadConvertedPayload, LeadCreated, type LeadCreatedPayload, LeadDisqualified, type LeadDisqualifiedPayload, type LeadFilter, type LeadRepository, LeadScored, type LeadScoredPayload, type LeadScorer, LeadService, type LeadServiceConfig, type LeadStatus, LeadStatusChanged, type LeadStatusChangedPayload, type LeadStatusEntry, type LoseInput, type LossReason, type LossReasonRepository, type MoveStageInput, NotFoundError, type Note, NoteAdded, type NoteAddedPayload, type NoteRepository, NullOutreachPort, OPPORTUNITY_STATUSES, OPPORTUNITY_TRANSITIONS, type Opportunity, OpportunityAbandoned, type OpportunityAbandonedPayload, OpportunityCreated, type OpportunityCreatedPayload, type OpportunityFilter, OpportunityLost, type OpportunityLostPayload, type OpportunityRepository, OpportunityService, type OpportunityServiceConfig, OpportunityStageAdvanced, type OpportunityStageAdvancedPayload, OpportunityStageRegressed, type OpportunityStageRegressedPayload, type OpportunityStatus, type OpportunityStatusEntry, OpportunityWon, type OpportunityWonPayload, type OutreachPort, type OutreachSendRequest, type OutreachSendResult, type Pipeline, PipelineArchived, type PipelineArchivedPayload, PipelineCreated, type PipelineCreatedPayload, type PipelineRepository, PipelineService, type PipelineServiceConfig, type RecordedEvent, type RecordedOutreach, SUBJECT_KINDS, type Stage, StageNotInPipelineError, type SubjectKind, canTransitionActivity, canTransitionLead, canTransitionOpportunity, composeScorers, createSequentialIdGenerator, crmEventDefinitions, cryptoIdGenerator, defaultLeadScorer, forecast, pipelineConversion, systemClock };
|
|
1420
|
+
//# sourceMappingURL=index.d.ts.map
|