@openmrs/esm-billing-app 1.0.2-pre.863 → 1.0.2-pre.866
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/dist/4344.js +1 -1
- package/dist/4344.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 +6 -6
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/billable-services/create-edit/add-billable-service.component.tsx +88 -67
- package/src/billable-services/create-edit/add-billable-service.test.tsx +558 -1
|
@@ -591,9 +591,9 @@
|
|
|
591
591
|
"initial": false,
|
|
592
592
|
"entry": false,
|
|
593
593
|
"recorded": false,
|
|
594
|
-
"size":
|
|
594
|
+
"size": 1178229,
|
|
595
595
|
"sizes": {
|
|
596
|
-
"javascript":
|
|
596
|
+
"javascript": 1178187,
|
|
597
597
|
"consume-shared": 42
|
|
598
598
|
},
|
|
599
599
|
"names": [],
|
|
@@ -607,7 +607,7 @@
|
|
|
607
607
|
"auxiliaryFiles": [
|
|
608
608
|
"4344.js.map"
|
|
609
609
|
],
|
|
610
|
-
"hash": "
|
|
610
|
+
"hash": "29bf4b30203bd689",
|
|
611
611
|
"childrenByOrder": {}
|
|
612
612
|
},
|
|
613
613
|
{
|
|
@@ -1236,10 +1236,10 @@
|
|
|
1236
1236
|
"initial": true,
|
|
1237
1237
|
"entry": true,
|
|
1238
1238
|
"recorded": false,
|
|
1239
|
-
"size":
|
|
1239
|
+
"size": 5461683,
|
|
1240
1240
|
"sizes": {
|
|
1241
1241
|
"consume-shared": 210,
|
|
1242
|
-
"javascript":
|
|
1242
|
+
"javascript": 5439027,
|
|
1243
1243
|
"share-init": 336,
|
|
1244
1244
|
"runtime": 22110
|
|
1245
1245
|
},
|
|
@@ -1256,7 +1256,7 @@
|
|
|
1256
1256
|
"auxiliaryFiles": [
|
|
1257
1257
|
"main.js.map"
|
|
1258
1258
|
],
|
|
1259
|
-
"hash": "
|
|
1259
|
+
"hash": "bc8a8b57973d7d6d",
|
|
1260
1260
|
"childrenByOrder": {}
|
|
1261
1261
|
},
|
|
1262
1262
|
{
|
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"}],"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-bill-line-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"}],"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-bill-line-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.866"}
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ import { Add, TrashCan } from '@carbon/react/icons';
|
|
|
20
20
|
import { z } from 'zod';
|
|
21
21
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
22
22
|
import { getCoreTranslation, navigate, ResponsiveWrapper, showSnackbar, useDebounce } from '@openmrs/esm-framework';
|
|
23
|
-
import type { BillableService,
|
|
23
|
+
import type { BillableService, ServicePrice } from '../../types';
|
|
24
24
|
import {
|
|
25
25
|
createBillableService,
|
|
26
26
|
updateBillableService,
|
|
@@ -30,34 +30,74 @@ import {
|
|
|
30
30
|
} from '../billable-service.resource';
|
|
31
31
|
import styles from './add-billable-service.scss';
|
|
32
32
|
|
|
33
|
-
interface
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
interface PaymentModeForm {
|
|
39
|
-
paymentMode: string;
|
|
40
|
-
price: string | number | undefined;
|
|
33
|
+
interface AddBillableServiceProps {
|
|
34
|
+
serviceToEdit?: BillableService;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
onServiceUpdated?: () => void;
|
|
37
|
+
isModal?: boolean;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
interface BillableServiceFormData {
|
|
44
41
|
name: string;
|
|
45
|
-
|
|
42
|
+
payment: PaymentModeForm[];
|
|
46
43
|
serviceType: ServiceType | null;
|
|
47
44
|
concept?: { uuid: string; display: string } | null;
|
|
48
|
-
|
|
45
|
+
shortName?: string;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
interface
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
onServiceUpdated?: () => void;
|
|
55
|
-
isModal?: boolean;
|
|
48
|
+
interface PaymentModeForm {
|
|
49
|
+
paymentMode: string;
|
|
50
|
+
price: string | number | undefined;
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
interface ServiceType {
|
|
54
|
+
uuid: string;
|
|
55
|
+
display: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DEFAULT_PAYMENT_OPTION: PaymentModeForm = { paymentMode: '', price: '' };
|
|
59
59
|
const MAX_NAME_LENGTH = 255;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Transforms a BillableService into form data structure
|
|
63
|
+
* Centralizes the mapping logic to avoid duplication between defaultValues and reset()
|
|
64
|
+
* Exported for testing
|
|
65
|
+
*/
|
|
66
|
+
export const transformServiceToFormData = (service?: BillableService): BillableServiceFormData => {
|
|
67
|
+
if (!service) {
|
|
68
|
+
return {
|
|
69
|
+
name: '',
|
|
70
|
+
shortName: '',
|
|
71
|
+
serviceType: null,
|
|
72
|
+
concept: null,
|
|
73
|
+
payment: [DEFAULT_PAYMENT_OPTION],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
name: service.name || '',
|
|
79
|
+
shortName: service.shortName || '',
|
|
80
|
+
serviceType: service.serviceType || null,
|
|
81
|
+
concept: service.concept ? { uuid: service.concept.uuid, display: service.concept.display } : null,
|
|
82
|
+
payment: service.servicePrices?.map((servicePrice: ServicePrice) => ({
|
|
83
|
+
paymentMode: servicePrice.paymentMode?.uuid || '',
|
|
84
|
+
price: servicePrice.price ?? '',
|
|
85
|
+
})) || [DEFAULT_PAYMENT_OPTION],
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Normalizes price value from form (string | number | undefined) to number
|
|
91
|
+
* Handles Carbon NumberInput which can return either type
|
|
92
|
+
* Exported for testing
|
|
93
|
+
*/
|
|
94
|
+
export const normalizePrice = (price: string | number | undefined): number => {
|
|
95
|
+
if (typeof price === 'number') {
|
|
96
|
+
return price;
|
|
97
|
+
}
|
|
98
|
+
return parseFloat(String(price));
|
|
99
|
+
};
|
|
100
|
+
|
|
61
101
|
const createBillableServiceSchema = (t: TFunction) => {
|
|
62
102
|
const servicePriceSchema = z.object({
|
|
63
103
|
paymentMode: z
|
|
@@ -143,18 +183,7 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
143
183
|
reset,
|
|
144
184
|
} = useForm<BillableServiceFormData>({
|
|
145
185
|
mode: 'all',
|
|
146
|
-
defaultValues:
|
|
147
|
-
name: serviceToEdit?.name || '',
|
|
148
|
-
shortName: serviceToEdit?.shortName || '',
|
|
149
|
-
serviceType: serviceToEdit?.serviceType || null,
|
|
150
|
-
concept: serviceToEdit?.concept
|
|
151
|
-
? { uuid: serviceToEdit.concept.uuid, display: serviceToEdit.concept.display }
|
|
152
|
-
: null,
|
|
153
|
-
payment: serviceToEdit?.servicePrices?.map((servicePrice: ServicePrice) => ({
|
|
154
|
-
paymentMode: servicePrice.paymentMode?.uuid || '',
|
|
155
|
-
price: servicePrice.price || '',
|
|
156
|
-
})) || [DEFAULT_PAYMENT_OPTION],
|
|
157
|
-
},
|
|
186
|
+
defaultValues: transformServiceToFormData(serviceToEdit),
|
|
158
187
|
resolver: zodResolver(billableServiceSchema),
|
|
159
188
|
});
|
|
160
189
|
const { fields, remove, append } = useFieldArray({ name: 'payment', control });
|
|
@@ -174,22 +203,13 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
174
203
|
to: window.getOpenmrsSpaBase() + 'billable-services',
|
|
175
204
|
});
|
|
176
205
|
|
|
206
|
+
// Re-initialize form when editing and dependencies load
|
|
207
|
+
// Needed because serviceTypes/paymentModes may not be available during initial render
|
|
177
208
|
useEffect(() => {
|
|
178
209
|
if (serviceToEdit && !isLoadingPaymentModes && !isLoadingServiceTypes) {
|
|
179
|
-
reset(
|
|
180
|
-
name: serviceToEdit.name || '',
|
|
181
|
-
shortName: serviceToEdit.shortName || '',
|
|
182
|
-
serviceType: serviceToEdit.serviceType || null,
|
|
183
|
-
concept: serviceToEdit.concept
|
|
184
|
-
? { uuid: serviceToEdit.concept.uuid, display: serviceToEdit.concept.display }
|
|
185
|
-
: null,
|
|
186
|
-
payment: serviceToEdit.servicePrices.map((payment: ServicePrice) => ({
|
|
187
|
-
paymentMode: payment.paymentMode?.uuid || '',
|
|
188
|
-
price: payment.price || '',
|
|
189
|
-
})),
|
|
190
|
-
});
|
|
210
|
+
reset(transformServiceToFormData(serviceToEdit));
|
|
191
211
|
}
|
|
192
|
-
}, [serviceToEdit, isLoadingPaymentModes,
|
|
212
|
+
}, [serviceToEdit, isLoadingPaymentModes, isLoadingServiceTypes, reset]);
|
|
193
213
|
|
|
194
214
|
const onSubmit = async (data: BillableServiceFormData) => {
|
|
195
215
|
const payload = {
|
|
@@ -201,7 +221,7 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
201
221
|
return {
|
|
202
222
|
paymentMode: payment.paymentMode,
|
|
203
223
|
name: mode?.name || 'Unknown',
|
|
204
|
-
price:
|
|
224
|
+
price: normalizePrice(payment.price),
|
|
205
225
|
};
|
|
206
226
|
}),
|
|
207
227
|
serviceStatus: 'ENABLED',
|
|
@@ -277,13 +297,14 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
277
297
|
<Layer>
|
|
278
298
|
<TextInput
|
|
279
299
|
{...field}
|
|
300
|
+
enableCounter
|
|
280
301
|
id="serviceName"
|
|
281
|
-
type="text"
|
|
282
|
-
labelText={t('serviceName', 'Service name')}
|
|
283
|
-
placeholder={t('enterServiceName', 'Enter service name')}
|
|
284
|
-
maxLength={MAX_NAME_LENGTH}
|
|
285
302
|
invalid={!!errors.name}
|
|
286
303
|
invalidText={errors.name?.message}
|
|
304
|
+
labelText={t('serviceName', 'Service name')}
|
|
305
|
+
maxCount={MAX_NAME_LENGTH}
|
|
306
|
+
placeholder={t('enterServiceName', 'Enter service name')}
|
|
307
|
+
type="text"
|
|
287
308
|
/>
|
|
288
309
|
</Layer>
|
|
289
310
|
)}
|
|
@@ -298,14 +319,15 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
298
319
|
<Layer>
|
|
299
320
|
<TextInput
|
|
300
321
|
{...field}
|
|
301
|
-
|
|
322
|
+
enableCounter
|
|
302
323
|
id="serviceShortName"
|
|
303
|
-
type="text"
|
|
304
|
-
labelText={t('shortName', 'Short name')}
|
|
305
|
-
placeholder={t('enterServiceShortName', 'Enter service short name')}
|
|
306
|
-
maxLength={MAX_NAME_LENGTH}
|
|
307
324
|
invalid={!!errors.shortName}
|
|
308
325
|
invalidText={errors.shortName?.message}
|
|
326
|
+
labelText={t('shortName', 'Short name')}
|
|
327
|
+
maxCount={MAX_NAME_LENGTH}
|
|
328
|
+
placeholder={t('enterServiceShortName', 'Enter service short name')}
|
|
329
|
+
type="text"
|
|
330
|
+
value={field.value || ''}
|
|
309
331
|
/>
|
|
310
332
|
</Layer>
|
|
311
333
|
)}
|
|
@@ -315,15 +337,15 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
315
337
|
<FormLabel className={styles.conceptLabel}>{t('associatedConcept', 'Associated concept')}</FormLabel>
|
|
316
338
|
<ResponsiveWrapper>
|
|
317
339
|
<Search
|
|
318
|
-
ref={searchInputRef}
|
|
319
340
|
id="conceptsSearch"
|
|
320
341
|
labelText={t('associatedConcept', 'Associated concept')}
|
|
321
|
-
placeholder={t('searchConcepts', 'Search associated concept')}
|
|
322
342
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
|
|
323
343
|
onClear={() => {
|
|
324
344
|
setSearchTerm('');
|
|
325
345
|
setValue('concept', null);
|
|
326
346
|
}}
|
|
347
|
+
placeholder={t('searchConcepts', 'Search associated concept')}
|
|
348
|
+
ref={searchInputRef}
|
|
327
349
|
value={selectedConcept?.display || searchTerm}
|
|
328
350
|
/>
|
|
329
351
|
</ResponsiveWrapper>
|
|
@@ -340,7 +362,6 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
340
362
|
<ul className={styles.conceptsList}>
|
|
341
363
|
{searchResults?.map((searchResult) => (
|
|
342
364
|
<li
|
|
343
|
-
role="menuitem"
|
|
344
365
|
className={styles.service}
|
|
345
366
|
key={searchResult.concept.uuid}
|
|
346
367
|
onClick={() => {
|
|
@@ -349,7 +370,8 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
349
370
|
display: searchResult.display,
|
|
350
371
|
});
|
|
351
372
|
setSearchTerm('');
|
|
352
|
-
}}
|
|
373
|
+
}}
|
|
374
|
+
role="menuitem">
|
|
353
375
|
{searchResult.display}
|
|
354
376
|
</li>
|
|
355
377
|
))}
|
|
@@ -399,14 +421,14 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
399
421
|
<Layer>
|
|
400
422
|
<Dropdown
|
|
401
423
|
id={`paymentMode-${index}`}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
label={t('selectPaymentMode', 'Select payment mode')}
|
|
424
|
+
invalid={!!errors?.payment?.[index]?.paymentMode}
|
|
425
|
+
invalidText={errors?.payment?.[index]?.paymentMode?.message}
|
|
405
426
|
items={paymentModes ?? []}
|
|
406
427
|
itemToString={(item) => (item ? item.name : '')}
|
|
428
|
+
label={t('selectPaymentMode', 'Select payment mode')}
|
|
429
|
+
onChange={({ selectedItem }) => field.onChange(selectedItem.uuid)}
|
|
407
430
|
selectedItem={paymentModes.find((mode) => mode.uuid === field.value)}
|
|
408
|
-
|
|
409
|
-
invalidText={errors?.payment?.[index]?.paymentMode?.message}
|
|
431
|
+
titleText={t('paymentMode', 'Payment mode')}
|
|
410
432
|
/>
|
|
411
433
|
</Layer>
|
|
412
434
|
)}
|
|
@@ -424,12 +446,11 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
424
446
|
label={t('sellingPrice', 'Selling price')}
|
|
425
447
|
min={0}
|
|
426
448
|
onChange={(_, { value }) => {
|
|
427
|
-
|
|
428
|
-
field.onChange(numValue);
|
|
449
|
+
field.onChange(value === '' || value === undefined ? '' : value);
|
|
429
450
|
}}
|
|
430
451
|
placeholder={t('enterSellingPrice', 'Enter selling price')}
|
|
431
452
|
step={0.01}
|
|
432
|
-
value={field.value
|
|
453
|
+
value={field.value === undefined || field.value === null ? '' : field.value}
|
|
433
454
|
/>
|
|
434
455
|
</Layer>
|
|
435
456
|
)}
|
|
@@ -440,12 +461,12 @@ const AddBillableService: React.FC<AddBillableServiceProps> = ({
|
|
|
440
461
|
</div>
|
|
441
462
|
))}
|
|
442
463
|
<Button
|
|
464
|
+
className={styles.paymentButtons}
|
|
465
|
+
iconDescription={t('add', 'Add')}
|
|
443
466
|
kind="tertiary"
|
|
444
|
-
type="button"
|
|
445
467
|
onClick={handleAppendPaymentMode}
|
|
446
|
-
className={styles.paymentButtons}
|
|
447
468
|
renderIcon={(props) => <Add size={24} {...props} />}
|
|
448
|
-
|
|
469
|
+
type="button">
|
|
449
470
|
{t('addPaymentOption', 'Add payment option')}
|
|
450
471
|
</Button>
|
|
451
472
|
{getPaymentErrorMessage() && <div className={styles.errorMessage}>{getPaymentErrorMessage()}</div>}
|