@hed-hog/operations 0.0.300 → 0.0.301

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