@voyant-travel/relationships 0.119.2
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/LICENSE +201 -0
- package/README.md +36 -0
- package/dist/action-ledger-capabilities.d.ts +20 -0
- package/dist/action-ledger-capabilities.d.ts.map +1 -0
- package/dist/action-ledger-capabilities.js +16 -0
- package/dist/events.d.ts +23 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +9 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/route-runtime.d.ts +21 -0
- package/dist/route-runtime.d.ts.map +1 -0
- package/dist/route-runtime.js +28 -0
- package/dist/routes/accounts.d.ts +1460 -0
- package/dist/routes/accounts.d.ts.map +1 -0
- package/dist/routes/accounts.js +274 -0
- package/dist/routes/activities.d.ts +299 -0
- package/dist/routes/activities.d.ts.map +1 -0
- package/dist/routes/activities.js +64 -0
- package/dist/routes/custom-fields.d.ts +256 -0
- package/dist/routes/custom-fields.d.ts.map +1 -0
- package/dist/routes/custom-fields.js +47 -0
- 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 +2945 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/person-documents.d.ts +519 -0
- package/dist/routes/person-documents.d.ts.map +1 -0
- package/dist/routes/person-documents.js +240 -0
- package/dist/routes/person-relationships.d.ts +189 -0
- package/dist/routes/person-relationships.d.ts.map +1 -0
- package/dist/routes/person-relationships.js +36 -0
- package/dist/schema-accounts.d.ts +2099 -0
- package/dist/schema-accounts.d.ts.map +1 -0
- package/dist/schema-accounts.js +312 -0
- package/dist/schema-activities.d.ts +821 -0
- package/dist/schema-activities.d.ts.map +1 -0
- package/dist/schema-activities.js +92 -0
- package/dist/schema-relations.d.ts +47 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +70 -0
- package/dist/schema-shared.d.ts +10 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +36 -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 +6 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +5 -0
- package/dist/service/accounts-merge.d.ts +63 -0
- package/dist/service/accounts-merge.d.ts.map +1 -0
- package/dist/service/accounts-merge.js +382 -0
- package/dist/service/accounts-organizations.d.ts +97 -0
- package/dist/service/accounts-organizations.d.ts.map +1 -0
- package/dist/service/accounts-organizations.js +70 -0
- package/dist/service/accounts-people.d.ts +1315 -0
- package/dist/service/accounts-people.d.ts.map +1 -0
- package/dist/service/accounts-people.js +409 -0
- package/dist/service/accounts-resolve.d.ts +76 -0
- package/dist/service/accounts-resolve.d.ts.map +1 -0
- package/dist/service/accounts-resolve.js +103 -0
- package/dist/service/accounts-shared.d.ts +68 -0
- package/dist/service/accounts-shared.d.ts.map +1 -0
- package/dist/service/accounts-shared.js +149 -0
- package/dist/service/accounts.d.ts +1465 -0
- package/dist/service/accounts.d.ts.map +1 -0
- package/dist/service/accounts.js +13 -0
- package/dist/service/activities.d.ts +486 -0
- package/dist/service/activities.d.ts.map +1 -0
- package/dist/service/activities.js +114 -0
- package/dist/service/custom-fields.d.ts +118 -0
- package/dist/service/custom-fields.d.ts.map +1 -0
- package/dist/service/custom-fields.js +88 -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/helpers.d.ts +22 -0
- package/dist/service/helpers.d.ts.map +1 -0
- package/dist/service/helpers.js +39 -0
- package/dist/service/index.d.ts +4434 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +17 -0
- package/dist/service/person-documents.d.ts +1201 -0
- package/dist/service/person-documents.d.ts.map +1 -0
- package/dist/service/person-documents.js +240 -0
- package/dist/service/person-relationships.d.ts +502 -0
- package/dist/service/person-relationships.d.ts.map +1 -0
- package/dist/service/person-relationships.js +121 -0
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts-people.d.ts","sourceRoot":"","sources":["../../src/service/accounts-people.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// agent-quality: file-size exception -- owner: crm; existing service module stays co-located until a dedicated split preserves behavior and tests.
|
|
2
|
+
import { identityContactPoints } from "@voyant-travel/identity/schema";
|
|
3
|
+
import { identityService } from "@voyant-travel/identity/service";
|
|
4
|
+
import { toCsvRow } from "@voyant-travel/utils";
|
|
5
|
+
import { and, asc, desc, eq, exists, gte, ilike, lte, or, sql } from "drizzle-orm";
|
|
6
|
+
import { communicationLog, organizationNotes, organizations, people, personNotes, personPaymentMethods, segments, } from "../schema.js";
|
|
7
|
+
import { deletePersonIdentity, hydratePeople, organizationEntityType, personBaseFields, personEntityType, syncPersonIdentity, } from "./accounts-shared.js";
|
|
8
|
+
import { paginate } from "./helpers.js";
|
|
9
|
+
function unaccentedIlike(column, term) {
|
|
10
|
+
// agent-quality: raw-sql reviewed -- owner: crm; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
11
|
+
return sql `unaccent(coalesce(${column}, '')) ILIKE unaccent(${term})`;
|
|
12
|
+
}
|
|
13
|
+
function buildPersonSearchCondition(db, search) {
|
|
14
|
+
const trimmedSearch = search.trim();
|
|
15
|
+
if (!trimmedSearch)
|
|
16
|
+
return undefined;
|
|
17
|
+
const term = `%${trimmedSearch}%`;
|
|
18
|
+
const tokens = trimmedSearch.split(/\s+/).filter(Boolean);
|
|
19
|
+
const digits = trimmedSearch.replace(/\D/g, "");
|
|
20
|
+
const searchablePersonColumns = [
|
|
21
|
+
people.firstName,
|
|
22
|
+
people.middleName,
|
|
23
|
+
people.lastName,
|
|
24
|
+
people.jobTitle,
|
|
25
|
+
];
|
|
26
|
+
const contactPointConditions = [
|
|
27
|
+
ilike(identityContactPoints.value, term),
|
|
28
|
+
ilike(identityContactPoints.normalizedValue, term),
|
|
29
|
+
];
|
|
30
|
+
if (digits) {
|
|
31
|
+
const digitsTerm = `%${digits}%`;
|
|
32
|
+
contactPointConditions.push(
|
|
33
|
+
// agent-quality: raw-sql reviewed -- owner: crm; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
34
|
+
sql `regexp_replace(${identityContactPoints.value}, '[^0-9]+', '', 'g') ILIKE ${digitsTerm}`,
|
|
35
|
+
// agent-quality: raw-sql reviewed -- owner: crm; dynamic SQL interpolation uses Drizzle parameter binding or vetted SQL identifiers.
|
|
36
|
+
sql `regexp_replace(coalesce(${identityContactPoints.normalizedValue}, ''), '[^0-9]+', '', 'g') ILIKE ${digitsTerm}`);
|
|
37
|
+
}
|
|
38
|
+
const tokenizedPersonCondition = tokens.length
|
|
39
|
+
? and(...tokens.map((token) => or(...searchablePersonColumns.map((column) => unaccentedIlike(column, `%${token}%`)))))
|
|
40
|
+
: undefined;
|
|
41
|
+
return or(tokenizedPersonCondition, exists(db
|
|
42
|
+
.select({ one: sql `1` })
|
|
43
|
+
.from(identityContactPoints)
|
|
44
|
+
.where(and(eq(identityContactPoints.entityType, personEntityType), eq(identityContactPoints.entityId, people.id), or(eq(identityContactPoints.kind, "email"), eq(identityContactPoints.kind, "phone")), or(...contactPointConditions)))));
|
|
45
|
+
}
|
|
46
|
+
export const peopleAccountsService = {
|
|
47
|
+
async listPeople(db, query) {
|
|
48
|
+
const conditions = [];
|
|
49
|
+
if (query.organizationId)
|
|
50
|
+
conditions.push(eq(people.organizationId, query.organizationId));
|
|
51
|
+
if (query.ownerId)
|
|
52
|
+
conditions.push(eq(people.ownerId, query.ownerId));
|
|
53
|
+
if (query.relation)
|
|
54
|
+
conditions.push(eq(people.relation, query.relation));
|
|
55
|
+
if (query.status)
|
|
56
|
+
conditions.push(eq(people.status, query.status));
|
|
57
|
+
if (query.search) {
|
|
58
|
+
const searchCondition = buildPersonSearchCondition(db, query.search);
|
|
59
|
+
if (searchCondition)
|
|
60
|
+
conditions.push(searchCondition);
|
|
61
|
+
}
|
|
62
|
+
const where = conditions.length ? and(...conditions) : undefined;
|
|
63
|
+
const sortColumns = (() => {
|
|
64
|
+
switch (query.sortBy) {
|
|
65
|
+
case "name":
|
|
66
|
+
return [people.firstName, people.lastName];
|
|
67
|
+
case "relation":
|
|
68
|
+
return [people.relation];
|
|
69
|
+
case "status":
|
|
70
|
+
return [people.status];
|
|
71
|
+
case "createdAt":
|
|
72
|
+
return [people.createdAt];
|
|
73
|
+
default:
|
|
74
|
+
return [people.updatedAt];
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
const sortFn = query.sortDir === "asc" ? asc : desc;
|
|
78
|
+
const orderBy = [...sortColumns.map((col) => sortFn(col)), desc(people.updatedAt)];
|
|
79
|
+
const result = await paginate(db
|
|
80
|
+
.select()
|
|
81
|
+
.from(people)
|
|
82
|
+
.where(where)
|
|
83
|
+
.limit(query.limit)
|
|
84
|
+
.offset(query.offset)
|
|
85
|
+
.orderBy(...orderBy), db.select({ count: sql `count(*)::int` }).from(people).where(where), query.limit, query.offset);
|
|
86
|
+
return {
|
|
87
|
+
...result,
|
|
88
|
+
data: await hydratePeople(db, result.data, { fallbackOnError: true }),
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
async getPersonById(db, id) {
|
|
92
|
+
const [row] = await db.select().from(people).where(eq(people.id, id)).limit(1);
|
|
93
|
+
if (!row)
|
|
94
|
+
return null;
|
|
95
|
+
const [hydrated] = await hydratePeople(db, [row]);
|
|
96
|
+
return hydrated ?? null;
|
|
97
|
+
},
|
|
98
|
+
async createPerson(db, data) {
|
|
99
|
+
const [row] = await db
|
|
100
|
+
.insert(people)
|
|
101
|
+
.values({
|
|
102
|
+
...personBaseFields(data),
|
|
103
|
+
firstName: data.firstName,
|
|
104
|
+
lastName: data.lastName,
|
|
105
|
+
})
|
|
106
|
+
.returning();
|
|
107
|
+
if (!row) {
|
|
108
|
+
throw new Error("Failed to create person");
|
|
109
|
+
}
|
|
110
|
+
await syncPersonIdentity(db, row.id, data);
|
|
111
|
+
return this.getPersonById(db, row.id);
|
|
112
|
+
},
|
|
113
|
+
async updatePerson(db, id, data) {
|
|
114
|
+
const existing = await this.getPersonById(db, id);
|
|
115
|
+
if (!existing)
|
|
116
|
+
return null;
|
|
117
|
+
await db
|
|
118
|
+
.update(people)
|
|
119
|
+
.set({ ...personBaseFields(data), updatedAt: new Date() })
|
|
120
|
+
.where(eq(people.id, id));
|
|
121
|
+
await syncPersonIdentity(db, id, {
|
|
122
|
+
email: data.email === undefined ? existing.email : data.email,
|
|
123
|
+
phone: data.phone === undefined ? existing.phone : data.phone,
|
|
124
|
+
website: data.website === undefined ? existing.website : data.website,
|
|
125
|
+
});
|
|
126
|
+
return this.getPersonById(db, id);
|
|
127
|
+
},
|
|
128
|
+
async deletePerson(db, id) {
|
|
129
|
+
await deletePersonIdentity(db, id);
|
|
130
|
+
const [row] = await db.delete(people).where(eq(people.id, id)).returning({ id: people.id });
|
|
131
|
+
return row ?? null;
|
|
132
|
+
},
|
|
133
|
+
listContactMethods(db, entityType, entityId) {
|
|
134
|
+
return identityService.listContactPointsForEntity(db, entityType === "organization" ? organizationEntityType : personEntityType, entityId);
|
|
135
|
+
},
|
|
136
|
+
async createContactMethod(db, entityType, entityId, data) {
|
|
137
|
+
return identityService.createContactPoint(db, {
|
|
138
|
+
...data,
|
|
139
|
+
entityType: entityType === "organization" ? organizationEntityType : personEntityType,
|
|
140
|
+
entityId,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
async updateContactMethod(db, id, data) {
|
|
144
|
+
return identityService.updateContactPoint(db, id, data);
|
|
145
|
+
},
|
|
146
|
+
async deleteContactMethod(db, id) {
|
|
147
|
+
return identityService.deleteContactPoint(db, id);
|
|
148
|
+
},
|
|
149
|
+
listAddresses(db, entityType, entityId) {
|
|
150
|
+
return identityService.listAddressesForEntity(db, entityType === "organization" ? organizationEntityType : personEntityType, entityId);
|
|
151
|
+
},
|
|
152
|
+
async createAddress(db, entityType, entityId, data) {
|
|
153
|
+
return identityService.createAddress(db, {
|
|
154
|
+
...data,
|
|
155
|
+
entityType: entityType === "organization" ? organizationEntityType : personEntityType,
|
|
156
|
+
entityId,
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
async updateAddress(db, id, data) {
|
|
160
|
+
return identityService.updateAddress(db, id, data);
|
|
161
|
+
},
|
|
162
|
+
async deleteAddress(db, id) {
|
|
163
|
+
return identityService.deleteAddress(db, id);
|
|
164
|
+
},
|
|
165
|
+
listPersonNotes(db, personId) {
|
|
166
|
+
return db
|
|
167
|
+
.select()
|
|
168
|
+
.from(personNotes)
|
|
169
|
+
.where(eq(personNotes.personId, personId))
|
|
170
|
+
.orderBy(personNotes.createdAt);
|
|
171
|
+
},
|
|
172
|
+
async createPersonNote(db, personId, userId, data) {
|
|
173
|
+
const [existing] = await db
|
|
174
|
+
.select({ id: people.id })
|
|
175
|
+
.from(people)
|
|
176
|
+
.where(eq(people.id, personId))
|
|
177
|
+
.limit(1);
|
|
178
|
+
if (!existing)
|
|
179
|
+
return null;
|
|
180
|
+
const [row] = await db
|
|
181
|
+
.insert(personNotes)
|
|
182
|
+
.values({ personId, authorId: userId, content: data.content })
|
|
183
|
+
.returning();
|
|
184
|
+
return row;
|
|
185
|
+
},
|
|
186
|
+
listOrganizationNotes(db, organizationId) {
|
|
187
|
+
return db
|
|
188
|
+
.select()
|
|
189
|
+
.from(organizationNotes)
|
|
190
|
+
.where(eq(organizationNotes.organizationId, organizationId))
|
|
191
|
+
.orderBy(organizationNotes.createdAt);
|
|
192
|
+
},
|
|
193
|
+
async createOrganizationNote(db, organizationId, userId, data) {
|
|
194
|
+
const [existing] = await db
|
|
195
|
+
.select({ id: organizations.id })
|
|
196
|
+
.from(organizations)
|
|
197
|
+
.where(eq(organizations.id, organizationId))
|
|
198
|
+
.limit(1);
|
|
199
|
+
if (!existing)
|
|
200
|
+
return null;
|
|
201
|
+
const [row] = await db
|
|
202
|
+
.insert(organizationNotes)
|
|
203
|
+
.values({ organizationId, authorId: userId, content: data.content })
|
|
204
|
+
.returning();
|
|
205
|
+
return row;
|
|
206
|
+
},
|
|
207
|
+
async updatePersonNote(db, id, content) {
|
|
208
|
+
const [row] = await db
|
|
209
|
+
.update(personNotes)
|
|
210
|
+
.set({ content })
|
|
211
|
+
.where(eq(personNotes.id, id))
|
|
212
|
+
.returning();
|
|
213
|
+
return row ?? null;
|
|
214
|
+
},
|
|
215
|
+
async deletePersonNote(db, id) {
|
|
216
|
+
const [row] = await db.delete(personNotes).where(eq(personNotes.id, id)).returning();
|
|
217
|
+
return row ?? null;
|
|
218
|
+
},
|
|
219
|
+
// ── Payment methods ────────────────────────────────────────────────────
|
|
220
|
+
listPersonPaymentMethods(db, personId) {
|
|
221
|
+
return db
|
|
222
|
+
.select()
|
|
223
|
+
.from(personPaymentMethods)
|
|
224
|
+
.where(eq(personPaymentMethods.personId, personId))
|
|
225
|
+
.orderBy(desc(personPaymentMethods.isDefault), desc(personPaymentMethods.createdAt));
|
|
226
|
+
},
|
|
227
|
+
async createPersonPaymentMethod(db, personId, data) {
|
|
228
|
+
const [existing] = await db
|
|
229
|
+
.select({ id: people.id })
|
|
230
|
+
.from(people)
|
|
231
|
+
.where(eq(people.id, personId))
|
|
232
|
+
.limit(1);
|
|
233
|
+
if (!existing)
|
|
234
|
+
return null;
|
|
235
|
+
if (data.isDefault) {
|
|
236
|
+
// Only one default per person — clear the others first.
|
|
237
|
+
await db
|
|
238
|
+
.update(personPaymentMethods)
|
|
239
|
+
.set({ isDefault: false })
|
|
240
|
+
.where(eq(personPaymentMethods.personId, personId));
|
|
241
|
+
}
|
|
242
|
+
const [row] = await db
|
|
243
|
+
.insert(personPaymentMethods)
|
|
244
|
+
.values({ personId, ...data })
|
|
245
|
+
.returning();
|
|
246
|
+
return row ?? null;
|
|
247
|
+
},
|
|
248
|
+
async updatePersonPaymentMethod(db, id, data) {
|
|
249
|
+
if (data.isDefault) {
|
|
250
|
+
const [target] = await db
|
|
251
|
+
.select({ personId: personPaymentMethods.personId })
|
|
252
|
+
.from(personPaymentMethods)
|
|
253
|
+
.where(eq(personPaymentMethods.id, id))
|
|
254
|
+
.limit(1);
|
|
255
|
+
if (target) {
|
|
256
|
+
await db
|
|
257
|
+
.update(personPaymentMethods)
|
|
258
|
+
.set({ isDefault: false })
|
|
259
|
+
.where(eq(personPaymentMethods.personId, target.personId));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const [row] = await db
|
|
263
|
+
.update(personPaymentMethods)
|
|
264
|
+
.set(data)
|
|
265
|
+
.where(eq(personPaymentMethods.id, id))
|
|
266
|
+
.returning();
|
|
267
|
+
return row ?? null;
|
|
268
|
+
},
|
|
269
|
+
async deletePersonPaymentMethod(db, id) {
|
|
270
|
+
const [row] = await db
|
|
271
|
+
.delete(personPaymentMethods)
|
|
272
|
+
.where(eq(personPaymentMethods.id, id))
|
|
273
|
+
.returning();
|
|
274
|
+
return row ?? null;
|
|
275
|
+
},
|
|
276
|
+
async updateOrganizationNote(db, id, content) {
|
|
277
|
+
const [row] = await db
|
|
278
|
+
.update(organizationNotes)
|
|
279
|
+
.set({ content })
|
|
280
|
+
.where(eq(organizationNotes.id, id))
|
|
281
|
+
.returning();
|
|
282
|
+
return row ?? null;
|
|
283
|
+
},
|
|
284
|
+
async deleteOrganizationNote(db, id) {
|
|
285
|
+
const [row] = await db.delete(organizationNotes).where(eq(organizationNotes.id, id)).returning();
|
|
286
|
+
return row ?? null;
|
|
287
|
+
},
|
|
288
|
+
async listCommunications(db, personId, query) {
|
|
289
|
+
const conditions = [eq(communicationLog.personId, personId)];
|
|
290
|
+
if (query.channel)
|
|
291
|
+
conditions.push(eq(communicationLog.channel, query.channel));
|
|
292
|
+
if (query.direction)
|
|
293
|
+
conditions.push(eq(communicationLog.direction, query.direction));
|
|
294
|
+
if (query.dateFrom)
|
|
295
|
+
conditions.push(gte(communicationLog.createdAt, new Date(query.dateFrom)));
|
|
296
|
+
if (query.dateTo)
|
|
297
|
+
conditions.push(lte(communicationLog.createdAt, new Date(query.dateTo)));
|
|
298
|
+
return db
|
|
299
|
+
.select()
|
|
300
|
+
.from(communicationLog)
|
|
301
|
+
.where(and(...conditions))
|
|
302
|
+
.limit(query.limit)
|
|
303
|
+
.offset(query.offset)
|
|
304
|
+
.orderBy(desc(communicationLog.createdAt));
|
|
305
|
+
},
|
|
306
|
+
async createCommunication(db, personId, data) {
|
|
307
|
+
const [existing] = await db
|
|
308
|
+
.select({ id: people.id })
|
|
309
|
+
.from(people)
|
|
310
|
+
.where(eq(people.id, personId))
|
|
311
|
+
.limit(1);
|
|
312
|
+
if (!existing)
|
|
313
|
+
return null;
|
|
314
|
+
const [row] = await db
|
|
315
|
+
.insert(communicationLog)
|
|
316
|
+
.values({
|
|
317
|
+
personId,
|
|
318
|
+
organizationId: data.organizationId ?? null,
|
|
319
|
+
channel: data.channel,
|
|
320
|
+
direction: data.direction,
|
|
321
|
+
subject: data.subject ?? null,
|
|
322
|
+
content: data.content ?? null,
|
|
323
|
+
sentAt: data.sentAt ? new Date(data.sentAt) : null,
|
|
324
|
+
})
|
|
325
|
+
.returning();
|
|
326
|
+
return row;
|
|
327
|
+
},
|
|
328
|
+
listSegments(db) {
|
|
329
|
+
return db.select().from(segments).orderBy(segments.createdAt);
|
|
330
|
+
},
|
|
331
|
+
async createSegment(db, data) {
|
|
332
|
+
const [row] = await db.insert(segments).values(data).returning();
|
|
333
|
+
return row;
|
|
334
|
+
},
|
|
335
|
+
async deleteSegment(db, segmentId) {
|
|
336
|
+
const [row] = await db
|
|
337
|
+
.delete(segments)
|
|
338
|
+
.where(eq(segments.id, segmentId))
|
|
339
|
+
.returning({ id: segments.id });
|
|
340
|
+
return row ?? null;
|
|
341
|
+
},
|
|
342
|
+
async exportPeopleCsv(db) {
|
|
343
|
+
const rows = await hydratePeople(db, await db.select().from(people).orderBy(people.createdAt));
|
|
344
|
+
const headers = [
|
|
345
|
+
"id",
|
|
346
|
+
"firstName",
|
|
347
|
+
"lastName",
|
|
348
|
+
"jobTitle",
|
|
349
|
+
"relation",
|
|
350
|
+
"preferredLanguage",
|
|
351
|
+
"preferredCurrency",
|
|
352
|
+
"email",
|
|
353
|
+
"phone",
|
|
354
|
+
"website",
|
|
355
|
+
"organizationId",
|
|
356
|
+
];
|
|
357
|
+
const csvLines = [toCsvRow(headers)];
|
|
358
|
+
for (const row of rows) {
|
|
359
|
+
// toCsvRow quotes delimiters/quotes/newlines AND neutralizes
|
|
360
|
+
// spreadsheet formula-injection prefixes (= + - @ tab CR); see L4.
|
|
361
|
+
csvLines.push(toCsvRow(headers.map((header) => row[header])));
|
|
362
|
+
}
|
|
363
|
+
return csvLines.join("\n");
|
|
364
|
+
},
|
|
365
|
+
async importPeopleCsv(db, csvText) {
|
|
366
|
+
const lines = csvText.split("\n").filter((line) => line.trim());
|
|
367
|
+
if (lines.length < 2) {
|
|
368
|
+
return { error: "CSV must have a header row and at least one data row" };
|
|
369
|
+
}
|
|
370
|
+
const headers = lines[0].split(",").map((header) => header.trim());
|
|
371
|
+
const rows = [];
|
|
372
|
+
for (let i = 1; i < lines.length; i++) {
|
|
373
|
+
const values = lines[i].split(",").map((value) => value.trim());
|
|
374
|
+
const row = {};
|
|
375
|
+
for (let j = 0; j < headers.length; j++) {
|
|
376
|
+
const header = headers[j];
|
|
377
|
+
const value = values[j];
|
|
378
|
+
if (header && value) {
|
|
379
|
+
row[header] = value;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
rows.push(row);
|
|
383
|
+
}
|
|
384
|
+
const imported = [];
|
|
385
|
+
const errors = [];
|
|
386
|
+
for (let i = 0; i < rows.length; i++) {
|
|
387
|
+
const row = rows[i];
|
|
388
|
+
const result = (await import("../validation.js")).insertPersonSchema.safeParse({
|
|
389
|
+
firstName: row.firstName || "",
|
|
390
|
+
lastName: row.lastName || "",
|
|
391
|
+
jobTitle: row.jobTitle || null,
|
|
392
|
+
relation: row.relation || null,
|
|
393
|
+
preferredLanguage: row.preferredLanguage || null,
|
|
394
|
+
preferredCurrency: row.preferredCurrency || null,
|
|
395
|
+
email: row.email || null,
|
|
396
|
+
phone: row.phone || null,
|
|
397
|
+
website: row.website || null,
|
|
398
|
+
organizationId: row.organizationId || null,
|
|
399
|
+
tags: [],
|
|
400
|
+
});
|
|
401
|
+
if (!result.success) {
|
|
402
|
+
errors.push({ row: i + 2, error: result.error.message });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
imported.push(await this.createPerson(db, result.data));
|
|
406
|
+
}
|
|
407
|
+
return { imported: imported.length, errors };
|
|
408
|
+
},
|
|
409
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import type { people } from "../schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Best-effort contact snapshot used by booking/storefront flows to
|
|
5
|
+
* resolve (or upsert) a CRM person from billing or traveler payloads.
|
|
6
|
+
* Empty-string and whitespace-only fields are normalized to `null`.
|
|
7
|
+
*/
|
|
8
|
+
export interface PersonContactInput {
|
|
9
|
+
firstName?: string | null;
|
|
10
|
+
lastName?: string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Single-string display name when first/last aren't provided
|
|
13
|
+
* separately — split on whitespace.
|
|
14
|
+
*/
|
|
15
|
+
name?: string | null;
|
|
16
|
+
email?: string | null;
|
|
17
|
+
phone?: string | null;
|
|
18
|
+
preferredLanguage?: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface UpsertPersonFromContactOptions {
|
|
21
|
+
/** `source` recorded on the `people` row (`"storefront-booking"`, etc.). */
|
|
22
|
+
source?: string | null;
|
|
23
|
+
/** `source_ref` recorded on the `people` row — e.g. the session id. */
|
|
24
|
+
sourceRef?: string | null;
|
|
25
|
+
/** Tags propagated to the new person when one is created. */
|
|
26
|
+
tags?: string[];
|
|
27
|
+
/** Status override; defaults to `"active"`. */
|
|
28
|
+
status?: "active" | "inactive";
|
|
29
|
+
/**
|
|
30
|
+
* When true, returns `null` instead of creating a person if the
|
|
31
|
+
* snapshot has no email or phone dedupe key.
|
|
32
|
+
*/
|
|
33
|
+
requireContactPoint?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Derives `{ firstName, lastName }` for a CRM person from whatever
|
|
37
|
+
* contact bits the caller has. Mirrors the storefront-intake helpers
|
|
38
|
+
* (`personNameFromContact` / `personNameFromNewsletter`) so identity
|
|
39
|
+
* resolution from booking/traveler payloads stays symmetric. Falls
|
|
40
|
+
* back to the email local-part before resorting to literal placeholders
|
|
41
|
+
* — `people.first_name`/`last_name` are NOT NULL and the issue #961
|
|
42
|
+
* acceptance criteria call out that the literal `"Unknown"` should
|
|
43
|
+
* never be inserted.
|
|
44
|
+
*/
|
|
45
|
+
export declare function personNameFromContact(input: PersonContactInput): {
|
|
46
|
+
firstName: string;
|
|
47
|
+
lastName: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Returns the first person whose normalized contact point matches
|
|
51
|
+
* `(kind, value)`. Email and website are normalized to lowercase; phone
|
|
52
|
+
* is trimmed. Returns `null` when no match exists, the value resolves to
|
|
53
|
+
* an empty string, or the matching contact point is attached to a
|
|
54
|
+
* non-person entity (organizations share the same table).
|
|
55
|
+
*/
|
|
56
|
+
export declare function findPersonByContactPoint(db: PostgresJsDatabase, query: {
|
|
57
|
+
kind: "email" | "phone" | "website";
|
|
58
|
+
value: string | null | undefined;
|
|
59
|
+
}): Promise<typeof people.$inferSelect | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Finds an existing CRM person by normalized email (then phone as a
|
|
62
|
+
* fallback) or creates a new one from the supplied contact snapshot.
|
|
63
|
+
* Used by booking session bootstrap + confirm flows so storefront
|
|
64
|
+
* bookings produce a real CRM record without each consumer reinventing
|
|
65
|
+
* the dedupe key. The created row carries the supplied `source` /
|
|
66
|
+
* `sourceRef` so the audit trail mirrors lead/newsletter signals.
|
|
67
|
+
*
|
|
68
|
+
* Lookup order: email → phone. The first hit wins; ties never happen
|
|
69
|
+
* because identity contact points are unique per `(kind, normalized)`
|
|
70
|
+
* for a given entity but consumers can re-use the same email across
|
|
71
|
+
* people via the unique-by-person sync logic. In that case the first
|
|
72
|
+
* person we see is returned — callers that need stricter ownership
|
|
73
|
+
* semantics should resolve themselves and pass a `personId` directly.
|
|
74
|
+
*/
|
|
75
|
+
export declare function upsertPersonFromContact(db: PostgresJsDatabase, contact: PersonContactInput, options?: UpsertPersonFromContactOptions): Promise<typeof people.$inferSelect | null>;
|
|
76
|
+
//# sourceMappingURL=accounts-resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts-resolve.d.ts","sourceRoot":"","sources":["../../src/service/accounts-resolve.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAK1C;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,8BAA8B;IAC7C,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAA;IAC9B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAaD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,kBAAkB,GAAG;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB,CAUA;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE;IAAE,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CAAE,GAC/E,OAAO,CAAC,OAAO,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAmB5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,OAAO,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAgC5C"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { identityContactPoints } from "@voyant-travel/identity/schema";
|
|
2
|
+
import { and, eq } from "drizzle-orm";
|
|
3
|
+
import { peopleAccountsService } from "./accounts-people.js";
|
|
4
|
+
import { personEntityType } from "./accounts-shared.js";
|
|
5
|
+
import { normalizeContactValue, toNullableTrimmed } from "./helpers.js";
|
|
6
|
+
function splitName(name) {
|
|
7
|
+
if (!name)
|
|
8
|
+
return {};
|
|
9
|
+
const parts = name.trim().split(/\s+/);
|
|
10
|
+
if (parts.length === 0)
|
|
11
|
+
return {};
|
|
12
|
+
if (parts.length === 1)
|
|
13
|
+
return { firstName: parts[0] };
|
|
14
|
+
return { firstName: parts[0], lastName: parts.slice(1).join(" ") };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Derives `{ firstName, lastName }` for a CRM person from whatever
|
|
18
|
+
* contact bits the caller has. Mirrors the storefront-intake helpers
|
|
19
|
+
* (`personNameFromContact` / `personNameFromNewsletter`) so identity
|
|
20
|
+
* resolution from booking/traveler payloads stays symmetric. Falls
|
|
21
|
+
* back to the email local-part before resorting to literal placeholders
|
|
22
|
+
* — `people.first_name`/`last_name` are NOT NULL and the issue #961
|
|
23
|
+
* acceptance criteria call out that the literal `"Unknown"` should
|
|
24
|
+
* never be inserted.
|
|
25
|
+
*/
|
|
26
|
+
export function personNameFromContact(input) {
|
|
27
|
+
const split = splitName(input.name ?? null);
|
|
28
|
+
const emailLocalPart = input.email
|
|
29
|
+
?.split("@")[0]
|
|
30
|
+
?.replace(/[._-]+/g, " ")
|
|
31
|
+
.trim();
|
|
32
|
+
const firstName = toNullableTrimmed(input.firstName) ?? split.firstName ?? emailLocalPart ?? "Customer";
|
|
33
|
+
const lastName = toNullableTrimmed(input.lastName) ?? split.lastName ?? "Guest";
|
|
34
|
+
return { firstName, lastName };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the first person whose normalized contact point matches
|
|
38
|
+
* `(kind, value)`. Email and website are normalized to lowercase; phone
|
|
39
|
+
* is trimmed. Returns `null` when no match exists, the value resolves to
|
|
40
|
+
* an empty string, or the matching contact point is attached to a
|
|
41
|
+
* non-person entity (organizations share the same table).
|
|
42
|
+
*/
|
|
43
|
+
export async function findPersonByContactPoint(db, query) {
|
|
44
|
+
const value = toNullableTrimmed(query.value);
|
|
45
|
+
if (!value)
|
|
46
|
+
return null;
|
|
47
|
+
const normalized = normalizeContactValue(query.kind, value);
|
|
48
|
+
const [row] = await db
|
|
49
|
+
.select({ personId: identityContactPoints.entityId })
|
|
50
|
+
.from(identityContactPoints)
|
|
51
|
+
.where(and(eq(identityContactPoints.entityType, personEntityType), eq(identityContactPoints.kind, query.kind), eq(identityContactPoints.normalizedValue, normalized)))
|
|
52
|
+
.limit(1);
|
|
53
|
+
if (!row)
|
|
54
|
+
return null;
|
|
55
|
+
return peopleAccountsService.getPersonById(db, row.personId);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Finds an existing CRM person by normalized email (then phone as a
|
|
59
|
+
* fallback) or creates a new one from the supplied contact snapshot.
|
|
60
|
+
* Used by booking session bootstrap + confirm flows so storefront
|
|
61
|
+
* bookings produce a real CRM record without each consumer reinventing
|
|
62
|
+
* the dedupe key. The created row carries the supplied `source` /
|
|
63
|
+
* `sourceRef` so the audit trail mirrors lead/newsletter signals.
|
|
64
|
+
*
|
|
65
|
+
* Lookup order: email → phone. The first hit wins; ties never happen
|
|
66
|
+
* because identity contact points are unique per `(kind, normalized)`
|
|
67
|
+
* for a given entity but consumers can re-use the same email across
|
|
68
|
+
* people via the unique-by-person sync logic. In that case the first
|
|
69
|
+
* person we see is returned — callers that need stricter ownership
|
|
70
|
+
* semantics should resolve themselves and pass a `personId` directly.
|
|
71
|
+
*/
|
|
72
|
+
export async function upsertPersonFromContact(db, contact, options = {}) {
|
|
73
|
+
const email = toNullableTrimmed(contact.email);
|
|
74
|
+
const phone = toNullableTrimmed(contact.phone);
|
|
75
|
+
if (email) {
|
|
76
|
+
const existing = await findPersonByContactPoint(db, { kind: "email", value: email });
|
|
77
|
+
if (existing)
|
|
78
|
+
return existing;
|
|
79
|
+
}
|
|
80
|
+
if (phone) {
|
|
81
|
+
const existing = await findPersonByContactPoint(db, { kind: "phone", value: phone });
|
|
82
|
+
if (existing)
|
|
83
|
+
return existing;
|
|
84
|
+
}
|
|
85
|
+
if (options.requireContactPoint && !email && !phone) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
// No match — create a new person. `personNameFromContact` ensures
|
|
89
|
+
// first/last name are populated even when only an email is on file.
|
|
90
|
+
const { firstName, lastName } = personNameFromContact(contact);
|
|
91
|
+
return peopleAccountsService.createPerson(db, {
|
|
92
|
+
firstName,
|
|
93
|
+
lastName,
|
|
94
|
+
email,
|
|
95
|
+
phone,
|
|
96
|
+
website: null,
|
|
97
|
+
status: options.status ?? "active",
|
|
98
|
+
source: options.source ?? null,
|
|
99
|
+
sourceRef: options.sourceRef ?? null,
|
|
100
|
+
preferredLanguage: toNullableTrimmed(contact.preferredLanguage),
|
|
101
|
+
tags: options.tags ?? [],
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { insertAddressSchema, insertContactPointSchema, updateAddressSchema, updateContactPointSchema } from "@voyant-travel/identity/validation";
|
|
2
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import type { communicationListQuerySchema, insertCommunicationLogSchema, insertOrganizationNoteSchema, insertOrganizationSchema, insertPersonNoteSchema, insertPersonSchema, insertSegmentSchema, organizationListQuerySchema, personListQuerySchema, updateOrganizationSchema, updatePersonSchema } from "../validation.js";
|
|
5
|
+
export type OrganizationListQuery = z.infer<typeof organizationListQuerySchema>;
|
|
6
|
+
export type CreateOrganizationInput = z.infer<typeof insertOrganizationSchema>;
|
|
7
|
+
export type UpdateOrganizationInput = z.infer<typeof updateOrganizationSchema>;
|
|
8
|
+
export type PersonListQuery = z.infer<typeof personListQuerySchema>;
|
|
9
|
+
export type CreatePersonInput = z.infer<typeof insertPersonSchema>;
|
|
10
|
+
export type UpdatePersonInput = z.infer<typeof updatePersonSchema>;
|
|
11
|
+
export type CreateContactPointInput = z.infer<typeof insertContactPointSchema>;
|
|
12
|
+
export type UpdateContactPointInput = z.infer<typeof updateContactPointSchema>;
|
|
13
|
+
export type CreateAddressInput = z.infer<typeof insertAddressSchema>;
|
|
14
|
+
export type UpdateAddressInput = z.infer<typeof updateAddressSchema>;
|
|
15
|
+
export type CreatePersonNoteInput = z.infer<typeof insertPersonNoteSchema>;
|
|
16
|
+
export type CreateOrganizationNoteInput = z.infer<typeof insertOrganizationNoteSchema>;
|
|
17
|
+
export type CreateCommunicationLogInput = z.infer<typeof insertCommunicationLogSchema>;
|
|
18
|
+
export type CommunicationListQuery = z.infer<typeof communicationListQuerySchema>;
|
|
19
|
+
export type CreateSegmentInput = z.infer<typeof insertSegmentSchema>;
|
|
20
|
+
export declare const organizationEntityType = "organization";
|
|
21
|
+
export declare const personEntityType = "person";
|
|
22
|
+
export declare const personBaseIdentitySource = "relationships.person.base";
|
|
23
|
+
type PersonIdentityInput = Pick<CreatePersonInput, "email" | "phone" | "website">;
|
|
24
|
+
export type PersonHydratedFields = {
|
|
25
|
+
email: string | null;
|
|
26
|
+
phone: string | null;
|
|
27
|
+
website: string | null;
|
|
28
|
+
};
|
|
29
|
+
type HydratePeopleOptions = {
|
|
30
|
+
fallbackOnError?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function personBaseFields(data: CreatePersonInput | UpdatePersonInput): {
|
|
33
|
+
organizationId: string | null | undefined;
|
|
34
|
+
firstName: string | undefined;
|
|
35
|
+
middleName: string | null | undefined;
|
|
36
|
+
lastName: string | undefined;
|
|
37
|
+
gender: "M" | "F" | "X" | null | undefined;
|
|
38
|
+
jobTitle: string | null | undefined;
|
|
39
|
+
relation: "other" | "partner" | "supplier" | "client" | null | undefined;
|
|
40
|
+
preferredLanguage: string | null | undefined;
|
|
41
|
+
preferredCurrency: string | null | undefined;
|
|
42
|
+
ownerId: string | null | undefined;
|
|
43
|
+
status: "active" | "inactive" | "archived" | undefined;
|
|
44
|
+
source: string | null | undefined;
|
|
45
|
+
sourceRef: string | null | undefined;
|
|
46
|
+
tags: string[] | undefined;
|
|
47
|
+
dateOfBirth: string | null | undefined;
|
|
48
|
+
notes: string | null | undefined;
|
|
49
|
+
accessibilityEncrypted: {
|
|
50
|
+
enc: string;
|
|
51
|
+
} | null | undefined;
|
|
52
|
+
dietaryEncrypted: {
|
|
53
|
+
enc: string;
|
|
54
|
+
} | null | undefined;
|
|
55
|
+
loyaltyEncrypted: {
|
|
56
|
+
enc: string;
|
|
57
|
+
} | null | undefined;
|
|
58
|
+
insuranceEncrypted: {
|
|
59
|
+
enc: string;
|
|
60
|
+
} | null | undefined;
|
|
61
|
+
};
|
|
62
|
+
export declare function syncPersonIdentity(db: PostgresJsDatabase, personId: string, data: PersonIdentityInput): Promise<void>;
|
|
63
|
+
export declare function deletePersonIdentity(db: PostgresJsDatabase, personId: string): Promise<void>;
|
|
64
|
+
export declare function hydratePeople<T extends {
|
|
65
|
+
id: string;
|
|
66
|
+
}>(db: PostgresJsDatabase, rows: T[], options?: HydratePeopleOptions): Promise<Array<T & PersonHydratedFields>>;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=accounts-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts-shared.d.ts","sourceRoot":"","sources":["../../src/service/accounts-shared.ts"],"names":[],"mappings":"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"}
|