Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SF Automation flows and confirmation modal #178

Merged
merged 5 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion backend/emails/emails.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const currencyFormatter = new Intl.NumberFormat('en-CA', {
})

const getEmailToSection = async (reporter_id, recipients) => {
const emailToSet = new Set(['[email protected]', 'v2zheng@watonomous.ca'])
willi-li-am marked this conversation as resolved.
Show resolved Hide resolved
const emailToSet = new Set(['william.li@watonomous.ca'])

if (recipients.includes(EMAIL_RECIPIENTS.admin)) {
// TODO: use ADMIN_IDENTIFIERS (rename to ADMIN_EMAILS) after migrating to new onboarding data
Expand Down Expand Up @@ -86,6 +86,28 @@ const getUPRTicketInfoHTML = async (upr) => {
`
}

const getSFTicketInfoHTML = async (sf) => {
const reporter = await getUserByUID(sf.reporter_id)
return `
<p>
Ticket Code: ${sf.code} <br />
Sponsorship Fund: ${sf.name} <br />
Allocated Funding: ${sf.funding_allocation} <br/ >
Funding Spent: ${sf.funding_spent} <br />
${sf.proposal_url ? `Proposal URL: ${sf.proposal_url} <br />` : ``}
${
sf.presentation_url
? `Presentation URL: ${sf.presentation_url} <br />`
: ``
}
Status: ${sf.status} <br />
Reporter: ${reporter.displayName} &lt;${reporter.email}&gt; <br />
Created: ${new Date(sf.createdAt).toDateString()} <br />
Claim Deadline: ${new Date(sf.claim_deadline).toDateString()} <br />
</p>
`
}

const getTicketLinkHTML = (ticketPath) => `
<p>
View the ticket here:
Expand Down Expand Up @@ -255,6 +277,60 @@ const sendEmailPPRReimbursedToReporter = async (ppr) => {
})
}

const sendEmailSFReimbursementRequestToCoordinator = async (sf) => {
const Subject = `[Action Needed] Submit Reimbursement Request ${sf.codename}`
const HTMLPart =
getMainMessageHTML(
`Claim has been submitted for ${sf.codename}! Please review it and submit a reimbursement request. Visit the ticket link below to confirm you have submitted the reimbursement request.`
) +
(await getSFTicketInfoHTML(sf)) +
getTicketLinkHTML(sf.path)
const To = await getEmailToSection(sf.reporter_id, [
EMAIL_RECIPIENTS.coordinator,
])
await sendEmail({
Subject,
HTMLPart,
To,
})
}

const sendEmailSFConfirmReimbursementSubmitToCoordinator = async (sf) => {
const Subject = `[Action Needed] Confirm Reimbursement Received ${sf.codename}`
const HTMLPart =
getMainMessageHTML(
`Please visit the ticket link below to confirm you have received the reimbursement for ${sf.codename}.`
) +
(await getSFTicketInfoHTML(sf)) +
getTicketLinkHTML(sf.path)
const To = await getEmailToSection(sf.reporter_id, [
EMAIL_RECIPIENTS.coordinator,
])
await sendEmail({
Subject,
HTMLPart,
To,
})
}

const sendEmailSFReimbursementReceivedToTeam = async (sf) => {
const Subject = `[Reimbursed] ${sf.codename}`
const HTMLPart =
getMainMessageHTML(`${sf.codename} has been reimbursed.`) +
(await getSFTicketInfoHTML(sf)) +
getTicketLinkHTML(sf.path)
const To = await getEmailToSection(sf.reporter_id, [
EMAIL_RECIPIENTS.finance,
EMAIL_RECIPIENTS.coordinator,
EMAIL_RECIPIENTS.team_captain,
])
await sendEmail({
Subject,
HTMLPart,
To,
})
}

const PurchaseRequestInvalidated = (purchaseRequestDetails) => {
const { issue, reporter } = purchaseRequestDetails

Expand Down Expand Up @@ -584,6 +660,9 @@ module.exports = {
sendEmailPPRCreatedToApprovers,
sendEmailPPRPurchasedAndReceiptsSubmittedToCoordinator,
sendEmailPPRReimbursedToReporter,
sendEmailSFReimbursementRequestToCoordinator,
sendEmailSFConfirmReimbursementSubmitToCoordinator,
sendEmailSFReimbursementReceivedToTeam,
PurchaseRequestInvalidated,
PersonalPurchaseApproved,
UWFinancePurchaseApproved,
Expand Down
2 changes: 1 addition & 1 deletion backend/models/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const TICKET_ENDPOINTS = Object.freeze({
UPR: '/uwfinancepurchases',
})

const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he']
const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he', 'william.li']
const TEAM_CAPTAIN_TITLES = ['Team Captain']
const DIRECTOR_TITLES = ['Director']

Expand Down
36 changes: 32 additions & 4 deletions backend/service/sponsorshipfunds.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ const {
getAnnotatedUWFinancePurchasesByIdList,
getAnnotatedSponsorshipFundsByIdList,
} = require('./annotatedGetters')
const {
sendEmailSFReimbursementRequestToCoordinator,
sendEmailSFConfirmReimbursementSubmitToCoordinator,
sendEmailSFReimbursementReceivedToTeam,
} = require('../emails/emails')

const getAllSponsorshipFunds = () => {
return getAnnotatedSponsorshipFundsByIdList()
Expand Down Expand Up @@ -49,10 +54,33 @@ const createSponsorshipFund = (body) => {
return newSponsorshipFund.save()
}

const updateSponsorshipFund = (id, body) => {
return SponsorshipFund.findByIdAndUpdate(id, body, {
new: true,
})
const updateSponsorshipFund = async (id, body) => {
const newSponsorshipFund = await SponsorshipFund.findByIdAndUpdate(
id,
body,
{
new: true,
}
)

const annotatedSponsorshipFund = await getSponsorshipFund(id)
const status = annotatedSponsorshipFund.status

if (status === 'CLAIM_SUBMITTED') {
sendEmailSFReimbursementRequestToCoordinator(annotatedSponsorshipFund)
}

if (status === 'SUBMITTED_TO_SF') {
sendEmailSFConfirmReimbursementSubmitToCoordinator(
annotatedSponsorshipFund
)
}

if (status === 'REIMBURSED') {
sendEmailSFReimbursementReceivedToTeam(annotatedSponsorshipFund)
}

return newSponsorshipFund
}

const deleteSponsorshipFund = async (id) => {
Expand Down
45 changes: 45 additions & 0 deletions frontend/src/components/ConfirmationModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
Button,
} from '@chakra-ui/react'

const ConfirmationModal = ({ onConfirm, isOpen, onClose }) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Submission (Placeholder)</ModalHeader>
<ModalCloseButton />
<ModalBody>
<div>
Are you sure you want to submit this, you cant undo bla
bla bla (PLACEHOLDER)
</div>
</ModalBody>

<ModalFooter>
<Button colorScheme="red" mr={3} onClick={onClose}>
Cancel
</Button>
<Button
colorScheme="green"
onClick={() => {
onClose()
onConfirm()
}}
>
Confirm
</Button>
</ModalFooter>
</ModalContent>
</Modal>
)
}

export default ConfirmationModal
96 changes: 84 additions & 12 deletions frontend/src/components/TicketContent/SFAdminContentTable.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,66 @@
import { Button, Center, Heading, VStack, Link } from '@chakra-ui/react'
import {
Button,
Center,
Heading,
VStack,
Link,
useDisclosure,
} from '@chakra-ui/react'
import React from 'react'
import { useGetPreserveParamsHref } from '../../hooks/hooks'
import { useGetCurrentTicket } from '../../hooks/hooks'
import { TICKET_ENDPOINTS } from '../../constants'
import { useSetRecoilState } from 'recoil'
import { allTicketsState } from '../../state/atoms'
import { axiosPreset } from '../../axiosConfig'
import { getAllTickets } from '../../utils/globalSetters'
import ConfirmationModal from '../ConfirmationModal'

const SFAdminContentTable = () => {
const getPreserveParamsHref = useGetPreserveParamsHref()
const currentTicket = useGetCurrentTicket()
const setAllTickets = useSetRecoilState(allTicketsState)
const {
isOpen: isConfirmationOpen,
onOpen: onOpenConfirmation,
onClose: onCloseConfirmation,
} = useDisclosure()

const transitionStatusText = (status) => {
if (status === 'ALLOCATED') {
return 'Submit Claim'
}
if (status === 'CLAIM_SUBMITTED') {
return 'Confirm Reimbursement Submission'
}
if (status === 'SUBMITTED_TO_SF') {
return 'Confirm Reimbursement'
}
if (status === 'REIMBURSED') {
return 'Reimbursement Confirmed'
}
}
const nextStatus = (status) => {
if (status === 'ALLOCATED') {
return 'CLAIM_SUBMITTED'
}
if (status === 'CLAIM_SUBMITTED') {
return 'SUBMITTED_TO_SF'
}
if (status === 'SUBMITTED_TO_SF') {
return 'REIMBURSED'
}
}
const handleUpdateStatus = async (nextStatus) => {
const payload = {
status: nextStatus,
}
await axiosPreset.patch(
`${TICKET_ENDPOINTS.SF}/${currentTicket._id}`,
payload
)
await getAllTickets(setAllTickets)
}
return (
<VStack
border="1px solid black"
Expand All @@ -16,17 +71,34 @@ const SFAdminContentTable = () => {
<Heading size="md">Admin View</Heading>
{
<Center pb="7px" gap="10px">
<Button
colorScheme="blue"
size="sm"
disabled={
currentTicket?.po_number?.length +
currentTicket?.requisition_number?.length ===
0
}
>
Transition Status
</Button>
{isConfirmationOpen && (
<ConfirmationModal
onClose={onCloseConfirmation}
isOpen={isConfirmationOpen}
onConfirm={() =>
handleUpdateStatus(
nextStatus(currentTicket.status)
)
}
/>
)}
{currentTicket.status !== 'REIMBURSED' && (
<Button
colorScheme="blue"
size="sm"
disabled={
currentTicket?.po_number?.length +
currentTicket?.requisition_number
?.length ===
0
}
onClick={() => {
onOpenConfirmation()
}}
>
{transitionStatusText(currentTicket.status)}
</Button>
)}
{/* can remove getPreserveParamsHref if it does not make sense to preserve params */}
<Link
href={getPreserveParamsHref(
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const APPROVAL_LEVELS = Object.freeze({
admin_approval: 'admin_approval',
})

export const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he']
export const ADMIN_IDENTIFIERS = ['drayside', 'v2zheng', 'jw4he', 'william.li']
export const TEAM_CAPTAIN_TITLES = ['Team Captain']
export const DIRECTOR_TITLES = ['Director']

Expand Down
Loading