@hed-hog/operations 0.0.305 → 0.0.306

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/controllers/operations-timesheets.controller.d.ts +21 -0
  2. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-timesheets.controller.js +12 -0
  4. package/dist/controllers/operations-timesheets.controller.js.map +1 -1
  5. package/dist/dto/update-collaborator-type.dto.d.ts +3 -1
  6. package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -1
  7. package/dist/dto/update-collaborator-type.dto.js +2 -1
  8. package/dist/dto/update-collaborator-type.dto.js.map +1 -1
  9. package/dist/operations.service.d.ts +22 -0
  10. package/dist/operations.service.d.ts.map +1 -1
  11. package/dist/operations.service.js +180 -47
  12. package/dist/operations.service.js.map +1 -1
  13. package/dist/operations.service.spec.js +73 -0
  14. package/dist/operations.service.spec.js.map +1 -1
  15. package/hedhog/data/menu.yaml +26 -26
  16. package/hedhog/data/operations_collaborator_type.yaml +76 -76
  17. package/hedhog/data/route.yaml +13 -0
  18. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +5 -3
  19. package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +1 -0
  20. package/hedhog/frontend/app/approvals/page.tsx.ejs +2 -2
  21. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +26 -15
  22. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +235 -72
  23. package/hedhog/frontend/app/timesheets/page.tsx.ejs +344 -134
  24. package/hedhog/frontend/messages/en.json +5 -0
  25. package/hedhog/frontend/messages/pt.json +7 -2
  26. package/hedhog/table/operations_collaborator.yaml +18 -18
  27. package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -43
  28. package/hedhog/table/operations_collaborator_type.yaml +33 -33
  29. package/hedhog/table/operations_contract_document.yaml +33 -33
  30. package/package.json +4 -4
  31. package/src/controllers/operations-timesheets.controller.ts +13 -0
  32. package/src/dto/create-collaborator-type.dto.ts +43 -43
  33. package/src/dto/create-collaborator.dto.ts +223 -223
  34. package/src/dto/list-collaborator-types.dto.ts +15 -15
  35. package/src/dto/list-collaborators.dto.ts +30 -30
  36. package/src/dto/update-collaborator-type.dto.ts +4 -3
  37. package/src/dto/update-collaborator.dto.ts +3 -3
  38. package/src/operations.service.spec.ts +96 -0
  39. package/src/operations.service.ts +257 -47
@@ -1,223 +1,223 @@
1
- import { Transform, Type } from 'class-transformer';
2
- import {
3
- IsBoolean,
4
- IsIn,
5
- IsInt,
6
- IsNumber,
7
- IsObject,
8
- IsOptional,
9
- IsString,
10
- MaxLength,
11
- ValidateNested,
12
- } from 'class-validator';
13
-
14
- class CollaboratorWeeklyScheduleDayDto {
15
- @IsString()
16
- @IsIn([
17
- 'monday',
18
- 'tuesday',
19
- 'wednesday',
20
- 'thursday',
21
- 'friday',
22
- 'saturday',
23
- 'sunday',
24
- ])
25
- weekday!:
26
- | 'monday'
27
- | 'tuesday'
28
- | 'wednesday'
29
- | 'thursday'
30
- | 'friday'
31
- | 'saturday'
32
- | 'sunday';
33
-
34
- @IsOptional()
35
- @IsBoolean()
36
- isWorkingDay?: boolean;
37
-
38
- @IsOptional()
39
- @IsString()
40
- startTime?: string | null;
41
-
42
- @IsOptional()
43
- @IsString()
44
- endTime?: string | null;
45
-
46
- @IsOptional()
47
- @Transform(({ value }) =>
48
- value === '' || value === undefined || value === null ? undefined : Number(value)
49
- )
50
- @IsNumber()
51
- breakMinutes?: number | null;
52
- }
53
-
54
- export class CollaboratorEquityParticipationDto {
55
- @IsOptional()
56
- @IsIn([
57
- 'partner_quota_holder',
58
- 'common_shareholder',
59
- 'preferred_shareholder',
60
- 'administrator',
61
- 'other',
62
- ])
63
- participationType?:
64
- | 'partner_quota_holder'
65
- | 'common_shareholder'
66
- | 'preferred_shareholder'
67
- | 'administrator'
68
- | 'other';
69
-
70
- @IsOptional()
71
- @Transform(({ value }) =>
72
- value === '' || value === undefined || value === null ? undefined : Number(value)
73
- )
74
- @IsNumber()
75
- percentage?: number | null;
76
-
77
- @IsOptional()
78
- @Transform(({ value }) =>
79
- value === '' || value === undefined || value === null ? undefined : Number(value)
80
- )
81
- @IsNumber()
82
- votingPower?: number | null;
83
-
84
- @IsOptional()
85
- @IsString()
86
- startDate?: string | null;
87
-
88
- @IsOptional()
89
- @IsString()
90
- endDate?: string | null;
91
-
92
- @IsOptional()
93
- @IsString()
94
- notes?: string | null;
95
- }
96
-
97
- export class CreateCollaboratorDto {
98
- @IsOptional()
99
- @Transform(({ value }) =>
100
- value === '' || value === undefined || value === null ? undefined : Number(value)
101
- )
102
- @IsInt()
103
- userId?: number | null;
104
-
105
- @IsOptional()
106
- @Transform(({ value }) =>
107
- value === '' || value === undefined || value === null ? undefined : Number(value)
108
- )
109
- @IsInt()
110
- personId?: number | null;
111
-
112
- @IsOptional()
113
- @IsString()
114
- @MaxLength(32)
115
- code?: string | null;
116
-
117
- @IsOptional()
118
- @IsString()
119
- @MaxLength(180)
120
- displayName?: string | null;
121
-
122
- @IsOptional()
123
- @Transform(({ value }) =>
124
- value === '' || value === undefined || value === null ? undefined : Number(value)
125
- )
126
- @IsInt()
127
- collaboratorTypeId?: number | null;
128
-
129
- @IsOptional()
130
- @IsString()
131
- @MaxLength(80)
132
- collaboratorTypeSlug?: string | null;
133
-
134
- @IsOptional()
135
- @IsString()
136
- @MaxLength(80)
137
- collaboratorType?: string | null;
138
-
139
- @IsOptional()
140
- @IsString()
141
- @MaxLength(120)
142
- department?: string | null;
143
-
144
- @IsOptional()
145
- @Transform(({ value }) =>
146
- value === '' || value === undefined || value === null ? undefined : Number(value)
147
- )
148
- @IsInt()
149
- departmentId?: number | null;
150
-
151
- @IsOptional()
152
- @Transform(({ value }) =>
153
- value === '' || value === undefined || value === null ? undefined : Number(value)
154
- )
155
- @IsInt()
156
- jobTitleId?: number | null;
157
-
158
- @IsOptional()
159
- @IsString()
160
- @MaxLength(120)
161
- title?: string | null;
162
-
163
- @IsOptional()
164
- @IsString()
165
- @MaxLength(120)
166
- levelLabel?: string | null;
167
-
168
- @IsOptional()
169
- @Transform(({ value }) =>
170
- value === '' || value === undefined || value === null ? undefined : Number(value)
171
- )
172
- @IsInt()
173
- supervisorCollaboratorId?: number | null;
174
-
175
- @IsOptional()
176
- @Transform(({ value }) =>
177
- value === '' || value === undefined || value === null ? undefined : Number(value)
178
- )
179
- @IsNumber()
180
- weeklyCapacityHours?: number | null;
181
-
182
- @IsOptional()
183
- @IsIn(['draft', 'active', 'on_leave', 'inactive'])
184
- status?: 'draft' | 'active' | 'on_leave' | 'inactive';
185
-
186
- @IsOptional()
187
- @IsString()
188
- joinedAt?: string | null;
189
-
190
- @IsOptional()
191
- @IsString()
192
- leftAt?: string | null;
193
-
194
- @IsOptional()
195
- @Transform(({ value }) =>
196
- value === '' || value === undefined || value === null ? undefined : Number(value)
197
- )
198
- @IsNumber()
199
- compensationAmount?: number | null;
200
-
201
- @IsOptional()
202
- @IsString()
203
- contractDescription?: string | null;
204
-
205
- @IsOptional()
206
- @IsBoolean()
207
- autoGenerateContractDraft?: boolean;
208
-
209
- @IsOptional()
210
- @ValidateNested({ each: true })
211
- @Type(() => CollaboratorWeeklyScheduleDayDto)
212
- weeklySchedule?: CollaboratorWeeklyScheduleDayDto[];
213
-
214
- @IsOptional()
215
- @IsObject()
216
- @ValidateNested()
217
- @Type(() => CollaboratorEquityParticipationDto)
218
- equityParticipation?: CollaboratorEquityParticipationDto | null;
219
-
220
- @IsOptional()
221
- @IsString()
222
- notes?: string | null;
223
- }
1
+ import { Transform, Type } from 'class-transformer';
2
+ import {
3
+ IsBoolean,
4
+ IsIn,
5
+ IsInt,
6
+ IsNumber,
7
+ IsObject,
8
+ IsOptional,
9
+ IsString,
10
+ MaxLength,
11
+ ValidateNested,
12
+ } from 'class-validator';
13
+
14
+ class CollaboratorWeeklyScheduleDayDto {
15
+ @IsString()
16
+ @IsIn([
17
+ 'monday',
18
+ 'tuesday',
19
+ 'wednesday',
20
+ 'thursday',
21
+ 'friday',
22
+ 'saturday',
23
+ 'sunday',
24
+ ])
25
+ weekday!:
26
+ | 'monday'
27
+ | 'tuesday'
28
+ | 'wednesday'
29
+ | 'thursday'
30
+ | 'friday'
31
+ | 'saturday'
32
+ | 'sunday';
33
+
34
+ @IsOptional()
35
+ @IsBoolean()
36
+ isWorkingDay?: boolean;
37
+
38
+ @IsOptional()
39
+ @IsString()
40
+ startTime?: string | null;
41
+
42
+ @IsOptional()
43
+ @IsString()
44
+ endTime?: string | null;
45
+
46
+ @IsOptional()
47
+ @Transform(({ value }) =>
48
+ value === '' || value === undefined || value === null ? undefined : Number(value)
49
+ )
50
+ @IsNumber()
51
+ breakMinutes?: number | null;
52
+ }
53
+
54
+ export class CollaboratorEquityParticipationDto {
55
+ @IsOptional()
56
+ @IsIn([
57
+ 'partner_quota_holder',
58
+ 'common_shareholder',
59
+ 'preferred_shareholder',
60
+ 'administrator',
61
+ 'other',
62
+ ])
63
+ participationType?:
64
+ | 'partner_quota_holder'
65
+ | 'common_shareholder'
66
+ | 'preferred_shareholder'
67
+ | 'administrator'
68
+ | 'other';
69
+
70
+ @IsOptional()
71
+ @Transform(({ value }) =>
72
+ value === '' || value === undefined || value === null ? undefined : Number(value)
73
+ )
74
+ @IsNumber()
75
+ percentage?: number | null;
76
+
77
+ @IsOptional()
78
+ @Transform(({ value }) =>
79
+ value === '' || value === undefined || value === null ? undefined : Number(value)
80
+ )
81
+ @IsNumber()
82
+ votingPower?: number | null;
83
+
84
+ @IsOptional()
85
+ @IsString()
86
+ startDate?: string | null;
87
+
88
+ @IsOptional()
89
+ @IsString()
90
+ endDate?: string | null;
91
+
92
+ @IsOptional()
93
+ @IsString()
94
+ notes?: string | null;
95
+ }
96
+
97
+ export class CreateCollaboratorDto {
98
+ @IsOptional()
99
+ @Transform(({ value }) =>
100
+ value === '' || value === undefined || value === null ? undefined : Number(value)
101
+ )
102
+ @IsInt()
103
+ userId?: number | null;
104
+
105
+ @IsOptional()
106
+ @Transform(({ value }) =>
107
+ value === '' || value === undefined || value === null ? undefined : Number(value)
108
+ )
109
+ @IsInt()
110
+ personId?: number | null;
111
+
112
+ @IsOptional()
113
+ @IsString()
114
+ @MaxLength(32)
115
+ code?: string | null;
116
+
117
+ @IsOptional()
118
+ @IsString()
119
+ @MaxLength(180)
120
+ displayName?: string | null;
121
+
122
+ @IsOptional()
123
+ @Transform(({ value }) =>
124
+ value === '' || value === undefined || value === null ? undefined : Number(value)
125
+ )
126
+ @IsInt()
127
+ collaboratorTypeId?: number | null;
128
+
129
+ @IsOptional()
130
+ @IsString()
131
+ @MaxLength(80)
132
+ collaboratorTypeSlug?: string | null;
133
+
134
+ @IsOptional()
135
+ @IsString()
136
+ @MaxLength(80)
137
+ collaboratorType?: string | null;
138
+
139
+ @IsOptional()
140
+ @IsString()
141
+ @MaxLength(120)
142
+ department?: string | null;
143
+
144
+ @IsOptional()
145
+ @Transform(({ value }) =>
146
+ value === '' || value === undefined || value === null ? undefined : Number(value)
147
+ )
148
+ @IsInt()
149
+ departmentId?: number | null;
150
+
151
+ @IsOptional()
152
+ @Transform(({ value }) =>
153
+ value === '' || value === undefined || value === null ? undefined : Number(value)
154
+ )
155
+ @IsInt()
156
+ jobTitleId?: number | null;
157
+
158
+ @IsOptional()
159
+ @IsString()
160
+ @MaxLength(120)
161
+ title?: string | null;
162
+
163
+ @IsOptional()
164
+ @IsString()
165
+ @MaxLength(120)
166
+ levelLabel?: string | null;
167
+
168
+ @IsOptional()
169
+ @Transform(({ value }) =>
170
+ value === '' || value === undefined || value === null ? undefined : Number(value)
171
+ )
172
+ @IsInt()
173
+ supervisorCollaboratorId?: number | null;
174
+
175
+ @IsOptional()
176
+ @Transform(({ value }) =>
177
+ value === '' || value === undefined || value === null ? undefined : Number(value)
178
+ )
179
+ @IsNumber()
180
+ weeklyCapacityHours?: number | null;
181
+
182
+ @IsOptional()
183
+ @IsIn(['draft', 'active', 'on_leave', 'inactive'])
184
+ status?: 'draft' | 'active' | 'on_leave' | 'inactive';
185
+
186
+ @IsOptional()
187
+ @IsString()
188
+ joinedAt?: string | null;
189
+
190
+ @IsOptional()
191
+ @IsString()
192
+ leftAt?: string | null;
193
+
194
+ @IsOptional()
195
+ @Transform(({ value }) =>
196
+ value === '' || value === undefined || value === null ? undefined : Number(value)
197
+ )
198
+ @IsNumber()
199
+ compensationAmount?: number | null;
200
+
201
+ @IsOptional()
202
+ @IsString()
203
+ contractDescription?: string | null;
204
+
205
+ @IsOptional()
206
+ @IsBoolean()
207
+ autoGenerateContractDraft?: boolean;
208
+
209
+ @IsOptional()
210
+ @ValidateNested({ each: true })
211
+ @Type(() => CollaboratorWeeklyScheduleDayDto)
212
+ weeklySchedule?: CollaboratorWeeklyScheduleDayDto[];
213
+
214
+ @IsOptional()
215
+ @IsObject()
216
+ @ValidateNested()
217
+ @Type(() => CollaboratorEquityParticipationDto)
218
+ equityParticipation?: CollaboratorEquityParticipationDto | null;
219
+
220
+ @IsOptional()
221
+ @IsString()
222
+ notes?: string | null;
223
+ }
@@ -1,15 +1,15 @@
1
- import { Transform } from 'class-transformer';
2
- import { IsBoolean, IsOptional } from 'class-validator';
3
-
4
- export class ListCollaboratorTypesDto {
5
- @IsOptional()
6
- @Transform(({ value }) => {
7
- if (value === '' || value === undefined || value === null) {
8
- return undefined;
9
- }
10
-
11
- return value === true || value === 'true' || value === '1';
12
- })
13
- @IsBoolean()
14
- active?: boolean;
15
- }
1
+ import { Transform } from 'class-transformer';
2
+ import { IsBoolean, IsOptional } from 'class-validator';
3
+
4
+ export class ListCollaboratorTypesDto {
5
+ @IsOptional()
6
+ @Transform(({ value }) => {
7
+ if (value === '' || value === undefined || value === null) {
8
+ return undefined;
9
+ }
10
+
11
+ return value === true || value === 'true' || value === '1';
12
+ })
13
+ @IsBoolean()
14
+ active?: boolean;
15
+ }
@@ -1,30 +1,30 @@
1
- import { PaginationDTO } from '@hed-hog/api-pagination';
2
- import { Transform } from 'class-transformer';
3
- import { IsInt, IsOptional, IsString } from 'class-validator';
4
-
5
- export class ListCollaboratorsDto extends PaginationDTO {
6
- @IsOptional()
7
- @IsString()
8
- status?: string;
9
-
10
- @IsOptional()
11
- @Transform(({ value }) =>
12
- value === '' || value === undefined || value === null ? undefined : Number(value)
13
- )
14
- @IsInt()
15
- collaboratorTypeId?: number;
16
-
17
- @IsOptional()
18
- @Transform(({ value }) =>
19
- value === '' || value === undefined || value === null ? undefined : Number(value)
20
- )
21
- @IsInt()
22
- departmentId?: number;
23
-
24
- @IsOptional()
25
- @Transform(({ value }) =>
26
- value === '' || value === undefined || value === null ? undefined : Number(value)
27
- )
28
- @IsInt()
29
- jobTitleId?: number;
30
- }
1
+ import { PaginationDTO } from '@hed-hog/api-pagination';
2
+ import { Transform } from 'class-transformer';
3
+ import { IsInt, IsOptional, IsString } from 'class-validator';
4
+
5
+ export class ListCollaboratorsDto extends PaginationDTO {
6
+ @IsOptional()
7
+ @IsString()
8
+ status?: string;
9
+
10
+ @IsOptional()
11
+ @Transform(({ value }) =>
12
+ value === '' || value === undefined || value === null ? undefined : Number(value)
13
+ )
14
+ @IsInt()
15
+ collaboratorTypeId?: number;
16
+
17
+ @IsOptional()
18
+ @Transform(({ value }) =>
19
+ value === '' || value === undefined || value === null ? undefined : Number(value)
20
+ )
21
+ @IsInt()
22
+ departmentId?: number;
23
+
24
+ @IsOptional()
25
+ @Transform(({ value }) =>
26
+ value === '' || value === undefined || value === null ? undefined : Number(value)
27
+ )
28
+ @IsInt()
29
+ jobTitleId?: number;
30
+ }
@@ -1,3 +1,4 @@
1
- import { CreateCollaboratorTypeDto } from './create-collaborator-type.dto';
2
-
3
- export class UpdateCollaboratorTypeDto extends CreateCollaboratorTypeDto {}
1
+ import { PartialType } from '@nestjs/mapped-types';
2
+ import { CreateCollaboratorTypeDto } from './create-collaborator-type.dto';
3
+
4
+ export class UpdateCollaboratorTypeDto extends PartialType(CreateCollaboratorTypeDto) {}
@@ -1,3 +1,3 @@
1
- import { CreateCollaboratorDto } from './create-collaborator.dto';
2
-
3
- export class UpdateCollaboratorDto extends CreateCollaboratorDto {}
1
+ import { CreateCollaboratorDto } from './create-collaborator.dto';
2
+
3
+ export class UpdateCollaboratorDto extends CreateCollaboratorDto {}
@@ -472,6 +472,102 @@ describe('OperationsService quick-entry timesheets', () => {
472
472
  expect((service as any).refreshTimesheetTotal).toHaveBeenCalledWith(tx, 55);
473
473
  });
474
474
 
475
+ it('updates quick entries and moves them to the correct weekly timesheet', async () => {
476
+ const tx = {
477
+ $executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
478
+ $queryRawUnsafe: jest.fn().mockResolvedValue([]),
479
+ };
480
+
481
+ (service as any).prisma.$transaction.mockImplementation(
482
+ async (callback: (client: unknown) => unknown) => callback(tx),
483
+ );
484
+
485
+ jest
486
+ .spyOn(service as any, 'getTimesheetEntryByIdForActor')
487
+ .mockResolvedValueOnce({
488
+ id: 93,
489
+ timesheetId: 55,
490
+ collaboratorId: 7,
491
+ projectId: 11,
492
+ projectAssignmentId: 33,
493
+ taskId: 44,
494
+ status: 'draft',
495
+ weekStartDate: '2026-04-06',
496
+ weekEndDate: '2026-04-12',
497
+ workDate: '2026-04-09',
498
+ hours: 1,
499
+ durationMinutes: 60,
500
+ })
501
+ .mockResolvedValueOnce({
502
+ id: 93,
503
+ timesheetId: 77,
504
+ collaboratorId: 7,
505
+ projectId: 12,
506
+ projectAssignmentId: 34,
507
+ taskId: 45,
508
+ status: 'draft',
509
+ weekStartDate: '2026-04-13',
510
+ weekEndDate: '2026-04-19',
511
+ workDate: '2026-04-14',
512
+ hours: 2,
513
+ durationMinutes: 120,
514
+ taskName: 'Review backlog',
515
+ });
516
+ jest.spyOn(service as any, 'resolveOwnedProjectAssignment').mockResolvedValue({
517
+ id: 34,
518
+ projectId: 12,
519
+ projectName: 'Project Boreal',
520
+ projectCode: 'OPS-12',
521
+ roleLabel: 'Engineer',
522
+ });
523
+ jest.spyOn(service as any, 'getOwnedTaskRecord').mockResolvedValue({
524
+ id: 45,
525
+ name: 'Review backlog',
526
+ projectAssignmentId: 34,
527
+ projectId: 12,
528
+ projectName: 'Project Boreal',
529
+ projectCode: 'OPS-12',
530
+ });
531
+ jest.spyOn(service as any, 'getOrCreateTimesheetForWorkDate').mockResolvedValue(77);
532
+ jest.spyOn(service as any, 'refreshTimesheetTotal').mockResolvedValue(undefined);
533
+ jest.spyOn(service as any, 'cleanupEmptyEditableTimesheet').mockResolvedValue(undefined);
534
+
535
+ const result = await service.updateTimesheetEntry(15, 93, {
536
+ projectId: 12,
537
+ projectAssignmentId: 34,
538
+ taskId: 45,
539
+ workDate: '2026-04-14',
540
+ duration: 2,
541
+ unit: 'hours',
542
+ description: 'Backlog refinement',
543
+ } as any);
544
+
545
+ expect(result).toEqual(
546
+ expect.objectContaining({
547
+ id: 93,
548
+ timesheetId: 77,
549
+ }),
550
+ );
551
+ expect(tx.$executeRawUnsafe).toHaveBeenCalledWith(
552
+ expect.stringContaining('UPDATE operations_timesheet_entry'),
553
+ 77,
554
+ 34,
555
+ 45,
556
+ 'Review backlog',
557
+ '2026-04-14',
558
+ 120,
559
+ 2,
560
+ 'Backlog refinement',
561
+ 93,
562
+ );
563
+ expect((service as any).refreshTimesheetTotal).toHaveBeenCalledWith(tx, 55);
564
+ expect((service as any).refreshTimesheetTotal).toHaveBeenCalledWith(tx, 77);
565
+ expect((service as any).cleanupEmptyEditableTimesheet).toHaveBeenCalledWith(
566
+ tx,
567
+ 55,
568
+ );
569
+ });
570
+
475
571
  it('rejects deleting entries from submitted timesheets', async () => {
476
572
  jest.spyOn(service as any, 'getTimesheetEntryByIdForActor').mockResolvedValue({
477
573
  id: 92,