Skip to content

Commit 4e9750b

Browse files
authored
Merge pull request #4206 from Northeastern-Electric-Racing/feature/descoping-cr
Descoping CR Feature
2 parents 60fa716 + 77c8092 commit 4e9750b

73 files changed

Lines changed: 1606 additions & 4703 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/backend/src/controllers/change-requests.controllers.ts

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,13 @@ export default class ChangeRequestsController {
8383

8484
static async reviewChangeRequest(req: Request, res: Response, next: NextFunction) {
8585
try {
86-
const { crId, reviewNotes, accepted, psId } = req.body;
86+
const { crId, reviewNotes, accepted } = req.body;
8787
const id = await ChangeRequestsService.reviewChangeRequest(
8888
req.currentUser,
8989
crId,
90-
reviewNotes,
9190
accepted,
9291
req.organization,
93-
psId
92+
reviewNotes
9493
);
9594
res.status(200).json({ message: `Change request #${id} successfully reviewed.` });
9695
} catch (error: unknown) {
@@ -100,14 +99,13 @@ export default class ChangeRequestsController {
10099

101100
static async createActivationChangeRequest(req: Request, res: Response, next: NextFunction) {
102101
try {
103-
const { wbsNum, type, leadId, managerId, startDate, confirmDetails } = req.body;
102+
const { wbsNum, leadId, managerId, startDate, confirmDetails } = req.body;
104103

105104
const id = await ChangeRequestsService.createActivationChangeRequest(
106105
req.currentUser,
107106
wbsNum.carNumber,
108107
wbsNum.projectNumber,
109108
wbsNum.workPackageNumber,
110-
type,
111109
leadId,
112110
managerId,
113111
startDate,
@@ -122,14 +120,14 @@ export default class ChangeRequestsController {
122120

123121
static async createStageGateChangeRequest(req: Request, res: Response, next: NextFunction) {
124122
try {
125-
const { wbsNum, type, confirmDone } = req.body;
123+
const { wbsNum, confirmDone, dateCompleted } = req.body;
126124
const id = await ChangeRequestsService.createStageGateChangeRequest(
127125
req.currentUser,
128126
wbsNum.carNumber,
129127
wbsNum.projectNumber,
130128
wbsNum.workPackageNumber,
131-
type,
132129
confirmDone,
130+
new Date(dateCompleted),
133131
req.organization
134132
);
135133
res.status(200).json({ message: `Successfully created stage gate request with id #${id}` });
@@ -140,10 +138,9 @@ export default class ChangeRequestsController {
140138

141139
static async createBudgetChangeRequest(req: Request, res: Response, next: NextFunction) {
142140
try {
143-
const { otherReasonId, accountCodeId, type, proposedBudget } = req.body;
141+
const { otherReasonId, accountCodeId, proposedBudget } = req.body;
144142
const cr = await ChangeRequestsService.createBudgetChangeRequest(
145143
req.currentUser,
146-
type,
147144
proposedBudget,
148145
req.organization,
149146
otherReasonId,
@@ -176,7 +173,7 @@ export default class ChangeRequestsController {
176173

177174
static async createStandardChangeRequest(req: Request, res: Response, next: NextFunction) {
178175
try {
179-
const { wbsNum, type, what, why, proposedSolutions, projectProposedChanges, workPackageProposedChanges } = req.body;
176+
const { wbsNum, why, requestedReviewerId, projectProposedChanges, workPackageProposedChanges } = req.body;
180177
if (workPackageProposedChanges && workPackageProposedChanges.stage === 'NONE') {
181178
workPackageProposedChanges.stage = null;
182179
}
@@ -186,11 +183,9 @@ export default class ChangeRequestsController {
186183
wbsNum.carNumber,
187184
wbsNum.projectNumber,
188185
wbsNum.workPackageNumber,
189-
type,
190-
what,
191186
why,
192-
proposedSolutions,
193187
req.organization,
188+
requestedReviewerId,
194189
projectProposedChanges,
195190
workPackageProposedChanges
196191
);
@@ -200,24 +195,6 @@ export default class ChangeRequestsController {
200195
}
201196
}
202197

203-
static async addProposedSolution(req: Request, res: Response, next: NextFunction) {
204-
try {
205-
const { crId, budgetImpact, description, timelineImpact, scopeImpact } = req.body;
206-
const id = await ChangeRequestsService.addProposedSolution(
207-
req.currentUser,
208-
crId,
209-
budgetImpact,
210-
description,
211-
timelineImpact,
212-
scopeImpact,
213-
req.organization
214-
);
215-
res.status(200).json({ message: `Successfully added proposed solution with id #${id}` });
216-
} catch (error: unknown) {
217-
next(error);
218-
}
219-
}
220-
221198
static async deleteChangeRequest(req: Request, res: Response, next: NextFunction) {
222199
try {
223200
const { crId } = req.params as Record<string, string>;

src/backend/src/controllers/slack.controllers.ts

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { getWorkspaceId, replyToMessageInThread } from '../integrations/slack.js';
22
import OrganizationsService from '../services/organizations.services.js';
3-
import SlackServices, { SlackBlockActionBody, SaboSubmissionActionValue } from '../services/slack.services.js';
3+
import SlackServices, {
4+
SlackBlockActionBody,
5+
SaboSubmissionActionValue,
6+
CrApprovalActionValue
7+
} from '../services/slack.services.js';
8+
import { tryParseJson } from '../utils/slack.utils.js';
49

510
export default class SlackController {
611
static async processMessageEvent(event: any) {
@@ -30,33 +35,75 @@ export default class SlackController {
3035
const [firstAction] = actions;
3136

3237
try {
33-
// Action-specific validation: verify action_id
34-
if (firstAction.action_id !== 'sabo_submitted_confirmation') {
35-
console.error('Unexpected action_id:', firstAction.action_id);
38+
// Action-specific validation: verify value format
39+
const parsed = tryParseJson<SaboSubmissionActionValue>(firstAction.value);
40+
if (!parsed.ok) {
41+
await replyToMessageInThread(
42+
channelId,
43+
threadTs,
44+
`❌ An error occurred: Invalid action data format.\n\n*Error:* ${parsed.error}\n*Value:* \`${firstAction.value}\`\n\nPlease contact the software team.`
45+
);
46+
return;
47+
}
48+
const actionValue = parsed.data;
49+
50+
// Validate that reimbursementRequestId exists in the parsed value
51+
if (!actionValue.reimbursementRequestId || typeof actionValue.reimbursementRequestId !== 'string') {
52+
const actionValueStr = JSON.stringify(actionValue, null, 2);
3653
await replyToMessageInThread(
3754
channelId,
3855
threadTs,
39-
`❌ An error occurred: Unexpected action type "${firstAction.action_id}". Please contact the software team.`
56+
`❌ An error occurred: Missing or invalid reimbursement request ID.\n\n*Parsed value:*\n\`\`\`${actionValueStr}\`\`\`\n\nPlease contact the software team.`
4057
);
4158
return;
4259
}
4360

61+
// Extract validated fields
62+
const userSlackId = user.id;
63+
const { reimbursementRequestId } = actionValue;
64+
65+
// Pass the extracted fields to the service layer for business logic
66+
await SlackServices.handleSaboSubmittedAction(userSlackId, reimbursementRequestId);
67+
} catch (error: unknown) {
68+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
69+
await replyToMessageInThread(
70+
channelId,
71+
threadTs,
72+
`❌ An unexpected error occurred while processing your request.\n\n*Error message:* ${errorMessage}\n\nPlease contact the software team and provide them with this information.`
73+
);
74+
throw error;
75+
}
76+
}
77+
78+
static async handleApproveCRAction(
79+
body: SlackBlockActionBody,
80+
respond: (msg: {
81+
response_type?: 'ephemeral';
82+
text?: string;
83+
replace_original?: boolean;
84+
delete_original?: boolean;
85+
}) => Promise<unknown>
86+
) {
87+
const { user, container, actions } = body;
88+
const channelId = container.channel_id;
89+
const threadTs = container.thread_ts || container.message_ts;
90+
const [firstAction] = actions;
91+
92+
try {
4493
// Action-specific validation: verify value format
45-
let actionValue: SaboSubmissionActionValue;
46-
try {
47-
actionValue = JSON.parse(firstAction.value);
48-
} catch (parseError) {
49-
const parseErrorMsg = parseError instanceof Error ? parseError.message : 'Unknown parse error';
94+
const parsed = tryParseJson<CrApprovalActionValue>(firstAction.value);
95+
if (!parsed.ok) {
5096
await replyToMessageInThread(
5197
channelId,
5298
threadTs,
53-
`❌ An error occurred: Invalid action data format.\n\n*Error:* ${parseErrorMsg}\n*Value:* \`${firstAction.value}\`\n\nPlease contact the software team.`
99+
`❌ An error occurred: Invalid action data format.\n\n*Error:* ${parsed.error}\n*Value:* \`${firstAction.value}\`\n\nPlease contact the software team.`
54100
);
55101
return;
56102
}
103+
const actionValue = parsed.data;
57104

58-
// Validate that reimbursementRequestId exists in the parsed value
59-
if (!actionValue.reimbursementRequestId || typeof actionValue.reimbursementRequestId !== 'string') {
105+
// Validate that changeRequestId exists in the parsed value
106+
if (!actionValue.crId || typeof actionValue.crId !== 'string') {
60107
const actionValueStr = JSON.stringify(actionValue, null, 2);
61108
await replyToMessageInThread(
62109
channelId,
@@ -68,10 +115,10 @@ export default class SlackController {
68115

69116
// Extract validated fields
70117
const userSlackId = user.id;
71-
const { reimbursementRequestId } = actionValue;
118+
const { crId } = actionValue;
72119

73120
// Pass the extracted fields to the service layer for business logic
74-
await SlackServices.handleSaboSubmittedAction(userSlackId, reimbursementRequestId);
121+
await SlackServices.handleApproveCRAction(userSlackId, crId, respond);
75122
} catch (error: unknown) {
76123
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
77124
await replyToMessageInThread(

0 commit comments

Comments
 (0)