@hed-hog/operations 0.0.300 → 0.0.301
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/operations.controller.d.ts +713 -31
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.controller.js +157 -0
- package/dist/operations.controller.js.map +1 -1
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +5 -1
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.proposal.subscriber.d.ts +11 -0
- package/dist/operations.proposal.subscriber.d.ts.map +1 -0
- package/dist/operations.proposal.subscriber.js +80 -0
- package/dist/operations.proposal.subscriber.js.map +1 -0
- package/dist/operations.proposal.subscriber.spec.d.ts +2 -0
- package/dist/operations.proposal.subscriber.spec.d.ts.map +1 -0
- package/dist/operations.proposal.subscriber.spec.js +88 -0
- package/dist/operations.proposal.subscriber.spec.js.map +1 -0
- package/dist/operations.service.d.ts +490 -46
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +2442 -119
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.d.ts +2 -0
- package/dist/operations.service.spec.d.ts.map +1 -0
- package/dist/operations.service.spec.js +159 -0
- package/dist/operations.service.spec.js.map +1 -0
- package/hedhog/data/menu.yaml +34 -0
- package/hedhog/data/role_route.yaml +39 -0
- package/hedhog/data/route.yaml +130 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +8 -6
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +1163 -327
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -0
- package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +631 -0
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +353 -27
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +1926 -87
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +526 -0
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -0
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -0
- package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +370 -0
- package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +826 -0
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1251 -364
- package/hedhog/frontend/app/_components/section-card.tsx.ejs +48 -13
- package/hedhog/frontend/app/_lib/api.ts.ejs +2 -5
- package/hedhog/frontend/app/_lib/types.ts.ejs +76 -33
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +85 -8
- package/hedhog/frontend/app/approvals/page.tsx.ejs +90 -54
- package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +597 -140
- package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +941 -262
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +384 -0
- package/hedhog/frontend/app/departments/page.tsx.ejs +442 -0
- package/hedhog/frontend/app/page.tsx.ejs +36 -12
- package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/projects/new/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/projects/page.tsx.ejs +264 -102
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +50 -28
- package/hedhog/frontend/app/time-off/page.tsx.ejs +57 -31
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +85 -42
- package/hedhog/frontend/messages/en.json +473 -12
- package/hedhog/frontend/messages/pt.json +528 -66
- package/hedhog/table/operations_collaborator.yaml +20 -0
- package/hedhog/table/operations_contract.yaml +22 -1
- package/hedhog/table/operations_contract_document.yaml +33 -16
- package/hedhog/table/operations_contract_template.yaml +58 -0
- package/hedhog/table/operations_department.yaml +24 -0
- package/package.json +6 -4
- package/src/operations.controller.ts +122 -0
- package/src/operations.module.ts +6 -2
- package/src/operations.proposal.subscriber.spec.ts +121 -0
- package/src/operations.proposal.subscriber.ts +86 -0
- package/src/operations.service.spec.ts +210 -0
- package/src/operations.service.ts +3934 -212
|
@@ -8,6 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
exports.OperationsService = void 0;
|
|
13
16
|
const api_prisma_1 = require("@hed-hog/api-prisma");
|
|
@@ -16,10 +19,98 @@ const common_1 = require("@nestjs/common");
|
|
|
16
19
|
const COLLABORATOR_ROLE = 'admin-operations-collaborator';
|
|
17
20
|
const SUPERVISOR_ROLE = 'admin-operations-supervisor';
|
|
18
21
|
const DIRECTOR_ROLE = 'admin-operations-director';
|
|
22
|
+
const CONTRACT_CATEGORY_VALUES = [
|
|
23
|
+
'employee',
|
|
24
|
+
'contractor',
|
|
25
|
+
'client',
|
|
26
|
+
'supplier',
|
|
27
|
+
'vendor',
|
|
28
|
+
'partner',
|
|
29
|
+
'internal',
|
|
30
|
+
'other',
|
|
31
|
+
];
|
|
32
|
+
const CONTRACT_TYPE_VALUES = [
|
|
33
|
+
'clt',
|
|
34
|
+
'pj',
|
|
35
|
+
'freelancer_agreement',
|
|
36
|
+
'service_agreement',
|
|
37
|
+
'fixed_term',
|
|
38
|
+
'recurring_service',
|
|
39
|
+
'nda',
|
|
40
|
+
'amendment',
|
|
41
|
+
'addendum',
|
|
42
|
+
'other',
|
|
43
|
+
];
|
|
44
|
+
const BILLING_MODEL_VALUES = [
|
|
45
|
+
'time_and_material',
|
|
46
|
+
'monthly_retainer',
|
|
47
|
+
'fixed_price',
|
|
48
|
+
];
|
|
49
|
+
const SIGNATURE_STATUS_VALUES = [
|
|
50
|
+
'not_started',
|
|
51
|
+
'pending',
|
|
52
|
+
'partially_signed',
|
|
53
|
+
'signed',
|
|
54
|
+
'expired',
|
|
55
|
+
];
|
|
56
|
+
const CONTRACT_STATUS_VALUES = [
|
|
57
|
+
'draft',
|
|
58
|
+
'under_review',
|
|
59
|
+
'active',
|
|
60
|
+
'renewal',
|
|
61
|
+
'expired',
|
|
62
|
+
'closed',
|
|
63
|
+
'archived',
|
|
64
|
+
];
|
|
65
|
+
const CONTRACT_CREATION_MODE_VALUES = [
|
|
66
|
+
'blank',
|
|
67
|
+
'template',
|
|
68
|
+
'upload',
|
|
69
|
+
'duplicate',
|
|
70
|
+
];
|
|
71
|
+
const ORIGIN_TYPE_VALUES = [
|
|
72
|
+
'manual',
|
|
73
|
+
'employee_hiring',
|
|
74
|
+
'client_project',
|
|
75
|
+
'crm_proposal',
|
|
76
|
+
];
|
|
77
|
+
const CONTRACT_DOCUMENT_TYPE_VALUES = [
|
|
78
|
+
'source_upload',
|
|
79
|
+
'generated_pdf',
|
|
80
|
+
'attachment',
|
|
81
|
+
'other',
|
|
82
|
+
];
|
|
83
|
+
const CONTRACT_DOCUMENT_EXTRACTION_STATUS_VALUES = [
|
|
84
|
+
'pending',
|
|
85
|
+
'processing',
|
|
86
|
+
'completed',
|
|
87
|
+
'failed',
|
|
88
|
+
'skipped',
|
|
89
|
+
];
|
|
90
|
+
const PARTY_ROLE_VALUES = [
|
|
91
|
+
'employee',
|
|
92
|
+
'employer',
|
|
93
|
+
'client',
|
|
94
|
+
'supplier',
|
|
95
|
+
'vendor',
|
|
96
|
+
'partner',
|
|
97
|
+
'witness',
|
|
98
|
+
'internal_owner',
|
|
99
|
+
'other',
|
|
100
|
+
];
|
|
101
|
+
const PARTY_TYPE_VALUES = ['individual', 'company', 'internal_team', 'other'];
|
|
102
|
+
const SIGNATURE_ITEM_STATUS_VALUES = ['pending', 'signed', 'rejected'];
|
|
103
|
+
const FINANCIAL_TERM_TYPE_VALUES = ['value', 'payment', 'revenue', 'fine', 'other'];
|
|
104
|
+
const RECURRENCE_VALUES = ['one_time', 'monthly', 'quarterly', 'yearly', 'other'];
|
|
105
|
+
const REVISION_TYPE_VALUES = ['amendment', 'renewal', 'revision', 'addendum', 'other'];
|
|
106
|
+
const REVISION_STATUS_VALUES = ['draft', 'active', 'completed', 'cancelled'];
|
|
19
107
|
let OperationsService = class OperationsService {
|
|
20
|
-
constructor(prisma, integrationApi) {
|
|
108
|
+
constructor(prisma, aiService, integrationApi, fileService, settingService) {
|
|
21
109
|
this.prisma = prisma;
|
|
110
|
+
this.aiService = aiService;
|
|
22
111
|
this.integrationApi = integrationApi;
|
|
112
|
+
this.fileService = fileService;
|
|
113
|
+
this.settingService = settingService;
|
|
23
114
|
}
|
|
24
115
|
async getDashboard(userId) {
|
|
25
116
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -97,10 +188,14 @@ let OperationsService = class OperationsService {
|
|
|
97
188
|
const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 'c.id', actor.isDirector);
|
|
98
189
|
return this.queryRows(`SELECT c.id,
|
|
99
190
|
c.user_id AS "userId",
|
|
191
|
+
c.person_id AS "personId",
|
|
100
192
|
c.code,
|
|
101
193
|
c.collaborator_type AS "collaboratorType",
|
|
102
|
-
c.display_name AS "displayName",
|
|
103
|
-
|
|
194
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
195
|
+
person_record.name AS "personName",
|
|
196
|
+
person_record.avatar_id AS "personAvatarId",
|
|
197
|
+
c.department_id AS "departmentId",
|
|
198
|
+
COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
|
|
104
199
|
c.title,
|
|
105
200
|
c.level_label AS "levelLabel",
|
|
106
201
|
c.weekly_capacity_hours AS "weeklyCapacityHours",
|
|
@@ -114,6 +209,11 @@ let OperationsService = class OperationsService {
|
|
|
114
209
|
hiring_contract.status AS "contractStatus",
|
|
115
210
|
COUNT(DISTINCT pa.id)::int AS "activeAssignments"
|
|
116
211
|
FROM operations_collaborator c
|
|
212
|
+
LEFT JOIN person person_record
|
|
213
|
+
ON person_record.id = c.person_id
|
|
214
|
+
LEFT JOIN operations_department department_record
|
|
215
|
+
ON department_record.id = c.department_id
|
|
216
|
+
AND department_record.deleted_at IS NULL
|
|
117
217
|
LEFT JOIN operations_collaborator s
|
|
118
218
|
ON s.id = c.supervisor_collaborator_id
|
|
119
219
|
LEFT JOIN operations_project_assignment pa
|
|
@@ -131,8 +231,8 @@ let OperationsService = class OperationsService {
|
|
|
131
231
|
) hiring_contract ON TRUE
|
|
132
232
|
WHERE c.deleted_at IS NULL
|
|
133
233
|
AND ${filter.clause}
|
|
134
|
-
GROUP BY c.id, s.id, hiring_contract.id, hiring_contract.status
|
|
135
|
-
ORDER BY c.display_name ASC`, filter.params);
|
|
234
|
+
GROUP BY c.id, person_record.id, department_record.id, s.id, hiring_contract.id, hiring_contract.status
|
|
235
|
+
ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, filter.params);
|
|
136
236
|
}
|
|
137
237
|
async getMyCollaborator(userId) {
|
|
138
238
|
const actor = await this.getActorContext(userId);
|
|
@@ -169,10 +269,14 @@ let OperationsService = class OperationsService {
|
|
|
169
269
|
const teamFilter = this.buildIdFilter(actor.teamCollaboratorIds, 'c.id', false);
|
|
170
270
|
const teamMembers = await this.queryRows(`SELECT c.id,
|
|
171
271
|
c.user_id AS "userId",
|
|
272
|
+
c.person_id AS "personId",
|
|
172
273
|
c.code,
|
|
173
|
-
c.display_name AS "displayName",
|
|
274
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
275
|
+
person_record.name AS "personName",
|
|
276
|
+
person_record.avatar_id AS "personAvatarId",
|
|
174
277
|
c.collaborator_type AS "collaboratorType",
|
|
175
|
-
c.
|
|
278
|
+
c.department_id AS "departmentId",
|
|
279
|
+
COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
|
|
176
280
|
c.title,
|
|
177
281
|
c.status,
|
|
178
282
|
COUNT(DISTINCT pa.id)::int AS "activeAssignments",
|
|
@@ -180,6 +284,11 @@ let OperationsService = class OperationsService {
|
|
|
180
284
|
COUNT(DISTINCT tor.id) FILTER (WHERE tor.status = 'submitted')::int AS "pendingTimeOffRequests",
|
|
181
285
|
COUNT(DISTINCT sar.id) FILTER (WHERE sar.status = 'submitted')::int AS "pendingScheduleAdjustmentRequests"
|
|
182
286
|
FROM operations_collaborator c
|
|
287
|
+
LEFT JOIN person person_record
|
|
288
|
+
ON person_record.id = c.person_id
|
|
289
|
+
LEFT JOIN operations_department department_record
|
|
290
|
+
ON department_record.id = c.department_id
|
|
291
|
+
AND department_record.deleted_at IS NULL
|
|
183
292
|
LEFT JOIN operations_project_assignment pa
|
|
184
293
|
ON pa.collaborator_id = c.id
|
|
185
294
|
AND pa.deleted_at IS NULL
|
|
@@ -197,8 +306,8 @@ let OperationsService = class OperationsService {
|
|
|
197
306
|
AND sar.deleted_at IS NULL
|
|
198
307
|
AND sar.status = 'submitted'
|
|
199
308
|
WHERE c.deleted_at IS NULL AND ${teamFilter.clause}
|
|
200
|
-
GROUP BY c.id
|
|
201
|
-
ORDER BY c.display_name ASC`, teamFilter.params);
|
|
309
|
+
GROUP BY c.id, person_record.id, department_record.id
|
|
310
|
+
ORDER BY COALESCE(NULLIF(c.display_name, ''), person_record.name) ASC`, teamFilter.params);
|
|
202
311
|
const [teamProjects, pendingApprovalQueue, pendingTimeOffRequests, pendingScheduleAdjustmentRequests] = await Promise.all([
|
|
203
312
|
this.queryRows(`SELECT p.id,
|
|
204
313
|
p.code,
|
|
@@ -341,18 +450,33 @@ let OperationsService = class OperationsService {
|
|
|
341
450
|
};
|
|
342
451
|
}
|
|
343
452
|
async createCollaborator(userId, data) {
|
|
453
|
+
var _a, _b, _c, _d;
|
|
344
454
|
const actor = await this.getActorContext(userId);
|
|
345
455
|
this.ensureDirector(actor);
|
|
346
|
-
|
|
456
|
+
const resolvedPerson = data.personId
|
|
457
|
+
? await this.getPersonById(Number(data.personId))
|
|
458
|
+
: null;
|
|
459
|
+
const resolvedDisplayName = (_d = (_b = (_a = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = data.displayName) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : '';
|
|
460
|
+
if (!resolvedDisplayName) {
|
|
461
|
+
throw new common_1.BadRequestException('Field "personId" is required.');
|
|
462
|
+
}
|
|
347
463
|
const collaboratorId = await this.prisma.$transaction(async (tx) => {
|
|
348
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
464
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
465
|
+
const normalizedCode = String((_a = data.code) !== null && _a !== void 0 ? _a : '').trim() ||
|
|
466
|
+
(await this.generateCollaboratorCode(tx));
|
|
467
|
+
const resolvedDepartment = await this.resolveDepartmentReference(tx, {
|
|
468
|
+
departmentId: (_b = data.departmentId) !== null && _b !== void 0 ? _b : null,
|
|
469
|
+
departmentName: data.department,
|
|
470
|
+
});
|
|
349
471
|
const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_collaborator (
|
|
350
472
|
user_id,
|
|
473
|
+
person_id,
|
|
351
474
|
supervisor_collaborator_id,
|
|
352
475
|
code,
|
|
353
476
|
collaborator_type,
|
|
354
477
|
display_name,
|
|
355
478
|
department,
|
|
479
|
+
department_id,
|
|
356
480
|
title,
|
|
357
481
|
level_label,
|
|
358
482
|
weekly_capacity_hours,
|
|
@@ -363,23 +487,26 @@ let OperationsService = class OperationsService {
|
|
|
363
487
|
created_at,
|
|
364
488
|
updated_at
|
|
365
489
|
) VALUES (
|
|
366
|
-
$1, $2, $3,
|
|
367
|
-
|
|
490
|
+
$1, $2, $3, $4,
|
|
491
|
+
$5::operations_collaborator_collaborator_type_7dd7b0ada2_enum,
|
|
492
|
+
$6, $7, $8, $9, $10, $11,
|
|
493
|
+
$12::operations_collaborator_status_ef779877d4_enum,
|
|
494
|
+
$13::date, $14::date, $15, NOW(), NOW()
|
|
368
495
|
)
|
|
369
|
-
RETURNING id`, data.userId, (
|
|
370
|
-
const createdCollaboratorId = (
|
|
496
|
+
RETURNING id`, (_c = data.userId) !== null && _c !== void 0 ? _c : null, (_d = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.id) !== null && _d !== void 0 ? _d : null, (_e = data.supervisorCollaboratorId) !== null && _e !== void 0 ? _e : null, normalizedCode, (_f = data.collaboratorType) !== null && _f !== void 0 ? _f : 'other', resolvedDisplayName, (_g = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _g !== void 0 ? _g : null, (_h = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _h !== void 0 ? _h : null, (_j = data.title) !== null && _j !== void 0 ? _j : null, (_k = data.levelLabel) !== null && _k !== void 0 ? _k : null, (_l = data.weeklyCapacityHours) !== null && _l !== void 0 ? _l : null, (_m = data.status) !== null && _m !== void 0 ? _m : 'active', (_o = data.joinedAt) !== null && _o !== void 0 ? _o : null, (_p = data.leftAt) !== null && _p !== void 0 ? _p : null, (_q = data.notes) !== null && _q !== void 0 ? _q : null));
|
|
497
|
+
const createdCollaboratorId = (_r = created[0]) === null || _r === void 0 ? void 0 : _r.id;
|
|
371
498
|
await this.replaceCollaboratorScheduleDays(tx, createdCollaboratorId, data.weeklySchedule);
|
|
372
499
|
if (data.autoGenerateContractDraft !== false) {
|
|
373
500
|
await this.createHiringContractDraft(tx, actor.userId, {
|
|
374
501
|
collaboratorId: createdCollaboratorId,
|
|
375
|
-
collaboratorCode:
|
|
376
|
-
displayName:
|
|
377
|
-
collaboratorType: (
|
|
378
|
-
supervisorCollaboratorId: (
|
|
379
|
-
startDate: (
|
|
380
|
-
weeklyCapacityHours: (
|
|
381
|
-
compensationAmount: (
|
|
382
|
-
description: (
|
|
502
|
+
collaboratorCode: normalizedCode,
|
|
503
|
+
displayName: resolvedDisplayName,
|
|
504
|
+
collaboratorType: (_s = data.collaboratorType) !== null && _s !== void 0 ? _s : 'other',
|
|
505
|
+
supervisorCollaboratorId: (_t = data.supervisorCollaboratorId) !== null && _t !== void 0 ? _t : null,
|
|
506
|
+
startDate: (_u = data.joinedAt) !== null && _u !== void 0 ? _u : null,
|
|
507
|
+
weeklyCapacityHours: (_v = data.weeklyCapacityHours) !== null && _v !== void 0 ? _v : null,
|
|
508
|
+
compensationAmount: (_w = data.compensationAmount) !== null && _w !== void 0 ? _w : null,
|
|
509
|
+
description: (_y = (_x = data.contractDescription) !== null && _x !== void 0 ? _x : data.notes) !== null && _y !== void 0 ? _y : null,
|
|
383
510
|
});
|
|
384
511
|
}
|
|
385
512
|
return createdCollaboratorId;
|
|
@@ -387,24 +514,41 @@ let OperationsService = class OperationsService {
|
|
|
387
514
|
return this.getCollaboratorByIdForUser(userId, collaboratorId);
|
|
388
515
|
}
|
|
389
516
|
async updateCollaborator(userId, collaboratorId, data) {
|
|
517
|
+
var _a, _b, _c, _d, _e;
|
|
390
518
|
const actor = await this.getActorContext(userId);
|
|
391
519
|
this.ensureDirector(actor);
|
|
392
520
|
await this.getCollaboratorById(collaboratorId);
|
|
393
521
|
const updates = [];
|
|
394
522
|
const params = [];
|
|
523
|
+
const resolvedPerson = data.personId !== undefined && data.personId !== null
|
|
524
|
+
? await this.getPersonById(Number(data.personId))
|
|
525
|
+
: null;
|
|
526
|
+
const resolvedDisplayName = (_d = (_b = (_a = resolvedPerson === null || resolvedPerson === void 0 ? void 0 : resolvedPerson.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : (_c = data.displayName) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : undefined;
|
|
527
|
+
this.pushUpdate(updates, params, 'user_id', data.userId);
|
|
528
|
+
this.pushUpdate(updates, params, 'person_id', data.personId);
|
|
395
529
|
this.pushUpdate(updates, params, 'supervisor_collaborator_id', data.supervisorCollaboratorId);
|
|
396
|
-
this.pushUpdate(updates, params, 'code', data.code);
|
|
397
|
-
this.pushUpdate(updates, params, 'collaborator_type', data.collaboratorType);
|
|
398
|
-
|
|
399
|
-
|
|
530
|
+
this.pushUpdate(updates, params, 'code', (_e = data.code) === null || _e === void 0 ? void 0 : _e.trim());
|
|
531
|
+
this.pushUpdate(updates, params, 'collaborator_type', data.collaboratorType, 'operations_collaborator_collaborator_type_7dd7b0ada2_enum');
|
|
532
|
+
if (data.personId !== undefined || data.displayName !== undefined) {
|
|
533
|
+
this.pushUpdate(updates, params, 'display_name', resolvedDisplayName !== null && resolvedDisplayName !== void 0 ? resolvedDisplayName : null);
|
|
534
|
+
}
|
|
400
535
|
this.pushUpdate(updates, params, 'title', data.title);
|
|
401
536
|
this.pushUpdate(updates, params, 'level_label', data.levelLabel);
|
|
402
537
|
this.pushUpdate(updates, params, 'weekly_capacity_hours', data.weeklyCapacityHours);
|
|
403
|
-
this.pushUpdate(updates, params, 'status', data.status);
|
|
404
|
-
this.pushUpdate(updates, params, 'joined_at', data.joinedAt);
|
|
405
|
-
this.pushUpdate(updates, params, 'left_at', data.leftAt);
|
|
538
|
+
this.pushUpdate(updates, params, 'status', data.status, 'operations_collaborator_status_ef779877d4_enum');
|
|
539
|
+
this.pushUpdate(updates, params, 'joined_at', data.joinedAt, 'date');
|
|
540
|
+
this.pushUpdate(updates, params, 'left_at', data.leftAt, 'date');
|
|
406
541
|
this.pushUpdate(updates, params, 'notes', data.notes);
|
|
407
542
|
await this.prisma.$transaction(async (tx) => {
|
|
543
|
+
var _a, _b, _c;
|
|
544
|
+
if (data.department !== undefined || data.departmentId !== undefined) {
|
|
545
|
+
const resolvedDepartment = await this.resolveDepartmentReference(tx, {
|
|
546
|
+
departmentId: (_a = data.departmentId) !== null && _a !== void 0 ? _a : null,
|
|
547
|
+
departmentName: data.department,
|
|
548
|
+
});
|
|
549
|
+
this.pushUpdate(updates, params, 'department', (_b = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.name) !== null && _b !== void 0 ? _b : null);
|
|
550
|
+
this.pushUpdate(updates, params, 'department_id', (_c = resolvedDepartment === null || resolvedDepartment === void 0 ? void 0 : resolvedDepartment.id) !== null && _c !== void 0 ? _c : null);
|
|
551
|
+
}
|
|
408
552
|
if (updates.length) {
|
|
409
553
|
params.push(collaboratorId);
|
|
410
554
|
await tx.$executeRawUnsafe(`UPDATE operations_collaborator
|
|
@@ -418,6 +562,109 @@ let OperationsService = class OperationsService {
|
|
|
418
562
|
});
|
|
419
563
|
return this.getCollaboratorByIdForUser(userId, collaboratorId);
|
|
420
564
|
}
|
|
565
|
+
async listDepartments(userId) {
|
|
566
|
+
const actor = await this.getActorContext(userId);
|
|
567
|
+
this.ensureCollaborator(actor);
|
|
568
|
+
return this.queryRows(`SELECT d.id,
|
|
569
|
+
d.slug,
|
|
570
|
+
d.code,
|
|
571
|
+
d.name,
|
|
572
|
+
d.description,
|
|
573
|
+
CASE WHEN d.deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status,
|
|
574
|
+
COUNT(DISTINCT c.id)::int AS "collaboratorCount",
|
|
575
|
+
d.created_at AS "createdAt",
|
|
576
|
+
d.updated_at AS "updatedAt"
|
|
577
|
+
FROM operations_department d
|
|
578
|
+
LEFT JOIN operations_collaborator c
|
|
579
|
+
ON c.deleted_at IS NULL
|
|
580
|
+
AND (
|
|
581
|
+
c.department_id = d.id
|
|
582
|
+
OR (
|
|
583
|
+
c.department_id IS NULL
|
|
584
|
+
AND LOWER(COALESCE(c.department, '')) = LOWER(d.name)
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
GROUP BY d.id
|
|
588
|
+
ORDER BY CASE WHEN d.deleted_at IS NULL THEN 0 ELSE 1 END ASC,
|
|
589
|
+
d.name ASC`);
|
|
590
|
+
}
|
|
591
|
+
async createDepartment(userId, data) {
|
|
592
|
+
const actor = await this.getActorContext(userId);
|
|
593
|
+
this.ensureDirector(actor);
|
|
594
|
+
const name = this.normalizeOptionalText(data.name);
|
|
595
|
+
if (!name) {
|
|
596
|
+
throw new common_1.BadRequestException('Department name is required.');
|
|
597
|
+
}
|
|
598
|
+
return this.prisma.$transaction(async (tx) => {
|
|
599
|
+
var _a, _b, _c, _d;
|
|
600
|
+
await this.assertDepartmentNameAvailable(tx, name);
|
|
601
|
+
const normalizedCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null;
|
|
602
|
+
if (normalizedCode) {
|
|
603
|
+
await this.assertDepartmentCodeAvailable(tx, normalizedCode);
|
|
604
|
+
}
|
|
605
|
+
const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_department (
|
|
606
|
+
slug,
|
|
607
|
+
code,
|
|
608
|
+
name,
|
|
609
|
+
description,
|
|
610
|
+
deleted_at,
|
|
611
|
+
created_at,
|
|
612
|
+
updated_at
|
|
613
|
+
) VALUES (
|
|
614
|
+
$1, $2, $3, $4,
|
|
615
|
+
CASE WHEN $5 = 'inactive' THEN NOW() ELSE NULL END,
|
|
616
|
+
NOW(), NOW()
|
|
617
|
+
)
|
|
618
|
+
RETURNING id`, await this.generateUniqueDepartmentSlug(tx, name), normalizedCode, name, this.normalizeOptionalText(data.description), (_c = data.status) !== null && _c !== void 0 ? _c : 'active'));
|
|
619
|
+
const createdDepartmentId = (_d = created[0]) === null || _d === void 0 ? void 0 : _d.id;
|
|
620
|
+
if (!createdDepartmentId) {
|
|
621
|
+
throw new common_1.BadRequestException('Unable to create the department.');
|
|
622
|
+
}
|
|
623
|
+
return this.getDepartmentById(tx, createdDepartmentId, true);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
async updateDepartment(userId, departmentId, data) {
|
|
627
|
+
const actor = await this.getActorContext(userId);
|
|
628
|
+
this.ensureDirector(actor);
|
|
629
|
+
return this.prisma.$transaction(async (tx) => {
|
|
630
|
+
var _a, _b, _c, _d, _e, _f;
|
|
631
|
+
const current = await this.getDepartmentById(tx, departmentId, true);
|
|
632
|
+
const nextName = data.name !== undefined
|
|
633
|
+
? this.normalizeOptionalText(data.name)
|
|
634
|
+
: current.name;
|
|
635
|
+
if (!nextName) {
|
|
636
|
+
throw new common_1.BadRequestException('Department name is required.');
|
|
637
|
+
}
|
|
638
|
+
if (String(nextName).toLowerCase() !== String(current.name).toLowerCase()) {
|
|
639
|
+
await this.assertDepartmentNameAvailable(tx, nextName, departmentId);
|
|
640
|
+
}
|
|
641
|
+
const nextCode = data.code !== undefined
|
|
642
|
+
? (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null
|
|
643
|
+
: ((_c = current.code) !== null && _c !== void 0 ? _c : null);
|
|
644
|
+
if (nextCode) {
|
|
645
|
+
await this.assertDepartmentCodeAvailable(tx, nextCode, departmentId);
|
|
646
|
+
}
|
|
647
|
+
const nextDescription = data.description !== undefined
|
|
648
|
+
? this.normalizeOptionalText(data.description)
|
|
649
|
+
: ((_d = current.description) !== null && _d !== void 0 ? _d : null);
|
|
650
|
+
const nextStatus = (_f = (_e = data.status) !== null && _e !== void 0 ? _e : current.status) !== null && _f !== void 0 ? _f : 'active';
|
|
651
|
+
const nextSlug = String(nextName).toLowerCase() !== String(current.name).toLowerCase()
|
|
652
|
+
? await this.generateUniqueDepartmentSlug(tx, nextName, departmentId)
|
|
653
|
+
: current.slug;
|
|
654
|
+
await tx.$executeRawUnsafe(`UPDATE operations_department
|
|
655
|
+
SET slug = $1,
|
|
656
|
+
code = $2,
|
|
657
|
+
name = $3,
|
|
658
|
+
description = $4,
|
|
659
|
+
deleted_at = CASE
|
|
660
|
+
WHEN $5 = 'inactive' THEN COALESCE(deleted_at, NOW())
|
|
661
|
+
ELSE NULL
|
|
662
|
+
END,
|
|
663
|
+
updated_at = NOW()
|
|
664
|
+
WHERE id = $6`, nextSlug, nextCode, nextName, nextDescription, nextStatus, departmentId);
|
|
665
|
+
return this.getDepartmentById(tx, departmentId, true);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
421
668
|
async listProjects(userId) {
|
|
422
669
|
const actor = await this.getActorContext(userId);
|
|
423
670
|
const filter = this.buildIdFilter(actor.visibleProjectIds, 'p.id', actor.isDirector);
|
|
@@ -466,7 +713,7 @@ let OperationsService = class OperationsService {
|
|
|
466
713
|
this.ensureDirector(actor);
|
|
467
714
|
this.requireFields(data, ['code', 'name']);
|
|
468
715
|
const createdProjectId = await this.prisma.$transaction(async (tx) => {
|
|
469
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
716
|
+
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;
|
|
470
717
|
const created = await tx.$queryRawUnsafe(`INSERT INTO operations_project (
|
|
471
718
|
contract_id,
|
|
472
719
|
manager_collaborator_id,
|
|
@@ -483,8 +730,11 @@ let OperationsService = class OperationsService {
|
|
|
483
730
|
created_at,
|
|
484
731
|
updated_at
|
|
485
732
|
) VALUES (
|
|
486
|
-
$1, $2, $3, $4, $5, $6,
|
|
487
|
-
|
|
733
|
+
$1, $2, $3, $4, $5, $6,
|
|
734
|
+
$7::operations_project_status_965e8d4b2d_enum,
|
|
735
|
+
$8,
|
|
736
|
+
$9::operations_project_delivery_model_75ee11b3b7_enum,
|
|
737
|
+
$10, $11::date, $12::date, NOW(), NOW()
|
|
488
738
|
)
|
|
489
739
|
RETURNING id`, (_a = data.contractId) !== null && _a !== void 0 ? _a : null, (_b = data.managerCollaboratorId) !== null && _b !== void 0 ? _b : null, data.code, data.name, (_c = data.clientName) !== null && _c !== void 0 ? _c : null, (_d = data.summary) !== null && _d !== void 0 ? _d : null, (_e = data.status) !== null && _e !== void 0 ? _e : 'planning', (_f = data.progressPercent) !== null && _f !== void 0 ? _f : null, (_g = data.deliveryModel) !== null && _g !== void 0 ? _g : 'project_delivery', (_h = data.budgetAmount) !== null && _h !== void 0 ? _h : null, (_j = data.startDate) !== null && _j !== void 0 ? _j : null, (_k = data.endDate) !== null && _k !== void 0 ? _k : null);
|
|
490
740
|
const projectId = (_l = created[0]) === null || _l === void 0 ? void 0 : _l.id;
|
|
@@ -494,18 +744,19 @@ let OperationsService = class OperationsService {
|
|
|
494
744
|
if (!data.contractId && data.autoGenerateContractDraft !== false) {
|
|
495
745
|
const contractId = await this.createProjectContractDraft(tx, actor.userId, {
|
|
496
746
|
projectId,
|
|
747
|
+
contractTemplateId: (_o = data.contractTemplateId) !== null && _o !== void 0 ? _o : null,
|
|
497
748
|
projectCode: data.code,
|
|
498
749
|
projectName: data.name,
|
|
499
|
-
clientName: (
|
|
500
|
-
managerCollaboratorId: (
|
|
501
|
-
startDate: (
|
|
502
|
-
endDate: (
|
|
503
|
-
budgetAmount: (
|
|
504
|
-
monthlyHourCap: (
|
|
505
|
-
billingModel: (
|
|
506
|
-
contractCode: (
|
|
507
|
-
contractName: (
|
|
508
|
-
description: (
|
|
750
|
+
clientName: (_p = data.clientName) !== null && _p !== void 0 ? _p : data.name,
|
|
751
|
+
managerCollaboratorId: (_q = data.managerCollaboratorId) !== null && _q !== void 0 ? _q : null,
|
|
752
|
+
startDate: (_r = data.startDate) !== null && _r !== void 0 ? _r : null,
|
|
753
|
+
endDate: (_s = data.endDate) !== null && _s !== void 0 ? _s : null,
|
|
754
|
+
budgetAmount: (_t = data.budgetAmount) !== null && _t !== void 0 ? _t : null,
|
|
755
|
+
monthlyHourCap: (_u = data.monthlyHourCap) !== null && _u !== void 0 ? _u : null,
|
|
756
|
+
billingModel: (_v = data.billingModel) !== null && _v !== void 0 ? _v : 'time_and_material',
|
|
757
|
+
contractCode: (_w = data.contractCode) !== null && _w !== void 0 ? _w : null,
|
|
758
|
+
contractName: (_x = data.contractName) !== null && _x !== void 0 ? _x : null,
|
|
759
|
+
description: (_z = (_y = data.contractDescription) !== null && _y !== void 0 ? _y : data.summary) !== null && _z !== void 0 ? _z : null,
|
|
509
760
|
});
|
|
510
761
|
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
511
762
|
SET contract_id = $1,
|
|
@@ -519,7 +770,7 @@ let OperationsService = class OperationsService {
|
|
|
519
770
|
async updateProject(userId, projectId, data) {
|
|
520
771
|
const actor = await this.getActorContext(userId);
|
|
521
772
|
this.ensureDirector(actor);
|
|
522
|
-
await this.getProjectById(userId, projectId);
|
|
773
|
+
const currentProject = await this.getProjectById(userId, projectId);
|
|
523
774
|
const updates = [];
|
|
524
775
|
const params = [];
|
|
525
776
|
this.pushUpdate(updates, params, 'contract_id', data.contractId);
|
|
@@ -528,13 +779,14 @@ let OperationsService = class OperationsService {
|
|
|
528
779
|
this.pushUpdate(updates, params, 'name', data.name);
|
|
529
780
|
this.pushUpdate(updates, params, 'client_name', data.clientName);
|
|
530
781
|
this.pushUpdate(updates, params, 'summary', data.summary);
|
|
531
|
-
this.pushUpdate(updates, params, 'status', data.status);
|
|
782
|
+
this.pushUpdate(updates, params, 'status', data.status, 'operations_project_status_965e8d4b2d_enum');
|
|
532
783
|
this.pushUpdate(updates, params, 'progress_percent', data.progressPercent);
|
|
533
|
-
this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel);
|
|
784
|
+
this.pushUpdate(updates, params, 'delivery_model', data.deliveryModel, 'operations_project_delivery_model_75ee11b3b7_enum');
|
|
534
785
|
this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
|
|
535
|
-
this.pushUpdate(updates, params, 'start_date', data.startDate);
|
|
536
|
-
this.pushUpdate(updates, params, 'end_date', data.endDate);
|
|
786
|
+
this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
|
|
787
|
+
this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
|
|
537
788
|
await this.prisma.$transaction(async (tx) => {
|
|
789
|
+
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;
|
|
538
790
|
if (updates.length) {
|
|
539
791
|
params.push(projectId);
|
|
540
792
|
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
@@ -545,9 +797,162 @@ let OperationsService = class OperationsService {
|
|
|
545
797
|
if (data.teamAssignments) {
|
|
546
798
|
await this.replaceProjectAssignments(tx, projectId, data.teamAssignments);
|
|
547
799
|
}
|
|
800
|
+
const nextContractId = data.contractId !== undefined
|
|
801
|
+
? data.contractId
|
|
802
|
+
: ((_a = currentProject.contractId) !== null && _a !== void 0 ? _a : null);
|
|
803
|
+
const shouldGenerateDraft = !nextContractId && data.autoGenerateContractDraft === true;
|
|
804
|
+
if (shouldGenerateDraft) {
|
|
805
|
+
const contractId = await this.createProjectContractDraft(tx, actor.userId, {
|
|
806
|
+
projectId,
|
|
807
|
+
contractTemplateId: (_b = data.contractTemplateId) !== null && _b !== void 0 ? _b : null,
|
|
808
|
+
projectCode: (_c = data.code) !== null && _c !== void 0 ? _c : currentProject.code,
|
|
809
|
+
projectName: (_d = data.name) !== null && _d !== void 0 ? _d : currentProject.name,
|
|
810
|
+
clientName: (_f = (_e = data.clientName) !== null && _e !== void 0 ? _e : currentProject.clientName) !== null && _f !== void 0 ? _f : currentProject.name,
|
|
811
|
+
managerCollaboratorId: (_h = (_g = data.managerCollaboratorId) !== null && _g !== void 0 ? _g : currentProject.managerCollaboratorId) !== null && _h !== void 0 ? _h : null,
|
|
812
|
+
startDate: (_k = (_j = data.startDate) !== null && _j !== void 0 ? _j : currentProject.startDate) !== null && _k !== void 0 ? _k : null,
|
|
813
|
+
endDate: (_m = (_l = data.endDate) !== null && _l !== void 0 ? _l : currentProject.endDate) !== null && _m !== void 0 ? _m : null,
|
|
814
|
+
budgetAmount: (_p = (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : currentProject.budgetAmount) !== null && _p !== void 0 ? _p : null,
|
|
815
|
+
monthlyHourCap: (_s = (_q = data.monthlyHourCap) !== null && _q !== void 0 ? _q : (_r = currentProject.relatedContract) === null || _r === void 0 ? void 0 : _r.monthlyHourCap) !== null && _s !== void 0 ? _s : null,
|
|
816
|
+
billingModel: (_v = (_t = data.billingModel) !== null && _t !== void 0 ? _t : (_u = currentProject.relatedContract) === null || _u === void 0 ? void 0 : _u.billingModel) !== null && _v !== void 0 ? _v : 'time_and_material',
|
|
817
|
+
contractCode: (_y = (_w = data.contractCode) !== null && _w !== void 0 ? _w : (_x = currentProject.relatedContract) === null || _x === void 0 ? void 0 : _x.code) !== null && _y !== void 0 ? _y : null,
|
|
818
|
+
contractName: (_1 = (_z = data.contractName) !== null && _z !== void 0 ? _z : (_0 = currentProject.relatedContract) === null || _0 === void 0 ? void 0 : _0.name) !== null && _1 !== void 0 ? _1 : null,
|
|
819
|
+
description: (_6 = (_5 = (_4 = (_2 = data.contractDescription) !== null && _2 !== void 0 ? _2 : (_3 = currentProject.relatedContract) === null || _3 === void 0 ? void 0 : _3.description) !== null && _4 !== void 0 ? _4 : data.summary) !== null && _5 !== void 0 ? _5 : currentProject.summary) !== null && _6 !== void 0 ? _6 : null,
|
|
820
|
+
});
|
|
821
|
+
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
822
|
+
SET contract_id = $1,
|
|
823
|
+
updated_at = NOW()
|
|
824
|
+
WHERE id = $2`, contractId, projectId);
|
|
825
|
+
}
|
|
548
826
|
});
|
|
549
827
|
return this.getProjectById(userId, projectId);
|
|
550
828
|
}
|
|
829
|
+
async listContractTemplates(userId) {
|
|
830
|
+
const actor = await this.getActorContext(userId);
|
|
831
|
+
this.ensureDirector(actor);
|
|
832
|
+
return this.queryRows(`SELECT t.id,
|
|
833
|
+
t.slug,
|
|
834
|
+
t.code,
|
|
835
|
+
t.name,
|
|
836
|
+
t.description,
|
|
837
|
+
t.contract_category AS "contractCategory",
|
|
838
|
+
t.contract_type AS "contractType",
|
|
839
|
+
t.billing_model AS "billingModel",
|
|
840
|
+
t.signature_status AS "signatureStatus",
|
|
841
|
+
t.is_active AS "isActive",
|
|
842
|
+
t.status,
|
|
843
|
+
t.content_html AS "contentHtml",
|
|
844
|
+
t.created_at AS "createdAt",
|
|
845
|
+
t.updated_at AS "updatedAt",
|
|
846
|
+
COUNT(DISTINCT c.id)::int AS "usageCount"
|
|
847
|
+
FROM operations_contract_template t
|
|
848
|
+
LEFT JOIN operations_contract c
|
|
849
|
+
ON c.contract_template_id = t.id
|
|
850
|
+
AND c.deleted_at IS NULL
|
|
851
|
+
WHERE t.deleted_at IS NULL
|
|
852
|
+
GROUP BY t.id
|
|
853
|
+
ORDER BY CASE
|
|
854
|
+
WHEN t.status = 'active' THEN 0
|
|
855
|
+
WHEN t.status = 'draft' THEN 1
|
|
856
|
+
WHEN t.status = 'inactive' THEN 2
|
|
857
|
+
ELSE 3
|
|
858
|
+
END,
|
|
859
|
+
t.name ASC`);
|
|
860
|
+
}
|
|
861
|
+
async getContractTemplateById(userId, templateId) {
|
|
862
|
+
const actor = await this.getActorContext(userId);
|
|
863
|
+
this.ensureDirector(actor);
|
|
864
|
+
return this.getContractTemplateRecord(this.prisma, templateId);
|
|
865
|
+
}
|
|
866
|
+
async createContractTemplate(userId, data) {
|
|
867
|
+
const actor = await this.getActorContext(userId);
|
|
868
|
+
this.ensureDirector(actor);
|
|
869
|
+
const name = this.normalizeOptionalText(data.name);
|
|
870
|
+
if (!name) {
|
|
871
|
+
throw new common_1.BadRequestException('Contract template name is required.');
|
|
872
|
+
}
|
|
873
|
+
return this.prisma.$transaction(async (tx) => {
|
|
874
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
875
|
+
await this.assertContractTemplateNameAvailable(tx, name);
|
|
876
|
+
const nextCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null;
|
|
877
|
+
if (nextCode) {
|
|
878
|
+
await this.assertContractTemplateCodeAvailable(tx, nextCode);
|
|
879
|
+
}
|
|
880
|
+
const nextStatus = (_c = data.status) !== null && _c !== void 0 ? _c : 'active';
|
|
881
|
+
const isActive = (_d = data.isActive) !== null && _d !== void 0 ? _d : !['inactive', 'archived'].includes(nextStatus);
|
|
882
|
+
const created = (await tx.$queryRawUnsafe(`INSERT INTO operations_contract_template (
|
|
883
|
+
slug,
|
|
884
|
+
code,
|
|
885
|
+
name,
|
|
886
|
+
description,
|
|
887
|
+
contract_category,
|
|
888
|
+
contract_type,
|
|
889
|
+
billing_model,
|
|
890
|
+
signature_status,
|
|
891
|
+
is_active,
|
|
892
|
+
status,
|
|
893
|
+
content_html,
|
|
894
|
+
created_at,
|
|
895
|
+
updated_at
|
|
896
|
+
) VALUES (
|
|
897
|
+
$1, $2, $3, $4,
|
|
898
|
+
$5, $6, $7, $8, $9, $10, $11,
|
|
899
|
+
NOW(), NOW()
|
|
900
|
+
)
|
|
901
|
+
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)));
|
|
902
|
+
const templateId = (_j = created[0]) === null || _j === void 0 ? void 0 : _j.id;
|
|
903
|
+
if (!templateId) {
|
|
904
|
+
throw new common_1.BadRequestException('Unable to create the contract template.');
|
|
905
|
+
}
|
|
906
|
+
return this.getContractTemplateRecord(tx, templateId, true);
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
async updateContractTemplate(userId, templateId, data) {
|
|
910
|
+
const actor = await this.getActorContext(userId);
|
|
911
|
+
this.ensureDirector(actor);
|
|
912
|
+
return this.prisma.$transaction(async (tx) => {
|
|
913
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
914
|
+
const current = await this.getContractTemplateRecord(tx, templateId, true);
|
|
915
|
+
const nextName = data.name !== undefined
|
|
916
|
+
? this.normalizeOptionalText(data.name)
|
|
917
|
+
: current.name;
|
|
918
|
+
if (!nextName) {
|
|
919
|
+
throw new common_1.BadRequestException('Contract template name is required.');
|
|
920
|
+
}
|
|
921
|
+
if (String(nextName).toLowerCase() !== String(current.name).toLowerCase()) {
|
|
922
|
+
await this.assertContractTemplateNameAvailable(tx, nextName, templateId);
|
|
923
|
+
}
|
|
924
|
+
const nextCode = data.code !== undefined
|
|
925
|
+
? (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : null
|
|
926
|
+
: ((_c = current.code) !== null && _c !== void 0 ? _c : null);
|
|
927
|
+
if (nextCode) {
|
|
928
|
+
await this.assertContractTemplateCodeAvailable(tx, nextCode, templateId);
|
|
929
|
+
}
|
|
930
|
+
const nextStatus = (_e = (_d = data.status) !== null && _d !== void 0 ? _d : current.status) !== null && _e !== void 0 ? _e : 'active';
|
|
931
|
+
const nextIsActive = (_f = data.isActive) !== null && _f !== void 0 ? _f : !['inactive', 'archived'].includes(nextStatus);
|
|
932
|
+
const nextSlug = String(nextName).toLowerCase() !== String(current.name).toLowerCase()
|
|
933
|
+
? await this.generateUniqueContractTemplateSlug(tx, nextName, templateId)
|
|
934
|
+
: current.slug;
|
|
935
|
+
await tx.$executeRawUnsafe(`UPDATE operations_contract_template
|
|
936
|
+
SET slug = $1,
|
|
937
|
+
code = $2,
|
|
938
|
+
name = $3,
|
|
939
|
+
description = $4,
|
|
940
|
+
contract_category = $5,
|
|
941
|
+
contract_type = $6,
|
|
942
|
+
billing_model = $7,
|
|
943
|
+
signature_status = $8,
|
|
944
|
+
is_active = $9,
|
|
945
|
+
status = $10,
|
|
946
|
+
content_html = $11,
|
|
947
|
+
updated_at = NOW()
|
|
948
|
+
WHERE id = $12`, nextSlug, nextCode, nextName, data.description !== undefined
|
|
949
|
+
? this.normalizeOptionalText(data.description)
|
|
950
|
+
: ((_g = current.description) !== null && _g !== void 0 ? _g : null), (_j = (_h = data.contractCategory) !== null && _h !== void 0 ? _h : current.contractCategory) !== null && _j !== void 0 ? _j : 'client', (_l = (_k = data.contractType) !== null && _k !== void 0 ? _k : current.contractType) !== null && _l !== void 0 ? _l : 'service_agreement', (_o = (_m = data.billingModel) !== null && _m !== void 0 ? _m : current.billingModel) !== null && _o !== void 0 ? _o : 'time_and_material', (_q = (_p = data.signatureStatus) !== null && _p !== void 0 ? _p : current.signatureStatus) !== null && _q !== void 0 ? _q : 'not_started', nextIsActive, nextStatus, data.contentHtml !== undefined
|
|
951
|
+
? this.normalizeOptionalText(data.contentHtml)
|
|
952
|
+
: ((_r = current.contentHtml) !== null && _r !== void 0 ? _r : null), templateId);
|
|
953
|
+
return this.getContractTemplateRecord(tx, templateId, true);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
551
956
|
async listContracts(userId) {
|
|
552
957
|
const actor = await this.getActorContext(userId);
|
|
553
958
|
const params = [];
|
|
@@ -574,6 +979,10 @@ let OperationsService = class OperationsService {
|
|
|
574
979
|
c.billing_model AS "billingModel",
|
|
575
980
|
c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
|
|
576
981
|
c.related_collaborator_id AS "relatedCollaboratorId",
|
|
982
|
+
c.contract_template_id AS "contractTemplateId",
|
|
983
|
+
template.name AS "contractTemplateName",
|
|
984
|
+
template.slug AS "contractTemplateSlug",
|
|
985
|
+
template.code AS "contractTemplateCode",
|
|
577
986
|
c.origin_type AS "originType",
|
|
578
987
|
c.origin_id AS "originId",
|
|
579
988
|
c.start_date AS "startDate",
|
|
@@ -583,19 +992,23 @@ let OperationsService = class OperationsService {
|
|
|
583
992
|
c.budget_amount AS "budgetAmount",
|
|
584
993
|
c.monthly_hour_cap AS "monthlyHourCap",
|
|
585
994
|
c.status,
|
|
995
|
+
c.creation_mode AS "creationMode",
|
|
996
|
+
c.wizard_step AS "wizardStep",
|
|
586
997
|
c.description,
|
|
587
998
|
m.display_name AS "accountManagerName",
|
|
588
999
|
linked.display_name AS "relatedCollaboratorName",
|
|
589
|
-
COALESCE(primary_party.display_name, linked.display_name, c.client_name) AS "mainRelatedPartyName",
|
|
590
|
-
COALESCE(financials.value_amount, 0) AS "valueAmount",
|
|
591
|
-
COALESCE(financials.payment_amount, 0) AS "paymentAmount",
|
|
592
|
-
COALESCE(financials.revenue_amount, 0) AS "revenueAmount",
|
|
593
|
-
COALESCE(financials.fine_amount, 0) AS "fineAmount",
|
|
594
|
-
COALESCE(pdf_document.file_name, '') AS "currentPdfFileName",
|
|
1000
|
+
MAX(COALESCE(primary_party.display_name, linked.display_name, c.client_name)) AS "mainRelatedPartyName",
|
|
1001
|
+
MAX(COALESCE(financials.value_amount, 0)) AS "valueAmount",
|
|
1002
|
+
MAX(COALESCE(financials.payment_amount, 0)) AS "paymentAmount",
|
|
1003
|
+
MAX(COALESCE(financials.revenue_amount, 0)) AS "revenueAmount",
|
|
1004
|
+
MAX(COALESCE(financials.fine_amount, 0)) AS "fineAmount",
|
|
1005
|
+
MAX(COALESCE(pdf_document.file_name, '')) AS "currentPdfFileName",
|
|
595
1006
|
COUNT(DISTINCT p.id)::int AS "projectCount"
|
|
596
1007
|
FROM operations_contract c
|
|
597
1008
|
LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
|
|
598
1009
|
LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
|
|
1010
|
+
LEFT JOIN operations_contract_template template
|
|
1011
|
+
ON template.id = c.contract_template_id
|
|
599
1012
|
LEFT JOIN LATERAL (
|
|
600
1013
|
SELECT cp.display_name
|
|
601
1014
|
FROM operations_contract_party cp
|
|
@@ -620,7 +1033,7 @@ let OperationsService = class OperationsService {
|
|
|
620
1033
|
WHERE cd.contract_id = c.id
|
|
621
1034
|
AND cd.deleted_at IS NULL
|
|
622
1035
|
AND cd.is_current = true
|
|
623
|
-
AND cd.document_type IN ('
|
|
1036
|
+
AND cd.document_type IN ('source_upload', 'generated_pdf')
|
|
624
1037
|
ORDER BY cd.id DESC
|
|
625
1038
|
LIMIT 1
|
|
626
1039
|
) pdf_document ON TRUE
|
|
@@ -628,8 +1041,8 @@ let OperationsService = class OperationsService {
|
|
|
628
1041
|
ON p.contract_id = c.id
|
|
629
1042
|
AND p.deleted_at IS NULL
|
|
630
1043
|
WHERE ${accessClause}
|
|
631
|
-
GROUP BY c.id, m.id, linked.id
|
|
632
|
-
ORDER BY c.name ASC`, params);
|
|
1044
|
+
GROUP BY c.id, m.id, linked.id, template.id
|
|
1045
|
+
ORDER BY COALESCE(c.name, c.code, CONCAT('draft-', c.id)) ASC`, params);
|
|
633
1046
|
}
|
|
634
1047
|
async getContractById(userId, contractId) {
|
|
635
1048
|
var _a, _b, _c;
|
|
@@ -645,6 +1058,10 @@ let OperationsService = class OperationsService {
|
|
|
645
1058
|
c.billing_model AS "billingModel",
|
|
646
1059
|
c.account_manager_collaborator_id AS "accountManagerCollaboratorId",
|
|
647
1060
|
c.related_collaborator_id AS "relatedCollaboratorId",
|
|
1061
|
+
c.contract_template_id AS "contractTemplateId",
|
|
1062
|
+
template.name AS "contractTemplateName",
|
|
1063
|
+
template.slug AS "contractTemplateSlug",
|
|
1064
|
+
template.code AS "contractTemplateCode",
|
|
648
1065
|
c.origin_type AS "originType",
|
|
649
1066
|
c.origin_id AS "originId",
|
|
650
1067
|
c.start_date AS "startDate",
|
|
@@ -654,6 +1071,8 @@ let OperationsService = class OperationsService {
|
|
|
654
1071
|
c.budget_amount AS "budgetAmount",
|
|
655
1072
|
c.monthly_hour_cap AS "monthlyHourCap",
|
|
656
1073
|
c.status,
|
|
1074
|
+
c.creation_mode AS "creationMode",
|
|
1075
|
+
c.wizard_step AS "wizardStep",
|
|
657
1076
|
c.description,
|
|
658
1077
|
c.content_html AS "contentHtml",
|
|
659
1078
|
m.display_name AS "accountManagerName",
|
|
@@ -661,6 +1080,8 @@ let OperationsService = class OperationsService {
|
|
|
661
1080
|
FROM operations_contract c
|
|
662
1081
|
LEFT JOIN operations_collaborator m ON m.id = c.account_manager_collaborator_id
|
|
663
1082
|
LEFT JOIN operations_collaborator linked ON linked.id = c.related_collaborator_id
|
|
1083
|
+
LEFT JOIN operations_contract_template template
|
|
1084
|
+
ON template.id = c.contract_template_id
|
|
664
1085
|
WHERE c.id = $1
|
|
665
1086
|
AND c.deleted_at IS NULL`, [contractId]);
|
|
666
1087
|
if (!contract) {
|
|
@@ -739,10 +1160,13 @@ let OperationsService = class OperationsService {
|
|
|
739
1160
|
ORDER BY id ASC`, [contractId]),
|
|
740
1161
|
this.queryRows(`SELECT id,
|
|
741
1162
|
document_type AS "documentType",
|
|
1163
|
+
file_id AS "fileId",
|
|
742
1164
|
file_name AS "fileName",
|
|
743
1165
|
mime_type AS "mimeType",
|
|
744
1166
|
file_content_base64 AS "fileContentBase64",
|
|
745
1167
|
is_current AS "isCurrent",
|
|
1168
|
+
extraction_status AS "extractionStatus",
|
|
1169
|
+
extraction_summary AS "extractionSummary",
|
|
746
1170
|
notes,
|
|
747
1171
|
created_at AS "createdAt"
|
|
748
1172
|
FROM operations_contract_document
|
|
@@ -781,9 +1205,9 @@ let OperationsService = class OperationsService {
|
|
|
781
1205
|
async createContract(userId, data) {
|
|
782
1206
|
const actor = await this.getActorContext(userId);
|
|
783
1207
|
this.ensureDirector(actor);
|
|
784
|
-
this.requireFields(data, ['code', 'name', 'clientName', 'startDate']);
|
|
785
1208
|
const createdId = await this.prisma.$transaction(async (tx) => {
|
|
786
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
1209
|
+
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;
|
|
1210
|
+
const normalizedCode = (_b = (_a = this.normalizeOptionalText(data.code)) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : (await this.generateContractCode(tx));
|
|
787
1211
|
const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
|
|
788
1212
|
code,
|
|
789
1213
|
name,
|
|
@@ -795,6 +1219,7 @@ let OperationsService = class OperationsService {
|
|
|
795
1219
|
billing_model,
|
|
796
1220
|
account_manager_collaborator_id,
|
|
797
1221
|
related_collaborator_id,
|
|
1222
|
+
contract_template_id,
|
|
798
1223
|
origin_type,
|
|
799
1224
|
origin_id,
|
|
800
1225
|
start_date,
|
|
@@ -804,23 +1229,42 @@ let OperationsService = class OperationsService {
|
|
|
804
1229
|
budget_amount,
|
|
805
1230
|
monthly_hour_cap,
|
|
806
1231
|
status,
|
|
1232
|
+
creation_mode,
|
|
1233
|
+
wizard_step,
|
|
807
1234
|
description,
|
|
808
1235
|
content_html,
|
|
809
1236
|
created_at,
|
|
810
1237
|
updated_at
|
|
811
1238
|
) VALUES (
|
|
812
|
-
$1, $2,
|
|
813
|
-
|
|
814
|
-
$
|
|
1239
|
+
$1, $2,
|
|
1240
|
+
$3::operations_contract_contract_category_70d553ea09_enum,
|
|
1241
|
+
$4::operations_contract_contract_type_48331e2ebf_enum,
|
|
1242
|
+
$5,
|
|
1243
|
+
$6::operations_contract_signature_status_2cb7282a7b_enum,
|
|
1244
|
+
$7,
|
|
1245
|
+
$8::operations_contract_billing_model_409dc7fea2_enum,
|
|
1246
|
+
$9,
|
|
1247
|
+
$10,
|
|
1248
|
+
$11,
|
|
1249
|
+
$12::operations_contract_origin_type_07a7cc2b5d_enum,
|
|
1250
|
+
$13,
|
|
1251
|
+
$14::date,
|
|
1252
|
+
$15::date, $16::date, $17::date, $18, $19,
|
|
1253
|
+
$20::operations_contract_status_a0395962df_enum,
|
|
1254
|
+
$21::operations_contract_creation_mode_98ba669209_enum,
|
|
1255
|
+
$22,
|
|
1256
|
+
$23,
|
|
1257
|
+
$24,
|
|
1258
|
+
NOW(), NOW()
|
|
815
1259
|
)
|
|
816
|
-
RETURNING id`,
|
|
817
|
-
const contractId = (
|
|
1260
|
+
RETURNING id`, normalizedCode, this.normalizeOptionalText(data.name), (_c = data.contractCategory) !== null && _c !== void 0 ? _c : 'client', (_d = data.contractType) !== null && _d !== void 0 ? _d : 'service_agreement', this.normalizeOptionalText(data.clientName), (_e = data.signatureStatus) !== null && _e !== void 0 ? _e : 'not_started', (_f = data.isActive) !== null && _f !== void 0 ? _f : true, (_g = data.billingModel) !== null && _g !== void 0 ? _g : 'time_and_material', (_h = data.accountManagerCollaboratorId) !== null && _h !== void 0 ? _h : null, (_j = data.relatedCollaboratorId) !== null && _j !== void 0 ? _j : null, (_k = data.contractTemplateId) !== null && _k !== void 0 ? _k : null, (_l = data.originType) !== null && _l !== void 0 ? _l : 'manual', (_m = data.originId) !== null && _m !== void 0 ? _m : null, this.normalizeOptionalText((_o = data.startDate) !== null && _o !== void 0 ? _o : null), (_p = data.endDate) !== null && _p !== void 0 ? _p : null, (_q = data.signedAt) !== null && _q !== void 0 ? _q : null, (_s = (_r = data.effectiveDate) !== null && _r !== void 0 ? _r : data.startDate) !== null && _s !== void 0 ? _s : null, (_t = data.budgetAmount) !== null && _t !== void 0 ? _t : null, (_u = data.monthlyHourCap) !== null && _u !== void 0 ? _u : null, (_v = data.status) !== null && _v !== void 0 ? _v : 'draft', (_w = data.creationMode) !== null && _w !== void 0 ? _w : 'blank', (_x = data.wizardStep) !== null && _x !== void 0 ? _x : 0, (_y = data.description) !== null && _y !== void 0 ? _y : null, (_z = data.contentHtml) !== null && _z !== void 0 ? _z : null);
|
|
1261
|
+
const contractId = (_0 = created[0]) === null || _0 === void 0 ? void 0 : _0.id;
|
|
818
1262
|
await this.replaceContractParties(tx, contractId, data.parties);
|
|
819
1263
|
await this.replaceContractSignatures(tx, contractId, data.signatures);
|
|
820
1264
|
await this.replaceContractFinancialTerms(tx, contractId, data.financialTerms);
|
|
821
1265
|
await this.replaceContractRevisions(tx, contractId, data.revisions);
|
|
822
1266
|
if (data.replaceUploadedPdfDocument) {
|
|
823
|
-
await this.
|
|
1267
|
+
await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
|
|
824
1268
|
}
|
|
825
1269
|
await this.insertContractHistory(tx, contractId, userId, 'created', data.originType === 'manual'
|
|
826
1270
|
? 'Manual contract created from registry.'
|
|
@@ -829,34 +1273,642 @@ let OperationsService = class OperationsService {
|
|
|
829
1273
|
});
|
|
830
1274
|
return this.getContractById(userId, createdId);
|
|
831
1275
|
}
|
|
1276
|
+
async createContractDraft(userId, data) {
|
|
1277
|
+
const actor = await this.getActorContext(userId);
|
|
1278
|
+
this.ensureDirector(actor);
|
|
1279
|
+
const creationMode = this.normalizeEnumValue(data.creationMode, CONTRACT_CREATION_MODE_VALUES, 'blank');
|
|
1280
|
+
const selectedTemplate = data.templateId && data.templateId > 0
|
|
1281
|
+
? await this.getContractTemplateById(userId, data.templateId)
|
|
1282
|
+
: null;
|
|
1283
|
+
const duplicateSource = data.duplicateFromId && data.duplicateFromId > 0
|
|
1284
|
+
? await this.getContractById(userId, data.duplicateFromId)
|
|
1285
|
+
: null;
|
|
1286
|
+
const storedSourceFile = data.sourceFileId && data.sourceFileId > 0
|
|
1287
|
+
? await this.prisma.file.findUnique({
|
|
1288
|
+
where: { id: data.sourceFileId },
|
|
1289
|
+
include: { file_mimetype: true },
|
|
1290
|
+
})
|
|
1291
|
+
: null;
|
|
1292
|
+
if (data.sourceFileId && !storedSourceFile) {
|
|
1293
|
+
throw new common_1.NotFoundException('Source contract file not found.');
|
|
1294
|
+
}
|
|
1295
|
+
const createdId = await this.prisma.$transaction(async (tx) => {
|
|
1296
|
+
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, _8, _9, _10, _11, _12, _13, _14, _15, _16;
|
|
1297
|
+
const generatedCode = await this.generateContractCode(tx);
|
|
1298
|
+
const duplicateNameBase = (_b = (_a = this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.name)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.code)) !== null && _b !== void 0 ? _b : 'Contract';
|
|
1299
|
+
const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
|
|
1300
|
+
code,
|
|
1301
|
+
name,
|
|
1302
|
+
contract_category,
|
|
1303
|
+
contract_type,
|
|
1304
|
+
client_name,
|
|
1305
|
+
signature_status,
|
|
1306
|
+
is_active,
|
|
1307
|
+
billing_model,
|
|
1308
|
+
account_manager_collaborator_id,
|
|
1309
|
+
related_collaborator_id,
|
|
1310
|
+
contract_template_id,
|
|
1311
|
+
origin_type,
|
|
1312
|
+
origin_id,
|
|
1313
|
+
start_date,
|
|
1314
|
+
end_date,
|
|
1315
|
+
signed_at,
|
|
1316
|
+
effective_date,
|
|
1317
|
+
budget_amount,
|
|
1318
|
+
monthly_hour_cap,
|
|
1319
|
+
status,
|
|
1320
|
+
creation_mode,
|
|
1321
|
+
wizard_step,
|
|
1322
|
+
description,
|
|
1323
|
+
content_html,
|
|
1324
|
+
created_by_user_id,
|
|
1325
|
+
updated_by_user_id,
|
|
1326
|
+
created_at,
|
|
1327
|
+
updated_at
|
|
1328
|
+
) VALUES (
|
|
1329
|
+
$1,
|
|
1330
|
+
$2,
|
|
1331
|
+
$3::operations_contract_contract_category_70d553ea09_enum,
|
|
1332
|
+
$4::operations_contract_contract_type_48331e2ebf_enum,
|
|
1333
|
+
$5,
|
|
1334
|
+
$6::operations_contract_signature_status_2cb7282a7b_enum,
|
|
1335
|
+
$7,
|
|
1336
|
+
$8::operations_contract_billing_model_409dc7fea2_enum,
|
|
1337
|
+
$9,
|
|
1338
|
+
$10,
|
|
1339
|
+
$11,
|
|
1340
|
+
$12::operations_contract_origin_type_07a7cc2b5d_enum,
|
|
1341
|
+
$13,
|
|
1342
|
+
$14::date,
|
|
1343
|
+
$15::date,
|
|
1344
|
+
$16::date,
|
|
1345
|
+
$17::date,
|
|
1346
|
+
$18,
|
|
1347
|
+
$19,
|
|
1348
|
+
$20::operations_contract_status_a0395962df_enum,
|
|
1349
|
+
$21::operations_contract_creation_mode_98ba669209_enum,
|
|
1350
|
+
$22,
|
|
1351
|
+
$23,
|
|
1352
|
+
$24,
|
|
1353
|
+
$25,
|
|
1354
|
+
$25,
|
|
1355
|
+
NOW(),
|
|
1356
|
+
NOW()
|
|
1357
|
+
)
|
|
1358
|
+
RETURNING id`, generatedCode, creationMode === 'duplicate'
|
|
1359
|
+
? `${duplicateNameBase} Copy`
|
|
1360
|
+
: this.normalizeOptionalText(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.name), (_d = (_c = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractCategory) !== null && _c !== void 0 ? _c : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractCategory) !== null && _d !== void 0 ? _d : 'client', (_f = (_e = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractType) !== null && _e !== void 0 ? _e : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractType) !== null && _f !== void 0 ? _f : 'service_agreement', this.normalizeOptionalText(duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.clientName), (_h = (_g = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.signatureStatus) !== null && _g !== void 0 ? _g : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.signatureStatus) !== null && _h !== void 0 ? _h : 'not_started', (_j = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.isActive) !== null && _j !== void 0 ? _j : true, (_l = (_k = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.billingModel) !== null && _k !== void 0 ? _k : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.billingModel) !== null && _l !== void 0 ? _l : 'time_and_material', (_m = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.accountManagerCollaboratorId) !== null && _m !== void 0 ? _m : null, (_o = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.relatedCollaboratorId) !== null && _o !== void 0 ? _o : null, (_q = (_p = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contractTemplateId) !== null && _p !== void 0 ? _p : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _q !== void 0 ? _q : null, (_r = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.originType) !== null && _r !== void 0 ? _r : 'manual', (_s = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.originId) !== null && _s !== void 0 ? _s : null, (_t = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.startDate) !== null && _t !== void 0 ? _t : null, (_u = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.endDate) !== null && _u !== void 0 ? _u : null, (_v = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.signedAt) !== null && _v !== void 0 ? _v : null, (_w = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.effectiveDate) !== null && _w !== void 0 ? _w : null, (_x = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.budgetAmount) !== null && _x !== void 0 ? _x : null, (_y = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.monthlyHourCap) !== null && _y !== void 0 ? _y : null, 'draft', creationMode, creationMode === 'upload' ? 0 : creationMode === 'blank' ? 0 : 1, (_0 = (_z = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.description) !== null && _z !== void 0 ? _z : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.description) !== null && _0 !== void 0 ? _0 : null, (_2 = (_1 = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.contentHtml) !== null && _1 !== void 0 ? _1 : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contentHtml) !== null && _2 !== void 0 ? _2 : null, userId);
|
|
1361
|
+
const contractId = (_3 = created[0]) === null || _3 === void 0 ? void 0 : _3.id;
|
|
1362
|
+
if (duplicateSource) {
|
|
1363
|
+
await this.replaceContractParties(tx, contractId, duplicateSource.parties);
|
|
1364
|
+
await this.replaceContractSignatures(tx, contractId, duplicateSource.signatures);
|
|
1365
|
+
await this.replaceContractFinancialTerms(tx, contractId, duplicateSource.financialTerms);
|
|
1366
|
+
await this.replaceContractRevisions(tx, contractId, duplicateSource.revisions);
|
|
1367
|
+
const currentSourceDocument = (_4 = duplicateSource.documents.find((document) => document.isCurrent && document.documentType === 'source_upload')) !== null && _4 !== void 0 ? _4 : null;
|
|
1368
|
+
if (currentSourceDocument) {
|
|
1369
|
+
await this.replaceContractDocument(tx, contractId, 'source_upload', {
|
|
1370
|
+
fileId: (_5 = currentSourceDocument.fileId) !== null && _5 !== void 0 ? _5 : null,
|
|
1371
|
+
fileName: currentSourceDocument.fileName,
|
|
1372
|
+
mimeType: currentSourceDocument.mimeType,
|
|
1373
|
+
fileContentBase64: (_6 = currentSourceDocument.fileContentBase64) !== null && _6 !== void 0 ? _6 : null,
|
|
1374
|
+
notes: (_7 = currentSourceDocument.notes) !== null && _7 !== void 0 ? _7 : null,
|
|
1375
|
+
extractionStatus: (_8 = currentSourceDocument.extractionStatus) !== null && _8 !== void 0 ? _8 : 'skipped',
|
|
1376
|
+
extractionSummary: (_9 = currentSourceDocument.extractionSummary) !== null && _9 !== void 0 ? _9 : null,
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (storedSourceFile) {
|
|
1381
|
+
await this.replaceContractDocument(tx, contractId, 'source_upload', {
|
|
1382
|
+
fileId: storedSourceFile.id,
|
|
1383
|
+
fileName: (_10 = this.normalizeOptionalText(data.sourceFileName)) !== null && _10 !== void 0 ? _10 : storedSourceFile.filename,
|
|
1384
|
+
mimeType: (_13 = (_11 = this.normalizeOptionalText(data.sourceMimeType)) !== null && _11 !== void 0 ? _11 : (_12 = storedSourceFile.file_mimetype) === null || _12 === void 0 ? void 0 : _12.name) !== null && _13 !== void 0 ? _13 : 'application/pdf',
|
|
1385
|
+
notes: 'Source contract document uploaded during draft creation.',
|
|
1386
|
+
extractionStatus: 'pending',
|
|
1387
|
+
extractionSummary: null,
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
await this.insertContractHistory(tx, contractId, userId, 'draft_created', `Contract draft created with mode ${creationMode}.`, JSON.stringify({
|
|
1391
|
+
creationMode,
|
|
1392
|
+
templateId: (_14 = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _14 !== void 0 ? _14 : null,
|
|
1393
|
+
duplicateFromId: (_15 = duplicateSource === null || duplicateSource === void 0 ? void 0 : duplicateSource.id) !== null && _15 !== void 0 ? _15 : null,
|
|
1394
|
+
sourceFileId: (_16 = storedSourceFile === null || storedSourceFile === void 0 ? void 0 : storedSourceFile.id) !== null && _16 !== void 0 ? _16 : null,
|
|
1395
|
+
}));
|
|
1396
|
+
return contractId;
|
|
1397
|
+
});
|
|
1398
|
+
return this.getContractById(userId, createdId);
|
|
1399
|
+
}
|
|
1400
|
+
async extractContractSource(userId, contractId, data) {
|
|
1401
|
+
var _a, _b;
|
|
1402
|
+
const actor = await this.getActorContext(userId);
|
|
1403
|
+
this.ensureDirector(actor);
|
|
1404
|
+
const contract = await this.getContractById(userId, contractId);
|
|
1405
|
+
const sourceDocument = contract.documents.find((document) => document.isCurrent && document.documentType === 'source_upload');
|
|
1406
|
+
if (!sourceDocument) {
|
|
1407
|
+
throw new common_1.BadRequestException('No source document is attached to this contract draft.');
|
|
1408
|
+
}
|
|
1409
|
+
await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
|
|
1410
|
+
SET extraction_status = 'processing',
|
|
1411
|
+
updated_at = NOW()
|
|
1412
|
+
WHERE id = $1`, sourceDocument.id);
|
|
1413
|
+
try {
|
|
1414
|
+
const extracted = await this.extractContractDraft(userId, {
|
|
1415
|
+
contractId,
|
|
1416
|
+
provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
|
|
1417
|
+
promptMessage: (_b = data.promptMessage) !== null && _b !== void 0 ? _b : null,
|
|
1418
|
+
});
|
|
1419
|
+
await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
|
|
1420
|
+
SET extraction_status = 'completed',
|
|
1421
|
+
extraction_summary = $2,
|
|
1422
|
+
updated_at = NOW()
|
|
1423
|
+
WHERE id = $1`, sourceDocument.id, extracted.summary || extracted.description || extracted.name || null);
|
|
1424
|
+
await this.insertContractHistory(this.prisma, contractId, userId, 'source_extracted', 'Source contract document extracted with AI.');
|
|
1425
|
+
return extracted;
|
|
1426
|
+
}
|
|
1427
|
+
catch (error) {
|
|
1428
|
+
await this.prisma.$executeRawUnsafe(`UPDATE operations_contract_document
|
|
1429
|
+
SET extraction_status = 'failed',
|
|
1430
|
+
updated_at = NOW()
|
|
1431
|
+
WHERE id = $1`, sourceDocument.id);
|
|
1432
|
+
throw error;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
async generateContractContent(userId, contractId, data = {}) {
|
|
1436
|
+
const actor = await this.getActorContext(userId);
|
|
1437
|
+
this.ensureDirector(actor);
|
|
1438
|
+
const contract = await this.getContractById(userId, contractId);
|
|
1439
|
+
const contentHtml = await this.generateContractContentHtml(contract, data);
|
|
1440
|
+
await this.prisma.$transaction(async (tx) => {
|
|
1441
|
+
var _a;
|
|
1442
|
+
await tx.$executeRawUnsafe(`UPDATE operations_contract
|
|
1443
|
+
SET content_html = $2,
|
|
1444
|
+
wizard_step = CASE
|
|
1445
|
+
WHEN COALESCE(wizard_step, 0) < 4 THEN 4
|
|
1446
|
+
ELSE wizard_step
|
|
1447
|
+
END,
|
|
1448
|
+
updated_by_user_id = $3,
|
|
1449
|
+
updated_at = NOW()
|
|
1450
|
+
WHERE id = $1`, contractId, contentHtml, userId);
|
|
1451
|
+
await this.insertContractHistory(tx, contractId, userId, 'content_generated', 'Contract content generated automatically for editing.', JSON.stringify({
|
|
1452
|
+
provider: (_a = data.provider) !== null && _a !== void 0 ? _a : null,
|
|
1453
|
+
overwrite: Boolean(data.overwrite),
|
|
1454
|
+
hasPrompt: Boolean(this.normalizeOptionalText(data.promptMessage)),
|
|
1455
|
+
}));
|
|
1456
|
+
});
|
|
1457
|
+
return this.getContractById(userId, contractId);
|
|
1458
|
+
}
|
|
1459
|
+
async reviewContractLegally(userId, contractId, data = {}) {
|
|
1460
|
+
const actor = await this.getActorContext(userId);
|
|
1461
|
+
this.ensureDirector(actor);
|
|
1462
|
+
const contract = await this.getContractById(userId, contractId);
|
|
1463
|
+
const review = await this.buildContractLegalReview(contract, data);
|
|
1464
|
+
await this.insertContractHistory(this.prisma, contractId, userId, 'legal_reviewed', 'Advisory legal checklist updated for this contract.', JSON.stringify(review));
|
|
1465
|
+
return review;
|
|
1466
|
+
}
|
|
1467
|
+
async generateContractPdf(userId, contractId) {
|
|
1468
|
+
const actor = await this.getActorContext(userId);
|
|
1469
|
+
this.ensureDirector(actor);
|
|
1470
|
+
const contract = await this.getContractById(userId, contractId);
|
|
1471
|
+
const pdfBuffer = await this.renderContractPdfBuffer(contract);
|
|
1472
|
+
const fileName = this.buildContractPdfFileName(contract);
|
|
1473
|
+
const uploadedFile = await this.fileService.upload('operations/contracts/generated', {
|
|
1474
|
+
fieldname: 'file',
|
|
1475
|
+
originalname: fileName,
|
|
1476
|
+
encoding: '7bit',
|
|
1477
|
+
mimetype: 'application/pdf',
|
|
1478
|
+
size: pdfBuffer.length,
|
|
1479
|
+
destination: '',
|
|
1480
|
+
filename: fileName,
|
|
1481
|
+
path: '',
|
|
1482
|
+
buffer: pdfBuffer,
|
|
1483
|
+
});
|
|
1484
|
+
await this.prisma.$transaction(async (tx) => {
|
|
1485
|
+
await this.replaceContractDocument(tx, contractId, 'generated_pdf', {
|
|
1486
|
+
fileId: uploadedFile.id,
|
|
1487
|
+
fileName,
|
|
1488
|
+
mimeType: 'application/pdf',
|
|
1489
|
+
notes: 'PDF generated from contract rich text content.',
|
|
1490
|
+
extractionStatus: 'skipped',
|
|
1491
|
+
extractionSummary: null,
|
|
1492
|
+
});
|
|
1493
|
+
await this.insertContractHistory(tx, contractId, userId, 'pdf_generated', 'Generated a branded PDF version of the contract.');
|
|
1494
|
+
});
|
|
1495
|
+
return {
|
|
1496
|
+
contractId,
|
|
1497
|
+
fileId: uploadedFile.id,
|
|
1498
|
+
fileName,
|
|
1499
|
+
mimeType: 'application/pdf',
|
|
1500
|
+
documentType: 'generated_pdf',
|
|
1501
|
+
downloadUrl: `/file/open/${uploadedFile.id}`,
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
async extractContractDraft(userId, data) {
|
|
1505
|
+
var _a;
|
|
1506
|
+
const actor = await this.getActorContext(userId);
|
|
1507
|
+
this.ensureDirector(actor);
|
|
1508
|
+
const uploadFile = await this.resolveContractExtractionFile(userId, data);
|
|
1509
|
+
const aiResult = await this.aiService.chat({
|
|
1510
|
+
provider: data.provider === 'gemini' ? 'gemini' : 'openai',
|
|
1511
|
+
model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
|
|
1512
|
+
message: this.normalizeExtractionString(data.promptMessage) ||
|
|
1513
|
+
'Analyze the attached contract and extract a structured draft for the contract form.',
|
|
1514
|
+
systemPrompt: this.buildContractExtractionSystemPrompt(),
|
|
1515
|
+
}, uploadFile);
|
|
1516
|
+
const parsed = this.parseAiJsonPayload(String((_a = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _a !== void 0 ? _a : ''));
|
|
1517
|
+
const draft = this.normalizeContractExtractDraft(parsed);
|
|
1518
|
+
const warnings = [...draft.warnings];
|
|
1519
|
+
if (uploadFile.mimetype === 'application/msword' ||
|
|
1520
|
+
uploadFile.mimetype ===
|
|
1521
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
|
|
1522
|
+
warnings.push('Word extraction is best-effort. Review names, dates, and values before saving.');
|
|
1523
|
+
}
|
|
1524
|
+
return Object.assign(Object.assign({}, draft), { warnings: Array.from(new Set(warnings.filter(Boolean))) });
|
|
1525
|
+
}
|
|
1526
|
+
async createContractFromProposalIntegration(payload) {
|
|
1527
|
+
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;
|
|
1528
|
+
const sourceEntityId = String(payload.proposalId || '').trim();
|
|
1529
|
+
if (!sourceEntityId) {
|
|
1530
|
+
throw new common_1.BadRequestException('proposalId is required for CRM proposal integration.');
|
|
1531
|
+
}
|
|
1532
|
+
const proposal = payload.proposal || {
|
|
1533
|
+
code: (_a = payload.code) !== null && _a !== void 0 ? _a : null,
|
|
1534
|
+
title: (_b = payload.title) !== null && _b !== void 0 ? _b : null,
|
|
1535
|
+
contractCategory: (_d = (_c = payload.commercialTerms) === null || _c === void 0 ? void 0 : _c.contractCategory) !== null && _d !== void 0 ? _d : 'client',
|
|
1536
|
+
contractType: (_f = (_e = payload.commercialTerms) === null || _e === void 0 ? void 0 : _e.contractType) !== null && _f !== void 0 ? _f : 'service_agreement',
|
|
1537
|
+
billingModel: (_h = (_g = payload.commercialTerms) === null || _g === void 0 ? void 0 : _g.billingModel) !== null && _h !== void 0 ? _h : 'fixed_price',
|
|
1538
|
+
validFrom: (_k = (_j = payload.commercialTerms) === null || _j === void 0 ? void 0 : _j.validFrom) !== null && _k !== void 0 ? _k : null,
|
|
1539
|
+
validUntil: (_o = (_l = payload.validUntil) !== null && _l !== void 0 ? _l : (_m = payload.commercialTerms) === null || _m === void 0 ? void 0 : _m.validUntil) !== null && _o !== void 0 ? _o : null,
|
|
1540
|
+
totalAmount: (_p = payload.total) !== null && _p !== void 0 ? _p : null,
|
|
1541
|
+
totalAmountCents: payload.total != null ? Math.round(Number(payload.total) * 100) : null,
|
|
1542
|
+
notes: (_r = (_q = payload.commercialTerms) === null || _q === void 0 ? void 0 : _q.notes) !== null && _r !== void 0 ? _r : null,
|
|
1543
|
+
};
|
|
1544
|
+
const person = payload.person || {};
|
|
1545
|
+
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
1546
|
+
const contractCategory = (_s = proposal.contractCategory) !== null && _s !== void 0 ? _s : 'client';
|
|
1547
|
+
const contractType = (_t = proposal.contractType) !== null && _t !== void 0 ? _t : 'service_agreement';
|
|
1548
|
+
const billingModel = (_u = proposal.billingModel) !== null && _u !== void 0 ? _u : 'fixed_price';
|
|
1549
|
+
const clientName = (_x = (_w = (_v = this.normalizeOptionalText(person.tradeName)) !== null && _v !== void 0 ? _v : this.normalizeOptionalText(person.name)) !== null && _w !== void 0 ? _w : this.normalizeOptionalText(proposal.title)) !== null && _x !== void 0 ? _x : `Proposal ${sourceEntityId}`;
|
|
1550
|
+
const contractName = (_y = this.normalizeOptionalText(proposal.title)) !== null && _y !== void 0 ? _y : `Proposal ${sourceEntityId}`;
|
|
1551
|
+
const financialTerms = items
|
|
1552
|
+
.map((item) => {
|
|
1553
|
+
var _a, _b, _c;
|
|
1554
|
+
return ({
|
|
1555
|
+
termType: this.normalizeEnumValue(item.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
|
|
1556
|
+
label: (_a = this.normalizeOptionalText(item.name)) !== null && _a !== void 0 ? _a : 'Commercial term',
|
|
1557
|
+
amount: Number((_b = item.amount) !== null && _b !== void 0 ? _b : Number(item.totalAmountCents || 0) / 100),
|
|
1558
|
+
recurrence: this.normalizeEnumValue(item.recurrence, RECURRENCE_VALUES, 'one_time'),
|
|
1559
|
+
dueDay: (_c = item.dueDay) !== null && _c !== void 0 ? _c : null,
|
|
1560
|
+
notes: this.normalizeOptionalText(item.description),
|
|
1561
|
+
});
|
|
1562
|
+
})
|
|
1563
|
+
.filter((term) => Number.isFinite(term.amount) && term.amount > 0);
|
|
1564
|
+
const fallbackAmount = Number((_z = proposal.totalAmount) !== null && _z !== void 0 ? _z : 0);
|
|
1565
|
+
if (financialTerms.length === 0 && Number.isFinite(fallbackAmount) && fallbackAmount > 0) {
|
|
1566
|
+
financialTerms.push({
|
|
1567
|
+
termType: 'revenue',
|
|
1568
|
+
label: contractName,
|
|
1569
|
+
amount: fallbackAmount,
|
|
1570
|
+
recurrence: 'one_time',
|
|
1571
|
+
dueDay: null,
|
|
1572
|
+
notes: this.normalizeOptionalText(proposal.notes),
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
const primaryPartyRole = ['employee', 'contractor'].includes(contractCategory)
|
|
1576
|
+
? 'employee'
|
|
1577
|
+
: ['supplier', 'vendor'].includes(contractCategory)
|
|
1578
|
+
? 'supplier'
|
|
1579
|
+
: contractCategory === 'partner'
|
|
1580
|
+
? 'partner'
|
|
1581
|
+
: 'client';
|
|
1582
|
+
return this.prisma.$transaction(async (tx) => {
|
|
1583
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
1584
|
+
await tx.$queryRawUnsafe(`SELECT pg_advisory_xact_lock(hashtext($1))`, `operations:crm_proposal:${sourceEntityId}`);
|
|
1585
|
+
const existingContracts = await tx.$queryRawUnsafe(`SELECT id, code
|
|
1586
|
+
FROM operations_contract
|
|
1587
|
+
WHERE deleted_at IS NULL
|
|
1588
|
+
AND origin_type = $1::operations_contract_origin_type_07a7cc2b5d_enum
|
|
1589
|
+
AND origin_id::text = $2
|
|
1590
|
+
ORDER BY id DESC
|
|
1591
|
+
LIMIT 1`, 'crm_proposal', sourceEntityId);
|
|
1592
|
+
const existingContract = existingContracts[0];
|
|
1593
|
+
if (existingContract === null || existingContract === void 0 ? void 0 : existingContract.id) {
|
|
1594
|
+
return {
|
|
1595
|
+
id: existingContract.id,
|
|
1596
|
+
code: (_a = existingContract.code) !== null && _a !== void 0 ? _a : `CONTRACT-${existingContract.id}`,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
const generatedCode = await this.generateContractCode(tx);
|
|
1600
|
+
const created = await tx.$queryRawUnsafe(`INSERT INTO operations_contract (
|
|
1601
|
+
code,
|
|
1602
|
+
name,
|
|
1603
|
+
contract_category,
|
|
1604
|
+
contract_type,
|
|
1605
|
+
client_name,
|
|
1606
|
+
signature_status,
|
|
1607
|
+
is_active,
|
|
1608
|
+
billing_model,
|
|
1609
|
+
account_manager_collaborator_id,
|
|
1610
|
+
related_collaborator_id,
|
|
1611
|
+
contract_template_id,
|
|
1612
|
+
origin_type,
|
|
1613
|
+
origin_id,
|
|
1614
|
+
start_date,
|
|
1615
|
+
end_date,
|
|
1616
|
+
signed_at,
|
|
1617
|
+
effective_date,
|
|
1618
|
+
budget_amount,
|
|
1619
|
+
monthly_hour_cap,
|
|
1620
|
+
status,
|
|
1621
|
+
creation_mode,
|
|
1622
|
+
wizard_step,
|
|
1623
|
+
description,
|
|
1624
|
+
content_html,
|
|
1625
|
+
created_by_user_id,
|
|
1626
|
+
updated_by_user_id,
|
|
1627
|
+
created_at,
|
|
1628
|
+
updated_at
|
|
1629
|
+
) VALUES (
|
|
1630
|
+
$1,
|
|
1631
|
+
$2,
|
|
1632
|
+
$3::operations_contract_contract_category_70d553ea09_enum,
|
|
1633
|
+
$4::operations_contract_contract_type_48331e2ebf_enum,
|
|
1634
|
+
$5,
|
|
1635
|
+
$6::operations_contract_signature_status_2cb7282a7b_enum,
|
|
1636
|
+
$7,
|
|
1637
|
+
$8::operations_contract_billing_model_409dc7fea2_enum,
|
|
1638
|
+
$9,
|
|
1639
|
+
$10,
|
|
1640
|
+
$11,
|
|
1641
|
+
$12::operations_contract_origin_type_07a7cc2b5d_enum,
|
|
1642
|
+
$13,
|
|
1643
|
+
$14::date,
|
|
1644
|
+
$15::date,
|
|
1645
|
+
$16::date,
|
|
1646
|
+
$17::date,
|
|
1647
|
+
$18,
|
|
1648
|
+
$19,
|
|
1649
|
+
$20::operations_contract_status_a0395962df_enum,
|
|
1650
|
+
$21::operations_contract_creation_mode_98ba669209_enum,
|
|
1651
|
+
$22,
|
|
1652
|
+
$23,
|
|
1653
|
+
$24,
|
|
1654
|
+
$25,
|
|
1655
|
+
$25,
|
|
1656
|
+
NOW(),
|
|
1657
|
+
NOW()
|
|
1658
|
+
)
|
|
1659
|
+
RETURNING id`, generatedCode, contractName, contractCategory, contractType, clientName, 'not_started', true, billingModel, null, null, null, 'crm_proposal', sourceEntityId, (_b = proposal.validFrom) !== null && _b !== void 0 ? _b : null, (_c = proposal.validUntil) !== null && _c !== void 0 ? _c : null, null, (_d = proposal.validFrom) !== null && _d !== void 0 ? _d : null, fallbackAmount > 0 ? fallbackAmount : null, null, 'draft', 'blank', 1, this.normalizeOptionalText(proposal.notes), this.normalizeOptionalText((_e = payload.revision) === null || _e === void 0 ? void 0 : _e.contentHtml), Number(payload.approvedByUserId) || null);
|
|
1660
|
+
const contractId = (_f = created[0]) === null || _f === void 0 ? void 0 : _f.id;
|
|
1661
|
+
if (!contractId) {
|
|
1662
|
+
throw new common_1.BadRequestException('Could not create contract draft from proposal.');
|
|
1663
|
+
}
|
|
1664
|
+
await this.replaceContractParties(tx, contractId, [
|
|
1665
|
+
{
|
|
1666
|
+
partyRole: primaryPartyRole,
|
|
1667
|
+
partyType: person.tradeName ? 'company' : 'individual',
|
|
1668
|
+
displayName: clientName,
|
|
1669
|
+
documentNumber: this.normalizeOptionalText(person.document),
|
|
1670
|
+
email: this.normalizeOptionalText(person.email),
|
|
1671
|
+
phone: this.normalizeOptionalText(person.phone),
|
|
1672
|
+
isPrimary: true,
|
|
1673
|
+
},
|
|
1674
|
+
]);
|
|
1675
|
+
await this.replaceContractFinancialTerms(tx, contractId, financialTerms.map((term) => ({
|
|
1676
|
+
termType: term.termType,
|
|
1677
|
+
label: term.label,
|
|
1678
|
+
amount: term.amount,
|
|
1679
|
+
recurrence: term.recurrence,
|
|
1680
|
+
dueDay: term.dueDay,
|
|
1681
|
+
notes: term.notes,
|
|
1682
|
+
})));
|
|
1683
|
+
await this.replaceContractRevisions(tx, contractId, [
|
|
1684
|
+
{
|
|
1685
|
+
revisionType: 'revision',
|
|
1686
|
+
title: (_h = (_g = payload.revision) === null || _g === void 0 ? void 0 : _g.title) !== null && _h !== void 0 ? _h : contractName,
|
|
1687
|
+
effectiveDate: (_j = proposal.validFrom) !== null && _j !== void 0 ? _j : null,
|
|
1688
|
+
status: 'draft',
|
|
1689
|
+
summary: (_l = this.normalizeOptionalText((_k = payload.revision) === null || _k === void 0 ? void 0 : _k.summary)) !== null && _l !== void 0 ? _l : this.normalizeOptionalText(proposal.notes),
|
|
1690
|
+
},
|
|
1691
|
+
]);
|
|
1692
|
+
await this.insertContractHistory(tx, contractId, Number(payload.approvedByUserId) || null, 'created', `Draft contract generated from CRM proposal ${(_m = proposal.code) !== null && _m !== void 0 ? _m : sourceEntityId}.`, JSON.stringify({
|
|
1693
|
+
correlationId: payload.correlationId || `proposal:${sourceEntityId}`,
|
|
1694
|
+
proposalId: (_o = payload.proposalId) !== null && _o !== void 0 ? _o : (Number(sourceEntityId) > 0 ? Number(sourceEntityId) : null),
|
|
1695
|
+
proposalRevisionId: (_p = payload.proposalRevisionId) !== null && _p !== void 0 ? _p : null,
|
|
1696
|
+
sourceModule: 'contact',
|
|
1697
|
+
sourceEntity: 'proposal',
|
|
1698
|
+
sourceId: sourceEntityId,
|
|
1699
|
+
approvedByUserId: Number(payload.approvedByUserId) || null,
|
|
1700
|
+
}));
|
|
1701
|
+
await this.integrationApi.publishEvent({
|
|
1702
|
+
eventName: 'operations.contract.created',
|
|
1703
|
+
sourceModule: 'operations',
|
|
1704
|
+
aggregateType: 'contract',
|
|
1705
|
+
aggregateId: String(contractId),
|
|
1706
|
+
payload: this.buildContractCreatedEventPayload({
|
|
1707
|
+
id: contractId,
|
|
1708
|
+
code: generatedCode,
|
|
1709
|
+
name: contractName,
|
|
1710
|
+
clientName,
|
|
1711
|
+
contractCategory,
|
|
1712
|
+
contractType,
|
|
1713
|
+
billingModel,
|
|
1714
|
+
originType: 'crm_proposal',
|
|
1715
|
+
originId: sourceEntityId,
|
|
1716
|
+
startDate: (_q = proposal.validFrom) !== null && _q !== void 0 ? _q : null,
|
|
1717
|
+
endDate: (_r = proposal.validUntil) !== null && _r !== void 0 ? _r : null,
|
|
1718
|
+
description: this.normalizeOptionalText(proposal.notes),
|
|
1719
|
+
financialTerms,
|
|
1720
|
+
}, payload, String(payload.locale || '').trim() || 'en', Number(payload.approvedByUserId) || undefined),
|
|
1721
|
+
metadata: {
|
|
1722
|
+
producer: 'operations',
|
|
1723
|
+
correlationId: payload.correlationId || `proposal:${sourceEntityId}`,
|
|
1724
|
+
sourceModule: 'operations',
|
|
1725
|
+
sourceEntity: 'contract',
|
|
1726
|
+
sourceId: String(contractId),
|
|
1727
|
+
originType: 'crm_proposal',
|
|
1728
|
+
originId: sourceEntityId,
|
|
1729
|
+
},
|
|
1730
|
+
}, {
|
|
1731
|
+
persistenceClient: tx,
|
|
1732
|
+
});
|
|
1733
|
+
return {
|
|
1734
|
+
id: contractId,
|
|
1735
|
+
code: generatedCode,
|
|
1736
|
+
};
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
buildContractCreatedEventPayload(contract, payload, locale = 'en', createdByUserId) {
|
|
1740
|
+
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;
|
|
1741
|
+
return {
|
|
1742
|
+
contractId: contract.id,
|
|
1743
|
+
proposalId: (_a = payload.proposalId) !== null && _a !== void 0 ? _a : null,
|
|
1744
|
+
proposalRevisionId: (_b = payload.proposalRevisionId) !== null && _b !== void 0 ? _b : null,
|
|
1745
|
+
personId: (_e = (_c = payload.personId) !== null && _c !== void 0 ? _c : (_d = payload.person) === null || _d === void 0 ? void 0 : _d.id) !== null && _e !== void 0 ? _e : null,
|
|
1746
|
+
createdByUserId: createdByUserId !== null && createdByUserId !== void 0 ? createdByUserId : null,
|
|
1747
|
+
createdAt: new Date().toISOString(),
|
|
1748
|
+
locale,
|
|
1749
|
+
correlationId: payload.correlationId || `proposal:${(_g = (_f = payload.proposalId) !== null && _f !== void 0 ? _f : contract.originId) !== null && _g !== void 0 ? _g : contract.id}`,
|
|
1750
|
+
sourceModule: 'operations',
|
|
1751
|
+
sourceEntity: 'contract',
|
|
1752
|
+
sourceId: String(contract.id),
|
|
1753
|
+
source_module: 'operations',
|
|
1754
|
+
source_entity: 'contract',
|
|
1755
|
+
source_id: String(contract.id),
|
|
1756
|
+
contract: {
|
|
1757
|
+
id: contract.id,
|
|
1758
|
+
code: (_h = contract.code) !== null && _h !== void 0 ? _h : null,
|
|
1759
|
+
name: (_j = contract.name) !== null && _j !== void 0 ? _j : null,
|
|
1760
|
+
clientName: (_k = contract.clientName) !== null && _k !== void 0 ? _k : null,
|
|
1761
|
+
status: 'draft',
|
|
1762
|
+
contractCategory: (_l = contract.contractCategory) !== null && _l !== void 0 ? _l : 'client',
|
|
1763
|
+
contractType: (_m = contract.contractType) !== null && _m !== void 0 ? _m : 'service_agreement',
|
|
1764
|
+
billingModel: (_o = contract.billingModel) !== null && _o !== void 0 ? _o : 'fixed_price',
|
|
1765
|
+
originType: (_p = contract.originType) !== null && _p !== void 0 ? _p : 'manual',
|
|
1766
|
+
originId: (_q = contract.originId) !== null && _q !== void 0 ? _q : null,
|
|
1767
|
+
startDate: (_r = contract.startDate) !== null && _r !== void 0 ? _r : null,
|
|
1768
|
+
endDate: (_s = contract.endDate) !== null && _s !== void 0 ? _s : null,
|
|
1769
|
+
description: (_t = contract.description) !== null && _t !== void 0 ? _t : null,
|
|
1770
|
+
financialTerms: (_u = contract.financialTerms) !== null && _u !== void 0 ? _u : [],
|
|
1771
|
+
},
|
|
1772
|
+
proposal: (_v = payload.proposal) !== null && _v !== void 0 ? _v : {
|
|
1773
|
+
code: (_w = payload.code) !== null && _w !== void 0 ? _w : null,
|
|
1774
|
+
title: (_x = payload.title) !== null && _x !== void 0 ? _x : null,
|
|
1775
|
+
totalAmount: (_y = payload.total) !== null && _y !== void 0 ? _y : null,
|
|
1776
|
+
notes: (_0 = (_z = payload.commercialTerms) === null || _z === void 0 ? void 0 : _z.notes) !== null && _0 !== void 0 ? _0 : null,
|
|
1777
|
+
},
|
|
1778
|
+
person: (_1 = payload.person) !== null && _1 !== void 0 ? _1 : null,
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
async buildContractActivatedEventPayload(contract, locale = 'en', activatedByUserId) {
|
|
1782
|
+
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, _8, _9;
|
|
1783
|
+
let personId = null;
|
|
1784
|
+
let proposalId = null;
|
|
1785
|
+
let personRecord = null;
|
|
1786
|
+
if (contract.originType === 'crm_proposal' && contract.originId) {
|
|
1787
|
+
const parsedProposalId = Number(contract.originId);
|
|
1788
|
+
if (Number.isInteger(parsedProposalId) && parsedProposalId > 0) {
|
|
1789
|
+
proposalId = parsedProposalId;
|
|
1790
|
+
const proposal = await this.prisma.proposal.findUnique({
|
|
1791
|
+
where: { id: parsedProposalId },
|
|
1792
|
+
include: {
|
|
1793
|
+
person: {
|
|
1794
|
+
select: {
|
|
1795
|
+
id: true,
|
|
1796
|
+
name: true,
|
|
1797
|
+
trade_name: true,
|
|
1798
|
+
email: true,
|
|
1799
|
+
phone: true,
|
|
1800
|
+
document: true,
|
|
1801
|
+
},
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
});
|
|
1805
|
+
if (proposal) {
|
|
1806
|
+
personId = (_c = (_a = proposal.person_id) !== null && _a !== void 0 ? _a : (_b = proposal.person) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null;
|
|
1807
|
+
personRecord = (_d = proposal.person) !== null && _d !== void 0 ? _d : null;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
const financialTerms = ((_e = contract.financialTerms) !== null && _e !== void 0 ? _e : []).map((term) => {
|
|
1812
|
+
var _a, _b, _c, _d, _e;
|
|
1813
|
+
return ({
|
|
1814
|
+
label: term.label,
|
|
1815
|
+
termType: (_a = term.termType) !== null && _a !== void 0 ? _a : 'value',
|
|
1816
|
+
amount: Number((_b = term.amount) !== null && _b !== void 0 ? _b : 0),
|
|
1817
|
+
recurrence: (_c = term.recurrence) !== null && _c !== void 0 ? _c : 'one_time',
|
|
1818
|
+
dueDay: (_d = term.dueDay) !== null && _d !== void 0 ? _d : null,
|
|
1819
|
+
notes: (_e = term.notes) !== null && _e !== void 0 ? _e : null,
|
|
1820
|
+
});
|
|
1821
|
+
});
|
|
1822
|
+
const totalAmount = financialTerms.reduce((sum, term) => sum + (Number.isFinite(term.amount) ? term.amount : 0), 0) || Number((_f = contract.budgetAmount) !== null && _f !== void 0 ? _f : 0);
|
|
1823
|
+
return {
|
|
1824
|
+
contractId: contract.id,
|
|
1825
|
+
proposalId,
|
|
1826
|
+
personId,
|
|
1827
|
+
activatedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
|
|
1828
|
+
signedByUserId: activatedByUserId !== null && activatedByUserId !== void 0 ? activatedByUserId : null,
|
|
1829
|
+
activatedAt: new Date().toISOString(),
|
|
1830
|
+
signedAt: (_j = (_h = (_g = contract.signedAt) !== null && _g !== void 0 ? _g : contract.effectiveDate) !== null && _h !== void 0 ? _h : contract.startDate) !== null && _j !== void 0 ? _j : null,
|
|
1831
|
+
locale,
|
|
1832
|
+
correlationId: proposalId ? `proposal:${proposalId}` : `contract:${contract.id}`,
|
|
1833
|
+
sourceModule: 'operations',
|
|
1834
|
+
sourceEntity: 'contract',
|
|
1835
|
+
sourceId: String(contract.id),
|
|
1836
|
+
source_module: 'operations',
|
|
1837
|
+
source_entity: 'contract',
|
|
1838
|
+
source_id: String(contract.id),
|
|
1839
|
+
contract: {
|
|
1840
|
+
code: (_k = contract.code) !== null && _k !== void 0 ? _k : null,
|
|
1841
|
+
name: (_l = contract.name) !== null && _l !== void 0 ? _l : null,
|
|
1842
|
+
clientName: (_m = contract.clientName) !== null && _m !== void 0 ? _m : null,
|
|
1843
|
+
contractCategory: (_o = contract.contractCategory) !== null && _o !== void 0 ? _o : 'client',
|
|
1844
|
+
contractType: (_p = contract.contractType) !== null && _p !== void 0 ? _p : 'service_agreement',
|
|
1845
|
+
billingModel: (_q = contract.billingModel) !== null && _q !== void 0 ? _q : 'fixed_price',
|
|
1846
|
+
originType: (_r = contract.originType) !== null && _r !== void 0 ? _r : 'manual',
|
|
1847
|
+
originId: (_s = contract.originId) !== null && _s !== void 0 ? _s : null,
|
|
1848
|
+
startDate: (_t = contract.startDate) !== null && _t !== void 0 ? _t : null,
|
|
1849
|
+
endDate: (_u = contract.endDate) !== null && _u !== void 0 ? _u : null,
|
|
1850
|
+
signedAt: (_v = contract.signedAt) !== null && _v !== void 0 ? _v : null,
|
|
1851
|
+
effectiveDate: (_w = contract.effectiveDate) !== null && _w !== void 0 ? _w : null,
|
|
1852
|
+
budgetAmount: (_x = contract.budgetAmount) !== null && _x !== void 0 ? _x : null,
|
|
1853
|
+
description: (_y = contract.description) !== null && _y !== void 0 ? _y : null,
|
|
1854
|
+
financialTerms,
|
|
1855
|
+
parties: (_z = contract.parties) !== null && _z !== void 0 ? _z : [],
|
|
1856
|
+
},
|
|
1857
|
+
person: personRecord
|
|
1858
|
+
? {
|
|
1859
|
+
id: (_0 = personRecord.id) !== null && _0 !== void 0 ? _0 : null,
|
|
1860
|
+
name: (_1 = personRecord.name) !== null && _1 !== void 0 ? _1 : null,
|
|
1861
|
+
tradeName: (_2 = personRecord.trade_name) !== null && _2 !== void 0 ? _2 : null,
|
|
1862
|
+
email: (_3 = personRecord.email) !== null && _3 !== void 0 ? _3 : null,
|
|
1863
|
+
phone: (_4 = personRecord.phone) !== null && _4 !== void 0 ? _4 : null,
|
|
1864
|
+
document: (_5 = personRecord.document) !== null && _5 !== void 0 ? _5 : null,
|
|
1865
|
+
}
|
|
1866
|
+
: null,
|
|
1867
|
+
receivable: {
|
|
1868
|
+
personId,
|
|
1869
|
+
documentNumber: (_6 = contract.code) !== null && _6 !== void 0 ? _6 : `CONTRACT-${contract.id}`,
|
|
1870
|
+
totalAmount,
|
|
1871
|
+
description: (_9 = (_8 = (_7 = contract.description) !== null && _7 !== void 0 ? _7 : contract.name) !== null && _8 !== void 0 ? _8 : contract.code) !== null && _9 !== void 0 ? _9 : null,
|
|
1872
|
+
},
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
832
1875
|
async updateContract(userId, contractId, data) {
|
|
1876
|
+
var _a, _b;
|
|
833
1877
|
const actor = await this.getActorContext(userId);
|
|
834
1878
|
this.ensureDirector(actor);
|
|
835
|
-
await this.getContractById(userId, contractId);
|
|
1879
|
+
const current = await this.getContractById(userId, contractId);
|
|
1880
|
+
const nextStatus = this.normalizeEnumValue(data.status, CONTRACT_STATUS_VALUES, (_a = current.status) !== null && _a !== void 0 ? _a : 'draft');
|
|
1881
|
+
const nextSignatureStatus = this.normalizeEnumValue(data.signatureStatus, SIGNATURE_STATUS_VALUES, (_b = current.signatureStatus) !== null && _b !== void 0 ? _b : 'not_started');
|
|
1882
|
+
const shouldPublishSigned = current.signatureStatus !== 'signed' && nextSignatureStatus === 'signed';
|
|
1883
|
+
const shouldPublishActivation = current.status !== 'active' && nextStatus === 'active';
|
|
836
1884
|
const updates = [];
|
|
837
1885
|
const params = [];
|
|
838
1886
|
this.pushUpdate(updates, params, 'code', data.code);
|
|
839
1887
|
this.pushUpdate(updates, params, 'name', data.name);
|
|
840
|
-
this.pushUpdate(updates, params, 'contract_category', data.contractCategory);
|
|
841
|
-
this.pushUpdate(updates, params, 'contract_type', data.contractType);
|
|
1888
|
+
this.pushUpdate(updates, params, 'contract_category', data.contractCategory, 'operations_contract_contract_category_70d553ea09_enum');
|
|
1889
|
+
this.pushUpdate(updates, params, 'contract_type', data.contractType, 'operations_contract_contract_type_48331e2ebf_enum');
|
|
842
1890
|
this.pushUpdate(updates, params, 'client_name', data.clientName);
|
|
843
|
-
this.pushUpdate(updates, params, 'signature_status', data.signatureStatus);
|
|
1891
|
+
this.pushUpdate(updates, params, 'signature_status', data.signatureStatus, 'operations_contract_signature_status_2cb7282a7b_enum');
|
|
844
1892
|
this.pushUpdate(updates, params, 'is_active', data.isActive);
|
|
845
|
-
this.pushUpdate(updates, params, 'billing_model', data.billingModel);
|
|
1893
|
+
this.pushUpdate(updates, params, 'billing_model', data.billingModel, 'operations_contract_billing_model_409dc7fea2_enum');
|
|
846
1894
|
this.pushUpdate(updates, params, 'account_manager_collaborator_id', data.accountManagerCollaboratorId);
|
|
847
1895
|
this.pushUpdate(updates, params, 'related_collaborator_id', data.relatedCollaboratorId);
|
|
848
|
-
this.pushUpdate(updates, params, '
|
|
1896
|
+
this.pushUpdate(updates, params, 'contract_template_id', data.contractTemplateId);
|
|
1897
|
+
this.pushUpdate(updates, params, 'origin_type', data.originType, 'operations_contract_origin_type_07a7cc2b5d_enum');
|
|
849
1898
|
this.pushUpdate(updates, params, 'origin_id', data.originId);
|
|
850
|
-
this.pushUpdate(updates, params, 'start_date', data.startDate);
|
|
851
|
-
this.pushUpdate(updates, params, 'end_date', data.endDate);
|
|
852
|
-
this.pushUpdate(updates, params, 'signed_at', data.signedAt);
|
|
853
|
-
this.pushUpdate(updates, params, 'effective_date', data.effectiveDate);
|
|
1899
|
+
this.pushUpdate(updates, params, 'start_date', data.startDate, 'date');
|
|
1900
|
+
this.pushUpdate(updates, params, 'end_date', data.endDate, 'date');
|
|
1901
|
+
this.pushUpdate(updates, params, 'signed_at', data.signedAt, 'date');
|
|
1902
|
+
this.pushUpdate(updates, params, 'effective_date', data.effectiveDate, 'date');
|
|
854
1903
|
this.pushUpdate(updates, params, 'budget_amount', data.budgetAmount);
|
|
855
1904
|
this.pushUpdate(updates, params, 'monthly_hour_cap', data.monthlyHourCap);
|
|
856
|
-
this.pushUpdate(updates, params, 'status', data.status);
|
|
1905
|
+
this.pushUpdate(updates, params, 'status', data.status, 'operations_contract_status_a0395962df_enum');
|
|
1906
|
+
this.pushUpdate(updates, params, 'creation_mode', data.creationMode, 'operations_contract_creation_mode_98ba669209_enum');
|
|
1907
|
+
this.pushUpdate(updates, params, 'wizard_step', data.wizardStep);
|
|
857
1908
|
this.pushUpdate(updates, params, 'description', data.description);
|
|
858
1909
|
this.pushUpdate(updates, params, 'content_html', data.contentHtml);
|
|
859
1910
|
await this.prisma.$transaction(async (tx) => {
|
|
1911
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y;
|
|
860
1912
|
if (updates.length) {
|
|
861
1913
|
params.push(contractId);
|
|
862
1914
|
await tx.$executeRawUnsafe(`UPDATE operations_contract
|
|
@@ -877,12 +1929,110 @@ let OperationsService = class OperationsService {
|
|
|
877
1929
|
await this.replaceContractRevisions(tx, contractId, data.revisions);
|
|
878
1930
|
}
|
|
879
1931
|
if (data.replaceUploadedPdfDocument) {
|
|
880
|
-
await this.
|
|
1932
|
+
await this.replaceContractDocument(tx, contractId, 'source_upload', data.replaceUploadedPdfDocument);
|
|
881
1933
|
}
|
|
882
1934
|
await this.insertContractHistory(tx, contractId, userId, 'updated', 'Contract registry data updated.');
|
|
1935
|
+
if (shouldPublishSigned || shouldPublishActivation) {
|
|
1936
|
+
const contractEventPayload = await this.buildContractActivatedEventPayload({
|
|
1937
|
+
id: current.id,
|
|
1938
|
+
code: (_a = data.code) !== null && _a !== void 0 ? _a : current.code,
|
|
1939
|
+
name: (_b = data.name) !== null && _b !== void 0 ? _b : current.name,
|
|
1940
|
+
clientName: (_c = data.clientName) !== null && _c !== void 0 ? _c : current.clientName,
|
|
1941
|
+
contractCategory: (_d = data.contractCategory) !== null && _d !== void 0 ? _d : current.contractCategory,
|
|
1942
|
+
contractType: (_e = data.contractType) !== null && _e !== void 0 ? _e : current.contractType,
|
|
1943
|
+
billingModel: (_f = data.billingModel) !== null && _f !== void 0 ? _f : current.billingModel,
|
|
1944
|
+
originType: (_g = data.originType) !== null && _g !== void 0 ? _g : current.originType,
|
|
1945
|
+
originId: (_h = data.originId) !== null && _h !== void 0 ? _h : current.originId,
|
|
1946
|
+
startDate: (_j = data.startDate) !== null && _j !== void 0 ? _j : current.startDate,
|
|
1947
|
+
endDate: (_k = data.endDate) !== null && _k !== void 0 ? _k : current.endDate,
|
|
1948
|
+
signedAt: (_l = data.signedAt) !== null && _l !== void 0 ? _l : current.signedAt,
|
|
1949
|
+
effectiveDate: (_m = data.effectiveDate) !== null && _m !== void 0 ? _m : current.effectiveDate,
|
|
1950
|
+
budgetAmount: (_o = data.budgetAmount) !== null && _o !== void 0 ? _o : current.budgetAmount,
|
|
1951
|
+
description: (_p = data.description) !== null && _p !== void 0 ? _p : current.description,
|
|
1952
|
+
financialTerms: (_q = data.financialTerms) !== null && _q !== void 0 ? _q : current.financialTerms,
|
|
1953
|
+
parties: (_r = data.parties) !== null && _r !== void 0 ? _r : current.parties,
|
|
1954
|
+
}, 'en', userId);
|
|
1955
|
+
if (shouldPublishSigned) {
|
|
1956
|
+
await this.integrationApi.publishEvent({
|
|
1957
|
+
eventName: 'operations.contract.signed',
|
|
1958
|
+
sourceModule: 'operations',
|
|
1959
|
+
aggregateType: 'contract',
|
|
1960
|
+
aggregateId: String(contractId),
|
|
1961
|
+
payload: Object.assign(Object.assign({}, contractEventPayload), { signedByUserId: userId !== null && userId !== void 0 ? userId : null, signedAt: (_u = (_t = (_s = data.signedAt) !== null && _s !== void 0 ? _s : current.signedAt) !== null && _t !== void 0 ? _t : contractEventPayload.signedAt) !== null && _u !== void 0 ? _u : new Date().toISOString() }),
|
|
1962
|
+
metadata: {
|
|
1963
|
+
producer: 'operations',
|
|
1964
|
+
correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
|
|
1965
|
+
sourceModule: 'operations',
|
|
1966
|
+
sourceEntity: 'contract',
|
|
1967
|
+
sourceId: String(contractId),
|
|
1968
|
+
originType: (_v = data.originType) !== null && _v !== void 0 ? _v : current.originType,
|
|
1969
|
+
originId: (_w = data.originId) !== null && _w !== void 0 ? _w : current.originId,
|
|
1970
|
+
lifecycle: 'signed',
|
|
1971
|
+
},
|
|
1972
|
+
}, {
|
|
1973
|
+
persistenceClient: tx,
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
if (shouldPublishActivation) {
|
|
1977
|
+
await this.integrationApi.publishEvent({
|
|
1978
|
+
eventName: 'operations.contract.activated',
|
|
1979
|
+
sourceModule: 'operations',
|
|
1980
|
+
aggregateType: 'contract',
|
|
1981
|
+
aggregateId: String(contractId),
|
|
1982
|
+
payload: contractEventPayload,
|
|
1983
|
+
metadata: {
|
|
1984
|
+
producer: 'operations',
|
|
1985
|
+
correlationId: contractEventPayload.correlationId || `contract:${contractId}`,
|
|
1986
|
+
sourceModule: 'operations',
|
|
1987
|
+
sourceEntity: 'contract',
|
|
1988
|
+
sourceId: String(contractId),
|
|
1989
|
+
originType: (_x = data.originType) !== null && _x !== void 0 ? _x : current.originType,
|
|
1990
|
+
originId: (_y = data.originId) !== null && _y !== void 0 ? _y : current.originId,
|
|
1991
|
+
lifecycle: 'activated',
|
|
1992
|
+
},
|
|
1993
|
+
}, {
|
|
1994
|
+
persistenceClient: tx,
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
883
1998
|
});
|
|
884
1999
|
return this.getContractById(userId, contractId);
|
|
885
2000
|
}
|
|
2001
|
+
async removeContract(userId, contractId) {
|
|
2002
|
+
const actor = await this.getActorContext(userId);
|
|
2003
|
+
this.ensureDirector(actor);
|
|
2004
|
+
const current = await this.getContractById(userId, contractId);
|
|
2005
|
+
await this.prisma.$transaction(async (tx) => {
|
|
2006
|
+
var _a, _b;
|
|
2007
|
+
await tx.$executeRawUnsafe(`UPDATE operations_project
|
|
2008
|
+
SET contract_id = NULL,
|
|
2009
|
+
updated_at = NOW()
|
|
2010
|
+
WHERE contract_id = $1
|
|
2011
|
+
AND deleted_at IS NULL`, contractId);
|
|
2012
|
+
for (const tableName of [
|
|
2013
|
+
'operations_contract_party',
|
|
2014
|
+
'operations_contract_signature',
|
|
2015
|
+
'operations_contract_financial_term',
|
|
2016
|
+
'operations_contract_document',
|
|
2017
|
+
'operations_contract_revision',
|
|
2018
|
+
]) {
|
|
2019
|
+
await tx.$executeRawUnsafe(`UPDATE ${tableName}
|
|
2020
|
+
SET deleted_at = NOW(),
|
|
2021
|
+
updated_at = NOW()
|
|
2022
|
+
WHERE contract_id = $1
|
|
2023
|
+
AND deleted_at IS NULL`, contractId);
|
|
2024
|
+
}
|
|
2025
|
+
await this.insertContractHistory(tx, contractId, userId, 'deleted', `Contract "${(_b = (_a = current.name) !== null && _a !== void 0 ? _a : current.code) !== null && _b !== void 0 ? _b : `#${contractId}`}" removed from registry.`);
|
|
2026
|
+
await tx.$executeRawUnsafe(`UPDATE operations_contract
|
|
2027
|
+
SET deleted_at = NOW(),
|
|
2028
|
+
is_active = false,
|
|
2029
|
+
status = 'archived',
|
|
2030
|
+
updated_at = NOW()
|
|
2031
|
+
WHERE id = $1
|
|
2032
|
+
AND deleted_at IS NULL`, contractId);
|
|
2033
|
+
});
|
|
2034
|
+
return { success: true };
|
|
2035
|
+
}
|
|
886
2036
|
async listTimesheets(userId) {
|
|
887
2037
|
const actor = await this.getActorContext(userId);
|
|
888
2038
|
const filter = this.buildIdFilter(actor.visibleCollaboratorIds, 't.collaborator_id', actor.isDirector);
|
|
@@ -958,7 +2108,7 @@ let OperationsService = class OperationsService {
|
|
|
958
2108
|
status,
|
|
959
2109
|
created_at,
|
|
960
2110
|
updated_at
|
|
961
|
-
) VALUES ($1, $2, $3, $4, $5, 'draft', NOW(), NOW())
|
|
2111
|
+
) VALUES ($1, $2, $3::date, $4::date, $5, 'draft', NOW(), NOW())
|
|
962
2112
|
RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, data.weekStartDate, data.weekEndDate, (_b = data.notes) !== null && _b !== void 0 ? _b : null));
|
|
963
2113
|
const timesheetId = (_c = row[0]) === null || _c === void 0 ? void 0 : _c.id;
|
|
964
2114
|
await this.replaceTimesheetEntries(tx, timesheetId, (_d = data.entries) !== null && _d !== void 0 ? _d : [], collaboratorId);
|
|
@@ -979,8 +2129,8 @@ let OperationsService = class OperationsService {
|
|
|
979
2129
|
await this.prisma.$transaction(async (tx) => {
|
|
980
2130
|
const updates = [];
|
|
981
2131
|
const params = [];
|
|
982
|
-
this.pushUpdate(updates, params, 'week_start_date', data.weekStartDate);
|
|
983
|
-
this.pushUpdate(updates, params, 'week_end_date', data.weekEndDate);
|
|
2132
|
+
this.pushUpdate(updates, params, 'week_start_date', data.weekStartDate, 'date');
|
|
2133
|
+
this.pushUpdate(updates, params, 'week_end_date', data.weekEndDate, 'date');
|
|
984
2134
|
this.pushUpdate(updates, params, 'notes', data.notes);
|
|
985
2135
|
if (updates.length) {
|
|
986
2136
|
params.push(timesheetId);
|
|
@@ -1079,7 +2229,17 @@ let OperationsService = class OperationsService {
|
|
|
1079
2229
|
submitted_at,
|
|
1080
2230
|
created_at,
|
|
1081
2231
|
updated_at
|
|
1082
|
-
) VALUES (
|
|
2232
|
+
) VALUES (
|
|
2233
|
+
$1,
|
|
2234
|
+
$2,
|
|
2235
|
+
$3::operations_time_off_request_request_type_fdcf258f8e_enum,
|
|
2236
|
+
$4::date,
|
|
2237
|
+
$5::date,
|
|
2238
|
+
$6,
|
|
2239
|
+
'submitted',
|
|
2240
|
+
$7,
|
|
2241
|
+
NOW(), NOW(), NOW()
|
|
2242
|
+
)
|
|
1083
2243
|
RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, (_b = data.requestType) !== null && _b !== void 0 ? _b : 'vacation', data.startDate, data.endDate, (_c = data.totalDays) !== null && _c !== void 0 ? _c : null, (_d = data.reason) !== null && _d !== void 0 ? _d : null));
|
|
1084
2244
|
const requestId = (_e = row[0]) === null || _e === void 0 ? void 0 : _e.id;
|
|
1085
2245
|
await this.upsertApproval(tx, {
|
|
@@ -1188,7 +2348,14 @@ let OperationsService = class OperationsService {
|
|
|
1188
2348
|
created_at,
|
|
1189
2349
|
updated_at
|
|
1190
2350
|
) VALUES (
|
|
1191
|
-
$1,
|
|
2351
|
+
$1,
|
|
2352
|
+
$2,
|
|
2353
|
+
$3::operations_schedule_adjustment_request_request__d3f1202fa6_enum,
|
|
2354
|
+
$4::date,
|
|
2355
|
+
$5::date,
|
|
2356
|
+
'submitted',
|
|
2357
|
+
$6,
|
|
2358
|
+
NOW(), NOW(), NOW()
|
|
1192
2359
|
)
|
|
1193
2360
|
RETURNING id`, collaboratorId, (_a = collaborator.supervisorId) !== null && _a !== void 0 ? _a : null, (_b = data.requestScope) !== null && _b !== void 0 ? _b : 'temporary', data.effectiveStartDate, (_c = data.effectiveEndDate) !== null && _c !== void 0 ? _c : null, (_d = data.reason) !== null && _d !== void 0 ? _d : null));
|
|
1194
2361
|
const requestId = (_e = row[0]) === null || _e === void 0 ? void 0 : _e.id;
|
|
@@ -1202,7 +2369,11 @@ let OperationsService = class OperationsService {
|
|
|
1202
2369
|
break_minutes,
|
|
1203
2370
|
created_at,
|
|
1204
2371
|
updated_at
|
|
1205
|
-
) VALUES (
|
|
2372
|
+
) VALUES (
|
|
2373
|
+
$1,
|
|
2374
|
+
$2::operations_schedule_adjustment_day_weekday_ba6542bf86_enum,
|
|
2375
|
+
$3, $4::time, $5::time, $6, NOW(), NOW()
|
|
2376
|
+
)`, requestId, day.weekday, (_f = day.isWorkingDay) !== null && _f !== void 0 ? _f : true, (_g = day.startTime) !== null && _g !== void 0 ? _g : null, (_h = day.endTime) !== null && _h !== void 0 ? _h : null, (_j = day.breakMinutes) !== null && _j !== void 0 ? _j : null);
|
|
1206
2377
|
}
|
|
1207
2378
|
await this.upsertApproval(tx, {
|
|
1208
2379
|
targetType: 'schedule_adjustment_request',
|
|
@@ -1371,14 +2542,14 @@ let OperationsService = class OperationsService {
|
|
|
1371
2542
|
await this.prisma.$transaction(async (tx) => {
|
|
1372
2543
|
var _a, _b;
|
|
1373
2544
|
await tx.$executeRawUnsafe(`UPDATE operations_approval
|
|
1374
|
-
SET status = $1,
|
|
2545
|
+
SET status = $1::operations_approval_status_747aa313d9_enum,
|
|
1375
2546
|
decided_at = NOW(),
|
|
1376
2547
|
decision_note = $2,
|
|
1377
2548
|
updated_at = NOW()
|
|
1378
2549
|
WHERE id = $3`, nextStatus, (_a = data.note) !== null && _a !== void 0 ? _a : null, approvalId);
|
|
1379
2550
|
if (approval.targetType === 'timesheet') {
|
|
1380
2551
|
await tx.$executeRawUnsafe(`UPDATE operations_timesheet
|
|
1381
|
-
SET status = $1,
|
|
2552
|
+
SET status = $1::operations_timesheet_status_128a8ecd30_enum,
|
|
1382
2553
|
reviewed_at = NOW(),
|
|
1383
2554
|
approver_collaborator_id = $2,
|
|
1384
2555
|
updated_at = NOW()
|
|
@@ -1386,7 +2557,7 @@ let OperationsService = class OperationsService {
|
|
|
1386
2557
|
}
|
|
1387
2558
|
if (approval.targetType === 'time_off_request') {
|
|
1388
2559
|
await tx.$executeRawUnsafe(`UPDATE operations_time_off_request
|
|
1389
|
-
SET status = $1,
|
|
2560
|
+
SET status = $1::operations_time_off_request_status_c0b28799ee_enum,
|
|
1390
2561
|
reviewed_at = NOW(),
|
|
1391
2562
|
approver_collaborator_id = $2,
|
|
1392
2563
|
submitted_at = COALESCE(submitted_at, NOW()),
|
|
@@ -1395,7 +2566,7 @@ let OperationsService = class OperationsService {
|
|
|
1395
2566
|
}
|
|
1396
2567
|
if (approval.targetType === 'schedule_adjustment_request') {
|
|
1397
2568
|
await tx.$executeRawUnsafe(`UPDATE operations_schedule_adjustment_request
|
|
1398
|
-
SET status = $1,
|
|
2569
|
+
SET status = $1::operations_schedule_adjustment_request_status_159e10f34e_enum,
|
|
1399
2570
|
reviewed_at = NOW(),
|
|
1400
2571
|
approver_collaborator_id = $2,
|
|
1401
2572
|
submitted_at = COALESCE(submitted_at, NOW()),
|
|
@@ -1460,10 +2631,12 @@ let OperationsService = class OperationsService {
|
|
|
1460
2631
|
}
|
|
1461
2632
|
async getCollaboratorByUserId(userId) {
|
|
1462
2633
|
return this.querySingle(`SELECT c.id,
|
|
1463
|
-
c.display_name AS "displayName",
|
|
2634
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
1464
2635
|
s.id AS "supervisorId",
|
|
1465
2636
|
s.display_name AS "supervisorName"
|
|
1466
2637
|
FROM operations_collaborator c
|
|
2638
|
+
LEFT JOIN person person_record
|
|
2639
|
+
ON person_record.id = c.person_id
|
|
1467
2640
|
LEFT JOIN operations_collaborator s
|
|
1468
2641
|
ON s.id = c.supervisor_collaborator_id
|
|
1469
2642
|
WHERE c.user_id = $1
|
|
@@ -1471,10 +2644,12 @@ let OperationsService = class OperationsService {
|
|
|
1471
2644
|
}
|
|
1472
2645
|
async getCollaboratorById(collaboratorId) {
|
|
1473
2646
|
const collaborator = await this.querySingle(`SELECT c.id,
|
|
1474
|
-
c.display_name AS "displayName",
|
|
2647
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
1475
2648
|
s.id AS "supervisorId",
|
|
1476
2649
|
s.display_name AS "supervisorName"
|
|
1477
2650
|
FROM operations_collaborator c
|
|
2651
|
+
LEFT JOIN person person_record
|
|
2652
|
+
ON person_record.id = c.person_id
|
|
1478
2653
|
LEFT JOIN operations_collaborator s
|
|
1479
2654
|
ON s.id = c.supervisor_collaborator_id
|
|
1480
2655
|
WHERE c.id = $1
|
|
@@ -1484,6 +2659,186 @@ let OperationsService = class OperationsService {
|
|
|
1484
2659
|
}
|
|
1485
2660
|
return collaborator;
|
|
1486
2661
|
}
|
|
2662
|
+
async getPersonById(personId) {
|
|
2663
|
+
const person = await this.querySingle(`SELECT id, name, type
|
|
2664
|
+
FROM person
|
|
2665
|
+
WHERE id = $1`, [personId]);
|
|
2666
|
+
if (!person) {
|
|
2667
|
+
throw new common_1.NotFoundException('Person not found.');
|
|
2668
|
+
}
|
|
2669
|
+
return person;
|
|
2670
|
+
}
|
|
2671
|
+
async getDepartmentById(client, departmentId, includeInactive = false) {
|
|
2672
|
+
var _a;
|
|
2673
|
+
const departments = (await client.$queryRawUnsafe(`SELECT id,
|
|
2674
|
+
slug,
|
|
2675
|
+
code,
|
|
2676
|
+
name,
|
|
2677
|
+
description,
|
|
2678
|
+
created_at AS "createdAt",
|
|
2679
|
+
updated_at AS "updatedAt",
|
|
2680
|
+
deleted_at AS "deletedAt",
|
|
2681
|
+
CASE WHEN deleted_at IS NULL THEN 'active' ELSE 'inactive' END AS status
|
|
2682
|
+
FROM operations_department
|
|
2683
|
+
WHERE id = $1
|
|
2684
|
+
AND ($2::boolean OR deleted_at IS NULL)`, departmentId, includeInactive));
|
|
2685
|
+
const department = (_a = departments[0]) !== null && _a !== void 0 ? _a : null;
|
|
2686
|
+
if (!department) {
|
|
2687
|
+
throw new common_1.NotFoundException('Department not found.');
|
|
2688
|
+
}
|
|
2689
|
+
return department;
|
|
2690
|
+
}
|
|
2691
|
+
async assertDepartmentNameAvailable(client, name, excludeDepartmentId) {
|
|
2692
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2693
|
+
FROM operations_department
|
|
2694
|
+
WHERE LOWER(name) = LOWER($1)
|
|
2695
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2696
|
+
LIMIT 1`, name, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
|
|
2697
|
+
if (existing[0]) {
|
|
2698
|
+
throw new common_1.BadRequestException('A department with this name already exists.');
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
async assertDepartmentCodeAvailable(client, code, excludeDepartmentId) {
|
|
2702
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2703
|
+
FROM operations_department
|
|
2704
|
+
WHERE UPPER(COALESCE(code, '')) = UPPER($1)
|
|
2705
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2706
|
+
LIMIT 1`, code, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
|
|
2707
|
+
if (existing[0]) {
|
|
2708
|
+
throw new common_1.BadRequestException('A department with this code already exists.');
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
async resolveDepartmentReference(client, input) {
|
|
2712
|
+
var _a;
|
|
2713
|
+
if (input.departmentName !== undefined) {
|
|
2714
|
+
const normalizedDepartmentName = this.normalizeOptionalText(input.departmentName);
|
|
2715
|
+
if (!normalizedDepartmentName) {
|
|
2716
|
+
return null;
|
|
2717
|
+
}
|
|
2718
|
+
const existingByName = (await client.$queryRawUnsafe(`SELECT id, name
|
|
2719
|
+
FROM operations_department
|
|
2720
|
+
WHERE deleted_at IS NULL
|
|
2721
|
+
AND LOWER(name) = LOWER($1)
|
|
2722
|
+
ORDER BY id ASC
|
|
2723
|
+
LIMIT 1`, normalizedDepartmentName));
|
|
2724
|
+
if (existingByName[0]) {
|
|
2725
|
+
return existingByName[0];
|
|
2726
|
+
}
|
|
2727
|
+
const slug = await this.generateUniqueDepartmentSlug(client, normalizedDepartmentName);
|
|
2728
|
+
const created = (await client.$queryRawUnsafe(`INSERT INTO operations_department (
|
|
2729
|
+
slug,
|
|
2730
|
+
code,
|
|
2731
|
+
name,
|
|
2732
|
+
description,
|
|
2733
|
+
created_at,
|
|
2734
|
+
updated_at
|
|
2735
|
+
) VALUES ($1, NULL, $2, NULL, NOW(), NOW())
|
|
2736
|
+
RETURNING id, name`, slug, normalizedDepartmentName));
|
|
2737
|
+
return (_a = created[0]) !== null && _a !== void 0 ? _a : null;
|
|
2738
|
+
}
|
|
2739
|
+
if (typeof input.departmentId === 'number' &&
|
|
2740
|
+
Number.isInteger(input.departmentId) &&
|
|
2741
|
+
input.departmentId > 0) {
|
|
2742
|
+
return this.getDepartmentById(client, input.departmentId);
|
|
2743
|
+
}
|
|
2744
|
+
return null;
|
|
2745
|
+
}
|
|
2746
|
+
async generateUniqueDepartmentSlug(client, label, excludeDepartmentId) {
|
|
2747
|
+
const baseSlug = this.slugifyValue(label) || `department-${Date.now().toString(36)}`;
|
|
2748
|
+
for (let attempt = 0; attempt < 25; attempt += 1) {
|
|
2749
|
+
const candidate = attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`;
|
|
2750
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2751
|
+
FROM operations_department
|
|
2752
|
+
WHERE slug = $1
|
|
2753
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2754
|
+
LIMIT 1`, candidate, excludeDepartmentId !== null && excludeDepartmentId !== void 0 ? excludeDepartmentId : null));
|
|
2755
|
+
if (!existing.length) {
|
|
2756
|
+
return candidate;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
return `${baseSlug}-${Date.now().toString(36)}`;
|
|
2760
|
+
}
|
|
2761
|
+
async getContractTemplateRecord(client, templateId, includeInactive = false) {
|
|
2762
|
+
const template = (await client.$queryRawUnsafe(`SELECT t.id,
|
|
2763
|
+
t.slug,
|
|
2764
|
+
t.code,
|
|
2765
|
+
t.name,
|
|
2766
|
+
t.description,
|
|
2767
|
+
t.contract_category AS "contractCategory",
|
|
2768
|
+
t.contract_type AS "contractType",
|
|
2769
|
+
t.billing_model AS "billingModel",
|
|
2770
|
+
t.signature_status AS "signatureStatus",
|
|
2771
|
+
t.is_active AS "isActive",
|
|
2772
|
+
t.status,
|
|
2773
|
+
t.content_html AS "contentHtml",
|
|
2774
|
+
COUNT(DISTINCT c.id)::int AS "usageCount",
|
|
2775
|
+
t.created_at AS "createdAt",
|
|
2776
|
+
t.updated_at AS "updatedAt"
|
|
2777
|
+
FROM operations_contract_template t
|
|
2778
|
+
LEFT JOIN operations_contract c
|
|
2779
|
+
ON c.contract_template_id = t.id
|
|
2780
|
+
AND c.deleted_at IS NULL
|
|
2781
|
+
WHERE t.id = $1
|
|
2782
|
+
AND ($2::boolean = true OR t.deleted_at IS NULL)
|
|
2783
|
+
GROUP BY t.id
|
|
2784
|
+
LIMIT 1`, templateId, includeInactive));
|
|
2785
|
+
const record = template[0];
|
|
2786
|
+
if (!record) {
|
|
2787
|
+
throw new common_1.NotFoundException('Contract template not found.');
|
|
2788
|
+
}
|
|
2789
|
+
return record;
|
|
2790
|
+
}
|
|
2791
|
+
async assertContractTemplateNameAvailable(client, name, excludeTemplateId) {
|
|
2792
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2793
|
+
FROM operations_contract_template
|
|
2794
|
+
WHERE LOWER(name) = LOWER($1)
|
|
2795
|
+
AND deleted_at IS NULL
|
|
2796
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2797
|
+
LIMIT 1`, name, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
|
|
2798
|
+
if (existing[0]) {
|
|
2799
|
+
throw new common_1.BadRequestException('A contract template with this name already exists.');
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
async assertContractTemplateCodeAvailable(client, code, excludeTemplateId) {
|
|
2803
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2804
|
+
FROM operations_contract_template
|
|
2805
|
+
WHERE UPPER(COALESCE(code, '')) = UPPER($1)
|
|
2806
|
+
AND deleted_at IS NULL
|
|
2807
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2808
|
+
LIMIT 1`, code, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
|
|
2809
|
+
if (existing[0]) {
|
|
2810
|
+
throw new common_1.BadRequestException('A contract template with this code already exists.');
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
async generateUniqueContractTemplateSlug(client, label, excludeTemplateId) {
|
|
2814
|
+
const baseSlug = this.slugifyValue(label) || `contract-template-${Date.now().toString(36)}`;
|
|
2815
|
+
for (let attempt = 0; attempt < 25; attempt += 1) {
|
|
2816
|
+
const candidate = attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`;
|
|
2817
|
+
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
2818
|
+
FROM operations_contract_template
|
|
2819
|
+
WHERE slug = $1
|
|
2820
|
+
AND ($2::int IS NULL OR id <> $2)
|
|
2821
|
+
LIMIT 1`, candidate, excludeTemplateId !== null && excludeTemplateId !== void 0 ? excludeTemplateId : null));
|
|
2822
|
+
if (!existing.length) {
|
|
2823
|
+
return candidate;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
return `${baseSlug}-${Date.now().toString(36)}`;
|
|
2827
|
+
}
|
|
2828
|
+
slugifyValue(value) {
|
|
2829
|
+
return (value !== null && value !== void 0 ? value : '')
|
|
2830
|
+
.normalize('NFKD')
|
|
2831
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
2832
|
+
.toLowerCase()
|
|
2833
|
+
.trim()
|
|
2834
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
2835
|
+
.replace(/^-+|-+$/g, '')
|
|
2836
|
+
.slice(0, 80);
|
|
2837
|
+
}
|
|
2838
|
+
normalizeOptionalText(value) {
|
|
2839
|
+
const normalized = String(value !== null && value !== void 0 ? value : '').trim();
|
|
2840
|
+
return normalized || null;
|
|
2841
|
+
}
|
|
1487
2842
|
async getProjectDetails(projectId, actorCollaboratorId) {
|
|
1488
2843
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1489
2844
|
const project = await this.querySingle(`SELECT p.id,
|
|
@@ -1589,10 +2944,14 @@ let OperationsService = class OperationsService {
|
|
|
1589
2944
|
var _a, _b, _c, _d, _e, _f;
|
|
1590
2945
|
const collaborator = await this.querySingle(`SELECT c.id,
|
|
1591
2946
|
c.user_id AS "userId",
|
|
2947
|
+
c.person_id AS "personId",
|
|
2948
|
+
person_record.name AS "personName",
|
|
2949
|
+
person_record.avatar_id AS "personAvatarId",
|
|
1592
2950
|
c.code,
|
|
1593
2951
|
c.collaborator_type AS "collaboratorType",
|
|
1594
|
-
c.display_name AS "displayName",
|
|
1595
|
-
c.
|
|
2952
|
+
COALESCE(NULLIF(c.display_name, ''), person_record.name) AS "displayName",
|
|
2953
|
+
c.department_id AS "departmentId",
|
|
2954
|
+
COALESCE(NULLIF(department_record.name, ''), NULLIF(c.department, '')) AS "department",
|
|
1596
2955
|
c.title,
|
|
1597
2956
|
c.level_label AS "levelLabel",
|
|
1598
2957
|
c.weekly_capacity_hours AS "weeklyCapacityHours",
|
|
@@ -1606,6 +2965,11 @@ let OperationsService = class OperationsService {
|
|
|
1606
2965
|
hiring_contract.status AS "contractStatus",
|
|
1607
2966
|
COUNT(DISTINCT pa.id)::int AS "activeAssignments"
|
|
1608
2967
|
FROM operations_collaborator c
|
|
2968
|
+
LEFT JOIN person person_record
|
|
2969
|
+
ON person_record.id = c.person_id
|
|
2970
|
+
LEFT JOIN operations_department department_record
|
|
2971
|
+
ON department_record.id = c.department_id
|
|
2972
|
+
AND department_record.deleted_at IS NULL
|
|
1609
2973
|
LEFT JOIN operations_collaborator s
|
|
1610
2974
|
ON s.id = c.supervisor_collaborator_id
|
|
1611
2975
|
LEFT JOIN operations_project_assignment pa
|
|
@@ -1623,7 +2987,7 @@ let OperationsService = class OperationsService {
|
|
|
1623
2987
|
) hiring_contract ON TRUE
|
|
1624
2988
|
WHERE c.id = $1
|
|
1625
2989
|
AND c.deleted_at IS NULL
|
|
1626
|
-
GROUP BY c.id, s.id, hiring_contract.id, hiring_contract.status`, [collaboratorId]);
|
|
2990
|
+
GROUP BY c.id, person_record.id, department_record.id, s.id, hiring_contract.id, hiring_contract.status`, [collaboratorId]);
|
|
1627
2991
|
if (!collaborator) {
|
|
1628
2992
|
throw new common_1.NotFoundException('Collaborator not found.');
|
|
1629
2993
|
}
|
|
@@ -1770,7 +3134,7 @@ let OperationsService = class OperationsService {
|
|
|
1770
3134
|
description,
|
|
1771
3135
|
created_at,
|
|
1772
3136
|
updated_at
|
|
1773
|
-
) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`, timesheetId, (_a = entry.projectAssignmentId) !== null && _a !== void 0 ? _a : null, (_b = entry.activityLabel) !== null && _b !== void 0 ? _b : null, entry.workDate, entry.hours, (_c = entry.description) !== null && _c !== void 0 ? _c : null);
|
|
3137
|
+
) VALUES ($1, $2, $3, $4::date, $5, $6, NOW(), NOW())`, timesheetId, (_a = entry.projectAssignmentId) !== null && _a !== void 0 ? _a : null, (_b = entry.activityLabel) !== null && _b !== void 0 ? _b : null, entry.workDate, entry.hours, (_c = entry.description) !== null && _c !== void 0 ? _c : null);
|
|
1774
3138
|
}
|
|
1775
3139
|
}
|
|
1776
3140
|
async refreshTimesheetTotal(client, timesheetId) {
|
|
@@ -1788,7 +3152,7 @@ let OperationsService = class OperationsService {
|
|
|
1788
3152
|
var _a, _b;
|
|
1789
3153
|
const existing = (await client.$queryRawUnsafe(`SELECT id
|
|
1790
3154
|
FROM operations_approval
|
|
1791
|
-
WHERE target_type = $1
|
|
3155
|
+
WHERE target_type = $1::operations_approval_target_type_32d3f04385_enum
|
|
1792
3156
|
AND target_id = $2
|
|
1793
3157
|
AND deleted_at IS NULL`, input.targetType, input.targetId));
|
|
1794
3158
|
let approvalId = (_a = existing[0]) === null || _a === void 0 ? void 0 : _a.id;
|
|
@@ -1802,7 +3166,10 @@ let OperationsService = class OperationsService {
|
|
|
1802
3166
|
submitted_at,
|
|
1803
3167
|
created_at,
|
|
1804
3168
|
updated_at
|
|
1805
|
-
) VALUES (
|
|
3169
|
+
) VALUES (
|
|
3170
|
+
$1::operations_approval_target_type_32d3f04385_enum,
|
|
3171
|
+
$2, $3, $4, 'pending', NOW(), NOW(), NOW()
|
|
3172
|
+
)
|
|
1806
3173
|
RETURNING id`, input.targetType, input.targetId, input.requesterCollaboratorId, input.approverCollaboratorId));
|
|
1807
3174
|
approvalId = (_b = created[0]) === null || _b === void 0 ? void 0 : _b.id;
|
|
1808
3175
|
await this.insertApprovalHistory(client, approvalId, input.requesterCollaboratorId, 'created', null);
|
|
@@ -1828,7 +3195,13 @@ let OperationsService = class OperationsService {
|
|
|
1828
3195
|
action,
|
|
1829
3196
|
note,
|
|
1830
3197
|
created_at
|
|
1831
|
-
) VALUES (
|
|
3198
|
+
) VALUES (
|
|
3199
|
+
$1,
|
|
3200
|
+
$2,
|
|
3201
|
+
$3::operations_approval_history_action_c8478a05d6_enum,
|
|
3202
|
+
$4,
|
|
3203
|
+
NOW()
|
|
3204
|
+
)`, approvalId, actorCollaboratorId, action, note);
|
|
1832
3205
|
}
|
|
1833
3206
|
async assertProjectAccess(actor, projectId) {
|
|
1834
3207
|
if (actor.isDirector)
|
|
@@ -1941,7 +3314,7 @@ let OperationsService = class OperationsService {
|
|
|
1941
3314
|
break_minutes,
|
|
1942
3315
|
created_at,
|
|
1943
3316
|
updated_at
|
|
1944
|
-
) VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())`, collaboratorId, day.weekday, (_a = day.isWorkingDay) !== null && _a !== void 0 ? _a : true, day.isWorkingDay === false ? null : (_b = day.startTime) !== null && _b !== void 0 ? _b : null, day.isWorkingDay === false ? null : (_c = day.endTime) !== null && _c !== void 0 ? _c : null, (_d = day.breakMinutes) !== null && _d !== void 0 ? _d : null);
|
|
3317
|
+
) VALUES ($1, $2::operations_collaborator_schedule_day_weekday_5d79528f6c_enum, $3, $4::time, $5::time, $6, NOW(), NOW())`, collaboratorId, day.weekday, (_a = day.isWorkingDay) !== null && _a !== void 0 ? _a : true, day.isWorkingDay === false ? null : (_b = day.startTime) !== null && _b !== void 0 ? _b : null, day.isWorkingDay === false ? null : (_c = day.endTime) !== null && _c !== void 0 ? _c : null, (_d = day.breakMinutes) !== null && _d !== void 0 ? _d : null);
|
|
1945
3318
|
}
|
|
1946
3319
|
}
|
|
1947
3320
|
async replaceProjectAssignments(client, projectId, teamAssignments) {
|
|
@@ -1965,7 +3338,9 @@ let OperationsService = class OperationsService {
|
|
|
1965
3338
|
created_at,
|
|
1966
3339
|
updated_at
|
|
1967
3340
|
) VALUES (
|
|
1968
|
-
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
3341
|
+
$1, $2, $3, $4, $5, $6, $7::date, $8::date,
|
|
3342
|
+
$9::operations_project_assignment_status_155b459bbf_enum,
|
|
3343
|
+
NOW(), NOW()
|
|
1969
3344
|
)`, projectId, assignment.collaboratorId, (_a = assignment.roleLabel) !== null && _a !== void 0 ? _a : 'Team Member', (_b = assignment.allocationPercent) !== null && _b !== void 0 ? _b : null, (_c = assignment.weeklyHours) !== null && _c !== void 0 ? _c : null, (_d = assignment.isBillable) !== null && _d !== void 0 ? _d : true, (_e = assignment.startDate) !== null && _e !== void 0 ? _e : null, (_f = assignment.endDate) !== null && _f !== void 0 ? _f : null, (_g = assignment.status) !== null && _g !== void 0 ? _g : 'active');
|
|
1970
3345
|
}
|
|
1971
3346
|
}
|
|
@@ -2032,14 +3407,59 @@ let OperationsService = class OperationsService {
|
|
|
2032
3407
|
created_at,
|
|
2033
3408
|
updated_at
|
|
2034
3409
|
) VALUES (
|
|
2035
|
-
$1, $2,
|
|
2036
|
-
|
|
3410
|
+
$1, $2,
|
|
3411
|
+
$3::operations_contract_contract_category_70d553ea09_enum,
|
|
3412
|
+
$4::operations_contract_contract_type_48331e2ebf_enum,
|
|
3413
|
+
$5, 'not_started', true,
|
|
3414
|
+
$6::operations_contract_billing_model_409dc7fea2_enum,
|
|
3415
|
+
$7, $8, 'employee_hiring', $9, $10::date, NULL,
|
|
3416
|
+
NULL, $10::date, $11, $12, 'draft', $13, NULL, $14, $14, NOW(), NOW()
|
|
2037
3417
|
)`, contractCode, contractName, this.mapContractCategoryForCollaboratorType(input.collaboratorType), this.mapContractTypeForCollaboratorType(input.collaboratorType), input.displayName, this.mapBillingModelForCollaboratorType(input.collaboratorType), input.supervisorCollaboratorId, input.collaboratorId, input.collaboratorId, (_a = input.startDate) !== null && _a !== void 0 ? _a : new Date().toISOString().slice(0, 10), (_b = input.compensationAmount) !== null && _b !== void 0 ? _b : null, input.weeklyCapacityHours
|
|
2038
3418
|
? Math.round(Number(input.weeklyCapacityHours) * 4)
|
|
2039
3419
|
: null, (_c = input.description) !== null && _c !== void 0 ? _c : null, createdByUserId);
|
|
2040
3420
|
}
|
|
2041
3421
|
async createProjectContractDraft(client, createdByUserId, input) {
|
|
2042
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
3422
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
3423
|
+
const templateRows = input.contractTemplateId
|
|
3424
|
+
? (await client.$queryRawUnsafe(`SELECT id,
|
|
3425
|
+
code,
|
|
3426
|
+
name,
|
|
3427
|
+
description,
|
|
3428
|
+
contract_category AS "contractCategory",
|
|
3429
|
+
contract_type AS "contractType",
|
|
3430
|
+
billing_model AS "billingModel",
|
|
3431
|
+
signature_status AS "signatureStatus",
|
|
3432
|
+
content_html AS "contentHtml"
|
|
3433
|
+
FROM operations_contract_template
|
|
3434
|
+
WHERE id = $1
|
|
3435
|
+
AND deleted_at IS NULL
|
|
3436
|
+
LIMIT 1`, input.contractTemplateId))
|
|
3437
|
+
: [];
|
|
3438
|
+
const selectedTemplate = (_a = templateRows[0]) !== null && _a !== void 0 ? _a : null;
|
|
3439
|
+
const templateContext = {
|
|
3440
|
+
project_code: input.projectCode,
|
|
3441
|
+
project_name: input.projectName,
|
|
3442
|
+
client_name: input.clientName,
|
|
3443
|
+
start_date: (_b = input.startDate) !== null && _b !== void 0 ? _b : '',
|
|
3444
|
+
end_date: (_c = input.endDate) !== null && _c !== void 0 ? _c : '',
|
|
3445
|
+
budget_amount: input.budgetAmount !== null && input.budgetAmount !== undefined
|
|
3446
|
+
? String(input.budgetAmount)
|
|
3447
|
+
: '',
|
|
3448
|
+
monthly_hour_cap: input.monthlyHourCap !== null && input.monthlyHourCap !== undefined
|
|
3449
|
+
? String(input.monthlyHourCap)
|
|
3450
|
+
: '',
|
|
3451
|
+
};
|
|
3452
|
+
const applyTemplateVariables = (value) => {
|
|
3453
|
+
const source = value !== null && value !== void 0 ? value : '';
|
|
3454
|
+
return Object.entries(templateContext).reduce((result, [key, replacement]) => result.split(`{{${key}}}`).join(replacement || ''), source);
|
|
3455
|
+
};
|
|
3456
|
+
const templateCodePrefix = this.normalizeOptionalText(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.code);
|
|
3457
|
+
const generatedContractCode = ((_e = (_d = this.normalizeOptionalText(input.contractCode)) !== null && _d !== void 0 ? _d : (templateCodePrefix
|
|
3458
|
+
? `${templateCodePrefix}-${input.projectCode}`
|
|
3459
|
+
: null)) !== null && _e !== void 0 ? _e : `PRJ-${input.projectCode}`).slice(0, 40);
|
|
3460
|
+
const generatedContractName = (_g = (_f = this.normalizeOptionalText(input.contractName)) !== null && _f !== void 0 ? _f : this.normalizeOptionalText(applyTemplateVariables(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.name))) !== null && _g !== void 0 ? _g : `${input.projectName} Service Agreement`;
|
|
3461
|
+
const generatedDescription = this.normalizeOptionalText(applyTemplateVariables((_h = input.description) !== null && _h !== void 0 ? _h : selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.description));
|
|
3462
|
+
const generatedContentHtml = this.normalizeOptionalText(applyTemplateVariables(selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contentHtml));
|
|
2043
3463
|
const created = await client.$queryRawUnsafe(`INSERT INTO operations_contract (
|
|
2044
3464
|
code,
|
|
2045
3465
|
name,
|
|
@@ -2051,6 +3471,7 @@ let OperationsService = class OperationsService {
|
|
|
2051
3471
|
billing_model,
|
|
2052
3472
|
account_manager_collaborator_id,
|
|
2053
3473
|
related_collaborator_id,
|
|
3474
|
+
contract_template_id,
|
|
2054
3475
|
origin_type,
|
|
2055
3476
|
origin_id,
|
|
2056
3477
|
start_date,
|
|
@@ -2067,11 +3488,35 @@ let OperationsService = class OperationsService {
|
|
|
2067
3488
|
created_at,
|
|
2068
3489
|
updated_at
|
|
2069
3490
|
) VALUES (
|
|
2070
|
-
$1,
|
|
2071
|
-
$
|
|
3491
|
+
$1,
|
|
3492
|
+
$2,
|
|
3493
|
+
$3::operations_contract_contract_category_70d553ea09_enum,
|
|
3494
|
+
$4::operations_contract_contract_type_48331e2ebf_enum,
|
|
3495
|
+
$5,
|
|
3496
|
+
$6::operations_contract_signature_status_2cb7282a7b_enum,
|
|
3497
|
+
true,
|
|
3498
|
+
$7::operations_contract_billing_model_409dc7fea2_enum,
|
|
3499
|
+
$8,
|
|
3500
|
+
NULL,
|
|
3501
|
+
$9,
|
|
3502
|
+
'client_project',
|
|
3503
|
+
$10,
|
|
3504
|
+
$11::date,
|
|
3505
|
+
$12::date,
|
|
3506
|
+
NULL,
|
|
3507
|
+
$11::date,
|
|
3508
|
+
$13,
|
|
3509
|
+
$14,
|
|
3510
|
+
'draft',
|
|
3511
|
+
$15,
|
|
3512
|
+
$16,
|
|
3513
|
+
$17,
|
|
3514
|
+
$17,
|
|
3515
|
+
NOW(),
|
|
3516
|
+
NOW()
|
|
2072
3517
|
)
|
|
2073
|
-
RETURNING id`, (
|
|
2074
|
-
return (
|
|
3518
|
+
RETURNING id`, generatedContractCode, generatedContractName, (_j = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractCategory) !== null && _j !== void 0 ? _j : 'client', (_k = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.contractType) !== null && _k !== void 0 ? _k : 'service_agreement', input.clientName, (_l = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.signatureStatus) !== null && _l !== void 0 ? _l : 'not_started', (_m = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.billingModel) !== null && _m !== void 0 ? _m : input.billingModel, input.managerCollaboratorId, (_o = selectedTemplate === null || selectedTemplate === void 0 ? void 0 : selectedTemplate.id) !== null && _o !== void 0 ? _o : null, input.projectId, (_p = input.startDate) !== null && _p !== void 0 ? _p : new Date().toISOString().slice(0, 10), (_q = input.endDate) !== null && _q !== void 0 ? _q : null, (_r = input.budgetAmount) !== null && _r !== void 0 ? _r : null, (_s = input.monthlyHourCap) !== null && _s !== void 0 ? _s : null, generatedDescription, generatedContentHtml, createdByUserId);
|
|
3519
|
+
return (_t = created[0]) === null || _t === void 0 ? void 0 : _t.id;
|
|
2075
3520
|
}
|
|
2076
3521
|
async replaceContractParties(client, contractId, parties) {
|
|
2077
3522
|
var _a, _b, _c, _d, _e, _f;
|
|
@@ -2093,7 +3538,10 @@ let OperationsService = class OperationsService {
|
|
|
2093
3538
|
created_at,
|
|
2094
3539
|
updated_at
|
|
2095
3540
|
) VALUES (
|
|
2096
|
-
$1,
|
|
3541
|
+
$1,
|
|
3542
|
+
$2::operations_contract_party_party_role_b66e8515fc_enum,
|
|
3543
|
+
$3::operations_contract_party_party_type_1383bb371c_enum,
|
|
3544
|
+
$4, $5, $6, $7, $8, NOW(), NOW()
|
|
2097
3545
|
)`, contractId, (_a = party.partyRole) !== null && _a !== void 0 ? _a : 'other', (_b = party.partyType) !== null && _b !== void 0 ? _b : 'company', party.displayName, (_c = party.documentNumber) !== null && _c !== void 0 ? _c : null, (_d = party.email) !== null && _d !== void 0 ? _d : null, (_e = party.phone) !== null && _e !== void 0 ? _e : null, (_f = party.isPrimary) !== null && _f !== void 0 ? _f : false);
|
|
2098
3546
|
}
|
|
2099
3547
|
}
|
|
@@ -2115,7 +3563,9 @@ let OperationsService = class OperationsService {
|
|
|
2115
3563
|
created_at,
|
|
2116
3564
|
updated_at
|
|
2117
3565
|
) VALUES (
|
|
2118
|
-
$1, $2, $3, $4,
|
|
3566
|
+
$1, $2, $3, $4,
|
|
3567
|
+
$5::operations_contract_signature_signer_status_1e6fbe2519_enum,
|
|
3568
|
+
$6::timestamp, NOW(), NOW()
|
|
2119
3569
|
)`, contractId, signature.signerName, (_a = signature.signerRole) !== null && _a !== void 0 ? _a : null, (_b = signature.signerEmail) !== null && _b !== void 0 ? _b : null, (_c = signature.status) !== null && _c !== void 0 ? _c : 'pending', (_d = signature.signedAt) !== null && _d !== void 0 ? _d : null);
|
|
2120
3570
|
}
|
|
2121
3571
|
}
|
|
@@ -2138,7 +3588,14 @@ let OperationsService = class OperationsService {
|
|
|
2138
3588
|
created_at,
|
|
2139
3589
|
updated_at
|
|
2140
3590
|
) VALUES (
|
|
2141
|
-
$1,
|
|
3591
|
+
$1,
|
|
3592
|
+
$2::operations_contract_financial_term_term_type_700635c06a_enum,
|
|
3593
|
+
$3,
|
|
3594
|
+
$4,
|
|
3595
|
+
$5::operations_contract_financial_term_recurrence_ba90bbe3bf_enum,
|
|
3596
|
+
$6,
|
|
3597
|
+
$7,
|
|
3598
|
+
NOW(), NOW()
|
|
2142
3599
|
)`, contractId, (_a = term.termType) !== null && _a !== void 0 ? _a : 'value', term.label, term.amount, (_b = term.recurrence) !== null && _b !== void 0 ? _b : 'one_time', (_c = term.dueDay) !== null && _c !== void 0 ? _c : null, (_d = term.notes) !== null && _d !== void 0 ? _d : null);
|
|
2143
3600
|
}
|
|
2144
3601
|
}
|
|
@@ -2160,31 +3617,340 @@ let OperationsService = class OperationsService {
|
|
|
2160
3617
|
created_at,
|
|
2161
3618
|
updated_at
|
|
2162
3619
|
) VALUES (
|
|
2163
|
-
$1,
|
|
3620
|
+
$1,
|
|
3621
|
+
$2::operations_contract_revision_revision_type_cf5ba1a538_enum,
|
|
3622
|
+
$3,
|
|
3623
|
+
$4::date,
|
|
3624
|
+
$5::operations_contract_revision_status_f44f35bb66_enum,
|
|
3625
|
+
$6,
|
|
3626
|
+
NOW(), NOW()
|
|
2164
3627
|
)`, contractId, (_a = revision.revisionType) !== null && _a !== void 0 ? _a : 'revision', revision.title, (_b = revision.effectiveDate) !== null && _b !== void 0 ? _b : null, (_c = revision.status) !== null && _c !== void 0 ? _c : 'draft', (_d = revision.summary) !== null && _d !== void 0 ? _d : null);
|
|
2165
3628
|
}
|
|
2166
3629
|
}
|
|
2167
|
-
async
|
|
2168
|
-
var _a;
|
|
3630
|
+
async replaceContractDocument(client, contractId, documentType, document) {
|
|
3631
|
+
var _a, _b, _c, _d, _e;
|
|
2169
3632
|
await client.$executeRawUnsafe(`UPDATE operations_contract_document
|
|
2170
3633
|
SET is_current = false,
|
|
2171
3634
|
updated_at = NOW()
|
|
2172
3635
|
WHERE contract_id = $1
|
|
2173
3636
|
AND deleted_at IS NULL
|
|
2174
|
-
AND document_type
|
|
3637
|
+
AND document_type = $2`, contractId, documentType);
|
|
2175
3638
|
await client.$executeRawUnsafe(`INSERT INTO operations_contract_document (
|
|
2176
3639
|
contract_id,
|
|
2177
3640
|
document_type,
|
|
3641
|
+
file_id,
|
|
2178
3642
|
file_name,
|
|
2179
3643
|
mime_type,
|
|
2180
3644
|
file_content_base64,
|
|
2181
3645
|
is_current,
|
|
3646
|
+
extraction_status,
|
|
3647
|
+
extraction_summary,
|
|
2182
3648
|
notes,
|
|
2183
3649
|
created_at,
|
|
2184
3650
|
updated_at
|
|
2185
3651
|
) VALUES (
|
|
2186
|
-
$1,
|
|
2187
|
-
)`, contractId, document.fileName, document.mimeType, document.fileContentBase64, (
|
|
3652
|
+
$1, $2, $3, $4, $5, $6, true, $7, $8, $9, NOW(), NOW()
|
|
3653
|
+
)`, contractId, documentType, (_a = document.fileId) !== null && _a !== void 0 ? _a : null, document.fileName, document.mimeType, (_b = document.fileContentBase64) !== null && _b !== void 0 ? _b : null, (_c = document.extractionStatus) !== null && _c !== void 0 ? _c : 'skipped', (_d = document.extractionSummary) !== null && _d !== void 0 ? _d : null, (_e = document.notes) !== null && _e !== void 0 ? _e : null);
|
|
3654
|
+
}
|
|
3655
|
+
async resolveContractExtractionFile(userId, data) {
|
|
3656
|
+
if (data.contractId && data.contractId > 0) {
|
|
3657
|
+
const contract = await this.getContractById(userId, data.contractId);
|
|
3658
|
+
const sourceDocument = contract.documents.find((document) => document.isCurrent && document.documentType === 'source_upload');
|
|
3659
|
+
if (!sourceDocument) {
|
|
3660
|
+
throw new common_1.BadRequestException('No source document is attached to this contract draft.');
|
|
3661
|
+
}
|
|
3662
|
+
if (sourceDocument.fileId) {
|
|
3663
|
+
const { buffer } = await this.fileService.getBuffer(sourceDocument.fileId);
|
|
3664
|
+
return this.buildContractExtractionFile(sourceDocument.fileName, sourceDocument.mimeType, buffer);
|
|
3665
|
+
}
|
|
3666
|
+
const base64 = this.normalizeExtractionString(sourceDocument.fileContentBase64);
|
|
3667
|
+
if (!base64) {
|
|
3668
|
+
throw new common_1.BadRequestException('The current source document does not contain readable file content.');
|
|
3669
|
+
}
|
|
3670
|
+
return this.buildContractExtractionFile(sourceDocument.fileName, sourceDocument.mimeType, Buffer.from(base64, 'base64'));
|
|
3671
|
+
}
|
|
3672
|
+
const fileName = this.normalizeExtractionString(data.fileName);
|
|
3673
|
+
const fileContentBase64 = this.normalizeExtractionString(data.fileContentBase64);
|
|
3674
|
+
const mimeType = this.normalizeContractExtractionMimeType(data.mimeType, fileName);
|
|
3675
|
+
if (!fileName || !fileContentBase64) {
|
|
3676
|
+
throw new common_1.BadRequestException('fileName and fileContentBase64 are required.');
|
|
3677
|
+
}
|
|
3678
|
+
return this.buildContractExtractionFile(fileName, mimeType, Buffer.from(fileContentBase64, 'base64'));
|
|
3679
|
+
}
|
|
3680
|
+
buildContractExtractionFile(fileName, mimeType, buffer) {
|
|
3681
|
+
if (!this.isAllowedContractExtractionMimeType(mimeType)) {
|
|
3682
|
+
throw new common_1.BadRequestException('Only PDF, DOC, and DOCX files are supported for contract extraction.');
|
|
3683
|
+
}
|
|
3684
|
+
if (!buffer.length) {
|
|
3685
|
+
throw new common_1.BadRequestException('Unable to decode the uploaded contract document.');
|
|
3686
|
+
}
|
|
3687
|
+
if (buffer.length > 12 * 1024 * 1024) {
|
|
3688
|
+
throw new common_1.BadRequestException('The uploaded contract document must be smaller than 12 MB.');
|
|
3689
|
+
}
|
|
3690
|
+
return {
|
|
3691
|
+
fieldname: 'file',
|
|
3692
|
+
originalname: fileName,
|
|
3693
|
+
encoding: 'base64',
|
|
3694
|
+
mimetype: mimeType,
|
|
3695
|
+
size: buffer.length,
|
|
3696
|
+
destination: '',
|
|
3697
|
+
filename: fileName,
|
|
3698
|
+
path: '',
|
|
3699
|
+
buffer,
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
async renderContractPdfBuffer(contract) {
|
|
3703
|
+
var _a;
|
|
3704
|
+
const html = await this.buildContractPdfHtml(contract);
|
|
3705
|
+
let browser = null;
|
|
3706
|
+
try {
|
|
3707
|
+
const importPlaywright = new Function('moduleName', 'return import(moduleName);');
|
|
3708
|
+
const playwright = await importPlaywright('playwright');
|
|
3709
|
+
browser = await playwright.chromium.launch({ headless: true });
|
|
3710
|
+
const page = await browser.newPage();
|
|
3711
|
+
await page.setContent(html, { waitUntil: 'networkidle' });
|
|
3712
|
+
return Buffer.from(await page.pdf({
|
|
3713
|
+
format: 'A4',
|
|
3714
|
+
printBackground: true,
|
|
3715
|
+
margin: {
|
|
3716
|
+
top: '72px',
|
|
3717
|
+
right: '18px',
|
|
3718
|
+
bottom: '56px',
|
|
3719
|
+
left: '18px',
|
|
3720
|
+
},
|
|
3721
|
+
}));
|
|
3722
|
+
}
|
|
3723
|
+
catch (error) {
|
|
3724
|
+
throw new common_1.BadRequestException('PDF generation requires Playwright to be installed on the server.');
|
|
3725
|
+
}
|
|
3726
|
+
finally {
|
|
3727
|
+
await ((_a = browser === null || browser === void 0 ? void 0 : browser.close) === null || _a === void 0 ? void 0 : _a.call(browser));
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
async buildContractPdfHtml(contract) {
|
|
3731
|
+
var _a, _b, _c, _d, _e;
|
|
3732
|
+
const logoUrl = await this.resolveContractPdfLogoUrl();
|
|
3733
|
+
const contentHtml = (_c = (_a = this.normalizeOptionalText(contract.contentHtml)) !== null && _a !== void 0 ? _a : (_b = this.normalizeOptionalText(contract.description)) === null || _b === void 0 ? void 0 : _b.split(/\n{2,}/).map((paragraph) => `<p>${this.escapeHtml(paragraph)}</p>`).join('')) !== null && _c !== void 0 ? _c : '<p>No contract body was provided yet.</p>';
|
|
3734
|
+
const partiesHtml = ((_d = contract.parties) !== null && _d !== void 0 ? _d : []).length
|
|
3735
|
+
? contract.parties
|
|
3736
|
+
.map((party) => `
|
|
3737
|
+
<li>
|
|
3738
|
+
<strong>${this.escapeHtml(party.displayName || 'Party')}</strong>
|
|
3739
|
+
<span>${this.escapeHtml([party.partyRole, party.partyType].filter(Boolean).join(' • '))}</span>
|
|
3740
|
+
</li>`)
|
|
3741
|
+
.join('')
|
|
3742
|
+
: '<li><strong>No parties registered yet.</strong><span>Complete this contract later if needed.</span></li>';
|
|
3743
|
+
const financialTermsHtml = ((_e = contract.financialTerms) !== null && _e !== void 0 ? _e : []).length
|
|
3744
|
+
? contract.financialTerms
|
|
3745
|
+
.map((term) => `
|
|
3746
|
+
<li>
|
|
3747
|
+
<strong>${this.escapeHtml(term.label || 'Term')}</strong>
|
|
3748
|
+
<span>${this.escapeHtml([term.termType, term.amount, term.recurrence]
|
|
3749
|
+
.filter((item) => item !== null && item !== undefined && String(item).trim())
|
|
3750
|
+
.join(' • '))}</span>
|
|
3751
|
+
</li>`)
|
|
3752
|
+
.join('')
|
|
3753
|
+
: '<li><strong>No financial terms registered yet.</strong><span>The draft is intentionally lightweight.</span></li>';
|
|
3754
|
+
return `<!DOCTYPE html>
|
|
3755
|
+
<html lang="en">
|
|
3756
|
+
<head>
|
|
3757
|
+
<meta charset="utf-8" />
|
|
3758
|
+
<style>
|
|
3759
|
+
@page {
|
|
3760
|
+
size: A4;
|
|
3761
|
+
margin: 72px 18px 56px 18px;
|
|
3762
|
+
}
|
|
3763
|
+
body {
|
|
3764
|
+
color: #0f172a;
|
|
3765
|
+
font-family: Arial, sans-serif;
|
|
3766
|
+
font-size: 12px;
|
|
3767
|
+
line-height: 1.5;
|
|
3768
|
+
margin: 0;
|
|
3769
|
+
}
|
|
3770
|
+
header {
|
|
3771
|
+
align-items: center;
|
|
3772
|
+
border-bottom: 2px solid #dbeafe;
|
|
3773
|
+
display: flex;
|
|
3774
|
+
gap: 16px;
|
|
3775
|
+
justify-content: space-between;
|
|
3776
|
+
margin-bottom: 24px;
|
|
3777
|
+
padding-bottom: 16px;
|
|
3778
|
+
}
|
|
3779
|
+
.brand {
|
|
3780
|
+
align-items: center;
|
|
3781
|
+
display: flex;
|
|
3782
|
+
gap: 12px;
|
|
3783
|
+
}
|
|
3784
|
+
.brand img {
|
|
3785
|
+
height: 42px;
|
|
3786
|
+
max-width: 140px;
|
|
3787
|
+
object-fit: contain;
|
|
3788
|
+
}
|
|
3789
|
+
.eyebrow {
|
|
3790
|
+
color: #2563eb;
|
|
3791
|
+
font-size: 10px;
|
|
3792
|
+
font-weight: 700;
|
|
3793
|
+
letter-spacing: 0.12em;
|
|
3794
|
+
text-transform: uppercase;
|
|
3795
|
+
}
|
|
3796
|
+
h1 {
|
|
3797
|
+
font-size: 22px;
|
|
3798
|
+
margin: 4px 0 0;
|
|
3799
|
+
}
|
|
3800
|
+
.meta-grid {
|
|
3801
|
+
display: grid;
|
|
3802
|
+
gap: 12px;
|
|
3803
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
3804
|
+
margin: 20px 0;
|
|
3805
|
+
}
|
|
3806
|
+
.meta-card {
|
|
3807
|
+
background: #f8fafc;
|
|
3808
|
+
border: 1px solid #dbeafe;
|
|
3809
|
+
border-radius: 12px;
|
|
3810
|
+
padding: 12px;
|
|
3811
|
+
}
|
|
3812
|
+
.meta-card strong {
|
|
3813
|
+
display: block;
|
|
3814
|
+
font-size: 10px;
|
|
3815
|
+
letter-spacing: 0.08em;
|
|
3816
|
+
margin-bottom: 4px;
|
|
3817
|
+
text-transform: uppercase;
|
|
3818
|
+
}
|
|
3819
|
+
section {
|
|
3820
|
+
margin-top: 20px;
|
|
3821
|
+
}
|
|
3822
|
+
h2 {
|
|
3823
|
+
color: #1d4ed8;
|
|
3824
|
+
font-size: 14px;
|
|
3825
|
+
margin: 0 0 10px;
|
|
3826
|
+
}
|
|
3827
|
+
ul {
|
|
3828
|
+
list-style: none;
|
|
3829
|
+
margin: 0;
|
|
3830
|
+
padding: 0;
|
|
3831
|
+
}
|
|
3832
|
+
li {
|
|
3833
|
+
border: 1px solid #e2e8f0;
|
|
3834
|
+
border-radius: 10px;
|
|
3835
|
+
margin-bottom: 8px;
|
|
3836
|
+
padding: 10px 12px;
|
|
3837
|
+
}
|
|
3838
|
+
li strong {
|
|
3839
|
+
display: block;
|
|
3840
|
+
margin-bottom: 2px;
|
|
3841
|
+
}
|
|
3842
|
+
footer {
|
|
3843
|
+
border-top: 1px solid #dbeafe;
|
|
3844
|
+
color: #475569;
|
|
3845
|
+
font-size: 10px;
|
|
3846
|
+
margin-top: 28px;
|
|
3847
|
+
padding-top: 12px;
|
|
3848
|
+
}
|
|
3849
|
+
.content {
|
|
3850
|
+
border: 1px solid #e2e8f0;
|
|
3851
|
+
border-radius: 14px;
|
|
3852
|
+
padding: 18px;
|
|
3853
|
+
}
|
|
3854
|
+
.content p:first-child {
|
|
3855
|
+
margin-top: 0;
|
|
3856
|
+
}
|
|
3857
|
+
</style>
|
|
3858
|
+
</head>
|
|
3859
|
+
<body>
|
|
3860
|
+
<header>
|
|
3861
|
+
<div class="brand">
|
|
3862
|
+
<img src="${logoUrl}" alt="System logo" />
|
|
3863
|
+
<div>
|
|
3864
|
+
<div class="eyebrow">Contract Document</div>
|
|
3865
|
+
<h1>${this.escapeHtml(contract.name || contract.code || 'Contract draft')}</h1>
|
|
3866
|
+
</div>
|
|
3867
|
+
</div>
|
|
3868
|
+
<div>
|
|
3869
|
+
<div><strong>Code:</strong> ${this.escapeHtml(contract.code || 'Draft')}</div>
|
|
3870
|
+
<div><strong>Status:</strong> ${this.escapeHtml(this.humanizeEnumLabel(contract.status || 'draft'))}</div>
|
|
3871
|
+
</div>
|
|
3872
|
+
</header>
|
|
3873
|
+
|
|
3874
|
+
<div class="meta-grid">
|
|
3875
|
+
<div class="meta-card">
|
|
3876
|
+
<strong>Client</strong>
|
|
3877
|
+
${this.escapeHtml(contract.clientName || 'Not informed yet')}
|
|
3878
|
+
</div>
|
|
3879
|
+
<div class="meta-card">
|
|
3880
|
+
<strong>Type</strong>
|
|
3881
|
+
${this.escapeHtml(this.humanizeEnumLabel(contract.contractType || 'service_agreement'))}
|
|
3882
|
+
</div>
|
|
3883
|
+
<div class="meta-card">
|
|
3884
|
+
<strong>Period</strong>
|
|
3885
|
+
${this.escapeHtml([contract.startDate || 'Open start', contract.endDate || 'Open end'].join(' - '))}
|
|
3886
|
+
</div>
|
|
3887
|
+
<div class="meta-card">
|
|
3888
|
+
<strong>Billing</strong>
|
|
3889
|
+
${this.escapeHtml(this.humanizeEnumLabel(contract.billingModel || 'time_and_material'))}
|
|
3890
|
+
</div>
|
|
3891
|
+
</div>
|
|
3892
|
+
|
|
3893
|
+
<section>
|
|
3894
|
+
<h2>Related Parties</h2>
|
|
3895
|
+
<ul>${partiesHtml}</ul>
|
|
3896
|
+
</section>
|
|
3897
|
+
|
|
3898
|
+
<section>
|
|
3899
|
+
<h2>Financial Terms</h2>
|
|
3900
|
+
<ul>${financialTermsHtml}</ul>
|
|
3901
|
+
</section>
|
|
3902
|
+
|
|
3903
|
+
<section>
|
|
3904
|
+
<h2>Contract Body</h2>
|
|
3905
|
+
<div class="content">${contentHtml}</div>
|
|
3906
|
+
</section>
|
|
3907
|
+
|
|
3908
|
+
<footer>
|
|
3909
|
+
Generated on ${this.escapeHtml(new Date().toISOString())} for ${this.escapeHtml(contract.code || 'draft')}.
|
|
3910
|
+
</footer>
|
|
3911
|
+
</body>
|
|
3912
|
+
</html>`;
|
|
3913
|
+
}
|
|
3914
|
+
async resolveContractPdfLogoUrl() {
|
|
3915
|
+
var _a;
|
|
3916
|
+
const settings = await this.settingService.getSettingValues([
|
|
3917
|
+
'image-url',
|
|
3918
|
+
'icon-url',
|
|
3919
|
+
]);
|
|
3920
|
+
const configuredUrl = (_a = this.normalizeOptionalText(settings['image-url'])) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(settings['icon-url']);
|
|
3921
|
+
if (configuredUrl && /^https?:\/\//i.test(configuredUrl)) {
|
|
3922
|
+
return configuredUrl;
|
|
3923
|
+
}
|
|
3924
|
+
const inlineSvg = encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72">
|
|
3925
|
+
<rect width="240" height="72" rx="18" fill="#0f172a"/>
|
|
3926
|
+
<circle cx="38" cy="36" r="18" fill="#3b82f6"/>
|
|
3927
|
+
<text x="72" y="43" fill="#ffffff" font-size="26" font-family="Arial, sans-serif" font-weight="700">HedHog</text>
|
|
3928
|
+
</svg>`);
|
|
3929
|
+
return `data:image/svg+xml;charset=UTF-8,${inlineSvg}`;
|
|
3930
|
+
}
|
|
3931
|
+
buildContractPdfFileName(contract) {
|
|
3932
|
+
var _a, _b;
|
|
3933
|
+
const base = (_b = (_a = this.normalizeOptionalText(contract.code)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(contract.name)) !== null && _b !== void 0 ? _b : `contract-${contract.id}`;
|
|
3934
|
+
return `${base
|
|
3935
|
+
.toLowerCase()
|
|
3936
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
3937
|
+
.replace(/^-+|-+$/g, '')
|
|
3938
|
+
.slice(0, 80) || `contract-${contract.id}`}.pdf`;
|
|
3939
|
+
}
|
|
3940
|
+
humanizeEnumLabel(value) {
|
|
3941
|
+
return String(value !== null && value !== void 0 ? value : '')
|
|
3942
|
+
.split('_')
|
|
3943
|
+
.filter(Boolean)
|
|
3944
|
+
.map((part) => { var _a; return ((_a = part[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) + part.slice(1); })
|
|
3945
|
+
.join(' ');
|
|
3946
|
+
}
|
|
3947
|
+
escapeHtml(value) {
|
|
3948
|
+
return String(value !== null && value !== void 0 ? value : '')
|
|
3949
|
+
.replace(/&/g, '&')
|
|
3950
|
+
.replace(/</g, '<')
|
|
3951
|
+
.replace(/>/g, '>')
|
|
3952
|
+
.replace(/"/g, '"')
|
|
3953
|
+
.replace(/'/g, ''');
|
|
2188
3954
|
}
|
|
2189
3955
|
async insertContractHistory(client, contractId, actorUserId, action, note, metadataJson) {
|
|
2190
3956
|
await client.$executeRawUnsafe(`INSERT INTO operations_contract_history (
|
|
@@ -2198,6 +3964,555 @@ let OperationsService = class OperationsService {
|
|
|
2198
3964
|
$1, $2, $3, $4, $5, NOW()
|
|
2199
3965
|
)`, contractId, actorUserId, action, note, metadataJson !== null && metadataJson !== void 0 ? metadataJson : null);
|
|
2200
3966
|
}
|
|
3967
|
+
async generateCollaboratorCode(client) {
|
|
3968
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
3969
|
+
const candidate = `COL-${Date.now().toString(36).toUpperCase()}${Math.random()
|
|
3970
|
+
.toString(36)
|
|
3971
|
+
.slice(2, 6)
|
|
3972
|
+
.toUpperCase()}`;
|
|
3973
|
+
const existing = (await client.$queryRawUnsafe(`SELECT code
|
|
3974
|
+
FROM operations_collaborator
|
|
3975
|
+
WHERE code = $1
|
|
3976
|
+
LIMIT 1`, candidate));
|
|
3977
|
+
if (!existing.length) {
|
|
3978
|
+
return candidate;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
throw new common_1.BadRequestException('Unable to generate collaborator code.');
|
|
3982
|
+
}
|
|
3983
|
+
async generateContractCode(client) {
|
|
3984
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
3985
|
+
const candidate = `CTR-${Date.now().toString(36).toUpperCase()}${Math.random()
|
|
3986
|
+
.toString(36)
|
|
3987
|
+
.slice(2, 6)
|
|
3988
|
+
.toUpperCase()}`.slice(0, 40);
|
|
3989
|
+
const existing = (await client.$queryRawUnsafe(`SELECT code
|
|
3990
|
+
FROM operations_contract
|
|
3991
|
+
WHERE code = $1
|
|
3992
|
+
LIMIT 1`, candidate));
|
|
3993
|
+
if (!existing.length) {
|
|
3994
|
+
return candidate;
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
throw new common_1.BadRequestException('Unable to generate contract code.');
|
|
3998
|
+
}
|
|
3999
|
+
async generateContractContentHtml(contract, data = {}) {
|
|
4000
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
|
|
4001
|
+
const currentContent = this.normalizeOptionalText(contract.contentHtml);
|
|
4002
|
+
const primaryParty = (_d = (_b = ((_a = contract.parties) !== null && _a !== void 0 ? _a : []).find((party) => party.isPrimary)) !== null && _b !== void 0 ? _b : ((_c = contract.parties) !== null && _c !== void 0 ? _c : [])[0]) !== null && _d !== void 0 ? _d : null;
|
|
4003
|
+
const templateContext = {
|
|
4004
|
+
contract_code: (_e = this.normalizeOptionalText(contract.code)) !== null && _e !== void 0 ? _e : '',
|
|
4005
|
+
contract_name: (_f = this.normalizeOptionalText(contract.name)) !== null && _f !== void 0 ? _f : '',
|
|
4006
|
+
client_name: (_h = (_g = this.normalizeOptionalText(contract.clientName)) !== null && _g !== void 0 ? _g : this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.displayName)) !== null && _h !== void 0 ? _h : '',
|
|
4007
|
+
contract_category: (_j = this.normalizeOptionalText(contract.contractCategory)) !== null && _j !== void 0 ? _j : '',
|
|
4008
|
+
contract_type: (_k = this.normalizeOptionalText(contract.contractType)) !== null && _k !== void 0 ? _k : '',
|
|
4009
|
+
start_date: (_l = this.normalizeOptionalText(contract.startDate)) !== null && _l !== void 0 ? _l : '',
|
|
4010
|
+
end_date: (_m = this.normalizeOptionalText(contract.endDate)) !== null && _m !== void 0 ? _m : '',
|
|
4011
|
+
effective_date: (_o = this.normalizeOptionalText(contract.effectiveDate)) !== null && _o !== void 0 ? _o : '',
|
|
4012
|
+
signed_at: (_p = this.normalizeOptionalText(contract.signedAt)) !== null && _p !== void 0 ? _p : '',
|
|
4013
|
+
budget_amount: contract.budgetAmount !== null && contract.budgetAmount !== undefined
|
|
4014
|
+
? String(contract.budgetAmount)
|
|
4015
|
+
: '',
|
|
4016
|
+
monthly_hour_cap: contract.monthlyHourCap !== null &&
|
|
4017
|
+
contract.monthlyHourCap !== undefined
|
|
4018
|
+
? String(contract.monthlyHourCap)
|
|
4019
|
+
: '',
|
|
4020
|
+
primary_party_name: (_q = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.displayName)) !== null && _q !== void 0 ? _q : '',
|
|
4021
|
+
primary_party_document: (_r = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.documentNumber)) !== null && _r !== void 0 ? _r : '',
|
|
4022
|
+
primary_party_email: (_s = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.email)) !== null && _s !== void 0 ? _s : '',
|
|
4023
|
+
primary_party_phone: (_t = this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.phone)) !== null && _t !== void 0 ? _t : '',
|
|
4024
|
+
description: (_u = this.normalizeOptionalText(contract.description)) !== null && _u !== void 0 ? _u : '',
|
|
4025
|
+
};
|
|
4026
|
+
const applyTemplateVariables = (value) => {
|
|
4027
|
+
const source = value !== null && value !== void 0 ? value : '';
|
|
4028
|
+
return Object.entries(templateContext).reduce((result, [key, replacement]) => result.split(`{{${key}}}`).join(replacement || ''), source);
|
|
4029
|
+
};
|
|
4030
|
+
const currentHtmlWithVariables = this.normalizeOptionalText(applyTemplateVariables(currentContent));
|
|
4031
|
+
if (currentHtmlWithVariables && data.overwrite !== true) {
|
|
4032
|
+
return currentHtmlWithVariables;
|
|
4033
|
+
}
|
|
4034
|
+
const baseHtml = currentHtmlWithVariables !== null && currentHtmlWithVariables !== void 0 ? currentHtmlWithVariables : this.buildFallbackContractContent(contract);
|
|
4035
|
+
const promptMessage = (_v = this.normalizeOptionalText(data.promptMessage)) !== null && _v !== void 0 ? _v : 'Generate an initial contract body using the registered metadata and keep the language concise, professional, and ready for legal review.';
|
|
4036
|
+
try {
|
|
4037
|
+
const aiResult = await this.aiService.chat({
|
|
4038
|
+
provider: data.provider === 'gemini' ? 'gemini' : 'openai',
|
|
4039
|
+
model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
|
|
4040
|
+
message: promptMessage,
|
|
4041
|
+
systemPrompt: [
|
|
4042
|
+
'You draft professional business contract content for an operations back-office system.',
|
|
4043
|
+
'Return ONLY clean HTML suitable for a rich text editor preview.',
|
|
4044
|
+
'Do not include Markdown fences, commentary, or legal disclaimers inside the HTML.',
|
|
4045
|
+
'Keep the output concise, well-structured, and ready for legal revision.',
|
|
4046
|
+
'If a specific detail is missing, preserve placeholders like {{client_name}}, {{start_date}}, {{end_date}}, {{budget_amount}}, and {{monthly_hour_cap}} instead of inventing facts.',
|
|
4047
|
+
`Contract context:\n${JSON.stringify(templateContext, null, 2)}`,
|
|
4048
|
+
baseHtml
|
|
4049
|
+
? `Current base HTML:\n${baseHtml}`
|
|
4050
|
+
: 'There is no current HTML yet.',
|
|
4051
|
+
].join('\n\n'),
|
|
4052
|
+
});
|
|
4053
|
+
return ((_x = this.normalizeOptionalText(String((_w = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _w !== void 0 ? _w : ''))) !== null && _x !== void 0 ? _x : baseHtml);
|
|
4054
|
+
}
|
|
4055
|
+
catch (_y) {
|
|
4056
|
+
return baseHtml;
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
buildFallbackContractContent(contract) {
|
|
4060
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
4061
|
+
const title = this.escapeHtml((_b = (_a = this.normalizeOptionalText(contract.name)) !== null && _a !== void 0 ? _a : this.normalizeOptionalText(contract.code)) !== null && _b !== void 0 ? _b : 'Contract Draft');
|
|
4062
|
+
const description = this.escapeHtml((_c = this.normalizeOptionalText(contract.description)) !== null && _c !== void 0 ? _c : 'This draft summarizes the main commercial and operational conditions agreed between the parties.');
|
|
4063
|
+
const validityItems = [
|
|
4064
|
+
contract.startDate ? `Start date: ${contract.startDate}` : null,
|
|
4065
|
+
contract.endDate ? `End date: ${contract.endDate}` : null,
|
|
4066
|
+
contract.effectiveDate ? `Effective date: ${contract.effectiveDate}` : null,
|
|
4067
|
+
contract.signedAt ? `Signed at: ${contract.signedAt}` : null,
|
|
4068
|
+
]
|
|
4069
|
+
.filter(Boolean)
|
|
4070
|
+
.map((item) => `<li>${this.escapeHtml(String(item))}</li>`)
|
|
4071
|
+
.join('');
|
|
4072
|
+
const partiesHtml = ((_d = contract.parties) !== null && _d !== void 0 ? _d : []).length
|
|
4073
|
+
? ((_e = contract.parties) !== null && _e !== void 0 ? _e : [])
|
|
4074
|
+
.map((party) => `
|
|
4075
|
+
<li>
|
|
4076
|
+
<strong>${this.escapeHtml(party.displayName || 'Party')}</strong>
|
|
4077
|
+
<span>${this.escapeHtml([party.partyRole, party.partyType, party.documentNumber]
|
|
4078
|
+
.filter(Boolean)
|
|
4079
|
+
.join(' • '))}</span>
|
|
4080
|
+
</li>`)
|
|
4081
|
+
.join('')
|
|
4082
|
+
: '<li><strong>Main party</strong><span>{{client_name}}</span></li>';
|
|
4083
|
+
const financialTermsHtml = ((_f = contract.financialTerms) !== null && _f !== void 0 ? _f : []).length
|
|
4084
|
+
? ((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : [])
|
|
4085
|
+
.map((term) => `
|
|
4086
|
+
<li>
|
|
4087
|
+
<strong>${this.escapeHtml(term.label || 'Financial term')}</strong>
|
|
4088
|
+
<span>${this.escapeHtml([term.termType, term.amount, term.recurrence]
|
|
4089
|
+
.filter((item) => item !== null &&
|
|
4090
|
+
item !== undefined &&
|
|
4091
|
+
String(item).trim())
|
|
4092
|
+
.join(' • '))}</span>
|
|
4093
|
+
</li>`)
|
|
4094
|
+
.join('')
|
|
4095
|
+
: '<li><strong>Commercial conditions</strong><span>Define prices, billing cadence, and penalties before signature.</span></li>';
|
|
4096
|
+
return `
|
|
4097
|
+
<h1>${title}</h1>
|
|
4098
|
+
<p>${description}</p>
|
|
4099
|
+
<h2>1. Parties</h2>
|
|
4100
|
+
<ul>${partiesHtml}</ul>
|
|
4101
|
+
<h2>2. Scope and object</h2>
|
|
4102
|
+
<p>The parties agree to execute the services and obligations described in this contract, according to the agreed operational, technical, and commercial conditions.</p>
|
|
4103
|
+
<h2>3. Validity</h2>
|
|
4104
|
+
${validityItems ? `<ul>${validityItems}</ul>` : '<p>Define start date, term, renewal, and termination conditions.</p>'}
|
|
4105
|
+
<h2>4. Financial terms</h2>
|
|
4106
|
+
<ul>${financialTermsHtml}</ul>
|
|
4107
|
+
<h2>5. General obligations</h2>
|
|
4108
|
+
<p>Review confidentiality, data protection, service levels, termination, and liability clauses before formal approval.</p>
|
|
4109
|
+
`;
|
|
4110
|
+
}
|
|
4111
|
+
async buildContractLegalReview(contract, data = {}) {
|
|
4112
|
+
var _a, _b, _c;
|
|
4113
|
+
const heuristicReview = this.buildHeuristicContractLegalReview(contract);
|
|
4114
|
+
const promptMessage = (_a = this.normalizeOptionalText(data.promptMessage)) !== null && _a !== void 0 ? _a : 'Review this contract draft and return an advisory legal checklist with practical warnings and missing items.';
|
|
4115
|
+
try {
|
|
4116
|
+
const aiResult = await this.aiService.chat({
|
|
4117
|
+
provider: data.provider === 'gemini' ? 'gemini' : 'openai',
|
|
4118
|
+
model: data.provider === 'gemini' ? 'gemini-1.5-flash' : 'gpt-4o-mini',
|
|
4119
|
+
message: promptMessage,
|
|
4120
|
+
systemPrompt: [
|
|
4121
|
+
'You are an advisory legal-review assistant for business contracts.',
|
|
4122
|
+
'Return ONLY valid JSON. Do not include Markdown fences or commentary.',
|
|
4123
|
+
'This review is non-blocking and should help a human reviewer spot likely gaps.',
|
|
4124
|
+
'Expected JSON shape:',
|
|
4125
|
+
JSON.stringify({
|
|
4126
|
+
summary: '',
|
|
4127
|
+
missingFields: [],
|
|
4128
|
+
warnings: [],
|
|
4129
|
+
checklist: [],
|
|
4130
|
+
status: 'attention_required',
|
|
4131
|
+
}, null, 2),
|
|
4132
|
+
`Contract metadata:\n${JSON.stringify({
|
|
4133
|
+
code: contract.code,
|
|
4134
|
+
name: contract.name,
|
|
4135
|
+
clientName: contract.clientName,
|
|
4136
|
+
contractCategory: contract.contractCategory,
|
|
4137
|
+
contractType: contract.contractType,
|
|
4138
|
+
startDate: contract.startDate,
|
|
4139
|
+
endDate: contract.endDate,
|
|
4140
|
+
effectiveDate: contract.effectiveDate,
|
|
4141
|
+
signatureStatus: contract.signatureStatus,
|
|
4142
|
+
parties: contract.parties,
|
|
4143
|
+
financialTerms: contract.financialTerms,
|
|
4144
|
+
}, null, 2)}`,
|
|
4145
|
+
`Contract HTML:\n${(_b = this.normalizeOptionalText(contract.contentHtml)) !== null && _b !== void 0 ? _b : ''}`,
|
|
4146
|
+
].join('\n\n'),
|
|
4147
|
+
});
|
|
4148
|
+
const parsed = this.parseAiJsonPayload(String((_c = aiResult === null || aiResult === void 0 ? void 0 : aiResult.content) !== null && _c !== void 0 ? _c : ''));
|
|
4149
|
+
const missingFields = Array.from(new Set([
|
|
4150
|
+
...heuristicReview.missingFields,
|
|
4151
|
+
...this.normalizeStringList(parsed.missingFields),
|
|
4152
|
+
]));
|
|
4153
|
+
const warnings = Array.from(new Set([
|
|
4154
|
+
...heuristicReview.warnings,
|
|
4155
|
+
...this.normalizeStringList(parsed.warnings),
|
|
4156
|
+
]));
|
|
4157
|
+
const checklist = Array.from(new Set([
|
|
4158
|
+
...this.normalizeStringList(parsed.checklist),
|
|
4159
|
+
...heuristicReview.checklist,
|
|
4160
|
+
]));
|
|
4161
|
+
const parsedStatus = this.normalizeExtractionString(parsed.status);
|
|
4162
|
+
const status = parsedStatus === 'ready_for_revision' ||
|
|
4163
|
+
parsedStatus === 'attention_required'
|
|
4164
|
+
? parsedStatus
|
|
4165
|
+
: missingFields.length || warnings.length
|
|
4166
|
+
? 'attention_required'
|
|
4167
|
+
: 'ready_for_revision';
|
|
4168
|
+
return {
|
|
4169
|
+
summary: this.normalizeExtractionString(parsed.summary) ||
|
|
4170
|
+
heuristicReview.summary,
|
|
4171
|
+
missingFields,
|
|
4172
|
+
warnings,
|
|
4173
|
+
checklist,
|
|
4174
|
+
status,
|
|
4175
|
+
reviewedAt: new Date().toISOString(),
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
catch (_d) {
|
|
4179
|
+
return heuristicReview;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
buildHeuristicContractLegalReview(contract) {
|
|
4183
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
4184
|
+
const contentText = ((_a = this.normalizeOptionalText(contract.contentHtml)) !== null && _a !== void 0 ? _a : '')
|
|
4185
|
+
.replace(/<[^>]+>/g, ' ')
|
|
4186
|
+
.replace(/\s+/g, ' ')
|
|
4187
|
+
.trim()
|
|
4188
|
+
.toLowerCase();
|
|
4189
|
+
const primaryParty = (_e = (_c = ((_b = contract.parties) !== null && _b !== void 0 ? _b : []).find((party) => party.isPrimary)) !== null && _c !== void 0 ? _c : ((_d = contract.parties) !== null && _d !== void 0 ? _d : [])[0]) !== null && _e !== void 0 ? _e : null;
|
|
4190
|
+
const missingFields = [];
|
|
4191
|
+
const warnings = [];
|
|
4192
|
+
const checklist = [];
|
|
4193
|
+
if (!this.normalizeOptionalText(contract.name)) {
|
|
4194
|
+
missingFields.push('Contract title');
|
|
4195
|
+
checklist.push('Attention: define a clear contract title.');
|
|
4196
|
+
}
|
|
4197
|
+
else {
|
|
4198
|
+
checklist.push('OK: contract title is registered.');
|
|
4199
|
+
}
|
|
4200
|
+
if (!this.normalizeOptionalText(contract.clientName)) {
|
|
4201
|
+
missingFields.push('Client or primary party name');
|
|
4202
|
+
checklist.push('Attention: confirm the client or main party identification.');
|
|
4203
|
+
}
|
|
4204
|
+
else {
|
|
4205
|
+
checklist.push('OK: the main party is identified.');
|
|
4206
|
+
}
|
|
4207
|
+
if (!contract.startDate && !contract.effectiveDate) {
|
|
4208
|
+
missingFields.push('Start or effective date');
|
|
4209
|
+
checklist.push('Attention: define the term start or effective date.');
|
|
4210
|
+
}
|
|
4211
|
+
else {
|
|
4212
|
+
checklist.push('OK: validity dates were provided.');
|
|
4213
|
+
}
|
|
4214
|
+
if (!((_f = contract.parties) !== null && _f !== void 0 ? _f : []).length) {
|
|
4215
|
+
missingFields.push('Related parties');
|
|
4216
|
+
warnings.push('No related parties are registered in the contract draft.');
|
|
4217
|
+
}
|
|
4218
|
+
if (!this.normalizeOptionalText(primaryParty === null || primaryParty === void 0 ? void 0 : primaryParty.documentNumber)) {
|
|
4219
|
+
warnings.push('Consider confirming the primary party document number.');
|
|
4220
|
+
}
|
|
4221
|
+
if (!((_g = contract.financialTerms) !== null && _g !== void 0 ? _g : []).length && contract.budgetAmount == null) {
|
|
4222
|
+
warnings.push('Commercial conditions are still generic; define prices, recurrence, and penalties.');
|
|
4223
|
+
checklist.push('Attention: validate billing, adjustments, and penalties.');
|
|
4224
|
+
}
|
|
4225
|
+
else {
|
|
4226
|
+
checklist.push('OK: there is at least one financial reference to review.');
|
|
4227
|
+
}
|
|
4228
|
+
if (!contentText) {
|
|
4229
|
+
missingFields.push('Contract body content');
|
|
4230
|
+
warnings.push('The contract body is still empty and needs a first draft.');
|
|
4231
|
+
}
|
|
4232
|
+
else {
|
|
4233
|
+
if (contentText.length < 600) {
|
|
4234
|
+
warnings.push('The contract body is still brief; validate scope, obligations, and exceptions in detail.');
|
|
4235
|
+
}
|
|
4236
|
+
if (!/(rescis|terminat|cancel)/.test(contentText)) {
|
|
4237
|
+
warnings.push('Add a termination / rescission clause with notice and penalty rules.');
|
|
4238
|
+
}
|
|
4239
|
+
if (!/(confiden|sigil|data protect|privacy)/.test(contentText)) {
|
|
4240
|
+
warnings.push('Review confidentiality and data protection clauses when applicable.');
|
|
4241
|
+
}
|
|
4242
|
+
if (!/(foro|jurisd|governing law|applicable law)/.test(contentText)) {
|
|
4243
|
+
warnings.push('Review the governing law / forum clause before signature.');
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
const summary = missingFields.length || warnings.length
|
|
4247
|
+
? `Advisory legal review found ${missingFields.length} missing item(s) and ${warnings.length} point(s) for attention before final approval.`
|
|
4248
|
+
: 'Advisory legal review found the draft structurally consistent for human legal revision.';
|
|
4249
|
+
return {
|
|
4250
|
+
summary,
|
|
4251
|
+
missingFields: Array.from(new Set(missingFields)),
|
|
4252
|
+
warnings: Array.from(new Set(warnings)),
|
|
4253
|
+
checklist: Array.from(new Set(checklist)),
|
|
4254
|
+
status: missingFields.length || warnings.length
|
|
4255
|
+
? 'attention_required'
|
|
4256
|
+
: 'ready_for_revision',
|
|
4257
|
+
reviewedAt: new Date().toISOString(),
|
|
4258
|
+
};
|
|
4259
|
+
}
|
|
4260
|
+
buildContractExtractionSystemPrompt() {
|
|
4261
|
+
return [
|
|
4262
|
+
'You extract structured draft data from uploaded business contracts.',
|
|
4263
|
+
'Return ONLY valid JSON. Do not include Markdown fences, prose, or comments.',
|
|
4264
|
+
'Use empty strings or empty arrays when the document is unclear; never invent legal facts.',
|
|
4265
|
+
'Dates must use YYYY-MM-DD whenever the document provides enough confidence.',
|
|
4266
|
+
'For contentHtml, return clean HTML suitable for a rich text editor preview.',
|
|
4267
|
+
'Expected JSON shape:',
|
|
4268
|
+
JSON.stringify({
|
|
4269
|
+
code: '',
|
|
4270
|
+
name: '',
|
|
4271
|
+
clientName: '',
|
|
4272
|
+
contractCategory: 'client',
|
|
4273
|
+
contractType: 'service_agreement',
|
|
4274
|
+
signatureStatus: 'not_started',
|
|
4275
|
+
billingModel: 'time_and_material',
|
|
4276
|
+
originType: 'manual',
|
|
4277
|
+
originId: '',
|
|
4278
|
+
startDate: '',
|
|
4279
|
+
endDate: '',
|
|
4280
|
+
signedAt: '',
|
|
4281
|
+
effectiveDate: '',
|
|
4282
|
+
budgetAmount: '',
|
|
4283
|
+
monthlyHourCap: '',
|
|
4284
|
+
status: 'draft',
|
|
4285
|
+
description: '',
|
|
4286
|
+
contentHtml: '',
|
|
4287
|
+
parties: [
|
|
4288
|
+
{
|
|
4289
|
+
displayName: '',
|
|
4290
|
+
partyRole: 'client',
|
|
4291
|
+
partyType: 'company',
|
|
4292
|
+
documentNumber: '',
|
|
4293
|
+
email: '',
|
|
4294
|
+
phone: '',
|
|
4295
|
+
isPrimary: true,
|
|
4296
|
+
},
|
|
4297
|
+
],
|
|
4298
|
+
signatures: [
|
|
4299
|
+
{
|
|
4300
|
+
signerName: '',
|
|
4301
|
+
signerRole: '',
|
|
4302
|
+
signerEmail: '',
|
|
4303
|
+
status: 'pending',
|
|
4304
|
+
signedAt: '',
|
|
4305
|
+
},
|
|
4306
|
+
],
|
|
4307
|
+
financialTerms: [
|
|
4308
|
+
{
|
|
4309
|
+
label: '',
|
|
4310
|
+
termType: 'value',
|
|
4311
|
+
amount: '',
|
|
4312
|
+
recurrence: 'one_time',
|
|
4313
|
+
dueDay: '',
|
|
4314
|
+
notes: '',
|
|
4315
|
+
},
|
|
4316
|
+
],
|
|
4317
|
+
revisions: [
|
|
4318
|
+
{
|
|
4319
|
+
title: '',
|
|
4320
|
+
revisionType: 'revision',
|
|
4321
|
+
effectiveDate: '',
|
|
4322
|
+
status: 'draft',
|
|
4323
|
+
summary: '',
|
|
4324
|
+
},
|
|
4325
|
+
],
|
|
4326
|
+
summary: '',
|
|
4327
|
+
missingFields: [],
|
|
4328
|
+
warnings: [],
|
|
4329
|
+
}, null, 2),
|
|
4330
|
+
].join('\n\n');
|
|
4331
|
+
}
|
|
4332
|
+
parseAiJsonPayload(content) {
|
|
4333
|
+
var _a;
|
|
4334
|
+
const trimmed = String(content !== null && content !== void 0 ? content : '').trim();
|
|
4335
|
+
if (!trimmed) {
|
|
4336
|
+
throw new common_1.BadRequestException('AI returned an empty contract draft response.');
|
|
4337
|
+
}
|
|
4338
|
+
const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
|
4339
|
+
const candidate = ((_a = fencedMatch === null || fencedMatch === void 0 ? void 0 : fencedMatch[1]) === null || _a === void 0 ? void 0 : _a.trim()) || trimmed;
|
|
4340
|
+
try {
|
|
4341
|
+
return JSON.parse(candidate);
|
|
4342
|
+
}
|
|
4343
|
+
catch (_b) {
|
|
4344
|
+
const firstBraceIndex = candidate.indexOf('{');
|
|
4345
|
+
const lastBraceIndex = candidate.lastIndexOf('}');
|
|
4346
|
+
if (firstBraceIndex >= 0 && lastBraceIndex > firstBraceIndex) {
|
|
4347
|
+
try {
|
|
4348
|
+
return JSON.parse(candidate.slice(firstBraceIndex, lastBraceIndex + 1));
|
|
4349
|
+
}
|
|
4350
|
+
catch (_c) {
|
|
4351
|
+
throw new common_1.BadRequestException('AI returned an invalid contract draft payload.');
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
throw new common_1.BadRequestException('AI returned an invalid contract draft payload.');
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
normalizeContractExtractDraft(raw) {
|
|
4358
|
+
const warnings = this.normalizeStringList(raw.warnings);
|
|
4359
|
+
const missingFields = this.normalizeStringList(raw.missingFields);
|
|
4360
|
+
const draft = {
|
|
4361
|
+
code: this.normalizeExtractionString(raw.code),
|
|
4362
|
+
name: this.normalizeExtractionString(raw.name),
|
|
4363
|
+
clientName: this.normalizeExtractionString(raw.clientName),
|
|
4364
|
+
contractCategory: this.normalizeEnumValue(raw.contractCategory, CONTRACT_CATEGORY_VALUES, 'client'),
|
|
4365
|
+
contractType: this.normalizeEnumValue(raw.contractType, CONTRACT_TYPE_VALUES, 'service_agreement'),
|
|
4366
|
+
signatureStatus: this.normalizeEnumValue(raw.signatureStatus, SIGNATURE_STATUS_VALUES, 'not_started'),
|
|
4367
|
+
isActive: raw.isActive === false ? false : true,
|
|
4368
|
+
billingModel: this.normalizeEnumValue(raw.billingModel, BILLING_MODEL_VALUES, 'time_and_material'),
|
|
4369
|
+
originType: this.normalizeEnumValue(raw.originType, ORIGIN_TYPE_VALUES, 'manual'),
|
|
4370
|
+
originId: this.normalizeExtractionString(raw.originId),
|
|
4371
|
+
startDate: this.normalizeExtractionDate(raw.startDate),
|
|
4372
|
+
endDate: this.normalizeExtractionDate(raw.endDate),
|
|
4373
|
+
signedAt: this.normalizeExtractionDate(raw.signedAt),
|
|
4374
|
+
effectiveDate: this.normalizeExtractionDate(raw.effectiveDate),
|
|
4375
|
+
budgetAmount: this.normalizeExtractionNumber(raw.budgetAmount),
|
|
4376
|
+
monthlyHourCap: this.normalizeExtractionNumber(raw.monthlyHourCap),
|
|
4377
|
+
status: this.normalizeEnumValue(raw.status, CONTRACT_STATUS_VALUES, 'draft'),
|
|
4378
|
+
description: this.normalizeExtractionString(raw.description),
|
|
4379
|
+
contentHtml: this.normalizeExtractionString(raw.contentHtml),
|
|
4380
|
+
summary: this.normalizeExtractionString(raw.summary),
|
|
4381
|
+
parties: this.normalizeObjectList(raw.parties)
|
|
4382
|
+
.map((party) => ({
|
|
4383
|
+
displayName: this.normalizeExtractionString(party.displayName),
|
|
4384
|
+
partyRole: this.normalizeEnumValue(party.partyRole, PARTY_ROLE_VALUES, 'client'),
|
|
4385
|
+
partyType: this.normalizeEnumValue(party.partyType, PARTY_TYPE_VALUES, 'company'),
|
|
4386
|
+
documentNumber: this.normalizeExtractionString(party.documentNumber),
|
|
4387
|
+
email: this.normalizeExtractionString(party.email),
|
|
4388
|
+
phone: this.normalizeExtractionString(party.phone),
|
|
4389
|
+
isPrimary: this.normalizeExtractionBoolean(party.isPrimary),
|
|
4390
|
+
}))
|
|
4391
|
+
.filter((party) => party.displayName),
|
|
4392
|
+
signatures: this.normalizeObjectList(raw.signatures)
|
|
4393
|
+
.map((signature) => ({
|
|
4394
|
+
signerName: this.normalizeExtractionString(signature.signerName),
|
|
4395
|
+
signerRole: this.normalizeExtractionString(signature.signerRole),
|
|
4396
|
+
signerEmail: this.normalizeExtractionString(signature.signerEmail),
|
|
4397
|
+
status: this.normalizeEnumValue(signature.status, SIGNATURE_ITEM_STATUS_VALUES, 'pending'),
|
|
4398
|
+
signedAt: this.normalizeExtractionDate(signature.signedAt),
|
|
4399
|
+
}))
|
|
4400
|
+
.filter((signature) => signature.signerName),
|
|
4401
|
+
financialTerms: this.normalizeObjectList(raw.financialTerms)
|
|
4402
|
+
.map((term) => ({
|
|
4403
|
+
label: this.normalizeExtractionString(term.label),
|
|
4404
|
+
termType: this.normalizeEnumValue(term.termType, FINANCIAL_TERM_TYPE_VALUES, 'value'),
|
|
4405
|
+
amount: this.normalizeExtractionNumber(term.amount),
|
|
4406
|
+
recurrence: this.normalizeEnumValue(term.recurrence, RECURRENCE_VALUES, 'one_time'),
|
|
4407
|
+
dueDay: this.normalizeExtractionNumber(term.dueDay),
|
|
4408
|
+
notes: this.normalizeExtractionString(term.notes),
|
|
4409
|
+
}))
|
|
4410
|
+
.filter((term) => term.label),
|
|
4411
|
+
revisions: this.normalizeObjectList(raw.revisions)
|
|
4412
|
+
.map((revision) => ({
|
|
4413
|
+
title: this.normalizeExtractionString(revision.title),
|
|
4414
|
+
revisionType: this.normalizeEnumValue(revision.revisionType, REVISION_TYPE_VALUES, 'revision'),
|
|
4415
|
+
effectiveDate: this.normalizeExtractionDate(revision.effectiveDate),
|
|
4416
|
+
status: this.normalizeEnumValue(revision.status, REVISION_STATUS_VALUES, 'draft'),
|
|
4417
|
+
summary: this.normalizeExtractionString(revision.summary),
|
|
4418
|
+
}))
|
|
4419
|
+
.filter((revision) => revision.title),
|
|
4420
|
+
};
|
|
4421
|
+
if (!draft.name)
|
|
4422
|
+
missingFields.push('Contract title');
|
|
4423
|
+
if (!draft.clientName)
|
|
4424
|
+
missingFields.push('Client name');
|
|
4425
|
+
if (!draft.startDate)
|
|
4426
|
+
missingFields.push('Start date');
|
|
4427
|
+
if (!draft.contentHtml && !draft.summary && !draft.description) {
|
|
4428
|
+
warnings.push('The contract body was not extracted with enough confidence. Use the editor to complete it.');
|
|
4429
|
+
}
|
|
4430
|
+
if (!draft.parties.length) {
|
|
4431
|
+
warnings.push('No related parties were identified with confidence.');
|
|
4432
|
+
}
|
|
4433
|
+
return Object.assign(Object.assign({}, draft), { summary: draft.summary || draft.description || draft.name, missingFields: Array.from(new Set(missingFields.filter(Boolean))), warnings: Array.from(new Set(warnings.filter(Boolean))) });
|
|
4434
|
+
}
|
|
4435
|
+
normalizeExtractionString(value) {
|
|
4436
|
+
return typeof value === 'string'
|
|
4437
|
+
? value.trim()
|
|
4438
|
+
: value === null || value === undefined
|
|
4439
|
+
? ''
|
|
4440
|
+
: String(value).trim();
|
|
4441
|
+
}
|
|
4442
|
+
normalizeExtractionBoolean(value) {
|
|
4443
|
+
if (typeof value === 'boolean') {
|
|
4444
|
+
return value;
|
|
4445
|
+
}
|
|
4446
|
+
const normalized = this.normalizeExtractionString(value).toLowerCase();
|
|
4447
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes';
|
|
4448
|
+
}
|
|
4449
|
+
normalizeExtractionNumber(value) {
|
|
4450
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
4451
|
+
return value;
|
|
4452
|
+
}
|
|
4453
|
+
const normalized = this.normalizeExtractionString(value);
|
|
4454
|
+
if (!normalized) {
|
|
4455
|
+
return '';
|
|
4456
|
+
}
|
|
4457
|
+
const direct = Number(normalized);
|
|
4458
|
+
if (Number.isFinite(direct)) {
|
|
4459
|
+
return direct;
|
|
4460
|
+
}
|
|
4461
|
+
const ptBr = normalized
|
|
4462
|
+
.split('.')
|
|
4463
|
+
.join('')
|
|
4464
|
+
.replace(',', '.');
|
|
4465
|
+
const parsed = Number(ptBr);
|
|
4466
|
+
return Number.isFinite(parsed) ? parsed : normalized;
|
|
4467
|
+
}
|
|
4468
|
+
normalizeExtractionDate(value) {
|
|
4469
|
+
var _a;
|
|
4470
|
+
const normalized = this.normalizeExtractionString(value);
|
|
4471
|
+
if (!normalized) {
|
|
4472
|
+
return '';
|
|
4473
|
+
}
|
|
4474
|
+
const match = normalized.match(/\d{4}-\d{2}-\d{2}/);
|
|
4475
|
+
return (_a = match === null || match === void 0 ? void 0 : match[0]) !== null && _a !== void 0 ? _a : '';
|
|
4476
|
+
}
|
|
4477
|
+
normalizeStringList(value) {
|
|
4478
|
+
return Array.isArray(value)
|
|
4479
|
+
? value
|
|
4480
|
+
.map((item) => this.normalizeExtractionString(item))
|
|
4481
|
+
.filter(Boolean)
|
|
4482
|
+
: [];
|
|
4483
|
+
}
|
|
4484
|
+
normalizeObjectList(value) {
|
|
4485
|
+
if (!Array.isArray(value)) {
|
|
4486
|
+
return [];
|
|
4487
|
+
}
|
|
4488
|
+
return value.filter((item) => Boolean(item) && typeof item === 'object');
|
|
4489
|
+
}
|
|
4490
|
+
normalizeEnumValue(value, allowed, fallback) {
|
|
4491
|
+
const normalized = this.normalizeExtractionString(value);
|
|
4492
|
+
return allowed.includes(normalized) ? normalized : fallback;
|
|
4493
|
+
}
|
|
4494
|
+
normalizeContractExtractionMimeType(mimeType, fileName) {
|
|
4495
|
+
const current = this.normalizeExtractionString(mimeType).toLowerCase();
|
|
4496
|
+
if (current && current !== 'application/octet-stream') {
|
|
4497
|
+
return current;
|
|
4498
|
+
}
|
|
4499
|
+
const name = this.normalizeExtractionString(fileName).toLowerCase();
|
|
4500
|
+
if (name.endsWith('.pdf'))
|
|
4501
|
+
return 'application/pdf';
|
|
4502
|
+
if (name.endsWith('.doc'))
|
|
4503
|
+
return 'application/msword';
|
|
4504
|
+
if (name.endsWith('.docx')) {
|
|
4505
|
+
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
4506
|
+
}
|
|
4507
|
+
return current || 'application/octet-stream';
|
|
4508
|
+
}
|
|
4509
|
+
isAllowedContractExtractionMimeType(mimeType) {
|
|
4510
|
+
return [
|
|
4511
|
+
'application/pdf',
|
|
4512
|
+
'application/msword',
|
|
4513
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
4514
|
+
].includes(mimeType);
|
|
4515
|
+
}
|
|
2201
4516
|
requireFields(input, required) {
|
|
2202
4517
|
for (const field of required) {
|
|
2203
4518
|
const value = input[field];
|
|
@@ -2223,15 +4538,18 @@ let OperationsService = class OperationsService {
|
|
|
2223
4538
|
...new Set(values.filter((value) => typeof value === 'number')),
|
|
2224
4539
|
];
|
|
2225
4540
|
}
|
|
2226
|
-
pushUpdate(updates, params, column, value) {
|
|
4541
|
+
pushUpdate(updates, params, column, value, castType) {
|
|
2227
4542
|
if (value === undefined)
|
|
2228
4543
|
return;
|
|
2229
4544
|
params.push(value);
|
|
2230
|
-
|
|
4545
|
+
const placeholder = castType
|
|
4546
|
+
? `$${params.length}::${castType}`
|
|
4547
|
+
: `$${params.length}`;
|
|
4548
|
+
updates.push(`${column} = ${placeholder}`);
|
|
2231
4549
|
}
|
|
2232
|
-
param(params, value) {
|
|
4550
|
+
param(params, value, castType) {
|
|
2233
4551
|
params.push(value);
|
|
2234
|
-
return `$${params.length}`;
|
|
4552
|
+
return castType ? `$${params.length}::${castType}` : `$${params.length}`;
|
|
2235
4553
|
}
|
|
2236
4554
|
groupBy(items, key) {
|
|
2237
4555
|
return items.reduce((accumulator, item) => {
|
|
@@ -2257,7 +4575,12 @@ let OperationsService = class OperationsService {
|
|
|
2257
4575
|
exports.OperationsService = OperationsService;
|
|
2258
4576
|
exports.OperationsService = OperationsService = __decorate([
|
|
2259
4577
|
(0, common_1.Injectable)(),
|
|
4578
|
+
__param(3, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.FileService))),
|
|
4579
|
+
__param(4, (0, common_1.Inject)((0, common_1.forwardRef)(() => core_1.SettingService))),
|
|
2260
4580
|
__metadata("design:paramtypes", [api_prisma_1.PrismaService,
|
|
2261
|
-
core_1.
|
|
4581
|
+
core_1.AiService,
|
|
4582
|
+
core_1.IntegrationDeveloperApiService,
|
|
4583
|
+
core_1.FileService,
|
|
4584
|
+
core_1.SettingService])
|
|
2262
4585
|
], OperationsService);
|
|
2263
4586
|
//# sourceMappingURL=operations.service.js.map
|