diff --git a/auth-web/package-lock.json b/auth-web/package-lock.json index 8b26477ae6..f681bae2b8 100644 --- a/auth-web/package-lock.json +++ b/auth-web/package-lock.json @@ -1,12 +1,12 @@ { "name": "auth-web", - "version": "2.6.47", + "version": "2.6.48", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "auth-web", - "version": "2.6.47", + "version": "2.6.48", "dependencies": { "@bcrs-shared-components/base-address": "2.0.3", "@bcrs-shared-components/bread-crumb": "1.0.8", diff --git a/auth-web/package.json b/auth-web/package.json index f14f514587..379b710d36 100644 --- a/auth-web/package.json +++ b/auth-web/package.json @@ -1,6 +1,6 @@ { "name": "auth-web", - "version": "2.6.47", + "version": "2.6.48", "appName": "Auth Web", "sbcName": "SBC Common Components", "private": true, diff --git a/auth-web/src/components/pay/eft/ShortNameRefund.vue b/auth-web/src/components/pay/eft/ShortNameRefund.vue new file mode 100644 index 0000000000..c78d99f208 --- /dev/null +++ b/auth-web/src/components/pay/eft/ShortNameRefund.vue @@ -0,0 +1,152 @@ + + + + + mdi-file-document + + Short Name Refund + + + No refund initiated. SBC Finance can initiate refund if a CAS supplier number is created for the short name. + + Initiate Refund + + + + + + + + diff --git a/auth-web/src/models/refund.ts b/auth-web/src/models/refund.ts index 38c88f9e5c..d944dd0c43 100644 --- a/auth-web/src/models/refund.ts +++ b/auth-web/src/models/refund.ts @@ -8,3 +8,12 @@ export interface RefundRevenueType { refundAmount: number refundType: string } + +export interface EftRefundRequest { + shortNameId: number + refundAmount: number + casSupplierNum: string + refundEmail: string + comment?: string + shortName?: string +} diff --git a/auth-web/src/resources/BreadcrumbResources.ts b/auth-web/src/resources/BreadcrumbResources.ts index 82a27f4f9c..8d30352ed5 100644 --- a/auth-web/src/resources/BreadcrumbResources.ts +++ b/auth-web/src/resources/BreadcrumbResources.ts @@ -39,6 +39,11 @@ export const ShortNameDetailsBreadcrumb: BreadcrumbIF = { to: { name: 'shortnamedetails' } } +export const ShortNameRefundBreadcrumb: BreadcrumbIF = { + text: 'Refund Information', + to: { name: 'shortnamerefund' } +} + export const CreatAccountBreadcrumb: BreadcrumbIF = { text: 'Create Account', to: { name: 'chooseauthmethodview' } diff --git a/auth-web/src/routes/router.ts b/auth-web/src/routes/router.ts index a116e1483c..27076a2d50 100644 --- a/auth-web/src/routes/router.ts +++ b/auth-web/src/routes/router.ts @@ -4,6 +4,7 @@ import { MyBusinessRegistryBreadcrumb, RegistryDashboardBreadcrumb, RegistryHomeBreadcrumb, ShortNameDetailsBreadcrumb, ShortNameMappingBreadcrumb, + ShortNameRefundBreadcrumb, StaffBusinessRegistryBreadcrumb, StaffDashboardBreadcrumb } from '@/resources/BreadcrumbResources' @@ -71,6 +72,7 @@ import SetupAccountView from '@/views/auth/staff/SetupAccountView.vue' import SetupGovmAccountView from '@/views/auth/staff/SetupGovmAccountView.vue' import ShortNameDetailsView from '@/views/pay/eft/ShortNameDetailsView.vue' import ShortNameMappingView from '@/views/pay/ShortNameMappingView.vue' +import ShortNameRefundView from '@/views/pay/eft/ShortNameRefundView.vue' import SigninView from '@/views/auth/SigninView.vue' import SignoutView from '@/views/auth/SignoutView.vue' import StaffActiveAccountsTable from '@/components/auth/staff/account-management/StaffActiveAccountsTable.vue' @@ -887,6 +889,26 @@ export function getRoutes (): RouteConfig[] { }, props: (route) => ({ shortNameId: route.params.shortNameId }) }, + { + path: '/pay/shortname-details/:shortNameId/refund', + name: 'shortnamerefund', + component: ShortNameRefundView, + meta: { + requiresAuth: true, + allowedRoles: [Role.EftRefund], + breadcrumb: [ + StaffDashboardBreadcrumb, + ShortNameMappingBreadcrumb, + ShortNameDetailsBreadcrumb, + ShortNameRefundBreadcrumb + ], + showNavBar: true + }, + props: route => ({ + shortNameDetails: JSON.parse(route.query.shortNameDetails || '{}'), + unsettledAmount: route.query.unsettledAmount || '' + }) + }, { path: '/pay/refund', name: 'refund', diff --git a/auth-web/src/services/payment.services.ts b/auth-web/src/services/payment.services.ts index 33292b2872..279801e25e 100644 --- a/auth-web/src/services/payment.services.ts +++ b/auth-web/src/services/payment.services.ts @@ -1,4 +1,5 @@ import { EFTShortnameResponse, EFTTransactionFilterParams, EFTTransactionListResponse } from '@/models/eft-transaction' +import { EftRefundRequest, RefundRequest } from '@/models/refund' import { FilingTypeResponse, GLCode, GLCodeResponse } from '@/models/Staff' import { Invoice, InvoiceListResponse } from '@/models/invoice' import { LinkedShortNameFilterParams, ShortNameSummaryFilterParams } from '@/models/pay/short-name' @@ -16,7 +17,6 @@ import { AxiosPromise } from 'axios' import ConfigHelper from '@/util/config-helper' import { Payment } from '@/models/Payment' import { PaymentTypes } from '@/util/constants' -import { RefundRequest } from '@/models/refund' import { axios } from '@/util/http-util' export default class PaymentService { @@ -51,6 +51,11 @@ export default class PaymentService { return axios.post(url, refundPayload) } + static refundEFT (refundPayload: EftRefundRequest): AxiosPromise { + const url = `${ConfigHelper.getPayAPIURL()}/eft-shortnames/shortname_refund` + return axios.post(url, refundPayload) + } + static downloadOBInvoice (paymentId: string): AxiosPromise { const url = `${ConfigHelper.getPayAPIURL()}/payment-requests/${paymentId}/reports` const headers = { diff --git a/auth-web/src/stores/org.ts b/auth-web/src/stores/org.ts index 73ff578e48..e2d9dc075e 100644 --- a/auth-web/src/stores/org.ts +++ b/auth-web/src/stores/org.ts @@ -37,6 +37,7 @@ import { import { BcolAccountDetails, BcolProfile } from '@/models/bcol' import { CreateRequestBody as CreateInvitationRequestBody, Invitation } from '@/models/Invitation' import { EFTInvoiceListResponse, FailedEFTInvoice, FailedInvoice, NonSufficientFundsInvoiceListResponse } from '@/models/invoice' +import { EftRefundRequest, RefundRequest } from './../models/refund' import { Products, ProductsRequestBody } from '@/models/Staff' import { StatementFilterParams, StatementNotificationSettings, StatementSettings, StatementsSummary } from '@/models/statement' import { computed, reactive, toRefs } from '@vue/composition-api' @@ -53,7 +54,6 @@ import KeyCloakService from 'sbc-common-components/src/services/keycloak.service import OrgService from '@/services/org.services' import PaymentService from '@/services/payment.services' import PermissionService from '@/services/permission.services' -import { RefundRequest } from './../models/refund' import StaffService from '@/services/staff.services' import UserService from '@/services/user.services' import { UserSettings } from 'sbc-common-components/src/models/userSettings' @@ -856,6 +856,11 @@ export const useOrgStore = defineStore('org', () => { return response?.data || {} } + async function refundEFT (refundPayload: EftRefundRequest) { + const response = await PaymentService.refundEFT(refundPayload) + return response?.data || {} + } + async function updateInvoicePaymentMethodAsCreditCard (invoicePayload) { const response = await PaymentService.updateInvoicePaymentMethodAsCreditCard(invoicePayload.paymentId, invoicePayload.accountId) return response?.data || {} @@ -1157,6 +1162,7 @@ export const useOrgStore = defineStore('org', () => { addToCurrentSelectedProducts, resetoCurrentSelectedProducts, refundInvoice, + refundEFT, setSubscribedProducts, currentSelectedProductsPremiumOnly, fetchCurrentOrganizationGLInfo, diff --git a/auth-web/src/util/constants.ts b/auth-web/src/util/constants.ts index e7be78905b..1d64e6ee3b 100644 --- a/auth-web/src/util/constants.ts +++ b/auth-web/src/util/constants.ts @@ -54,6 +54,7 @@ export enum Role { FasSearch = 'fas_search', ViewAllTransactions = 'view_all_transactions', ManageEft = 'manage_eft', + EftRefund = 'eft_refund', CreateCredits = 'create_credits', FasRefund = 'fas_refund', BcolStaffAdmin = 'bcol_staff_admin' diff --git a/auth-web/src/views/pay/eft/ShortNameDetailsView.vue b/auth-web/src/views/pay/eft/ShortNameDetailsView.vue index d608259d2f..c17815f1dc 100644 --- a/auth-web/src/views/pay/eft/ShortNameDetailsView.vue +++ b/auth-web/src/views/pay/eft/ShortNameDetailsView.vue @@ -16,10 +16,36 @@ {{ unsettledAmountHeader }} - Review and verify short name details + Review and verify short name details {{ canEFTRefund }}, {{ displayRefundAlert }} + + + + mdi-alert + + + Please verify if the {{ unsettledAmount }} balance is eligible for a refund, or you can link to a new account. + + + + + , @@ -49,22 +86,35 @@ export default defineComponent({ } }, setup (props) { + const userStore = useUserStore() + const currentUser = computed(() => userStore.currentUser) const state = reactive({ - shortNameDetails: {}, + shortNameDetails: {} as ShortNameDetails, highlightIndex: -1, snackbar: false, - snackbarText: '' + snackbarText: '', + unsettledAmount: '', + displayRefundAlert: false, + canEFTRefund: computed((): boolean => currentUser.value?.roles?.includes(Role.EftRefund)) }) onMounted(async () => { await loadShortname(props.shortNameId) + const details: ShortNameDetails = state.shortNameDetails + + state.unsettledAmount = details.creditsRemaining !== undefined + ? CommonUtils.formatAmount(details.creditsRemaining) : '' + + state.displayRefundAlert = ( + (details.creditsRemaining > 0 && details.linkedAccountsCount > 0) || + (details.creditsRemaining > 0 && details.linkedAccountsCount <= 0 && + moment(details.lastPaymentReceivedDate).isBefore(moment().subtract(30, 'days'))) + ) }) const unsettledAmountHeader = computed(() => { - const details = state.shortNameDetails - const unsettledAmount = details.creditsRemaining !== undefined - ? CommonUtils.formatAmount(details.creditsRemaining) : '' - return `Unsettled Amount for ${details.shortName}: ${unsettledAmount}` + const details: ShortNameDetails = state.shortNameDetails + return `Unsettled Amount for ${details.shortName}: ${state.unsettledAmount}` }) async function onLinkAccount (account: any, results: Array) { @@ -104,6 +154,17 @@ export default defineComponent({ diff --git a/auth-web/src/views/pay/eft/ShortNameRefundView.vue b/auth-web/src/views/pay/eft/ShortNameRefundView.vue new file mode 100644 index 0000000000..4d6c288af9 --- /dev/null +++ b/auth-web/src/views/pay/eft/ShortNameRefundView.vue @@ -0,0 +1,267 @@ + + + + + Refund Information + + + + + + + + + + Short Name + + + {{ shortNameDetails.shortName }} + + + + + + Unsettled Amount on Short Name + + + {{ unsettledAmount }} + + + + + + Refund Amount + + + + + + + CAS Supplier Number + + + + + + + Email + + + + + + + Staff Comment + + + + + + + + + {{ backButtonLabel }} + + + {{ buttonText }} + + + + + + + + + + diff --git a/auth-web/tests/unit/components/ShortNameRefund.spec.ts b/auth-web/tests/unit/components/ShortNameRefund.spec.ts new file mode 100644 index 0000000000..0713dffe99 --- /dev/null +++ b/auth-web/tests/unit/components/ShortNameRefund.spec.ts @@ -0,0 +1,43 @@ +import { createLocalVue, mount } from '@vue/test-utils' +import ShortNameRefund from '@/components/pay/eft/ShortNameRefund.vue' +import Vue from 'vue' +import VueRouter from 'vue-router' +import Vuetify from 'vuetify' +import sinon from 'sinon' + +Vue.use(Vuetify) +Vue.use(VueRouter) + +describe('ShortNameRefund.vue', () => { + let wrapper + const localVue = createLocalVue() + const vuetify = new Vuetify({}) + let sandbox + + beforeEach(() => { + sandbox = sinon.createSandbox() + wrapper = mount(ShortNameRefund, { + propsData: { + shortNameDetails: { shortName: 'SHORTNAME', id: 1 }, + unsettledAmount: '100.0' + }, + localVue, + vuetify + }) + }) + + afterEach(() => { + wrapper.destroy() + sandbox.restore() + }) + + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + it('renders the component correctly', () => { + expect(wrapper.find('.card-title').text()).toContain('Short Name Refund') + expect(wrapper.find('.card-content span').text()) + .toContain('No refund initiated. SBC Finance can initiate refund if a CAS supplier number is created for the short name.') + }) +}) diff --git a/auth-web/tests/unit/views/ShortNameRefundView.spec.ts b/auth-web/tests/unit/views/ShortNameRefundView.spec.ts new file mode 100644 index 0000000000..538339360a --- /dev/null +++ b/auth-web/tests/unit/views/ShortNameRefundView.spec.ts @@ -0,0 +1,62 @@ +import { createLocalVue, mount } from '@vue/test-utils' +import ShortNameRefundView from '@/views/pay/eft/ShortNameRefundView.vue' +import Vue from 'vue' +import VueRouter from 'vue-router' +import Vuetify from 'vuetify' +import sinon from 'sinon' + +Vue.use(Vuetify) +Vue.use(VueRouter) + +describe('ShortNameRefundView.vue', () => { + let wrapper + const localVue = createLocalVue() + const vuetify = new Vuetify({}) + let sandbox + + beforeEach(() => { + sandbox = sinon.createSandbox() + wrapper = mount(ShortNameRefundView, { + propsData: { + shortNameDetails: { shortName: 'TEST', creditsRemaining: '500.0' }, + unsettledAmount: '100.0' + }, + localVue, + vuetify + }) + }) + + afterEach(() => { + wrapper.destroy() + sandbox.restore() + }) + + it('is a Vue instance', () => { + expect(wrapper.vm).toBeTruthy() + }) + + it('renders the component correctly', () => { + expect(wrapper.find('.view-header__title').text()).toBe('Refund Information') + expect(wrapper.find('[data-test="refundAmount"]').exists()).toBe(true) + expect(wrapper.find('[data-test="casSupplierNumber"]').exists()).toBe(true) + expect(wrapper.find('[data-test="email"]').exists()).toBe(true) + expect(wrapper.find('[data-test="staffComment"]').exists()).toBe(true) + }) + + it('clears the form when the cancel button is clicked', async () => { + await wrapper.setData({ + refundAmount: 100.00, + casSupplierNum: 'CAS-123', + email: 'test@example.com', + staffComment: 'Test comment' + }) + + const cancelButton = wrapper.find('[data-test="btn-edit-routing-cancel"]') + await cancelButton.trigger('click') + + expect(wrapper.vm.$data.refundAmount).toBe(undefined) + expect(wrapper.vm.$data.casSupplierNum).toBe('') + expect(wrapper.vm.$data.email).toBe('') + expect(wrapper.vm.$data.staffComment).toBe('') + }) +})
- Review and verify short name details + Review and verify short name details {{ canEFTRefund }}, {{ displayRefundAlert }}
+ Please verify if the {{ unsettledAmount }} balance is eligible for a refund, or you can link to a new account. +