diff --git a/src/bill-history/bill-history.component.tsx b/src/bill-history/bill-history.component.tsx index 8b44f79..02b66ec 100644 --- a/src/bill-history/bill-history.component.tsx +++ b/src/bill-history/bill-history.component.tsx @@ -18,8 +18,9 @@ import { TableRow, Tile, } from '@carbon/react'; -import { isDesktop, useLayoutType, usePagination } from '@openmrs/esm-framework'; +import { isDesktop, useConfig, useLayoutType, usePagination } from '@openmrs/esm-framework'; import { + CardHeader, EmptyDataIllustration, ErrorState, launchPatientWorkspace, @@ -28,19 +29,20 @@ import { import { useBills } from '../billing.resource'; import InvoiceTable from '../invoice/invoice-table.component'; import styles from './bill-history.scss'; +import { Add } from '@carbon/react/icons'; +import { convertToCurrency } from '../helpers'; interface BillHistoryProps { patientUuid: string; } const BillHistory: React.FC = ({ patientUuid }) => { - const PAGE_SIZE = 10; const { t } = useTranslation(); const { bills, isLoading, error } = useBills(patientUuid); const layout = useLayoutType(); const responsiveSize = isDesktop(layout) ? 'sm' : 'lg'; - const { paginated, goTo, results, currentPage } = usePagination(bills, PAGE_SIZE); - const { pageSizes } = usePaginationInfo(PAGE_SIZE, bills?.length, currentPage, results?.length); + const { paginated, goTo, results, currentPage } = usePagination(bills); + const { pageSize } = useConfig(); const headerData = [ { @@ -67,7 +69,7 @@ const BillHistory: React.FC = ({ patientUuid }) => { const rowData = results?.map((bill) => ({ id: bill.uuid, uuid: bill.uuid, - billTotal: bill.totalAmount, + billTotal: convertToCurrency(bill.totalAmount), visitTime: bill.dateCreated, identifier: bill.identifier, billedItems: setBilledItems(bill), @@ -108,79 +110,88 @@ const BillHistory: React.FC = ({ patientUuid }) => { } return ( -
- - {({ - rows, - headers, - getExpandHeaderProps, - getTableProps, - getTableContainerProps, - getHeaderProps, - getRowProps, - }) => ( - - - - - - {headers.map((header, i) => ( - - {header.header} - - ))} - - - - {rows.map((row, i) => { - const currentBill = bills?.find((bill) => bill.uuid === row.id); + <> + + + +
+ + {({ + rows, + headers, + getExpandHeaderProps, + getTableProps, + getTableContainerProps, + getHeaderProps, + getRowProps, + }) => ( + +
+ + + + {headers.map((header, i) => ( + + {header.header} + + ))} + + + + {rows.map((row, i) => { + const currentBill = bills?.find((bill) => bill.uuid === row.id); - return ( - - - {row.cells.map((cell) => ( - {cell.value} - ))} - - {row.isExpanded ? ( - -
- -
-
- ) : ( - - )} -
- ); - })} -
-
-
+ return ( + + + {row.cells.map((cell) => ( + {cell.value} + ))} + + {row.isExpanded ? ( + +
+ +
+
+ ) : ( + + )} +
+ ); + })} + + + + )} +
+ {paginated && ( + { + if (newPage !== currentPage) { + goTo(newPage); + } + }} + /> )} - - {paginated && ( - { - if (newPage !== currentPage) { - goTo(newPage); - } - }} - /> - )} -
+ + ); }; diff --git a/src/billing-form/billing-form.component.tsx b/src/billing-form/billing-form.component.tsx index f0ed760..319cd63 100644 --- a/src/billing-form/billing-form.component.tsx +++ b/src/billing-form/billing-form.component.tsx @@ -1,23 +1,257 @@ -import React from 'react'; -import { RadioButtonGroup, RadioButton } from '@carbon/react'; -import { useTranslation } from 'react-i18next'; +import React, { useState, useRef } from 'react'; +import { + ButtonSet, + Button, + RadioButtonGroup, + RadioButton, + Search, + Table, + TableHead, + TableBody, + TableHeader, + TableRow, + TableCell, +} from '@carbon/react'; import styles from './billing-form.scss'; +import { useTranslation } from 'react-i18next'; +import { showSnackbar, useConfig } from '@openmrs/esm-framework'; +import { useFetchSearchResults, processBillItems } from '../billing.resource'; +import { mutate } from 'swr'; +import { convertToCurrency } from '../helpers'; type BillingFormProps = { patientUuid: string; + closeWorkspace: () => void; }; -const BillingForm: React.FC = ({ patientUuid }) => { +const BillingForm: React.FC = ({ patientUuid, closeWorkspace }) => { const { t } = useTranslation(); + + const [grandTotal, setGrandTotal] = useState(0); + const [searchOptions, setSearchOptions] = useState([]); + const [billItems, setBillItems] = useState([]); + const [searchVal, setSearchVal] = useState(''); + const [category, setCategory] = useState(''); + + const toggleSearch = (choiceSelected) => { + (document.getElementById('searchField') as HTMLInputElement).disabled = false; + setCategory(choiceSelected === 'Stock Item' ? 'Stock Item' : 'Service'); + }; + + const calculateTotal = (event, itemName) => { + const quantity = parseInt(event.target.value); + const updatedItems = billItems.map((item) => { + if (item.Item.toLowerCase().includes(itemName.toLowerCase())) { + const price = item.Price; + const total = price * quantity; + return { ...item, Qnty: quantity, Total: total }; + } + return item; + }); + + setBillItems(updatedItems); + + const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0); + setGrandTotal(updatedGrandTotal); + }; + + const calculateTotalAfterAddBillItem = () => { + const sum = billItems.reduce((acc, item) => acc + item.Price, 0); + setGrandTotal(sum); + }; + + const addItemToBill = (event, itemid, itemname, itemcategory, itemPrice) => { + const newItem = { + uuid: itemid, + Item: itemname, + Qnty: 1, + Price: itemPrice, + Total: itemPrice, + category: itemcategory, + }; + + setBillItems([...billItems, newItem]); + setSearchOptions([]); + calculateTotalAfterAddBillItem(); + (document.getElementById('searchField') as HTMLInputElement).value = ''; + }; + + const { data, error, isLoading, isValidating } = useFetchSearchResults(searchVal, category); + + const filterItems = (val) => { + setSearchVal(val); + + if (!isLoading && data) { + const res = data as { results: any[] }; + + const options = res.results.map((o) => { + if (o.commonName && o.commonName !== '') { + return { + uuid: o.uuid || '', + Item: o.commonName, + Qnty: 1, + Price: 10, + Total: 10, + category: 'StockItem', + }; + } else if (o.name.toLowerCase().includes(val.toLowerCase())) { + return { + uuid: o.uuid || '', + Item: o.name, + Qnty: 1, + Price: o.servicePrices[0].price, + Total: o.servicePrices[0].price, + category: 'Service', + }; + } + }); + + setSearchOptions(options.filter((option) => option)); // Filter out undefined/null values + } + }; + + const postBillItems = () => { + const bill = { + cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590', + cashier: 'f9badd80-ab76-11e2-9e96-0800200c9a66', + lineItems: [], + payments: [], + patient: patientUuid, + status: 'PENDING', + }; + + billItems.forEach((item) => { + const lineItem: any = { + quantity: parseInt(item.Qnty), + price: item.Price, + priceName: 'Default', + priceUuid: '7b9171ac-d3c1-49b4-beff-c9902aee5245', + lineItemOrder: 0, + paymentStatus: 'PENDING', + }; + + if (item.category === 'StockItem') { + lineItem.item = item.uuid; + } else { + lineItem.billableService = item.uuid; + } + + bill.lineItems.push(lineItem); + }); + + const url = `/ws/rest/v1/cashier/bill`; + processBillItems(bill).then( + () => { + closeWorkspace(); + mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true }); + showSnackbar({ + title: t('billItems', 'Save Bill'), + subtitle: 'Bill processing has been successful', + kind: 'success', + timeoutInMs: 3000, + }); + }, + (error) => { + showSnackbar({ title: 'Bill processing error', kind: 'error', subtitle: error }); + }, + ); + }; + return (
- - + defaultSelected="radio-1" + className={styles.billingItem} + onChange={toggleSearch}> + + + +
+ {}} + className={styles.billingItem} + onKeyUp={(e) => { + filterItems(e.target.value); + }} + /> + +
    + {searchOptions.map((row) => ( +
  • + +
  • + ))} +
+
+ + + + + Item + Quantity + Price + Total + + + + {billItems && Array.isArray(billItems) ? ( + billItems.map((row) => ( + + {row.Item} + + { + calculateTotal(e, row.Item); + row.Qnty = e.target.value; + }} + /> + + {row.Price} + + {row.Total} + + + )) + ) : ( +

Loading...

+ )} + + + + Grand Total: + {convertToCurrency(grandTotal)} + +
+
+ + + + +
); }; diff --git a/src/billing-form/billing-form.scss b/src/billing-form/billing-form.scss index 30150b8..ea78569 100644 --- a/src/billing-form/billing-form.scss +++ b/src/billing-form/billing-form.scss @@ -3,3 +3,23 @@ .billingFormContainer { padding: layout.$spacing-05; } + +.billingItem { + margin-top: 30px; +} + +.searchContent { + position: absolute; + background-color: #fff; + min-width: 230px; + overflow: auto; + border: 1px solid #ddd; + z-index: 1; + width: 92%; +} + +.searchItem { + border-bottom: 0.1rem solid; + border-bottom-color: silver; + cursor: pointer; +} \ No newline at end of file diff --git a/src/billing.resource.ts b/src/billing.resource.ts index c1c5032..a3675f4 100644 --- a/src/billing.resource.ts +++ b/src/billing.resource.ts @@ -118,3 +118,26 @@ export const usePatientPaymentInfo = (patientUuid: string) => { return paymentInformation; }; + +export function useFetchSearchResults(searchVal, category) { + let url = ``; + if (category == 'Stock Item') { + url = `/ws/rest/v1/stockmanagement/stockitem?v=default&limit=10&q=${searchVal}`; + } else { + url = `/ws/rest/v1/cashier/billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`; + } + const { data, error, isLoading, isValidating } = useSWR(searchVal ? url : null, openmrsFetch, {}); + + return { data: data?.data, error, isLoading: isLoading, isValidating }; +} + +export const processBillItems = (payload) => { + const url = `/ws/rest/v1/cashier/bill`; + return openmrsFetch(url, { + method: 'POST', + body: payload, + headers: { + 'Content-Type': 'application/json', + }, + }); +}; diff --git a/src/config-schema.ts b/src/config-schema.ts index 2d7cb38..f47328a 100644 --- a/src/config-schema.ts +++ b/src/config-schema.ts @@ -33,10 +33,18 @@ export const configSchema = { _description: 'The default currency for the application. Specify the currency code (e.g., KES, UGX, GBP).', _default: 'KES', }, + + pageSize: { + _type: Type.String, + _description: 'The default page size', + _default: 10, + }, }; export interface ConfigObject { patientCatergory: Object; defaultCurrency: string; catergoryConcepts: Object; + pageSize; + object; } diff --git a/translations/am.json b/translations/am.json index 4954da7..65a5eb6 100644 --- a/translations/am.json +++ b/translations/am.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", diff --git a/translations/en.json b/translations/en.json index 4954da7..65a5eb6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", diff --git a/translations/es.json b/translations/es.json index 4954da7..65a5eb6 100644 --- a/translations/es.json +++ b/translations/es.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", diff --git a/translations/fr.json b/translations/fr.json index 4954da7..65a5eb6 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", diff --git a/translations/he.json b/translations/he.json index 4954da7..65a5eb6 100644 --- a/translations/he.json +++ b/translations/he.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered", diff --git a/translations/km.json b/translations/km.json index 4954da7..65a5eb6 100644 --- a/translations/km.json +++ b/translations/km.json @@ -1,5 +1,6 @@ { "actions": "Actions", + "addBill": "Add bill item(s)", "addBillableServices": "Add Billable Services", "addNewBillableService": "Add new billable service", "addNewService": "Add new service", @@ -14,6 +15,7 @@ "billedTo": "Billed to", "billErrorService": "Bill service error", "billing": "Billing", + "billingHistory": "Billing History", "billItem": "Bill item", "billItems": "Bill Items", "billList": "Bill list", @@ -31,7 +33,6 @@ "checkFilters": "Check the filters above", "discard": "Discard", "discount": "Discount", - "drug": "Drug", "enterAmount": "Enter amount", "enterConcept": "Associated concept", "enterReferenceNumber": "Enter ref. number", @@ -60,7 +61,6 @@ "noMatchingBillsToDisplay": "No matching bills to display", "noMatchingItemsToDisplay": "No matching items to display", "noMatchingServicesToDisplay": "No matching services to display", - "nonDrug": "Non drug", "noResultsFor": "No results for", "noServicesToDisplay": "There are no services to display", "ok": "OK", @@ -91,6 +91,7 @@ "selectPaymentMethod": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", + "service": "Service", "serviceMetrics": "Service Metrics", "serviceName": "Service Name", "serviceShortName": "Short Name", @@ -98,6 +99,7 @@ "serviceType": "Service Type", "shortName": "Short Name", "status": "Service Status", + "stockItem": "Stock Item", "total": "Total", "totalAmount": "Total Amount", "totalTendered": "Total Tendered",