@openmrs/esm-billing-app 1.4.1-pre.2132 → 1.5.0

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.
@@ -12,8 +12,10 @@ import {
12
12
  NumberInput,
13
13
  } from '@carbon/react';
14
14
  import {
15
+ ExtensionSlot,
15
16
  useConfig,
16
17
  useLayoutType,
18
+ usePatient,
17
19
  useVisit,
18
20
  showSnackbar,
19
21
  getCoreTranslation,
@@ -60,6 +62,7 @@ const BillingForm: React.FC<Workspace2DefinitionProps<BillingFormProps>> = ({
60
62
  }) => {
61
63
  const isTablet = useLayoutType() === 'tablet';
62
64
  const { t } = useTranslation();
65
+ const { patient } = usePatient(patientUuid);
63
66
  const { mutate: swrMutate } = useSWRConfig();
64
67
  const { defaultCurrency, postBilledItems } = useConfig<BillingConfig>();
65
68
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -272,152 +275,157 @@ const BillingForm: React.FC<Workspace2DefinitionProps<BillingFormProps>> = ({
272
275
  title={isEditMode ? t('addItemsToBill', 'Add items to bill') : t('addBillItems', 'Add bill items')}
273
276
  hasUnsavedChanges={selectedItems.length > 0}>
274
277
  <Form className={styles.form} onSubmit={handleSubmit}>
275
- <div className={styles.grid}>
276
- {billUuid && isLoadingBill ? (
277
- <InlineLoading description={getCoreTranslation('loading') + '...'} />
278
- ) : billUuid && billError ? (
279
- <InlineNotification
280
- kind="error"
281
- lowContrast
282
- title={t('errorLoadingBill', 'Error loading bill')}
283
- subtitle={billError?.message}
284
- />
285
- ) : isEditMode && billableServicesError ? (
286
- <InlineNotification
287
- kind="error"
288
- lowContrast
289
- title={t('errorLoadingBillableServices', 'Error loading billable services')}
290
- subtitle={billableServicesError?.message}
291
- />
292
- ) : (
293
- <>
294
- {isEditMode && (
295
- <div className={styles.existingItemsContainer}>
296
- <h4 className={styles.sectionHeading}>{t('existingItems', 'Existing items')}</h4>
297
- {bill.lineItems.map((item) => (
298
- <div key={item.uuid} className={styles.existingItemRow}>
299
- <span className={styles.existingItemName}>
300
- {item.billableService || item.item || item.display}
301
- </span>
302
- <span className={styles.existingItemDetail}>
303
- {item.quantity} x {convertToCurrency(item.price, defaultCurrency)}
304
- </span>
305
- <span className={styles.existingItemTotal}>
306
- {convertToCurrency(item.price * item.quantity, defaultCurrency)}
307
- </span>
308
- </div>
309
- ))}
310
- <div className={styles.existingItemsSubtotal}>
311
- <strong>
312
- {t('subtotal', 'Subtotal')}: {convertToCurrency(existingItemsTotal, defaultCurrency)}
313
- </strong>
314
- </div>
315
- </div>
316
- )}
317
- {isEditMode && <h4 className={styles.sectionHeading}>{t('newItems', 'New items')}</h4>}
318
- {isLoading ? (
319
- <InlineLoading description={getCoreTranslation('loading') + '...'} />
320
- ) : error ? (
321
- <InlineNotification
322
- kind="error"
323
- lowContrast
324
- title={t('errorLoadingBillableServices', 'Error loading billable services')}
325
- subtitle={error?.message}
326
- />
327
- ) : (
328
- <ComboBox
329
- id="searchItems"
330
- onChange={({ selectedItem: item }: { selectedItem: BillableItem }) => selectBillableItem(item)}
331
- itemToString={(item: BillableItem) => item?.name || ''}
332
- items={availableBillableItems}
333
- titleText={t('searchItems', 'Search items and services')}
334
- />
335
- )}
336
- </>
278
+ <div className={styles.formContent}>
279
+ {isTablet && patient && patientUuid && (
280
+ <ExtensionSlot name="patient-header-slot" state={{ patient, patientUuid, hideActionsOverflow: true }} />
337
281
  )}
338
- {selectedItems && selectedItems.length > 0 && (
339
- <div className={styles.selectedItemsContainer}>
340
- <h4 className={styles.sectionHeading}>{t('selectedItems', 'Selected items')}</h4>
341
- {selectedItems.map((item) => (
342
- <div key={item.uuid} className={styles.itemCard}>
343
- <div className={styles.itemHeader}>
344
- <span className={styles.itemName}>{item.display}</span>
345
- <IconButton
346
- align="top-end"
347
- kind="ghost"
348
- label={t('remove', 'Remove')}
349
- onClick={() => removeSelectedBillableItem(item.uuid)}>
350
- <TrashCanIcon size={16} />
351
- </IconButton>
282
+ <div className={styles.grid}>
283
+ {billUuid && isLoadingBill ? (
284
+ <InlineLoading description={getCoreTranslation('loading') + '...'} />
285
+ ) : billUuid && billError ? (
286
+ <InlineNotification
287
+ kind="error"
288
+ lowContrast
289
+ title={t('errorLoadingBill', 'Error loading bill')}
290
+ subtitle={billError?.message}
291
+ />
292
+ ) : isEditMode && billableServicesError ? (
293
+ <InlineNotification
294
+ kind="error"
295
+ lowContrast
296
+ title={t('errorLoadingBillableServices', 'Error loading billable services')}
297
+ subtitle={billableServicesError?.message}
298
+ />
299
+ ) : (
300
+ <>
301
+ {isEditMode && (
302
+ <div className={styles.existingItemsContainer}>
303
+ <h4 className={styles.sectionHeading}>{t('existingItems', 'Existing items')}</h4>
304
+ {bill.lineItems.map((item) => (
305
+ <div key={item.uuid} className={styles.existingItemRow}>
306
+ <span className={styles.existingItemName}>
307
+ {item.billableService || item.item || item.display}
308
+ </span>
309
+ <span className={styles.existingItemDetail}>
310
+ {item.quantity} x {convertToCurrency(item.price, defaultCurrency)}
311
+ </span>
312
+ <span className={styles.existingItemTotal}>
313
+ {convertToCurrency(item.price * item.quantity, defaultCurrency)}
314
+ </span>
315
+ </div>
316
+ ))}
317
+ <div className={styles.existingItemsSubtotal}>
318
+ <strong>
319
+ {t('subtotal', 'Subtotal')}: {convertToCurrency(existingItemsTotal, defaultCurrency)}
320
+ </strong>
321
+ </div>
352
322
  </div>
323
+ )}
324
+ {isEditMode && <h4 className={styles.sectionHeading}>{t('newItems', 'New items')}</h4>}
325
+ {isLoading ? (
326
+ <InlineLoading description={getCoreTranslation('loading') + '...'} />
327
+ ) : error ? (
328
+ <InlineNotification
329
+ kind="error"
330
+ lowContrast
331
+ title={t('errorLoadingBillableServices', 'Error loading billable services')}
332
+ subtitle={error?.message}
333
+ />
334
+ ) : (
335
+ <ComboBox
336
+ id="searchItems"
337
+ onChange={({ selectedItem: item }: { selectedItem: BillableItem }) => selectBillableItem(item)}
338
+ itemToString={(item: BillableItem) => item?.name || ''}
339
+ items={availableBillableItems}
340
+ titleText={t('searchItems', 'Search items and services')}
341
+ />
342
+ )}
343
+ </>
344
+ )}
345
+ {selectedItems && selectedItems.length > 0 && (
346
+ <div className={styles.selectedItemsContainer}>
347
+ <h4 className={styles.sectionHeading}>{t('selectedItems', 'Selected items')}</h4>
348
+ {selectedItems.map((item) => (
349
+ <div key={item.uuid} className={styles.itemCard}>
350
+ <div className={styles.itemHeader}>
351
+ <span className={styles.itemName}>{item.display}</span>
352
+ <IconButton
353
+ align="top-end"
354
+ kind="ghost"
355
+ label={t('remove', 'Remove')}
356
+ onClick={() => removeSelectedBillableItem(item.uuid)}>
357
+ <TrashCanIcon size={16} />
358
+ </IconButton>
359
+ </div>
353
360
 
354
- <div className={styles.itemControls}>
355
- {item.availablePaymentMethods && item.availablePaymentMethods.length > 1 ? (
356
- <div className={styles.controlSection}>
357
- <label>{t('selectPaymentMethod', 'Select payment method')}</label>
358
- <ComboBox
359
- id={`payment-method-${item.uuid}`}
360
- items={item.availablePaymentMethods}
361
- itemToString={(method: ServicePrice) =>
362
- method
363
- ? `${method.name} - ${convertToCurrency(
364
- typeof method.price === 'number' ? method.price : parseFloat(method.price),
365
- defaultCurrency,
366
- )}`
367
- : ''
368
- }
369
- selectedItem={item.selectedPaymentMethod}
370
- onChange={({ selectedItem }) => {
371
- if (selectedItem) {
372
- updatePaymentMethod(item.uuid, selectedItem);
361
+ <div className={styles.itemControls}>
362
+ {item.availablePaymentMethods && item.availablePaymentMethods.length > 1 ? (
363
+ <div className={styles.controlSection}>
364
+ <label>{t('selectPaymentMethod', 'Select payment method')}</label>
365
+ <ComboBox
366
+ id={`payment-method-${item.uuid}`}
367
+ items={item.availablePaymentMethods}
368
+ itemToString={(method: ServicePrice) =>
369
+ method
370
+ ? `${method.name} - ${convertToCurrency(
371
+ typeof method.price === 'number' ? method.price : parseFloat(method.price),
372
+ defaultCurrency,
373
+ )}`
374
+ : ''
373
375
  }
376
+ selectedItem={item.selectedPaymentMethod}
377
+ onChange={({ selectedItem }) => {
378
+ if (selectedItem) {
379
+ updatePaymentMethod(item.uuid, selectedItem);
380
+ }
381
+ }}
382
+ placeholder={t('selectPaymentMethod', 'Select payment method')}
383
+ titleText=""
384
+ />
385
+ </div>
386
+ ) : (
387
+ <div className={styles.controlSection}>
388
+ <label>{t('unitPrice', 'Unit price')}</label>
389
+ <span className={styles.priceDisplay}>{convertToCurrency(item.price, defaultCurrency)}</span>
390
+ </div>
391
+ )}
392
+
393
+ <div className={styles.controlSection}>
394
+ <label>{t('quantity', 'Quantity')}</label>
395
+ <NumberInput
396
+ allowEmpty
397
+ disableWheel
398
+ hideSteppers
399
+ id={`quantity-${item.uuid}`}
400
+ invalid={!item.quantity || item.quantity < 1}
401
+ invalidText={t('quantityMustBeAtLeastOne', 'Quantity must be at least 1')}
402
+ onChange={(_, { value }) => {
403
+ const number = parseFloat(String(value));
404
+ updateQuantity(item.uuid, isNaN(number) ? 0 : number);
374
405
  }}
375
- placeholder={t('selectPaymentMethod', 'Select payment method')}
376
- titleText=""
406
+ value={item.quantity || ''}
377
407
  />
378
408
  </div>
379
- ) : (
409
+
380
410
  <div className={styles.controlSection}>
381
- <label>{t('unitPrice', 'Unit price')}</label>
382
- <span className={styles.priceDisplay}>{convertToCurrency(item.price, defaultCurrency)}</span>
411
+ <label>{t('total', 'Total')}</label>
412
+ <span className={styles.totalDisplay}>
413
+ {convertToCurrency(item.price * item.quantity, defaultCurrency)}
414
+ </span>
383
415
  </div>
384
- )}
385
-
386
- <div className={styles.controlSection}>
387
- <label>{t('quantity', 'Quantity')}</label>
388
- <NumberInput
389
- allowEmpty
390
- disableWheel
391
- hideSteppers
392
- id={`quantity-${item.uuid}`}
393
- invalid={!item.quantity || item.quantity < 1}
394
- invalidText={t('quantityMustBeAtLeastOne', 'Quantity must be at least 1')}
395
- onChange={(_, { value }) => {
396
- const number = parseFloat(String(value));
397
- updateQuantity(item.uuid, isNaN(number) ? 0 : number);
398
- }}
399
- value={item.quantity || ''}
400
- />
401
- </div>
402
-
403
- <div className={styles.controlSection}>
404
- <label>{t('total', 'Total')}</label>
405
- <span className={styles.totalDisplay}>
406
- {convertToCurrency(item.price * item.quantity, defaultCurrency)}
407
- </span>
408
416
  </div>
409
417
  </div>
410
- </div>
411
- ))}
418
+ ))}
412
419
 
413
- <div className={styles.grandTotal}>
414
- <strong>
415
- {t('grandTotal', 'Grand total')}:{' '}
416
- {convertToCurrency(existingItemsTotal + calculateTotalAmount(selectedItems), defaultCurrency)}
417
- </strong>
420
+ <div className={styles.grandTotal}>
421
+ <strong>
422
+ {t('grandTotal', 'Grand total')}:{' '}
423
+ {convertToCurrency(existingItemsTotal + calculateTotalAmount(selectedItems), defaultCurrency)}
424
+ </strong>
425
+ </div>
418
426
  </div>
419
- </div>
420
- )}
427
+ )}
428
+ </div>
421
429
  </div>
422
430
 
423
431
  <ButtonSet className={isTablet ? styles.tablet : styles.desktop}>
@@ -1 +0,0 @@
1
- {"hash":"8872ba2be2ec04be","duration":9885}