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) SJT-107 Billing - Integrate Billing with Lab orders, drug orde… #96

Merged
merged 4 commits into from
Dec 5, 2024
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
4 changes: 2 additions & 2 deletions packages/esm-billing-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ehospital/esm-billing-app",
"version": "1.1.0",
"version": "1.1.3",
"description": "Billing frontend module for use in O3",
"browser": "dist/ehospital-esm-billing-app.js",
"main": "src/index.ts",
Expand Down Expand Up @@ -120,5 +120,5 @@
"*.{js,jsx,ts,tsx}": "eslint --cache --fix"
},
"packageManager": "[email protected]",
"gitHead": "b5e50a7d0160032b1d3f20dec5d5eaf83717fafc"
"gitHead": "536537e2d9cdf3b676cdbeaaa3c32a9d3189b19a"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useBillableItem, useSockItemInventory } from '../useBillableItem';
import { useTranslation } from 'react-i18next';
import styles from './drug-order.scss';
import { convertToCurrency } from '../../../helpers';
import { useConfig } from '@openmrs/esm-framework';

type DrugOrderProps = {
order: {
Expand All @@ -25,6 +26,7 @@ const DrugOrder: React.FC<DrugOrderProps> = ({ order }) => {
const { t } = useTranslation();
const { stockItem, isLoading: isLoadingInventory } = useSockItemInventory(order?.drug?.uuid);
const { billableItem, isLoading } = useBillableItem(order?.drug.concept.uuid);
const {defaultCurrency} = useConfig()
if (isLoading || isLoadingInventory) {
return null;
}
Expand Down Expand Up @@ -53,7 +55,7 @@ const DrugOrder: React.FC<DrugOrderProps> = ({ order }) => {
billableItem?.servicePrices.map((item) => (
<div key={item.uuid} className={styles.itemContainer}>
<span className={styles.bold}>{t('unitPrice', 'Unit price ')}</span>
<span>{convertToCurrency(item.price)}</span>
<span>{convertToCurrency(item.price, defaultCurrency)}</span>
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Tile,
InlineNotification,
} from '@carbon/react';
import { useConfig } from '@openmrs/esm-framework';

type PriceInfoOrderProps = {
billableItem: any;
Expand All @@ -19,7 +20,7 @@ type PriceInfoOrderProps = {

const PriceInfoOrder: React.FC<PriceInfoOrderProps> = ({ billableItem, error }) => {
const { t } = useTranslation();

const {defaultCurrency} = useConfig()
const hasPrice = billableItem?.servicePrices?.length > 0;

if (error || !hasPrice) {
Expand All @@ -46,7 +47,7 @@ const PriceInfoOrder: React.FC<PriceInfoOrderProps> = ({ billableItem, error })
{billableItem.servicePrices.map((priceItem: any) => (
<StructuredListRow key={priceItem.uuid}>
<StructuredListCell className={styles.cell}>{priceItem.paymentMode.name}</StructuredListCell>
<StructuredListCell className={styles.cell}>{convertToCurrency(priceItem.price)}</StructuredListCell>
<StructuredListCell className={styles.cell}>{convertToCurrency(priceItem.price, defaultCurrency)}</StructuredListCell>
</StructuredListRow>
))}
</StructuredListBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type BillableItemResponse = {
export const useBillableItem = (billableItemId: string) => {
const customRepresentation = `v=custom:(uuid,name,concept:(uuid,display),servicePrices:(uuid,price,paymentMode:(uuid,name)))`;
const { data, error, isLoading } = useSWRImmutable<{ data: { results: Array<BillableItemResponse> } }>(
`${restBaseUrl}/cashier/billableService?${customRepresentation}`,
`${restBaseUrl}/billing/billableService?${customRepresentation}`,
openmrsFetch,
);
const billableItem = data?.data?.results?.find((item) => item?.concept?.uuid === billableItemId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import {
Button,
ComboButton,
DataTable,
DataTableSkeleton,
InlineLoading,
MenuItem,
OverflowMenu,
OverflowMenuItem,
Pagination,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
TableToolbar,
TableToolbarContent,
TableToolbarSearch,
} from '@carbon/react';
import { Add, CategoryAdd, Download, Upload, WatsonHealthScalpelSelect } from '@carbon/react/icons';
import { ErrorState, launchWorkspace, showModal, useLayoutType, usePagination, useConfig } from '@openmrs/esm-framework';
import { EmptyState, usePaginationInfo } from '@openmrs/esm-patient-common-lib';
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { convertToCurrency } from '../../helpers';
import styles from './charge-summary-table.scss';
import { useChargeSummaries } from './charge-summary.resource';
import { searchTableData } from './form-helper';

const defaultPageSize = 10;

const ChargeSummaryTable: React.FC = () => {
const { t } = useTranslation();
const layout = useLayoutType();
const size = layout === 'tablet' ? 'lg' : 'md';
const { isLoading, isValidating, error, mutate, chargeSummaryItems } = useChargeSummaries();
const [pageSize, setPageSize] = useState(defaultPageSize);
const [searchString, setSearchString] = useState('');
const {defaultCurrency} = useConfig()

const searchResults = useMemo(
() => searchTableData(chargeSummaryItems, searchString),
[chargeSummaryItems, searchString],
);

const { results, goTo, currentPage } = usePagination(searchResults, pageSize);
const { pageSizes } = usePaginationInfo(defaultPageSize, chargeSummaryItems.length, currentPage, results.length);

const headers = [
{
key: 'name',
header: t('name', 'Name'),
},
{
key: 'shortName',
header: t('shortName', 'Short Name'),
},
{
key: 'serviceStatus',
header: t('status', 'Status'),
},
{
key: 'serviceType',
header: t('type', 'Type'),
},
{
key: 'servicePrices',
header: t('prices', 'Prices'),
},
];

const rows = results.map((service) => {
return {
id: service.uuid,
name: service.name,
shortName: service.shortName,
serviceStatus: service.serviceStatus,
serviceType: service?.serviceType?.display ?? t('stockItem', 'Stock Item'),
servicePrices: service.servicePrices
.map((price) => `${price.name} : ${convertToCurrency(price.price, defaultCurrency)}`)
.join(', '),
};
});

// TODO: Implement handleDelete
const handleDelete = (service) => {};

const handleEdit = (service) => {
Boolean(service?.serviceType?.display)
? launchWorkspace('billable-service-form', {
initialValues: service,
workspaceTitle: t('editServiceChargeItem', 'Edit Service Charge Item'),
})
: launchWorkspace('commodity-form', {
initialValues: service,
workspaceTitle: t('editChargeItem', 'Edit Charge Item'),
});
};

const openBulkUploadModal = () => {
const dispose = showModal('bulk-import-billable-services-modal', {
closeModal: () => dispose(),
});
};

if (isLoading) {
return <DataTableSkeleton headers={headers} aria-label="sample table" showHeader={false} showToolbar={false} />;
}

if (error) {
return <ErrorState error={error} headerTitle={t('billableServicesError', 'Billable services error')} />;
}

if (!chargeSummaryItems.length) {
return (
<EmptyState
headerTitle={t('chargeItems', 'Charge Items')}
launchForm={() => launchWorkspace('billable-service-form')}
displayText={t('chargeItemsDescription', 'Charge Items')}
/>
);
}

return (
<>
<DataTable size={size} useZebraStyles rows={rows} headers={headers}>
{({ rows, headers, getHeaderProps, getRowProps, getTableProps, getToolbarProps, getTableContainerProps }) => (
<TableContainer className={styles.tableContainer} {...getTableContainerProps()}>
<TableToolbar {...getToolbarProps()} aria-label="data table toolbar">
<TableToolbarContent>
<TableToolbarSearch
placeHolder={t('searchForChargeItem', 'Search for charge item')}
onChange={(e) => setSearchString(e.target.value)}
persistent
size={size}
/>
{isValidating && (
<InlineLoading status="active" iconDescription="Loading" description="Loading data..." />
)}
<ComboButton tooltipAlignment="left" label={t('actions', 'Action')}>
<MenuItem
renderIcon={CategoryAdd}
onClick={() => launchWorkspace('billable-service-form')}
label={t('addServiceChargeItem', 'Add charge service')}
/>
<MenuItem
renderIcon={WatsonHealthScalpelSelect}
onClick={() => launchWorkspace('commodity-form')}
label={t('addCommodityChargeItem', 'Add charge item')}
/>
<MenuItem onClick={openBulkUploadModal} label={t('bulkUpload', 'Bulk Upload')} renderIcon={Upload} />
<MenuItem
label={t('downloadTemplate', 'Download template')}
renderIcon={Download}
/>
</ComboButton>
</TableToolbarContent>
</TableToolbar>
<Table {...getTableProps()} aria-label={t('chargeItem', 'Charge items table')}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader
key={header.key}
{...getHeaderProps({
header,
})}>
{header.header}
</TableHeader>
))}
<TableHeader aria-label={t('overflowMenu', 'Overflow menu')} />
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow
key={row.id}
{...getRowProps({
row,
})}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value}</TableCell>
))}
<TableCell className="cds--table-column-menu">
<OverflowMenu size={size} flipped>
<OverflowMenuItem
itemText={t('editChargeItem', 'Edit charge item')}
onClick={() => handleEdit(results[index])}
/>
</OverflowMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</DataTable>
<Pagination
backwardText={t('previousPage', 'Previous page')}
forwardText={t('nextPage', 'Next page')}
itemsPerPageText={t('itemsPerPage', 'Items per page')}
onChange={({ page, pageSize }) => {
setPageSize(pageSize);
goTo(page);
}}
page={currentPage}
pageSize={defaultPageSize}
pageSizes={pageSizes}
size="sm"
totalItems={chargeSummaryItems.length}
/>
</>
);
};

export default ChargeSummaryTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@use '@carbon/layout';

.tableContainer {
display: flex;
flex-direction: column;

& > section {
position: relative;
margin-bottom: layout.$spacing-01;
}
}

.iconMarginLeft {
margin-left: layout.$spacing-05;
}

.bulkUploadButton {
margin-right: layout.$spacing-05;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWR from 'swr';

export type ChargeAble = {
uuid: string;
name: string;
shortName: string;
serviceStatus: 'ENABLED' | 'DISABLED';
stockItem: string;
serviceType: {
uuid: string;
display: string;
};
servicePrices: Array<{
uuid: string;
name: string;
price: number;
}>;
concept: {
uuid: string;
display: string;
};
};

type ChargeAblesResponse = {
results: Array<ChargeAble>;
};

export const useChargeSummaries = () => {
const url = `${restBaseUrl}/billing/billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,paymentMode,price),concept:(uuid,display))`;
const { data, isLoading, isValidating, error, mutate } = useSWR<{ data: ChargeAblesResponse }>(url, openmrsFetch, {
errorRetryCount: 0,
});

return {
chargeSummaryItems: data?.data?.results ?? [],
isLoading,
isValidating,
error,
mutate,
};
};
Loading
Loading