@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
|
@@ -2,6 +2,15 @@ columns:
|
|
|
2
2
|
- type: pk
|
|
3
3
|
- name: user_id
|
|
4
4
|
type: int
|
|
5
|
+
isNullable: true
|
|
6
|
+
- name: person_id
|
|
7
|
+
type: fk
|
|
8
|
+
isNullable: true
|
|
9
|
+
references:
|
|
10
|
+
table: person
|
|
11
|
+
column: id
|
|
12
|
+
onDelete: SET NULL
|
|
13
|
+
onUpdate: CASCADE
|
|
5
14
|
- name: supervisor_collaborator_id
|
|
6
15
|
type: fk
|
|
7
16
|
isNullable: true
|
|
@@ -24,6 +33,14 @@ columns:
|
|
|
24
33
|
type: varchar
|
|
25
34
|
length: 120
|
|
26
35
|
isNullable: true
|
|
36
|
+
- name: department_id
|
|
37
|
+
type: fk
|
|
38
|
+
isNullable: true
|
|
39
|
+
references:
|
|
40
|
+
table: operations_department
|
|
41
|
+
column: id
|
|
42
|
+
onDelete: SET NULL
|
|
43
|
+
onUpdate: CASCADE
|
|
27
44
|
- name: title
|
|
28
45
|
type: varchar
|
|
29
46
|
length: 120
|
|
@@ -59,9 +76,12 @@ columns:
|
|
|
59
76
|
indices:
|
|
60
77
|
- columns: [user_id]
|
|
61
78
|
isUnique: true
|
|
79
|
+
- columns: [person_id]
|
|
80
|
+
isUnique: true
|
|
62
81
|
- columns: [code]
|
|
63
82
|
isUnique: true
|
|
64
83
|
- columns: [collaborator_type]
|
|
65
84
|
- columns: [supervisor_collaborator_id]
|
|
85
|
+
- columns: [department_id]
|
|
66
86
|
- columns: [status]
|
|
67
87
|
- columns: [deleted_at]
|
|
@@ -6,6 +6,7 @@ columns:
|
|
|
6
6
|
- name: name
|
|
7
7
|
type: varchar
|
|
8
8
|
length: 180
|
|
9
|
+
isNullable: true
|
|
9
10
|
- name: contract_category
|
|
10
11
|
type: enum
|
|
11
12
|
values: [employee, contractor, client, supplier, vendor, partner, internal, other]
|
|
@@ -17,6 +18,7 @@ columns:
|
|
|
17
18
|
- name: client_name
|
|
18
19
|
type: varchar
|
|
19
20
|
length: 180
|
|
21
|
+
isNullable: true
|
|
20
22
|
- name: signature_status
|
|
21
23
|
type: enum
|
|
22
24
|
values: [not_started, pending, partially_signed, signed, expired]
|
|
@@ -44,15 +46,24 @@ columns:
|
|
|
44
46
|
column: id
|
|
45
47
|
onDelete: SET NULL
|
|
46
48
|
onUpdate: CASCADE
|
|
49
|
+
- name: contract_template_id
|
|
50
|
+
type: fk
|
|
51
|
+
isNullable: true
|
|
52
|
+
references:
|
|
53
|
+
table: operations_contract_template
|
|
54
|
+
column: id
|
|
55
|
+
onDelete: SET NULL
|
|
56
|
+
onUpdate: CASCADE
|
|
47
57
|
- name: origin_type
|
|
48
58
|
type: enum
|
|
49
|
-
values: [manual, employee_hiring, client_project]
|
|
59
|
+
values: [manual, employee_hiring, client_project, crm_proposal]
|
|
50
60
|
default: manual
|
|
51
61
|
- name: origin_id
|
|
52
62
|
type: int
|
|
53
63
|
isNullable: true
|
|
54
64
|
- name: start_date
|
|
55
65
|
type: date
|
|
66
|
+
isNullable: true
|
|
56
67
|
- name: end_date
|
|
57
68
|
type: date
|
|
58
69
|
isNullable: true
|
|
@@ -74,6 +85,13 @@ columns:
|
|
|
74
85
|
type: enum
|
|
75
86
|
values: [draft, under_review, active, renewal, expired, closed, archived]
|
|
76
87
|
default: draft
|
|
88
|
+
- name: creation_mode
|
|
89
|
+
type: enum
|
|
90
|
+
values: [blank, template, upload, duplicate]
|
|
91
|
+
default: blank
|
|
92
|
+
- name: wizard_step
|
|
93
|
+
type: int
|
|
94
|
+
default: 0
|
|
77
95
|
- name: description
|
|
78
96
|
type: text
|
|
79
97
|
isNullable: true
|
|
@@ -97,6 +115,7 @@ indices:
|
|
|
97
115
|
isUnique: true
|
|
98
116
|
- columns: [account_manager_collaborator_id]
|
|
99
117
|
- columns: [related_collaborator_id]
|
|
118
|
+
- columns: [contract_template_id]
|
|
100
119
|
- columns: [contract_category]
|
|
101
120
|
- columns: [contract_type]
|
|
102
121
|
- columns: [origin_type]
|
|
@@ -104,6 +123,8 @@ indices:
|
|
|
104
123
|
- columns: [signature_status]
|
|
105
124
|
- columns: [is_active]
|
|
106
125
|
- columns: [status]
|
|
126
|
+
- columns: [creation_mode]
|
|
127
|
+
- columns: [wizard_step]
|
|
107
128
|
- columns: [start_date]
|
|
108
129
|
- columns: [end_date]
|
|
109
130
|
- columns: [deleted_at]
|
|
@@ -7,25 +7,40 @@ columns:
|
|
|
7
7
|
column: id
|
|
8
8
|
onDelete: CASCADE
|
|
9
9
|
onUpdate: CASCADE
|
|
10
|
-
- name: document_type
|
|
11
|
-
type: enum
|
|
12
|
-
values: [
|
|
13
|
-
default: attachment
|
|
14
|
-
- name:
|
|
15
|
-
type:
|
|
16
|
-
|
|
10
|
+
- name: document_type
|
|
11
|
+
type: enum
|
|
12
|
+
values: [source_upload, generated_pdf, attachment, other]
|
|
13
|
+
default: attachment
|
|
14
|
+
- name: file_id
|
|
15
|
+
type: fk
|
|
16
|
+
isNullable: true
|
|
17
|
+
references:
|
|
18
|
+
table: file
|
|
19
|
+
column: id
|
|
20
|
+
onDelete: SET NULL
|
|
21
|
+
onUpdate: CASCADE
|
|
22
|
+
- name: file_name
|
|
23
|
+
type: varchar
|
|
24
|
+
length: 200
|
|
17
25
|
- name: mime_type
|
|
18
26
|
type: varchar
|
|
19
27
|
length: 120
|
|
20
28
|
- name: file_content_base64
|
|
21
29
|
type: text
|
|
22
30
|
isNullable: true
|
|
23
|
-
- name: is_current
|
|
24
|
-
type: boolean
|
|
25
|
-
default: true
|
|
26
|
-
- name:
|
|
27
|
-
type:
|
|
28
|
-
|
|
31
|
+
- name: is_current
|
|
32
|
+
type: boolean
|
|
33
|
+
default: true
|
|
34
|
+
- name: extraction_status
|
|
35
|
+
type: enum
|
|
36
|
+
values: [pending, processing, completed, failed, skipped]
|
|
37
|
+
default: skipped
|
|
38
|
+
- name: extraction_summary
|
|
39
|
+
type: text
|
|
40
|
+
isNullable: true
|
|
41
|
+
- name: notes
|
|
42
|
+
type: text
|
|
43
|
+
isNullable: true
|
|
29
44
|
- name: deleted_at
|
|
30
45
|
type: datetime
|
|
31
46
|
isNullable: true
|
|
@@ -34,6 +49,8 @@ columns:
|
|
|
34
49
|
|
|
35
50
|
indices:
|
|
36
51
|
- columns: [contract_id]
|
|
37
|
-
- columns: [document_type]
|
|
38
|
-
- columns: [
|
|
39
|
-
- columns: [
|
|
52
|
+
- columns: [document_type]
|
|
53
|
+
- columns: [file_id]
|
|
54
|
+
- columns: [is_current]
|
|
55
|
+
- columns: [extraction_status]
|
|
56
|
+
- columns: [deleted_at]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
columns:
|
|
2
|
+
- type: pk
|
|
3
|
+
- type: slug
|
|
4
|
+
- name: code
|
|
5
|
+
type: varchar
|
|
6
|
+
length: 40
|
|
7
|
+
isNullable: true
|
|
8
|
+
- name: name
|
|
9
|
+
type: varchar
|
|
10
|
+
length: 180
|
|
11
|
+
- name: description
|
|
12
|
+
type: text
|
|
13
|
+
isNullable: true
|
|
14
|
+
- name: contract_category
|
|
15
|
+
type: enum
|
|
16
|
+
values: [employee, contractor, client, supplier, vendor, partner, internal, other]
|
|
17
|
+
default: client
|
|
18
|
+
- name: contract_type
|
|
19
|
+
type: enum
|
|
20
|
+
values: [clt, pj, freelancer_agreement, service_agreement, fixed_term, recurring_service, nda, amendment, addendum, other]
|
|
21
|
+
default: service_agreement
|
|
22
|
+
- name: billing_model
|
|
23
|
+
type: enum
|
|
24
|
+
values: [time_and_material, monthly_retainer, fixed_price]
|
|
25
|
+
default: time_and_material
|
|
26
|
+
- name: signature_status
|
|
27
|
+
type: enum
|
|
28
|
+
values: [not_started, pending, partially_signed, signed, expired]
|
|
29
|
+
default: not_started
|
|
30
|
+
- name: is_active
|
|
31
|
+
type: boolean
|
|
32
|
+
default: true
|
|
33
|
+
- name: status
|
|
34
|
+
type: enum
|
|
35
|
+
values: [draft, active, inactive, archived]
|
|
36
|
+
default: active
|
|
37
|
+
- name: content_html
|
|
38
|
+
type: text
|
|
39
|
+
isNullable: true
|
|
40
|
+
- name: deleted_at
|
|
41
|
+
type: datetime
|
|
42
|
+
isNullable: true
|
|
43
|
+
- type: created_at
|
|
44
|
+
- type: updated_at
|
|
45
|
+
|
|
46
|
+
indices:
|
|
47
|
+
- columns: [slug]
|
|
48
|
+
isUnique: true
|
|
49
|
+
- columns: [code]
|
|
50
|
+
isUnique: true
|
|
51
|
+
- columns: [name]
|
|
52
|
+
- columns: [contract_category]
|
|
53
|
+
- columns: [contract_type]
|
|
54
|
+
- columns: [billing_model]
|
|
55
|
+
- columns: [signature_status]
|
|
56
|
+
- columns: [is_active]
|
|
57
|
+
- columns: [status]
|
|
58
|
+
- columns: [deleted_at]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
columns:
|
|
2
|
+
- type: pk
|
|
3
|
+
- type: slug
|
|
4
|
+
- name: code
|
|
5
|
+
type: varchar
|
|
6
|
+
length: 32
|
|
7
|
+
isNullable: true
|
|
8
|
+
- name: name
|
|
9
|
+
type: varchar
|
|
10
|
+
length: 120
|
|
11
|
+
- name: description
|
|
12
|
+
type: text
|
|
13
|
+
isNullable: true
|
|
14
|
+
- name: deleted_at
|
|
15
|
+
type: datetime
|
|
16
|
+
isNullable: true
|
|
17
|
+
- type: created_at
|
|
18
|
+
- type: updated_at
|
|
19
|
+
|
|
20
|
+
indices:
|
|
21
|
+
- columns: [code]
|
|
22
|
+
isUnique: true
|
|
23
|
+
- columns: [name]
|
|
24
|
+
- columns: [deleted_at]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/operations",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.301",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"@nestjs/jwt": "^11",
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
12
|
"@hed-hog/api": "0.0.6",
|
|
13
|
-
"@hed-hog/api-pagination": "0.0.7",
|
|
14
13
|
"@hed-hog/api-types": "0.0.1",
|
|
15
|
-
"@hed-hog/api-locale": "0.0.14",
|
|
16
14
|
"@hed-hog/api-prisma": "0.0.6",
|
|
17
|
-
"@hed-hog/
|
|
15
|
+
"@hed-hog/api-locale": "0.0.14",
|
|
16
|
+
"@hed-hog/api-pagination": "0.0.7",
|
|
17
|
+
"@hed-hog/core": "0.0.301",
|
|
18
|
+
"@hed-hog/contact": "0.0.301"
|
|
18
19
|
},
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
],
|
|
31
32
|
"scripts": {
|
|
32
33
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
|
34
|
+
"test": "jest --config jest.config.ts --runInBand",
|
|
33
35
|
"prebuild": "pnpm --dir ../.. exec ts-node ./scripts/build-dependencies.ts libraries/operations",
|
|
34
36
|
"build": "tsc --project tsconfig.production.json",
|
|
35
37
|
"patch": "pnpm exec ts-node ../../scripts/patch.ts libraries/operations",
|
|
@@ -2,6 +2,7 @@ import { Role, User } from '@hed-hog/api';
|
|
|
2
2
|
import {
|
|
3
3
|
Body,
|
|
4
4
|
Controller,
|
|
5
|
+
Delete,
|
|
5
6
|
Get,
|
|
6
7
|
Param,
|
|
7
8
|
ParseIntPipe,
|
|
@@ -61,6 +62,25 @@ export class OperationsController {
|
|
|
61
62
|
);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
@Get('departments')
|
|
66
|
+
listDepartments(@User() user) {
|
|
67
|
+
return this.operationsService.listDepartments(Number(user?.id || 0));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Post('departments')
|
|
71
|
+
createDepartment(@User() user, @Body() data) {
|
|
72
|
+
return this.operationsService.createDepartment(Number(user?.id || 0), data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Patch('departments/:id')
|
|
76
|
+
updateDepartment(
|
|
77
|
+
@User() user,
|
|
78
|
+
@Param('id', ParseIntPipe) id: number,
|
|
79
|
+
@Body() data
|
|
80
|
+
) {
|
|
81
|
+
return this.operationsService.updateDepartment(Number(user?.id || 0), id, data);
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
@Get('projects')
|
|
65
85
|
listProjects(@User() user) {
|
|
66
86
|
return this.operationsService.listProjects(Number(user?.id || 0));
|
|
@@ -81,6 +101,40 @@ export class OperationsController {
|
|
|
81
101
|
return this.operationsService.updateProject(Number(user?.id || 0), id, data);
|
|
82
102
|
}
|
|
83
103
|
|
|
104
|
+
@Get('contract-templates')
|
|
105
|
+
listContractTemplates(@User() user) {
|
|
106
|
+
return this.operationsService.listContractTemplates(Number(user?.id || 0));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Get('contract-templates/:id')
|
|
110
|
+
getContractTemplate(@User() user, @Param('id', ParseIntPipe) id: number) {
|
|
111
|
+
return this.operationsService.getContractTemplateById(
|
|
112
|
+
Number(user?.id || 0),
|
|
113
|
+
id
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Post('contract-templates')
|
|
118
|
+
createContractTemplate(@User() user, @Body() data) {
|
|
119
|
+
return this.operationsService.createContractTemplate(
|
|
120
|
+
Number(user?.id || 0),
|
|
121
|
+
data
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Patch('contract-templates/:id')
|
|
126
|
+
updateContractTemplate(
|
|
127
|
+
@User() user,
|
|
128
|
+
@Param('id', ParseIntPipe) id: number,
|
|
129
|
+
@Body() data
|
|
130
|
+
) {
|
|
131
|
+
return this.operationsService.updateContractTemplate(
|
|
132
|
+
Number(user?.id || 0),
|
|
133
|
+
id,
|
|
134
|
+
data
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
84
138
|
@Get('contracts')
|
|
85
139
|
listContracts(@User() user) {
|
|
86
140
|
return this.operationsService.listContracts(Number(user?.id || 0));
|
|
@@ -91,11 +145,63 @@ export class OperationsController {
|
|
|
91
145
|
return this.operationsService.getContractById(Number(user?.id || 0), id);
|
|
92
146
|
}
|
|
93
147
|
|
|
148
|
+
@Post('contracts/drafts')
|
|
149
|
+
createContractDraft(@User() user, @Body() data) {
|
|
150
|
+
return this.operationsService.createContractDraft(Number(user?.id || 0), data);
|
|
151
|
+
}
|
|
152
|
+
|
|
94
153
|
@Post('contracts')
|
|
95
154
|
createContract(@User() user, @Body() data) {
|
|
96
155
|
return this.operationsService.createContract(Number(user?.id || 0), data);
|
|
97
156
|
}
|
|
98
157
|
|
|
158
|
+
@Post('contracts/extract-draft')
|
|
159
|
+
extractContractDraft(@User() user, @Body() data) {
|
|
160
|
+
return this.operationsService.extractContractDraft(
|
|
161
|
+
Number(user?.id || 0),
|
|
162
|
+
data
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@Post('contracts/:id/extract-source')
|
|
167
|
+
extractContractSource(
|
|
168
|
+
@User() user,
|
|
169
|
+
@Param('id', ParseIntPipe) id: number,
|
|
170
|
+
@Body() data
|
|
171
|
+
) {
|
|
172
|
+
return this.operationsService.extractContractSource(
|
|
173
|
+
Number(user?.id || 0),
|
|
174
|
+
id,
|
|
175
|
+
data
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Post('contracts/:id/generate-content')
|
|
180
|
+
generateContractContent(
|
|
181
|
+
@User() user,
|
|
182
|
+
@Param('id', ParseIntPipe) id: number,
|
|
183
|
+
@Body() data
|
|
184
|
+
) {
|
|
185
|
+
return this.operationsService.generateContractContent(
|
|
186
|
+
Number(user?.id || 0),
|
|
187
|
+
id,
|
|
188
|
+
data
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@Post('contracts/:id/legal-review')
|
|
193
|
+
reviewContractLegally(
|
|
194
|
+
@User() user,
|
|
195
|
+
@Param('id', ParseIntPipe) id: number,
|
|
196
|
+
@Body() data
|
|
197
|
+
) {
|
|
198
|
+
return this.operationsService.reviewContractLegally(
|
|
199
|
+
Number(user?.id || 0),
|
|
200
|
+
id,
|
|
201
|
+
data
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
99
205
|
@Patch('contracts/:id')
|
|
100
206
|
updateContract(
|
|
101
207
|
@User() user,
|
|
@@ -105,6 +211,22 @@ export class OperationsController {
|
|
|
105
211
|
return this.operationsService.updateContract(Number(user?.id || 0), id, data);
|
|
106
212
|
}
|
|
107
213
|
|
|
214
|
+
@Delete('contracts/:id')
|
|
215
|
+
removeContract(@User() user, @Param('id', ParseIntPipe) id: number) {
|
|
216
|
+
return this.operationsService.removeContract(Number(user?.id || 0), id);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@Post('contracts/:id/generate-pdf')
|
|
220
|
+
generateContractPdf(
|
|
221
|
+
@User() user,
|
|
222
|
+
@Param('id', ParseIntPipe) id: number
|
|
223
|
+
) {
|
|
224
|
+
return this.operationsService.generateContractPdf(
|
|
225
|
+
Number(user?.id || 0),
|
|
226
|
+
id
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
108
230
|
@Get('timesheets')
|
|
109
231
|
listTimesheets(@User() user) {
|
|
110
232
|
return this.operationsService.listTimesheets(Number(user?.id || 0));
|
package/src/operations.module.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { LocaleModule } from '@hed-hog/api-locale';
|
|
2
2
|
import { PaginationModule } from '@hed-hog/api-pagination';
|
|
3
3
|
import { PrismaModule } from '@hed-hog/api-prisma';
|
|
4
|
-
import { IntegrationModule } from '@hed-hog/core';
|
|
4
|
+
import { AiModule, FileModule, IntegrationModule, SettingModule } from '@hed-hog/core';
|
|
5
5
|
import { forwardRef, Module } from '@nestjs/common';
|
|
6
6
|
import { ConfigModule } from '@nestjs/config';
|
|
7
7
|
import { OperationsController } from './operations.controller';
|
|
8
|
+
import { OperationsProposalSubscriber } from './operations.proposal.subscriber';
|
|
8
9
|
import { OperationsService } from './operations.service';
|
|
9
10
|
|
|
10
11
|
@Module({
|
|
@@ -13,10 +14,13 @@ import { OperationsService } from './operations.service';
|
|
|
13
14
|
forwardRef(() => PaginationModule),
|
|
14
15
|
forwardRef(() => PrismaModule),
|
|
15
16
|
forwardRef(() => LocaleModule),
|
|
17
|
+
forwardRef(() => AiModule),
|
|
18
|
+
forwardRef(() => FileModule),
|
|
16
19
|
forwardRef(() => IntegrationModule),
|
|
20
|
+
forwardRef(() => SettingModule),
|
|
17
21
|
],
|
|
18
22
|
controllers: [OperationsController],
|
|
19
|
-
providers: [OperationsService],
|
|
23
|
+
providers: [OperationsService, OperationsProposalSubscriber],
|
|
20
24
|
exports: [OperationsService],
|
|
21
25
|
})
|
|
22
26
|
export class OperationsModule {}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
|
|
3
|
+
import { OperationsProposalSubscriber } from './operations.proposal.subscriber';
|
|
4
|
+
|
|
5
|
+
describe('OperationsProposalSubscriber', () => {
|
|
6
|
+
let integrationApi: {
|
|
7
|
+
subscribeMany: jest.Mock;
|
|
8
|
+
findLinksBySource: jest.Mock;
|
|
9
|
+
createLink: jest.Mock;
|
|
10
|
+
};
|
|
11
|
+
let operationsService: {
|
|
12
|
+
createContractFromProposalIntegration: jest.Mock;
|
|
13
|
+
};
|
|
14
|
+
let subscriber: OperationsProposalSubscriber;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
integrationApi = {
|
|
18
|
+
subscribeMany: jest.fn(),
|
|
19
|
+
findLinksBySource: jest.fn(),
|
|
20
|
+
createLink: jest.fn().mockResolvedValue(undefined),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
operationsService = {
|
|
24
|
+
createContractFromProposalIntegration: jest
|
|
25
|
+
.fn()
|
|
26
|
+
.mockResolvedValue({ id: 88, code: 'CTR-088' }),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
subscriber = new OperationsProposalSubscriber(
|
|
30
|
+
integrationApi as any,
|
|
31
|
+
operationsService as any,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('registers handlers for the proposal approval and conversion flow', () => {
|
|
40
|
+
subscriber.onModuleInit();
|
|
41
|
+
|
|
42
|
+
const subscriptions = integrationApi.subscribeMany.mock.calls[0][0];
|
|
43
|
+
|
|
44
|
+
expect(subscriptions).toHaveLength(2);
|
|
45
|
+
expect(subscriptions.map((entry: { eventName: string }) => entry.eventName)).toEqual(
|
|
46
|
+
expect.arrayContaining([
|
|
47
|
+
'contact.proposal.convert_requested',
|
|
48
|
+
'contact.proposal.approved',
|
|
49
|
+
]),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('skips duplicate contract generation when the integration link already exists', async () => {
|
|
54
|
+
subscriber.onModuleInit();
|
|
55
|
+
|
|
56
|
+
const subscriptions = integrationApi.subscribeMany.mock.calls[0][0];
|
|
57
|
+
const convertRequested = subscriptions.find(
|
|
58
|
+
(entry: { eventName: string }) => entry.eventName === 'contact.proposal.convert_requested',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
integrationApi.findLinksBySource.mockResolvedValue([
|
|
62
|
+
{
|
|
63
|
+
targetModule: 'operations',
|
|
64
|
+
targetEntityType: 'contract',
|
|
65
|
+
targetEntityId: '88',
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
await convertRequested.handler({
|
|
70
|
+
eventName: 'contact.proposal.convert_requested',
|
|
71
|
+
sourceModule: 'contact',
|
|
72
|
+
aggregateType: 'proposal',
|
|
73
|
+
aggregateId: '1001',
|
|
74
|
+
payload: {
|
|
75
|
+
proposalId: 1001,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(operationsService.createContractFromProposalIntegration).not.toHaveBeenCalled();
|
|
80
|
+
expect(integrationApi.createLink).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('creates the integration link after generating a contract draft', async () => {
|
|
84
|
+
subscriber.onModuleInit();
|
|
85
|
+
|
|
86
|
+
const subscriptions = integrationApi.subscribeMany.mock.calls[0][0];
|
|
87
|
+
const approved = subscriptions.find(
|
|
88
|
+
(entry: { eventName: string }) => entry.eventName === 'contact.proposal.approved',
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
integrationApi.findLinksBySource.mockResolvedValue([]);
|
|
92
|
+
|
|
93
|
+
await approved.handler({
|
|
94
|
+
eventName: 'contact.proposal.approved',
|
|
95
|
+
sourceModule: 'contact',
|
|
96
|
+
aggregateType: 'proposal',
|
|
97
|
+
aggregateId: '1002',
|
|
98
|
+
payload: {
|
|
99
|
+
proposalId: 1002,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(operationsService.createContractFromProposalIntegration).toHaveBeenCalledWith({
|
|
104
|
+
proposalId: 1002,
|
|
105
|
+
});
|
|
106
|
+
expect(integrationApi.createLink).toHaveBeenCalledWith(
|
|
107
|
+
expect.objectContaining({
|
|
108
|
+
sourceModule: 'contact',
|
|
109
|
+
sourceEntityType: 'proposal',
|
|
110
|
+
sourceEntityId: '1002',
|
|
111
|
+
targetModule: 'operations',
|
|
112
|
+
targetEntityType: 'contract',
|
|
113
|
+
targetEntityId: '88',
|
|
114
|
+
metadata: expect.objectContaining({
|
|
115
|
+
eventName: 'contact.proposal.approved',
|
|
116
|
+
contractCode: 'CTR-088',
|
|
117
|
+
}),
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntegrationDeveloperApiService,
|
|
3
|
+
LinkType,
|
|
4
|
+
} from '@hed-hog/core';
|
|
5
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
6
|
+
import { OperationsService } from './operations.service';
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class OperationsProposalSubscriber implements OnModuleInit {
|
|
10
|
+
private readonly logger = new Logger(OperationsProposalSubscriber.name);
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
14
|
+
private readonly operationsService: OperationsService,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
onModuleInit(): void {
|
|
18
|
+
const handler = async (event) => {
|
|
19
|
+
const payload = (event.payload || {}) as {
|
|
20
|
+
proposalId?: number;
|
|
21
|
+
};
|
|
22
|
+
const sourceEntityType =
|
|
23
|
+
String(event.aggregateType || '').trim() || 'proposal';
|
|
24
|
+
const sourceEntityId =
|
|
25
|
+
String(payload.proposalId || event.aggregateId || '').trim() ||
|
|
26
|
+
event.aggregateId;
|
|
27
|
+
|
|
28
|
+
if (!sourceEntityId) {
|
|
29
|
+
throw new Error(`Missing proposal aggregate id for ${event.eventName}.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const existingLinks = await this.integrationApi.findLinksBySource({
|
|
33
|
+
module: event.sourceModule,
|
|
34
|
+
entityType: sourceEntityType,
|
|
35
|
+
entityId: sourceEntityId,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const alreadyLinked = existingLinks.some(
|
|
39
|
+
(link) =>
|
|
40
|
+
link.targetModule === 'operations' &&
|
|
41
|
+
link.targetEntityType === 'contract',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (alreadyLinked) {
|
|
45
|
+
this.logger.debug(
|
|
46
|
+
`Skipping duplicate contract generation for ${sourceEntityType}:${sourceEntityId} from ${event.eventName}`,
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const createdContract =
|
|
52
|
+
await this.operationsService.createContractFromProposalIntegration(
|
|
53
|
+
event.payload || {},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
await this.integrationApi.createLink({
|
|
57
|
+
sourceModule: event.sourceModule,
|
|
58
|
+
sourceEntityType,
|
|
59
|
+
sourceEntityId,
|
|
60
|
+
targetModule: 'operations',
|
|
61
|
+
targetEntityType: 'contract',
|
|
62
|
+
targetEntityId: String(createdContract.id),
|
|
63
|
+
linkType: LinkType.REFERENCE,
|
|
64
|
+
metadata: {
|
|
65
|
+
eventName: event.eventName,
|
|
66
|
+
contractCode: createdContract.code,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
this.integrationApi.subscribeMany([
|
|
72
|
+
{
|
|
73
|
+
eventName: 'contact.proposal.convert_requested',
|
|
74
|
+
consumerName: 'operations.contract-from-proposal',
|
|
75
|
+
priority: 10,
|
|
76
|
+
handler,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
eventName: 'contact.proposal.approved',
|
|
80
|
+
consumerName: 'operations.contract-from-proposal-legacy',
|
|
81
|
+
priority: 5,
|
|
82
|
+
handler,
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
}
|