@hed-hog/operations 0.0.319 → 0.0.322

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 (38) hide show
  1. package/dist/controllers/operations-tasks.controller.d.ts +22 -0
  2. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-tasks.controller.js +37 -0
  4. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  5. package/dist/dto/create-task.dto.d.ts.map +1 -1
  6. package/dist/dto/create-task.dto.js +0 -1
  7. package/dist/dto/create-task.dto.js.map +1 -1
  8. package/dist/dto/update-task.dto.d.ts.map +1 -1
  9. package/dist/dto/update-task.dto.js +0 -1
  10. package/dist/dto/update-task.dto.js.map +1 -1
  11. package/dist/operations.service.d.ts +22 -0
  12. package/dist/operations.service.d.ts.map +1 -1
  13. package/dist/operations.service.js +187 -132
  14. package/dist/operations.service.js.map +1 -1
  15. package/hedhog/data/operations_cost_type.yaml +95 -95
  16. package/hedhog/data/route.yaml +39 -0
  17. package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -884
  18. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +23 -23
  19. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +49 -22
  20. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +2968 -624
  21. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +62 -68
  22. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +388 -0
  23. package/hedhog/frontend/app/_lib/types.ts.ejs +179 -178
  24. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +121 -11
  25. package/hedhog/frontend/app/projects/page.tsx.ejs +105 -22
  26. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -771
  27. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -809
  28. package/hedhog/frontend/messages/en.json +143 -2
  29. package/hedhog/frontend/messages/pt.json +143 -2
  30. package/hedhog/table/operations_task_file.yaml +23 -0
  31. package/package.json +5 -5
  32. package/src/controllers/operations-reports.controller.ts +32 -32
  33. package/src/controllers/operations-tasks.controller.ts +43 -9
  34. package/src/dto/create-task.dto.ts +0 -1
  35. package/src/dto/list-reports.dto.ts +51 -51
  36. package/src/dto/update-task.dto.ts +0 -1
  37. package/src/operations.module.ts +5 -5
  38. package/src/operations.service.ts +754 -632
@@ -1425,6 +1425,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1425
1425
  p.code,
1426
1426
  p.name,
1427
1427
  p.client_name AS "clientName",
1428
+ cp.avatar_id AS "clientAvatarId",
1428
1429
  p.summary,
1429
1430
  p.status,
1430
1431
  p.progress_percent AS "progressPercent",
@@ -1435,17 +1436,20 @@ let OperationsService = OperationsService_1 = class OperationsService {
1435
1436
  c.name AS "contractName",
1436
1437
  c.status AS "contractStatus",
1437
1438
  m.display_name AS "managerName",
1439
+ mp.avatar_id AS "managerAvatarId",
1438
1440
  ${ownAssignmentSelect}
1439
1441
  COUNT(DISTINCT pa.id)::int AS "teamSize"
1440
1442
  FROM operations_project p
1441
1443
  LEFT JOIN operations_contract c ON c.id = p.contract_id
1442
1444
  LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
1445
+ LEFT JOIN person cp ON cp.id = p.client_person_id
1446
+ LEFT JOIN person mp ON mp.id = m.person_id
1443
1447
  LEFT JOIN operations_project_assignment pa
1444
1448
  ON pa.project_id = p.id
1445
1449
  AND pa.deleted_at IS NULL
1446
1450
  AND pa.status IN ('planned', 'active')
1447
1451
  WHERE ${whereClause}
1448
- GROUP BY p.id, c.id, m.id`;
1452
+ GROUP BY p.id, c.id, m.id, cp.id, mp.id`;
1449
1453
  if (!pagination) {
1450
1454
  return this.queryRows(`${baseQuery} ORDER BY p.name ASC`, params);
1451
1455
  }
@@ -1645,25 +1649,31 @@ let OperationsService = OperationsService_1 = class OperationsService {
1645
1649
  }
1646
1650
  this.requireFields(data, ['name']);
1647
1651
  let assignmentId = null;
1648
- let projectId = null;
1652
+ let projectId = (_a = data.projectId) !== null && _a !== void 0 ? _a : null;
1649
1653
  if (data.projectId || data.projectAssignmentId) {
1650
- const assignment = await this.resolveProjectAssignmentForActor(this.prisma, actor, {
1651
- projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
1652
- projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
1653
- });
1654
- await this.assertProjectAccess(actor, assignment.projectId);
1655
- assignmentId = assignment.id;
1656
- projectId = assignment.projectId;
1657
- }
1658
- else if (data.projectId) {
1659
- projectId = data.projectId;
1660
- await this.assertProjectAccess(actor, projectId);
1661
- }
1662
- else {
1663
- throw new common_1.BadRequestException('Either projectId or projectAssignmentId is required.');
1664
- }
1665
- if (!projectId) {
1666
- projectId = (_c = data.projectId) !== null && _c !== void 0 ? _c : null;
1654
+ if (actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1655
+ const assignment = await this.resolveProjectAssignmentForActor(this.prisma, actor, {
1656
+ projectId: (_b = data.projectId) !== null && _b !== void 0 ? _b : null,
1657
+ projectAssignmentId: (_c = data.projectAssignmentId) !== null && _c !== void 0 ? _c : null,
1658
+ });
1659
+ await this.assertProjectAccess(actor, assignment.projectId);
1660
+ assignmentId = assignment.id;
1661
+ projectId = assignment.projectId;
1662
+ }
1663
+ else {
1664
+ if (data.projectId) {
1665
+ await this.assertProjectAccess(actor, data.projectId);
1666
+ projectId = data.projectId;
1667
+ }
1668
+ if (data.projectAssignmentId) {
1669
+ const assignment = await this.resolveProjectAssignmentForActor(this.prisma, actor, {
1670
+ projectId: (_d = data.projectId) !== null && _d !== void 0 ? _d : null,
1671
+ projectAssignmentId: data.projectAssignmentId,
1672
+ });
1673
+ assignmentId = assignment.id;
1674
+ projectId = assignment.projectId;
1675
+ }
1676
+ }
1667
1677
  }
1668
1678
  const name = this.normalizeOptionalText(data.name);
1669
1679
  if (!name) {
@@ -1673,8 +1683,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
1673
1683
  FROM operations_task
1674
1684
  WHERE project_id = $1
1675
1685
  AND status = $2::operations_task_status_574c143dbe_enum
1676
- AND deleted_at IS NULL`, [projectId, (_d = data.status) !== null && _d !== void 0 ? _d : 'todo']);
1677
- const nextPosition = ((_e = maxPositionRow === null || maxPositionRow === void 0 ? void 0 : maxPositionRow.max_pos) !== null && _e !== void 0 ? _e : -1) + 1;
1686
+ AND deleted_at IS NULL`, [projectId, (_e = data.status) !== null && _e !== void 0 ? _e : 'todo']);
1687
+ const nextPosition = ((_f = maxPositionRow === null || maxPositionRow === void 0 ? void 0 : maxPositionRow.max_pos) !== null && _f !== void 0 ? _f : -1) + 1;
1678
1688
  const created = await this.querySingle(`INSERT INTO operations_task (
1679
1689
  project_id,
1680
1690
  project_assignment_id,
@@ -1698,7 +1708,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
1698
1708
  RETURNING id`, [
1699
1709
  projectId,
1700
1710
  assignmentId,
1701
- (_g = (_f = data.assigneeCollaboratorId) !== null && _f !== void 0 ? _f : actor.collaboratorId) !== null && _g !== void 0 ? _g : null,
1711
+ (_g = data.assigneeCollaboratorId) !== null && _g !== void 0 ? _g : null,
1702
1712
  name,
1703
1713
  this.normalizeOptionalText(data.description),
1704
1714
  (_h = data.priority) !== null && _h !== void 0 ? _h : 'medium',
@@ -1789,6 +1799,51 @@ let OperationsService = OperationsService_1 = class OperationsService {
1789
1799
  });
1790
1800
  return { success: true };
1791
1801
  }
1802
+ async listTaskFiles(userId, taskId) {
1803
+ const actor = await this.getActorContext(userId);
1804
+ if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1805
+ throw new common_1.ForbiddenException('Operations collaborator access is required.');
1806
+ }
1807
+ const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
1808
+ await this.assertProjectAccess(actor, current.projectId);
1809
+ const rows = (await this.prisma.$queryRawUnsafe(`SELECT tf.id, tf.file_id, f.filename, f.size, m.name AS mimetype, tf.created_at
1810
+ FROM operations_task_file tf
1811
+ JOIN file f ON f.id = tf.file_id
1812
+ JOIN file_mimetype m ON m.id = f.mimetype_id
1813
+ WHERE tf.operations_task_id = $1
1814
+ ORDER BY tf.created_at ASC`, taskId));
1815
+ return rows;
1816
+ }
1817
+ async addTaskFile(userId, taskId, file) {
1818
+ const actor = await this.getActorContext(userId);
1819
+ if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1820
+ throw new common_1.ForbiddenException('Operations collaborator access is required.');
1821
+ }
1822
+ const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
1823
+ await this.assertProjectAccess(actor, current.projectId);
1824
+ const uploaded = await this.fileService.upload(`operations/tasks/${taskId}`, file);
1825
+ await this.prisma.$executeRawUnsafe(`INSERT INTO operations_task_file (operations_task_id, file_id, created_at, updated_at)
1826
+ VALUES ($1, $2, NOW(), NOW())`, taskId, uploaded.id);
1827
+ return uploaded;
1828
+ }
1829
+ async removeTaskFile(userId, taskId, fileRelationId) {
1830
+ const actor = await this.getActorContext(userId);
1831
+ if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
1832
+ throw new common_1.ForbiddenException('Operations collaborator access is required.');
1833
+ }
1834
+ const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
1835
+ await this.assertProjectAccess(actor, current.projectId);
1836
+ const rows = (await this.prisma.$queryRawUnsafe(`SELECT file_id FROM operations_task_file WHERE id = $1 AND operations_task_id = $2`, fileRelationId, taskId));
1837
+ if (!rows.length) {
1838
+ throw new common_1.NotFoundException('Task file attachment not found.');
1839
+ }
1840
+ const fileId = rows[0].file_id;
1841
+ await this.prisma.$executeRawUnsafe(`DELETE FROM operations_task_file WHERE id = $1`, fileRelationId);
1842
+ if (fileId) {
1843
+ await this.fileService.delete('en', { ids: [fileId] });
1844
+ }
1845
+ return { success: true };
1846
+ }
1792
1847
  async listTimesheetEntries(userId, paginationParams) {
1793
1848
  var _a, _b;
1794
1849
  const actor = await this.getActorContext(userId);
@@ -7127,57 +7182,57 @@ let OperationsService = OperationsService_1 = class OperationsService {
7127
7182
  if (filters.client && filters.client !== 'all') {
7128
7183
  where.push(`COALESCE(NULLIF(p.client_name, ''), NULLIF(contract_record.client_name, ''), '-') = ${this.param(params, filters.client)}`);
7129
7184
  }
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 ')}
7185
+ const dbRows = await this.queryRows(`SELECT p.id,
7186
+ p.name,
7187
+ COALESCE(NULLIF(p.client_name, ''), NULLIF(contract_record.client_name, ''), '-') AS client,
7188
+ COALESCE(NULLIF(manager_record.display_name, ''), '-') AS manager,
7189
+ COALESCE(NULLIF(p.delivery_model::text, ''), '-') AS squad,
7190
+ p.status::text AS status,
7191
+ COALESCE(contract_record.billing_model::text, p.delivery_model::text, 'fixed_price') AS "contractType",
7192
+ TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
7193
+ TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
7194
+ COALESCE(p.budget_amount, contract_record.budget_amount, 0)::text AS "contractedRevenue",
7195
+ COALESCE(p.progress_percent, 0)::text AS "progressPercent",
7196
+ COALESCE(assignment_stats.weekly_hours, 0)::text AS "weeklyHours",
7197
+ COALESCE(time_stats.actual_hours, 0)::text AS "actualHours",
7198
+ COALESCE(time_stats.billable_hours, 0)::text AS "billableHours",
7199
+ COALESCE(task_stats.open_tasks, 0)::text AS "openTasks",
7200
+ COALESCE(task_stats.backlog_hours, 0)::text AS "backlogHours",
7201
+ COALESCE(task_stats.future_deliveries, 0)::text AS "futureDeliveries"
7202
+ FROM operations_project p
7203
+ LEFT JOIN operations_contract contract_record
7204
+ ON contract_record.id = p.contract_id
7205
+ AND contract_record.deleted_at IS NULL
7206
+ LEFT JOIN operations_collaborator manager_record
7207
+ ON manager_record.id = p.manager_collaborator_id
7208
+ AND manager_record.deleted_at IS NULL
7209
+ LEFT JOIN LATERAL (
7210
+ SELECT COALESCE(SUM(pa.weekly_hours), 0) AS weekly_hours
7211
+ FROM operations_project_assignment pa
7212
+ WHERE pa.project_id = p.id
7213
+ AND pa.deleted_at IS NULL
7214
+ AND pa.status IN ('planned', 'active')
7215
+ ) assignment_stats ON TRUE
7216
+ LEFT JOIN LATERAL (
7217
+ SELECT COALESCE(SUM(entry.hours), 0) AS actual_hours,
7218
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true), 0) AS billable_hours
7219
+ FROM operations_timesheet_entry entry
7220
+ JOIN operations_project_assignment pa
7221
+ ON pa.id = entry.project_assignment_id
7222
+ AND pa.deleted_at IS NULL
7223
+ WHERE pa.project_id = p.id
7224
+ AND entry.deleted_at IS NULL
7225
+ AND entry.work_date BETWEEN $1::date AND $2::date
7226
+ ) time_stats ON TRUE
7227
+ LEFT JOIN LATERAL (
7228
+ SELECT COUNT(*) FILTER (WHERE task.status IN ('todo', 'doing', 'review')) AS open_tasks,
7229
+ COALESCE(SUM(task.estimate_hours) FILTER (WHERE task.status IN ('todo', 'doing', 'review')), 0) AS backlog_hours,
7230
+ COUNT(*) FILTER (WHERE task.due_date > $2::date AND task.status IN ('todo', 'doing', 'review')) AS future_deliveries
7231
+ FROM operations_task task
7232
+ WHERE task.project_id = p.id
7233
+ AND task.deleted_at IS NULL
7234
+ ) task_stats ON TRUE
7235
+ WHERE ${where.join(' AND ')}
7181
7236
  ORDER BY p.name ASC`, params);
7182
7237
  const fromDate = new Date(`${from}T00:00:00`);
7183
7238
  const toDate = new Date(`${to}T00:00:00`);
@@ -7382,65 +7437,65 @@ let OperationsService = OperationsService_1 = class OperationsService {
7382
7437
  if (filters.contractType && filters.contractType !== 'all') {
7383
7438
  where.push(`COALESCE(collaborator_type.name, collaborator_type.slug, '-') = ${this.param(params, filters.contractType)}`);
7384
7439
  }
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 ')}
7440
+ const dbRows = await this.queryRows(`SELECT c.id,
7441
+ COALESCE(NULLIF(c.display_name, ''), person_record.name, c.code) AS name,
7442
+ COALESCE(job_title_record.name, c.title, '-') AS role,
7443
+ COALESCE(c.level_label, '-') AS seniority,
7444
+ COALESCE(department_record.name, '-') AS department,
7445
+ COALESCE(collaborator_type.name, collaborator_type.slug, '-') AS "contractType",
7446
+ TO_CHAR(c.joined_at, 'YYYY-MM-DD') AS "startDate",
7447
+ TO_CHAR(c.left_at, 'YYYY-MM-DD') AS "endDate",
7448
+ COALESCE(c.weekly_capacity_hours, 40)::text AS "weeklyCapacityHours",
7449
+ COALESCE(cost_stats.salary_cost, 0)::text AS "salaryCost",
7450
+ COALESCE(cost_stats.benefits_cost, 0)::text AS "benefitsCost",
7451
+ COALESCE(cost_stats.taxes_cost, 0)::text AS "taxesCost",
7452
+ COALESCE(cost_stats.tools_cost, 0)::text AS "toolsCost",
7453
+ COALESCE(value_stats.billable_value, 0)::text AS "billableValue",
7454
+ COALESCE(value_stats.allocated_hours, 0)::text AS "allocatedHours",
7455
+ COALESCE(value_stats.billable_hours, 0)::text AS "billableHours",
7456
+ COALESCE(project_stats.projects, 0)::text AS projects
7457
+ FROM operations_collaborator c
7458
+ LEFT JOIN person person_record ON person_record.id = c.person_id
7459
+ LEFT JOIN operations_department department_record
7460
+ ON department_record.id = c.department_id
7461
+ AND department_record.deleted_at IS NULL
7462
+ LEFT JOIN operations_job_title job_title_record
7463
+ ON job_title_record.id = c.job_title_id
7464
+ AND job_title_record.deleted_at IS NULL
7465
+ LEFT JOIN operations_collaborator_type collaborator_type
7466
+ ON collaborator_type.id = c.collaborator_type_id
7467
+ AND collaborator_type.deleted_at IS NULL
7468
+ LEFT JOIN LATERAL (
7469
+ SELECT COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('salario-base', 'pro-labore')), 0) AS salary_cost,
7470
+ 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,
7471
+ 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,
7472
+ COALESCE(SUM(cost.amount) FILTER (WHERE cost.recurrence::text = 'monthly' AND cost_type.slug IN ('software-licenca', 'equipamento')), 0) AS tools_cost
7473
+ FROM operations_collaborator_cost cost
7474
+ LEFT JOIN operations_cost_type cost_type ON cost_type.id = cost.cost_type_id
7475
+ WHERE cost.collaborator_id = c.id
7476
+ AND (cost.start_date IS NULL OR cost.start_date <= $2::date)
7477
+ AND (cost.end_date IS NULL OR cost.end_date >= $1::date)
7478
+ ) cost_stats ON TRUE
7479
+ LEFT JOIN LATERAL (
7480
+ SELECT COALESCE(SUM(entry.hours), 0) AS allocated_hours,
7481
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true), 0) AS billable_hours,
7482
+ COALESCE(SUM(entry.hours) FILTER (WHERE pa.is_billable = true) * 120, 0) AS billable_value
7483
+ FROM operations_timesheet_entry entry
7484
+ JOIN operations_project_assignment pa
7485
+ ON pa.id = entry.project_assignment_id
7486
+ AND pa.deleted_at IS NULL
7487
+ WHERE pa.collaborator_id = c.id
7488
+ AND entry.deleted_at IS NULL
7489
+ AND entry.work_date BETWEEN $1::date AND $2::date
7490
+ ) value_stats ON TRUE
7491
+ LEFT JOIN LATERAL (
7492
+ SELECT COUNT(DISTINCT pa.project_id) AS projects
7493
+ FROM operations_project_assignment pa
7494
+ WHERE pa.collaborator_id = c.id
7495
+ AND pa.deleted_at IS NULL
7496
+ AND pa.status IN ('planned', 'active')
7497
+ ) project_stats ON TRUE
7498
+ WHERE ${where.join(' AND ')}
7444
7499
  ORDER BY name ASC`, params);
7445
7500
  const fromDate = new Date(`${from}T00:00:00`);
7446
7501
  const toDate = new Date(`${to}T00:00:00`);