@hed-hog/operations 0.0.304 → 0.0.305
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/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-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 +68 -12
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +380 -101
- package/dist/operations.service.js.map +1 -1
- package/hedhog/data/route.yaml +13 -0
- 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/_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/collaborator-types/page.tsx.ejs +502 -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/messages/en.json +27 -4
- package/hedhog/frontend/messages/pt.json +27 -4
- package/hedhog/table/operations_project.yaml +9 -0
- package/hedhog/table/operations_task.yaml +43 -4
- package/package.json +5 -5
- package/src/controllers/operations-tasks.controller.ts +11 -0
- package/src/dto/create-task.dto.ts +47 -7
- package/src/dto/list-tasks.dto.ts +3 -3
- package/src/dto/update-task.dto.ts +47 -7
- package/src/operations.service.ts +556 -88
|
@@ -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'] as const;
|
|
108
108
|
const REVISION_TYPE_VALUES = ['amendment', 'renewal', 'revision', 'addendum', 'other'] as const;
|
|
109
109
|
const REVISION_STATUS_VALUES = ['draft', 'active', 'completed', 'cancelled'] as const;
|
|
110
|
-
const TASK_STATUS_VALUES = ['
|
|
110
|
+
const TASK_STATUS_VALUES = ['todo', 'doing', 'review', 'done'] as const;
|
|
111
111
|
|
|
112
112
|
type ApprovalAction = 'approve' | 'reject';
|
|
113
113
|
type ApprovalTargetType =
|
|
@@ -548,6 +548,7 @@ type ProjectPayload = {
|
|
|
548
548
|
contractId?: number | null;
|
|
549
549
|
contractTemplateId?: number | null;
|
|
550
550
|
managerCollaboratorId?: number | null;
|
|
551
|
+
clientPersonId?: number | null;
|
|
551
552
|
code: string;
|
|
552
553
|
name: string;
|
|
553
554
|
clientName?: string | null;
|
|
@@ -606,9 +607,15 @@ type TimesheetEntryPayload = {
|
|
|
606
607
|
type TaskPayload = {
|
|
607
608
|
projectId?: number | null;
|
|
608
609
|
projectAssignmentId?: number | null;
|
|
610
|
+
assigneeCollaboratorId?: number | null;
|
|
609
611
|
name?: string | null;
|
|
610
612
|
description?: string | null;
|
|
613
|
+
priority?: 'low' | 'medium' | 'high';
|
|
611
614
|
status?: (typeof TASK_STATUS_VALUES)[number];
|
|
615
|
+
dueDate?: string | null;
|
|
616
|
+
estimateHours?: number | null;
|
|
617
|
+
position?: number;
|
|
618
|
+
tags?: string | null;
|
|
612
619
|
};
|
|
613
620
|
|
|
614
621
|
type QuickTimesheetEntryPayload = {
|
|
@@ -1743,6 +1750,28 @@ export class OperationsService {
|
|
|
1743
1750
|
data.equityParticipation
|
|
1744
1751
|
);
|
|
1745
1752
|
}
|
|
1753
|
+
|
|
1754
|
+
if (
|
|
1755
|
+
data.compensationAmount !== undefined ||
|
|
1756
|
+
data.contractDescription !== undefined ||
|
|
1757
|
+
data.autoGenerateContractDraft !== undefined ||
|
|
1758
|
+
data.joinedAt !== undefined ||
|
|
1759
|
+
data.weeklyCapacityHours !== undefined ||
|
|
1760
|
+
data.supervisorCollaboratorId !== undefined ||
|
|
1761
|
+
data.collaboratorType !== undefined ||
|
|
1762
|
+
data.collaboratorTypeId !== undefined ||
|
|
1763
|
+
data.collaboratorTypeSlug !== undefined ||
|
|
1764
|
+
data.code !== undefined ||
|
|
1765
|
+
data.personId !== undefined ||
|
|
1766
|
+
data.displayName !== undefined
|
|
1767
|
+
) {
|
|
1768
|
+
await this.syncHiringContractDraft(
|
|
1769
|
+
tx as any,
|
|
1770
|
+
actor.userId,
|
|
1771
|
+
collaboratorId,
|
|
1772
|
+
data
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
1746
1775
|
});
|
|
1747
1776
|
|
|
1748
1777
|
return this.getCollaboratorByIdForUser(userId, collaboratorId);
|
|
@@ -2002,8 +2031,8 @@ export class OperationsService {
|
|
|
2002
2031
|
p.progress_percent AS "progressPercent",
|
|
2003
2032
|
p.delivery_model AS "deliveryModel",
|
|
2004
2033
|
p.budget_amount AS "budgetAmount",
|
|
2005
|
-
p.start_date AS "startDate",
|
|
2006
|
-
p.end_date AS "endDate",
|
|
2034
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
2035
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
2007
2036
|
c.name AS "contractName",
|
|
2008
2037
|
c.status AS "contractStatus",
|
|
2009
2038
|
m.display_name AS "managerName",
|
|
@@ -2105,8 +2134,8 @@ export class OperationsService {
|
|
|
2105
2134
|
MAX(pa.id)::int AS "projectAssignmentId",
|
|
2106
2135
|
MAX(pa.role_label) AS "roleLabel",
|
|
2107
2136
|
p.status,
|
|
2108
|
-
p.start_date AS "startDate",
|
|
2109
|
-
p.end_date AS "endDate"
|
|
2137
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
2138
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate"
|
|
2110
2139
|
FROM operations_project_assignment pa
|
|
2111
2140
|
JOIN operations_project p
|
|
2112
2141
|
ON p.id = pa.project_id
|
|
@@ -2255,54 +2284,92 @@ export class OperationsService {
|
|
|
2255
2284
|
|
|
2256
2285
|
async createTask(userId: number, data: TaskPayload) {
|
|
2257
2286
|
const actor = await this.getActorContext(userId);
|
|
2258
|
-
|
|
2287
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
2288
|
+
throw new ForbiddenException(
|
|
2289
|
+
'Operations collaborator access is required.'
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2259
2292
|
this.requireFields(data as Record<string, unknown>, ['name']);
|
|
2260
2293
|
|
|
2261
|
-
|
|
2262
|
-
|
|
2294
|
+
let assignmentId: number | null = null;
|
|
2295
|
+
let projectId: number | null = null;
|
|
2296
|
+
|
|
2297
|
+
if (data.projectId || data.projectAssignmentId) {
|
|
2298
|
+
const assignment = await this.resolveProjectAssignmentForActor(
|
|
2299
|
+
this.prisma,
|
|
2300
|
+
actor,
|
|
2301
|
+
{
|
|
2302
|
+
projectId: data.projectId ?? null,
|
|
2303
|
+
projectAssignmentId: data.projectAssignmentId ?? null,
|
|
2304
|
+
}
|
|
2305
|
+
);
|
|
2306
|
+
await this.assertProjectAccess(actor, assignment.projectId);
|
|
2307
|
+
assignmentId = assignment.id;
|
|
2308
|
+
projectId = assignment.projectId;
|
|
2309
|
+
} else if (data.projectId) {
|
|
2310
|
+
projectId = data.projectId;
|
|
2311
|
+
await this.assertProjectAccess(actor, projectId);
|
|
2312
|
+
} else {
|
|
2313
|
+
throw new BadRequestException('Either projectId or projectAssignmentId is required.');
|
|
2263
2314
|
}
|
|
2264
2315
|
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
{
|
|
2269
|
-
projectId: data.projectId ?? null,
|
|
2270
|
-
projectAssignmentId: data.projectAssignmentId ?? null,
|
|
2271
|
-
}
|
|
2272
|
-
);
|
|
2273
|
-
await this.assertProjectAccess(actor, assignment.projectId);
|
|
2316
|
+
if (!projectId) {
|
|
2317
|
+
projectId = data.projectId ?? null;
|
|
2318
|
+
}
|
|
2274
2319
|
|
|
2275
2320
|
const name = this.normalizeOptionalText(data.name);
|
|
2276
2321
|
if (!name) {
|
|
2277
2322
|
throw new BadRequestException('Field "name" is required.');
|
|
2278
2323
|
}
|
|
2279
2324
|
|
|
2325
|
+
const maxPositionRow = await this.querySingle<{ max_pos: number | null }>(
|
|
2326
|
+
`SELECT MAX(position) AS max_pos
|
|
2327
|
+
FROM operations_task
|
|
2328
|
+
WHERE project_id = $1
|
|
2329
|
+
AND status = $2::operations_task_status_574c143dbe_enum
|
|
2330
|
+
AND deleted_at IS NULL`,
|
|
2331
|
+
[projectId, data.status ?? 'todo']
|
|
2332
|
+
);
|
|
2333
|
+
const nextPosition = ((maxPositionRow?.max_pos ?? -1) as number) + 1;
|
|
2334
|
+
|
|
2280
2335
|
const created = await this.querySingle<{ id: number }>(
|
|
2281
2336
|
`INSERT INTO operations_task (
|
|
2337
|
+
project_id,
|
|
2282
2338
|
project_assignment_id,
|
|
2339
|
+
assignee_collaborator_id,
|
|
2283
2340
|
name,
|
|
2284
2341
|
description,
|
|
2342
|
+
priority,
|
|
2285
2343
|
status,
|
|
2344
|
+
due_date,
|
|
2345
|
+
estimate_hours,
|
|
2346
|
+
position,
|
|
2347
|
+
tags,
|
|
2286
2348
|
created_at,
|
|
2287
2349
|
updated_at
|
|
2288
2350
|
) VALUES (
|
|
2289
|
-
$1,
|
|
2290
|
-
$
|
|
2291
|
-
$
|
|
2292
|
-
$
|
|
2293
|
-
NOW(),
|
|
2294
|
-
NOW()
|
|
2351
|
+
$1, $2, $3, $4, $5,
|
|
2352
|
+
$6::operations_task_priority_394ab327eb_enum,
|
|
2353
|
+
$7::operations_task_status_574c143dbe_enum,
|
|
2354
|
+
$8::date, $9::decimal, $10, $11, NOW(), NOW()
|
|
2295
2355
|
)
|
|
2296
2356
|
RETURNING id`,
|
|
2297
2357
|
[
|
|
2298
|
-
|
|
2358
|
+
projectId,
|
|
2359
|
+
assignmentId,
|
|
2360
|
+
data.assigneeCollaboratorId ?? null,
|
|
2299
2361
|
name,
|
|
2300
2362
|
this.normalizeOptionalText(data.description),
|
|
2301
|
-
data.
|
|
2363
|
+
data.priority ?? 'medium',
|
|
2364
|
+
data.status ?? 'todo',
|
|
2365
|
+
data.dueDate ?? null,
|
|
2366
|
+
data.estimateHours ?? null,
|
|
2367
|
+
data.position ?? nextPosition,
|
|
2368
|
+
data.tags ?? null,
|
|
2302
2369
|
]
|
|
2303
2370
|
);
|
|
2304
2371
|
|
|
2305
|
-
return this.
|
|
2372
|
+
return this.getProjectBoardTask(created?.id ?? 0);
|
|
2306
2373
|
}
|
|
2307
2374
|
|
|
2308
2375
|
async updateTask(
|
|
@@ -2311,15 +2378,15 @@ export class OperationsService {
|
|
|
2311
2378
|
data: Partial<TaskPayload>
|
|
2312
2379
|
) {
|
|
2313
2380
|
const actor = await this.getActorContext(userId);
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2381
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
2382
|
+
throw new ForbiddenException(
|
|
2383
|
+
'Operations collaborator access is required.'
|
|
2384
|
+
);
|
|
2318
2385
|
}
|
|
2319
2386
|
|
|
2320
|
-
const current = await this.
|
|
2387
|
+
const current = await this.getTaskRecordForActor(
|
|
2321
2388
|
this.prisma,
|
|
2322
|
-
actor
|
|
2389
|
+
actor,
|
|
2323
2390
|
taskId
|
|
2324
2391
|
);
|
|
2325
2392
|
await this.assertProjectAccess(actor, current.projectId);
|
|
@@ -2334,56 +2401,72 @@ export class OperationsService {
|
|
|
2334
2401
|
}
|
|
2335
2402
|
|
|
2336
2403
|
await this.prisma.$transaction(async (tx) => {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
? await this.resolveOwnedProjectAssignment(
|
|
2340
|
-
tx as any,
|
|
2341
|
-
actor.collaboratorId as number,
|
|
2342
|
-
{
|
|
2343
|
-
projectId: data.projectId ?? null,
|
|
2344
|
-
projectAssignmentId: data.projectAssignmentId ?? null,
|
|
2345
|
-
}
|
|
2346
|
-
)
|
|
2347
|
-
: {
|
|
2348
|
-
id: current.projectAssignmentId,
|
|
2349
|
-
projectId: current.projectId,
|
|
2350
|
-
};
|
|
2404
|
+
let nextAssignmentId = current.projectAssignmentId;
|
|
2405
|
+
let nextProjectId = current.projectId;
|
|
2351
2406
|
|
|
2352
|
-
|
|
2407
|
+
if (data.projectId !== undefined || data.projectAssignmentId !== undefined) {
|
|
2408
|
+
const nextAssignment = await this.resolveProjectAssignmentForActor(
|
|
2409
|
+
tx as any,
|
|
2410
|
+
actor,
|
|
2411
|
+
{
|
|
2412
|
+
projectId: data.projectId ?? null,
|
|
2413
|
+
projectAssignmentId: data.projectAssignmentId ?? null,
|
|
2414
|
+
}
|
|
2415
|
+
);
|
|
2416
|
+
await this.assertProjectAccess(actor, nextAssignment.projectId);
|
|
2417
|
+
nextAssignmentId = nextAssignment.id;
|
|
2418
|
+
nextProjectId = nextAssignment.projectId;
|
|
2419
|
+
}
|
|
2353
2420
|
|
|
2354
2421
|
await (tx as any).$executeRawUnsafe(
|
|
2355
2422
|
`UPDATE operations_task
|
|
2356
|
-
SET
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2423
|
+
SET project_id = $1,
|
|
2424
|
+
project_assignment_id = $2,
|
|
2425
|
+
assignee_collaborator_id = $3,
|
|
2426
|
+
name = $4,
|
|
2427
|
+
description = $5,
|
|
2428
|
+
priority = $6::operations_task_priority_394ab327eb_enum,
|
|
2429
|
+
status = $7::operations_task_status_574c143dbe_enum,
|
|
2430
|
+
due_date = $8::date,
|
|
2431
|
+
estimate_hours = $9::decimal,
|
|
2432
|
+
position = $10,
|
|
2433
|
+
tags = $11,
|
|
2360
2434
|
updated_at = NOW()
|
|
2361
|
-
WHERE id = $
|
|
2435
|
+
WHERE id = $12
|
|
2362
2436
|
AND deleted_at IS NULL`,
|
|
2363
|
-
|
|
2437
|
+
nextProjectId,
|
|
2438
|
+
nextAssignmentId,
|
|
2439
|
+
data.assigneeCollaboratorId !== undefined
|
|
2440
|
+
? (data.assigneeCollaboratorId ?? null)
|
|
2441
|
+
: current.assigneeCollaboratorId,
|
|
2364
2442
|
nextName,
|
|
2365
2443
|
data.description !== undefined
|
|
2366
2444
|
? this.normalizeOptionalText(data.description)
|
|
2367
2445
|
: (current.description ?? null),
|
|
2446
|
+
data.priority ?? current.priority,
|
|
2368
2447
|
data.status ?? current.status,
|
|
2448
|
+
data.dueDate !== undefined ? (data.dueDate ?? null) : current.dueDate,
|
|
2449
|
+
data.estimateHours !== undefined ? (data.estimateHours ?? null) : current.estimateHours,
|
|
2450
|
+
data.position !== undefined ? data.position : current.position,
|
|
2451
|
+
data.tags !== undefined ? (data.tags ?? null) : current.tags,
|
|
2369
2452
|
taskId
|
|
2370
2453
|
);
|
|
2371
2454
|
});
|
|
2372
2455
|
|
|
2373
|
-
return this.
|
|
2456
|
+
return this.getProjectBoardTask(taskId);
|
|
2374
2457
|
}
|
|
2375
2458
|
|
|
2376
2459
|
async removeTask(userId: number, taskId: number) {
|
|
2377
2460
|
const actor = await this.getActorContext(userId);
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2461
|
+
if (!actor.isCollaborator && !actor.isDirector && !actor.isSupervisor) {
|
|
2462
|
+
throw new ForbiddenException(
|
|
2463
|
+
'Operations collaborator access is required.'
|
|
2464
|
+
);
|
|
2382
2465
|
}
|
|
2383
2466
|
|
|
2384
|
-
const current = await this.
|
|
2467
|
+
const current = await this.getTaskRecordForActor(
|
|
2385
2468
|
this.prisma,
|
|
2386
|
-
actor
|
|
2469
|
+
actor,
|
|
2387
2470
|
taskId
|
|
2388
2471
|
);
|
|
2389
2472
|
await this.assertProjectAccess(actor, current.projectId);
|
|
@@ -2392,7 +2475,6 @@ export class OperationsService {
|
|
|
2392
2475
|
await (tx as any).$executeRawUnsafe(
|
|
2393
2476
|
`UPDATE operations_task
|
|
2394
2477
|
SET deleted_at = COALESCE(deleted_at, NOW()),
|
|
2395
|
-
status = 'archived',
|
|
2396
2478
|
updated_at = NOW()
|
|
2397
2479
|
WHERE id = $1
|
|
2398
2480
|
AND deleted_at IS NULL`,
|
|
@@ -2732,6 +2814,7 @@ export class OperationsService {
|
|
|
2732
2814
|
`INSERT INTO operations_project (
|
|
2733
2815
|
contract_id,
|
|
2734
2816
|
manager_collaborator_id,
|
|
2817
|
+
client_person_id,
|
|
2735
2818
|
code,
|
|
2736
2819
|
name,
|
|
2737
2820
|
client_name,
|
|
@@ -2745,15 +2828,16 @@ export class OperationsService {
|
|
|
2745
2828
|
created_at,
|
|
2746
2829
|
updated_at
|
|
2747
2830
|
) VALUES (
|
|
2748
|
-
$1, $2, $3, $4, $5, $6,
|
|
2749
|
-
$
|
|
2750
|
-
$
|
|
2751
|
-
$
|
|
2752
|
-
$
|
|
2831
|
+
$1, $2, $3, $4, $5, $6, $7,
|
|
2832
|
+
$8::operations_project_status_965e8d4b2d_enum,
|
|
2833
|
+
$9,
|
|
2834
|
+
$10::operations_project_delivery_model_75ee11b3b7_enum,
|
|
2835
|
+
$11, $12::date, $13::date, NOW(), NOW()
|
|
2753
2836
|
)
|
|
2754
2837
|
RETURNING id`,
|
|
2755
2838
|
data.contractId ?? null,
|
|
2756
2839
|
data.managerCollaboratorId ?? null,
|
|
2840
|
+
data.clientPersonId ?? null,
|
|
2757
2841
|
data.code,
|
|
2758
2842
|
data.name,
|
|
2759
2843
|
data.clientName ?? null,
|
|
@@ -2823,6 +2907,7 @@ export class OperationsService {
|
|
|
2823
2907
|
const params: unknown[] = [];
|
|
2824
2908
|
this.pushUpdate(updates, params, 'contract_id', data.contractId);
|
|
2825
2909
|
this.pushUpdate(updates, params, 'manager_collaborator_id', data.managerCollaboratorId);
|
|
2910
|
+
this.pushUpdate(updates, params, 'client_person_id', data.clientPersonId);
|
|
2826
2911
|
this.pushUpdate(updates, params, 'code', data.code);
|
|
2827
2912
|
this.pushUpdate(updates, params, 'name', data.name);
|
|
2828
2913
|
this.pushUpdate(updates, params, 'client_name', data.clientName);
|
|
@@ -2839,7 +2924,7 @@ export class OperationsService {
|
|
|
2839
2924
|
updates,
|
|
2840
2925
|
params,
|
|
2841
2926
|
'delivery_model',
|
|
2842
|
-
data.deliveryModel,
|
|
2927
|
+
(data.deliveryModel as string | null | undefined) === '' ? null : data.deliveryModel,
|
|
2843
2928
|
'operations_project_delivery_model_75ee11b3b7_enum'
|
|
2844
2929
|
);
|
|
2845
2930
|
this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
|
|
@@ -2869,7 +2954,7 @@ export class OperationsService {
|
|
|
2869
2954
|
const nextContractId =
|
|
2870
2955
|
data.contractId !== undefined
|
|
2871
2956
|
? data.contractId
|
|
2872
|
-
: (currentProject.
|
|
2957
|
+
: (currentProject.relatedContract?.id ?? null);
|
|
2873
2958
|
const shouldGenerateDraft =
|
|
2874
2959
|
!nextContractId && data.autoGenerateContractDraft === true;
|
|
2875
2960
|
|
|
@@ -2922,6 +3007,39 @@ export class OperationsService {
|
|
|
2922
3007
|
contractId,
|
|
2923
3008
|
projectId
|
|
2924
3009
|
);
|
|
3010
|
+
} else if (
|
|
3011
|
+
nextContractId &&
|
|
3012
|
+
(data.monthlyHourCap !== undefined || data.billingModel !== undefined)
|
|
3013
|
+
) {
|
|
3014
|
+
const contractUpdates: string[] = [];
|
|
3015
|
+
const contractParams: unknown[] = [];
|
|
3016
|
+
|
|
3017
|
+
this.pushUpdate(
|
|
3018
|
+
contractUpdates,
|
|
3019
|
+
contractParams,
|
|
3020
|
+
'monthly_hour_cap',
|
|
3021
|
+
data.monthlyHourCap
|
|
3022
|
+
);
|
|
3023
|
+
this.pushUpdate(
|
|
3024
|
+
contractUpdates,
|
|
3025
|
+
contractParams,
|
|
3026
|
+
'billing_model',
|
|
3027
|
+
(data.billingModel as string | null | undefined) === ''
|
|
3028
|
+
? null
|
|
3029
|
+
: data.billingModel,
|
|
3030
|
+
'operations_contract_billing_model_409dc7fea2_enum'
|
|
3031
|
+
);
|
|
3032
|
+
|
|
3033
|
+
if (contractUpdates.length) {
|
|
3034
|
+
contractParams.push(nextContractId);
|
|
3035
|
+
await (tx as any).$executeRawUnsafe(
|
|
3036
|
+
`UPDATE operations_contract
|
|
3037
|
+
SET ${contractUpdates.join(', ')},
|
|
3038
|
+
updated_at = NOW()
|
|
3039
|
+
WHERE id = $${contractParams.length}`,
|
|
3040
|
+
...contractParams
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
2925
3043
|
}
|
|
2926
3044
|
});
|
|
2927
3045
|
|
|
@@ -6608,6 +6726,8 @@ export class OperationsService {
|
|
|
6608
6726
|
id: number;
|
|
6609
6727
|
contractId: number | null;
|
|
6610
6728
|
managerCollaboratorId: number | null;
|
|
6729
|
+
clientPersonId: number | null;
|
|
6730
|
+
clientAvatarId: number | null;
|
|
6611
6731
|
code: string;
|
|
6612
6732
|
name: string;
|
|
6613
6733
|
clientName: string | null;
|
|
@@ -6629,6 +6749,8 @@ export class OperationsService {
|
|
|
6629
6749
|
`SELECT p.id,
|
|
6630
6750
|
p.contract_id AS "contractId",
|
|
6631
6751
|
p.manager_collaborator_id AS "managerCollaboratorId",
|
|
6752
|
+
p.client_person_id AS "clientPersonId",
|
|
6753
|
+
client_person.avatar_id AS "clientAvatarId",
|
|
6632
6754
|
p.code,
|
|
6633
6755
|
p.name,
|
|
6634
6756
|
p.client_name AS "clientName",
|
|
@@ -6637,8 +6759,8 @@ export class OperationsService {
|
|
|
6637
6759
|
p.progress_percent AS "progressPercent",
|
|
6638
6760
|
p.delivery_model AS "deliveryModel",
|
|
6639
6761
|
p.budget_amount AS "budgetAmount",
|
|
6640
|
-
p.start_date AS "startDate",
|
|
6641
|
-
p.end_date AS "endDate",
|
|
6762
|
+
TO_CHAR(p.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
6763
|
+
TO_CHAR(p.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
6642
6764
|
c.name AS "contractName",
|
|
6643
6765
|
c.status AS "contractStatus",
|
|
6644
6766
|
c.contract_category AS "contractCategory",
|
|
@@ -6649,6 +6771,7 @@ export class OperationsService {
|
|
|
6649
6771
|
FROM operations_project p
|
|
6650
6772
|
LEFT JOIN operations_contract c ON c.id = p.contract_id
|
|
6651
6773
|
LEFT JOIN operations_collaborator m ON m.id = p.manager_collaborator_id
|
|
6774
|
+
LEFT JOIN person client_person ON client_person.id = p.client_person_id
|
|
6652
6775
|
LEFT JOIN operations_project_assignment pa
|
|
6653
6776
|
ON pa.project_id = p.id
|
|
6654
6777
|
AND pa.deleted_at IS NULL
|
|
@@ -6664,7 +6787,7 @@ export class OperationsService {
|
|
|
6664
6787
|
) project_role_locale ON TRUE
|
|
6665
6788
|
WHERE p.id = $1
|
|
6666
6789
|
AND p.deleted_at IS NULL
|
|
6667
|
-
GROUP BY p.id, c.id, m.id`,
|
|
6790
|
+
GROUP BY p.id, c.id, m.id, client_person.id`,
|
|
6668
6791
|
[projectId, actorCollaboratorId ?? null]
|
|
6669
6792
|
);
|
|
6670
6793
|
|
|
@@ -6677,6 +6800,9 @@ export class OperationsService {
|
|
|
6677
6800
|
this.queryRows<{
|
|
6678
6801
|
id: number;
|
|
6679
6802
|
collaboratorId: number;
|
|
6803
|
+
userId: number | null;
|
|
6804
|
+
personAvatarId: number | null;
|
|
6805
|
+
userPhotoId: number | null;
|
|
6680
6806
|
collaboratorName: string;
|
|
6681
6807
|
projectRoleId: number | null;
|
|
6682
6808
|
roleLabel: string | null;
|
|
@@ -6689,17 +6815,22 @@ export class OperationsService {
|
|
|
6689
6815
|
}>(
|
|
6690
6816
|
`SELECT pa.id,
|
|
6691
6817
|
pa.collaborator_id AS "collaboratorId",
|
|
6818
|
+
c.user_id AS "userId",
|
|
6819
|
+
person_record.avatar_id AS "personAvatarId",
|
|
6820
|
+
collaborator_user.photo_id AS "userPhotoId",
|
|
6692
6821
|
c.display_name AS "collaboratorName",
|
|
6693
6822
|
pa.project_role_id AS "projectRoleId",
|
|
6694
6823
|
COALESCE(project_role_locale.name, pa.role_label) AS "roleLabel",
|
|
6695
6824
|
pa.allocation_percent AS "allocationPercent",
|
|
6696
6825
|
pa.weekly_hours AS "weeklyHours",
|
|
6697
6826
|
pa.is_billable AS "isBillable",
|
|
6698
|
-
pa.start_date AS "startDate",
|
|
6699
|
-
pa.end_date AS "endDate",
|
|
6827
|
+
TO_CHAR(pa.start_date, 'YYYY-MM-DD') AS "startDate",
|
|
6828
|
+
TO_CHAR(pa.end_date, 'YYYY-MM-DD') AS "endDate",
|
|
6700
6829
|
pa.status
|
|
6701
6830
|
FROM operations_project_assignment pa
|
|
6702
6831
|
JOIN operations_collaborator c ON c.id = pa.collaborator_id
|
|
6832
|
+
LEFT JOIN person person_record ON person_record.id = c.person_id
|
|
6833
|
+
LEFT JOIN "user" collaborator_user ON collaborator_user.id = c.user_id
|
|
6703
6834
|
LEFT JOIN operations_project_role project_role
|
|
6704
6835
|
ON project_role.id = pa.project_role_id
|
|
6705
6836
|
AND project_role.deleted_at IS NULL
|
|
@@ -6739,8 +6870,8 @@ export class OperationsService {
|
|
|
6739
6870
|
contract_category AS "contractCategory",
|
|
6740
6871
|
billing_model AS "billingModel",
|
|
6741
6872
|
status,
|
|
6742
|
-
start_date AS "startDate",
|
|
6743
|
-
end_date AS "endDate",
|
|
6873
|
+
TO_CHAR(start_date, 'YYYY-MM-DD') AS "startDate",
|
|
6874
|
+
TO_CHAR(end_date, 'YYYY-MM-DD') AS "endDate",
|
|
6744
6875
|
budget_amount AS "budgetAmount",
|
|
6745
6876
|
monthly_hour_cap AS "monthlyHourCap",
|
|
6746
6877
|
description,
|
|
@@ -6951,7 +7082,8 @@ export class OperationsService {
|
|
|
6951
7082
|
FROM operations_contract c
|
|
6952
7083
|
WHERE c.related_collaborator_id = $1
|
|
6953
7084
|
AND c.deleted_at IS NULL
|
|
6954
|
-
ORDER BY c.
|
|
7085
|
+
ORDER BY CASE WHEN c.origin_type = 'employee_hiring' THEN 0 ELSE 1 END,
|
|
7086
|
+
c.created_at DESC`,
|
|
6955
7087
|
[collaboratorId]
|
|
6956
7088
|
),
|
|
6957
7089
|
this.queryRows(
|
|
@@ -7137,6 +7269,69 @@ export class OperationsService {
|
|
|
7137
7269
|
return assignment[0];
|
|
7138
7270
|
}
|
|
7139
7271
|
|
|
7272
|
+
private async resolveProjectAssignmentForActor(
|
|
7273
|
+
client: any,
|
|
7274
|
+
actor: ActorContext,
|
|
7275
|
+
input: {
|
|
7276
|
+
projectId?: number | null;
|
|
7277
|
+
projectAssignmentId?: number | null;
|
|
7278
|
+
}
|
|
7279
|
+
) {
|
|
7280
|
+
if (actor.collaboratorId && !actor.isDirector && !actor.isSupervisor) {
|
|
7281
|
+
return this.resolveOwnedProjectAssignment(
|
|
7282
|
+
client,
|
|
7283
|
+
actor.collaboratorId,
|
|
7284
|
+
input
|
|
7285
|
+
);
|
|
7286
|
+
}
|
|
7287
|
+
|
|
7288
|
+
if (!input.projectId && !input.projectAssignmentId) {
|
|
7289
|
+
throw new BadRequestException(
|
|
7290
|
+
'Either projectId or projectAssignmentId is required.'
|
|
7291
|
+
);
|
|
7292
|
+
}
|
|
7293
|
+
|
|
7294
|
+
const params: unknown[] = [];
|
|
7295
|
+
const filters = ['pa.deleted_at IS NULL', 'p.deleted_at IS NULL'];
|
|
7296
|
+
|
|
7297
|
+
if (input.projectAssignmentId) {
|
|
7298
|
+
filters.push(`pa.id = ${this.param(params, input.projectAssignmentId)}`);
|
|
7299
|
+
}
|
|
7300
|
+
|
|
7301
|
+
if (input.projectId) {
|
|
7302
|
+
filters.push(`pa.project_id = ${this.param(params, input.projectId)}`);
|
|
7303
|
+
}
|
|
7304
|
+
|
|
7305
|
+
const assignment = (await client.$queryRawUnsafe(
|
|
7306
|
+
`SELECT pa.id,
|
|
7307
|
+
pa.project_id AS "projectId",
|
|
7308
|
+
p.name AS "projectName",
|
|
7309
|
+
p.code AS "projectCode",
|
|
7310
|
+
pa.role_label AS "roleLabel"
|
|
7311
|
+
FROM operations_project_assignment pa
|
|
7312
|
+
JOIN operations_project p
|
|
7313
|
+
ON p.id = pa.project_id
|
|
7314
|
+
WHERE ${filters.join(' AND ')}
|
|
7315
|
+
ORDER BY CASE WHEN pa.status = 'active' THEN 0 ELSE 1 END,
|
|
7316
|
+
pa.start_date DESC NULLS LAST,
|
|
7317
|
+
pa.id DESC
|
|
7318
|
+
LIMIT 1`,
|
|
7319
|
+
...params
|
|
7320
|
+
)) as Array<{
|
|
7321
|
+
id: number;
|
|
7322
|
+
projectId: number;
|
|
7323
|
+
projectName: string;
|
|
7324
|
+
projectCode: string | null;
|
|
7325
|
+
roleLabel: string | null;
|
|
7326
|
+
}>;
|
|
7327
|
+
|
|
7328
|
+
if (!assignment[0]) {
|
|
7329
|
+
throw new NotFoundException('Project assignment not found.');
|
|
7330
|
+
}
|
|
7331
|
+
|
|
7332
|
+
return assignment[0];
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7140
7335
|
private async getOwnedTaskRecord(
|
|
7141
7336
|
client: any,
|
|
7142
7337
|
collaboratorId: number,
|
|
@@ -7146,21 +7341,33 @@ export class OperationsService {
|
|
|
7146
7341
|
`SELECT t.id,
|
|
7147
7342
|
t.name,
|
|
7148
7343
|
t.description,
|
|
7344
|
+
t.priority,
|
|
7149
7345
|
t.status,
|
|
7346
|
+
t.due_date AS "dueDate",
|
|
7347
|
+
t.estimate_hours AS "estimateHours",
|
|
7348
|
+
t.position,
|
|
7349
|
+
t.tags,
|
|
7350
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
7150
7351
|
t.project_assignment_id AS "projectAssignmentId",
|
|
7151
|
-
pa.project_id AS "projectId",
|
|
7352
|
+
COALESCE(t.project_id, pa.project_id) AS "projectId",
|
|
7152
7353
|
p.name AS "projectName",
|
|
7153
7354
|
p.code AS "projectCode"
|
|
7154
7355
|
FROM operations_task t
|
|
7155
|
-
JOIN operations_project_assignment pa
|
|
7356
|
+
LEFT JOIN operations_project_assignment pa
|
|
7156
7357
|
ON pa.id = t.project_assignment_id
|
|
7157
7358
|
AND pa.deleted_at IS NULL
|
|
7158
|
-
JOIN operations_project p
|
|
7159
|
-
ON p.id = pa.project_id
|
|
7359
|
+
LEFT JOIN operations_project p
|
|
7360
|
+
ON p.id = COALESCE(t.project_id, pa.project_id)
|
|
7160
7361
|
AND p.deleted_at IS NULL
|
|
7161
7362
|
WHERE t.id = $1
|
|
7162
7363
|
AND t.deleted_at IS NULL
|
|
7163
|
-
AND
|
|
7364
|
+
AND (
|
|
7365
|
+
pa.collaborator_id = $2
|
|
7366
|
+
OR t.project_id IN (
|
|
7367
|
+
SELECT pa2.project_id FROM operations_project_assignment pa2
|
|
7368
|
+
WHERE pa2.collaborator_id = $2 AND pa2.deleted_at IS NULL
|
|
7369
|
+
)
|
|
7370
|
+
)
|
|
7164
7371
|
LIMIT 1`,
|
|
7165
7372
|
taskId,
|
|
7166
7373
|
collaboratorId
|
|
@@ -7168,8 +7375,14 @@ export class OperationsService {
|
|
|
7168
7375
|
id: number;
|
|
7169
7376
|
name: string;
|
|
7170
7377
|
description: string | null;
|
|
7378
|
+
priority: string;
|
|
7171
7379
|
status: string;
|
|
7172
|
-
|
|
7380
|
+
dueDate: string | null;
|
|
7381
|
+
estimateHours: number | null;
|
|
7382
|
+
position: number;
|
|
7383
|
+
tags: string | null;
|
|
7384
|
+
assigneeCollaboratorId: number | null;
|
|
7385
|
+
projectAssignmentId: number | null;
|
|
7173
7386
|
projectId: number;
|
|
7174
7387
|
projectName: string;
|
|
7175
7388
|
projectCode: string | null;
|
|
@@ -7184,12 +7397,127 @@ export class OperationsService {
|
|
|
7184
7397
|
return task[0];
|
|
7185
7398
|
}
|
|
7186
7399
|
|
|
7187
|
-
private async
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7400
|
+
private async getTaskRecordForActor(
|
|
7401
|
+
client: any,
|
|
7402
|
+
actor: ActorContext,
|
|
7403
|
+
taskId: number
|
|
7404
|
+
) {
|
|
7405
|
+
if (actor.collaboratorId && !actor.isDirector && !actor.isSupervisor) {
|
|
7406
|
+
return this.getOwnedTaskRecord(client, actor.collaboratorId, taskId);
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
const task = await this.getProjectBoardTask(taskId);
|
|
7410
|
+
if (!task?.projectId) {
|
|
7411
|
+
throw new NotFoundException('Task not found.');
|
|
7412
|
+
}
|
|
7413
|
+
|
|
7414
|
+
return task;
|
|
7415
|
+
}
|
|
7416
|
+
|
|
7417
|
+
async listProjectBoardTasks(userId: number, projectId: number) {
|
|
7418
|
+
const actor = await this.getActorContext(userId);
|
|
7419
|
+
this.ensureCollaborator(actor);
|
|
7420
|
+
await this.assertProjectAccess(actor, projectId);
|
|
7421
|
+
|
|
7422
|
+
const rows = await this.queryRows<{
|
|
7423
|
+
id: number;
|
|
7424
|
+
name: string;
|
|
7425
|
+
description: string | null;
|
|
7426
|
+
priority: string;
|
|
7427
|
+
status: string;
|
|
7428
|
+
dueDate: string | null;
|
|
7429
|
+
estimateHours: number | null;
|
|
7430
|
+
position: number;
|
|
7431
|
+
tags: string | null;
|
|
7432
|
+
assigneeCollaboratorId: number | null;
|
|
7433
|
+
assigneeName: string | null;
|
|
7434
|
+
assigneeUserPhotoId: number | null;
|
|
7435
|
+
assigneePersonAvatarId: number | null;
|
|
7436
|
+
projectAssignmentId: number | null;
|
|
7437
|
+
createdAt: string;
|
|
7438
|
+
}>(
|
|
7439
|
+
`SELECT t.id,
|
|
7440
|
+
t.name,
|
|
7441
|
+
t.description,
|
|
7442
|
+
t.priority,
|
|
7443
|
+
t.status,
|
|
7444
|
+
t.due_date AS "dueDate",
|
|
7445
|
+
t.estimate_hours AS "estimateHours",
|
|
7446
|
+
t.position,
|
|
7447
|
+
t.tags,
|
|
7448
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
7449
|
+
ac.display_name AS "assigneeName",
|
|
7450
|
+
au.photo_id AS "assigneeUserPhotoId",
|
|
7451
|
+
ap.avatar_id AS "assigneePersonAvatarId",
|
|
7452
|
+
t.project_assignment_id AS "projectAssignmentId",
|
|
7453
|
+
t.created_at AS "createdAt"
|
|
7454
|
+
FROM operations_task t
|
|
7455
|
+
LEFT JOIN operations_collaborator ac
|
|
7456
|
+
ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
|
|
7457
|
+
LEFT JOIN "user" au
|
|
7458
|
+
ON au.id = ac.user_id
|
|
7459
|
+
LEFT JOIN person ap
|
|
7460
|
+
ON ap.id = ac.person_id
|
|
7461
|
+
WHERE COALESCE(t.project_id, (
|
|
7462
|
+
SELECT pa.project_id FROM operations_project_assignment pa
|
|
7463
|
+
WHERE pa.id = t.project_assignment_id AND pa.deleted_at IS NULL
|
|
7464
|
+
LIMIT 1
|
|
7465
|
+
)) = $1
|
|
7466
|
+
AND t.deleted_at IS NULL
|
|
7467
|
+
ORDER BY t.status ASC, t.position ASC, t.id ASC`,
|
|
7468
|
+
[projectId]
|
|
7469
|
+
);
|
|
7470
|
+
|
|
7471
|
+
return rows;
|
|
7472
|
+
}
|
|
7473
|
+
|
|
7474
|
+
private async getProjectBoardTask(taskId: number) {
|
|
7475
|
+
const rows = await this.queryRows<{
|
|
7476
|
+
id: number;
|
|
7477
|
+
name: string;
|
|
7478
|
+
description: string | null;
|
|
7479
|
+
priority: string;
|
|
7480
|
+
status: string;
|
|
7481
|
+
dueDate: string | null;
|
|
7482
|
+
estimateHours: number | null;
|
|
7483
|
+
position: number;
|
|
7484
|
+
tags: string | null;
|
|
7485
|
+
assigneeCollaboratorId: number | null;
|
|
7486
|
+
assigneeName: string | null;
|
|
7487
|
+
assigneeUserPhotoId: number | null;
|
|
7488
|
+
assigneePersonAvatarId: number | null;
|
|
7489
|
+
projectAssignmentId: number | null;
|
|
7490
|
+
projectId: number | null;
|
|
7491
|
+
createdAt: string;
|
|
7492
|
+
}>(
|
|
7493
|
+
`SELECT t.id,
|
|
7494
|
+
t.name,
|
|
7495
|
+
t.description,
|
|
7496
|
+
t.priority,
|
|
7497
|
+
t.status,
|
|
7498
|
+
t.due_date AS "dueDate",
|
|
7499
|
+
t.estimate_hours AS "estimateHours",
|
|
7500
|
+
t.position,
|
|
7501
|
+
t.tags,
|
|
7502
|
+
t.assignee_collaborator_id AS "assigneeCollaboratorId",
|
|
7503
|
+
ac.display_name AS "assigneeName",
|
|
7504
|
+
au.photo_id AS "assigneeUserPhotoId",
|
|
7505
|
+
ap.avatar_id AS "assigneePersonAvatarId",
|
|
7506
|
+
t.project_assignment_id AS "projectAssignmentId",
|
|
7507
|
+
COALESCE(t.project_id, pa.project_id) AS "projectId",
|
|
7508
|
+
t.created_at AS "createdAt"
|
|
7509
|
+
FROM operations_task t
|
|
7510
|
+
LEFT JOIN operations_collaborator ac
|
|
7511
|
+
ON ac.id = t.assignee_collaborator_id AND ac.deleted_at IS NULL
|
|
7512
|
+
LEFT JOIN "user" au ON au.id = ac.user_id
|
|
7513
|
+
LEFT JOIN person ap ON ap.id = ac.person_id
|
|
7514
|
+
LEFT JOIN operations_project_assignment pa
|
|
7515
|
+
ON pa.id = t.project_assignment_id AND pa.deleted_at IS NULL
|
|
7516
|
+
WHERE t.id = $1`,
|
|
7517
|
+
[taskId]
|
|
7518
|
+
);
|
|
7519
|
+
|
|
7520
|
+
return rows[0] ?? null;
|
|
7193
7521
|
}
|
|
7194
7522
|
|
|
7195
7523
|
private async getOrCreateTimesheetForWorkDate(
|
|
@@ -8055,6 +8383,146 @@ export class OperationsService {
|
|
|
8055
8383
|
);
|
|
8056
8384
|
}
|
|
8057
8385
|
|
|
8386
|
+
private async syncHiringContractDraft(
|
|
8387
|
+
client: any,
|
|
8388
|
+
updatedByUserId: number,
|
|
8389
|
+
collaboratorId: number,
|
|
8390
|
+
data: Partial<CollaboratorPayload>
|
|
8391
|
+
) {
|
|
8392
|
+
const collaborator = await client.$queryRawUnsafe(
|
|
8393
|
+
`SELECT c.id,
|
|
8394
|
+
c.code,
|
|
8395
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
8396
|
+
collaborator_type.slug AS "collaboratorTypeSlug",
|
|
8397
|
+
c.supervisor_collaborator_id AS "supervisorCollaboratorId",
|
|
8398
|
+
c.joined_at AS "joinedAt",
|
|
8399
|
+
c.weekly_capacity_hours AS "weeklyCapacityHours"
|
|
8400
|
+
FROM operations_collaborator c
|
|
8401
|
+
LEFT JOIN person person_record
|
|
8402
|
+
ON person_record.id = c.person_id
|
|
8403
|
+
LEFT JOIN operations_collaborator_type collaborator_type
|
|
8404
|
+
ON collaborator_type.id = c.collaborator_type_id
|
|
8405
|
+
AND collaborator_type.deleted_at IS NULL
|
|
8406
|
+
WHERE c.id = $1
|
|
8407
|
+
AND c.deleted_at IS NULL
|
|
8408
|
+
LIMIT 1`,
|
|
8409
|
+
collaboratorId
|
|
8410
|
+
) as Array<{
|
|
8411
|
+
id: number;
|
|
8412
|
+
code: string | null;
|
|
8413
|
+
displayName: string | null;
|
|
8414
|
+
collaboratorTypeSlug: string | null;
|
|
8415
|
+
supervisorCollaboratorId: number | null;
|
|
8416
|
+
joinedAt: string | null;
|
|
8417
|
+
weeklyCapacityHours: number | null;
|
|
8418
|
+
}>;
|
|
8419
|
+
|
|
8420
|
+
const currentCollaborator = collaborator[0] ?? null;
|
|
8421
|
+
|
|
8422
|
+
if (!currentCollaborator) {
|
|
8423
|
+
throw new NotFoundException('Collaborator not found.');
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
const hiringContracts = (await client.$queryRawUnsafe(
|
|
8427
|
+
`SELECT id,
|
|
8428
|
+
budget_amount AS "budgetAmount",
|
|
8429
|
+
description
|
|
8430
|
+
FROM operations_contract
|
|
8431
|
+
WHERE related_collaborator_id = $1
|
|
8432
|
+
AND origin_type = 'employee_hiring'
|
|
8433
|
+
AND deleted_at IS NULL
|
|
8434
|
+
ORDER BY created_at DESC
|
|
8435
|
+
LIMIT 1`,
|
|
8436
|
+
collaboratorId
|
|
8437
|
+
)) as Array<{
|
|
8438
|
+
id: number;
|
|
8439
|
+
budgetAmount: number | null;
|
|
8440
|
+
description: string | null;
|
|
8441
|
+
}>;
|
|
8442
|
+
|
|
8443
|
+
const hiringContract = hiringContracts[0] ?? null;
|
|
8444
|
+
const collaboratorCode =
|
|
8445
|
+
this.normalizeOptionalText(currentCollaborator.code) ??
|
|
8446
|
+
`COL-${collaboratorId}`;
|
|
8447
|
+
const displayName =
|
|
8448
|
+
this.normalizeOptionalText(currentCollaborator.displayName) ??
|
|
8449
|
+
`Collaborator ${collaboratorId}`;
|
|
8450
|
+
const collaboratorTypeSlug =
|
|
8451
|
+
this.normalizeOptionalText(currentCollaborator.collaboratorTypeSlug) ??
|
|
8452
|
+
'other';
|
|
8453
|
+
const startDate =
|
|
8454
|
+
data.joinedAt !== undefined
|
|
8455
|
+
? data.joinedAt ?? null
|
|
8456
|
+
: currentCollaborator.joinedAt ?? null;
|
|
8457
|
+
const weeklyCapacityHours =
|
|
8458
|
+
data.weeklyCapacityHours !== undefined
|
|
8459
|
+
? data.weeklyCapacityHours ?? null
|
|
8460
|
+
: currentCollaborator.weeklyCapacityHours ?? null;
|
|
8461
|
+
const compensationAmount =
|
|
8462
|
+
data.compensationAmount !== undefined
|
|
8463
|
+
? data.compensationAmount ?? null
|
|
8464
|
+
: hiringContract?.budgetAmount ?? null;
|
|
8465
|
+
const description =
|
|
8466
|
+
data.contractDescription !== undefined
|
|
8467
|
+
? this.normalizeOptionalText(data.contractDescription)
|
|
8468
|
+
: hiringContract?.description ?? null;
|
|
8469
|
+
const supervisorCollaboratorId =
|
|
8470
|
+
data.supervisorCollaboratorId !== undefined
|
|
8471
|
+
? data.supervisorCollaboratorId ?? null
|
|
8472
|
+
: currentCollaborator.supervisorCollaboratorId ?? null;
|
|
8473
|
+
|
|
8474
|
+
if (!hiringContract) {
|
|
8475
|
+
if (data.autoGenerateContractDraft === false) {
|
|
8476
|
+
return;
|
|
8477
|
+
}
|
|
8478
|
+
|
|
8479
|
+
await this.createHiringContractDraft(client, updatedByUserId, {
|
|
8480
|
+
collaboratorId,
|
|
8481
|
+
collaboratorCode,
|
|
8482
|
+
displayName,
|
|
8483
|
+
collaboratorType: collaboratorTypeSlug,
|
|
8484
|
+
supervisorCollaboratorId,
|
|
8485
|
+
startDate,
|
|
8486
|
+
weeklyCapacityHours,
|
|
8487
|
+
compensationAmount,
|
|
8488
|
+
description,
|
|
8489
|
+
});
|
|
8490
|
+
return;
|
|
8491
|
+
}
|
|
8492
|
+
|
|
8493
|
+
await client.$executeRawUnsafe(
|
|
8494
|
+
`UPDATE operations_contract
|
|
8495
|
+
SET code = $1,
|
|
8496
|
+
name = $2,
|
|
8497
|
+
contract_category = $3::operations_contract_contract_category_70d553ea09_enum,
|
|
8498
|
+
contract_type = $4::operations_contract_contract_type_48331e2ebf_enum,
|
|
8499
|
+
client_name = $5,
|
|
8500
|
+
billing_model = $6::operations_contract_billing_model_409dc7fea2_enum,
|
|
8501
|
+
account_manager_collaborator_id = $7,
|
|
8502
|
+
start_date = $8::date,
|
|
8503
|
+
effective_date = $8::date,
|
|
8504
|
+
budget_amount = $9,
|
|
8505
|
+
monthly_hour_cap = $10,
|
|
8506
|
+
description = $11,
|
|
8507
|
+
updated_by_user_id = $12,
|
|
8508
|
+
updated_at = NOW()
|
|
8509
|
+
WHERE id = $13`,
|
|
8510
|
+
`HIR-${collaboratorCode}`,
|
|
8511
|
+
this.buildHiringContractName(displayName, collaboratorTypeSlug),
|
|
8512
|
+
this.mapContractCategoryForCollaboratorType(collaboratorTypeSlug),
|
|
8513
|
+
this.mapContractTypeForCollaboratorType(collaboratorTypeSlug),
|
|
8514
|
+
displayName,
|
|
8515
|
+
this.mapBillingModelForCollaboratorType(collaboratorTypeSlug),
|
|
8516
|
+
supervisorCollaboratorId,
|
|
8517
|
+
startDate ?? new Date().toISOString().slice(0, 10),
|
|
8518
|
+
compensationAmount,
|
|
8519
|
+
weeklyCapacityHours ? Math.round(Number(weeklyCapacityHours) * 4) : null,
|
|
8520
|
+
description,
|
|
8521
|
+
updatedByUserId,
|
|
8522
|
+
hiringContract.id
|
|
8523
|
+
);
|
|
8524
|
+
}
|
|
8525
|
+
|
|
8058
8526
|
private async createProjectContractDraft(
|
|
8059
8527
|
client: any,
|
|
8060
8528
|
createdByUserId: number,
|