@hed-hog/operations 0.0.304 → 0.0.306
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-projects.controller.d.ts +15 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.d.ts +41 -10
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.js +11 -0
- package/dist/controllers/operations-tasks.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +21 -0
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +12 -0
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/create-task.dto.d.ts +7 -1
- package/dist/dto/create-task.dto.d.ts.map +1 -1
- package/dist/dto/create-task.dto.js +38 -5
- package/dist/dto/create-task.dto.js.map +1 -1
- package/dist/dto/list-tasks.dto.d.ts +1 -1
- package/dist/dto/list-tasks.dto.d.ts.map +1 -1
- package/dist/dto/list-tasks.dto.js +2 -2
- package/dist/dto/list-tasks.dto.js.map +1 -1
- package/dist/dto/update-collaborator-type.dto.d.ts +3 -1
- package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -1
- package/dist/dto/update-collaborator-type.dto.js +2 -1
- package/dist/dto/update-collaborator-type.dto.js.map +1 -1
- package/dist/dto/update-task.dto.d.ts +7 -1
- package/dist/dto/update-task.dto.d.ts.map +1 -1
- package/dist/dto/update-task.dto.js +38 -5
- package/dist/dto/update-task.dto.js.map +1 -1
- package/dist/operations.service.d.ts +90 -12
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +560 -148
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +73 -0
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +26 -26
- package/hedhog/data/operations_collaborator_type.yaml +76 -76
- package/hedhog/data/route.yaml +26 -0
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +5 -3
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +44 -44
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +168 -213
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +528 -403
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
- package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +1 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +5 -0
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +7 -7
- package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
- package/hedhog/frontend/app/approvals/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +513 -502
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +10 -7
- package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
- package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/page.tsx.ejs +360 -133
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +235 -72
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +344 -134
- package/hedhog/frontend/messages/en.json +32 -4
- package/hedhog/frontend/messages/pt.json +34 -6
- package/hedhog/table/operations_collaborator.yaml +18 -18
- package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -43
- package/hedhog/table/operations_collaborator_type.yaml +33 -33
- package/hedhog/table/operations_contract_document.yaml +33 -33
- package/hedhog/table/operations_project.yaml +9 -0
- package/hedhog/table/operations_task.yaml +43 -4
- package/package.json +6 -6
- package/src/controllers/operations-tasks.controller.ts +11 -0
- package/src/controllers/operations-timesheets.controller.ts +13 -0
- package/src/dto/create-collaborator-type.dto.ts +43 -43
- package/src/dto/create-collaborator.dto.ts +223 -223
- package/src/dto/create-task.dto.ts +47 -7
- package/src/dto/list-collaborator-types.dto.ts +15 -15
- package/src/dto/list-collaborators.dto.ts +30 -30
- package/src/dto/list-tasks.dto.ts +3 -3
- package/src/dto/update-collaborator-type.dto.ts +4 -3
- package/src/dto/update-collaborator.dto.ts +3 -3
- package/src/dto/update-task.dto.ts +47 -7
- package/src/operations.service.spec.ts +96 -0
- package/src/operations.service.ts +813 -135
|
@@ -107,7 +107,7 @@ const FINANCIAL_TERM_TYPE_VALUES = ['value', 'payment', 'revenue', 'fine', 'othe
|
|
|
107
107
|
const RECURRENCE_VALUES = ['one_time', 'monthly', 'quarterly', 'yearly', 'other'];
|
|
108
108
|
const REVISION_TYPE_VALUES = ['amendment', 'renewal', 'revision', 'addendum', 'other'];
|
|
109
109
|
const REVISION_STATUS_VALUES = ['draft', 'active', 'completed', 'cancelled'];
|
|
110
|
-
const TASK_STATUS_VALUES = ['
|
|
110
|
+
const TASK_STATUS_VALUES = ['todo', 'doing', 'review', 'done'];
|
|
111
111
|
let OperationsService = OperationsService_1 = class OperationsService {
|
|
112
112
|
constructor(prisma, aiService, integrationApi, fileService, settingService, accessService, localeService) {
|
|
113
113
|
this.prisma = prisma;
|
|
@@ -834,6 +834,20 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
834
834
|
if (data.equityParticipation !== undefined) {
|
|
835
835
|
await this.replaceCollaboratorEquityParticipation(tx, collaboratorId, data.equityParticipation);
|
|
836
836
|
}
|
|
837
|
+
if (data.compensationAmount !== undefined ||
|
|
838
|
+
data.contractDescription !== undefined ||
|
|
839
|
+
data.autoGenerateContractDraft !== undefined ||
|
|
840
|
+
data.joinedAt !== undefined ||
|
|
841
|
+
data.weeklyCapacityHours !== undefined ||
|
|
842
|
+
data.supervisorCollaboratorId !== undefined ||
|
|
843
|
+
data.collaboratorType !== undefined ||
|
|
844
|
+
data.collaboratorTypeId !== undefined ||
|
|
845
|
+
data.collaboratorTypeSlug !== undefined ||
|
|
846
|
+
data.code !== undefined ||
|
|
847
|
+
data.personId !== undefined ||
|
|
848
|
+
data.displayName !== undefined) {
|
|
849
|
+
await this.syncHiringContractDraft(tx, actor.userId, collaboratorId, data);
|
|
850
|
+
}
|
|
837
851
|
});
|
|
838
852
|
return this.getCollaboratorByIdForUser(userId, collaboratorId);
|
|
839
853
|
}
|
|
@@ -1021,8 +1035,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1021
1035
|
p.progress_percent AS "progressPercent",
|
|
1022
1036
|
p.delivery_model AS "deliveryModel",
|
|
1023
1037
|
p.budget_amount AS "budgetAmount",
|
|
1024
|
-
p.start_date AS "startDate",
|
|
1025
|
-
p.end_date AS "endDate",
|
|
1038
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
1039
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
1026
1040
|
c.name AS "contractName",
|
|
1027
1041
|
c.status AS "contractStatus",
|
|
1028
1042
|
m.display_name AS "managerName",
|
|
@@ -1043,20 +1057,18 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1043
1057
|
var _a, _b;
|
|
1044
1058
|
const actor = await this.getActorContext(userId);
|
|
1045
1059
|
this.ensureCollaborator(actor);
|
|
1046
|
-
if (!actor.collaboratorId) {
|
|
1047
|
-
throw new common_1.BadRequestException('Collaborator context is required.');
|
|
1048
|
-
}
|
|
1049
1060
|
const pagination = this.normalizePaginationParams(paginationParams, {
|
|
1050
1061
|
defaultSortField: 'name',
|
|
1051
1062
|
defaultSortOrder: 'asc',
|
|
1052
1063
|
allowedSortFields: ['name', 'code', 'clientName', 'startDate', 'endDate'],
|
|
1053
1064
|
});
|
|
1054
|
-
const
|
|
1065
|
+
const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
|
|
1066
|
+
const params = [...filter.params];
|
|
1055
1067
|
const filters = [
|
|
1056
1068
|
'p.deleted_at IS NULL',
|
|
1057
1069
|
'pa.deleted_at IS NULL',
|
|
1058
|
-
`pa.collaborator_id = $1`,
|
|
1059
1070
|
`pa.status IN ('planned', 'active')`,
|
|
1071
|
+
filter.clause,
|
|
1060
1072
|
];
|
|
1061
1073
|
if (pagination.search) {
|
|
1062
1074
|
const searchPlaceholder = this.param(params, `%${pagination.search}%`);
|
|
@@ -1090,8 +1102,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1090
1102
|
MAX(pa.id)::int AS "projectAssignmentId",
|
|
1091
1103
|
MAX(pa.role_label) AS "roleLabel",
|
|
1092
1104
|
p.status,
|
|
1093
|
-
p.start_date AS "startDate",
|
|
1094
|
-
p.end_date AS "endDate"
|
|
1105
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
1106
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate"
|
|
1095
1107
|
FROM operations_project_assignment pa
|
|
1096
1108
|
JOIN operations_project p
|
|
1097
1109
|
ON p.id = pa.project_id
|
|
@@ -1106,21 +1118,25 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1106
1118
|
var _a, _b;
|
|
1107
1119
|
const actor = await this.getActorContext(userId);
|
|
1108
1120
|
this.ensureCollaborator(actor);
|
|
1109
|
-
if (!actor.collaboratorId) {
|
|
1110
|
-
throw new common_1.BadRequestException('Collaborator context is required.');
|
|
1111
|
-
}
|
|
1112
1121
|
const pagination = this.normalizePaginationParams(paginationParams, {
|
|
1113
1122
|
defaultSortField: 'name',
|
|
1114
1123
|
defaultSortOrder: 'asc',
|
|
1115
1124
|
allowedSortFields: ['name', 'projectName', 'status', 'createdAt'],
|
|
1116
1125
|
});
|
|
1117
|
-
const
|
|
1126
|
+
const projectFilter = this.buildIdFilter(actor.visibleProjectIds, 'COALESCE(t.project_id, pa.project_id)', actor.isDirector);
|
|
1127
|
+
const params = [...projectFilter.params];
|
|
1118
1128
|
const filters = [
|
|
1119
1129
|
't.deleted_at IS NULL',
|
|
1120
|
-
'pa.deleted_at IS NULL',
|
|
1121
1130
|
'p.deleted_at IS NULL',
|
|
1122
|
-
|
|
1123
|
-
`
|
|
1131
|
+
projectFilter.clause,
|
|
1132
|
+
`(
|
|
1133
|
+
t.project_id IS NOT NULL
|
|
1134
|
+
OR (
|
|
1135
|
+
pa.id IS NOT NULL
|
|
1136
|
+
AND pa.deleted_at IS NULL
|
|
1137
|
+
AND pa.status IN ('planned', 'active')
|
|
1138
|
+
)
|
|
1139
|
+
)`,
|
|
1124
1140
|
];
|
|
1125
1141
|
if (pagination.search) {
|
|
1126
1142
|
const searchPlaceholder = this.param(params, `%${pagination.search}%`);
|
|
@@ -1135,7 +1151,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1135
1151
|
filters.push(`pa.id = ${this.param(params, paginationParams.projectAssignmentId)}`);
|
|
1136
1152
|
}
|
|
1137
1153
|
if (paginationParams.projectId) {
|
|
1138
|
-
filters.push(`pa.project_id = ${this.param(params, paginationParams.projectId)}`);
|
|
1154
|
+
filters.push(`COALESCE(t.project_id, pa.project_id) = ${this.param(params, paginationParams.projectId)}`);
|
|
1139
1155
|
}
|
|
1140
1156
|
if (paginationParams.status) {
|
|
1141
1157
|
filters.push(`t.status = ${this.param(params, paginationParams.status)}`);
|
|
@@ -1143,10 +1159,10 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1143
1159
|
const whereClause = filters.join(' AND ');
|
|
1144
1160
|
const totalRow = await this.querySingle(`SELECT COUNT(*)::text AS total
|
|
1145
1161
|
FROM operations_task t
|
|
1146
|
-
JOIN operations_project_assignment pa
|
|
1162
|
+
LEFT JOIN operations_project_assignment pa
|
|
1147
1163
|
ON pa.id = t.project_assignment_id
|
|
1148
1164
|
JOIN operations_project p
|
|
1149
|
-
ON p.id = pa.project_id
|
|
1165
|
+
ON p.id = COALESCE(t.project_id, pa.project_id)
|
|
1150
1166
|
WHERE ${whereClause}`, params);
|
|
1151
1167
|
const sortColumn = (_a = {
|
|
1152
1168
|
name: 't.name',
|
|
@@ -1161,16 +1177,16 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1161
1177
|
t.name,
|
|
1162
1178
|
t.description,
|
|
1163
1179
|
t.status,
|
|
1164
|
-
pa.project_id AS "projectId",
|
|
1180
|
+
COALESCE(t.project_id, pa.project_id) AS "projectId",
|
|
1165
1181
|
pa.id AS "projectAssignmentId",
|
|
1166
1182
|
p.name AS "projectName",
|
|
1167
1183
|
p.code AS "projectCode",
|
|
1168
1184
|
t.created_at AS "createdAt"
|
|
1169
1185
|
FROM operations_task t
|
|
1170
|
-
JOIN operations_project_assignment pa
|
|
1186
|
+
LEFT JOIN operations_project_assignment pa
|
|
1171
1187
|
ON pa.id = t.project_assignment_id
|
|
1172
1188
|
JOIN operations_project p
|
|
1173
|
-
ON p.id = pa.project_id
|
|
1189
|
+
ON p.id = COALESCE(t.project_id, pa.project_id)
|
|
1174
1190
|
WHERE ${whereClause}
|
|
1175
1191
|
ORDER BY ${sortColumn} ${pagination.sortOrder.toUpperCase()}, t.id ASC
|
|
1176
1192
|
LIMIT ${limitPlaceholder}
|
|
@@ -1178,52 +1194,84 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1178
1194
|
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);
|
|
1179
1195
|
}
|
|
1180
1196
|
async createTask(userId, data) {
|
|
1181
|
-
var _a, _b, _c, _d;
|
|
1197
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1182
1198
|
const actor = await this.getActorContext(userId);
|
|
1183
|
-
|
|
1199
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
1200
|
+
throw new common_1.ForbiddenException('Operations collaborator access is required.');
|
|
1201
|
+
}
|
|
1184
1202
|
this.requireFields(data, ['name']);
|
|
1185
|
-
|
|
1186
|
-
|
|
1203
|
+
let assignmentId = null;
|
|
1204
|
+
let projectId = null;
|
|
1205
|
+
if (data.projectId || data.projectAssignmentId) {
|
|
1206
|
+
const assignment = await this.resolveProjectAssignmentForActor(this.prisma, actor, {
|
|
1207
|
+
projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
|
|
1208
|
+
projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
|
|
1209
|
+
});
|
|
1210
|
+
await this.assertProjectAccess(actor, assignment.projectId);
|
|
1211
|
+
assignmentId = assignment.id;
|
|
1212
|
+
projectId = assignment.projectId;
|
|
1213
|
+
}
|
|
1214
|
+
else if (data.projectId) {
|
|
1215
|
+
projectId = data.projectId;
|
|
1216
|
+
await this.assertProjectAccess(actor, projectId);
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
throw new common_1.BadRequestException('Either projectId or projectAssignmentId is required.');
|
|
1220
|
+
}
|
|
1221
|
+
if (!projectId) {
|
|
1222
|
+
projectId = (_c = data.projectId) !== null && _c !== void 0 ? _c : null;
|
|
1187
1223
|
}
|
|
1188
|
-
const assignment = await this.resolveOwnedProjectAssignment(this.prisma, actor.collaboratorId, {
|
|
1189
|
-
projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
|
|
1190
|
-
projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
|
|
1191
|
-
});
|
|
1192
|
-
await this.assertProjectAccess(actor, assignment.projectId);
|
|
1193
1224
|
const name = this.normalizeOptionalText(data.name);
|
|
1194
1225
|
if (!name) {
|
|
1195
1226
|
throw new common_1.BadRequestException('Field "name" is required.');
|
|
1196
1227
|
}
|
|
1228
|
+
const maxPositionRow = await this.querySingle(`SELECT MAX(position) AS max_pos
|
|
1229
|
+
FROM operations_task
|
|
1230
|
+
WHERE project_id = $1
|
|
1231
|
+
AND status = $2::operations_task_status_574c143dbe_enum
|
|
1232
|
+
AND deleted_at IS NULL`, [projectId, (_d = data.status) !== null && _d !== void 0 ? _d : 'todo']);
|
|
1233
|
+
const nextPosition = ((_e = maxPositionRow === null || maxPositionRow === void 0 ? void 0 : maxPositionRow.max_pos) !== null && _e !== void 0 ? _e : -1) + 1;
|
|
1197
1234
|
const created = await this.querySingle(`INSERT INTO operations_task (
|
|
1235
|
+
project_id,
|
|
1198
1236
|
project_assignment_id,
|
|
1237
|
+
assignee_collaborator_id,
|
|
1199
1238
|
name,
|
|
1200
1239
|
description,
|
|
1240
|
+
priority,
|
|
1201
1241
|
status,
|
|
1242
|
+
due_date,
|
|
1243
|
+
estimate_hours,
|
|
1244
|
+
position,
|
|
1245
|
+
tags,
|
|
1202
1246
|
created_at,
|
|
1203
1247
|
updated_at
|
|
1204
1248
|
) VALUES (
|
|
1205
|
-
$1,
|
|
1206
|
-
$
|
|
1207
|
-
$
|
|
1208
|
-
$
|
|
1209
|
-
NOW(),
|
|
1210
|
-
NOW()
|
|
1249
|
+
$1, $2, $3, $4, $5,
|
|
1250
|
+
$6::operations_task_priority_394ab327eb_enum,
|
|
1251
|
+
$7::operations_task_status_574c143dbe_enum,
|
|
1252
|
+
$8::date, $9::decimal, $10, $11, NOW(), NOW()
|
|
1211
1253
|
)
|
|
1212
1254
|
RETURNING id`, [
|
|
1213
|
-
|
|
1255
|
+
projectId,
|
|
1256
|
+
assignmentId,
|
|
1257
|
+
(_f = data.assigneeCollaboratorId) !== null && _f !== void 0 ? _f : null,
|
|
1214
1258
|
name,
|
|
1215
1259
|
this.normalizeOptionalText(data.description),
|
|
1216
|
-
(
|
|
1260
|
+
(_g = data.priority) !== null && _g !== void 0 ? _g : 'medium',
|
|
1261
|
+
(_h = data.status) !== null && _h !== void 0 ? _h : 'todo',
|
|
1262
|
+
(_j = data.dueDate) !== null && _j !== void 0 ? _j : null,
|
|
1263
|
+
(_k = data.estimateHours) !== null && _k !== void 0 ? _k : null,
|
|
1264
|
+
(_l = data.position) !== null && _l !== void 0 ? _l : nextPosition,
|
|
1265
|
+
(_m = data.tags) !== null && _m !== void 0 ? _m : null,
|
|
1217
1266
|
]);
|
|
1218
|
-
return this.
|
|
1267
|
+
return this.getProjectBoardTask((_o = created === null || created === void 0 ? void 0 : created.id) !== null && _o !== void 0 ? _o : 0);
|
|
1219
1268
|
}
|
|
1220
1269
|
async updateTask(userId, taskId, data) {
|
|
1221
1270
|
const actor = await this.getActorContext(userId);
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
throw new common_1.BadRequestException('Collaborator context is required.');
|
|
1271
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
1272
|
+
throw new common_1.ForbiddenException('Operations collaborator access is required.');
|
|
1225
1273
|
}
|
|
1226
|
-
const current = await this.
|
|
1274
|
+
const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
|
|
1227
1275
|
await this.assertProjectAccess(actor, current.projectId);
|
|
1228
1276
|
const nextName = data.name !== undefined
|
|
1229
1277
|
? this.normalizeOptionalText(data.name)
|
|
@@ -1232,42 +1280,50 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1232
1280
|
throw new common_1.BadRequestException('Field "name" is required.');
|
|
1233
1281
|
}
|
|
1234
1282
|
await this.prisma.$transaction(async (tx) => {
|
|
1235
|
-
var _a, _b, _c, _d;
|
|
1236
|
-
|
|
1237
|
-
|
|
1283
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
1284
|
+
let nextAssignmentId = current.projectAssignmentId;
|
|
1285
|
+
let nextProjectId = current.projectId;
|
|
1286
|
+
if (data.projectId !== undefined || data.projectAssignmentId !== undefined) {
|
|
1287
|
+
const nextAssignment = await this.resolveProjectAssignmentForActor(tx, actor, {
|
|
1238
1288
|
projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
|
|
1239
1289
|
projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
|
|
1240
|
-
})
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
await this.assertProjectAccess(actor, nextAssignment.projectId);
|
|
1290
|
+
});
|
|
1291
|
+
await this.assertProjectAccess(actor, nextAssignment.projectId);
|
|
1292
|
+
nextAssignmentId = nextAssignment.id;
|
|
1293
|
+
nextProjectId = nextAssignment.projectId;
|
|
1294
|
+
}
|
|
1246
1295
|
await tx.$executeRawUnsafe(`UPDATE operations_task
|
|
1247
|
-
SET
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1296
|
+
SET project_id = $1,
|
|
1297
|
+
project_assignment_id = $2,
|
|
1298
|
+
assignee_collaborator_id = $3,
|
|
1299
|
+
name = $4,
|
|
1300
|
+
description = $5,
|
|
1301
|
+
priority = $6::operations_task_priority_394ab327eb_enum,
|
|
1302
|
+
status = $7::operations_task_status_574c143dbe_enum,
|
|
1303
|
+
due_date = $8::date,
|
|
1304
|
+
estimate_hours = $9::decimal,
|
|
1305
|
+
position = $10,
|
|
1306
|
+
tags = $11,
|
|
1251
1307
|
updated_at = NOW()
|
|
1252
|
-
WHERE id = $
|
|
1253
|
-
AND deleted_at IS NULL`,
|
|
1308
|
+
WHERE id = $12
|
|
1309
|
+
AND deleted_at IS NULL`, nextProjectId, nextAssignmentId, data.assigneeCollaboratorId !== undefined
|
|
1310
|
+
? ((_c = data.assigneeCollaboratorId) !== null && _c !== void 0 ? _c : null)
|
|
1311
|
+
: current.assigneeCollaboratorId, nextName, data.description !== undefined
|
|
1254
1312
|
? this.normalizeOptionalText(data.description)
|
|
1255
|
-
: ((
|
|
1313
|
+
: ((_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);
|
|
1256
1314
|
});
|
|
1257
|
-
return this.
|
|
1315
|
+
return this.getProjectBoardTask(taskId);
|
|
1258
1316
|
}
|
|
1259
1317
|
async removeTask(userId, taskId) {
|
|
1260
1318
|
const actor = await this.getActorContext(userId);
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
throw new common_1.BadRequestException('Collaborator context is required.');
|
|
1319
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
1320
|
+
throw new common_1.ForbiddenException('Operations collaborator access is required.');
|
|
1264
1321
|
}
|
|
1265
|
-
const current = await this.
|
|
1322
|
+
const current = await this.getTaskRecordForActor(this.prisma, actor, taskId);
|
|
1266
1323
|
await this.assertProjectAccess(actor, current.projectId);
|
|
1267
1324
|
await this.prisma.$transaction(async (tx) => {
|
|
1268
1325
|
await tx.$executeRawUnsafe(`UPDATE operations_task
|
|
1269
1326
|
SET deleted_at = COALESCE(deleted_at, NOW()),
|
|
1270
|
-
status = 'archived',
|
|
1271
1327
|
updated_at = NOW()
|
|
1272
1328
|
WHERE id = $1
|
|
1273
1329
|
AND deleted_at IS NULL`, taskId);
|
|
@@ -1409,8 +1465,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1409
1465
|
const resolvedTask = data.taskId
|
|
1410
1466
|
? await this.getOwnedTaskRecord(tx, actor.collaboratorId, data.taskId)
|
|
1411
1467
|
: null;
|
|
1412
|
-
if (resolvedTask && resolvedTask.
|
|
1413
|
-
throw new common_1.BadRequestException('The selected task does not belong to the chosen project
|
|
1468
|
+
if (resolvedTask && resolvedTask.projectId !== assignment.projectId) {
|
|
1469
|
+
throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
|
|
1414
1470
|
}
|
|
1415
1471
|
const activityLabel = (_e = (_d = (_c = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _c !== void 0 ? _c : taskLabel) !== null && _d !== void 0 ? _d : assignment.roleLabel) !== null && _e !== void 0 ? _e : assignment.projectName;
|
|
1416
1472
|
if (!activityLabel) {
|
|
@@ -1446,6 +1502,68 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1446
1502
|
});
|
|
1447
1503
|
return this.getTimesheetEntryByIdForActor(actor, createdEntryId);
|
|
1448
1504
|
}
|
|
1505
|
+
async updateTimesheetEntry(userId, entryId, data) {
|
|
1506
|
+
var _a;
|
|
1507
|
+
const actor = await this.getActorContext(userId);
|
|
1508
|
+
this.ensureCollaborator(actor);
|
|
1509
|
+
this.requireFields(data, ['workDate', 'duration']);
|
|
1510
|
+
if (!actor.collaboratorId && !actor.isDirector) {
|
|
1511
|
+
throw new common_1.BadRequestException('Collaborator context is required.');
|
|
1512
|
+
}
|
|
1513
|
+
const entry = await this.getTimesheetEntryByIdForActor(actor, entryId);
|
|
1514
|
+
if (!actor.isDirector && entry.collaboratorId !== actor.collaboratorId) {
|
|
1515
|
+
throw new common_1.ForbiddenException('Only the entry owner can update this timesheet entry.');
|
|
1516
|
+
}
|
|
1517
|
+
if (!['draft', 'rejected'].includes(entry.status)) {
|
|
1518
|
+
throw new common_1.BadRequestException('Only draft or rejected timesheet entries can be edited.');
|
|
1519
|
+
}
|
|
1520
|
+
const collaboratorId = actor.isDirector
|
|
1521
|
+
? entry.collaboratorId
|
|
1522
|
+
: actor.collaboratorId;
|
|
1523
|
+
const durationMinutes = this.normalizeDurationMinutes(data.duration, data.unit);
|
|
1524
|
+
const taskLabel = (_a = this.normalizeOptionalText(data.taskName)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(data.activityLabel);
|
|
1525
|
+
const targetWeek = this.getWorkWeekRange(data.workDate);
|
|
1526
|
+
const isSameWeek = entry.weekStartDate === targetWeek.weekStartDate &&
|
|
1527
|
+
entry.weekEndDate === targetWeek.weekEndDate;
|
|
1528
|
+
await this.prisma.$transaction(async (tx) => {
|
|
1529
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1530
|
+
const assignment = await this.resolveOwnedProjectAssignment(tx, collaboratorId, {
|
|
1531
|
+
projectId: (_a = data.projectId) !== null && _a !== void 0 ? _a : null,
|
|
1532
|
+
projectAssignmentId: (_b = data.projectAssignmentId) !== null && _b !== void 0 ? _b : null,
|
|
1533
|
+
});
|
|
1534
|
+
const resolvedTask = data.taskId
|
|
1535
|
+
? await this.getOwnedTaskRecord(tx, collaboratorId, data.taskId)
|
|
1536
|
+
: null;
|
|
1537
|
+
if (resolvedTask && resolvedTask.projectId !== assignment.projectId) {
|
|
1538
|
+
throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
|
|
1539
|
+
}
|
|
1540
|
+
const activityLabel = (_e = (_d = (_c = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _c !== void 0 ? _c : taskLabel) !== null && _d !== void 0 ? _d : assignment.roleLabel) !== null && _e !== void 0 ? _e : assignment.projectName;
|
|
1541
|
+
if (!activityLabel) {
|
|
1542
|
+
throw new common_1.BadRequestException('A task is required for the timesheet entry.');
|
|
1543
|
+
}
|
|
1544
|
+
const targetTimesheetId = isSameWeek
|
|
1545
|
+
? entry.timesheetId
|
|
1546
|
+
: await this.getOrCreateTimesheetForWorkDate(tx, collaboratorId, data.workDate);
|
|
1547
|
+
await tx.$executeRawUnsafe(`UPDATE operations_timesheet_entry
|
|
1548
|
+
SET timesheet_id = $1,
|
|
1549
|
+
project_assignment_id = $2,
|
|
1550
|
+
task_id = $3,
|
|
1551
|
+
activity_label = $4,
|
|
1552
|
+
work_date = $5::date,
|
|
1553
|
+
duration_minutes = $6,
|
|
1554
|
+
hours = $7,
|
|
1555
|
+
description = $8,
|
|
1556
|
+
updated_at = NOW()
|
|
1557
|
+
WHERE id = $9
|
|
1558
|
+
AND deleted_at IS NULL`, targetTimesheetId, assignment.id, (_g = (_f = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.id) !== null && _f !== void 0 ? _f : data.taskId) !== null && _g !== void 0 ? _g : null, activityLabel, data.workDate, durationMinutes, Number((durationMinutes / 60).toFixed(2)), this.normalizeOptionalText(data.description), entryId);
|
|
1559
|
+
await this.refreshTimesheetTotal(tx, entry.timesheetId);
|
|
1560
|
+
if (targetTimesheetId !== entry.timesheetId) {
|
|
1561
|
+
await this.refreshTimesheetTotal(tx, targetTimesheetId);
|
|
1562
|
+
}
|
|
1563
|
+
await this.cleanupEmptyEditableTimesheet(tx, entry.timesheetId);
|
|
1564
|
+
});
|
|
1565
|
+
return this.getTimesheetEntryByIdForActor(actor, entryId);
|
|
1566
|
+
}
|
|
1449
1567
|
async removeTimesheetEntry(userId, entryId) {
|
|
1450
1568
|
const actor = await this.getActorContext(userId);
|
|
1451
1569
|
this.ensureCollaborator(actor);
|
|
@@ -1466,6 +1584,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1466
1584
|
WHERE id = $1
|
|
1467
1585
|
AND deleted_at IS NULL`, entryId);
|
|
1468
1586
|
await this.refreshTimesheetTotal(tx, entry.timesheetId);
|
|
1587
|
+
await this.cleanupEmptyEditableTimesheet(tx, entry.timesheetId);
|
|
1469
1588
|
});
|
|
1470
1589
|
return { success: true };
|
|
1471
1590
|
}
|
|
@@ -1479,10 +1598,11 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1479
1598
|
this.ensureDirector(actor);
|
|
1480
1599
|
this.requireFields(data, ['code', 'name']);
|
|
1481
1600
|
const createdProjectId = await this.prisma.$transaction(async (tx) => {
|
|
1482
|
-
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;
|
|
1601
|
+
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;
|
|
1483
1602
|
const created = await tx.$queryRawUnsafe(`INSERT INTO operations_project (
|
|
1484
1603
|
contract_id,
|
|
1485
1604
|
manager_collaborator_id,
|
|
1605
|
+
client_person_id,
|
|
1486
1606
|
code,
|
|
1487
1607
|
name,
|
|
1488
1608
|
client_name,
|
|
@@ -1496,33 +1616,33 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1496
1616
|
created_at,
|
|
1497
1617
|
updated_at
|
|
1498
1618
|
) VALUES (
|
|
1499
|
-
$1, $2, $3, $4, $5, $6,
|
|
1500
|
-
$
|
|
1501
|
-
$
|
|
1502
|
-
$
|
|
1503
|
-
$
|
|
1619
|
+
$1, $2, $3, $4, $5, $6, $7,
|
|
1620
|
+
$8::operations_project_status_965e8d4b2d_enum,
|
|
1621
|
+
$9,
|
|
1622
|
+
$10::operations_project_delivery_model_75ee11b3b7_enum,
|
|
1623
|
+
$11, $12::date, $13::date, NOW(), NOW()
|
|
1504
1624
|
)
|
|
1505
|
-
RETURNING id`, (_a = data.contractId) !== null && _a !== void 0 ? _a : null, (_b = data.managerCollaboratorId) !== null && _b !== void 0 ? _b : null, data.code, data.name, (
|
|
1506
|
-
const projectId = (
|
|
1507
|
-
if ((
|
|
1625
|
+
RETURNING id`, (_a = data.contractId) !== null && _a !== void 0 ? _a : null, (_b = data.managerCollaboratorId) !== null && _b !== void 0 ? _b : null, (_c = data.clientPersonId) !== null && _c !== void 0 ? _c : null, data.code, data.name, (_d = data.clientName) !== null && _d !== void 0 ? _d : null, (_e = data.summary) !== null && _e !== void 0 ? _e : null, (_f = data.status) !== null && _f !== void 0 ? _f : 'planning', (_g = data.progressPercent) !== null && _g !== void 0 ? _g : null, (_h = data.deliveryModel) !== null && _h !== void 0 ? _h : 'project_delivery', (_j = data.budgetAmount) !== null && _j !== void 0 ? _j : null, (_k = data.startDate) !== null && _k !== void 0 ? _k : null, (_l = data.endDate) !== null && _l !== void 0 ? _l : null);
|
|
1626
|
+
const projectId = (_m = created[0]) === null || _m === void 0 ? void 0 : _m.id;
|
|
1627
|
+
if ((_o = data.teamAssignments) === null || _o === void 0 ? void 0 : _o.length) {
|
|
1508
1628
|
await this.replaceProjectAssignments(tx, projectId, data.teamAssignments);
|
|
1509
1629
|
}
|
|
1510
1630
|
if (!data.contractId && data.autoGenerateContractDraft !== false) {
|
|
1511
1631
|
const contractId = await this.createProjectContractDraft(tx, actor.userId, {
|
|
1512
1632
|
projectId,
|
|
1513
|
-
contractTemplateId: (
|
|
1633
|
+
contractTemplateId: (_p = data.contractTemplateId) !== null && _p !== void 0 ? _p : null,
|
|
1514
1634
|
projectCode: data.code,
|
|
1515
1635
|
projectName: data.name,
|
|
1516
|
-
clientName: (
|
|
1517
|
-
managerCollaboratorId: (
|
|
1518
|
-
startDate: (
|
|
1519
|
-
endDate: (
|
|
1520
|
-
budgetAmount: (
|
|
1521
|
-
monthlyHourCap: (
|
|
1522
|
-
billingModel: (
|
|
1523
|
-
contractCode: (
|
|
1524
|
-
contractName: (
|
|
1525
|
-
description: (
|
|
1636
|
+
clientName: (_q = data.clientName) !== null && _q !== void 0 ? _q : data.name,
|
|
1637
|
+
managerCollaboratorId: (_r = data.managerCollaboratorId) !== null && _r !== void 0 ? _r : null,
|
|
1638
|
+
startDate: (_s = data.startDate) !== null && _s !== void 0 ? _s : null,
|
|
1639
|
+
endDate: (_t = data.endDate) !== null && _t !== void 0 ? _t : null,
|
|
1640
|
+
budgetAmount: (_u = data.budgetAmount) !== null && _u !== void 0 ? _u : null,
|
|
1641
|
+
monthlyHourCap: (_v = data.monthlyHourCap) !== null && _v !== void 0 ? _v : null,
|
|
1642
|
+
billingModel: (_w = data.billingModel) !== null && _w !== void 0 ? _w : 'time_and_material',
|
|
1643
|
+
contractCode: (_x = data.contractCode) !== null && _x !== void 0 ? _x : null,
|
|
1644
|
+
contractName: (_y = data.contractName) !== null && _y !== void 0 ? _y : null,
|
|
1645
|
+
description: (_0 = (_z = data.contractDescription) !== null && _z !== void 0 ? _z : data.summary) !== null && _0 !== void 0 ? _0 : null,
|
|
1526
1646
|
});
|
|
1527
1647
|
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
1528
1648
|
SET contract_id = $1,
|
|
@@ -1541,18 +1661,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1541
1661
|
const params = [];
|
|
1542
1662
|
this.pushUpdate(updates, params, 'contract_id', data.contractId);
|
|
1543
1663
|
this.pushUpdate(updates, params, 'manager_collaborator_id', data.managerCollaboratorId);
|
|
1664
|
+
this.pushUpdate(updates, params, 'client_person_id', data.clientPersonId);
|
|
1544
1665
|
this.pushUpdate(updates, params, 'code', data.code);
|
|
1545
1666
|
this.pushUpdate(updates, params, 'name', data.name);
|
|
1546
1667
|
this.pushUpdate(updates, params, 'client_name', data.clientName);
|
|
1547
1668
|
this.pushUpdate(updates, params, 'summary', data.summary);
|
|
1548
1669
|
this.pushUpdate(updates, params, 'status', data.status, 'operations_project_status_965e8d4b2d_enum');
|
|
1549
1670
|
this.pushUpdate(updates, params, 'progress_percent', data.progressPercent);
|
|
1550
|
-
this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel, 'operations_project_delivery_model_75ee11b3b7_enum');
|
|
1671
|
+
this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel === '' ? null : data.deliveryModel, 'operations_project_delivery_model_75ee11b3b7_enum');
|
|
1551
1672
|
this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
|
|
1552
1673
|
this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
|
|
1553
1674
|
this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
|
|
1554
1675
|
await this.prisma.$transaction(async (tx) => {
|
|
1555
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
|
|
1676
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7;
|
|
1556
1677
|
if (updates.length) {
|
|
1557
1678
|
params.push(projectId);
|
|
1558
1679
|
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
@@ -1565,30 +1686,46 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1565
1686
|
}
|
|
1566
1687
|
const nextContractId = data.contractId !== undefined
|
|
1567
1688
|
? data.contractId
|
|
1568
|
-
: ((_a = currentProject.
|
|
1689
|
+
: ((_b = (_a = currentProject.relatedContract) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
|
|
1569
1690
|
const shouldGenerateDraft = !nextContractId && data.autoGenerateContractDraft === true;
|
|
1570
1691
|
if (shouldGenerateDraft) {
|
|
1571
1692
|
const contractId = await this.createProjectContractDraft(tx, actor.userId, {
|
|
1572
1693
|
projectId,
|
|
1573
|
-
contractTemplateId: (
|
|
1574
|
-
projectCode: (
|
|
1575
|
-
projectName: (
|
|
1576
|
-
clientName: (
|
|
1577
|
-
managerCollaboratorId: (
|
|
1578
|
-
startDate: (
|
|
1579
|
-
endDate: (
|
|
1580
|
-
budgetAmount: (
|
|
1581
|
-
monthlyHourCap: (
|
|
1582
|
-
billingModel: (
|
|
1583
|
-
contractCode: (
|
|
1584
|
-
contractName: (
|
|
1585
|
-
description: (
|
|
1694
|
+
contractTemplateId: (_c = data.contractTemplateId) !== null && _c !== void 0 ? _c : null,
|
|
1695
|
+
projectCode: (_d = data.code) !== null && _d !== void 0 ? _d : currentProject.code,
|
|
1696
|
+
projectName: (_e = data.name) !== null && _e !== void 0 ? _e : currentProject.name,
|
|
1697
|
+
clientName: (_g = (_f = data.clientName) !== null && _f !== void 0 ? _f : currentProject.clientName) !== null && _g !== void 0 ? _g : currentProject.name,
|
|
1698
|
+
managerCollaboratorId: (_j = (_h = data.managerCollaboratorId) !== null && _h !== void 0 ? _h : currentProject.managerCollaboratorId) !== null && _j !== void 0 ? _j : null,
|
|
1699
|
+
startDate: (_l = (_k = data.startDate) !== null && _k !== void 0 ? _k : currentProject.startDate) !== null && _l !== void 0 ? _l : null,
|
|
1700
|
+
endDate: (_o = (_m = data.endDate) !== null && _m !== void 0 ? _m : currentProject.endDate) !== null && _o !== void 0 ? _o : null,
|
|
1701
|
+
budgetAmount: (_q = (_p = data.budgetAmount) !== null && _p !== void 0 ? _p : currentProject.budgetAmount) !== null && _q !== void 0 ? _q : null,
|
|
1702
|
+
monthlyHourCap: (_t = (_r = data.monthlyHourCap) !== null && _r !== void 0 ? _r : (_s = currentProject.relatedContract) === null || _s === void 0 ? void 0 : _s.monthlyHourCap) !== null && _t !== void 0 ? _t : null,
|
|
1703
|
+
billingModel: (_w = (_u = data.billingModel) !== null && _u !== void 0 ? _u : (_v = currentProject.relatedContract) === null || _v === void 0 ? void 0 : _v.billingModel) !== null && _w !== void 0 ? _w : 'time_and_material',
|
|
1704
|
+
contractCode: (_z = (_x = data.contractCode) !== null && _x !== void 0 ? _x : (_y = currentProject.relatedContract) === null || _y === void 0 ? void 0 : _y.code) !== null && _z !== void 0 ? _z : null,
|
|
1705
|
+
contractName: (_2 = (_0 = data.contractName) !== null && _0 !== void 0 ? _0 : (_1 = currentProject.relatedContract) === null || _1 === void 0 ? void 0 : _1.name) !== null && _2 !== void 0 ? _2 : null,
|
|
1706
|
+
description: (_7 = (_6 = (_5 = (_3 = data.contractDescription) !== null && _3 !== void 0 ? _3 : (_4 = currentProject.relatedContract) === null || _4 === void 0 ? void 0 : _4.description) !== null && _5 !== void 0 ? _5 : data.summary) !== null && _6 !== void 0 ? _6 : currentProject.summary) !== null && _7 !== void 0 ? _7 : null,
|
|
1586
1707
|
});
|
|
1587
1708
|
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
1588
1709
|
SET contract_id = $1,
|
|
1589
1710
|
updated_at = NOW()
|
|
1590
1711
|
WHERE id = $2`, contractId, projectId);
|
|
1591
1712
|
}
|
|
1713
|
+
else if (nextContractId &&
|
|
1714
|
+
(data.monthlyHourCap !== undefined || data.billingModel !== undefined)) {
|
|
1715
|
+
const contractUpdates = [];
|
|
1716
|
+
const contractParams = [];
|
|
1717
|
+
this.pushUpdate(contractUpdates, contractParams, 'monthly_hour_cap', data.monthlyHourCap);
|
|
1718
|
+
this.pushUpdate(contractUpdates, contractParams, 'billing_model', data.billingModel === ''
|
|
1719
|
+
? null
|
|
1720
|
+
: data.billingModel, 'operations_contract_billing_model_409dc7fea2_enum');
|
|
1721
|
+
if (contractUpdates.length) {
|
|
1722
|
+
contractParams.push(nextContractId);
|
|
1723
|
+
await tx.$executeRawUnsafe(`UPDATE operations_contract
|
|
1724
|
+
SET ${contractUpdates.join(', ')},
|
|
1725
|
+
updated_at = NOW()
|
|
1726
|
+
WHERE id = $${contractParams.length}`, ...contractParams);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1592
1729
|
});
|
|
1593
1730
|
return this.getProjectById(userId, projectId);
|
|
1594
1731
|
}
|
|
@@ -1661,7 +1798,11 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1661
1798
|
updated_at
|
|
1662
1799
|
) VALUES (
|
|
1663
1800
|
$1, $2, $3, $4,
|
|
1664
|
-
$5,
|
|
1801
|
+
$5::text::operations_contract_template_contract_category_49bb07a713_enum,
|
|
1802
|
+
$6::text::operations_contract_template_contract_type_3962dbda6a_enum,
|
|
1803
|
+
$7::text::operations_contract_template_billing_model_384a7c60e2_enum,
|
|
1804
|
+
$8::text::operations_contract_template_signature_status_56cb6d625b_enum,
|
|
1805
|
+
$9, $10::text::operations_contract_template_status_c9d2e90231_enum, $11,
|
|
1665
1806
|
NOW(), NOW()
|
|
1666
1807
|
)
|
|
1667
1808
|
RETURNING id`, await this.generateUniqueContractTemplateSlug(tx, name), nextCode, name, this.normalizeOptionalText(data.description), (_e = data.contractCategory) !== null && _e !== void 0 ? _e : 'client', (_f = data.contractType) !== null && _f !== void 0 ? _f : 'service_agreement', (_g = data.billingModel) !== null && _g !== void 0 ? _g : 'time_and_material', (_h = data.signatureStatus) !== null && _h !== void 0 ? _h : 'not_started', isActive, nextStatus, this.normalizeOptionalText(data.contentHtml)));
|
|
@@ -1703,12 +1844,12 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
1703
1844
|
code = $2,
|
|
1704
1845
|
name = $3,
|
|
1705
1846
|
description = $4,
|
|
1706
|
-
contract_category = $5,
|
|
1707
|
-
contract_type = $6,
|
|
1708
|
-
billing_model = $7,
|
|
1709
|
-
signature_status = $8,
|
|
1847
|
+
contract_category = $5::text::operations_contract_template_contract_category_49bb07a713_enum,
|
|
1848
|
+
contract_type = $6::text::operations_contract_template_contract_type_3962dbda6a_enum,
|
|
1849
|
+
billing_model = $7::text::operations_contract_template_billing_model_384a7c60e2_enum,
|
|
1850
|
+
signature_status = $8::text::operations_contract_template_signature_status_56cb6d625b_enum,
|
|
1710
1851
|
is_active = $9,
|
|
1711
|
-
status = $10,
|
|
1852
|
+
status = $10::text::operations_contract_template_status_c9d2e90231_enum,
|
|
1712
1853
|
content_html = $11,
|
|
1713
1854
|
updated_at = NOW()
|
|
1714
1855
|
WHERE id = $12`, nextSlug, nextCode, nextName, data.description !== undefined
|
|
@@ -2954,7 +3095,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
2954
3095
|
return this.listSingleTimesheet(actor, timesheetId);
|
|
2955
3096
|
}
|
|
2956
3097
|
async submitTimesheet(userId, timesheetId) {
|
|
2957
|
-
var _a, _b;
|
|
3098
|
+
var _a, _b, _c;
|
|
2958
3099
|
const actor = await this.getActorContext(userId);
|
|
2959
3100
|
const current = await this.getTimesheetById(timesheetId);
|
|
2960
3101
|
if (!actor.isDirector && current.collaboratorId !== actor.collaboratorId) {
|
|
@@ -2964,7 +3105,15 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
2964
3105
|
throw new common_1.BadRequestException('Only draft or rejected timesheets can be submitted.');
|
|
2965
3106
|
}
|
|
2966
3107
|
const collaborator = await this.getCollaboratorById(current.collaboratorId);
|
|
2967
|
-
const
|
|
3108
|
+
const projectManagerRow = await this.querySingle(`SELECT p.manager_collaborator_id AS "managerCollaboratorId"
|
|
3109
|
+
FROM operations_timesheet_entry e
|
|
3110
|
+
LEFT JOIN operations_project_assignment pa ON pa.id = e.project_assignment_id
|
|
3111
|
+
LEFT JOIN operations_project p ON p.id = pa.project_id
|
|
3112
|
+
WHERE e.timesheet_id = $1
|
|
3113
|
+
AND e.deleted_at IS NULL
|
|
3114
|
+
AND p.manager_collaborator_id IS NOT NULL
|
|
3115
|
+
LIMIT 1`, [timesheetId]);
|
|
3116
|
+
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;
|
|
2968
3117
|
if (!approverId) {
|
|
2969
3118
|
throw new common_1.BadRequestException('An approver is required before submitting a timesheet.');
|
|
2970
3119
|
}
|
|
@@ -3120,7 +3269,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3120
3269
|
FROM operations_schedule_adjustment_day
|
|
3121
3270
|
WHERE schedule_adjustment_request_id = ANY($1::int[])
|
|
3122
3271
|
ORDER BY id ASC`, [requests.map((item) => item.id)]);
|
|
3123
|
-
const currentSchedule = await this.queryRows(`SELECT
|
|
3272
|
+
const currentSchedule = await this.queryRows(`SELECT DISTINCT ON (collaborator_id, weekday)
|
|
3273
|
+
collaborator_id AS "collaboratorId",
|
|
3124
3274
|
weekday,
|
|
3125
3275
|
is_working_day AS "isWorkingDay",
|
|
3126
3276
|
start_time AS "startTime",
|
|
@@ -3128,7 +3278,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3128
3278
|
break_minutes AS "breakMinutes"
|
|
3129
3279
|
FROM operations_collaborator_schedule_day
|
|
3130
3280
|
WHERE collaborator_id = ANY($1::int[])
|
|
3131
|
-
ORDER BY id
|
|
3281
|
+
ORDER BY collaborator_id, weekday, id DESC`, [this.uniqueNumbers(requests.map((item) => item.collaboratorId))]);
|
|
3132
3282
|
const grouped = this.groupBy(days, 'requestId');
|
|
3133
3283
|
const currentScheduleByCollaborator = this.groupBy(currentSchedule, 'collaboratorId');
|
|
3134
3284
|
return requests.map((request) => {
|
|
@@ -3452,17 +3602,44 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3452
3602
|
};
|
|
3453
3603
|
}
|
|
3454
3604
|
async getCollaboratorByUserId(userId) {
|
|
3455
|
-
return this.querySingle(`
|
|
3605
|
+
return this.querySingle(`WITH linked_collaborator AS (
|
|
3606
|
+
SELECT person_id
|
|
3607
|
+
FROM operations_collaborator
|
|
3608
|
+
WHERE user_id = $1
|
|
3609
|
+
AND deleted_at IS NULL
|
|
3610
|
+
ORDER BY id DESC
|
|
3611
|
+
LIMIT 1
|
|
3612
|
+
)
|
|
3613
|
+
SELECT c.id,
|
|
3614
|
+
c.user_id AS "userId",
|
|
3615
|
+
c.person_id AS "personId",
|
|
3456
3616
|
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
3457
3617
|
s.id AS "supervisorId",
|
|
3458
|
-
s.display_name AS "supervisorName"
|
|
3618
|
+
s.display_name AS "supervisorName",
|
|
3619
|
+
COUNT(pa.id) FILTER (
|
|
3620
|
+
WHERE pa.deleted_at IS NULL
|
|
3621
|
+
AND pa.status IN ('planned', 'active')
|
|
3622
|
+
)::int AS "activeAssignments"
|
|
3459
3623
|
FROM operations_collaborator c
|
|
3460
3624
|
LEFT JOIN person person_record
|
|
3461
3625
|
ON person_record.id = c.person_id
|
|
3462
3626
|
LEFT JOIN operations_collaborator s
|
|
3463
3627
|
ON s.id = c.supervisor_collaborator_id
|
|
3464
|
-
|
|
3465
|
-
|
|
3628
|
+
LEFT JOIN operations_project_assignment pa
|
|
3629
|
+
ON pa.collaborator_id = c.id
|
|
3630
|
+
WHERE c.deleted_at IS NULL
|
|
3631
|
+
AND (
|
|
3632
|
+
c.user_id = $1
|
|
3633
|
+
OR (
|
|
3634
|
+
c.person_id IS NOT NULL
|
|
3635
|
+
AND c.person_id = (SELECT person_id FROM linked_collaborator)
|
|
3636
|
+
)
|
|
3637
|
+
)
|
|
3638
|
+
GROUP BY c.id, person_record.id, s.id
|
|
3639
|
+
ORDER BY "activeAssignments" DESC,
|
|
3640
|
+
CASE WHEN c.user_id = $1 THEN 0 ELSE 1 END,
|
|
3641
|
+
c.id ASC
|
|
3642
|
+
LIMIT 1`, [userId]);
|
|
3466
3643
|
}
|
|
3467
3644
|
async getCollaboratorById(collaboratorId) {
|
|
3468
3645
|
const collaborator = await this.querySingle(`SELECT c.id,
|
|
@@ -3930,6 +4107,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3930
4107
|
const project = await this.querySingle(`SELECT p.id,
|
|
3931
4108
|
p.contract_id AS "contractId",
|
|
3932
4109
|
p.manager_collaborator_id AS "managerCollaboratorId",
|
|
4110
|
+
p.client_person_id AS "clientPersonId",
|
|
4111
|
+
client_person.avatar_id AS "clientAvatarId",
|
|
3933
4112
|
p.code,
|
|
3934
4113
|
p.name,
|
|
3935
4114
|
p.client_name AS "clientName",
|
|
@@ -3938,8 +4117,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3938
4117
|
p.progress_percent AS "progressPercent",
|
|
3939
4118
|
p.delivery_model AS "deliveryModel",
|
|
3940
4119
|
p.budget_amount AS "budgetAmount",
|
|
3941
|
-
p.start_date AS "startDate",
|
|
3942
|
-
p.end_date AS "endDate",
|
|
4120
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
4121
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
3943
4122
|
c.name AS "contractName",
|
|
3944
4123
|
c.status AS "contractStatus",
|
|
3945
4124
|
c.contract_category AS "contractCategory",
|
|
@@ -3950,6 +4129,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3950
4129
|
FROM operations_project p
|
|
3951
4130
|
LEFT JOIN operations_contract c ON c.id = p.contract_id
|
|
3952
4131
|
LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
|
|
4132
|
+
LEFT JOIN person client_person ON client_person.id = p.client_person_id
|
|
3953
4133
|
LEFT JOIN operations_project_assignment pa
|
|
3954
4134
|
ON pa.project_id = p.id
|
|
3955
4135
|
AND pa.deleted_at IS NULL
|
|
@@ -3965,24 +4145,29 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
3965
4145
|
) project_role_locale ON TRUE
|
|
3966
4146
|
WHERE p.id = $1
|
|
3967
4147
|
AND p.deleted_at IS NULL
|
|
3968
|
-
GROUP BY p.id, c.id, m.id`, [projectId, actorCollaboratorId !== null && actorCollaboratorId !== void 0 ? actorCollaboratorId : null]);
|
|
4148
|
+
GROUP BY p.id, c.id, m.id, client_person.id`, [projectId, actorCollaboratorId !== null && actorCollaboratorId !== void 0 ? actorCollaboratorId : null]);
|
|
3969
4149
|
if (!project) {
|
|
3970
4150
|
throw new common_1.NotFoundException('Project not found.');
|
|
3971
4151
|
}
|
|
3972
4152
|
const [assignments, relatedContract, timesheetSummary, operationalIndicators] = await Promise.all([
|
|
3973
4153
|
this.queryRows(`SELECT pa.id,
|
|
3974
4154
|
pa.collaborator_id AS "collaboratorId",
|
|
4155
|
+
c.user_id AS "userId",
|
|
4156
|
+
person_record.avatar_id AS "personAvatarId",
|
|
4157
|
+
collaborator_user.photo_id AS "userPhotoId",
|
|
3975
4158
|
c.display_name AS "collaboratorName",
|
|
3976
4159
|
pa.project_role_id AS "projectRoleId",
|
|
3977
4160
|
COALESCE(project_role_locale.name, pa.role_label) AS "roleLabel",
|
|
3978
4161
|
pa.allocation_percent AS "allocationPercent",
|
|
3979
4162
|
pa.weekly_hours AS "weeklyHours",
|
|
3980
4163
|
pa.is_billable AS "isBillable",
|
|
3981
|
-
pa.start_date AS "startDate",
|
|
3982
|
-
pa.end_date AS "endDate",
|
|
4164
|
+
TO_CHAR(pa.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
4165
|
+
TO_CHAR(pa.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
3983
4166
|
pa.status
|
|
3984
4167
|
FROM operations_project_assignment pa
|
|
3985
4168
|
JOIN operations_collaborator c ON c.id = pa.collaborator_id
|
|
4169
|
+
LEFT JOIN person person_record ON person_record.id = c.person_id
|
|
4170
|
+
LEFT JOIN "user" collaborator_user ON collaborator_user.id = c.user_id
|
|
3986
4171
|
LEFT JOIN operations_project_role project_role
|
|
3987
4172
|
ON project_role.id = pa.project_role_id
|
|
3988
4173
|
AND project_role.deleted_at IS NULL
|
|
@@ -4004,8 +4189,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4004
4189
|
contract_category AS "contractCategory",
|
|
4005
4190
|
billing_model AS "billingModel",
|
|
4006
4191
|
status,
|
|
4007
|
-
start_date AS "startDate",
|
|
4008
|
-
end_date AS "endDate",
|
|
4192
|
+
TO_CHAR(start_date, 'YYYY-MM-DD') AS "startDate",
|
|
4193
|
+
TO_CHAR(end_date, 'YYYY-MM-DD') AS "endDate",
|
|
4009
4194
|
budget_amount AS "budgetAmount",
|
|
4010
4195
|
monthly_hour_cap AS "monthlyHourCap",
|
|
4011
4196
|
description,
|
|
@@ -4152,7 +4337,8 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4152
4337
|
FROM operations_contract c
|
|
4153
4338
|
WHERE c.related_collaborator_id = $1
|
|
4154
4339
|
AND c.deleted_at IS NULL
|
|
4155
|
-
ORDER BY c.
|
|
4340
|
+
ORDER BY CASE WHEN c.origin_type = 'employee_hiring' THEN 0 ELSE 1 END,
|
|
4341
|
+
c.created_at DESC`, [collaboratorId]),
|
|
4156
4342
|
this.queryRows(`SELECT weekday,
|
|
4157
4343
|
is_working_day AS "isWorkingDay",
|
|
4158
4344
|
start_time AS "startTime",
|
|
@@ -4260,34 +4446,148 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4260
4446
|
}
|
|
4261
4447
|
return assignment[0];
|
|
4262
4448
|
}
|
|
4449
|
+
async resolveProjectAssignmentForActor(client, actor, input) {
|
|
4450
|
+
if (actor.collaboratorId && !actor.isDirector && !actor.isSupervisor) {
|
|
4451
|
+
return this.resolveOwnedProjectAssignment(client, actor.collaboratorId, input);
|
|
4452
|
+
}
|
|
4453
|
+
if (!input.projectId && !input.projectAssignmentId) {
|
|
4454
|
+
throw new common_1.BadRequestException('Either projectId or projectAssignmentId is required.');
|
|
4455
|
+
}
|
|
4456
|
+
const params = [];
|
|
4457
|
+
const filters = ['pa.deleted_at IS NULL', 'p.deleted_at IS NULL'];
|
|
4458
|
+
if (input.projectAssignmentId) {
|
|
4459
|
+
filters.push(`pa.id = ${this.param(params, input.projectAssignmentId)}`);
|
|
4460
|
+
}
|
|
4461
|
+
if (input.projectId) {
|
|
4462
|
+
filters.push(`pa.project_id = ${this.param(params, input.projectId)}`);
|
|
4463
|
+
}
|
|
4464
|
+
const assignment = (await client.$queryRawUnsafe(`SELECT pa.id,
|
|
4465
|
+
pa.project_id AS "projectId",
|
|
4466
|
+
p.name AS "projectName",
|
|
4467
|
+
p.code AS "projectCode",
|
|
4468
|
+
pa.role_label AS "roleLabel"
|
|
4469
|
+
FROM operations_project_assignment pa
|
|
4470
|
+
JOIN operations_project p
|
|
4471
|
+
ON p.id = pa.project_id
|
|
4472
|
+
WHERE ${filters.join(' AND ')}
|
|
4473
|
+
ORDER BY CASE WHEN pa.status = 'active' THEN 0 ELSE 1 END,
|
|
4474
|
+
pa.start_date DESC NULLS LAST,
|
|
4475
|
+
pa.id DESC
|
|
4476
|
+
LIMIT 1`, ...params));
|
|
4477
|
+
if (!assignment[0]) {
|
|
4478
|
+
throw new common_1.NotFoundException('Project assignment not found.');
|
|
4479
|
+
}
|
|
4480
|
+
return assignment[0];
|
|
4481
|
+
}
|
|
4263
4482
|
async getOwnedTaskRecord(client, collaboratorId, taskId) {
|
|
4264
4483
|
const task = (await client.$queryRawUnsafe(`SELECT t.id,
|
|
4265
4484
|
t.name,
|
|
4266
4485
|
t.description,
|
|
4486
|
+
t.priority,
|
|
4267
4487
|
t.status,
|
|
4488
|
+
t.due_date AS "dueDate",
|
|
4489
|
+
t.estimate_hours AS "estimateHours",
|
|
4490
|
+
t.position,
|
|
4491
|
+
t.tags,
|
|
4492
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
4268
4493
|
t.project_assignment_id AS "projectAssignmentId",
|
|
4269
|
-
pa.project_id AS "projectId",
|
|
4494
|
+
COALESCE(t.project_id, pa.project_id) AS "projectId",
|
|
4270
4495
|
p.name AS "projectName",
|
|
4271
4496
|
p.code AS "projectCode"
|
|
4272
4497
|
FROM operations_task t
|
|
4273
|
-
JOIN operations_project_assignment pa
|
|
4498
|
+
LEFT JOIN operations_project_assignment pa
|
|
4274
4499
|
ON pa.id = t.project_assignment_id
|
|
4275
4500
|
AND pa.deleted_at IS NULL
|
|
4276
|
-
JOIN operations_project p
|
|
4277
|
-
ON p.id = pa.project_id
|
|
4501
|
+
LEFT JOIN operations_project p
|
|
4502
|
+
ON p.id = COALESCE(t.project_id, pa.project_id)
|
|
4278
4503
|
AND p.deleted_at IS NULL
|
|
4279
4504
|
WHERE t.id = $1
|
|
4280
4505
|
AND t.deleted_at IS NULL
|
|
4281
|
-
AND
|
|
4506
|
+
AND (
|
|
4507
|
+
pa.collaborator_id = $2
|
|
4508
|
+
OR t.project_id IN (
|
|
4509
|
+
SELECT pa2.project_id FROM operations_project_assignment pa2
|
|
4510
|
+
WHERE pa2.collaborator_id = $2 AND pa2.deleted_at IS NULL
|
|
4511
|
+
)
|
|
4512
|
+
)
|
|
4282
4513
|
LIMIT 1`, taskId, collaboratorId));
|
|
4283
4514
|
if (!task[0]) {
|
|
4284
4515
|
throw new common_1.ForbiddenException('The selected task is not assigned to the authenticated collaborator.');
|
|
4285
4516
|
}
|
|
4286
4517
|
return task[0];
|
|
4287
4518
|
}
|
|
4288
|
-
async
|
|
4289
|
-
|
|
4290
|
-
|
|
4519
|
+
async getTaskRecordForActor(client, actor, taskId) {
|
|
4520
|
+
if (actor.collaboratorId && !actor.isDirector && !actor.isSupervisor) {
|
|
4521
|
+
return this.getOwnedTaskRecord(client, actor.collaboratorId, taskId);
|
|
4522
|
+
}
|
|
4523
|
+
const task = await this.getProjectBoardTask(taskId);
|
|
4524
|
+
if (!(task === null || task === void 0 ? void 0 : task.projectId)) {
|
|
4525
|
+
throw new common_1.NotFoundException('Task not found.');
|
|
4526
|
+
}
|
|
4527
|
+
return task;
|
|
4528
|
+
}
|
|
4529
|
+
async listProjectBoardTasks(userId, projectId) {
|
|
4530
|
+
const actor = await this.getActorContext(userId);
|
|
4531
|
+
this.ensureCollaborator(actor);
|
|
4532
|
+
await this.assertProjectAccess(actor, projectId);
|
|
4533
|
+
const rows = await this.queryRows(`SELECT t.id,
|
|
4534
|
+
t.name,
|
|
4535
|
+
t.description,
|
|
4536
|
+
t.priority,
|
|
4537
|
+
t.status,
|
|
4538
|
+
t.due_date AS "dueDate",
|
|
4539
|
+
t.estimate_hours AS "estimateHours",
|
|
4540
|
+
t.position,
|
|
4541
|
+
t.tags,
|
|
4542
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
4543
|
+
ac.display_name AS "assigneeName",
|
|
4544
|
+
au.photo_id AS "assigneeUserPhotoId",
|
|
4545
|
+
ap.avatar_id AS "assigneePersonAvatarId",
|
|
4546
|
+
t.project_assignment_id AS "projectAssignmentId",
|
|
4547
|
+
t.created_at AS "createdAt"
|
|
4548
|
+
FROM operations_task t
|
|
4549
|
+
LEFT JOIN operations_collaborator ac
|
|
4550
|
+
ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
|
|
4551
|
+
LEFT JOIN "user" au
|
|
4552
|
+
ON au.id = ac.user_id
|
|
4553
|
+
LEFT JOIN person ap
|
|
4554
|
+
ON ap.id = ac.person_id
|
|
4555
|
+
WHERE COALESCE(t.project_id, (
|
|
4556
|
+
SELECT pa.project_id FROM operations_project_assignment pa
|
|
4557
|
+
WHERE pa.id = t.project_assignment_id AND pa.deleted_at IS NULL
|
|
4558
|
+
LIMIT 1
|
|
4559
|
+
)) = $1
|
|
4560
|
+
AND t.deleted_at IS NULL
|
|
4561
|
+
ORDER BY t.status ASC, t.position ASC, t.id ASC`, [projectId]);
|
|
4562
|
+
return rows;
|
|
4563
|
+
}
|
|
4564
|
+
async getProjectBoardTask(taskId) {
|
|
4565
|
+
var _a;
|
|
4566
|
+
const rows = await this.queryRows(`SELECT t.id,
|
|
4567
|
+
t.name,
|
|
4568
|
+
t.description,
|
|
4569
|
+
t.priority,
|
|
4570
|
+
t.status,
|
|
4571
|
+
t.due_date AS "dueDate",
|
|
4572
|
+
t.estimate_hours AS "estimateHours",
|
|
4573
|
+
t.position,
|
|
4574
|
+
t.tags,
|
|
4575
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
4576
|
+
ac.display_name AS "assigneeName",
|
|
4577
|
+
au.photo_id AS "assigneeUserPhotoId",
|
|
4578
|
+
ap.avatar_id AS "assigneePersonAvatarId",
|
|
4579
|
+
t.project_assignment_id AS "projectAssignmentId",
|
|
4580
|
+
COALESCE(t.project_id, pa.project_id) AS "projectId",
|
|
4581
|
+
t.created_at AS "createdAt"
|
|
4582
|
+
FROM operations_task t
|
|
4583
|
+
LEFT JOIN operations_collaborator ac
|
|
4584
|
+
ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
|
|
4585
|
+
LEFT JOIN "user" au ON au.id = ac.user_id
|
|
4586
|
+
LEFT JOIN person ap ON ap.id = ac.person_id
|
|
4587
|
+
LEFT JOIN operations_project_assignment pa
|
|
4588
|
+
ON pa.id = t.project_assignment_id AND pa.deleted_at IS NULL
|
|
4589
|
+
WHERE t.id = $1`, [taskId]);
|
|
4590
|
+
return (_a = rows[0]) !== null && _a !== void 0 ? _a : null;
|
|
4291
4591
|
}
|
|
4292
4592
|
async getOrCreateTimesheetForWorkDate(client, collaboratorId, workDate) {
|
|
4293
4593
|
var _a, _b, _c;
|
|
@@ -4389,7 +4689,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4389
4689
|
return timesheet;
|
|
4390
4690
|
}
|
|
4391
4691
|
async replaceTimesheetEntries(client, timesheetId, entries, collaboratorId) {
|
|
4392
|
-
var _a, _b, _c, _d, _e, _f;
|
|
4692
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
4393
4693
|
await client.$executeRawUnsafe(`UPDATE operations_timesheet_entry
|
|
4394
4694
|
SET deleted_at = NOW()
|
|
4395
4695
|
WHERE timesheet_id = $1
|
|
@@ -4399,17 +4699,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4399
4699
|
const assignmentIds = entries
|
|
4400
4700
|
.map((entry) => entry.projectAssignmentId)
|
|
4401
4701
|
.filter((value) => typeof value === 'number');
|
|
4702
|
+
const assignmentMap = new Map();
|
|
4402
4703
|
if (assignmentIds.length) {
|
|
4403
|
-
const assignments = (await client.$queryRawUnsafe(`SELECT id,
|
|
4704
|
+
const assignments = (await client.$queryRawUnsafe(`SELECT id,
|
|
4705
|
+
collaborator_id AS "collaboratorId",
|
|
4706
|
+
project_id AS "projectId"
|
|
4404
4707
|
FROM operations_project_assignment
|
|
4405
4708
|
WHERE id = ANY($1::int[])
|
|
4406
4709
|
AND deleted_at IS NULL`, assignmentIds));
|
|
4407
|
-
|
|
4408
|
-
assignment.id,
|
|
4409
|
-
|
|
4410
|
-
]));
|
|
4710
|
+
assignments.forEach((assignment) => {
|
|
4711
|
+
assignmentMap.set(assignment.id, assignment);
|
|
4712
|
+
});
|
|
4411
4713
|
for (const assignmentId of assignmentIds) {
|
|
4412
|
-
if (assignmentMap.get(assignmentId) !== collaboratorId) {
|
|
4714
|
+
if (((_a = assignmentMap.get(assignmentId)) === null || _a === void 0 ? void 0 : _a.collaboratorId) !== collaboratorId) {
|
|
4413
4715
|
throw new common_1.ForbiddenException('Timesheet entries must use assignments owned by the target collaborator.');
|
|
4414
4716
|
}
|
|
4415
4717
|
}
|
|
@@ -4420,16 +4722,19 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4420
4722
|
const resolvedTask = entry.taskId
|
|
4421
4723
|
? await this.getOwnedTaskRecord(client, collaboratorId, entry.taskId)
|
|
4422
4724
|
: null;
|
|
4725
|
+
const selectedAssignment = entry.projectAssignmentId
|
|
4726
|
+
? (_b = assignmentMap.get(entry.projectAssignmentId)) !== null && _b !== void 0 ? _b : null
|
|
4727
|
+
: null;
|
|
4423
4728
|
if (resolvedTask &&
|
|
4424
|
-
|
|
4425
|
-
resolvedTask.
|
|
4426
|
-
throw new common_1.BadRequestException('The selected task does not belong to the chosen project
|
|
4729
|
+
selectedAssignment &&
|
|
4730
|
+
resolvedTask.projectId !== selectedAssignment.projectId) {
|
|
4731
|
+
throw new common_1.BadRequestException('The selected task does not belong to the chosen project.');
|
|
4427
4732
|
}
|
|
4428
|
-
const resolvedAssignmentId = (
|
|
4733
|
+
const resolvedAssignmentId = (_d = (_c = entry.projectAssignmentId) !== null && _c !== void 0 ? _c : resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.projectAssignmentId) !== null && _d !== void 0 ? _d : null;
|
|
4429
4734
|
if (entry.taskId && !resolvedAssignmentId) {
|
|
4430
4735
|
throw new common_1.BadRequestException('The selected task must belong to a project assignment.');
|
|
4431
4736
|
}
|
|
4432
|
-
const activityLabel = (
|
|
4737
|
+
const activityLabel = (_f = (_e = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.name) !== null && _e !== void 0 ? _e : this.normalizeOptionalText(entry.taskName)) !== null && _f !== void 0 ? _f : this.normalizeOptionalText(entry.activityLabel);
|
|
4433
4738
|
if (!entry.workDate) {
|
|
4434
4739
|
throw new common_1.BadRequestException('Timesheet entry workDate is required.');
|
|
4435
4740
|
}
|
|
@@ -4444,7 +4749,7 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4444
4749
|
description,
|
|
4445
4750
|
created_at,
|
|
4446
4751
|
updated_at
|
|
4447
|
-
) VALUES ($1, $2, $3, $4, $5::date, $6, $7, $8, NOW(), NOW())`, timesheetId, resolvedAssignmentId, (
|
|
4752
|
+
) VALUES ($1, $2, $3, $4, $5::date, $6, $7, $8, NOW(), NOW())`, timesheetId, resolvedAssignmentId, (_h = (_g = resolvedTask === null || resolvedTask === void 0 ? void 0 : resolvedTask.id) !== null && _g !== void 0 ? _g : entry.taskId) !== null && _h !== void 0 ? _h : null, activityLabel !== null && activityLabel !== void 0 ? activityLabel : null, entry.workDate, durationMinutes, hours, this.normalizeOptionalText(entry.description));
|
|
4448
4753
|
}
|
|
4449
4754
|
}
|
|
4450
4755
|
async refreshTimesheetTotal(client, timesheetId) {
|
|
@@ -4470,6 +4775,29 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4470
4775
|
updated_at = NOW()
|
|
4471
4776
|
WHERE id = $1`, timesheetId);
|
|
4472
4777
|
}
|
|
4778
|
+
async cleanupEmptyEditableTimesheet(client, timesheetId) {
|
|
4779
|
+
var _a;
|
|
4780
|
+
const candidate = (await client.$queryRawUnsafe(`SELECT t.id
|
|
4781
|
+
FROM operations_timesheet t
|
|
4782
|
+
WHERE t.id = $1
|
|
4783
|
+
AND t.deleted_at IS NULL
|
|
4784
|
+
AND t.status IN ('draft', 'rejected')
|
|
4785
|
+
AND NOT EXISTS (
|
|
4786
|
+
SELECT 1
|
|
4787
|
+
FROM operations_timesheet_entry e
|
|
4788
|
+
WHERE e.timesheet_id = t.id
|
|
4789
|
+
AND e.deleted_at IS NULL
|
|
4790
|
+
)
|
|
4791
|
+
LIMIT 1`, timesheetId));
|
|
4792
|
+
if (!((_a = candidate[0]) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4795
|
+
await client.$executeRawUnsafe(`UPDATE operations_timesheet
|
|
4796
|
+
SET deleted_at = NOW(),
|
|
4797
|
+
updated_at = NOW()
|
|
4798
|
+
WHERE id = $1
|
|
4799
|
+
AND deleted_at IS NULL`, timesheetId);
|
|
4800
|
+
}
|
|
4473
4801
|
async upsertApproval(client, input) {
|
|
4474
4802
|
var _a, _b;
|
|
4475
4803
|
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
@@ -4846,6 +5174,90 @@ let OperationsService = OperationsService_1 = class OperationsService {
|
|
|
4846
5174
|
? Math.round(Number(input.weeklyCapacityHours) * 4)
|
|
4847
5175
|
: null, (_c = input.description) !== null && _c !== void 0 ? _c : null, createdByUserId);
|
|
4848
5176
|
}
|
|
5177
|
+
async syncHiringContractDraft(client, updatedByUserId, collaboratorId, data) {
|
|
5178
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
5179
|
+
const collaborator = await client.$queryRawUnsafe(`SELECT c.id,
|
|
5180
|
+
c.code,
|
|
5181
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
5182
|
+
collaborator_type.slug AS "collaboratorTypeSlug",
|
|
5183
|
+
c.supervisor_collaborator_id AS "supervisorCollaboratorId",
|
|
5184
|
+
c.joined_at AS "joinedAt",
|
|
5185
|
+
c.weekly_capacity_hours AS "weeklyCapacityHours"
|
|
5186
|
+
FROM operations_collaborator c
|
|
5187
|
+
LEFT JOIN person person_record
|
|
5188
|
+
ON person_record.id = c.person_id
|
|
5189
|
+
LEFT JOIN operations_collaborator_type collaborator_type
|
|
5190
|
+
ON collaborator_type.id = c.collaborator_type_id
|
|
5191
|
+
AND collaborator_type.deleted_at IS NULL
|
|
5192
|
+
WHERE c.id = $1
|
|
5193
|
+
AND c.deleted_at IS NULL
|
|
5194
|
+
LIMIT 1`, collaboratorId);
|
|
5195
|
+
const currentCollaborator = (_a = collaborator[0]) !== null && _a !== void 0 ? _a : null;
|
|
5196
|
+
if (!currentCollaborator) {
|
|
5197
|
+
throw new common_1.NotFoundException('Collaborator not found.');
|
|
5198
|
+
}
|
|
5199
|
+
const hiringContracts = (await client.$queryRawUnsafe(`SELECT id,
|
|
5200
|
+
budget_amount AS "budgetAmount",
|
|
5201
|
+
description
|
|
5202
|
+
FROM operations_contract
|
|
5203
|
+
WHERE related_collaborator_id = $1
|
|
5204
|
+
AND origin_type = 'employee_hiring'
|
|
5205
|
+
AND deleted_at IS NULL
|
|
5206
|
+
ORDER BY created_at DESC
|
|
5207
|
+
LIMIT 1`, collaboratorId));
|
|
5208
|
+
const hiringContract = (_b = hiringContracts[0]) !== null && _b !== void 0 ? _b : null;
|
|
5209
|
+
const collaboratorCode = (_c = this.normalizeOptionalText(currentCollaborator.code)) !== null && _c !== void 0 ? _c : `COL-${collaboratorId}`;
|
|
5210
|
+
const displayName = (_d = this.normalizeOptionalText(currentCollaborator.displayName)) !== null && _d !== void 0 ? _d : `Collaborator ${collaboratorId}`;
|
|
5211
|
+
const collaboratorTypeSlug = (_e = this.normalizeOptionalText(currentCollaborator.collaboratorTypeSlug)) !== null && _e !== void 0 ? _e : 'other';
|
|
5212
|
+
const startDate = data.joinedAt !== undefined
|
|
5213
|
+
? (_f = data.joinedAt) !== null && _f !== void 0 ? _f : null
|
|
5214
|
+
: (_g = currentCollaborator.joinedAt) !== null && _g !== void 0 ? _g : null;
|
|
5215
|
+
const weeklyCapacityHours = data.weeklyCapacityHours !== undefined
|
|
5216
|
+
? (_h = data.weeklyCapacityHours) !== null && _h !== void 0 ? _h : null
|
|
5217
|
+
: (_j = currentCollaborator.weeklyCapacityHours) !== null && _j !== void 0 ? _j : null;
|
|
5218
|
+
const compensationAmount = data.compensationAmount !== undefined
|
|
5219
|
+
? (_k = data.compensationAmount) !== null && _k !== void 0 ? _k : null
|
|
5220
|
+
: (_l = hiringContract === null || hiringContract === void 0 ? void 0 : hiringContract.budgetAmount) !== null && _l !== void 0 ? _l : null;
|
|
5221
|
+
const description = data.contractDescription !== undefined
|
|
5222
|
+
? this.normalizeOptionalText(data.contractDescription)
|
|
5223
|
+
: (_m = hiringContract === null || hiringContract === void 0 ? void 0 : hiringContract.description) !== null && _m !== void 0 ? _m : null;
|
|
5224
|
+
const supervisorCollaboratorId = data.supervisorCollaboratorId !== undefined
|
|
5225
|
+
? (_o = data.supervisorCollaboratorId) !== null && _o !== void 0 ? _o : null
|
|
5226
|
+
: (_p = currentCollaborator.supervisorCollaboratorId) !== null && _p !== void 0 ? _p : null;
|
|
5227
|
+
if (!hiringContract) {
|
|
5228
|
+
if (data.autoGenerateContractDraft === false) {
|
|
5229
|
+
return;
|
|
5230
|
+
}
|
|
5231
|
+
await this.createHiringContractDraft(client, updatedByUserId, {
|
|
5232
|
+
collaboratorId,
|
|
5233
|
+
collaboratorCode,
|
|
5234
|
+
displayName,
|
|
5235
|
+
collaboratorType: collaboratorTypeSlug,
|
|
5236
|
+
supervisorCollaboratorId,
|
|
5237
|
+
startDate,
|
|
5238
|
+
weeklyCapacityHours,
|
|
5239
|
+
compensationAmount,
|
|
5240
|
+
description,
|
|
5241
|
+
});
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
await client.$executeRawUnsafe(`UPDATE operations_contract
|
|
5245
|
+
SET code = $1,
|
|
5246
|
+
name = $2,
|
|
5247
|
+
contract_category = $3::operations_contract_contract_category_70d553ea09_enum,
|
|
5248
|
+
contract_type = $4::operations_contract_contract_type_48331e2ebf_enum,
|
|
5249
|
+
client_name = $5,
|
|
5250
|
+
billing_model = $6::operations_contract_billing_model_409dc7fea2_enum,
|
|
5251
|
+
account_manager_collaborator_id = $7,
|
|
5252
|
+
start_date = $8::date,
|
|
5253
|
+
effective_date = $8::date,
|
|
5254
|
+
budget_amount = $9,
|
|
5255
|
+
monthly_hour_cap = $10,
|
|
5256
|
+
description = $11,
|
|
5257
|
+
updated_by_user_id = $12,
|
|
5258
|
+
updated_at = NOW()
|
|
5259
|
+
WHERE id = $13`, `HIR-${collaboratorCode}`, this.buildHiringContractName(displayName, collaboratorTypeSlug), this.mapContractCategoryForCollaboratorType(collaboratorTypeSlug), this.mapContractTypeForCollaboratorType(collaboratorTypeSlug), displayName, this.mapBillingModelForCollaboratorType(collaboratorTypeSlug), supervisorCollaboratorId, startDate !== null && startDate !== void 0 ? startDate : new Date().toISOString().slice(0, 10), compensationAmount, weeklyCapacityHours ? Math.round(Number(weeklyCapacityHours) * 4) : null, description, updatedByUserId, hiringContract.id);
|
|
5260
|
+
}
|
|
4849
5261
|
async createProjectContractDraft(client, createdByUserId, input) {
|
|
4850
5262
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
4851
5263
|
const templateRows = input.contractTemplateId
|