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

(feat) : added a refund option to the bill manager. #296

Merged
merged 11 commits into from
Aug 7, 2024
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be using colors from carbon token system e.g

@use '@cabon/colors';

}

.refundedItem:hover {
background-color: #fecaca !important;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using !important isn't recommend and used as the last resort. I would discourage its usage here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was actually a last resort. I couldnt get anything else to work. But I did not try the carbon/colors option so maybe that will work. Thanks for the feedback will work on the rest of the feedback before the end of today.

}

.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;
amosmachora marked this conversation as resolved.
Show resolved Hide resolved
}

.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',
amosmachora marked this conversation as resolved.
Show resolved Hide resolved
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'),
amosmachora marked this conversation as resolved.
Show resolved Hide resolved
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
Loading