@hed-hog/contact 0.0.295 → 0.0.297
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/contact-type/contact-type.controller.d.ts +1 -1
- package/dist/contact-type/contact-type.service.d.ts +1 -1
- package/dist/document-type/document-type.controller.d.ts +1 -1
- package/dist/document-type/document-type.service.d.ts +1 -1
- package/dist/person/dto/reports-query.dto.d.ts +8 -0
- package/dist/person/dto/reports-query.dto.d.ts.map +1 -0
- package/dist/person/dto/reports-query.dto.js +33 -0
- package/dist/person/dto/reports-query.dto.js.map +1 -0
- package/dist/person/person.controller.d.ts +67 -10
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +26 -6
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +61 -5
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +656 -298
- package/dist/person/person.service.js.map +1 -1
- package/dist/person-relation-type/person-relation-type.controller.d.ts +2 -2
- package/dist/person-relation-type/person-relation-type.service.d.ts +2 -2
- package/hedhog/data/menu.yaml +163 -163
- package/hedhog/data/route.yaml +68 -60
- package/hedhog/frontend/app/_lib/crm-sections.tsx.ejs +9 -9
- package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +573 -573
- package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +9 -9
- package/hedhog/frontend/app/accounts/page.tsx.ejs +970 -970
- package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +240 -240
- package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +66 -66
- package/hedhog/frontend/app/activities/page.tsx.ejs +460 -460
- package/hedhog/frontend/app/dashboard/_components/dashboard-types.ts.ejs +70 -70
- package/hedhog/frontend/app/dashboard/page.tsx.ejs +639 -639
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +785 -785
- package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +10 -12
- package/hedhog/frontend/app/reports/_components/report-types.ts.ejs +84 -0
- package/hedhog/frontend/app/reports/page.tsx.ejs +1196 -15
- package/hedhog/frontend/messages/en.json +242 -123
- package/hedhog/frontend/messages/pt.json +242 -123
- package/hedhog/table/crm_activity.yaml +68 -68
- package/hedhog/table/crm_stage_history.yaml +34 -0
- package/hedhog/table/person_company.yaml +27 -27
- package/package.json +9 -9
- package/src/person/dto/account.dto.ts +100 -100
- package/src/person/dto/activity.dto.ts +54 -54
- package/src/person/dto/dashboard-query.dto.ts +25 -25
- package/src/person/dto/followup-query.dto.ts +25 -25
- package/src/person/dto/reports-query.dto.ts +25 -0
- package/src/person/person.controller.ts +176 -159
- package/src/person/person.service.ts +4825 -4288
|
@@ -56,6 +56,14 @@ const CRM_DASHBOARD_SOURCE_ORDER = [
|
|
|
56
56
|
'outbound',
|
|
57
57
|
'other',
|
|
58
58
|
];
|
|
59
|
+
const CRM_REPORT_ACTIVITY_ORDER = [
|
|
60
|
+
'call',
|
|
61
|
+
'email',
|
|
62
|
+
'meeting',
|
|
63
|
+
'whatsapp',
|
|
64
|
+
'task',
|
|
65
|
+
'note',
|
|
66
|
+
];
|
|
59
67
|
let PersonService = class PersonService {
|
|
60
68
|
constructor(prismaService, paginationService, fileService, settingService) {
|
|
61
69
|
this.prismaService = prismaService;
|
|
@@ -197,6 +205,235 @@ let PersonService = class PersonService {
|
|
|
197
205
|
lists,
|
|
198
206
|
};
|
|
199
207
|
}
|
|
208
|
+
async getReports(query, locale) {
|
|
209
|
+
var _a, _b;
|
|
210
|
+
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
211
|
+
const { start, end } = this.resolveReportsRange(query, locale);
|
|
212
|
+
const startIso = start.toISOString();
|
|
213
|
+
const endIso = end.toISOString();
|
|
214
|
+
const createdPeople = await this.prismaService.person.findMany({
|
|
215
|
+
where: Object.assign({ created_at: {
|
|
216
|
+
gte: start,
|
|
217
|
+
lte: end,
|
|
218
|
+
} }, (allowCompanyRegistration ? {} : { type: 'individual' })),
|
|
219
|
+
select: {
|
|
220
|
+
id: true,
|
|
221
|
+
name: true,
|
|
222
|
+
type: true,
|
|
223
|
+
status: true,
|
|
224
|
+
avatar_id: true,
|
|
225
|
+
created_at: true,
|
|
226
|
+
updated_at: true,
|
|
227
|
+
person_metadata: true,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
const enrichedCreatedPeople = await this.enrichPeople(createdPeople, allowCompanyRegistration);
|
|
231
|
+
const visibilityFilter = allowCompanyRegistration
|
|
232
|
+
? api_prisma_1.Prisma.empty
|
|
233
|
+
: api_prisma_1.Prisma.sql `AND p.type = 'individual'`;
|
|
234
|
+
const stageRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
235
|
+
SELECT
|
|
236
|
+
sh.to_stage,
|
|
237
|
+
sh.from_stage,
|
|
238
|
+
sh.changed_at,
|
|
239
|
+
sh.changed_by_user_id
|
|
240
|
+
FROM crm_stage_history sh
|
|
241
|
+
INNER JOIN person p ON p.id = sh.person_id
|
|
242
|
+
WHERE 1 = 1
|
|
243
|
+
${visibilityFilter}
|
|
244
|
+
AND sh.changed_at >= CAST(${startIso} AS TIMESTAMPTZ)
|
|
245
|
+
AND sh.changed_at <= CAST(${endIso} AS TIMESTAMPTZ)
|
|
246
|
+
`);
|
|
247
|
+
const activityRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
248
|
+
SELECT
|
|
249
|
+
a.type,
|
|
250
|
+
a.source_kind,
|
|
251
|
+
a.created_at,
|
|
252
|
+
a.completed_at,
|
|
253
|
+
a.owner_user_id,
|
|
254
|
+
a.created_by_user_id,
|
|
255
|
+
a.completed_by_user_id
|
|
256
|
+
FROM crm_activity a
|
|
257
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
258
|
+
WHERE 1 = 1
|
|
259
|
+
${visibilityFilter}
|
|
260
|
+
AND (
|
|
261
|
+
(
|
|
262
|
+
a.created_at >= CAST(${startIso} AS TIMESTAMPTZ)
|
|
263
|
+
AND a.created_at <= CAST(${endIso} AS TIMESTAMPTZ)
|
|
264
|
+
)
|
|
265
|
+
OR (
|
|
266
|
+
a.source_kind = 'followup'
|
|
267
|
+
AND a.completed_at IS NOT NULL
|
|
268
|
+
AND a.completed_at >= CAST(${startIso} AS TIMESTAMPTZ)
|
|
269
|
+
AND a.completed_at <= CAST(${endIso} AS TIMESTAMPTZ)
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
`);
|
|
273
|
+
const summary = {
|
|
274
|
+
new_leads: enrichedCreatedPeople.length,
|
|
275
|
+
qualified_moves: 0,
|
|
276
|
+
customer_moves: 0,
|
|
277
|
+
lost_moves: 0,
|
|
278
|
+
interactions: 0,
|
|
279
|
+
followups_completed: 0,
|
|
280
|
+
conversion_rate: 0,
|
|
281
|
+
};
|
|
282
|
+
const sourceTotals = new Map(CRM_DASHBOARD_SOURCE_ORDER.map((key) => [key, 0]));
|
|
283
|
+
const currentStageTotals = new Map(CRM_DASHBOARD_STAGE_ORDER.map((key) => [key, 0]));
|
|
284
|
+
const activityTypeTotals = new Map(CRM_REPORT_ACTIVITY_ORDER.map((key) => [key, 0]));
|
|
285
|
+
const timelineByPeriod = new Map();
|
|
286
|
+
const ownersById = new Map();
|
|
287
|
+
for (const person of enrichedCreatedPeople) {
|
|
288
|
+
const source = ((_a = person.source) !== null && _a !== void 0 ? _a : 'other');
|
|
289
|
+
const lifecycleStage = ((_b = person.lifecycle_stage) !== null && _b !== void 0 ? _b : 'new');
|
|
290
|
+
sourceTotals.set(source, (sourceTotals.get(source) || 0) + 1);
|
|
291
|
+
currentStageTotals.set(lifecycleStage, (currentStageTotals.get(lifecycleStage) || 0) + 1);
|
|
292
|
+
const period = this.getReportPeriodKey(person.created_at, query.group_by);
|
|
293
|
+
if (!period) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const bucket = this.getOrCreateReportTimelineBucket(timelineByPeriod, period);
|
|
297
|
+
bucket.new_leads += 1;
|
|
298
|
+
}
|
|
299
|
+
for (const row of stageRows) {
|
|
300
|
+
if (row.from_stage === row.to_stage) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const period = this.getReportPeriodKey(row.changed_at, query.group_by);
|
|
304
|
+
if (!period) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const bucket = this.getOrCreateReportTimelineBucket(timelineByPeriod, period);
|
|
308
|
+
const ownerUserId = this.coerceNumber(row.changed_by_user_id);
|
|
309
|
+
if (row.to_stage === 'qualified') {
|
|
310
|
+
summary.qualified_moves += 1;
|
|
311
|
+
bucket.qualified_moves += 1;
|
|
312
|
+
}
|
|
313
|
+
if (row.to_stage === 'customer') {
|
|
314
|
+
summary.customer_moves += 1;
|
|
315
|
+
bucket.customer_moves += 1;
|
|
316
|
+
if (ownerUserId > 0) {
|
|
317
|
+
const current = ownersById.get(ownerUserId) ||
|
|
318
|
+
{
|
|
319
|
+
owner_user_id: ownerUserId,
|
|
320
|
+
owner_name: `#${ownerUserId}`,
|
|
321
|
+
interactions: 0,
|
|
322
|
+
followups_completed: 0,
|
|
323
|
+
customer_moves: 0,
|
|
324
|
+
};
|
|
325
|
+
current.customer_moves += 1;
|
|
326
|
+
ownersById.set(ownerUserId, current);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (row.to_stage === 'lost') {
|
|
330
|
+
summary.lost_moves += 1;
|
|
331
|
+
bucket.lost_moves += 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const row of activityRows) {
|
|
335
|
+
const createdInRange = this.isDateWithinRange(row.created_at, { start, end });
|
|
336
|
+
const completedInRange = row.completed_at != null && this.isDateWithinRange(row.completed_at, { start, end });
|
|
337
|
+
if (createdInRange && CRM_REPORT_ACTIVITY_ORDER.includes(row.type)) {
|
|
338
|
+
const type = row.type;
|
|
339
|
+
activityTypeTotals.set(type, (activityTypeTotals.get(type) || 0) + 1);
|
|
340
|
+
}
|
|
341
|
+
if (createdInRange && row.source_kind === 'interaction') {
|
|
342
|
+
summary.interactions += 1;
|
|
343
|
+
const period = this.getReportPeriodKey(row.created_at, query.group_by);
|
|
344
|
+
if (period) {
|
|
345
|
+
const bucket = this.getOrCreateReportTimelineBucket(timelineByPeriod, period);
|
|
346
|
+
bucket.interactions += 1;
|
|
347
|
+
}
|
|
348
|
+
const ownerUserId = this.coerceNumber(row.owner_user_id) ||
|
|
349
|
+
this.coerceNumber(row.completed_by_user_id) ||
|
|
350
|
+
this.coerceNumber(row.created_by_user_id);
|
|
351
|
+
if (ownerUserId > 0) {
|
|
352
|
+
const current = ownersById.get(ownerUserId) ||
|
|
353
|
+
{
|
|
354
|
+
owner_user_id: ownerUserId,
|
|
355
|
+
owner_name: `#${ownerUserId}`,
|
|
356
|
+
interactions: 0,
|
|
357
|
+
followups_completed: 0,
|
|
358
|
+
customer_moves: 0,
|
|
359
|
+
};
|
|
360
|
+
current.interactions += 1;
|
|
361
|
+
ownersById.set(ownerUserId, current);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (completedInRange && row.source_kind === 'followup') {
|
|
365
|
+
summary.followups_completed += 1;
|
|
366
|
+
const period = this.getReportPeriodKey(row.completed_at, query.group_by);
|
|
367
|
+
if (period) {
|
|
368
|
+
const bucket = this.getOrCreateReportTimelineBucket(timelineByPeriod, period);
|
|
369
|
+
bucket.followups_completed += 1;
|
|
370
|
+
}
|
|
371
|
+
const ownerUserId = this.coerceNumber(row.completed_by_user_id) ||
|
|
372
|
+
this.coerceNumber(row.owner_user_id) ||
|
|
373
|
+
this.coerceNumber(row.created_by_user_id);
|
|
374
|
+
if (ownerUserId > 0) {
|
|
375
|
+
const current = ownersById.get(ownerUserId) ||
|
|
376
|
+
{
|
|
377
|
+
owner_user_id: ownerUserId,
|
|
378
|
+
owner_name: `#${ownerUserId}`,
|
|
379
|
+
interactions: 0,
|
|
380
|
+
followups_completed: 0,
|
|
381
|
+
customer_moves: 0,
|
|
382
|
+
};
|
|
383
|
+
current.followups_completed += 1;
|
|
384
|
+
ownersById.set(ownerUserId, current);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
summary.conversion_rate =
|
|
389
|
+
summary.new_leads > 0 ? summary.customer_moves / summary.new_leads : 0;
|
|
390
|
+
const ownerIds = Array.from(ownersById.keys());
|
|
391
|
+
if (ownerIds.length > 0) {
|
|
392
|
+
const owners = await this.prismaService.user.findMany({
|
|
393
|
+
where: {
|
|
394
|
+
id: {
|
|
395
|
+
in: ownerIds,
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
select: {
|
|
399
|
+
id: true,
|
|
400
|
+
name: true,
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
for (const owner of owners) {
|
|
404
|
+
const current = ownersById.get(owner.id);
|
|
405
|
+
if (current) {
|
|
406
|
+
current.owner_name = owner.name || `#${owner.id}`;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const timeline = Array.from(timelineByPeriod.keys())
|
|
411
|
+
.sort((left, right) => left.localeCompare(right))
|
|
412
|
+
.map((period) => {
|
|
413
|
+
const bucket = timelineByPeriod.get(period);
|
|
414
|
+
return Object.assign(Object.assign({}, bucket), { conversion_rate: bucket.new_leads > 0 ? bucket.customer_moves / bucket.new_leads : 0 });
|
|
415
|
+
});
|
|
416
|
+
return {
|
|
417
|
+
summary,
|
|
418
|
+
timeline,
|
|
419
|
+
breakdowns: {
|
|
420
|
+
source: CRM_DASHBOARD_SOURCE_ORDER.map((key) => ({
|
|
421
|
+
key,
|
|
422
|
+
total: sourceTotals.get(key) || 0,
|
|
423
|
+
})),
|
|
424
|
+
current_stage: CRM_DASHBOARD_STAGE_ORDER.map((key) => ({
|
|
425
|
+
key,
|
|
426
|
+
total: currentStageTotals.get(key) || 0,
|
|
427
|
+
})),
|
|
428
|
+
activity_type: CRM_REPORT_ACTIVITY_ORDER.map((key) => ({
|
|
429
|
+
key,
|
|
430
|
+
total: activityTypeTotals.get(key) || 0,
|
|
431
|
+
})),
|
|
432
|
+
},
|
|
433
|
+
owners: Array.from(ownersById.values()).sort((left, right) => left.owner_name.localeCompare(right.owner_name)),
|
|
434
|
+
table: timeline,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
200
437
|
async getOwnerOptions(currentUserId) {
|
|
201
438
|
const where = {
|
|
202
439
|
OR: [
|
|
@@ -281,13 +518,13 @@ let PersonService = class PersonService {
|
|
|
281
518
|
const excludedPersonFilter = excludedPersonId > 0
|
|
282
519
|
? api_prisma_1.Prisma.sql ` AND c.person_id <> ${excludedPersonId}`
|
|
283
520
|
: api_prisma_1.Prisma.empty;
|
|
284
|
-
const phoneRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
285
|
-
SELECT DISTINCT c.person_id
|
|
286
|
-
FROM contact c
|
|
287
|
-
JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
288
|
-
WHERE UPPER(ct.code) IN ('PHONE', 'MOBILE', 'WHATSAPP')
|
|
289
|
-
AND regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') = ${normalizedPhone}
|
|
290
|
-
${excludedPersonFilter}
|
|
521
|
+
const phoneRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
522
|
+
SELECT DISTINCT c.person_id
|
|
523
|
+
FROM contact c
|
|
524
|
+
JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
525
|
+
WHERE UPPER(ct.code) IN ('PHONE', 'MOBILE', 'WHATSAPP')
|
|
526
|
+
AND regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') = ${normalizedPhone}
|
|
527
|
+
${excludedPersonFilter}
|
|
291
528
|
`);
|
|
292
529
|
for (const row of phoneRows) {
|
|
293
530
|
this.addDuplicateReason(reasonsByPersonId, row.person_id, 'phone');
|
|
@@ -300,12 +537,12 @@ let PersonService = class PersonService {
|
|
|
300
537
|
const documentTypeFilter = documentTypeId > 0
|
|
301
538
|
? api_prisma_1.Prisma.sql ` AND d.document_type_id = ${documentTypeId}`
|
|
302
539
|
: api_prisma_1.Prisma.empty;
|
|
303
|
-
const documentRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
304
|
-
SELECT DISTINCT d.person_id
|
|
305
|
-
FROM document d
|
|
306
|
-
WHERE regexp_replace(COALESCE(d.value, ''), '[^0-9]+', '', 'g') = ${normalizedDocument}
|
|
307
|
-
${excludedPersonFilter}
|
|
308
|
-
${documentTypeFilter}
|
|
540
|
+
const documentRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
541
|
+
SELECT DISTINCT d.person_id
|
|
542
|
+
FROM document d
|
|
543
|
+
WHERE regexp_replace(COALESCE(d.value, ''), '[^0-9]+', '', 'g') = ${normalizedDocument}
|
|
544
|
+
${excludedPersonFilter}
|
|
545
|
+
${documentTypeFilter}
|
|
309
546
|
`);
|
|
310
547
|
for (const row of documentRows) {
|
|
311
548
|
this.addDuplicateReason(reasonsByPersonId, row.person_id, 'document');
|
|
@@ -480,26 +717,26 @@ let PersonService = class PersonService {
|
|
|
480
717
|
lifecycleStage: paginationParams.lifecycle_stage,
|
|
481
718
|
});
|
|
482
719
|
const orderBy = this.getAccountOrderBySql(paginationParams.sortField, paginationParams.sortOrder);
|
|
483
|
-
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
484
|
-
SELECT COUNT(*) AS total
|
|
485
|
-
FROM person p
|
|
486
|
-
INNER JOIN person_company pc ON pc.id = p.id
|
|
487
|
-
WHERE p.type = 'company'
|
|
488
|
-
${filters}
|
|
720
|
+
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
721
|
+
SELECT COUNT(*) AS total
|
|
722
|
+
FROM person p
|
|
723
|
+
INNER JOIN person_company pc ON pc.id = p.id
|
|
724
|
+
WHERE p.type = 'company'
|
|
725
|
+
${filters}
|
|
489
726
|
`);
|
|
490
727
|
const total = this.coerceCount((_a = totalRows[0]) === null || _a === void 0 ? void 0 : _a.total);
|
|
491
728
|
if (total === 0) {
|
|
492
729
|
return this.createEmptyAccountPagination(page, pageSize);
|
|
493
730
|
}
|
|
494
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
495
|
-
SELECT p.id AS person_id
|
|
496
|
-
FROM person p
|
|
497
|
-
INNER JOIN person_company pc ON pc.id = p.id
|
|
498
|
-
WHERE p.type = 'company'
|
|
499
|
-
${filters}
|
|
500
|
-
ORDER BY ${orderBy}, p.id ASC
|
|
501
|
-
LIMIT ${pageSize}
|
|
502
|
-
OFFSET ${skip}
|
|
731
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
732
|
+
SELECT p.id AS person_id
|
|
733
|
+
FROM person p
|
|
734
|
+
INNER JOIN person_company pc ON pc.id = p.id
|
|
735
|
+
WHERE p.type = 'company'
|
|
736
|
+
${filters}
|
|
737
|
+
ORDER BY ${orderBy}, p.id ASC
|
|
738
|
+
LIMIT ${pageSize}
|
|
739
|
+
OFFSET ${skip}
|
|
503
740
|
`);
|
|
504
741
|
const personIds = rows.map((row) => row.person_id).filter((id) => id > 0);
|
|
505
742
|
if (personIds.length === 0) {
|
|
@@ -547,15 +784,15 @@ let PersonService = class PersonService {
|
|
|
547
784
|
prospects: 0,
|
|
548
785
|
};
|
|
549
786
|
}
|
|
550
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
551
|
-
SELECT
|
|
552
|
-
COUNT(*) AS total,
|
|
553
|
-
COUNT(*) FILTER (WHERE p.status = 'active') AS active,
|
|
554
|
-
COUNT(*) FILTER (WHERE pc.account_lifecycle_stage = 'customer') AS customers,
|
|
555
|
-
COUNT(*) FILTER (WHERE pc.account_lifecycle_stage = 'prospect') AS prospects
|
|
556
|
-
FROM person p
|
|
557
|
-
INNER JOIN person_company pc ON pc.id = p.id
|
|
558
|
-
WHERE p.type = 'company'
|
|
787
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
788
|
+
SELECT
|
|
789
|
+
COUNT(*) AS total,
|
|
790
|
+
COUNT(*) FILTER (WHERE p.status = 'active') AS active,
|
|
791
|
+
COUNT(*) FILTER (WHERE pc.account_lifecycle_stage = 'customer') AS customers,
|
|
792
|
+
COUNT(*) FILTER (WHERE pc.account_lifecycle_stage = 'prospect') AS prospects
|
|
793
|
+
FROM person p
|
|
794
|
+
INNER JOIN person_company pc ON pc.id = p.id
|
|
795
|
+
WHERE p.type = 'company'
|
|
559
796
|
`);
|
|
560
797
|
return {
|
|
561
798
|
total: this.coerceCount((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.total),
|
|
@@ -665,44 +902,44 @@ let PersonService = class PersonService {
|
|
|
665
902
|
type: paginationParams.type,
|
|
666
903
|
priority: paginationParams.priority,
|
|
667
904
|
});
|
|
668
|
-
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
669
|
-
SELECT COUNT(*) AS total
|
|
670
|
-
FROM crm_activity a
|
|
671
|
-
INNER JOIN person p ON p.id = a.person_id
|
|
672
|
-
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
673
|
-
WHERE 1 = 1
|
|
674
|
-
${filters}
|
|
905
|
+
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
906
|
+
SELECT COUNT(*) AS total
|
|
907
|
+
FROM crm_activity a
|
|
908
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
909
|
+
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
910
|
+
WHERE 1 = 1
|
|
911
|
+
${filters}
|
|
675
912
|
`);
|
|
676
913
|
const total = this.coerceCount((_a = totalRows[0]) === null || _a === void 0 ? void 0 : _a.total);
|
|
677
914
|
if (total === 0) {
|
|
678
915
|
return this.createEmptyCrmActivityPagination(page, pageSize);
|
|
679
916
|
}
|
|
680
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
681
|
-
SELECT
|
|
682
|
-
a.id,
|
|
683
|
-
a.person_id,
|
|
684
|
-
a.owner_user_id,
|
|
685
|
-
a.type,
|
|
686
|
-
a.subject,
|
|
687
|
-
a.notes,
|
|
688
|
-
a.due_at,
|
|
689
|
-
a.completed_at,
|
|
690
|
-
a.created_at,
|
|
691
|
-
a.priority,
|
|
692
|
-
p.name AS person_name,
|
|
693
|
-
p.type AS person_type,
|
|
694
|
-
p.status AS person_status,
|
|
695
|
-
pc.trade_name AS person_trade_name,
|
|
696
|
-
owner_user.name AS owner_user_name
|
|
697
|
-
FROM crm_activity a
|
|
698
|
-
INNER JOIN person p ON p.id = a.person_id
|
|
699
|
-
LEFT JOIN person_company pc ON pc.id = p.id
|
|
700
|
-
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
701
|
-
WHERE 1 = 1
|
|
702
|
-
${filters}
|
|
703
|
-
ORDER BY a.due_at ASC, a.id ASC
|
|
704
|
-
LIMIT ${pageSize}
|
|
705
|
-
OFFSET ${skip}
|
|
917
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
918
|
+
SELECT
|
|
919
|
+
a.id,
|
|
920
|
+
a.person_id,
|
|
921
|
+
a.owner_user_id,
|
|
922
|
+
a.type,
|
|
923
|
+
a.subject,
|
|
924
|
+
a.notes,
|
|
925
|
+
a.due_at,
|
|
926
|
+
a.completed_at,
|
|
927
|
+
a.created_at,
|
|
928
|
+
a.priority,
|
|
929
|
+
p.name AS person_name,
|
|
930
|
+
p.type AS person_type,
|
|
931
|
+
p.status AS person_status,
|
|
932
|
+
pc.trade_name AS person_trade_name,
|
|
933
|
+
owner_user.name AS owner_user_name
|
|
934
|
+
FROM crm_activity a
|
|
935
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
936
|
+
LEFT JOIN person_company pc ON pc.id = p.id
|
|
937
|
+
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
938
|
+
WHERE 1 = 1
|
|
939
|
+
${filters}
|
|
940
|
+
ORDER BY a.due_at ASC, a.id ASC
|
|
941
|
+
LIMIT ${pageSize}
|
|
942
|
+
OFFSET ${skip}
|
|
706
943
|
`);
|
|
707
944
|
const data = rows.map((row) => this.mapCrmActivityListRow(row));
|
|
708
945
|
const lastPage = Math.max(1, Math.ceil(total / pageSize));
|
|
@@ -722,24 +959,24 @@ let PersonService = class PersonService {
|
|
|
722
959
|
const visibilityFilter = allowCompanyRegistration
|
|
723
960
|
? api_prisma_1.Prisma.empty
|
|
724
961
|
: api_prisma_1.Prisma.sql `AND p.type = 'individual'`;
|
|
725
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
726
|
-
SELECT
|
|
727
|
-
COUNT(*) AS total,
|
|
728
|
-
COUNT(*) FILTER (
|
|
729
|
-
WHERE a.completed_at IS NULL
|
|
730
|
-
AND a.due_at >= NOW()
|
|
731
|
-
) AS pending,
|
|
732
|
-
COUNT(*) FILTER (
|
|
733
|
-
WHERE a.completed_at IS NULL
|
|
734
|
-
AND a.due_at < NOW()
|
|
735
|
-
) AS overdue,
|
|
736
|
-
COUNT(*) FILTER (
|
|
737
|
-
WHERE a.completed_at IS NOT NULL
|
|
738
|
-
) AS completed
|
|
739
|
-
FROM crm_activity a
|
|
740
|
-
INNER JOIN person p ON p.id = a.person_id
|
|
741
|
-
WHERE 1 = 1
|
|
742
|
-
${visibilityFilter}
|
|
962
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
963
|
+
SELECT
|
|
964
|
+
COUNT(*) AS total,
|
|
965
|
+
COUNT(*) FILTER (
|
|
966
|
+
WHERE a.completed_at IS NULL
|
|
967
|
+
AND a.due_at >= NOW()
|
|
968
|
+
) AS pending,
|
|
969
|
+
COUNT(*) FILTER (
|
|
970
|
+
WHERE a.completed_at IS NULL
|
|
971
|
+
AND a.due_at < NOW()
|
|
972
|
+
) AS overdue,
|
|
973
|
+
COUNT(*) FILTER (
|
|
974
|
+
WHERE a.completed_at IS NOT NULL
|
|
975
|
+
) AS completed
|
|
976
|
+
FROM crm_activity a
|
|
977
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
978
|
+
WHERE 1 = 1
|
|
979
|
+
${visibilityFilter}
|
|
743
980
|
`);
|
|
744
981
|
return {
|
|
745
982
|
total: this.coerceCount((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.total),
|
|
@@ -753,37 +990,37 @@ let PersonService = class PersonService {
|
|
|
753
990
|
const visibilityFilter = allowCompanyRegistration
|
|
754
991
|
? api_prisma_1.Prisma.empty
|
|
755
992
|
: api_prisma_1.Prisma.sql `AND p.type = 'individual'`;
|
|
756
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
757
|
-
SELECT
|
|
758
|
-
a.id,
|
|
759
|
-
a.person_id,
|
|
760
|
-
a.owner_user_id,
|
|
761
|
-
a.created_by_user_id,
|
|
762
|
-
a.completed_by_user_id,
|
|
763
|
-
a.type,
|
|
764
|
-
a.subject,
|
|
765
|
-
a.notes,
|
|
766
|
-
a.due_at,
|
|
767
|
-
a.completed_at,
|
|
768
|
-
a.created_at,
|
|
769
|
-
a.priority,
|
|
770
|
-
a.source_kind,
|
|
771
|
-
p.name AS person_name,
|
|
772
|
-
p.type AS person_type,
|
|
773
|
-
p.status AS person_status,
|
|
774
|
-
pc.trade_name AS person_trade_name,
|
|
775
|
-
owner_user.name AS owner_user_name,
|
|
776
|
-
created_by_user.name AS created_by_user_name,
|
|
777
|
-
completed_by_user.name AS completed_by_user_name
|
|
778
|
-
FROM crm_activity a
|
|
779
|
-
INNER JOIN person p ON p.id = a.person_id
|
|
780
|
-
LEFT JOIN person_company pc ON pc.id = p.id
|
|
781
|
-
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
782
|
-
LEFT JOIN "user" created_by_user ON created_by_user.id = a.created_by_user_id
|
|
783
|
-
LEFT JOIN "user" completed_by_user ON completed_by_user.id = a.completed_by_user_id
|
|
784
|
-
WHERE a.id = ${id}
|
|
785
|
-
${visibilityFilter}
|
|
786
|
-
LIMIT 1
|
|
993
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
994
|
+
SELECT
|
|
995
|
+
a.id,
|
|
996
|
+
a.person_id,
|
|
997
|
+
a.owner_user_id,
|
|
998
|
+
a.created_by_user_id,
|
|
999
|
+
a.completed_by_user_id,
|
|
1000
|
+
a.type,
|
|
1001
|
+
a.subject,
|
|
1002
|
+
a.notes,
|
|
1003
|
+
a.due_at,
|
|
1004
|
+
a.completed_at,
|
|
1005
|
+
a.created_at,
|
|
1006
|
+
a.priority,
|
|
1007
|
+
a.source_kind,
|
|
1008
|
+
p.name AS person_name,
|
|
1009
|
+
p.type AS person_type,
|
|
1010
|
+
p.status AS person_status,
|
|
1011
|
+
pc.trade_name AS person_trade_name,
|
|
1012
|
+
owner_user.name AS owner_user_name,
|
|
1013
|
+
created_by_user.name AS created_by_user_name,
|
|
1014
|
+
completed_by_user.name AS completed_by_user_name
|
|
1015
|
+
FROM crm_activity a
|
|
1016
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
1017
|
+
LEFT JOIN person_company pc ON pc.id = p.id
|
|
1018
|
+
LEFT JOIN "user" owner_user ON owner_user.id = a.owner_user_id
|
|
1019
|
+
LEFT JOIN "user" created_by_user ON created_by_user.id = a.created_by_user_id
|
|
1020
|
+
LEFT JOIN "user" completed_by_user ON completed_by_user.id = a.completed_by_user_id
|
|
1021
|
+
WHERE a.id = ${id}
|
|
1022
|
+
${visibilityFilter}
|
|
1023
|
+
LIMIT 1
|
|
787
1024
|
`);
|
|
788
1025
|
const row = rows[0];
|
|
789
1026
|
if (!row) {
|
|
@@ -798,17 +1035,17 @@ let PersonService = class PersonService {
|
|
|
798
1035
|
? api_prisma_1.Prisma.empty
|
|
799
1036
|
: api_prisma_1.Prisma.sql `AND p.type = 'individual'`;
|
|
800
1037
|
return this.prismaService.$transaction(async (tx) => {
|
|
801
|
-
const rows = (await tx.$queryRaw(api_prisma_1.Prisma.sql `
|
|
802
|
-
SELECT
|
|
803
|
-
a.id,
|
|
804
|
-
a.person_id,
|
|
805
|
-
a.completed_at,
|
|
806
|
-
a.source_kind
|
|
807
|
-
FROM crm_activity a
|
|
808
|
-
INNER JOIN person p ON p.id = a.person_id
|
|
809
|
-
WHERE a.id = ${id}
|
|
810
|
-
${visibilityFilter}
|
|
811
|
-
LIMIT 1
|
|
1038
|
+
const rows = (await tx.$queryRaw(api_prisma_1.Prisma.sql `
|
|
1039
|
+
SELECT
|
|
1040
|
+
a.id,
|
|
1041
|
+
a.person_id,
|
|
1042
|
+
a.completed_at,
|
|
1043
|
+
a.source_kind
|
|
1044
|
+
FROM crm_activity a
|
|
1045
|
+
INNER JOIN person p ON p.id = a.person_id
|
|
1046
|
+
WHERE a.id = ${id}
|
|
1047
|
+
${visibilityFilter}
|
|
1048
|
+
LIMIT 1
|
|
812
1049
|
`));
|
|
813
1050
|
const activity = rows[0];
|
|
814
1051
|
if (!activity) {
|
|
@@ -821,13 +1058,13 @@ let PersonService = class PersonService {
|
|
|
821
1058
|
};
|
|
822
1059
|
}
|
|
823
1060
|
const completedAt = new Date();
|
|
824
|
-
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
825
|
-
UPDATE crm_activity
|
|
826
|
-
SET
|
|
827
|
-
completed_at = CAST(${completedAt.toISOString()} AS TIMESTAMPTZ),
|
|
828
|
-
completed_by_user_id = ${actorUserId},
|
|
829
|
-
updated_at = NOW()
|
|
830
|
-
WHERE id = ${id}
|
|
1061
|
+
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
1062
|
+
UPDATE crm_activity
|
|
1063
|
+
SET
|
|
1064
|
+
completed_at = CAST(${completedAt.toISOString()} AS TIMESTAMPTZ),
|
|
1065
|
+
completed_by_user_id = ${actorUserId},
|
|
1066
|
+
updated_at = NOW()
|
|
1067
|
+
WHERE id = ${id}
|
|
831
1068
|
`);
|
|
832
1069
|
if (activity.source_kind === 'followup') {
|
|
833
1070
|
await this.upsertMetadataValue(tx, activity.person_id, NEXT_ACTION_AT_METADATA_KEY, null);
|
|
@@ -856,30 +1093,30 @@ let PersonService = class PersonService {
|
|
|
856
1093
|
dateTo: paginationParams.date_to,
|
|
857
1094
|
});
|
|
858
1095
|
const followupTimestampSql = this.getFollowupTimestampSql();
|
|
859
|
-
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
860
|
-
SELECT COUNT(*) AS total
|
|
861
|
-
FROM person p
|
|
862
|
-
INNER JOIN person_metadata pm_next
|
|
863
|
-
ON pm_next.person_id = p.id
|
|
864
|
-
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
865
|
-
WHERE 1 = 1
|
|
866
|
-
${filters}
|
|
1096
|
+
const totalRows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
1097
|
+
SELECT COUNT(*) AS total
|
|
1098
|
+
FROM person p
|
|
1099
|
+
INNER JOIN person_metadata pm_next
|
|
1100
|
+
ON pm_next.person_id = p.id
|
|
1101
|
+
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
1102
|
+
WHERE 1 = 1
|
|
1103
|
+
${filters}
|
|
867
1104
|
`);
|
|
868
1105
|
const total = this.coerceCount((_a = totalRows[0]) === null || _a === void 0 ? void 0 : _a.total);
|
|
869
1106
|
if (total === 0) {
|
|
870
1107
|
return this.createEmptyFollowupPagination(page, pageSize);
|
|
871
1108
|
}
|
|
872
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
873
|
-
SELECT p.id AS person_id
|
|
874
|
-
FROM person p
|
|
875
|
-
INNER JOIN person_metadata pm_next
|
|
876
|
-
ON pm_next.person_id = p.id
|
|
877
|
-
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
878
|
-
WHERE 1 = 1
|
|
879
|
-
${filters}
|
|
880
|
-
ORDER BY ${followupTimestampSql} ASC, p.id ASC
|
|
881
|
-
LIMIT ${pageSize}
|
|
882
|
-
OFFSET ${skip}
|
|
1109
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
1110
|
+
SELECT p.id AS person_id
|
|
1111
|
+
FROM person p
|
|
1112
|
+
INNER JOIN person_metadata pm_next
|
|
1113
|
+
ON pm_next.person_id = p.id
|
|
1114
|
+
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
1115
|
+
WHERE 1 = 1
|
|
1116
|
+
${filters}
|
|
1117
|
+
ORDER BY ${followupTimestampSql} ASC, p.id ASC
|
|
1118
|
+
LIMIT ${pageSize}
|
|
1119
|
+
OFFSET ${skip}
|
|
883
1120
|
`);
|
|
884
1121
|
const personIds = rows.map((row) => row.person_id).filter((id) => id > 0);
|
|
885
1122
|
const people = personIds.length > 0
|
|
@@ -948,25 +1185,25 @@ let PersonService = class PersonService {
|
|
|
948
1185
|
});
|
|
949
1186
|
const followupTimestampSql = this.getFollowupTimestampSql();
|
|
950
1187
|
const { todayStartIso, tomorrowStartIso } = this.getFollowupDayBoundaryIsoStrings();
|
|
951
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
952
|
-
SELECT
|
|
953
|
-
COUNT(*) AS total,
|
|
954
|
-
COUNT(*) FILTER (
|
|
955
|
-
WHERE ${followupTimestampSql} >= CAST(${todayStartIso} AS TIMESTAMPTZ)
|
|
956
|
-
AND ${followupTimestampSql} < CAST(${tomorrowStartIso} AS TIMESTAMPTZ)
|
|
957
|
-
) AS today,
|
|
958
|
-
COUNT(*) FILTER (
|
|
959
|
-
WHERE ${followupTimestampSql} < CAST(${todayStartIso} AS TIMESTAMPTZ)
|
|
960
|
-
) AS overdue,
|
|
961
|
-
COUNT(*) FILTER (
|
|
962
|
-
WHERE ${followupTimestampSql} >= CAST(${tomorrowStartIso} AS TIMESTAMPTZ)
|
|
963
|
-
) AS upcoming
|
|
964
|
-
FROM person p
|
|
965
|
-
INNER JOIN person_metadata pm_next
|
|
966
|
-
ON pm_next.person_id = p.id
|
|
967
|
-
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
968
|
-
WHERE 1 = 1
|
|
969
|
-
${filters}
|
|
1188
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
1189
|
+
SELECT
|
|
1190
|
+
COUNT(*) AS total,
|
|
1191
|
+
COUNT(*) FILTER (
|
|
1192
|
+
WHERE ${followupTimestampSql} >= CAST(${todayStartIso} AS TIMESTAMPTZ)
|
|
1193
|
+
AND ${followupTimestampSql} < CAST(${tomorrowStartIso} AS TIMESTAMPTZ)
|
|
1194
|
+
) AS today,
|
|
1195
|
+
COUNT(*) FILTER (
|
|
1196
|
+
WHERE ${followupTimestampSql} < CAST(${todayStartIso} AS TIMESTAMPTZ)
|
|
1197
|
+
) AS overdue,
|
|
1198
|
+
COUNT(*) FILTER (
|
|
1199
|
+
WHERE ${followupTimestampSql} >= CAST(${tomorrowStartIso} AS TIMESTAMPTZ)
|
|
1200
|
+
) AS upcoming
|
|
1201
|
+
FROM person p
|
|
1202
|
+
INNER JOIN person_metadata pm_next
|
|
1203
|
+
ON pm_next.person_id = p.id
|
|
1204
|
+
AND pm_next.key = ${NEXT_ACTION_AT_METADATA_KEY}
|
|
1205
|
+
WHERE 1 = 1
|
|
1206
|
+
${filters}
|
|
970
1207
|
`);
|
|
971
1208
|
return {
|
|
972
1209
|
total: this.coerceCount((_a = rows[0]) === null || _a === void 0 ? void 0 : _a.total),
|
|
@@ -1069,10 +1306,21 @@ let PersonService = class PersonService {
|
|
|
1069
1306
|
next_action_at: normalizedNextActionAt,
|
|
1070
1307
|
};
|
|
1071
1308
|
}
|
|
1072
|
-
async updateLifecycleStage(id, data, locale) {
|
|
1309
|
+
async updateLifecycleStage(id, data, locale, user) {
|
|
1073
1310
|
const person = await this.ensurePersonAccessible(id, locale);
|
|
1311
|
+
const currentLifecycleStage = await this.getPersonLifecycleStage(person.id);
|
|
1312
|
+
const nextLifecycleStage = this.normalizeTextOrNull(data.lifecycle_stage);
|
|
1313
|
+
const actorUserId = this.coerceNumber(user === null || user === void 0 ? void 0 : user.id) || null;
|
|
1074
1314
|
await this.prismaService.$transaction(async (tx) => {
|
|
1075
1315
|
await this.upsertMetadataValue(tx, person.id, LIFECYCLE_STAGE_METADATA_KEY, data.lifecycle_stage);
|
|
1316
|
+
if (nextLifecycleStage && currentLifecycleStage !== nextLifecycleStage) {
|
|
1317
|
+
await this.registerStageTransition(tx, {
|
|
1318
|
+
personId: person.id,
|
|
1319
|
+
fromStage: currentLifecycleStage,
|
|
1320
|
+
toStage: nextLifecycleStage,
|
|
1321
|
+
changedByUserId: actorUserId,
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1076
1324
|
});
|
|
1077
1325
|
return {
|
|
1078
1326
|
success: true,
|
|
@@ -1112,7 +1360,7 @@ let PersonService = class PersonService {
|
|
|
1112
1360
|
return person;
|
|
1113
1361
|
});
|
|
1114
1362
|
}
|
|
1115
|
-
async update(id, data, locale) {
|
|
1363
|
+
async update(id, data, locale, user) {
|
|
1116
1364
|
const allowCompanyRegistration = await this.isCompanyRegistrationAllowed();
|
|
1117
1365
|
const person = await this.prismaService.person.findUnique({ where: { id } });
|
|
1118
1366
|
if (!person) {
|
|
@@ -1126,6 +1374,8 @@ let PersonService = class PersonService {
|
|
|
1126
1374
|
nextType: data.type,
|
|
1127
1375
|
locale,
|
|
1128
1376
|
});
|
|
1377
|
+
const currentLifecycleStage = await this.getPersonLifecycleStage(id);
|
|
1378
|
+
const nextLifecycleStage = this.normalizeTextOrNull(data.lifecycle_stage);
|
|
1129
1379
|
const incomingContacts = Array.isArray(data.contacts) ? data.contacts : [];
|
|
1130
1380
|
const incomingAddresses = Array.isArray(data.addresses) ? data.addresses : [];
|
|
1131
1381
|
const incomingDocuments = Array.isArray(data.documents) ? data.documents : [];
|
|
@@ -1164,6 +1414,14 @@ let PersonService = class PersonService {
|
|
|
1164
1414
|
await this.syncContacts(tx, id, incomingContacts);
|
|
1165
1415
|
await this.syncAddresses(tx, id, incomingAddresses, locale);
|
|
1166
1416
|
await this.syncDocuments(tx, id, incomingDocuments);
|
|
1417
|
+
if (nextLifecycleStage && currentLifecycleStage !== nextLifecycleStage) {
|
|
1418
|
+
await this.registerStageTransition(tx, {
|
|
1419
|
+
personId: id,
|
|
1420
|
+
fromStage: currentLifecycleStage,
|
|
1421
|
+
toStage: nextLifecycleStage,
|
|
1422
|
+
changedByUserId: this.coerceNumber(user === null || user === void 0 ? void 0 : user.id) || null,
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1167
1425
|
return { success: true };
|
|
1168
1426
|
})
|
|
1169
1427
|
.then(async (result) => {
|
|
@@ -1401,6 +1659,64 @@ let PersonService = class PersonService {
|
|
|
1401
1659
|
},
|
|
1402
1660
|
};
|
|
1403
1661
|
}
|
|
1662
|
+
resolveReportsRange(query, locale) {
|
|
1663
|
+
const start = this.startOfDay(this.parseDateOrThrow(query.date_from, locale));
|
|
1664
|
+
const end = this.endOfDay(this.parseDateOrThrow(query.date_to, locale));
|
|
1665
|
+
if (start.getTime() > end.getTime()) {
|
|
1666
|
+
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.dateMustBeString', locale, 'date_from must be less than or equal to date_to.'));
|
|
1667
|
+
}
|
|
1668
|
+
return { start, end };
|
|
1669
|
+
}
|
|
1670
|
+
getReportPeriodKey(dateValue, groupBy) {
|
|
1671
|
+
const date = this.parseDateOrNull(dateValue);
|
|
1672
|
+
if (!date) {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
if (groupBy === 'day') {
|
|
1676
|
+
return this.toDateKey(date);
|
|
1677
|
+
}
|
|
1678
|
+
if (groupBy === 'week') {
|
|
1679
|
+
return this.toDateKey(this.startOfWeek(date));
|
|
1680
|
+
}
|
|
1681
|
+
if (groupBy === 'month') {
|
|
1682
|
+
const year = date.getFullYear();
|
|
1683
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1684
|
+
return `${year}-${month}`;
|
|
1685
|
+
}
|
|
1686
|
+
return String(date.getFullYear());
|
|
1687
|
+
}
|
|
1688
|
+
getOrCreateReportTimelineBucket(buckets, period) {
|
|
1689
|
+
const existing = buckets.get(period);
|
|
1690
|
+
if (existing) {
|
|
1691
|
+
return existing;
|
|
1692
|
+
}
|
|
1693
|
+
const next = {
|
|
1694
|
+
period,
|
|
1695
|
+
label: period,
|
|
1696
|
+
new_leads: 0,
|
|
1697
|
+
qualified_moves: 0,
|
|
1698
|
+
customer_moves: 0,
|
|
1699
|
+
lost_moves: 0,
|
|
1700
|
+
interactions: 0,
|
|
1701
|
+
followups_completed: 0,
|
|
1702
|
+
conversion_rate: 0,
|
|
1703
|
+
};
|
|
1704
|
+
buckets.set(period, next);
|
|
1705
|
+
return next;
|
|
1706
|
+
}
|
|
1707
|
+
startOfWeek(value) {
|
|
1708
|
+
const date = new Date(value);
|
|
1709
|
+
date.setHours(0, 0, 0, 0);
|
|
1710
|
+
const day = (date.getDay() + 6) % 7;
|
|
1711
|
+
date.setDate(date.getDate() - day);
|
|
1712
|
+
return date;
|
|
1713
|
+
}
|
|
1714
|
+
toDateKey(value) {
|
|
1715
|
+
const year = value.getFullYear();
|
|
1716
|
+
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
1717
|
+
const day = String(value.getDate()).padStart(2, '0');
|
|
1718
|
+
return `${year}-${month}-${day}`;
|
|
1719
|
+
}
|
|
1404
1720
|
buildDashboardOwnerPerformance(people) {
|
|
1405
1721
|
var _a, _b;
|
|
1406
1722
|
const byOwnerId = new Map();
|
|
@@ -1476,7 +1792,7 @@ let PersonService = class PersonService {
|
|
|
1476
1792
|
return [];
|
|
1477
1793
|
}
|
|
1478
1794
|
const personIds = people.map((person) => person.id);
|
|
1479
|
-
const [companies, individuals, companyBranches,
|
|
1795
|
+
const [companies, individuals, companyBranches, employerMetadataRaw] = await Promise.all([
|
|
1480
1796
|
this.prismaService.person_company.findMany({
|
|
1481
1797
|
where: { id: { in: personIds } },
|
|
1482
1798
|
}),
|
|
@@ -1496,8 +1812,9 @@ let PersonService = class PersonService {
|
|
|
1496
1812
|
person_id: true,
|
|
1497
1813
|
value: true,
|
|
1498
1814
|
},
|
|
1499
|
-
})
|
|
1815
|
+
}),
|
|
1500
1816
|
]);
|
|
1817
|
+
const employerMetadata = allowCompanyRegistration ? employerMetadataRaw : [];
|
|
1501
1818
|
const companyById = new Map(companies.map((item) => [item.id, item]));
|
|
1502
1819
|
const individualById = new Map(individuals.map((item) => [item.id, item]));
|
|
1503
1820
|
const branchesByHeadquarterId = new Map();
|
|
@@ -1722,6 +2039,47 @@ let PersonService = class PersonService {
|
|
|
1722
2039
|
})
|
|
1723
2040
|
.filter((item) => item != null));
|
|
1724
2041
|
}
|
|
2042
|
+
async getPersonLifecycleStage(personId) {
|
|
2043
|
+
var _a;
|
|
2044
|
+
const metadata = await this.prismaService.person_metadata.findFirst({
|
|
2045
|
+
where: {
|
|
2046
|
+
person_id: personId,
|
|
2047
|
+
key: LIFECYCLE_STAGE_METADATA_KEY,
|
|
2048
|
+
},
|
|
2049
|
+
select: {
|
|
2050
|
+
value: true,
|
|
2051
|
+
},
|
|
2052
|
+
});
|
|
2053
|
+
return (_a = this.metadataToString(metadata === null || metadata === void 0 ? void 0 : metadata.value)) !== null && _a !== void 0 ? _a : 'new';
|
|
2054
|
+
}
|
|
2055
|
+
async registerStageTransition(tx, { personId, fromStage, toStage, changedByUserId, }) {
|
|
2056
|
+
if (fromStage === toStage) {
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const fromStageSql = fromStage
|
|
2060
|
+
? api_prisma_1.Prisma.sql `CAST(${fromStage} AS crm_stage_history_from_stage_enum)`
|
|
2061
|
+
: api_prisma_1.Prisma.sql `NULL`;
|
|
2062
|
+
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2063
|
+
INSERT INTO crm_stage_history (
|
|
2064
|
+
person_id,
|
|
2065
|
+
from_stage,
|
|
2066
|
+
to_stage,
|
|
2067
|
+
changed_by_user_id,
|
|
2068
|
+
changed_at,
|
|
2069
|
+
created_at,
|
|
2070
|
+
updated_at
|
|
2071
|
+
)
|
|
2072
|
+
VALUES (
|
|
2073
|
+
${personId},
|
|
2074
|
+
${fromStageSql},
|
|
2075
|
+
CAST(${toStage} AS crm_stage_history_to_stage_enum),
|
|
2076
|
+
${changedByUserId},
|
|
2077
|
+
NOW(),
|
|
2078
|
+
NOW(),
|
|
2079
|
+
NOW()
|
|
2080
|
+
)
|
|
2081
|
+
`);
|
|
2082
|
+
}
|
|
1725
2083
|
async syncPersonSubtypeData(tx, personId, currentType, data, locale) {
|
|
1726
2084
|
var _a, _b, _c;
|
|
1727
2085
|
const targetType = (_a = data.type) !== null && _a !== void 0 ? _a : currentType;
|
|
@@ -2365,94 +2723,94 @@ let PersonService = class PersonService {
|
|
|
2365
2723
|
return ownerUserId && ownerUserId > 0 ? ownerUserId : null;
|
|
2366
2724
|
}
|
|
2367
2725
|
async upsertFollowupActivity(tx, { personId, ownerUserId, dueAt, notes, actorUserId, }) {
|
|
2368
|
-
const existingRows = (await tx.$queryRaw(api_prisma_1.Prisma.sql `
|
|
2369
|
-
SELECT id
|
|
2370
|
-
FROM crm_activity
|
|
2371
|
-
WHERE person_id = ${personId}
|
|
2372
|
-
AND source_kind = 'followup'
|
|
2373
|
-
AND completed_at IS NULL
|
|
2374
|
-
ORDER BY id DESC
|
|
2375
|
-
LIMIT 1
|
|
2726
|
+
const existingRows = (await tx.$queryRaw(api_prisma_1.Prisma.sql `
|
|
2727
|
+
SELECT id
|
|
2728
|
+
FROM crm_activity
|
|
2729
|
+
WHERE person_id = ${personId}
|
|
2730
|
+
AND source_kind = 'followup'
|
|
2731
|
+
AND completed_at IS NULL
|
|
2732
|
+
ORDER BY id DESC
|
|
2733
|
+
LIMIT 1
|
|
2376
2734
|
`));
|
|
2377
2735
|
const existing = existingRows[0];
|
|
2378
2736
|
const normalizedNotes = this.normalizeTextOrNull(notes);
|
|
2379
2737
|
if (existing) {
|
|
2380
|
-
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2381
|
-
UPDATE crm_activity
|
|
2382
|
-
SET
|
|
2383
|
-
owner_user_id = ${ownerUserId},
|
|
2384
|
-
type = CAST(${'task'} AS crm_activity_type_enum),
|
|
2385
|
-
subject = ${this.getFollowupActivitySubject()},
|
|
2386
|
-
notes = ${normalizedNotes},
|
|
2387
|
-
due_at = CAST(${dueAt} AS TIMESTAMPTZ),
|
|
2388
|
-
priority = CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2389
|
-
updated_at = NOW()
|
|
2390
|
-
WHERE id = ${existing.id}
|
|
2738
|
+
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2739
|
+
UPDATE crm_activity
|
|
2740
|
+
SET
|
|
2741
|
+
owner_user_id = ${ownerUserId},
|
|
2742
|
+
type = CAST(${'task'} AS crm_activity_type_enum),
|
|
2743
|
+
subject = ${this.getFollowupActivitySubject()},
|
|
2744
|
+
notes = ${normalizedNotes},
|
|
2745
|
+
due_at = CAST(${dueAt} AS TIMESTAMPTZ),
|
|
2746
|
+
priority = CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2747
|
+
updated_at = NOW()
|
|
2748
|
+
WHERE id = ${existing.id}
|
|
2391
2749
|
`);
|
|
2392
2750
|
return;
|
|
2393
2751
|
}
|
|
2394
|
-
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2395
|
-
INSERT INTO crm_activity (
|
|
2396
|
-
person_id,
|
|
2397
|
-
owner_user_id,
|
|
2398
|
-
created_by_user_id,
|
|
2399
|
-
type,
|
|
2400
|
-
subject,
|
|
2401
|
-
notes,
|
|
2402
|
-
due_at,
|
|
2403
|
-
priority,
|
|
2404
|
-
source_kind,
|
|
2405
|
-
created_at,
|
|
2406
|
-
updated_at
|
|
2407
|
-
)
|
|
2408
|
-
VALUES (
|
|
2409
|
-
${personId},
|
|
2410
|
-
${ownerUserId},
|
|
2411
|
-
${actorUserId},
|
|
2412
|
-
CAST(${'task'} AS crm_activity_type_enum),
|
|
2413
|
-
${this.getFollowupActivitySubject()},
|
|
2414
|
-
${normalizedNotes},
|
|
2415
|
-
CAST(${dueAt} AS TIMESTAMPTZ),
|
|
2416
|
-
CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2417
|
-
CAST(${'followup'} AS crm_activity_source_kind_enum),
|
|
2418
|
-
NOW(),
|
|
2419
|
-
NOW()
|
|
2420
|
-
)
|
|
2752
|
+
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2753
|
+
INSERT INTO crm_activity (
|
|
2754
|
+
person_id,
|
|
2755
|
+
owner_user_id,
|
|
2756
|
+
created_by_user_id,
|
|
2757
|
+
type,
|
|
2758
|
+
subject,
|
|
2759
|
+
notes,
|
|
2760
|
+
due_at,
|
|
2761
|
+
priority,
|
|
2762
|
+
source_kind,
|
|
2763
|
+
created_at,
|
|
2764
|
+
updated_at
|
|
2765
|
+
)
|
|
2766
|
+
VALUES (
|
|
2767
|
+
${personId},
|
|
2768
|
+
${ownerUserId},
|
|
2769
|
+
${actorUserId},
|
|
2770
|
+
CAST(${'task'} AS crm_activity_type_enum),
|
|
2771
|
+
${this.getFollowupActivitySubject()},
|
|
2772
|
+
${normalizedNotes},
|
|
2773
|
+
CAST(${dueAt} AS TIMESTAMPTZ),
|
|
2774
|
+
CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2775
|
+
CAST(${'followup'} AS crm_activity_source_kind_enum),
|
|
2776
|
+
NOW(),
|
|
2777
|
+
NOW()
|
|
2778
|
+
)
|
|
2421
2779
|
`);
|
|
2422
2780
|
}
|
|
2423
2781
|
async createCompletedInteractionActivity(tx, { personId, ownerUserId, interaction, actorUserId, }) {
|
|
2424
2782
|
const completedAt = new Date(interaction.created_at);
|
|
2425
|
-
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2426
|
-
INSERT INTO crm_activity (
|
|
2427
|
-
person_id,
|
|
2428
|
-
owner_user_id,
|
|
2429
|
-
created_by_user_id,
|
|
2430
|
-
completed_by_user_id,
|
|
2431
|
-
type,
|
|
2432
|
-
subject,
|
|
2433
|
-
notes,
|
|
2434
|
-
due_at,
|
|
2435
|
-
completed_at,
|
|
2436
|
-
priority,
|
|
2437
|
-
source_kind,
|
|
2438
|
-
created_at,
|
|
2439
|
-
updated_at
|
|
2440
|
-
)
|
|
2441
|
-
VALUES (
|
|
2442
|
-
${personId},
|
|
2443
|
-
${ownerUserId},
|
|
2444
|
-
${actorUserId},
|
|
2445
|
-
${actorUserId},
|
|
2446
|
-
CAST(${interaction.type} AS crm_activity_type_enum),
|
|
2447
|
-
${this.getInteractionActivitySubject(interaction.type)},
|
|
2448
|
-
${this.normalizeTextOrNull(interaction.notes)},
|
|
2449
|
-
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2450
|
-
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2451
|
-
CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2452
|
-
CAST(${'interaction'} AS crm_activity_source_kind_enum),
|
|
2453
|
-
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2454
|
-
NOW()
|
|
2455
|
-
)
|
|
2783
|
+
await tx.$executeRaw(api_prisma_1.Prisma.sql `
|
|
2784
|
+
INSERT INTO crm_activity (
|
|
2785
|
+
person_id,
|
|
2786
|
+
owner_user_id,
|
|
2787
|
+
created_by_user_id,
|
|
2788
|
+
completed_by_user_id,
|
|
2789
|
+
type,
|
|
2790
|
+
subject,
|
|
2791
|
+
notes,
|
|
2792
|
+
due_at,
|
|
2793
|
+
completed_at,
|
|
2794
|
+
priority,
|
|
2795
|
+
source_kind,
|
|
2796
|
+
created_at,
|
|
2797
|
+
updated_at
|
|
2798
|
+
)
|
|
2799
|
+
VALUES (
|
|
2800
|
+
${personId},
|
|
2801
|
+
${ownerUserId},
|
|
2802
|
+
${actorUserId},
|
|
2803
|
+
${actorUserId},
|
|
2804
|
+
CAST(${interaction.type} AS crm_activity_type_enum),
|
|
2805
|
+
${this.getInteractionActivitySubject(interaction.type)},
|
|
2806
|
+
${this.normalizeTextOrNull(interaction.notes)},
|
|
2807
|
+
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2808
|
+
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2809
|
+
CAST(${'medium'} AS crm_activity_priority_enum),
|
|
2810
|
+
CAST(${'interaction'} AS crm_activity_source_kind_enum),
|
|
2811
|
+
CAST(${interaction.created_at} AS TIMESTAMPTZ),
|
|
2812
|
+
NOW()
|
|
2813
|
+
)
|
|
2456
2814
|
`);
|
|
2457
2815
|
}
|
|
2458
2816
|
getFollowupActivitySubject() {
|
|
@@ -2722,33 +3080,33 @@ let PersonService = class PersonService {
|
|
|
2722
3080
|
const searchLike = `%${search}%`;
|
|
2723
3081
|
const normalizedDigits = this.normalizeDigits(search);
|
|
2724
3082
|
const digitsLike = `%${normalizedDigits}%`;
|
|
2725
|
-
filters.push(api_prisma_1.Prisma.sql `
|
|
2726
|
-
AND (
|
|
2727
|
-
p.name ILIKE ${searchLike}
|
|
2728
|
-
OR COALESCE(pc.trade_name, '') ILIKE ${searchLike}
|
|
2729
|
-
OR COALESCE(pc.city, '') ILIKE ${searchLike}
|
|
2730
|
-
OR COALESCE(pc.state, '') ILIKE ${searchLike}
|
|
2731
|
-
OR EXISTS (
|
|
2732
|
-
SELECT 1
|
|
2733
|
-
FROM contact c
|
|
2734
|
-
INNER JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
2735
|
-
WHERE c.person_id = p.id
|
|
2736
|
-
AND UPPER(ct.code) = 'EMAIL'
|
|
2737
|
-
AND c.value ILIKE ${searchLike}
|
|
2738
|
-
)
|
|
3083
|
+
filters.push(api_prisma_1.Prisma.sql `
|
|
3084
|
+
AND (
|
|
3085
|
+
p.name ILIKE ${searchLike}
|
|
3086
|
+
OR COALESCE(pc.trade_name, '') ILIKE ${searchLike}
|
|
3087
|
+
OR COALESCE(pc.city, '') ILIKE ${searchLike}
|
|
3088
|
+
OR COALESCE(pc.state, '') ILIKE ${searchLike}
|
|
3089
|
+
OR EXISTS (
|
|
3090
|
+
SELECT 1
|
|
3091
|
+
FROM contact c
|
|
3092
|
+
INNER JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
3093
|
+
WHERE c.person_id = p.id
|
|
3094
|
+
AND UPPER(ct.code) = 'EMAIL'
|
|
3095
|
+
AND c.value ILIKE ${searchLike}
|
|
3096
|
+
)
|
|
2739
3097
|
${normalizedDigits.length > 0
|
|
2740
|
-
? api_prisma_1.Prisma.sql `
|
|
2741
|
-
OR EXISTS (
|
|
2742
|
-
SELECT 1
|
|
2743
|
-
FROM contact c
|
|
2744
|
-
INNER JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
2745
|
-
WHERE c.person_id = p.id
|
|
2746
|
-
AND UPPER(ct.code) IN ('PHONE', 'MOBILE', 'WHATSAPP')
|
|
2747
|
-
AND regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') ILIKE ${digitsLike}
|
|
2748
|
-
)
|
|
3098
|
+
? api_prisma_1.Prisma.sql `
|
|
3099
|
+
OR EXISTS (
|
|
3100
|
+
SELECT 1
|
|
3101
|
+
FROM contact c
|
|
3102
|
+
INNER JOIN contact_type ct ON ct.id = c.contact_type_id
|
|
3103
|
+
WHERE c.person_id = p.id
|
|
3104
|
+
AND UPPER(ct.code) IN ('PHONE', 'MOBILE', 'WHATSAPP')
|
|
3105
|
+
AND regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') ILIKE ${digitsLike}
|
|
3106
|
+
)
|
|
2749
3107
|
`
|
|
2750
|
-
: api_prisma_1.Prisma.empty}
|
|
2751
|
-
)
|
|
3108
|
+
: api_prisma_1.Prisma.empty}
|
|
3109
|
+
)
|
|
2752
3110
|
`);
|
|
2753
3111
|
}
|
|
2754
3112
|
return filters.length > 0 ? api_prisma_1.Prisma.join(filters, '\n') : api_prisma_1.Prisma.empty;
|
|
@@ -2798,13 +3156,13 @@ let PersonService = class PersonService {
|
|
|
2798
3156
|
}
|
|
2799
3157
|
if (search) {
|
|
2800
3158
|
const searchLike = `%${search}%`;
|
|
2801
|
-
filters.push(api_prisma_1.Prisma.sql `
|
|
2802
|
-
AND (
|
|
2803
|
-
a.subject ILIKE ${searchLike}
|
|
2804
|
-
OR COALESCE(a.notes, '') ILIKE ${searchLike}
|
|
2805
|
-
OR p.name ILIKE ${searchLike}
|
|
2806
|
-
OR COALESCE(owner_user.name, '') ILIKE ${searchLike}
|
|
2807
|
-
)
|
|
3159
|
+
filters.push(api_prisma_1.Prisma.sql `
|
|
3160
|
+
AND (
|
|
3161
|
+
a.subject ILIKE ${searchLike}
|
|
3162
|
+
OR COALESCE(a.notes, '') ILIKE ${searchLike}
|
|
3163
|
+
OR p.name ILIKE ${searchLike}
|
|
3164
|
+
OR COALESCE(owner_user.name, '') ILIKE ${searchLike}
|
|
3165
|
+
)
|
|
2808
3166
|
`);
|
|
2809
3167
|
}
|
|
2810
3168
|
return filters.length > 0 ? api_prisma_1.Prisma.join(filters, '\n') : api_prisma_1.Prisma.empty;
|
|
@@ -2885,16 +3243,16 @@ let PersonService = class PersonService {
|
|
|
2885
3243
|
}
|
|
2886
3244
|
async findPersonIdsByNormalizedDigits(normalizedDigits) {
|
|
2887
3245
|
const likeValue = `%${normalizedDigits}%`;
|
|
2888
|
-
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
2889
|
-
SELECT DISTINCT p.id
|
|
2890
|
-
FROM person p
|
|
2891
|
-
LEFT JOIN contact c ON c.person_id = p.id
|
|
2892
|
-
LEFT JOIN document d ON d.person_id = p.id
|
|
2893
|
-
LEFT JOIN person_address pa ON pa.person_id = p.id
|
|
2894
|
-
LEFT JOIN address a ON a.id = pa.address_id
|
|
2895
|
-
WHERE regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
2896
|
-
OR regexp_replace(COALESCE(d.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
2897
|
-
OR regexp_replace(COALESCE(a.postal_code, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
3246
|
+
const rows = await this.prismaService.$queryRaw(api_prisma_1.Prisma.sql `
|
|
3247
|
+
SELECT DISTINCT p.id
|
|
3248
|
+
FROM person p
|
|
3249
|
+
LEFT JOIN contact c ON c.person_id = p.id
|
|
3250
|
+
LEFT JOIN document d ON d.person_id = p.id
|
|
3251
|
+
LEFT JOIN person_address pa ON pa.person_id = p.id
|
|
3252
|
+
LEFT JOIN address a ON a.id = pa.address_id
|
|
3253
|
+
WHERE regexp_replace(COALESCE(c.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
3254
|
+
OR regexp_replace(COALESCE(d.value, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
3255
|
+
OR regexp_replace(COALESCE(a.postal_code, ''), '[^0-9]+', '', 'g') ILIKE ${likeValue}
|
|
2898
3256
|
`);
|
|
2899
3257
|
return rows.map((row) => row.id);
|
|
2900
3258
|
}
|