@voyantjs/crm 0.26.3 → 0.26.4

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"customer-signals.d.ts","sourceRoot":"","sources":["../../src/service/customer-signals.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAG5B,OAAO,KAAK,EACV,6BAA6B,EAC7B,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,kBAAkB,CAAA;AAGzB,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAClF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAClF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AAgBnF,eAAO,MAAM,sBAAsB;IACjC;;;OAGG;4BAC2B,kBAAkB,SAAS,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;IA6BhF;;;;OAIG;6BACsB,kBAAkB,YAAY,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAQjC,kBAAkB,MAAM,MAAM;;;;;;;;;;;;;;;;;;;6BAK3B,kBAAkB,QAAQ,yBAAyB;;;;;;;;;;;;;;;;;;;6BAYnD,kBAAkB,MAAM,MAAM,QAAQ,yBAAyB;;;;;;;;;;;;;;;;;;;6BAa/D,kBAAkB,MAAM,MAAM;;;IAQ7D;;;;OAIG;uCAEG,kBAAkB,YACZ,MAAM,aACL,MAAM;;;;;;;;;;;;;;;;;;;CAapB,CAAA"}
@@ -0,0 +1,112 @@
1
+ import { and, asc, desc, eq, ilike, or, sql } from "drizzle-orm";
2
+ import { customerSignals, people } from "../schema.js";
3
+ import { paginate } from "./helpers.js";
4
+ async function personExists(db, personId) {
5
+ const [row] = await db
6
+ .select({ id: people.id })
7
+ .from(people)
8
+ .where(eq(people.id, personId))
9
+ .limit(1);
10
+ return Boolean(row);
11
+ }
12
+ function normaliseFollowUpAt(value) {
13
+ if (value == null)
14
+ return value ?? null;
15
+ return new Date(value);
16
+ }
17
+ export const customerSignalsService = {
18
+ /**
19
+ * Top-level list with filters. Defaults to newest-first so the
20
+ * sales-pipeline UI shows fresh inquiries on top.
21
+ */
22
+ async listCustomerSignals(db, query) {
23
+ const conditions = [];
24
+ if (query.personId)
25
+ conditions.push(eq(customerSignals.personId, query.personId));
26
+ if (query.assignedToUserId) {
27
+ conditions.push(eq(customerSignals.assignedToUserId, query.assignedToUserId));
28
+ }
29
+ if (query.status)
30
+ conditions.push(eq(customerSignals.status, query.status));
31
+ if (query.kind)
32
+ conditions.push(eq(customerSignals.kind, query.kind));
33
+ if (query.productId)
34
+ conditions.push(eq(customerSignals.productId, query.productId));
35
+ if (query.search) {
36
+ const term = `%${query.search}%`;
37
+ conditions.push(or(ilike(customerSignals.notes, term)));
38
+ }
39
+ const where = conditions.length ? and(...conditions) : undefined;
40
+ return paginate(db
41
+ .select()
42
+ .from(customerSignals)
43
+ .where(where)
44
+ .limit(query.limit)
45
+ .offset(query.offset)
46
+ .orderBy(desc(customerSignals.createdAt)), db.select({ count: sql `count(*)::int` }).from(customerSignals).where(where), query.limit, query.offset);
47
+ },
48
+ /**
49
+ * Per-person convenience list — same shape, ordered oldest-first
50
+ * so a person's signal history reads chronologically alongside
51
+ * their notes / activities.
52
+ */
53
+ listSignalsForPerson(db, personId) {
54
+ return db
55
+ .select()
56
+ .from(customerSignals)
57
+ .where(eq(customerSignals.personId, personId))
58
+ .orderBy(asc(customerSignals.createdAt));
59
+ },
60
+ async getCustomerSignal(db, id) {
61
+ const [row] = await db.select().from(customerSignals).where(eq(customerSignals.id, id)).limit(1);
62
+ return row ?? null;
63
+ },
64
+ async createCustomerSignal(db, data) {
65
+ if (!(await personExists(db, data.personId)))
66
+ return null;
67
+ const [row] = await db
68
+ .insert(customerSignals)
69
+ .values({
70
+ ...data,
71
+ followUpAt: normaliseFollowUpAt(data.followUpAt),
72
+ })
73
+ .returning();
74
+ return row ?? null;
75
+ },
76
+ async updateCustomerSignal(db, id, data) {
77
+ const updates = { ...data, updatedAt: new Date() };
78
+ if (data.followUpAt !== undefined) {
79
+ updates.followUpAt = normaliseFollowUpAt(data.followUpAt);
80
+ }
81
+ const [row] = await db
82
+ .update(customerSignals)
83
+ .set(updates)
84
+ .where(eq(customerSignals.id, id))
85
+ .returning();
86
+ return row ?? null;
87
+ },
88
+ async deleteCustomerSignal(db, id) {
89
+ const [row] = await db
90
+ .delete(customerSignals)
91
+ .where(eq(customerSignals.id, id))
92
+ .returning({ id: customerSignals.id });
93
+ return row ?? null;
94
+ },
95
+ /**
96
+ * Closes the loop: marks the signal as converted and records the
97
+ * booking it became. The caller (operator UI) is responsible for
98
+ * actually creating the booking; this is just the bookkeeping.
99
+ */
100
+ async resolveCustomerSignalToBooking(db, signalId, bookingId) {
101
+ const [row] = await db
102
+ .update(customerSignals)
103
+ .set({
104
+ resolvedBookingId: bookingId,
105
+ status: "converted",
106
+ updatedAt: new Date(),
107
+ })
108
+ .where(eq(customerSignals.id, signalId))
109
+ .returning();
110
+ return row ?? null;
111
+ },
112
+ };