Skip to content

Commit 1174433

Browse files
authored
Merge pull request #1567 from Northeastern-Electric-Racing/#1330-wp-create-backend-redesign
#1330: redesigned the create wp endpoint
2 parents 850fb1e + de18489 commit 1174433

7 files changed

Lines changed: 136 additions & 70 deletions

File tree

src/backend/src/controllers/work-packages.controllers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default class WorkPackagesController {
3232
// Create a work package with the given details
3333
static async createWorkPackage(req: Request, res: Response, next: NextFunction) {
3434
try {
35-
const { projectWbsNum, name, crId, startDate, duration, blockedBy, expectedActivities, deliverables } = req.body;
35+
const { name, crId, startDate, duration, blockedBy, expectedActivities, deliverables } = req.body;
3636

3737
let { stage } = req.body;
3838
if (stage === 'NONE') {
@@ -43,7 +43,6 @@ export default class WorkPackagesController {
4343

4444
const wbsString: string = await WorkPackagesService.createWorkPackage(
4545
user,
46-
projectWbsNum,
4746
name,
4847
crId,
4948
stage,

src/backend/src/prisma/seed-data/work-packages.seed.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { descBulletConverter } from '../../utils/description-bullets.utils';
1818
*/
1919
export const seedWorkPackage = async (
2020
creator: User,
21-
projectWbsNumber: WbsNumber,
2221
name: string,
2322
changeRequestId: number,
2423
stage: WorkPackageStage | null,
@@ -37,7 +36,6 @@ export const seedWorkPackage = async (
3736
}> => {
3837
const workPackage1WbsString = await WorkPackagesService.createWorkPackage(
3938
creator,
40-
projectWbsNumber,
4139
name,
4240
changeRequestId,
4341
stage,

src/backend/src/prisma/seed.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,15 +316,74 @@ const performSeed: () => Promise<void> = async () => {
316316
joeBlow.userId
317317
);
318318

319+
/**
320+
* Change Requests for Creating Work Packages
321+
*/
322+
323+
const changeRequestWP1Id: number = await ChangeRequestsService.createStandardChangeRequest(
324+
cyborg,
325+
project1WbsNumber.carNumber,
326+
project1WbsNumber.projectNumber,
327+
project1WbsNumber.workPackageNumber,
328+
CR_Type.OTHER,
329+
'Initial Change Request',
330+
[
331+
{
332+
type: Scope_CR_Why_Type.INITIALIZATION,
333+
explain: 'need this to initialize work packages'
334+
}
335+
]
336+
);
337+
338+
// make a proposed solution for it
339+
const proposedSolution2Id: string = await ChangeRequestsService.addProposedSolution(
340+
cyborg,
341+
changeRequestWP1Id,
342+
0,
343+
'Initializing seed data',
344+
0,
345+
'no scope impact'
346+
);
347+
348+
// approve the change request
349+
await ChangeRequestsService.reviewChangeRequest(batman, changeRequestWP1Id, 'LGTM', true, proposedSolution2Id);
350+
351+
const changeRequestWP5Id: number = await ChangeRequestsService.createStandardChangeRequest(
352+
cyborg,
353+
project5WbsNumber.carNumber,
354+
project5WbsNumber.projectNumber,
355+
project5WbsNumber.workPackageNumber,
356+
CR_Type.OTHER,
357+
'Initial Change Request',
358+
[
359+
{
360+
type: Scope_CR_Why_Type.INITIALIZATION,
361+
explain: 'need this to initialize work packages'
362+
}
363+
]
364+
);
365+
366+
// make a proposed solution for it
367+
const proposedSolution5Id: string = await ChangeRequestsService.addProposedSolution(
368+
cyborg,
369+
changeRequestWP5Id,
370+
0,
371+
'Initializing seed data',
372+
0,
373+
'no scope impact'
374+
);
375+
376+
// approve the change request
377+
await ChangeRequestsService.reviewChangeRequest(batman, changeRequestWP5Id, 'LGTM', true, proposedSolution5Id);
378+
319379
/**
320380
* Work Packages
321381
*/
322382
/** Work Package 1 */
323383
const { workPackageWbsNumber: workPackage1WbsNumber, workPackage: workPackage1 } = await seedWorkPackage(
324384
joeShmoe,
325-
project1WbsNumber,
326385
'Bodywork Concept of Design',
327-
changeRequest1Id,
386+
changeRequestWP1Id,
328387
WorkPackageStage.Design,
329388
'01/01/2023',
330389
3,
@@ -363,9 +422,8 @@ const performSeed: () => Promise<void> = async () => {
363422
/** Work Package 2 */
364423
const { workPackageWbsNumber: workPackage2WbsNumber, workPackage: workPackage2 } = await seedWorkPackage(
365424
thomasEmrax,
366-
project1WbsNumber,
367425
'Adhesive Shear Strength Test',
368-
changeRequest1Id,
426+
changeRequestWP1Id,
369427
WorkPackageStage.Research,
370428
'01/22/2023',
371429
5,
@@ -387,9 +445,8 @@ const performSeed: () => Promise<void> = async () => {
387445
/** Work Package 3 */
388446
const workPackage3WbsString = await WorkPackagesService.createWorkPackage(
389447
thomasEmrax,
390-
project5WbsNumber,
391448
'Manufacture Wiring Harness',
392-
changeRequest1Id,
449+
changeRequestWP5Id,
393450
WorkPackageStage.Manufacturing,
394451
'02/01/2023',
395452
3,

src/backend/src/routes/work-packages.routes.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ workPackagesRouter.post(
1111
'/create',
1212
intMinZero(body('crId')),
1313
nonEmptyString(body('name')),
14-
intMinZero(body('projectWbsNum.carNumber')),
15-
intMinZero(body('projectWbsNum.projectNumber')),
16-
intMinZero(body('projectWbsNum.workPackageNumber')),
1714
isWorkPackageStageOrNone(body('stage')),
1815
isDate(body('startDate')),
1916
intMinZero(body('duration')),
17+
body('blockedBy').isArray(),
2018
intMinZero(body('blockedBy.*.carNumber')),
2119
intMinZero(body('blockedBy.*.projectNumber')),
2220
intMinZero(body('blockedBy.*.workPackageNumber')),

src/backend/src/services/work-packages.services.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ export default class WorkPackagesService {
111111
/**
112112
* Creates a Work_Package in the database
113113
* @param user the user creating the work package
114-
* @param projectWbsNum the WBS number of the attached project
115114
* @param name the name of the new work package
116115
* @param crId the id of the change request creating this work package
117116
* @param stage the stage of the work package
@@ -125,7 +124,6 @@ export default class WorkPackagesService {
125124
*/
126125
static async createWorkPackage(
127126
user: User,
128-
projectWbsNum: WbsNumber,
129127
name: string,
130128
crId: number,
131129
stage: WorkPackageStage | null,
@@ -137,30 +135,11 @@ export default class WorkPackagesService {
137135
): Promise<string> {
138136
if (isGuest(user.role)) throw new AccessDeniedGuestException('create work packages');
139137

140-
await validateChangeRequestAccepted(crId);
141-
142-
// get the corresponding project so we can find the next wbs number
143-
// and what number work package this should be
144-
const { carNumber, projectNumber, workPackageNumber } = projectWbsNum;
145-
146-
if (workPackageNumber !== 0) {
147-
throw new HttpException(
148-
400,
149-
`Given WBS Number ${carNumber}.${projectNumber}.${workPackageNumber} is not for a project.`
150-
);
151-
}
152-
153-
if (blockedBy.find((dep: WbsNumber) => equalsWbsNumber(dep, projectWbsNum))) {
154-
throw new HttpException(400, 'A Work Package cannot have its own project as a blocker');
155-
}
138+
const changeRequest = await validateChangeRequestAccepted(crId);
156139

157140
const wbsElem = await prisma.wBS_Element.findUnique({
158141
where: {
159-
wbsNumber: {
160-
carNumber,
161-
projectNumber,
162-
workPackageNumber
163-
}
142+
wbsElementId: changeRequest.wbsElementId
164143
},
165144
include: {
166145
project: {
@@ -171,10 +150,32 @@ export default class WorkPackagesService {
171150
}
172151
});
173152

174-
if (!wbsElem) throw new NotFoundException('WBS Element', `${carNumber}.${projectNumber}.${workPackageNumber}`);
153+
if (!wbsElem) throw new NotFoundException('WBS Element', changeRequest.wbsElementId);
154+
155+
// get the corresponding project so we can find the next wbs number
156+
// and what number work package this should be
157+
const { carNumber, projectNumber, workPackageNumber } = wbsElem;
158+
159+
const projectWbsNum: WbsNumber = {
160+
carNumber,
161+
projectNumber,
162+
workPackageNumber
163+
};
164+
175165
if (wbsElem.dateDeleted)
176166
throw new DeletedException('WBS Element', wbsPipe({ carNumber, projectNumber, workPackageNumber }));
177167

168+
if (workPackageNumber !== 0) {
169+
throw new HttpException(
170+
400,
171+
`Given WBS Number ${carNumber}.${projectNumber}.${workPackageNumber} is not for a project.`
172+
);
173+
}
174+
175+
if (blockedBy.find((dep: WbsNumber) => equalsWbsNumber(dep, projectWbsNum))) {
176+
throw new HttpException(400, 'A Work Package cannot have its own project as a blocker');
177+
}
178+
178179
const { project } = wbsElem;
179180

180181
if (!project) throw new NotFoundException('Project', `${carNumber}.${projectNumber}.${workPackageNumber}`);

src/backend/tests/test-data/wbs-element.test-data.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,17 @@ export const prismaWbsElement1: PrismaWbsElement = {
1313
projectLeadId: 4,
1414
projectManagerId: 5
1515
};
16+
17+
export const prismaWbsElement2: PrismaWbsElement = {
18+
wbsElementId: 1,
19+
status: PrismaWBSElementStatus.ACTIVE,
20+
carNumber: 1,
21+
projectNumber: 2,
22+
workPackageNumber: 2,
23+
dateCreated: new Date(),
24+
dateDeleted: null,
25+
name: 'car',
26+
deletedByUserId: null,
27+
projectLeadId: 4,
28+
projectManagerId: 5
29+
};

src/backend/tests/work-packages.test.ts

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import prisma from '../src/prisma/prisma';
22
import { batman, wonderwoman } from './test-data/users.test-data';
3-
import { prismaWbsElement1 } from './test-data/wbs-element.test-data';
3+
import { prismaWbsElement1, prismaWbsElement2 } from './test-data/wbs-element.test-data';
44
import { prismaChangeRequest1 } from './test-data/change-requests.test-data';
55
import { calculateWorkPackageProgress } from '../src/utils/work-packages.utils';
66
import {
@@ -41,18 +41,17 @@ describe('Work Packages', () => {
4141
const expectedActivities = ['ayo'];
4242
const deliverables = ['ajdhjakfjafja'];
4343
const stage = WorkPackageStage.Design;
44-
const createWorkPackageArgs: [
45-
User,
46-
WbsNumber,
47-
string,
48-
number,
49-
WorkPackageStage,
50-
string,
51-
number,
52-
WbsNumber[],
53-
string[],
54-
string[]
55-
] = [batman, projectWbsNum, name, crId, stage, startDate, duration, blockedBy, expectedActivities, deliverables];
44+
const createWorkPackageArgs: [User, string, number, WorkPackageStage, string, number, WbsNumber[], string[], string[]] = [
45+
batman,
46+
name,
47+
crId,
48+
stage,
49+
startDate,
50+
duration,
51+
blockedBy,
52+
expectedActivities,
53+
deliverables
54+
];
5655
/*********************************************************/
5756

5857
afterEach(() => {
@@ -73,16 +72,12 @@ describe('Work Packages', () => {
7372

7473
describe('createWorkPackage', () => {
7574
test('createWorkPackage fails if WBS number does not represent a project', async () => {
75+
vi.spyOn(prisma.wBS_Element, 'findUnique').mockResolvedValue(prismaWbsElement2);
7676
vi.spyOn(prisma.change_Request, 'findUnique').mockResolvedValue(prismaChangeRequest1);
7777

7878
const callCreateWP = async () => {
7979
return await WorkPackageService.createWorkPackage(
8080
batman,
81-
{
82-
carNumber: 1,
83-
projectNumber: 2,
84-
workPackageNumber: 2
85-
},
8681
name,
8782
crId,
8883
stage,
@@ -115,12 +110,12 @@ describe('Work Packages', () => {
115110
});
116111

117112
test('createWorkPackage fails if user does not have access', async () => {
113+
vi.spyOn(prisma.wBS_Element, 'findUnique').mockResolvedValue(prismaWbsElement2);
118114
vi.spyOn(prisma.change_Request, 'findUnique').mockResolvedValue(prismaChangeRequest1);
119115

120116
const callCreateWP = async () => {
121117
return await WorkPackageService.createWorkPackage(
122118
wonderwoman,
123-
projectWbsNum,
124119
name,
125120
crId,
126121
stage,
@@ -156,7 +151,9 @@ describe('Work Packages', () => {
156151
return await WorkPackageService.createWorkPackage.apply(null, createWorkPackageArgs);
157152
};
158153

159-
await expect(callCreateWP).rejects.toThrowError(new NotFoundException('WBS Element', '1.2.0'));
154+
await expect(callCreateWP).rejects.toThrowError(
155+
new NotFoundException('WBS Element', prismaChangeRequest1.wbsElementId.toString())
156+
);
160157
});
161158

162159
test('createWorkPackage fails if the associated wbsElem does not have a project object', async () => {
@@ -167,22 +164,24 @@ describe('Work Packages', () => {
167164
return await WorkPackageService.createWorkPackage.apply(null, createWorkPackageArgs);
168165
};
169166

170-
await expect(callCreateWP).rejects.toThrowError(new NotFoundException('WBS Element', '1.2.0'));
167+
await expect(callCreateWP).rejects.toThrowError(
168+
new NotFoundException('WBS Element', prismaChangeRequest1.wbsElementId.toString())
169+
);
171170
});
172171

173172
test("fails if the blocked by include the work package's own project", async () => {
174-
const argsToTest: [
175-
User,
176-
WbsNumber,
177-
string,
178-
number,
179-
WorkPackageStage,
180-
string,
181-
number,
182-
WbsNumber[],
183-
string[],
184-
string[]
185-
] = [batman, projectWbsNum, name, crId, stage, startDate, duration, [projectWbsNum], expectedActivities, deliverables];
173+
vi.spyOn(prisma.wBS_Element, 'findUnique').mockResolvedValueOnce(prismaWbsElement1);
174+
const argsToTest: [User, string, number, WorkPackageStage, string, number, WbsNumber[], string[], string[]] = [
175+
batman,
176+
name,
177+
crId,
178+
stage,
179+
startDate,
180+
duration,
181+
[projectWbsNum],
182+
expectedActivities,
183+
deliverables
184+
];
186185

187186
const callCreateWP = async () => {
188187
return await WorkPackageService.createWorkPackage.apply(null, argsToTest);

0 commit comments

Comments
 (0)