Skip to content

Commit ee4db27

Browse files
committed
Validate engagement date overrides against the project
1 parent 3628604 commit ee4db27

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

src/components/engagement/handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './validate-eng-date-overrides-on-project-change.handler';
12
export * from './set-initial-end-date.handler';
23
export * from './update-engagement-status.handler';
34
export * from './set-last-status-date.handler';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { isNotFalsy, NonEmptyArray } from '@seedcompany/common';
2+
import { CalendarDate, ID, RangeException, type UnsecuredDto } from '~/common';
3+
import { EventsHandler, IEventHandler } from '~/core';
4+
import type { Project } from '../../project/dto';
5+
import { ProjectUpdatedEvent } from '../../project/events';
6+
import { EngagementService } from '../engagement.service';
7+
8+
@EventsHandler(ProjectUpdatedEvent)
9+
export class ValidateEngDateOverridesOnProjectChangeHandlerSetLastStatusDate
10+
implements IEventHandler<ProjectUpdatedEvent>
11+
{
12+
constructor(private readonly engagements: EngagementService) {}
13+
14+
async handle(event: ProjectUpdatedEvent) {
15+
const { changes, updated, session } = event;
16+
17+
if (changes.mouStart === undefined && changes.mouEnd === undefined) {
18+
return;
19+
}
20+
21+
const project = {
22+
id: updated.id,
23+
name: updated.name,
24+
mouStart: updated.mouStart,
25+
mouEnd: updated.mouEnd,
26+
};
27+
const engagements = await this.engagements.listAllByProjectId(
28+
project.id,
29+
session,
30+
);
31+
const errors = engagements
32+
.flatMap<
33+
EngagementDateOverrideConflictException['engagements'][0] | null
34+
>((eng) => {
35+
const common = {
36+
id: eng.id,
37+
} as const;
38+
const { startDateOverride: start, endDateOverride: end } = eng;
39+
return [
40+
project.mouStart && start && project.mouStart > start
41+
? {
42+
...common,
43+
point: 'start' as const,
44+
date: start,
45+
}
46+
: null,
47+
project.mouEnd && end && project.mouEnd < end
48+
? {
49+
...common,
50+
point: 'end' as const,
51+
date: end,
52+
}
53+
: null,
54+
];
55+
})
56+
.filter(isNotFalsy);
57+
if (errors.length === 0) {
58+
return;
59+
}
60+
throw new EngagementDateOverrideConflictException(project, [
61+
errors[0]!,
62+
...errors.slice(1),
63+
]);
64+
}
65+
}
66+
67+
class EngagementDateOverrideConflictException extends RangeException {
68+
constructor(
69+
readonly project: Pick<
70+
UnsecuredDto<Project>,
71+
'id' | 'name' | 'mouStart' | 'mouEnd'
72+
>,
73+
readonly engagements: NonEmptyArray<
74+
Readonly<{
75+
id: ID<'Engagement'>;
76+
point: 'start' | 'end';
77+
date: CalendarDate;
78+
}>
79+
>,
80+
) {
81+
const message = [
82+
engagements.length === 1
83+
? 'An engagement has a date outside the new range'
84+
: 'Some engagements have dates outside the new range',
85+
...engagements.map((eng) => {
86+
const pointStr = eng.point === 'start' ? 'Start' : 'End';
87+
const dateStr = eng.date.toISO();
88+
return ` - ${pointStr} date of ${eng.id} is ${dateStr}`;
89+
}),
90+
].join('\n');
91+
super({ message });
92+
}
93+
}

0 commit comments

Comments
 (0)