Skip to content

Commit 23d6dfb

Browse files
authored
Merge pull request #1313 from Northeastern-Electric-Racing/#1290-add-allowed-refund-sources-to-expense-types
#1290 - add allowed refund sources to expense type
2 parents c0462e3 + b12709d commit 23d6dfb

18 files changed

Lines changed: 177 additions & 84 deletions

File tree

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,15 @@ export default class ReimbursementRequestsController {
162162

163163
static async createExpenseType(req: Request, res: Response, next: NextFunction) {
164164
try {
165-
const { name, code, allowed } = req.body;
165+
const { name, code, allowed, allowedRefundSources } = req.body;
166166
const user = await getCurrentUser(res);
167-
const createdExpenseType = await ReimbursementRequestService.createExpenseType(user, name, code, allowed);
167+
const createdExpenseType = await ReimbursementRequestService.createExpenseType(
168+
user,
169+
name,
170+
code,
171+
allowed,
172+
allowedRefundSources
173+
);
168174
res.status(200).json(createdExpenseType);
169175
} catch (error: unknown) {
170176
next(error);
@@ -269,14 +275,15 @@ export default class ReimbursementRequestsController {
269275
static async editExpenseTypeCode(req: Request, res: Response, next: NextFunction) {
270276
try {
271277
const { expenseTypeId } = req.params;
272-
const { name, code, allowed } = req.body;
278+
const { name, code, allowed, allowedRefundSources } = req.body;
273279
const submitter = await getCurrentUser(res);
274280
const expenseTypeUpdated = await ReimbursementRequestService.editExpenseType(
275281
expenseTypeId,
276282
code,
277283
name,
278284
allowed,
279-
submitter
285+
submitter,
286+
allowedRefundSources
280287
);
281288
res.status(200).json(expenseTypeUpdated);
282289
} catch (error: unknown) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "Expense_Type" ADD COLUMN "allowedRefundSources" "Club_Accounts"[];

src/backend/src/prisma/schema.prisma

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,12 @@ model Vendor {
429429
}
430430

431431
model Expense_Type {
432-
expenseTypeId String @id @default(uuid())
433-
name String
434-
code Int
435-
allowed Boolean
436-
requests Reimbursement_Request[]
432+
expenseTypeId String @id @default(uuid())
433+
name String
434+
code Int
435+
allowed Boolean
436+
allowedRefundSources Club_Accounts[]
437+
requests Reimbursement_Request[]
437438
}
438439

439440
model Reimbursement {

src/backend/src/prisma/seed.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import {
99
CR_Type,
10+
Club_Accounts,
1011
PrismaClient,
1112
Scope_CR_Why_Type,
1213
Task_Priority,
@@ -750,7 +751,10 @@ const performSeed: () => Promise<void> = async () => {
750751

751752
const vendors: Vendor[] = [vendor, vendor2, vendor3];
752753

753-
const expenseType = await ReimbursementRequestService.createExpenseType(thomasEmrax, 'Equipment', 123, true);
754+
const expenseType = await ReimbursementRequestService.createExpenseType(thomasEmrax, 'Equipment', 123, true, [
755+
Club_Accounts.CASH,
756+
Club_Accounts.BUDGET
757+
]);
754758

755759
await ReimbursementRequestService.createReimbursementRequest(
756760
thomasEmrax,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ reimbursementRequestsRouter.post(
8686
reimbursementRequestsRouter.post(
8787
'/vendors/create',
8888
nonEmptyString(body('name')),
89+
body('allowedRefundSources').isArray(),
90+
isAccount(body('allowedRefundSources.*')),
8991
validateInputs,
9092
ReimbursementRequestController.createVendor
9193
);
@@ -95,6 +97,8 @@ reimbursementRequestsRouter.post(
9597
nonEmptyString(body('name')),
9698
intMinZero(body('code')),
9799
body('allowed').isBoolean(),
100+
body('allowedRefundSources').isArray(),
101+
isAccount(body('allowedRefundSources.*')),
98102
validateInputs,
99103
ReimbursementRequestController.createExpenseType
100104
);

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// eslint-disable-next-line @typescript-eslint/no-unused-vars
77
import type { Multer } from 'multer';
8-
import { Reimbursement_Request, Reimbursement_Status_Type, User } from '@prisma/client';
8+
import { Club_Accounts, Reimbursement_Request, Reimbursement_Status_Type, User } from '@prisma/client';
99
import {
1010
ClubAccount,
1111
ExpenseType,
@@ -131,6 +131,10 @@ export default class ReimbursementRequestService {
131131

132132
if (!expenseType.allowed) throw new HttpException(400, `The expense type ${expenseType.name} is not allowed!`);
133133

134+
if (!expenseType.allowedRefundSources.includes(account)) {
135+
throw new HttpException(400, 'The submitted refund source is not allowed to be used with the submitted expense type');
136+
}
137+
134138
const validatedReimbursementProudcts = await validateReimbursementProducts(reimbursementProducts);
135139

136140
const createdReimbursementRequest = await prisma.reimbursement_Request.create({
@@ -268,6 +272,9 @@ export default class ReimbursementRequestService {
268272

269273
if (!expenseType) throw new NotFoundException('Expense Type', expenseTypeId);
270274
if (!expenseType.allowed) throw new HttpException(400, 'Expense Type Not Allowed');
275+
if (!expenseType.allowedRefundSources.includes(account)) {
276+
throw new HttpException(400, 'The submitted refund source is not allowed to be used with the submitted expense type');
277+
}
271278

272279
await updateReimbursementProducts(
273280
oldReimbursementRequest.reimbursementProducts,
@@ -463,15 +470,23 @@ export default class ReimbursementRequestService {
463470
* @param name The name of the expense type
464471
* @param code the expense type's SABO code
465472
* @param allowed whether or not this expense type is allowed
473+
* @param allowedRefundSources an array of Club_Accounts representing allowed refund sources
466474
* @returns the created expense type
467475
*/
468-
static async createExpenseType(submitter: User, name: string, code: number, allowed: boolean) {
476+
static async createExpenseType(
477+
submitter: User,
478+
name: string,
479+
code: number,
480+
allowed: boolean,
481+
allowedRefundSources: Club_Accounts[]
482+
) {
469483
if (!isAdmin(submitter.role)) throw new AccessDeniedAdminOnlyException('create expense types');
470484
const expense = await prisma.expense_Type.create({
471485
data: {
472486
name,
473487
allowed,
474-
code
488+
code,
489+
allowedRefundSources
475490
}
476491
});
477492

@@ -487,7 +502,14 @@ export default class ReimbursementRequestService {
487502
* @param submitter the person editing expense type code number
488503
* @returns the updated expense type
489504
*/
490-
static async editExpenseType(expenseTypeId: string, code: number, name: string, allowed: boolean, submitter: User) {
505+
static async editExpenseType(
506+
expenseTypeId: string,
507+
code: number,
508+
name: string,
509+
allowed: boolean,
510+
submitter: User,
511+
allowedRefundSources: Club_Accounts[]
512+
) {
491513
if (!isHead(submitter.role))
492514
throw new AccessDeniedException('Only the head or admin can update account code number and name');
493515

@@ -502,7 +524,8 @@ export default class ReimbursementRequestService {
502524
data: {
503525
name,
504526
code,
505-
allowed
527+
allowed,
528+
allowedRefundSources
506529
}
507530
});
508531

src/backend/src/transformers/reimbursement-requests.transformer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export const expenseTypeTransformer = (expenseType: Prisma.Expense_TypeGetPayloa
8383
expenseTypeId: expenseType.expenseTypeId,
8484
name: expenseType.name,
8585
code: expenseType.code,
86-
allowed: expenseType.allowed
86+
allowed: expenseType.allowed,
87+
allowedRefundSources: expenseType.allowedRefundSources as ClubAccount[]
8788
};
8889
};
8990

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,20 @@ describe('Reimbursement Requests', () => {
6767
describe('Expense Tests', () => {
6868
test('Create Expense Type fails for non admins', async () => {
6969
await expect(
70-
ReimbursementRequestService.createExpenseType(wonderwoman, Parts.name, Parts.code, Parts.allowed)
70+
ReimbursementRequestService.createExpenseType(wonderwoman, Parts.name, Parts.code, Parts.allowed, [ClubAccount.CASH])
7171
).rejects.toThrow(new AccessDeniedAdminOnlyException('create expense types'));
7272
});
7373

7474
test('Create Expense Type Successfully returns expense type Id', async () => {
7575
vi.spyOn(prisma.expense_Type, 'create').mockResolvedValue(Parts);
7676

77-
const expenseType = await ReimbursementRequestService.createExpenseType(batman, Parts.name, Parts.code, Parts.allowed);
77+
const expenseType = await ReimbursementRequestService.createExpenseType(
78+
batman,
79+
Parts.name,
80+
Parts.code,
81+
Parts.allowed,
82+
[ClubAccount.BUDGET]
83+
);
7884

7985
expect(expenseType.expenseTypeId).toBe(Parts.expenseTypeId);
8086
});

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import reimbursementRequestQueryArgs from '../../src/prisma-query-args/reimbursement-requests.query-args';
1313
import { alfred, batman } from './users.test-data';
1414
import { prismaWbsElement1 } from './wbs-element.test-data';
15-
import { ClubAccount, ReimbursementRequest } from 'shared';
15+
import { ClubAccount, ExpenseType, ReimbursementRequest } from 'shared';
1616
import { wbsNumOf } from '../../src/utils/utils';
1717
import userTransformer from '../../src/transformers/user.transformer';
1818

@@ -26,7 +26,8 @@ export const Parts: PrismaExpenseType = {
2626
expenseTypeId: 'PARTS',
2727
name: 'hammer',
2828
code: 12245,
29-
allowed: true
29+
allowed: true,
30+
allowedRefundSources: [Club_Accounts.CASH, Club_Accounts.BUDGET]
3031
};
3132

3233
export const GiveMeMyMoney: PrismaReimbursementRequest = {
@@ -146,7 +147,7 @@ export const sharedGiveMeMyMoney: ReimbursementRequest = {
146147
dateOfExpense: GiveMeMyMoney.dateOfExpense,
147148
totalCost: GiveMeMyMoney.totalCost,
148149
receiptPictures: [],
149-
expenseType: Parts,
150+
expenseType: Parts as ExpenseType,
150151
vendor: PopEyes,
151152
recipient: userTransformer(batman),
152153
saboId: undefined,

src/frontend/src/hooks/finance.hooks.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ import {
4040

4141
export interface CreateReimbursementRequestPayload {
4242
vendorId: string;
43-
account: ClubAccount;
4443
dateOfExpense: Date;
4544
expenseTypeId: string;
4645
reimbursementProducts: ReimbursementProductCreateArgs[];
4746
totalCost: number;
47+
account: ClubAccount;
4848
}
4949

5050
export interface EditReimbursementRequestPayload extends CreateReimbursementRequestPayload {
@@ -55,6 +55,7 @@ export interface ExpenseTypePayload {
5555
code: number;
5656
name: string;
5757
allowed: boolean;
58+
allowedRefundSources: ClubAccount[];
5859
}
5960

6061
/**

0 commit comments

Comments
 (0)