Skip to content

Commit

Permalink
(feat) Add ability to make payments (#84)
Browse files Browse the repository at this point in the history
* (feat) Add ability to make payments

* (feat) Complete the initial workflow for bill payment
  • Loading branch information
donaldkibet authored Dec 7, 2023
1 parent 366698c commit 6d41aff
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 136 deletions.
15 changes: 13 additions & 2 deletions packages/esm-billing-app/src/billing.resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const useBills = (patientUuid?: string) => {
patientUuid: bill?.patient?.uuid,
status: bill?.status,
receiptNumber: bill?.receiptNumber,
cashierName: bill?.cashier?.display,
cashier: bill?.cashier,
cashPointUuid: bill?.cashPoint?.uuid,
cashPointName: bill?.cashPoint?.name,
cashPointLocation: bill?.cashPoint?.location?.display,
Expand Down Expand Up @@ -66,7 +66,7 @@ export const useBill = (billUuid: string) => {
patientUuid: bill?.patient?.uuid,
status: bill?.status,
receiptNumber: bill?.receiptNumber,
cashierName: bill?.cashier?.display,
cashier: bill?.cashier,
cashPointUuid: bill?.cashPoint?.uuid,
cashPointName: bill?.cashPoint?.name,
cashPointLocation: bill?.cashPoint?.location?.display,
Expand All @@ -91,3 +91,14 @@ export const useBill = (billUuid: string) => {
mutate,
};
};

export const processBillPayment = (payload, billUuid: string) => {
const url = `/ws/rest/v1/cashier/bill/${billUuid}`;
return openmrsFetch(url, {
method: 'POST',
body: payload,
headers: {
'Content-Type': 'application/json',
},
});
};
13 changes: 10 additions & 3 deletions packages/esm-billing-app/src/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ export function calculateTotalAmount(lineItems: Array<LineItem>) {
}

export const convertToCurrency = (amountToConvert: number) => {
if (amountToConvert === 0) {
return '--';
const formatter = new Intl.NumberFormat('en-KE', {
style: 'currency',
currency: 'KES',
minimumFractionDigits: 2,
});
let formattedAmount = formatter.format(Math.abs(amountToConvert));
if (amountToConvert < 0) {
formattedAmount = `(${formattedAmount})`;
}
return new Intl.NumberFormat('en-KE', { currency: 'KSH', style: 'currency' }).format(amountToConvert);

return formattedAmount;
};
39 changes: 25 additions & 14 deletions packages/esm-billing-app/src/invoice/invoice-table.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@ import {
TableCell,
DataTableSkeleton,
} from '@carbon/react';
import { Information } from '@carbon/react/icons';
import { isDesktop, useLayoutType } from '@openmrs/esm-framework';
import { useBill } from '../billing.resource';
import styles from './invoice-table.scss';
import { useTranslation } from 'react-i18next';

type InvoiceTableProps = {
billUuid: string;
};

const InvoiceTable: React.FC<InvoiceTableProps> = ({ billUuid }) => {
const layout = useLayoutType();
const { t } = useTranslation();
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
const tableTitle = t('lineItems', 'Line items');
const tableDescription = (
<p className={styles.tableDescription}>
<span>{t('lineItemsToBeBilled', 'Line items to be billed')}</span>
<Information />
</p>
);
const { bill, isLoading } = useBill(billUuid);
const headerData = [
{ header: 'No', key: 'no' },
Expand All @@ -32,18 +42,19 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ billUuid }) => {
{ header: 'Total', key: 'total' },
];

const rowData = bill?.lineItems?.map((item, index) => {
return {
no: `${index + 1}`,
id: `${item.uuid}`,
billItem: item.item,
billCode: bill.receiptNumber,
status: bill.status,
quantity: item.quantity,
price: item.price,
total: item.price * item.quantity,
};
});
const rowData =
bill?.lineItems?.map((item, index) => {
return {
no: `${index + 1}`,
id: `${item.uuid}`,
billItem: item.item,
billCode: bill.receiptNumber,
status: bill.status,
quantity: item.quantity,
price: item.price,
total: item.price * item.quantity,
};
}) ?? [];

if (isLoading) {
return (
Expand All @@ -62,8 +73,8 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ billUuid }) => {
return (
<div className={styles.invoiceContainer}>
<DataTable isSortable rows={rowData} headers={headerData} size={responsiveSize} useZebraStyles={true}>
{({ rows, headers, getRowProps, getTableProps }) => (
<TableContainer>
{({ rows, headers, getRowProps, getTableProps, getToolbarProps }) => (
<TableContainer title={tableTitle} description={tableDescription}>
<Table {...getTableProps()} aria-label="Invoice line items">
<TableHead>
<TableRow>
Expand Down
22 changes: 21 additions & 1 deletion packages/esm-billing-app/src/invoice/invoice-table.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
@use '@carbon/colors';
@use '@carbon/layout';
@use '@carbon/type';
@import '~@openmrs/esm-styleguide/src/vars';

.invoiceContainer {
margin: 0 layout.$spacing-05;
margin: 0 layout.$spacing-05 layout.$spacing-05 layout.$spacing-05;
}

.tableDescription {
display: flex;
align-items: flex-start;
margin-top: layout.$spacing-02;
column-gap: layout.$spacing-01;

::after {
content: '';
display: block;
width: 2rem;
padding-top: 0.188rem;
border-bottom: 0.375rem solid var(--brand-03);
}

& > span {
@include type.type-style('body-01');
}
}
72 changes: 16 additions & 56 deletions packages/esm-billing-app/src/invoice/invoice.component.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,32 @@
import React from 'react';
import { Button, InlineLoading } from '@carbon/react';
import { ArrowLeft } from '@carbon/react/icons';
import { ExtensionSlot, isDesktop, navigate, useLayoutType, usePatient } from '@openmrs/esm-framework';
import { InlineLoading } from '@carbon/react';
import { ExtensionSlot, usePatient } from '@openmrs/esm-framework';
import { useParams } from 'react-router-dom';
import styles from './invoice.scss';
import InvoiceTable from './invoice-table.component';
import Payments from './payments/payments.component';
import { useTranslation } from 'react-i18next';
import { useBill } from '../billing.resource';
import { convertToCurrency } from '../helpers';
import { ErrorState } from '@openmrs/esm-patient-common-lib';
import { useTranslation } from 'react-i18next';

type InvoiceProps = {};

const Invoice: React.FC<InvoiceProps> = () => {
const params = useParams();
const layout = useLayoutType();
const { t } = useTranslation();
const { patient, patientUuid, isLoading } = usePatient(params?.patientUuid);
const { bill } = useBill(params?.billUuid);
const { bill, isLoading: isLoadingBilling, error } = useBill(params?.billUuid);

const invoiceDetails = {
'Total Amount': bill?.totalAmount,
'Amount Tendered': bill?.tenderedAmount,
'Total Amount': convertToCurrency(bill?.totalAmount),
'Amount Tendered': convertToCurrency(bill?.tenderedAmount),
'Invoice Number': bill.receiptNumber,
'Date And Time': bill?.dateCreated,
'Invoice Status': bill?.status,
};

const invoiceTotal = {
'Total Amount': bill?.totalAmount,
'Amount Tendered': bill?.tenderedAmount,
'Discount Amount': 0,
'Amount due': bill?.totalAmount - bill?.tenderedAmount,
};

const navigateToDashboard = () =>
navigate({
to: window.getOpenmrsSpaBase() + 'home/billing',
});

if (isLoading) {
if (isLoading && isLoadingBilling) {
return (
<div className={styles.invoiceContainer}>
<InlineLoading
Expand All @@ -50,36 +39,22 @@ const Invoice: React.FC<InvoiceProps> = () => {
);
}

if (error) {
return <ErrorState headerTitle={t('invoiceError', 'Invoice error')} error={error} />;
}

return (
<div className={styles.invoiceContainer}>
<ExtensionSlot name="patient-header-slot" state={{ patient, patientUuid }} />
{patient && patientUuid && <ExtensionSlot name="patient-header-slot" state={{ patient, patientUuid }} />}
<section className={styles.details}>
{Object.entries(invoiceDetails).map(([key, val]) => (
<InvoiceDetails key={key} label={key} value={val} />
))}
</section>

<div className={styles.backButton}>
<Button
kind="ghost"
renderIcon={(props) => <ArrowLeft size={24} {...props} />}
iconDescription="Return to billing dashboard"
size="sm"
onClick={navigateToDashboard}>
<span>Back to dashboard</span>
</Button>
</div>

<div>
<InvoiceTable billUuid={bill?.uuid} />
<div className={styles.paymentSection}>
<Payments />
<div className={styles.invoicePaymentsContainer}>
{Object.entries(invoiceTotal).map(([key, val]) => (
<InvoicePaymentBreakdown label={key} value={val} />
))}
</div>
</div>
{bill && <Payments bill={bill} />}
</div>
</div>
);
Expand All @@ -100,18 +75,3 @@ function InvoiceDetails({ label, value }: InvoiceDetailsProps) {
}

export default Invoice;

interface InvoicePaymentBreakdown {
label: string;
value: number;
}

function InvoicePaymentBreakdown({ label, value }: InvoicePaymentBreakdown) {
const { t } = useTranslation();

return (
<div className={styles.label}>
{label} : <span className={styles.billDetail}>{value}</span>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import styles from './invoice-breakdown.scss';

type InvoiceBreakDownProps = {
label: string;
value: string;
};

export const InvoiceBreakDown: React.FC<InvoiceBreakDownProps> = ({ label, value }) => {
return (
<div className={styles.invoiceBreakdown}>
<span className={styles.label}>{label} : </span>
<span className={styles.value}>{value}</span>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@use '@carbon/colors';
@use '@carbon/layout';
@use '@carbon/type';

.invoiceBreakdown {
display: grid;
grid-template-columns: 1fr 1fr;
align-items: flex-end;
}

.label {
@include type.type-style('heading-02');
color: colors.$gray-70;
margin: layout.$spacing-01;
text-align: end;
}

.value {
@include type.type-style('heading-02');
margin-top: layout.$spacing-04;
text-align: start;
margin-left: layout.$spacing-02;
color: colors.$black;
}
Loading

0 comments on commit 6d41aff

Please sign in to comment.