Skip to content

Commit 73a5ff7

Browse files
committed
Assign directors as members if needed when the project's region is assigned
1 parent 2824e51 commit 73a5ff7

File tree

7 files changed

+387
-56
lines changed

7 files changed

+387
-56
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ResourceLoader } from '~/core';
2+
import { EventsHandler } from '~/core/events';
3+
import { ProjectUpdatedEvent } from '../../events';
4+
import { ProjectMemberRepository } from '../project-member.repository';
5+
6+
@EventsHandler(ProjectUpdatedEvent)
7+
export class ProjectRegionDefaultsDirectorMembershipHandler {
8+
constructor(
9+
private readonly repo: ProjectMemberRepository,
10+
private readonly resources: ResourceLoader,
11+
) {}
12+
13+
async handle(event: ProjectUpdatedEvent) {
14+
const { fieldRegionId } = event.changes;
15+
if (!fieldRegionId) {
16+
return;
17+
}
18+
19+
const fieldRegion = await this.resources.load('FieldRegion', fieldRegionId);
20+
if (fieldRegion.director.value) {
21+
await this.repo.addDefaultForRole(
22+
'RegionalDirector',
23+
event.updated.id,
24+
fieldRegion.director.value.id,
25+
);
26+
}
27+
28+
if (fieldRegion.fieldZone.value) {
29+
const fieldZone = await this.resources.load(
30+
'FieldZone',
31+
fieldRegion.fieldZone.value.id,
32+
);
33+
if (fieldZone.director.value) {
34+
await this.repo.addDefaultForRole(
35+
'FieldOperationsDirector',
36+
event.updated.id,
37+
fieldZone.director.value.id,
38+
);
39+
}
40+
}
41+
}
42+
}

src/components/project/project-member/project-member.gel.repository.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,54 @@ export class ProjectMemberGelRepository
7373
];
7474
}
7575

76+
async addDefaultForRole(
77+
role: Role,
78+
project: ID<'Project'>,
79+
user: ID<'User'>,
80+
) {
81+
await this.db.run(this.addDefaultForRoleQuery, {
82+
role,
83+
project,
84+
user,
85+
});
86+
}
87+
private readonly addDefaultForRoleQuery = e.params(
88+
{
89+
role: e.Role,
90+
project: e.uuid,
91+
user: e.uuid,
92+
},
93+
($) => {
94+
const project = e.cast(e.Project, $.project);
95+
const user = e.cast(e.User, $.user);
96+
97+
const membersWithRole = e.select(project.members, (member) => ({
98+
filter: e.all(
99+
e.set(
100+
e.op(member.active, '=', true),
101+
e.op($.role, 'in', member.roles),
102+
),
103+
),
104+
}));
105+
const hasMemberWithRole = e.op('exists', membersWithRole);
106+
const createNew = e.insert(e.Project.Member, {
107+
project,
108+
projectContext: project.projectContext,
109+
user,
110+
roles: $.role,
111+
});
112+
const exp = e.op(
113+
'if',
114+
hasMemberWithRole,
115+
'then',
116+
membersWithRole,
117+
'else',
118+
createNew,
119+
);
120+
return e.select(exp);
121+
},
122+
);
123+
76124
async replaceMembershipsOnOpenProjects(
77125
oldDirector: ID<'User'>,
78126
newDirector: ID<'User'>,

src/components/project/project-member/project-member.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { UserModule } from '../../user/user.module';
55
import { ProjectModule } from '../project.module';
66
import { AvailableRolesToProjectResolver } from './available-roles-to-project.resolver';
77
import { DirectorChangeApplyToProjectMembersHandler } from './handlers/director-change-apply-to-project-members.handler';
8+
import { ProjectRegionDefaultsDirectorMembershipHandler } from './handlers/project-region-defaults-director-membership.handler';
89
import { RegionsZoneChangesAppliesDirectorChangeToProjectMembersHandler } from './handlers/regions-zone-changes-applies-director-change-to-project-members.handler';
910
import { AddInactiveAtMigration } from './migrations/add-inactive-at.migration';
1011
import { ProjectMemberGelRepository } from './project-member.gel.repository';
@@ -28,6 +29,7 @@ import { ProjectMemberService } from './project-member.service';
2829
AddInactiveAtMigration,
2930
DirectorChangeApplyToProjectMembersHandler,
3031
RegionsZoneChangesAppliesDirectorChangeToProjectMembersHandler,
32+
ProjectRegionDefaultsDirectorMembershipHandler,
3133
],
3234
exports: [ProjectMemberService],
3335
})

src/components/project/project-member/project-member.repository.ts

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
sorting,
3333
updateProperty,
3434
variable,
35+
Variable,
3536
} from '~/core/database/query';
3637
import { type FilterFn } from '~/core/database/query/filters';
3738
import { conditionalOn } from '~/core/database/query/properties/update-property';
@@ -202,6 +203,41 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) {
202203
.run();
203204
}
204205

206+
async addDefaultForRole(
207+
role: Role,
208+
project: ID<'Project'>,
209+
user: ID<'User'>,
210+
) {
211+
const now = DateTime.now();
212+
await this.db
213+
.query()
214+
.apply((q) => {
215+
q.params.addParam(now, 'now');
216+
})
217+
.match(node('project', 'Project', { id: project }))
218+
.subQuery('project', (sub) =>
219+
sub
220+
.match([
221+
node('project'),
222+
relation('out', '', 'member', ACTIVE),
223+
node('node', 'ProjectMember'),
224+
])
225+
.apply(
226+
projectMemberFilters({
227+
active: true,
228+
roles: [role],
229+
}),
230+
)
231+
.with('count(node) as members')
232+
.raw('WHERE members = 0')
233+
.return('true as filtered'),
234+
)
235+
.with('*')
236+
.apply(await this.upsertMember(user, role))
237+
.return<{ id: ID<'ProjectMember'> }>('project.id as id')
238+
.executeAndLogStats();
239+
}
240+
205241
async replaceMembershipsOnOpenProjects(
206242
oldDirector: ID<'User'>,
207243
newDirector: ID<'User'>,
@@ -211,20 +247,11 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) {
211247
) {
212248
const nowVal = DateTime.now();
213249
const now = variable('$now');
214-
const createMember = await createNode(ProjectMember, {
215-
baseNodeProps: {
216-
id: variable(randomUUID()),
217-
createdAt: now,
218-
},
219-
initialProps: {
220-
roles: [role],
221-
inactiveAt: null,
222-
modifiedAt: now,
223-
},
224-
});
225250
const result = await this.db
226251
.query()
227-
.raw('', { now: nowVal })
252+
.apply((q) => {
253+
q.params.addParam(nowVal, 'now');
254+
})
228255
.match([
229256
node('project', 'Project'),
230257
relation('out', '', 'member', ACTIVE),
@@ -282,15 +309,39 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) {
282309
)
283310
.return('stats as oldMemberStats'),
284311
)
285-
.subQuery('project', (sub) =>
312+
.with('project')
313+
.apply(await this.upsertMember(newDirector, role))
314+
.return<{ id: ID }>('project.id as id')
315+
.run();
316+
return {
317+
projects: result.map(({ id }) => id) as readonly ID[],
318+
timestampId: nowVal,
319+
};
320+
}
321+
322+
protected async upsertMember(user: ID<'User'> | Variable, role: Role) {
323+
const now = variable('$now');
324+
const createMember = await createNode(ProjectMember, {
325+
baseNodeProps: {
326+
id: variable(randomUUID()),
327+
createdAt: now,
328+
},
329+
initialProps: {
330+
roles: [role],
331+
inactiveAt: null,
332+
modifiedAt: now,
333+
},
334+
});
335+
return (query: Query) =>
336+
query.subQuery('project', (sub) =>
286337
sub
287338
.match([
288339
[
289340
node('project'),
290341
relation('out', '', 'member', ACTIVE),
291342
node('node', 'ProjectMember'),
292343
relation('out', '', 'user', ACTIVE),
293-
node('', 'User', { id: newDirector }),
344+
node('', 'User', { id: user }),
294345
],
295346
[
296347
node('node', 'ProjectMember'),
@@ -339,25 +390,19 @@ export class ProjectMemberRepository extends DtoRepository(ProjectMember) {
339390
relation('out', '', 'member', ACTIVE),
340391
node('', 'ProjectMember'),
341392
relation('out', '', 'user', ACTIVE),
342-
node('', 'User', { id: newDirector }),
393+
node('', 'User', { id: user }),
343394
]),
344395
),
345396
)
346397
.apply(createMember)
347398
.apply(
348399
createRelationships(ProjectMember, {
349400
in: { member: variable('project') },
350-
out: { user: ['User', newDirector] },
401+
out: { user: user instanceof Variable ? user : ['User', user] },
351402
}),
352403
)
353404
.return('node as member'),
354-
)
355-
.return<{ id: ID }>('project.id as id')
356-
.run();
357-
return {
358-
projects: result.map(({ id }) => id) as readonly ID[],
359-
timestampId: nowVal,
360-
};
405+
);
361406
}
362407
}
363408

0 commit comments

Comments
 (0)