Skip to content

Commit ef3281e

Browse files
authored
Merge pull request #3964 from Northeastern-Electric-Racing/rr-bug-again
fixed reimbursement request bugs
2 parents 346499c + 6e39841 commit ef3281e

4 files changed

Lines changed: 186 additions & 13 deletions

File tree

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,21 @@ export const updateReimbursementProducts = async (
125125
}
126126

127127
//if a product has an id that means it existed before and was updated
128-
const updatedOtherExistingProducts = updatedOtherReimbursementProducts.filter((product) => product.id);
129-
130-
const updatedWbsExistingProducts = updatedWbsReimbursementProducts.filter((product) => product.id);
128+
const updatedOtherExistingProducts = updatedOtherReimbursementProducts.filter(
129+
(product): product is OtherReimbursementProductCreateArgs & { id: string } => !!product.id
130+
);
131131

132-
const updatedExistingProducts = (updatedOtherExistingProducts as ReimbursementProductCreateArgs[]).concat(
133-
updatedWbsExistingProducts as ReimbursementProductCreateArgs[]
132+
const updatedWbsExistingProducts = updatedWbsReimbursementProducts.filter(
133+
(product): product is WbsReimbursementProductCreateArgs & { id: string } => !!product.id
134134
);
135135

136+
const updatedExistingProducts = (
137+
updatedOtherExistingProducts as (ReimbursementProductCreateArgs & { id: string })[]
138+
).concat(updatedWbsExistingProducts as (ReimbursementProductCreateArgs & { id: string })[]);
139+
136140
validateUpdatedProductsExistInDatabase(currentReimbursementProducts, updatedExistingProducts);
137141

138-
const updatedExistingProductIds = updatedExistingProducts.map((product) => product.id!);
142+
const updatedExistingProductIds = updatedExistingProducts.map((product) => product.id);
139143

140144
//if the product does not have an id that means it is new
141145
const newOtherProducts = updatedOtherReimbursementProducts.filter((product) => !product.id);
@@ -159,15 +163,25 @@ export const updateReimbursementProducts = async (
159163
*
160164
* @param products the products to update
161165
*/
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
166+
const updateExistingProducts = async (products: (ReimbursementProductCreateArgs & { id: string })[]) => {
167+
//updates the cost, name, and refund sources of the remaining products, which should be products that existed before that were not deleted
164168
// 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
165169
for (const product of products) {
170+
const refundSources = product.refundSources.map((rs) => ({
171+
indexCode: { connect: { indexCodeId: rs.indexCode.indexCodeId } },
172+
amount: rs.amount
173+
}));
174+
175+
// Delete old refund sources and update product atomically
166176
await prisma.reimbursement_Product.update({
167177
where: { reimbursementProductId: product.id },
168178
data: {
169179
name: product.name,
170-
cost: product.cost
180+
cost: product.cost,
181+
refundSources: {
182+
deleteMany: {},
183+
create: refundSources
184+
}
171185
}
172186
});
173187
}

src/backend/tests/unmocked/reimbursement-requests.test.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,4 +1125,153 @@ describe('Reimbursement Requests', () => {
11251125
expect(updatedVendor.taxExempt).toBe(true);
11261126
});
11271127
});
1128+
1129+
describe('Editing a reimbursement request', () => {
1130+
test('editing preserves refund sources on existing products', async () => {
1131+
// Get the original product info
1132+
const [originalProduct] = reimbursementRequest.reimbursementProducts;
1133+
1134+
// Edit the request, updating the product name but keeping the same refund source
1135+
await ReimbursementRequestService.editReimbursementRequest(
1136+
reimbursementRequest.reimbursementRequestId,
1137+
createdVendor.vendorId,
1138+
createdIndexCode.indexCodeId,
1139+
createdAccountCode.accountCodeId,
1140+
reimbursementRequest.totalCost,
1141+
[],
1142+
[
1143+
{
1144+
id: originalProduct.reimbursementProductId,
1145+
name: 'UPDATED GLUE',
1146+
reason: {
1147+
carNumber: 0,
1148+
projectNumber: 0,
1149+
workPackageNumber: 0
1150+
},
1151+
cost: 200000,
1152+
refundSources: [
1153+
{
1154+
indexCode: createdIndexCode,
1155+
amount: 200
1156+
}
1157+
]
1158+
}
1159+
],
1160+
[],
1161+
createdUser,
1162+
org,
1163+
new Date()
1164+
);
1165+
1166+
// Fetch the updated request and verify refund sources are preserved
1167+
const updatedRR = await ReimbursementRequestService.getSingleReimbursementRequest(
1168+
createdUser,
1169+
reimbursementRequest.reimbursementRequestId,
1170+
org
1171+
);
1172+
1173+
expect(updatedRR.reimbursementProducts).toHaveLength(1);
1174+
expect(updatedRR.reimbursementProducts[0].name).toEqual('UPDATED GLUE');
1175+
expect(updatedRR.reimbursementProducts[0].cost).toEqual(200000);
1176+
expect(updatedRR.reimbursementProducts[0].refundSources).toHaveLength(1);
1177+
expect(updatedRR.reimbursementProducts[0].refundSources[0].amount).toEqual(200);
1178+
expect(updatedRR.reimbursementProducts[0].refundSources[0].indexCode.indexCodeId).toEqual(
1179+
createdIndexCode.indexCodeId
1180+
);
1181+
});
1182+
1183+
test('editing updates refund source amounts on existing products', async () => {
1184+
const [originalProduct] = reimbursementRequest.reimbursementProducts;
1185+
1186+
// Edit with a different refund source amount
1187+
await ReimbursementRequestService.editReimbursementRequest(
1188+
reimbursementRequest.reimbursementRequestId,
1189+
createdVendor.vendorId,
1190+
createdIndexCode.indexCodeId,
1191+
createdAccountCode.accountCodeId,
1192+
reimbursementRequest.totalCost,
1193+
[],
1194+
[
1195+
{
1196+
id: originalProduct.reimbursementProductId,
1197+
name: 'GLUE',
1198+
reason: {
1199+
carNumber: 0,
1200+
projectNumber: 0,
1201+
workPackageNumber: 0
1202+
},
1203+
cost: 300000,
1204+
refundSources: [
1205+
{
1206+
indexCode: createdIndexCode,
1207+
amount: 300
1208+
}
1209+
]
1210+
}
1211+
],
1212+
[],
1213+
createdUser,
1214+
org,
1215+
new Date()
1216+
);
1217+
1218+
const updatedRR = await ReimbursementRequestService.getSingleReimbursementRequest(
1219+
createdUser,
1220+
reimbursementRequest.reimbursementRequestId,
1221+
org
1222+
);
1223+
1224+
expect(updatedRR.reimbursementProducts).toHaveLength(1);
1225+
expect(updatedRR.reimbursementProducts[0].cost).toEqual(300000);
1226+
expect(updatedRR.reimbursementProducts[0].refundSources).toHaveLength(1);
1227+
expect(updatedRR.reimbursementProducts[0].refundSources[0].amount).toEqual(300);
1228+
});
1229+
1230+
test('editing with new products (no id) creates them with refund sources', async () => {
1231+
// Edit the request, replacing the old product with a new one (no id)
1232+
await ReimbursementRequestService.editReimbursementRequest(
1233+
reimbursementRequest.reimbursementRequestId,
1234+
createdVendor.vendorId,
1235+
createdIndexCode.indexCodeId,
1236+
createdAccountCode.accountCodeId,
1237+
reimbursementRequest.totalCost,
1238+
[],
1239+
[
1240+
{
1241+
name: 'NEW TAPE',
1242+
reason: {
1243+
carNumber: 0,
1244+
projectNumber: 0,
1245+
workPackageNumber: 0
1246+
},
1247+
cost: 500,
1248+
refundSources: [
1249+
{
1250+
indexCode: createdIndexCode,
1251+
amount: 500
1252+
}
1253+
]
1254+
}
1255+
],
1256+
[],
1257+
createdUser,
1258+
org,
1259+
new Date()
1260+
);
1261+
1262+
const updatedRR = await ReimbursementRequestService.getSingleReimbursementRequest(
1263+
createdUser,
1264+
reimbursementRequest.reimbursementRequestId,
1265+
org
1266+
);
1267+
1268+
// Old product should be soft-deleted, new one created
1269+
const activeProducts = updatedRR.reimbursementProducts;
1270+
expect(activeProducts).toHaveLength(1);
1271+
expect(activeProducts[0].name).toEqual('NEW TAPE');
1272+
expect(activeProducts[0].cost).toEqual(500);
1273+
expect(activeProducts[0].refundSources).toHaveLength(1);
1274+
expect(activeProducts[0].refundSources[0].amount).toEqual(500);
1275+
});
1276+
});
11281277
});

src/frontend/src/pages/FinancePage/EditReimbursementRequest/EditReimbursementRequestRenderedDefaultValues.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const EditReimbursementRequestRenderedDefaultValues: React.FC<{
4343
dateOfExpense: reimbursementRequest.dateOfExpense ? new Date(reimbursementRequest.dateOfExpense) : undefined,
4444
accountCodeId: reimbursementRequest.accountCode.accountCodeId,
4545
reimbursementProducts: reimbursementRequest.reimbursementProducts.map((product) => ({
46+
id: product.reimbursementProductId,
4647
reason: (product.reimbursementProductReason as WBSElementData).wbsNum
4748
? (product.reimbursementProductReason as WBSElementData).wbsNum
4849
: (product.reimbursementProductReason as OtherProductReason),

src/frontend/src/pages/FinancePage/ReimbursementRequestForm/ReimbursementFormView.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,19 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
121121
const { mutateAsync: createVendor } = useCreateVendor();
122122
const user = useCurrentUser();
123123

124-
// to grab all the proper refund sources
125-
const refundSources: CreateRefundSourceArgs[] = Array.from(
126-
new Set(reimbursementProducts.flatMap((product) => product.refundSources).filter((source) => source.amount > 0))
127-
);
124+
// to grab all the proper refund sources, deduplicated by indexCodeId
125+
const refundSources: CreateRefundSourceArgs[] = (() => {
126+
const allSources = reimbursementProducts
127+
.flatMap((product) => product.refundSources)
128+
.filter((source) => source.amount > 0);
129+
const seen = new Set<string>();
130+
return allSources.filter((source) => {
131+
const id = source.indexCode.indexCodeId;
132+
if (seen.has(id)) return false;
133+
seen.add(id);
134+
return true;
135+
});
136+
})();
128137

129138
const [hasConfirmedFinance, setHasConfirmedFinance] = useState(refundSources.length > 1);
130139
const toast = useToast();

0 commit comments

Comments
 (0)