@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.
Files changed (36) hide show
  1. package/dist/person/person.service.d.ts +2 -0
  2. package/dist/person/person.service.d.ts.map +1 -1
  3. package/dist/person/person.service.js +111 -127
  4. package/dist/person/person.service.js.map +1 -1
  5. package/dist/person/person.service.spec.d.ts +2 -0
  6. package/dist/person/person.service.spec.d.ts.map +1 -0
  7. package/dist/person/person.service.spec.js +106 -0
  8. package/dist/person/person.service.spec.js.map +1 -0
  9. package/dist/proposal/proposal.service.d.ts +5 -0
  10. package/dist/proposal/proposal.service.d.ts.map +1 -1
  11. package/dist/proposal/proposal.service.js +242 -19
  12. package/dist/proposal/proposal.service.js.map +1 -1
  13. package/dist/proposal/proposal.service.spec.js +153 -165
  14. package/dist/proposal/proposal.service.spec.js.map +1 -1
  15. package/hedhog/data/menu.yaml +35 -18
  16. package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +517 -346
  17. package/hedhog/frontend/app/activities/_components/activity-detail-sheet.tsx.ejs +42 -17
  18. package/hedhog/frontend/app/activities/_components/activity-types.ts.ejs +1 -1
  19. package/hedhog/frontend/app/activities/page.tsx.ejs +315 -101
  20. package/hedhog/frontend/app/follow-ups/page.tsx.ejs +172 -22
  21. package/hedhog/frontend/app/page.tsx.ejs +1 -1
  22. package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +1 -1
  23. package/hedhog/frontend/app/pipeline/_components/lead-detail-sheet.tsx.ejs +1 -1
  24. package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +505 -428
  25. package/hedhog/frontend/app/pipeline/page.tsx.ejs +30 -4
  26. package/hedhog/frontend/app/proposals/_components/proposals-management-page.tsx.ejs +773 -0
  27. package/hedhog/frontend/app/proposals/approvals/page.tsx.ejs +5 -0
  28. package/hedhog/frontend/app/proposals/page.tsx.ejs +5 -0
  29. package/hedhog/frontend/app/reports/page.tsx.ejs +431 -375
  30. package/hedhog/frontend/messages/en.json +100 -1
  31. package/hedhog/frontend/messages/pt.json +100 -1
  32. package/package.json +6 -6
  33. package/src/person/person.service.spec.ts +143 -0
  34. package/src/person/person.service.ts +147 -158
  35. package/src/proposal/proposal.service.spec.ts +196 -0
  36. 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":"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"}
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
- { person: { name: { contains: search, mode: 'insensitive' } } },
49
- { person: { trade_name: { contains: search, mode: 'insensitive' } } },
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
- throw new common_1.BadRequestException('PDF generation requires Playwright to be installed on the server.');
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 ((_a = browser === null || browser === void 0 ? void 0 : browser.close) === null || _a === void 0 ? void 0 : _a.call(browser));
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
- return client.proposal.findFirst({
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;