@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.
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/routes/customer-signals.d.ts +281 -0
- package/dist/routes/customer-signals.d.ts.map +1 -0
- package/dist/routes/customer-signals.js +45 -0
- package/dist/routes/index.d.ts +271 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/schema-signals.d.ts +324 -0
- package/dist/schema-signals.d.ts.map +1 -0
- package/dist/schema-signals.js +80 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service/customer-signals.d.ts +733 -0
- package/dist/service/customer-signals.d.ts.map +1 -0
- package/dist/service/customer-signals.js +112 -0
- package/dist/service/index.d.ts +712 -0
- package/dist/service/index.d.ts.map +1 -1
- package/dist/service/index.js +3 -0
- package/dist/validation.d.ts +140 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +56 -0
- package/package.json +6 -6
|
@@ -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
|
+
};
|