Skip to content

Commit e503651

Browse files
authored
Merge pull request #1714 from Northeastern-Electric-Racing/#1427-BOM-Form-Modal
#1713 Bill of Materials Frontend
2 parents e37b4d7 + 00fd214 commit e503651

52 files changed

Lines changed: 2377 additions & 125 deletions

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/projects.controllers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,25 @@ export default class ProjectsController {
348348
next(error);
349349
}
350350
}
351+
352+
static async getAllUnits(_req: Request, res: Response, next: NextFunction) {
353+
try {
354+
const user = await getCurrentUser(res);
355+
const units = await ProjectsService.getAllUnits(user);
356+
res.status(200).json(units);
357+
} catch (error: unknown) {
358+
next(error);
359+
}
360+
}
361+
362+
static async createUnit(req: Request, res: Response, next: NextFunction) {
363+
try {
364+
const { name } = req.body;
365+
const user = await getCurrentUser(res);
366+
const createdUnit = await ProjectsService.createUnit(name, user);
367+
res.status(200).json(createdUnit);
368+
} catch (error: unknown) {
369+
next(error);
370+
}
371+
}
351372
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export const assemblyQueryArgs = {
2+
include: {
3+
userCreated: true,
4+
userDeleted: true,
5+
materials: true
6+
}
7+
};
8+
9+
export const materialQueryArgs = {
10+
include: {
11+
assembly: {
12+
...assemblyQueryArgs
13+
},
14+
wbsElement: true,
15+
userCreated: true,
16+
userDeleted: true,
17+
materialType: true,
18+
quantityUnit: true,
19+
manufacturer: true
20+
}
21+
};

src/backend/src/prisma-query-args/manufacturers.query-args.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { Prisma } from '@prisma/client';
77

88
const manufacturerQueryArgs = Prisma.validator<Prisma.ManufacturerArgs>()({
99
include: {
10-
materials: true
10+
materials: true,
11+
userCreated: true
1112
}
1213
});
1314

src/backend/src/prisma-query-args/material-type.query-args.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { Prisma } from '@prisma/client';
77

88
const materialTypeQueryArgs = Prisma.validator<Prisma.Material_TypeArgs>()({
99
include: {
10-
materials: true
10+
materials: true,
11+
userCreated: true
1112
}
1213
});
1314

src/backend/src/prisma-query-args/projects.query-args.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Prisma } from '@prisma/client';
22
import taskQueryArgs from './tasks.query-args';
33
import linkQueryArgs from './links.query-args';
4+
import { assemblyQueryArgs, materialQueryArgs } from './bom.query-args';
45

56
const projectQueryArgs = Prisma.validator<Prisma.ProjectArgs>()({
67
include: {
@@ -10,7 +11,15 @@ const projectQueryArgs = Prisma.validator<Prisma.ProjectArgs>()({
1011
projectManager: true,
1112
tasks: { where: { dateDeleted: null }, ...taskQueryArgs },
1213
links: { ...linkQueryArgs },
13-
changes: { where: { changeRequest: { dateDeleted: null } }, include: { implementer: true } }
14+
changes: { where: { changeRequest: { dateDeleted: null } }, include: { implementer: true } },
15+
materials: {
16+
where: { dateDeleted: null },
17+
...materialQueryArgs
18+
},
19+
assemblies: {
20+
where: { dateDeleted: null },
21+
...assemblyQueryArgs
22+
}
1423
}
1524
},
1625
teams: { include: { members: true, head: true, leads: true } },
@@ -29,7 +38,13 @@ const projectQueryArgs = Prisma.validator<Prisma.ProjectArgs>()({
2938
projectLead: true,
3039
projectManager: true,
3140
links: { ...linkQueryArgs },
32-
changes: { where: { changeRequest: { dateDeleted: null } }, include: { implementer: true } }
41+
changes: { where: { changeRequest: { dateDeleted: null } }, include: { implementer: true } },
42+
materials: {
43+
...materialQueryArgs
44+
},
45+
assemblies: {
46+
...assemblyQueryArgs
47+
}
3348
}
3449
},
3550
blockedBy: { where: { dateDeleted: null } },

src/backend/src/prisma/migrations/20231119081759_add_bom/migration.sql renamed to src/backend/src/prisma/migrations/20231122182628_add_boms/migration.sql

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ CREATE TABLE "Material_Type" (
5252
"name" TEXT NOT NULL,
5353
"dateCreated" TIMESTAMP(3) NOT NULL,
5454
"dateDeleted" TIMESTAMP(3),
55-
"creatorId" INTEGER NOT NULL,
55+
"userCreatedId" INTEGER NOT NULL,
5656

5757
CONSTRAINT "Material_Type_pkey" PRIMARY KEY ("name")
5858
);
@@ -61,15 +61,12 @@ CREATE TABLE "Material_Type" (
6161
CREATE TABLE "Manufacturer" (
6262
"name" TEXT NOT NULL,
6363
"dateCreated" TIMESTAMP(3) NOT NULL,
64-
"creatorId" INTEGER NOT NULL,
64+
"userCreatedId" INTEGER NOT NULL,
6565
"dateDeleted" TIMESTAMP(3),
6666

6767
CONSTRAINT "Manufacturer_pkey" PRIMARY KEY ("name")
6868
);
6969

70-
-- CreateIndex
71-
CREATE UNIQUE INDEX "Assembly_name_key" ON "Assembly"("name");
72-
7370
-- AddForeignKey
7471
ALTER TABLE "Assembly" ADD CONSTRAINT "Assembly_userDeletedId_fkey" FOREIGN KEY ("userDeletedId") REFERENCES "User"("userId") ON DELETE SET NULL ON UPDATE CASCADE;
7572

@@ -99,3 +96,9 @@ ALTER TABLE "Material" ADD CONSTRAINT "Material_manufacturerName_fkey" FOREIGN K
9996

10097
-- AddForeignKey
10198
ALTER TABLE "Material" ADD CONSTRAINT "Material_unitName_fkey" FOREIGN KEY ("unitName") REFERENCES "Unit"("name") ON DELETE SET NULL ON UPDATE CASCADE;
99+
100+
-- AddForeignKey
101+
ALTER TABLE "Material_Type" ADD CONSTRAINT "Material_Type_userCreatedId_fkey" FOREIGN KEY ("userCreatedId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
102+
103+
-- AddForeignKey
104+
ALTER TABLE "Manufacturer" ADD CONSTRAINT "Manufacturer_userCreatedId_fkey" FOREIGN KEY ("userCreatedId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;

src/backend/src/prisma/schema.prisma

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ model User {
114114
createdAssemblies Assembly[] @relation(name: "assemblyCreator")
115115
deletedMaterials Material[] @relation(name: "materialDeleter")
116116
createdMaterials Material[] @relation(name: "materialCreator")
117+
createdMaterialTypes Material_Type[] @relation(name: "materialTypeCreator")
118+
createdManufacturers Manufacturer[] @relation(name: "manufacturerCreator")
117119
}
118120

119121
model Team {
@@ -490,7 +492,7 @@ model Unit {
490492

491493
model Assembly {
492494
assemblyId String @id @default(uuid())
493-
name String @unique
495+
name String
494496
pdmFileName String?
495497
dateDeleted DateTime?
496498
userDeleted User? @relation(fields: [userDeletedId], references: [userId], name: "assemblyDeleter")
@@ -533,17 +535,19 @@ model Material {
533535
}
534536

535537
model Material_Type {
536-
name String @id
537-
dateCreated DateTime
538+
name String @id
539+
dateCreated DateTime
538540
dateDeleted DateTime?
539-
creatorId Int
540-
materials Material[]
541+
userCreatedId Int
542+
userCreated User @relation(name: "materialTypeCreator", fields: [userCreatedId], references: [userId])
543+
materials Material[]
541544
}
542545

543546
model Manufacturer {
544-
name String @id
545-
dateCreated DateTime
546-
creatorId Int
547+
name String @id
548+
dateCreated DateTime
549+
userCreatedId Int
550+
userCreated User @relation(name: "manufacturerCreator", fields: [userCreatedId], references: [userId])
547551
dateDeleted DateTime?
548-
materials Material[]
552+
materials Material[]
549553
}

src/backend/src/prisma/seed.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import ChangeRequestsService from '../services/change-requests.services';
2222
import projectQueryArgs from '../prisma-query-args/projects.query-args';
2323
import TeamsService from '../services/teams.services';
2424
import WorkPackagesService from '../services/work-packages.services';
25-
import { ClubAccount, StandardChangeRequest, validateWBS, WbsElementStatus, WorkPackageStage } from 'shared';
25+
import { ClubAccount, MaterialStatus, StandardChangeRequest, validateWBS, WbsElementStatus, WorkPackageStage } from 'shared';
2626
import TasksService from '../services/tasks.services';
2727
import DescriptionBulletsService from '../services/description-bullets.services';
2828
import { seedProject } from './seed-data/projects.seed';
@@ -881,6 +881,51 @@ const performSeed: () => Promise<void> = async () => {
881881
*/
882882
await ProjectsService.createManufacturer(thomasEmrax, 'Digikey');
883883
await ProjectsService.createMaterialType('Resistor', thomasEmrax);
884+
885+
const assembly1 = await ProjectsService.createAssembly('1', thomasEmrax, {
886+
carNumber: 1,
887+
projectNumber: 1,
888+
workPackageNumber: 0
889+
});
890+
891+
await ProjectsService.createMaterial(
892+
thomasEmrax,
893+
'10k Resistor',
894+
MaterialStatus.Ordered,
895+
'Resistor',
896+
'Digikey',
897+
'abcdef',
898+
20,
899+
30,
900+
600,
901+
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
902+
'Here are some notes',
903+
{
904+
carNumber: 1,
905+
projectNumber: 1,
906+
workPackageNumber: 0
907+
}
908+
);
909+
910+
await ProjectsService.createMaterial(
911+
thomasEmrax,
912+
'20k Resistor',
913+
MaterialStatus.Ordered,
914+
'Resistor',
915+
'Digikey',
916+
'bacfed',
917+
10,
918+
7,
919+
70,
920+
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
921+
'Here are some more notes',
922+
{
923+
carNumber: 1,
924+
projectNumber: 1,
925+
workPackageNumber: 0
926+
},
927+
assembly1.assemblyId
928+
);
884929
};
885930

886931
performSeed()

src/backend/src/routes/projects.routes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ projectRouter.post(
7878
ProjectsController.assignMaterialAssembly
7979
);
8080
projectRouter.post(
81-
'/material/:wbsNum/create',
81+
'/bom/material/:wbsNum/create',
8282
nonEmptyString(body('name')),
8383
nonEmptyString(body('assemblyId').optional()),
8484
isMaterialStatus(body('status')),
@@ -119,4 +119,7 @@ projectRouter.delete('/bom/material-type/:materialTypeId/delete', ProjectsContro
119119
projectRouter.delete('/bom/assembly/:assemblyId/delete', ProjectsController.deleteAssemblyType);
120120
projectRouter.post('/bom/material/:materialId/delete', ProjectsController.deleteMaterial);
121121

122+
projectRouter.post('/bom/units/create', nonEmptyString(body('name')), ProjectsController.createUnit);
123+
projectRouter.get('/bom/units', ProjectsController.getAllUnits);
124+
122125
export default projectRouter;

src/backend/src/services/projects.services.ts

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Manufacturer,
1111
MaterialType,
1212
Project,
13+
Unit,
1314
WbsNumber,
1415
wbsPipe
1516
} from 'shared';
@@ -30,12 +31,13 @@ import { wbsNumOf } from '../utils/utils';
3031
import WorkPackagesService from './work-packages.services';
3132
import linkQueryArgs from '../prisma-query-args/links.query-args';
3233
import linkTypeQueryArgs from '../prisma-query-args/link-types.query-args';
33-
import manufacturerQueryArgs from '../prisma-query-args/manufacturers.query-args';
3434
import materialTypeQueryArgs from '../prisma-query-args/material-type.query-args';
3535
import { linkTypeTransformer } from '../transformers/links.transformer';
36-
import { manufacturerTransformer } from '../transformers/manufacturer.transformer';
3736
import { isUserPartOfTeams } from '../utils/teams.utils';
3837
import { materialTypeTransformer } from '../transformers/material-type.transformer';
38+
import { materialPreviewTransformer } from '../transformers/material.transformer';
39+
import manufacturerQueryArgs from '../prisma-query-args/manufacturers.query-args';
40+
import manufacturerTransformer from '../transformers/manufacturer.transformer';
3941

4042
export default class ProjectsService {
4143
/**
@@ -585,7 +587,7 @@ export default class ProjectsService {
585587
* Create an assembly
586588
* @param name The name of the assembly to be created
587589
* @param userCreated The user creating the assembly
588-
* @param wbsElementId The
590+
* @param wbsElementId The wbsElement that the created assembly is associated with
589591
* @param pdmFileName optional - The name of the file holding the assembly
590592
* @returns the project that the user has favorited/unfavorited
591593
* @throws if the project wbs doesn't exist or is not corresponding to a project
@@ -613,9 +615,9 @@ export default class ProjectsService {
613615
if (!project) throw new NotFoundException('Project', wbsPipe(wbsNumber));
614616
if (project.wbsElement.dateDeleted) throw new DeletedException('Project', project.projectId);
615617

616-
const checkAssembly = await prisma.assembly.findUnique({ where: { name } });
617-
618-
if (checkAssembly) throw new HttpException(400, `${name} already exists as an assembly!`);
618+
console.log(project.wbsElement.assemblies);
619+
if (project.wbsElement.assemblies.some((assembly) => assembly.name === name && !assembly.dateDeleted))
620+
throw new HttpException(400, `${name} already exists as an assembly on this project!`);
619621

620622
const { teams, wbsElementId } = project;
621623

@@ -656,7 +658,7 @@ export default class ProjectsService {
656658
if (manufacturer) throw new HttpException(400, `${name} already exists as a manufacturer!`);
657659

658660
const newManufacturer = await prisma.manufacturer.create({
659-
data: { name, dateCreated: new Date(), creatorId: submitter.userId }
661+
data: { name, dateCreated: new Date(), userCreatedId: submitter.userId }
660662
});
661663

662664
return newManufacturer;
@@ -754,7 +756,7 @@ export default class ProjectsService {
754756
data: {
755757
name,
756758
dateCreated: new Date(),
757-
creatorId: submitter.userId
759+
userCreatedId: submitter.userId
758760
}
759761
});
760762

@@ -1028,4 +1030,46 @@ export default class ProjectsService {
10281030

10291031
return updatedMaterial;
10301032
}
1033+
1034+
/**
1035+
* Gets all the units in the database with all their materials
1036+
* @returns all the units in the database
1037+
*/
1038+
static async getAllUnits(user: User): Promise<Unit[]> {
1039+
if (isGuest(user.role)) throw new AccessDeniedGuestException('get units');
1040+
1041+
const units = await prisma.unit.findMany({
1042+
include: {
1043+
materials: true
1044+
}
1045+
});
1046+
1047+
return units.map((unit) => {
1048+
return { ...unit, materials: unit.materials.map(materialPreviewTransformer) };
1049+
});
1050+
}
1051+
1052+
/**
1053+
* Creates a new unit
1054+
* @param submitter the user who's creating the unit
1055+
* @param name the name of the unit
1056+
* @throws if the submitter is a guest or the given unit name already exists
1057+
*/
1058+
static async createUnit(name: string, submitter: User): Promise<Unit> {
1059+
if (isGuest(submitter.role)) throw new AccessDeniedGuestException('create units');
1060+
1061+
const unit = await prisma.unit.findUnique({
1062+
where: {
1063+
name
1064+
}
1065+
});
1066+
1067+
if (unit) throw new HttpException(400, `${name} already exists as a unit!`);
1068+
1069+
const newUnit = await prisma.unit.create({
1070+
data: { name }
1071+
});
1072+
1073+
return { ...newUnit, materials: [] };
1074+
}
10311075
}

0 commit comments

Comments
 (0)