Skip to content

Commit

Permalink
(feat) : added a refund option to the bill manager. (#296)
Browse files Browse the repository at this point in the history
* feat: initial work on refund item functionality

* fix: billable service uuid for refunded bills

* refactor: filter out the refunded bill line items

* feat: credit amount header

* feat: highlighting for refunded bill item

* refactor: comments out and adding a no refunded item fallback

* fix: removed space on line item status

* generated translations and index.ts

* fix: background shade for only refunded line items
  • Loading branch information
amosmachora authored Aug 7, 2024
1 parent baa4d7f commit b07fe0b
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
StructuredListBody,
StructuredListWrapper,
Layer,
Checkbox,
OverflowMenu,
OverflowMenuItem,
} from '@carbon/react';
Expand All @@ -19,13 +18,57 @@ import { launchWorkspace, showModal } from '@openmrs/esm-framework';
const BillLineItems: React.FC<{ bill: MappedBill }> = ({ bill }) => {
const { t } = useTranslation();

return (
<Layer>
<StructuredListWrapper className={styles.billListContainer} selection={true} isCondensed>
<StructuredListHead>
<StructuredListRow head>
<StructuredListCell head>{t('billItem', 'Bill item')}</StructuredListCell>
<StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
<StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
<StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
<StructuredListCell head>{t('actions', 'Actions')}</StructuredListCell>
</StructuredListRow>
</StructuredListHead>
<StructuredListBody>
{bill?.lineItems.map((lineItem) => (
<LineItemRow bill={bill} lineItem={lineItem} key={lineItem.uuid} />
))}
</StructuredListBody>
</StructuredListWrapper>
</Layer>
);
};

const LineItemRow = ({ lineItem, bill }: { lineItem: LineItem; bill: MappedBill }) => {
const refundedLineItemUUIDs = bill.lineItems.filter((li) => Math.sign(li.price) === -1).map((li) => li.uuid);
const isRefundedLineItem = refundedLineItemUUIDs.includes(lineItem.uuid);

const refundedLineItemBillableServiceUUIDs = bill.lineItems
.filter((li) => Math.sign(li.price) === -1)
.map((li) => li.billableService.split(':').at(0));

const isRefundedBillableService = refundedLineItemBillableServiceUUIDs.includes(
lineItem.billableService.split(':').at(0),
);

const { t } = useTranslation();

const handleOpenEditLineItemWorkspace = (lineItem: LineItem) => {
launchWorkspace('edit-bill-form', {
workspaceTitle: t('editBillForm', 'Edit Bill Form'),
lineItem,
});
};

const handleOpenRefundLineItemModal = (lineItem: LineItem) => {
const dispose = showModal('refund-bill-modal', {
onClose: () => dispose(),
bill,
lineItem,
});
};

const handleOpenCancelLineItemModal = () => {
const dispose = showModal('cancel-bill-modal', {
onClose: () => dispose(),
Expand All @@ -39,38 +82,24 @@ const BillLineItems: React.FC<{ bill: MappedBill }> = ({ bill }) => {
};

return (
<Layer>
<StructuredListWrapper className={styles.billListContainer} selection={true} isCondensed>
<StructuredListHead>
<StructuredListRow head>
<StructuredListCell head>{t('billItem', 'Bill item')}</StructuredListCell>
<StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
<StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
<StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
<StructuredListCell head>{t('actions', 'Actions')}</StructuredListCell>
</StructuredListRow>
</StructuredListHead>
<StructuredListBody>
{bill?.lineItems.map((lineItem) => (
<StructuredListRow>
<StructuredListCell>
{lineItem.item === '' ? extractString(lineItem.billableService) : extractString(lineItem.item)}
</StructuredListCell>
<StructuredListCell>{lineItem.quantity}</StructuredListCell>
<StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
<StructuredListCell>{convertToCurrency(lineItem.price * lineItem.quantity)}</StructuredListCell>
<StructuredListCell>
<OverflowMenu aria-label="overflow-menu" align="bottom">
<OverflowMenuItem itemText="Edit Item" onClick={() => handleOpenEditLineItemWorkspace(lineItem)} />
<OverflowMenuItem itemText="Cancel Item" onClick={handleOpenCancelLineItemModal} />
<OverflowMenuItem itemText="Delete Item" onClick={handleOpenDeleteLineItemModal} />
</OverflowMenu>
</StructuredListCell>
</StructuredListRow>
))}
</StructuredListBody>
</StructuredListWrapper>
</Layer>
<StructuredListRow className={isRefundedLineItem && styles.refundedItem}>
<StructuredListCell>
{lineItem.item === '' ? extractString(lineItem.billableService) : extractString(lineItem.item)}
</StructuredListCell>
<StructuredListCell>{lineItem.quantity}</StructuredListCell>
<StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
<StructuredListCell>{convertToCurrency(lineItem.price * lineItem.quantity)}</StructuredListCell>
<StructuredListCell>
<OverflowMenu aria-label="overflow-menu">
<OverflowMenuItem itemText="Edit Item" onClick={() => handleOpenEditLineItemWorkspace(lineItem)} />
<OverflowMenuItem itemText="Cancel Item" onClick={handleOpenCancelLineItemModal} />
{!isRefundedBillableService && (
<OverflowMenuItem itemText="Refund Item" onClick={() => handleOpenRefundLineItemModal(lineItem)} />
)}
<OverflowMenuItem itemText="Delete Item" onClick={handleOpenDeleteLineItemModal} />
</OverflowMenu>
</StructuredListCell>
</StructuredListRow>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@
.dataTableSkeleton {
margin-top: 0.625rem;
}

.refundedItem {
background-color: #fee2e2;
}

.refundedItem:hover {
background-color: #fecaca !important;
}

.refundedItem:active {
background-color: #fca5a5 !important;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@
.modalHeaderHeading {
@include type.type-style('heading-compact-02');
}

.button_spinner {
padding: 0;
margin-right: 12px;
}

.loading_wrapper {
display: flex;
align-items: center;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useState } from 'react';
import { ModalHeader, ModalBody, ModalFooter, Button, Loading } from '@carbon/react';
import styles from './cancel-bill.scss';
import { useTranslation } from 'react-i18next';
import { showSnackbar } from '@openmrs/esm-framework';
import { processBillItems } from '../../../billing.resource';
import { mutate } from 'swr';
import { LineItem, MappedBill } from '../../../types';

export const RefundBillModal: React.FC<{
onClose: () => void;
bill: MappedBill;
lineItem: LineItem;
}> = ({ onClose, bill, lineItem }) => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);

const refundBillItems = () => {
const lineItemToBeRefunded = {
item: lineItem.uuid,
quantity: lineItem.quantity,
price: -lineItem.price,
priceName: lineItem.priceName,
priceUuid: lineItem.priceUuid,
lineItemOrder: lineItem.lineItemOrder,
paymentStatus: 'CREDITED',
billableService: lineItem.billableService.split(':').at(0),
};

const billWithRefund = {
cashPoint: bill.cashPointUuid,
cashier: bill.cashier.uuid,
lineItems: [lineItemToBeRefunded],
payments: bill.payments,
patient: bill.patientUuid,
status: bill.status,
};

setIsLoading(true);
onClose();
processBillItems(billWithRefund)
.then(() => {
mutate((key) => typeof key === 'string' && key.startsWith('/ws/rest/v1/cashier/bill'), undefined, {
revalidate: true,
});
showSnackbar({
title: t('billItems', 'Refund Items'),
subtitle: 'Item has been successfully refunded.',
kind: 'success',
timeoutInMs: 3000,
});
})
.catch((error) => {
showSnackbar({ title: 'An error occurred trying to refund item', kind: 'error', subtitle: error.message });
})
.finally(() => setIsLoading(false));
};

return (
<React.Fragment>
<ModalHeader onClose={onClose} className={styles.modalHeaderLabel} closeModal={onClose}>
{t('refundBill', 'Refund Bill')}
</ModalHeader>
<ModalBody className={styles.modalHeaderHeading}>
{t('refundBillDescription', 'Are you sure you want to refund this bill? Proceed cautiously.')}
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={onClose}>
{t('cancel', 'Cancel')}
</Button>
<Button kind="danger" onClick={refundBillItems}>
{isLoading ? (
<div className={styles.loading_wrapper}>
<Loading className={styles.button_spinner} withOverlay={false} small />
{t('processingPayment', 'Processing Payment')}
</div>
) : (
t('refund', 'Refund')
)}
</Button>
</ModalFooter>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from '@carbon/react';
import { convertToCurrency, extractString } from '../../helpers';
import { useTranslation } from 'react-i18next';
import { EmptyDataIllustration, EmptyState } from '@openmrs/esm-patient-common-lib';
import { EmptyState } from '@openmrs/esm-patient-common-lib';
import { MappedBill } from '../../types';
import styles from '../../bills-table/bills-table.scss';
import BillLineItems from './bill-line-items.component';
Expand All @@ -31,17 +31,30 @@ type PatientBillsProps = {
const PatientBills: React.FC<PatientBillsProps> = ({ bills }) => {
const { t } = useTranslation();

const hasRefundedItems = bills.some((bill) => bill.lineItems.some((li) => Math.sign(li.price) === -1));

const tableHeaders = [
{ header: 'Date', key: 'date' },
{ header: 'Billable Service', key: 'billableService' },
{ header: 'Status', key: 'status' },
{ header: 'Total Amount', key: 'totalAmount' },
];

if (hasRefundedItems) {
tableHeaders.splice(2, 0, { header: 'Refunded Amount', key: 'creditAmount' });
}

const tableRows = bills.map((bill) => ({
id: `${bill.uuid}`,
date: bill.dateCreated,
billableService: extractString(bill.billingService),
totalAmount: convertToCurrency(bill.totalAmount),
status: bill.status,
creditAmount: hasRefundedItems
? convertToCurrency(
bill.lineItems
.filter((li) => Math.sign(li.price) === -1)
.reduce((acc, curr) => acc + Math.abs(curr.price), 0),
)
: convertToCurrency(0),
}));

const handleOpenWaiveBillWorkspace = (bill: MappedBill) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/esm-billing-app/src/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const convertToCurrency = (amountToConvert: number) => {
let formattedAmount = formatter.format(Math.abs(amountToConvert));

if (amountToConvert < 0) {
formattedAmount = `(${formattedAmount})`;
formattedAmount = `- ${formattedAmount}`;
}

return formattedAmount;
Expand Down
4 changes: 4 additions & 0 deletions packages/esm-billing-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@
"name": "cancel-bill-modal",
"component": "cancelBillModal"
},
{
"name": "refund-bill-modal",
"component": "refundBillModal"
},
{
"name": "delete-bill-modal",
"component": "deleteBillModal"
Expand Down
1 change: 1 addition & 0 deletions packages/esm-billing-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"treatmentstart": "Treatment Start",
"unitPrice": "Unit price",
"unsettledBill": "Unsettled bill for test.",
"update": "Update",
"valid": "Valid SHA Number",
"validatingSHANumber": "Validating SHA Number",
"validSHANumber": "SHA number is valid, proceed with care",
Expand Down

0 comments on commit b07fe0b

Please sign in to comment.