Skip to content

Commit 0f6048e

Browse files
committed
alert for finance delegates
1 parent 9089c03 commit 0f6048e

3 files changed

Lines changed: 86 additions & 27 deletions

File tree

src/backend/src/services/organizations.services.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -514,24 +514,26 @@ export default class OrganizationsService {
514514
*/
515515
static async setFinanceDelegates(submitter: User, organizationId: string, userIds: string[]): Promise<User[]> {
516516
if (!(await userHasPermission(submitter.userId, organizationId, isAdmin))) {
517-
throw new AccessDeniedAdminOnlyException('set finance delegates');
517+
throw new AccessDeniedAdminOnlyException('only admins can set finance delegates');
518518
}
519519

520+
const userIdsNoDuplicates = Array.from(new Set(userIds));
521+
520522
const users = await prisma.user.findMany({
521523
where: {
522-
userId: { in: userIds }
524+
userId: { in: userIdsNoDuplicates }
523525
}
524526
});
525527

526-
if (users.length !== userIds.length) {
528+
if (users.length !== userIdsNoDuplicates.length) {
527529
throw new NotFoundException('User', 'one or more user IDs');
528530
}
529531

530532
const updatedOrg = await prisma.organization.update({
531533
where: { organizationId },
532534
data: {
533535
financeDelegates: {
534-
set: userIds.map((userId) => ({ userId }))
536+
set: userIdsNoDuplicates.map((userId) => ({ userId }))
535537
}
536538
},
537539
include: {

src/frontend/src/pages/FinancePage/ReimbursementRequestDetailPage/ReimbursementRequestDetailsView.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,6 @@ const ReimbursementRequestDetailsView: React.FC<ReimbursementRequestDetailsViewP
305305
// Check if the RR has purchase details (receipts and date of expense)
306306
const hasPurchaseDetails = reimbursementRequest.receiptPictures.length > 0 && !!reimbursementRequest.dateOfExpense;
307307

308-
// Create a type that extends ButtonInfo with a show property
309308
type ButtonInfoWithShow = ButtonInfo & { show?: boolean };
310309

311310
const allButtons: ButtonInfoWithShow[] = [
@@ -421,7 +420,6 @@ const ReimbursementRequestDetailsView: React.FC<ReimbursementRequestDetailsViewP
421420
}
422421
];
423422

424-
// Filter out buttons that shouldn't be shown and remove the show property
425423
const buttons: ButtonInfo[] = allButtons.filter((button) => button.show !== false).map(({ show, ...button }) => button);
426424

427425
const sortedStatus = reimbursementRequest.reimbursementStatuses.sort((a) => a.dateCreated.getDate());

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

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,13 @@ import { useToast } from '../../../hooks/toasts.hooks';
5353
import { Link as RouterLink } from 'react-router-dom';
5454
import { routes } from '../../../utils/routes';
5555
import { wbsNumComparator } from 'shared/src/validate-wbs';
56-
import { codeAndRefundSourceName, accountCodePipe } from '../../../utils/pipes';
56+
import { codeAndRefundSourceName, accountCodePipe, fullNamePipe } from '../../../utils/pipes';
5757
import NERModal from '../../../components/NERModal';
5858
import CheckList from '../../../components/CheckList';
5959
import { useCreateVendor } from '../../../hooks/finance.hooks';
60+
import { useGetFinanceDelegates } from '../../../hooks/organizations.hooks';
61+
import LoadingIndicator from '../../../components/LoadingIndicator';
62+
import ErrorPage from '../../ErrorPage';
6063

6164
interface ReimbursementRequestFormViewProps {
6265
allVendors: Vendor[];
@@ -232,6 +235,16 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
232235

233236
wbsElementAutocompleteOptions.sort((wbsNum1, wbsNum2) => wbsNumComparator(wbsNum1.id, wbsNum2.id));
234237

238+
const { isLoading, isError, error, data: financeDelegates } = useGetFinanceDelegates();
239+
240+
if (isLoading || !financeDelegates) {
241+
return <LoadingIndicator />;
242+
}
243+
244+
if (isError) {
245+
return <ErrorPage message={error.message} />;
246+
}
247+
235248
const ReceiptFileInput = () => (
236249
<FormControl>
237250
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
@@ -293,39 +306,24 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
293306
items={[
294307
{
295308
resolved: false,
296-
detail:
297-
'I certify my receipts with expenses greater than $75 include an itemized description of goods or service purchased.',
309+
detail: `I certify that I have a Concur account${financeDelegates.length > 0 ? ', and have assigned ' + financeDelegates.map(fullNamePipe).join(', ') + ' as delegate(s) to submit this reimbursement request on my behalf.' : '.'}`,
298310
id: '1'
299311
},
300312
{
301313
resolved: false,
302-
detail: `I certify my receipts include the vendor's name (for ex. Amazon, stop and shop, Target).`,
314+
detail:
315+
'I certify my receipts with expenses greater than $75 include an itemized description of goods or service purchased.',
303316
id: '2'
304317
},
305-
{
306-
resolved: false,
307-
detail: `I certify my receipts include a Transaction Date for each expense.`,
308-
id: '3'
309-
},
310-
{
311-
resolved: false,
312-
detail: `I certify my receipts include the amount paid for each expense.`,
313-
id: '4'
314-
},
315-
{
316-
resolved: false,
317-
detail: `I certify my receipts include the form of payment for each expense (Cash, check or last four digits of credit card).`,
318-
id: '5'
319-
},
320318
{
321319
resolved: false,
322320
detail: `This reimbursement request is "NOT" for a faculty or full-time staff member.`,
323-
id: '6'
321+
id: '3'
324322
},
325323
{
326324
resolved: false,
327325
detail: `The reimbursement does not include sales tax unless it is for a prepared meal or hotel.`,
328-
id: '7'
326+
id: '4'
329327
}
330328
]}
331329
isDisabled={false}
@@ -372,6 +370,67 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
372370
</Snackbar>
373371
)}
374372

373+
<Box
374+
sx={{
375+
backgroundColor: 'rgba(211, 47, 47, 0.1)',
376+
border: '2px solid #d32f2f',
377+
borderRadius: '8px',
378+
padding: '16px',
379+
marginBottom: '20px',
380+
width: '100%'
381+
}}
382+
>
383+
<Box
384+
sx={{
385+
display: 'flex',
386+
justifyContent: 'center',
387+
alignItems: 'center',
388+
gap: '8px',
389+
marginBottom: '12px'
390+
}}
391+
>
392+
<Typography
393+
sx={{
394+
color: '#d32f2f',
395+
fontWeight: 'bold',
396+
fontSize: '16px',
397+
textAlign: 'center'
398+
}}
399+
>
400+
To submit Reimbursement Requests you{' '}
401+
<span style={{ textDecoration: 'underline' }}>
402+
<strong>must</strong>
403+
</span>{' '}
404+
assign the below users as delegates on Concur
405+
</Typography>
406+
<Tooltip
407+
title="In order for the finance team to submit reimbursements on your behalf, you must assign them as delegates in concur. To do this, go to concur (reach out to your head if you do not have concur access), click your profile -> Profile Settings -> Expense Delegates -> add all finance delegates, and ensure they have can prepare permissions."
408+
placement="right"
409+
>
410+
<HelpIcon sx={{ color: '#d32f2f', fontSize: '20px', cursor: 'pointer' }} />
411+
</Tooltip>
412+
</Box>
413+
{financeDelegates.length > 0 && (
414+
<Box sx={{ display: 'flex', justifyContent: 'center', flexWrap: 'wrap', gap: '8px' }}>
415+
{financeDelegates.map((delegate) => (
416+
<Typography
417+
key={delegate.userId}
418+
sx={{
419+
color: '#d32f2f',
420+
fontWeight: 'bold',
421+
fontSize: '14px',
422+
backgroundColor: 'rgba(211, 47, 47, 0.2)',
423+
padding: '4px 12px',
424+
borderRadius: '12px'
425+
}}
426+
>
427+
{fullNamePipe(delegate)}
428+
</Typography>
429+
))}
430+
</Box>
431+
)}
432+
</Box>
433+
375434
<Grid item container spacing={5} md={12} xs={12} sx={{ '&.MuiGrid-item': { height: 'fit-content' } }}>
376435
<Grid item xs={12} md={6}>
377436
<Grid item xs={12}>

0 commit comments

Comments
 (0)