@hed-hog/contact 0.0.301 → 0.0.302
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/person/person.service.d.ts +2 -0
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +111 -127
- package/dist/person/person.service.js.map +1 -1
- package/dist/person/person.service.spec.d.ts +2 -0
- package/dist/person/person.service.spec.d.ts.map +1 -0
- package/dist/person/person.service.spec.js +106 -0
- package/dist/person/person.service.spec.js.map +1 -0
- package/dist/proposal/proposal.service.d.ts +5 -0
- package/dist/proposal/proposal.service.d.ts.map +1 -1
- package/dist/proposal/proposal.service.js +242 -19
- package/dist/proposal/proposal.service.js.map +1 -1
- package/dist/proposal/proposal.service.spec.js +153 -165
- package/dist/proposal/proposal.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +35 -18
- package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +517 -346
- package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +42 -17
- package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +1 -1
- package/hedhog/frontend/app/activities/page.tsx.ejs +315 -101
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +172 -22
- package/hedhog/frontend/app/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +505 -428
- package/hedhog/frontend/app/pipeline/page.tsx.ejs +30 -4
- package/hedhog/frontend/app/proposals/_components/proposals-management-page.tsx.ejs +773 -0
- package/hedhog/frontend/app/proposals/approvals/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/proposals/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/reports/page.tsx.ejs +431 -375
- package/hedhog/frontend/messages/en.json +100 -1
- package/hedhog/frontend/messages/pt.json +100 -1
- package/package.json +6 -6
- package/src/person/person.service.spec.ts +143 -0
- package/src/person/person.service.ts +147 -158
- package/src/proposal/proposal.service.spec.ts +196 -0
- package/src/proposal/proposal.service.ts +348 -18
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/// <reference types="jest" />
|
|
4
|
+
const create_interaction_dto_1 = require("./dto/create-interaction.dto");
|
|
5
|
+
const person_service_1 = require("./person.service");
|
|
6
|
+
describe('PersonService CRM interaction/follow-up regression coverage', () => {
|
|
7
|
+
let prisma;
|
|
8
|
+
let tx;
|
|
9
|
+
let service;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tx = {
|
|
12
|
+
crm_activity: {
|
|
13
|
+
findFirst: jest.fn().mockResolvedValue(null),
|
|
14
|
+
create: jest.fn().mockResolvedValue({ id: 1 }),
|
|
15
|
+
update: jest.fn().mockResolvedValue({ id: 1 }),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
prisma = {
|
|
19
|
+
$transaction: jest.fn().mockImplementation(async (callback) => callback(tx)),
|
|
20
|
+
};
|
|
21
|
+
service = new person_service_1.PersonService(prisma, {}, {}, {
|
|
22
|
+
getSettingValues: jest.fn().mockResolvedValue({}),
|
|
23
|
+
});
|
|
24
|
+
jest
|
|
25
|
+
.spyOn(service, 'ensurePersonAccessible')
|
|
26
|
+
.mockResolvedValue({ id: 42, type: 'individual', status: 'active' });
|
|
27
|
+
jest.spyOn(service, 'getPersonOwnerUserId').mockResolvedValue(7);
|
|
28
|
+
jest.spyOn(service, 'loadInteractionsFromTx').mockResolvedValue([]);
|
|
29
|
+
jest.spyOn(service, 'upsertMetadataValue').mockResolvedValue(undefined);
|
|
30
|
+
});
|
|
31
|
+
it('persists a completed crm activity when registering an interaction', async () => {
|
|
32
|
+
await service.createInteraction(42, {
|
|
33
|
+
type: create_interaction_dto_1.PersonInteractionTypeDTO.CALL,
|
|
34
|
+
notes: 'Ligação inicial',
|
|
35
|
+
}, 'en', { id: 9, name: 'Root User' });
|
|
36
|
+
expect(tx.crm_activity.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
37
|
+
data: expect.objectContaining({
|
|
38
|
+
person_id: 42,
|
|
39
|
+
owner_user_id: 7,
|
|
40
|
+
created_by_user_id: 9,
|
|
41
|
+
completed_by_user_id: 9,
|
|
42
|
+
type: create_interaction_dto_1.PersonInteractionTypeDTO.CALL,
|
|
43
|
+
subject: 'Call',
|
|
44
|
+
notes: 'Ligação inicial',
|
|
45
|
+
priority: 'medium',
|
|
46
|
+
source_kind: 'interaction',
|
|
47
|
+
due_at: expect.any(Date),
|
|
48
|
+
completed_at: expect.any(Date),
|
|
49
|
+
created_at: expect.any(Date),
|
|
50
|
+
updated_at: expect.any(Date),
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
it('creates an open follow-up activity when none exists', async () => {
|
|
55
|
+
tx.crm_activity.findFirst.mockResolvedValue(null);
|
|
56
|
+
await service.scheduleFollowup(42, {
|
|
57
|
+
next_action_at: '2026-04-09T13:48:31.715Z',
|
|
58
|
+
notes: 'Retornar por telefone',
|
|
59
|
+
}, 'en', { id: 9, name: 'Root User' });
|
|
60
|
+
expect(tx.crm_activity.findFirst).toHaveBeenCalledWith({
|
|
61
|
+
where: {
|
|
62
|
+
person_id: 42,
|
|
63
|
+
source_kind: 'followup',
|
|
64
|
+
completed_at: null,
|
|
65
|
+
},
|
|
66
|
+
orderBy: {
|
|
67
|
+
id: 'desc',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
expect(tx.crm_activity.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
71
|
+
data: expect.objectContaining({
|
|
72
|
+
person_id: 42,
|
|
73
|
+
owner_user_id: 7,
|
|
74
|
+
created_by_user_id: 9,
|
|
75
|
+
type: 'task',
|
|
76
|
+
subject: 'Follow-up',
|
|
77
|
+
notes: 'Retornar por telefone',
|
|
78
|
+
priority: 'medium',
|
|
79
|
+
source_kind: 'followup',
|
|
80
|
+
due_at: new Date('2026-04-09T13:48:31.715Z'),
|
|
81
|
+
}),
|
|
82
|
+
}));
|
|
83
|
+
});
|
|
84
|
+
it('updates the existing open follow-up activity when rescheduling', async () => {
|
|
85
|
+
tx.crm_activity.findFirst.mockResolvedValue({ id: 88 });
|
|
86
|
+
await service.scheduleFollowup(42, {
|
|
87
|
+
next_action_at: '2026-04-10T15:00:00.000Z',
|
|
88
|
+
notes: 'Reagendar reunião',
|
|
89
|
+
}, 'en', { id: 11, name: 'Owner User' });
|
|
90
|
+
expect(tx.crm_activity.update).toHaveBeenCalledWith({
|
|
91
|
+
where: {
|
|
92
|
+
id: 88,
|
|
93
|
+
},
|
|
94
|
+
data: expect.objectContaining({
|
|
95
|
+
owner_user_id: 7,
|
|
96
|
+
type: 'task',
|
|
97
|
+
subject: 'Follow-up',
|
|
98
|
+
notes: 'Reagendar reunião',
|
|
99
|
+
due_at: new Date('2026-04-10T15:00:00.000Z'),
|
|
100
|
+
priority: 'medium',
|
|
101
|
+
updated_at: expect.any(Date),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=person.service.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"person.service.spec.js","sourceRoot":"","sources":["../../src/person/person.service.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -65,6 +65,9 @@ export declare class ProposalService {
|
|
|
65
65
|
private publishProposalLifecycleEvent;
|
|
66
66
|
private buildProposalLifecyclePayload;
|
|
67
67
|
private assertPersonExists;
|
|
68
|
+
private syncPersonDealValueFromApprovedProposals;
|
|
69
|
+
private upsertPersonMetadataValue;
|
|
70
|
+
private normalizePersonMetadataValue;
|
|
68
71
|
private resolveProposalCode;
|
|
69
72
|
private normalizeItems;
|
|
70
73
|
private normalizeDocuments;
|
|
@@ -89,6 +92,8 @@ export declare class ProposalService {
|
|
|
89
92
|
private assertProposalWorkflowReadiness;
|
|
90
93
|
private assertCanEdit;
|
|
91
94
|
private normalizeOptionalText;
|
|
95
|
+
private getPrimaryPersonContactValue;
|
|
96
|
+
private attachPersonTradeNames;
|
|
92
97
|
private loadProposalDetail;
|
|
93
98
|
private loadProposalIntegrationSnapshot;
|
|
94
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal.service.d.ts","sourceRoot":"","sources":["../../src/proposal/proposal.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EACH,WAAW,EACX,8BAA8B,EAC9B,cAAc,EACjB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"proposal.service.d.ts","sourceRoot":"","sources":["../../src/proposal/proposal.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,11 +42,30 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
42
42
|
where.status = params.status;
|
|
43
43
|
}
|
|
44
44
|
if (search) {
|
|
45
|
+
const matchingCompanyRows = await this.prisma.person_company.findMany({
|
|
46
|
+
where: {
|
|
47
|
+
trade_name: { contains: search, mode: 'insensitive' },
|
|
48
|
+
},
|
|
49
|
+
select: {
|
|
50
|
+
id: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const matchingCompanyPersonIds = matchingCompanyRows
|
|
54
|
+
.map((item) => Number(item.id))
|
|
55
|
+
.filter((id) => id > 0);
|
|
45
56
|
where.OR = [
|
|
46
57
|
{ code: { contains: search, mode: 'insensitive' } },
|
|
47
58
|
{ title: { contains: search, mode: 'insensitive' } },
|
|
48
|
-
{
|
|
49
|
-
|
|
59
|
+
{
|
|
60
|
+
person: {
|
|
61
|
+
is: {
|
|
62
|
+
name: { contains: search, mode: 'insensitive' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
...(matchingCompanyPersonIds.length > 0
|
|
67
|
+
? [{ person_id: { in: matchingCompanyPersonIds } }]
|
|
68
|
+
: []),
|
|
50
69
|
];
|
|
51
70
|
}
|
|
52
71
|
const [data, total] = await Promise.all([
|
|
@@ -57,9 +76,6 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
57
76
|
select: {
|
|
58
77
|
id: true,
|
|
59
78
|
name: true,
|
|
60
|
-
trade_name: true,
|
|
61
|
-
email: true,
|
|
62
|
-
phone: true,
|
|
63
79
|
},
|
|
64
80
|
},
|
|
65
81
|
proposal_revision: {
|
|
@@ -87,10 +103,11 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
87
103
|
}),
|
|
88
104
|
this.prisma.proposal.count({ where }),
|
|
89
105
|
]);
|
|
106
|
+
const normalizedData = await this.attachPersonTradeNames(this.prisma, data);
|
|
90
107
|
const page = Math.floor(skip / take) + 1;
|
|
91
108
|
const lastPage = Math.max(1, Math.ceil(total / take));
|
|
92
109
|
return {
|
|
93
|
-
data,
|
|
110
|
+
data: normalizedData,
|
|
94
111
|
total,
|
|
95
112
|
page,
|
|
96
113
|
pageSize: take,
|
|
@@ -188,6 +205,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
188
205
|
await this.publishProposalLifecycleEvent(tx, proposal_event_types_1.PROPOSAL_EVENT_NAMES.CREATED, proposal.id, locale, createdByUserId);
|
|
189
206
|
return proposal;
|
|
190
207
|
});
|
|
208
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, createdProposal.person_id);
|
|
191
209
|
return this.getById(createdProposal.id, locale);
|
|
192
210
|
}
|
|
193
211
|
async update(id, data, locale, userId) {
|
|
@@ -346,6 +364,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
346
364
|
}
|
|
347
365
|
}
|
|
348
366
|
});
|
|
367
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, current.person_id);
|
|
349
368
|
return this.getById(id, locale);
|
|
350
369
|
}
|
|
351
370
|
async submitForApproval(id, data, locale, userId) {
|
|
@@ -534,6 +553,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
534
553
|
},
|
|
535
554
|
});
|
|
536
555
|
});
|
|
556
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, current.person_id);
|
|
537
557
|
return this.getById(id, locale);
|
|
538
558
|
}
|
|
539
559
|
async reject(id, data, locale, userId) {
|
|
@@ -618,6 +638,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
618
638
|
note: this.normalizeOptionalText(data.note),
|
|
619
639
|
});
|
|
620
640
|
});
|
|
641
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, current.person_id);
|
|
621
642
|
return this.getById(id, locale);
|
|
622
643
|
}
|
|
623
644
|
async cancel(id, data, locale, userId) {
|
|
@@ -678,6 +699,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
678
699
|
},
|
|
679
700
|
});
|
|
680
701
|
});
|
|
702
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, current.person_id);
|
|
681
703
|
return this.getById(id, locale);
|
|
682
704
|
}
|
|
683
705
|
async requestConversion(id, data, locale, userId) {
|
|
@@ -793,6 +815,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
793
815
|
},
|
|
794
816
|
});
|
|
795
817
|
});
|
|
818
|
+
await this.syncPersonDealValueFromApprovedProposals(this.prisma, current.person_id);
|
|
796
819
|
return this.getById(proposalId, locale);
|
|
797
820
|
}
|
|
798
821
|
async generateDocument(id, locale, userId) {
|
|
@@ -897,6 +920,19 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
897
920
|
if (ids.length === 0) {
|
|
898
921
|
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('validation.idsMustBeArray', locale, 'No valid proposal ids were informed'));
|
|
899
922
|
}
|
|
923
|
+
const impactedProposals = await this.prisma.proposal.findMany({
|
|
924
|
+
where: {
|
|
925
|
+
id: { in: ids },
|
|
926
|
+
deleted_at: null,
|
|
927
|
+
},
|
|
928
|
+
select: {
|
|
929
|
+
id: true,
|
|
930
|
+
person_id: true,
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
const impactedPersonIds = Array.from(new Set(impactedProposals
|
|
934
|
+
.map((proposal) => Number((proposal === null || proposal === void 0 ? void 0 : proposal.person_id) || 0))
|
|
935
|
+
.filter((personId) => personId > 0)));
|
|
900
936
|
await this.prisma.$transaction(async (tx) => {
|
|
901
937
|
const now = new Date();
|
|
902
938
|
await tx.proposal.updateMany({
|
|
@@ -949,6 +985,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
949
985
|
},
|
|
950
986
|
});
|
|
951
987
|
});
|
|
988
|
+
await Promise.all(impactedPersonIds.map((personId) => this.syncPersonDealValueFromApprovedProposals(this.prisma, personId)));
|
|
952
989
|
return {
|
|
953
990
|
deleted: ids.length,
|
|
954
991
|
ids,
|
|
@@ -1148,6 +1185,86 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1148
1185
|
throw new common_1.BadRequestException((0, api_locale_1.getLocaleText)('personNotFound', locale, 'Person not found'));
|
|
1149
1186
|
}
|
|
1150
1187
|
}
|
|
1188
|
+
async syncPersonDealValueFromApprovedProposals(client, personId) {
|
|
1189
|
+
var _a;
|
|
1190
|
+
const normalizedPersonId = Number(personId || 0);
|
|
1191
|
+
if (!Number.isInteger(normalizedPersonId) || normalizedPersonId <= 0) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const aggregation = await client.proposal.aggregate({
|
|
1195
|
+
where: {
|
|
1196
|
+
deleted_at: null,
|
|
1197
|
+
person_id: normalizedPersonId,
|
|
1198
|
+
status: {
|
|
1199
|
+
in: [proposal_dto_1.ProposalStatus.APPROVED, proposal_dto_1.ProposalStatus.CONTRACT_GENERATED],
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1202
|
+
_sum: {
|
|
1203
|
+
total_amount_cents: true,
|
|
1204
|
+
},
|
|
1205
|
+
});
|
|
1206
|
+
const totalAmountCents = Number(((_a = aggregation === null || aggregation === void 0 ? void 0 : aggregation._sum) === null || _a === void 0 ? void 0 : _a.total_amount_cents) || 0);
|
|
1207
|
+
const dealValue = totalAmountCents > 0 ? (totalAmountCents / 100).toFixed(2) : null;
|
|
1208
|
+
await this.upsertPersonMetadataValue(client, normalizedPersonId, 'deal_value', dealValue);
|
|
1209
|
+
}
|
|
1210
|
+
async upsertPersonMetadataValue(client, personId, key, value) {
|
|
1211
|
+
if (!client.person_metadata) {
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const existing = await client.person_metadata.findFirst({
|
|
1215
|
+
where: {
|
|
1216
|
+
person_id: personId,
|
|
1217
|
+
key,
|
|
1218
|
+
},
|
|
1219
|
+
select: { id: true },
|
|
1220
|
+
});
|
|
1221
|
+
const normalizedValue = this.normalizePersonMetadataValue(value);
|
|
1222
|
+
if (normalizedValue == null) {
|
|
1223
|
+
if (existing) {
|
|
1224
|
+
await client.person_metadata.delete({
|
|
1225
|
+
where: { id: existing.id },
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if (existing) {
|
|
1231
|
+
await client.person_metadata.update({
|
|
1232
|
+
where: { id: existing.id },
|
|
1233
|
+
data: { value: normalizedValue },
|
|
1234
|
+
});
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
await client.person_metadata.create({
|
|
1238
|
+
data: {
|
|
1239
|
+
person_id: personId,
|
|
1240
|
+
key,
|
|
1241
|
+
value: normalizedValue,
|
|
1242
|
+
},
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
normalizePersonMetadataValue(value) {
|
|
1246
|
+
if (value == null)
|
|
1247
|
+
return null;
|
|
1248
|
+
if (typeof value === 'string') {
|
|
1249
|
+
const trimmed = value.trim();
|
|
1250
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1251
|
+
}
|
|
1252
|
+
if (typeof value === 'number') {
|
|
1253
|
+
return Number.isFinite(value) ? value : null;
|
|
1254
|
+
}
|
|
1255
|
+
if (typeof value === 'boolean') {
|
|
1256
|
+
return value;
|
|
1257
|
+
}
|
|
1258
|
+
if (Array.isArray(value) || typeof value === 'object') {
|
|
1259
|
+
try {
|
|
1260
|
+
return JSON.parse(JSON.stringify(value));
|
|
1261
|
+
}
|
|
1262
|
+
catch (_a) {
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1151
1268
|
async resolveProposalCode(code) {
|
|
1152
1269
|
var _a;
|
|
1153
1270
|
const normalized = (_a = this.normalizeOptionalText(code)) === null || _a === void 0 ? void 0 : _a.toUpperCase();
|
|
@@ -1308,7 +1425,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1308
1425
|
return renderVersion;
|
|
1309
1426
|
}
|
|
1310
1427
|
async renderProposalPdfBuffer(proposal, locale, html) {
|
|
1311
|
-
var _a;
|
|
1428
|
+
var _a, _b, _c;
|
|
1312
1429
|
const renderedHtml = html !== null && html !== void 0 ? html : (await this.buildProposalDocumentHtml(proposal, locale));
|
|
1313
1430
|
let browser = null;
|
|
1314
1431
|
try {
|
|
@@ -1329,10 +1446,16 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1329
1446
|
}));
|
|
1330
1447
|
}
|
|
1331
1448
|
catch (error) {
|
|
1332
|
-
|
|
1449
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1450
|
+
const errorStack = error instanceof Error ? ((_a = error.stack) !== null && _a !== void 0 ? _a : error.message) : String(error);
|
|
1451
|
+
const missingPlaywrightRuntime = /Cannot find package ['"]playwright['"]|Cannot find module ['"]playwright['"]|Executable doesn't exist|browserType\.launch|Failed to launch|Please run the following command to download new browsers/i.test(errorMessage);
|
|
1452
|
+
this.logger.error(`Failed to generate proposal PDF for proposal ${(_b = proposal === null || proposal === void 0 ? void 0 : proposal.id) !== null && _b !== void 0 ? _b : 'unknown'} (locale=${locale}). ${errorMessage}`, errorStack);
|
|
1453
|
+
throw new common_1.InternalServerErrorException(missingPlaywrightRuntime
|
|
1454
|
+
? 'PDF generation is unavailable because Playwright/Chromium is not installed on the server. Run `pnpm --filter api run playwright:install` in the API environment.'
|
|
1455
|
+
: 'Failed to generate the PDF document. Check server logs for details.');
|
|
1333
1456
|
}
|
|
1334
1457
|
finally {
|
|
1335
|
-
await ((
|
|
1458
|
+
await ((_c = browser === null || browser === void 0 ? void 0 : browser.close) === null || _c === void 0 ? void 0 : _c.call(browser));
|
|
1336
1459
|
}
|
|
1337
1460
|
}
|
|
1338
1461
|
async buildProposalDocumentHtml(proposal, locale = 'en') {
|
|
@@ -1801,8 +1924,115 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1801
1924
|
const normalized = String(value || '').trim();
|
|
1802
1925
|
return normalized.length > 0 ? normalized : null;
|
|
1803
1926
|
}
|
|
1927
|
+
getPrimaryPersonContactValue(contacts, codes) {
|
|
1928
|
+
var _a, _b;
|
|
1929
|
+
const normalizedCodes = new Set(codes.map((code) => code.toUpperCase()));
|
|
1930
|
+
const items = Array.isArray(contacts)
|
|
1931
|
+
? contacts.filter((contact) => {
|
|
1932
|
+
var _a;
|
|
1933
|
+
return normalizedCodes.has(String(((_a = contact === null || contact === void 0 ? void 0 : contact.contact_type) === null || _a === void 0 ? void 0 : _a.code) || '').toUpperCase());
|
|
1934
|
+
})
|
|
1935
|
+
: [];
|
|
1936
|
+
const primary = items.find((contact) => contact === null || contact === void 0 ? void 0 : contact.is_primary);
|
|
1937
|
+
const fallback = items[0];
|
|
1938
|
+
const value = (_b = (_a = primary === null || primary === void 0 ? void 0 : primary.value) !== null && _a !== void 0 ? _a : fallback === null || fallback === void 0 ? void 0 : fallback.value) !== null && _b !== void 0 ? _b : null;
|
|
1939
|
+
return value != null && String(value).trim().length > 0
|
|
1940
|
+
? String(value).trim()
|
|
1941
|
+
: null;
|
|
1942
|
+
}
|
|
1943
|
+
async attachPersonTradeNames(client, input) {
|
|
1944
|
+
var _a, _b;
|
|
1945
|
+
if (!input) {
|
|
1946
|
+
return input;
|
|
1947
|
+
}
|
|
1948
|
+
const proposals = Array.isArray(input) ? input : [input];
|
|
1949
|
+
const personIds = Array.from(new Set(proposals
|
|
1950
|
+
.map((proposal) => { var _a, _b, _c; return Number((_c = (_b = (_a = proposal === null || proposal === void 0 ? void 0 : proposal.person) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : proposal === null || proposal === void 0 ? void 0 : proposal.person_id) !== null && _c !== void 0 ? _c : 0); })
|
|
1951
|
+
.filter((id) => id > 0)));
|
|
1952
|
+
if (personIds.length === 0) {
|
|
1953
|
+
return input;
|
|
1954
|
+
}
|
|
1955
|
+
const [companyRows, contactRows, documentRows] = await Promise.all([
|
|
1956
|
+
client.person_company.findMany({
|
|
1957
|
+
where: {
|
|
1958
|
+
id: {
|
|
1959
|
+
in: personIds,
|
|
1960
|
+
},
|
|
1961
|
+
},
|
|
1962
|
+
select: {
|
|
1963
|
+
id: true,
|
|
1964
|
+
trade_name: true,
|
|
1965
|
+
},
|
|
1966
|
+
}),
|
|
1967
|
+
client.contact.findMany({
|
|
1968
|
+
where: {
|
|
1969
|
+
person_id: {
|
|
1970
|
+
in: personIds,
|
|
1971
|
+
},
|
|
1972
|
+
},
|
|
1973
|
+
include: {
|
|
1974
|
+
contact_type: {
|
|
1975
|
+
select: {
|
|
1976
|
+
code: true,
|
|
1977
|
+
},
|
|
1978
|
+
},
|
|
1979
|
+
},
|
|
1980
|
+
orderBy: [{ is_primary: 'desc' }, { id: 'asc' }],
|
|
1981
|
+
}),
|
|
1982
|
+
client.document.findMany({
|
|
1983
|
+
where: {
|
|
1984
|
+
person_id: {
|
|
1985
|
+
in: personIds,
|
|
1986
|
+
},
|
|
1987
|
+
},
|
|
1988
|
+
select: {
|
|
1989
|
+
id: true,
|
|
1990
|
+
person_id: true,
|
|
1991
|
+
value: true,
|
|
1992
|
+
},
|
|
1993
|
+
orderBy: [{ id: 'asc' }],
|
|
1994
|
+
}),
|
|
1995
|
+
]);
|
|
1996
|
+
const tradeNameByPersonId = new Map(companyRows.map((row) => {
|
|
1997
|
+
var _a;
|
|
1998
|
+
return [
|
|
1999
|
+
row.id,
|
|
2000
|
+
(_a = row.trade_name) !== null && _a !== void 0 ? _a : null,
|
|
2001
|
+
];
|
|
2002
|
+
}));
|
|
2003
|
+
const contactsByPersonId = new Map();
|
|
2004
|
+
for (const contact of contactRows) {
|
|
2005
|
+
const current = (_a = contactsByPersonId.get(Number(contact.person_id))) !== null && _a !== void 0 ? _a : [];
|
|
2006
|
+
current.push(contact);
|
|
2007
|
+
contactsByPersonId.set(Number(contact.person_id), current);
|
|
2008
|
+
}
|
|
2009
|
+
const documentsByPersonId = new Map();
|
|
2010
|
+
for (const document of documentRows) {
|
|
2011
|
+
const current = (_b = documentsByPersonId.get(Number(document.person_id))) !== null && _b !== void 0 ? _b : [];
|
|
2012
|
+
current.push(document);
|
|
2013
|
+
documentsByPersonId.set(Number(document.person_id), current);
|
|
2014
|
+
}
|
|
2015
|
+
const normalized = proposals.map((proposal) => {
|
|
2016
|
+
var _a, _b, _c, _d;
|
|
2017
|
+
if (!(proposal === null || proposal === void 0 ? void 0 : proposal.person)) {
|
|
2018
|
+
return proposal;
|
|
2019
|
+
}
|
|
2020
|
+
const personId = Number(proposal.person.id);
|
|
2021
|
+
const contacts = (_a = contactsByPersonId.get(personId)) !== null && _a !== void 0 ? _a : [];
|
|
2022
|
+
const documents = (_b = documentsByPersonId.get(personId)) !== null && _b !== void 0 ? _b : [];
|
|
2023
|
+
return Object.assign(Object.assign({}, proposal), { person: Object.assign(Object.assign({}, proposal.person), { trade_name: (_c = tradeNameByPersonId.get(personId)) !== null && _c !== void 0 ? _c : null, email: this.getPrimaryPersonContactValue(contacts, ['EMAIL']), phone: this.getPrimaryPersonContactValue(contacts, [
|
|
2024
|
+
'PHONE',
|
|
2025
|
+
'MOBILE',
|
|
2026
|
+
'WHATSAPP',
|
|
2027
|
+
]), document: ((_d = documents[0]) === null || _d === void 0 ? void 0 : _d.value) != null &&
|
|
2028
|
+
String(documents[0].value).trim().length > 0
|
|
2029
|
+
? String(documents[0].value).trim()
|
|
2030
|
+
: null }) });
|
|
2031
|
+
});
|
|
2032
|
+
return Array.isArray(input) ? normalized : normalized[0];
|
|
2033
|
+
}
|
|
1804
2034
|
async loadProposalDetail(client, proposalId) {
|
|
1805
|
-
|
|
2035
|
+
const proposal = await client.proposal.findFirst({
|
|
1806
2036
|
where: {
|
|
1807
2037
|
id: proposalId,
|
|
1808
2038
|
deleted_at: null,
|
|
@@ -1812,10 +2042,6 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1812
2042
|
select: {
|
|
1813
2043
|
id: true,
|
|
1814
2044
|
name: true,
|
|
1815
|
-
trade_name: true,
|
|
1816
|
-
email: true,
|
|
1817
|
-
phone: true,
|
|
1818
|
-
document: true,
|
|
1819
2045
|
},
|
|
1820
2046
|
},
|
|
1821
2047
|
proposal_revision: {
|
|
@@ -1856,6 +2082,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1856
2082
|
},
|
|
1857
2083
|
},
|
|
1858
2084
|
});
|
|
2085
|
+
return this.attachPersonTradeNames(client, proposal);
|
|
1859
2086
|
}
|
|
1860
2087
|
async loadProposalIntegrationSnapshot(client, proposalId) {
|
|
1861
2088
|
const proposal = await client.proposal.findFirst({
|
|
@@ -1868,10 +2095,6 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1868
2095
|
select: {
|
|
1869
2096
|
id: true,
|
|
1870
2097
|
name: true,
|
|
1871
|
-
trade_name: true,
|
|
1872
|
-
email: true,
|
|
1873
|
-
phone: true,
|
|
1874
|
-
document: true,
|
|
1875
2098
|
type: true,
|
|
1876
2099
|
},
|
|
1877
2100
|
},
|
|
@@ -1898,7 +2121,7 @@ let ProposalService = ProposalService_1 = class ProposalService {
|
|
|
1898
2121
|
if (!proposal) {
|
|
1899
2122
|
throw new common_1.NotFoundException('Proposal snapshot not found.');
|
|
1900
2123
|
}
|
|
1901
|
-
return proposal;
|
|
2124
|
+
return this.attachPersonTradeNames(client, proposal);
|
|
1902
2125
|
}
|
|
1903
2126
|
};
|
|
1904
2127
|
exports.ProposalService = ProposalService;
|