@hed-hog/operations 0.0.318 → 0.0.319

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 (137) hide show
  1. package/dist/controllers/operations-collaborator-costs.controller.d.ts +144 -0
  2. package/dist/controllers/operations-collaborator-costs.controller.d.ts.map +1 -0
  3. package/dist/controllers/operations-collaborator-costs.controller.js +162 -0
  4. package/dist/controllers/operations-collaborator-costs.controller.js.map +1 -0
  5. package/dist/controllers/operations-collaborators.controller.d.ts +14 -0
  6. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-collaborators.controller.js +11 -0
  8. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  9. package/dist/controllers/operations-projects.controller.d.ts +31 -0
  10. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  11. package/dist/controllers/operations-projects.controller.js +23 -0
  12. package/dist/controllers/operations-projects.controller.js.map +1 -1
  13. package/dist/controllers/operations-reports.controller.d.ts +199 -0
  14. package/dist/controllers/operations-reports.controller.d.ts.map +1 -0
  15. package/dist/controllers/operations-reports.controller.js +53 -0
  16. package/dist/controllers/operations-reports.controller.js.map +1 -0
  17. package/dist/controllers/operations-tasks.controller.d.ts +41 -2
  18. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  19. package/dist/controllers/operations-tasks.controller.js +17 -5
  20. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  21. package/dist/dto/create-collaborator-cost.dto.d.ts +16 -0
  22. package/dist/dto/create-collaborator-cost.dto.d.ts.map +1 -0
  23. package/dist/dto/create-collaborator-cost.dto.js +88 -0
  24. package/dist/dto/create-collaborator-cost.dto.js.map +1 -0
  25. package/dist/dto/create-collaborator.dto.d.ts +0 -1
  26. package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
  27. package/dist/dto/create-collaborator.dto.js +0 -6
  28. package/dist/dto/create-collaborator.dto.js.map +1 -1
  29. package/dist/dto/create-cost-type.dto.d.ts +13 -0
  30. package/dist/dto/create-cost-type.dto.d.ts.map +1 -0
  31. package/dist/dto/create-cost-type.dto.js +87 -0
  32. package/dist/dto/create-cost-type.dto.js.map +1 -0
  33. package/dist/dto/list-approvals.dto.d.ts +2 -0
  34. package/dist/dto/list-approvals.dto.d.ts.map +1 -1
  35. package/dist/dto/list-approvals.dto.js +10 -0
  36. package/dist/dto/list-approvals.dto.js.map +1 -1
  37. package/dist/dto/list-collaborator-costs.dto.d.ts +5 -0
  38. package/dist/dto/list-collaborator-costs.dto.d.ts.map +1 -0
  39. package/dist/dto/list-collaborator-costs.dto.js +23 -0
  40. package/dist/dto/list-collaborator-costs.dto.js.map +1 -0
  41. package/dist/dto/list-cost-types.dto.d.ts +6 -0
  42. package/dist/dto/list-cost-types.dto.d.ts.map +1 -0
  43. package/dist/dto/list-cost-types.dto.js +35 -0
  44. package/dist/dto/list-cost-types.dto.js.map +1 -0
  45. package/dist/dto/list-my-projects.dto.d.ts +5 -0
  46. package/dist/dto/list-my-projects.dto.d.ts.map +1 -0
  47. package/dist/dto/list-my-projects.dto.js +23 -0
  48. package/dist/dto/list-my-projects.dto.js.map +1 -0
  49. package/dist/dto/list-my-tasks.dto.d.ts +6 -0
  50. package/dist/dto/list-my-tasks.dto.d.ts.map +1 -0
  51. package/dist/dto/list-my-tasks.dto.js +33 -0
  52. package/dist/dto/list-my-tasks.dto.js.map +1 -0
  53. package/dist/dto/list-projects.dto.d.ts +1 -0
  54. package/dist/dto/list-projects.dto.d.ts.map +1 -1
  55. package/dist/dto/list-projects.dto.js +7 -0
  56. package/dist/dto/list-projects.dto.js.map +1 -1
  57. package/dist/dto/list-reports.dto.d.ts +16 -0
  58. package/dist/dto/list-reports.dto.d.ts.map +1 -0
  59. package/dist/dto/list-reports.dto.js +75 -0
  60. package/dist/dto/list-reports.dto.js.map +1 -0
  61. package/dist/dto/list-tasks.dto.d.ts +2 -0
  62. package/dist/dto/list-tasks.dto.d.ts.map +1 -1
  63. package/dist/dto/list-tasks.dto.js +12 -0
  64. package/dist/dto/list-tasks.dto.js.map +1 -1
  65. package/dist/dto/list-timesheets.dto.d.ts +2 -0
  66. package/dist/dto/list-timesheets.dto.d.ts.map +1 -1
  67. package/dist/dto/list-timesheets.dto.js +10 -0
  68. package/dist/dto/list-timesheets.dto.js.map +1 -1
  69. package/dist/dto/update-collaborator-cost.dto.d.ts +6 -0
  70. package/dist/dto/update-collaborator-cost.dto.d.ts.map +1 -0
  71. package/dist/dto/update-collaborator-cost.dto.js +9 -0
  72. package/dist/dto/update-collaborator-cost.dto.js.map +1 -0
  73. package/dist/dto/update-task.dto.d.ts +1 -0
  74. package/dist/dto/update-task.dto.d.ts.map +1 -1
  75. package/dist/dto/update-task.dto.js +6 -0
  76. package/dist/dto/update-task.dto.js.map +1 -1
  77. package/dist/operations.module.d.ts.map +1 -1
  78. package/dist/operations.module.js +4 -0
  79. package/dist/operations.module.js.map +1 -1
  80. package/dist/operations.service.d.ts +457 -3
  81. package/dist/operations.service.d.ts.map +1 -1
  82. package/dist/operations.service.js +1445 -208
  83. package/dist/operations.service.js.map +1 -1
  84. package/dist/operations.service.spec.js +31 -7
  85. package/dist/operations.service.spec.js.map +1 -1
  86. package/hedhog/data/menu.yaml +112 -7
  87. package/hedhog/data/operations_cost_type.yaml +166 -0
  88. package/hedhog/data/route.yaml +185 -0
  89. package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -0
  90. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +94 -15
  91. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +219 -94
  92. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +21 -32
  93. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +178 -89
  94. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +1185 -0
  95. package/hedhog/frontend/app/_components/operations-calendar-view.tsx.ejs +306 -0
  96. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +943 -782
  97. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +223 -0
  98. package/hedhog/frontend/app/_lib/api.ts.ejs +162 -0
  99. package/hedhog/frontend/app/_lib/types.ts.ejs +229 -3
  100. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +11 -3
  101. package/hedhog/frontend/app/approvals/page.tsx.ejs +191 -46
  102. package/hedhog/frontend/app/collaborators/page.tsx.ejs +133 -25
  103. package/hedhog/frontend/app/my-projects/[id]/page.tsx.ejs +11 -0
  104. package/hedhog/frontend/app/my-projects/page.tsx.ejs +440 -0
  105. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +1304 -0
  106. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -0
  107. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -0
  108. package/hedhog/frontend/app/timesheets/page.tsx.ejs +322 -58
  109. package/hedhog/frontend/messages/en.json +234 -25
  110. package/hedhog/frontend/messages/pt.json +234 -25
  111. package/hedhog/table/operations_collaborator.yaml +0 -4
  112. package/hedhog/table/operations_collaborator_compensation_history.yaml +28 -0
  113. package/hedhog/table/operations_collaborator_cost.yaml +56 -0
  114. package/hedhog/table/operations_cost_type.yaml +38 -0
  115. package/package.json +6 -6
  116. package/src/controllers/operations-collaborator-costs.controller.ts +147 -0
  117. package/src/controllers/operations-collaborators.controller.ts +19 -8
  118. package/src/controllers/operations-projects.controller.ts +19 -8
  119. package/src/controllers/operations-reports.controller.ts +32 -0
  120. package/src/controllers/operations-tasks.controller.ts +32 -12
  121. package/src/dto/create-collaborator-cost.dto.ts +78 -0
  122. package/src/dto/create-collaborator.dto.ts +9 -14
  123. package/src/dto/create-cost-type.dto.ts +62 -0
  124. package/src/dto/list-approvals.dto.ts +8 -0
  125. package/src/dto/list-collaborator-costs.dto.ts +8 -0
  126. package/src/dto/list-cost-types.dto.ts +19 -0
  127. package/src/dto/list-my-projects.dto.ts +8 -0
  128. package/src/dto/list-my-tasks.dto.ts +17 -0
  129. package/src/dto/list-projects.dto.ts +7 -1
  130. package/src/dto/list-reports.dto.ts +51 -0
  131. package/src/dto/list-tasks.dto.ts +11 -1
  132. package/src/dto/list-timesheets.dto.ts +8 -0
  133. package/src/dto/update-collaborator-cost.dto.ts +4 -0
  134. package/src/dto/update-task.dto.ts +6 -0
  135. package/src/operations.module.ts +7 -3
  136. package/src/operations.service.spec.ts +45 -7
  137. package/src/operations.service.ts +1992 -225
@@ -528,7 +528,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
528
528
  OR COALESCE(person_record.name, '') ILIKE ${searchPlaceholder}
529
529
  OR COALESCE(c.code, '') ILIKE ${searchPlaceholder}
530
530
  OR COALESCE(department_record.name, '') ILIKE ${searchPlaceholder}
531
- OR COALESCE(c.department, '') ILIKE ${searchPlaceholder}
532
531
  OR COALESCE(job_title_record.name, '') ILIKE ${searchPlaceholder}
533
532
  OR COALESCE(c.title, '') ILIKE ${searchPlaceholder}
534
533
  OR COALESCE(s.display_name, '') ILIKE ${searchPlaceholder}
@@ -546,7 +545,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
546
545
  person_record.name AS "personName",
547
546
  person_record.avatar_id AS "personAvatarId",
548
547
  c.department_id AS "departmentId",
549
- COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
548
+ department_record.name AS "department",
550
549
  c.job_title_id AS "jobTitleId",
551
550
  COALESCE(NULLIF(job_title_record.name, ''), NULLIF(c.title, '')) AS "title",
552
551
  c.level_label AS "levelLabel",
@@ -631,7 +630,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
631
630
  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
631
  }
633
632
  async getCollaboratorStats(userId, filters = {}) {
634
- var _a, _b, _c, _d, _e;
633
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
635
634
  const actor = await this.getActorContext(userId);
636
635
  this.ensureCollaborator(actor);
637
636
  const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'c.id', actor.isDirector);
@@ -664,45 +663,101 @@ let OperationsService = OperationsService_1 = class OperationsService {
664
663
  OR COALESCE(person_record.name, '') ILIKE ${searchPlaceholder}
665
664
  OR COALESCE(c.code, '') ILIKE ${searchPlaceholder}
666
665
  OR COALESCE(department_record.name, '') ILIKE ${searchPlaceholder}
667
- OR COALESCE(c.department, '') ILIKE ${searchPlaceholder}
668
666
  OR COALESCE(job_title_record.name, '') ILIKE ${searchPlaceholder}
669
667
  OR COALESCE(c.title, '') ILIKE ${searchPlaceholder}
670
668
  OR COALESCE(s.display_name, '') ILIKE ${searchPlaceholder}
671
669
  )`);
672
670
  }
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);
671
+ const baseWhere = where.slice(0, 2); // access control only: deleted_at + visibility
672
+ const result = await this.querySingle(`WITH filtered AS (
673
+ SELECT DISTINCT c.id
674
+ FROM operations_collaborator c
675
+ LEFT JOIN person person_record
676
+ ON person_record.id = c.person_id
677
+ LEFT JOIN operations_collaborator_type collaborator_type
678
+ ON collaborator_type.id = c.collaborator_type_id
679
+ AND collaborator_type.deleted_at IS NULL
680
+ LEFT JOIN operations_department department_record
681
+ ON department_record.id = c.department_id
682
+ AND department_record.deleted_at IS NULL
683
+ LEFT JOIN operations_job_title job_title_record
684
+ ON job_title_record.id = c.job_title_id
685
+ AND job_title_record.deleted_at IS NULL
686
+ LEFT JOIN operations_collaborator s
687
+ ON s.id = c.supervisor_collaborator_id
688
+ WHERE ${where.join(' AND ')}
689
+ ),
690
+ global_filtered AS (
691
+ SELECT DISTINCT c.id,
692
+ COALESCE(hc.budget_amount, 0) AS salary
693
+ FROM operations_collaborator c
694
+ LEFT JOIN LATERAL (
695
+ SELECT oc.budget_amount
696
+ FROM operations_contract oc
697
+ WHERE oc.related_collaborator_id = c.id
698
+ AND oc.deleted_at IS NULL
699
+ ORDER BY CASE WHEN oc.origin_type = 'employee_hiring' THEN 0 ELSE 1 END,
700
+ oc.created_at DESC
701
+ LIMIT 1
702
+ ) hc ON TRUE
703
+ WHERE ${baseWhere.join(' AND ')}
704
+ ),
705
+ global_cost_totals AS (
706
+ SELECT cc.collaborator_id,
707
+ COALESCE(SUM(cc.amount), 0) AS total_cost
708
+ FROM operations_collaborator_cost cc
709
+ WHERE cc.collaborator_id IN (SELECT id FROM global_filtered)
710
+ GROUP BY cc.collaborator_id
711
+ ),
712
+ global_count AS (
713
+ SELECT COUNT(*)::int AS cnt FROM global_filtered
714
+ ),
715
+ agg AS (
716
+ SELECT
717
+ (SELECT COUNT(*)::int FROM filtered) AS total,
718
+ COUNT(DISTINCT c.id) FILTER (WHERE c.status = 'active')::int AS active,
719
+ COUNT(DISTINCT c.id) FILTER (WHERE c.status = 'on_leave')::int AS "onLeave",
720
+ COUNT(DISTINCT c.id) FILTER (WHERE hc.id IS NOT NULL)::int AS "withContracts"
721
+ FROM operations_collaborator c
722
+ LEFT JOIN LATERAL (
723
+ SELECT oc.id
724
+ FROM operations_contract oc
725
+ WHERE oc.related_collaborator_id = c.id
726
+ AND oc.deleted_at IS NULL
727
+ ORDER BY CASE WHEN oc.origin_type = 'employee_hiring' THEN 0 ELSE 1 END,
728
+ oc.created_at DESC
729
+ LIMIT 1
730
+ ) hc ON TRUE
731
+ WHERE c.id IN (SELECT id FROM filtered)
732
+ )
733
+ SELECT
734
+ agg.total,
735
+ agg.active,
736
+ agg."onLeave",
737
+ agg."withContracts",
738
+ COALESCE((SELECT SUM(f.salary) FROM global_filtered f), 0)::text AS "totalSalary",
739
+ COALESCE((SELECT SUM(ct.total_cost) FROM global_cost_totals ct), 0)::text AS "totalCosts",
740
+ (
741
+ COALESCE((SELECT SUM(f.salary) FROM global_filtered f), 0) +
742
+ COALESCE((SELECT SUM(ct.total_cost) FROM global_cost_totals ct), 0)
743
+ )::text AS "avgSalaryPlusCosts",
744
+ CASE WHEN (SELECT cnt FROM global_count) > 0
745
+ THEN (
746
+ COALESCE((SELECT SUM(f.salary) FROM global_filtered f), 0) +
747
+ COALESCE((SELECT SUM(ct.total_cost) FROM global_cost_totals ct), 0)
748
+ ) / (SELECT cnt FROM global_count)
749
+ ELSE 0
750
+ END::text AS "avgSalaryPlusCostsPerCollaborator"
751
+ FROM agg`, params);
701
752
  return {
702
753
  total: Number((_b = result === null || result === void 0 ? void 0 : result.total) !== null && _b !== void 0 ? _b : 0),
703
754
  active: Number((_c = result === null || result === void 0 ? void 0 : result.active) !== null && _c !== void 0 ? _c : 0),
704
755
  onLeave: Number((_d = result === null || result === void 0 ? void 0 : result.onLeave) !== null && _d !== void 0 ? _d : 0),
705
756
  withContracts: Number((_e = result === null || result === void 0 ? void 0 : result.withContracts) !== null && _e !== void 0 ? _e : 0),
757
+ totalSalary: Number((_f = result === null || result === void 0 ? void 0 : result.totalSalary) !== null && _f !== void 0 ? _f : 0),
758
+ totalCosts: Number((_g = result === null || result === void 0 ? void 0 : result.totalCosts) !== null && _g !== void 0 ? _g : 0),
759
+ avgSalaryPlusCosts: Number((_h = result === null || result === void 0 ? void 0 : result.avgSalaryPlusCosts) !== null && _h !== void 0 ? _h : 0),
760
+ avgSalaryPlusCostsPerCollaborator: Number((_j = result === null || result === void 0 ? void 0 : result.avgSalaryPlusCostsPerCollaborator) !== null && _j !== void 0 ? _j : 0),
706
761
  };
707
762
  }
708
763
  async getMyCollaborator(userId) {
@@ -749,7 +804,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
749
804
  person_record.name AS "personName",
750
805
  person_record.avatar_id AS "personAvatarId",
751
806
  c.department_id AS "departmentId",
752
- COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
807
+ department_record.name AS "department",
753
808
  c.job_title_id AS "jobTitleId",
754
809
  COALESCE(NULLIF(job_title_record.name, ''), NULLIF(c.title, '')) AS "title",
755
810
  c.status,
@@ -942,12 +997,11 @@ let OperationsService = OperationsService_1 = class OperationsService {
942
997
  throw new common_1.BadRequestException('Field "personId" is required.');
943
998
  }
944
999
  const collaboratorId = await this.prisma.$transaction(async (tx) => {
945
- 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;
1000
+ 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;
946
1001
  const normalizedCode = String((_a = data.code) !== null && _a !== void 0 ? _a : '').trim() ||
947
1002
  (await this.generateCollaboratorCode(tx));
948
1003
  const resolvedDepartment = await this.resolveDepartmentReference(tx, {
949
1004
  departmentId: (_b = data.departmentId) !== null && _b !== void 0 ? _b : null,
950
- departmentName: data.department,
951
1005
  });
952
1006
  const resolvedJobTitle = await this.resolveJobTitleReference(tx, {
953
1007
  jobTitleId: (_c = data.jobTitleId) !== null && _c !== void 0 ? _c : null,
@@ -964,7 +1018,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
964
1018
  code,
965
1019
  collaborator_type_id,
966
1020
  display_name,
967
- department,
968
1021
  department_id,
969
1022
  job_title_id,
970
1023
  title,
@@ -978,12 +1031,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
978
1031
  updated_at
979
1032
  ) VALUES (
980
1033
  $1, $2, $3, $4, $5,
981
- $6, $7, $8, $9, $10, $11, $12,
982
- $13::operations_collaborator_status_ef779877d4_enum,
983
- $14::date, $15::date, $16, NOW(), NOW()
1034
+ $6, $7, $8, $9, $10, $11,
1035
+ $12::operations_collaborator_status_ef779877d4_enum,
1036
+ $13::date, $14::date, $15, NOW(), NOW()
984
1037
  )
985
- RETURNING id`, (_g = data.userId) !== null && _g !== void 0 ? _g : null, (_h = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.id) !== null && _h !== void 0 ? _h : null, (_j = data.supervisorCollaboratorId) !== null && _j !== void 0 ? _j : null, normalizedCode, (_k = resolvedCollaboratorType === null || resolvedCollaboratorType === void 0 ? void 0 : resolvedCollaboratorType.id) !== null && _k !== void 0 ? _k : null, resolvedDisplayName, (_l = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _l !== void 0 ? _l : null, (_m = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _m !== void 0 ? _m : null, (_o = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.id) !== null && _o !== void 0 ? _o : null, (_p = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.name) !== null && _p !== void 0 ? _p : this.normalizeOptionalText(data.title), (_q = data.levelLabel) !== null && _q !== void 0 ? _q : null, (_r = data.weeklyCapacityHours) !== null && _r !== void 0 ? _r : null, normalizedStatus !== null && normalizedStatus !== void 0 ? normalizedStatus : 'active', (_s = data.joinedAt) !== null && _s !== void 0 ? _s : null, (_t = data.leftAt) !== null && _t !== void 0 ? _t : null, (_u = data.notes) !== null && _u !== void 0 ? _u : null));
986
- const createdCollaboratorId = (_v = created[0]) === null || _v === void 0 ? void 0 : _v.id;
1038
+ RETURNING id`, (_g = data.userId) !== null && _g !== void 0 ? _g : null, (_h = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.id) !== null && _h !== void 0 ? _h : null, (_j = data.supervisorCollaboratorId) !== null && _j !== void 0 ? _j : null, normalizedCode, (_k = resolvedCollaboratorType === null || resolvedCollaboratorType === void 0 ? void 0 : resolvedCollaboratorType.id) !== null && _k !== void 0 ? _k : null, resolvedDisplayName, (_l = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _l !== void 0 ? _l : null, (_m = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.id) !== null && _m !== void 0 ? _m : null, (_o = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.name) !== null && _o !== void 0 ? _o : this.normalizeOptionalText(data.title), (_p = data.levelLabel) !== null && _p !== void 0 ? _p : null, (_q = data.weeklyCapacityHours) !== null && _q !== void 0 ? _q : null, normalizedStatus !== null && normalizedStatus !== void 0 ? normalizedStatus : 'active', (_r = data.joinedAt) !== null && _r !== void 0 ? _r : null, (_s = data.leftAt) !== null && _s !== void 0 ? _s : null, (_t = data.notes) !== null && _t !== void 0 ? _t : null));
1039
+ const createdCollaboratorId = (_u = created[0]) === null || _u === void 0 ? void 0 : _u.id;
987
1040
  await this.replaceCollaboratorScheduleDays(tx, createdCollaboratorId, data.weeklySchedule);
988
1041
  await this.replaceCollaboratorEquityParticipation(tx, createdCollaboratorId, data.equityParticipation);
989
1042
  if (data.autoGenerateContractDraft !== false) {
@@ -991,14 +1044,17 @@ let OperationsService = OperationsService_1 = class OperationsService {
991
1044
  collaboratorId: createdCollaboratorId,
992
1045
  collaboratorCode: normalizedCode,
993
1046
  displayName: resolvedDisplayName,
994
- collaboratorType: ((_x = (_w = resolvedCollaboratorType === null || resolvedCollaboratorType === void 0 ? void 0 : resolvedCollaboratorType.slug) !== null && _w !== void 0 ? _w : data.collaboratorType) !== null && _x !== void 0 ? _x : 'other'),
995
- supervisorCollaboratorId: (_y = data.supervisorCollaboratorId) !== null && _y !== void 0 ? _y : null,
996
- startDate: (_z = data.joinedAt) !== null && _z !== void 0 ? _z : null,
997
- weeklyCapacityHours: (_0 = data.weeklyCapacityHours) !== null && _0 !== void 0 ? _0 : null,
998
- compensationAmount: (_1 = data.compensationAmount) !== null && _1 !== void 0 ? _1 : null,
999
- description: (_3 = (_2 = data.contractDescription) !== null && _2 !== void 0 ? _2 : data.notes) !== null && _3 !== void 0 ? _3 : null,
1047
+ collaboratorType: ((_w = (_v = resolvedCollaboratorType === null || resolvedCollaboratorType === void 0 ? void 0 : resolvedCollaboratorType.slug) !== null && _v !== void 0 ? _v : data.collaboratorType) !== null && _w !== void 0 ? _w : 'other'),
1048
+ supervisorCollaboratorId: (_x = data.supervisorCollaboratorId) !== null && _x !== void 0 ? _x : null,
1049
+ startDate: (_y = data.joinedAt) !== null && _y !== void 0 ? _y : null,
1050
+ weeklyCapacityHours: (_z = data.weeklyCapacityHours) !== null && _z !== void 0 ? _z : null,
1051
+ compensationAmount: (_0 = data.compensationAmount) !== null && _0 !== void 0 ? _0 : null,
1052
+ description: (_2 = (_1 = data.contractDescription) !== null && _1 !== void 0 ? _1 : data.notes) !== null && _2 !== void 0 ? _2 : null,
1000
1053
  });
1001
1054
  }
1055
+ if (data.compensationAmount != null) {
1056
+ await this.insertCollaboratorCompensationHistory(tx, createdCollaboratorId, Number(data.compensationAmount), actor.userId, null);
1057
+ }
1002
1058
  return createdCollaboratorId;
1003
1059
  });
1004
1060
  return this.getCollaboratorByIdForUser(userId, collaboratorId);
@@ -1029,7 +1085,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1029
1085
  this.pushUpdate(updates, params, 'left_at', data.leftAt, 'date');
1030
1086
  this.pushUpdate(updates, params, 'notes', data.notes);
1031
1087
  await this.prisma.$transaction(async (tx) => {
1032
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1088
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1033
1089
  if (data.collaboratorType !== undefined ||
1034
1090
  data.collaboratorTypeId !== undefined ||
1035
1091
  data.collaboratorTypeSlug !== undefined) {
@@ -1039,21 +1095,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
1039
1095
  });
1040
1096
  this.pushUpdate(updates, params, 'collaborator_type_id', (_d = resolvedCollaboratorType === null || resolvedCollaboratorType === void 0 ? void 0 : resolvedCollaboratorType.id) !== null && _d !== void 0 ? _d : null);
1041
1097
  }
1042
- if (data.department !== undefined || data.departmentId !== undefined) {
1098
+ if (data.departmentId !== undefined) {
1043
1099
  const resolvedDepartment = await this.resolveDepartmentReference(tx, {
1044
1100
  departmentId: (_e = data.departmentId) !== null && _e !== void 0 ? _e : null,
1045
- departmentName: data.department,
1046
1101
  });
1047
- this.pushUpdate(updates, params, 'department', (_f = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _f !== void 0 ? _f : null);
1048
- this.pushUpdate(updates, params, 'department_id', (_g = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _g !== void 0 ? _g : null);
1102
+ this.pushUpdate(updates, params, 'department_id', (_f = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _f !== void 0 ? _f : null);
1049
1103
  }
1050
1104
  if (data.title !== undefined || data.jobTitleId !== undefined) {
1051
1105
  const resolvedJobTitle = await this.resolveJobTitleReference(tx, {
1052
- jobTitleId: (_h = data.jobTitleId) !== null && _h !== void 0 ? _h : null,
1106
+ jobTitleId: (_g = data.jobTitleId) !== null && _g !== void 0 ? _g : null,
1053
1107
  jobTitleName: data.title,
1054
1108
  });
1055
- this.pushUpdate(updates, params, 'job_title_id', (_j = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.id) !== null && _j !== void 0 ? _j : null);
1056
- this.pushUpdate(updates, params, 'title', (_k = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.name) !== null && _k !== void 0 ? _k : this.normalizeOptionalText(data.title));
1109
+ this.pushUpdate(updates, params, 'job_title_id', (_h = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.id) !== null && _h !== void 0 ? _h : null);
1110
+ this.pushUpdate(updates, params, 'title', (_j = resolvedJobTitle === null || resolvedJobTitle === void 0 ? void 0 : resolvedJobTitle.name) !== null && _j !== void 0 ? _j : this.normalizeOptionalText(data.title));
1057
1111
  }
1058
1112
  if (updates.length) {
1059
1113
  params.push(collaboratorId);
@@ -1081,10 +1135,31 @@ let OperationsService = OperationsService_1 = class OperationsService {
1081
1135
  data.personId !== undefined ||
1082
1136
  data.displayName !== undefined) {
1083
1137
  await this.syncHiringContractDraft(tx, actor.userId, collaboratorId, data);
1138
+ if (data.compensationAmount !== undefined &&
1139
+ data.compensationAmount !== null) {
1140
+ await this.insertCollaboratorCompensationHistory(tx, collaboratorId, Number(data.compensationAmount), actor.userId, null);
1141
+ }
1084
1142
  }
1085
1143
  });
1086
1144
  return this.getCollaboratorByIdForUser(userId, collaboratorId);
1087
1145
  }
1146
+ async getCollaboratorCompensationHistory(userId, collaboratorId) {
1147
+ const actor = await this.getActorContext(userId);
1148
+ this.ensureDirector(actor);
1149
+ await this.getCollaboratorById(collaboratorId);
1150
+ return this.queryRows(`SELECT h.id,
1151
+ h.collaborator_id AS "collaboratorId",
1152
+ h.amount::text AS amount,
1153
+ h.effective_date::text AS "effectiveDate",
1154
+ h.actor_user_id AS "actorUserId",
1155
+ u.name AS "actorName",
1156
+ h.notes,
1157
+ h.created_at AS "createdAt"
1158
+ FROM operations_collaborator_compensation_history h
1159
+ LEFT JOIN "user" u ON u.id = h.actor_user_id
1160
+ WHERE h.collaborator_id = $1
1161
+ ORDER BY h.created_at DESC`, [collaboratorId]);
1162
+ }
1088
1163
  async listDepartments(userId, filters = {}) {
1089
1164
  var _a, _b;
1090
1165
  const actor = await this.getActorContext(userId);
@@ -1125,13 +1200,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1125
1200
  FROM operations_department d
1126
1201
  LEFT JOIN operations_collaborator c
1127
1202
  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)
1133
- )
1134
- )
1203
+ AND c.department_id = d.id
1135
1204
  ${whereClause}
1136
1205
  GROUP BY d.id`;
1137
1206
  if (!pagination) {
@@ -1298,7 +1367,14 @@ let OperationsService = OperationsService_1 = class OperationsService {
1298
1367
  async listProjects(userId, filters = {}) {
1299
1368
  var _a, _b;
1300
1369
  const actor = await this.getActorContext(userId);
1301
- const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
1370
+ const myOnly = filters.myOnly === true;
1371
+ // When myOnly=true: restrict to projects the current collaborator is assigned to.
1372
+ // Otherwise (general list): show all projects in the system regardless of role scope.
1373
+ const filter = myOnly
1374
+ ? this.buildIdFilter(actor.collaboratorId
1375
+ ? await this.getAssignedProjectIds([actor.collaboratorId])
1376
+ : [], 'p.id', false)
1377
+ : this.buildIdFilter([], 'p.id', true);
1302
1378
  const assignmentParams = [];
1303
1379
  const ownAssignmentSelect = actor.collaboratorId
1304
1380
  ? `MAX(CASE WHEN pa.collaborator_id = ${this.param(assignmentParams, actor.collaboratorId)} THEN pa.id END)::int AS "myAssignmentId",
@@ -1313,21 +1389,36 @@ let OperationsService = OperationsService_1 = class OperationsService {
1313
1389
  })
1314
1390
  : null;
1315
1391
  const params = [...assignmentParams, ...filter.params];
1316
- const where = ['p.deleted_at IS NULL', filter.clause];
1392
+ const totalParams = [...filter.params];
1393
+ const where = [
1394
+ 'p.deleted_at IS NULL',
1395
+ this.shiftSqlPlaceholders(filter.clause, assignmentParams.length),
1396
+ ];
1397
+ const totalWhere = ['p.deleted_at IS NULL', filter.clause];
1317
1398
  if (filters.status && filters.status !== 'all') {
1318
1399
  where.push(`p.status::text = ${this.param(params, filters.status)}`);
1400
+ totalWhere.push(`p.status::text = ${this.param(totalParams, filters.status)}`);
1319
1401
  }
1320
1402
  if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
1321
1403
  const searchPlaceholder = this.param(params, `%${pagination.search}%`);
1404
+ const totalSearchPlaceholder = this.param(totalParams, `%${pagination.search}%`);
1322
1405
  where.push(`(
1323
1406
  COALESCE(p.name, '') ILIKE ${searchPlaceholder}
1324
1407
  OR COALESCE(p.code, '') ILIKE ${searchPlaceholder}
1325
1408
  OR COALESCE(p.client_name, '') ILIKE ${searchPlaceholder}
1326
1409
  OR COALESCE(c.name, '') ILIKE ${searchPlaceholder}
1327
1410
  OR COALESCE(m.display_name, '') ILIKE ${searchPlaceholder}
1411
+ )`);
1412
+ totalWhere.push(`(
1413
+ COALESCE(p.name, '') ILIKE ${totalSearchPlaceholder}
1414
+ OR COALESCE(p.code, '') ILIKE ${totalSearchPlaceholder}
1415
+ OR COALESCE(p.client_name, '') ILIKE ${totalSearchPlaceholder}
1416
+ OR COALESCE(c.name, '') ILIKE ${totalSearchPlaceholder}
1417
+ OR COALESCE(m.display_name, '') ILIKE ${totalSearchPlaceholder}
1328
1418
  )`);
1329
1419
  }
1330
1420
  const whereClause = where.join(' AND ');
1421
+ const totalWhereClause = totalWhere.join(' AND ');
1331
1422
  const baseQuery = `SELECT p.id,
1332
1423
  p.contract_id AS "contractId",
1333
1424
  p.manager_collaborator_id AS "managerCollaboratorId",
@@ -1362,7 +1453,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1362
1453
  FROM operations_project p
1363
1454
  LEFT JOIN operations_contract c ON c.id = p.contract_id
1364
1455
  LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
1365
- WHERE ${whereClause}`, params);
1456
+ WHERE ${totalWhereClause}`, totalParams);
1366
1457
  const sortColumn = (_a = {
1367
1458
  name: 'p.name',
1368
1459
  code: 'p.code',
@@ -1445,15 +1536,23 @@ let OperationsService = OperationsService_1 = class OperationsService {
1445
1536
  var _a, _b;
1446
1537
  const actor = await this.getActorContext(userId);
1447
1538
  this.ensureCollaborator(actor);
1539
+ const myOnly = paginationParams.myOnly === true;
1540
+ const archivedOnly = paginationParams.archived === true;
1448
1541
  const pagination = this.normalizePaginationParams(paginationParams, {
1449
1542
  defaultSortField: 'name',
1450
1543
  defaultSortOrder: 'asc',
1451
1544
  allowedSortFields: ['name', 'projectName', 'status', 'createdAt'],
1452
1545
  });
1453
- const projectFilter = this.buildIdFilter(actor.visibleProjectIds, 'COALESCE(t.project_id, pa.project_id)', actor.isDirector);
1546
+ // When myOnly=true: restrict to tasks linked to the current collaborator's assignment.
1547
+ // Otherwise (general list): show all tasks in the system regardless of role scope.
1548
+ const projectFilter = myOnly
1549
+ ? this.buildIdFilter(actor.collaboratorId
1550
+ ? await this.getAssignedProjectIds([actor.collaboratorId])
1551
+ : [], 'COALESCE(t.project_id, pa.project_id)', false)
1552
+ : this.buildIdFilter([], 'COALESCE(t.project_id, pa.project_id)', true);
1454
1553
  const params = [...projectFilter.params];
1455
1554
  const filters = [
1456
- 't.deleted_at IS NULL',
1555
+ archivedOnly ? 't.deleted_at IS NOT NULL' : 't.deleted_at IS NULL',
1457
1556
  'p.deleted_at IS NULL',
1458
1557
  projectFilter.clause,
1459
1558
  `(
@@ -1477,6 +1576,9 @@ let OperationsService = OperationsService_1 = class OperationsService {
1477
1576
  if (paginationParams.projectAssignmentId) {
1478
1577
  filters.push(`pa.id = ${this.param(params, paginationParams.projectAssignmentId)}`);
1479
1578
  }
1579
+ else if (myOnly && actor.collaboratorId) {
1580
+ filters.push(`pa.collaborator_id = ${this.param(params, actor.collaboratorId)}`);
1581
+ }
1480
1582
  if (paginationParams.projectId) {
1481
1583
  filters.push(`COALESCE(t.project_id, pa.project_id) = ${this.param(params, paginationParams.projectId)}`);
1482
1584
  }
@@ -1504,14 +1606,29 @@ let OperationsService = OperationsService_1 = class OperationsService {
1504
1606
  t.name,
1505
1607
  t.description,
1506
1608
  t.status,
1609
+ t.priority,
1507
1610
  COALESCE(t.project_id, pa.project_id) AS "projectId",
1508
1611
  pa.id AS "projectAssignmentId",
1509
1612
  p.name AS "projectName",
1510
1613
  p.code AS "projectCode",
1511
- t.created_at AS "createdAt"
1614
+ t.due_date AS "dueDate",
1615
+ t.estimate_hours AS "estimateHours",
1616
+ t.tags,
1617
+ ac.display_name AS "assigneeName",
1618
+ au.photo_id AS "assigneeUserPhotoId",
1619
+ ap.avatar_id AS "assigneePersonAvatarId",
1620
+ t.created_at AS "createdAt",
1621
+ t.deleted_at AS "deletedAt"
1512
1622
  FROM operations_task t
1513
1623
  LEFT JOIN operations_project_assignment pa
1514
1624
  ON pa.id = t.project_assignment_id
1625
+ LEFT JOIN operations_collaborator ac
1626
+ ON ac.id = t.assignee_collaborator_id
1627
+ AND ac.deleted_at IS NULL
1628
+ LEFT JOIN "user" au
1629
+ ON au.id = ac.user_id
1630
+ LEFT JOIN person ap
1631
+ ON ap.id = ac.person_id
1515
1632
  JOIN operations_project p
1516
1633
  ON p.id = COALESCE(t.project_id, pa.project_id)
1517
1634
  WHERE ${whereClause}
@@ -1521,7 +1638,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1521
1638
  return this.buildPaginationResult(rows.map((row) => (Object.assign(Object.assign({}, row), { label: [row.name, row.projectName].filter(Boolean).join(' • ') }))), Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
1522
1639
  }
1523
1640
  async createTask(userId, data) {
1524
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1641
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
1525
1642
  const actor = await this.getActorContext(userId);
1526
1643
  if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1527
1644
  throw new common_1.ForbiddenException('Operations collaborator access is required.');
@@ -1581,17 +1698,17 @@ let OperationsService = OperationsService_1 = class OperationsService {
1581
1698
  RETURNING id`, [
1582
1699
  projectId,
1583
1700
  assignmentId,
1584
- (_f = data.assigneeCollaboratorId) !== null && _f !== void 0 ? _f : null,
1701
+ (_g = (_f = data.assigneeCollaboratorId) !== null && _f !== void 0 ? _f : actor.collaboratorId) !== null && _g !== void 0 ? _g : null,
1585
1702
  name,
1586
1703
  this.normalizeOptionalText(data.description),
1587
- (_g = data.priority) !== null && _g !== void 0 ? _g : 'medium',
1588
- (_h = data.status) !== null && _h !== void 0 ? _h : 'todo',
1589
- (_j = data.dueDate) !== null && _j !== void 0 ? _j : null,
1590
- (_k = data.estimateHours) !== null && _k !== void 0 ? _k : null,
1591
- (_l = data.position) !== null && _l !== void 0 ? _l : nextPosition,
1592
- (_m = data.tags) !== null && _m !== void 0 ? _m : null,
1704
+ (_h = data.priority) !== null && _h !== void 0 ? _h : 'medium',
1705
+ (_j = data.status) !== null && _j !== void 0 ? _j : 'todo',
1706
+ (_k = data.dueDate) !== null && _k !== void 0 ? _k : null,
1707
+ (_l = data.estimateHours) !== null && _l !== void 0 ? _l : null,
1708
+ (_m = data.position) !== null && _m !== void 0 ? _m : nextPosition,
1709
+ (_o = data.tags) !== null && _o !== void 0 ? _o : null,
1593
1710
  ]);
1594
- return this.getProjectBoardTask((_o = created === null || created === void 0 ? void 0 : created.id) !== null && _o !== void 0 ? _o : 0);
1711
+ return this.getProjectBoardTask((_p = created === null || created === void 0 ? void 0 : created.id) !== null && _p !== void 0 ? _p : 0);
1595
1712
  }
1596
1713
  async updateTask(userId, taskId, data) {
1597
1714
  const actor = await this.getActorContext(userId);
@@ -1607,9 +1724,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
1607
1724
  throw new common_1.BadRequestException('Field "name" is required.');
1608
1725
  }
1609
1726
  await this.prisma.$transaction(async (tx) => {
1610
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1727
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1611
1728
  let nextAssignmentId = current.projectAssignmentId;
1612
1729
  let nextProjectId = current.projectId;
1730
+ const nextArchived = data.archived !== undefined ? data.archived : Boolean(current.deletedAt);
1731
+ const nextStatus = data.status !== undefined
1732
+ ? data.status
1733
+ : current.deletedAt && data.archived === false
1734
+ ? 'todo'
1735
+ : current.status;
1613
1736
  if (data.projectId !== undefined || data.projectAssignmentId !== undefined) {
1614
1737
  const nextAssignment = await this.resolveProjectAssignmentForActor(tx, actor, {
1615
1738
  projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
@@ -1631,17 +1754,21 @@ let OperationsService = OperationsService_1 = class OperationsService {
1631
1754
  estimate_hours = $9::decimal,
1632
1755
  position = $10,
1633
1756
  tags = $11,
1757
+ deleted_at = CASE
1758
+ WHEN $12::boolean THEN COALESCE(deleted_at, NOW())
1759
+ ELSE NULL
1760
+ END,
1634
1761
  updated_at = NOW()
1635
- WHERE id = $12
1636
- AND deleted_at IS NULL`, nextProjectId, nextAssignmentId, data.assigneeCollaboratorId !== undefined
1762
+ WHERE id = $13
1763
+ AND ($14::boolean = true OR deleted_at IS NULL)`, nextProjectId, nextAssignmentId, data.assigneeCollaboratorId !== undefined
1637
1764
  ? ((_c = data.assigneeCollaboratorId) !== null && _c !== void 0 ? _c : null)
1638
1765
  : current.assigneeCollaboratorId, nextName, data.description !== undefined
1639
1766
  ? this.normalizeOptionalText(data.description)
1640
- : ((_d = current.description) !== null && _d !== void 0 ? _d : null), (_e = data.priority) !== null && _e !== void 0 ? _e : current.priority, (_f = data.status) !== null && _f !== void 0 ? _f : current.status, data.dueDate !== undefined ? ((_g = data.dueDate) !== null && _g !== void 0 ? _g : null) : current.dueDate, data.estimateHours !== undefined ? ((_h = data.estimateHours) !== null && _h !== void 0 ? _h : null) : current.estimateHours, data.position !== undefined ? data.position : current.position, data.tags !== undefined ? ((_j = data.tags) !== null && _j !== void 0 ? _j : null) : current.tags, taskId);
1767
+ : ((_d = current.description) !== null && _d !== void 0 ? _d : null), (_e = data.priority) !== null && _e !== void 0 ? _e : current.priority, nextStatus, data.dueDate !== undefined ? ((_f = data.dueDate) !== null && _f !== void 0 ? _f : null) : current.dueDate, data.estimateHours !== undefined ? ((_g = data.estimateHours) !== null && _g !== void 0 ? _g : null) : current.estimateHours, data.position !== undefined ? data.position : current.position, data.tags !== undefined ? ((_h = data.tags) !== null && _h !== void 0 ? _h : null) : current.tags, nextArchived, taskId, Boolean(current.deletedAt));
1641
1768
  });
1642
1769
  return this.getProjectBoardTask(taskId);
1643
1770
  }
1644
- async removeTask(userId, taskId) {
1771
+ async removeTask(userId, taskId, permanent = false) {
1645
1772
  const actor = await this.getActorContext(userId);
1646
1773
  if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1647
1774
  throw new common_1.ForbiddenException('Operations collaborator access is required.');
@@ -1649,6 +1776,11 @@ let OperationsService = OperationsService_1 = class OperationsService {
1649
1776
  const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
1650
1777
  await this.assertProjectAccess(actor, current.projectId);
1651
1778
  await this.prisma.$transaction(async (tx) => {
1779
+ if (permanent) {
1780
+ await tx.$executeRawUnsafe(`DELETE FROM operations_task
1781
+ WHERE id = $1`, taskId);
1782
+ return;
1783
+ }
1652
1784
  await tx.$executeRawUnsafe(`UPDATE operations_task
1653
1785
  SET deleted_at = COALESCE(deleted_at, NOW()),
1654
1786
  updated_at = NOW()
@@ -1825,6 +1957,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1825
1957
  )
1826
1958
  RETURNING id`, timesheetId, 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)));
1827
1959
  await this.refreshTimesheetTotal(tx, timesheetId);
1960
+ await this.submitTimesheetForApproval(tx, timesheetId, actor.collaboratorId);
1828
1961
  return (_j = (_h = created[0]) === null || _h === void 0 ? void 0 : _h.id) !== null && _j !== void 0 ? _j : 0;
1829
1962
  });
1830
1963
  return this.getTimesheetEntryByIdForActor(actor, createdEntryId);
@@ -1841,8 +1974,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
1841
1974
  if (!actor.isDirector && entry.collaboratorId !== actor.collaboratorId) {
1842
1975
  throw new common_1.ForbiddenException('Only the entry owner can update this timesheet entry.');
1843
1976
  }
1844
- if (!['draft', 'rejected'].includes(entry.status)) {
1845
- throw new common_1.BadRequestException('Only draft or rejected timesheet entries can be edited.');
1977
+ if (entry.status === 'approved') {
1978
+ throw new common_1.BadRequestException('Approved timesheet entries can no longer be edited.');
1846
1979
  }
1847
1980
  const collaboratorId = actor.isDirector
1848
1981
  ? entry.collaboratorId
@@ -1888,6 +2021,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1888
2021
  await this.refreshTimesheetTotal(tx, targetTimesheetId);
1889
2022
  }
1890
2023
  await this.cleanupEmptyEditableTimesheet(tx, entry.timesheetId);
2024
+ await this.submitTimesheetForApproval(tx, targetTimesheetId, collaboratorId);
1891
2025
  });
1892
2026
  return this.getTimesheetEntryByIdForActor(actor, entryId);
1893
2027
  }
@@ -1901,8 +2035,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
1901
2035
  if (!actor.isDirector && entry.collaboratorId !== actor.collaboratorId) {
1902
2036
  throw new common_1.ForbiddenException('Only the entry owner can delete this timesheet entry.');
1903
2037
  }
1904
- if (!['draft', 'rejected'].includes(entry.status)) {
1905
- throw new common_1.BadRequestException('Only draft or rejected timesheet entries can be deleted.');
2038
+ if (entry.status === 'approved') {
2039
+ throw new common_1.BadRequestException('Approved timesheet entries can no longer be deleted.');
1906
2040
  }
1907
2041
  await this.prisma.$transaction(async (tx) => {
1908
2042
  await tx.$executeRawUnsafe(`UPDATE operations_timesheet_entry
@@ -2298,7 +2432,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2298
2432
  const actor = await this.getActorContext(userId);
2299
2433
  this.ensureDirector(actor);
2300
2434
  const createdId = await this.prisma.$transaction(async (tx) => {
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;
2435
+ 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;
2302
2436
  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));
2303
2437
  const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
2304
2438
  code,
@@ -2353,6 +2487,9 @@ let OperationsService = OperationsService_1 = class OperationsService {
2353
2487
  if (data.replaceUploadedPdfDocument) {
2354
2488
  await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
2355
2489
  }
2490
+ if ((_0 = data.additionalUploadedDocuments) === null || _0 === void 0 ? void 0 : _0.length) {
2491
+ await this.appendContractDocuments(tx, contractId, 'attachment', data.additionalUploadedDocuments);
2492
+ }
2356
2493
  await this.insertContractHistory(tx, contractId, userId, 'created', data.originType === 'manual'
2357
2494
  ? 'Manual contract created from registry.'
2358
2495
  : `Contract registered from origin ${data.originType}.`);
@@ -2720,7 +2857,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
2720
2857
  this.pushUpdate(updates, params, 'description', data.description);
2721
2858
  this.pushUpdate(updates, params, 'content_html', data.contentHtml);
2722
2859
  await this.prisma.$transaction(async (tx) => {
2723
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
2860
+ 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;
2724
2861
  if (updates.length) {
2725
2862
  params.push(contractId);
2726
2863
  await tx.$executeRawUnsafe(`UPDATE operations_contract
@@ -2734,25 +2871,34 @@ let OperationsService = OperationsService_1 = class OperationsService {
2734
2871
  if (data.replaceUploadedPdfDocument) {
2735
2872
  await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
2736
2873
  }
2874
+ if ((_a = data.additionalUploadedDocuments) === null || _a === void 0 ? void 0 : _a.length) {
2875
+ await this.appendContractDocuments(tx, contractId, 'attachment', data.additionalUploadedDocuments);
2876
+ }
2877
+ if ((_b = data.deletedDocumentIds) === null || _b === void 0 ? void 0 : _b.length) {
2878
+ await this.softDeleteContractDocuments(tx, contractId, data.deletedDocumentIds);
2879
+ }
2737
2880
  await this.insertContractHistory(tx, contractId, userId, 'updated', 'Contract registry data updated.');
2881
+ if ((_c = data.deletedDocumentIds) === null || _c === void 0 ? void 0 : _c.length) {
2882
+ await this.insertContractHistory(tx, contractId, userId, 'updated', 'Contract documents removed.');
2883
+ }
2738
2884
  if (shouldPublishSigned || shouldPublishActivation) {
2739
2885
  const contractEventPayload = await this.buildContractActivatedEventPayload({
2740
2886
  id: current.id,
2741
- code: (_a = data.code) !== null && _a !== void 0 ? _a : current.code,
2742
- name: (_b = data.name) !== null && _b !== void 0 ? _b : current.name,
2743
- clientName: (_c = data.clientName) !== null && _c !== void 0 ? _c : current.clientName,
2744
- contractCategory: (_d = data.contractCategory) !== null && _d !== void 0 ? _d : current.contractCategory,
2745
- contractType: (_e = data.contractType) !== null && _e !== void 0 ? _e : current.contractType,
2746
- billingModel: (_f = data.billingModel) !== null && _f !== void 0 ? _f : current.billingModel,
2747
- originType: (_g = data.originType) !== null && _g !== void 0 ? _g : current.originType,
2748
- originId: (_h = data.originId) !== null && _h !== void 0 ? _h : current.originId,
2749
- startDate: (_j = data.startDate) !== null && _j !== void 0 ? _j : current.startDate,
2750
- endDate: (_k = data.endDate) !== null && _k !== void 0 ? _k : current.endDate,
2751
- signedAt: (_l = data.signedAt) !== null && _l !== void 0 ? _l : current.signedAt,
2752
- effectiveDate: (_m = data.effectiveDate) !== null && _m !== void 0 ? _m : current.effectiveDate,
2753
- budgetAmount: (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : current.budgetAmount,
2754
- description: (_p = data.description) !== null && _p !== void 0 ? _p : current.description,
2755
- parties: (_q = data.parties) !== null && _q !== void 0 ? _q : current.parties,
2887
+ code: (_d = data.code) !== null && _d !== void 0 ? _d : current.code,
2888
+ name: (_e = data.name) !== null && _e !== void 0 ? _e : current.name,
2889
+ clientName: (_f = data.clientName) !== null && _f !== void 0 ? _f : current.clientName,
2890
+ contractCategory: (_g = data.contractCategory) !== null && _g !== void 0 ? _g : current.contractCategory,
2891
+ contractType: (_h = data.contractType) !== null && _h !== void 0 ? _h : current.contractType,
2892
+ billingModel: (_j = data.billingModel) !== null && _j !== void 0 ? _j : current.billingModel,
2893
+ originType: (_k = data.originType) !== null && _k !== void 0 ? _k : current.originType,
2894
+ originId: (_l = data.originId) !== null && _l !== void 0 ? _l : current.originId,
2895
+ startDate: (_m = data.startDate) !== null && _m !== void 0 ? _m : current.startDate,
2896
+ endDate: (_o = data.endDate) !== null && _o !== void 0 ? _o : current.endDate,
2897
+ signedAt: (_p = data.signedAt) !== null && _p !== void 0 ? _p : current.signedAt,
2898
+ effectiveDate: (_q = data.effectiveDate) !== null && _q !== void 0 ? _q : current.effectiveDate,
2899
+ budgetAmount: (_r = data.budgetAmount) !== null && _r !== void 0 ? _r : current.budgetAmount,
2900
+ description: (_s = data.description) !== null && _s !== void 0 ? _s : current.description,
2901
+ parties: (_t = data.parties) !== null && _t !== void 0 ? _t : current.parties,
2756
2902
  }, 'en', userId);
2757
2903
  if (shouldPublishSigned) {
2758
2904
  await this.integrationApi.publishEvent({
@@ -2760,15 +2906,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
2760
2906
  sourceModule: 'operations',
2761
2907
  aggregateType: 'contract',
2762
2908
  aggregateId: String(contractId),
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() }),
2909
+ payload: Object.assign(Object.assign({}, contractEventPayload), { signedByUserId: userId !== null && userId !== void 0 ? userId : null, signedAt: (_w = (_v = (_u = data.signedAt) !== null && _u !== void 0 ? _u : current.signedAt) !== null && _v !== void 0 ? _v : contractEventPayload.signedAt) !== null && _w !== void 0 ? _w : new Date().toISOString() }),
2764
2910
  metadata: {
2765
2911
  producer: 'operations',
2766
2912
  correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
2767
2913
  sourceModule: 'operations',
2768
2914
  sourceEntity: 'contract',
2769
2915
  sourceId: String(contractId),
2770
- originType: (_u = data.originType) !== null && _u !== void 0 ? _u : current.originType,
2771
- originId: (_v = data.originId) !== null && _v !== void 0 ? _v : current.originId,
2916
+ originType: (_x = data.originType) !== null && _x !== void 0 ? _x : current.originType,
2917
+ originId: (_y = data.originId) !== null && _y !== void 0 ? _y : current.originId,
2772
2918
  lifecycle: 'signed',
2773
2919
  },
2774
2920
  }, {
@@ -2788,8 +2934,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2788
2934
  sourceModule: 'operations',
2789
2935
  sourceEntity: 'contract',
2790
2936
  sourceId: String(contractId),
2791
- originType: (_w = data.originType) !== null && _w !== void 0 ? _w : current.originType,
2792
- originId: (_x = data.originId) !== null && _x !== void 0 ? _x : current.originId,
2937
+ originType: (_z = data.originType) !== null && _z !== void 0 ? _z : current.originType,
2938
+ originId: (_0 = data.originId) !== null && _0 !== void 0 ? _0 : current.originId,
2793
2939
  lifecycle: 'activated',
2794
2940
  },
2795
2941
  }, {
@@ -2848,6 +2994,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
2848
2994
  if (filters.status && filters.status !== 'all') {
2849
2995
  where.push(`t.status::text = ${this.param(params, filters.status)}`);
2850
2996
  }
2997
+ if (filters.dateFrom) {
2998
+ where.push(`t.week_start_date >= ${this.param(params, filters.dateFrom)}::date`);
2999
+ }
3000
+ if (filters.dateTo) {
3001
+ where.push(`t.week_start_date <= ${this.param(params, filters.dateTo)}::date`);
3002
+ }
2851
3003
  if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
2852
3004
  const searchPlaceholder = this.param(params, `%${pagination.search}%`);
2853
3005
  where.push(`(
@@ -2874,8 +3026,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2874
3026
  c.display_name AS "collaboratorName",
2875
3027
  t.approver_collaborator_id AS "approverCollaboratorId",
2876
3028
  a.display_name AS "approverName",
2877
- t.week_start_date AS "weekStartDate",
2878
- t.week_end_date AS "weekEndDate",
3029
+ TO_CHAR(t.week_start_date, 'YYYY-MM-DD') AS "weekStartDate",
3030
+ TO_CHAR(t.week_end_date, 'YYYY-MM-DD') AS "weekEndDate",
2879
3031
  t.total_hours AS "totalHours",
2880
3032
  t.status,
2881
3033
  t.submitted_at AS "submittedAt",
@@ -2984,8 +3136,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
2984
3136
  if (!actor.isDirector && current.collaboratorId !== actor.collaboratorId) {
2985
3137
  throw new common_1.ForbiddenException('You can only update your own timesheets.');
2986
3138
  }
2987
- if (!actor.isDirector && !['draft', 'rejected'].includes(current.status)) {
2988
- throw new common_1.BadRequestException('Only draft or rejected timesheets can be edited.');
3139
+ if (!actor.isDirector && current.status === 'approved') {
3140
+ throw new common_1.BadRequestException('Approved timesheets can no longer be edited.');
2989
3141
  }
2990
3142
  await this.prisma.$transaction(async (tx) => {
2991
3143
  const updates = [];
@@ -3008,50 +3160,16 @@ let OperationsService = OperationsService_1 = class OperationsService {
3008
3160
  return this.listSingleTimesheet(actor, timesheetId);
3009
3161
  }
3010
3162
  async submitTimesheet(userId, timesheetId) {
3011
- var _a, _b, _c;
3012
3163
  const actor = await this.getActorContext(userId);
3013
3164
  const current = await this.getTimesheetById(timesheetId);
3014
3165
  if (!actor.isDirector && current.collaboratorId !== actor.collaboratorId) {
3015
3166
  throw new common_1.ForbiddenException('You can only submit your own timesheets.');
3016
3167
  }
3017
- if (!actor.isDirector && !['draft', 'rejected'].includes(current.status)) {
3018
- throw new common_1.BadRequestException('Only draft or rejected timesheets can be submitted.');
3019
- }
3020
- const collaborator = await this.getCollaboratorById(current.collaboratorId);
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;
3030
- if (!approverId) {
3031
- throw new common_1.BadRequestException('An approver is required before submitting a timesheet.');
3032
- }
3033
- const activeEntries = await this.querySingle(`SELECT EXISTS (
3034
- SELECT 1
3035
- FROM operations_timesheet_entry
3036
- WHERE timesheet_id = $1
3037
- AND deleted_at IS NULL
3038
- ) AS exists`, [timesheetId]);
3039
- if (!(activeEntries === null || activeEntries === void 0 ? void 0 : activeEntries.exists)) {
3040
- throw new common_1.BadRequestException('Cannot submit a timesheet without active entries.');
3168
+ if (!actor.isDirector && current.status === 'approved') {
3169
+ throw new common_1.BadRequestException('Approved timesheets can no longer be submitted.');
3041
3170
  }
3042
3171
  await this.prisma.$transaction(async (tx) => {
3043
- await tx.$executeRawUnsafe(`UPDATE operations_timesheet
3044
- SET status = 'submitted',
3045
- approver_collaborator_id = $1,
3046
- submitted_at = NOW(),
3047
- updated_at = NOW()
3048
- WHERE id = $2`, approverId, timesheetId);
3049
- await this.upsertApproval(tx, {
3050
- targetType: 'timesheet',
3051
- targetId: timesheetId,
3052
- requesterCollaboratorId: current.collaboratorId,
3053
- approverCollaboratorId: approverId,
3054
- });
3172
+ await this.submitTimesheetForApproval(tx, timesheetId, current.collaboratorId, current.approverCollaboratorId);
3055
3173
  });
3056
3174
  return this.listSingleTimesheet(actor, timesheetId);
3057
3175
  }
@@ -3399,6 +3517,20 @@ let OperationsService = OperationsService_1 = class OperationsService {
3399
3517
  if (filters.targetType && filters.targetType !== 'all') {
3400
3518
  where.push(`a.target_type = ${this.param(params, filters.targetType)}::text::operations_approval_target_type_32d3f04385_enum`);
3401
3519
  }
3520
+ if (filters.dateFrom) {
3521
+ where.push(`(
3522
+ (a.target_type = 'timesheet' AND t.week_start_date >= ${this.param(params, filters.dateFrom)}::date)
3523
+ OR (a.target_type = 'time_off_request' AND tor.start_date >= ${this.param(params, filters.dateFrom)}::date)
3524
+ OR (a.target_type = 'schedule_adjustment_request' AND sar.effective_start_date >= ${this.param(params, filters.dateFrom)}::date)
3525
+ )`);
3526
+ }
3527
+ if (filters.dateTo) {
3528
+ where.push(`(
3529
+ (a.target_type = 'timesheet' AND t.week_start_date <= ${this.param(params, filters.dateTo)}::date)
3530
+ OR (a.target_type = 'time_off_request' AND tor.start_date <= ${this.param(params, filters.dateTo)}::date)
3531
+ OR (a.target_type = 'schedule_adjustment_request' AND sar.effective_start_date <= ${this.param(params, filters.dateTo)}::date)
3532
+ )`);
3533
+ }
3402
3534
  if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
3403
3535
  const searchPlaceholder = this.param(params, `%${pagination.search}%`);
3404
3536
  where.push(`(
@@ -3421,8 +3553,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
3421
3553
  a.submitted_at AS "submittedAt",
3422
3554
  a.decided_at AS "decidedAt",
3423
3555
  a.decision_note AS "decisionNote",
3424
- t.week_start_date AS "timesheetWeekStartDate",
3425
- t.week_end_date AS "timesheetWeekEndDate",
3556
+ TO_CHAR(t.week_start_date, 'YYYY-MM-DD') AS "timesheetWeekStartDate",
3557
+ TO_CHAR(t.week_end_date, 'YYYY-MM-DD') AS "timesheetWeekEndDate",
3426
3558
  t.total_hours AS "timesheetTotalHours",
3427
3559
  COALESCE(
3428
3560
  STRING_AGG(DISTINCT p.name, ', ') FILTER (WHERE p.name IS NOT NULL),
@@ -3519,8 +3651,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
3519
3651
  a.submitted_at AS "submittedAt",
3520
3652
  a.decided_at AS "decidedAt",
3521
3653
  a.decision_note AS "decisionNote",
3522
- t.week_start_date AS "timesheetWeekStartDate",
3523
- t.week_end_date AS "timesheetWeekEndDate",
3654
+ TO_CHAR(t.week_start_date, 'YYYY-MM-DD') AS "timesheetWeekStartDate",
3655
+ TO_CHAR(t.week_end_date, 'YYYY-MM-DD') AS "timesheetWeekEndDate",
3524
3656
  t.total_hours AS "timesheetTotalHours",
3525
3657
  COALESCE(
3526
3658
  STRING_AGG(DISTINCT p.name, ', ') FILTER (WHERE p.name IS NOT NULL),
@@ -3742,7 +3874,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
3742
3874
  WHERE id = $1`, [approvalId]);
3743
3875
  }
3744
3876
  async getActorContext(userId) {
3745
- var _a, _b;
3877
+ var _a;
3746
3878
  const roleSlugs = (await this.prisma.role.findMany({
3747
3879
  where: {
3748
3880
  role_user: {
@@ -3762,7 +3894,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
3762
3894
  if (!collaborator && isCollaborator && !isDirector) {
3763
3895
  throw new common_1.NotFoundException('The authenticated user does not have a linked operations collaborator profile.');
3764
3896
  }
3765
- const collaboratorId = (_a = collaborator === null || collaborator === void 0 ? void 0 : collaborator.id) !== null && _a !== void 0 ? _a : null;
3897
+ const collaboratorId = this.normalizeIntegerId(collaborator === null || collaborator === void 0 ? void 0 : collaborator.id);
3766
3898
  const teamCollaboratorIds = isSupervisor && collaboratorId
3767
3899
  ? await this.getDirectReportIds(collaboratorId)
3768
3900
  : [];
@@ -3770,7 +3902,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
3770
3902
  userId,
3771
3903
  roleSlugs,
3772
3904
  collaboratorId,
3773
- collaboratorName: (_b = collaborator === null || collaborator === void 0 ? void 0 : collaborator.displayName) !== null && _b !== void 0 ? _b : null,
3905
+ collaboratorName: (_a = collaborator === null || collaborator === void 0 ? void 0 : collaborator.displayName) !== null && _a !== void 0 ? _a : null,
3774
3906
  isDirector,
3775
3907
  isSupervisor,
3776
3908
  isCollaborator,
@@ -3893,33 +4025,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
3893
4025
  }
3894
4026
  }
3895
4027
  async resolveDepartmentReference(client, input) {
3896
- var _a;
3897
- if (input.departmentName !== undefined) {
3898
- const normalizedDepartmentName = this.normalizeOptionalText(input.departmentName);
3899
- if (!normalizedDepartmentName) {
3900
- return null;
3901
- }
3902
- const existingByName = (await client.$queryRawUnsafe(`SELECT id, name
3903
- FROM operations_department
3904
- WHERE deleted_at IS NULL
3905
- AND LOWER(name) = LOWER($1)
3906
- ORDER BY id ASC
3907
- LIMIT 1`, normalizedDepartmentName));
3908
- if (existingByName[0]) {
3909
- return existingByName[0];
3910
- }
3911
- const slug = await this.generateUniqueDepartmentSlug(client, normalizedDepartmentName);
3912
- const created = (await client.$queryRawUnsafe(`INSERT INTO operations_department (
3913
- slug,
3914
- code,
3915
- name,
3916
- description,
3917
- created_at,
3918
- updated_at
3919
- ) VALUES ($1, NULL, $2, NULL, NOW(), NOW())
3920
- RETURNING id, name`, slug, normalizedDepartmentName));
3921
- return (_a = created[0]) !== null && _a !== void 0 ? _a : null;
3922
- }
3923
4028
  if (typeof input.departmentId === 'number' &&
3924
4029
  Number.isInteger(input.departmentId) &&
3925
4030
  input.departmentId > 0) {
@@ -4422,7 +4527,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
4422
4527
  collaborator_type.name AS "collaboratorType",
4423
4528
  COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
4424
4529
  c.department_id AS "departmentId",
4425
- COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
4530
+ department_record.name AS "department",
4426
4531
  c.job_title_id AS "jobTitleId",
4427
4532
  COALESCE(NULLIF(job_title_record.name, ''), NULLIF(c.title, '')) AS "title",
4428
4533
  c.level_label AS "levelLabel",
@@ -4573,20 +4678,21 @@ let OperationsService = OperationsService_1 = class OperationsService {
4573
4678
  }, scheduleAdjustmentRequests });
4574
4679
  }
4575
4680
  async getDirectReportIds(collaboratorId) {
4576
- return (await this.queryRows(`SELECT id
4681
+ return this.uniqueNumbers((await this.queryRows(`SELECT id
4577
4682
  FROM operations_collaborator
4578
4683
  WHERE supervisor_collaborator_id = $1
4579
4684
  AND deleted_at IS NULL
4580
- ORDER BY id ASC`, [collaboratorId])).map((row) => row.id);
4685
+ ORDER BY id ASC`, [collaboratorId])).map((row) => row.id));
4581
4686
  }
4582
4687
  async getAssignedProjectIds(collaboratorIds) {
4583
- if (!collaboratorIds.length)
4688
+ const normalizedCollaboratorIds = this.uniqueNumbers(collaboratorIds);
4689
+ if (!normalizedCollaboratorIds.length)
4584
4690
  return [];
4585
- return (await this.queryRows(`SELECT DISTINCT project_id AS "projectId"
4691
+ return this.uniqueNumbers((await this.queryRows(`SELECT DISTINCT project_id AS "projectId"
4586
4692
  FROM operations_project_assignment
4587
4693
  WHERE deleted_at IS NULL
4588
4694
  AND status IN ('planned', 'active')
4589
- AND collaborator_id = ANY($1::int[])`, [collaboratorIds])).map((row) => row.projectId);
4695
+ AND collaborator_id = ANY($1::int[])`, [normalizedCollaboratorIds])).map((row) => row.projectId));
4590
4696
  }
4591
4697
  async resolveOwnedProjectAssignment(client, collaboratorId, input) {
4592
4698
  if (!input.projectId && !input.projectAssignmentId) {
@@ -4670,7 +4776,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
4670
4776
  t.project_assignment_id AS "projectAssignmentId",
4671
4777
  COALESCE(t.project_id, pa.project_id) AS "projectId",
4672
4778
  p.name AS "projectName",
4673
- p.code AS "projectCode"
4779
+ p.code AS "projectCode",
4780
+ t.deleted_at AS "deletedAt"
4674
4781
  FROM operations_task t
4675
4782
  LEFT JOIN operations_project_assignment pa
4676
4783
  ON pa.id = t.project_assignment_id
@@ -4679,7 +4786,6 @@ let OperationsService = OperationsService_1 = class OperationsService {
4679
4786
  ON p.id = COALESCE(t.project_id, pa.project_id)
4680
4787
  AND p.deleted_at IS NULL
4681
4788
  WHERE t.id = $1
4682
- AND t.deleted_at IS NULL
4683
4789
  AND (
4684
4790
  pa.collaborator_id = $2
4685
4791
  OR t.project_id IN (
@@ -4755,7 +4861,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
4755
4861
  ap.avatar_id AS "assigneePersonAvatarId",
4756
4862
  t.project_assignment_id AS "projectAssignmentId",
4757
4863
  COALESCE(t.project_id, pa.project_id) AS "projectId",
4758
- t.created_at AS "createdAt"
4864
+ t.created_at AS "createdAt",
4865
+ t.deleted_at AS "deletedAt"
4759
4866
  FROM operations_task t
4760
4867
  LEFT JOIN operations_collaborator ac
4761
4868
  ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
@@ -4779,8 +4886,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
4779
4886
  ORDER BY id DESC
4780
4887
  LIMIT 1`, collaboratorId, weekStartDate, weekEndDate));
4781
4888
  if (existing[0]) {
4782
- if (!['draft', 'rejected'].includes(existing[0].status)) {
4783
- throw new common_1.BadRequestException('The timesheet for this week has already been submitted or approved.');
4889
+ if (existing[0].status === 'approved') {
4890
+ throw new common_1.BadRequestException('The timesheet for this week has already been approved.');
4784
4891
  }
4785
4892
  return existing[0].id;
4786
4893
  }
@@ -4954,11 +5061,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
4954
5061
  }
4955
5062
  async cleanupEmptyEditableTimesheet(client, timesheetId) {
4956
5063
  var _a;
4957
- const candidate = (await client.$queryRawUnsafe(`SELECT t.id
5064
+ const candidate = (await client.$queryRawUnsafe(`SELECT t.id,
5065
+ t.status
4958
5066
  FROM operations_timesheet t
4959
5067
  WHERE t.id = $1
4960
5068
  AND t.deleted_at IS NULL
4961
- AND t.status IN ('draft', 'rejected')
5069
+ AND t.status IN ('draft', 'rejected', 'submitted')
4962
5070
  AND NOT EXISTS (
4963
5071
  SELECT 1
4964
5072
  FROM operations_timesheet_entry e
@@ -4974,6 +5082,52 @@ let OperationsService = OperationsService_1 = class OperationsService {
4974
5082
  updated_at = NOW()
4975
5083
  WHERE id = $1
4976
5084
  AND deleted_at IS NULL`, timesheetId);
5085
+ if (candidate[0].status === 'submitted') {
5086
+ await client.$executeRawUnsafe(`UPDATE operations_approval
5087
+ SET deleted_at = NOW(),
5088
+ updated_at = NOW()
5089
+ WHERE target_type = 'timesheet'::operations_approval_target_type_32d3f04385_enum
5090
+ AND target_id = $1
5091
+ AND deleted_at IS NULL`, timesheetId);
5092
+ }
5093
+ }
5094
+ async submitTimesheetForApproval(client, timesheetId, collaboratorId, currentApproverCollaboratorId) {
5095
+ var _a, _b, _c, _d, _e;
5096
+ const collaborator = await this.getCollaboratorById(collaboratorId);
5097
+ const projectManagerRow = await client.$queryRawUnsafe(`SELECT p.manager_collaborator_id AS "managerCollaboratorId"
5098
+ FROM operations_timesheet_entry e
5099
+ LEFT JOIN operations_project_assignment pa ON pa.id = e.project_assignment_id
5100
+ LEFT JOIN operations_project p ON p.id = pa.project_id
5101
+ WHERE e.timesheet_id = $1
5102
+ AND e.deleted_at IS NULL
5103
+ AND p.manager_collaborator_id IS NOT NULL
5104
+ LIMIT 1`, timesheetId);
5105
+ const approverId = (_d = (_c = (_b = (_a = projectManagerRow[0]) === null || _a === void 0 ? void 0 : _a.managerCollaboratorId) !== null && _b !== void 0 ? _b : currentApproverCollaboratorId) !== null && _c !== void 0 ? _c : collaborator.supervisorId) !== null && _d !== void 0 ? _d : null;
5106
+ if (!approverId) {
5107
+ throw new common_1.BadRequestException('An approver is required before submitting a timesheet.');
5108
+ }
5109
+ const activeEntries = await client.$queryRawUnsafe(`SELECT EXISTS (
5110
+ SELECT 1
5111
+ FROM operations_timesheet_entry
5112
+ WHERE timesheet_id = $1
5113
+ AND deleted_at IS NULL
5114
+ ) AS exists`, timesheetId);
5115
+ if (!((_e = activeEntries[0]) === null || _e === void 0 ? void 0 : _e.exists)) {
5116
+ throw new common_1.BadRequestException('Cannot submit a timesheet without active entries.');
5117
+ }
5118
+ await client.$executeRawUnsafe(`UPDATE operations_timesheet
5119
+ SET status = 'submitted',
5120
+ approver_collaborator_id = $1,
5121
+ submitted_at = NOW(),
5122
+ updated_at = NOW()
5123
+ WHERE id = $2
5124
+ AND deleted_at IS NULL`, approverId, timesheetId);
5125
+ await this.upsertApproval(client, {
5126
+ targetType: 'timesheet',
5127
+ targetId: timesheetId,
5128
+ requesterCollaboratorId: collaboratorId,
5129
+ approverCollaboratorId: approverId,
5130
+ });
4977
5131
  }
4978
5132
  async upsertApproval(client, input) {
4979
5133
  var _a, _b;
@@ -5486,6 +5640,40 @@ let OperationsService = OperationsService_1 = class OperationsService {
5486
5640
  $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()
5487
5641
  )`, 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);
5488
5642
  }
5643
+ async appendContractDocuments(client, contractId, documentType, documents) {
5644
+ var _a, _b, _c, _d, _e;
5645
+ for (const document of documents) {
5646
+ await client.$executeRawUnsafe(`INSERT INTO operations_contract_document (
5647
+ contract_id,
5648
+ document_type,
5649
+ file_id,
5650
+ file_name,
5651
+ mime_type,
5652
+ file_content_base64,
5653
+ is_current,
5654
+ extraction_status,
5655
+ extraction_summary,
5656
+ notes,
5657
+ created_at,
5658
+ updated_at
5659
+ ) VALUES (
5660
+ $1, $2::operations_contract_document_document_type_15ebaff0c9_enum, $3, $4, $5, $6, false, $7::operations_contract_document_extraction_status_6d94c231f3_enum, $8, $9, NOW(), NOW()
5661
+ )`, 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);
5662
+ }
5663
+ }
5664
+ async softDeleteContractDocuments(client, contractId, documentIds) {
5665
+ const normalizedIds = Array.from(new Set(documentIds.filter((value) => Number.isInteger(value) && Number(value) > 0)));
5666
+ if (!normalizedIds.length) {
5667
+ return;
5668
+ }
5669
+ await client.$executeRawUnsafe(`UPDATE operations_contract_document
5670
+ SET deleted_at = NOW(),
5671
+ updated_at = NOW(),
5672
+ is_current = false
5673
+ WHERE contract_id = $1
5674
+ AND id = ANY($2::int[])
5675
+ AND deleted_at IS NULL`, contractId, normalizedIds);
5676
+ }
5489
5677
  async resolveContractExtractionFile(userId, data) {
5490
5678
  if (data.contractId && data.contractId > 0) {
5491
5679
  const contract = await this.getContractById(userId, data.contractId);
@@ -5788,6 +5976,17 @@ let OperationsService = OperationsService_1 = class OperationsService {
5788
5976
  $1, $2, $3, $4, $5, NOW()
5789
5977
  )`, contractId, actorUserId, action, note, metadataJson !== null && metadataJson !== void 0 ? metadataJson : null);
5790
5978
  }
5979
+ async insertCollaboratorCompensationHistory(client, collaboratorId, amount, actorUserId, notes) {
5980
+ await client.$executeRawUnsafe(`INSERT INTO operations_collaborator_compensation_history (
5981
+ collaborator_id,
5982
+ amount,
5983
+ actor_user_id,
5984
+ notes,
5985
+ created_at
5986
+ ) VALUES (
5987
+ $1, $2, $3, $4, NOW()
5988
+ )`, collaboratorId, amount, actorUserId, notes !== null && notes !== void 0 ? notes : null);
5989
+ }
5791
5990
  async generateCollaboratorCode(client) {
5792
5991
  for (let attempt = 0; attempt < 5; attempt += 1) {
5793
5992
  const candidate = `COL-${Date.now().toString(36).toUpperCase()}${Math.random()
@@ -6279,16 +6478,9 @@ let OperationsService = OperationsService_1 = class OperationsService {
6279
6478
  return value.toISOString().slice(0, 10);
6280
6479
  }
6281
6480
  getWorkWeekRange(workDate) {
6282
- const date = this.parseDateOnly(workDate);
6283
- const day = date.getUTCDay();
6284
- const diffToMonday = day === 0 ? -6 : 1 - day;
6285
- const weekStart = new Date(date);
6286
- weekStart.setUTCDate(weekStart.getUTCDate() + diffToMonday);
6287
- const weekEnd = new Date(weekStart);
6288
- weekEnd.setUTCDate(weekStart.getUTCDate() + 6);
6289
6481
  return {
6290
- weekStartDate: this.formatDateOnly(weekStart),
6291
- weekEndDate: this.formatDateOnly(weekEnd),
6482
+ weekStartDate: workDate,
6483
+ weekEndDate: workDate,
6292
6484
  };
6293
6485
  }
6294
6486
  normalizePaginationParams(input = {}, options) {
@@ -6417,22 +6609,39 @@ let OperationsService = OperationsService_1 = class OperationsService {
6417
6609
  }
6418
6610
  }
6419
6611
  buildIdFilter(ids, column, allowAll) {
6612
+ const normalizedIds = this.uniqueNumbers(ids);
6420
6613
  if (allowAll) {
6421
6614
  return { clause: '1 = 1', params: [] };
6422
6615
  }
6423
- if (!ids.length) {
6616
+ if (!normalizedIds.length) {
6424
6617
  return { clause: '1 = 0', params: [] };
6425
6618
  }
6426
6619
  return {
6427
6620
  clause: `${column} = ANY($1::int[])`,
6428
- params: [ids],
6621
+ params: [normalizedIds],
6429
6622
  };
6430
6623
  }
6431
6624
  uniqueNumbers(values) {
6432
6625
  return [
6433
- ...new Set(values.filter((value) => typeof value === 'number')),
6626
+ ...new Set(values
6627
+ .map((value) => this.normalizeIntegerId(value))
6628
+ .filter((value) => value !== null)),
6434
6629
  ];
6435
6630
  }
6631
+ normalizeIntegerId(value) {
6632
+ if (typeof value === 'number' && Number.isInteger(value)) {
6633
+ return value;
6634
+ }
6635
+ if (typeof value === 'bigint') {
6636
+ const normalized = Number(value);
6637
+ return Number.isSafeInteger(normalized) ? normalized : null;
6638
+ }
6639
+ if (typeof value === 'string') {
6640
+ const normalized = Number(value);
6641
+ return Number.isInteger(normalized) ? normalized : null;
6642
+ }
6643
+ return null;
6644
+ }
6436
6645
  pushUpdate(updates, params, column, value, castType) {
6437
6646
  if (value === undefined)
6438
6647
  return;
@@ -6466,12 +6675,1040 @@ let OperationsService = OperationsService_1 = class OperationsService {
6466
6675
  async execute(sql, params = []) {
6467
6676
  return this.prisma.$executeRawUnsafe(sql, ...params);
6468
6677
  }
6678
+ shiftSqlPlaceholders(sql, offset) {
6679
+ if (offset <= 0) {
6680
+ return sql;
6681
+ }
6682
+ return sql.replace(/\$(\d+)/g, (_match, index) => {
6683
+ return `$${Number(index) + offset}`;
6684
+ });
6685
+ }
6469
6686
  async getNextCollaboratorTypeSortOrder(client = this.prisma) {
6470
6687
  var _a, _b;
6471
6688
  const row = (await client.$queryRawUnsafe(`SELECT COALESCE(MAX(sort_order), 0) + 1 AS "nextSortOrder"
6472
6689
  FROM operations_collaborator_type`));
6473
6690
  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
6691
  }
6692
+ // ─── Collaborator-scoped endpoints (no scope manipulation) ─────────────────
6693
+ async listMyProjects(userId, filters = {}) {
6694
+ var _a, _b;
6695
+ const actor = await this.getActorContext(userId);
6696
+ const filter = this.buildIdFilter(actor.collaboratorId
6697
+ ? await this.getAssignedProjectIds([actor.collaboratorId])
6698
+ : [], 'p.id', false);
6699
+ const assignmentParams = [];
6700
+ const ownAssignmentSelect = actor.collaboratorId
6701
+ ? `MAX(CASE WHEN pa.collaborator_id = ${this.param(assignmentParams, actor.collaboratorId)} THEN pa.id END)::int AS "myAssignmentId",
6702
+ MAX(CASE WHEN pa.collaborator_id = ${this.param(assignmentParams, actor.collaboratorId)} THEN pa.role_label END) AS "myRoleLabel",`
6703
+ : `NULL::int AS "myAssignmentId",
6704
+ NULL::varchar AS "myRoleLabel",`;
6705
+ const pagination = this.shouldPaginate(filters)
6706
+ ? this.normalizePaginationParams(filters, {
6707
+ defaultSortField: 'name',
6708
+ defaultSortOrder: 'asc',
6709
+ allowedSortFields: ['name', 'code', 'clientName', 'startDate', 'endDate', 'status'],
6710
+ })
6711
+ : null;
6712
+ const params = [...assignmentParams, ...filter.params];
6713
+ const totalParams = [...filter.params];
6714
+ const where = [
6715
+ 'p.deleted_at IS NULL',
6716
+ this.shiftSqlPlaceholders(filter.clause, assignmentParams.length),
6717
+ ];
6718
+ const totalWhere = ['p.deleted_at IS NULL', filter.clause];
6719
+ if (filters.status && filters.status !== 'all') {
6720
+ where.push(`p.status::text = ${this.param(params, filters.status)}`);
6721
+ totalWhere.push(`p.status::text = ${this.param(totalParams, filters.status)}`);
6722
+ }
6723
+ if (pagination === null || pagination === void 0 ? void 0 : pagination.search) {
6724
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
6725
+ const totalSearchPlaceholder = this.param(totalParams, `%${pagination.search}%`);
6726
+ where.push(`(
6727
+ COALESCE(p.name, '') ILIKE ${searchPlaceholder}
6728
+ OR COALESCE(p.code, '') ILIKE ${searchPlaceholder}
6729
+ OR COALESCE(p.client_name, '') ILIKE ${searchPlaceholder}
6730
+ OR COALESCE(c.name, '') ILIKE ${searchPlaceholder}
6731
+ OR COALESCE(m.display_name, '') ILIKE ${searchPlaceholder}
6732
+ )`);
6733
+ totalWhere.push(`(
6734
+ COALESCE(p.name, '') ILIKE ${totalSearchPlaceholder}
6735
+ OR COALESCE(p.code, '') ILIKE ${totalSearchPlaceholder}
6736
+ OR COALESCE(p.client_name, '') ILIKE ${totalSearchPlaceholder}
6737
+ OR COALESCE(c.name, '') ILIKE ${totalSearchPlaceholder}
6738
+ OR COALESCE(m.display_name, '') ILIKE ${totalSearchPlaceholder}
6739
+ )`);
6740
+ }
6741
+ const whereClause = where.join(' AND ');
6742
+ const totalWhereClause = totalWhere.join(' AND ');
6743
+ const baseQuery = `SELECT p.id,
6744
+ p.contract_id AS "contractId",
6745
+ p.manager_collaborator_id AS "managerCollaboratorId",
6746
+ p.code,
6747
+ p.name,
6748
+ p.client_name AS "clientName",
6749
+ p.summary,
6750
+ p.status,
6751
+ p.progress_percent AS "progressPercent",
6752
+ p.delivery_model AS "deliveryModel",
6753
+ TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
6754
+ TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
6755
+ m.display_name AS "managerName",
6756
+ ${ownAssignmentSelect}
6757
+ COUNT(DISTINCT pa.id)::int AS "teamSize"
6758
+ FROM operations_project p
6759
+ LEFT JOIN operations_contract c ON c.id = p.contract_id
6760
+ LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
6761
+ LEFT JOIN operations_project_assignment pa
6762
+ ON pa.project_id = p.id
6763
+ AND pa.deleted_at IS NULL
6764
+ AND pa.status IN ('planned', 'active')
6765
+ WHERE ${whereClause}
6766
+ GROUP BY p.id, c.id, m.id`;
6767
+ if (!pagination) {
6768
+ return this.queryRows(`${baseQuery} ORDER BY p.name ASC`, params);
6769
+ }
6770
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
6771
+ FROM operations_project p
6772
+ LEFT JOIN operations_contract c ON c.id = p.contract_id
6773
+ LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
6774
+ WHERE ${totalWhereClause}`, totalParams);
6775
+ const sortColumn = (_a = {
6776
+ name: 'p.name',
6777
+ code: 'p.code',
6778
+ clientName: 'p.client_name',
6779
+ startDate: 'p.start_date',
6780
+ endDate: 'p.end_date',
6781
+ status: 'p.status',
6782
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 'p.name';
6783
+ const queryParams = [...params];
6784
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
6785
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
6786
+ const rows = await this.queryRows(`${baseQuery}
6787
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, p.id ASC
6788
+ LIMIT ${limitPlaceholder}
6789
+ OFFSET ${offsetPlaceholder}`, queryParams);
6790
+ 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);
6791
+ }
6792
+ async getMyProjectSummary(userId, projectId) {
6793
+ var _a;
6794
+ const actor = await this.getActorContext(userId);
6795
+ await this.assertProjectAccess(actor, projectId);
6796
+ const projectParams = [];
6797
+ const myCollaboratorId = (_a = actor.collaboratorId) !== null && _a !== void 0 ? _a : null;
6798
+ const collaboratorPlaceholder = this.param(projectParams, myCollaboratorId);
6799
+ const project = await this.querySingle(`SELECT p.id,
6800
+ p.code,
6801
+ p.name,
6802
+ p.status,
6803
+ p.summary,
6804
+ TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
6805
+ TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
6806
+ MAX(CASE WHEN pa.collaborator_id = ${collaboratorPlaceholder} THEN pa.id END)::int AS "myAssignmentId",
6807
+ MAX(CASE WHEN pa.collaborator_id = ${collaboratorPlaceholder} THEN COALESCE(project_role_locale.name, pa.role_label) END) AS "myRoleLabel"
6808
+ FROM operations_project p
6809
+ LEFT JOIN operations_project_assignment pa
6810
+ ON pa.project_id = p.id
6811
+ AND pa.deleted_at IS NULL
6812
+ LEFT JOIN operations_project_role project_role
6813
+ ON project_role.id = pa.project_role_id
6814
+ AND project_role.deleted_at IS NULL
6815
+ LEFT JOIN LATERAL (
6816
+ SELECT prl.name
6817
+ FROM operations_project_role_locale prl
6818
+ WHERE prl.operations_project_role_id = project_role.id
6819
+ ORDER BY prl.id ASC
6820
+ LIMIT 1
6821
+ ) project_role_locale ON TRUE
6822
+ WHERE p.id = ${this.param(projectParams, projectId)}
6823
+ AND p.deleted_at IS NULL
6824
+ GROUP BY p.id`, projectParams);
6825
+ if (!project) {
6826
+ throw new common_1.NotFoundException('Project not found.');
6827
+ }
6828
+ const [assignments, tasks] = await Promise.all([
6829
+ this.queryRows(`SELECT pa.id,
6830
+ pa.collaborator_id AS "collaboratorId",
6831
+ c.display_name AS "collaboratorName",
6832
+ COALESCE(project_role_locale.name, pa.role_label) AS "roleLabel",
6833
+ pa.status,
6834
+ person_record.avatar_id AS "avatarId",
6835
+ collaborator_user.photo_id AS "userPhotoId"
6836
+ FROM operations_project_assignment pa
6837
+ JOIN operations_collaborator c ON c.id = pa.collaborator_id
6838
+ LEFT JOIN person person_record ON person_record.id = c.person_id
6839
+ LEFT JOIN "user" collaborator_user ON collaborator_user.id = c.user_id
6840
+ LEFT JOIN operations_project_role project_role
6841
+ ON project_role.id = pa.project_role_id
6842
+ AND project_role.deleted_at IS NULL
6843
+ LEFT JOIN LATERAL (
6844
+ SELECT prl.name
6845
+ FROM operations_project_role_locale prl
6846
+ WHERE prl.operations_project_role_id = project_role.id
6847
+ ORDER BY prl.id ASC
6848
+ LIMIT 1
6849
+ ) project_role_locale ON TRUE
6850
+ WHERE pa.project_id = $1
6851
+ AND pa.deleted_at IS NULL
6852
+ ORDER BY c.display_name ASC`, [projectId]),
6853
+ this.queryRows(`SELECT t.id,
6854
+ t.name,
6855
+ t.description,
6856
+ t.priority,
6857
+ t.status,
6858
+ t.due_date AS "dueDate",
6859
+ t.estimate_hours AS "estimateHours",
6860
+ t.position,
6861
+ t.tags,
6862
+ t.assignee_collaborator_id AS "assigneeCollaboratorId",
6863
+ ac.display_name AS "assigneeName",
6864
+ au.photo_id AS "assigneeUserPhotoId",
6865
+ ap.avatar_id AS "assigneePersonAvatarId",
6866
+ t.project_assignment_id AS "projectAssignmentId",
6867
+ t.created_at AS "createdAt"
6868
+ FROM operations_task t
6869
+ LEFT JOIN operations_collaborator ac
6870
+ ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
6871
+ LEFT JOIN "user" au ON au.id = ac.user_id
6872
+ LEFT JOIN person ap ON ap.id = ac.person_id
6873
+ WHERE COALESCE(t.project_id, (
6874
+ SELECT pa.project_id FROM operations_project_assignment pa
6875
+ WHERE pa.id = t.project_assignment_id AND pa.deleted_at IS NULL
6876
+ LIMIT 1
6877
+ )) = $1
6878
+ AND t.deleted_at IS NULL
6879
+ ORDER BY t.status ASC, t.position ASC, t.id ASC`, [projectId]),
6880
+ ]);
6881
+ return Object.assign(Object.assign({}, project), { assignments,
6882
+ tasks });
6883
+ }
6884
+ async listMyTasks(userId, paginationParams = {}) {
6885
+ var _a, _b;
6886
+ const actor = await this.getActorContext(userId);
6887
+ this.ensureCollaborator(actor);
6888
+ const pagination = this.normalizePaginationParams(paginationParams, {
6889
+ defaultSortField: 'name',
6890
+ defaultSortOrder: 'asc',
6891
+ allowedSortFields: ['name', 'projectName', 'status', 'createdAt'],
6892
+ });
6893
+ const projectFilter = this.buildIdFilter(actor.collaboratorId
6894
+ ? await this.getAssignedProjectIds([actor.collaboratorId])
6895
+ : [], 'COALESCE(t.project_id, pa.project_id)', false);
6896
+ const params = [...projectFilter.params];
6897
+ const archivedOnly = paginationParams.archived === true;
6898
+ const filters = [
6899
+ archivedOnly ? 't.deleted_at IS NOT NULL' : 't.deleted_at IS NULL',
6900
+ 'p.deleted_at IS NULL',
6901
+ projectFilter.clause,
6902
+ `(
6903
+ t.project_id IS NOT NULL
6904
+ OR (
6905
+ pa.id IS NOT NULL
6906
+ AND pa.deleted_at IS NULL
6907
+ AND pa.status IN ('planned', 'active')
6908
+ )
6909
+ )`,
6910
+ ];
6911
+ if (actor.collaboratorId) {
6912
+ filters.push(`pa.collaborator_id = ${this.param(params, actor.collaboratorId)}`);
6913
+ }
6914
+ if (pagination.search) {
6915
+ const searchPlaceholder = this.param(params, `%${pagination.search}%`);
6916
+ filters.push(`(
6917
+ COALESCE(t.name, '') ILIKE ${searchPlaceholder}
6918
+ OR COALESCE(t.description, '') ILIKE ${searchPlaceholder}
6919
+ OR COALESCE(p.name, '') ILIKE ${searchPlaceholder}
6920
+ OR COALESCE(p.code, '') ILIKE ${searchPlaceholder}
6921
+ )`);
6922
+ }
6923
+ if (paginationParams.status) {
6924
+ filters.push(`t.status::text = ${this.param(params, paginationParams.status)}`);
6925
+ }
6926
+ const whereClause = filters.join(' AND ');
6927
+ const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
6928
+ FROM operations_task t
6929
+ LEFT JOIN operations_project_assignment pa
6930
+ ON pa.id = t.project_assignment_id
6931
+ JOIN operations_project p
6932
+ ON p.id = COALESCE(t.project_id, pa.project_id)
6933
+ WHERE ${whereClause}`, params);
6934
+ const sortColumn = (_a = {
6935
+ name: 't.name',
6936
+ projectName: 'p.name',
6937
+ status: 't.status',
6938
+ createdAt: 't.created_at',
6939
+ }[pagination.sortField]) !== null && _a !== void 0 ? _a : 't.name';
6940
+ const queryParams = [...params];
6941
+ const limitPlaceholder = this.param(queryParams, pagination.pageSize);
6942
+ const offsetPlaceholder = this.param(queryParams, pagination.offset);
6943
+ const rows = await this.queryRows(`SELECT t.id,
6944
+ t.name,
6945
+ t.description,
6946
+ t.status,
6947
+ t.priority,
6948
+ COALESCE(t.project_id, pa.project_id) AS "projectId",
6949
+ pa.id AS "projectAssignmentId",
6950
+ p.name AS "projectName",
6951
+ p.code AS "projectCode",
6952
+ t.due_date AS "dueDate",
6953
+ t.estimate_hours AS "estimateHours",
6954
+ t.tags,
6955
+ ac.display_name AS "assigneeName",
6956
+ au.photo_id AS "assigneeUserPhotoId",
6957
+ ap.avatar_id AS "assigneePersonAvatarId",
6958
+ t.created_at AS "createdAt",
6959
+ t.deleted_at AS "deletedAt"
6960
+ FROM operations_task t
6961
+ LEFT JOIN operations_project_assignment pa
6962
+ ON pa.id = t.project_assignment_id
6963
+ LEFT JOIN operations_collaborator ac
6964
+ ON ac.id = t.assignee_collaborator_id
6965
+ AND ac.deleted_at IS NULL
6966
+ LEFT JOIN "user" au
6967
+ ON au.id = ac.user_id
6968
+ LEFT JOIN person ap
6969
+ ON ap.id = ac.person_id
6970
+ JOIN operations_project p
6971
+ ON p.id = COALESCE(t.project_id, pa.project_id)
6972
+ WHERE ${whereClause}
6973
+ ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, t.id ASC
6974
+ LIMIT ${limitPlaceholder}
6975
+ OFFSET ${offsetPlaceholder}`, queryParams);
6976
+ return this.buildPaginationResult(rows.map((row) => (Object.assign(Object.assign({}, row), { label: [row.name, row.projectName].filter(Boolean).join(' • ') }))), Number((_b = totalRow === null || totalRow === void 0 ? void 0 : totalRow.total) !== null && _b !== void 0 ? _b : 0), pagination.page, pagination.pageSize);
6977
+ }
6978
+ // ──────────────────────────────────────────────────────────────────────────
6979
+ // Cost Types
6980
+ // ──────────────────────────────────────────────────────────────────────────
6981
+ async listCostTypes(userId, filters = {}) {
6982
+ var _a;
6983
+ await this.getActorContext(userId);
6984
+ const params = [];
6985
+ const where = [];
6986
+ if (filters.active === true) {
6987
+ where.push('ct.is_active = true');
6988
+ }
6989
+ if ((_a = filters.search) === null || _a === void 0 ? void 0 : _a.trim()) {
6990
+ const p = this.param(params, `%${filters.search.trim()}%`);
6991
+ where.push(`(COALESCE(ct.name, '') ILIKE ${p} OR COALESCE(ct.slug, '') ILIKE ${p} OR COALESCE(ct.code, '') ILIKE ${p} OR COALESCE(ct.category, '') ILIKE ${p})`);
6992
+ }
6993
+ const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '';
6994
+ const rows = await this.queryRows(`SELECT ct.id,
6995
+ ct.slug,
6996
+ ct.name,
6997
+ ct.code,
6998
+ ct.category,
6999
+ ct.description,
7000
+ ct.default_recurrence AS "defaultRecurrence",
7001
+ ct.is_allocatable AS "isAllocatable",
7002
+ ct.is_depreciable AS "isDepreciable",
7003
+ ct.is_active AS "isActive",
7004
+ ct.created_at AS "createdAt"
7005
+ FROM operations_cost_type ct
7006
+ ${whereClause}
7007
+ ORDER BY ct.name ASC`, params);
7008
+ return rows;
7009
+ }
7010
+ async createCostType(userId, data) {
7011
+ var _a, _b, _c, _d, _e;
7012
+ const actor = await this.getActorContext(userId);
7013
+ this.ensureDirector(actor);
7014
+ const name = this.normalizeOptionalText(data.name);
7015
+ if (!name) {
7016
+ throw new common_1.BadRequestException('Cost type name is required.');
7017
+ }
7018
+ const baseSlug = ((_a = data.slug) === null || _a === void 0 ? void 0 : _a.trim()) || name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
7019
+ const existing = await this.querySingle(`SELECT id FROM operations_cost_type WHERE slug = $1 LIMIT 1`, [baseSlug]);
7020
+ const finalSlug = existing
7021
+ ? `${baseSlug}-${Date.now()}`
7022
+ : baseSlug;
7023
+ const created = await this.querySingle(`INSERT INTO operations_cost_type (slug, name, code, category, description, default_recurrence, is_allocatable, is_depreciable, is_active, created_at, updated_at)
7024
+ VALUES ($1, $2, $3, $4, $5, $6::operations_cost_type_default_recurrence_82bb00323e_enum, $7, $8, $9, NOW(), NOW())
7025
+ RETURNING id`, [
7026
+ finalSlug,
7027
+ name,
7028
+ this.normalizeOptionalText(data.code),
7029
+ this.normalizeOptionalText(data.category),
7030
+ this.normalizeOptionalText(data.description),
7031
+ (_b = data.defaultRecurrence) !== null && _b !== void 0 ? _b : null,
7032
+ (_c = data.isAllocatable) !== null && _c !== void 0 ? _c : null,
7033
+ (_d = data.isDepreciable) !== null && _d !== void 0 ? _d : null,
7034
+ (_e = data.isActive) !== null && _e !== void 0 ? _e : true,
7035
+ ]);
7036
+ if (!(created === null || created === void 0 ? void 0 : created.id)) {
7037
+ throw new common_1.BadRequestException('Unable to create cost type.');
7038
+ }
7039
+ return this.querySingle(`SELECT id, slug, name, code, category, description, default_recurrence AS "defaultRecurrence", is_allocatable AS "isAllocatable", is_depreciable AS "isDepreciable", is_active AS "isActive" FROM operations_cost_type WHERE id = $1`, [created.id]);
7040
+ }
7041
+ // ──────────────────────────────────────────────────────────────────────────
7042
+ // Collaborator Costs
7043
+ // ──────────────────────────────────────────────────────────────────────────
7044
+ async listCollaboratorCosts(userId, collaboratorId) {
7045
+ await this.getActorContext(userId);
7046
+ return this.queryRows(`SELECT cc.id,
7047
+ cc.collaborator_id AS "collaboratorId",
7048
+ cc.cost_type_id AS "costTypeId",
7049
+ ct.name AS "costTypeName",
7050
+ ct.slug AS "costTypeSlug",
7051
+ cc.amount::text AS amount,
7052
+ cc.currency,
7053
+ cc.recurrence,
7054
+ cc.allocatable,
7055
+ cc.reference_date::text AS "referenceDate",
7056
+ cc.start_date::text AS "startDate",
7057
+ cc.end_date::text AS "endDate",
7058
+ cc.depreciation_months AS "depreciationMonths",
7059
+ cc.description,
7060
+ cc.notes,
7061
+ cc.created_at AS "createdAt"
7062
+ FROM operations_collaborator_cost cc
7063
+ JOIN operations_cost_type ct ON ct.id = cc.cost_type_id
7064
+ WHERE cc.collaborator_id = $1
7065
+ ORDER BY cc.created_at DESC`, [collaboratorId]);
7066
+ }
7067
+ async getCollaboratorCostsSummary(userId, collaboratorId) {
7068
+ await this.getActorContext(userId);
7069
+ const costs = await this.listCollaboratorCosts(userId, collaboratorId);
7070
+ let monthlyTotal = 0;
7071
+ let allocatableTotal = 0;
7072
+ for (const c of costs) {
7073
+ const amount = parseFloat(c.amount);
7074
+ let monthly = 0;
7075
+ if (c.recurrence === 'one_time') {
7076
+ monthly = c.depreciationMonths && c.depreciationMonths > 0 ? amount / c.depreciationMonths : 0;
7077
+ }
7078
+ else if (c.recurrence === 'monthly') {
7079
+ monthly = amount;
7080
+ }
7081
+ else if (c.recurrence === 'yearly') {
7082
+ monthly = amount / 12;
7083
+ }
7084
+ else if (c.recurrence === 'quarterly') {
7085
+ monthly = amount / 3;
7086
+ }
7087
+ else {
7088
+ monthly = amount;
7089
+ }
7090
+ monthlyTotal += monthly;
7091
+ if (c.allocatable !== false) {
7092
+ allocatableTotal += monthly;
7093
+ }
7094
+ }
7095
+ return {
7096
+ monthlyTotal: Math.round(monthlyTotal * 100) / 100,
7097
+ allocatableTotal: Math.round(allocatableTotal * 100) / 100,
7098
+ nonAllocatableTotal: Math.round((monthlyTotal - allocatableTotal) * 100) / 100,
7099
+ count: costs.length,
7100
+ };
7101
+ }
7102
+ async getProjectsReport(userId, filters = {}) {
7103
+ var _a, _b, _c, _d;
7104
+ const actor = await this.getActorContext(userId);
7105
+ this.ensureDirector(actor);
7106
+ const currentYear = new Date().getFullYear();
7107
+ const from = /^\d{4}-\d{2}-\d{2}$/.test(String((_a = filters.from) !== null && _a !== void 0 ? _a : ''))
7108
+ ? String(filters.from)
7109
+ : `${currentYear}-01-01`;
7110
+ const to = /^\d{4}-\d{2}-\d{2}$/.test(String((_b = filters.to) !== null && _b !== void 0 ? _b : ''))
7111
+ ? String(filters.to)
7112
+ : `${currentYear}-12-31`;
7113
+ const scenario = filters.scenario === 'growth' || filters.scenario === 'conservative'
7114
+ ? filters.scenario
7115
+ : 'base';
7116
+ const multiplier = scenario === 'growth'
7117
+ ? { revenue: 1.16, cost: 1.09, backlog: 1.22 }
7118
+ : scenario === 'conservative'
7119
+ ? { revenue: 0.9, cost: 0.96, backlog: 0.82 }
7120
+ : { revenue: 1, cost: 1, backlog: 1 };
7121
+ const params = [from, to];
7122
+ const where = [
7123
+ 'p.deleted_at IS NULL',
7124
+ '(p.end_date IS NULL OR p.end_date >= $1::date)',
7125
+ '(p.start_date IS NULL OR p.start_date <= $2::date)',
7126
+ ];
7127
+ if (filters.client && filters.client !== 'all') {
7128
+ where.push(`COALESCE(NULLIF(p.client_name, ''), NULLIF(contract_record.client_name, ''), '-') = ${this.param(params, filters.client)}`);
7129
+ }
7130
+ const dbRows = await this.queryRows(`SELECT p.id,
7131
+ p.name,
7132
+ COALESCE(NULLIF(p.client_name, ''), NULLIF(contract_record.client_name, ''), '-') AS client,
7133
+ COALESCE(NULLIF(manager_record.display_name, ''), '-') AS manager,
7134
+ COALESCE(NULLIF(p.delivery_model::text, ''), '-') AS squad,
7135
+ p.status::text AS status,
7136
+ COALESCE(contract_record.billing_model::text, p.delivery_model::text, 'fixed_price') AS "contractType",
7137
+ TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
7138
+ TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
7139
+ COALESCE(p.budget_amount, contract_record.budget_amount, 0)::text AS "contractedRevenue",
7140
+ COALESCE(p.progress_percent, 0)::text AS "progressPercent",
7141
+ COALESCE(assignment_stats.weekly_hours, 0)::text AS "weeklyHours",
7142
+ COALESCE(time_stats.actual_hours, 0)::text AS "actualHours",
7143
+ COALESCE(time_stats.billable_hours, 0)::text AS "billableHours",
7144
+ COALESCE(task_stats.open_tasks, 0)::text AS "openTasks",
7145
+ COALESCE(task_stats.backlog_hours, 0)::text AS "backlogHours",
7146
+ COALESCE(task_stats.future_deliveries, 0)::text AS "futureDeliveries"
7147
+ FROM operations_project p
7148
+ LEFT JOIN operations_contract contract_record
7149
+ ON contract_record.id = p.contract_id
7150
+ AND contract_record.deleted_at IS NULL
7151
+ LEFT JOIN operations_collaborator manager_record
7152
+ ON manager_record.id = p.manager_collaborator_id
7153
+ AND manager_record.deleted_at IS NULL
7154
+ LEFT JOIN LATERAL (
7155
+ SELECT COALESCE(SUM(pa.weekly_hours), 0) AS weekly_hours
7156
+ FROM operations_project_assignment pa
7157
+ WHERE pa.project_id = p.id
7158
+ AND pa.deleted_at IS NULL
7159
+ AND pa.status IN ('planned', 'active')
7160
+ ) assignment_stats ON TRUE
7161
+ LEFT JOIN LATERAL (
7162
+ SELECT COALESCE(SUM(entry.hours), 0) AS actual_hours,
7163
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true), 0) AS billable_hours
7164
+ FROM operations_timesheet_entry entry
7165
+ JOIN operations_project_assignment pa
7166
+ ON pa.id = entry.project_assignment_id
7167
+ AND pa.deleted_at IS NULL
7168
+ WHERE pa.project_id = p.id
7169
+ AND entry.deleted_at IS NULL
7170
+ AND entry.work_date BETWEEN $1::date AND $2::date
7171
+ ) time_stats ON TRUE
7172
+ LEFT JOIN LATERAL (
7173
+ SELECT COUNT(*) FILTER (WHERE task.status IN ('todo', 'doing', 'review')) AS open_tasks,
7174
+ COALESCE(SUM(task.estimate_hours) FILTER (WHERE task.status IN ('todo', 'doing', 'review')), 0) AS backlog_hours,
7175
+ COUNT(*) FILTER (WHERE task.due_date > $2::date AND task.status IN ('todo', 'doing', 'review')) AS future_deliveries
7176
+ FROM operations_task task
7177
+ WHERE task.project_id = p.id
7178
+ AND task.deleted_at IS NULL
7179
+ ) task_stats ON TRUE
7180
+ WHERE ${where.join(' AND ')}
7181
+ ORDER BY p.name ASC`, params);
7182
+ const fromDate = new Date(`${from}T00:00:00`);
7183
+ const toDate = new Date(`${to}T00:00:00`);
7184
+ const periodWeeks = Math.max(1, Math.ceil((toDate.getTime() - fromDate.getTime()) / 604800000));
7185
+ const rows = dbRows
7186
+ .map((row) => {
7187
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
7188
+ const progress = Number((_a = row.progressPercent) !== null && _a !== void 0 ? _a : 0);
7189
+ const contractedRevenue = Number((_b = row.contractedRevenue) !== null && _b !== void 0 ? _b : 0);
7190
+ const recognizedRevenue = contractedRevenue * (progress / 100);
7191
+ const actualHours = Number((_c = row.actualHours) !== null && _c !== void 0 ? _c : 0);
7192
+ const plannedHours = Math.max(Number((_d = row.weeklyHours) !== null && _d !== void 0 ? _d : 0) * periodWeeks, actualHours);
7193
+ const realizedCost = 0;
7194
+ const reportStatus = row.status === 'paused'
7195
+ ? 'paused'
7196
+ : row.status === 'at_risk'
7197
+ ? 'attention'
7198
+ : row.endDate && new Date(`${row.endDate}T00:00:00`) < new Date() && progress < 100
7199
+ ? 'late'
7200
+ : 'on_track';
7201
+ const risk = reportStatus === 'late' || (plannedHours && actualHours / plannedHours > 1)
7202
+ ? 'alto'
7203
+ : reportStatus === 'attention'
7204
+ ? 'médio'
7205
+ : 'baixo';
7206
+ return {
7207
+ id: Number(row.id),
7208
+ name: row.name,
7209
+ client: (_e = row.client) !== null && _e !== void 0 ? _e : '-',
7210
+ manager: (_f = row.manager) !== null && _f !== void 0 ? _f : '-',
7211
+ squad: String((_g = row.squad) !== null && _g !== void 0 ? _g : '-').replace(/_/g, ' '),
7212
+ status: reportStatus,
7213
+ contractType: row.contractType === 'monthly_retainer'
7214
+ ? 'retainer'
7215
+ : row.contractType === 'time_and_material'
7216
+ ? 'time_materials'
7217
+ : 'fixed_price',
7218
+ priority: risk === 'alto' ? 'alta' : risk === 'médio' ? 'média' : 'baixa',
7219
+ startDate: row.startDate,
7220
+ endDate: row.endDate,
7221
+ contractedRevenue,
7222
+ recognizedRevenue,
7223
+ realizedCost,
7224
+ forecastCost: realizedCost,
7225
+ teamCost: realizedCost,
7226
+ infraCost: 0,
7227
+ licenseCost: 0,
7228
+ thirdPartyCost: 0,
7229
+ reworkCost: 0,
7230
+ plannedHours,
7231
+ actualHours,
7232
+ billableHours: Number((_h = row.billableHours) !== null && _h !== void 0 ? _h : 0),
7233
+ reworkHours: 0,
7234
+ internalHours: Math.max(actualHours - Number((_j = row.billableHours) !== null && _j !== void 0 ? _j : 0), 0),
7235
+ allocatedCapacity: plannedHours ? (actualHours / plannedHours) * 100 : 0,
7236
+ physicalProgress: progress,
7237
+ financialProgress: contractedRevenue ? (recognizedRevenue / contractedRevenue) * 100 : 0,
7238
+ backlogValue: Math.max(contractedRevenue - recognizedRevenue, 0),
7239
+ futureDeliveries: Number((_k = row.futureDeliveries) !== null && _k !== void 0 ? _k : 0),
7240
+ risk,
7241
+ recommendation: risk === 'alto'
7242
+ ? 'Revisar escopo, prazo ou capacidade alocada.'
7243
+ : risk === 'médio'
7244
+ ? 'Acompanhar margem, entregas e consumo de horas.'
7245
+ : 'Manter ritmo e proteger capacidade planejada.',
7246
+ };
7247
+ })
7248
+ .filter((row) => !filters.status || filters.status === 'all' || row.status === filters.status);
7249
+ const summary = rows.reduce((acc, row) => {
7250
+ acc.contractedRevenue += row.contractedRevenue;
7251
+ acc.recognizedRevenue += row.recognizedRevenue;
7252
+ acc.realizedCost += row.realizedCost;
7253
+ acc.forecastCost += row.forecastCost;
7254
+ acc.plannedHours += row.plannedHours;
7255
+ acc.actualHours += row.actualHours;
7256
+ acc.billableHours += row.billableHours;
7257
+ acc.reworkHours += row.reworkHours;
7258
+ acc.backlogValue += row.backlogValue;
7259
+ acc.avgDeadline += row.physicalProgress;
7260
+ acc.avgAllocation += row.allocatedCapacity;
7261
+ acc.atRisk += row.risk === 'alto' ? 1 : 0;
7262
+ return acc;
7263
+ }, {
7264
+ contractedRevenue: 0,
7265
+ recognizedRevenue: 0,
7266
+ realizedCost: 0,
7267
+ forecastCost: 0,
7268
+ profit: 0,
7269
+ margin: 0,
7270
+ plannedHours: 0,
7271
+ actualHours: 0,
7272
+ billableHours: 0,
7273
+ reworkHours: 0,
7274
+ backlogValue: 0,
7275
+ avgDeadline: 0,
7276
+ avgAllocation: 0,
7277
+ atRisk: 0,
7278
+ burnRate: 0,
7279
+ });
7280
+ summary.profit = summary.recognizedRevenue - summary.realizedCost;
7281
+ summary.margin = summary.recognizedRevenue ? (summary.profit / summary.recognizedRevenue) * 100 : 0;
7282
+ summary.avgDeadline = rows.length ? summary.avgDeadline / rows.length : 0;
7283
+ summary.avgAllocation = rows.length ? summary.avgAllocation / rows.length : 0;
7284
+ summary.burnRate = summary.plannedHours ? (summary.actualHours / summary.plannedHours) * 100 : 0;
7285
+ const forecast = Array.from({ length: 12 }, (_, index) => {
7286
+ const monthDate = new Date(fromDate);
7287
+ monthDate.setMonth(fromDate.getMonth() + index);
7288
+ const revenue = Math.round((summary.recognizedRevenue / 12) * multiplier.revenue);
7289
+ const cost = Math.round((summary.realizedCost / 12) * multiplier.cost);
7290
+ const profit = revenue - cost;
7291
+ return {
7292
+ month: monthDate.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', ''),
7293
+ revenue,
7294
+ cost,
7295
+ profit,
7296
+ backlog: Math.round(Math.max(profit, 0) * multiplier.backlog),
7297
+ planned: Math.round(summary.plannedHours / 12),
7298
+ actual: Math.round(summary.actualHours / 12),
7299
+ };
7300
+ });
7301
+ return {
7302
+ filters: {
7303
+ from,
7304
+ to,
7305
+ status: (_c = filters.status) !== null && _c !== void 0 ? _c : 'all',
7306
+ client: (_d = filters.client) !== null && _d !== void 0 ? _d : 'all',
7307
+ scenario,
7308
+ clients: [...new Set(dbRows.map((row) => { var _a; return (_a = row.client) !== null && _a !== void 0 ? _a : '-'; }))].sort(),
7309
+ },
7310
+ summary,
7311
+ forecast,
7312
+ costComposition: [
7313
+ { name: 'Equipe', value: summary.realizedCost },
7314
+ { name: 'Infraestrutura', value: 0 },
7315
+ { name: 'Licenças', value: 0 },
7316
+ { name: 'Terceiros', value: 0 },
7317
+ { name: 'Retrabalho', value: 0 },
7318
+ ],
7319
+ hoursByProject: rows.map((row) => ({
7320
+ project: row.name.split(' ').slice(0, 2).join(' '),
7321
+ Faturável: row.billableHours,
7322
+ Interno: row.internalHours,
7323
+ Retrabalho: row.reworkHours,
7324
+ Livre: Math.max(row.plannedHours - row.actualHours, 0),
7325
+ })),
7326
+ health: rows.map((row) => ({
7327
+ project: row.name.split(' ').slice(0, 2).join(' '),
7328
+ margem: Math.max(row.recognizedRevenue ? ((row.recognizedRevenue - row.realizedCost) / row.recognizedRevenue) * 100 : 0, 0),
7329
+ prazo: row.physicalProgress,
7330
+ alocacao: row.allocatedCapacity,
7331
+ saude: Math.max(100 - (row.risk === 'alto' ? 28 : row.risk === 'médio' ? 14 : 0), 35),
7332
+ })),
7333
+ ranking: rows
7334
+ .map((row) => ({
7335
+ name: row.name.split(' ').slice(0, 2).join(' '),
7336
+ Lucro: row.recognizedRevenue - row.realizedCost,
7337
+ Custo: row.realizedCost,
7338
+ }))
7339
+ .sort((a, b) => b.Lucro - a.Lucro),
7340
+ progress: forecast.map((row) => ({
7341
+ month: row.month,
7342
+ Planejado: row.planned,
7343
+ Realizado: row.actual,
7344
+ })),
7345
+ planningCards: [
7346
+ { title: 'Acelerar críticas', value: String(summary.atRisk), description: 'Projetos com risco alto devem receber revisão de prazo e capacidade.' },
7347
+ { title: 'Renegociar escopo', value: Math.round(summary.backlogValue).toString(), description: 'Backlog financeiro ainda não reconhecido no período.' },
7348
+ { title: 'Realocar equipe', value: Math.round(Math.max(summary.plannedHours - summary.actualHours, 0)).toString(), description: 'Horas planejadas ainda não consumidas.' },
7349
+ { title: 'Priorizar margem', value: rows.filter((row) => row.recognizedRevenue > row.realizedCost).length.toString(), description: 'Projetos com contribuição positiva no recorte.' },
7350
+ ],
7351
+ rows,
7352
+ };
7353
+ }
7354
+ async getCollaboratorsReport(userId, filters = {}) {
7355
+ var _a, _b, _c, _d;
7356
+ const actor = await this.getActorContext(userId);
7357
+ this.ensureDirector(actor);
7358
+ const currentYear = new Date().getFullYear();
7359
+ const from = /^\d{4}-\d{2}-\d{2}$/.test(String((_a = filters.from) !== null && _a !== void 0 ? _a : ''))
7360
+ ? String(filters.from)
7361
+ : `${currentYear}-01-01`;
7362
+ const to = /^\d{4}-\d{2}-\d{2}$/.test(String((_b = filters.to) !== null && _b !== void 0 ? _b : ''))
7363
+ ? String(filters.to)
7364
+ : `${currentYear}-12-31`;
7365
+ const scenario = filters.scenario === 'growth' || filters.scenario === 'conservative'
7366
+ ? filters.scenario
7367
+ : 'base';
7368
+ const multiplier = scenario === 'growth'
7369
+ ? { revenue: 1.18, cost: 1.11, capacity: 1.08 }
7370
+ : scenario === 'conservative'
7371
+ ? { revenue: 0.9, cost: 0.96, capacity: 0.94 }
7372
+ : { revenue: 1, cost: 1, capacity: 1 };
7373
+ const params = [from, to];
7374
+ const where = [
7375
+ 'c.deleted_at IS NULL',
7376
+ '(c.joined_at IS NULL OR c.joined_at <= $2::date)',
7377
+ '(c.left_at IS NULL OR c.left_at >= $1::date)',
7378
+ ];
7379
+ if (filters.department && filters.department !== 'all') {
7380
+ where.push(`COALESCE(department_record.name, '-') = ${this.param(params, filters.department)}`);
7381
+ }
7382
+ if (filters.contractType && filters.contractType !== 'all') {
7383
+ where.push(`COALESCE(collaborator_type.name, collaborator_type.slug, '-') = ${this.param(params, filters.contractType)}`);
7384
+ }
7385
+ const dbRows = await this.queryRows(`SELECT c.id,
7386
+ COALESCE(NULLIF(c.display_name, ''), person_record.name, c.code) AS name,
7387
+ COALESCE(job_title_record.name, c.title, '-') AS role,
7388
+ COALESCE(c.level_label, '-') AS seniority,
7389
+ COALESCE(department_record.name, '-') AS department,
7390
+ COALESCE(collaborator_type.name, collaborator_type.slug, '-') AS "contractType",
7391
+ TO_CHAR(c.joined_at, 'YYYY-MM-DD') AS "startDate",
7392
+ TO_CHAR(c.left_at, 'YYYY-MM-DD') AS "endDate",
7393
+ COALESCE(c.weekly_capacity_hours, 40)::text AS "weeklyCapacityHours",
7394
+ COALESCE(cost_stats.salary_cost, 0)::text AS "salaryCost",
7395
+ COALESCE(cost_stats.benefits_cost, 0)::text AS "benefitsCost",
7396
+ COALESCE(cost_stats.taxes_cost, 0)::text AS "taxesCost",
7397
+ COALESCE(cost_stats.tools_cost, 0)::text AS "toolsCost",
7398
+ COALESCE(value_stats.billable_value, 0)::text AS "billableValue",
7399
+ COALESCE(value_stats.allocated_hours, 0)::text AS "allocatedHours",
7400
+ COALESCE(value_stats.billable_hours, 0)::text AS "billableHours",
7401
+ COALESCE(project_stats.projects, 0)::text AS projects
7402
+ FROM operations_collaborator c
7403
+ LEFT JOIN person person_record ON person_record.id = c.person_id
7404
+ LEFT JOIN operations_department department_record
7405
+ ON department_record.id = c.department_id
7406
+ AND department_record.deleted_at IS NULL
7407
+ LEFT JOIN operations_job_title job_title_record
7408
+ ON job_title_record.id = c.job_title_id
7409
+ AND job_title_record.deleted_at IS NULL
7410
+ LEFT JOIN operations_collaborator_type collaborator_type
7411
+ ON collaborator_type.id = c.collaborator_type_id
7412
+ AND collaborator_type.deleted_at IS NULL
7413
+ LEFT JOIN LATERAL (
7414
+ SELECT COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('salario-base', 'pro-labore')), 0) AS salary_cost,
7415
+ COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('vale-refeicao', 'vale-alimentacao', 'vale-transporte', 'plano-saude', 'plano-odontologico', 'seguro-vida')), 0) AS benefits_cost,
7416
+ COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('inss-patronal', 'fgts', 'rat-fap', 'terceiros-sistema-s', 'provisao-decimo-terceiro', 'provisao-ferias')), 0) AS taxes_cost,
7417
+ COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('software-licenca', 'equipamento')), 0) AS tools_cost
7418
+ FROM operations_collaborator_cost cost
7419
+ LEFT JOIN operations_cost_type cost_type ON cost_type.id = cost.cost_type_id
7420
+ WHERE cost.collaborator_id = c.id
7421
+ AND (cost.start_date IS NULL OR cost.start_date <= $2::date)
7422
+ AND (cost.end_date IS NULL OR cost.end_date >= $1::date)
7423
+ ) cost_stats ON TRUE
7424
+ LEFT JOIN LATERAL (
7425
+ SELECT COALESCE(SUM(entry.hours), 0) AS allocated_hours,
7426
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true), 0) AS billable_hours,
7427
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true) * 120, 0) AS billable_value
7428
+ FROM operations_timesheet_entry entry
7429
+ JOIN operations_project_assignment pa
7430
+ ON pa.id = entry.project_assignment_id
7431
+ AND pa.deleted_at IS NULL
7432
+ WHERE pa.collaborator_id = c.id
7433
+ AND entry.deleted_at IS NULL
7434
+ AND entry.work_date BETWEEN $1::date AND $2::date
7435
+ ) value_stats ON TRUE
7436
+ LEFT JOIN LATERAL (
7437
+ SELECT COUNT(DISTINCT pa.project_id) AS projects
7438
+ FROM operations_project_assignment pa
7439
+ WHERE pa.collaborator_id = c.id
7440
+ AND pa.deleted_at IS NULL
7441
+ AND pa.status IN ('planned', 'active')
7442
+ ) project_stats ON TRUE
7443
+ WHERE ${where.join(' AND ')}
7444
+ ORDER BY name ASC`, params);
7445
+ const fromDate = new Date(`${from}T00:00:00`);
7446
+ const toDate = new Date(`${to}T00:00:00`);
7447
+ const periodWeeks = Math.max(1, Math.ceil((toDate.getTime() - fromDate.getTime()) / 604800000));
7448
+ const rows = dbRows.map((row) => {
7449
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
7450
+ const salaryCost = Number((_a = row.salaryCost) !== null && _a !== void 0 ? _a : 0);
7451
+ const benefitsCost = Number((_b = row.benefitsCost) !== null && _b !== void 0 ? _b : 0);
7452
+ const taxesCost = Number((_c = row.taxesCost) !== null && _c !== void 0 ? _c : 0);
7453
+ const toolsCost = Number((_d = row.toolsCost) !== null && _d !== void 0 ? _d : 0);
7454
+ const availableHours = Number((_e = row.weeklyCapacityHours) !== null && _e !== void 0 ? _e : 40) * periodWeeks;
7455
+ const allocatedHours = Number((_f = row.allocatedHours) !== null && _f !== void 0 ? _f : 0);
7456
+ const billableHours = Number((_g = row.billableHours) !== null && _g !== void 0 ? _g : 0);
7457
+ const allocation = availableHours ? (allocatedHours / availableHours) * 100 : 0;
7458
+ const risk = allocation >= 98 ? 'alto' : allocation < 75 ? 'médio' : 'baixo';
7459
+ return {
7460
+ id: Number(row.id),
7461
+ name: row.name,
7462
+ role: (_h = row.role) !== null && _h !== void 0 ? _h : '-',
7463
+ seniority: (_j = row.seniority) !== null && _j !== void 0 ? _j : '-',
7464
+ department: (_k = row.department) !== null && _k !== void 0 ? _k : '-',
7465
+ contractType: (_l = row.contractType) !== null && _l !== void 0 ? _l : '-',
7466
+ startDate: row.startDate,
7467
+ endDate: row.endDate,
7468
+ salaryCost,
7469
+ benefitsCost,
7470
+ taxesCost,
7471
+ toolsCost,
7472
+ billableValue: Number((_m = row.billableValue) !== null && _m !== void 0 ? _m : 0),
7473
+ availableHours,
7474
+ allocatedHours,
7475
+ billableHours,
7476
+ internalHours: Math.max(allocatedHours - billableHours, 0),
7477
+ overtimeHours: Math.max(allocatedHours - availableHours, 0),
7478
+ projects: Number((_o = row.projects) !== null && _o !== void 0 ? _o : 0),
7479
+ risk,
7480
+ recommendation: risk === 'alto'
7481
+ ? 'Reduzir sobrecarga ou redistribuir entregas.'
7482
+ : risk === 'médio'
7483
+ ? 'Acompanhar alocação e buscar maior aproveitamento faturável.'
7484
+ : 'Manter alocação e proteger margem.',
7485
+ };
7486
+ });
7487
+ const summary = rows.reduce((acc, row) => {
7488
+ acc.cost += row.salaryCost + row.benefitsCost + row.taxesCost + row.toolsCost;
7489
+ acc.salary += row.salaryCost;
7490
+ acc.benefits += row.benefitsCost;
7491
+ acc.taxes += row.taxesCost;
7492
+ acc.tools += row.toolsCost;
7493
+ acc.billableValue += row.billableValue;
7494
+ acc.availableHours += row.availableHours;
7495
+ acc.allocatedHours += row.allocatedHours;
7496
+ acc.billableHours += row.billableHours;
7497
+ acc.internalHours += row.internalHours;
7498
+ acc.overtimeHours += row.overtimeHours;
7499
+ acc.overloadCount += row.risk === 'alto' ? 1 : 0;
7500
+ return acc;
7501
+ }, {
7502
+ cost: 0,
7503
+ salary: 0,
7504
+ benefits: 0,
7505
+ taxes: 0,
7506
+ tools: 0,
7507
+ billableValue: 0,
7508
+ profit: 0,
7509
+ margin: 0,
7510
+ availableHours: 0,
7511
+ allocatedHours: 0,
7512
+ billableHours: 0,
7513
+ internalHours: 0,
7514
+ overtimeHours: 0,
7515
+ freeHours: 0,
7516
+ allocation: 0,
7517
+ utilization: 0,
7518
+ overloadCount: 0,
7519
+ hourlyCost: 0,
7520
+ });
7521
+ summary.profit = summary.billableValue - summary.cost;
7522
+ summary.margin = summary.billableValue ? (summary.profit / summary.billableValue) * 100 : 0;
7523
+ summary.freeHours = Math.max(summary.availableHours - summary.allocatedHours, 0);
7524
+ summary.allocation = summary.availableHours ? (summary.allocatedHours / summary.availableHours) * 100 : 0;
7525
+ summary.utilization = summary.availableHours ? (summary.billableHours / summary.availableHours) * 100 : 0;
7526
+ summary.hourlyCost = summary.allocatedHours ? summary.cost / summary.allocatedHours : 0;
7527
+ const forecast = Array.from({ length: 12 }, (_, index) => {
7528
+ const monthDate = new Date(fromDate);
7529
+ monthDate.setMonth(fromDate.getMonth() + index);
7530
+ const revenue = Math.round((summary.billableValue / 12) * multiplier.revenue);
7531
+ const cost = Math.round((summary.cost / 12) * multiplier.cost);
7532
+ const profit = revenue - cost;
7533
+ return {
7534
+ month: monthDate.toLocaleDateString('pt-BR', { month: 'short' }).replace('.', ''),
7535
+ revenue,
7536
+ cost,
7537
+ profit,
7538
+ margin: revenue ? Math.round((profit / revenue) * 100) : 0,
7539
+ capacity: Math.round((summary.allocatedHours / 12) * multiplier.capacity),
7540
+ };
7541
+ });
7542
+ const departments = [...new Set(dbRows.map((row) => { var _a; return (_a = row.department) !== null && _a !== void 0 ? _a : '-'; }))].sort();
7543
+ return {
7544
+ filters: {
7545
+ from,
7546
+ to,
7547
+ department: (_c = filters.department) !== null && _c !== void 0 ? _c : 'all',
7548
+ contractType: (_d = filters.contractType) !== null && _d !== void 0 ? _d : 'all',
7549
+ scenario,
7550
+ departments,
7551
+ contractTypes: [...new Set(dbRows.map((row) => { var _a; return (_a = row.contractType) !== null && _a !== void 0 ? _a : '-'; }))].sort(),
7552
+ },
7553
+ summary,
7554
+ forecast,
7555
+ costComposition: [
7556
+ { name: 'Salários / Contratos', value: summary.salary },
7557
+ { name: 'Benefícios', value: summary.benefits },
7558
+ { name: 'Encargos', value: summary.taxes },
7559
+ { name: 'Ferramentas', value: summary.tools },
7560
+ ],
7561
+ capacityByDepartment: departments.map((department) => {
7562
+ const departmentRows = rows.filter((row) => row.department === department);
7563
+ const available = departmentRows.reduce((sum, row) => sum + row.availableHours, 0);
7564
+ const allocated = departmentRows.reduce((sum, row) => sum + row.allocatedHours, 0);
7565
+ return {
7566
+ department,
7567
+ Faturável: departmentRows.reduce((sum, row) => sum + row.billableHours, 0),
7568
+ Interno: departmentRows.reduce((sum, row) => sum + row.internalHours, 0),
7569
+ Livre: Math.max(available - allocated, 0),
7570
+ Sobrecarga: Math.max(allocated - available, 0),
7571
+ };
7572
+ }),
7573
+ health: departments.map((department) => {
7574
+ const departmentRows = rows.filter((row) => row.department === department);
7575
+ const value = departmentRows.reduce((sum, row) => sum + row.billableValue, 0);
7576
+ const cost = departmentRows.reduce((sum, row) => sum + row.salaryCost + row.benefitsCost + row.taxesCost + row.toolsCost, 0);
7577
+ const available = departmentRows.reduce((sum, row) => sum + row.availableHours, 0);
7578
+ const allocated = departmentRows.reduce((sum, row) => sum + row.allocatedHours, 0);
7579
+ const billable = departmentRows.reduce((sum, row) => sum + row.billableHours, 0);
7580
+ return {
7581
+ department,
7582
+ margem: Math.max(value ? ((value - cost) / value) * 100 : 0, 0),
7583
+ alocacao: available ? (allocated / available) * 100 : 0,
7584
+ utilizacao: available ? (billable / available) * 100 : 0,
7585
+ saude: Math.max(100 - departmentRows.filter((row) => row.risk === 'alto').length * 12, 45),
7586
+ };
7587
+ }),
7588
+ ranking: rows
7589
+ .map((row) => ({
7590
+ name: row.name.split(' ')[0],
7591
+ Custo: row.salaryCost + row.benefitsCost + row.taxesCost + row.toolsCost,
7592
+ Lucro: row.billableValue - (row.salaryCost + row.benefitsCost + row.taxesCost + row.toolsCost),
7593
+ }))
7594
+ .sort((a, b) => b.Lucro - a.Lucro),
7595
+ planningCards: [
7596
+ { title: 'Contratar', value: String(summary.overloadCount), description: 'Pessoas em sobrecarga no recorte.' },
7597
+ { title: 'Realocar', value: Math.round(summary.freeHours).toString(), description: 'Horas livres estimadas no período.' },
7598
+ { title: 'Reduzir sobrecarga', value: Math.round(summary.overtimeHours).toString(), description: 'Horas acima da capacidade cadastrada.' },
7599
+ { title: 'Aumentar venda', value: Math.round(summary.utilization).toString(), description: 'Percentual de utilização faturável.' },
7600
+ ],
7601
+ rows,
7602
+ };
7603
+ }
7604
+ async deleteCollaboratorCostById(userId, costId) {
7605
+ const actor = await this.getActorContext(userId);
7606
+ this.ensureDirector(actor);
7607
+ const cost = await this.querySingle(`SELECT id, collaborator_id AS "collaboratorId" FROM operations_collaborator_cost WHERE id = $1 LIMIT 1`, [costId]);
7608
+ if (!cost) {
7609
+ throw new common_1.NotFoundException('Collaborator cost not found.');
7610
+ }
7611
+ await this.prisma.$queryRawUnsafe(`DELETE FROM operations_collaborator_cost WHERE id = $1`, costId);
7612
+ return { success: true };
7613
+ }
7614
+ async createCollaboratorCost(userId, collaboratorId, data) {
7615
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
7616
+ const actor = await this.getActorContext(userId);
7617
+ this.ensureDirector(actor);
7618
+ const collaborator = await this.querySingle(`SELECT id FROM operations_collaborator WHERE id = $1 AND deleted_at IS NULL LIMIT 1`, [collaboratorId]);
7619
+ if (!collaborator) {
7620
+ throw new common_1.NotFoundException('Collaborator not found.');
7621
+ }
7622
+ const costType = await this.querySingle(`SELECT id FROM operations_cost_type WHERE id = $1 AND is_active = true LIMIT 1`, [data.costTypeId]);
7623
+ if (!costType) {
7624
+ throw new common_1.BadRequestException('Cost type not found or inactive.');
7625
+ }
7626
+ const created = await this.querySingle(`INSERT INTO operations_collaborator_cost
7627
+ (collaborator_id, cost_type_id, amount, currency, recurrence, allocatable, reference_date, start_date, end_date, depreciation_months, description, notes, created_at, updated_at)
7628
+ VALUES ($1, $2, $3, $4, $5::operations_collaborator_cost_recurrence_56fbe60170_enum, $6, $7::date, $8::date, $9::date, $10, $11, $12, NOW(), NOW())
7629
+ RETURNING id`, [
7630
+ collaboratorId,
7631
+ data.costTypeId,
7632
+ data.amount,
7633
+ (_a = data.currency) !== null && _a !== void 0 ? _a : 'BRL',
7634
+ (_b = data.recurrence) !== null && _b !== void 0 ? _b : 'monthly',
7635
+ (_c = data.allocatable) !== null && _c !== void 0 ? _c : true,
7636
+ (_e = (_d = data.referenceDate) !== null && _d !== void 0 ? _d : data.startDate) !== null && _e !== void 0 ? _e : null,
7637
+ (_f = data.startDate) !== null && _f !== void 0 ? _f : null,
7638
+ (_g = data.endDate) !== null && _g !== void 0 ? _g : null,
7639
+ (_h = data.depreciationMonths) !== null && _h !== void 0 ? _h : null,
7640
+ (_j = data.description) !== null && _j !== void 0 ? _j : null,
7641
+ (_k = data.notes) !== null && _k !== void 0 ? _k : null,
7642
+ ]);
7643
+ if (!(created === null || created === void 0 ? void 0 : created.id)) {
7644
+ throw new common_1.BadRequestException('Unable to create collaborator cost.');
7645
+ }
7646
+ const rows = await this.listCollaboratorCosts(userId, collaboratorId);
7647
+ return (_l = rows.find((r) => r.id === created.id)) !== null && _l !== void 0 ? _l : null;
7648
+ }
7649
+ async updateCollaboratorCost(userId, collaboratorId, costId, data) {
7650
+ var _a, _b, _c, _d, _e, _f, _g, _h;
7651
+ const actor = await this.getActorContext(userId);
7652
+ this.ensureDirector(actor);
7653
+ const cost = await this.querySingle(`SELECT id, collaborator_id AS "collaboratorId" FROM operations_collaborator_cost WHERE id = $1 ${collaboratorId ? 'AND collaborator_id = $2' : ''} LIMIT 1`, collaboratorId ? [costId, collaboratorId] : [costId]);
7654
+ if (!cost) {
7655
+ throw new common_1.NotFoundException('Collaborator cost not found.');
7656
+ }
7657
+ const resolvedCollaboratorId = collaboratorId || cost.collaboratorId;
7658
+ const sets = [];
7659
+ const params = [];
7660
+ if (data.costTypeId !== undefined) {
7661
+ sets.push(`cost_type_id = ${this.param(params, data.costTypeId)}`);
7662
+ }
7663
+ if (data.amount !== undefined) {
7664
+ sets.push(`amount = ${this.param(params, data.amount)}`);
7665
+ }
7666
+ if (data.currency !== undefined) {
7667
+ sets.push(`currency = ${this.param(params, data.currency)}`);
7668
+ }
7669
+ if (data.recurrence !== undefined) {
7670
+ sets.push(`recurrence = ${this.param(params, data.recurrence)}::operations_collaborator_cost_recurrence_56fbe60170_enum`);
7671
+ }
7672
+ if (data.allocatable !== undefined) {
7673
+ sets.push(`allocatable = ${this.param(params, data.allocatable)}`);
7674
+ }
7675
+ if ('referenceDate' in data) {
7676
+ sets.push(`reference_date = ${this.param(params, (_a = data.referenceDate) !== null && _a !== void 0 ? _a : null)}::date`);
7677
+ }
7678
+ if ('startDate' in data) {
7679
+ sets.push(`start_date = ${this.param(params, (_b = data.startDate) !== null && _b !== void 0 ? _b : null)}::date`);
7680
+ }
7681
+ if ('endDate' in data) {
7682
+ sets.push(`end_date = ${this.param(params, (_c = data.endDate) !== null && _c !== void 0 ? _c : null)}::date`);
7683
+ }
7684
+ if ('depreciationMonths' in data) {
7685
+ sets.push(`depreciation_months = ${this.param(params, (_d = data.depreciationMonths) !== null && _d !== void 0 ? _d : null)}`);
7686
+ }
7687
+ if ('description' in data) {
7688
+ sets.push(`description = ${this.param(params, (_e = data.description) !== null && _e !== void 0 ? _e : null)}`);
7689
+ }
7690
+ if ('notes' in data) {
7691
+ sets.push(`notes = ${this.param(params, (_f = data.notes) !== null && _f !== void 0 ? _f : null)}`);
7692
+ }
7693
+ if (sets.length === 0) {
7694
+ const rows = await this.listCollaboratorCosts(userId, resolvedCollaboratorId);
7695
+ return (_g = rows.find((r) => r.id === costId)) !== null && _g !== void 0 ? _g : null;
7696
+ }
7697
+ sets.push(`updated_at = NOW()`);
7698
+ await this.prisma.$queryRawUnsafe(`UPDATE operations_collaborator_cost SET ${sets.join(', ')} WHERE id = ${this.param(params, costId)}`, ...params);
7699
+ const rows = await this.listCollaboratorCosts(userId, resolvedCollaboratorId);
7700
+ return (_h = rows.find((r) => r.id === costId)) !== null && _h !== void 0 ? _h : null;
7701
+ }
7702
+ async deleteCollaboratorCost(userId, collaboratorId, costId) {
7703
+ const actor = await this.getActorContext(userId);
7704
+ this.ensureDirector(actor);
7705
+ const cost = await this.querySingle(`SELECT id FROM operations_collaborator_cost WHERE id = $1 AND collaborator_id = $2 LIMIT 1`, [costId, collaboratorId]);
7706
+ if (!cost) {
7707
+ throw new common_1.NotFoundException('Collaborator cost not found.');
7708
+ }
7709
+ await this.prisma.$queryRawUnsafe(`DELETE FROM operations_collaborator_cost WHERE id = $1`, costId);
7710
+ return { success: true };
7711
+ }
6475
7712
  };
6476
7713
  exports.OperationsService = OperationsService;
6477
7714
  exports.OperationsService = OperationsService = OperationsService_1 = __decorate([