@hed-hog/operations 0.0.305 → 0.0.309

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 (138) hide show
  1. package/dist/controllers/operations-approvals.controller.d.ts +114 -1
  2. package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-approvals.controller.js +16 -3
  4. package/dist/controllers/operations-approvals.controller.js.map +1 -1
  5. package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
  6. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-collaborators.controller.js +16 -3
  8. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  9. package/dist/controllers/operations-contracts.controller.d.ts +14 -453
  10. package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
  11. package/dist/controllers/operations-contracts.controller.js +11 -112
  12. package/dist/controllers/operations-contracts.controller.js.map +1 -1
  13. package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
  14. package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
  15. package/dist/controllers/operations-org-structure.controller.js +18 -5
  16. package/dist/controllers/operations-org-structure.controller.js.map +1 -1
  17. package/dist/controllers/operations-projects.controller.d.ts +28 -4
  18. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  19. package/dist/controllers/operations-projects.controller.js +17 -5
  20. package/dist/controllers/operations-projects.controller.js.map +1 -1
  21. package/dist/controllers/operations-timesheets.controller.d.ts +52 -4
  22. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
  23. package/dist/controllers/operations-timesheets.controller.js +28 -11
  24. package/dist/controllers/operations-timesheets.controller.js.map +1 -1
  25. package/dist/dto/list-approvals.dto.d.ts +6 -0
  26. package/dist/dto/list-approvals.dto.d.ts.map +1 -0
  27. package/dist/dto/list-approvals.dto.js +28 -0
  28. package/dist/dto/list-approvals.dto.js.map +1 -0
  29. package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
  30. package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
  31. package/dist/dto/list-collaborator-types.dto.js +7 -1
  32. package/dist/dto/list-collaborator-types.dto.js.map +1 -1
  33. package/dist/dto/list-collaborators.dto.d.ts +1 -0
  34. package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
  35. package/dist/dto/list-collaborators.dto.js +5 -0
  36. package/dist/dto/list-collaborators.dto.js.map +1 -1
  37. package/dist/dto/list-contracts.dto.d.ts +8 -0
  38. package/dist/dto/list-contracts.dto.d.ts.map +1 -0
  39. package/dist/dto/list-contracts.dto.js +38 -0
  40. package/dist/dto/list-contracts.dto.js.map +1 -0
  41. package/dist/dto/list-departments.dto.d.ts +5 -0
  42. package/dist/dto/list-departments.dto.d.ts.map +1 -0
  43. package/dist/dto/list-departments.dto.js +23 -0
  44. package/dist/dto/list-departments.dto.js.map +1 -0
  45. package/dist/dto/list-projects.dto.d.ts +5 -0
  46. package/dist/dto/list-projects.dto.d.ts.map +1 -0
  47. package/dist/dto/list-projects.dto.js +23 -0
  48. package/dist/dto/list-projects.dto.js.map +1 -0
  49. package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
  50. package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
  51. package/dist/dto/list-schedule-adjustments.dto.js +23 -0
  52. package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
  53. package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
  54. package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
  55. package/dist/dto/list-time-off-requests.dto.js +23 -0
  56. package/dist/dto/list-time-off-requests.dto.js.map +1 -0
  57. package/dist/dto/list-timesheets.dto.d.ts +5 -0
  58. package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
  59. package/dist/dto/list-timesheets.dto.js +23 -0
  60. package/dist/dto/list-timesheets.dto.js.map +1 -0
  61. package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
  62. package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
  63. package/dist/dto/reorder-collaborator-types.dto.js +25 -0
  64. package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
  65. package/dist/dto/update-collaborator-type.dto.d.ts +3 -1
  66. package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -1
  67. package/dist/dto/update-collaborator-type.dto.js +2 -1
  68. package/dist/dto/update-collaborator-type.dto.js.map +1 -1
  69. package/dist/operations.service.d.ts +362 -271
  70. package/dist/operations.service.d.ts.map +1 -1
  71. package/dist/operations.service.js +1195 -1098
  72. package/dist/operations.service.js.map +1 -1
  73. package/dist/operations.service.spec.js +73 -22
  74. package/dist/operations.service.spec.js.map +1 -1
  75. package/hedhog/data/menu.yaml +19 -55
  76. package/hedhog/data/operations_collaborator_type.yaml +76 -76
  77. package/hedhog/data/route.yaml +52 -70
  78. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +5 -3
  79. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
  80. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
  81. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
  82. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
  83. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
  84. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
  85. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
  86. package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +1 -0
  87. package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
  88. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
  89. package/hedhog/frontend/app/approvals/page.tsx.ejs +843 -151
  90. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +457 -154
  91. package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
  92. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  93. package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
  94. package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
  95. package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
  96. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +546 -118
  97. package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
  98. package/hedhog/frontend/app/timesheets/page.tsx.ejs +647 -342
  99. package/hedhog/frontend/messages/en.json +148 -14
  100. package/hedhog/frontend/messages/pt.json +199 -56
  101. package/hedhog/table/operations_collaborator.yaml +18 -18
  102. package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -43
  103. package/hedhog/table/operations_collaborator_type.yaml +33 -33
  104. package/hedhog/table/operations_contract.yaml +0 -9
  105. package/hedhog/table/operations_contract_document.yaml +33 -33
  106. package/package.json +4 -4
  107. package/src/controllers/operations-approvals.controller.ts +9 -3
  108. package/src/controllers/operations-collaborators.controller.ts +15 -2
  109. package/src/controllers/operations-contracts.controller.ts +8 -92
  110. package/src/controllers/operations-org-structure.controller.ts +17 -4
  111. package/src/controllers/operations-projects.controller.ts +10 -4
  112. package/src/controllers/operations-timesheets.controller.ts +30 -8
  113. package/src/dto/create-collaborator-type.dto.ts +43 -43
  114. package/src/dto/create-collaborator.dto.ts +223 -223
  115. package/src/dto/list-approvals.dto.ts +12 -0
  116. package/src/dto/list-collaborator-types.dto.ts +20 -15
  117. package/src/dto/list-collaborators.dto.ts +34 -30
  118. package/src/dto/list-contracts.dto.ts +20 -0
  119. package/src/dto/list-departments.dto.ts +8 -0
  120. package/src/dto/list-projects.dto.ts +8 -0
  121. package/src/dto/list-schedule-adjustments.dto.ts +8 -0
  122. package/src/dto/list-time-off-requests.dto.ts +8 -0
  123. package/src/dto/list-timesheets.dto.ts +8 -0
  124. package/src/dto/reorder-collaborator-types.dto.ts +10 -0
  125. package/src/dto/update-collaborator-type.dto.ts +4 -3
  126. package/src/dto/update-collaborator.dto.ts +3 -3
  127. package/src/operations.service.spec.ts +96 -30
  128. package/src/operations.service.ts +1738 -1777
  129. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
  130. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
  131. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
  132. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
  133. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
  134. package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
  135. package/hedhog/table/operations_contract_financial_term.yaml +0 -40
  136. package/hedhog/table/operations_contract_revision.yaml +0 -38
  137. package/hedhog/table/operations_contract_signature.yaml +0 -38
  138. package/hedhog/table/operations_contract_template.yaml +0 -58
@@ -102,11 +102,6 @@ const PARTY_ROLE_VALUES = [
102
102
  'other',
103
103
  ];
104
104
  const PARTY_TYPE_VALUES = ['individual', 'company', 'internal_team', 'other'];
105
- const SIGNATURE_ITEM_STATUS_VALUES = ['pending', 'signed', 'rejected'];
106
- const FINANCIAL_TERM_TYPE_VALUES = ['value', 'payment', 'revenue', 'fine', 'other'];
107
- const RECURRENCE_VALUES = ['one_time', 'monthly', 'quarterly', 'yearly', 'other'];
108
- const REVISION_TYPE_VALUES = ['amendment', 'renewal', 'revision', 'addendum', 'other'];
109
- const REVISION_STATUS_VALUES = ['draft', 'active', 'completed', 'cancelled'];
110
105
  const TASK_STATUS_VALUES = ['todo', 'doing', 'review', 'done'];
111
106
  let OperationsService = OperationsService_1 = class OperationsService {
112
107
  constructor(prisma, aiService, integrationApi, fileService, settingService, accessService, localeService) {
@@ -154,35 +149,82 @@ let OperationsService = OperationsService_1 = class OperationsService {
154
149
  LIMIT 1`));
155
150
  return (_b = (_a = fallbackLocales[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
156
151
  }
157
- async listCollaboratorTypes(userId, filters) {
152
+ async listCollaboratorTypes(userId, filters = {}) {
153
+ var _a, _b;
158
154
  const actor = await this.getActorContext(userId);
159
155
  this.ensureCollaborator(actor);
160
- return this.queryRows(`SELECT ct.id,
161
- ct.slug,
162
- ct.name,
163
- ct.description,
164
- ct.category,
165
- ct.is_active AS "isActive",
166
- ct.sort_order AS "sortOrder",
167
- CASE
168
- WHEN ct.deleted_at IS NULL AND ct.is_active THEN 'active'
169
- ELSE 'inactive'
170
- END AS status,
171
- COUNT(DISTINCT c.id)::int AS "collaboratorCount",
172
- ct.created_at AS "createdAt",
173
- ct.updated_at AS "updatedAt"
156
+ const params = [];
157
+ const where = [];
158
+ if (filters.active === true || filters.status === 'active') {
159
+ where.push('(ct.deleted_at IS NULL AND ct.is_active = true)');
160
+ }
161
+ else if (filters.status === 'inactive') {
162
+ where.push('(ct.deleted_at IS NOT NULL OR ct.is_active = false)');
163
+ }
164
+ const pagination = this.shouldPaginate(filters)
165
+ ? this.normalizePaginationParams(filters, {
166
+ defaultSortField: 'sortOrder',
167
+ defaultSortOrder: 'asc',
168
+ allowedSortFields: ['name', 'slug', 'category', 'sortOrder', 'createdAt'],
169
+ })
170
+ : null;
171
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
172
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
173
+ where.push(`(
174
+ COALESCE(ct.name, '') ILIKE ${searchPlaceholder}
175
+ OR COALESCE(ct.slug, '') ILIKE ${searchPlaceholder}
176
+ OR COALESCE(ct.description, '') ILIKE ${searchPlaceholder}
177
+ OR COALESCE(ct.category, '') ILIKE ${searchPlaceholder}
178
+ )`);
179
+ }
180
+ const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '';
181
+ const baseQuery = `SELECT ct.id,
182
+ ct.slug,
183
+ ct.name,
184
+ ct.description,
185
+ ct.category,
186
+ ct.is_active AS "isActive",
187
+ ct.sort_order AS "sortOrder",
188
+ CASE
189
+ WHEN ct.deleted_at IS NULL AND ct.is_active THEN 'active'
190
+ ELSE 'inactive'
191
+ END AS status,
192
+ COUNT(DISTINCT c.id)::int AS "collaboratorCount",
193
+ ct.created_at AS "createdAt",
194
+ ct.updated_at AS "updatedAt"
195
+ FROM operations_collaborator_type ct
196
+ LEFT JOIN operations_collaborator c
197
+ ON c.deleted_at IS NULL
198
+ AND c.collaborator_type_id = ct.id
199
+ ${whereClause}
200
+ GROUP BY ct.id`;
201
+ if (!pagination) {
202
+ return this.queryRows(`${baseQuery}
203
+ ORDER BY CASE
204
+ WHEN ct.deleted_at IS NULL AND ct.is_active THEN 0
205
+ ELSE 1
206
+ END ASC,
207
+ ct.sort_order ASC,
208
+ ct.name ASC`, params);
209
+ }
210
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
174
211
  FROM operations_collaborator_type ct
175
- LEFT JOIN operations_collaborator c
176
- ON c.deleted_at IS NULL
177
- AND c.collaborator_type_id = ct.id
178
- WHERE ($1::boolean = false OR (ct.deleted_at IS NULL AND ct.is_active = true))
179
- GROUP BY ct.id
180
- ORDER BY CASE
181
- WHEN ct.deleted_at IS NULL AND ct.is_active THEN 0
182
- ELSE 1
183
- END ASC,
184
- ct.sort_order ASC,
185
- ct.name ASC`, [Boolean(filters === null || filters === void 0 ? void 0 : filters.active)]);
212
+ ${whereClause}`, params);
213
+ const sortColumn = (_a = {
214
+ name: 'ct.name',
215
+ slug: 'ct.slug',
216
+ category: 'ct.category',
217
+ sortOrder: 'ct.sort_order',
218
+ createdAt: 'ct.created_at',
219
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'ct.name';
220
+ const queryParams = [...params];
221
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
222
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
223
+ const rows = await this.queryRows(`${baseQuery}
224
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, ct.id ASC
225
+ LIMIT ${limitPlaceholder}
226
+ OFFSET ${offsetPlaceholder}`, queryParams);
227
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
186
228
  }
187
229
  async createCollaboratorType(userId, data) {
188
230
  const actor = await this.getActorContext(userId);
@@ -196,6 +238,9 @@ let OperationsService = OperationsService_1 = class OperationsService {
196
238
  await this.assertCollaboratorTypeNameAvailable(tx, name);
197
239
  const nextSlug = await this.buildCollaboratorTypeSlug(tx, name, data.slug);
198
240
  const nextIsActive = (_a = data.isActive) !== null && _a !== void 0 ? _a : (data.status ? data.status === 'active' : true);
241
+ const nextSortOrder = data.sortOrder !== undefined && Number.isFinite(Number(data.sortOrder))
242
+ ? Number(data.sortOrder)
243
+ : await this.getNextCollaboratorTypeSortOrder(tx);
199
244
  const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_collaborator_type (
200
245
  slug,
201
246
  name,
@@ -211,7 +256,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
211
256
  CASE WHEN $5::boolean THEN NULL ELSE NOW() END,
212
257
  NOW(), NOW()
213
258
  )
214
- RETURNING id`, nextSlug, name, this.normalizeOptionalText(data.description), this.normalizeOptionalText(data.category), nextIsActive, Number.isFinite(Number(data.sortOrder)) ? Number(data.sortOrder) : 0));
259
+ RETURNING id`, nextSlug, name, this.normalizeOptionalText(data.description), this.normalizeOptionalText(data.category), nextIsActive, nextSortOrder));
215
260
  const createdCollaboratorTypeId = (_b = created[0]) === null || _b === void 0 ? void 0 : _b.id;
216
261
  if (!createdCollaboratorTypeId) {
217
262
  throw new common_1.BadRequestException('Unable to create the collaborator type.');
@@ -261,6 +306,40 @@ let OperationsService = OperationsService_1 = class OperationsService {
261
306
  return this.getCollaboratorTypeById(tx, collaboratorTypeId, true);
262
307
  });
263
308
  }
309
+ async reorderCollaboratorTypes(userId, ids) {
310
+ const actor = await this.getActorContext(userId);
311
+ this.ensureDirector(actor);
312
+ const normalizedIds = [
313
+ ...new Set(ids
314
+ .map((value) => Number(value))
315
+ .filter((value) => Number.isInteger(value) && value > 0)),
316
+ ];
317
+ if (!normalizedIds.length) {
318
+ throw new common_1.BadRequestException('At least one collaborator type is required.');
319
+ }
320
+ return this.prisma.$transaction(async (tx) => {
321
+ const existingRows = (await tx.$queryRawUnsafe(`SELECT id
322
+ FROM operations_collaborator_type
323
+ WHERE id = ANY($1::int[])`, normalizedIds));
324
+ if (existingRows.length !== normalizedIds.length) {
325
+ throw new common_1.NotFoundException('One or more collaborator types were not found.');
326
+ }
327
+ await tx.$executeRawUnsafe(`UPDATE operations_collaborator_type AS ct
328
+ SET sort_order = updates.sort_order,
329
+ updated_at = NOW()
330
+ FROM (
331
+ SELECT UNNEST($1::int[]) AS id,
332
+ GENERATE_SERIES(1, array_length($1::int[], 1)) AS sort_order
333
+ ) AS updates
334
+ WHERE ct.id = updates.id`, normalizedIds);
335
+ return this.listCollaboratorTypes(userId, {
336
+ page: 1,
337
+ pageSize: Math.max(normalizedIds.length, 1),
338
+ sortField: 'sortOrder',
339
+ sortOrder: 'asc',
340
+ });
341
+ });
342
+ }
264
343
  async listProjectRoles(userId) {
265
344
  const actor = await this.getActorContext(userId);
266
345
  this.ensureCollaborator(actor);
@@ -409,11 +488,54 @@ let OperationsService = OperationsService_1 = class OperationsService {
409
488
  recentTimesheets,
410
489
  };
411
490
  }
412
- async listCollaborators(userId) {
491
+ async listCollaborators(userId, filters = {}) {
492
+ var _a, _b;
413
493
  const actor = await this.getActorContext(userId);
414
494
  this.ensureCollaborator(actor);
415
495
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'c.id', actor.isDirector);
416
- return this.queryRows(`SELECT c.id,
496
+ const pagination = this.shouldPaginate(filters)
497
+ ? this.normalizePaginationParams(filters, {
498
+ defaultSortField: 'displayName',
499
+ defaultSortOrder: 'asc',
500
+ allowedSortFields: ['displayName', 'code', 'joinedAt', 'status'],
501
+ })
502
+ : null;
503
+ const params = [...filter.params];
504
+ const where = ['c.deleted_at IS NULL', filter.clause];
505
+ if (filters.status && filters.status !== 'all') {
506
+ where.push(`c.status::text = ${this.param(params, filters.status)}`);
507
+ }
508
+ if (filters.collaboratorTypeId) {
509
+ where.push(`c.collaborator_type_id = ${this.param(params, Number(filters.collaboratorTypeId))}`);
510
+ }
511
+ else if (filters.collaboratorType && filters.collaboratorType !== 'all') {
512
+ const collaboratorTypePlaceholder = this.param(params, filters.collaboratorType);
513
+ where.push(`(
514
+ collaborator_type.slug = ${collaboratorTypePlaceholder}
515
+ OR collaborator_type.name = ${collaboratorTypePlaceholder}
516
+ )`);
517
+ }
518
+ if (filters.departmentId) {
519
+ where.push(`c.department_id = ${this.param(params, Number(filters.departmentId))}`);
520
+ }
521
+ if (filters.jobTitleId) {
522
+ where.push(`c.job_title_id = ${this.param(params, Number(filters.jobTitleId))}`);
523
+ }
524
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
525
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
526
+ where.push(`(
527
+ COALESCE(c.display_name, '') ILIKE ${searchPlaceholder}
528
+ OR COALESCE(person_record.name, '') ILIKE ${searchPlaceholder}
529
+ OR COALESCE(c.code, '') ILIKE ${searchPlaceholder}
530
+ OR COALESCE(department_record.name, '') ILIKE ${searchPlaceholder}
531
+ OR COALESCE(c.department, '') ILIKE ${searchPlaceholder}
532
+ OR COALESCE(job_title_record.name, '') ILIKE ${searchPlaceholder}
533
+ OR COALESCE(c.title, '') ILIKE ${searchPlaceholder}
534
+ OR COALESCE(s.display_name, '') ILIKE ${searchPlaceholder}
535
+ )`);
536
+ }
537
+ const whereClause = where.join(' AND ');
538
+ const baseQuery = `SELECT c.id,
417
539
  c.user_id AS "userId",
418
540
  c.person_id AS "personId",
419
541
  c.code,
@@ -466,10 +588,122 @@ let OperationsService = OperationsService_1 = class OperationsService {
466
588
  oc.created_at DESC
467
589
  LIMIT 1
468
590
  ) hiring_contract ON TRUE
469
- WHERE c.deleted_at IS NULL
470
- AND ${filter.clause}
591
+ WHERE ${whereClause}
471
592
  GROUP BY c.id, person_record.id, collaborator_type.id, department_record.id, job_title_record.id, s.id, hiring_contract.id, hiring_contract.status, hiring_contract.budget_amount
472
- ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, filter.params);
593
+ `;
594
+ if (!pagination) {
595
+ return this.queryRows(`${baseQuery}
596
+ ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, params);
597
+ }
598
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
599
+ FROM (
600
+ SELECT c.id
601
+ FROM operations_collaborator c
602
+ LEFT JOIN person person_record
603
+ ON person_record.id = c.person_id
604
+ LEFT JOIN operations_collaborator_type collaborator_type
605
+ ON collaborator_type.id = c.collaborator_type_id
606
+ AND collaborator_type.deleted_at IS NULL
607
+ LEFT JOIN operations_department department_record
608
+ ON department_record.id = c.department_id
609
+ AND department_record.deleted_at IS NULL
610
+ LEFT JOIN operations_job_title job_title_record
611
+ ON job_title_record.id = c.job_title_id
612
+ AND job_title_record.deleted_at IS NULL
613
+ LEFT JOIN operations_collaborator s
614
+ ON s.id = c.supervisor_collaborator_id
615
+ WHERE ${whereClause}
616
+ GROUP BY c.id, person_record.id, collaborator_type.id, department_record.id, job_title_record.id, s.id
617
+ ) collaborator_rows`, params);
618
+ const sortColumn = (_a = {
619
+ displayName: `COALESCE(NULLIF(c.display_name, ''), person_record.name)`,
620
+ code: 'c.code',
621
+ joinedAt: 'c.joined_at',
622
+ status: 'c.status',
623
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : `COALESCE(NULLIF(c.display_name, ''), person_record.name)`;
624
+ const queryParams = [...params];
625
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
626
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
627
+ const rows = await this.queryRows(`${baseQuery}
628
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, c.id ASC
629
+ LIMIT ${limitPlaceholder}
630
+ OFFSET ${offsetPlaceholder}`, queryParams);
631
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
632
+ }
633
+ async getCollaboratorStats(userId, filters = {}) {
634
+ var _a, _b, _c, _d, _e;
635
+ const actor = await this.getActorContext(userId);
636
+ this.ensureCollaborator(actor);
637
+ const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'c.id', actor.isDirector);
638
+ const params = [...filter.params];
639
+ const where = ['c.deleted_at IS NULL', filter.clause];
640
+ if (filters.status && filters.status !== 'all') {
641
+ where.push(`c.status::text = ${this.param(params, filters.status)}`);
642
+ }
643
+ if (filters.collaboratorTypeId) {
644
+ where.push(`c.collaborator_type_id = ${this.param(params, Number(filters.collaboratorTypeId))}`);
645
+ }
646
+ else if (filters.collaboratorType && filters.collaboratorType !== 'all') {
647
+ const collaboratorTypePlaceholder = this.param(params, filters.collaboratorType);
648
+ where.push(`(
649
+ collaborator_type.slug = ${collaboratorTypePlaceholder}
650
+ OR collaborator_type.name = ${collaboratorTypePlaceholder}
651
+ )`);
652
+ }
653
+ if (filters.departmentId) {
654
+ where.push(`c.department_id = ${this.param(params, Number(filters.departmentId))}`);
655
+ }
656
+ if (filters.jobTitleId) {
657
+ where.push(`c.job_title_id = ${this.param(params, Number(filters.jobTitleId))}`);
658
+ }
659
+ const normalizedSearch = String((_a = filters.search) !== null && _a !== void 0 ? _a : '').trim();
660
+ if (normalizedSearch) {
661
+ const searchPlaceholder = this.param(params, `%${normalizedSearch}%`);
662
+ where.push(`(
663
+ COALESCE(c.display_name, '') ILIKE ${searchPlaceholder}
664
+ OR COALESCE(person_record.name, '') ILIKE ${searchPlaceholder}
665
+ OR COALESCE(c.code, '') ILIKE ${searchPlaceholder}
666
+ OR COALESCE(department_record.name, '') ILIKE ${searchPlaceholder}
667
+ OR COALESCE(c.department, '') ILIKE ${searchPlaceholder}
668
+ OR COALESCE(job_title_record.name, '') ILIKE ${searchPlaceholder}
669
+ OR COALESCE(c.title, '') ILIKE ${searchPlaceholder}
670
+ OR COALESCE(s.display_name, '') ILIKE ${searchPlaceholder}
671
+ )`);
672
+ }
673
+ const result = await this.querySingle(`SELECT COUNT(DISTINCT c.id)::int AS total,
674
+ COUNT(DISTINCT c.id) FILTER (WHERE c.status = 'active')::int AS active,
675
+ COUNT(DISTINCT c.id) FILTER (WHERE c.status = 'on_leave')::int AS "onLeave",
676
+ COUNT(DISTINCT c.id) FILTER (WHERE hiring_contract.id IS NOT NULL)::int AS "withContracts"
677
+ FROM operations_collaborator c
678
+ LEFT JOIN person person_record
679
+ ON person_record.id = c.person_id
680
+ LEFT JOIN operations_collaborator_type collaborator_type
681
+ ON collaborator_type.id = c.collaborator_type_id
682
+ AND collaborator_type.deleted_at IS NULL
683
+ LEFT JOIN operations_department department_record
684
+ ON department_record.id = c.department_id
685
+ AND department_record.deleted_at IS NULL
686
+ LEFT JOIN operations_job_title job_title_record
687
+ ON job_title_record.id = c.job_title_id
688
+ AND job_title_record.deleted_at IS NULL
689
+ LEFT JOIN operations_collaborator s
690
+ ON s.id = c.supervisor_collaborator_id
691
+ LEFT JOIN LATERAL (
692
+ SELECT oc.id
693
+ FROM operations_contract oc
694
+ WHERE oc.related_collaborator_id = c.id
695
+ AND oc.deleted_at IS NULL
696
+ ORDER BY CASE WHEN oc.origin_type = 'employee_hiring' THEN 0 ELSE 1 END,
697
+ oc.created_at DESC
698
+ LIMIT 1
699
+ ) hiring_contract ON TRUE
700
+ WHERE ${where.join(' AND ')}`, params);
701
+ return {
702
+ total: Number((_b = result === null || result === void 0 ? void 0 : result.total) !== null && _b !== void 0 ? _b : 0),
703
+ active: Number((_c = result === null || result === void 0 ? void 0 : result.active) !== null && _c !== void 0 ? _c : 0),
704
+ onLeave: Number((_d = result === null || result === void 0 ? void 0 : result.onLeave) !== null && _d !== void 0 ? _d : 0),
705
+ withContracts: Number((_e = result === null || result === void 0 ? void 0 : result.withContracts) !== null && _e !== void 0 ? _e : 0),
706
+ };
473
707
  }
474
708
  async getMyCollaborator(userId) {
475
709
  const actor = await this.getActorContext(userId);
@@ -596,12 +830,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
596
830
  ''
597
831
  ) AS "timesheetProjectNames",
598
832
  tor.request_type AS "timeOffType",
599
- tor.start_date AS "timeOffStartDate",
600
- tor.end_date AS "timeOffEndDate",
833
+ tor.start_date::text AS "timeOffStartDate",
834
+ tor.end_date::text AS "timeOffEndDate",
601
835
  tor.reason AS "timeOffReason",
602
836
  sar.request_scope AS "scheduleRequestScope",
603
- sar.effective_start_date AS "scheduleStartDate",
604
- sar.effective_end_date AS "scheduleEndDate",
837
+ sar.effective_start_date::text AS "scheduleStartDate",
838
+ sar.effective_end_date::text AS "scheduleEndDate",
605
839
  sar.reason AS "scheduleReason"
606
840
  FROM operations_approval a
607
841
  JOIN operations_collaborator requester
@@ -633,8 +867,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
633
867
  tor.collaborator_id AS "collaboratorId",
634
868
  c.display_name AS "collaboratorName",
635
869
  tor.request_type AS "requestType",
636
- tor.start_date AS "startDate",
637
- tor.end_date AS "endDate",
870
+ tor.start_date::text AS "startDate",
871
+ tor.end_date::text AS "endDate",
638
872
  tor.total_days AS "totalDays",
639
873
  tor.status,
640
874
  tor.reason,
@@ -655,8 +889,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
655
889
  sar.collaborator_id AS "collaboratorId",
656
890
  c.display_name AS "collaboratorName",
657
891
  sar.request_scope AS "requestScope",
658
- sar.effective_start_date AS "effectiveStartDate",
659
- sar.effective_end_date AS "effectiveEndDate",
892
+ sar.effective_start_date::text AS "effectiveStartDate",
893
+ sar.effective_end_date::text AS "effectiveEndDate",
660
894
  sar.status,
661
895
  sar.reason,
662
896
  sar.submitted_at AS "submittedAt",
@@ -851,31 +1085,77 @@ let OperationsService = OperationsService_1 = class OperationsService {
851
1085
  });
852
1086
  return this.getCollaboratorByIdForUser(userId, collaboratorId);
853
1087
  }
854
- async listDepartments(userId) {
1088
+ async listDepartments(userId, filters = {}) {
1089
+ var _a, _b;
855
1090
  const actor = await this.getActorContext(userId);
856
1091
  this.ensureCollaborator(actor);
857
- return this.queryRows(`SELECT d.id,
858
- d.slug,
859
- d.code,
860
- d.name,
861
- d.description,
862
- CASE WHEN d.deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status,
863
- COUNT(DISTINCT c.id)::int AS "collaboratorCount",
864
- d.created_at AS "createdAt",
865
- d.updated_at AS "updatedAt"
866
- FROM operations_department d
867
- LEFT JOIN operations_collaborator c
868
- ON c.deleted_at IS NULL
869
- AND (
870
- c.department_id = d.id
871
- OR (
872
- c.department_id IS NULL
873
- AND LOWER(COALESCE(c.department, '')) = LOWER(d.name)
874
- )
1092
+ const pagination = this.shouldPaginate(filters)
1093
+ ? this.normalizePaginationParams(filters, {
1094
+ defaultSortField: 'name',
1095
+ defaultSortOrder: 'asc',
1096
+ allowedSortFields: ['name', 'code', 'createdAt', 'updatedAt'],
1097
+ })
1098
+ : null;
1099
+ const params = [];
1100
+ const where = [];
1101
+ if (filters.status === 'active') {
1102
+ where.push('d.deleted_at IS NULL');
1103
+ }
1104
+ else if (filters.status === 'inactive') {
1105
+ where.push('d.deleted_at IS NOT NULL');
1106
+ }
1107
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
1108
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
1109
+ where.push(`(
1110
+ COALESCE(d.name, '') ILIKE ${searchPlaceholder}
1111
+ OR COALESCE(d.code, '') ILIKE ${searchPlaceholder}
1112
+ OR COALESCE(d.description, '') ILIKE ${searchPlaceholder}
1113
+ )`);
1114
+ }
1115
+ const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '';
1116
+ const baseQuery = `SELECT d.id,
1117
+ d.slug,
1118
+ d.code,
1119
+ d.name,
1120
+ d.description,
1121
+ CASE WHEN d.deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status,
1122
+ COUNT(DISTINCT c.id)::int AS "collaboratorCount",
1123
+ d.created_at AS "createdAt",
1124
+ d.updated_at AS "updatedAt"
1125
+ FROM operations_department d
1126
+ LEFT JOIN operations_collaborator c
1127
+ ON c.deleted_at IS NULL
1128
+ AND (
1129
+ c.department_id = d.id
1130
+ OR (
1131
+ c.department_id IS NULL
1132
+ AND LOWER(COALESCE(c.department, '')) = LOWER(d.name)
875
1133
  )
876
- GROUP BY d.id
877
- ORDER BY CASE WHEN d.deleted_at IS NULL THEN 0 ELSE 1 END ASC,
878
- d.name ASC`);
1134
+ )
1135
+ ${whereClause}
1136
+ GROUP BY d.id`;
1137
+ if (!pagination) {
1138
+ return this.queryRows(`${baseQuery}
1139
+ ORDER BY CASE WHEN d.deleted_at IS NULL THEN 0 ELSE 1 END ASC,
1140
+ d.name ASC`, params);
1141
+ }
1142
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
1143
+ FROM operations_department d
1144
+ ${whereClause}`, params);
1145
+ const sortColumn = (_a = {
1146
+ name: 'd.name',
1147
+ code: 'd.code',
1148
+ createdAt: 'd.created_at',
1149
+ updatedAt: 'd.updated_at',
1150
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'd.name';
1151
+ const queryParams = [...params];
1152
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
1153
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
1154
+ const rows = await this.queryRows(`${baseQuery}
1155
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, d.id ASC
1156
+ LIMIT ${limitPlaceholder}
1157
+ OFFSET ${offsetPlaceholder}`, queryParams);
1158
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
879
1159
  }
880
1160
  async listJobTitles(userId) {
881
1161
  const actor = await this.getActorContext(userId);
@@ -1015,7 +1295,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
1015
1295
  return this.getDepartmentById(tx, departmentId, true);
1016
1296
  });
1017
1297
  }
1018
- async listProjects(userId) {
1298
+ async listProjects(userId, filters = {}) {
1299
+ var _a, _b;
1019
1300
  const actor = await this.getActorContext(userId);
1020
1301
  const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
1021
1302
  const assignmentParams = [];
@@ -1024,7 +1305,30 @@ let OperationsService = OperationsService_1 = class OperationsService {
1024
1305
  MAX(CASE WHEN pa.collaborator_id = ${this.param(assignmentParams, actor.collaboratorId)} THEN pa.role_label END) AS "myRoleLabel",`
1025
1306
  : `NULL::int AS "myAssignmentId",
1026
1307
  NULL::varchar AS "myRoleLabel",`;
1027
- return this.queryRows(`SELECT p.id,
1308
+ const pagination = this.shouldPaginate(filters)
1309
+ ? this.normalizePaginationParams(filters, {
1310
+ defaultSortField: 'name',
1311
+ defaultSortOrder: 'asc',
1312
+ allowedSortFields: ['name', 'code', 'clientName', 'startDate', 'endDate', 'status'],
1313
+ })
1314
+ : null;
1315
+ const params = [...assignmentParams, ...filter.params];
1316
+ const where = ['p.deleted_at IS NULL', filter.clause];
1317
+ if (filters.status && filters.status !== 'all') {
1318
+ where.push(`p.status::text = ${this.param(params, filters.status)}`);
1319
+ }
1320
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
1321
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
1322
+ where.push(`(
1323
+ COALESCE(p.name, '') ILIKE ${searchPlaceholder}
1324
+ OR COALESCE(p.code, '') ILIKE ${searchPlaceholder}
1325
+ OR COALESCE(p.client_name, '') ILIKE ${searchPlaceholder}
1326
+ OR COALESCE(c.name, '') ILIKE ${searchPlaceholder}
1327
+ OR COALESCE(m.display_name, '') ILIKE ${searchPlaceholder}
1328
+ )`);
1329
+ }
1330
+ const whereClause = where.join(' AND ');
1331
+ const baseQuery = `SELECT p.id,
1028
1332
  p.contract_id AS "contractId",
1029
1333
  p.manager_collaborator_id AS "managerCollaboratorId",
1030
1334
  p.code,
@@ -1049,28 +1353,49 @@ let OperationsService = OperationsService_1 = class OperationsService {
1049
1353
  ON pa.project_id = p.id
1050
1354
  AND pa.deleted_at IS NULL
1051
1355
  AND pa.status IN ('planned', 'active')
1052
- WHERE p.deleted_at IS NULL AND ${filter.clause}
1053
- GROUP BY p.id, c.id, m.id
1054
- ORDER BY p.name ASC`, [...assignmentParams, ...filter.params]);
1356
+ WHERE ${whereClause}
1357
+ GROUP BY p.id, c.id, m.id`;
1358
+ if (!pagination) {
1359
+ return this.queryRows(`${baseQuery} ORDER BY p.name ASC`, params);
1360
+ }
1361
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
1362
+ FROM operations_project p
1363
+ LEFT JOIN operations_contract c ON c.id = p.contract_id
1364
+ LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
1365
+ WHERE ${whereClause}`, params);
1366
+ const sortColumn = (_a = {
1367
+ name: 'p.name',
1368
+ code: 'p.code',
1369
+ clientName: 'p.client_name',
1370
+ startDate: 'p.start_date',
1371
+ endDate: 'p.end_date',
1372
+ status: 'p.status',
1373
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'p.name';
1374
+ const queryParams = [...params];
1375
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
1376
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
1377
+ const rows = await this.queryRows(`${baseQuery}
1378
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, p.id ASC
1379
+ LIMIT ${limitPlaceholder}
1380
+ OFFSET ${offsetPlaceholder}`, queryParams);
1381
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
1055
1382
  }
1056
1383
  async listProjectOptions(userId, paginationParams) {
1057
1384
  var _a, _b;
1058
1385
  const actor = await this.getActorContext(userId);
1059
1386
  this.ensureCollaborator(actor);
1060
- if (!actor.collaboratorId) {
1061
- throw new common_1.BadRequestException('Collaborator context is required.');
1062
- }
1063
1387
  const pagination = this.normalizePaginationParams(paginationParams, {
1064
1388
  defaultSortField: 'name',
1065
1389
  defaultSortOrder: 'asc',
1066
1390
  allowedSortFields: ['name', 'code', 'clientName', 'startDate', 'endDate'],
1067
1391
  });
1068
- const params = [actor.collaboratorId];
1392
+ const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
1393
+ const params = [...filter.params];
1069
1394
  const filters = [
1070
1395
  'p.deleted_at IS NULL',
1071
1396
  'pa.deleted_at IS NULL',
1072
- `pa.collaborator_id = $1`,
1073
1397
  `pa.status IN ('planned', 'active')`,
1398
+ filter.clause,
1074
1399
  ];
1075
1400
  if (pagination.search) {
1076
1401
  const searchPlaceholder = this.param(params, `%${pagination.search}%`);
@@ -1120,21 +1445,25 @@ let OperationsService = OperationsService_1 = class OperationsService {
1120
1445
  var _a, _b;
1121
1446
  const actor = await this.getActorContext(userId);
1122
1447
  this.ensureCollaborator(actor);
1123
- if (!actor.collaboratorId) {
1124
- throw new common_1.BadRequestException('Collaborator context is required.');
1125
- }
1126
1448
  const pagination = this.normalizePaginationParams(paginationParams, {
1127
1449
  defaultSortField: 'name',
1128
1450
  defaultSortOrder: 'asc',
1129
1451
  allowedSortFields: ['name', 'projectName', 'status', 'createdAt'],
1130
1452
  });
1131
- const params = [actor.collaboratorId];
1453
+ const projectFilter = this.buildIdFilter(actor.visibleProjectIds, 'COALESCE(t.project_id, pa.project_id)', actor.isDirector);
1454
+ const params = [...projectFilter.params];
1132
1455
  const filters = [
1133
1456
  't.deleted_at IS NULL',
1134
- 'pa.deleted_at IS NULL',
1135
1457
  'p.deleted_at IS NULL',
1136
- `pa.collaborator_id = $1`,
1137
- `pa.status IN ('planned', 'active')`,
1458
+ projectFilter.clause,
1459
+ `(
1460
+ t.project_id IS NOT NULL
1461
+ OR (
1462
+ pa.id IS NOT NULL
1463
+ AND pa.deleted_at IS NULL
1464
+ AND pa.status IN ('planned', 'active')
1465
+ )
1466
+ )`,
1138
1467
  ];
1139
1468
  if (pagination.search) {
1140
1469
  const searchPlaceholder = this.param(params, `%${pagination.search}%`);
@@ -1149,18 +1478,18 @@ let OperationsService = OperationsService_1 = class OperationsService {
1149
1478
  filters.push(`pa.id = ${this.param(params, paginationParams.projectAssignmentId)}`);
1150
1479
  }
1151
1480
  if (paginationParams.projectId) {
1152
- filters.push(`pa.project_id = ${this.param(params, paginationParams.projectId)}`);
1481
+ filters.push(`COALESCE(t.project_id, pa.project_id) = ${this.param(params, paginationParams.projectId)}`);
1153
1482
  }
1154
1483
  if (paginationParams.status) {
1155
- filters.push(`t.status = ${this.param(params, paginationParams.status)}`);
1484
+ filters.push(`t.status::text = ${this.param(params, paginationParams.status)}`);
1156
1485
  }
1157
1486
  const whereClause = filters.join(' AND ');
1158
1487
  const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
1159
1488
  FROM operations_task t
1160
- JOIN operations_project_assignment pa
1489
+ LEFT JOIN operations_project_assignment pa
1161
1490
  ON pa.id = t.project_assignment_id
1162
1491
  JOIN operations_project p
1163
- ON p.id = pa.project_id
1492
+ ON p.id = COALESCE(t.project_id, pa.project_id)
1164
1493
  WHERE ${whereClause}`, params);
1165
1494
  const sortColumn = (_a = {
1166
1495
  name: 't.name',
@@ -1175,16 +1504,16 @@ let OperationsService = OperationsService_1 = class OperationsService {
1175
1504
  t.name,
1176
1505
  t.description,
1177
1506
  t.status,
1178
- pa.project_id AS "projectId",
1507
+ COALESCE(t.project_id, pa.project_id) AS "projectId",
1179
1508
  pa.id AS "projectAssignmentId",
1180
1509
  p.name AS "projectName",
1181
1510
  p.code AS "projectCode",
1182
1511
  t.created_at AS "createdAt"
1183
1512
  FROM operations_task t
1184
- JOIN operations_project_assignment pa
1513
+ LEFT JOIN operations_project_assignment pa
1185
1514
  ON pa.id = t.project_assignment_id
1186
1515
  JOIN operations_project p
1187
- ON p.id = pa.project_id
1516
+ ON p.id = COALESCE(t.project_id, pa.project_id)
1188
1517
  WHERE ${whereClause}
1189
1518
  ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, t.id ASC
1190
1519
  LIMIT ${limitPlaceholder}
@@ -1463,8 +1792,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
1463
1792
  const resolvedTask = data.taskId
1464
1793
  ? await this.getOwnedTaskRecord(tx, actor.collaboratorId, data.taskId)
1465
1794
  : null;
1466
- if (resolvedTask && resolvedTask.projectAssignmentId !== assignment.id) {
1467
- throw new common_1.BadRequestException('The selected task does not belong to the chosen project assignment.');
1795
+ if (resolvedTask && resolvedTask.projectId !== assignment.projectId) {
1796
+ throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
1468
1797
  }
1469
1798
  const activityLabel = (_e = (_d = (_c = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _c !== void 0 ? _c : taskLabel) !== null && _d !== void 0 ? _d : assignment.roleLabel) !== null && _e !== void 0 ? _e : assignment.projectName;
1470
1799
  if (!activityLabel) {
@@ -1500,6 +1829,68 @@ let OperationsService = OperationsService_1 = class OperationsService {
1500
1829
  });
1501
1830
  return this.getTimesheetEntryByIdForActor(actor, createdEntryId);
1502
1831
  }
1832
+ async updateTimesheetEntry(userId, entryId, data) {
1833
+ var _a;
1834
+ const actor = await this.getActorContext(userId);
1835
+ this.ensureCollaborator(actor);
1836
+ this.requireFields(data, ['workDate', 'duration']);
1837
+ if (!actor.collaboratorId && !actor.isDirector) {
1838
+ throw new common_1.BadRequestException('Collaborator context is required.');
1839
+ }
1840
+ const entry = await this.getTimesheetEntryByIdForActor(actor, entryId);
1841
+ if (!actor.isDirector && entry.collaboratorId !== actor.collaboratorId) {
1842
+ throw new common_1.ForbiddenException('Only the entry owner can update this timesheet entry.');
1843
+ }
1844
+ if (!['draft', 'rejected'].includes(entry.status)) {
1845
+ throw new common_1.BadRequestException('Only draft or rejected timesheet entries can be edited.');
1846
+ }
1847
+ const collaboratorId = actor.isDirector
1848
+ ? entry.collaboratorId
1849
+ : actor.collaboratorId;
1850
+ const durationMinutes = this.normalizeDurationMinutes(data.duration, data.unit);
1851
+ const taskLabel = (_a = this.normalizeOptionalText(data.taskName)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(data.activityLabel);
1852
+ const targetWeek = this.getWorkWeekRange(data.workDate);
1853
+ const isSameWeek = entry.weekStartDate === targetWeek.weekStartDate &&
1854
+ entry.weekEndDate === targetWeek.weekEndDate;
1855
+ await this.prisma.$transaction(async (tx) => {
1856
+ var _a, _b, _c, _d, _e, _f, _g;
1857
+ const assignment = await this.resolveOwnedProjectAssignment(tx, collaboratorId, {
1858
+ projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
1859
+ projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
1860
+ });
1861
+ const resolvedTask = data.taskId
1862
+ ? await this.getOwnedTaskRecord(tx, collaboratorId, data.taskId)
1863
+ : null;
1864
+ if (resolvedTask && resolvedTask.projectId !== assignment.projectId) {
1865
+ throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
1866
+ }
1867
+ const activityLabel = (_e = (_d = (_c = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _c !== void 0 ? _c : taskLabel) !== null && _d !== void 0 ? _d : assignment.roleLabel) !== null && _e !== void 0 ? _e : assignment.projectName;
1868
+ if (!activityLabel) {
1869
+ throw new common_1.BadRequestException('A task is required for the timesheet entry.');
1870
+ }
1871
+ const targetTimesheetId = isSameWeek
1872
+ ? entry.timesheetId
1873
+ : await this.getOrCreateTimesheetForWorkDate(tx, collaboratorId, data.workDate);
1874
+ await tx.$executeRawUnsafe(`UPDATE operations_timesheet_entry
1875
+ SET timesheet_id = $1,
1876
+ project_assignment_id = $2,
1877
+ task_id = $3,
1878
+ activity_label = $4,
1879
+ work_date = $5::date,
1880
+ duration_minutes = $6,
1881
+ hours = $7,
1882
+ description = $8,
1883
+ updated_at = NOW()
1884
+ WHERE id = $9
1885
+ AND deleted_at IS NULL`, targetTimesheetId, assignment.id, (_g = (_f = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.id) !== null && _f !== void 0 ? _f : data.taskId) !== null && _g !== void 0 ? _g : null, activityLabel, data.workDate, durationMinutes, Number((durationMinutes / 60).toFixed(2)), this.normalizeOptionalText(data.description), entryId);
1886
+ await this.refreshTimesheetTotal(tx, entry.timesheetId);
1887
+ if (targetTimesheetId !== entry.timesheetId) {
1888
+ await this.refreshTimesheetTotal(tx, targetTimesheetId);
1889
+ }
1890
+ await this.cleanupEmptyEditableTimesheet(tx, entry.timesheetId);
1891
+ });
1892
+ return this.getTimesheetEntryByIdForActor(actor, entryId);
1893
+ }
1503
1894
  async removeTimesheetEntry(userId, entryId) {
1504
1895
  const actor = await this.getActorContext(userId);
1505
1896
  this.ensureCollaborator(actor);
@@ -1520,6 +1911,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1520
1911
  WHERE id = $1
1521
1912
  AND deleted_at IS NULL`, entryId);
1522
1913
  await this.refreshTimesheetTotal(tx, entry.timesheetId);
1914
+ await this.cleanupEmptyEditableTimesheet(tx, entry.timesheetId);
1523
1915
  });
1524
1916
  return { success: true };
1525
1917
  }
@@ -1533,7 +1925,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1533
1925
  this.ensureDirector(actor);
1534
1926
  this.requireFields(data, ['code', 'name']);
1535
1927
  const createdProjectId = await this.prisma.$transaction(async (tx) => {
1536
- 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;
1928
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1537
1929
  const created = await tx.$queryRawUnsafe(`INSERT INTO operations_project (
1538
1930
  contract_id,
1539
1931
  manager_collaborator_id,
@@ -1562,28 +1954,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1562
1954
  if ((_o = data.teamAssignments) === null || _o === void 0 ? void 0 : _o.length) {
1563
1955
  await this.replaceProjectAssignments(tx, projectId, data.teamAssignments);
1564
1956
  }
1565
- if (!data.contractId && data.autoGenerateContractDraft !== false) {
1566
- const contractId = await this.createProjectContractDraft(tx, actor.userId, {
1567
- projectId,
1568
- contractTemplateId: (_p = data.contractTemplateId) !== null && _p !== void 0 ? _p : null,
1569
- projectCode: data.code,
1570
- projectName: data.name,
1571
- clientName: (_q = data.clientName) !== null && _q !== void 0 ? _q : data.name,
1572
- managerCollaboratorId: (_r = data.managerCollaboratorId) !== null && _r !== void 0 ? _r : null,
1573
- startDate: (_s = data.startDate) !== null && _s !== void 0 ? _s : null,
1574
- endDate: (_t = data.endDate) !== null && _t !== void 0 ? _t : null,
1575
- budgetAmount: (_u = data.budgetAmount) !== null && _u !== void 0 ? _u : null,
1576
- monthlyHourCap: (_v = data.monthlyHourCap) !== null && _v !== void 0 ? _v : null,
1577
- billingModel: (_w = data.billingModel) !== null && _w !== void 0 ? _w : 'time_and_material',
1578
- contractCode: (_x = data.contractCode) !== null && _x !== void 0 ? _x : null,
1579
- contractName: (_y = data.contractName) !== null && _y !== void 0 ? _y : null,
1580
- description: (_0 = (_z = data.contractDescription) !== null && _z !== void 0 ? _z : data.summary) !== null && _0 !== void 0 ? _0 : null,
1581
- });
1582
- await tx.$executeRawUnsafe(`UPDATE operations_project
1583
- SET contract_id = $1,
1584
- updated_at = NOW()
1585
- WHERE id = $2`, contractId, projectId);
1586
- }
1587
1957
  return projectId;
1588
1958
  });
1589
1959
  return this.getProjectById(userId, createdProjectId);
@@ -1608,7 +1978,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1608
1978
  this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
1609
1979
  this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
1610
1980
  await this.prisma.$transaction(async (tx) => {
1611
- 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;
1981
+ var _a, _b;
1612
1982
  if (updates.length) {
1613
1983
  params.push(projectId);
1614
1984
  await tx.$executeRawUnsafe(`UPDATE operations_project
@@ -1622,30 +1992,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1622
1992
  const nextContractId = data.contractId !== undefined
1623
1993
  ? data.contractId
1624
1994
  : ((_b = (_a = currentProject.relatedContract) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
1625
- const shouldGenerateDraft = !nextContractId && data.autoGenerateContractDraft === true;
1626
- if (shouldGenerateDraft) {
1627
- const contractId = await this.createProjectContractDraft(tx, actor.userId, {
1628
- projectId,
1629
- contractTemplateId: (_c = data.contractTemplateId) !== null && _c !== void 0 ? _c : null,
1630
- projectCode: (_d = data.code) !== null && _d !== void 0 ? _d : currentProject.code,
1631
- projectName: (_e = data.name) !== null && _e !== void 0 ? _e : currentProject.name,
1632
- clientName: (_g = (_f = data.clientName) !== null && _f !== void 0 ? _f : currentProject.clientName) !== null && _g !== void 0 ? _g : currentProject.name,
1633
- managerCollaboratorId: (_j = (_h = data.managerCollaboratorId) !== null && _h !== void 0 ? _h : currentProject.managerCollaboratorId) !== null && _j !== void 0 ? _j : null,
1634
- startDate: (_l = (_k = data.startDate) !== null && _k !== void 0 ? _k : currentProject.startDate) !== null && _l !== void 0 ? _l : null,
1635
- endDate: (_o = (_m = data.endDate) !== null && _m !== void 0 ? _m : currentProject.endDate) !== null && _o !== void 0 ? _o : null,
1636
- budgetAmount: (_q = (_p = data.budgetAmount) !== null && _p !== void 0 ? _p : currentProject.budgetAmount) !== null && _q !== void 0 ? _q : null,
1637
- monthlyHourCap: (_t = (_r = data.monthlyHourCap) !== null && _r !== void 0 ? _r : (_s = currentProject.relatedContract) === null || _s === void 0 ? void 0 : _s.monthlyHourCap) !== null && _t !== void 0 ? _t : null,
1638
- billingModel: (_w = (_u = data.billingModel) !== null && _u !== void 0 ? _u : (_v = currentProject.relatedContract) === null || _v === void 0 ? void 0 : _v.billingModel) !== null && _w !== void 0 ? _w : 'time_and_material',
1639
- contractCode: (_z = (_x = data.contractCode) !== null && _x !== void 0 ? _x : (_y = currentProject.relatedContract) === null || _y === void 0 ? void 0 : _y.code) !== null && _z !== void 0 ? _z : null,
1640
- contractName: (_2 = (_0 = data.contractName) !== null && _0 !== void 0 ? _0 : (_1 = currentProject.relatedContract) === null || _1 === void 0 ? void 0 : _1.name) !== null && _2 !== void 0 ? _2 : null,
1641
- description: (_7 = (_6 = (_5 = (_3 = data.contractDescription) !== null && _3 !== void 0 ? _3 : (_4 = currentProject.relatedContract) === null || _4 === void 0 ? void 0 : _4.description) !== null && _5 !== void 0 ? _5 : data.summary) !== null && _6 !== void 0 ? _6 : currentProject.summary) !== null && _7 !== void 0 ? _7 : null,
1642
- });
1643
- await tx.$executeRawUnsafe(`UPDATE operations_project
1644
- SET contract_id = $1,
1645
- updated_at = NOW()
1646
- WHERE id = $2`, contractId, projectId);
1647
- }
1648
- else if (nextContractId &&
1995
+ if (nextContractId &&
1649
1996
  (data.monthlyHourCap !== undefined || data.billingModel !== undefined)) {
1650
1997
  const contractUpdates = [];
1651
1998
  const contractParams = [];
@@ -1664,149 +2011,54 @@ let OperationsService = OperationsService_1 = class OperationsService {
1664
2011
  });
1665
2012
  return this.getProjectById(userId, projectId);
1666
2013
  }
1667
- async listContractTemplates(userId) {
1668
- const actor = await this.getActorContext(userId);
1669
- this.ensureDirector(actor);
1670
- return this.queryRows(`SELECT t.id,
1671
- t.slug,
1672
- t.code,
1673
- t.name,
1674
- t.description,
1675
- t.contract_category AS "contractCategory",
1676
- t.contract_type AS "contractType",
1677
- t.billing_model AS "billingModel",
1678
- t.signature_status AS "signatureStatus",
1679
- t.is_active AS "isActive",
1680
- t.status,
1681
- t.content_html AS "contentHtml",
1682
- t.created_at AS "createdAt",
1683
- t.updated_at AS "updatedAt",
1684
- COUNT(DISTINCT c.id)::int AS "usageCount"
1685
- FROM operations_contract_template t
1686
- LEFT JOIN operations_contract c
1687
- ON c.contract_template_id = t.id
1688
- AND c.deleted_at IS NULL
1689
- WHERE t.deleted_at IS NULL
1690
- GROUP BY t.id
1691
- ORDER BY CASE
1692
- WHEN t.status = 'active' THEN 0
1693
- WHEN t.status = 'draft' THEN 1
1694
- WHEN t.status = 'inactive' THEN 2
1695
- ELSE 3
1696
- END,
1697
- t.name ASC`);
1698
- }
1699
- async getContractTemplateById(userId, templateId) {
1700
- const actor = await this.getActorContext(userId);
1701
- this.ensureDirector(actor);
1702
- return this.getContractTemplateRecord(this.prisma, templateId);
1703
- }
1704
- async createContractTemplate(userId, data) {
2014
+ async listContracts(userId, filters = {}) {
2015
+ var _a, _b;
1705
2016
  const actor = await this.getActorContext(userId);
1706
- this.ensureDirector(actor);
1707
- const name = this.normalizeOptionalText(data.name);
1708
- if (!name) {
1709
- throw new common_1.BadRequestException('Contract template name is required.');
2017
+ const params = [];
2018
+ const accessClause = actor.isDirector
2019
+ ? 'c.deleted_at IS NULL'
2020
+ : `c.deleted_at IS NULL AND (
2021
+ c.related_collaborator_id = ANY(${this.param(params, actor.visibleCollaboratorIds)}::int[])
2022
+ OR EXISTS (
2023
+ SELECT 1
2024
+ FROM operations_project p_access
2025
+ WHERE p_access.contract_id = c.id
2026
+ AND p_access.deleted_at IS NULL
2027
+ AND p_access.id = ANY(${this.param(params, actor.visibleProjectIds)}::int[])
2028
+ )
2029
+ )`;
2030
+ const pagination = this.shouldPaginate(filters)
2031
+ ? this.normalizePaginationParams(filters, {
2032
+ defaultSortField: 'name',
2033
+ defaultSortOrder: 'asc',
2034
+ allowedSortFields: ['name', 'code', 'clientName', 'startDate', 'endDate', 'status'],
2035
+ })
2036
+ : null;
2037
+ const where = [accessClause];
2038
+ if (filters.status && filters.status !== 'all') {
2039
+ where.push(`c.status::text = ${this.param(params, filters.status)}`);
1710
2040
  }
1711
- return this.prisma.$transaction(async (tx) => {
1712
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1713
- await this.assertContractTemplateNameAvailable(tx, name);
1714
- const nextCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null;
1715
- if (nextCode) {
1716
- await this.assertContractTemplateCodeAvailable(tx, nextCode);
1717
- }
1718
- const nextStatus = (_c = data.status) !== null && _c !== void 0 ? _c : 'active';
1719
- const isActive = (_d = data.isActive) !== null && _d !== void 0 ? _d : !['inactive', 'archived'].includes(nextStatus);
1720
- const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_contract_template (
1721
- slug,
1722
- code,
1723
- name,
1724
- description,
1725
- contract_category,
1726
- contract_type,
1727
- billing_model,
1728
- signature_status,
1729
- is_active,
1730
- status,
1731
- content_html,
1732
- created_at,
1733
- updated_at
1734
- ) VALUES (
1735
- $1, $2, $3, $4,
1736
- $5, $6, $7, $8, $9, $10, $11,
1737
- NOW(), NOW()
1738
- )
1739
- 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)));
1740
- const templateId = (_j = created[0]) === null || _j === void 0 ? void 0 : _j.id;
1741
- if (!templateId) {
1742
- throw new common_1.BadRequestException('Unable to create the contract template.');
1743
- }
1744
- return this.getContractTemplateRecord(tx, templateId, true);
1745
- });
1746
- }
1747
- async updateContractTemplate(userId, templateId, data) {
1748
- const actor = await this.getActorContext(userId);
1749
- this.ensureDirector(actor);
1750
- return this.prisma.$transaction(async (tx) => {
1751
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
1752
- const current = await this.getContractTemplateRecord(tx, templateId, true);
1753
- const nextName = data.name !== undefined
1754
- ? this.normalizeOptionalText(data.name)
1755
- : current.name;
1756
- if (!nextName) {
1757
- throw new common_1.BadRequestException('Contract template name is required.');
1758
- }
1759
- if (String(nextName).toLowerCase() !== String(current.name).toLowerCase()) {
1760
- await this.assertContractTemplateNameAvailable(tx, nextName, templateId);
1761
- }
1762
- const nextCode = data.code !== undefined
1763
- ? (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null
1764
- : ((_c = current.code) !== null && _c !== void 0 ? _c : null);
1765
- if (nextCode) {
1766
- await this.assertContractTemplateCodeAvailable(tx, nextCode, templateId);
1767
- }
1768
- const nextStatus = (_e = (_d = data.status) !== null && _d !== void 0 ? _d : current.status) !== null && _e !== void 0 ? _e : 'active';
1769
- const nextIsActive = (_f = data.isActive) !== null && _f !== void 0 ? _f : !['inactive', 'archived'].includes(nextStatus);
1770
- const nextSlug = String(nextName).toLowerCase() !== String(current.name).toLowerCase()
1771
- ? await this.generateUniqueContractTemplateSlug(tx, nextName, templateId)
1772
- : current.slug;
1773
- await tx.$executeRawUnsafe(`UPDATE operations_contract_template
1774
- SET slug = $1,
1775
- code = $2,
1776
- name = $3,
1777
- description = $4,
1778
- contract_category = $5,
1779
- contract_type = $6,
1780
- billing_model = $7,
1781
- signature_status = $8,
1782
- is_active = $9,
1783
- status = $10,
1784
- content_html = $11,
1785
- updated_at = NOW()
1786
- WHERE id = $12`, nextSlug, nextCode, nextName, data.description !== undefined
1787
- ? this.normalizeOptionalText(data.description)
1788
- : ((_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
1789
- ? this.normalizeOptionalText(data.contentHtml)
1790
- : ((_r = current.contentHtml) !== null && _r !== void 0 ? _r : null), templateId);
1791
- return this.getContractTemplateRecord(tx, templateId, true);
1792
- });
1793
- }
1794
- async listContracts(userId) {
1795
- const actor = await this.getActorContext(userId);
1796
- const params = [];
1797
- const accessClause = actor.isDirector
1798
- ? 'c.deleted_at IS NULL'
1799
- : `c.deleted_at IS NULL AND (
1800
- c.related_collaborator_id = ANY(${this.param(params, actor.visibleCollaboratorIds)}::int[])
1801
- OR EXISTS (
1802
- SELECT 1
1803
- FROM operations_project p_access
1804
- WHERE p_access.contract_id = c.id
1805
- AND p_access.deleted_at IS NULL
1806
- AND p_access.id = ANY(${this.param(params, actor.visibleProjectIds)}::int[])
1807
- )
1808
- )`;
1809
- return this.queryRows(`SELECT c.id,
2041
+ if (filters.contractCategory && filters.contractCategory !== 'all') {
2042
+ where.push(`c.contract_category = ${this.param(params, filters.contractCategory)}::text::operations_contract_contract_category_70d553ea09_enum`);
2043
+ }
2044
+ if (filters.originType && filters.originType !== 'all') {
2045
+ where.push(`c.origin_type = ${this.param(params, filters.originType)}::text::operations_contract_origin_type_07a7cc2b5d_enum`);
2046
+ }
2047
+ if (filters.isActive === 'true' || filters.isActive === 'false') {
2048
+ where.push(`c.is_active = ${this.param(params, filters.isActive === 'true')}`);
2049
+ }
2050
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
2051
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
2052
+ where.push(`(
2053
+ COALESCE(c.name, '') ILIKE ${searchPlaceholder}
2054
+ OR COALESCE(c.code, '') ILIKE ${searchPlaceholder}
2055
+ OR COALESCE(c.client_name, '') ILIKE ${searchPlaceholder}
2056
+ OR COALESCE(m.display_name, '') ILIKE ${searchPlaceholder}
2057
+ OR COALESCE(linked.display_name, '') ILIKE ${searchPlaceholder}
2058
+ )`);
2059
+ }
2060
+ const whereClause = where.join(' AND ');
2061
+ const baseQuery = `SELECT c.id,
1810
2062
  c.code,
1811
2063
  c.name,
1812
2064
  c.contract_category AS "contractCategory",
@@ -1817,10 +2069,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1817
2069
  c.billing_model AS "billingModel",
1818
2070
  c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
1819
2071
  c.related_collaborator_id AS "relatedCollaboratorId",
1820
- c.contract_template_id AS "contractTemplateId",
1821
- template.name AS "contractTemplateName",
1822
- template.slug AS "contractTemplateSlug",
1823
- template.code AS "contractTemplateCode",
1824
2072
  c.origin_type AS "originType",
1825
2073
  c.origin_id AS "originId",
1826
2074
  c.start_date AS "startDate",
@@ -1836,17 +2084,11 @@ let OperationsService = OperationsService_1 = class OperationsService {
1836
2084
  m.display_name AS "accountManagerName",
1837
2085
  linked.display_name AS "relatedCollaboratorName",
1838
2086
  MAX(COALESCE(primary_party.display_name, linked.display_name, c.client_name)) AS "mainRelatedPartyName",
1839
- MAX(COALESCE(financials.value_amount, 0)) AS "valueAmount",
1840
- MAX(COALESCE(financials.payment_amount, 0)) AS "paymentAmount",
1841
- MAX(COALESCE(financials.revenue_amount, 0)) AS "revenueAmount",
1842
- MAX(COALESCE(financials.fine_amount, 0)) AS "fineAmount",
1843
2087
  MAX(COALESCE(pdf_document.file_name, '')) AS "currentPdfFileName",
1844
2088
  COUNT(DISTINCT p.id)::int AS "projectCount"
1845
2089
  FROM operations_contract c
1846
2090
  LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
1847
2091
  LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
1848
- LEFT JOIN operations_contract_template template
1849
- ON template.id = c.contract_template_id
1850
2092
  LEFT JOIN LATERAL (
1851
2093
  SELECT cp.display_name
1852
2094
  FROM operations_contract_party cp
@@ -1855,16 +2097,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1855
2097
  ORDER BY cp.is_primary DESC, cp.id ASC
1856
2098
  LIMIT 1
1857
2099
  ) primary_party ON TRUE
1858
- LEFT JOIN LATERAL (
1859
- SELECT
1860
- SUM(CASE WHEN term_type = 'value' THEN amount ELSE 0 END) AS value_amount,
1861
- SUM(CASE WHEN term_type = 'payment' THEN amount ELSE 0 END) AS payment_amount,
1862
- SUM(CASE WHEN term_type = 'revenue' THEN amount ELSE 0 END) AS revenue_amount,
1863
- SUM(CASE WHEN term_type = 'fine' THEN amount ELSE 0 END) AS fine_amount
1864
- FROM operations_contract_financial_term ft
1865
- WHERE ft.contract_id = c.id
1866
- AND ft.deleted_at IS NULL
1867
- ) financials ON TRUE
1868
2100
  LEFT JOIN LATERAL (
1869
2101
  SELECT cd.file_name
1870
2102
  FROM operations_contract_document cd
@@ -1878,9 +2110,71 @@ let OperationsService = OperationsService_1 = class OperationsService {
1878
2110
  LEFT JOIN operations_project p
1879
2111
  ON p.contract_id = c.id
1880
2112
  AND p.deleted_at IS NULL
1881
- WHERE ${accessClause}
1882
- GROUP BY c.id, m.id, linked.id, template.id
1883
- ORDER BY COALESCE(c.name, c.code, CONCAT('draft-', c.id)) ASC`, params);
2113
+ WHERE ${whereClause}
2114
+ GROUP BY c.id, m.id, linked.id`;
2115
+ if (!pagination) {
2116
+ return this.queryRows(`${baseQuery}
2117
+ ORDER BY COALESCE(c.name, c.code, CONCAT('draft-', c.id)) ASC`, params);
2118
+ }
2119
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
2120
+ FROM operations_contract c
2121
+ LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
2122
+ LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
2123
+ WHERE ${whereClause}`, params);
2124
+ const sortColumn = (_a = {
2125
+ name: `COALESCE(c.name, c.code, CONCAT('draft-', c.id))`,
2126
+ code: 'c.code',
2127
+ clientName: 'c.client_name',
2128
+ startDate: 'c.start_date',
2129
+ endDate: 'c.end_date',
2130
+ status: 'c.status',
2131
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : `COALESCE(c.name, c.code, CONCAT('draft-', c.id))`;
2132
+ const queryParams = [...params];
2133
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
2134
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
2135
+ const rows = await this.queryRows(`${baseQuery}
2136
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, c.id ASC
2137
+ LIMIT ${limitPlaceholder}
2138
+ OFFSET ${offsetPlaceholder}`, queryParams);
2139
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
2140
+ }
2141
+ async getContractStats(userId) {
2142
+ var _a, _b, _c, _d;
2143
+ const actor = await this.getActorContext(userId);
2144
+ const params = [];
2145
+ const accessClause = actor.isDirector
2146
+ ? 'c.deleted_at IS NULL'
2147
+ : `c.deleted_at IS NULL AND (
2148
+ c.related_collaborator_id = ANY(${this.param(params, actor.visibleCollaboratorIds)}::int[])
2149
+ OR EXISTS (
2150
+ SELECT 1
2151
+ FROM operations_project p_access
2152
+ WHERE p_access.contract_id = c.id
2153
+ AND p_access.deleted_at IS NULL
2154
+ AND p_access.id = ANY(${this.param(params, actor.visibleProjectIds)}::int[])
2155
+ )
2156
+ )`;
2157
+ const stats = await this.querySingle(`SELECT COUNT(*)::int AS total,
2158
+ COUNT(*) FILTER (WHERE c.is_active = true)::int AS active,
2159
+ COUNT(*) FILTER (WHERE c.is_active = false)::int AS inactive,
2160
+ COUNT(*) FILTER (
2161
+ WHERE EXISTS (
2162
+ SELECT 1
2163
+ FROM operations_contract_document cd
2164
+ WHERE cd.contract_id = c.id
2165
+ AND cd.deleted_at IS NULL
2166
+ AND cd.is_current = true
2167
+ AND cd.document_type IN ('source_upload', 'generated_pdf')
2168
+ )
2169
+ )::int AS "withFile"
2170
+ FROM operations_contract c
2171
+ WHERE ${accessClause}`, params);
2172
+ return {
2173
+ total: Number((_a = stats === null || stats === void 0 ? void 0 : stats.total) !== null && _a !== void 0 ? _a : 0),
2174
+ active: Number((_b = stats === null || stats === void 0 ? void 0 : stats.active) !== null && _b !== void 0 ? _b : 0),
2175
+ inactive: Number((_c = stats === null || stats === void 0 ? void 0 : stats.inactive) !== null && _c !== void 0 ? _c : 0),
2176
+ withFile: Number((_d = stats === null || stats === void 0 ? void 0 : stats.withFile) !== null && _d !== void 0 ? _d : 0),
2177
+ };
1884
2178
  }
1885
2179
  async getContractById(userId, contractId) {
1886
2180
  var _a, _b, _c;
@@ -1896,10 +2190,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1896
2190
  c.billing_model AS "billingModel",
1897
2191
  c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
1898
2192
  c.related_collaborator_id AS "relatedCollaboratorId",
1899
- c.contract_template_id AS "contractTemplateId",
1900
- template.name AS "contractTemplateName",
1901
- template.slug AS "contractTemplateSlug",
1902
- template.code AS "contractTemplateCode",
1903
2193
  c.origin_type AS "originType",
1904
2194
  c.origin_id AS "originId",
1905
2195
  c.start_date AS "startDate",
@@ -1918,8 +2208,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1918
2208
  FROM operations_contract c
1919
2209
  LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
1920
2210
  LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
1921
- LEFT JOIN operations_contract_template template
1922
- ON template.id = c.contract_template_id
1923
2211
  WHERE c.id = $1
1924
2212
  AND c.deleted_at IS NULL`, [contractId]);
1925
2213
  if (!contract) {
@@ -1946,7 +2234,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1946
2234
  throw new common_1.ForbiddenException('You do not have access to this contract.');
1947
2235
  }
1948
2236
  }
1949
- const [projects, scheduleSummary, parties, signatures, financialTerms, documents, revisions, history] = await Promise.all([
2237
+ const [projects, scheduleSummary, parties, documents, history] = await Promise.all([
1950
2238
  this.queryRows(`SELECT id, code, name, status
1951
2239
  FROM operations_project
1952
2240
  WHERE contract_id = $1
@@ -1975,27 +2263,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
1975
2263
  WHERE contract_id = $1
1976
2264
  AND deleted_at IS NULL
1977
2265
  ORDER BY is_primary DESC, id ASC`, [contractId]),
1978
- this.queryRows(`SELECT id,
1979
- signer_name AS "signerName",
1980
- signer_role AS "signerRole",
1981
- signer_email AS "signerEmail",
1982
- signer_status AS status,
1983
- signed_at AS "signedAt"
1984
- FROM operations_contract_signature
1985
- WHERE contract_id = $1
1986
- AND deleted_at IS NULL
1987
- ORDER BY id ASC`, [contractId]),
1988
- this.queryRows(`SELECT id,
1989
- term_type AS "termType",
1990
- label,
1991
- amount,
1992
- recurrence,
1993
- due_day AS "dueDay",
1994
- notes
1995
- FROM operations_contract_financial_term
1996
- WHERE contract_id = $1
1997
- AND deleted_at IS NULL
1998
- ORDER BY id ASC`, [contractId]),
1999
2266
  this.queryRows(`SELECT id,
2000
2267
  document_type AS "documentType",
2001
2268
  file_id AS "fileId",
@@ -2011,16 +2278,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2011
2278
  WHERE contract_id = $1
2012
2279
  AND deleted_at IS NULL
2013
2280
  ORDER BY is_current DESC, id DESC`, [contractId]),
2014
- this.queryRows(`SELECT id,
2015
- revision_type AS "revisionType",
2016
- title,
2017
- effective_date AS "effectiveDate",
2018
- status,
2019
- summary
2020
- FROM operations_contract_revision
2021
- WHERE contract_id = $1
2022
- AND deleted_at IS NULL
2023
- ORDER BY effective_date DESC NULLS LAST, id DESC`, [contractId]),
2024
2281
  this.queryRows(`SELECT id,
2025
2282
  actor_user_id AS "actorUserId",
2026
2283
  action,
@@ -2034,17 +2291,14 @@ let OperationsService = OperationsService_1 = class OperationsService {
2034
2291
  return Object.assign(Object.assign({}, contract), { mainRelatedPartyName: (_c = (_b = (_a = parties.find((party) => party.isPrimary)) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : contract.relatedCollaboratorName) !== null && _c !== void 0 ? _c : contract.clientName, projects,
2035
2292
  scheduleSummary,
2036
2293
  parties,
2037
- signatures,
2038
- financialTerms,
2039
2294
  documents,
2040
- revisions,
2041
2295
  history });
2042
2296
  }
2043
2297
  async createContract(userId, data) {
2044
2298
  const actor = await this.getActorContext(userId);
2045
2299
  this.ensureDirector(actor);
2046
2300
  const createdId = await this.prisma.$transaction(async (tx) => {
2047
- 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;
2301
+ 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;
2048
2302
  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));
2049
2303
  const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
2050
2304
  code,
@@ -2057,7 +2311,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2057
2311
  billing_model,
2058
2312
  account_manager_collaborator_id,
2059
2313
  related_collaborator_id,
2060
- contract_template_id,
2061
2314
  origin_type,
2062
2315
  origin_id,
2063
2316
  start_date,
@@ -2083,24 +2336,20 @@ let OperationsService = OperationsService_1 = class OperationsService {
2083
2336
  $8::operations_contract_billing_model_409dc7fea2_enum,
2084
2337
  $9,
2085
2338
  $10,
2086
- $11,
2087
- $12::operations_contract_origin_type_07a7cc2b5d_enum,
2088
- $13,
2089
- $14::date,
2090
- $15::date, $16::date, $17::date, $18, $19,
2091
- $20::operations_contract_status_a0395962df_enum,
2092
- $21::operations_contract_creation_mode_98ba669209_enum,
2339
+ $11::operations_contract_origin_type_07a7cc2b5d_enum,
2340
+ $12,
2341
+ $13::date,
2342
+ $14::date, $15::date, $16::date, $17, $18,
2343
+ $19::operations_contract_status_a0395962df_enum,
2344
+ $20::operations_contract_creation_mode_98ba669209_enum,
2345
+ $21,
2093
2346
  $22,
2094
2347
  $23,
2095
- $24,
2096
2348
  NOW(), NOW()
2097
2349
  )
2098
- 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);
2099
- const contractId = (_0 = created[0]) === null || _0 === void 0 ? void 0 : _0.id;
2350
+ 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.originType) !== null && _k !== void 0 ? _k : 'manual', (_l = data.originId) !== null && _l !== void 0 ? _l : null, this.normalizeOptionalText((_m = data.startDate) !== null && _m !== void 0 ? _m : null), (_o = data.endDate) !== null && _o !== void 0 ? _o : null, (_p = data.signedAt) !== null && _p !== void 0 ? _p : null, (_r = (_q = data.effectiveDate) !== null && _q !== void 0 ? _q : data.startDate) !== null && _r !== void 0 ? _r : null, (_s = data.budgetAmount) !== null && _s !== void 0 ? _s : null, (_t = data.monthlyHourCap) !== null && _t !== void 0 ? _t : null, (_u = data.status) !== null && _u !== void 0 ? _u : 'draft', (_v = data.creationMode) !== null && _v !== void 0 ? _v : 'blank', (_w = data.wizardStep) !== null && _w !== void 0 ? _w : 0, (_x = data.description) !== null && _x !== void 0 ? _x : null, (_y = data.contentHtml) !== null && _y !== void 0 ? _y : null);
2351
+ const contractId = (_z = created[0]) === null || _z === void 0 ? void 0 : _z.id;
2100
2352
  await this.replaceContractParties(tx, contractId, data.parties);
2101
- await this.replaceContractSignatures(tx, contractId, data.signatures);
2102
- await this.replaceContractFinancialTerms(tx, contractId, data.financialTerms);
2103
- await this.replaceContractRevisions(tx, contractId, data.revisions);
2104
2353
  if (data.replaceUploadedPdfDocument) {
2105
2354
  await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
2106
2355
  }
@@ -2111,258 +2360,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2111
2360
  });
2112
2361
  return this.getContractById(userId, createdId);
2113
2362
  }
2114
- async createContractDraft(userId, data) {
2115
- const actor = await this.getActorContext(userId);
2116
- this.ensureDirector(actor);
2117
- const creationMode = this.normalizeEnumValue(data.creationMode, CONTRACT_CREATION_MODE_VALUES, 'blank');
2118
- const selectedTemplate = data.templateId && data.templateId > 0
2119
- ? await this.getContractTemplateById(userId, data.templateId)
2120
- : null;
2121
- const duplicateSource = data.duplicateFromId && data.duplicateFromId > 0
2122
- ? await this.getContractById(userId, data.duplicateFromId)
2123
- : null;
2124
- const storedSourceFile = data.sourceFileId && data.sourceFileId > 0
2125
- ? await this.prisma.file.findUnique({
2126
- where: { id: data.sourceFileId },
2127
- include: { file_mimetype: true },
2128
- })
2129
- : null;
2130
- if (data.sourceFileId && !storedSourceFile) {
2131
- throw new common_1.NotFoundException('Source contract file not found.');
2132
- }
2133
- const createdId = await this.prisma.$transaction(async (tx) => {
2134
- 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;
2135
- const generatedCode = await this.generateContractCode(tx);
2136
- 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';
2137
- const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
2138
- code,
2139
- name,
2140
- contract_category,
2141
- contract_type,
2142
- client_name,
2143
- signature_status,
2144
- is_active,
2145
- billing_model,
2146
- account_manager_collaborator_id,
2147
- related_collaborator_id,
2148
- contract_template_id,
2149
- origin_type,
2150
- origin_id,
2151
- start_date,
2152
- end_date,
2153
- signed_at,
2154
- effective_date,
2155
- budget_amount,
2156
- monthly_hour_cap,
2157
- status,
2158
- creation_mode,
2159
- wizard_step,
2160
- description,
2161
- content_html,
2162
- created_by_user_id,
2163
- updated_by_user_id,
2164
- created_at,
2165
- updated_at
2166
- ) VALUES (
2167
- $1,
2168
- $2,
2169
- $3::operations_contract_contract_category_70d553ea09_enum,
2170
- $4::operations_contract_contract_type_48331e2ebf_enum,
2171
- $5,
2172
- $6::operations_contract_signature_status_2cb7282a7b_enum,
2173
- $7,
2174
- $8::operations_contract_billing_model_409dc7fea2_enum,
2175
- $9,
2176
- $10,
2177
- $11,
2178
- $12::operations_contract_origin_type_07a7cc2b5d_enum,
2179
- $13,
2180
- $14::date,
2181
- $15::date,
2182
- $16::date,
2183
- $17::date,
2184
- $18,
2185
- $19,
2186
- $20::operations_contract_status_a0395962df_enum,
2187
- $21::operations_contract_creation_mode_98ba669209_enum,
2188
- $22,
2189
- $23,
2190
- $24,
2191
- $25,
2192
- $25,
2193
- NOW(),
2194
- NOW()
2195
- )
2196
- RETURNING id`, generatedCode, creationMode === 'duplicate'
2197
- ? `${duplicateNameBase} Copy`
2198
- : 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);
2199
- const contractId = (_3 = created[0]) === null || _3 === void 0 ? void 0 : _3.id;
2200
- if (duplicateSource) {
2201
- await this.replaceContractParties(tx, contractId, duplicateSource.parties);
2202
- await this.replaceContractSignatures(tx, contractId, duplicateSource.signatures);
2203
- await this.replaceContractFinancialTerms(tx, contractId, duplicateSource.financialTerms);
2204
- await this.replaceContractRevisions(tx, contractId, duplicateSource.revisions);
2205
- const currentSourceDocument = (_4 = duplicateSource.documents.find((document) => document.isCurrent && document.documentType === 'source_upload')) !== null && _4 !== void 0 ? _4 : null;
2206
- if (currentSourceDocument) {
2207
- await this.replaceContractDocument(tx, contractId, 'source_upload', {
2208
- fileId: (_5 = currentSourceDocument.fileId) !== null && _5 !== void 0 ? _5 : null,
2209
- fileName: currentSourceDocument.fileName,
2210
- mimeType: currentSourceDocument.mimeType,
2211
- fileContentBase64: (_6 = currentSourceDocument.fileContentBase64) !== null && _6 !== void 0 ? _6 : null,
2212
- notes: (_7 = currentSourceDocument.notes) !== null && _7 !== void 0 ? _7 : null,
2213
- extractionStatus: (_8 = currentSourceDocument.extractionStatus) !== null && _8 !== void 0 ? _8 : 'skipped',
2214
- extractionSummary: (_9 = currentSourceDocument.extractionSummary) !== null && _9 !== void 0 ? _9 : null,
2215
- });
2216
- }
2217
- }
2218
- if (storedSourceFile) {
2219
- await this.replaceContractDocument(tx, contractId, 'source_upload', {
2220
- fileId: storedSourceFile.id,
2221
- fileName: (_10 = this.normalizeOptionalText(data.sourceFileName)) !== null && _10 !== void 0 ? _10 : storedSourceFile.filename,
2222
- 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',
2223
- notes: 'Source contract document uploaded during draft creation.',
2224
- extractionStatus: 'pending',
2225
- extractionSummary: null,
2226
- });
2227
- }
2228
- await this.insertContractHistory(tx, contractId, userId, 'draft_created', `Contract draft created with mode ${creationMode}.`, JSON.stringify({
2229
- creationMode,
2230
- templateId: (_14 = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _14 !== void 0 ? _14 : null,
2231
- duplicateFromId: (_15 = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.id) !== null && _15 !== void 0 ? _15 : null,
2232
- sourceFileId: (_16 = storedSourceFile === null || storedSourceFile === void 0 ? void 0 : storedSourceFile.id) !== null && _16 !== void 0 ? _16 : null,
2233
- }));
2234
- return contractId;
2235
- });
2236
- return this.getContractById(userId, createdId);
2237
- }
2238
- async extractContractSource(userId, contractId, data) {
2239
- var _a, _b;
2240
- const actor = await this.getActorContext(userId);
2241
- this.ensureDirector(actor);
2242
- const contract = await this.getContractById(userId, contractId);
2243
- const sourceDocument = contract.documents.find((document) => document.isCurrent && document.documentType === 'source_upload');
2244
- if (!sourceDocument) {
2245
- throw new common_1.BadRequestException('No source document is attached to this contract draft.');
2246
- }
2247
- await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
2248
- SET extraction_status = 'processing',
2249
- updated_at = NOW()
2250
- WHERE id = $1`, sourceDocument.id);
2251
- try {
2252
- const extracted = await this.extractContractDraft(userId, {
2253
- contractId,
2254
- provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
2255
- promptMessage: (_b = data.promptMessage) !== null && _b !== void 0 ? _b : null,
2256
- });
2257
- await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
2258
- SET extraction_status = 'completed',
2259
- extraction_summary = $2,
2260
- updated_at = NOW()
2261
- WHERE id = $1`, sourceDocument.id, extracted.summary || extracted.description || extracted.name || null);
2262
- await this.insertContractHistory(this.prisma, contractId, userId, 'source_extracted', 'Source contract document extracted with AI.');
2263
- return extracted;
2264
- }
2265
- catch (error) {
2266
- await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
2267
- SET extraction_status = 'failed',
2268
- updated_at = NOW()
2269
- WHERE id = $1`, sourceDocument.id);
2270
- throw error;
2271
- }
2272
- }
2273
- async generateContractContent(userId, contractId, data = {}) {
2274
- const actor = await this.getActorContext(userId);
2275
- this.ensureDirector(actor);
2276
- const contract = await this.getContractById(userId, contractId);
2277
- const contentHtml = await this.generateContractContentHtml(contract, data);
2278
- await this.prisma.$transaction(async (tx) => {
2279
- var _a;
2280
- await tx.$executeRawUnsafe(`UPDATE operations_contract
2281
- SET content_html = $2,
2282
- wizard_step = CASE
2283
- WHEN COALESCE(wizard_step, 0) < 4 THEN 4
2284
- ELSE wizard_step
2285
- END,
2286
- updated_by_user_id = $3,
2287
- updated_at = NOW()
2288
- WHERE id = $1`, contractId, contentHtml, userId);
2289
- await this.insertContractHistory(tx, contractId, userId, 'content_generated', 'Contract content generated automatically for editing.', JSON.stringify({
2290
- provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
2291
- overwrite: Boolean(data.overwrite),
2292
- hasPrompt: Boolean(this.normalizeOptionalText(data.promptMessage)),
2293
- }));
2294
- });
2295
- return this.getContractById(userId, contractId);
2296
- }
2297
- async reviewContractLegally(userId, contractId, data = {}) {
2298
- const actor = await this.getActorContext(userId);
2299
- this.ensureDirector(actor);
2300
- const contract = await this.getContractById(userId, contractId);
2301
- const review = await this.buildContractLegalReview(contract, data);
2302
- await this.insertContractHistory(this.prisma, contractId, userId, 'legal_reviewed', 'Advisory legal checklist updated for this contract.', JSON.stringify(review));
2303
- return review;
2304
- }
2305
- async generateContractPdf(userId, contractId) {
2306
- const actor = await this.getActorContext(userId);
2307
- this.ensureDirector(actor);
2308
- const contract = await this.getContractById(userId, contractId);
2309
- const pdfBuffer = await this.renderContractPdfBuffer(contract);
2310
- const fileName = this.buildContractPdfFileName(contract);
2311
- const uploadedFile = await this.fileService.upload('operations/contracts/generated', {
2312
- fieldname: 'file',
2313
- originalname: fileName,
2314
- encoding: '7bit',
2315
- mimetype: 'application/pdf',
2316
- size: pdfBuffer.length,
2317
- destination: '',
2318
- filename: fileName,
2319
- path: '',
2320
- buffer: pdfBuffer,
2321
- });
2322
- await this.prisma.$transaction(async (tx) => {
2323
- await this.replaceContractDocument(tx, contractId, 'generated_pdf', {
2324
- fileId: uploadedFile.id,
2325
- fileName,
2326
- mimeType: 'application/pdf',
2327
- notes: 'PDF generated from contract rich text content.',
2328
- extractionStatus: 'skipped',
2329
- extractionSummary: null,
2330
- });
2331
- await this.insertContractHistory(tx, contractId, userId, 'pdf_generated', 'Generated a branded PDF version of the contract.');
2332
- });
2333
- return {
2334
- contractId,
2335
- fileId: uploadedFile.id,
2336
- fileName,
2337
- mimeType: 'application/pdf',
2338
- documentType: 'generated_pdf',
2339
- downloadUrl: `/file/open/${uploadedFile.id}`,
2340
- };
2341
- }
2342
- async extractContractDraft(userId, data) {
2343
- var _a;
2344
- const actor = await this.getActorContext(userId);
2345
- this.ensureDirector(actor);
2346
- const uploadFile = await this.resolveContractExtractionFile(userId, data);
2347
- const aiResult = await this.aiService.chat({
2348
- provider: data.provider === 'gemini' ? 'gemini' : 'openai',
2349
- model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
2350
- message: this.normalizeExtractionString(data.promptMessage) ||
2351
- 'Analyze the attached contract and extract a structured draft for the contract form.',
2352
- systemPrompt: this.buildContractExtractionSystemPrompt(),
2353
- }, uploadFile);
2354
- const parsed = this.parseAiJsonPayload(String((_a = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _a !== void 0 ? _a : ''));
2355
- const draft = this.normalizeContractExtractDraft(parsed);
2356
- const warnings = [...draft.warnings];
2357
- if (uploadFile.mimetype === 'application/msword' ||
2358
- uploadFile.mimetype ===
2359
- 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
2360
- warnings.push('Word extraction is best-effort. Review names, dates, and values before saving.');
2361
- }
2362
- return Object.assign(Object.assign({}, draft), { warnings: Array.from(new Set(warnings.filter(Boolean))) });
2363
- }
2364
2363
  async createContractFromProposalIntegration(payload) {
2365
- 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;
2364
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
2366
2365
  const sourceEntityId = String(payload.proposalId || '').trim();
2367
2366
  if (!sourceEntityId) {
2368
2367
  throw new common_1.BadRequestException('proposalId is required for CRM proposal integration.');
@@ -2386,30 +2385,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2386
2385
  const billingModel = (_u = proposal.billingModel) !== null && _u !== void 0 ? _u : 'fixed_price';
2387
2386
  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}`;
2388
2387
  const contractName = (_y = this.normalizeOptionalText(proposal.title)) !== null && _y !== void 0 ? _y : `Proposal ${sourceEntityId}`;
2389
- const financialTerms = items
2390
- .map((item) => {
2391
- var _a, _b, _c;
2392
- return ({
2393
- termType: this.normalizeEnumValue(item.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
2394
- label: (_a = this.normalizeOptionalText(item.name)) !== null && _a !== void 0 ? _a : 'Commercial term',
2395
- amount: Number((_b = item.amount) !== null && _b !== void 0 ? _b : Number(item.totalAmountCents || 0) / 100),
2396
- recurrence: this.normalizeEnumValue(item.recurrence, RECURRENCE_VALUES, 'one_time'),
2397
- dueDay: (_c = item.dueDay) !== null && _c !== void 0 ? _c : null,
2398
- notes: this.normalizeOptionalText(item.description),
2399
- });
2400
- })
2401
- .filter((term) => Number.isFinite(term.amount) && term.amount > 0);
2402
- const fallbackAmount = Number((_z = proposal.totalAmount) !== null && _z !== void 0 ? _z : 0);
2403
- if (financialTerms.length === 0 && Number.isFinite(fallbackAmount) && fallbackAmount > 0) {
2404
- financialTerms.push({
2405
- termType: 'revenue',
2406
- label: contractName,
2407
- amount: fallbackAmount,
2408
- recurrence: 'one_time',
2409
- dueDay: null,
2410
- notes: this.normalizeOptionalText(proposal.notes),
2411
- });
2412
- }
2413
2388
  const primaryPartyRole = ['employee', 'contractor'].includes(contractCategory)
2414
2389
  ? 'employee'
2415
2390
  : ['supplier', 'vendor'].includes(contractCategory)
@@ -2418,7 +2393,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2418
2393
  ? 'partner'
2419
2394
  : 'client';
2420
2395
  return this.prisma.$transaction(async (tx) => {
2421
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
2396
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2422
2397
  await tx.$queryRawUnsafe(`SELECT pg_advisory_xact_lock(hashtext($1))`, `operations:crm_proposal:${sourceEntityId}`);
2423
2398
  const existingContracts = await tx.$queryRawUnsafe(`SELECT id, code
2424
2399
  FROM operations_contract
@@ -2446,7 +2421,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2446
2421
  billing_model,
2447
2422
  account_manager_collaborator_id,
2448
2423
  related_collaborator_id,
2449
- contract_template_id,
2450
2424
  origin_type,
2451
2425
  origin_id,
2452
2426
  start_date,
@@ -2494,8 +2468,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2494
2468
  NOW(),
2495
2469
  NOW()
2496
2470
  )
2497
- 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);
2498
- const contractId = (_f = created[0]) === null || _f === void 0 ? void 0 : _f.id;
2471
+ 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, Number((_e = proposal.totalAmount) !== null && _e !== void 0 ? _e : 0) > 0 ? Number((_f = proposal.totalAmount) !== null && _f !== void 0 ? _f : 0) : null, null, 'draft', 'blank', 1, this.normalizeOptionalText(proposal.notes), this.normalizeOptionalText((_g = payload.revision) === null || _g === void 0 ? void 0 : _g.contentHtml), Number(payload.approvedByUserId) || null);
2472
+ const contractId = (_h = created[0]) === null || _h === void 0 ? void 0 : _h.id;
2499
2473
  if (!contractId) {
2500
2474
  throw new common_1.BadRequestException('Could not create contract draft from proposal.');
2501
2475
  }
@@ -2510,27 +2484,10 @@ let OperationsService = OperationsService_1 = class OperationsService {
2510
2484
  isPrimary: true,
2511
2485
  },
2512
2486
  ]);
2513
- await this.replaceContractFinancialTerms(tx, contractId, financialTerms.map((term) => ({
2514
- termType: term.termType,
2515
- label: term.label,
2516
- amount: term.amount,
2517
- recurrence: term.recurrence,
2518
- dueDay: term.dueDay,
2519
- notes: term.notes,
2520
- })));
2521
- await this.replaceContractRevisions(tx, contractId, [
2522
- {
2523
- revisionType: 'revision',
2524
- title: (_h = (_g = payload.revision) === null || _g === void 0 ? void 0 : _g.title) !== null && _h !== void 0 ? _h : contractName,
2525
- effectiveDate: (_j = proposal.validFrom) !== null && _j !== void 0 ? _j : null,
2526
- status: 'draft',
2527
- summary: (_l = this.normalizeOptionalText((_k = payload.revision) === null || _k === void 0 ? void 0 : _k.summary)) !== null && _l !== void 0 ? _l : this.normalizeOptionalText(proposal.notes),
2528
- },
2529
- ]);
2530
- 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({
2487
+ await this.insertContractHistory(tx, contractId, Number(payload.approvedByUserId) || null, 'created', `Draft contract generated from CRM proposal ${(_j = proposal.code) !== null && _j !== void 0 ? _j : sourceEntityId}.`, JSON.stringify({
2531
2488
  correlationId: payload.correlationId || `proposal:${sourceEntityId}`,
2532
- proposalId: (_o = payload.proposalId) !== null && _o !== void 0 ? _o : (Number(sourceEntityId) > 0 ? Number(sourceEntityId) : null),
2533
- proposalRevisionId: (_p = payload.proposalRevisionId) !== null && _p !== void 0 ? _p : null,
2489
+ proposalId: (_k = payload.proposalId) !== null && _k !== void 0 ? _k : (Number(sourceEntityId) > 0 ? Number(sourceEntityId) : null),
2490
+ proposalRevisionId: (_l = payload.proposalRevisionId) !== null && _l !== void 0 ? _l : null,
2534
2491
  sourceModule: 'contact',
2535
2492
  sourceEntity: 'proposal',
2536
2493
  sourceId: sourceEntityId,
@@ -2551,10 +2508,9 @@ let OperationsService = OperationsService_1 = class OperationsService {
2551
2508
  billingModel,
2552
2509
  originType: 'crm_proposal',
2553
2510
  originId: sourceEntityId,
2554
- startDate: (_q = proposal.validFrom) !== null && _q !== void 0 ? _q : null,
2555
- endDate: (_r = proposal.validUntil) !== null && _r !== void 0 ? _r : null,
2511
+ startDate: (_m = proposal.validFrom) !== null && _m !== void 0 ? _m : null,
2512
+ endDate: (_o = proposal.validUntil) !== null && _o !== void 0 ? _o : null,
2556
2513
  description: this.normalizeOptionalText(proposal.notes),
2557
- financialTerms,
2558
2514
  }, payload, String(payload.locale || '').trim() || 'en', Number(payload.approvedByUserId) || undefined),
2559
2515
  metadata: {
2560
2516
  producer: 'operations',
@@ -2575,7 +2531,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2575
2531
  });
2576
2532
  }
2577
2533
  buildContractCreatedEventPayload(contract, payload, locale = 'en', createdByUserId) {
2578
- 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;
2534
+ 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;
2579
2535
  return {
2580
2536
  contractId: contract.id,
2581
2537
  proposalId: (_a = payload.proposalId) !== null && _a !== void 0 ? _a : null,
@@ -2605,19 +2561,18 @@ let OperationsService = OperationsService_1 = class OperationsService {
2605
2561
  startDate: (_r = contract.startDate) !== null && _r !== void 0 ? _r : null,
2606
2562
  endDate: (_s = contract.endDate) !== null && _s !== void 0 ? _s : null,
2607
2563
  description: (_t = contract.description) !== null && _t !== void 0 ? _t : null,
2608
- financialTerms: (_u = contract.financialTerms) !== null && _u !== void 0 ? _u : [],
2609
2564
  },
2610
- proposal: (_v = payload.proposal) !== null && _v !== void 0 ? _v : {
2611
- code: (_w = payload.code) !== null && _w !== void 0 ? _w : null,
2612
- title: (_x = payload.title) !== null && _x !== void 0 ? _x : null,
2613
- totalAmount: (_y = payload.total) !== null && _y !== void 0 ? _y : null,
2614
- notes: (_0 = (_z = payload.commercialTerms) === null || _z === void 0 ? void 0 : _z.notes) !== null && _0 !== void 0 ? _0 : null,
2565
+ proposal: (_u = payload.proposal) !== null && _u !== void 0 ? _u : {
2566
+ code: (_v = payload.code) !== null && _v !== void 0 ? _v : null,
2567
+ title: (_w = payload.title) !== null && _w !== void 0 ? _w : null,
2568
+ totalAmount: (_x = payload.total) !== null && _x !== void 0 ? _x : null,
2569
+ notes: (_z = (_y = payload.commercialTerms) === null || _y === void 0 ? void 0 : _y.notes) !== null && _z !== void 0 ? _z : null,
2615
2570
  },
2616
- person: (_1 = payload.person) !== null && _1 !== void 0 ? _1 : null,
2571
+ person: (_0 = payload.person) !== null && _0 !== void 0 ? _0 : null,
2617
2572
  };
2618
2573
  }
2619
2574
  async buildContractActivatedEventPayload(contract, locale = 'en', activatedByUserId) {
2620
- 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;
2575
+ 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;
2621
2576
  let personId = null;
2622
2577
  let proposalId = null;
2623
2578
  let personRecord = null;
@@ -2678,18 +2633,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2678
2633
  }
2679
2634
  }
2680
2635
  }
2681
- const financialTerms = ((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : []).map((term) => {
2682
- var _a, _b, _c, _d, _e;
2683
- return ({
2684
- label: term.label,
2685
- termType: (_a = term.termType) !== null && _a !== void 0 ? _a : 'value',
2686
- amount: Number((_b = term.amount) !== null && _b !== void 0 ? _b : 0),
2687
- recurrence: (_c = term.recurrence) !== null && _c !== void 0 ? _c : 'one_time',
2688
- dueDay: (_d = term.dueDay) !== null && _d !== void 0 ? _d : null,
2689
- notes: (_e = term.notes) !== null && _e !== void 0 ? _e : null,
2690
- });
2691
- });
2692
- const totalAmount = financialTerms.reduce((sum, term) => sum + (Number.isFinite(term.amount) ? term.amount : 0), 0) || Number((_h = contract.budgetAmount) !== null && _h !== void 0 ? _h : 0);
2636
+ const totalAmount = Number((_g = contract.budgetAmount) !== null && _g !== void 0 ? _g : 0);
2693
2637
  return {
2694
2638
  contractId: contract.id,
2695
2639
  proposalId,
@@ -2697,7 +2641,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2697
2641
  activatedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
2698
2642
  signedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
2699
2643
  activatedAt: new Date().toISOString(),
2700
- signedAt: (_l = (_k = (_j = contract.signedAt) !== null && _j !== void 0 ? _j : contract.effectiveDate) !== null && _k !== void 0 ? _k : contract.startDate) !== null && _l !== void 0 ? _l : null,
2644
+ signedAt: (_k = (_j = (_h = contract.signedAt) !== null && _h !== void 0 ? _h : contract.effectiveDate) !== null && _j !== void 0 ? _j : contract.startDate) !== null && _k !== void 0 ? _k : null,
2701
2645
  locale,
2702
2646
  correlationId: proposalId ? `proposal:${proposalId}` : `contract:${contract.id}`,
2703
2647
  sourceModule: 'operations',
@@ -2707,38 +2651,37 @@ let OperationsService = OperationsService_1 = class OperationsService {
2707
2651
  source_entity: 'contract',
2708
2652
  source_id: String(contract.id),
2709
2653
  contract: {
2710
- code: (_m = contract.code) !== null && _m !== void 0 ? _m : null,
2711
- name: (_o = contract.name) !== null && _o !== void 0 ? _o : null,
2712
- clientName: (_p = contract.clientName) !== null && _p !== void 0 ? _p : null,
2713
- contractCategory: (_q = contract.contractCategory) !== null && _q !== void 0 ? _q : 'client',
2714
- contractType: (_r = contract.contractType) !== null && _r !== void 0 ? _r : 'service_agreement',
2715
- billingModel: (_s = contract.billingModel) !== null && _s !== void 0 ? _s : 'fixed_price',
2716
- originType: (_t = contract.originType) !== null && _t !== void 0 ? _t : 'manual',
2717
- originId: (_u = contract.originId) !== null && _u !== void 0 ? _u : null,
2718
- startDate: (_v = contract.startDate) !== null && _v !== void 0 ? _v : null,
2719
- endDate: (_w = contract.endDate) !== null && _w !== void 0 ? _w : null,
2720
- signedAt: (_x = contract.signedAt) !== null && _x !== void 0 ? _x : null,
2721
- effectiveDate: (_y = contract.effectiveDate) !== null && _y !== void 0 ? _y : null,
2722
- budgetAmount: (_z = contract.budgetAmount) !== null && _z !== void 0 ? _z : null,
2723
- description: (_0 = contract.description) !== null && _0 !== void 0 ? _0 : null,
2724
- financialTerms,
2725
- parties: (_1 = contract.parties) !== null && _1 !== void 0 ? _1 : [],
2654
+ code: (_l = contract.code) !== null && _l !== void 0 ? _l : null,
2655
+ name: (_m = contract.name) !== null && _m !== void 0 ? _m : null,
2656
+ clientName: (_o = contract.clientName) !== null && _o !== void 0 ? _o : null,
2657
+ contractCategory: (_p = contract.contractCategory) !== null && _p !== void 0 ? _p : 'client',
2658
+ contractType: (_q = contract.contractType) !== null && _q !== void 0 ? _q : 'service_agreement',
2659
+ billingModel: (_r = contract.billingModel) !== null && _r !== void 0 ? _r : 'fixed_price',
2660
+ originType: (_s = contract.originType) !== null && _s !== void 0 ? _s : 'manual',
2661
+ originId: (_t = contract.originId) !== null && _t !== void 0 ? _t : null,
2662
+ startDate: (_u = contract.startDate) !== null && _u !== void 0 ? _u : null,
2663
+ endDate: (_v = contract.endDate) !== null && _v !== void 0 ? _v : null,
2664
+ signedAt: (_w = contract.signedAt) !== null && _w !== void 0 ? _w : null,
2665
+ effectiveDate: (_x = contract.effectiveDate) !== null && _x !== void 0 ? _x : null,
2666
+ budgetAmount: (_y = contract.budgetAmount) !== null && _y !== void 0 ? _y : null,
2667
+ description: (_z = contract.description) !== null && _z !== void 0 ? _z : null,
2668
+ parties: (_0 = contract.parties) !== null && _0 !== void 0 ? _0 : [],
2726
2669
  },
2727
2670
  person: personRecord
2728
2671
  ? {
2729
- id: (_2 = personRecord.id) !== null && _2 !== void 0 ? _2 : null,
2730
- name: (_3 = personRecord.name) !== null && _3 !== void 0 ? _3 : null,
2731
- tradeName: (_4 = personRecord.trade_name) !== null && _4 !== void 0 ? _4 : null,
2732
- email: (_5 = personRecord.email) !== null && _5 !== void 0 ? _5 : null,
2733
- phone: (_6 = personRecord.phone) !== null && _6 !== void 0 ? _6 : null,
2734
- document: (_7 = personRecord.document) !== null && _7 !== void 0 ? _7 : null,
2672
+ id: (_1 = personRecord.id) !== null && _1 !== void 0 ? _1 : null,
2673
+ name: (_2 = personRecord.name) !== null && _2 !== void 0 ? _2 : null,
2674
+ tradeName: (_3 = personRecord.trade_name) !== null && _3 !== void 0 ? _3 : null,
2675
+ email: (_4 = personRecord.email) !== null && _4 !== void 0 ? _4 : null,
2676
+ phone: (_5 = personRecord.phone) !== null && _5 !== void 0 ? _5 : null,
2677
+ document: (_6 = personRecord.document) !== null && _6 !== void 0 ? _6 : null,
2735
2678
  }
2736
2679
  : null,
2737
2680
  receivable: {
2738
2681
  personId,
2739
- documentNumber: (_8 = contract.code) !== null && _8 !== void 0 ? _8 : `CONTRACT-${contract.id}`,
2682
+ documentNumber: (_7 = contract.code) !== null && _7 !== void 0 ? _7 : `CONTRACT-${contract.id}`,
2740
2683
  totalAmount,
2741
- description: (_11 = (_10 = (_9 = contract.description) !== null && _9 !== void 0 ? _9 : contract.name) !== null && _10 !== void 0 ? _10 : contract.code) !== null && _11 !== void 0 ? _11 : null,
2684
+ description: (_10 = (_9 = (_8 = contract.description) !== null && _8 !== void 0 ? _8 : contract.name) !== null && _9 !== void 0 ? _9 : contract.code) !== null && _10 !== void 0 ? _10 : null,
2742
2685
  },
2743
2686
  };
2744
2687
  }
@@ -2763,7 +2706,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2763
2706
  this.pushUpdate(updates, params, 'billing_model', data.billingModel, 'operations_contract_billing_model_409dc7fea2_enum');
2764
2707
  this.pushUpdate(updates, params, 'account_manager_collaborator_id', data.accountManagerCollaboratorId);
2765
2708
  this.pushUpdate(updates, params, 'related_collaborator_id', data.relatedCollaboratorId);
2766
- this.pushUpdate(updates, params, 'contract_template_id', data.contractTemplateId);
2767
2709
  this.pushUpdate(updates, params, 'origin_type', data.originType, 'operations_contract_origin_type_07a7cc2b5d_enum');
2768
2710
  this.pushUpdate(updates, params, 'origin_id', data.originId);
2769
2711
  this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
@@ -2778,7 +2720,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2778
2720
  this.pushUpdate(updates, params, 'description', data.description);
2779
2721
  this.pushUpdate(updates, params, 'content_html', data.contentHtml);
2780
2722
  await this.prisma.$transaction(async (tx) => {
2781
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
2723
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
2782
2724
  if (updates.length) {
2783
2725
  params.push(contractId);
2784
2726
  await tx.$executeRawUnsafe(`UPDATE operations_contract
@@ -2789,15 +2731,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
2789
2731
  if (data.parties) {
2790
2732
  await this.replaceContractParties(tx, contractId, data.parties);
2791
2733
  }
2792
- if (data.signatures) {
2793
- await this.replaceContractSignatures(tx, contractId, data.signatures);
2794
- }
2795
- if (data.financialTerms) {
2796
- await this.replaceContractFinancialTerms(tx, contractId, data.financialTerms);
2797
- }
2798
- if (data.revisions) {
2799
- await this.replaceContractRevisions(tx, contractId, data.revisions);
2800
- }
2801
2734
  if (data.replaceUploadedPdfDocument) {
2802
2735
  await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
2803
2736
  }
@@ -2819,8 +2752,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2819
2752
  effectiveDate: (_m = data.effectiveDate) !== null && _m !== void 0 ? _m : current.effectiveDate,
2820
2753
  budgetAmount: (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : current.budgetAmount,
2821
2754
  description: (_p = data.description) !== null && _p !== void 0 ? _p : current.description,
2822
- financialTerms: (_q = data.financialTerms) !== null && _q !== void 0 ? _q : current.financialTerms,
2823
- parties: (_r = data.parties) !== null && _r !== void 0 ? _r : current.parties,
2755
+ parties: (_q = data.parties) !== null && _q !== void 0 ? _q : current.parties,
2824
2756
  }, 'en', userId);
2825
2757
  if (shouldPublishSigned) {
2826
2758
  await this.integrationApi.publishEvent({
@@ -2828,15 +2760,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
2828
2760
  sourceModule: 'operations',
2829
2761
  aggregateType: 'contract',
2830
2762
  aggregateId: String(contractId),
2831
- 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() }),
2763
+ payload: Object.assign(Object.assign({}, contractEventPayload), { signedByUserId: userId !== null && userId !== void 0 ? userId : null, signedAt: (_t = (_s = (_r = data.signedAt) !== null && _r !== void 0 ? _r : current.signedAt) !== null && _s !== void 0 ? _s : contractEventPayload.signedAt) !== null && _t !== void 0 ? _t : new Date().toISOString() }),
2832
2764
  metadata: {
2833
2765
  producer: 'operations',
2834
2766
  correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
2835
2767
  sourceModule: 'operations',
2836
2768
  sourceEntity: 'contract',
2837
2769
  sourceId: String(contractId),
2838
- originType: (_v = data.originType) !== null && _v !== void 0 ? _v : current.originType,
2839
- originId: (_w = data.originId) !== null && _w !== void 0 ? _w : current.originId,
2770
+ originType: (_u = data.originType) !== null && _u !== void 0 ? _u : current.originType,
2771
+ originId: (_v = data.originId) !== null && _v !== void 0 ? _v : current.originId,
2840
2772
  lifecycle: 'signed',
2841
2773
  },
2842
2774
  }, {
@@ -2856,8 +2788,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2856
2788
  sourceModule: 'operations',
2857
2789
  sourceEntity: 'contract',
2858
2790
  sourceId: String(contractId),
2859
- originType: (_x = data.originType) !== null && _x !== void 0 ? _x : current.originType,
2860
- originId: (_y = data.originId) !== null && _y !== void 0 ? _y : current.originId,
2791
+ originType: (_w = data.originType) !== null && _w !== void 0 ? _w : current.originType,
2792
+ originId: (_x = data.originId) !== null && _x !== void 0 ? _x : current.originId,
2861
2793
  lifecycle: 'activated',
2862
2794
  },
2863
2795
  }, {
@@ -2881,10 +2813,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2881
2813
  AND deleted_at IS NULL`, contractId);
2882
2814
  for (const tableName of [
2883
2815
  'operations_contract_party',
2884
- 'operations_contract_signature',
2885
- 'operations_contract_financial_term',
2886
2816
  'operations_contract_document',
2887
- 'operations_contract_revision',
2888
2817
  ]) {
2889
2818
  await tx.$executeRawUnsafe(`UPDATE ${tableName}
2890
2819
  SET deleted_at = NOW(),
@@ -2903,9 +2832,43 @@ let OperationsService = OperationsService_1 = class OperationsService {
2903
2832
  });
2904
2833
  return { success: true };
2905
2834
  }
2906
- async listTimesheets(userId) {
2835
+ async listTimesheets(userId, filters = {}) {
2836
+ var _a, _b, _c, _d, _e, _f;
2907
2837
  const actor = await this.getActorContext(userId);
2908
2838
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 't.collaborator_id', actor.isDirector);
2839
+ const pagination = this.shouldPaginate(filters)
2840
+ ? this.normalizePaginationParams(filters, {
2841
+ defaultSortField: 'weekStartDate',
2842
+ defaultSortOrder: 'desc',
2843
+ allowedSortFields: ['weekStartDate', 'weekEndDate', 'collaboratorName', 'status'],
2844
+ })
2845
+ : null;
2846
+ const params = [...filter.params];
2847
+ const where = ['t.deleted_at IS NULL', filter.clause];
2848
+ if (filters.status && filters.status !== 'all') {
2849
+ where.push(`t.status::text = ${this.param(params, filters.status)}`);
2850
+ }
2851
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
2852
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
2853
+ where.push(`(
2854
+ COALESCE(c.display_name, '') ILIKE ${searchPlaceholder}
2855
+ OR COALESCE(a.display_name, '') ILIKE ${searchPlaceholder}
2856
+ OR COALESCE(t.notes, '') ILIKE ${searchPlaceholder}
2857
+ OR COALESCE(approval.decision_note, '') ILIKE ${searchPlaceholder}
2858
+ )`);
2859
+ }
2860
+ const whereClause = where.join(' AND ');
2861
+ const sortColumn = (_b = {
2862
+ weekStartDate: 't.week_start_date',
2863
+ weekEndDate: 't.week_end_date',
2864
+ collaboratorName: 'c.display_name',
2865
+ status: 't.status',
2866
+ }[(_a = pagination === null || pagination === void 0 ? void 0 : pagination.sortField) !== null && _a !== void 0 ? _a : 'weekStartDate']) !== null && _b !== void 0 ? _b : 't.week_start_date';
2867
+ const headerParams = [...params];
2868
+ const limitSql = pagination
2869
+ ? `LIMIT ${this.param(headerParams, pagination.pageSize)}
2870
+ OFFSET ${this.param(headerParams, pagination.offset)}`
2871
+ : '';
2909
2872
  const headers = await this.queryRows(`SELECT t.id,
2910
2873
  t.collaborator_id AS "collaboratorId",
2911
2874
  c.display_name AS "collaboratorName",
@@ -2926,11 +2889,26 @@ let OperationsService = OperationsService_1 = class OperationsService {
2926
2889
  ON approval.target_type = 'timesheet'
2927
2890
  AND approval.target_id = t.id
2928
2891
  AND approval.deleted_at IS NULL
2929
- WHERE t.deleted_at IS NULL AND ${filter.clause}
2930
- ORDER BY t.week_start_date DESC, t.id DESC`, filter.params);
2892
+ WHERE ${whereClause}
2893
+ ORDER BY ${sortColumn} ${(_d = (_c = pagination === null || pagination === void 0 ? void 0 : pagination.sortOrder) === null || _c === void 0 ? void 0 : _c.toUpperCase()) !== null && _d !== void 0 ? _d : 'DESC'}, t.id DESC
2894
+ ${limitSql}`, headerParams);
2931
2895
  if (!headers.length) {
2896
+ if (pagination) {
2897
+ return this.buildPaginationResult([], 0, pagination.page, pagination.pageSize);
2898
+ }
2932
2899
  return headers;
2933
2900
  }
2901
+ const total = pagination
2902
+ ? Number((_f = (_e = (await this.querySingle(`SELECT COUNT(*)::text AS total
2903
+ FROM operations_timesheet t
2904
+ JOIN operations_collaborator c ON c.id = t.collaborator_id
2905
+ LEFT JOIN operations_collaborator a ON a.id = t.approver_collaborator_id
2906
+ LEFT JOIN operations_approval approval
2907
+ ON approval.target_type = 'timesheet'
2908
+ AND approval.target_id = t.id
2909
+ AND approval.deleted_at IS NULL
2910
+ WHERE ${whereClause}`, params))) === null || _e === void 0 ? void 0 : _e.total) !== null && _f !== void 0 ? _f : 0)
2911
+ : 0;
2934
2912
  const entries = await this.queryRows(`SELECT e.id,
2935
2913
  e.timesheet_id AS "timesheetId",
2936
2914
  e.project_assignment_id AS "projectAssignmentId",
@@ -2957,10 +2935,14 @@ let OperationsService = OperationsService_1 = class OperationsService {
2957
2935
  AND e.timesheet_id = ANY($1::int[])
2958
2936
  ORDER BY e.work_date ASC, e.id ASC`, [headers.map((item) => item.id)]);
2959
2937
  const grouped = this.groupBy(entries, 'timesheetId');
2960
- return headers.map((timesheet) => {
2938
+ const data = headers.map((timesheet) => {
2961
2939
  var _a;
2962
2940
  return (Object.assign(Object.assign({}, timesheet), { entries: (_a = grouped[timesheet.id]) !== null && _a !== void 0 ? _a : [] }));
2963
2941
  });
2942
+ if (!pagination) {
2943
+ return data;
2944
+ }
2945
+ return this.buildPaginationResult(data, total, pagination.page, pagination.pageSize);
2964
2946
  }
2965
2947
  async createTimesheet(userId, data) {
2966
2948
  const actor = await this.getActorContext(userId);
@@ -3026,7 +3008,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
3026
3008
  return this.listSingleTimesheet(actor, timesheetId);
3027
3009
  }
3028
3010
  async submitTimesheet(userId, timesheetId) {
3029
- var _a, _b;
3011
+ var _a, _b, _c;
3030
3012
  const actor = await this.getActorContext(userId);
3031
3013
  const current = await this.getTimesheetById(timesheetId);
3032
3014
  if (!actor.isDirector && current.collaboratorId !== actor.collaboratorId) {
@@ -3036,7 +3018,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
3036
3018
  throw new common_1.BadRequestException('Only draft or rejected timesheets can be submitted.');
3037
3019
  }
3038
3020
  const collaborator = await this.getCollaboratorById(current.collaboratorId);
3039
- const approverId = (_b = (_a = current.approverCollaboratorId) !== null && _a !== void 0 ? _a : collaborator.supervisorId) !== null && _b !== void 0 ? _b : null;
3021
+ const projectManagerRow = await this.querySingle(`SELECT p.manager_collaborator_id AS "managerCollaboratorId"
3022
+ FROM operations_timesheet_entry e
3023
+ LEFT JOIN operations_project_assignment pa ON pa.id = e.project_assignment_id
3024
+ LEFT JOIN operations_project p ON p.id = pa.project_id
3025
+ WHERE e.timesheet_id = $1
3026
+ AND e.deleted_at IS NULL
3027
+ AND p.manager_collaborator_id IS NOT NULL
3028
+ LIMIT 1`, [timesheetId]);
3029
+ const approverId = (_c = (_b = (_a = projectManagerRow === null || projectManagerRow === void 0 ? void 0 : projectManagerRow.managerCollaboratorId) !== null && _a !== void 0 ? _a : current.approverCollaboratorId) !== null && _b !== void 0 ? _b : collaborator.supervisorId) !== null && _c !== void 0 ? _c : null;
3040
3030
  if (!approverId) {
3041
3031
  throw new common_1.BadRequestException('An approver is required before submitting a timesheet.');
3042
3032
  }
@@ -3065,17 +3055,40 @@ let OperationsService = OperationsService_1 = class OperationsService {
3065
3055
  });
3066
3056
  return this.listSingleTimesheet(actor, timesheetId);
3067
3057
  }
3068
- async listTimeOffRequests(userId) {
3058
+ async listTimeOffRequests(userId, filters = {}) {
3059
+ var _a, _b;
3069
3060
  const actor = await this.getActorContext(userId);
3070
3061
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'tor.collaborator_id', actor.isDirector);
3071
- return this.queryRows(`SELECT tor.id,
3062
+ const pagination = this.shouldPaginate(filters)
3063
+ ? this.normalizePaginationParams(filters, {
3064
+ defaultSortField: 'startDate',
3065
+ defaultSortOrder: 'desc',
3066
+ allowedSortFields: ['startDate', 'endDate', 'collaboratorName', 'status'],
3067
+ })
3068
+ : null;
3069
+ const params = [...filter.params];
3070
+ const where = ['tor.deleted_at IS NULL', filter.clause];
3071
+ if (filters.status && filters.status !== 'all') {
3072
+ where.push(`tor.status::text = ${this.param(params, filters.status)}`);
3073
+ }
3074
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
3075
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
3076
+ where.push(`(
3077
+ COALESCE(c.display_name, '') ILIKE ${searchPlaceholder}
3078
+ OR COALESCE(a.display_name, '') ILIKE ${searchPlaceholder}
3079
+ OR COALESCE(tor.reason, '') ILIKE ${searchPlaceholder}
3080
+ OR COALESCE(tor.request_type::text, '') ILIKE ${searchPlaceholder}
3081
+ )`);
3082
+ }
3083
+ const whereClause = where.join(' AND ');
3084
+ const baseQuery = `SELECT tor.id,
3072
3085
  tor.collaborator_id AS "collaboratorId",
3073
3086
  c.display_name AS "collaboratorName",
3074
3087
  tor.approver_collaborator_id AS "approverCollaboratorId",
3075
3088
  a.display_name AS "approverName",
3076
3089
  tor.request_type AS "requestType",
3077
- tor.start_date AS "startDate",
3078
- tor.end_date AS "endDate",
3090
+ tor.start_date::text AS "startDate",
3091
+ tor.end_date::text AS "endDate",
3079
3092
  tor.total_days AS "totalDays",
3080
3093
  tor.status,
3081
3094
  tor.reason,
@@ -3089,8 +3102,30 @@ let OperationsService = OperationsService_1 = class OperationsService {
3089
3102
  ON approval.target_type = 'time_off_request'
3090
3103
  AND approval.target_id = tor.id
3091
3104
  AND approval.deleted_at IS NULL
3092
- WHERE tor.deleted_at IS NULL AND ${filter.clause}
3093
- ORDER BY tor.start_date DESC, tor.id DESC`, filter.params);
3105
+ WHERE ${whereClause}`;
3106
+ if (!pagination) {
3107
+ return this.queryRows(`${baseQuery}
3108
+ ORDER BY tor.start_date DESC, tor.id DESC`, params);
3109
+ }
3110
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
3111
+ FROM operations_time_off_request tor
3112
+ JOIN operations_collaborator c ON c.id = tor.collaborator_id
3113
+ LEFT JOIN operations_collaborator a ON a.id = tor.approver_collaborator_id
3114
+ WHERE ${whereClause}`, params);
3115
+ const sortColumn = (_a = {
3116
+ startDate: 'tor.start_date',
3117
+ endDate: 'tor.end_date',
3118
+ collaboratorName: 'c.display_name',
3119
+ status: 'tor.status',
3120
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'tor.start_date';
3121
+ const queryParams = [...params];
3122
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
3123
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
3124
+ const rows = await this.queryRows(`${baseQuery}
3125
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, tor.id DESC
3126
+ LIMIT ${limitPlaceholder}
3127
+ OFFSET ${offsetPlaceholder}`, queryParams);
3128
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
3094
3129
  }
3095
3130
  async createTimeOffRequest(userId, data) {
3096
3131
  const actor = await this.getActorContext(userId);
@@ -3145,8 +3180,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
3145
3180
  collaborator_id AS "collaboratorId",
3146
3181
  approver_collaborator_id AS "approverCollaboratorId",
3147
3182
  request_type AS "requestType",
3148
- start_date AS "startDate",
3149
- end_date AS "endDate",
3183
+ start_date::text AS "startDate",
3184
+ end_date::text AS "endDate",
3150
3185
  total_days AS "totalDays",
3151
3186
  status,
3152
3187
  reason,
@@ -3155,17 +3190,56 @@ let OperationsService = OperationsService_1 = class OperationsService {
3155
3190
  FROM operations_time_off_request
3156
3191
  WHERE id = $1`, [created]);
3157
3192
  }
3158
- async listScheduleAdjustments(userId) {
3193
+ async listScheduleAdjustments(userId, filters = {}) {
3194
+ var _a, _b, _c, _d, _e, _f;
3159
3195
  const actor = await this.getActorContext(userId);
3160
3196
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'sar.collaborator_id', actor.isDirector);
3197
+ const pagination = this.shouldPaginate(filters)
3198
+ ? this.normalizePaginationParams(filters, {
3199
+ defaultSortField: 'effectiveStartDate',
3200
+ defaultSortOrder: 'desc',
3201
+ allowedSortFields: [
3202
+ 'effectiveStartDate',
3203
+ 'effectiveEndDate',
3204
+ 'collaboratorName',
3205
+ 'status',
3206
+ ],
3207
+ })
3208
+ : null;
3209
+ const params = [...filter.params];
3210
+ const where = ['sar.deleted_at IS NULL', filter.clause];
3211
+ if (filters.status && filters.status !== 'all') {
3212
+ where.push(`sar.status::text = ${this.param(params, filters.status)}`);
3213
+ }
3214
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
3215
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
3216
+ where.push(`(
3217
+ COALESCE(c.display_name, '') ILIKE ${searchPlaceholder}
3218
+ OR COALESCE(a.display_name, '') ILIKE ${searchPlaceholder}
3219
+ OR COALESCE(sar.reason, '') ILIKE ${searchPlaceholder}
3220
+ OR COALESCE(sar.request_scope::text, '') ILIKE ${searchPlaceholder}
3221
+ )`);
3222
+ }
3223
+ const whereClause = where.join(' AND ');
3224
+ const sortColumn = (_b = {
3225
+ effectiveStartDate: 'sar.effective_start_date',
3226
+ effectiveEndDate: 'sar.effective_end_date',
3227
+ collaboratorName: 'c.display_name',
3228
+ status: 'sar.status',
3229
+ }[(_a = pagination === null || pagination === void 0 ? void 0 : pagination.sortField) !== null && _a !== void 0 ? _a : 'effectiveStartDate']) !== null && _b !== void 0 ? _b : 'sar.effective_start_date';
3230
+ const queryParams = [...params];
3231
+ const limitSql = pagination
3232
+ ? `LIMIT ${this.param(queryParams, pagination.pageSize)}
3233
+ OFFSET ${this.param(queryParams, pagination.offset)}`
3234
+ : '';
3161
3235
  const requests = await this.queryRows(`SELECT sar.id,
3162
3236
  sar.collaborator_id AS "collaboratorId",
3163
3237
  c.display_name AS "collaboratorName",
3164
3238
  sar.approver_collaborator_id AS "approverCollaboratorId",
3165
3239
  a.display_name AS "approverName",
3166
3240
  sar.request_scope AS "requestScope",
3167
- sar.effective_start_date AS "effectiveStartDate",
3168
- sar.effective_end_date AS "effectiveEndDate",
3241
+ sar.effective_start_date::text AS "effectiveStartDate",
3242
+ sar.effective_end_date::text AS "effectiveEndDate",
3169
3243
  sar.status,
3170
3244
  sar.reason,
3171
3245
  sar.submitted_at AS "submittedAt",
@@ -3178,11 +3252,22 @@ let OperationsService = OperationsService_1 = class OperationsService {
3178
3252
  ON approval.target_type = 'schedule_adjustment_request'
3179
3253
  AND approval.target_id = sar.id
3180
3254
  AND approval.deleted_at IS NULL
3181
- WHERE sar.deleted_at IS NULL AND ${filter.clause}
3182
- ORDER BY sar.effective_start_date DESC, sar.id DESC`, filter.params);
3255
+ WHERE ${whereClause}
3256
+ ORDER BY ${sortColumn} ${(_d = (_c = pagination === null || pagination === void 0 ? void 0 : pagination.sortOrder) === null || _c === void 0 ? void 0 : _c.toUpperCase()) !== null && _d !== void 0 ? _d : 'DESC'}, sar.id DESC
3257
+ ${limitSql}`, queryParams);
3183
3258
  if (!requests.length) {
3259
+ if (pagination) {
3260
+ return this.buildPaginationResult([], 0, pagination.page, pagination.pageSize);
3261
+ }
3184
3262
  return requests;
3185
3263
  }
3264
+ const total = pagination
3265
+ ? Number((_f = (_e = (await this.querySingle(`SELECT COUNT(*)::text AS total
3266
+ FROM operations_schedule_adjustment_request sar
3267
+ JOIN operations_collaborator c ON c.id = sar.collaborator_id
3268
+ LEFT JOIN operations_collaborator a ON a.id = sar.approver_collaborator_id
3269
+ WHERE ${whereClause}`, params))) === null || _e === void 0 ? void 0 : _e.total) !== null && _f !== void 0 ? _f : 0)
3270
+ : 0;
3186
3271
  const days = await this.queryRows(`SELECT schedule_adjustment_request_id AS "requestId",
3187
3272
  weekday,
3188
3273
  is_working_day AS "isWorkingDay",
@@ -3192,7 +3277,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
3192
3277
  FROM operations_schedule_adjustment_day
3193
3278
  WHERE schedule_adjustment_request_id = ANY($1::int[])
3194
3279
  ORDER BY id ASC`, [requests.map((item) => item.id)]);
3195
- const currentSchedule = await this.queryRows(`SELECT collaborator_id AS "collaboratorId",
3280
+ const currentSchedule = await this.queryRows(`SELECT DISTINCT ON (collaborator_id, weekday)
3281
+ collaborator_id AS "collaboratorId",
3196
3282
  weekday,
3197
3283
  is_working_day AS "isWorkingDay",
3198
3284
  start_time AS "startTime",
@@ -3200,13 +3286,17 @@ let OperationsService = OperationsService_1 = class OperationsService {
3200
3286
  break_minutes AS "breakMinutes"
3201
3287
  FROM operations_collaborator_schedule_day
3202
3288
  WHERE collaborator_id = ANY($1::int[])
3203
- ORDER BY id ASC`, [this.uniqueNumbers(requests.map((item) => item.collaboratorId))]);
3289
+ ORDER BY collaborator_id, weekday, id DESC`, [this.uniqueNumbers(requests.map((item) => item.collaboratorId))]);
3204
3290
  const grouped = this.groupBy(days, 'requestId');
3205
3291
  const currentScheduleByCollaborator = this.groupBy(currentSchedule, 'collaboratorId');
3206
- return requests.map((request) => {
3292
+ const data = requests.map((request) => {
3207
3293
  var _a, _b;
3208
3294
  return (Object.assign(Object.assign({}, request), { days: (_a = grouped[request.id]) !== null && _a !== void 0 ? _a : [], currentSchedule: (_b = currentScheduleByCollaborator[request.collaboratorId]) !== null && _b !== void 0 ? _b : [] }));
3209
3295
  });
3296
+ if (!pagination) {
3297
+ return data;
3298
+ }
3299
+ return this.buildPaginationResult(data, total, pagination.page, pagination.pageSize);
3210
3300
  }
3211
3301
  async createScheduleAdjustmentRequest(userId, data) {
3212
3302
  const actor = await this.getActorContext(userId);
@@ -3278,8 +3368,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
3278
3368
  collaborator_id AS "collaboratorId",
3279
3369
  approver_collaborator_id AS "approverCollaboratorId",
3280
3370
  request_scope AS "requestScope",
3281
- effective_start_date AS "effectiveStartDate",
3282
- effective_end_date AS "effectiveEndDate",
3371
+ effective_start_date::text AS "effectiveStartDate",
3372
+ effective_end_date::text AS "effectiveEndDate",
3283
3373
  status,
3284
3374
  reason,
3285
3375
  submitted_at AS "submittedAt",
@@ -3287,14 +3377,138 @@ let OperationsService = OperationsService_1 = class OperationsService {
3287
3377
  FROM operations_schedule_adjustment_request
3288
3378
  WHERE id = $1`, [created]);
3289
3379
  }
3290
- async listApprovals(userId) {
3380
+ async listApprovals(userId, filters = {}) {
3381
+ var _a, _b;
3382
+ const actor = await this.getActorContext(userId);
3383
+ this.ensureSupervisor(actor);
3384
+ const params = [];
3385
+ const clause = actor.isDirector
3386
+ ? 'a.deleted_at IS NULL'
3387
+ : `a.deleted_at IS NULL AND a.approver_collaborator_id = ${this.param(params, actor.collaboratorId)}`;
3388
+ const pagination = this.shouldPaginate(filters)
3389
+ ? this.normalizePaginationParams(filters, {
3390
+ defaultSortField: 'submittedAt',
3391
+ defaultSortOrder: 'desc',
3392
+ allowedSortFields: ['submittedAt', 'decidedAt', 'requesterName', 'status'],
3393
+ })
3394
+ : null;
3395
+ const where = [clause];
3396
+ if (filters.status && filters.status !== 'all') {
3397
+ where.push(`a.status::text = ${this.param(params, filters.status)}`);
3398
+ }
3399
+ if (filters.targetType && filters.targetType !== 'all') {
3400
+ where.push(`a.target_type = ${this.param(params, filters.targetType)}::text::operations_approval_target_type_32d3f04385_enum`);
3401
+ }
3402
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
3403
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
3404
+ where.push(`(
3405
+ COALESCE(requester.display_name, '') ILIKE ${searchPlaceholder}
3406
+ OR COALESCE(approver.display_name, '') ILIKE ${searchPlaceholder}
3407
+ OR COALESCE(a.decision_note, '') ILIKE ${searchPlaceholder}
3408
+ OR COALESCE(tor.reason, '') ILIKE ${searchPlaceholder}
3409
+ OR COALESCE(sar.reason, '') ILIKE ${searchPlaceholder}
3410
+ )`);
3411
+ }
3412
+ const whereClause = where.join(' AND ');
3413
+ const baseQuery = `SELECT a.id,
3414
+ a.target_type AS "targetType",
3415
+ a.target_id AS "targetId",
3416
+ a.requester_collaborator_id AS "requesterCollaboratorId",
3417
+ requester.display_name AS "requesterName",
3418
+ a.approver_collaborator_id AS "approverCollaboratorId",
3419
+ approver.display_name AS "approverName",
3420
+ a.status,
3421
+ a.submitted_at AS "submittedAt",
3422
+ a.decided_at AS "decidedAt",
3423
+ a.decision_note AS "decisionNote",
3424
+ t.week_start_date AS "timesheetWeekStartDate",
3425
+ t.week_end_date AS "timesheetWeekEndDate",
3426
+ t.total_hours AS "timesheetTotalHours",
3427
+ COALESCE(
3428
+ STRING_AGG(DISTINCT p.name, ', ') FILTER (WHERE p.name IS NOT NULL),
3429
+ ''
3430
+ ) AS "timesheetProjectNames",
3431
+ tor.request_type AS "timeOffType",
3432
+ tor.start_date::text AS "timeOffStartDate",
3433
+ tor.end_date::text AS "timeOffEndDate",
3434
+ tor.reason AS "timeOffReason",
3435
+ sar.request_scope AS "scheduleRequestScope",
3436
+ sar.effective_start_date::text AS "scheduleStartDate",
3437
+ sar.effective_end_date::text AS "scheduleEndDate",
3438
+ sar.reason AS "scheduleReason"
3439
+ FROM operations_approval a
3440
+ JOIN operations_collaborator requester
3441
+ ON requester.id = a.requester_collaborator_id
3442
+ LEFT JOIN operations_collaborator approver
3443
+ ON approver.id = a.approver_collaborator_id
3444
+ LEFT JOIN operations_timesheet t
3445
+ ON a.target_type = 'timesheet'
3446
+ AND t.id = a.target_id
3447
+ LEFT JOIN operations_timesheet_entry te
3448
+ ON te.timesheet_id = t.id
3449
+ AND te.deleted_at IS NULL
3450
+ LEFT JOIN operations_project_assignment pa
3451
+ ON pa.id = te.project_assignment_id
3452
+ LEFT JOIN operations_project p
3453
+ ON p.id = pa.project_id
3454
+ LEFT JOIN operations_time_off_request tor
3455
+ ON a.target_type = 'time_off_request'
3456
+ AND tor.id = a.target_id
3457
+ LEFT JOIN operations_schedule_adjustment_request sar
3458
+ ON a.target_type = 'schedule_adjustment_request'
3459
+ AND sar.id = a.target_id
3460
+ WHERE ${whereClause}
3461
+ GROUP BY a.id, requester.id, approver.id, t.id, tor.id, sar.id`;
3462
+ if (!pagination) {
3463
+ return this.queryRows(`${baseQuery}
3464
+ ORDER BY a.submitted_at DESC, a.id DESC`, params);
3465
+ }
3466
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
3467
+ FROM operations_approval a
3468
+ JOIN operations_collaborator requester
3469
+ ON requester.id = a.requester_collaborator_id
3470
+ LEFT JOIN operations_collaborator approver
3471
+ ON approver.id = a.approver_collaborator_id
3472
+ LEFT JOIN operations_timesheet t
3473
+ ON a.target_type = 'timesheet'
3474
+ AND t.id = a.target_id
3475
+ LEFT JOIN operations_time_off_request tor
3476
+ ON a.target_type = 'time_off_request'
3477
+ AND tor.id = a.target_id
3478
+ LEFT JOIN operations_schedule_adjustment_request sar
3479
+ ON a.target_type = 'schedule_adjustment_request'
3480
+ AND sar.id = a.target_id
3481
+ WHERE ${whereClause}`, params);
3482
+ const sortColumn = (_a = {
3483
+ submittedAt: 'a.submitted_at',
3484
+ decidedAt: 'a.decided_at',
3485
+ requesterName: 'requester.display_name',
3486
+ status: 'a.status',
3487
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'a.submitted_at';
3488
+ const queryParams = [...params];
3489
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
3490
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
3491
+ const rows = await this.queryRows(`${baseQuery}
3492
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, a.id DESC
3493
+ LIMIT ${limitPlaceholder}
3494
+ OFFSET ${offsetPlaceholder}`, queryParams);
3495
+ return this.buildPaginationResult(rows, Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
3496
+ }
3497
+ async approve(userId, approvalId, data) {
3498
+ return this.decideApproval(userId, approvalId, 'approve', data);
3499
+ }
3500
+ async reject(userId, approvalId, data) {
3501
+ return this.decideApproval(userId, approvalId, 'reject', data);
3502
+ }
3503
+ async getApprovalDetail(userId, approvalId) {
3291
3504
  const actor = await this.getActorContext(userId);
3292
3505
  this.ensureSupervisor(actor);
3293
3506
  const params = [];
3294
- const clause = actor.isDirector
3507
+ const actorClause = actor.isDirector
3295
3508
  ? 'a.deleted_at IS NULL'
3296
3509
  : `a.deleted_at IS NULL AND a.approver_collaborator_id = ${this.param(params, actor.collaboratorId)}`;
3297
- return this.queryRows(`SELECT a.id,
3510
+ const idPlaceholder = this.param(params, approvalId);
3511
+ const row = await this.querySingle(`SELECT a.id,
3298
3512
  a.target_type AS "targetType",
3299
3513
  a.target_id AS "targetId",
3300
3514
  a.requester_collaborator_id AS "requesterCollaboratorId",
@@ -3313,12 +3527,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
3313
3527
  ''
3314
3528
  ) AS "timesheetProjectNames",
3315
3529
  tor.request_type AS "timeOffType",
3316
- tor.start_date AS "timeOffStartDate",
3317
- tor.end_date AS "timeOffEndDate",
3530
+ tor.start_date::text AS "timeOffStartDate",
3531
+ tor.end_date::text AS "timeOffEndDate",
3318
3532
  tor.reason AS "timeOffReason",
3319
3533
  sar.request_scope AS "scheduleRequestScope",
3320
- sar.effective_start_date AS "scheduleStartDate",
3321
- sar.effective_end_date AS "scheduleEndDate",
3534
+ sar.effective_start_date::text AS "scheduleStartDate",
3535
+ sar.effective_end_date::text AS "scheduleEndDate",
3322
3536
  sar.reason AS "scheduleReason"
3323
3537
  FROM operations_approval a
3324
3538
  JOIN operations_collaborator requester
@@ -3341,15 +3555,64 @@ let OperationsService = OperationsService_1 = class OperationsService {
3341
3555
  LEFT JOIN operations_schedule_adjustment_request sar
3342
3556
  ON a.target_type = 'schedule_adjustment_request'
3343
3557
  AND sar.id = a.target_id
3344
- WHERE ${clause}
3345
- GROUP BY a.id, requester.id, approver.id, t.id, tor.id, sar.id
3346
- ORDER BY a.submitted_at DESC, a.id DESC`, params);
3347
- }
3348
- async approve(userId, approvalId, data) {
3349
- return this.decideApproval(userId, approvalId, 'approve', data);
3350
- }
3351
- async reject(userId, approvalId, data) {
3352
- return this.decideApproval(userId, approvalId, 'reject', data);
3558
+ WHERE ${actorClause}
3559
+ AND a.id = ${idPlaceholder}
3560
+ GROUP BY a.id, requester.id, approver.id, t.id, tor.id, sar.id`, params);
3561
+ if (!row) {
3562
+ throw new Error('Approval not found or access denied');
3563
+ }
3564
+ if (row.targetType === 'timesheet') {
3565
+ const entries = await this.queryRows(`SELECT e.id,
3566
+ e.timesheet_id AS "timesheetId",
3567
+ e.project_assignment_id AS "projectAssignmentId",
3568
+ pa.project_id AS "projectId",
3569
+ p.name AS "projectName",
3570
+ pa.role_label AS "roleLabel",
3571
+ e.task_id AS "taskId",
3572
+ task_record.name AS "taskName",
3573
+ COALESCE(task_record.name, e.activity_label) AS "activityLabel",
3574
+ e.work_date AS "workDate",
3575
+ COALESCE(NULLIF(e.duration_minutes, 0), ROUND(COALESCE(e.hours, 0)::numeric * 60))::int AS "durationMinutes",
3576
+ COALESCE(
3577
+ e.hours,
3578
+ ROUND((COALESCE(NULLIF(e.duration_minutes, 0), 0)::numeric / 60), 2)
3579
+ ) AS hours,
3580
+ e.description
3581
+ FROM operations_timesheet_entry e
3582
+ LEFT JOIN operations_project_assignment pa ON pa.id = e.project_assignment_id
3583
+ LEFT JOIN operations_project p ON p.id = pa.project_id
3584
+ LEFT JOIN operations_task task_record
3585
+ ON task_record.id = e.task_id
3586
+ AND task_record.deleted_at IS NULL
3587
+ WHERE e.deleted_at IS NULL
3588
+ AND e.timesheet_id = $1
3589
+ ORDER BY e.work_date ASC, e.id ASC`, [row.targetId]);
3590
+ return Object.assign(Object.assign({}, row), { entries });
3591
+ }
3592
+ if (row.targetType === 'schedule_adjustment_request') {
3593
+ const days = await this.queryRows(`SELECT schedule_adjustment_request_id AS "requestId",
3594
+ weekday,
3595
+ is_working_day AS "isWorkingDay",
3596
+ start_time AS "startTime",
3597
+ end_time AS "endTime",
3598
+ break_minutes AS "breakMinutes"
3599
+ FROM operations_schedule_adjustment_day
3600
+ WHERE schedule_adjustment_request_id = $1
3601
+ ORDER BY id ASC`, [row.targetId]);
3602
+ const collaboratorIdParam = [row.requesterCollaboratorId];
3603
+ const currentSchedule = await this.queryRows(`SELECT DISTINCT ON (collaborator_id, weekday)
3604
+ collaborator_id AS "collaboratorId",
3605
+ weekday,
3606
+ is_working_day AS "isWorkingDay",
3607
+ start_time AS "startTime",
3608
+ end_time AS "endTime",
3609
+ break_minutes AS "breakMinutes"
3610
+ FROM operations_collaborator_schedule_day
3611
+ WHERE collaborator_id = $1
3612
+ ORDER BY collaborator_id, weekday, id DESC`, collaboratorIdParam);
3613
+ return Object.assign(Object.assign({}, row), { days, currentSchedule });
3614
+ }
3615
+ return row;
3353
3616
  }
3354
3617
  async publishAccountsPayableReference(userId, data) {
3355
3618
  var _a, _b, _c, _d;
@@ -3524,17 +3787,44 @@ let OperationsService = OperationsService_1 = class OperationsService {
3524
3787
  };
3525
3788
  }
3526
3789
  async getCollaboratorByUserId(userId) {
3527
- return this.querySingle(`SELECT c.id,
3790
+ return this.querySingle(`WITH linked_collaborator AS (
3791
+ SELECT person_id
3792
+ FROM operations_collaborator
3793
+ WHERE user_id = $1
3794
+ AND deleted_at IS NULL
3795
+ ORDER BY id DESC
3796
+ LIMIT 1
3797
+ )
3798
+ SELECT c.id,
3799
+ c.user_id AS "userId",
3800
+ c.person_id AS "personId",
3528
3801
  COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
3529
3802
  s.id AS "supervisorId",
3530
- s.display_name AS "supervisorName"
3803
+ s.display_name AS "supervisorName",
3804
+ COUNT(pa.id) FILTER (
3805
+ WHERE pa.deleted_at IS NULL
3806
+ AND pa.status IN ('planned', 'active')
3807
+ )::int AS "activeAssignments"
3531
3808
  FROM operations_collaborator c
3532
3809
  LEFT JOIN person person_record
3533
3810
  ON person_record.id = c.person_id
3534
3811
  LEFT JOIN operations_collaborator s
3535
3812
  ON s.id = c.supervisor_collaborator_id
3536
- WHERE c.user_id = $1
3537
- AND c.deleted_at IS NULL`, [userId]);
3813
+ LEFT JOIN operations_project_assignment pa
3814
+ ON pa.collaborator_id = c.id
3815
+ WHERE c.deleted_at IS NULL
3816
+ AND (
3817
+ c.user_id = $1
3818
+ OR (
3819
+ c.person_id IS NOT NULL
3820
+ AND c.person_id = (SELECT person_id FROM linked_collaborator)
3821
+ )
3822
+ )
3823
+ GROUP BY c.id, person_record.id, s.id
3824
+ ORDER BY "activeAssignments" DESC,
3825
+ CASE WHEN c.user_id = $1 THEN 0 ELSE 1 END,
3826
+ c.id ASC
3827
+ LIMIT 1`, [userId]);
3538
3828
  }
3539
3829
  async getCollaboratorById(collaboratorId) {
3540
3830
  const collaborator = await this.querySingle(`SELECT c.id,
@@ -3916,73 +4206,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
3916
4206
  LIMIT 1`, normalizedLookup));
3917
4207
  return (_b = collaboratorTypes[0]) !== null && _b !== void 0 ? _b : null;
3918
4208
  }
3919
- async getContractTemplateRecord(client, templateId, includeInactive = false) {
3920
- const template = (await client.$queryRawUnsafe(`SELECT t.id,
3921
- t.slug,
3922
- t.code,
3923
- t.name,
3924
- t.description,
3925
- t.contract_category AS "contractCategory",
3926
- t.contract_type AS "contractType",
3927
- t.billing_model AS "billingModel",
3928
- t.signature_status AS "signatureStatus",
3929
- t.is_active AS "isActive",
3930
- t.status,
3931
- t.content_html AS "contentHtml",
3932
- COUNT(DISTINCT c.id)::int AS "usageCount",
3933
- t.created_at AS "createdAt",
3934
- t.updated_at AS "updatedAt"
3935
- FROM operations_contract_template t
3936
- LEFT JOIN operations_contract c
3937
- ON c.contract_template_id = t.id
3938
- AND c.deleted_at IS NULL
3939
- WHERE t.id = $1
3940
- AND ($2::boolean = true OR t.deleted_at IS NULL)
3941
- GROUP BY t.id
3942
- LIMIT 1`, templateId, includeInactive));
3943
- const record = template[0];
3944
- if (!record) {
3945
- throw new common_1.NotFoundException('Contract template not found.');
3946
- }
3947
- return record;
3948
- }
3949
- async assertContractTemplateNameAvailable(client, name, excludeTemplateId) {
3950
- const existing = (await client.$queryRawUnsafe(`SELECT id
3951
- FROM operations_contract_template
3952
- WHERE LOWER(name) = LOWER($1)
3953
- AND deleted_at IS NULL
3954
- AND ($2::int IS NULL OR id <> $2)
3955
- LIMIT 1`, name, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
3956
- if (existing[0]) {
3957
- throw new common_1.BadRequestException('A contract template with this name already exists.');
3958
- }
3959
- }
3960
- async assertContractTemplateCodeAvailable(client, code, excludeTemplateId) {
3961
- const existing = (await client.$queryRawUnsafe(`SELECT id
3962
- FROM operations_contract_template
3963
- WHERE UPPER(COALESCE(code, '')) = UPPER($1)
3964
- AND deleted_at IS NULL
3965
- AND ($2::int IS NULL OR id <> $2)
3966
- LIMIT 1`, code, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
3967
- if (existing[0]) {
3968
- throw new common_1.BadRequestException('A contract template with this code already exists.');
3969
- }
3970
- }
3971
- async generateUniqueContractTemplateSlug(client, label, excludeTemplateId) {
3972
- const baseSlug = this.slugifyValue(label) || `contract-template-${Date.now().toString(36)}`;
3973
- for (let attempt = 0; attempt < 25; attempt += 1) {
3974
- const candidate = attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`;
3975
- const existing = (await client.$queryRawUnsafe(`SELECT id
3976
- FROM operations_contract_template
3977
- WHERE slug = $1
3978
- AND ($2::int IS NULL OR id <> $2)
3979
- LIMIT 1`, candidate, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
3980
- if (!existing.length) {
3981
- return candidate;
3982
- }
3983
- }
3984
- return `${baseSlug}-${Date.now().toString(36)}`;
3985
- }
3986
4209
  slugifyValue(value) {
3987
4210
  return (value !== null && value !== void 0 ? value : '')
3988
4211
  .normalize('NFKD')
@@ -4108,7 +4331,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
4108
4331
  WHERE pa.project_id = $1
4109
4332
  AND pa.deleted_at IS NULL`, [projectId]),
4110
4333
  this.querySingle(`SELECT COUNT(*) FILTER (WHERE status IN ('planned', 'active'))::text AS "activeAssignments",
4111
- COUNT(*) FILTER (WHERE is_billable = true AND status IN ('planned', 'active'))::text AS "billableAssignments",
4334
+ COUNT(*) FILTER (WHERE status = 'completed')::text AS "completedAssignments",
4112
4335
  COALESCE(AVG(allocation_percent), 0)::text AS "averageAllocation",
4113
4336
  COALESCE(SUM(weekly_hours), 0)::text AS "totalWeeklyHours"
4114
4337
  FROM operations_project_assignment
@@ -4122,11 +4345,70 @@ let OperationsService = OperationsService_1 = class OperationsService {
4122
4345
  totalHours: Number((_c = timesheetSummary === null || timesheetSummary === void 0 ? void 0 : timesheetSummary.totalHours) !== null && _c !== void 0 ? _c : 0),
4123
4346
  }, operationalIndicators: {
4124
4347
  activeAssignments: Number((_d = operationalIndicators === null || operationalIndicators === void 0 ? void 0 : operationalIndicators.activeAssignments) !== null && _d !== void 0 ? _d : 0),
4125
- billableAssignments: Number((_e = operationalIndicators === null || operationalIndicators === void 0 ? void 0 : operationalIndicators.billableAssignments) !== null && _e !== void 0 ? _e : 0),
4348
+ completedAssignments: Number((_e = operationalIndicators === null || operationalIndicators === void 0 ? void 0 : operationalIndicators.completedAssignments) !== null && _e !== void 0 ? _e : 0),
4126
4349
  averageAllocation: Number((_f = operationalIndicators === null || operationalIndicators === void 0 ? void 0 : operationalIndicators.averageAllocation) !== null && _f !== void 0 ? _f : 0),
4127
4350
  totalWeeklyHours: Number((_g = operationalIndicators === null || operationalIndicators === void 0 ? void 0 : operationalIndicators.totalWeeklyHours) !== null && _g !== void 0 ? _g : 0),
4128
4351
  } });
4129
4352
  }
4353
+ async getProjectStats(userId, projectId) {
4354
+ var _a, _b, _c;
4355
+ const [weeklyVelocityRows, allocationRows, quickRadarRow] = await Promise.all([
4356
+ this.prisma.$queryRawUnsafe(`SELECT TO_CHAR(DATE_TRUNC('week', e.work_date), 'DD/MM') AS "weekStart",
4357
+ COALESCE(SUM(e.hours), 0)::text AS "loggedHours"
4358
+ FROM operations_project_assignment pa
4359
+ JOIN operations_timesheet_entry e ON e.project_assignment_id = pa.id AND e.deleted_at IS NULL
4360
+ WHERE pa.project_id = $1
4361
+ AND pa.deleted_at IS NULL
4362
+ AND e.work_date >= NOW() - INTERVAL '7 weeks'
4363
+ GROUP BY DATE_TRUNC('week', e.work_date)
4364
+ ORDER BY DATE_TRUNC('week', e.work_date) ASC
4365
+ LIMIT 7`, projectId),
4366
+ this.prisma.$queryRawUnsafe(`SELECT c.display_name AS "name",
4367
+ COALESCE(pa.allocation_percent, 0)::text AS "allocation"
4368
+ FROM operations_project_assignment pa
4369
+ JOIN operations_collaborator c ON c.id = pa.collaborator_id AND c.deleted_at IS NULL
4370
+ WHERE pa.project_id = $1
4371
+ AND pa.deleted_at IS NULL
4372
+ AND pa.status IN ('planned', 'active')
4373
+ ORDER BY c.display_name ASC
4374
+ LIMIT 6`, projectId),
4375
+ this.prisma.$queryRawUnsafe(`SELECT
4376
+ (SELECT COUNT(*)::text
4377
+ FROM operations_task tk
4378
+ WHERE tk.project_id = $1
4379
+ AND tk.deleted_at IS NULL
4380
+ AND tk.assignee_collaborator_id IS NOT NULL
4381
+ AND tk.status IN ('todo', 'doing', 'review')
4382
+ ) AS "activeAssignments",
4383
+ COUNT(DISTINCT ts.id) FILTER (WHERE ts.status = 'submitted')::text AS "pendingTimesheets",
4384
+ COALESCE(SUM(pa.weekly_hours) FILTER (WHERE pa.status IN ('planned', 'active')), 0)::text AS "totalWeeklyHours"
4385
+ FROM operations_project_assignment pa
4386
+ LEFT JOIN operations_timesheet_entry e ON e.project_assignment_id = pa.id AND e.deleted_at IS NULL
4387
+ LEFT JOIN operations_timesheet ts ON ts.id = e.timesheet_id AND ts.deleted_at IS NULL
4388
+ WHERE pa.project_id = $1 AND pa.deleted_at IS NULL`, projectId).then(rows => Array.isArray(rows) ? rows[0] : rows),
4389
+ ]);
4390
+ const getInitials = (name) => name
4391
+ .split(' ')
4392
+ .map((part) => part[0])
4393
+ .slice(0, 2)
4394
+ .join('')
4395
+ .toUpperCase();
4396
+ return {
4397
+ weeklyVelocity: weeklyVelocityRows.map((row) => ({
4398
+ weekLabel: row.weekStart,
4399
+ loggedHours: Number(row.loggedHours),
4400
+ })),
4401
+ allocationByCollaborator: allocationRows.map((row) => ({
4402
+ name: getInitials(row.name),
4403
+ allocation: Math.round(Number(row.allocation)),
4404
+ })),
4405
+ quickRadar: {
4406
+ activeAssignments: Number((_a = quickRadarRow === null || quickRadarRow === void 0 ? void 0 : quickRadarRow.activeAssignments) !== null && _a !== void 0 ? _a : 0),
4407
+ pendingTimesheets: Number((_b = quickRadarRow === null || quickRadarRow === void 0 ? void 0 : quickRadarRow.pendingTimesheets) !== null && _b !== void 0 ? _b : 0),
4408
+ totalWeeklyHours: Number((_c = quickRadarRow === null || quickRadarRow === void 0 ? void 0 : quickRadarRow.totalWeeklyHours) !== null && _c !== void 0 ? _c : 0),
4409
+ },
4410
+ };
4411
+ }
4130
4412
  async getCollaboratorDetails(collaboratorId) {
4131
4413
  var _a, _b, _c, _d, _e, _f;
4132
4414
  const collaborator = await this.querySingle(`SELECT c.id,
@@ -4268,8 +4550,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
4268
4550
  AND deleted_at IS NULL`, [collaboratorId]),
4269
4551
  this.queryRows(`SELECT id,
4270
4552
  request_scope AS "requestScope",
4271
- effective_start_date AS "effectiveStartDate",
4272
- effective_end_date AS "effectiveEndDate",
4553
+ effective_start_date::text AS "effectiveStartDate",
4554
+ effective_end_date::text AS "effectiveEndDate",
4273
4555
  status,
4274
4556
  reason
4275
4557
  FROM operations_schedule_adjustment_request
@@ -4584,7 +4866,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
4584
4866
  return timesheet;
4585
4867
  }
4586
4868
  async replaceTimesheetEntries(client, timesheetId, entries, collaboratorId) {
4587
- var _a, _b, _c, _d, _e, _f;
4869
+ var _a, _b, _c, _d, _e, _f, _g, _h;
4588
4870
  await client.$executeRawUnsafe(`UPDATE operations_timesheet_entry
4589
4871
  SET deleted_at = NOW()
4590
4872
  WHERE timesheet_id = $1
@@ -4594,17 +4876,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
4594
4876
  const assignmentIds = entries
4595
4877
  .map((entry) => entry.projectAssignmentId)
4596
4878
  .filter((value) => typeof value === 'number');
4879
+ const assignmentMap = new Map();
4597
4880
  if (assignmentIds.length) {
4598
- const assignments = (await client.$queryRawUnsafe(`SELECT id, collaborator_id AS "collaboratorId"
4881
+ const assignments = (await client.$queryRawUnsafe(`SELECT id,
4882
+ collaborator_id AS "collaboratorId",
4883
+ project_id AS "projectId"
4599
4884
  FROM operations_project_assignment
4600
4885
  WHERE id = ANY($1::int[])
4601
4886
  AND deleted_at IS NULL`, assignmentIds));
4602
- const assignmentMap = new Map(assignments.map((assignment) => [
4603
- assignment.id,
4604
- assignment.collaboratorId,
4605
- ]));
4887
+ assignments.forEach((assignment) => {
4888
+ assignmentMap.set(assignment.id, assignment);
4889
+ });
4606
4890
  for (const assignmentId of assignmentIds) {
4607
- if (assignmentMap.get(assignmentId) !== collaboratorId) {
4891
+ if (((_a = assignmentMap.get(assignmentId)) === null || _a === void 0 ? void 0 : _a.collaboratorId) !== collaboratorId) {
4608
4892
  throw new common_1.ForbiddenException('Timesheet entries must use assignments owned by the target collaborator.');
4609
4893
  }
4610
4894
  }
@@ -4615,16 +4899,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
4615
4899
  const resolvedTask = entry.taskId
4616
4900
  ? await this.getOwnedTaskRecord(client, collaboratorId, entry.taskId)
4617
4901
  : null;
4902
+ const selectedAssignment = entry.projectAssignmentId
4903
+ ? (_b = assignmentMap.get(entry.projectAssignmentId)) !== null && _b !== void 0 ? _b : null
4904
+ : null;
4618
4905
  if (resolvedTask &&
4619
- entry.projectAssignmentId &&
4620
- resolvedTask.projectAssignmentId !== entry.projectAssignmentId) {
4621
- throw new common_1.BadRequestException('The selected task does not belong to the chosen project assignment.');
4906
+ selectedAssignment &&
4907
+ resolvedTask.projectId !== selectedAssignment.projectId) {
4908
+ throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
4622
4909
  }
4623
- const resolvedAssignmentId = (_b = (_a = entry.projectAssignmentId) !== null && _a !== void 0 ? _a : resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.projectAssignmentId) !== null && _b !== void 0 ? _b : null;
4910
+ const resolvedAssignmentId = (_d = (_c = entry.projectAssignmentId) !== null && _c !== void 0 ? _c : resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.projectAssignmentId) !== null && _d !== void 0 ? _d : null;
4624
4911
  if (entry.taskId && !resolvedAssignmentId) {
4625
4912
  throw new common_1.BadRequestException('The selected task must belong to a project assignment.');
4626
4913
  }
4627
- const activityLabel = (_d = (_c = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _c !== void 0 ? _c : this.normalizeOptionalText(entry.taskName)) !== null && _d !== void 0 ? _d : this.normalizeOptionalText(entry.activityLabel);
4914
+ const activityLabel = (_f = (_e = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _e !== void 0 ? _e : this.normalizeOptionalText(entry.taskName)) !== null && _f !== void 0 ? _f : this.normalizeOptionalText(entry.activityLabel);
4628
4915
  if (!entry.workDate) {
4629
4916
  throw new common_1.BadRequestException('Timesheet entry workDate is required.');
4630
4917
  }
@@ -4639,7 +4926,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
4639
4926
  description,
4640
4927
  created_at,
4641
4928
  updated_at
4642
- ) VALUES ($1, $2, $3, $4, $5::date, $6, $7, $8, NOW(), NOW())`, timesheetId, resolvedAssignmentId, (_f = (_e = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.id) !== null && _e !== void 0 ? _e : entry.taskId) !== null && _f !== void 0 ? _f : null, activityLabel !== null && activityLabel !== void 0 ? activityLabel : null, entry.workDate, durationMinutes, hours, this.normalizeOptionalText(entry.description));
4929
+ ) VALUES ($1, $2, $3, $4, $5::date, $6, $7, $8, NOW(), NOW())`, timesheetId, resolvedAssignmentId, (_h = (_g = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.id) !== null && _g !== void 0 ? _g : entry.taskId) !== null && _h !== void 0 ? _h : null, activityLabel !== null && activityLabel !== void 0 ? activityLabel : null, entry.workDate, durationMinutes, hours, this.normalizeOptionalText(entry.description));
4643
4930
  }
4644
4931
  }
4645
4932
  async refreshTimesheetTotal(client, timesheetId) {
@@ -4665,6 +4952,29 @@ let OperationsService = OperationsService_1 = class OperationsService {
4665
4952
  updated_at = NOW()
4666
4953
  WHERE id = $1`, timesheetId);
4667
4954
  }
4955
+ async cleanupEmptyEditableTimesheet(client, timesheetId) {
4956
+ var _a;
4957
+ const candidate = (await client.$queryRawUnsafe(`SELECT t.id
4958
+ FROM operations_timesheet t
4959
+ WHERE t.id = $1
4960
+ AND t.deleted_at IS NULL
4961
+ AND t.status IN ('draft', 'rejected')
4962
+ AND NOT EXISTS (
4963
+ SELECT 1
4964
+ FROM operations_timesheet_entry e
4965
+ WHERE e.timesheet_id = t.id
4966
+ AND e.deleted_at IS NULL
4967
+ )
4968
+ LIMIT 1`, timesheetId));
4969
+ if (!((_a = candidate[0]) === null || _a === void 0 ? void 0 : _a.id)) {
4970
+ return;
4971
+ }
4972
+ await client.$executeRawUnsafe(`UPDATE operations_timesheet
4973
+ SET deleted_at = NOW(),
4974
+ updated_at = NOW()
4975
+ WHERE id = $1
4976
+ AND deleted_at IS NULL`, timesheetId);
4977
+ }
4668
4978
  async upsertApproval(client, input) {
4669
4979
  var _a, _b;
4670
4980
  const existing = (await client.$queryRawUnsafe(`SELECT id
@@ -4844,7 +5154,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
4844
5154
  }
4845
5155
  }
4846
5156
  async replaceProjectAssignments(client, projectId, teamAssignments) {
4847
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
5157
+ var _a, _b, _c, _d, _e, _f, _g, _h;
4848
5158
  await client.$executeRawUnsafe(`UPDATE operations_project_assignment
4849
5159
  SET deleted_at = NOW(),
4850
5160
  updated_at = NOW()
@@ -4863,17 +5173,16 @@ let OperationsService = OperationsService_1 = class OperationsService {
4863
5173
  role_label,
4864
5174
  allocation_percent,
4865
5175
  weekly_hours,
4866
- is_billable,
4867
5176
  start_date,
4868
5177
  end_date,
4869
5178
  status,
4870
5179
  created_at,
4871
5180
  updated_at
4872
5181
  ) VALUES (
4873
- $1, $2, $3, $4, $5, $6, $7, $8::date, $9::date,
4874
- $10::operations_project_assignment_status_155b459bbf_enum,
5182
+ $1, $2, $3, $4, $5, $6, $7::date, $8::date,
5183
+ $9::operations_project_assignment_status_155b459bbf_enum,
4875
5184
  NOW(), NOW()
4876
- )`, projectId, assignment.collaboratorId, (_a = projectRole === null || projectRole === void 0 ? void 0 : projectRole.id) !== null && _a !== void 0 ? _a : null, (_c = (_b = this.normalizeOptionalText(assignment.roleLabel)) !== null && _b !== void 0 ? _b : projectRole === null || projectRole === void 0 ? void 0 : projectRole.name) !== null && _c !== void 0 ? _c : 'Team Member', (_d = assignment.allocationPercent) !== null && _d !== void 0 ? _d : null, (_e = assignment.weeklyHours) !== null && _e !== void 0 ? _e : null, (_f = assignment.isBillable) !== null && _f !== void 0 ? _f : true, (_g = assignment.startDate) !== null && _g !== void 0 ? _g : null, (_h = assignment.endDate) !== null && _h !== void 0 ? _h : null, (_j = assignment.status) !== null && _j !== void 0 ? _j : 'active');
5185
+ )`, projectId, assignment.collaboratorId, (_a = projectRole === null || projectRole === void 0 ? void 0 : projectRole.id) !== null && _a !== void 0 ? _a : null, (_c = (_b = this.normalizeOptionalText(assignment.roleLabel)) !== null && _b !== void 0 ? _b : projectRole === null || projectRole === void 0 ? void 0 : projectRole.name) !== null && _c !== void 0 ? _c : 'Team Member', (_d = assignment.allocationPercent) !== null && _d !== void 0 ? _d : null, (_e = assignment.weeklyHours) !== null && _e !== void 0 ? _e : null, (_f = assignment.startDate) !== null && _f !== void 0 ? _f : null, (_g = assignment.endDate) !== null && _g !== void 0 ? _g : null, (_h = assignment.status) !== null && _h !== void 0 ? _h : 'active');
4877
5186
  }
4878
5187
  }
4879
5188
  async replaceCollaboratorEquityParticipation(client, collaboratorId, equityParticipation) {
@@ -5125,106 +5434,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
5125
5434
  updated_at = NOW()
5126
5435
  WHERE id = $13`, `HIR-${collaboratorCode}`, this.buildHiringContractName(displayName, collaboratorTypeSlug), this.mapContractCategoryForCollaboratorType(collaboratorTypeSlug), this.mapContractTypeForCollaboratorType(collaboratorTypeSlug), displayName, this.mapBillingModelForCollaboratorType(collaboratorTypeSlug), supervisorCollaboratorId, startDate !== null && startDate !== void 0 ? startDate : new Date().toISOString().slice(0, 10), compensationAmount, weeklyCapacityHours ? Math.round(Number(weeklyCapacityHours) * 4) : null, description, updatedByUserId, hiringContract.id);
5127
5436
  }
5128
- async createProjectContractDraft(client, createdByUserId, input) {
5129
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
5130
- const templateRows = input.contractTemplateId
5131
- ? (await client.$queryRawUnsafe(`SELECT id,
5132
- code,
5133
- name,
5134
- description,
5135
- contract_category AS "contractCategory",
5136
- contract_type AS "contractType",
5137
- billing_model AS "billingModel",
5138
- signature_status AS "signatureStatus",
5139
- content_html AS "contentHtml"
5140
- FROM operations_contract_template
5141
- WHERE id = $1
5142
- AND deleted_at IS NULL
5143
- LIMIT 1`, input.contractTemplateId))
5144
- : [];
5145
- const selectedTemplate = (_a = templateRows[0]) !== null && _a !== void 0 ? _a : null;
5146
- const templateContext = {
5147
- project_code: input.projectCode,
5148
- project_name: input.projectName,
5149
- client_name: input.clientName,
5150
- start_date: (_b = input.startDate) !== null && _b !== void 0 ? _b : '',
5151
- end_date: (_c = input.endDate) !== null && _c !== void 0 ? _c : '',
5152
- budget_amount: input.budgetAmount !== null && input.budgetAmount !== undefined
5153
- ? String(input.budgetAmount)
5154
- : '',
5155
- monthly_hour_cap: input.monthlyHourCap !== null && input.monthlyHourCap !== undefined
5156
- ? String(input.monthlyHourCap)
5157
- : '',
5158
- };
5159
- const applyTemplateVariables = (value) => {
5160
- const source = value !== null && value !== void 0 ? value : '';
5161
- return Object.entries(templateContext).reduce((result, [key, replacement]) => result.split(`{{${key}}}`).join(replacement || ''), source);
5162
- };
5163
- const templateCodePrefix = this.normalizeOptionalText(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.code);
5164
- const generatedContractCode = ((_e = (_d = this.normalizeOptionalText(input.contractCode)) !== null && _d !== void 0 ? _d : (templateCodePrefix
5165
- ? `${templateCodePrefix}-${input.projectCode}`
5166
- : null)) !== null && _e !== void 0 ? _e : `PRJ-${input.projectCode}`).slice(0, 40);
5167
- 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`;
5168
- const generatedDescription = this.normalizeOptionalText(applyTemplateVariables((_h = input.description) !== null && _h !== void 0 ? _h : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.description));
5169
- const generatedContentHtml = this.normalizeOptionalText(applyTemplateVariables(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contentHtml));
5170
- const created = await client.$queryRawUnsafe(`INSERT INTO operations_contract (
5171
- code,
5172
- name,
5173
- contract_category,
5174
- contract_type,
5175
- client_name,
5176
- signature_status,
5177
- is_active,
5178
- billing_model,
5179
- account_manager_collaborator_id,
5180
- related_collaborator_id,
5181
- contract_template_id,
5182
- origin_type,
5183
- origin_id,
5184
- start_date,
5185
- end_date,
5186
- signed_at,
5187
- effective_date,
5188
- budget_amount,
5189
- monthly_hour_cap,
5190
- status,
5191
- description,
5192
- content_html,
5193
- created_by_user_id,
5194
- updated_by_user_id,
5195
- created_at,
5196
- updated_at
5197
- ) VALUES (
5198
- $1,
5199
- $2,
5200
- $3::operations_contract_contract_category_70d553ea09_enum,
5201
- $4::operations_contract_contract_type_48331e2ebf_enum,
5202
- $5,
5203
- $6::operations_contract_signature_status_2cb7282a7b_enum,
5204
- true,
5205
- $7::operations_contract_billing_model_409dc7fea2_enum,
5206
- $8,
5207
- NULL,
5208
- $9,
5209
- 'client_project',
5210
- $10,
5211
- $11::date,
5212
- $12::date,
5213
- NULL,
5214
- $11::date,
5215
- $13,
5216
- $14,
5217
- 'draft',
5218
- $15,
5219
- $16,
5220
- $17,
5221
- $17,
5222
- NOW(),
5223
- NOW()
5224
- )
5225
- 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);
5226
- return (_t = created[0]) === null || _t === void 0 ? void 0 : _t.id;
5227
- }
5228
5437
  async replaceContractParties(client, contractId, parties) {
5229
5438
  var _a, _b, _c, _d, _e, _f;
5230
5439
  await client.$executeRawUnsafe(`UPDATE operations_contract_party
@@ -5252,88 +5461,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
5252
5461
  )`, 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);
5253
5462
  }
5254
5463
  }
5255
- async replaceContractSignatures(client, contractId, signatures) {
5256
- var _a, _b, _c, _d;
5257
- await client.$executeRawUnsafe(`UPDATE operations_contract_signature
5258
- SET deleted_at = NOW(),
5259
- updated_at = NOW()
5260
- WHERE contract_id = $1
5261
- AND deleted_at IS NULL`, contractId);
5262
- for (const signature of signatures !== null && signatures !== void 0 ? signatures : []) {
5263
- await client.$executeRawUnsafe(`INSERT INTO operations_contract_signature (
5264
- contract_id,
5265
- signer_name,
5266
- signer_role,
5267
- signer_email,
5268
- signer_status,
5269
- signed_at,
5270
- created_at,
5271
- updated_at
5272
- ) VALUES (
5273
- $1, $2, $3, $4,
5274
- $5::operations_contract_signature_signer_status_1e6fbe2519_enum,
5275
- $6::timestamp, NOW(), NOW()
5276
- )`, 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);
5277
- }
5278
- }
5279
- async replaceContractFinancialTerms(client, contractId, financialTerms) {
5280
- var _a, _b, _c, _d;
5281
- await client.$executeRawUnsafe(`UPDATE operations_contract_financial_term
5282
- SET deleted_at = NOW(),
5283
- updated_at = NOW()
5284
- WHERE contract_id = $1
5285
- AND deleted_at IS NULL`, contractId);
5286
- for (const term of financialTerms !== null && financialTerms !== void 0 ? financialTerms : []) {
5287
- await client.$executeRawUnsafe(`INSERT INTO operations_contract_financial_term (
5288
- contract_id,
5289
- term_type,
5290
- label,
5291
- amount,
5292
- recurrence,
5293
- due_day,
5294
- notes,
5295
- created_at,
5296
- updated_at
5297
- ) VALUES (
5298
- $1,
5299
- $2::operations_contract_financial_term_term_type_700635c06a_enum,
5300
- $3,
5301
- $4,
5302
- $5::operations_contract_financial_term_recurrence_ba90bbe3bf_enum,
5303
- $6,
5304
- $7,
5305
- NOW(), NOW()
5306
- )`, 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);
5307
- }
5308
- }
5309
- async replaceContractRevisions(client, contractId, revisions) {
5310
- var _a, _b, _c, _d;
5311
- await client.$executeRawUnsafe(`UPDATE operations_contract_revision
5312
- SET deleted_at = NOW(),
5313
- updated_at = NOW()
5314
- WHERE contract_id = $1
5315
- AND deleted_at IS NULL`, contractId);
5316
- for (const revision of revisions !== null && revisions !== void 0 ? revisions : []) {
5317
- await client.$executeRawUnsafe(`INSERT INTO operations_contract_revision (
5318
- contract_id,
5319
- revision_type,
5320
- title,
5321
- effective_date,
5322
- status,
5323
- summary,
5324
- created_at,
5325
- updated_at
5326
- ) VALUES (
5327
- $1,
5328
- $2::operations_contract_revision_revision_type_cf5ba1a538_enum,
5329
- $3,
5330
- $4::date,
5331
- $5::operations_contract_revision_status_f44f35bb66_enum,
5332
- $6,
5333
- NOW(), NOW()
5334
- )`, 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);
5335
- }
5336
- }
5337
5464
  async replaceContractDocument(client, contractId, documentType, document) {
5338
5465
  var _a, _b, _c, _d, _e;
5339
5466
  await client.$executeRawUnsafe(`UPDATE operations_contract_document
@@ -5341,7 +5468,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
5341
5468
  updated_at = NOW()
5342
5469
  WHERE contract_id = $1
5343
5470
  AND deleted_at IS NULL
5344
- AND document_type = $2`, contractId, documentType);
5471
+ AND document_type::text = $2`, contractId, documentType);
5345
5472
  await client.$executeRawUnsafe(`INSERT INTO operations_contract_document (
5346
5473
  contract_id,
5347
5474
  document_type,
@@ -5356,7 +5483,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
5356
5483
  created_at,
5357
5484
  updated_at
5358
5485
  ) VALUES (
5359
- $1, $2, $3, $4, $5, $6, true, $7, $8, $9, NOW(), NOW()
5486
+ $1, $2::operations_contract_document_document_type_15ebaff0c9_enum, $3, $4, $5, $6, true, $7::operations_contract_document_extraction_status_6d94c231f3_enum, $8, $9, NOW(), NOW()
5360
5487
  )`, 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);
5361
5488
  }
5362
5489
  async resolveContractExtractionFile(userId, data) {
@@ -5441,7 +5568,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
5441
5568
  }
5442
5569
  }
5443
5570
  async buildContractPdfHtml(contract) {
5444
- var _a, _b, _c, _d, _e;
5571
+ var _a, _b, _c, _d;
5445
5572
  const logoUrl = await this.resolveContractPdfLogoUrl();
5446
5573
  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>';
5447
5574
  const partiesHtml = ((_d = contract.parties) !== null && _d !== void 0 ? _d : []).length
@@ -5453,17 +5580,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
5453
5580
  </li>`)
5454
5581
  .join('')
5455
5582
  : '<li><strong>No parties registered yet.</strong><span>Complete this contract later if needed.</span></li>';
5456
- const financialTermsHtml = ((_e = contract.financialTerms) !== null && _e !== void 0 ? _e : []).length
5457
- ? contract.financialTerms
5458
- .map((term) => `
5459
- <li>
5460
- <strong>${this.escapeHtml(term.label || 'Term')}</strong>
5461
- <span>${this.escapeHtml([term.termType, term.amount, term.recurrence]
5462
- .filter((item) => item !== null && item !== undefined && String(item).trim())
5463
- .join(' • '))}</span>
5464
- </li>`)
5465
- .join('')
5466
- : '<li><strong>No financial terms registered yet.</strong><span>The draft is intentionally lightweight.</span></li>';
5467
5583
  return `<!DOCTYPE html>
5468
5584
  <html lang="en">
5469
5585
  <head>
@@ -5608,11 +5724,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
5608
5724
  <ul>${partiesHtml}</ul>
5609
5725
  </section>
5610
5726
 
5611
- <section>
5612
- <h2>Financial Terms</h2>
5613
- <ul>${financialTermsHtml}</ul>
5614
- </section>
5615
-
5616
5727
  <section>
5617
5728
  <h2>Contract Body</h2>
5618
5729
  <div class="content">${contentHtml}</div>
@@ -5853,7 +5964,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
5853
5964
  effectiveDate: contract.effectiveDate,
5854
5965
  signatureStatus: contract.signatureStatus,
5855
5966
  parties: contract.parties,
5856
- financialTerms: contract.financialTerms,
5857
5967
  }, null, 2)}`,
5858
5968
  `Contract HTML:\n${(_b = this.normalizeOptionalText(contract.contentHtml)) !== null && _b !== void 0 ? _b : ''}`,
5859
5969
  ].join('\n\n'),
@@ -5893,7 +6003,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
5893
6003
  }
5894
6004
  }
5895
6005
  buildHeuristicContractLegalReview(contract) {
5896
- var _a, _b, _c, _d, _e, _f, _g;
6006
+ var _a, _b, _c, _d, _e, _f;
5897
6007
  const contentText = ((_a = this.normalizeOptionalText(contract.contentHtml)) !== null && _a !== void 0 ? _a : '')
5898
6008
  .replace(/<[^>]+>/g, ' ')
5899
6009
  .replace(/\s+/g, ' ')
@@ -5931,12 +6041,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
5931
6041
  if (!this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.documentNumber)) {
5932
6042
  warnings.push('Consider confirming the primary party document number.');
5933
6043
  }
5934
- if (!((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : []).length && contract.budgetAmount == null) {
5935
- warnings.push('Commercial conditions are still generic; define prices, recurrence, and penalties.');
5936
- checklist.push('Attention: validate billing, adjustments, and penalties.');
6044
+ if (contract.budgetAmount == null) {
6045
+ warnings.push('Commercial conditions are still generic; define a budget amount before signature.');
6046
+ checklist.push('Attention: validate billing and penalties.');
5937
6047
  }
5938
6048
  else {
5939
- checklist.push('OK: there is at least one financial reference to review.');
6049
+ checklist.push('OK: there is a budget reference to review.');
5940
6050
  }
5941
6051
  if (!contentText) {
5942
6052
  missingFields.push('Contract body content');
@@ -6102,34 +6212,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
6102
6212
  isPrimary: this.normalizeExtractionBoolean(party.isPrimary),
6103
6213
  }))
6104
6214
  .filter((party) => party.displayName),
6105
- signatures: this.normalizeObjectList(raw.signatures)
6106
- .map((signature) => ({
6107
- signerName: this.normalizeExtractionString(signature.signerName),
6108
- signerRole: this.normalizeExtractionString(signature.signerRole),
6109
- signerEmail: this.normalizeExtractionString(signature.signerEmail),
6110
- status: this.normalizeEnumValue(signature.status, SIGNATURE_ITEM_STATUS_VALUES, 'pending'),
6111
- signedAt: this.normalizeExtractionDate(signature.signedAt),
6112
- }))
6113
- .filter((signature) => signature.signerName),
6114
- financialTerms: this.normalizeObjectList(raw.financialTerms)
6115
- .map((term) => ({
6116
- label: this.normalizeExtractionString(term.label),
6117
- termType: this.normalizeEnumValue(term.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
6118
- amount: this.normalizeExtractionNumber(term.amount),
6119
- recurrence: this.normalizeEnumValue(term.recurrence, RECURRENCE_VALUES, 'one_time'),
6120
- dueDay: this.normalizeExtractionNumber(term.dueDay),
6121
- notes: this.normalizeExtractionString(term.notes),
6122
- }))
6123
- .filter((term) => term.label),
6124
- revisions: this.normalizeObjectList(raw.revisions)
6125
- .map((revision) => ({
6126
- title: this.normalizeExtractionString(revision.title),
6127
- revisionType: this.normalizeEnumValue(revision.revisionType, REVISION_TYPE_VALUES, 'revision'),
6128
- effectiveDate: this.normalizeExtractionDate(revision.effectiveDate),
6129
- status: this.normalizeEnumValue(revision.status, REVISION_STATUS_VALUES, 'draft'),
6130
- summary: this.normalizeExtractionString(revision.summary),
6131
- }))
6132
- .filter((revision) => revision.title),
6133
6215
  };
6134
6216
  if (!draft.name)
6135
6217
  missingFields.push('Contract title');
@@ -6231,6 +6313,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
6231
6313
  offset: (page - 1) * pageSize,
6232
6314
  };
6233
6315
  }
6316
+ shouldPaginate(input) {
6317
+ return [
6318
+ input.page,
6319
+ input.pageSize,
6320
+ input.search,
6321
+ input.sortField,
6322
+ input.sortOrder,
6323
+ ].some((value) => value !== undefined && value !== null && value !== '');
6324
+ }
6234
6325
  buildPaginationResult(data, total, page, pageSize) {
6235
6326
  const lastPage = Math.max(1, Math.ceil(total / Math.max(pageSize, 1)));
6236
6327
  return {
@@ -6375,6 +6466,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
6375
6466
  async execute(sql, params = []) {
6376
6467
  return this.prisma.$executeRawUnsafe(sql, ...params);
6377
6468
  }
6469
+ async getNextCollaboratorTypeSortOrder(client = this.prisma) {
6470
+ var _a, _b;
6471
+ const row = (await client.$queryRawUnsafe(`SELECT COALESCE(MAX(sort_order), 0) + 1 AS "nextSortOrder"
6472
+ FROM operations_collaborator_type`));
6473
+ return Number((_b = (_a = row === null || row === void 0 ? void 0 : row[0]) === null || _a === void 0 ? void 0 : _a.nextSortOrder) !== null && _b !== void 0 ? _b : 1);
6474
+ }
6378
6475
  };
6379
6476
  exports.OperationsService = OperationsService;
6380
6477
  exports.OperationsService = OperationsService = OperationsService_1 = __decorate([