@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.
- package/dist/controllers/operations-tasks.controller.d.ts +22 -0
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.js +37 -0
- package/dist/controllers/operations-tasks.controller.js.map +1 -1
- package/dist/dto/create-task.dto.d.ts.map +1 -1
- package/dist/dto/create-task.dto.js +0 -1
- package/dist/dto/create-task.dto.js.map +1 -1
- package/dist/dto/update-task.dto.d.ts.map +1 -1
- package/dist/dto/update-task.dto.js +0 -1
- package/dist/dto/update-task.dto.js.map +1 -1
- package/dist/operations.service.d.ts +22 -0
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +187 -132
- package/dist/operations.service.js.map +1 -1
- package/hedhog/data/operations_cost_type.yaml +95 -95
- package/hedhog/data/route.yaml +39 -0
- package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -884
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +23 -23
- package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +49 -22
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +2968 -624
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +62 -68
- package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +388 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +179 -178
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +121 -11
- package/hedhog/frontend/app/projects/page.tsx.ejs +105 -22
- package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -771
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -809
- package/hedhog/frontend/messages/en.json +143 -2
- package/hedhog/frontend/messages/pt.json +143 -2
- package/hedhog/table/operations_task_file.yaml +23 -0
- package/package.json +5 -5
- package/src/controllers/operations-reports.controller.ts +32 -32
- package/src/controllers/operations-tasks.controller.ts +43 -9
- package/src/dto/create-task.dto.ts +0 -1
- package/src/dto/list-reports.dto.ts +51 -51
- package/src/dto/update-task.dto.ts +0 -1
- package/src/operations.module.ts +5 -5
- 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
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
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, (
|
|
1677
|
-
const nextPosition = ((
|
|
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 =
|
|
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`);
|