@openmrs/esm-billing-app 1.0.2-pre.666 → 1.0.2-pre.673

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/1119.js +1 -1
  2. package/dist/1197.js +1 -1
  3. package/dist/2146.js +1 -1
  4. package/dist/2690.js +1 -1
  5. package/dist/3099.js +1 -1
  6. package/dist/3584.js +1 -1
  7. package/dist/4055.js +1 -1
  8. package/dist/4132.js +1 -1
  9. package/dist/4300.js +1 -1
  10. package/dist/4335.js +1 -1
  11. package/dist/4618.js +1 -1
  12. package/dist/4652.js +1 -1
  13. package/dist/4739.js +1 -1
  14. package/dist/4739.js.map +1 -1
  15. package/dist/4944.js +1 -1
  16. package/dist/5173.js +1 -1
  17. package/dist/5241.js +1 -1
  18. package/dist/5442.js +1 -1
  19. package/dist/5661.js +1 -1
  20. package/dist/6022.js +1 -1
  21. package/dist/6468.js +1 -1
  22. package/dist/6679.js +1 -1
  23. package/dist/6840.js +1 -1
  24. package/dist/6859.js +1 -1
  25. package/dist/7097.js +1 -1
  26. package/dist/7159.js +1 -1
  27. package/dist/723.js +1 -1
  28. package/dist/7617.js +1 -1
  29. package/dist/795.js +1 -1
  30. package/dist/8163.js +1 -1
  31. package/dist/8349.js +1 -1
  32. package/dist/8618.js +1 -1
  33. package/dist/8638.js +1 -1
  34. package/dist/8638.js.map +1 -1
  35. package/dist/890.js +1 -1
  36. package/dist/9214.js +1 -1
  37. package/dist/9538.js +1 -1
  38. package/dist/9569.js +1 -1
  39. package/dist/986.js +1 -1
  40. package/dist/9879.js +1 -1
  41. package/dist/9895.js +1 -1
  42. package/dist/9900.js +1 -1
  43. package/dist/9913.js +1 -1
  44. package/dist/main.js +1 -1
  45. package/dist/main.js.map +1 -1
  46. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +127 -127
  47. package/dist/routes.json +1 -1
  48. package/package.json +1 -1
  49. package/src/billing-form/billing-form.component.tsx +109 -261
  50. package/src/billing-form/billing-form.scss +3 -0
  51. package/src/billing.resource.ts +5 -12
  52. package/src/types/index.ts +13 -12
  53. package/translations/am.json +1 -0
  54. package/translations/ar.json +1 -0
  55. package/translations/ar_SY.json +1 -0
  56. package/translations/bn.json +1 -0
  57. package/translations/de.json +1 -0
  58. package/translations/en.json +0 -6
  59. package/translations/en_US.json +1 -0
  60. package/translations/es.json +1 -0
  61. package/translations/es_MX.json +1 -0
  62. package/translations/fr.json +1 -0
  63. package/translations/he.json +1 -0
  64. package/translations/hi.json +1 -0
  65. package/translations/hi_IN.json +1 -0
  66. package/translations/id.json +1 -0
  67. package/translations/it.json +1 -0
  68. package/translations/ka.json +1 -0
  69. package/translations/km.json +1 -0
  70. package/translations/ku.json +1 -0
  71. package/translations/ky.json +1 -0
  72. package/translations/lg.json +1 -0
  73. package/translations/ne.json +1 -0
  74. package/translations/pl.json +1 -0
  75. package/translations/pt.json +1 -0
  76. package/translations/pt_BR.json +1 -0
  77. package/translations/qu.json +1 -0
  78. package/translations/ro_RO.json +1 -0
  79. package/translations/ru_RU.json +1 -0
  80. package/translations/si.json +1 -0
  81. package/translations/sw.json +1 -0
  82. package/translations/sw_KE.json +1 -0
  83. package/translations/tr.json +1 -0
  84. package/translations/tr_TR.json +1 -0
  85. package/translations/uk.json +1 -0
  86. package/translations/uz.json +1 -0
  87. package/translations/uz@Latn.json +1 -0
  88. package/translations/uz_UZ.json +1 -0
  89. package/translations/vi.json +1 -0
  90. package/translations/zh.json +1 -0
  91. package/translations/zh_CN.json +1 -0
package/dist/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.666"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":">=2.24.0","fhir2":">=1.2"},"pages":[{"component":"billableServicesHome","route":"billable-services"}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"},"featureFlag":"billing"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"name":"billing-patient-summary","component":"billingPatientSummary","slot":"patient-chart-billing-dashboard-slot","order":10,"meta":{"columnSpan":4}},{"name":"billing-summary-dashboard-link","component":"billingSummaryDashboardLink","slot":"patient-chart-dashboard-slot","order":11,"meta":{"columns":1,"columnSpan":1,"slot":"patient-chart-billing-dashboard-slot","path":"Billing history"},"featureFlag":"billing"},{"name":"billable-services-app-menu-item","component":"billableServicesAppMenuItem","slot":"app-menu-item-slot","meta":{"name":"Billable Services"}},{"name":"billing-checkin-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","featureFlag":"billing"},{"slot":"system-admin-page-card-link-slot","component":"billableServicesCardLink","name":"billable-services-admin-card-link"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"billing-home-tiles-ext","slot":"billing-home-tiles-slot","component":"serviceMetrics"},{"name":"edit-bill-line-item-dialog","component":"editBillLineItemDialog","online":true,"offline":true}],"modals":[{"name":"require-billing-modal","component":"requirePaymentModal"}],"workspaces":[{"name":"billing-form-workspace","title":"billingForm","component":"billingFormWorkspace","type":"form"}],"featureFlags":[{"flagName":"billing","label":"Billing module","description":"This feature introduces navigation links on the patient chart and home page to allow accessing the billing module features"}],"version":"1.0.2-pre.673"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-billing-app",
3
- "version": "1.0.2-pre.666",
3
+ "version": "1.0.2-pre.673",
4
4
  "description": "O3 frontend module for handling billing concerns in healthcare settings",
5
5
  "browser": "dist/openmrs-esm-billing-app.js",
6
6
  "main": "src/index.ts",
@@ -1,31 +1,28 @@
1
- import React, { useState, useEffect, useMemo } from 'react';
2
- import fuzzy from 'fuzzy';
3
- import isEmpty from 'lodash-es/isEmpty';
1
+ import React, { useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { mutate } from 'swr';
4
4
  import {
5
5
  Button,
6
6
  ButtonSet,
7
+ ComboBox,
7
8
  Form,
9
+ NumberInput,
8
10
  InlineLoading,
9
- RadioButton,
10
- RadioButtonGroup,
11
- Search,
12
- Stack,
11
+ InlineNotification,
13
12
  Table,
14
- TableBody,
15
- TableCell,
16
13
  TableHead,
17
- TableHeader,
14
+ TableBody,
18
15
  TableRow,
16
+ TableHeader,
17
+ TableCell,
19
18
  } from '@carbon/react';
20
19
  import { TrashCan } from '@carbon/react/icons';
21
- import { mutate } from 'swr';
22
- import { useTranslation } from 'react-i18next';
23
- import { z } from 'zod';
24
- import { showSnackbar, showToast, useConfig, useDebounce, useLayoutType } from '@openmrs/esm-framework';
20
+ import { useConfig, useLayoutType, showSnackbar } from '@openmrs/esm-framework';
21
+ import { processBillItems, useBillableServices } from '../billing.resource';
22
+ import { calculateTotalAmount, convertToCurrency } from '../helpers/functions';
23
+ import type { BillingConfig } from '../config-schema';
24
+ import type { BillableItem, LineItem } from '../types';
25
25
  import { apiBasePath } from '../constants';
26
- import { convertToCurrency } from '../helpers';
27
- import { type BillabeItem } from '../types';
28
- import { useFetchSearchResults, processBillItems } from '../billing.resource';
29
26
  import styles from './billing-form.scss';
30
27
 
31
28
  type BillingFormProps = {
@@ -34,150 +31,47 @@ type BillingFormProps = {
34
31
  };
35
32
 
36
33
  const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }) => {
37
- const { t } = useTranslation();
38
- const { defaultCurrency, postBilledItems } = useConfig();
39
34
  const isTablet = useLayoutType() === 'tablet';
40
-
41
- const [grandTotal, setGrandTotal] = useState(0);
42
- const [searchOptions, setSearchOptions] = useState([]);
43
- const [billItems, setBillItems] = useState([]);
44
- const [category, setCategory] = useState('');
45
- const [saveDisabled, setSaveDisabled] = useState<boolean>(false);
35
+ const { t } = useTranslation();
36
+ const { defaultCurrency, postBilledItems } = useConfig<BillingConfig>();
46
37
  const [isSubmitting, setIsSubmitting] = useState(false);
47
- const [addedItems, setAddedItems] = useState([]);
48
- const [searchTerm, setSearchTerm] = useState('');
49
- const debouncedSearchTerm = useDebounce(searchTerm);
50
- const [disableSearch, setDisableSearch] = useState<boolean>(true);
51
-
52
- const toggleSearch = (choiceSelected) => {
53
- if (!isEmpty(choiceSelected)) {
54
- setDisableSearch(false);
55
- }
56
- setCategory(choiceSelected === 'Stock Item' ? 'Stock Item' : 'Service');
57
- };
58
-
59
- const billItemSchema = z.object({
60
- Qnty: z.number().min(1, t('quantityGreaterThanZero', 'Quantity must be at least one for all items.')), // zod logic
61
- });
62
-
63
- const calculateTotal = (event, itemName) => {
64
- const quantity = parseInt(event.target.value);
65
- let isValid = true;
66
-
67
- try {
68
- billItemSchema.parse({ Qnty: quantity });
69
- } catch (error) {
70
- isValid = false;
71
- const parsedErrorMessage = JSON.parse(error.message);
72
- showToast({
73
- title: t('saveBill', 'Save Bill'),
74
- kind: 'error',
75
- description: parsedErrorMessage[0].message,
76
- });
38
+ const [selectedItems, setSelectedItems] = useState<LineItem[]>([]);
39
+ const { data, error, isLoading } = useBillableServices();
40
+
41
+ const selectBillableItem = (item: BillableItem) => {
42
+ if (!item) return;
43
+ const existingItem = selectedItems.find((selectedItem) => selectedItem.uuid === item.uuid);
44
+
45
+ if (existingItem) {
46
+ const updatedItem = { ...existingItem, quantity: existingItem.quantity + 1 };
47
+ setSelectedItems(
48
+ [...selectedItems].map((selectedItem) => (selectedItem.uuid === item.uuid ? updatedItem : selectedItem)),
49
+ );
50
+ return;
77
51
  }
78
52
 
79
- const updatedItems = billItems.map((item) => {
80
- if (item.Item.toLowerCase().includes(itemName.toLowerCase())) {
81
- return { ...item, Qnty: quantity, Total: quantity > 0 ? item.Price * quantity : 0 };
82
- }
83
- return item;
84
- });
85
-
86
- const anyInvalidQuantity = updatedItems.some((item) => item.Qnty <= 0);
87
-
88
- setSaveDisabled(!isValid || anyInvalidQuantity);
89
-
90
- setBillItems(updatedItems);
91
-
92
- const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
93
- setGrandTotal(updatedGrandTotal);
94
- };
95
-
96
- const calculateTotalAfterAddBillItem = (items) => {
97
- const sum = items.reduce((acc, item) => acc + item.Price * item.Qnty, 0);
98
- setGrandTotal(sum);
53
+ const mappedItem: LineItem = {
54
+ uuid: item.uuid,
55
+ display: item.name,
56
+ quantity: 1,
57
+ price: item.servicePrices?.length > 0 ? parseFloat(item.servicePrices?.[0]?.price) : 0,
58
+ billableService: item.uuid,
59
+ paymentStatus: 'PENDING',
60
+ lineItemOrder: 0,
61
+ };
62
+ setSelectedItems([...selectedItems, mappedItem]);
99
63
  };
100
64
 
101
- const addItemToBill = (event, itemid, itemname, itemcategory, itemPrice) => {
102
- const existingItemIndex = billItems.findIndex((item) => item.uuid === itemid);
103
-
104
- let updatedItems = [];
105
- if (existingItemIndex >= 0) {
106
- updatedItems = billItems.map((item, index) => {
107
- if (index === existingItemIndex) {
108
- const updatedQuantity = item.Qnty + 1;
109
- return { ...item, Qnty: updatedQuantity, Total: updatedQuantity * item.Price };
110
- }
111
- return item;
112
- });
113
- } else {
114
- const newItem = {
115
- uuid: itemid,
116
- Item: itemname,
117
- Qnty: 1,
118
- Price: itemPrice,
119
- Total: itemPrice,
120
- category: itemcategory,
121
- };
122
- updatedItems = [...billItems, newItem];
123
- setAddedItems([...addedItems, newItem]);
124
- }
125
-
126
- setBillItems(updatedItems);
127
- calculateTotalAfterAddBillItem(updatedItems);
128
- (document.getElementById('searchField') as HTMLInputElement).value = '';
65
+ const updateQuantity = (uuid: string, quantity: number) => {
66
+ const updatedItems = [...selectedItems].map((item) => (item.uuid === uuid ? { ...item, quantity } : item));
67
+ setSelectedItems(updatedItems);
129
68
  };
130
69
 
131
- const removeItemFromBill = (uuid) => {
132
- const updatedItems = billItems.filter((item) => item.uuid !== uuid);
133
- setBillItems(updatedItems);
134
-
135
- // Update the list of added items
136
- setAddedItems(addedItems.filter((item) => item.uuid !== uuid));
137
-
138
- const updatedGrandTotal = updatedItems.reduce((acc, item) => acc + item.Total, 0);
139
- setGrandTotal(updatedGrandTotal);
70
+ const removeSelectedBillableItem = (uuid: string) => {
71
+ const updatedItems = [...selectedItems].filter((item) => item.uuid !== uuid);
72
+ setSelectedItems(updatedItems);
140
73
  };
141
74
 
142
- const { data, error, isLoading, isValidating } = useFetchSearchResults(debouncedSearchTerm, category);
143
-
144
- const handleSearchTermChange = (e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value);
145
-
146
- const filterItems = useMemo(() => {
147
- if (!debouncedSearchTerm || isLoading || error) {
148
- return [];
149
- }
150
-
151
- const res = data as { results: BillabeItem[] };
152
- const existingItemUuids = new Set(billItems.map((item) => item.uuid));
153
-
154
- const preprocessedData = res?.results
155
- ?.map((item) => {
156
- return {
157
- uuid: item.uuid || '',
158
- Item: item.commonName ? item.commonName : item.name,
159
- Qnty: 1,
160
- Price: item.commonName ? 10 : item.servicePrices[0]?.price,
161
- Total: item.commonName ? 10 : item.servicePrices[0]?.price,
162
- category: item.commonName ? 'StockItem' : 'Service',
163
- };
164
- })
165
- .filter((item) => !existingItemUuids.has(item.uuid));
166
-
167
- return debouncedSearchTerm
168
- ? fuzzy
169
- .filter(debouncedSearchTerm, preprocessedData, {
170
- extract: (o) => `${o.Item}`,
171
- })
172
- .sort((r1, r2) => r1.score - r2.score)
173
- .map((result) => result.original)
174
- : searchOptions;
175
- }, [debouncedSearchTerm, isLoading, error, data, billItems, searchOptions]);
176
-
177
- useEffect(() => {
178
- setSearchOptions(filterItems);
179
- }, [filterItems]);
180
-
181
75
  const postBillItems = () => {
182
76
  setIsSubmitting(true);
183
77
  const bill = {
@@ -189,23 +83,16 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
189
83
  status: 'PENDING',
190
84
  };
191
85
 
192
- billItems.forEach((item) => {
193
- const lineItem: any = {
194
- quantity: parseInt(item.Qnty),
195
- price: item.Price,
196
- priceName: 'Default',
197
- priceUuid: postBilledItems.priceUuid,
86
+ selectedItems.forEach((item) => {
87
+ const lineItem: LineItem = {
88
+ quantity: item.quantity,
89
+ price: item.price,
198
90
  lineItemOrder: 0,
199
91
  paymentStatus: 'PENDING',
92
+ billableService: item.uuid,
200
93
  };
201
94
 
202
- if (item.category === 'StockItem') {
203
- lineItem.item = item.uuid;
204
- } else {
205
- lineItem.billableService = item.uuid;
206
- }
207
-
208
- bill?.lineItems.push(lineItem);
95
+ bill.lineItems.push(lineItem);
209
96
  });
210
97
 
211
98
  const url = `${apiBasePath}bill`;
@@ -233,57 +120,28 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
233
120
  );
234
121
  };
235
122
 
236
- const handleClearSearchTerm = () => {
237
- setSearchOptions([]);
238
- };
239
-
240
123
  return (
241
124
  <Form className={styles.form}>
242
125
  <div className={styles.grid}>
243
- <Stack>
244
- <RadioButtonGroup
245
- legendText={t('selectCategory', 'Select category')}
246
- name="radio-button-group"
247
- defaultSelected="radio-1"
248
- className={styles.mt2}
249
- onChange={toggleSearch}>
250
- <RadioButton labelText={t('stockItem', 'Stock Item')} value="Stock Item" id="stockItem" />
251
- <RadioButton labelText={t('service', 'Service')} value="Service" id="service" />
252
- </RadioButtonGroup>
253
- </Stack>
254
- <Stack>
255
- <Search
256
- size="lg"
257
- id="searchField"
258
- disabled={disableSearch}
259
- closeButtonLabelText={t('clearSearchInput', 'Clear search input')}
260
- className={styles.mt2}
261
- placeholder={t('searchItems', 'Search items and services')}
262
- labelText={t('searchItems', 'Search items and services')}
263
- onKeyUp={handleSearchTermChange}
264
- onClear={handleClearSearchTerm}
126
+ {isLoading ? (
127
+ <InlineLoading description={t('loading', 'Loading') + '...'} />
128
+ ) : error ? (
129
+ <InlineNotification
130
+ kind="error"
131
+ lowContrast
132
+ title={t('billErrorService', 'Bill service error')}
133
+ subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
265
134
  />
266
- </Stack>
267
- <Stack>
268
- <ul className={styles.searchContent}>
269
- {searchOptions?.length > 0 &&
270
- searchOptions?.map((row) => (
271
- <li key={row.uuid} className={styles.searchItem}>
272
- <Button
273
- id={row.uuid}
274
- onClick={(e) => addItemToBill(e, row.uuid, row.Item, row.category, row.Price)}
275
- style={{ background: 'inherit', color: 'black' }}>
276
- {row.Item} Qnty.{row.Qnty} {defaultCurrency}.{row.Price}
277
- </Button>
278
- </li>
279
- ))}
280
-
281
- {searchOptions?.length === 0 && !isLoading && !!debouncedSearchTerm && (
282
- <p>{t('noResultsFound', 'No results found')}</p>
283
- )}
284
- </ul>
285
- </Stack>
286
- <Stack>
135
+ ) : (
136
+ <ComboBox
137
+ id="searchItems"
138
+ onChange={({ selectedItem: item }: { selectedItem: BillableItem }) => selectBillableItem(item)}
139
+ itemToString={(item: BillableItem) => item?.name || ''}
140
+ items={data ?? []}
141
+ titleText={t('searchItems', 'Search items and services')}
142
+ />
143
+ )}
144
+ {selectedItems && selectedItems.length > 0 && (
287
145
  <Table aria-label="sample table" className={styles.mt2}>
288
146
  <TableHead>
289
147
  <TableRow>
@@ -295,63 +153,53 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
295
153
  </TableRow>
296
154
  </TableHead>
297
155
  <TableBody>
298
- {billItems && Array.isArray(billItems) ? (
299
- billItems.map((row) => (
300
- <TableRow>
301
- <TableCell>{row.Item}</TableCell>
302
- <TableCell>
303
- <input
304
- type="number"
305
- className={`form-control ${row.Qnty <= 0 ? styles.invalidInput : ''}`}
306
- id={row.Item}
307
- min={0}
308
- max={100}
309
- value={row.Qnty}
310
- onChange={(e) => {
311
- calculateTotal(e, row.Item);
312
- row.Qnty = e.target.value;
313
- }}
314
- />
315
- </TableCell>
316
- <TableCell id={row.Item + 'Price'}>{row.Price}</TableCell>
317
- <TableCell id={row.Item + 'Total'} className="totalValue">
318
- {row.Total}
319
- </TableCell>
320
- <TableCell>
321
- <TrashCan onClick={() => removeItemFromBill(row.uuid)} className={styles.removeButton} />
322
- </TableCell>
323
- </TableRow>
324
- ))
325
- ) : (
326
- <p>{t('loading', 'Loading...')}</p>
327
- )}
156
+ {selectedItems.map((row) => (
157
+ <TableRow>
158
+ <TableCell>{row.display}</TableCell>
159
+ <TableCell>
160
+ <NumberInput
161
+ id={row.uuid}
162
+ min={1}
163
+ value={row.quantity}
164
+ onChange={(_, { value }) => {
165
+ const number = parseFloat(String(value));
166
+ updateQuantity(row.uuid, isNaN(number) ? 1 : number);
167
+ }}
168
+ />
169
+ </TableCell>
170
+ <TableCell id={row.uuid + 'Price'}>{row.price}</TableCell>
171
+ <TableCell id={row.uuid + 'Total'} className="totalValue">
172
+ {row.price * row.quantity}
173
+ </TableCell>
174
+ <TableCell>
175
+ <TrashCan className={styles.removeButton} onClick={() => removeSelectedBillableItem(row.uuid)} />
176
+ </TableCell>
177
+ </TableRow>
178
+ ))}
328
179
  <TableRow>
329
180
  <TableCell colSpan={3}></TableCell>
330
181
  <TableCell style={{ fontWeight: 'bold' }}>{t('grandTotal', 'Grand total')}:</TableCell>
331
- <TableCell id="GrandTotalSum">{convertToCurrency(grandTotal, defaultCurrency)}</TableCell>
182
+ <TableCell id="GrandTotalSum">
183
+ {convertToCurrency(calculateTotalAmount(selectedItems), defaultCurrency)}
184
+ </TableCell>
332
185
  </TableRow>
333
186
  </TableBody>
334
187
  </Table>
335
- </Stack>
336
-
337
- <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
338
- <Button className={styles.button} kind="secondary" disabled={isSubmitting} onClick={closeWorkspace}>
339
- {t('discard', 'Discard')}
340
- </Button>
341
- <Button
342
- className={styles.button}
343
- kind="primary"
344
- onClick={postBillItems}
345
- disabled={isSubmitting || saveDisabled}
346
- type="submit">
347
- {isSubmitting ? (
348
- <InlineLoading description={t('saving', 'Saving') + '...'} />
349
- ) : (
350
- t('saveAndClose', 'Save and close')
351
- )}
352
- </Button>
353
- </ButtonSet>
188
+ )}
354
189
  </div>
190
+
191
+ <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
192
+ <Button className={styles.button} kind="secondary" disabled={isSubmitting || selectedItems.length === 0} onClick={closeWorkspace}>
193
+ {t('discard', 'Discard')}
194
+ </Button>
195
+ <Button className={styles.button} kind="primary" onClick={postBillItems} disabled={isSubmitting} type="submit">
196
+ {isSubmitting ? (
197
+ <InlineLoading description={t('saving', 'Saving') + '...'} />
198
+ ) : (
199
+ t('saveAndClose', 'Save and close')
200
+ )}
201
+ </Button>
202
+ </ButtonSet>
355
203
  </Form>
356
204
  );
357
205
  };
@@ -73,6 +73,9 @@
73
73
 
74
74
  .grid {
75
75
  margin: layout.$spacing-05;
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: layout.$spacing-05;
76
79
  }
77
80
 
78
81
  .row {
@@ -9,11 +9,11 @@ import {
9
9
  openmrsFetch,
10
10
  useSession,
11
11
  useVisit,
12
- restBaseUrl,
13
12
  type SessionLocation,
13
+ useOpenmrsFetchAll,
14
14
  } from '@openmrs/esm-framework';
15
15
  import { apiBasePath, omrsDateFormat } from './constants';
16
- import type { MappedBill, PatientInvoice } from './types';
16
+ import type { MappedBill, PatientInvoice, BillableItem } from './types';
17
17
  import SelectedDateContext from './hooks/selectedDateContext';
18
18
 
19
19
  export const useBills = (patientUuid: string = '', billStatus: string = '') => {
@@ -134,16 +134,9 @@ export const usePatientPaymentInfo = (patientUuid: string) => {
134
134
  return paymentInformation;
135
135
  };
136
136
 
137
- export function useFetchSearchResults(searchVal, category) {
138
- let url = ``;
139
- if (category == 'Stock Item') {
140
- url = `${restBaseUrl}/stockmanagement/stockitem?v=default&limit=10&q=${searchVal}`;
141
- } else {
142
- url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
143
- }
144
- const { data, error, isLoading, isValidating } = useSWR(searchVal ? url : null, openmrsFetch, {});
145
-
146
- return { data: data?.data, error, isLoading: isLoading, isValidating };
137
+ export function useBillableServices() {
138
+ const url = `${apiBasePath}billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(display),servicePrices:(uuid,name,price,paymentMode))`;
139
+ return useOpenmrsFetchAll<BillableItem>(url);
147
140
  }
148
141
 
149
142
  export const processBillItems = (payload) => {
@@ -52,19 +52,19 @@ interface Provider {
52
52
  }
53
53
 
54
54
  export interface LineItem {
55
- uuid: string;
56
- display: string;
57
- voided: boolean;
58
- voidReason: string | null;
59
- item: string;
60
- billableService: string;
55
+ uuid?: string;
56
+ item?: string;
57
+ paymentStatus: string;
58
+ billableService?: string;
61
59
  quantity: number;
62
60
  price: number;
63
- priceName: string;
64
- priceUuid: string;
65
- lineItemOrder: number;
66
- resourceVersion: string;
67
- paymentStatus: string;
61
+ priceName?: string;
62
+ priceUuid?: string;
63
+ lineItemOrder?: number;
64
+ resourceVersion?: string;
65
+ display?: string;
66
+ voided?: boolean;
67
+ voidReason?: string | null;
68
68
  }
69
69
 
70
70
  interface PatientLink {
@@ -168,7 +168,7 @@ export type ServiceConcept = {
168
168
  display: string;
169
169
  };
170
170
 
171
- export type BillabeItem = {
171
+ export type BillableItem = {
172
172
  uuid: string;
173
173
  id?: number;
174
174
  name?: string;
@@ -177,6 +177,7 @@ export type BillabeItem = {
177
177
  };
178
178
 
179
179
  export type ServicePrice = {
180
+ name: string;
180
181
  price: string;
181
182
  uuid: string;
182
183
  };
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -54,7 +54,6 @@
54
54
  "cashPointUuid": "Cash Point UUID",
55
55
  "cashPointUuidPlaceholder": "Enter UUID",
56
56
  "checkFilters": "Check the filters above",
57
- "clearSearchInput": "Clear search input",
58
57
  "confirmDeleteMessage": "Are you sure you want to delete this payment mode? Proceed cautiously.",
59
58
  "createdSuccessfully": "Billable service created successfully",
60
59
  "currentPrice": "Current price",
@@ -117,7 +116,6 @@
117
116
  "noMatchingItemsToDisplay": "No matching items to display",
118
117
  "noMatchingServicesToDisplay": "No matching services to display",
119
118
  "noResultsFor": "No results for",
120
- "noResultsFound": "No results found",
121
119
  "noServicesToDisplay": "There are no services to display",
122
120
  "number": "No",
123
121
  "ok": "OK",
@@ -148,7 +146,6 @@
148
146
  "printReceipt": "Print receipt",
149
147
  "processPayment": "Process Payment",
150
148
  "quantity": "Quantity",
151
- "quantityGreaterThanZero": "Quantity must be greater than zero",
152
149
  "quantityRequired": "Quantity is required",
153
150
  "referenceNumber": "Reference number",
154
151
  "save": "Save",
@@ -160,13 +157,11 @@
160
157
  "searchItems": "Search items and services",
161
158
  "searchThisTable": "Search this table",
162
159
  "selectBillableService": "Select a billable service...",
163
- "selectCategory": "Select category",
164
160
  "selectLocation": "Select Location",
165
161
  "selectPatientCategory": "Select patient category",
166
162
  "selectPaymentMethod": "Select payment method",
167
163
  "sellingAmount": "Enter selling price",
168
164
  "sellingPrice": "Selling Price",
169
- "service": "Service",
170
165
  "serviceMetrics": "Service Metrics",
171
166
  "serviceName": "Service Name",
172
167
  "serviceNameExceedsLimit": "Service Name exceeds the character limit of {{MAX_NAME_LENGTH}}.",
@@ -177,7 +172,6 @@
177
172
  "shortName": "Short Name",
178
173
  "shortNameExceedsLimit": "Short Name exceeds the character limit of {{MAX_NAME_LENGTH}}.",
179
174
  "status": "Service Status",
180
- "stockItem": "Stock Item",
181
175
  "submitting": "Submitting...",
182
176
  "success": "Success",
183
177
  "total": "Total",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "action": "Action",
2
3
  "actions": "Actions",
3
4
  "addBill": "Add bill item(s)",
4
5
  "addBillableServices": "Add Billable Services",