@@ -87,6 +87,29 @@ export const validateReimbursementProducts = async (
8787 if ( ! wbsElement ) throw new NotFoundException ( 'WBS Element' , wbsPipe ( wbsNum ) ) ;
8888 if ( wbsElement . dateDeleted ) throw new DeletedException ( 'WBS Element' , wbsPipe ( wbsNum ) ) ;
8989
90+ // Validate material if materialId is provided
91+ if ( product . materialId ) {
92+ const material = await prisma . material . findUnique ( {
93+ where : { materialId : product . materialId }
94+ } ) ;
95+
96+ if ( ! material ) {
97+ throw new NotFoundException ( 'Material' , product . materialId ) ;
98+ }
99+
100+ if ( material . dateDeleted ) {
101+ throw new DeletedException ( 'Material' , product . materialId ) ;
102+ }
103+
104+ if ( material . wbsElementId !== wbsElement . wbsElementId ) {
105+ throw new HttpException ( 400 , `Material does not belong to project ${ wbsPipe ( wbsNum ) } ` ) ;
106+ }
107+
108+ if ( ! product . id && material . reimbursementRequestId ) {
109+ throw new HttpException ( 400 , `Material is already linked to another reimbursement request` ) ;
110+ }
111+ }
112+
90113 return {
91114 ...product ,
92115 wbsElementId : wbsElement . wbsElementId ,
@@ -147,32 +170,105 @@ export const updateReimbursementProducts = async (
147170 ( product ) => ! updatedExistingProductIds . includes ( product . reimbursementProductId )
148171 ) ;
149172
173+ // Unlink any materials from deleted products
174+ await unlinkMaterialsFromDeletedProducts ( deletedProducts ) ;
175+
150176 await updateDeletedProducts ( deletedProducts ) ;
151177
152178 await createNewProducts ( newOtherProducts , newWbsProducts , reimbursementRequestId , organizationId ) ;
153179
154- await updateExistingProducts ( updatedExistingProducts ) ;
180+ await updateExistingProducts ( updatedExistingProducts , currentReimbursementProducts ) ;
155181} ;
156182
157183/**
158- * updates the existing products in the database
159- *
160- * @param products the products to update
184+ * Unlinks materials from products that are being deleted
185+ * @param products the products being deleted
161186 */
162- const updateExistingProducts = async ( products : ReimbursementProductCreateArgs [ ] ) => {
163- //updates the cost and name of the remaining products, which should be products that existed before that were not deleted
164- // Does not update wbs element id because we are requiring the user on the frontend to delete it from the wbs number and then adding it to another one
165- for ( const product of products ) {
166- await prisma . reimbursement_Product . update ( {
167- where : { reimbursementProductId : product . id } ,
187+ const unlinkMaterialsFromDeletedProducts = async ( products : Reimbursement_Product [ ] ) => {
188+ const materialIds = products . filter ( ( p ) => p . materialId ) . map ( ( p ) => p . materialId ! ) ;
189+
190+ if ( materialIds . length > 0 ) {
191+ await prisma . material . updateMany ( {
192+ where : {
193+ materialId : { in : materialIds }
194+ } ,
168195 data : {
169- name : product . name ,
170- cost : product . cost
196+ reimbursementRequestId : null ,
197+ status : 'NOT_READY_TO_ORDER'
171198 }
172199 } ) ;
173200 }
174201} ;
175202
203+ /**
204+ * Updates the existing products in the database
205+ * Now handles both material-based and string-based products
206+ * @param products the products to update
207+ * @param currentProducts the current products in the database
208+ */
209+ const updateExistingProducts = async (
210+ products : ReimbursementProductCreateArgs [ ] ,
211+ currentProducts : Reimbursement_Product [ ]
212+ ) => {
213+ for ( const product of products ) {
214+ const currentProduct = currentProducts . find ( ( p ) => p . reimbursementProductId === product . id ) ;
215+
216+ // For material-based products
217+ if ( product . materialId ) {
218+ await prisma . reimbursement_Product . update ( {
219+ where : { reimbursementProductId : product . id } ,
220+ data : {
221+ name : null ,
222+ cost : product . cost ,
223+ materialId : product . materialId
224+ }
225+ } ) ;
226+
227+ // If the material changed, unlink old material and link new one
228+ if ( currentProduct ?. materialId && currentProduct . materialId !== product . materialId ) {
229+ await prisma . material . update ( {
230+ where : { materialId : currentProduct . materialId } ,
231+ data : {
232+ reimbursementRequestId : null ,
233+ status : 'NOT_READY_TO_ORDER'
234+ }
235+ } ) ;
236+ }
237+
238+ // Link new material
239+ await prisma . material . update ( {
240+ where : { materialId : product . materialId } ,
241+ data : {
242+ status : 'READY_TO_ORDER' ,
243+ reimbursementRequestId : currentProduct ?. reimbursementRequestId
244+ }
245+ } ) ;
246+ }
247+ // For string-based products
248+ else if ( product . name ) {
249+ await prisma . reimbursement_Product . update ( {
250+ where : { reimbursementProductId : product . id } ,
251+ data : {
252+ name : product . name ,
253+ cost : product . cost ,
254+ materialId : null
255+ }
256+ } ) ;
257+
258+ // If this product previously had a material linked, unlink it
259+ if ( currentProduct ?. materialId ) {
260+ await prisma . material . update ( {
261+ where : { materialId : currentProduct . materialId } ,
262+ data : {
263+ reimbursementRequestId : null ,
264+ status : 'NOT_READY_TO_ORDER'
265+ }
266+ } ) ;
267+ }
268+ }
269+ }
270+ } ;
271+
176272/**
177273 * validates that the products that should be updated in the database exist
178274 * @param currentReimbursementProducts The products that do exist in the database
@@ -286,17 +382,29 @@ export const createReimbursementProducts = async (
286382 amount : rs . amount
287383 } ) ) ;
288384
289- return await prisma . reimbursement_Product . create ( {
385+ const reimbursementProduct = await prisma . reimbursement_Product . create ( {
290386 data : {
291- name : product . name ,
387+ name : product . name ?? null ,
292388 cost : product . cost ,
293389 reimbursementRequestId,
390+ materialId : product . materialId ?? null ,
294391 refundSources : {
295392 create : refundSources
296393 } ,
297394 reimbursementProductReasonId : reimbursementProductReason . reimbursementProductReasonId
298395 }
299396 } ) ;
397+
398+ if ( product . materialId ) {
399+ await prisma . material . update ( {
400+ where : { materialId : product . materialId } ,
401+ data : {
402+ status : 'READY_TO_ORDER' ,
403+ reimbursementRequestId
404+ }
405+ } ) ;
406+ }
407+ return reimbursementProduct ;
300408 } ) ;
301409
302410 await Promise . all ( [ ...otherReimbursementProductPromises , ...wbsReimbursementProductPromises ] ) ;
@@ -443,3 +551,21 @@ export const validateRefund = async (user: User, refundAmount: number, organizat
443551 throw new HttpException ( 400 , 'Reimbursement is greater than the total amount owed' ) ;
444552 }
445553} ;
554+
555+ /**
556+ * Updates material statuses to ORDERED when payment details are added to an RR
557+ * Should be called when reimbursement status changes to PENDING_FINANCE
558+ * Only updates materials currently in READY_TO_ORDER status
559+ * @param reimbursementRequestId the id of the reimbursement request
560+ */
561+ export const updateMaterialStatusesOnPayment = async ( reimbursementRequestId : string ) : Promise < void > => {
562+ await prisma . material . updateMany ( {
563+ where : {
564+ reimbursementRequestId,
565+ dateDeleted : null
566+ } ,
567+ data : {
568+ status : 'ORDERED'
569+ }
570+ } ) ;
571+ } ;
0 commit comments