@openmrs/esm-billing-app 1.0.2-pre.92 → 1.0.2-pre.933

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 (214) hide show
  1. package/.eslintrc +16 -2
  2. package/README.md +54 -9
  3. package/__mocks__/bills.mock.ts +12 -0
  4. package/__mocks__/react-i18next.js +6 -5
  5. package/dist/1119.js +1 -1
  6. package/dist/1146.js +1 -2
  7. package/dist/1146.js.map +1 -1
  8. package/dist/1197.js +1 -1
  9. package/dist/1537.js +1 -0
  10. package/dist/1537.js.map +1 -0
  11. package/dist/1856.js +1 -0
  12. package/dist/1856.js.map +1 -0
  13. package/dist/2146.js +1 -1
  14. package/dist/2524.js +1 -0
  15. package/dist/2524.js.map +1 -0
  16. package/dist/2690.js +1 -1
  17. package/dist/3099.js +1 -1
  18. package/dist/3584.js +1 -1
  19. package/dist/3717.js +2 -0
  20. package/dist/3717.js.map +1 -0
  21. package/dist/4055.js +1 -1
  22. package/dist/4132.js +1 -1
  23. package/dist/4300.js +1 -1
  24. package/dist/4335.js +1 -1
  25. package/dist/4618.js +1 -1
  26. package/dist/4652.js +1 -1
  27. package/dist/4724.js +1 -0
  28. package/dist/4724.js.map +1 -0
  29. package/dist/4739.js +1 -1
  30. package/dist/4739.js.map +1 -1
  31. package/dist/4944.js +1 -1
  32. package/dist/5173.js +1 -1
  33. package/dist/5241.js +1 -1
  34. package/dist/5442.js +1 -1
  35. package/dist/5661.js +1 -1
  36. package/dist/6022.js +1 -1
  37. package/dist/6468.js +1 -1
  38. package/dist/6540.js +1 -1
  39. package/dist/6540.js.map +1 -1
  40. package/dist/6679.js +1 -1
  41. package/dist/6840.js +1 -1
  42. package/dist/6859.js +1 -1
  43. package/dist/7097.js +1 -1
  44. package/dist/7159.js +1 -1
  45. package/dist/723.js +1 -1
  46. package/dist/7255.js +1 -1
  47. package/dist/7255.js.map +1 -1
  48. package/dist/7617.js +1 -1
  49. package/dist/795.js +1 -1
  50. package/dist/8163.js +1 -1
  51. package/dist/8349.js +1 -1
  52. package/dist/8572.js +1 -0
  53. package/dist/8572.js.map +1 -0
  54. package/dist/8618.js +1 -1
  55. package/dist/8708.js +2 -0
  56. package/dist/{6557.js.LICENSE.txt → 8708.js.LICENSE.txt} +22 -0
  57. package/dist/8708.js.map +1 -0
  58. package/dist/890.js +1 -1
  59. package/dist/9214.js +1 -1
  60. package/dist/9538.js +1 -1
  61. package/dist/9569.js +1 -1
  62. package/dist/961.js +1 -1
  63. package/dist/961.js.map +1 -1
  64. package/dist/986.js +1 -1
  65. package/dist/9879.js +1 -1
  66. package/dist/9895.js +1 -1
  67. package/dist/9900.js +1 -1
  68. package/dist/9913.js +1 -1
  69. package/dist/main.js +1 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/openmrs-esm-billing-app.js +1 -1
  72. package/dist/openmrs-esm-billing-app.js.buildmanifest.json +271 -285
  73. package/dist/openmrs-esm-billing-app.js.map +1 -1
  74. package/dist/routes.json +1 -1
  75. package/e2e/README.md +19 -18
  76. package/e2e/core/test.ts +1 -1
  77. package/e2e/fixtures/api.ts +1 -1
  78. package/e2e/specs/sample-test.spec.ts +0 -1
  79. package/e2e/support/github/Dockerfile +1 -1
  80. package/package.json +18 -15
  81. package/src/bill-history/bill-history.component.tsx +20 -28
  82. package/src/bill-history/bill-history.scss +4 -94
  83. package/src/bill-history/bill-history.test.tsx +37 -78
  84. package/src/bill-item-actions/bill-item-actions.scss +21 -5
  85. package/src/bill-item-actions/edit-bill-item.modal.tsx +225 -0
  86. package/src/bill-item-actions/edit-bill-item.test.tsx +214 -40
  87. package/src/billable-services/bill-waiver/bill-selection.component.tsx +5 -5
  88. package/src/billable-services/bill-waiver/bill-waiver-form.component.tsx +28 -32
  89. package/src/billable-services/bill-waiver/patient-bills.component.tsx +7 -7
  90. package/src/billable-services/bill-waiver/utils.ts +13 -3
  91. package/src/billable-services/{create-edit/add-billable-service.scss → billable-service-form/billable-service-form.scss} +32 -64
  92. package/src/billable-services/billable-service-form/billable-service-form.test.tsx +898 -0
  93. package/src/billable-services/billable-service-form/billable-service-form.workspace.tsx +504 -0
  94. package/src/billable-services/billable-service.resource.ts +71 -27
  95. package/src/billable-services/billable-services-home.component.tsx +13 -42
  96. package/src/billable-services/billable-services-left-panel-link.component.tsx +48 -0
  97. package/src/billable-services/billable-services-left-panel-menu.component.tsx +46 -0
  98. package/src/billable-services/billable-services-menu-item/item.component.tsx +5 -4
  99. package/src/billable-services/billable-services.component.tsx +156 -152
  100. package/src/billable-services/billable-services.scss +29 -0
  101. package/src/billable-services/billable-services.test.tsx +6 -49
  102. package/src/billable-services/cash-point/add-cash-point.modal.tsx +170 -0
  103. package/src/billable-services/cash-point/cash-point-configuration.component.tsx +19 -193
  104. package/src/billable-services/cash-point/cash-point-configuration.scss +1 -5
  105. package/src/billable-services/dashboard/dashboard.component.tsx +0 -2
  106. package/src/billable-services/payment-modes/delete-payment-mode.modal.tsx +77 -0
  107. package/src/billable-services/payment-modes/payment-mode-form.modal.tsx +131 -0
  108. package/src/billable-services/payment-modes/payment-modes-config.component.tsx +139 -0
  109. package/src/billable-services/{payyment-modes → payment-modes}/payment-modes-config.scss +5 -4
  110. package/src/billable-services-admin-card-link.component.test.tsx +2 -2
  111. package/src/billable-services-admin-card-link.component.tsx +1 -1
  112. package/src/billing-dashboard/billing-dashboard.scss +1 -1
  113. package/src/billing-form/billing-checkin-form.component.tsx +21 -17
  114. package/src/billing-form/billing-checkin-form.test.tsx +99 -26
  115. package/src/billing-form/billing-form.component.tsx +222 -292
  116. package/src/billing-form/billing-form.scss +143 -0
  117. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +1 -1
  118. package/src/billing.resource.ts +69 -74
  119. package/src/bills-table/bills-table.component.tsx +3 -3
  120. package/src/bills-table/bills-table.test.tsx +98 -54
  121. package/src/config-schema.ts +52 -24
  122. package/src/dashboard.meta.ts +4 -2
  123. package/src/helpers/functions.ts +5 -4
  124. package/src/index.ts +71 -9
  125. package/src/invoice/invoice-table.component.tsx +36 -70
  126. package/src/invoice/invoice-table.scss +8 -5
  127. package/src/invoice/invoice-table.test.tsx +273 -62
  128. package/src/invoice/invoice.component.tsx +39 -32
  129. package/src/invoice/invoice.scss +11 -4
  130. package/src/invoice/invoice.test.tsx +324 -120
  131. package/src/invoice/payments/invoice-breakdown/invoice-breakdown.scss +9 -9
  132. package/src/invoice/payments/payment-form/payment-form.component.tsx +43 -34
  133. package/src/invoice/payments/payment-form/payment-form.scss +5 -6
  134. package/src/invoice/payments/payment-form/payment-form.test.tsx +216 -66
  135. package/src/invoice/payments/payment-history/payment-history.component.tsx +6 -4
  136. package/src/invoice/payments/payment-history/payment-history.test.tsx +9 -14
  137. package/src/invoice/payments/payments.component.tsx +55 -67
  138. package/src/invoice/payments/payments.scss +4 -3
  139. package/src/invoice/payments/payments.test.tsx +282 -0
  140. package/src/invoice/payments/utils.ts +15 -27
  141. package/src/invoice/printable-invoice/print-receipt.component.tsx +3 -2
  142. package/src/invoice/printable-invoice/print-receipt.test.tsx +14 -25
  143. package/src/invoice/printable-invoice/printable-footer.component.tsx +2 -2
  144. package/src/invoice/printable-invoice/printable-footer.test.tsx +4 -13
  145. package/src/invoice/printable-invoice/printable-invoice-header.component.tsx +12 -11
  146. package/src/invoice/printable-invoice/printable-invoice-header.test.tsx +16 -14
  147. package/src/invoice/printable-invoice/printable-invoice.component.tsx +20 -34
  148. package/src/left-panel-link.test.tsx +1 -4
  149. package/src/metrics-cards/metrics-cards.component.tsx +16 -6
  150. package/src/metrics-cards/metrics-cards.scss +4 -0
  151. package/src/metrics-cards/metrics-cards.test.tsx +18 -5
  152. package/src/modal/require-payment-modal.test.tsx +27 -22
  153. package/src/modal/{require-payment-modal.component.tsx → require-payment.modal.tsx} +18 -19
  154. package/src/routes.json +44 -20
  155. package/src/types/index.ts +86 -23
  156. package/translations/am.json +132 -77
  157. package/translations/ar.json +133 -78
  158. package/translations/ar_SY.json +133 -78
  159. package/translations/bn.json +135 -80
  160. package/translations/de.json +133 -78
  161. package/translations/en.json +134 -79
  162. package/translations/en_US.json +133 -78
  163. package/translations/es.json +132 -77
  164. package/translations/es_MX.json +133 -78
  165. package/translations/fr.json +138 -83
  166. package/translations/he.json +132 -77
  167. package/translations/hi.json +133 -78
  168. package/translations/hi_IN.json +133 -78
  169. package/translations/id.json +133 -78
  170. package/translations/it.json +159 -104
  171. package/translations/ka.json +133 -78
  172. package/translations/km.json +132 -77
  173. package/translations/ku.json +133 -78
  174. package/translations/ky.json +133 -78
  175. package/translations/lg.json +133 -78
  176. package/translations/ne.json +133 -78
  177. package/translations/pl.json +133 -78
  178. package/translations/pt.json +133 -78
  179. package/translations/pt_BR.json +133 -78
  180. package/translations/qu.json +133 -78
  181. package/translations/ro_RO.json +220 -165
  182. package/translations/ru_RU.json +133 -78
  183. package/translations/si.json +133 -78
  184. package/translations/sw.json +133 -78
  185. package/translations/sw_KE.json +133 -78
  186. package/translations/tr.json +133 -78
  187. package/translations/tr_TR.json +133 -78
  188. package/translations/uk.json +133 -78
  189. package/translations/uz.json +133 -78
  190. package/translations/uz@Latn.json +133 -78
  191. package/translations/uz_UZ.json +133 -78
  192. package/translations/vi.json +133 -78
  193. package/translations/zh.json +133 -78
  194. package/translations/zh_CN.json +163 -108
  195. package/dist/1146.js.LICENSE.txt +0 -21
  196. package/dist/2352.js +0 -1
  197. package/dist/2352.js.map +0 -1
  198. package/dist/246.js +0 -1
  199. package/dist/246.js.map +0 -1
  200. package/dist/4689.js +0 -2
  201. package/dist/4689.js.map +0 -1
  202. package/dist/6557.js +0 -2
  203. package/dist/6557.js.map +0 -1
  204. package/dist/8638.js +0 -1
  205. package/dist/8638.js.map +0 -1
  206. package/dist/9968.js +0 -1
  207. package/dist/9968.js.map +0 -1
  208. package/src/bill-item-actions/edit-bill-item.component.tsx +0 -221
  209. package/src/billable-services/create-edit/add-billable-service.component.tsx +0 -401
  210. package/src/billable-services/create-edit/add-billable-service.test.tsx +0 -154
  211. package/src/billable-services/dashboard/service-metrics.component.tsx +0 -41
  212. package/src/billable-services/payyment-modes/payment-modes-config.component.tsx +0 -280
  213. package/src/invoice/payments/payments.component.test.tsx +0 -121
  214. /package/dist/{4689.js.LICENSE.txt → 3717.js.LICENSE.txt} +0 -0
@@ -0,0 +1,131 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useForm, Controller } from 'react-hook-form';
4
+ import { z } from 'zod';
5
+ import { zodResolver } from '@hookform/resolvers/zod';
6
+ import { useSWRConfig } from 'swr';
7
+ import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextInput } from '@carbon/react';
8
+ import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
9
+ import { type PaymentModePayload } from '../../types';
10
+ import { apiBasePath } from '../../constants';
11
+ import { createPaymentMode, updatePaymentMode } from '../billable-service.resource';
12
+
13
+ type PaymentModeFormValues = {
14
+ uuid?: string;
15
+ name: string;
16
+ description: string;
17
+ };
18
+
19
+ interface PaymentModeFormModalProps {
20
+ closeModal: () => void;
21
+ editPaymentMode?: PaymentModeFormValues;
22
+ }
23
+
24
+ const PaymentModeFormModal: React.FC<PaymentModeFormModalProps> = ({ closeModal, editPaymentMode }) => {
25
+ const { t } = useTranslation();
26
+ const { mutate } = useSWRConfig();
27
+
28
+ const paymentModeSchema = z.object({
29
+ name: z.string().min(1, t('paymentModeNameRequired', 'Payment mode name is required')),
30
+ description: z.string().optional(),
31
+ });
32
+
33
+ const {
34
+ control,
35
+ handleSubmit,
36
+ reset,
37
+ formState: { errors, isSubmitting },
38
+ } = useForm<PaymentModeFormValues>({
39
+ resolver: zodResolver(paymentModeSchema),
40
+ defaultValues: {
41
+ name: editPaymentMode?.name ?? '',
42
+ description: editPaymentMode?.description ?? '',
43
+ },
44
+ });
45
+
46
+ const onSubmit = async (data: PaymentModeFormValues) => {
47
+ const url = `${apiBasePath}paymentMode`;
48
+ try {
49
+ const payload: PaymentModePayload = {
50
+ name: data.name,
51
+ description: data.description,
52
+ };
53
+ if (editPaymentMode?.uuid) {
54
+ await updatePaymentMode(editPaymentMode.uuid, payload);
55
+ } else {
56
+ await createPaymentMode(payload);
57
+ }
58
+
59
+ mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
60
+
61
+ showSnackbar({
62
+ title: t('success', 'Success'),
63
+ subtitle: t('paymentModeSaved', 'Payment mode was successfully saved.'),
64
+ kind: 'success',
65
+ });
66
+
67
+ closeModal();
68
+ reset({ name: '', description: '' });
69
+ } catch (err) {
70
+ showSnackbar({
71
+ title: getCoreTranslation('error'),
72
+ subtitle: err?.message || t('errorSavingPaymentMode', 'An error occurred while saving the payment mode.'),
73
+ kind: 'error',
74
+ isLowContrast: false,
75
+ });
76
+ }
77
+ };
78
+
79
+ return (
80
+ <>
81
+ <ModalHeader
82
+ closeModal={closeModal}
83
+ title={editPaymentMode ? t('editPaymentMode', 'Edit payment mode') : t('addPaymentMode', 'Add payment mode')}
84
+ />
85
+ <Form onSubmit={handleSubmit(onSubmit)}>
86
+ <ModalBody>
87
+ <Stack gap={5}>
88
+ <Controller
89
+ name="name"
90
+ control={control}
91
+ render={({ field }) => (
92
+ <TextInput
93
+ id="payment-mode-name"
94
+ labelText={t('paymentModeNameLabel', 'Payment mode name')}
95
+ placeholder={t('paymentModeNamePlaceholder', 'For example, Cash, Credit Card')}
96
+ invalid={!!errors.name}
97
+ invalidText={errors.name?.message}
98
+ {...field}
99
+ />
100
+ )}
101
+ />
102
+ <Controller
103
+ name="description"
104
+ control={control}
105
+ render={({ field }) => (
106
+ <TextInput
107
+ id="payment-mode-description"
108
+ labelText={t('description', 'Description')}
109
+ placeholder={t('descriptionPlaceholder', 'For example, Used for all cash transactions')}
110
+ invalid={!!errors.description}
111
+ invalidText={errors.description?.message}
112
+ {...field}
113
+ />
114
+ )}
115
+ />
116
+ </Stack>
117
+ </ModalBody>
118
+ <ModalFooter>
119
+ <Button kind="secondary" onClick={closeModal}>
120
+ {getCoreTranslation('cancel')}
121
+ </Button>
122
+ <Button type="submit" disabled={isSubmitting}>
123
+ {isSubmitting ? t('saving', 'Saving') + '...' : getCoreTranslation('save')}
124
+ </Button>
125
+ </ModalFooter>
126
+ </Form>
127
+ </>
128
+ );
129
+ };
130
+
131
+ export default PaymentModeFormModal;
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import {
3
+ Button,
4
+ DataTable,
5
+ InlineLoading,
6
+ OverflowMenu,
7
+ OverflowMenuItem,
8
+ Table,
9
+ TableBody,
10
+ TableCell,
11
+ TableContainer,
12
+ TableHead,
13
+ TableHeader,
14
+ TableRow,
15
+ } from '@carbon/react';
16
+ import { Add } from '@carbon/react/icons';
17
+ import { useTranslation } from 'react-i18next';
18
+ import { showModal, getCoreTranslation, ErrorState } from '@openmrs/esm-framework';
19
+ import { CardHeader } from '@openmrs/esm-patient-common-lib';
20
+ import { usePaymentModes, type PaymentMode } from '../billable-service.resource';
21
+ import styles from './payment-modes-config.scss';
22
+
23
+ const PaymentModesConfig: React.FC = () => {
24
+ const { t } = useTranslation();
25
+ const { paymentModes, error, isLoadingPaymentModes } = usePaymentModes();
26
+
27
+ const handleAddPaymentMode = () => {
28
+ const dispose = showModal('payment-mode-form-modal', {
29
+ closeModal: () => dispose(),
30
+ });
31
+ };
32
+
33
+ const handleDeletePaymentMode = (paymentMode: PaymentMode) => {
34
+ const dispose = showModal('delete-payment-mode-modal', {
35
+ paymentModeUuid: paymentMode.uuid,
36
+ paymentModeName: paymentMode.name,
37
+ closeModal: () => dispose(),
38
+ });
39
+ };
40
+
41
+ const handleEditPaymentMode = (paymentMode: PaymentMode) => {
42
+ const dispose = showModal('payment-mode-form-modal', {
43
+ editPaymentMode: paymentMode,
44
+ closeModal: () => dispose(),
45
+ });
46
+ };
47
+
48
+ const rowData = paymentModes.map((mode) => ({
49
+ id: mode.uuid,
50
+ name: mode.name,
51
+ description: mode.description || '--',
52
+ }));
53
+
54
+ const headerData = [
55
+ { key: 'name', header: t('name', 'Name') },
56
+ { key: 'description', header: t('description', 'Description') },
57
+ { key: 'actions', header: getCoreTranslation('actions') },
58
+ ];
59
+
60
+ if (isLoadingPaymentModes) {
61
+ return (
62
+ <InlineLoading
63
+ status="active"
64
+ iconDescription={getCoreTranslation('loading')}
65
+ description={t('loading', 'Loading data') + '...'}
66
+ />
67
+ );
68
+ }
69
+
70
+ if (error) {
71
+ return <ErrorState headerTitle={t('paymentMode', 'Payment mode')} error={error} />;
72
+ }
73
+ return (
74
+ <div className={styles.container}>
75
+ <div className={styles.card}>
76
+ <CardHeader title={t('paymentModeHistory', 'Payment mode history')}>
77
+ <Button renderIcon={Add} onClick={handleAddPaymentMode} kind="ghost">
78
+ {t('addNewPaymentMode', 'Add new payment mode')}
79
+ </Button>
80
+ </CardHeader>
81
+ <DataTable rows={rowData} headers={headerData} isSortable size="lg">
82
+ {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
83
+ <TableContainer>
84
+ <Table className={styles.table} {...getTableProps()}>
85
+ <TableHead>
86
+ <TableRow>
87
+ {headers.map((header) => (
88
+ <TableHeader key={header.key} {...getHeaderProps({ header })}>
89
+ {header.header}
90
+ </TableHeader>
91
+ ))}
92
+ </TableRow>
93
+ </TableHead>
94
+ <TableBody>
95
+ {rows.map((row) => (
96
+ <TableRow key={row.id} {...getRowProps({ row })}>
97
+ {row.cells.map((cell) =>
98
+ cell.info.header !== 'actions' ? (
99
+ <TableCell key={cell.id}>{cell.value}</TableCell>
100
+ ) : (
101
+ <TableCell key={cell.id}>
102
+ <OverflowMenu>
103
+ <OverflowMenuItem
104
+ className={styles.menuItem}
105
+ itemText={getCoreTranslation('edit')}
106
+ onClick={() => {
107
+ const selected = paymentModes.find((p) => p.uuid === row.id);
108
+ if (selected) {
109
+ handleEditPaymentMode(selected);
110
+ }
111
+ }}
112
+ />
113
+ <OverflowMenuItem
114
+ className={styles.menuItem}
115
+ itemText={getCoreTranslation('delete')}
116
+ onClick={() => {
117
+ const selected = paymentModes.find((p) => p.uuid === row.id);
118
+ if (selected) {
119
+ handleDeletePaymentMode(selected);
120
+ }
121
+ }}
122
+ />
123
+ </OverflowMenu>
124
+ </TableCell>
125
+ ),
126
+ )}
127
+ </TableRow>
128
+ ))}
129
+ </TableBody>
130
+ </Table>
131
+ </TableContainer>
132
+ )}
133
+ </DataTable>
134
+ </div>
135
+ </div>
136
+ );
137
+ };
138
+
139
+ export default PaymentModesConfig;
@@ -13,11 +13,12 @@
13
13
  padding: layout.$spacing-05;
14
14
  }
15
15
 
16
- .historyContainer {
17
- margin-top: layout.$spacing-05;
18
- }
19
16
 
20
17
  .table {
21
18
  width: 100%;
22
19
  table-layout: auto;
23
- }
20
+ }
21
+
22
+ .menuItem {
23
+ max-width: none;
24
+ }
@@ -8,10 +8,10 @@ describe('BillableServicesCardLink', () => {
8
8
  const manageBillableServicesText = screen.getByText('Manage billable services');
9
9
  expect(manageBillableServicesText).toHaveClass('heading');
10
10
 
11
- const billiableText = screen.getByText('Billable Services');
11
+ const billiableText = screen.getByText('Billable services', { exact: true });
12
12
  expect(billiableText).toHaveClass('content');
13
13
 
14
- const billiableServiceLink = screen.getByRole('link', { name: /Billable Services/i });
14
+ const billiableServiceLink = screen.getByRole('link', { name: /Manage billable services/i });
15
15
  expect(billiableServiceLink).toHaveAttribute('href', '/spa/billable-services');
16
16
  });
17
17
  });
@@ -12,7 +12,7 @@ const BillableServicesCardLink: React.FC = () => {
12
12
  <ClickableTile href={`${window.spaBase}/billable-services`} target="_blank" rel="noopener noreferrer">
13
13
  <div>
14
14
  <div className="heading">{header}</div>
15
- <div className="content">{t('billableServices', 'Billable Services')}</div>
15
+ <div className="content">{t('billableServices', 'Billable services')}</div>
16
16
  </div>
17
17
  <div className="iconWrapper">
18
18
  <ArrowRight size={16} />
@@ -7,7 +7,7 @@
7
7
  }
8
8
 
9
9
  .billsTableContainer {
10
- margin: layout.$spacing-07 layout.$spacing-05;
10
+ margin: layout.$spacing-05;
11
11
  }
12
12
 
13
13
  .illo {
@@ -1,12 +1,11 @@
1
1
  import React, { useCallback, useState } from 'react';
2
2
  import { Dropdown, InlineLoading, InlineNotification } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { showSnackbar, useConfig } from '@openmrs/esm-framework';
4
+ import { showSnackbar, getCoreTranslation } from '@openmrs/esm-framework';
5
5
  import { useCashPoint, useBillableItems, createPatientBill } from './billing-form.resource';
6
6
  import VisitAttributesForm from './visit-attributes/visit-attributes-form.component';
7
7
  import styles from './billing-checkin-form.scss';
8
8
 
9
- const DEFAULT_PRICE = 500.00001;
10
9
  const PENDING_PAYMENT_STATUS = 'PENDING';
11
10
 
12
11
  type BillingCheckInFormProps = {
@@ -22,20 +21,25 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
22
21
  const [paymentMethod, setPaymentMethod] = useState<any>();
23
22
  let lineList = [];
24
23
 
25
- const handleCreateExtraVisitInfo = useCallback((createBillPayload) => {
26
- createPatientBill(createBillPayload).then(
27
- (res) => {
28
- showSnackbar({ title: 'Patient Bill', subtitle: 'Patient has been billed successfully', kind: 'success' });
29
- },
30
- (error) => {
24
+ const handleCreateExtraVisitInfo = useCallback(
25
+ async (createBillPayload) => {
26
+ try {
27
+ await createPatientBill(createBillPayload);
31
28
  showSnackbar({
32
- title: 'Patient Bill Error',
33
- subtitle: 'An error has occurred while creating patient bill',
29
+ title: t('patientBill', 'Patient bill'),
30
+ subtitle: t('billCreatedSuccessfully', 'Bill created successfully'),
31
+ kind: 'success',
32
+ });
33
+ } catch (error) {
34
+ showSnackbar({
35
+ title: t('billCreationError', 'Bill creation error'),
36
+ subtitle: t('errorCreatingBill', 'An error occurred while creating the bill'),
34
37
  kind: 'error',
35
38
  });
36
- },
37
- );
38
- }, []);
39
+ }
40
+ },
41
+ [t],
42
+ );
39
43
 
40
44
  const handleBillingService = ({ selectedItem }) => {
41
45
  const cashPointUuid = cashPoints?.[0]?.uuid ?? '';
@@ -74,8 +78,8 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
74
78
  return (
75
79
  <InlineLoading
76
80
  status="active"
77
- iconDescription={t('loading', 'Loading')}
78
- description={t('loadingBillingServices', 'Loading billing services...')}
81
+ iconDescription={getCoreTranslation('loading')}
82
+ description={`${t('loadingBillingServices', 'Loading billing services')}...`}
79
83
  />
80
84
  );
81
85
  }
@@ -97,7 +101,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
97
101
  <InlineNotification
98
102
  kind="error"
99
103
  lowContrast
100
- title={t('billErrorService', 'Bill service error')}
104
+ title={t('billErrorService', 'Billing service error')}
101
105
  subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
102
106
  />
103
107
  );
@@ -111,7 +115,7 @@ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, se
111
115
  <div className={styles.sectionTitle}>{t('billing', 'Billing')}</div>
112
116
  <div className={styles.sectionField}></div>
113
117
  <Dropdown
114
- label={t('selectBillableService', 'Select a billable service...')}
118
+ label={t('selectBillableService', 'Select a billable service')}
115
119
  onChange={handleBillingService}
116
120
  id="billable-items"
117
121
  items={lineList}
@@ -1,9 +1,16 @@
1
1
  import React from 'react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { screen, render } from '@testing-library/react';
4
- import { useBillableItems, useCashPoint, createPatientBill, usePaymentMethods } from './billing-form.resource';
4
+ import { useConfig } from '@openmrs/esm-framework';
5
+ import { type BillingConfig } from '../config-schema';
6
+ import { useBillableItems, useCashPoint, usePaymentMethods } from './billing-form.resource';
5
7
  import BillingCheckInForm from './billing-checkin-form.component';
6
8
 
9
+ const mockUseConfig = jest.mocked(useConfig<BillingConfig>);
10
+ const mockUseCashPoint = jest.mocked(useCashPoint);
11
+ const mockUseBillableItems = jest.mocked(useBillableItems);
12
+ const mockUsePaymentMethods = jest.mocked(usePaymentMethods);
13
+
7
14
  const mockCashPoints = [
8
15
  {
9
16
  uuid: '54065383-b4d4-42d2-af4d-d250a1fd2590',
@@ -16,6 +23,7 @@ const mockCashPoints = [
16
23
  const mockBillableItems = [
17
24
  {
18
25
  uuid: 'b37dddd6-4490-4bf7-b694-43bf19d04059',
26
+ name: 'Consultation',
19
27
  conceptUuid: '1926AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
20
28
  conceptName: 'Consultation billable item',
21
29
  hasExpiration: false,
@@ -25,9 +33,21 @@ const mockBillableItems = [
25
33
  categoryName: 'Non Drug',
26
34
  commonName: 'Consultation',
27
35
  acronym: 'CONSULT',
36
+ servicePrices: [
37
+ {
38
+ uuid: 'price-1',
39
+ name: 'Default',
40
+ price: '100.00',
41
+ paymentMode: {
42
+ uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
43
+ name: 'Insurance',
44
+ },
45
+ },
46
+ ],
28
47
  },
29
48
  {
30
49
  uuid: 'b47dddd6-4490-4bf7-b694-43bf19d04059',
50
+ name: 'Lab Testing',
31
51
  conceptUuid: '1926AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
32
52
  conceptName: 'Lab Testing billable item',
33
53
  hasExpiration: false,
@@ -37,25 +57,65 @@ const mockBillableItems = [
37
57
  categoryName: 'Non Drug',
38
58
  commonName: 'Lab Testing',
39
59
  acronym: 'CONSULT',
60
+ servicePrices: [
61
+ {
62
+ uuid: 'price-2',
63
+ name: 'Default',
64
+ price: '500.00001',
65
+ paymentMode: {
66
+ uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
67
+ name: 'Insurance',
68
+ },
69
+ },
70
+ ],
40
71
  },
41
72
  ];
42
73
 
43
- const mockUseCashPoint = useCashPoint as jest.MockedFunction<typeof useCashPoint>;
44
- const mockUseBillableItems = useBillableItems as jest.MockedFunction<typeof useBillableItems>;
45
- const mockCreatePatientBill = createPatientBill as jest.MockedFunction<typeof createPatientBill>;
46
- const mockusePaymentMethods = usePaymentMethods as jest.MockedFunction<typeof usePaymentMethods>;
74
+ const mockPaymentMethods = [
75
+ {
76
+ uuid: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
77
+ name: 'Insurance',
78
+ description: 'Insurance payment',
79
+ },
80
+ {
81
+ uuid: '2c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
82
+ name: 'Cash',
83
+ description: 'Cash payment',
84
+ },
85
+ ];
47
86
 
48
87
  jest.mock('./billing-form.resource', () => ({
49
88
  useBillableItems: jest.fn(),
50
89
  useCashPoint: jest.fn(),
51
90
  createPatientBill: jest.fn(),
91
+ usePaymentMethods: jest.fn(),
52
92
  }));
53
93
 
54
94
  const testProps = { patientUuid: 'some-patient-uuid', setExtraVisitInfo: jest.fn() };
55
95
 
56
- xdescribe('BillingCheckInForm', () => {
96
+ describe('BillingCheckInForm', () => {
57
97
  beforeEach(() => {
58
98
  jest.resetAllMocks();
99
+ mockUseConfig.mockReturnValue({
100
+ patientCatergory: {
101
+ paymentDetails: 'fbc0702d-b4c9-4968-be63-af8ad3ad6239',
102
+ paymentMethods: '8553afa0-bdb9-4d3c-8a98-05fa9350aa85',
103
+ policyNumber: '3a988e33-a6c0-4b76-b924-01abb998944b',
104
+ insuranceScheme: 'aac48226-d143-4274-80e0-264db4e368ee',
105
+ patientCategory: '3b9dfac8-9e4d-11ee-8c90-0242ac120002',
106
+ formPayloadPending: '919b51c9-8e2e-468f-8354-181bf3e55786',
107
+ },
108
+ catergoryConcepts: {
109
+ payingDetails: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
110
+ nonPayingDetails: 'f3fb2d88-cccd-422c-8766-be101ba7bd2e',
111
+ insuranceDetails: 'beac329b-f1dc-4a33-9e7c-d95821a137a6',
112
+ },
113
+ nonPayingPatientCategories: {
114
+ childUnder5: '1528AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
115
+ student: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
116
+ },
117
+ } as BillingConfig);
118
+ mockUsePaymentMethods.mockReturnValue({ paymentModes: mockPaymentMethods, isLoading: false, error: null });
59
119
  });
60
120
 
61
121
  test('should show the loading spinner while retrieving data', () => {
@@ -72,59 +132,72 @@ xdescribe('BillingCheckInForm', () => {
72
132
  mockUseCashPoint.mockReturnValueOnce({ cashPoints: [], isLoading: false, error });
73
133
  renderBillingCheckinForm();
74
134
 
75
- expect(screen.getByText('Bill service error')).toBeInTheDocument();
76
- expect(screen.getByText('Error loading bill services')).toBeInTheDocument();
135
+ expect(screen.getByText(/billing service error/i)).toBeInTheDocument();
136
+ expect(screen.getByText(/error loading bill services/i)).toBeInTheDocument();
77
137
  });
78
138
 
79
139
  test('should render the form correctly and generate the required payload', async () => {
80
140
  const user = userEvent.setup();
81
- mockUseCashPoint.mockReturnValue({ cashPoints: [], isLoading: false, error: null });
141
+ mockUseCashPoint.mockReturnValue({ cashPoints: mockCashPoints, isLoading: false, error: null });
82
142
  mockUseBillableItems.mockReturnValue({ lineItems: mockBillableItems, isLoading: false, error: null });
83
143
  renderBillingCheckinForm();
84
144
 
85
145
  const paymentTypeSelect = screen.getByRole('group', { name: 'Payment Details' });
86
146
  expect(paymentTypeSelect).toBeInTheDocument();
87
147
 
148
+ // Select "Paying" radio button
88
149
  const paymentTypeRadio = screen.getByRole('radio', { name: 'Paying' });
89
150
  expect(paymentTypeRadio).toBeInTheDocument();
90
151
  await user.click(paymentTypeRadio);
91
152
 
92
- const billiableSelect = screen.getByRole('combobox', { name: 'Billable service' });
93
- expect(billiableSelect).toBeInTheDocument();
94
- await user.click(screen.getByRole('combobox', { name: 'Billable service' }));
153
+ // Wait for payment methods dropdown to appear and select a payment method
154
+ const paymentMethodsDropdown = await screen.findByRole('combobox', { name: /Payment methods/i });
155
+ expect(paymentMethodsDropdown).toBeInTheDocument();
156
+ await user.click(paymentMethodsDropdown);
95
157
 
96
- await user.click(screen.getByText('Lab Testing'));
158
+ // Select "Insurance" payment method
159
+ const insuranceOption = await screen.findByText('Insurance');
160
+ await user.click(insuranceOption);
161
+
162
+ // Now select billable service
163
+ const billableSelect = screen.getByRole('combobox', { name: 'Billable service' });
164
+ expect(billableSelect).toBeInTheDocument();
165
+ await user.click(billableSelect);
166
+
167
+ // Click on Lab Testing option
168
+ const labTestingOption = await screen.findByText(/Lab Testing \(Default:500\.00001\)/);
169
+ await user.click(labTestingOption);
97
170
 
98
171
  expect(testProps.setExtraVisitInfo).toHaveBeenCalled();
99
172
  expect(testProps.setExtraVisitInfo).toHaveBeenCalledWith({
100
173
  createBillPayload: {
101
174
  lineItems: [
102
175
  {
103
- item: 'b47dddd6-4490-4bf7-b694-43bf19d04059',
176
+ billableService: 'b47dddd6-4490-4bf7-b694-43bf19d04059',
104
177
  quantity: 1,
105
- price: 500.00001,
178
+ price: '500.00001',
106
179
  priceName: 'Default',
107
- priceUuid: '',
180
+ priceUuid: 'price-2',
108
181
  lineItemOrder: 0,
109
182
  paymentStatus: 'PENDING',
110
183
  },
111
184
  ],
112
- cashPoint: '',
185
+ cashPoint: '54065383-b4d4-42d2-af4d-d250a1fd2590',
113
186
  patient: 'some-patient-uuid',
114
187
  status: 'PENDING',
115
188
  payments: [],
116
189
  },
117
190
  handleCreateExtraVisitInfo: expect.anything(),
118
- attributes: [
119
- {
120
- attributeType: 'caf2124f-00a9-4620-a250-efd8535afd6d',
191
+ attributes: expect.arrayContaining([
192
+ expect.objectContaining({
193
+ attributeType: 'fbc0702d-b4c9-4968-be63-af8ad3ad6239',
194
+ value: '44b34972-6630-4e5a-a9f6-a6eb0f109650',
195
+ }),
196
+ expect.objectContaining({
197
+ attributeType: '8553afa0-bdb9-4d3c-8a98-05fa9350aa85',
121
198
  value: '1c30ee58-82d4-4ea4-a8c1-4bf2f9dfc8cf',
122
- },
123
- {
124
- attributeType: '919b51c9-8e2e-468f-8354-181bf3e55786',
125
- value: true,
126
- },
127
- ],
199
+ }),
200
+ ]),
128
201
  });
129
202
  });
130
203
  });