Skip to content

Commit 93d4b3d

Browse files
committed
Consultants, FA, FPM can only execute their transitions if they're a member
The roles higher than them can execute without membership constraints
1 parent 10e7bf4 commit 93d4b3d

File tree

8 files changed

+73
-42
lines changed

8 files changed

+73
-42
lines changed

src/components/authorization/policies/by-role/consultant-manager.policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { member, Policy, Role, sensMediumOrLower, sensOnlyLow } from '../util';
2+
import * as Consultant from './consultant.policy';
23

34
// NOTE: There could be other permissions for this role from other policies
45
@Policy(Role.ConsultantManager, (r) => [
@@ -40,6 +41,7 @@ import { member, Policy, Role, sensMediumOrLower, sensOnlyLow } from '../util';
4041
p.rootDirectory.whenAny(member, sensMediumOrLower).edit,
4142
])
4243
.children((c) => [c.posts.edit]),
44+
r.ProjectWorkflowEvent.transitions(...Consultant.projectTransitions).execute,
4345
[
4446
r.PeriodicReport,
4547
r.ProgressReportCommunityStory,

src/components/authorization/policies/by-role/consultant.policy.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
import { ProjectWorkflow } from '../../../project/workflow/project-workflow';
12
import { member, Policy, Role } from '../util';
23

4+
export const projectTransitions = ProjectWorkflow.pickNames(
5+
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
6+
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
7+
);
8+
39
// NOTE: There could be other permissions for this role from other policies
410
@Policy([Role.Consultant, Role.ConsultantManager], (r) => [
511
[
@@ -36,9 +42,9 @@ import { member, Policy, Role } from '../util';
3642
r.Project.when(member).specifically((p) => p.rootDirectory.edit),
3743
r.Unavailability.read,
3844
r.User.read.create,
39-
r.ProjectWorkflowEvent.read.transitions(
40-
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
41-
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
45+
r.ProjectWorkflowEvent.read.whenAll(
46+
member,
47+
r.ProjectWorkflowEvent.isTransitions(...projectTransitions),
4248
).execute,
4349
])
4450
export class ConsultantPolicy {}

src/components/authorization/policies/by-role/financial-analyst-lead.policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { member, Policy, Role, sensMediumOrLower } from '../util';
2+
import * as FA from './financial-analyst.policy';
23

34
// NOTE: There could be other permissions for this role from other policies
45
@Policy([Role.LeadFinancialAnalyst, Role.Controller], (r) => [
@@ -35,6 +36,7 @@ import { member, Policy, Role, sensMediumOrLower } from '../util';
3536
'financialReportReceivedAt',
3637
).edit,
3738
]),
39+
r.ProjectWorkflowEvent.transitions(...FA.projectTransitions).execute,
3840
r.ProjectMember.edit.create.delete,
3941
])
4042
export class FinancialAnalystLeadPolicy {}

src/components/authorization/policies/by-role/financial-analyst.policy.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ProjectWorkflow } from '../../../project/workflow/project-workflow';
12
import {
23
inherit,
34
member,
@@ -7,6 +8,13 @@ import {
78
sensOnlyLow,
89
} from '../util';
910

11+
export const projectTransitions = ProjectWorkflow.pickNames(
12+
'Pending Financial Endorsement -> Finalizing Proposal With Financial Endorsement',
13+
'Pending Financial Endorsement -> Finalizing Proposal Without Financial Endorsement',
14+
'Finalizing Completion -> Back To Active',
15+
'Finalizing Completion -> Completed',
16+
);
17+
1018
// NOTE: There could be other permissions for this role from other policies
1119
@Policy(
1220
[Role.FinancialAnalyst, Role.LeadFinancialAnalyst, Role.Controller],
@@ -61,11 +69,9 @@ import {
6169
])
6270
.children((c) => c.posts.edit),
6371
r.ProjectMember.read.when(member).edit.create.delete,
64-
r.ProjectWorkflowEvent.read.transitions(
65-
'Pending Financial Endorsement -> Finalizing Proposal With Financial Endorsement',
66-
'Pending Financial Endorsement -> Finalizing Proposal Without Financial Endorsement',
67-
'Finalizing Completion -> Back To Active',
68-
'Finalizing Completion -> Completed',
72+
r.ProjectWorkflowEvent.read.whenAll(
73+
member,
74+
r.ProjectWorkflowEvent.isTransitions(...projectTransitions),
6975
).execute,
7076
r.PeriodicReport.read.when(member).edit,
7177
r.StepProgress.read,

src/components/authorization/policies/by-role/project-manager.policy.ts

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { takeWhile } from 'lodash';
22
import { ProjectStep } from '../../../project/dto';
3+
import { ProjectWorkflow } from '../../../project/workflow/project-workflow';
34
import {
45
action,
56
field,
@@ -18,6 +19,39 @@ const stepsUntilFinancialEndorsement = takeWhile(
1819
(s) => s !== ProjectStep.PendingFinancialEndorsement,
1920
);
2021

22+
export const projectTransitions = ProjectWorkflow.pickNames([
23+
'Early Conversations -> Pending Regional Director Approval',
24+
'Early Conversations -> Pending Concept Approval',
25+
'Early Conversations -> Did Not Develop',
26+
'Prep for Consultant Endorsement -> Pending Consultant Endorsement',
27+
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Pending Concept Approval',
28+
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Did Not Develop',
29+
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
30+
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
31+
'Prep for Financial Endorsement -> Pending Financial Endorsement',
32+
'Prep for Financial Endorsement & Finalizing Proposal -> Pending Consultant Endorsement',
33+
'Finalizing Proposal -> Pending Regional Director Approval',
34+
'Finalizing Proposal -> Pending Financial Endorsement',
35+
'Active -> Discussing Change To Plan',
36+
'Active -> Discussing Termination',
37+
'Active -> Finalizing Completion',
38+
'Discussing Change To Plan -> Pending Change To Plan Approval',
39+
'Discussing Change To Plan -> Discussing Suspension',
40+
'Discussing Change To Plan -> Back To Active',
41+
'Pending Change To Plan Approval -> Discussing Change To Plan',
42+
'Pending Change To Plan Approval -> Pending Change To Plan Confirmation',
43+
'Pending Change To Plan Approval -> Back To Active',
44+
'Discussing Suspension -> Pending Suspension Approval',
45+
'Discussing Suspension -> Back To Active',
46+
'Suspended -> Discussing Reactivation',
47+
'Suspended & Discussing Reactivation -> Discussing Termination',
48+
'Discussing Reactivation -> Pending Reactivation Approval',
49+
'Discussing Termination -> Pending Termination Approval',
50+
'Discussing Termination -> Back To Most Recent',
51+
'Finalizing Completion -> Back To Active',
52+
'Finalizing Completion -> Completed',
53+
]);
54+
2155
// NOTE: There could be other permissions for this role from other policies
2256
@Policy(
2357
[Role.ProjectManager, Role.RegionalDirector, Role.FieldOperationsDirector],
@@ -93,37 +127,9 @@ const stepsUntilFinancialEndorsement = takeWhile(
93127
'Review Reject',
94128
'Review Approve',
95129
).execute,
96-
r.ProjectWorkflowEvent.read.transitions(
97-
'Early Conversations -> Pending Regional Director Approval',
98-
'Early Conversations -> Pending Concept Approval',
99-
'Early Conversations -> Did Not Develop',
100-
'Prep for Consultant Endorsement -> Pending Consultant Endorsement',
101-
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Pending Concept Approval',
102-
'Prep for Consultant & Financial Endorsement & Finalizing Proposal -> Did Not Develop',
103-
'Pending Consultant Endorsement -> Prep for Financial Endorsement With Consultant Endorsement',
104-
'Pending Consultant Endorsement -> Prep for Financial Endorsement Without Consultant Endorsement',
105-
'Prep for Financial Endorsement -> Pending Financial Endorsement',
106-
'Prep for Financial Endorsement & Finalizing Proposal -> Pending Consultant Endorsement',
107-
'Finalizing Proposal -> Pending Regional Director Approval',
108-
'Finalizing Proposal -> Pending Financial Endorsement',
109-
'Active -> Discussing Change To Plan',
110-
'Active -> Discussing Termination',
111-
'Active -> Finalizing Completion',
112-
'Discussing Change To Plan -> Pending Change To Plan Approval',
113-
'Discussing Change To Plan -> Discussing Suspension',
114-
'Discussing Change To Plan -> Back To Active',
115-
'Pending Change To Plan Approval -> Discussing Change To Plan',
116-
'Pending Change To Plan Approval -> Pending Change To Plan Confirmation',
117-
'Pending Change To Plan Approval -> Back To Active',
118-
'Discussing Suspension -> Pending Suspension Approval',
119-
'Discussing Suspension -> Back To Active',
120-
'Suspended -> Discussing Reactivation',
121-
'Suspended & Discussing Reactivation -> Discussing Termination',
122-
'Discussing Reactivation -> Pending Reactivation Approval',
123-
'Discussing Termination -> Pending Termination Approval',
124-
'Discussing Termination -> Back To Most Recent',
125-
'Finalizing Completion -> Back To Active',
126-
'Finalizing Completion -> Completed',
130+
r.ProjectWorkflowEvent.read.whenAll(
131+
member,
132+
r.ProjectWorkflowEvent.isTransitions(...projectTransitions),
127133
).execute,
128134
r.Project.read.create
129135
.when(member)

src/components/authorization/policies/by-role/regional-director.policy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { member, Policy, Role, sensMediumOrLower } from '../util';
2+
import * as PM from './project-manager.policy';
23

34
// NOTE: There could be other permissions for this role from other policies
45
@Policy([Role.RegionalDirector, Role.FieldOperationsDirector], (r) => [
@@ -8,6 +9,7 @@ import { member, Policy, Role, sensMediumOrLower } from '../util';
89
r.Project.when(member).edit.specifically(
910
(p) => p.rootDirectory.edit.when(sensMediumOrLower).read,
1011
),
12+
r.ProjectWorkflowEvent.transitions(...PM.projectTransitions).execute,
1113
r.ProjectWorkflowEvent.read.transitions(
1214
'Early Conversations -> Pending Finance Confirmation',
1315
'Pending Concept Approval -> Prep for Consultant Endorsement',

src/components/authorization/policies/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
export * from './by-role/administrator.policy';
2-
export * from './by-role/consultant.policy';
2+
export { ConsultantPolicy } from './by-role/consultant.policy';
33
export * from './by-role/consultant-manager.policy';
44
export * from './by-role/controller.policy';
55
export * from './by-role/experience-operations.policy';
66
export * from './by-role/field-operations-director.policy';
77
export * from './by-role/field-partner.policy';
8-
export * from './by-role/financial-analyst.policy';
8+
export { FinancialAnalystPolicy } from './by-role/financial-analyst.policy';
99
export * from './by-role/financial-analyst-lead.policy';
1010
export * from './by-role/fundraising.policy';
1111
export * from './by-role/intern.policy';
1212
export * from './by-role/investor-common.policy';
1313
export * from './by-role/leadership.policy';
1414
export * from './by-role/marketing.policy';
1515
export * from './by-role/mentor.policy';
16-
export * from './by-role/project-manager.policy';
16+
export { ProjectManagerPolicy } from './by-role/project-manager.policy';
1717
export * from './by-role/regional-director.policy';
1818
export * from './by-role/staff-member.policy';
1919
export * from './by-role/translator.policy';

src/components/workflow/define-workflow.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
EnumType,
55
ID,
66
MadeEnum,
7+
Many,
78
maybeMany,
89
NotFoundException,
910
ResourceShape,
@@ -79,6 +80,9 @@ export const defineWorkflow =
7980
}
8081
return transition;
8182
},
83+
pickNames: <Names extends TransitionNames>(
84+
...transitions: Array<Many<Names>>
85+
) => setOf(transitions.flat() as Names[]),
8286
// type-only props
8387
event: undefined as any,
8488
state: undefined as any,
@@ -129,4 +133,7 @@ export interface Workflow<
129133
/** type only */
130134
readonly resolvedTransition: Omit<Transition, 'to'> & { to: State };
131135
readonly transitionByKey: (key: ID) => Transition;
136+
readonly pickNames: <Names extends TransitionNames>(
137+
...keys: Array<Many<Names>>
138+
) => ReadonlySet<Names>;
132139
}

0 commit comments

Comments
 (0)