Skip to content

Commit 50ea47a

Browse files
committed
#3887 resolved merge conflicts
2 parents 27aa039 + b346137 commit 50ea47a

32 files changed

Lines changed: 1673 additions & 705 deletions

src/backend/src/controllers/projects.controllers.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -216,17 +216,17 @@ export default class ProjectsController {
216216
name,
217217
status,
218218
materialTypeName,
219+
linkUrl,
220+
wbsNum,
221+
req.organization,
219222
manufacturerName,
220223
manufacturerPartNumber,
221224
quantity,
222225
price,
223226
subtotal,
224-
linkUrl,
225-
wbsNum,
226-
req.organization,
227227
notes,
228228
assemblyId,
229-
pdmFileName === '' ? undefined : pdmFileName,
229+
pdmFileName,
230230
unitName,
231231
reimbursementRequestId
232232
);
@@ -236,6 +236,22 @@ export default class ProjectsController {
236236
}
237237
}
238238

239+
static async copyMaterialsToProject(req: Request, res: Response, next: NextFunction) {
240+
try {
241+
const { materialIds, destinationWbsNum } = req.body;
242+
243+
const newMaterialIds = await BillOfMaterialsService.copyMaterialsToProject(
244+
req.currentUser,
245+
materialIds,
246+
destinationWbsNum,
247+
req.organization
248+
);
249+
res.status(200).json(newMaterialIds);
250+
} catch (error: unknown) {
251+
next(error);
252+
}
253+
}
254+
239255
static async createManufacturer(req: Request, res: Response, next: NextFunction) {
240256
try {
241257
const { name } = req.body;
@@ -379,17 +395,17 @@ export default class ProjectsController {
379395
name,
380396
status,
381397
materialTypeName,
398+
linkUrl,
399+
req.organization,
382400
manufacturerName,
383401
manufacturerPartNumber,
384402
quantity,
385403
price,
386404
subtotal,
387-
linkUrl,
388-
req.organization,
389405
notes,
390406
unitName,
391407
assemblyId,
392-
pdmFileName === '' ? undefined : pdmFileName,
408+
pdmFileName,
393409
reimbursementRequestId
394410
);
395411
res.status(200).json(updatedMaterial);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const getMaterialQueryArgs = (organizationId: string) =>
2525
materialType: true,
2626
unit: true,
2727
manufacturer: true,
28+
reimbursementProducts: false,
2829
reimbursementRequest: getReimbursementRequestQueryArgs(organizationId)
2930
}
3031
});
@@ -37,6 +38,7 @@ export const getMaterialPreviewQueryArgs = (organizationId: string) =>
3738
unit: true,
3839
manufacturer: true,
3940
materialType: true,
41+
reimbursementProducts: false,
4042
reimbursementRequest: getReimbursementRequestQueryArgs(organizationId)
4143
}
4244
});

src/backend/src/prisma-query-args/reimbursement-products.query-args.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const getReimbursementProductQueryArgs = (organizationId: string) =>
2525
Prisma.validator<Prisma.Reimbursement_ProductDefaultArgs>()({
2626
include: {
2727
refundSources: getRefundSourceQueryArgs(organizationId),
28-
reimbursementProductReason: getReimbursementProductReasonQueryArgs(organizationId)
28+
reimbursementProductReason: getReimbursementProductReasonQueryArgs(organizationId),
29+
material: true
2930
}
3031
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
-- DropForeignKey
2+
ALTER TABLE "Material" DROP CONSTRAINT "Material_manufacturerId_fkey";
3+
4+
-- AlterTable
5+
ALTER TABLE "Material" ALTER COLUMN "manufacturerId" DROP NOT NULL,
6+
ALTER COLUMN "manufacturerPartNumber" DROP NOT NULL,
7+
ALTER COLUMN "quantity" DROP NOT NULL,
8+
ALTER COLUMN "price" DROP NOT NULL,
9+
ALTER COLUMN "subtotal" DROP NOT NULL;
10+
11+
-- AlterTable
12+
ALTER TABLE "Reimbursement_Product" ALTER COLUMN "name" DROP NOT NULL;
13+
14+
-- AlterTable
15+
ALTER TABLE "Reimbursement_Product" ADD COLUMN "materialId" TEXT;
16+
17+
-- CreateIndex
18+
CREATE INDEX "Reimbursement_Product_materialId_idx" ON "Reimbursement_Product"("materialId");
19+
20+
-- AddForeignKey
21+
ALTER TABLE "Reimbursement_Product" ADD CONSTRAINT "Reimbursement_Product_materialId_fkey" FOREIGN KEY ("materialId") REFERENCES "Material"("materialId") ON DELETE SET NULL ON UPDATE CASCADE;
22+
23+
-- AddForeignKey
24+
ALTER TABLE "Material" ADD CONSTRAINT "Material_manufacturerId_fkey" FOREIGN KEY ("manufacturerId") REFERENCES "Manufacturer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
25+
26+
DO $$
27+
DECLARE
28+
material_record RECORD;
29+
new_reason_id TEXT;
30+
BEGIN
31+
-- Loop through all materials that are linked to RRs but don't have products yet
32+
FOR material_record IN
33+
SELECT
34+
m."materialId",
35+
m."name",
36+
m."subtotal",
37+
m."wbsElementId",
38+
m."reimbursementRequestId"
39+
FROM "Material" m
40+
WHERE m."reimbursementRequestId" IS NOT NULL
41+
AND m."dateDeleted" IS NULL
42+
AND EXISTS (
43+
-- Only migrate if the RR isn't deleted
44+
SELECT 1 FROM "Reimbursement_Request" rr
45+
WHERE rr."reimbursementRequestId" = m."reimbursementRequestId"
46+
AND rr."dateDeleted" IS NULL
47+
)
48+
AND EXISTS (
49+
-- Only migrate if the WBS element isn't deleted
50+
SELECT 1 FROM "WBS_Element" wbs
51+
WHERE wbs."wbsElementId" = m."wbsElementId"
52+
AND wbs."dateDeleted" IS NULL
53+
)
54+
AND NOT EXISTS (
55+
-- Skip if a product already links to this material for this RR
56+
SELECT 1 FROM "Reimbursement_Product" rp
57+
WHERE rp."materialId" = m."materialId"
58+
AND rp."reimbursementRequestId" = m."reimbursementRequestId"
59+
)
60+
LOOP
61+
-- Create a new reason for this material (can't reuse due to @unique and one to one relation)
62+
INSERT INTO "Reimbursement_Product_Reason" (
63+
"reimbursementProductReasonId",
64+
"wbsElementId"
65+
) VALUES (
66+
gen_random_uuid(),
67+
material_record."wbsElementId"
68+
)
69+
RETURNING "reimbursementProductReasonId" INTO new_reason_id;
70+
71+
-- Create the product linked to the material
72+
INSERT INTO "Reimbursement_Product" (
73+
"reimbursementProductId",
74+
"name",
75+
"cost",
76+
"materialId",
77+
"reimbursementProductReasonId",
78+
"reimbursementRequestId"
79+
) VALUES (
80+
gen_random_uuid(),
81+
material_record."name",
82+
COALESCE(material_record."subtotal", 0),
83+
material_record."materialId",
84+
new_reason_id,
85+
material_record."reimbursementRequestId"
86+
);
87+
END LOOP;
88+
END $$;

src/backend/src/prisma/schema.prisma

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -745,9 +745,11 @@ model Reimbursement_Product_Reason {
745745

746746
model Reimbursement_Product {
747747
reimbursementProductId String @id @default(uuid())
748-
name String
748+
name String?
749749
dateDeleted DateTime?
750750
cost Int
751+
material Material? @relation(fields: [materialId], references: [materialId])
752+
materialId String?
751753
reimbursementProductReasonId String @unique
752754
reimbursementProductReason Reimbursement_Product_Reason @relation(fields: [reimbursementProductReasonId], references: [reimbursementProductReasonId])
753755
reimbursementRequestId String
@@ -756,6 +758,7 @@ model Reimbursement_Product {
756758
757759
@@index([reimbursementRequestId])
758760
@@index([reimbursementProductReasonId])
761+
@@index([materialId])
759762
}
760763

761764
model Refund_Source {
@@ -907,34 +910,35 @@ model Assembly {
907910
}
908911

909912
model Material {
910-
materialId String @id @default(uuid())
911-
assembly Assembly? @relation(fields: [assemblyId], references: [assemblyId])
913+
materialId String @id @default(uuid())
914+
assembly Assembly? @relation(fields: [assemblyId], references: [assemblyId])
912915
assemblyId String?
913916
name String
914-
wbsElement WBS_Element @relation(fields: [wbsElementId], references: [wbsElementId])
917+
wbsElement WBS_Element @relation(fields: [wbsElementId], references: [wbsElementId])
915918
wbsElementId String
916919
dateDeleted DateTime?
917-
userDeleted User? @relation(fields: [userDeletedId], references: [userId], name: "materialDeleter")
920+
userDeleted User? @relation(fields: [userDeletedId], references: [userId], name: "materialDeleter")
918921
userDeletedId String?
919922
dateCreated DateTime
920-
userCreated User @relation(fields: [userCreatedId], references: [userId], name: "materialCreator")
923+
userCreated User @relation(fields: [userCreatedId], references: [userId], name: "materialCreator")
921924
userCreatedId String
922925
status Material_Status
923-
materialType Material_Type @relation(fields: [materialTypeId], references: [id])
926+
materialType Material_Type @relation(fields: [materialTypeId], references: [id])
924927
materialTypeId String
925-
manufacturer Manufacturer @relation(fields: [manufacturerId], references: [id])
926-
manufacturerId String
927-
manufacturerPartNumber String
928+
manufacturer Manufacturer? @relation(fields: [manufacturerId], references: [id])
929+
manufacturerId String?
930+
manufacturerPartNumber String?
928931
pdmFileName String?
929-
quantity Decimal
930-
unit Unit? @relation(fields: [unitId], references: [id])
932+
quantity Decimal?
933+
unit Unit? @relation(fields: [unitId], references: [id])
931934
unitId String?
932-
price Int
933-
subtotal Int
935+
price Int?
936+
subtotal Int?
934937
linkUrl String
935938
notes String?
936-
reimbursementRequest Reimbursement_Request? @relation(fields: [reimbursementRequestId], references: [reimbursementRequestId])
939+
reimbursementRequest Reimbursement_Request? @relation(fields: [reimbursementRequestId], references: [reimbursementRequestId])
937940
reimbursementRequestId String?
941+
reimbursementProducts Reimbursement_Product[]
938942
939943
@@index([assemblyId])
940944
@@index([materialTypeId])

src/backend/src/prisma/seed.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2280,58 +2280,66 @@ const performSeed: () => Promise<void> = async () => {
22802280
'10k Resistor',
22812281
MaterialStatus.Ordered,
22822282
'Resistor',
2283-
'Digikey',
2284-
'abcdef',
2285-
new Decimal(20),
2286-
30,
2287-
600,
22882283
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
22892284
{
22902285
carNumber: 0,
22912286
projectNumber: 1,
22922287
workPackageNumber: 0
22932288
},
22942289
ner,
2295-
'Here are some notes'
2290+
'Digikey',
2291+
'abcdef',
2292+
new Decimal(20),
2293+
30,
2294+
600,
2295+
'Here are some notes',
2296+
assembly1.assemblyId,
2297+
undefined,
2298+
undefined,
2299+
undefined
22962300
);
22972301

22982302
await BillOfMaterialsService.createMaterial(
22992303
thomasEmrax,
23002304
'20k Resistor',
23012305
MaterialStatus.Ordered,
23022306
'Resistor',
2303-
'Digikey',
2304-
'bacfed',
2305-
new Decimal(10),
2306-
7,
2307-
70,
23082307
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
23092308
{
23102309
carNumber: 0,
23112310
projectNumber: 1,
23122311
workPackageNumber: 0
23132312
},
23142313
ner,
2315-
'Here are some more notes'
2314+
'Digikey',
2315+
'bacfed',
2316+
new Decimal(10),
2317+
7,
2318+
70,
2319+
'Here are some more notes',
2320+
undefined,
2321+
undefined,
2322+
undefined,
2323+
undefined
23162324
);
23172325

23182326
await BillOfMaterialsService.createMaterial(
23192327
thomasEmrax,
23202328
'100k Resistor',
23212329
MaterialStatus.ReadyToOrder,
23222330
'Resistor',
2323-
'Digikey',
2324-
'lalsd',
2325-
new Decimal(5),
2326-
10,
2327-
50,
23282331
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
23292332
{
23302333
carNumber: 0,
23312334
projectNumber: 1,
23322335
workPackageNumber: 0
23332336
},
23342337
ner,
2338+
'Digikey',
2339+
'lalsd',
2340+
new Decimal(5),
2341+
10,
2342+
50,
23352343
undefined,
23362344
undefined,
23372345
undefined,

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
validateInputs,
88
materialValidators
99
} from '../utils/validation.utils.js';
10+
import { validateWBS } from 'shared';
1011
import ProjectsController from '../controllers/projects.controllers.js';
1112

1213
const projectRouter = express.Router();
@@ -93,6 +94,16 @@ projectRouter.post(
9394
);
9495
projectRouter.post('/bom/material/:wbsNum/create', ...materialValidators, validateInputs, ProjectsController.createMaterial);
9596
projectRouter.post('/bom/material/:materialId/edit', ...materialValidators, validateInputs, ProjectsController.editMaterial);
97+
projectRouter.post(
98+
'/bom/material/copy',
99+
body('materialIds').isArray({ min: 1 }),
100+
nonEmptyString(body('materialIds.*')),
101+
body('destinationWbsNum').customSanitizer((value) => {
102+
return validateWBS(value);
103+
}),
104+
validateInputs,
105+
ProjectsController.copyMaterialsToProject
106+
);
96107

97108
projectRouter.post(
98109
'/bom/assembly/:assemblyId/edit',

src/backend/src/routes/reimbursement-requests.routes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
isOptionalDate,
1212
nonEmptyString,
1313
validateInputs,
14-
validateReimbursementProducts
14+
validateReimbursementProducts,
15+
validateReimbursementProductsForEdit
1516
} from '../utils/validation.utils.js';
1617
import ReimbursementRequestController from '../controllers/reimbursement-requests.controllers.js';
1718
import multer, { memoryStorage } from 'multer';
@@ -139,7 +140,7 @@ reimbursementRequestsRouter.post(
139140
nonEmptyString(body('receiptPictures.*.googleFileId')),
140141
nonEmptyString(body('accountCodeId')),
141142
intMinZero(body('totalCost')),
142-
validateReimbursementProducts(),
143+
validateReimbursementProductsForEdit(),
143144
validateInputs,
144145
ReimbursementRequestController.editReimbursementRequest
145146
);

0 commit comments

Comments
 (0)