Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
128 changes: 114 additions & 14 deletions web/pay-ui/app/components/RoutingSlip/TransactionDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import type { Invoice } from '@/interfaces/invoice'
import useTransactionDataTable from '@/composables/viewRoutingSlip/useTransactionDataTable'
import commonUtil from '@/utils/common-util'
import { RefundApprovalStatus, RefundApprovalStatusDisplay } from '~/utils'
import { RolePattern } from '~/enums'
import CommonUtils from '~/utils/common-util'

const route = useRoute()
const { getFeatureFlag } = useConnectLaunchDarkly()

interface Props {
invoices?: Invoice[]
Expand All @@ -20,8 +26,65 @@ const {
isAlreadyCancelled
} = useTransactionDataTable(toRef(props, 'invoices'))

const enableRefundRequestFlow = await getFeatureFlag(LDFlags.EnableFasRefundRequestFlow, false, 'await')

const formatDisplayDate = commonUtil.formatDisplayDate
const appendCurrencySymbol = commonUtil.appendCurrencySymbol

async function cancelTransaction(id: number) {
if (enableRefundRequestFlow) {
navigateTo({
path: `/transaction-view/${id}/initiateRefund`,
query: {
returnTo: route.path,
returnType: 'viewRoutingSlip',
returnToId: route.params.slipId
}
})
} else {
cancel(id)
}
}

async function viewRefundDetail(invoiceId: number, refundId: number) {
navigateTo({
path: `/transaction-view/${invoiceId}/refund-request/${refundId}`,
query: {
returnTo: route.path,
returnType: 'viewRoutingSlip',
returnToId: route.params.slipId
}
})
}

function isRefundable(invoiceRow: Invoice): boolean {
if (
!invoiceRow.latestRefundStatus
|| RefundApprovalStatus.DECLINED === invoiceRow.latestRefundStatus
) {
const productRole = (invoiceRow.product?.toLowerCase() ?? '')
+ RolePattern.ProductRefundRequester
return (!!invoiceRow.partialRefundable || !!invoiceRow.fullRefundable)
&& invoiceRow.total > 0
&& CommonUtils.canInitiateProductRefund(productRole)
}
return false
}

function showRefundRequestBadge(invoiceRow: Invoice) {
return !!(invoiceRow.latestRefundStatus
&& [RefundApprovalStatus.DECLINED, RefundApprovalStatus.PENDING_APPROVAL]
.includes(invoiceRow.latestRefundStatus as RefundApprovalStatus))
}

function showViewDetails(invoiceRow: Invoice) {
const isCancelled = isAlreadyCancelled(invoiceRow.statusCode)
const isRefund = invoiceRow.latestRefundStatus
&& ![RefundApprovalStatus.DECLINED]
.includes(invoiceRow.latestRefundStatus as RefundApprovalStatus)

return isCancelled || isRefund
}
</script>

<template>
Expand Down Expand Up @@ -58,6 +121,19 @@ const appendCurrencySymbol = commonUtil.appendCurrencySymbol
<template #total-cell="{ row }">
<div class="font-bold table-cell-text text-right">
{{ appendCurrencySymbol(row.original.total?.toFixed(2) || '0.00') }}
<br>
<span v-if="row.original.refund > 0" class="font-normal">
Refunded ({{ appendCurrencySymbol(row.original.refund?.toFixed(2) || '0.00') }})
</span>
<UBadge
v-else-if="showRefundRequestBadge(row.original)"
color="neutral"
class="!bg-gray-200 !text-gray-700 font-bold"
variant="solid"
size="md"
>
{{ RefundApprovalStatusDisplay[row.original.latestRefundStatus as RefundApprovalStatus]?.toUpperCase() }}
</UBadge>
</div>
</template>
<template #description-cell="{ row }">
Expand All @@ -73,26 +149,50 @@ const appendCurrencySymbol = commonUtil.appendCurrencySymbol
</div>
</template>
<template #actions-cell="{ row }">
<template v-if="isAlreadyCancelled(row.original.statusCode)">
<span
:data-test="commonUtil.getIndexedTag('text-cancel', row.index)"
class="text-error font-bold"
>
Cancelled
</span>
</template>
<template v-else>
<div v-can:fas_refund.hide>
<template v-if="enableRefundRequestFlow">
<template v-if="showViewDetails(row.original)">
<UButton
:data-test="commonUtil.getIndexedTag('btn-invoice-cancel', row.index)"
label="Cancel"
label="View Refund Detail"
variant="outline"
color="primary"
class="btn-table"
:disabled="disableCancelButton"
@click="cancel(row.original.id!)"
@click="viewRefundDetail(row.original.id, row.original.latestRefundId)"
/>
</div>
</template>
<template v-else-if="isRefundable(row.original)">
<UButton
:data-test="commonUtil.getIndexedTag('btn-invoice-cancel', row.index)"
label="Request Refund"
variant="outline"
color="primary"
class="btn-table"
@click="cancelTransaction(row.original.id!)"
/>
</template>
</template>
<template v-else>
<template v-if="isAlreadyCancelled(row.original.statusCode)">
<span
:data-test="commonUtil.getIndexedTag('text-cancel', row.index)"
class="text-error font-bold"
>
Cancelled
</span>
</template>
<template v-else>
<div v-can:fas_refund.hide>
<UButton
:data-test="commonUtil.getIndexedTag('btn-invoice-cancel', row.index)"
label="Cancel"
variant="outline"
color="primary"
class="btn-table"
:disabled="disableCancelButton"
@click="cancelTransaction(row.original.id!)"
/>
</div>
</template>
</template>
</template>
<template #empty>
Expand Down
2 changes: 1 addition & 1 deletion web/pay-ui/app/components/refund/RefundRequestsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ const columns = computed<TableColumn<RefundRequestResult>[]>(() => {

<template #refundAmount-cell="{ row }">
<div class="flex items-center justify-start gap-2">
<span>{{ formatAmount(row.original.transactionAmount) }}</span>
<span>{{ formatAmount(row.original.refundAmount) }}</span>
</div>
</template>

Expand Down
9 changes: 9 additions & 0 deletions web/pay-ui/app/composables/pay-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const usePayApi = () => {
return $payApi(`/fas/routing-slips/${routingNumber}`, options as Record<string, unknown>)
}

async function getRoutingSlipV2(
routingNumber: string,
options?: { showErrorToast?: boolean }
): Promise<RoutingSlip | undefined> {
const { payApiUrl } = useRuntimeConfig().public
return $payApi(`${payApiUrl}/api/v2/fas/routing-slips/${routingNumber}`, options as Record<string, unknown>)
}

async function postRoutingSlip(payload: CreateRoutingSlipPayload): Promise<RoutingSlip> {
return nuxtApp.$payApi('/fas/routing-slips', {
method: 'POST',
Expand Down Expand Up @@ -195,6 +203,7 @@ export const usePayApi = () => {
return {
getCodes,
getRoutingSlip,
getRoutingSlipV2,
postRoutingSlip,
postLinkRoutingSlip,
postSearchRoutingSlip,
Expand Down
15 changes: 15 additions & 0 deletions web/pay-ui/app/composables/useRoutingSlip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ export const useRoutingSlip = () => {
}
}

const getRoutingSlipV2 = async (getRoutingSlipRequestPayload: GetRoutingSlipRequestPayload) => {
try {
// Global Exception handler will handle this one.
const response = await usePayApi().getRoutingSlipV2(
getRoutingSlipRequestPayload.routingSlipNumber
)
if (response) {
store.routingSlip = response
}
} catch (error) {
console.error('error ', error) // 500 errors may not return data
}
}

const updateRoutingSlipStatus = async (
statusDetails: string | StatusDetails
) => {
Expand Down Expand Up @@ -385,6 +399,7 @@ export const useRoutingSlip = () => {
createRoutingSlip,
checkRoutingNumber,
getRoutingSlip,
getRoutingSlipV2,
updateRoutingSlipStatus,
updateRoutingSlipRefundStatus,
adjustRoutingSlip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function useTransactionDataTable(invoices: Ref<Invoice[]>) {
}

const invoiceNumber = invoice.references?.find(ref => ref.invoiceNumber)?.invoiceNumber
|| invoice.references?.[0]?.invoiceNumber
|| invoice.references?.[0]?.invoiceNumber || invoice.invoiceNumber

return {
id: invoice.id,
Expand All @@ -41,7 +41,13 @@ export default function useTransactionDataTable(invoices: Ref<Invoice[]>) {
total: invoice.total,
createdName: invoice.createdName,
createdBy: invoice.createdBy,
description: descriptions.length > 0 ? descriptions : ['N/A']
description: descriptions.length > 0 ? descriptions : ['N/A'],
refund: invoice.refund,
latestRefundId: invoice.latestRefundId,
latestRefundStatus: invoice.latestRefundStatus,
partialRefundable: invoice.partialRefundable,
fullRefundable: invoice.fullRefundable,
product: invoice.product
}
})
}
Expand Down
6 changes: 6 additions & 0 deletions web/pay-ui/app/interfaces/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface Invoice {
statusCode?: string
total?: number
details?: Detail[]
invoiceNumber?: string
latestRefundId?: number
latestRefundStatus?: string
partialRefundable?: boolean
Expand All @@ -53,6 +54,11 @@ export interface InvoiceDisplay {
createdBy?: string
description?: string[]
id?: number
latestRefundId?: number
latestRefundStatus?: string
partialRefundable?: boolean
fullRefundable?: boolean
product?: string
}

export interface LineItem {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ function viewDetails(index: number) {
function getStatusConfig(item: RefundHistoryItem) {
if (item.refundStatus === RefundApprovalStatus.PENDING_APPROVAL) {
return { label: 'REFUND REQUESTED', color: 'neutral' as const,
classes: '!bg-gray-200 !text-gray-700' }
classes: '!bg-gray-200 !text-gray-700 font-bold' }
}
if (item.refundStatus === RefundApprovalStatus.APPROVED && item.partialRefundLines?.length > 0) {
return { label: 'PARTIALLY REFUNDED', color: 'primary' as const }
}
if (item.refundStatus === RefundApprovalStatus.APPROVED) {
return { label: 'FULL REFUND APPROVED', color: 'primary' as const }
}
if (item.refundStatus === RefundApprovalStatus.APPROVAL_NOT_REQUIRED) {
return { label: 'APPROVAL NOT REQUIRED', color: 'primary' as const }
}
if (item.refundStatus === RefundApprovalStatus.DECLINED) {
return { label: 'REFUND DECLINED', color: 'red' as const }
return { label: 'REFUND DECLINED', color: 'neutral' as const,
classes: '!bg-gray-200 !text-gray-700 font-bold' }
}
return null
}
Expand Down
32 changes: 23 additions & 9 deletions web/pay-ui/app/pages/transaction-view/[id]/[...details].vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const details = computed(() => {
const mode = computed(() => details.value[0] || null)
const refundId = computed(() => details.value[1] ? Number(details.value[1]) : null)

const returnTo = route.query.returnTo as string | undefined
const returnType = route.query.returnType as string | undefined
const returnToId = route.query.returnToId as string | undefined

definePageMeta({
layout: 'connect-auth',
middleware: ['pay-auth'],
Expand Down Expand Up @@ -239,7 +243,11 @@ async function onProceedToConfirm() {
color: 'success'
})
if (state.refundFormStage === RefundRequestStage.DATA_VALIDATED) {
goToTransactionList()
if (returnTo) {
router.push(returnTo)
} else {
goToTransactionList()
}
}
} catch (error) {
console.error(`Refund request failed: ${error}`)
Expand Down Expand Up @@ -360,7 +368,12 @@ function goToTransactionList() {
}

function onCancel() {
goToTransactionList()
const returnTo = route.query.returnTo as string | undefined
if (returnTo) {
router.push(returnTo)
} else {
goToTransactionList()
}
}

onMounted(async () => {
Expand All @@ -371,13 +384,14 @@ onMounted(async () => {
}

setBreadcrumbs([
{
label: t('page.transactionView.breadcrumb.transactions'),
to: '/transactions'
},
{
label: t(getTransactionPageTitle())
}
...(returnType === 'viewRoutingSlip' && returnToId
? [
{ label: t('label.fasDashboard'), to: '/home' },
{ label: t('page.viewRoutingSlip.h1', { id: returnToId }), to: returnTo }
]
: [{ label: t('page.transactionView.breadcrumb.transactions'), to: '/transactions' }]
),
{ label: t(getTransactionPageTitle()) }
])
})
</script>
Expand Down
10 changes: 8 additions & 2 deletions web/pay-ui/app/pages/view-routing-slip/[slipId].vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useLoader } from '~/composables/common/useLoader'

const route = useRoute()
const { t } = useI18n()
const { getFeatureFlag } = useConnectLaunchDarkly()

definePageMeta({
layout: 'connect-auth',
Expand All @@ -35,7 +36,7 @@ setBreadcrumbs([
])
useViewRoutingSlip({ slipId })

const { getRoutingSlip, getLinkedRoutingSlips } = useRoutingSlip()
const { getRoutingSlip, getRoutingSlipV2, getLinkedRoutingSlips } = useRoutingSlip()
const { store } = useRoutingSlipStore()
const { isLoading, toggleLoading } = useLoader()

Expand All @@ -49,7 +50,12 @@ function handlePaymentAdjusted() {
onMounted(async () => {
toggleLoading(true)
try {
await getRoutingSlip({ routingSlipNumber: slipId })
const enableRefundRequestFlow = await getFeatureFlag(LDFlags.EnableFasRefundRequestFlow, false, 'await')
if (enableRefundRequestFlow) {
await getRoutingSlipV2({ routingSlipNumber: slipId })
} else {
await getRoutingSlip({ routingSlipNumber: slipId })
}
await getLinkedRoutingSlips(slipId)
} finally {
toggleLoading(false)
Expand Down
Loading
Loading