@openmrs/esm-billing-app 1.0.2-pre.761 → 1.0.2-pre.767
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.
- package/README.md +0 -1
- package/dist/4300.js +1 -1
- package/dist/4739.js +1 -1
- package/dist/4739.js.map +1 -1
- package/dist/942.js +1 -1
- package/dist/942.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-billing-app.js.buildmanifest.json +12 -12
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/bill-item-actions/edit-bill-item.modal.tsx +49 -28
- package/src/bill-item-actions/edit-bill-item.test.tsx +95 -7
- package/src/billing-form/billing-form.component.tsx +2 -0
- package/src/config-schema.ts +0 -6
- package/src/invoice/invoice-table.component.tsx +12 -16
- package/src/invoice/invoice-table.test.tsx +267 -30
- package/src/invoice/invoice.test.tsx +315 -85
- package/src/invoice/payments/payment-form/payment-form.component.tsx +2 -0
- package/translations/en.json +11 -4
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"initial": false,
|
|
72
72
|
"entry": false,
|
|
73
73
|
"recorded": false,
|
|
74
|
-
"size":
|
|
74
|
+
"size": 1174566,
|
|
75
75
|
"sizes": {
|
|
76
|
-
"javascript":
|
|
76
|
+
"javascript": 1174524,
|
|
77
77
|
"consume-shared": 42
|
|
78
78
|
},
|
|
79
79
|
"names": [],
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"auxiliaryFiles": [
|
|
88
88
|
"942.js.map"
|
|
89
89
|
],
|
|
90
|
-
"hash": "
|
|
90
|
+
"hash": "4c3e935e456ec877",
|
|
91
91
|
"childrenByOrder": {}
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -544,9 +544,9 @@
|
|
|
544
544
|
"initial": false,
|
|
545
545
|
"entry": false,
|
|
546
546
|
"recorded": false,
|
|
547
|
-
"size":
|
|
547
|
+
"size": 8850,
|
|
548
548
|
"sizes": {
|
|
549
|
-
"javascript":
|
|
549
|
+
"javascript": 8850
|
|
550
550
|
},
|
|
551
551
|
"names": [],
|
|
552
552
|
"idHints": [],
|
|
@@ -558,7 +558,7 @@
|
|
|
558
558
|
"4300.js"
|
|
559
559
|
],
|
|
560
560
|
"auxiliaryFiles": [],
|
|
561
|
-
"hash": "
|
|
561
|
+
"hash": "4e1e7e3ab3cd456c",
|
|
562
562
|
"childrenByOrder": {}
|
|
563
563
|
},
|
|
564
564
|
{
|
|
@@ -659,9 +659,9 @@
|
|
|
659
659
|
"initial": false,
|
|
660
660
|
"entry": false,
|
|
661
661
|
"recorded": false,
|
|
662
|
-
"size":
|
|
662
|
+
"size": 38056,
|
|
663
663
|
"sizes": {
|
|
664
|
-
"javascript":
|
|
664
|
+
"javascript": 38056
|
|
665
665
|
},
|
|
666
666
|
"names": [],
|
|
667
667
|
"idHints": [],
|
|
@@ -675,7 +675,7 @@
|
|
|
675
675
|
"auxiliaryFiles": [
|
|
676
676
|
"4739.js.map"
|
|
677
677
|
],
|
|
678
|
-
"hash": "
|
|
678
|
+
"hash": "76e266d7cea98afc",
|
|
679
679
|
"childrenByOrder": {}
|
|
680
680
|
},
|
|
681
681
|
{
|
|
@@ -1209,10 +1209,10 @@
|
|
|
1209
1209
|
"initial": true,
|
|
1210
1210
|
"entry": true,
|
|
1211
1211
|
"recorded": false,
|
|
1212
|
-
"size":
|
|
1212
|
+
"size": 5472396,
|
|
1213
1213
|
"sizes": {
|
|
1214
1214
|
"consume-shared": 210,
|
|
1215
|
-
"javascript":
|
|
1215
|
+
"javascript": 5449741,
|
|
1216
1216
|
"share-init": 336,
|
|
1217
1217
|
"runtime": 22109
|
|
1218
1218
|
},
|
|
@@ -1229,7 +1229,7 @@
|
|
|
1229
1229
|
"auxiliaryFiles": [
|
|
1230
1230
|
"main.js.map"
|
|
1231
1231
|
],
|
|
1232
|
-
"hash": "
|
|
1232
|
+
"hash": "5253e749f65b670a",
|
|
1233
1233
|
"childrenByOrder": {}
|
|
1234
1234
|
},
|
|
1235
1235
|
{
|
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":"editBillLineItemModal","online":true,"offline":true}],"modals":[{"name":"add-cash-point-modal","component":"addCashPointModal"},{"name":"add-payment-mode-modal","component":"addPaymentModeModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"edit-bill-item-modal","component":"editBillLineItemModal"},{"name":"edit-billable-service-modal","component":"editBillableServiceModal"},{"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.
|
|
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":"editBillLineItemModal","online":true,"offline":true}],"modals":[{"name":"add-cash-point-modal","component":"addCashPointModal"},{"name":"add-payment-mode-modal","component":"addPaymentModeModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"edit-bill-item-modal","component":"editBillLineItemModal"},{"name":"edit-billable-service-modal","component":"editBillableServiceModal"},{"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.767"}
|
package/package.json
CHANGED
|
@@ -40,8 +40,23 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
40
40
|
const schema = useMemo(
|
|
41
41
|
() =>
|
|
42
42
|
z.object({
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// NOTE: Frontend-only validation - quantities <1 or >100 can still be submitted via API.
|
|
44
|
+
// Backend (BillServiceImpl.java:100) has empty validate() method.
|
|
45
|
+
// TODO: Add server-side validation to enforce data integrity
|
|
46
|
+
quantity: z.coerce
|
|
47
|
+
.number({
|
|
48
|
+
required_error: t('quantityRequired', 'Quantity is required'),
|
|
49
|
+
invalid_type_error: t('quantityMustBeNumber', 'Quantity must be a valid number'),
|
|
50
|
+
})
|
|
51
|
+
.int(t('quantityMustBeInteger', 'Quantity must be a whole number'))
|
|
52
|
+
.min(1, t('quantityMustBeAtLeastOne', 'Quantity must be at least 1'))
|
|
53
|
+
.max(100, t('quantityCannotExceed100', 'Quantity cannot exceed 100')),
|
|
54
|
+
price: z.coerce
|
|
55
|
+
.number({
|
|
56
|
+
required_error: t('priceIsRequired', 'Price is required'),
|
|
57
|
+
invalid_type_error: t('priceMustBeNumber', 'Price must be a valid number'),
|
|
58
|
+
})
|
|
59
|
+
.positive(t('priceMustBePositive', 'Price must be greater than 0')),
|
|
45
60
|
}),
|
|
46
61
|
[t],
|
|
47
62
|
);
|
|
@@ -61,8 +76,8 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
61
76
|
watch,
|
|
62
77
|
} = useForm<BillLineItemForm>({
|
|
63
78
|
defaultValues: {
|
|
64
|
-
quantity: item.quantity
|
|
65
|
-
price: item.price
|
|
79
|
+
quantity: item.quantity,
|
|
80
|
+
price: item.price,
|
|
66
81
|
},
|
|
67
82
|
resolver: zodResolver(schema),
|
|
68
83
|
});
|
|
@@ -71,8 +86,10 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
71
86
|
const price = watch('price');
|
|
72
87
|
|
|
73
88
|
useEffect(() => {
|
|
74
|
-
const
|
|
75
|
-
|
|
89
|
+
const quantityNum = typeof quantity === 'number' ? quantity : parseFloat(quantity) || 0;
|
|
90
|
+
const priceNum = typeof price === 'number' ? price : parseFloat(price) || 0;
|
|
91
|
+
const newTotal = quantityNum * priceNum;
|
|
92
|
+
setTotal(isNaN(newTotal) ? 0 : newTotal);
|
|
76
93
|
}, [quantity, price]);
|
|
77
94
|
|
|
78
95
|
const onSubmit = async (data: BillLineItemForm) => {
|
|
@@ -80,8 +97,8 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
80
97
|
|
|
81
98
|
const newItem = {
|
|
82
99
|
...item,
|
|
83
|
-
quantity:
|
|
84
|
-
price:
|
|
100
|
+
quantity: data.quantity,
|
|
101
|
+
price: data.price,
|
|
85
102
|
billableService: getBillableServiceUuid(billableServices, item.billableService),
|
|
86
103
|
item: item?.item,
|
|
87
104
|
};
|
|
@@ -90,8 +107,9 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
90
107
|
.filter((currItem) => currItem.uuid !== item?.uuid)
|
|
91
108
|
.map((currItem) => ({
|
|
92
109
|
...currItem,
|
|
93
|
-
billableService: getBillableServiceUuid(billableServices,
|
|
110
|
+
billableService: getBillableServiceUuid(billableServices, currItem.billableService),
|
|
94
111
|
}));
|
|
112
|
+
|
|
95
113
|
const updatedLineItems = previousLineitems.concat(newItem);
|
|
96
114
|
|
|
97
115
|
const payload = {
|
|
@@ -107,16 +125,17 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
107
125
|
await updateBillItems(payload);
|
|
108
126
|
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
109
127
|
showSnackbar({
|
|
110
|
-
title: t('
|
|
111
|
-
subtitle: t('
|
|
128
|
+
title: t('lineItemUpdated', 'Line item updated'),
|
|
129
|
+
subtitle: t('lineItemUpdateSuccess', 'The bill line item has been updated successfully'),
|
|
112
130
|
kind: 'success',
|
|
113
131
|
});
|
|
114
132
|
closeModal();
|
|
115
133
|
} catch (error) {
|
|
116
134
|
showSnackbar({
|
|
117
|
-
title: t('
|
|
135
|
+
title: t('lineItemUpdateFailed', 'Failed to update line item'),
|
|
136
|
+
subtitle:
|
|
137
|
+
error?.message || t('lineItemUpdateErrorDefault', 'Unable to update the bill line item. Please try again.'),
|
|
118
138
|
kind: 'error',
|
|
119
|
-
subtitle: error?.message,
|
|
120
139
|
});
|
|
121
140
|
}
|
|
122
141
|
};
|
|
@@ -127,7 +146,7 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
127
146
|
|
|
128
147
|
return (
|
|
129
148
|
<>
|
|
130
|
-
<ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item
|
|
149
|
+
<ModalHeader closeModal={closeModal} title={t('editBillLineItem', 'Edit bill line item')} />
|
|
131
150
|
<Form onSubmit={handleSubmit(onSubmit, onError)}>
|
|
132
151
|
<ModalBody>
|
|
133
152
|
<Stack gap={5}>
|
|
@@ -138,28 +157,30 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
138
157
|
</div>
|
|
139
158
|
<section>
|
|
140
159
|
<p className={styles.label}>
|
|
141
|
-
{t('item', 'Item')}
|
|
160
|
+
{t('item', 'Item')}: {item?.billableService ? item?.billableService : item?.item}
|
|
142
161
|
</p>
|
|
143
162
|
<p className={styles.label}>
|
|
144
|
-
{t('currentPrice', 'Current price')}
|
|
163
|
+
{t('currentPrice', 'Current price')}: {item?.price}
|
|
145
164
|
</p>
|
|
146
165
|
<p className={styles.label}>
|
|
147
|
-
{t('status', 'status')}
|
|
166
|
+
{t('status', 'status')}: {item?.paymentStatus}
|
|
148
167
|
</p>
|
|
149
168
|
<Controller
|
|
150
169
|
name="quantity"
|
|
151
170
|
control={control}
|
|
152
171
|
render={({ field: { onChange, value } }) => (
|
|
153
172
|
<NumberInput
|
|
154
|
-
|
|
155
|
-
id="quantityInput"
|
|
156
|
-
min={0}
|
|
157
|
-
max={100}
|
|
158
|
-
value={value}
|
|
159
|
-
onChange={onChange}
|
|
173
|
+
disableWheel
|
|
160
174
|
className={styles.controlField}
|
|
161
|
-
|
|
175
|
+
hideSteppers
|
|
176
|
+
id="quantityInput"
|
|
177
|
+
invalid={!!errors.quantity}
|
|
162
178
|
invalidText={errors.quantity?.message}
|
|
179
|
+
label={t('quantity', 'Quantity')}
|
|
180
|
+
onChange={(_event, state: { value: number | string; direction: string }) => {
|
|
181
|
+
onChange(state.value);
|
|
182
|
+
}}
|
|
183
|
+
value={value}
|
|
163
184
|
/>
|
|
164
185
|
)}
|
|
165
186
|
/>
|
|
@@ -169,18 +190,18 @@ const EditBillLineItemModal: React.FC<EditBillLineItemModalProps> = ({ bill, clo
|
|
|
169
190
|
control={control}
|
|
170
191
|
render={({ field: { value } }) => (
|
|
171
192
|
<TextInput
|
|
193
|
+
className={styles.controlField}
|
|
194
|
+
helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
|
|
172
195
|
id="priceInput"
|
|
173
196
|
labelText={t('price', 'Unit Price')}
|
|
197
|
+
readOnly
|
|
174
198
|
value={value}
|
|
175
|
-
readOnly={true}
|
|
176
|
-
className={styles.controlField}
|
|
177
|
-
helperText={t('unitPriceHelperText', 'This is the unit price for this item.')}
|
|
178
199
|
/>
|
|
179
200
|
)}
|
|
180
201
|
/>
|
|
181
202
|
|
|
182
203
|
<p className={styles.label}>
|
|
183
|
-
{t('total', 'Total')}
|
|
204
|
+
{t('total', 'Total')}: {total}{' '}
|
|
184
205
|
</p>
|
|
185
206
|
|
|
186
207
|
{showErrorNotification && (
|
|
@@ -13,9 +13,15 @@ jest.mock('../billing.resource', () => ({
|
|
|
13
13
|
updateBillItems: jest.fn(() => Promise.resolve({})),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
+
const mockBillableServices = [
|
|
17
|
+
{ name: 'X-Ray Service', uuid: 'xray-uuid-123' },
|
|
18
|
+
{ name: 'Lab Test Service', uuid: 'lab-uuid-456' },
|
|
19
|
+
{ name: 'Consultation Service', uuid: 'consult-uuid-789' },
|
|
20
|
+
];
|
|
21
|
+
|
|
16
22
|
jest.mock('../billable-services/billable-service.resource', () => ({
|
|
17
23
|
useBillableServices: jest.fn(() => ({
|
|
18
|
-
billableServices:
|
|
24
|
+
billableServices: mockBillableServices,
|
|
19
25
|
})),
|
|
20
26
|
}));
|
|
21
27
|
|
|
@@ -40,7 +46,7 @@ const mockBill: MappedBill = {
|
|
|
40
46
|
voided: false,
|
|
41
47
|
voidReason: null,
|
|
42
48
|
priceName: 'Service Price',
|
|
43
|
-
billableService: '
|
|
49
|
+
billableService: 'X-Ray Service',
|
|
44
50
|
priceUuid: 'price-uuid',
|
|
45
51
|
lineItemOrder: 1,
|
|
46
52
|
resourceVersion: '1.0',
|
|
@@ -61,7 +67,7 @@ const mockItem = {
|
|
|
61
67
|
uuid: 'item-uuid',
|
|
62
68
|
quantity: 2,
|
|
63
69
|
price: 100,
|
|
64
|
-
billableService: '
|
|
70
|
+
billableService: 'X-Ray Service',
|
|
65
71
|
paymentStatus: 'UNPAID',
|
|
66
72
|
item: 'Test Service',
|
|
67
73
|
display: 'Test Service',
|
|
@@ -79,7 +85,7 @@ describe('ChangeStatus component', () => {
|
|
|
79
85
|
test('renders the form with correct fields and default values', () => {
|
|
80
86
|
render(<EditBillLineItemModal bill={mockBill} item={mockItem} closeModal={closeModalMock} />);
|
|
81
87
|
|
|
82
|
-
expect(screen.getByText('Edit bill line item
|
|
88
|
+
expect(screen.getByText('Edit bill line item')).toBeInTheDocument();
|
|
83
89
|
expect(screen.getByText('John Doe · Main Cashpoint · 123456')).toBeInTheDocument();
|
|
84
90
|
expect(screen.getByRole('spinbutton', { name: /Quantity/ })).toHaveValue(2);
|
|
85
91
|
expect(screen.getByLabelText(/Unit Price/)).toHaveValue('100');
|
|
@@ -107,8 +113,8 @@ describe('ChangeStatus component', () => {
|
|
|
107
113
|
await waitFor(() => {
|
|
108
114
|
expect(mockUpdateBillItems).toHaveBeenCalled();
|
|
109
115
|
expect(showSnackbar).toHaveBeenCalledWith({
|
|
110
|
-
title: '
|
|
111
|
-
subtitle: '
|
|
116
|
+
title: 'Line item updated',
|
|
117
|
+
subtitle: 'The bill line item has been updated successfully',
|
|
112
118
|
kind: 'success',
|
|
113
119
|
});
|
|
114
120
|
expect(closeModalMock).toHaveBeenCalled();
|
|
@@ -125,10 +131,92 @@ describe('ChangeStatus component', () => {
|
|
|
125
131
|
|
|
126
132
|
await waitFor(() => {
|
|
127
133
|
expect(mockShowSnackbar).toHaveBeenCalledWith({
|
|
128
|
-
title: '
|
|
134
|
+
title: 'Failed to update line item',
|
|
129
135
|
kind: 'error',
|
|
130
136
|
subtitle: 'Error occurred',
|
|
131
137
|
});
|
|
132
138
|
});
|
|
133
139
|
});
|
|
140
|
+
|
|
141
|
+
test('preserves billable service UUIDs for other line items when editing', async () => {
|
|
142
|
+
const user = userEvent.setup();
|
|
143
|
+
mockUpdateBillItems.mockResolvedValueOnce({} as FetchResponse<any>);
|
|
144
|
+
|
|
145
|
+
// Bill with multiple line items with different billable services
|
|
146
|
+
const billWithMultipleItems: MappedBill = {
|
|
147
|
+
...mockBill,
|
|
148
|
+
lineItems: [
|
|
149
|
+
{
|
|
150
|
+
uuid: 'item-1',
|
|
151
|
+
quantity: 1,
|
|
152
|
+
price: 100,
|
|
153
|
+
billableService: 'X-Ray Service',
|
|
154
|
+
paymentStatus: 'PENDING',
|
|
155
|
+
item: 'X-Ray',
|
|
156
|
+
display: 'X-Ray',
|
|
157
|
+
voided: false,
|
|
158
|
+
voidReason: null,
|
|
159
|
+
priceName: 'X-Ray Price',
|
|
160
|
+
priceUuid: 'xray-price-uuid',
|
|
161
|
+
lineItemOrder: 1,
|
|
162
|
+
resourceVersion: '1.0',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
uuid: 'item-2',
|
|
166
|
+
quantity: 2,
|
|
167
|
+
price: 50,
|
|
168
|
+
billableService: 'Lab Test Service',
|
|
169
|
+
paymentStatus: 'PENDING',
|
|
170
|
+
item: 'Lab Test',
|
|
171
|
+
display: 'Lab Test',
|
|
172
|
+
voided: false,
|
|
173
|
+
voidReason: null,
|
|
174
|
+
priceName: 'Lab Price',
|
|
175
|
+
priceUuid: 'lab-price-uuid',
|
|
176
|
+
lineItemOrder: 2,
|
|
177
|
+
resourceVersion: '1.0',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
uuid: 'item-3',
|
|
181
|
+
quantity: 1,
|
|
182
|
+
price: 200,
|
|
183
|
+
billableService: 'Consultation Service',
|
|
184
|
+
paymentStatus: 'PENDING',
|
|
185
|
+
item: 'Consultation',
|
|
186
|
+
display: 'Consultation',
|
|
187
|
+
voided: false,
|
|
188
|
+
voidReason: null,
|
|
189
|
+
priceName: 'Consult Price',
|
|
190
|
+
priceUuid: 'consult-price-uuid',
|
|
191
|
+
lineItemOrder: 3,
|
|
192
|
+
resourceVersion: '1.0',
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Editing the Lab Test item (item-2)
|
|
198
|
+
const itemToEdit = billWithMultipleItems.lineItems[1];
|
|
199
|
+
|
|
200
|
+
render(<EditBillLineItemModal bill={billWithMultipleItems} item={itemToEdit} closeModal={closeModalMock} />);
|
|
201
|
+
|
|
202
|
+
await user.click(screen.getByText(/Save/));
|
|
203
|
+
|
|
204
|
+
await waitFor(() => {
|
|
205
|
+
expect(mockUpdateBillItems).toHaveBeenCalled();
|
|
206
|
+
const payload = mockUpdateBillItems.mock.calls[0][0];
|
|
207
|
+
|
|
208
|
+
// Verify that each line item has the correct billable service UUID
|
|
209
|
+
const xrayItem = payload.lineItems.find((li) => li.uuid === 'item-1');
|
|
210
|
+
const consultItem = payload.lineItems.find((li) => li.uuid === 'item-3');
|
|
211
|
+
|
|
212
|
+
// These should NOT have the Lab Test UUID (lab-uuid-456)
|
|
213
|
+
// They should keep their original UUIDs
|
|
214
|
+
expect(xrayItem?.billableService).toBe('xray-uuid-123');
|
|
215
|
+
expect(consultItem?.billableService).toBe('consult-uuid-789');
|
|
216
|
+
|
|
217
|
+
// The edited item should have the Lab Test UUID
|
|
218
|
+
const labItem = payload.lineItems.find((li) => li.uuid === 'item-2');
|
|
219
|
+
expect(labItem?.billableService).toBe('lab-uuid-456');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
134
222
|
});
|
|
@@ -243,6 +243,8 @@ const BillingForm: React.FC<BillingFormProps> = ({ patientUuid, closeWorkspace }
|
|
|
243
243
|
<div className={styles.controlSection}>
|
|
244
244
|
<label>{t('quantity', 'Quantity')}</label>
|
|
245
245
|
<NumberInput
|
|
246
|
+
disableWheel
|
|
247
|
+
hideSteppers
|
|
246
248
|
id={`quantity-${item.uuid}`}
|
|
247
249
|
min={1}
|
|
248
250
|
value={item.quantity}
|
package/src/config-schema.ts
CHANGED
|
@@ -75,11 +75,6 @@ export const configSchema = {
|
|
|
75
75
|
_description: 'The default page size',
|
|
76
76
|
_default: 10,
|
|
77
77
|
},
|
|
78
|
-
showEditBillButton: {
|
|
79
|
-
_type: Type.Boolean,
|
|
80
|
-
_description: 'Whether to show the edit bill button or not.',
|
|
81
|
-
_default: false,
|
|
82
|
-
},
|
|
83
78
|
};
|
|
84
79
|
|
|
85
80
|
export interface BillingConfig {
|
|
@@ -115,5 +110,4 @@ export interface BillingConfig {
|
|
|
115
110
|
};
|
|
116
111
|
defaultCurrency: string;
|
|
117
112
|
pageSize: number;
|
|
118
|
-
showEditBillButton: boolean;
|
|
119
113
|
}
|
|
@@ -38,7 +38,7 @@ type InvoiceTableProps = {
|
|
|
38
38
|
|
|
39
39
|
const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
40
40
|
const { t } = useTranslation();
|
|
41
|
-
const { defaultCurrency
|
|
41
|
+
const { defaultCurrency } = useConfig<BillingConfig>();
|
|
42
42
|
const layout = useLayoutType();
|
|
43
43
|
const lineItems = useMemo(() => bill?.lineItems ?? [], [bill?.lineItems]);
|
|
44
44
|
const responsiveSize = isDesktop(layout) ? 'sm' : 'lg';
|
|
@@ -93,27 +93,23 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isLoadingBill }) => {
|
|
|
93
93
|
status: item.paymentStatus,
|
|
94
94
|
quantity: item.quantity,
|
|
95
95
|
price: convertToCurrency(item.price, defaultCurrency),
|
|
96
|
-
total: item.price * item.quantity,
|
|
96
|
+
total: convertToCurrency(item.price * item.quantity, defaultCurrency),
|
|
97
97
|
actionButton: (
|
|
98
98
|
<span>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
/>
|
|
109
|
-
) : (
|
|
110
|
-
'--'
|
|
111
|
-
)}
|
|
99
|
+
<Button
|
|
100
|
+
data-testid={`edit-button-${item.uuid}`}
|
|
101
|
+
renderIcon={Edit}
|
|
102
|
+
hasIconOnly
|
|
103
|
+
kind="ghost"
|
|
104
|
+
iconDescription={t('editThisBillItem', 'Edit this bill item')}
|
|
105
|
+
tooltipPosition="left"
|
|
106
|
+
onClick={() => handleSelectBillItem(item)}
|
|
107
|
+
/>
|
|
112
108
|
</span>
|
|
113
109
|
),
|
|
114
110
|
};
|
|
115
111
|
}) ?? [],
|
|
116
|
-
[filteredLineItems, bill?.receiptNumber, defaultCurrency,
|
|
112
|
+
[filteredLineItems, bill?.receiptNumber, defaultCurrency, t, handleSelectBillItem],
|
|
117
113
|
);
|
|
118
114
|
|
|
119
115
|
if (isLoadingBill) {
|