@hed-hog/operations 0.0.300 → 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 (73) hide show
  1. package/dist/operations.controller.d.ts +713 -31
  2. package/dist/operations.controller.d.ts.map +1 -1
  3. package/dist/operations.controller.js +157 -0
  4. package/dist/operations.controller.js.map +1 -1
  5. package/dist/operations.module.d.ts.map +1 -1
  6. package/dist/operations.module.js +5 -1
  7. package/dist/operations.module.js.map +1 -1
  8. package/dist/operations.proposal.subscriber.d.ts +11 -0
  9. package/dist/operations.proposal.subscriber.d.ts.map +1 -0
  10. package/dist/operations.proposal.subscriber.js +80 -0
  11. package/dist/operations.proposal.subscriber.js.map +1 -0
  12. package/dist/operations.proposal.subscriber.spec.d.ts +2 -0
  13. package/dist/operations.proposal.subscriber.spec.d.ts.map +1 -0
  14. package/dist/operations.proposal.subscriber.spec.js +88 -0
  15. package/dist/operations.proposal.subscriber.spec.js.map +1 -0
  16. package/dist/operations.service.d.ts +491 -46
  17. package/dist/operations.service.d.ts.map +1 -1
  18. package/dist/operations.service.js +2484 -121
  19. package/dist/operations.service.js.map +1 -1
  20. package/dist/operations.service.spec.d.ts +2 -0
  21. package/dist/operations.service.spec.d.ts.map +1 -0
  22. package/dist/operations.service.spec.js +159 -0
  23. package/dist/operations.service.spec.js.map +1 -0
  24. package/hedhog/data/menu.yaml +35 -22
  25. package/hedhog/data/role_route.yaml +39 -0
  26. package/hedhog/data/route.yaml +130 -0
  27. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +8 -6
  28. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +1163 -327
  29. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -0
  30. package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
  31. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +631 -0
  32. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +353 -27
  33. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +1926 -87
  34. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +526 -0
  35. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -0
  36. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -0
  37. package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +370 -0
  38. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +826 -0
  39. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1251 -364
  40. package/hedhog/frontend/app/_components/section-card.tsx.ejs +48 -13
  41. package/hedhog/frontend/app/_lib/api.ts.ejs +2 -5
  42. package/hedhog/frontend/app/_lib/types.ts.ejs +76 -33
  43. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +85 -8
  44. package/hedhog/frontend/app/approvals/page.tsx.ejs +90 -54
  45. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +2 -2
  46. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +2 -2
  47. package/hedhog/frontend/app/collaborators/page.tsx.ejs +597 -140
  48. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +2 -2
  49. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  50. package/hedhog/frontend/app/contracts/page.tsx.ejs +941 -262
  51. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +384 -0
  52. package/hedhog/frontend/app/departments/page.tsx.ejs +442 -0
  53. package/hedhog/frontend/app/page.tsx.ejs +3 -317
  54. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +2 -2
  55. package/hedhog/frontend/app/projects/new/page.tsx.ejs +2 -2
  56. package/hedhog/frontend/app/projects/page.tsx.ejs +264 -102
  57. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +50 -28
  58. package/hedhog/frontend/app/time-off/page.tsx.ejs +57 -31
  59. package/hedhog/frontend/app/timesheets/page.tsx.ejs +85 -42
  60. package/hedhog/frontend/messages/en.json +473 -12
  61. package/hedhog/frontend/messages/pt.json +528 -66
  62. package/hedhog/table/operations_collaborator.yaml +20 -0
  63. package/hedhog/table/operations_contract.yaml +22 -1
  64. package/hedhog/table/operations_contract_document.yaml +33 -16
  65. package/hedhog/table/operations_contract_template.yaml +58 -0
  66. package/hedhog/table/operations_department.yaml +24 -0
  67. package/package.json +7 -5
  68. package/src/operations.controller.ts +122 -0
  69. package/src/operations.module.ts +6 -2
  70. package/src/operations.proposal.subscriber.spec.ts +121 -0
  71. package/src/operations.proposal.subscriber.ts +86 -0
  72. package/src/operations.service.spec.ts +210 -0
  73. package/src/operations.service.ts +4026 -241
@@ -8,6 +8,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var OperationsService_1;
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
16
  exports.OperationsService = void 0;
13
17
  const api_prisma_1 = require("@hed-hog/api-prisma");
@@ -16,10 +20,99 @@ const common_1 = require("@nestjs/common");
16
20
  const COLLABORATOR_ROLE = 'admin-operations-collaborator';
17
21
  const SUPERVISOR_ROLE = 'admin-operations-supervisor';
18
22
  const DIRECTOR_ROLE = 'admin-operations-director';
19
- let OperationsService = class OperationsService {
20
- constructor(prisma, integrationApi) {
23
+ const CONTRACT_CATEGORY_VALUES = [
24
+ 'employee',
25
+ 'contractor',
26
+ 'client',
27
+ 'supplier',
28
+ 'vendor',
29
+ 'partner',
30
+ 'internal',
31
+ 'other',
32
+ ];
33
+ const CONTRACT_TYPE_VALUES = [
34
+ 'clt',
35
+ 'pj',
36
+ 'freelancer_agreement',
37
+ 'service_agreement',
38
+ 'fixed_term',
39
+ 'recurring_service',
40
+ 'nda',
41
+ 'amendment',
42
+ 'addendum',
43
+ 'other',
44
+ ];
45
+ const BILLING_MODEL_VALUES = [
46
+ 'time_and_material',
47
+ 'monthly_retainer',
48
+ 'fixed_price',
49
+ ];
50
+ const SIGNATURE_STATUS_VALUES = [
51
+ 'not_started',
52
+ 'pending',
53
+ 'partially_signed',
54
+ 'signed',
55
+ 'expired',
56
+ ];
57
+ const CONTRACT_STATUS_VALUES = [
58
+ 'draft',
59
+ 'under_review',
60
+ 'active',
61
+ 'renewal',
62
+ 'expired',
63
+ 'closed',
64
+ 'archived',
65
+ ];
66
+ const CONTRACT_CREATION_MODE_VALUES = [
67
+ 'blank',
68
+ 'template',
69
+ 'upload',
70
+ 'duplicate',
71
+ ];
72
+ const ORIGIN_TYPE_VALUES = [
73
+ 'manual',
74
+ 'employee_hiring',
75
+ 'client_project',
76
+ 'crm_proposal',
77
+ ];
78
+ const CONTRACT_DOCUMENT_TYPE_VALUES = [
79
+ 'source_upload',
80
+ 'generated_pdf',
81
+ 'attachment',
82
+ 'other',
83
+ ];
84
+ const CONTRACT_DOCUMENT_EXTRACTION_STATUS_VALUES = [
85
+ 'pending',
86
+ 'processing',
87
+ 'completed',
88
+ 'failed',
89
+ 'skipped',
90
+ ];
91
+ const PARTY_ROLE_VALUES = [
92
+ 'employee',
93
+ 'employer',
94
+ 'client',
95
+ 'supplier',
96
+ 'vendor',
97
+ 'partner',
98
+ 'witness',
99
+ 'internal_owner',
100
+ 'other',
101
+ ];
102
+ const PARTY_TYPE_VALUES = ['individual', 'company', 'internal_team', 'other'];
103
+ const SIGNATURE_ITEM_STATUS_VALUES = ['pending', 'signed', 'rejected'];
104
+ const FINANCIAL_TERM_TYPE_VALUES = ['value', 'payment', 'revenue', 'fine', 'other'];
105
+ const RECURRENCE_VALUES = ['one_time', 'monthly', 'quarterly', 'yearly', 'other'];
106
+ const REVISION_TYPE_VALUES = ['amendment', 'renewal', 'revision', 'addendum', 'other'];
107
+ const REVISION_STATUS_VALUES = ['draft', 'active', 'completed', 'cancelled'];
108
+ let OperationsService = OperationsService_1 = class OperationsService {
109
+ constructor(prisma, aiService, integrationApi, fileService, settingService) {
21
110
  this.prisma = prisma;
111
+ this.aiService = aiService;
22
112
  this.integrationApi = integrationApi;
113
+ this.fileService = fileService;
114
+ this.settingService = settingService;
115
+ this.logger = new common_1.Logger(OperationsService_1.name);
23
116
  }
24
117
  async getDashboard(userId) {
25
118
  var _a, _b, _c, _d, _e, _f, _g;
@@ -97,10 +190,14 @@ let OperationsService = class OperationsService {
97
190
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'c.id', actor.isDirector);
98
191
  return this.queryRows(`SELECT c.id,
99
192
  c.user_id AS "userId",
193
+ c.person_id AS "personId",
100
194
  c.code,
101
195
  c.collaborator_type AS "collaboratorType",
102
- c.display_name AS "displayName",
103
- c.department,
196
+ COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
197
+ person_record.name AS "personName",
198
+ person_record.avatar_id AS "personAvatarId",
199
+ c.department_id AS "departmentId",
200
+ COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
104
201
  c.title,
105
202
  c.level_label AS "levelLabel",
106
203
  c.weekly_capacity_hours AS "weeklyCapacityHours",
@@ -114,6 +211,11 @@ let OperationsService = class OperationsService {
114
211
  hiring_contract.status AS "contractStatus",
115
212
  COUNT(DISTINCT pa.id)::int AS "activeAssignments"
116
213
  FROM operations_collaborator c
214
+ LEFT JOIN person person_record
215
+ ON person_record.id = c.person_id
216
+ LEFT JOIN operations_department department_record
217
+ ON department_record.id = c.department_id
218
+ AND department_record.deleted_at IS NULL
117
219
  LEFT JOIN operations_collaborator s
118
220
  ON s.id = c.supervisor_collaborator_id
119
221
  LEFT JOIN operations_project_assignment pa
@@ -131,8 +233,8 @@ let OperationsService = class OperationsService {
131
233
  ) hiring_contract ON TRUE
132
234
  WHERE c.deleted_at IS NULL
133
235
  AND ${filter.clause}
134
- GROUP BY c.id, s.id, hiring_contract.id, hiring_contract.status
135
- ORDER BY c.display_name ASC`, filter.params);
236
+ GROUP BY c.id, person_record.id, department_record.id, s.id, hiring_contract.id, hiring_contract.status
237
+ ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, filter.params);
136
238
  }
137
239
  async getMyCollaborator(userId) {
138
240
  const actor = await this.getActorContext(userId);
@@ -169,10 +271,14 @@ let OperationsService = class OperationsService {
169
271
  const teamFilter = this.buildIdFilter(actor.teamCollaboratorIds, 'c.id', false);
170
272
  const teamMembers = await this.queryRows(`SELECT c.id,
171
273
  c.user_id AS "userId",
274
+ c.person_id AS "personId",
172
275
  c.code,
173
- c.display_name AS "displayName",
276
+ COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
277
+ person_record.name AS "personName",
278
+ person_record.avatar_id AS "personAvatarId",
174
279
  c.collaborator_type AS "collaboratorType",
175
- c.department,
280
+ c.department_id AS "departmentId",
281
+ COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
176
282
  c.title,
177
283
  c.status,
178
284
  COUNT(DISTINCT pa.id)::int AS "activeAssignments",
@@ -180,6 +286,11 @@ let OperationsService = class OperationsService {
180
286
  COUNT(DISTINCT tor.id) FILTER (WHERE tor.status = 'submitted')::int AS "pendingTimeOffRequests",
181
287
  COUNT(DISTINCT sar.id) FILTER (WHERE sar.status = 'submitted')::int AS "pendingScheduleAdjustmentRequests"
182
288
  FROM operations_collaborator c
289
+ LEFT JOIN person person_record
290
+ ON person_record.id = c.person_id
291
+ LEFT JOIN operations_department department_record
292
+ ON department_record.id = c.department_id
293
+ AND department_record.deleted_at IS NULL
183
294
  LEFT JOIN operations_project_assignment pa
184
295
  ON pa.collaborator_id = c.id
185
296
  AND pa.deleted_at IS NULL
@@ -197,8 +308,8 @@ let OperationsService = class OperationsService {
197
308
  AND sar.deleted_at IS NULL
198
309
  AND sar.status = 'submitted'
199
310
  WHERE c.deleted_at IS NULL AND ${teamFilter.clause}
200
- GROUP BY c.id
201
- ORDER BY c.display_name ASC`, teamFilter.params);
311
+ GROUP BY c.id, person_record.id, department_record.id
312
+ ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, teamFilter.params);
202
313
  const [teamProjects, pendingApprovalQueue, pendingTimeOffRequests, pendingScheduleAdjustmentRequests] = await Promise.all([
203
314
  this.queryRows(`SELECT p.id,
204
315
  p.code,
@@ -341,18 +452,33 @@ let OperationsService = class OperationsService {
341
452
  };
342
453
  }
343
454
  async createCollaborator(userId, data) {
455
+ var _a, _b, _c, _d;
344
456
  const actor = await this.getActorContext(userId);
345
457
  this.ensureDirector(actor);
346
- this.requireFields(data, ['userId', 'code', 'displayName']);
458
+ const resolvedPerson = data.personId
459
+ ? await this.getPersonById(Number(data.personId))
460
+ : null;
461
+ const resolvedDisplayName = (_d = (_b = (_a = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = data.displayName) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : '';
462
+ if (!resolvedDisplayName) {
463
+ throw new common_1.BadRequestException('Field "personId" is required.');
464
+ }
347
465
  const collaboratorId = await this.prisma.$transaction(async (tx) => {
348
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
466
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
467
+ const normalizedCode = String((_a = data.code) !== null && _a !== void 0 ? _a : '').trim() ||
468
+ (await this.generateCollaboratorCode(tx));
469
+ const resolvedDepartment = await this.resolveDepartmentReference(tx, {
470
+ departmentId: (_b = data.departmentId) !== null && _b !== void 0 ? _b : null,
471
+ departmentName: data.department,
472
+ });
349
473
  const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_collaborator (
350
474
  user_id,
475
+ person_id,
351
476
  supervisor_collaborator_id,
352
477
  code,
353
478
  collaborator_type,
354
479
  display_name,
355
480
  department,
481
+ department_id,
356
482
  title,
357
483
  level_label,
358
484
  weekly_capacity_hours,
@@ -363,23 +489,26 @@ let OperationsService = class OperationsService {
363
489
  created_at,
364
490
  updated_at
365
491
  ) VALUES (
366
- $1, $2, $3, COALESCE($4, 'other'), $5, $6, $7, $8, $9,
367
- COALESCE($10, 'active'), $11, $12, $13, NOW(), NOW()
492
+ $1, $2, $3, $4,
493
+ $5::operations_collaborator_collaborator_type_7dd7b0ada2_enum,
494
+ $6, $7, $8, $9, $10, $11,
495
+ $12::operations_collaborator_status_ef779877d4_enum,
496
+ $13::date, $14::date, $15, NOW(), NOW()
368
497
  )
369
- RETURNING id`, data.userId, (_a = data.supervisorCollaboratorId) !== null && _a !== void 0 ? _a : null, data.code, (_b = data.collaboratorType) !== null && _b !== void 0 ? _b : 'other', data.displayName, (_c = data.department) !== null && _c !== void 0 ? _c : null, (_d = data.title) !== null && _d !== void 0 ? _d : null, (_e = data.levelLabel) !== null && _e !== void 0 ? _e : null, (_f = data.weeklyCapacityHours) !== null && _f !== void 0 ? _f : null, (_g = data.status) !== null && _g !== void 0 ? _g : 'active', (_h = data.joinedAt) !== null && _h !== void 0 ? _h : null, (_j = data.leftAt) !== null && _j !== void 0 ? _j : null, (_k = data.notes) !== null && _k !== void 0 ? _k : null));
370
- const createdCollaboratorId = (_l = created[0]) === null || _l === void 0 ? void 0 : _l.id;
498
+ RETURNING id`, (_c = data.userId) !== null && _c !== void 0 ? _c : null, (_d = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.id) !== null && _d !== void 0 ? _d : null, (_e = data.supervisorCollaboratorId) !== null && _e !== void 0 ? _e : null, normalizedCode, (_f = data.collaboratorType) !== null && _f !== void 0 ? _f : 'other', resolvedDisplayName, (_g = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _g !== void 0 ? _g : null, (_h = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _h !== void 0 ? _h : null, (_j = data.title) !== null && _j !== void 0 ? _j : null, (_k = data.levelLabel) !== null && _k !== void 0 ? _k : null, (_l = data.weeklyCapacityHours) !== null && _l !== void 0 ? _l : null, (_m = data.status) !== null && _m !== void 0 ? _m : 'active', (_o = data.joinedAt) !== null && _o !== void 0 ? _o : null, (_p = data.leftAt) !== null && _p !== void 0 ? _p : null, (_q = data.notes) !== null && _q !== void 0 ? _q : null));
499
+ const createdCollaboratorId = (_r = created[0]) === null || _r === void 0 ? void 0 : _r.id;
371
500
  await this.replaceCollaboratorScheduleDays(tx, createdCollaboratorId, data.weeklySchedule);
372
501
  if (data.autoGenerateContractDraft !== false) {
373
502
  await this.createHiringContractDraft(tx, actor.userId, {
374
503
  collaboratorId: createdCollaboratorId,
375
- collaboratorCode: data.code,
376
- displayName: data.displayName,
377
- collaboratorType: (_m = data.collaboratorType) !== null && _m !== void 0 ? _m : 'other',
378
- supervisorCollaboratorId: (_o = data.supervisorCollaboratorId) !== null && _o !== void 0 ? _o : null,
379
- startDate: (_p = data.joinedAt) !== null && _p !== void 0 ? _p : null,
380
- weeklyCapacityHours: (_q = data.weeklyCapacityHours) !== null && _q !== void 0 ? _q : null,
381
- compensationAmount: (_r = data.compensationAmount) !== null && _r !== void 0 ? _r : null,
382
- description: (_t = (_s = data.contractDescription) !== null && _s !== void 0 ? _s : data.notes) !== null && _t !== void 0 ? _t : null,
504
+ collaboratorCode: normalizedCode,
505
+ displayName: resolvedDisplayName,
506
+ collaboratorType: (_s = data.collaboratorType) !== null && _s !== void 0 ? _s : 'other',
507
+ supervisorCollaboratorId: (_t = data.supervisorCollaboratorId) !== null && _t !== void 0 ? _t : null,
508
+ startDate: (_u = data.joinedAt) !== null && _u !== void 0 ? _u : null,
509
+ weeklyCapacityHours: (_v = data.weeklyCapacityHours) !== null && _v !== void 0 ? _v : null,
510
+ compensationAmount: (_w = data.compensationAmount) !== null && _w !== void 0 ? _w : null,
511
+ description: (_y = (_x = data.contractDescription) !== null && _x !== void 0 ? _x : data.notes) !== null && _y !== void 0 ? _y : null,
383
512
  });
384
513
  }
385
514
  return createdCollaboratorId;
@@ -387,24 +516,41 @@ let OperationsService = class OperationsService {
387
516
  return this.getCollaboratorByIdForUser(userId, collaboratorId);
388
517
  }
389
518
  async updateCollaborator(userId, collaboratorId, data) {
519
+ var _a, _b, _c, _d, _e;
390
520
  const actor = await this.getActorContext(userId);
391
521
  this.ensureDirector(actor);
392
522
  await this.getCollaboratorById(collaboratorId);
393
523
  const updates = [];
394
524
  const params = [];
525
+ const resolvedPerson = data.personId !== undefined && data.personId !== null
526
+ ? await this.getPersonById(Number(data.personId))
527
+ : null;
528
+ const resolvedDisplayName = (_d = (_b = (_a = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = data.displayName) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : undefined;
529
+ this.pushUpdate(updates, params, 'user_id', data.userId);
530
+ this.pushUpdate(updates, params, 'person_id', data.personId);
395
531
  this.pushUpdate(updates, params, 'supervisor_collaborator_id', data.supervisorCollaboratorId);
396
- this.pushUpdate(updates, params, 'code', data.code);
397
- this.pushUpdate(updates, params, 'collaborator_type', data.collaboratorType);
398
- this.pushUpdate(updates, params, 'display_name', data.displayName);
399
- this.pushUpdate(updates, params, 'department', data.department);
532
+ this.pushUpdate(updates, params, 'code', (_e = data.code) === null || _e === void 0 ? void 0 : _e.trim());
533
+ this.pushUpdate(updates, params, 'collaborator_type', data.collaboratorType, 'operations_collaborator_collaborator_type_7dd7b0ada2_enum');
534
+ if (data.personId !== undefined || data.displayName !== undefined) {
535
+ this.pushUpdate(updates, params, 'display_name', resolvedDisplayName !== null && resolvedDisplayName !== void 0 ? resolvedDisplayName : null);
536
+ }
400
537
  this.pushUpdate(updates, params, 'title', data.title);
401
538
  this.pushUpdate(updates, params, 'level_label', data.levelLabel);
402
539
  this.pushUpdate(updates, params, 'weekly_capacity_hours', data.weeklyCapacityHours);
403
- this.pushUpdate(updates, params, 'status', data.status);
404
- this.pushUpdate(updates, params, 'joined_at', data.joinedAt);
405
- this.pushUpdate(updates, params, 'left_at', data.leftAt);
540
+ this.pushUpdate(updates, params, 'status', data.status, 'operations_collaborator_status_ef779877d4_enum');
541
+ this.pushUpdate(updates, params, 'joined_at', data.joinedAt, 'date');
542
+ this.pushUpdate(updates, params, 'left_at', data.leftAt, 'date');
406
543
  this.pushUpdate(updates, params, 'notes', data.notes);
407
544
  await this.prisma.$transaction(async (tx) => {
545
+ var _a, _b, _c;
546
+ if (data.department !== undefined || data.departmentId !== undefined) {
547
+ const resolvedDepartment = await this.resolveDepartmentReference(tx, {
548
+ departmentId: (_a = data.departmentId) !== null && _a !== void 0 ? _a : null,
549
+ departmentName: data.department,
550
+ });
551
+ this.pushUpdate(updates, params, 'department', (_b = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _b !== void 0 ? _b : null);
552
+ this.pushUpdate(updates, params, 'department_id', (_c = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _c !== void 0 ? _c : null);
553
+ }
408
554
  if (updates.length) {
409
555
  params.push(collaboratorId);
410
556
  await tx.$executeRawUnsafe(`UPDATE operations_collaborator
@@ -418,6 +564,109 @@ let OperationsService = class OperationsService {
418
564
  });
419
565
  return this.getCollaboratorByIdForUser(userId, collaboratorId);
420
566
  }
567
+ async listDepartments(userId) {
568
+ const actor = await this.getActorContext(userId);
569
+ this.ensureCollaborator(actor);
570
+ return this.queryRows(`SELECT d.id,
571
+ d.slug,
572
+ d.code,
573
+ d.name,
574
+ d.description,
575
+ CASE WHEN d.deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status,
576
+ COUNT(DISTINCT c.id)::int AS "collaboratorCount",
577
+ d.created_at AS "createdAt",
578
+ d.updated_at AS "updatedAt"
579
+ FROM operations_department d
580
+ LEFT JOIN operations_collaborator c
581
+ ON c.deleted_at IS NULL
582
+ AND (
583
+ c.department_id = d.id
584
+ OR (
585
+ c.department_id IS NULL
586
+ AND LOWER(COALESCE(c.department, '')) = LOWER(d.name)
587
+ )
588
+ )
589
+ GROUP BY d.id
590
+ ORDER BY CASE WHEN d.deleted_at IS NULL THEN 0 ELSE 1 END ASC,
591
+ d.name ASC`);
592
+ }
593
+ async createDepartment(userId, data) {
594
+ const actor = await this.getActorContext(userId);
595
+ this.ensureDirector(actor);
596
+ const name = this.normalizeOptionalText(data.name);
597
+ if (!name) {
598
+ throw new common_1.BadRequestException('Department name is required.');
599
+ }
600
+ return this.prisma.$transaction(async (tx) => {
601
+ var _a, _b, _c, _d;
602
+ await this.assertDepartmentNameAvailable(tx, name);
603
+ const normalizedCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null;
604
+ if (normalizedCode) {
605
+ await this.assertDepartmentCodeAvailable(tx, normalizedCode);
606
+ }
607
+ const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_department (
608
+ slug,
609
+ code,
610
+ name,
611
+ description,
612
+ deleted_at,
613
+ created_at,
614
+ updated_at
615
+ ) VALUES (
616
+ $1, $2, $3, $4,
617
+ CASE WHEN $5 = 'inactive' THEN NOW() ELSE NULL END,
618
+ NOW(), NOW()
619
+ )
620
+ RETURNING id`, await this.generateUniqueDepartmentSlug(tx, name), normalizedCode, name, this.normalizeOptionalText(data.description), (_c = data.status) !== null && _c !== void 0 ? _c : 'active'));
621
+ const createdDepartmentId = (_d = created[0]) === null || _d === void 0 ? void 0 : _d.id;
622
+ if (!createdDepartmentId) {
623
+ throw new common_1.BadRequestException('Unable to create the department.');
624
+ }
625
+ return this.getDepartmentById(tx, createdDepartmentId, true);
626
+ });
627
+ }
628
+ async updateDepartment(userId, departmentId, data) {
629
+ const actor = await this.getActorContext(userId);
630
+ this.ensureDirector(actor);
631
+ return this.prisma.$transaction(async (tx) => {
632
+ var _a, _b, _c, _d, _e, _f;
633
+ const current = await this.getDepartmentById(tx, departmentId, true);
634
+ const nextName = data.name !== undefined
635
+ ? this.normalizeOptionalText(data.name)
636
+ : current.name;
637
+ if (!nextName) {
638
+ throw new common_1.BadRequestException('Department name is required.');
639
+ }
640
+ if (String(nextName).toLowerCase() !== String(current.name).toLowerCase()) {
641
+ await this.assertDepartmentNameAvailable(tx, nextName, departmentId);
642
+ }
643
+ const nextCode = data.code !== undefined
644
+ ? (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null
645
+ : ((_c = current.code) !== null && _c !== void 0 ? _c : null);
646
+ if (nextCode) {
647
+ await this.assertDepartmentCodeAvailable(tx, nextCode, departmentId);
648
+ }
649
+ const nextDescription = data.description !== undefined
650
+ ? this.normalizeOptionalText(data.description)
651
+ : ((_d = current.description) !== null && _d !== void 0 ? _d : null);
652
+ const nextStatus = (_f = (_e = data.status) !== null && _e !== void 0 ? _e : current.status) !== null && _f !== void 0 ? _f : 'active';
653
+ const nextSlug = String(nextName).toLowerCase() !== String(current.name).toLowerCase()
654
+ ? await this.generateUniqueDepartmentSlug(tx, nextName, departmentId)
655
+ : current.slug;
656
+ await tx.$executeRawUnsafe(`UPDATE operations_department
657
+ SET slug = $1,
658
+ code = $2,
659
+ name = $3,
660
+ description = $4,
661
+ deleted_at = CASE
662
+ WHEN $5 = 'inactive' THEN COALESCE(deleted_at, NOW())
663
+ ELSE NULL
664
+ END,
665
+ updated_at = NOW()
666
+ WHERE id = $6`, nextSlug, nextCode, nextName, nextDescription, nextStatus, departmentId);
667
+ return this.getDepartmentById(tx, departmentId, true);
668
+ });
669
+ }
421
670
  async listProjects(userId) {
422
671
  const actor = await this.getActorContext(userId);
423
672
  const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
@@ -466,7 +715,7 @@ let OperationsService = class OperationsService {
466
715
  this.ensureDirector(actor);
467
716
  this.requireFields(data, ['code', 'name']);
468
717
  const createdProjectId = await this.prisma.$transaction(async (tx) => {
469
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
718
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
470
719
  const created = await tx.$queryRawUnsafe(`INSERT INTO operations_project (
471
720
  contract_id,
472
721
  manager_collaborator_id,
@@ -483,8 +732,11 @@ let OperationsService = class OperationsService {
483
732
  created_at,
484
733
  updated_at
485
734
  ) VALUES (
486
- $1, $2, $3, $4, $5, $6, COALESCE($7, 'planning'), $8,
487
- COALESCE($9, 'project_delivery'), $10, $11, $12, NOW(), NOW()
735
+ $1, $2, $3, $4, $5, $6,
736
+ $7::operations_project_status_965e8d4b2d_enum,
737
+ $8,
738
+ $9::operations_project_delivery_model_75ee11b3b7_enum,
739
+ $10, $11::date, $12::date, NOW(), NOW()
488
740
  )
489
741
  RETURNING id`, (_a = data.contractId) !== null && _a !== void 0 ? _a : null, (_b = data.managerCollaboratorId) !== null && _b !== void 0 ? _b : null, data.code, data.name, (_c = data.clientName) !== null && _c !== void 0 ? _c : null, (_d = data.summary) !== null && _d !== void 0 ? _d : null, (_e = data.status) !== null && _e !== void 0 ? _e : 'planning', (_f = data.progressPercent) !== null && _f !== void 0 ? _f : null, (_g = data.deliveryModel) !== null && _g !== void 0 ? _g : 'project_delivery', (_h = data.budgetAmount) !== null && _h !== void 0 ? _h : null, (_j = data.startDate) !== null && _j !== void 0 ? _j : null, (_k = data.endDate) !== null && _k !== void 0 ? _k : null);
490
742
  const projectId = (_l = created[0]) === null || _l === void 0 ? void 0 : _l.id;
@@ -494,18 +746,19 @@ let OperationsService = class OperationsService {
494
746
  if (!data.contractId && data.autoGenerateContractDraft !== false) {
495
747
  const contractId = await this.createProjectContractDraft(tx, actor.userId, {
496
748
  projectId,
749
+ contractTemplateId: (_o = data.contractTemplateId) !== null && _o !== void 0 ? _o : null,
497
750
  projectCode: data.code,
498
751
  projectName: data.name,
499
- clientName: (_o = data.clientName) !== null && _o !== void 0 ? _o : data.name,
500
- managerCollaboratorId: (_p = data.managerCollaboratorId) !== null && _p !== void 0 ? _p : null,
501
- startDate: (_q = data.startDate) !== null && _q !== void 0 ? _q : null,
502
- endDate: (_r = data.endDate) !== null && _r !== void 0 ? _r : null,
503
- budgetAmount: (_s = data.budgetAmount) !== null && _s !== void 0 ? _s : null,
504
- monthlyHourCap: (_t = data.monthlyHourCap) !== null && _t !== void 0 ? _t : null,
505
- billingModel: (_u = data.billingModel) !== null && _u !== void 0 ? _u : 'time_and_material',
506
- contractCode: (_v = data.contractCode) !== null && _v !== void 0 ? _v : null,
507
- contractName: (_w = data.contractName) !== null && _w !== void 0 ? _w : null,
508
- description: (_y = (_x = data.contractDescription) !== null && _x !== void 0 ? _x : data.summary) !== null && _y !== void 0 ? _y : null,
752
+ clientName: (_p = data.clientName) !== null && _p !== void 0 ? _p : data.name,
753
+ managerCollaboratorId: (_q = data.managerCollaboratorId) !== null && _q !== void 0 ? _q : null,
754
+ startDate: (_r = data.startDate) !== null && _r !== void 0 ? _r : null,
755
+ endDate: (_s = data.endDate) !== null && _s !== void 0 ? _s : null,
756
+ budgetAmount: (_t = data.budgetAmount) !== null && _t !== void 0 ? _t : null,
757
+ monthlyHourCap: (_u = data.monthlyHourCap) !== null && _u !== void 0 ? _u : null,
758
+ billingModel: (_v = data.billingModel) !== null && _v !== void 0 ? _v : 'time_and_material',
759
+ contractCode: (_w = data.contractCode) !== null && _w !== void 0 ? _w : null,
760
+ contractName: (_x = data.contractName) !== null && _x !== void 0 ? _x : null,
761
+ description: (_z = (_y = data.contractDescription) !== null && _y !== void 0 ? _y : data.summary) !== null && _z !== void 0 ? _z : null,
509
762
  });
510
763
  await tx.$executeRawUnsafe(`UPDATE operations_project
511
764
  SET contract_id = $1,
@@ -519,7 +772,7 @@ let OperationsService = class OperationsService {
519
772
  async updateProject(userId, projectId, data) {
520
773
  const actor = await this.getActorContext(userId);
521
774
  this.ensureDirector(actor);
522
- await this.getProjectById(userId, projectId);
775
+ const currentProject = await this.getProjectById(userId, projectId);
523
776
  const updates = [];
524
777
  const params = [];
525
778
  this.pushUpdate(updates, params, 'contract_id', data.contractId);
@@ -528,13 +781,14 @@ let OperationsService = class OperationsService {
528
781
  this.pushUpdate(updates, params, 'name', data.name);
529
782
  this.pushUpdate(updates, params, 'client_name', data.clientName);
530
783
  this.pushUpdate(updates, params, 'summary', data.summary);
531
- this.pushUpdate(updates, params, 'status', data.status);
784
+ this.pushUpdate(updates, params, 'status', data.status, 'operations_project_status_965e8d4b2d_enum');
532
785
  this.pushUpdate(updates, params, 'progress_percent', data.progressPercent);
533
- this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel);
786
+ this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel, 'operations_project_delivery_model_75ee11b3b7_enum');
534
787
  this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
535
- this.pushUpdate(updates, params, 'start_date', data.startDate);
536
- this.pushUpdate(updates, params, 'end_date', data.endDate);
788
+ this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
789
+ this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
537
790
  await this.prisma.$transaction(async (tx) => {
791
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
538
792
  if (updates.length) {
539
793
  params.push(projectId);
540
794
  await tx.$executeRawUnsafe(`UPDATE operations_project
@@ -545,9 +799,162 @@ let OperationsService = class OperationsService {
545
799
  if (data.teamAssignments) {
546
800
  await this.replaceProjectAssignments(tx, projectId, data.teamAssignments);
547
801
  }
802
+ const nextContractId = data.contractId !== undefined
803
+ ? data.contractId
804
+ : ((_a = currentProject.contractId) !== null && _a !== void 0 ? _a : null);
805
+ const shouldGenerateDraft = !nextContractId && data.autoGenerateContractDraft === true;
806
+ if (shouldGenerateDraft) {
807
+ const contractId = await this.createProjectContractDraft(tx, actor.userId, {
808
+ projectId,
809
+ contractTemplateId: (_b = data.contractTemplateId) !== null && _b !== void 0 ? _b : null,
810
+ projectCode: (_c = data.code) !== null && _c !== void 0 ? _c : currentProject.code,
811
+ projectName: (_d = data.name) !== null && _d !== void 0 ? _d : currentProject.name,
812
+ clientName: (_f = (_e = data.clientName) !== null && _e !== void 0 ? _e : currentProject.clientName) !== null && _f !== void 0 ? _f : currentProject.name,
813
+ managerCollaboratorId: (_h = (_g = data.managerCollaboratorId) !== null && _g !== void 0 ? _g : currentProject.managerCollaboratorId) !== null && _h !== void 0 ? _h : null,
814
+ startDate: (_k = (_j = data.startDate) !== null && _j !== void 0 ? _j : currentProject.startDate) !== null && _k !== void 0 ? _k : null,
815
+ endDate: (_m = (_l = data.endDate) !== null && _l !== void 0 ? _l : currentProject.endDate) !== null && _m !== void 0 ? _m : null,
816
+ budgetAmount: (_p = (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : currentProject.budgetAmount) !== null && _p !== void 0 ? _p : null,
817
+ monthlyHourCap: (_s = (_q = data.monthlyHourCap) !== null && _q !== void 0 ? _q : (_r = currentProject.relatedContract) === null || _r === void 0 ? void 0 : _r.monthlyHourCap) !== null && _s !== void 0 ? _s : null,
818
+ billingModel: (_v = (_t = data.billingModel) !== null && _t !== void 0 ? _t : (_u = currentProject.relatedContract) === null || _u === void 0 ? void 0 : _u.billingModel) !== null && _v !== void 0 ? _v : 'time_and_material',
819
+ contractCode: (_y = (_w = data.contractCode) !== null && _w !== void 0 ? _w : (_x = currentProject.relatedContract) === null || _x === void 0 ? void 0 : _x.code) !== null && _y !== void 0 ? _y : null,
820
+ contractName: (_1 = (_z = data.contractName) !== null && _z !== void 0 ? _z : (_0 = currentProject.relatedContract) === null || _0 === void 0 ? void 0 : _0.name) !== null && _1 !== void 0 ? _1 : null,
821
+ description: (_6 = (_5 = (_4 = (_2 = data.contractDescription) !== null && _2 !== void 0 ? _2 : (_3 = currentProject.relatedContract) === null || _3 === void 0 ? void 0 : _3.description) !== null && _4 !== void 0 ? _4 : data.summary) !== null && _5 !== void 0 ? _5 : currentProject.summary) !== null && _6 !== void 0 ? _6 : null,
822
+ });
823
+ await tx.$executeRawUnsafe(`UPDATE operations_project
824
+ SET contract_id = $1,
825
+ updated_at = NOW()
826
+ WHERE id = $2`, contractId, projectId);
827
+ }
548
828
  });
549
829
  return this.getProjectById(userId, projectId);
550
830
  }
831
+ async listContractTemplates(userId) {
832
+ const actor = await this.getActorContext(userId);
833
+ this.ensureDirector(actor);
834
+ return this.queryRows(`SELECT t.id,
835
+ t.slug,
836
+ t.code,
837
+ t.name,
838
+ t.description,
839
+ t.contract_category AS "contractCategory",
840
+ t.contract_type AS "contractType",
841
+ t.billing_model AS "billingModel",
842
+ t.signature_status AS "signatureStatus",
843
+ t.is_active AS "isActive",
844
+ t.status,
845
+ t.content_html AS "contentHtml",
846
+ t.created_at AS "createdAt",
847
+ t.updated_at AS "updatedAt",
848
+ COUNT(DISTINCT c.id)::int AS "usageCount"
849
+ FROM operations_contract_template t
850
+ LEFT JOIN operations_contract c
851
+ ON c.contract_template_id = t.id
852
+ AND c.deleted_at IS NULL
853
+ WHERE t.deleted_at IS NULL
854
+ GROUP BY t.id
855
+ ORDER BY CASE
856
+ WHEN t.status = 'active' THEN 0
857
+ WHEN t.status = 'draft' THEN 1
858
+ WHEN t.status = 'inactive' THEN 2
859
+ ELSE 3
860
+ END,
861
+ t.name ASC`);
862
+ }
863
+ async getContractTemplateById(userId, templateId) {
864
+ const actor = await this.getActorContext(userId);
865
+ this.ensureDirector(actor);
866
+ return this.getContractTemplateRecord(this.prisma, templateId);
867
+ }
868
+ async createContractTemplate(userId, data) {
869
+ const actor = await this.getActorContext(userId);
870
+ this.ensureDirector(actor);
871
+ const name = this.normalizeOptionalText(data.name);
872
+ if (!name) {
873
+ throw new common_1.BadRequestException('Contract template name is required.');
874
+ }
875
+ return this.prisma.$transaction(async (tx) => {
876
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
877
+ await this.assertContractTemplateNameAvailable(tx, name);
878
+ const nextCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null;
879
+ if (nextCode) {
880
+ await this.assertContractTemplateCodeAvailable(tx, nextCode);
881
+ }
882
+ const nextStatus = (_c = data.status) !== null && _c !== void 0 ? _c : 'active';
883
+ const isActive = (_d = data.isActive) !== null && _d !== void 0 ? _d : !['inactive', 'archived'].includes(nextStatus);
884
+ const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_contract_template (
885
+ slug,
886
+ code,
887
+ name,
888
+ description,
889
+ contract_category,
890
+ contract_type,
891
+ billing_model,
892
+ signature_status,
893
+ is_active,
894
+ status,
895
+ content_html,
896
+ created_at,
897
+ updated_at
898
+ ) VALUES (
899
+ $1, $2, $3, $4,
900
+ $5, $6, $7, $8, $9, $10, $11,
901
+ NOW(), NOW()
902
+ )
903
+ RETURNING id`, await this.generateUniqueContractTemplateSlug(tx, name), nextCode, name, this.normalizeOptionalText(data.description), (_e = data.contractCategory) !== null && _e !== void 0 ? _e : 'client', (_f = data.contractType) !== null && _f !== void 0 ? _f : 'service_agreement', (_g = data.billingModel) !== null && _g !== void 0 ? _g : 'time_and_material', (_h = data.signatureStatus) !== null && _h !== void 0 ? _h : 'not_started', isActive, nextStatus, this.normalizeOptionalText(data.contentHtml)));
904
+ const templateId = (_j = created[0]) === null || _j === void 0 ? void 0 : _j.id;
905
+ if (!templateId) {
906
+ throw new common_1.BadRequestException('Unable to create the contract template.');
907
+ }
908
+ return this.getContractTemplateRecord(tx, templateId, true);
909
+ });
910
+ }
911
+ async updateContractTemplate(userId, templateId, data) {
912
+ const actor = await this.getActorContext(userId);
913
+ this.ensureDirector(actor);
914
+ return this.prisma.$transaction(async (tx) => {
915
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
916
+ const current = await this.getContractTemplateRecord(tx, templateId, true);
917
+ const nextName = data.name !== undefined
918
+ ? this.normalizeOptionalText(data.name)
919
+ : current.name;
920
+ if (!nextName) {
921
+ throw new common_1.BadRequestException('Contract template name is required.');
922
+ }
923
+ if (String(nextName).toLowerCase() !== String(current.name).toLowerCase()) {
924
+ await this.assertContractTemplateNameAvailable(tx, nextName, templateId);
925
+ }
926
+ const nextCode = data.code !== undefined
927
+ ? (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null
928
+ : ((_c = current.code) !== null && _c !== void 0 ? _c : null);
929
+ if (nextCode) {
930
+ await this.assertContractTemplateCodeAvailable(tx, nextCode, templateId);
931
+ }
932
+ const nextStatus = (_e = (_d = data.status) !== null && _d !== void 0 ? _d : current.status) !== null && _e !== void 0 ? _e : 'active';
933
+ const nextIsActive = (_f = data.isActive) !== null && _f !== void 0 ? _f : !['inactive', 'archived'].includes(nextStatus);
934
+ const nextSlug = String(nextName).toLowerCase() !== String(current.name).toLowerCase()
935
+ ? await this.generateUniqueContractTemplateSlug(tx, nextName, templateId)
936
+ : current.slug;
937
+ await tx.$executeRawUnsafe(`UPDATE operations_contract_template
938
+ SET slug = $1,
939
+ code = $2,
940
+ name = $3,
941
+ description = $4,
942
+ contract_category = $5,
943
+ contract_type = $6,
944
+ billing_model = $7,
945
+ signature_status = $8,
946
+ is_active = $9,
947
+ status = $10,
948
+ content_html = $11,
949
+ updated_at = NOW()
950
+ WHERE id = $12`, nextSlug, nextCode, nextName, data.description !== undefined
951
+ ? this.normalizeOptionalText(data.description)
952
+ : ((_g = current.description) !== null && _g !== void 0 ? _g : null), (_j = (_h = data.contractCategory) !== null && _h !== void 0 ? _h : current.contractCategory) !== null && _j !== void 0 ? _j : 'client', (_l = (_k = data.contractType) !== null && _k !== void 0 ? _k : current.contractType) !== null && _l !== void 0 ? _l : 'service_agreement', (_o = (_m = data.billingModel) !== null && _m !== void 0 ? _m : current.billingModel) !== null && _o !== void 0 ? _o : 'time_and_material', (_q = (_p = data.signatureStatus) !== null && _p !== void 0 ? _p : current.signatureStatus) !== null && _q !== void 0 ? _q : 'not_started', nextIsActive, nextStatus, data.contentHtml !== undefined
953
+ ? this.normalizeOptionalText(data.contentHtml)
954
+ : ((_r = current.contentHtml) !== null && _r !== void 0 ? _r : null), templateId);
955
+ return this.getContractTemplateRecord(tx, templateId, true);
956
+ });
957
+ }
551
958
  async listContracts(userId) {
552
959
  const actor = await this.getActorContext(userId);
553
960
  const params = [];
@@ -574,6 +981,10 @@ let OperationsService = class OperationsService {
574
981
  c.billing_model AS "billingModel",
575
982
  c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
576
983
  c.related_collaborator_id AS "relatedCollaboratorId",
984
+ c.contract_template_id AS "contractTemplateId",
985
+ template.name AS "contractTemplateName",
986
+ template.slug AS "contractTemplateSlug",
987
+ template.code AS "contractTemplateCode",
577
988
  c.origin_type AS "originType",
578
989
  c.origin_id AS "originId",
579
990
  c.start_date AS "startDate",
@@ -583,19 +994,23 @@ let OperationsService = class OperationsService {
583
994
  c.budget_amount AS "budgetAmount",
584
995
  c.monthly_hour_cap AS "monthlyHourCap",
585
996
  c.status,
997
+ c.creation_mode AS "creationMode",
998
+ c.wizard_step AS "wizardStep",
586
999
  c.description,
587
1000
  m.display_name AS "accountManagerName",
588
1001
  linked.display_name AS "relatedCollaboratorName",
589
- COALESCE(primary_party.display_name, linked.display_name, c.client_name) AS "mainRelatedPartyName",
590
- COALESCE(financials.value_amount, 0) AS "valueAmount",
591
- COALESCE(financials.payment_amount, 0) AS "paymentAmount",
592
- COALESCE(financials.revenue_amount, 0) AS "revenueAmount",
593
- COALESCE(financials.fine_amount, 0) AS "fineAmount",
594
- COALESCE(pdf_document.file_name, '') AS "currentPdfFileName",
1002
+ MAX(COALESCE(primary_party.display_name, linked.display_name, c.client_name)) AS "mainRelatedPartyName",
1003
+ MAX(COALESCE(financials.value_amount, 0)) AS "valueAmount",
1004
+ MAX(COALESCE(financials.payment_amount, 0)) AS "paymentAmount",
1005
+ MAX(COALESCE(financials.revenue_amount, 0)) AS "revenueAmount",
1006
+ MAX(COALESCE(financials.fine_amount, 0)) AS "fineAmount",
1007
+ MAX(COALESCE(pdf_document.file_name, '')) AS "currentPdfFileName",
595
1008
  COUNT(DISTINCT p.id)::int AS "projectCount"
596
1009
  FROM operations_contract c
597
1010
  LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
598
1011
  LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
1012
+ LEFT JOIN operations_contract_template template
1013
+ ON template.id = c.contract_template_id
599
1014
  LEFT JOIN LATERAL (
600
1015
  SELECT cp.display_name
601
1016
  FROM operations_contract_party cp
@@ -620,7 +1035,7 @@ let OperationsService = class OperationsService {
620
1035
  WHERE cd.contract_id = c.id
621
1036
  AND cd.deleted_at IS NULL
622
1037
  AND cd.is_current = true
623
- AND cd.document_type IN ('uploaded_pdf', 'generated_pdf')
1038
+ AND cd.document_type IN ('source_upload', 'generated_pdf')
624
1039
  ORDER BY cd.id DESC
625
1040
  LIMIT 1
626
1041
  ) pdf_document ON TRUE
@@ -628,8 +1043,8 @@ let OperationsService = class OperationsService {
628
1043
  ON p.contract_id = c.id
629
1044
  AND p.deleted_at IS NULL
630
1045
  WHERE ${accessClause}
631
- GROUP BY c.id, m.id, linked.id
632
- ORDER BY c.name ASC`, params);
1046
+ GROUP BY c.id, m.id, linked.id, template.id
1047
+ ORDER BY COALESCE(c.name, c.code, CONCAT('draft-', c.id)) ASC`, params);
633
1048
  }
634
1049
  async getContractById(userId, contractId) {
635
1050
  var _a, _b, _c;
@@ -645,6 +1060,10 @@ let OperationsService = class OperationsService {
645
1060
  c.billing_model AS "billingModel",
646
1061
  c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
647
1062
  c.related_collaborator_id AS "relatedCollaboratorId",
1063
+ c.contract_template_id AS "contractTemplateId",
1064
+ template.name AS "contractTemplateName",
1065
+ template.slug AS "contractTemplateSlug",
1066
+ template.code AS "contractTemplateCode",
648
1067
  c.origin_type AS "originType",
649
1068
  c.origin_id AS "originId",
650
1069
  c.start_date AS "startDate",
@@ -654,6 +1073,8 @@ let OperationsService = class OperationsService {
654
1073
  c.budget_amount AS "budgetAmount",
655
1074
  c.monthly_hour_cap AS "monthlyHourCap",
656
1075
  c.status,
1076
+ c.creation_mode AS "creationMode",
1077
+ c.wizard_step AS "wizardStep",
657
1078
  c.description,
658
1079
  c.content_html AS "contentHtml",
659
1080
  m.display_name AS "accountManagerName",
@@ -661,6 +1082,8 @@ let OperationsService = class OperationsService {
661
1082
  FROM operations_contract c
662
1083
  LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
663
1084
  LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
1085
+ LEFT JOIN operations_contract_template template
1086
+ ON template.id = c.contract_template_id
664
1087
  WHERE c.id = $1
665
1088
  AND c.deleted_at IS NULL`, [contractId]);
666
1089
  if (!contract) {
@@ -739,10 +1162,13 @@ let OperationsService = class OperationsService {
739
1162
  ORDER BY id ASC`, [contractId]),
740
1163
  this.queryRows(`SELECT id,
741
1164
  document_type AS "documentType",
1165
+ file_id AS "fileId",
742
1166
  file_name AS "fileName",
743
1167
  mime_type AS "mimeType",
744
1168
  file_content_base64 AS "fileContentBase64",
745
1169
  is_current AS "isCurrent",
1170
+ extraction_status AS "extractionStatus",
1171
+ extraction_summary AS "extractionSummary",
746
1172
  notes,
747
1173
  created_at AS "createdAt"
748
1174
  FROM operations_contract_document
@@ -781,9 +1207,9 @@ let OperationsService = class OperationsService {
781
1207
  async createContract(userId, data) {
782
1208
  const actor = await this.getActorContext(userId);
783
1209
  this.ensureDirector(actor);
784
- this.requireFields(data, ['code', 'name', 'clientName', 'startDate']);
785
1210
  const createdId = await this.prisma.$transaction(async (tx) => {
786
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
1211
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
1212
+ const normalizedCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : (await this.generateContractCode(tx));
787
1213
  const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
788
1214
  code,
789
1215
  name,
@@ -795,6 +1221,7 @@ let OperationsService = class OperationsService {
795
1221
  billing_model,
796
1222
  account_manager_collaborator_id,
797
1223
  related_collaborator_id,
1224
+ contract_template_id,
798
1225
  origin_type,
799
1226
  origin_id,
800
1227
  start_date,
@@ -804,23 +1231,42 @@ let OperationsService = class OperationsService {
804
1231
  budget_amount,
805
1232
  monthly_hour_cap,
806
1233
  status,
1234
+ creation_mode,
1235
+ wizard_step,
807
1236
  description,
808
1237
  content_html,
809
1238
  created_at,
810
1239
  updated_at
811
1240
  ) VALUES (
812
- $1, $2, COALESCE($3, 'client'), COALESCE($4, 'service_agreement'), $5, COALESCE($6, 'not_started'),
813
- COALESCE($7, true), COALESCE($8, 'time_and_material'), $9, $10, COALESCE($11, 'manual'), $12, $13,
814
- $14, $15, $16, $17, $18, COALESCE($19, 'draft'), $20, $21, NOW(), NOW()
1241
+ $1, $2,
1242
+ $3::operations_contract_contract_category_70d553ea09_enum,
1243
+ $4::operations_contract_contract_type_48331e2ebf_enum,
1244
+ $5,
1245
+ $6::operations_contract_signature_status_2cb7282a7b_enum,
1246
+ $7,
1247
+ $8::operations_contract_billing_model_409dc7fea2_enum,
1248
+ $9,
1249
+ $10,
1250
+ $11,
1251
+ $12::operations_contract_origin_type_07a7cc2b5d_enum,
1252
+ $13,
1253
+ $14::date,
1254
+ $15::date, $16::date, $17::date, $18, $19,
1255
+ $20::operations_contract_status_a0395962df_enum,
1256
+ $21::operations_contract_creation_mode_98ba669209_enum,
1257
+ $22,
1258
+ $23,
1259
+ $24,
1260
+ NOW(), NOW()
815
1261
  )
816
- RETURNING id`, data.code, data.name, (_a = data.contractCategory) !== null && _a !== void 0 ? _a : 'client', (_b = data.contractType) !== null && _b !== void 0 ? _b : 'service_agreement', data.clientName, (_c = data.signatureStatus) !== null && _c !== void 0 ? _c : 'not_started', (_d = data.isActive) !== null && _d !== void 0 ? _d : true, (_e = data.billingModel) !== null && _e !== void 0 ? _e : 'time_and_material', (_f = data.accountManagerCollaboratorId) !== null && _f !== void 0 ? _f : null, (_g = data.relatedCollaboratorId) !== null && _g !== void 0 ? _g : null, (_h = data.originType) !== null && _h !== void 0 ? _h : 'manual', (_j = data.originId) !== null && _j !== void 0 ? _j : null, data.startDate, (_k = data.endDate) !== null && _k !== void 0 ? _k : null, (_l = data.signedAt) !== null && _l !== void 0 ? _l : null, (_m = data.effectiveDate) !== null && _m !== void 0 ? _m : data.startDate, (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : null, (_p = data.monthlyHourCap) !== null && _p !== void 0 ? _p : null, (_q = data.status) !== null && _q !== void 0 ? _q : 'draft', (_r = data.description) !== null && _r !== void 0 ? _r : null, (_s = data.contentHtml) !== null && _s !== void 0 ? _s : null);
817
- const contractId = (_t = created[0]) === null || _t === void 0 ? void 0 : _t.id;
1262
+ RETURNING id`, normalizedCode, this.normalizeOptionalText(data.name), (_c = data.contractCategory) !== null && _c !== void 0 ? _c : 'client', (_d = data.contractType) !== null && _d !== void 0 ? _d : 'service_agreement', this.normalizeOptionalText(data.clientName), (_e = data.signatureStatus) !== null && _e !== void 0 ? _e : 'not_started', (_f = data.isActive) !== null && _f !== void 0 ? _f : true, (_g = data.billingModel) !== null && _g !== void 0 ? _g : 'time_and_material', (_h = data.accountManagerCollaboratorId) !== null && _h !== void 0 ? _h : null, (_j = data.relatedCollaboratorId) !== null && _j !== void 0 ? _j : null, (_k = data.contractTemplateId) !== null && _k !== void 0 ? _k : null, (_l = data.originType) !== null && _l !== void 0 ? _l : 'manual', (_m = data.originId) !== null && _m !== void 0 ? _m : null, this.normalizeOptionalText((_o = data.startDate) !== null && _o !== void 0 ? _o : null), (_p = data.endDate) !== null && _p !== void 0 ? _p : null, (_q = data.signedAt) !== null && _q !== void 0 ? _q : null, (_s = (_r = data.effectiveDate) !== null && _r !== void 0 ? _r : data.startDate) !== null && _s !== void 0 ? _s : null, (_t = data.budgetAmount) !== null && _t !== void 0 ? _t : null, (_u = data.monthlyHourCap) !== null && _u !== void 0 ? _u : null, (_v = data.status) !== null && _v !== void 0 ? _v : 'draft', (_w = data.creationMode) !== null && _w !== void 0 ? _w : 'blank', (_x = data.wizardStep) !== null && _x !== void 0 ? _x : 0, (_y = data.description) !== null && _y !== void 0 ? _y : null, (_z = data.contentHtml) !== null && _z !== void 0 ? _z : null);
1263
+ const contractId = (_0 = created[0]) === null || _0 === void 0 ? void 0 : _0.id;
818
1264
  await this.replaceContractParties(tx, contractId, data.parties);
819
1265
  await this.replaceContractSignatures(tx, contractId, data.signatures);
820
1266
  await this.replaceContractFinancialTerms(tx, contractId, data.financialTerms);
821
1267
  await this.replaceContractRevisions(tx, contractId, data.revisions);
822
1268
  if (data.replaceUploadedPdfDocument) {
823
- await this.replaceContractPdfDocument(tx, contractId, data.replaceUploadedPdfDocument);
1269
+ await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
824
1270
  }
825
1271
  await this.insertContractHistory(tx, contractId, userId, 'created', data.originType === 'manual'
826
1272
  ? 'Manual contract created from registry.'
@@ -829,34 +1275,674 @@ let OperationsService = class OperationsService {
829
1275
  });
830
1276
  return this.getContractById(userId, createdId);
831
1277
  }
1278
+ async createContractDraft(userId, data) {
1279
+ const actor = await this.getActorContext(userId);
1280
+ this.ensureDirector(actor);
1281
+ const creationMode = this.normalizeEnumValue(data.creationMode, CONTRACT_CREATION_MODE_VALUES, 'blank');
1282
+ const selectedTemplate = data.templateId && data.templateId > 0
1283
+ ? await this.getContractTemplateById(userId, data.templateId)
1284
+ : null;
1285
+ const duplicateSource = data.duplicateFromId && data.duplicateFromId > 0
1286
+ ? await this.getContractById(userId, data.duplicateFromId)
1287
+ : null;
1288
+ const storedSourceFile = data.sourceFileId && data.sourceFileId > 0
1289
+ ? await this.prisma.file.findUnique({
1290
+ where: { id: data.sourceFileId },
1291
+ include: { file_mimetype: true },
1292
+ })
1293
+ : null;
1294
+ if (data.sourceFileId && !storedSourceFile) {
1295
+ throw new common_1.NotFoundException('Source contract file not found.');
1296
+ }
1297
+ const createdId = await this.prisma.$transaction(async (tx) => {
1298
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16;
1299
+ const generatedCode = await this.generateContractCode(tx);
1300
+ const duplicateNameBase = (_b = (_a = this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.name)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.code)) !== null && _b !== void 0 ? _b : 'Contract';
1301
+ const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
1302
+ code,
1303
+ name,
1304
+ contract_category,
1305
+ contract_type,
1306
+ client_name,
1307
+ signature_status,
1308
+ is_active,
1309
+ billing_model,
1310
+ account_manager_collaborator_id,
1311
+ related_collaborator_id,
1312
+ contract_template_id,
1313
+ origin_type,
1314
+ origin_id,
1315
+ start_date,
1316
+ end_date,
1317
+ signed_at,
1318
+ effective_date,
1319
+ budget_amount,
1320
+ monthly_hour_cap,
1321
+ status,
1322
+ creation_mode,
1323
+ wizard_step,
1324
+ description,
1325
+ content_html,
1326
+ created_by_user_id,
1327
+ updated_by_user_id,
1328
+ created_at,
1329
+ updated_at
1330
+ ) VALUES (
1331
+ $1,
1332
+ $2,
1333
+ $3::operations_contract_contract_category_70d553ea09_enum,
1334
+ $4::operations_contract_contract_type_48331e2ebf_enum,
1335
+ $5,
1336
+ $6::operations_contract_signature_status_2cb7282a7b_enum,
1337
+ $7,
1338
+ $8::operations_contract_billing_model_409dc7fea2_enum,
1339
+ $9,
1340
+ $10,
1341
+ $11,
1342
+ $12::operations_contract_origin_type_07a7cc2b5d_enum,
1343
+ $13,
1344
+ $14::date,
1345
+ $15::date,
1346
+ $16::date,
1347
+ $17::date,
1348
+ $18,
1349
+ $19,
1350
+ $20::operations_contract_status_a0395962df_enum,
1351
+ $21::operations_contract_creation_mode_98ba669209_enum,
1352
+ $22,
1353
+ $23,
1354
+ $24,
1355
+ $25,
1356
+ $25,
1357
+ NOW(),
1358
+ NOW()
1359
+ )
1360
+ RETURNING id`, generatedCode, creationMode === 'duplicate'
1361
+ ? `${duplicateNameBase} Copy`
1362
+ : this.normalizeOptionalText(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.name), (_d = (_c = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractCategory) !== null && _c !== void 0 ? _c : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractCategory) !== null && _d !== void 0 ? _d : 'client', (_f = (_e = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractType) !== null && _e !== void 0 ? _e : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractType) !== null && _f !== void 0 ? _f : 'service_agreement', this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.clientName), (_h = (_g = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.signatureStatus) !== null && _g !== void 0 ? _g : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.signatureStatus) !== null && _h !== void 0 ? _h : 'not_started', (_j = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.isActive) !== null && _j !== void 0 ? _j : true, (_l = (_k = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.billingModel) !== null && _k !== void 0 ? _k : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.billingModel) !== null && _l !== void 0 ? _l : 'time_and_material', (_m = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.accountManagerCollaboratorId) !== null && _m !== void 0 ? _m : null, (_o = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.relatedCollaboratorId) !== null && _o !== void 0 ? _o : null, (_q = (_p = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractTemplateId) !== null && _p !== void 0 ? _p : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _q !== void 0 ? _q : null, (_r = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.originType) !== null && _r !== void 0 ? _r : 'manual', (_s = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.originId) !== null && _s !== void 0 ? _s : null, (_t = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.startDate) !== null && _t !== void 0 ? _t : null, (_u = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.endDate) !== null && _u !== void 0 ? _u : null, (_v = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.signedAt) !== null && _v !== void 0 ? _v : null, (_w = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.effectiveDate) !== null && _w !== void 0 ? _w : null, (_x = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.budgetAmount) !== null && _x !== void 0 ? _x : null, (_y = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.monthlyHourCap) !== null && _y !== void 0 ? _y : null, 'draft', creationMode, creationMode === 'upload' ? 0 : creationMode === 'blank' ? 0 : 1, (_0 = (_z = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.description) !== null && _z !== void 0 ? _z : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.description) !== null && _0 !== void 0 ? _0 : null, (_2 = (_1 = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contentHtml) !== null && _1 !== void 0 ? _1 : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contentHtml) !== null && _2 !== void 0 ? _2 : null, userId);
1363
+ const contractId = (_3 = created[0]) === null || _3 === void 0 ? void 0 : _3.id;
1364
+ if (duplicateSource) {
1365
+ await this.replaceContractParties(tx, contractId, duplicateSource.parties);
1366
+ await this.replaceContractSignatures(tx, contractId, duplicateSource.signatures);
1367
+ await this.replaceContractFinancialTerms(tx, contractId, duplicateSource.financialTerms);
1368
+ await this.replaceContractRevisions(tx, contractId, duplicateSource.revisions);
1369
+ const currentSourceDocument = (_4 = duplicateSource.documents.find((document) => document.isCurrent && document.documentType === 'source_upload')) !== null && _4 !== void 0 ? _4 : null;
1370
+ if (currentSourceDocument) {
1371
+ await this.replaceContractDocument(tx, contractId, 'source_upload', {
1372
+ fileId: (_5 = currentSourceDocument.fileId) !== null && _5 !== void 0 ? _5 : null,
1373
+ fileName: currentSourceDocument.fileName,
1374
+ mimeType: currentSourceDocument.mimeType,
1375
+ fileContentBase64: (_6 = currentSourceDocument.fileContentBase64) !== null && _6 !== void 0 ? _6 : null,
1376
+ notes: (_7 = currentSourceDocument.notes) !== null && _7 !== void 0 ? _7 : null,
1377
+ extractionStatus: (_8 = currentSourceDocument.extractionStatus) !== null && _8 !== void 0 ? _8 : 'skipped',
1378
+ extractionSummary: (_9 = currentSourceDocument.extractionSummary) !== null && _9 !== void 0 ? _9 : null,
1379
+ });
1380
+ }
1381
+ }
1382
+ if (storedSourceFile) {
1383
+ await this.replaceContractDocument(tx, contractId, 'source_upload', {
1384
+ fileId: storedSourceFile.id,
1385
+ fileName: (_10 = this.normalizeOptionalText(data.sourceFileName)) !== null && _10 !== void 0 ? _10 : storedSourceFile.filename,
1386
+ mimeType: (_13 = (_11 = this.normalizeOptionalText(data.sourceMimeType)) !== null && _11 !== void 0 ? _11 : (_12 = storedSourceFile.file_mimetype) === null || _12 === void 0 ? void 0 : _12.name) !== null && _13 !== void 0 ? _13 : 'application/pdf',
1387
+ notes: 'Source contract document uploaded during draft creation.',
1388
+ extractionStatus: 'pending',
1389
+ extractionSummary: null,
1390
+ });
1391
+ }
1392
+ await this.insertContractHistory(tx, contractId, userId, 'draft_created', `Contract draft created with mode ${creationMode}.`, JSON.stringify({
1393
+ creationMode,
1394
+ templateId: (_14 = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _14 !== void 0 ? _14 : null,
1395
+ duplicateFromId: (_15 = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.id) !== null && _15 !== void 0 ? _15 : null,
1396
+ sourceFileId: (_16 = storedSourceFile === null || storedSourceFile === void 0 ? void 0 : storedSourceFile.id) !== null && _16 !== void 0 ? _16 : null,
1397
+ }));
1398
+ return contractId;
1399
+ });
1400
+ return this.getContractById(userId, createdId);
1401
+ }
1402
+ async extractContractSource(userId, contractId, data) {
1403
+ var _a, _b;
1404
+ const actor = await this.getActorContext(userId);
1405
+ this.ensureDirector(actor);
1406
+ const contract = await this.getContractById(userId, contractId);
1407
+ const sourceDocument = contract.documents.find((document) => document.isCurrent && document.documentType === 'source_upload');
1408
+ if (!sourceDocument) {
1409
+ throw new common_1.BadRequestException('No source document is attached to this contract draft.');
1410
+ }
1411
+ await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
1412
+ SET extraction_status = 'processing',
1413
+ updated_at = NOW()
1414
+ WHERE id = $1`, sourceDocument.id);
1415
+ try {
1416
+ const extracted = await this.extractContractDraft(userId, {
1417
+ contractId,
1418
+ provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
1419
+ promptMessage: (_b = data.promptMessage) !== null && _b !== void 0 ? _b : null,
1420
+ });
1421
+ await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
1422
+ SET extraction_status = 'completed',
1423
+ extraction_summary = $2,
1424
+ updated_at = NOW()
1425
+ WHERE id = $1`, sourceDocument.id, extracted.summary || extracted.description || extracted.name || null);
1426
+ await this.insertContractHistory(this.prisma, contractId, userId, 'source_extracted', 'Source contract document extracted with AI.');
1427
+ return extracted;
1428
+ }
1429
+ catch (error) {
1430
+ await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
1431
+ SET extraction_status = 'failed',
1432
+ updated_at = NOW()
1433
+ WHERE id = $1`, sourceDocument.id);
1434
+ throw error;
1435
+ }
1436
+ }
1437
+ async generateContractContent(userId, contractId, data = {}) {
1438
+ const actor = await this.getActorContext(userId);
1439
+ this.ensureDirector(actor);
1440
+ const contract = await this.getContractById(userId, contractId);
1441
+ const contentHtml = await this.generateContractContentHtml(contract, data);
1442
+ await this.prisma.$transaction(async (tx) => {
1443
+ var _a;
1444
+ await tx.$executeRawUnsafe(`UPDATE operations_contract
1445
+ SET content_html = $2,
1446
+ wizard_step = CASE
1447
+ WHEN COALESCE(wizard_step, 0) < 4 THEN 4
1448
+ ELSE wizard_step
1449
+ END,
1450
+ updated_by_user_id = $3,
1451
+ updated_at = NOW()
1452
+ WHERE id = $1`, contractId, contentHtml, userId);
1453
+ await this.insertContractHistory(tx, contractId, userId, 'content_generated', 'Contract content generated automatically for editing.', JSON.stringify({
1454
+ provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
1455
+ overwrite: Boolean(data.overwrite),
1456
+ hasPrompt: Boolean(this.normalizeOptionalText(data.promptMessage)),
1457
+ }));
1458
+ });
1459
+ return this.getContractById(userId, contractId);
1460
+ }
1461
+ async reviewContractLegally(userId, contractId, data = {}) {
1462
+ const actor = await this.getActorContext(userId);
1463
+ this.ensureDirector(actor);
1464
+ const contract = await this.getContractById(userId, contractId);
1465
+ const review = await this.buildContractLegalReview(contract, data);
1466
+ await this.insertContractHistory(this.prisma, contractId, userId, 'legal_reviewed', 'Advisory legal checklist updated for this contract.', JSON.stringify(review));
1467
+ return review;
1468
+ }
1469
+ async generateContractPdf(userId, contractId) {
1470
+ const actor = await this.getActorContext(userId);
1471
+ this.ensureDirector(actor);
1472
+ const contract = await this.getContractById(userId, contractId);
1473
+ const pdfBuffer = await this.renderContractPdfBuffer(contract);
1474
+ const fileName = this.buildContractPdfFileName(contract);
1475
+ const uploadedFile = await this.fileService.upload('operations/contracts/generated', {
1476
+ fieldname: 'file',
1477
+ originalname: fileName,
1478
+ encoding: '7bit',
1479
+ mimetype: 'application/pdf',
1480
+ size: pdfBuffer.length,
1481
+ destination: '',
1482
+ filename: fileName,
1483
+ path: '',
1484
+ buffer: pdfBuffer,
1485
+ });
1486
+ await this.prisma.$transaction(async (tx) => {
1487
+ await this.replaceContractDocument(tx, contractId, 'generated_pdf', {
1488
+ fileId: uploadedFile.id,
1489
+ fileName,
1490
+ mimeType: 'application/pdf',
1491
+ notes: 'PDF generated from contract rich text content.',
1492
+ extractionStatus: 'skipped',
1493
+ extractionSummary: null,
1494
+ });
1495
+ await this.insertContractHistory(tx, contractId, userId, 'pdf_generated', 'Generated a branded PDF version of the contract.');
1496
+ });
1497
+ return {
1498
+ contractId,
1499
+ fileId: uploadedFile.id,
1500
+ fileName,
1501
+ mimeType: 'application/pdf',
1502
+ documentType: 'generated_pdf',
1503
+ downloadUrl: `/file/open/${uploadedFile.id}`,
1504
+ };
1505
+ }
1506
+ async extractContractDraft(userId, data) {
1507
+ var _a;
1508
+ const actor = await this.getActorContext(userId);
1509
+ this.ensureDirector(actor);
1510
+ const uploadFile = await this.resolveContractExtractionFile(userId, data);
1511
+ const aiResult = await this.aiService.chat({
1512
+ provider: data.provider === 'gemini' ? 'gemini' : 'openai',
1513
+ model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
1514
+ message: this.normalizeExtractionString(data.promptMessage) ||
1515
+ 'Analyze the attached contract and extract a structured draft for the contract form.',
1516
+ systemPrompt: this.buildContractExtractionSystemPrompt(),
1517
+ }, uploadFile);
1518
+ const parsed = this.parseAiJsonPayload(String((_a = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _a !== void 0 ? _a : ''));
1519
+ const draft = this.normalizeContractExtractDraft(parsed);
1520
+ const warnings = [...draft.warnings];
1521
+ if (uploadFile.mimetype === 'application/msword' ||
1522
+ uploadFile.mimetype ===
1523
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1524
+ warnings.push('Word extraction is best-effort. Review names, dates, and values before saving.');
1525
+ }
1526
+ return Object.assign(Object.assign({}, draft), { warnings: Array.from(new Set(warnings.filter(Boolean))) });
1527
+ }
1528
+ async createContractFromProposalIntegration(payload) {
1529
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
1530
+ const sourceEntityId = String(payload.proposalId || '').trim();
1531
+ if (!sourceEntityId) {
1532
+ throw new common_1.BadRequestException('proposalId is required for CRM proposal integration.');
1533
+ }
1534
+ const proposal = payload.proposal || {
1535
+ code: (_a = payload.code) !== null && _a !== void 0 ? _a : null,
1536
+ title: (_b = payload.title) !== null && _b !== void 0 ? _b : null,
1537
+ contractCategory: (_d = (_c = payload.commercialTerms) === null || _c === void 0 ? void 0 : _c.contractCategory) !== null && _d !== void 0 ? _d : 'client',
1538
+ contractType: (_f = (_e = payload.commercialTerms) === null || _e === void 0 ? void 0 : _e.contractType) !== null && _f !== void 0 ? _f : 'service_agreement',
1539
+ billingModel: (_h = (_g = payload.commercialTerms) === null || _g === void 0 ? void 0 : _g.billingModel) !== null && _h !== void 0 ? _h : 'fixed_price',
1540
+ validFrom: (_k = (_j = payload.commercialTerms) === null || _j === void 0 ? void 0 : _j.validFrom) !== null && _k !== void 0 ? _k : null,
1541
+ validUntil: (_o = (_l = payload.validUntil) !== null && _l !== void 0 ? _l : (_m = payload.commercialTerms) === null || _m === void 0 ? void 0 : _m.validUntil) !== null && _o !== void 0 ? _o : null,
1542
+ totalAmount: (_p = payload.total) !== null && _p !== void 0 ? _p : null,
1543
+ totalAmountCents: payload.total != null ? Math.round(Number(payload.total) * 100) : null,
1544
+ notes: (_r = (_q = payload.commercialTerms) === null || _q === void 0 ? void 0 : _q.notes) !== null && _r !== void 0 ? _r : null,
1545
+ };
1546
+ const person = payload.person || {};
1547
+ const items = Array.isArray(payload.items) ? payload.items : [];
1548
+ const contractCategory = (_s = proposal.contractCategory) !== null && _s !== void 0 ? _s : 'client';
1549
+ const contractType = (_t = proposal.contractType) !== null && _t !== void 0 ? _t : 'service_agreement';
1550
+ const billingModel = (_u = proposal.billingModel) !== null && _u !== void 0 ? _u : 'fixed_price';
1551
+ const clientName = (_x = (_w = (_v = this.normalizeOptionalText(person.tradeName)) !== null && _v !== void 0 ? _v : this.normalizeOptionalText(person.name)) !== null && _w !== void 0 ? _w : this.normalizeOptionalText(proposal.title)) !== null && _x !== void 0 ? _x : `Proposal ${sourceEntityId}`;
1552
+ const contractName = (_y = this.normalizeOptionalText(proposal.title)) !== null && _y !== void 0 ? _y : `Proposal ${sourceEntityId}`;
1553
+ const financialTerms = items
1554
+ .map((item) => {
1555
+ var _a, _b, _c;
1556
+ return ({
1557
+ termType: this.normalizeEnumValue(item.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
1558
+ label: (_a = this.normalizeOptionalText(item.name)) !== null && _a !== void 0 ? _a : 'Commercial term',
1559
+ amount: Number((_b = item.amount) !== null && _b !== void 0 ? _b : Number(item.totalAmountCents || 0) / 100),
1560
+ recurrence: this.normalizeEnumValue(item.recurrence, RECURRENCE_VALUES, 'one_time'),
1561
+ dueDay: (_c = item.dueDay) !== null && _c !== void 0 ? _c : null,
1562
+ notes: this.normalizeOptionalText(item.description),
1563
+ });
1564
+ })
1565
+ .filter((term) => Number.isFinite(term.amount) && term.amount > 0);
1566
+ const fallbackAmount = Number((_z = proposal.totalAmount) !== null && _z !== void 0 ? _z : 0);
1567
+ if (financialTerms.length === 0 && Number.isFinite(fallbackAmount) && fallbackAmount > 0) {
1568
+ financialTerms.push({
1569
+ termType: 'revenue',
1570
+ label: contractName,
1571
+ amount: fallbackAmount,
1572
+ recurrence: 'one_time',
1573
+ dueDay: null,
1574
+ notes: this.normalizeOptionalText(proposal.notes),
1575
+ });
1576
+ }
1577
+ const primaryPartyRole = ['employee', 'contractor'].includes(contractCategory)
1578
+ ? 'employee'
1579
+ : ['supplier', 'vendor'].includes(contractCategory)
1580
+ ? 'supplier'
1581
+ : contractCategory === 'partner'
1582
+ ? 'partner'
1583
+ : 'client';
1584
+ return this.prisma.$transaction(async (tx) => {
1585
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
1586
+ await tx.$queryRawUnsafe(`SELECT pg_advisory_xact_lock(hashtext($1))`, `operations:crm_proposal:${sourceEntityId}`);
1587
+ const existingContracts = await tx.$queryRawUnsafe(`SELECT id, code
1588
+ FROM operations_contract
1589
+ WHERE deleted_at IS NULL
1590
+ AND origin_type = $1::operations_contract_origin_type_07a7cc2b5d_enum
1591
+ AND origin_id::text = $2
1592
+ ORDER BY id DESC
1593
+ LIMIT 1`, 'crm_proposal', sourceEntityId);
1594
+ const existingContract = existingContracts[0];
1595
+ if (existingContract === null || existingContract === void 0 ? void 0 : existingContract.id) {
1596
+ return {
1597
+ id: existingContract.id,
1598
+ code: (_a = existingContract.code) !== null && _a !== void 0 ? _a : `CONTRACT-${existingContract.id}`,
1599
+ };
1600
+ }
1601
+ const generatedCode = await this.generateContractCode(tx);
1602
+ const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
1603
+ code,
1604
+ name,
1605
+ contract_category,
1606
+ contract_type,
1607
+ client_name,
1608
+ signature_status,
1609
+ is_active,
1610
+ billing_model,
1611
+ account_manager_collaborator_id,
1612
+ related_collaborator_id,
1613
+ contract_template_id,
1614
+ origin_type,
1615
+ origin_id,
1616
+ start_date,
1617
+ end_date,
1618
+ signed_at,
1619
+ effective_date,
1620
+ budget_amount,
1621
+ monthly_hour_cap,
1622
+ status,
1623
+ creation_mode,
1624
+ wizard_step,
1625
+ description,
1626
+ content_html,
1627
+ created_by_user_id,
1628
+ updated_by_user_id,
1629
+ created_at,
1630
+ updated_at
1631
+ ) VALUES (
1632
+ $1,
1633
+ $2,
1634
+ $3::operations_contract_contract_category_70d553ea09_enum,
1635
+ $4::operations_contract_contract_type_48331e2ebf_enum,
1636
+ $5,
1637
+ $6::operations_contract_signature_status_2cb7282a7b_enum,
1638
+ $7,
1639
+ $8::operations_contract_billing_model_409dc7fea2_enum,
1640
+ $9,
1641
+ $10,
1642
+ $11,
1643
+ $12::operations_contract_origin_type_07a7cc2b5d_enum,
1644
+ $13,
1645
+ $14::date,
1646
+ $15::date,
1647
+ $16::date,
1648
+ $17::date,
1649
+ $18,
1650
+ $19,
1651
+ $20::operations_contract_status_a0395962df_enum,
1652
+ $21::operations_contract_creation_mode_98ba669209_enum,
1653
+ $22,
1654
+ $23,
1655
+ $24,
1656
+ $25,
1657
+ $25,
1658
+ NOW(),
1659
+ NOW()
1660
+ )
1661
+ RETURNING id`, generatedCode, contractName, contractCategory, contractType, clientName, 'not_started', true, billingModel, null, null, null, 'crm_proposal', sourceEntityId, (_b = proposal.validFrom) !== null && _b !== void 0 ? _b : null, (_c = proposal.validUntil) !== null && _c !== void 0 ? _c : null, null, (_d = proposal.validFrom) !== null && _d !== void 0 ? _d : null, fallbackAmount > 0 ? fallbackAmount : null, null, 'draft', 'blank', 1, this.normalizeOptionalText(proposal.notes), this.normalizeOptionalText((_e = payload.revision) === null || _e === void 0 ? void 0 : _e.contentHtml), Number(payload.approvedByUserId) || null);
1662
+ const contractId = (_f = created[0]) === null || _f === void 0 ? void 0 : _f.id;
1663
+ if (!contractId) {
1664
+ throw new common_1.BadRequestException('Could not create contract draft from proposal.');
1665
+ }
1666
+ await this.replaceContractParties(tx, contractId, [
1667
+ {
1668
+ partyRole: primaryPartyRole,
1669
+ partyType: person.tradeName ? 'company' : 'individual',
1670
+ displayName: clientName,
1671
+ documentNumber: this.normalizeOptionalText(person.document),
1672
+ email: this.normalizeOptionalText(person.email),
1673
+ phone: this.normalizeOptionalText(person.phone),
1674
+ isPrimary: true,
1675
+ },
1676
+ ]);
1677
+ await this.replaceContractFinancialTerms(tx, contractId, financialTerms.map((term) => ({
1678
+ termType: term.termType,
1679
+ label: term.label,
1680
+ amount: term.amount,
1681
+ recurrence: term.recurrence,
1682
+ dueDay: term.dueDay,
1683
+ notes: term.notes,
1684
+ })));
1685
+ await this.replaceContractRevisions(tx, contractId, [
1686
+ {
1687
+ revisionType: 'revision',
1688
+ title: (_h = (_g = payload.revision) === null || _g === void 0 ? void 0 : _g.title) !== null && _h !== void 0 ? _h : contractName,
1689
+ effectiveDate: (_j = proposal.validFrom) !== null && _j !== void 0 ? _j : null,
1690
+ status: 'draft',
1691
+ summary: (_l = this.normalizeOptionalText((_k = payload.revision) === null || _k === void 0 ? void 0 : _k.summary)) !== null && _l !== void 0 ? _l : this.normalizeOptionalText(proposal.notes),
1692
+ },
1693
+ ]);
1694
+ await this.insertContractHistory(tx, contractId, Number(payload.approvedByUserId) || null, 'created', `Draft contract generated from CRM proposal ${(_m = proposal.code) !== null && _m !== void 0 ? _m : sourceEntityId}.`, JSON.stringify({
1695
+ correlationId: payload.correlationId || `proposal:${sourceEntityId}`,
1696
+ proposalId: (_o = payload.proposalId) !== null && _o !== void 0 ? _o : (Number(sourceEntityId) > 0 ? Number(sourceEntityId) : null),
1697
+ proposalRevisionId: (_p = payload.proposalRevisionId) !== null && _p !== void 0 ? _p : null,
1698
+ sourceModule: 'contact',
1699
+ sourceEntity: 'proposal',
1700
+ sourceId: sourceEntityId,
1701
+ approvedByUserId: Number(payload.approvedByUserId) || null,
1702
+ }));
1703
+ await this.integrationApi.publishEvent({
1704
+ eventName: 'operations.contract.created',
1705
+ sourceModule: 'operations',
1706
+ aggregateType: 'contract',
1707
+ aggregateId: String(contractId),
1708
+ payload: this.buildContractCreatedEventPayload({
1709
+ id: contractId,
1710
+ code: generatedCode,
1711
+ name: contractName,
1712
+ clientName,
1713
+ contractCategory,
1714
+ contractType,
1715
+ billingModel,
1716
+ originType: 'crm_proposal',
1717
+ originId: sourceEntityId,
1718
+ startDate: (_q = proposal.validFrom) !== null && _q !== void 0 ? _q : null,
1719
+ endDate: (_r = proposal.validUntil) !== null && _r !== void 0 ? _r : null,
1720
+ description: this.normalizeOptionalText(proposal.notes),
1721
+ financialTerms,
1722
+ }, payload, String(payload.locale || '').trim() || 'en', Number(payload.approvedByUserId) || undefined),
1723
+ metadata: {
1724
+ producer: 'operations',
1725
+ correlationId: payload.correlationId || `proposal:${sourceEntityId}`,
1726
+ sourceModule: 'operations',
1727
+ sourceEntity: 'contract',
1728
+ sourceId: String(contractId),
1729
+ originType: 'crm_proposal',
1730
+ originId: sourceEntityId,
1731
+ },
1732
+ }, {
1733
+ persistenceClient: tx,
1734
+ });
1735
+ return {
1736
+ id: contractId,
1737
+ code: generatedCode,
1738
+ };
1739
+ });
1740
+ }
1741
+ buildContractCreatedEventPayload(contract, payload, locale = 'en', createdByUserId) {
1742
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
1743
+ return {
1744
+ contractId: contract.id,
1745
+ proposalId: (_a = payload.proposalId) !== null && _a !== void 0 ? _a : null,
1746
+ proposalRevisionId: (_b = payload.proposalRevisionId) !== null && _b !== void 0 ? _b : null,
1747
+ personId: (_e = (_c = payload.personId) !== null && _c !== void 0 ? _c : (_d = payload.person) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : null,
1748
+ createdByUserId: createdByUserId !== null && createdByUserId !== void 0 ? createdByUserId : null,
1749
+ createdAt: new Date().toISOString(),
1750
+ locale,
1751
+ correlationId: payload.correlationId || `proposal:${(_g = (_f = payload.proposalId) !== null && _f !== void 0 ? _f : contract.originId) !== null && _g !== void 0 ? _g : contract.id}`,
1752
+ sourceModule: 'operations',
1753
+ sourceEntity: 'contract',
1754
+ sourceId: String(contract.id),
1755
+ source_module: 'operations',
1756
+ source_entity: 'contract',
1757
+ source_id: String(contract.id),
1758
+ contract: {
1759
+ id: contract.id,
1760
+ code: (_h = contract.code) !== null && _h !== void 0 ? _h : null,
1761
+ name: (_j = contract.name) !== null && _j !== void 0 ? _j : null,
1762
+ clientName: (_k = contract.clientName) !== null && _k !== void 0 ? _k : null,
1763
+ status: 'draft',
1764
+ contractCategory: (_l = contract.contractCategory) !== null && _l !== void 0 ? _l : 'client',
1765
+ contractType: (_m = contract.contractType) !== null && _m !== void 0 ? _m : 'service_agreement',
1766
+ billingModel: (_o = contract.billingModel) !== null && _o !== void 0 ? _o : 'fixed_price',
1767
+ originType: (_p = contract.originType) !== null && _p !== void 0 ? _p : 'manual',
1768
+ originId: (_q = contract.originId) !== null && _q !== void 0 ? _q : null,
1769
+ startDate: (_r = contract.startDate) !== null && _r !== void 0 ? _r : null,
1770
+ endDate: (_s = contract.endDate) !== null && _s !== void 0 ? _s : null,
1771
+ description: (_t = contract.description) !== null && _t !== void 0 ? _t : null,
1772
+ financialTerms: (_u = contract.financialTerms) !== null && _u !== void 0 ? _u : [],
1773
+ },
1774
+ proposal: (_v = payload.proposal) !== null && _v !== void 0 ? _v : {
1775
+ code: (_w = payload.code) !== null && _w !== void 0 ? _w : null,
1776
+ title: (_x = payload.title) !== null && _x !== void 0 ? _x : null,
1777
+ totalAmount: (_y = payload.total) !== null && _y !== void 0 ? _y : null,
1778
+ notes: (_0 = (_z = payload.commercialTerms) === null || _z === void 0 ? void 0 : _z.notes) !== null && _0 !== void 0 ? _0 : null,
1779
+ },
1780
+ person: (_1 = payload.person) !== null && _1 !== void 0 ? _1 : null,
1781
+ };
1782
+ }
1783
+ async buildContractActivatedEventPayload(contract, locale = 'en', activatedByUserId) {
1784
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11;
1785
+ let personId = null;
1786
+ let proposalId = null;
1787
+ let personRecord = null;
1788
+ if (contract.originType === 'crm_proposal' && contract.originId) {
1789
+ const parsedProposalId = Number(contract.originId);
1790
+ if (Number.isInteger(parsedProposalId) && parsedProposalId > 0) {
1791
+ proposalId = parsedProposalId;
1792
+ const proposal = await this.prisma.proposal.findUnique({
1793
+ where: { id: parsedProposalId },
1794
+ include: {
1795
+ person: {
1796
+ select: {
1797
+ id: true,
1798
+ name: true,
1799
+ },
1800
+ },
1801
+ },
1802
+ });
1803
+ if (proposal) {
1804
+ personId = (_c = (_a = proposal.person_id) !== null && _a !== void 0 ? _a : (_b = proposal.person) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null;
1805
+ const [companyRow, contactRows, documentRows] = personId != null
1806
+ ? await Promise.all([
1807
+ this.prisma.person_company.findUnique({
1808
+ where: { id: personId },
1809
+ select: { trade_name: true },
1810
+ }),
1811
+ this.prisma.contact.findMany({
1812
+ where: { person_id: personId },
1813
+ include: {
1814
+ contact_type: {
1815
+ select: { code: true },
1816
+ },
1817
+ },
1818
+ orderBy: [{ is_primary: 'desc' }, { id: 'asc' }],
1819
+ }),
1820
+ this.prisma.document.findMany({
1821
+ where: { person_id: personId },
1822
+ select: { value: true },
1823
+ orderBy: [{ id: 'asc' }],
1824
+ }),
1825
+ ])
1826
+ : [null, [], []];
1827
+ const resolveContactValue = (codes) => {
1828
+ var _a, _b;
1829
+ const normalizedCodes = new Set(codes.map((code) => code.toUpperCase()));
1830
+ const items = Array.isArray(contactRows)
1831
+ ? contactRows.filter((contact) => {
1832
+ var _a;
1833
+ return normalizedCodes.has(String(((_a = contact === null || contact === void 0 ? void 0 : contact.contact_type) === null || _a === void 0 ? void 0 : _a.code) || '').toUpperCase());
1834
+ })
1835
+ : [];
1836
+ const primary = items.find((contact) => contact === null || contact === void 0 ? void 0 : contact.is_primary);
1837
+ const fallback = items[0];
1838
+ return (_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;
1839
+ };
1840
+ personRecord = proposal.person
1841
+ ? Object.assign(Object.assign({}, proposal.person), { trade_name: (_d = companyRow === null || companyRow === void 0 ? void 0 : companyRow.trade_name) !== null && _d !== void 0 ? _d : null, email: resolveContactValue(['EMAIL']), phone: resolveContactValue(['PHONE', 'MOBILE', 'WHATSAPP']), document: (_f = (_e = documentRows[0]) === null || _e === void 0 ? void 0 : _e.value) !== null && _f !== void 0 ? _f : null }) : null;
1842
+ }
1843
+ }
1844
+ }
1845
+ const financialTerms = ((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : []).map((term) => {
1846
+ var _a, _b, _c, _d, _e;
1847
+ return ({
1848
+ label: term.label,
1849
+ termType: (_a = term.termType) !== null && _a !== void 0 ? _a : 'value',
1850
+ amount: Number((_b = term.amount) !== null && _b !== void 0 ? _b : 0),
1851
+ recurrence: (_c = term.recurrence) !== null && _c !== void 0 ? _c : 'one_time',
1852
+ dueDay: (_d = term.dueDay) !== null && _d !== void 0 ? _d : null,
1853
+ notes: (_e = term.notes) !== null && _e !== void 0 ? _e : null,
1854
+ });
1855
+ });
1856
+ const totalAmount = financialTerms.reduce((sum, term) => sum + (Number.isFinite(term.amount) ? term.amount : 0), 0) || Number((_h = contract.budgetAmount) !== null && _h !== void 0 ? _h : 0);
1857
+ return {
1858
+ contractId: contract.id,
1859
+ proposalId,
1860
+ personId,
1861
+ activatedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
1862
+ signedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
1863
+ activatedAt: new Date().toISOString(),
1864
+ signedAt: (_l = (_k = (_j = contract.signedAt) !== null && _j !== void 0 ? _j : contract.effectiveDate) !== null && _k !== void 0 ? _k : contract.startDate) !== null && _l !== void 0 ? _l : null,
1865
+ locale,
1866
+ correlationId: proposalId ? `proposal:${proposalId}` : `contract:${contract.id}`,
1867
+ sourceModule: 'operations',
1868
+ sourceEntity: 'contract',
1869
+ sourceId: String(contract.id),
1870
+ source_module: 'operations',
1871
+ source_entity: 'contract',
1872
+ source_id: String(contract.id),
1873
+ contract: {
1874
+ code: (_m = contract.code) !== null && _m !== void 0 ? _m : null,
1875
+ name: (_o = contract.name) !== null && _o !== void 0 ? _o : null,
1876
+ clientName: (_p = contract.clientName) !== null && _p !== void 0 ? _p : null,
1877
+ contractCategory: (_q = contract.contractCategory) !== null && _q !== void 0 ? _q : 'client',
1878
+ contractType: (_r = contract.contractType) !== null && _r !== void 0 ? _r : 'service_agreement',
1879
+ billingModel: (_s = contract.billingModel) !== null && _s !== void 0 ? _s : 'fixed_price',
1880
+ originType: (_t = contract.originType) !== null && _t !== void 0 ? _t : 'manual',
1881
+ originId: (_u = contract.originId) !== null && _u !== void 0 ? _u : null,
1882
+ startDate: (_v = contract.startDate) !== null && _v !== void 0 ? _v : null,
1883
+ endDate: (_w = contract.endDate) !== null && _w !== void 0 ? _w : null,
1884
+ signedAt: (_x = contract.signedAt) !== null && _x !== void 0 ? _x : null,
1885
+ effectiveDate: (_y = contract.effectiveDate) !== null && _y !== void 0 ? _y : null,
1886
+ budgetAmount: (_z = contract.budgetAmount) !== null && _z !== void 0 ? _z : null,
1887
+ description: (_0 = contract.description) !== null && _0 !== void 0 ? _0 : null,
1888
+ financialTerms,
1889
+ parties: (_1 = contract.parties) !== null && _1 !== void 0 ? _1 : [],
1890
+ },
1891
+ person: personRecord
1892
+ ? {
1893
+ id: (_2 = personRecord.id) !== null && _2 !== void 0 ? _2 : null,
1894
+ name: (_3 = personRecord.name) !== null && _3 !== void 0 ? _3 : null,
1895
+ tradeName: (_4 = personRecord.trade_name) !== null && _4 !== void 0 ? _4 : null,
1896
+ email: (_5 = personRecord.email) !== null && _5 !== void 0 ? _5 : null,
1897
+ phone: (_6 = personRecord.phone) !== null && _6 !== void 0 ? _6 : null,
1898
+ document: (_7 = personRecord.document) !== null && _7 !== void 0 ? _7 : null,
1899
+ }
1900
+ : null,
1901
+ receivable: {
1902
+ personId,
1903
+ documentNumber: (_8 = contract.code) !== null && _8 !== void 0 ? _8 : `CONTRACT-${contract.id}`,
1904
+ totalAmount,
1905
+ description: (_11 = (_10 = (_9 = contract.description) !== null && _9 !== void 0 ? _9 : contract.name) !== null && _10 !== void 0 ? _10 : contract.code) !== null && _11 !== void 0 ? _11 : null,
1906
+ },
1907
+ };
1908
+ }
832
1909
  async updateContract(userId, contractId, data) {
1910
+ var _a, _b;
833
1911
  const actor = await this.getActorContext(userId);
834
1912
  this.ensureDirector(actor);
835
- await this.getContractById(userId, contractId);
1913
+ const current = await this.getContractById(userId, contractId);
1914
+ const nextStatus = this.normalizeEnumValue(data.status, CONTRACT_STATUS_VALUES, (_a = current.status) !== null && _a !== void 0 ? _a : 'draft');
1915
+ const nextSignatureStatus = this.normalizeEnumValue(data.signatureStatus, SIGNATURE_STATUS_VALUES, (_b = current.signatureStatus) !== null && _b !== void 0 ? _b : 'not_started');
1916
+ const shouldPublishSigned = current.signatureStatus !== 'signed' && nextSignatureStatus === 'signed';
1917
+ const shouldPublishActivation = current.status !== 'active' && nextStatus === 'active';
836
1918
  const updates = [];
837
1919
  const params = [];
838
1920
  this.pushUpdate(updates, params, 'code', data.code);
839
1921
  this.pushUpdate(updates, params, 'name', data.name);
840
- this.pushUpdate(updates, params, 'contract_category', data.contractCategory);
841
- this.pushUpdate(updates, params, 'contract_type', data.contractType);
1922
+ this.pushUpdate(updates, params, 'contract_category', data.contractCategory, 'operations_contract_contract_category_70d553ea09_enum');
1923
+ this.pushUpdate(updates, params, 'contract_type', data.contractType, 'operations_contract_contract_type_48331e2ebf_enum');
842
1924
  this.pushUpdate(updates, params, 'client_name', data.clientName);
843
- this.pushUpdate(updates, params, 'signature_status', data.signatureStatus);
1925
+ this.pushUpdate(updates, params, 'signature_status', data.signatureStatus, 'operations_contract_signature_status_2cb7282a7b_enum');
844
1926
  this.pushUpdate(updates, params, 'is_active', data.isActive);
845
- this.pushUpdate(updates, params, 'billing_model', data.billingModel);
1927
+ this.pushUpdate(updates, params, 'billing_model', data.billingModel, 'operations_contract_billing_model_409dc7fea2_enum');
846
1928
  this.pushUpdate(updates, params, 'account_manager_collaborator_id', data.accountManagerCollaboratorId);
847
1929
  this.pushUpdate(updates, params, 'related_collaborator_id', data.relatedCollaboratorId);
848
- this.pushUpdate(updates, params, 'origin_type', data.originType);
1930
+ this.pushUpdate(updates, params, 'contract_template_id', data.contractTemplateId);
1931
+ this.pushUpdate(updates, params, 'origin_type', data.originType, 'operations_contract_origin_type_07a7cc2b5d_enum');
849
1932
  this.pushUpdate(updates, params, 'origin_id', data.originId);
850
- this.pushUpdate(updates, params, 'start_date', data.startDate);
851
- this.pushUpdate(updates, params, 'end_date', data.endDate);
852
- this.pushUpdate(updates, params, 'signed_at', data.signedAt);
853
- this.pushUpdate(updates, params, 'effective_date', data.effectiveDate);
1933
+ this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
1934
+ this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
1935
+ this.pushUpdate(updates, params, 'signed_at', data.signedAt, 'date');
1936
+ this.pushUpdate(updates, params, 'effective_date', data.effectiveDate, 'date');
854
1937
  this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
855
1938
  this.pushUpdate(updates, params, 'monthly_hour_cap', data.monthlyHourCap);
856
- this.pushUpdate(updates, params, 'status', data.status);
1939
+ this.pushUpdate(updates, params, 'status', data.status, 'operations_contract_status_a0395962df_enum');
1940
+ this.pushUpdate(updates, params, 'creation_mode', data.creationMode, 'operations_contract_creation_mode_98ba669209_enum');
1941
+ this.pushUpdate(updates, params, 'wizard_step', data.wizardStep);
857
1942
  this.pushUpdate(updates, params, 'description', data.description);
858
1943
  this.pushUpdate(updates, params, 'content_html', data.contentHtml);
859
1944
  await this.prisma.$transaction(async (tx) => {
1945
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
860
1946
  if (updates.length) {
861
1947
  params.push(contractId);
862
1948
  await tx.$executeRawUnsafe(`UPDATE operations_contract
@@ -877,12 +1963,110 @@ let OperationsService = class OperationsService {
877
1963
  await this.replaceContractRevisions(tx, contractId, data.revisions);
878
1964
  }
879
1965
  if (data.replaceUploadedPdfDocument) {
880
- await this.replaceContractPdfDocument(tx, contractId, data.replaceUploadedPdfDocument);
1966
+ await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
881
1967
  }
882
1968
  await this.insertContractHistory(tx, contractId, userId, 'updated', 'Contract registry data updated.');
1969
+ if (shouldPublishSigned || shouldPublishActivation) {
1970
+ const contractEventPayload = await this.buildContractActivatedEventPayload({
1971
+ id: current.id,
1972
+ code: (_a = data.code) !== null && _a !== void 0 ? _a : current.code,
1973
+ name: (_b = data.name) !== null && _b !== void 0 ? _b : current.name,
1974
+ clientName: (_c = data.clientName) !== null && _c !== void 0 ? _c : current.clientName,
1975
+ contractCategory: (_d = data.contractCategory) !== null && _d !== void 0 ? _d : current.contractCategory,
1976
+ contractType: (_e = data.contractType) !== null && _e !== void 0 ? _e : current.contractType,
1977
+ billingModel: (_f = data.billingModel) !== null && _f !== void 0 ? _f : current.billingModel,
1978
+ originType: (_g = data.originType) !== null && _g !== void 0 ? _g : current.originType,
1979
+ originId: (_h = data.originId) !== null && _h !== void 0 ? _h : current.originId,
1980
+ startDate: (_j = data.startDate) !== null && _j !== void 0 ? _j : current.startDate,
1981
+ endDate: (_k = data.endDate) !== null && _k !== void 0 ? _k : current.endDate,
1982
+ signedAt: (_l = data.signedAt) !== null && _l !== void 0 ? _l : current.signedAt,
1983
+ effectiveDate: (_m = data.effectiveDate) !== null && _m !== void 0 ? _m : current.effectiveDate,
1984
+ budgetAmount: (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : current.budgetAmount,
1985
+ description: (_p = data.description) !== null && _p !== void 0 ? _p : current.description,
1986
+ financialTerms: (_q = data.financialTerms) !== null && _q !== void 0 ? _q : current.financialTerms,
1987
+ parties: (_r = data.parties) !== null && _r !== void 0 ? _r : current.parties,
1988
+ }, 'en', userId);
1989
+ if (shouldPublishSigned) {
1990
+ await this.integrationApi.publishEvent({
1991
+ eventName: 'operations.contract.signed',
1992
+ sourceModule: 'operations',
1993
+ aggregateType: 'contract',
1994
+ aggregateId: String(contractId),
1995
+ payload: Object.assign(Object.assign({}, contractEventPayload), { signedByUserId: userId !== null && userId !== void 0 ? userId : null, signedAt: (_u = (_t = (_s = data.signedAt) !== null && _s !== void 0 ? _s : current.signedAt) !== null && _t !== void 0 ? _t : contractEventPayload.signedAt) !== null && _u !== void 0 ? _u : new Date().toISOString() }),
1996
+ metadata: {
1997
+ producer: 'operations',
1998
+ correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
1999
+ sourceModule: 'operations',
2000
+ sourceEntity: 'contract',
2001
+ sourceId: String(contractId),
2002
+ originType: (_v = data.originType) !== null && _v !== void 0 ? _v : current.originType,
2003
+ originId: (_w = data.originId) !== null && _w !== void 0 ? _w : current.originId,
2004
+ lifecycle: 'signed',
2005
+ },
2006
+ }, {
2007
+ persistenceClient: tx,
2008
+ });
2009
+ }
2010
+ if (shouldPublishActivation) {
2011
+ await this.integrationApi.publishEvent({
2012
+ eventName: 'operations.contract.activated',
2013
+ sourceModule: 'operations',
2014
+ aggregateType: 'contract',
2015
+ aggregateId: String(contractId),
2016
+ payload: contractEventPayload,
2017
+ metadata: {
2018
+ producer: 'operations',
2019
+ correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
2020
+ sourceModule: 'operations',
2021
+ sourceEntity: 'contract',
2022
+ sourceId: String(contractId),
2023
+ originType: (_x = data.originType) !== null && _x !== void 0 ? _x : current.originType,
2024
+ originId: (_y = data.originId) !== null && _y !== void 0 ? _y : current.originId,
2025
+ lifecycle: 'activated',
2026
+ },
2027
+ }, {
2028
+ persistenceClient: tx,
2029
+ });
2030
+ }
2031
+ }
883
2032
  });
884
2033
  return this.getContractById(userId, contractId);
885
2034
  }
2035
+ async removeContract(userId, contractId) {
2036
+ const actor = await this.getActorContext(userId);
2037
+ this.ensureDirector(actor);
2038
+ const current = await this.getContractById(userId, contractId);
2039
+ await this.prisma.$transaction(async (tx) => {
2040
+ var _a, _b;
2041
+ await tx.$executeRawUnsafe(`UPDATE operations_project
2042
+ SET contract_id = NULL,
2043
+ updated_at = NOW()
2044
+ WHERE contract_id = $1
2045
+ AND deleted_at IS NULL`, contractId);
2046
+ for (const tableName of [
2047
+ 'operations_contract_party',
2048
+ 'operations_contract_signature',
2049
+ 'operations_contract_financial_term',
2050
+ 'operations_contract_document',
2051
+ 'operations_contract_revision',
2052
+ ]) {
2053
+ await tx.$executeRawUnsafe(`UPDATE ${tableName}
2054
+ SET deleted_at = NOW(),
2055
+ updated_at = NOW()
2056
+ WHERE contract_id = $1
2057
+ AND deleted_at IS NULL`, contractId);
2058
+ }
2059
+ await this.insertContractHistory(tx, contractId, userId, 'deleted', `Contract "${(_b = (_a = current.name) !== null && _a !== void 0 ? _a : current.code) !== null && _b !== void 0 ? _b : `#${contractId}`}" removed from registry.`);
2060
+ await tx.$executeRawUnsafe(`UPDATE operations_contract
2061
+ SET deleted_at = NOW(),
2062
+ is_active = false,
2063
+ status = 'archived',
2064
+ updated_at = NOW()
2065
+ WHERE id = $1
2066
+ AND deleted_at IS NULL`, contractId);
2067
+ });
2068
+ return { success: true };
2069
+ }
886
2070
  async listTimesheets(userId) {
887
2071
  const actor = await this.getActorContext(userId);
888
2072
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 't.collaborator_id', actor.isDirector);
@@ -958,7 +2142,7 @@ let OperationsService = class OperationsService {
958
2142
  status,
959
2143
  created_at,
960
2144
  updated_at
961
- ) VALUES ($1, $2, $3, $4, $5, 'draft', NOW(), NOW())
2145
+ ) VALUES ($1, $2, $3::date, $4::date, $5, 'draft', NOW(), NOW())
962
2146
  RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, data.weekStartDate, data.weekEndDate, (_b = data.notes) !== null && _b !== void 0 ? _b : null));
963
2147
  const timesheetId = (_c = row[0]) === null || _c === void 0 ? void 0 : _c.id;
964
2148
  await this.replaceTimesheetEntries(tx, timesheetId, (_d = data.entries) !== null && _d !== void 0 ? _d : [], collaboratorId);
@@ -979,8 +2163,8 @@ let OperationsService = class OperationsService {
979
2163
  await this.prisma.$transaction(async (tx) => {
980
2164
  const updates = [];
981
2165
  const params = [];
982
- this.pushUpdate(updates, params, 'week_start_date', data.weekStartDate);
983
- this.pushUpdate(updates, params, 'week_end_date', data.weekEndDate);
2166
+ this.pushUpdate(updates, params, 'week_start_date', data.weekStartDate, 'date');
2167
+ this.pushUpdate(updates, params, 'week_end_date', data.weekEndDate, 'date');
984
2168
  this.pushUpdate(updates, params, 'notes', data.notes);
985
2169
  if (updates.length) {
986
2170
  params.push(timesheetId);
@@ -1079,7 +2263,17 @@ let OperationsService = class OperationsService {
1079
2263
  submitted_at,
1080
2264
  created_at,
1081
2265
  updated_at
1082
- ) VALUES ($1, $2, COALESCE($3, 'vacation'), $4, $5, $6, 'submitted', $7, NOW(), NOW(), NOW())
2266
+ ) VALUES (
2267
+ $1,
2268
+ $2,
2269
+ $3::operations_time_off_request_request_type_fdcf258f8e_enum,
2270
+ $4::date,
2271
+ $5::date,
2272
+ $6,
2273
+ 'submitted',
2274
+ $7,
2275
+ NOW(), NOW(), NOW()
2276
+ )
1083
2277
  RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, (_b = data.requestType) !== null && _b !== void 0 ? _b : 'vacation', data.startDate, data.endDate, (_c = data.totalDays) !== null && _c !== void 0 ? _c : null, (_d = data.reason) !== null && _d !== void 0 ? _d : null));
1084
2278
  const requestId = (_e = row[0]) === null || _e === void 0 ? void 0 : _e.id;
1085
2279
  await this.upsertApproval(tx, {
@@ -1188,7 +2382,14 @@ let OperationsService = class OperationsService {
1188
2382
  created_at,
1189
2383
  updated_at
1190
2384
  ) VALUES (
1191
- $1, $2, COALESCE($3, 'temporary'), $4, $5, 'submitted', $6, NOW(), NOW(), NOW()
2385
+ $1,
2386
+ $2,
2387
+ $3::operations_schedule_adjustment_request_request__d3f1202fa6_enum,
2388
+ $4::date,
2389
+ $5::date,
2390
+ 'submitted',
2391
+ $6,
2392
+ NOW(), NOW(), NOW()
1192
2393
  )
1193
2394
  RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, (_b = data.requestScope) !== null && _b !== void 0 ? _b : 'temporary', data.effectiveStartDate, (_c = data.effectiveEndDate) !== null && _c !== void 0 ? _c : null, (_d = data.reason) !== null && _d !== void 0 ? _d : null));
1194
2395
  const requestId = (_e = row[0]) === null || _e === void 0 ? void 0 : _e.id;
@@ -1202,7 +2403,11 @@ let OperationsService = class OperationsService {
1202
2403
  break_minutes,
1203
2404
  created_at,
1204
2405
  updated_at
1205
- ) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`, requestId, day.weekday, (_f = day.isWorkingDay) !== null && _f !== void 0 ? _f : true, (_g = day.startTime) !== null && _g !== void 0 ? _g : null, (_h = day.endTime) !== null && _h !== void 0 ? _h : null, (_j = day.breakMinutes) !== null && _j !== void 0 ? _j : null);
2406
+ ) VALUES (
2407
+ $1,
2408
+ $2::operations_schedule_adjustment_day_weekday_ba6542bf86_enum,
2409
+ $3, $4::time, $5::time, $6, NOW(), NOW()
2410
+ )`, requestId, day.weekday, (_f = day.isWorkingDay) !== null && _f !== void 0 ? _f : true, (_g = day.startTime) !== null && _g !== void 0 ? _g : null, (_h = day.endTime) !== null && _h !== void 0 ? _h : null, (_j = day.breakMinutes) !== null && _j !== void 0 ? _j : null);
1206
2411
  }
1207
2412
  await this.upsertApproval(tx, {
1208
2413
  targetType: 'schedule_adjustment_request',
@@ -1371,14 +2576,14 @@ let OperationsService = class OperationsService {
1371
2576
  await this.prisma.$transaction(async (tx) => {
1372
2577
  var _a, _b;
1373
2578
  await tx.$executeRawUnsafe(`UPDATE operations_approval
1374
- SET status = $1,
2579
+ SET status = $1::operations_approval_status_747aa313d9_enum,
1375
2580
  decided_at = NOW(),
1376
2581
  decision_note = $2,
1377
2582
  updated_at = NOW()
1378
2583
  WHERE id = $3`, nextStatus, (_a = data.note) !== null && _a !== void 0 ? _a : null, approvalId);
1379
2584
  if (approval.targetType === 'timesheet') {
1380
2585
  await tx.$executeRawUnsafe(`UPDATE operations_timesheet
1381
- SET status = $1,
2586
+ SET status = $1::operations_timesheet_status_128a8ecd30_enum,
1382
2587
  reviewed_at = NOW(),
1383
2588
  approver_collaborator_id = $2,
1384
2589
  updated_at = NOW()
@@ -1386,7 +2591,7 @@ let OperationsService = class OperationsService {
1386
2591
  }
1387
2592
  if (approval.targetType === 'time_off_request') {
1388
2593
  await tx.$executeRawUnsafe(`UPDATE operations_time_off_request
1389
- SET status = $1,
2594
+ SET status = $1::operations_time_off_request_status_c0b28799ee_enum,
1390
2595
  reviewed_at = NOW(),
1391
2596
  approver_collaborator_id = $2,
1392
2597
  submitted_at = COALESCE(submitted_at, NOW()),
@@ -1395,7 +2600,7 @@ let OperationsService = class OperationsService {
1395
2600
  }
1396
2601
  if (approval.targetType === 'schedule_adjustment_request') {
1397
2602
  await tx.$executeRawUnsafe(`UPDATE operations_schedule_adjustment_request
1398
- SET status = $1,
2603
+ SET status = $1::operations_schedule_adjustment_request_status_159e10f34e_enum,
1399
2604
  reviewed_at = NOW(),
1400
2605
  approver_collaborator_id = $2,
1401
2606
  submitted_at = COALESCE(submitted_at, NOW()),
@@ -1460,10 +2665,12 @@ let OperationsService = class OperationsService {
1460
2665
  }
1461
2666
  async getCollaboratorByUserId(userId) {
1462
2667
  return this.querySingle(`SELECT c.id,
1463
- c.display_name AS "displayName",
2668
+ COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
1464
2669
  s.id AS "supervisorId",
1465
2670
  s.display_name AS "supervisorName"
1466
2671
  FROM operations_collaborator c
2672
+ LEFT JOIN person person_record
2673
+ ON person_record.id = c.person_id
1467
2674
  LEFT JOIN operations_collaborator s
1468
2675
  ON s.id = c.supervisor_collaborator_id
1469
2676
  WHERE c.user_id = $1
@@ -1471,10 +2678,12 @@ let OperationsService = class OperationsService {
1471
2678
  }
1472
2679
  async getCollaboratorById(collaboratorId) {
1473
2680
  const collaborator = await this.querySingle(`SELECT c.id,
1474
- c.display_name AS "displayName",
2681
+ COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
1475
2682
  s.id AS "supervisorId",
1476
2683
  s.display_name AS "supervisorName"
1477
2684
  FROM operations_collaborator c
2685
+ LEFT JOIN person person_record
2686
+ ON person_record.id = c.person_id
1478
2687
  LEFT JOIN operations_collaborator s
1479
2688
  ON s.id = c.supervisor_collaborator_id
1480
2689
  WHERE c.id = $1
@@ -1484,6 +2693,186 @@ let OperationsService = class OperationsService {
1484
2693
  }
1485
2694
  return collaborator;
1486
2695
  }
2696
+ async getPersonById(personId) {
2697
+ const person = await this.querySingle(`SELECT id, name, type
2698
+ FROM person
2699
+ WHERE id = $1`, [personId]);
2700
+ if (!person) {
2701
+ throw new common_1.NotFoundException('Person not found.');
2702
+ }
2703
+ return person;
2704
+ }
2705
+ async getDepartmentById(client, departmentId, includeInactive = false) {
2706
+ var _a;
2707
+ const departments = (await client.$queryRawUnsafe(`SELECT id,
2708
+ slug,
2709
+ code,
2710
+ name,
2711
+ description,
2712
+ created_at AS "createdAt",
2713
+ updated_at AS "updatedAt",
2714
+ deleted_at AS "deletedAt",
2715
+ CASE WHEN deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status
2716
+ FROM operations_department
2717
+ WHERE id = $1
2718
+ AND ($2::boolean OR deleted_at IS NULL)`, departmentId, includeInactive));
2719
+ const department = (_a = departments[0]) !== null && _a !== void 0 ? _a : null;
2720
+ if (!department) {
2721
+ throw new common_1.NotFoundException('Department not found.');
2722
+ }
2723
+ return department;
2724
+ }
2725
+ async assertDepartmentNameAvailable(client, name, excludeDepartmentId) {
2726
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2727
+ FROM operations_department
2728
+ WHERE LOWER(name) = LOWER($1)
2729
+ AND ($2::int IS NULL OR id <> $2)
2730
+ LIMIT 1`, name, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
2731
+ if (existing[0]) {
2732
+ throw new common_1.BadRequestException('A department with this name already exists.');
2733
+ }
2734
+ }
2735
+ async assertDepartmentCodeAvailable(client, code, excludeDepartmentId) {
2736
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2737
+ FROM operations_department
2738
+ WHERE UPPER(COALESCE(code, '')) = UPPER($1)
2739
+ AND ($2::int IS NULL OR id <> $2)
2740
+ LIMIT 1`, code, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
2741
+ if (existing[0]) {
2742
+ throw new common_1.BadRequestException('A department with this code already exists.');
2743
+ }
2744
+ }
2745
+ async resolveDepartmentReference(client, input) {
2746
+ var _a;
2747
+ if (input.departmentName !== undefined) {
2748
+ const normalizedDepartmentName = this.normalizeOptionalText(input.departmentName);
2749
+ if (!normalizedDepartmentName) {
2750
+ return null;
2751
+ }
2752
+ const existingByName = (await client.$queryRawUnsafe(`SELECT id, name
2753
+ FROM operations_department
2754
+ WHERE deleted_at IS NULL
2755
+ AND LOWER(name) = LOWER($1)
2756
+ ORDER BY id ASC
2757
+ LIMIT 1`, normalizedDepartmentName));
2758
+ if (existingByName[0]) {
2759
+ return existingByName[0];
2760
+ }
2761
+ const slug = await this.generateUniqueDepartmentSlug(client, normalizedDepartmentName);
2762
+ const created = (await client.$queryRawUnsafe(`INSERT INTO operations_department (
2763
+ slug,
2764
+ code,
2765
+ name,
2766
+ description,
2767
+ created_at,
2768
+ updated_at
2769
+ ) VALUES ($1, NULL, $2, NULL, NOW(), NOW())
2770
+ RETURNING id, name`, slug, normalizedDepartmentName));
2771
+ return (_a = created[0]) !== null && _a !== void 0 ? _a : null;
2772
+ }
2773
+ if (typeof input.departmentId === 'number' &&
2774
+ Number.isInteger(input.departmentId) &&
2775
+ input.departmentId > 0) {
2776
+ return this.getDepartmentById(client, input.departmentId);
2777
+ }
2778
+ return null;
2779
+ }
2780
+ async generateUniqueDepartmentSlug(client, label, excludeDepartmentId) {
2781
+ const baseSlug = this.slugifyValue(label) || `department-${Date.now().toString(36)}`;
2782
+ for (let attempt = 0; attempt < 25; attempt += 1) {
2783
+ const candidate = attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`;
2784
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2785
+ FROM operations_department
2786
+ WHERE slug = $1
2787
+ AND ($2::int IS NULL OR id <> $2)
2788
+ LIMIT 1`, candidate, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
2789
+ if (!existing.length) {
2790
+ return candidate;
2791
+ }
2792
+ }
2793
+ return `${baseSlug}-${Date.now().toString(36)}`;
2794
+ }
2795
+ async getContractTemplateRecord(client, templateId, includeInactive = false) {
2796
+ const template = (await client.$queryRawUnsafe(`SELECT t.id,
2797
+ t.slug,
2798
+ t.code,
2799
+ t.name,
2800
+ t.description,
2801
+ t.contract_category AS "contractCategory",
2802
+ t.contract_type AS "contractType",
2803
+ t.billing_model AS "billingModel",
2804
+ t.signature_status AS "signatureStatus",
2805
+ t.is_active AS "isActive",
2806
+ t.status,
2807
+ t.content_html AS "contentHtml",
2808
+ COUNT(DISTINCT c.id)::int AS "usageCount",
2809
+ t.created_at AS "createdAt",
2810
+ t.updated_at AS "updatedAt"
2811
+ FROM operations_contract_template t
2812
+ LEFT JOIN operations_contract c
2813
+ ON c.contract_template_id = t.id
2814
+ AND c.deleted_at IS NULL
2815
+ WHERE t.id = $1
2816
+ AND ($2::boolean = true OR t.deleted_at IS NULL)
2817
+ GROUP BY t.id
2818
+ LIMIT 1`, templateId, includeInactive));
2819
+ const record = template[0];
2820
+ if (!record) {
2821
+ throw new common_1.NotFoundException('Contract template not found.');
2822
+ }
2823
+ return record;
2824
+ }
2825
+ async assertContractTemplateNameAvailable(client, name, excludeTemplateId) {
2826
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2827
+ FROM operations_contract_template
2828
+ WHERE LOWER(name) = LOWER($1)
2829
+ AND deleted_at IS NULL
2830
+ AND ($2::int IS NULL OR id <> $2)
2831
+ LIMIT 1`, name, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
2832
+ if (existing[0]) {
2833
+ throw new common_1.BadRequestException('A contract template with this name already exists.');
2834
+ }
2835
+ }
2836
+ async assertContractTemplateCodeAvailable(client, code, excludeTemplateId) {
2837
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2838
+ FROM operations_contract_template
2839
+ WHERE UPPER(COALESCE(code, '')) = UPPER($1)
2840
+ AND deleted_at IS NULL
2841
+ AND ($2::int IS NULL OR id <> $2)
2842
+ LIMIT 1`, code, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
2843
+ if (existing[0]) {
2844
+ throw new common_1.BadRequestException('A contract template with this code already exists.');
2845
+ }
2846
+ }
2847
+ async generateUniqueContractTemplateSlug(client, label, excludeTemplateId) {
2848
+ const baseSlug = this.slugifyValue(label) || `contract-template-${Date.now().toString(36)}`;
2849
+ for (let attempt = 0; attempt < 25; attempt += 1) {
2850
+ const candidate = attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`;
2851
+ const existing = (await client.$queryRawUnsafe(`SELECT id
2852
+ FROM operations_contract_template
2853
+ WHERE slug = $1
2854
+ AND ($2::int IS NULL OR id <> $2)
2855
+ LIMIT 1`, candidate, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
2856
+ if (!existing.length) {
2857
+ return candidate;
2858
+ }
2859
+ }
2860
+ return `${baseSlug}-${Date.now().toString(36)}`;
2861
+ }
2862
+ slugifyValue(value) {
2863
+ return (value !== null && value !== void 0 ? value : '')
2864
+ .normalize('NFKD')
2865
+ .replace(/[\u0300-\u036f]/g, '')
2866
+ .toLowerCase()
2867
+ .trim()
2868
+ .replace(/[^a-z0-9]+/g, '-')
2869
+ .replace(/^-+|-+$/g, '')
2870
+ .slice(0, 80);
2871
+ }
2872
+ normalizeOptionalText(value) {
2873
+ const normalized = String(value !== null && value !== void 0 ? value : '').trim();
2874
+ return normalized || null;
2875
+ }
1487
2876
  async getProjectDetails(projectId, actorCollaboratorId) {
1488
2877
  var _a, _b, _c, _d, _e, _f, _g;
1489
2878
  const project = await this.querySingle(`SELECT p.id,
@@ -1589,10 +2978,14 @@ let OperationsService = class OperationsService {
1589
2978
  var _a, _b, _c, _d, _e, _f;
1590
2979
  const collaborator = await this.querySingle(`SELECT c.id,
1591
2980
  c.user_id AS "userId",
2981
+ c.person_id AS "personId",
2982
+ person_record.name AS "personName",
2983
+ person_record.avatar_id AS "personAvatarId",
1592
2984
  c.code,
1593
2985
  c.collaborator_type AS "collaboratorType",
1594
- c.display_name AS "displayName",
1595
- c.department,
2986
+ COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
2987
+ c.department_id AS "departmentId",
2988
+ COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
1596
2989
  c.title,
1597
2990
  c.level_label AS "levelLabel",
1598
2991
  c.weekly_capacity_hours AS "weeklyCapacityHours",
@@ -1606,6 +2999,11 @@ let OperationsService = class OperationsService {
1606
2999
  hiring_contract.status AS "contractStatus",
1607
3000
  COUNT(DISTINCT pa.id)::int AS "activeAssignments"
1608
3001
  FROM operations_collaborator c
3002
+ LEFT JOIN person person_record
3003
+ ON person_record.id = c.person_id
3004
+ LEFT JOIN operations_department department_record
3005
+ ON department_record.id = c.department_id
3006
+ AND department_record.deleted_at IS NULL
1609
3007
  LEFT JOIN operations_collaborator s
1610
3008
  ON s.id = c.supervisor_collaborator_id
1611
3009
  LEFT JOIN operations_project_assignment pa
@@ -1623,7 +3021,7 @@ let OperationsService = class OperationsService {
1623
3021
  ) hiring_contract ON TRUE
1624
3022
  WHERE c.id = $1
1625
3023
  AND c.deleted_at IS NULL
1626
- GROUP BY c.id, s.id, hiring_contract.id, hiring_contract.status`, [collaboratorId]);
3024
+ GROUP BY c.id, person_record.id, department_record.id, s.id, hiring_contract.id, hiring_contract.status`, [collaboratorId]);
1627
3025
  if (!collaborator) {
1628
3026
  throw new common_1.NotFoundException('Collaborator not found.');
1629
3027
  }
@@ -1770,7 +3168,7 @@ let OperationsService = class OperationsService {
1770
3168
  description,
1771
3169
  created_at,
1772
3170
  updated_at
1773
- ) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`, timesheetId, (_a = entry.projectAssignmentId) !== null && _a !== void 0 ? _a : null, (_b = entry.activityLabel) !== null && _b !== void 0 ? _b : null, entry.workDate, entry.hours, (_c = entry.description) !== null && _c !== void 0 ? _c : null);
3171
+ ) VALUES ($1, $2, $3, $4::date, $5, $6, NOW(), NOW())`, timesheetId, (_a = entry.projectAssignmentId) !== null && _a !== void 0 ? _a : null, (_b = entry.activityLabel) !== null && _b !== void 0 ? _b : null, entry.workDate, entry.hours, (_c = entry.description) !== null && _c !== void 0 ? _c : null);
1774
3172
  }
1775
3173
  }
1776
3174
  async refreshTimesheetTotal(client, timesheetId) {
@@ -1788,7 +3186,7 @@ let OperationsService = class OperationsService {
1788
3186
  var _a, _b;
1789
3187
  const existing = (await client.$queryRawUnsafe(`SELECT id
1790
3188
  FROM operations_approval
1791
- WHERE target_type = $1
3189
+ WHERE target_type = $1::operations_approval_target_type_32d3f04385_enum
1792
3190
  AND target_id = $2
1793
3191
  AND deleted_at IS NULL`, input.targetType, input.targetId));
1794
3192
  let approvalId = (_a = existing[0]) === null || _a === void 0 ? void 0 : _a.id;
@@ -1802,7 +3200,10 @@ let OperationsService = class OperationsService {
1802
3200
  submitted_at,
1803
3201
  created_at,
1804
3202
  updated_at
1805
- ) VALUES ($1, $2, $3, $4, 'pending', NOW(), NOW(), NOW())
3203
+ ) VALUES (
3204
+ $1::operations_approval_target_type_32d3f04385_enum,
3205
+ $2, $3, $4, 'pending', NOW(), NOW(), NOW()
3206
+ )
1806
3207
  RETURNING id`, input.targetType, input.targetId, input.requesterCollaboratorId, input.approverCollaboratorId));
1807
3208
  approvalId = (_b = created[0]) === null || _b === void 0 ? void 0 : _b.id;
1808
3209
  await this.insertApprovalHistory(client, approvalId, input.requesterCollaboratorId, 'created', null);
@@ -1828,7 +3229,13 @@ let OperationsService = class OperationsService {
1828
3229
  action,
1829
3230
  note,
1830
3231
  created_at
1831
- ) VALUES ($1, $2, $3, $4, NOW())`, approvalId, actorCollaboratorId, action, note);
3232
+ ) VALUES (
3233
+ $1,
3234
+ $2,
3235
+ $3::operations_approval_history_action_c8478a05d6_enum,
3236
+ $4,
3237
+ NOW()
3238
+ )`, approvalId, actorCollaboratorId, action, note);
1832
3239
  }
1833
3240
  async assertProjectAccess(actor, projectId) {
1834
3241
  if (actor.isDirector)
@@ -1941,7 +3348,7 @@ let OperationsService = class OperationsService {
1941
3348
  break_minutes,
1942
3349
  created_at,
1943
3350
  updated_at
1944
- ) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`, collaboratorId, day.weekday, (_a = day.isWorkingDay) !== null && _a !== void 0 ? _a : true, day.isWorkingDay === false ? null : (_b = day.startTime) !== null && _b !== void 0 ? _b : null, day.isWorkingDay === false ? null : (_c = day.endTime) !== null && _c !== void 0 ? _c : null, (_d = day.breakMinutes) !== null && _d !== void 0 ? _d : null);
3351
+ ) VALUES ($1, $2::operations_collaborator_schedule_day_weekday_5d79528f6c_enum, $3, $4::time, $5::time, $6, NOW(), NOW())`, collaboratorId, day.weekday, (_a = day.isWorkingDay) !== null && _a !== void 0 ? _a : true, day.isWorkingDay === false ? null : (_b = day.startTime) !== null && _b !== void 0 ? _b : null, day.isWorkingDay === false ? null : (_c = day.endTime) !== null && _c !== void 0 ? _c : null, (_d = day.breakMinutes) !== null && _d !== void 0 ? _d : null);
1945
3352
  }
1946
3353
  }
1947
3354
  async replaceProjectAssignments(client, projectId, teamAssignments) {
@@ -1965,7 +3372,9 @@ let OperationsService = class OperationsService {
1965
3372
  created_at,
1966
3373
  updated_at
1967
3374
  ) VALUES (
1968
- $1, $2, $3, $4, $5, $6, $7, $8, COALESCE($9, 'active'), NOW(), NOW()
3375
+ $1, $2, $3, $4, $5, $6, $7::date, $8::date,
3376
+ $9::operations_project_assignment_status_155b459bbf_enum,
3377
+ NOW(), NOW()
1969
3378
  )`, projectId, assignment.collaboratorId, (_a = assignment.roleLabel) !== null && _a !== void 0 ? _a : 'Team Member', (_b = assignment.allocationPercent) !== null && _b !== void 0 ? _b : null, (_c = assignment.weeklyHours) !== null && _c !== void 0 ? _c : null, (_d = assignment.isBillable) !== null && _d !== void 0 ? _d : true, (_e = assignment.startDate) !== null && _e !== void 0 ? _e : null, (_f = assignment.endDate) !== null && _f !== void 0 ? _f : null, (_g = assignment.status) !== null && _g !== void 0 ? _g : 'active');
1970
3379
  }
1971
3380
  }
@@ -2032,14 +3441,59 @@ let OperationsService = class OperationsService {
2032
3441
  created_at,
2033
3442
  updated_at
2034
3443
  ) VALUES (
2035
- $1, $2, $3, $4, $5, 'not_started', true, $6, $7, $8, 'employee_hiring', $9, $10, NULL,
2036
- NULL, $10, $11, $12, 'draft', $13, NULL, $14, $14, NOW(), NOW()
3444
+ $1, $2,
3445
+ $3::operations_contract_contract_category_70d553ea09_enum,
3446
+ $4::operations_contract_contract_type_48331e2ebf_enum,
3447
+ $5, 'not_started', true,
3448
+ $6::operations_contract_billing_model_409dc7fea2_enum,
3449
+ $7, $8, 'employee_hiring', $9, $10::date, NULL,
3450
+ NULL, $10::date, $11, $12, 'draft', $13, NULL, $14, $14, NOW(), NOW()
2037
3451
  )`, contractCode, contractName, this.mapContractCategoryForCollaboratorType(input.collaboratorType), this.mapContractTypeForCollaboratorType(input.collaboratorType), input.displayName, this.mapBillingModelForCollaboratorType(input.collaboratorType), input.supervisorCollaboratorId, input.collaboratorId, input.collaboratorId, (_a = input.startDate) !== null && _a !== void 0 ? _a : new Date().toISOString().slice(0, 10), (_b = input.compensationAmount) !== null && _b !== void 0 ? _b : null, input.weeklyCapacityHours
2038
3452
  ? Math.round(Number(input.weeklyCapacityHours) * 4)
2039
3453
  : null, (_c = input.description) !== null && _c !== void 0 ? _c : null, createdByUserId);
2040
3454
  }
2041
3455
  async createProjectContractDraft(client, createdByUserId, input) {
2042
- var _a, _b, _c, _d, _e, _f, _g, _h;
3456
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
3457
+ const templateRows = input.contractTemplateId
3458
+ ? (await client.$queryRawUnsafe(`SELECT id,
3459
+ code,
3460
+ name,
3461
+ description,
3462
+ contract_category AS "contractCategory",
3463
+ contract_type AS "contractType",
3464
+ billing_model AS "billingModel",
3465
+ signature_status AS "signatureStatus",
3466
+ content_html AS "contentHtml"
3467
+ FROM operations_contract_template
3468
+ WHERE id = $1
3469
+ AND deleted_at IS NULL
3470
+ LIMIT 1`, input.contractTemplateId))
3471
+ : [];
3472
+ const selectedTemplate = (_a = templateRows[0]) !== null && _a !== void 0 ? _a : null;
3473
+ const templateContext = {
3474
+ project_code: input.projectCode,
3475
+ project_name: input.projectName,
3476
+ client_name: input.clientName,
3477
+ start_date: (_b = input.startDate) !== null && _b !== void 0 ? _b : '',
3478
+ end_date: (_c = input.endDate) !== null && _c !== void 0 ? _c : '',
3479
+ budget_amount: input.budgetAmount !== null && input.budgetAmount !== undefined
3480
+ ? String(input.budgetAmount)
3481
+ : '',
3482
+ monthly_hour_cap: input.monthlyHourCap !== null && input.monthlyHourCap !== undefined
3483
+ ? String(input.monthlyHourCap)
3484
+ : '',
3485
+ };
3486
+ const applyTemplateVariables = (value) => {
3487
+ const source = value !== null && value !== void 0 ? value : '';
3488
+ return Object.entries(templateContext).reduce((result, [key, replacement]) => result.split(`{{${key}}}`).join(replacement || ''), source);
3489
+ };
3490
+ const templateCodePrefix = this.normalizeOptionalText(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.code);
3491
+ const generatedContractCode = ((_e = (_d = this.normalizeOptionalText(input.contractCode)) !== null && _d !== void 0 ? _d : (templateCodePrefix
3492
+ ? `${templateCodePrefix}-${input.projectCode}`
3493
+ : null)) !== null && _e !== void 0 ? _e : `PRJ-${input.projectCode}`).slice(0, 40);
3494
+ const generatedContractName = (_g = (_f = this.normalizeOptionalText(input.contractName)) !== null && _f !== void 0 ? _f : this.normalizeOptionalText(applyTemplateVariables(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.name))) !== null && _g !== void 0 ? _g : `${input.projectName} Service Agreement`;
3495
+ const generatedDescription = this.normalizeOptionalText(applyTemplateVariables((_h = input.description) !== null && _h !== void 0 ? _h : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.description));
3496
+ const generatedContentHtml = this.normalizeOptionalText(applyTemplateVariables(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contentHtml));
2043
3497
  const created = await client.$queryRawUnsafe(`INSERT INTO operations_contract (
2044
3498
  code,
2045
3499
  name,
@@ -2051,6 +3505,7 @@ let OperationsService = class OperationsService {
2051
3505
  billing_model,
2052
3506
  account_manager_collaborator_id,
2053
3507
  related_collaborator_id,
3508
+ contract_template_id,
2054
3509
  origin_type,
2055
3510
  origin_id,
2056
3511
  start_date,
@@ -2067,11 +3522,35 @@ let OperationsService = class OperationsService {
2067
3522
  created_at,
2068
3523
  updated_at
2069
3524
  ) VALUES (
2070
- $1, $2, 'client', 'service_agreement', $3, 'not_started', true, $4, $5, NULL, 'client_project', $6,
2071
- $7, $8, NULL, $7, $9, $10, 'draft', $11, NULL, $12, $12, NOW(), NOW()
3525
+ $1,
3526
+ $2,
3527
+ $3::operations_contract_contract_category_70d553ea09_enum,
3528
+ $4::operations_contract_contract_type_48331e2ebf_enum,
3529
+ $5,
3530
+ $6::operations_contract_signature_status_2cb7282a7b_enum,
3531
+ true,
3532
+ $7::operations_contract_billing_model_409dc7fea2_enum,
3533
+ $8,
3534
+ NULL,
3535
+ $9,
3536
+ 'client_project',
3537
+ $10,
3538
+ $11::date,
3539
+ $12::date,
3540
+ NULL,
3541
+ $11::date,
3542
+ $13,
3543
+ $14,
3544
+ 'draft',
3545
+ $15,
3546
+ $16,
3547
+ $17,
3548
+ $17,
3549
+ NOW(),
3550
+ NOW()
2072
3551
  )
2073
- RETURNING id`, (_a = input.contractCode) !== null && _a !== void 0 ? _a : `PRJ-${input.projectCode}`, (_b = input.contractName) !== null && _b !== void 0 ? _b : `${input.projectName} Service Agreement`, input.clientName, input.billingModel, input.managerCollaboratorId, input.projectId, (_c = input.startDate) !== null && _c !== void 0 ? _c : new Date().toISOString().slice(0, 10), (_d = input.endDate) !== null && _d !== void 0 ? _d : null, (_e = input.budgetAmount) !== null && _e !== void 0 ? _e : null, (_f = input.monthlyHourCap) !== null && _f !== void 0 ? _f : null, (_g = input.description) !== null && _g !== void 0 ? _g : null, createdByUserId);
2074
- return (_h = created[0]) === null || _h === void 0 ? void 0 : _h.id;
3552
+ RETURNING id`, generatedContractCode, generatedContractName, (_j = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractCategory) !== null && _j !== void 0 ? _j : 'client', (_k = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractType) !== null && _k !== void 0 ? _k : 'service_agreement', input.clientName, (_l = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.signatureStatus) !== null && _l !== void 0 ? _l : 'not_started', (_m = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.billingModel) !== null && _m !== void 0 ? _m : input.billingModel, input.managerCollaboratorId, (_o = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _o !== void 0 ? _o : null, input.projectId, (_p = input.startDate) !== null && _p !== void 0 ? _p : new Date().toISOString().slice(0, 10), (_q = input.endDate) !== null && _q !== void 0 ? _q : null, (_r = input.budgetAmount) !== null && _r !== void 0 ? _r : null, (_s = input.monthlyHourCap) !== null && _s !== void 0 ? _s : null, generatedDescription, generatedContentHtml, createdByUserId);
3553
+ return (_t = created[0]) === null || _t === void 0 ? void 0 : _t.id;
2075
3554
  }
2076
3555
  async replaceContractParties(client, contractId, parties) {
2077
3556
  var _a, _b, _c, _d, _e, _f;
@@ -2093,7 +3572,10 @@ let OperationsService = class OperationsService {
2093
3572
  created_at,
2094
3573
  updated_at
2095
3574
  ) VALUES (
2096
- $1, COALESCE($2, 'other'), COALESCE($3, 'company'), $4, $5, $6, $7, COALESCE($8, false), NOW(), NOW()
3575
+ $1,
3576
+ $2::operations_contract_party_party_role_b66e8515fc_enum,
3577
+ $3::operations_contract_party_party_type_1383bb371c_enum,
3578
+ $4, $5, $6, $7, $8, NOW(), NOW()
2097
3579
  )`, contractId, (_a = party.partyRole) !== null && _a !== void 0 ? _a : 'other', (_b = party.partyType) !== null && _b !== void 0 ? _b : 'company', party.displayName, (_c = party.documentNumber) !== null && _c !== void 0 ? _c : null, (_d = party.email) !== null && _d !== void 0 ? _d : null, (_e = party.phone) !== null && _e !== void 0 ? _e : null, (_f = party.isPrimary) !== null && _f !== void 0 ? _f : false);
2098
3580
  }
2099
3581
  }
@@ -2115,7 +3597,9 @@ let OperationsService = class OperationsService {
2115
3597
  created_at,
2116
3598
  updated_at
2117
3599
  ) VALUES (
2118
- $1, $2, $3, $4, COALESCE($5, 'pending'), $6, NOW(), NOW()
3600
+ $1, $2, $3, $4,
3601
+ $5::operations_contract_signature_signer_status_1e6fbe2519_enum,
3602
+ $6::timestamp, NOW(), NOW()
2119
3603
  )`, contractId, signature.signerName, (_a = signature.signerRole) !== null && _a !== void 0 ? _a : null, (_b = signature.signerEmail) !== null && _b !== void 0 ? _b : null, (_c = signature.status) !== null && _c !== void 0 ? _c : 'pending', (_d = signature.signedAt) !== null && _d !== void 0 ? _d : null);
2120
3604
  }
2121
3605
  }
@@ -2138,7 +3622,14 @@ let OperationsService = class OperationsService {
2138
3622
  created_at,
2139
3623
  updated_at
2140
3624
  ) VALUES (
2141
- $1, COALESCE($2, 'value'), $3, $4, COALESCE($5, 'one_time'), $6, $7, NOW(), NOW()
3625
+ $1,
3626
+ $2::operations_contract_financial_term_term_type_700635c06a_enum,
3627
+ $3,
3628
+ $4,
3629
+ $5::operations_contract_financial_term_recurrence_ba90bbe3bf_enum,
3630
+ $6,
3631
+ $7,
3632
+ NOW(), NOW()
2142
3633
  )`, contractId, (_a = term.termType) !== null && _a !== void 0 ? _a : 'value', term.label, term.amount, (_b = term.recurrence) !== null && _b !== void 0 ? _b : 'one_time', (_c = term.dueDay) !== null && _c !== void 0 ? _c : null, (_d = term.notes) !== null && _d !== void 0 ? _d : null);
2143
3634
  }
2144
3635
  }
@@ -2160,31 +3651,346 @@ let OperationsService = class OperationsService {
2160
3651
  created_at,
2161
3652
  updated_at
2162
3653
  ) VALUES (
2163
- $1, COALESCE($2, 'revision'), $3, $4, COALESCE($5, 'draft'), $6, NOW(), NOW()
3654
+ $1,
3655
+ $2::operations_contract_revision_revision_type_cf5ba1a538_enum,
3656
+ $3,
3657
+ $4::date,
3658
+ $5::operations_contract_revision_status_f44f35bb66_enum,
3659
+ $6,
3660
+ NOW(), NOW()
2164
3661
  )`, contractId, (_a = revision.revisionType) !== null && _a !== void 0 ? _a : 'revision', revision.title, (_b = revision.effectiveDate) !== null && _b !== void 0 ? _b : null, (_c = revision.status) !== null && _c !== void 0 ? _c : 'draft', (_d = revision.summary) !== null && _d !== void 0 ? _d : null);
2165
3662
  }
2166
3663
  }
2167
- async replaceContractPdfDocument(client, contractId, document) {
2168
- var _a;
3664
+ async replaceContractDocument(client, contractId, documentType, document) {
3665
+ var _a, _b, _c, _d, _e;
2169
3666
  await client.$executeRawUnsafe(`UPDATE operations_contract_document
2170
3667
  SET is_current = false,
2171
3668
  updated_at = NOW()
2172
3669
  WHERE contract_id = $1
2173
3670
  AND deleted_at IS NULL
2174
- AND document_type IN ('uploaded_pdf', 'generated_pdf')`, contractId);
3671
+ AND document_type = $2`, contractId, documentType);
2175
3672
  await client.$executeRawUnsafe(`INSERT INTO operations_contract_document (
2176
3673
  contract_id,
2177
3674
  document_type,
3675
+ file_id,
2178
3676
  file_name,
2179
3677
  mime_type,
2180
3678
  file_content_base64,
2181
3679
  is_current,
3680
+ extraction_status,
3681
+ extraction_summary,
2182
3682
  notes,
2183
3683
  created_at,
2184
3684
  updated_at
2185
3685
  ) VALUES (
2186
- $1, 'uploaded_pdf', $2, $3, $4, true, $5, NOW(), NOW()
2187
- )`, contractId, document.fileName, document.mimeType, document.fileContentBase64, (_a = document.notes) !== null && _a !== void 0 ? _a : null);
3686
+ $1, $2, $3, $4, $5, $6, true, $7, $8, $9, NOW(), NOW()
3687
+ )`, contractId, documentType, (_a = document.fileId) !== null && _a !== void 0 ? _a : null, document.fileName, document.mimeType, (_b = document.fileContentBase64) !== null && _b !== void 0 ? _b : null, (_c = document.extractionStatus) !== null && _c !== void 0 ? _c : 'skipped', (_d = document.extractionSummary) !== null && _d !== void 0 ? _d : null, (_e = document.notes) !== null && _e !== void 0 ? _e : null);
3688
+ }
3689
+ async resolveContractExtractionFile(userId, data) {
3690
+ if (data.contractId && data.contractId > 0) {
3691
+ const contract = await this.getContractById(userId, data.contractId);
3692
+ const sourceDocument = contract.documents.find((document) => document.isCurrent && document.documentType === 'source_upload');
3693
+ if (!sourceDocument) {
3694
+ throw new common_1.BadRequestException('No source document is attached to this contract draft.');
3695
+ }
3696
+ if (sourceDocument.fileId) {
3697
+ const { buffer } = await this.fileService.getBuffer(sourceDocument.fileId);
3698
+ return this.buildContractExtractionFile(sourceDocument.fileName, sourceDocument.mimeType, buffer);
3699
+ }
3700
+ const base64 = this.normalizeExtractionString(sourceDocument.fileContentBase64);
3701
+ if (!base64) {
3702
+ throw new common_1.BadRequestException('The current source document does not contain readable file content.');
3703
+ }
3704
+ return this.buildContractExtractionFile(sourceDocument.fileName, sourceDocument.mimeType, Buffer.from(base64, 'base64'));
3705
+ }
3706
+ const fileName = this.normalizeExtractionString(data.fileName);
3707
+ const fileContentBase64 = this.normalizeExtractionString(data.fileContentBase64);
3708
+ const mimeType = this.normalizeContractExtractionMimeType(data.mimeType, fileName);
3709
+ if (!fileName || !fileContentBase64) {
3710
+ throw new common_1.BadRequestException('fileName and fileContentBase64 are required.');
3711
+ }
3712
+ return this.buildContractExtractionFile(fileName, mimeType, Buffer.from(fileContentBase64, 'base64'));
3713
+ }
3714
+ buildContractExtractionFile(fileName, mimeType, buffer) {
3715
+ if (!this.isAllowedContractExtractionMimeType(mimeType)) {
3716
+ throw new common_1.BadRequestException('Only PDF, DOC, and DOCX files are supported for contract extraction.');
3717
+ }
3718
+ if (!buffer.length) {
3719
+ throw new common_1.BadRequestException('Unable to decode the uploaded contract document.');
3720
+ }
3721
+ if (buffer.length > 12 * 1024 * 1024) {
3722
+ throw new common_1.BadRequestException('The uploaded contract document must be smaller than 12 MB.');
3723
+ }
3724
+ return {
3725
+ fieldname: 'file',
3726
+ originalname: fileName,
3727
+ encoding: 'base64',
3728
+ mimetype: mimeType,
3729
+ size: buffer.length,
3730
+ destination: '',
3731
+ filename: fileName,
3732
+ path: '',
3733
+ buffer,
3734
+ };
3735
+ }
3736
+ async renderContractPdfBuffer(contract) {
3737
+ var _a, _b, _c;
3738
+ const html = await this.buildContractPdfHtml(contract);
3739
+ let browser = null;
3740
+ try {
3741
+ const importPlaywright = new Function('moduleName', 'return import(moduleName);');
3742
+ const playwright = await importPlaywright('playwright');
3743
+ browser = await playwright.chromium.launch({ headless: true });
3744
+ const page = await browser.newPage();
3745
+ await page.setContent(html, { waitUntil: 'networkidle' });
3746
+ return Buffer.from(await page.pdf({
3747
+ format: 'A4',
3748
+ printBackground: true,
3749
+ margin: {
3750
+ top: '72px',
3751
+ right: '18px',
3752
+ bottom: '56px',
3753
+ left: '18px',
3754
+ },
3755
+ }));
3756
+ }
3757
+ catch (error) {
3758
+ const errorMessage = error instanceof Error ? error.message : String(error);
3759
+ const errorStack = error instanceof Error ? ((_a = error.stack) !== null && _a !== void 0 ? _a : error.message) : String(error);
3760
+ 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);
3761
+ this.logger.error(`Failed to generate contract PDF for contract ${(_b = contract === null || contract === void 0 ? void 0 : contract.id) !== null && _b !== void 0 ? _b : 'unknown'}. ${errorMessage}`, errorStack);
3762
+ throw new common_1.InternalServerErrorException(missingPlaywrightRuntime
3763
+ ? 'PDF generation is unavailable because Playwright/Chromium is not installed on the server. Run `pnpm --filter api run playwright:install` in the API environment.'
3764
+ : 'Failed to generate the PDF document. Check server logs for details.');
3765
+ }
3766
+ finally {
3767
+ await ((_c = browser === null || browser === void 0 ? void 0 : browser.close) === null || _c === void 0 ? void 0 : _c.call(browser));
3768
+ }
3769
+ }
3770
+ async buildContractPdfHtml(contract) {
3771
+ var _a, _b, _c, _d, _e;
3772
+ const logoUrl = await this.resolveContractPdfLogoUrl();
3773
+ const contentHtml = (_c = (_a = this.normalizeOptionalText(contract.contentHtml)) !== null && _a !== void 0 ? _a : (_b = this.normalizeOptionalText(contract.description)) === null || _b === void 0 ? void 0 : _b.split(/\n{2,}/).map((paragraph) => `<p>${this.escapeHtml(paragraph)}</p>`).join('')) !== null && _c !== void 0 ? _c : '<p>No contract body was provided yet.</p>';
3774
+ const partiesHtml = ((_d = contract.parties) !== null && _d !== void 0 ? _d : []).length
3775
+ ? contract.parties
3776
+ .map((party) => `
3777
+ <li>
3778
+ <strong>${this.escapeHtml(party.displayName || 'Party')}</strong>
3779
+ <span>${this.escapeHtml([party.partyRole, party.partyType].filter(Boolean).join(' • '))}</span>
3780
+ </li>`)
3781
+ .join('')
3782
+ : '<li><strong>No parties registered yet.</strong><span>Complete this contract later if needed.</span></li>';
3783
+ const financialTermsHtml = ((_e = contract.financialTerms) !== null && _e !== void 0 ? _e : []).length
3784
+ ? contract.financialTerms
3785
+ .map((term) => `
3786
+ <li>
3787
+ <strong>${this.escapeHtml(term.label || 'Term')}</strong>
3788
+ <span>${this.escapeHtml([term.termType, term.amount, term.recurrence]
3789
+ .filter((item) => item !== null && item !== undefined && String(item).trim())
3790
+ .join(' • '))}</span>
3791
+ </li>`)
3792
+ .join('')
3793
+ : '<li><strong>No financial terms registered yet.</strong><span>The draft is intentionally lightweight.</span></li>';
3794
+ return `<!DOCTYPE html>
3795
+ <html lang="en">
3796
+ <head>
3797
+ <meta charset="utf-8" />
3798
+ <style>
3799
+ @page {
3800
+ size: A4;
3801
+ margin: 72px 18px 56px 18px;
3802
+ }
3803
+ body {
3804
+ color: #0f172a;
3805
+ font-family: Arial, sans-serif;
3806
+ font-size: 12px;
3807
+ line-height: 1.5;
3808
+ margin: 0;
3809
+ }
3810
+ header {
3811
+ align-items: center;
3812
+ border-bottom: 2px solid #dbeafe;
3813
+ display: flex;
3814
+ gap: 16px;
3815
+ justify-content: space-between;
3816
+ margin-bottom: 24px;
3817
+ padding-bottom: 16px;
3818
+ }
3819
+ .brand {
3820
+ align-items: center;
3821
+ display: flex;
3822
+ gap: 12px;
3823
+ }
3824
+ .brand img {
3825
+ height: 42px;
3826
+ max-width: 140px;
3827
+ object-fit: contain;
3828
+ }
3829
+ .eyebrow {
3830
+ color: #2563eb;
3831
+ font-size: 10px;
3832
+ font-weight: 700;
3833
+ letter-spacing: 0.12em;
3834
+ text-transform: uppercase;
3835
+ }
3836
+ h1 {
3837
+ font-size: 22px;
3838
+ margin: 4px 0 0;
3839
+ }
3840
+ .meta-grid {
3841
+ display: grid;
3842
+ gap: 12px;
3843
+ grid-template-columns: repeat(2, minmax(0, 1fr));
3844
+ margin: 20px 0;
3845
+ }
3846
+ .meta-card {
3847
+ background: #f8fafc;
3848
+ border: 1px solid #dbeafe;
3849
+ border-radius: 12px;
3850
+ padding: 12px;
3851
+ }
3852
+ .meta-card strong {
3853
+ display: block;
3854
+ font-size: 10px;
3855
+ letter-spacing: 0.08em;
3856
+ margin-bottom: 4px;
3857
+ text-transform: uppercase;
3858
+ }
3859
+ section {
3860
+ margin-top: 20px;
3861
+ }
3862
+ h2 {
3863
+ color: #1d4ed8;
3864
+ font-size: 14px;
3865
+ margin: 0 0 10px;
3866
+ }
3867
+ ul {
3868
+ list-style: none;
3869
+ margin: 0;
3870
+ padding: 0;
3871
+ }
3872
+ li {
3873
+ border: 1px solid #e2e8f0;
3874
+ border-radius: 10px;
3875
+ margin-bottom: 8px;
3876
+ padding: 10px 12px;
3877
+ }
3878
+ li strong {
3879
+ display: block;
3880
+ margin-bottom: 2px;
3881
+ }
3882
+ footer {
3883
+ border-top: 1px solid #dbeafe;
3884
+ color: #475569;
3885
+ font-size: 10px;
3886
+ margin-top: 28px;
3887
+ padding-top: 12px;
3888
+ }
3889
+ .content {
3890
+ border: 1px solid #e2e8f0;
3891
+ border-radius: 14px;
3892
+ padding: 18px;
3893
+ }
3894
+ .content p:first-child {
3895
+ margin-top: 0;
3896
+ }
3897
+ </style>
3898
+ </head>
3899
+ <body>
3900
+ <header>
3901
+ <div class="brand">
3902
+ <img src="${logoUrl}" alt="System logo" />
3903
+ <div>
3904
+ <div class="eyebrow">Contract Document</div>
3905
+ <h1>${this.escapeHtml(contract.name || contract.code || 'Contract draft')}</h1>
3906
+ </div>
3907
+ </div>
3908
+ <div>
3909
+ <div><strong>Code:</strong> ${this.escapeHtml(contract.code || 'Draft')}</div>
3910
+ <div><strong>Status:</strong> ${this.escapeHtml(this.humanizeEnumLabel(contract.status || 'draft'))}</div>
3911
+ </div>
3912
+ </header>
3913
+
3914
+ <div class="meta-grid">
3915
+ <div class="meta-card">
3916
+ <strong>Client</strong>
3917
+ ${this.escapeHtml(contract.clientName || 'Not informed yet')}
3918
+ </div>
3919
+ <div class="meta-card">
3920
+ <strong>Type</strong>
3921
+ ${this.escapeHtml(this.humanizeEnumLabel(contract.contractType || 'service_agreement'))}
3922
+ </div>
3923
+ <div class="meta-card">
3924
+ <strong>Period</strong>
3925
+ ${this.escapeHtml([contract.startDate || 'Open start', contract.endDate || 'Open end'].join(' - '))}
3926
+ </div>
3927
+ <div class="meta-card">
3928
+ <strong>Billing</strong>
3929
+ ${this.escapeHtml(this.humanizeEnumLabel(contract.billingModel || 'time_and_material'))}
3930
+ </div>
3931
+ </div>
3932
+
3933
+ <section>
3934
+ <h2>Related Parties</h2>
3935
+ <ul>${partiesHtml}</ul>
3936
+ </section>
3937
+
3938
+ <section>
3939
+ <h2>Financial Terms</h2>
3940
+ <ul>${financialTermsHtml}</ul>
3941
+ </section>
3942
+
3943
+ <section>
3944
+ <h2>Contract Body</h2>
3945
+ <div class="content">${contentHtml}</div>
3946
+ </section>
3947
+
3948
+ <footer>
3949
+ Generated on ${this.escapeHtml(new Date().toISOString())} for ${this.escapeHtml(contract.code || 'draft')}.
3950
+ </footer>
3951
+ </body>
3952
+ </html>`;
3953
+ }
3954
+ async resolveContractPdfLogoUrl() {
3955
+ var _a;
3956
+ const settings = await this.settingService.getSettingValues([
3957
+ 'image-url',
3958
+ 'icon-url',
3959
+ ]);
3960
+ const configuredUrl = (_a = this.normalizeOptionalText(settings['image-url'])) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(settings['icon-url']);
3961
+ if (configuredUrl && /^https?:\/\//i.test(configuredUrl)) {
3962
+ return configuredUrl;
3963
+ }
3964
+ const inlineSvg = encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72">
3965
+ <rect width="240" height="72" rx="18" fill="#0f172a"/>
3966
+ <circle cx="38" cy="36" r="18" fill="#3b82f6"/>
3967
+ <text x="72" y="43" fill="#ffffff" font-size="26" font-family="Arial, sans-serif" font-weight="700">HedHog</text>
3968
+ </svg>`);
3969
+ return `data:image/svg+xml;charset=UTF-8,${inlineSvg}`;
3970
+ }
3971
+ buildContractPdfFileName(contract) {
3972
+ var _a, _b;
3973
+ const base = (_b = (_a = this.normalizeOptionalText(contract.code)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(contract.name)) !== null && _b !== void 0 ? _b : `contract-${contract.id}`;
3974
+ return `${base
3975
+ .toLowerCase()
3976
+ .replace(/[^a-z0-9]+/g, '-')
3977
+ .replace(/^-+|-+$/g, '')
3978
+ .slice(0, 80) || `contract-${contract.id}`}.pdf`;
3979
+ }
3980
+ humanizeEnumLabel(value) {
3981
+ return String(value !== null && value !== void 0 ? value : '')
3982
+ .split('_')
3983
+ .filter(Boolean)
3984
+ .map((part) => { var _a; return ((_a = part[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) + part.slice(1); })
3985
+ .join(' ');
3986
+ }
3987
+ escapeHtml(value) {
3988
+ return String(value !== null && value !== void 0 ? value : '')
3989
+ .replace(/&/g, '&amp;')
3990
+ .replace(/</g, '&lt;')
3991
+ .replace(/>/g, '&gt;')
3992
+ .replace(/"/g, '&quot;')
3993
+ .replace(/'/g, '&#39;');
2188
3994
  }
2189
3995
  async insertContractHistory(client, contractId, actorUserId, action, note, metadataJson) {
2190
3996
  await client.$executeRawUnsafe(`INSERT INTO operations_contract_history (
@@ -2198,6 +4004,555 @@ let OperationsService = class OperationsService {
2198
4004
  $1, $2, $3, $4, $5, NOW()
2199
4005
  )`, contractId, actorUserId, action, note, metadataJson !== null && metadataJson !== void 0 ? metadataJson : null);
2200
4006
  }
4007
+ async generateCollaboratorCode(client) {
4008
+ for (let attempt = 0; attempt < 5; attempt += 1) {
4009
+ const candidate = `COL-${Date.now().toString(36).toUpperCase()}${Math.random()
4010
+ .toString(36)
4011
+ .slice(2, 6)
4012
+ .toUpperCase()}`;
4013
+ const existing = (await client.$queryRawUnsafe(`SELECT code
4014
+ FROM operations_collaborator
4015
+ WHERE code = $1
4016
+ LIMIT 1`, candidate));
4017
+ if (!existing.length) {
4018
+ return candidate;
4019
+ }
4020
+ }
4021
+ throw new common_1.BadRequestException('Unable to generate collaborator code.');
4022
+ }
4023
+ async generateContractCode(client) {
4024
+ for (let attempt = 0; attempt < 5; attempt += 1) {
4025
+ const candidate = `CTR-${Date.now().toString(36).toUpperCase()}${Math.random()
4026
+ .toString(36)
4027
+ .slice(2, 6)
4028
+ .toUpperCase()}`.slice(0, 40);
4029
+ const existing = (await client.$queryRawUnsafe(`SELECT code
4030
+ FROM operations_contract
4031
+ WHERE code = $1
4032
+ LIMIT 1`, candidate));
4033
+ if (!existing.length) {
4034
+ return candidate;
4035
+ }
4036
+ }
4037
+ throw new common_1.BadRequestException('Unable to generate contract code.');
4038
+ }
4039
+ async generateContractContentHtml(contract, data = {}) {
4040
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
4041
+ const currentContent = this.normalizeOptionalText(contract.contentHtml);
4042
+ const primaryParty = (_d = (_b = ((_a = contract.parties) !== null && _a !== void 0 ? _a : []).find((party) => party.isPrimary)) !== null && _b !== void 0 ? _b : ((_c = contract.parties) !== null && _c !== void 0 ? _c : [])[0]) !== null && _d !== void 0 ? _d : null;
4043
+ const templateContext = {
4044
+ contract_code: (_e = this.normalizeOptionalText(contract.code)) !== null && _e !== void 0 ? _e : '',
4045
+ contract_name: (_f = this.normalizeOptionalText(contract.name)) !== null && _f !== void 0 ? _f : '',
4046
+ client_name: (_h = (_g = this.normalizeOptionalText(contract.clientName)) !== null && _g !== void 0 ? _g : this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.displayName)) !== null && _h !== void 0 ? _h : '',
4047
+ contract_category: (_j = this.normalizeOptionalText(contract.contractCategory)) !== null && _j !== void 0 ? _j : '',
4048
+ contract_type: (_k = this.normalizeOptionalText(contract.contractType)) !== null && _k !== void 0 ? _k : '',
4049
+ start_date: (_l = this.normalizeOptionalText(contract.startDate)) !== null && _l !== void 0 ? _l : '',
4050
+ end_date: (_m = this.normalizeOptionalText(contract.endDate)) !== null && _m !== void 0 ? _m : '',
4051
+ effective_date: (_o = this.normalizeOptionalText(contract.effectiveDate)) !== null && _o !== void 0 ? _o : '',
4052
+ signed_at: (_p = this.normalizeOptionalText(contract.signedAt)) !== null && _p !== void 0 ? _p : '',
4053
+ budget_amount: contract.budgetAmount !== null && contract.budgetAmount !== undefined
4054
+ ? String(contract.budgetAmount)
4055
+ : '',
4056
+ monthly_hour_cap: contract.monthlyHourCap !== null &&
4057
+ contract.monthlyHourCap !== undefined
4058
+ ? String(contract.monthlyHourCap)
4059
+ : '',
4060
+ primary_party_name: (_q = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.displayName)) !== null && _q !== void 0 ? _q : '',
4061
+ primary_party_document: (_r = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.documentNumber)) !== null && _r !== void 0 ? _r : '',
4062
+ primary_party_email: (_s = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.email)) !== null && _s !== void 0 ? _s : '',
4063
+ primary_party_phone: (_t = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.phone)) !== null && _t !== void 0 ? _t : '',
4064
+ description: (_u = this.normalizeOptionalText(contract.description)) !== null && _u !== void 0 ? _u : '',
4065
+ };
4066
+ const applyTemplateVariables = (value) => {
4067
+ const source = value !== null && value !== void 0 ? value : '';
4068
+ return Object.entries(templateContext).reduce((result, [key, replacement]) => result.split(`{{${key}}}`).join(replacement || ''), source);
4069
+ };
4070
+ const currentHtmlWithVariables = this.normalizeOptionalText(applyTemplateVariables(currentContent));
4071
+ if (currentHtmlWithVariables && data.overwrite !== true) {
4072
+ return currentHtmlWithVariables;
4073
+ }
4074
+ const baseHtml = currentHtmlWithVariables !== null && currentHtmlWithVariables !== void 0 ? currentHtmlWithVariables : this.buildFallbackContractContent(contract);
4075
+ const promptMessage = (_v = this.normalizeOptionalText(data.promptMessage)) !== null && _v !== void 0 ? _v : 'Generate an initial contract body using the registered metadata and keep the language concise, professional, and ready for legal review.';
4076
+ try {
4077
+ const aiResult = await this.aiService.chat({
4078
+ provider: data.provider === 'gemini' ? 'gemini' : 'openai',
4079
+ model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
4080
+ message: promptMessage,
4081
+ systemPrompt: [
4082
+ 'You draft professional business contract content for an operations back-office system.',
4083
+ 'Return ONLY clean HTML suitable for a rich text editor preview.',
4084
+ 'Do not include Markdown fences, commentary, or legal disclaimers inside the HTML.',
4085
+ 'Keep the output concise, well-structured, and ready for legal revision.',
4086
+ 'If a specific detail is missing, preserve placeholders like {{client_name}}, {{start_date}}, {{end_date}}, {{budget_amount}}, and {{monthly_hour_cap}} instead of inventing facts.',
4087
+ `Contract context:\n${JSON.stringify(templateContext, null, 2)}`,
4088
+ baseHtml
4089
+ ? `Current base HTML:\n${baseHtml}`
4090
+ : 'There is no current HTML yet.',
4091
+ ].join('\n\n'),
4092
+ });
4093
+ return ((_x = this.normalizeOptionalText(String((_w = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _w !== void 0 ? _w : ''))) !== null && _x !== void 0 ? _x : baseHtml);
4094
+ }
4095
+ catch (_y) {
4096
+ return baseHtml;
4097
+ }
4098
+ }
4099
+ buildFallbackContractContent(contract) {
4100
+ var _a, _b, _c, _d, _e, _f, _g;
4101
+ const title = this.escapeHtml((_b = (_a = this.normalizeOptionalText(contract.name)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(contract.code)) !== null && _b !== void 0 ? _b : 'Contract Draft');
4102
+ const description = this.escapeHtml((_c = this.normalizeOptionalText(contract.description)) !== null && _c !== void 0 ? _c : 'This draft summarizes the main commercial and operational conditions agreed between the parties.');
4103
+ const validityItems = [
4104
+ contract.startDate ? `Start date: ${contract.startDate}` : null,
4105
+ contract.endDate ? `End date: ${contract.endDate}` : null,
4106
+ contract.effectiveDate ? `Effective date: ${contract.effectiveDate}` : null,
4107
+ contract.signedAt ? `Signed at: ${contract.signedAt}` : null,
4108
+ ]
4109
+ .filter(Boolean)
4110
+ .map((item) => `<li>${this.escapeHtml(String(item))}</li>`)
4111
+ .join('');
4112
+ const partiesHtml = ((_d = contract.parties) !== null && _d !== void 0 ? _d : []).length
4113
+ ? ((_e = contract.parties) !== null && _e !== void 0 ? _e : [])
4114
+ .map((party) => `
4115
+ <li>
4116
+ <strong>${this.escapeHtml(party.displayName || 'Party')}</strong>
4117
+ <span>${this.escapeHtml([party.partyRole, party.partyType, party.documentNumber]
4118
+ .filter(Boolean)
4119
+ .join(' • '))}</span>
4120
+ </li>`)
4121
+ .join('')
4122
+ : '<li><strong>Main party</strong><span>{{client_name}}</span></li>';
4123
+ const financialTermsHtml = ((_f = contract.financialTerms) !== null && _f !== void 0 ? _f : []).length
4124
+ ? ((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : [])
4125
+ .map((term) => `
4126
+ <li>
4127
+ <strong>${this.escapeHtml(term.label || 'Financial term')}</strong>
4128
+ <span>${this.escapeHtml([term.termType, term.amount, term.recurrence]
4129
+ .filter((item) => item !== null &&
4130
+ item !== undefined &&
4131
+ String(item).trim())
4132
+ .join(' • '))}</span>
4133
+ </li>`)
4134
+ .join('')
4135
+ : '<li><strong>Commercial conditions</strong><span>Define prices, billing cadence, and penalties before signature.</span></li>';
4136
+ return `
4137
+ <h1>${title}</h1>
4138
+ <p>${description}</p>
4139
+ <h2>1. Parties</h2>
4140
+ <ul>${partiesHtml}</ul>
4141
+ <h2>2. Scope and object</h2>
4142
+ <p>The parties agree to execute the services and obligations described in this contract, according to the agreed operational, technical, and commercial conditions.</p>
4143
+ <h2>3. Validity</h2>
4144
+ ${validityItems ? `<ul>${validityItems}</ul>` : '<p>Define start date, term, renewal, and termination conditions.</p>'}
4145
+ <h2>4. Financial terms</h2>
4146
+ <ul>${financialTermsHtml}</ul>
4147
+ <h2>5. General obligations</h2>
4148
+ <p>Review confidentiality, data protection, service levels, termination, and liability clauses before formal approval.</p>
4149
+ `;
4150
+ }
4151
+ async buildContractLegalReview(contract, data = {}) {
4152
+ var _a, _b, _c;
4153
+ const heuristicReview = this.buildHeuristicContractLegalReview(contract);
4154
+ const promptMessage = (_a = this.normalizeOptionalText(data.promptMessage)) !== null && _a !== void 0 ? _a : 'Review this contract draft and return an advisory legal checklist with practical warnings and missing items.';
4155
+ try {
4156
+ const aiResult = await this.aiService.chat({
4157
+ provider: data.provider === 'gemini' ? 'gemini' : 'openai',
4158
+ model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
4159
+ message: promptMessage,
4160
+ systemPrompt: [
4161
+ 'You are an advisory legal-review assistant for business contracts.',
4162
+ 'Return ONLY valid JSON. Do not include Markdown fences or commentary.',
4163
+ 'This review is non-blocking and should help a human reviewer spot likely gaps.',
4164
+ 'Expected JSON shape:',
4165
+ JSON.stringify({
4166
+ summary: '',
4167
+ missingFields: [],
4168
+ warnings: [],
4169
+ checklist: [],
4170
+ status: 'attention_required',
4171
+ }, null, 2),
4172
+ `Contract metadata:\n${JSON.stringify({
4173
+ code: contract.code,
4174
+ name: contract.name,
4175
+ clientName: contract.clientName,
4176
+ contractCategory: contract.contractCategory,
4177
+ contractType: contract.contractType,
4178
+ startDate: contract.startDate,
4179
+ endDate: contract.endDate,
4180
+ effectiveDate: contract.effectiveDate,
4181
+ signatureStatus: contract.signatureStatus,
4182
+ parties: contract.parties,
4183
+ financialTerms: contract.financialTerms,
4184
+ }, null, 2)}`,
4185
+ `Contract HTML:\n${(_b = this.normalizeOptionalText(contract.contentHtml)) !== null && _b !== void 0 ? _b : ''}`,
4186
+ ].join('\n\n'),
4187
+ });
4188
+ const parsed = this.parseAiJsonPayload(String((_c = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _c !== void 0 ? _c : ''));
4189
+ const missingFields = Array.from(new Set([
4190
+ ...heuristicReview.missingFields,
4191
+ ...this.normalizeStringList(parsed.missingFields),
4192
+ ]));
4193
+ const warnings = Array.from(new Set([
4194
+ ...heuristicReview.warnings,
4195
+ ...this.normalizeStringList(parsed.warnings),
4196
+ ]));
4197
+ const checklist = Array.from(new Set([
4198
+ ...this.normalizeStringList(parsed.checklist),
4199
+ ...heuristicReview.checklist,
4200
+ ]));
4201
+ const parsedStatus = this.normalizeExtractionString(parsed.status);
4202
+ const status = parsedStatus === 'ready_for_revision' ||
4203
+ parsedStatus === 'attention_required'
4204
+ ? parsedStatus
4205
+ : missingFields.length || warnings.length
4206
+ ? 'attention_required'
4207
+ : 'ready_for_revision';
4208
+ return {
4209
+ summary: this.normalizeExtractionString(parsed.summary) ||
4210
+ heuristicReview.summary,
4211
+ missingFields,
4212
+ warnings,
4213
+ checklist,
4214
+ status,
4215
+ reviewedAt: new Date().toISOString(),
4216
+ };
4217
+ }
4218
+ catch (_d) {
4219
+ return heuristicReview;
4220
+ }
4221
+ }
4222
+ buildHeuristicContractLegalReview(contract) {
4223
+ var _a, _b, _c, _d, _e, _f, _g;
4224
+ const contentText = ((_a = this.normalizeOptionalText(contract.contentHtml)) !== null && _a !== void 0 ? _a : '')
4225
+ .replace(/<[^>]+>/g, ' ')
4226
+ .replace(/\s+/g, ' ')
4227
+ .trim()
4228
+ .toLowerCase();
4229
+ const primaryParty = (_e = (_c = ((_b = contract.parties) !== null && _b !== void 0 ? _b : []).find((party) => party.isPrimary)) !== null && _c !== void 0 ? _c : ((_d = contract.parties) !== null && _d !== void 0 ? _d : [])[0]) !== null && _e !== void 0 ? _e : null;
4230
+ const missingFields = [];
4231
+ const warnings = [];
4232
+ const checklist = [];
4233
+ if (!this.normalizeOptionalText(contract.name)) {
4234
+ missingFields.push('Contract title');
4235
+ checklist.push('Attention: define a clear contract title.');
4236
+ }
4237
+ else {
4238
+ checklist.push('OK: contract title is registered.');
4239
+ }
4240
+ if (!this.normalizeOptionalText(contract.clientName)) {
4241
+ missingFields.push('Client or primary party name');
4242
+ checklist.push('Attention: confirm the client or main party identification.');
4243
+ }
4244
+ else {
4245
+ checklist.push('OK: the main party is identified.');
4246
+ }
4247
+ if (!contract.startDate && !contract.effectiveDate) {
4248
+ missingFields.push('Start or effective date');
4249
+ checklist.push('Attention: define the term start or effective date.');
4250
+ }
4251
+ else {
4252
+ checklist.push('OK: validity dates were provided.');
4253
+ }
4254
+ if (!((_f = contract.parties) !== null && _f !== void 0 ? _f : []).length) {
4255
+ missingFields.push('Related parties');
4256
+ warnings.push('No related parties are registered in the contract draft.');
4257
+ }
4258
+ if (!this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.documentNumber)) {
4259
+ warnings.push('Consider confirming the primary party document number.');
4260
+ }
4261
+ if (!((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : []).length && contract.budgetAmount == null) {
4262
+ warnings.push('Commercial conditions are still generic; define prices, recurrence, and penalties.');
4263
+ checklist.push('Attention: validate billing, adjustments, and penalties.');
4264
+ }
4265
+ else {
4266
+ checklist.push('OK: there is at least one financial reference to review.');
4267
+ }
4268
+ if (!contentText) {
4269
+ missingFields.push('Contract body content');
4270
+ warnings.push('The contract body is still empty and needs a first draft.');
4271
+ }
4272
+ else {
4273
+ if (contentText.length < 600) {
4274
+ warnings.push('The contract body is still brief; validate scope, obligations, and exceptions in detail.');
4275
+ }
4276
+ if (!/(rescis|terminat|cancel)/.test(contentText)) {
4277
+ warnings.push('Add a termination / rescission clause with notice and penalty rules.');
4278
+ }
4279
+ if (!/(confiden|sigil|data protect|privacy)/.test(contentText)) {
4280
+ warnings.push('Review confidentiality and data protection clauses when applicable.');
4281
+ }
4282
+ if (!/(foro|jurisd|governing law|applicable law)/.test(contentText)) {
4283
+ warnings.push('Review the governing law / forum clause before signature.');
4284
+ }
4285
+ }
4286
+ const summary = missingFields.length || warnings.length
4287
+ ? `Advisory legal review found ${missingFields.length} missing item(s) and ${warnings.length} point(s) for attention before final approval.`
4288
+ : 'Advisory legal review found the draft structurally consistent for human legal revision.';
4289
+ return {
4290
+ summary,
4291
+ missingFields: Array.from(new Set(missingFields)),
4292
+ warnings: Array.from(new Set(warnings)),
4293
+ checklist: Array.from(new Set(checklist)),
4294
+ status: missingFields.length || warnings.length
4295
+ ? 'attention_required'
4296
+ : 'ready_for_revision',
4297
+ reviewedAt: new Date().toISOString(),
4298
+ };
4299
+ }
4300
+ buildContractExtractionSystemPrompt() {
4301
+ return [
4302
+ 'You extract structured draft data from uploaded business contracts.',
4303
+ 'Return ONLY valid JSON. Do not include Markdown fences, prose, or comments.',
4304
+ 'Use empty strings or empty arrays when the document is unclear; never invent legal facts.',
4305
+ 'Dates must use YYYY-MM-DD whenever the document provides enough confidence.',
4306
+ 'For contentHtml, return clean HTML suitable for a rich text editor preview.',
4307
+ 'Expected JSON shape:',
4308
+ JSON.stringify({
4309
+ code: '',
4310
+ name: '',
4311
+ clientName: '',
4312
+ contractCategory: 'client',
4313
+ contractType: 'service_agreement',
4314
+ signatureStatus: 'not_started',
4315
+ billingModel: 'time_and_material',
4316
+ originType: 'manual',
4317
+ originId: '',
4318
+ startDate: '',
4319
+ endDate: '',
4320
+ signedAt: '',
4321
+ effectiveDate: '',
4322
+ budgetAmount: '',
4323
+ monthlyHourCap: '',
4324
+ status: 'draft',
4325
+ description: '',
4326
+ contentHtml: '',
4327
+ parties: [
4328
+ {
4329
+ displayName: '',
4330
+ partyRole: 'client',
4331
+ partyType: 'company',
4332
+ documentNumber: '',
4333
+ email: '',
4334
+ phone: '',
4335
+ isPrimary: true,
4336
+ },
4337
+ ],
4338
+ signatures: [
4339
+ {
4340
+ signerName: '',
4341
+ signerRole: '',
4342
+ signerEmail: '',
4343
+ status: 'pending',
4344
+ signedAt: '',
4345
+ },
4346
+ ],
4347
+ financialTerms: [
4348
+ {
4349
+ label: '',
4350
+ termType: 'value',
4351
+ amount: '',
4352
+ recurrence: 'one_time',
4353
+ dueDay: '',
4354
+ notes: '',
4355
+ },
4356
+ ],
4357
+ revisions: [
4358
+ {
4359
+ title: '',
4360
+ revisionType: 'revision',
4361
+ effectiveDate: '',
4362
+ status: 'draft',
4363
+ summary: '',
4364
+ },
4365
+ ],
4366
+ summary: '',
4367
+ missingFields: [],
4368
+ warnings: [],
4369
+ }, null, 2),
4370
+ ].join('\n\n');
4371
+ }
4372
+ parseAiJsonPayload(content) {
4373
+ var _a;
4374
+ const trimmed = String(content !== null && content !== void 0 ? content : '').trim();
4375
+ if (!trimmed) {
4376
+ throw new common_1.BadRequestException('AI returned an empty contract draft response.');
4377
+ }
4378
+ const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
4379
+ const candidate = ((_a = fencedMatch === null || fencedMatch === void 0 ? void 0 : fencedMatch[1]) === null || _a === void 0 ? void 0 : _a.trim()) || trimmed;
4380
+ try {
4381
+ return JSON.parse(candidate);
4382
+ }
4383
+ catch (_b) {
4384
+ const firstBraceIndex = candidate.indexOf('{');
4385
+ const lastBraceIndex = candidate.lastIndexOf('}');
4386
+ if (firstBraceIndex >= 0 && lastBraceIndex > firstBraceIndex) {
4387
+ try {
4388
+ return JSON.parse(candidate.slice(firstBraceIndex, lastBraceIndex + 1));
4389
+ }
4390
+ catch (_c) {
4391
+ throw new common_1.BadRequestException('AI returned an invalid contract draft payload.');
4392
+ }
4393
+ }
4394
+ throw new common_1.BadRequestException('AI returned an invalid contract draft payload.');
4395
+ }
4396
+ }
4397
+ normalizeContractExtractDraft(raw) {
4398
+ const warnings = this.normalizeStringList(raw.warnings);
4399
+ const missingFields = this.normalizeStringList(raw.missingFields);
4400
+ const draft = {
4401
+ code: this.normalizeExtractionString(raw.code),
4402
+ name: this.normalizeExtractionString(raw.name),
4403
+ clientName: this.normalizeExtractionString(raw.clientName),
4404
+ contractCategory: this.normalizeEnumValue(raw.contractCategory, CONTRACT_CATEGORY_VALUES, 'client'),
4405
+ contractType: this.normalizeEnumValue(raw.contractType, CONTRACT_TYPE_VALUES, 'service_agreement'),
4406
+ signatureStatus: this.normalizeEnumValue(raw.signatureStatus, SIGNATURE_STATUS_VALUES, 'not_started'),
4407
+ isActive: raw.isActive === false ? false : true,
4408
+ billingModel: this.normalizeEnumValue(raw.billingModel, BILLING_MODEL_VALUES, 'time_and_material'),
4409
+ originType: this.normalizeEnumValue(raw.originType, ORIGIN_TYPE_VALUES, 'manual'),
4410
+ originId: this.normalizeExtractionString(raw.originId),
4411
+ startDate: this.normalizeExtractionDate(raw.startDate),
4412
+ endDate: this.normalizeExtractionDate(raw.endDate),
4413
+ signedAt: this.normalizeExtractionDate(raw.signedAt),
4414
+ effectiveDate: this.normalizeExtractionDate(raw.effectiveDate),
4415
+ budgetAmount: this.normalizeExtractionNumber(raw.budgetAmount),
4416
+ monthlyHourCap: this.normalizeExtractionNumber(raw.monthlyHourCap),
4417
+ status: this.normalizeEnumValue(raw.status, CONTRACT_STATUS_VALUES, 'draft'),
4418
+ description: this.normalizeExtractionString(raw.description),
4419
+ contentHtml: this.normalizeExtractionString(raw.contentHtml),
4420
+ summary: this.normalizeExtractionString(raw.summary),
4421
+ parties: this.normalizeObjectList(raw.parties)
4422
+ .map((party) => ({
4423
+ displayName: this.normalizeExtractionString(party.displayName),
4424
+ partyRole: this.normalizeEnumValue(party.partyRole, PARTY_ROLE_VALUES, 'client'),
4425
+ partyType: this.normalizeEnumValue(party.partyType, PARTY_TYPE_VALUES, 'company'),
4426
+ documentNumber: this.normalizeExtractionString(party.documentNumber),
4427
+ email: this.normalizeExtractionString(party.email),
4428
+ phone: this.normalizeExtractionString(party.phone),
4429
+ isPrimary: this.normalizeExtractionBoolean(party.isPrimary),
4430
+ }))
4431
+ .filter((party) => party.displayName),
4432
+ signatures: this.normalizeObjectList(raw.signatures)
4433
+ .map((signature) => ({
4434
+ signerName: this.normalizeExtractionString(signature.signerName),
4435
+ signerRole: this.normalizeExtractionString(signature.signerRole),
4436
+ signerEmail: this.normalizeExtractionString(signature.signerEmail),
4437
+ status: this.normalizeEnumValue(signature.status, SIGNATURE_ITEM_STATUS_VALUES, 'pending'),
4438
+ signedAt: this.normalizeExtractionDate(signature.signedAt),
4439
+ }))
4440
+ .filter((signature) => signature.signerName),
4441
+ financialTerms: this.normalizeObjectList(raw.financialTerms)
4442
+ .map((term) => ({
4443
+ label: this.normalizeExtractionString(term.label),
4444
+ termType: this.normalizeEnumValue(term.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
4445
+ amount: this.normalizeExtractionNumber(term.amount),
4446
+ recurrence: this.normalizeEnumValue(term.recurrence, RECURRENCE_VALUES, 'one_time'),
4447
+ dueDay: this.normalizeExtractionNumber(term.dueDay),
4448
+ notes: this.normalizeExtractionString(term.notes),
4449
+ }))
4450
+ .filter((term) => term.label),
4451
+ revisions: this.normalizeObjectList(raw.revisions)
4452
+ .map((revision) => ({
4453
+ title: this.normalizeExtractionString(revision.title),
4454
+ revisionType: this.normalizeEnumValue(revision.revisionType, REVISION_TYPE_VALUES, 'revision'),
4455
+ effectiveDate: this.normalizeExtractionDate(revision.effectiveDate),
4456
+ status: this.normalizeEnumValue(revision.status, REVISION_STATUS_VALUES, 'draft'),
4457
+ summary: this.normalizeExtractionString(revision.summary),
4458
+ }))
4459
+ .filter((revision) => revision.title),
4460
+ };
4461
+ if (!draft.name)
4462
+ missingFields.push('Contract title');
4463
+ if (!draft.clientName)
4464
+ missingFields.push('Client name');
4465
+ if (!draft.startDate)
4466
+ missingFields.push('Start date');
4467
+ if (!draft.contentHtml && !draft.summary && !draft.description) {
4468
+ warnings.push('The contract body was not extracted with enough confidence. Use the editor to complete it.');
4469
+ }
4470
+ if (!draft.parties.length) {
4471
+ warnings.push('No related parties were identified with confidence.');
4472
+ }
4473
+ return Object.assign(Object.assign({}, draft), { summary: draft.summary || draft.description || draft.name, missingFields: Array.from(new Set(missingFields.filter(Boolean))), warnings: Array.from(new Set(warnings.filter(Boolean))) });
4474
+ }
4475
+ normalizeExtractionString(value) {
4476
+ return typeof value === 'string'
4477
+ ? value.trim()
4478
+ : value === null || value === undefined
4479
+ ? ''
4480
+ : String(value).trim();
4481
+ }
4482
+ normalizeExtractionBoolean(value) {
4483
+ if (typeof value === 'boolean') {
4484
+ return value;
4485
+ }
4486
+ const normalized = this.normalizeExtractionString(value).toLowerCase();
4487
+ return normalized === 'true' || normalized === '1' || normalized === 'yes';
4488
+ }
4489
+ normalizeExtractionNumber(value) {
4490
+ if (typeof value === 'number' && Number.isFinite(value)) {
4491
+ return value;
4492
+ }
4493
+ const normalized = this.normalizeExtractionString(value);
4494
+ if (!normalized) {
4495
+ return '';
4496
+ }
4497
+ const direct = Number(normalized);
4498
+ if (Number.isFinite(direct)) {
4499
+ return direct;
4500
+ }
4501
+ const ptBr = normalized
4502
+ .split('.')
4503
+ .join('')
4504
+ .replace(',', '.');
4505
+ const parsed = Number(ptBr);
4506
+ return Number.isFinite(parsed) ? parsed : normalized;
4507
+ }
4508
+ normalizeExtractionDate(value) {
4509
+ var _a;
4510
+ const normalized = this.normalizeExtractionString(value);
4511
+ if (!normalized) {
4512
+ return '';
4513
+ }
4514
+ const match = normalized.match(/\d{4}-\d{2}-\d{2}/);
4515
+ return (_a = match === null || match === void 0 ? void 0 : match[0]) !== null && _a !== void 0 ? _a : '';
4516
+ }
4517
+ normalizeStringList(value) {
4518
+ return Array.isArray(value)
4519
+ ? value
4520
+ .map((item) => this.normalizeExtractionString(item))
4521
+ .filter(Boolean)
4522
+ : [];
4523
+ }
4524
+ normalizeObjectList(value) {
4525
+ if (!Array.isArray(value)) {
4526
+ return [];
4527
+ }
4528
+ return value.filter((item) => Boolean(item) && typeof item === 'object');
4529
+ }
4530
+ normalizeEnumValue(value, allowed, fallback) {
4531
+ const normalized = this.normalizeExtractionString(value);
4532
+ return allowed.includes(normalized) ? normalized : fallback;
4533
+ }
4534
+ normalizeContractExtractionMimeType(mimeType, fileName) {
4535
+ const current = this.normalizeExtractionString(mimeType).toLowerCase();
4536
+ if (current && current !== 'application/octet-stream') {
4537
+ return current;
4538
+ }
4539
+ const name = this.normalizeExtractionString(fileName).toLowerCase();
4540
+ if (name.endsWith('.pdf'))
4541
+ return 'application/pdf';
4542
+ if (name.endsWith('.doc'))
4543
+ return 'application/msword';
4544
+ if (name.endsWith('.docx')) {
4545
+ return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
4546
+ }
4547
+ return current || 'application/octet-stream';
4548
+ }
4549
+ isAllowedContractExtractionMimeType(mimeType) {
4550
+ return [
4551
+ 'application/pdf',
4552
+ 'application/msword',
4553
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
4554
+ ].includes(mimeType);
4555
+ }
2201
4556
  requireFields(input, required) {
2202
4557
  for (const field of required) {
2203
4558
  const value = input[field];
@@ -2223,15 +4578,18 @@ let OperationsService = class OperationsService {
2223
4578
  ...new Set(values.filter((value) => typeof value === 'number')),
2224
4579
  ];
2225
4580
  }
2226
- pushUpdate(updates, params, column, value) {
4581
+ pushUpdate(updates, params, column, value, castType) {
2227
4582
  if (value === undefined)
2228
4583
  return;
2229
4584
  params.push(value);
2230
- updates.push(`${column} = $${params.length}`);
4585
+ const placeholder = castType
4586
+ ? `$${params.length}::${castType}`
4587
+ : `$${params.length}`;
4588
+ updates.push(`${column} = ${placeholder}`);
2231
4589
  }
2232
- param(params, value) {
4590
+ param(params, value, castType) {
2233
4591
  params.push(value);
2234
- return `$${params.length}`;
4592
+ return castType ? `$${params.length}::${castType}` : `$${params.length}`;
2235
4593
  }
2236
4594
  groupBy(items, key) {
2237
4595
  return items.reduce((accumulator, item) => {
@@ -2255,9 +4613,14 @@ let OperationsService = class OperationsService {
2255
4613
  }
2256
4614
  };
2257
4615
  exports.OperationsService = OperationsService;
2258
- exports.OperationsService = OperationsService = __decorate([
4616
+ exports.OperationsService = OperationsService = OperationsService_1 = __decorate([
2259
4617
  (0, common_1.Injectable)(),
4618
+ __param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.FileService))),
4619
+ __param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.SettingService))),
2260
4620
  __metadata("design:paramtypes", [api_prisma_1.PrismaService,
2261
- core_1.IntegrationDeveloperApiService])
4621
+ core_1.AiService,
4622
+ core_1.IntegrationDeveloperApiService,
4623
+ core_1.FileService,
4624
+ core_1.SettingService])
2262
4625
  ], OperationsService);
2263
4626
  //# sourceMappingURL=operations.service.js.map