@kenyaemr/esm-billing-app 5.3.8-pre.1596 → 5.3.8-pre.1608

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/routes.json CHANGED
@@ -1 +1 @@
1
- {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"billableServicesHome","route":"billable-services"},{"component":"requirePaymentModal","routeRegex":"^patient/.+/chart","online":true,"offline":false}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"}},{"component":"benefitsPackageDashboardLink","name":"benefits-package-dashboard-link","slot":"patient-chart-dashboard-slot","meta":{"name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot","path":"insurance-benefits","columns":1,"columnSpan":1},"featureFlag":"healthInformationExchange"},{"component":"benefitsPackage","name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"component":"benefitsEligibilyRequestForm","name":"benefits-eligibility-request-form"},{"component":"benefitsPreAuthForm","name":"benefits-pre-auth-form"},{"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","layoutMode":"anchored"}},{"name":"billing-check-in-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm"},{"name":"require-billing-modal","component":"requirePaymentModal"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"initiate-payment-modal","component":"initiatePaymentDialog"},{"name":"delete-billableservice-modal","component":"deleteBillableServiceModal"},{"name":"refund-bill-modal","component":"refundBillModal"},{"name":"delete-bill-modal","component":"deleteBillModal"},{"name":"lab-order-billable-item","component":"labOrder","slot":"top-of-lab-order-form-slot"},{"name":"procedure-order-billable-item","component":"procedureOrder","slot":"top-of-procedure-order-form-slot"},{"name":"imaging-order-billable-item","component":"imagingOrder","slot":"top-of-imaging-order-form-slot"},{"name":"price-info-order","component":"priceInfoOrder"},{"name":"drug-order-billable-item","component":"drugOrder","slot":"medication-info-slot"},{"name":"billing-test-order-action","component":"testOrderAction","slot":"tests-ordered-actions-slot","order":0},{"component":"billingOverviewLink","name":"billing-overview-link","slot":"billing-dashboard-link-slot"},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-link-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-link-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-link-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-link-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-link-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-link-slot"},{"component":"claimsManagementSideNavGroup","name":"claims-management-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"claims-management","title":"Claims management Overview","slot":"case-management-slot"},"featureFlag":"healthInformationExchange"},{"component":"claimsManagementOverviewDashboardLink","name":"claims-management-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"preAuthRequestsDashboardLink","name":"preauthrequest-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"claimsOverview","name":"claims-overview-dashboard-link","slot":"claims-management-overview-slot"},{"component":"waiveBillActionButton","name":"waive-bill-action-button","slot":"bill-actions-slot"},{"component":"deleteBillActionButton","name":"delete-bill-action-button","slot":"bill-actions-slot"},{"component":"refundLineItem","name":"refund-line-item","slot":"bill-actions-overflow-menu-slot"},{"name":"edit-line-item","component":"editLineItem","slot":"bill-actions-overflow-menu-slot"},{"name":"cancel-line-item","component":"cancelLineItem","slot":"bill-actions-overflow-menu-slot"}],"workspaces":[{"name":"waive-bill-form","component":"waiveBillForm","title":"Waive Bill Form","type":"other-form"},{"name":"edit-bill-form","component":"editBillForm","title":"Edit Bill Form","type":"other-form"},{"name":"billable-service-form","component":"addServiceForm","title":"Create Charge Item Form","type":"other-form"},{"name":"commodity-form","component":"addCommodityForm","title":"Create Charge Item Form","type":"other-form"},{"name":"billing-form","component":"billingForm","title":"Billing Form","type":"other-form","width":"extra-wide"},{"name":"payment-mode-workspace","component":"paymentModeWorkspace","title":"Payment Mode Workspace","type":"other-form"},{"name":"cancel-bill-workspace","component":"cancelBillWorkspace","title":"Cancel Bill Workspace","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"name":"clock-in-modal","component":"clockIn"},{"name":"clock-out-modal","component":"clockOut"},{"name":"bulk-import-billable-services-modal","component":"bulkImportBillableServicesModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"retry-claim-request-modal","component":"retryClaimRequestModal"},{"name":"paid-bill-receipt-print-preview-modal","component":"paidBillReceiptPrintPreviewModal"}],"version":"5.3.8-pre.1596"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemr":"^19.0.0"},"pages":[{"component":"billableServicesHome","route":"billable-services"},{"component":"requirePaymentModal","routeRegex":"^patient/.+/chart","online":true,"offline":false}],"extensions":[{"component":"billingDashboardLink","name":"billing-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"billing","title":"billing","slot":"billing-dashboard-slot"}},{"component":"benefitsPackageDashboardLink","name":"benefits-package-dashboard-link","slot":"patient-chart-dashboard-slot","meta":{"name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot","path":"insurance-benefits","columns":1,"columnSpan":1},"featureFlag":"healthInformationExchange"},{"component":"benefitsPackage","name":"benefits-package","slot":"patient-chart-benefits-dashboard-slot"},{"component":"root","name":"billing-dashboard-root","slot":"billing-dashboard-slot"},{"component":"benefitsEligibilyRequestForm","name":"benefits-eligibility-request-form"},{"component":"benefitsPreAuthForm","name":"benefits-pre-auth-form"},{"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","layoutMode":"anchored"}},{"name":"billing-check-in-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm"},{"name":"require-billing-modal","component":"requirePaymentModal"},{"name":"patient-banner-billing-tags","component":"visitAttributeTags","slot":"patient-banner-tags-slot","order":2},{"name":"initiate-payment-modal","component":"initiatePaymentDialog"},{"name":"delete-billableservice-modal","component":"deleteBillableServiceModal"},{"name":"refund-bill-modal","component":"refundBillModal"},{"name":"delete-bill-modal","component":"deleteBillModal"},{"name":"lab-order-billable-item","component":"labOrder","slot":"top-of-lab-order-form-slot"},{"name":"procedure-order-billable-item","component":"procedureOrder","slot":"top-of-procedure-order-form-slot"},{"name":"imaging-order-billable-item","component":"imagingOrder","slot":"top-of-imaging-order-form-slot"},{"name":"price-info-order","component":"priceInfoOrder"},{"name":"drug-order-billable-item","component":"drugOrder","slot":"medication-info-slot"},{"name":"billing-test-order-action","component":"testOrderAction","slot":"tests-ordered-actions-slot","order":0},{"component":"billingOverviewLink","name":"billing-overview-link","slot":"billing-dashboard-link-slot"},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-link-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-link-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-link-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-link-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-link-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-link-slot"},{"component":"claimsManagementSideNavGroup","name":"claims-management-dashboard-link","slot":"homepage-dashboard-slot","meta":{"name":"claims-management","title":"Claims management Overview","slot":"case-management-slot"},"featureFlag":"healthInformationExchange"},{"component":"claimsManagementOverviewDashboardLink","name":"claims-management-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"preAuthRequestsDashboardLink","name":"preauthrequest-overview-link","slot":"claims-management-dashboard-link-slot"},{"component":"claimsOverview","name":"claims-overview-dashboard-link","slot":"claims-management-overview-slot"},{"component":"waiveBillActionButton","name":"waive-bill-action-button","slot":"bill-actions-slot"},{"component":"deleteBillActionButton","name":"delete-bill-action-button","slot":"bill-actions-slot"},{"component":"refundLineItem","name":"refund-line-item","slot":"bill-actions-overflow-menu-slot"},{"name":"edit-line-item","component":"editLineItem","slot":"bill-actions-overflow-menu-slot"},{"name":"cancel-line-item","component":"cancelLineItem","slot":"bill-actions-overflow-menu-slot"}],"workspaces":[{"name":"waive-bill-form","component":"waiveBillForm","title":"Waive Bill Form","type":"other-form"},{"name":"edit-bill-form","component":"editBillForm","title":"Edit Bill Form","type":"other-form"},{"name":"billable-service-form","component":"addServiceForm","title":"Create Charge Item Form","type":"other-form"},{"name":"commodity-form","component":"addCommodityForm","title":"Create Charge Item Form","type":"other-form"},{"name":"billing-form","component":"billingForm","title":"Billing Form","type":"other-form","width":"extra-wide"},{"name":"payment-mode-workspace","component":"paymentModeWorkspace","title":"Payment Mode Workspace","type":"other-form"},{"name":"cancel-bill-workspace","component":"cancelBillWorkspace","title":"Cancel Bill Workspace","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"name":"clock-in-modal","component":"clockIn"},{"name":"clock-out-modal","component":"clockOut"},{"name":"bulk-import-billable-services-modal","component":"bulkImportBillableServicesModal"},{"name":"delete-payment-mode-modal","component":"deletePaymentModeModal"},{"name":"retry-claim-request-modal","component":"retryClaimRequestModal"},{"name":"paid-bill-receipt-print-preview-modal","component":"paidBillReceiptPrintPreviewModal"}],"version":"5.3.8-pre.1608"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-billing-app",
3
- "version": "5.3.8-pre.1596",
3
+ "version": "5.3.8-pre.1608",
4
4
  "description": "Billing app for KenyaEMR",
5
5
  "browser": "dist/kenyaemr-esm-billing-app.js",
6
6
  "main": "src/index.ts",
@@ -2,7 +2,8 @@ import React from 'react';
2
2
  import { launchWorkspace } from '@openmrs/esm-framework';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { OverflowMenuItem } from '@carbon/react';
5
- import { LineItem, MappedBill } from '../../../types';
5
+ import { LineItem, MappedBill, PaymentStatus } from '../../../types';
6
+ import Payments from '../../../invoice/payments/payments.component';
6
7
 
7
8
  type CancelLineItemProps = {
8
9
  lineItem: LineItem;
@@ -11,6 +12,10 @@ type CancelLineItemProps = {
11
12
 
12
13
  const CancelLineItem: React.FC<CancelLineItemProps> = ({ lineItem, bill }) => {
13
14
  const { t } = useTranslation();
15
+
16
+ if (lineItem.paymentStatus == PaymentStatus.PAID) {
17
+ return null;
18
+ }
14
19
  const handleCancelLineItemWorkspace = () => {
15
20
  launchWorkspace('cancel-bill-workspace', {
16
21
  workspaceTitle: t('cancelBillForm', 'Cancel Bill Form'),
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { Button } from '@carbon/react';
3
3
  import { TrashCan } from '@carbon/react/icons';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { MappedBill } from '../../../types';
5
+ import { MappedBill, PaymentStatus } from '../../../types';
6
6
  import { showModal } from '@openmrs/esm-framework';
7
7
 
8
8
  type DeleteBillActionButtonProps = {
@@ -11,6 +11,10 @@ type DeleteBillActionButtonProps = {
11
11
 
12
12
  const DeleteBillActionButton: React.FC<DeleteBillActionButtonProps> = ({ bill }) => {
13
13
  const { t } = useTranslation();
14
+
15
+ if (bill.status == PaymentStatus.PAID) {
16
+ return null;
17
+ }
14
18
  const handleOpenDeleteBillModal = (bill: MappedBill) => {
15
19
  const dispose = showModal('delete-bill-modal', {
16
20
  bill,
@@ -1,6 +1,6 @@
1
1
  import { launchWorkspace } from '@openmrs/esm-framework';
2
2
  import React from 'react';
3
- import { LineItem, MappedBill } from '../../../types';
3
+ import { LineItem, MappedBill, PaymentStatus } from '../../../types';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { OverflowMenuItem } from '@carbon/react';
6
6
 
@@ -11,6 +11,11 @@ type EditLineItemProps = {
11
11
 
12
12
  const EditLineItem: React.FC<EditLineItemProps> = ({ lineItem, bill }) => {
13
13
  const { t } = useTranslation();
14
+
15
+ if (lineItem.paymentStatus == PaymentStatus.PAID) {
16
+ return null;
17
+ }
18
+
14
19
  const handleOpenEditLineItemWorkspace = (lineItem: LineItem) => {
15
20
  launchWorkspace('edit-bill-form', {
16
21
  workspaceTitle: t('editBillForm', 'Edit Bill Form'),
@@ -1,6 +1,6 @@
1
1
  import { OverflowMenuItem } from '@carbon/react';
2
2
  import React from 'react';
3
- import { LineItem, MappedBill } from '../../../types';
3
+ import { LineItem, MappedBill, PaymentStatus } from '../../../types';
4
4
  import { showModal } from '@openmrs/esm-framework';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
@@ -24,6 +24,10 @@ const RefundLineItem: React.FC<RefundLineItemProps> = ({ lineItem, bill, isRefun
24
24
  return null;
25
25
  }
26
26
 
27
+ if (lineItem.paymentStatus !== PaymentStatus.PAID) {
28
+ return null;
29
+ }
30
+
27
31
  return <OverflowMenuItem itemText={t('refundItem', 'Refund item')} onClick={() => handleOpenRefundLineItemModal()} />;
28
32
  };
29
33
 
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { Button } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Scalpel } from '@carbon/react/icons';
5
- import { MappedBill } from '../../../types';
5
+ import { MappedBill, PaymentStatus } from '../../../types';
6
6
  import { launchWorkspace } from '@openmrs/esm-framework';
7
7
 
8
8
  type WaiveBillActionButtonProps = {
@@ -12,6 +12,9 @@ type WaiveBillActionButtonProps = {
12
12
  const WaiveBillActionButton: React.FC<WaiveBillActionButtonProps> = ({ bill }) => {
13
13
  const { t } = useTranslation();
14
14
 
15
+ if (bill.status == PaymentStatus.PAID) {
16
+ return null;
17
+ }
15
18
  const handleOpenWaiveBillWorkspace = (bill: MappedBill) => {
16
19
  launchWorkspace('waive-bill-form', {
17
20
  workspaceTitle: 'Waive Bill Form',
@@ -10,7 +10,7 @@ import {
10
10
  } from '@carbon/react';
11
11
  import { useTranslation } from 'react-i18next';
12
12
  import { convertToCurrency, extractString } from '../../helpers';
13
- import { LineItem, MappedBill } from '../../types';
13
+ import { LineItem, MappedBill, PaymentStatus } from '../../types';
14
14
  import styles from './bill-manager.scss';
15
15
  import { ExtensionSlot } from '@openmrs/esm-framework';
16
16
 
@@ -26,6 +26,7 @@ const BillLineItems: React.FC<{ bill: MappedBill }> = ({ bill }) => {
26
26
  <StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
27
27
  <StructuredListCell head>{t('unitPrice', 'Unit Price')}</StructuredListCell>
28
28
  <StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
29
+ <StructuredListCell head>{t('status', 'Status')}</StructuredListCell>
29
30
  <StructuredListCell head>{t('actions', 'Actions')}</StructuredListCell>
30
31
  </StructuredListRow>
31
32
  </StructuredListHead>
@@ -52,18 +53,22 @@ const LineItemRow = ({ lineItem, bill }: { lineItem: LineItem; bill: MappedBill
52
53
  lineItem.billableService.split(':').at(0),
53
54
  );
54
55
 
56
+ const extensionHeight = lineItem.paymentStatus === PaymentStatus.PAID ? '3em' : '5em';
57
+
55
58
  return (
56
59
  <StructuredListRow className={isRefundedLineItem && styles.refundedItem}>
57
60
  <StructuredListCell>
58
- {lineItem.item === '' ? extractString(lineItem.billableService) : extractString(lineItem.item)}
61
+ {lineItem.item === '' ? lineItem.billableService.split(':')[1] : extractString(lineItem.item)}
59
62
  </StructuredListCell>
60
63
  <StructuredListCell>{lineItem.quantity}</StructuredListCell>
61
64
  <StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
62
65
  <StructuredListCell>{convertToCurrency(lineItem.price * lineItem.quantity)}</StructuredListCell>
66
+ <StructuredListCell>{lineItem.paymentStatus}</StructuredListCell>
67
+
63
68
  <StructuredListCell>
64
69
  <OverflowMenu aria-label="overflow-menu">
65
70
  <ExtensionSlot
66
- style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', height: '7.5rem' }}
71
+ style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', height: extensionHeight }}
67
72
  className="cds--overflow-menu-options__option"
68
73
  name="bill-actions-overflow-menu-slot"
69
74
  state={{ lineItem, bill, isRefundedBillableService }}
@@ -21,9 +21,7 @@ const BillManager: React.FC<BillManagerProps> = () => {
21
21
  const [patientUuid, setPatientUuid] = React.useState<string>(undefined);
22
22
  const { t } = useTranslation();
23
23
  const { patientBills: bills, isLoading } = usePatientBills(patientUuid);
24
- const filteredBills =
25
- bills.filter((bill) => !Boolean(bill.totalAmount === bill.tenderedAmount) && patientUuid === bill.patientUuid) ??
26
- [];
24
+ const filteredBills = bills.filter((bill) => patientUuid === bill.patientUuid) ?? [];
27
25
 
28
26
  return (
29
27
  <>
@@ -32,11 +32,18 @@ export const RefundBillModal: React.FC<RefundBillModalProps> = ({ onClose, bill,
32
32
  ...(billableServiceUuid && { billableService: billableServiceUuid }),
33
33
  };
34
34
 
35
+ const billPayments = bill.payments.map((payment) => ({
36
+ instanceType: payment.instanceType.uuid,
37
+ attributes: [],
38
+ amount: payment.amount,
39
+ amountTendered: payment.amountTendered,
40
+ }));
41
+
35
42
  const billWithRefund = {
36
43
  cashPoint: bill.cashPointUuid,
37
44
  cashier: bill.cashier.uuid,
38
45
  lineItems: [lineItemToBeRefunded],
39
- payments: bill.payments,
46
+ payments: billPayments,
40
47
  patient: bill.patientUuid,
41
48
  status: bill.status,
42
49
  };
@@ -72,22 +79,26 @@ export const RefundBillModal: React.FC<RefundBillModalProps> = ({ onClose, bill,
72
79
  {t('refundBill', 'Refund Bill')}
73
80
  </ModalHeader>
74
81
  <ModalBody className={styles.modalHeaderHeading}>
75
- {t('refundBillDescription', 'Are you sure you want to refund this bill? Proceed cautiously.')}
82
+ {lineItem.paymentStatus === PaymentStatus.PAID
83
+ ? t('refundBillDescription', 'Are you sure you want to refund this bill? Proceed cautiously.')
84
+ : t('refundBillDescription', 'Refund is only allowed for paid bills')}
76
85
  </ModalBody>
77
86
  <ModalFooter>
78
87
  <Button kind="secondary" onClick={onClose}>
79
88
  {t('cancel', 'Cancel')}
80
89
  </Button>
81
- <Button kind="danger" onClick={refundBillItems} disabled={isLoading}>
82
- {isLoading ? (
83
- <div className={styles.loading_wrapper}>
84
- <Loading className={styles.button_spinner} withOverlay={false} small />
85
- {t('processingPayment', 'Processing Payment')}
86
- </div>
87
- ) : (
88
- t('refund', 'Refund')
89
- )}
90
- </Button>
90
+ {lineItem.paymentStatus === PaymentStatus.PAID && (
91
+ <Button kind="danger" onClick={refundBillItems} disabled={isLoading}>
92
+ {isLoading ? (
93
+ <div className={styles.loading_wrapper}>
94
+ <Loading className={styles.button_spinner} withOverlay={false} small />
95
+ {t('processingPayment', 'Processing Payment')}
96
+ </div>
97
+ ) : (
98
+ t('refund', 'Refund')
99
+ )}
100
+ </Button>
101
+ )}
91
102
  </ModalFooter>
92
103
  </>
93
104
  );
@@ -59,6 +59,7 @@ export const PaymentHistoryTable = ({
59
59
  return {
60
60
  ...row,
61
61
  totalAmount: convertToCurrency(row.payments.reduce((acc, payment) => acc + payment.amountTendered, 0)),
62
+ referenceCodes: row.payments.map(({ attributes }) => attributes.map(({ value }) => value).join(' ')).join(', '),
62
63
  };
63
64
  });
64
65
 
@@ -71,18 +72,18 @@ export const PaymentHistoryTable = ({
71
72
  });
72
73
  const data = dataForExport.map((row: (typeof transformedRows)[0]) => {
73
74
  return {
75
+ 'Receipt Number': row.receiptNumber,
74
76
  'Patient ID': row.identifier,
75
77
  'Patient Name': row.patientName,
76
- 'Receipt Number': row.receiptNumber,
77
- 'Total Amount': row.lineItems.reduce((acc, item) => acc + item.price, 0),
78
- 'Payment Mode': row.payments.map((payment: (typeof row.payments)[0]) => payment.instanceType.name).join(', '),
79
- 'Payment Date': dayjs(row.payments[0].dateCreated).format('DD-MM-YYYY'),
80
- 'Payment Amount': row.payments.reduce((acc, payment) => acc + payment.amountTendered, 0),
78
+ 'Mode of Payment': row.payments
79
+ .map((payment: (typeof row.payments)[0]) => payment.instanceType.name)
80
+ .join(', '),
81
+ 'Total Amount Due': row.lineItems.reduce((acc, item) => acc + item.price, 0),
82
+ 'Date of Payment': dayjs(row.payments[0].dateCreated).format('DD-MM-YYYY'),
83
+ 'Total Amount Paid': row.payments.reduce((acc, payment) => acc + payment.amountTendered, 0),
81
84
  'Reason/Reference': row.payments
82
- .map((payment: (typeof row.payments)[0]) =>
83
- payment.attributes.map((attribute: (typeof payment.attributes)[0]) => attribute.attributeType.name),
84
- )
85
- .join(' '),
85
+ .map(({ attributes }) => attributes.map(({ value }) => value).join(' '))
86
+ .join(', '),
86
87
  };
87
88
  });
88
89
 
@@ -28,6 +28,10 @@ export interface BillingConfig {
28
28
  emergencyPriorityConceptUuid: string;
29
29
  };
30
30
  paymentMethodsUuidsThatShouldNotShowPrompt: Array<string>;
31
+ promptDuration: {
32
+ enable: boolean;
33
+ duration: number;
34
+ };
31
35
  }
32
36
 
33
37
  export const configSchema: ConfigSchema = {
@@ -182,4 +186,13 @@ export const configSchema: ConfigSchema = {
182
186
  },
183
187
  _default: ['beac329b-f1dc-4a33-9e7c-d95821a137a6'],
184
188
  },
189
+ promptDuration: {
190
+ _type: Type.Object,
191
+ _description:
192
+ 'The duration in hours for the prompt to be shown, if the duration is less than this, the prompt will be shown',
193
+ _default: {
194
+ enable: true,
195
+ duration: 24,
196
+ },
197
+ },
185
198
  };
@@ -65,24 +65,19 @@ export const getGender = (gender: string, t) => {
65
65
 
66
66
  /**
67
67
  * Extracts and returns the substring after the first colon (:) in the input string.
68
- * The input string is expected to be in the format "uuid:string".
68
+ * If there's no colon or the input is invalid, returns the original string.
69
+ * The input string is typically in the format "uuid:string".
69
70
  *
70
71
  * @param {string} input - The input string from which the substring is to be extracted.
71
- * @returns {string} The substring found after the first colon in the input string.
72
+ * @returns {string} The substring found after the first colon, or the original string if no colon is present.
72
73
  */
73
74
  export function extractString(input: string): string {
74
- const parts = input
75
- .split(' ')
76
- .map((s) => s.split(':')[1])
77
- .filter((s) => Boolean(s));
78
-
79
- const firstTwoBillableServices = parts.slice(0, 2);
80
-
81
- if (parts.length <= 2) {
82
- return firstTwoBillableServices.join(', ');
75
+ if (!input || typeof input !== 'string') {
76
+ return '';
83
77
  }
84
78
 
85
- return `${firstTwoBillableServices.join(', ')} & ${parts.length - 2} other services`;
79
+ const parts = input.split(':');
80
+ return parts.length > 1 ? parts[1] : input;
86
81
  }
87
82
 
88
83
  // cleans the provider display name
@@ -63,7 +63,7 @@ export const PatientBills: React.FC<PatientBillsProps> = ({ bills, onCancel, pat
63
63
  style={{ textDecoration: 'none', maxWidth: '50%' }}
64
64
  to={billingUrl}
65
65
  templateParams={{ patientUuid: bill.patientUuid, uuid: bill.uuid }}>
66
- {bill.lineItems.map((item) => extractString(item.billableService)).join(', ')}
66
+ {bill.lineItems.map((item) => item.billableService.split(':')[1]).join(', ')}
67
67
  </ConfigurableLink>
68
68
  ),
69
69
  totalAmount: convertToCurrency(bill.totalAmount),
@@ -77,7 +77,7 @@ const PromptPaymentModal: React.FC<PromptPaymentModalProps> = () => {
77
77
  <StructuredListBody>
78
78
  {lineItems.map((lineItem) => {
79
79
  return (
80
- <StructuredListRow>
80
+ <StructuredListRow key={lineItem.uuid}>
81
81
  <StructuredListCell>{extractString(lineItem.billableService || lineItem.item)}</StructuredListCell>
82
82
  <StructuredListCell>{lineItem.quantity}</StructuredListCell>
83
83
  <StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
@@ -0,0 +1,200 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import PromptPaymentModal from './prompt-payment-modal.component';
4
+ import { useBillingPrompt } from './prompt-payment.resource';
5
+ import { navigate, useConfig } from '@openmrs/esm-framework';
6
+ import { PaymentStatus } from '../types';
7
+ import userEvent from '@testing-library/user-event';
8
+
9
+ const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
10
+
11
+ const mockMappedBill = [
12
+ {
13
+ uuid: '123e4567-e89b-12d3-a456-426614174000',
14
+ id: 1,
15
+ patientUuid: 'patient-uuid-123',
16
+ patientName: 'John Doe',
17
+ cashPointUuid: 'cashpoint-uuid-123',
18
+ cashPointName: 'Main Reception',
19
+ cashPointLocation: 'Hospital Wing A',
20
+ cashier: {
21
+ uuid: 'cashier-uuid-123',
22
+ display: 'Dr. Jane Smith',
23
+ links: [
24
+ {
25
+ rel: 'self',
26
+ uri: 'http://example.com/provider/cashier-uuid-123',
27
+ resourceAlias: 'provider',
28
+ },
29
+ ],
30
+ },
31
+ receiptNumber: 'REC-2023-001',
32
+ status: PaymentStatus.PENDING,
33
+ identifier: 'BILL-001',
34
+ dateCreated: '2023-08-15T10:30:00.000Z',
35
+ dateCreatedUnformatted: '2023-08-15',
36
+ lineItems: [
37
+ {
38
+ uuid: 'lineitem-uuid-123',
39
+ display: 'Consultation Fee',
40
+ voided: false,
41
+ voidReason: null,
42
+ item: 'Consultation',
43
+ billableService: 'some-uuid:General Consultation',
44
+ quantity: 1,
45
+ price: 50.0,
46
+ priceName: 'Standard Price',
47
+ priceUuid: 'price-uuid-123',
48
+ lineItemOrder: 1,
49
+ resourceVersion: '1.0',
50
+ paymentStatus: 'PENDING',
51
+ itemOrServiceConceptUuid: 'concept-uuid-123',
52
+ serviceTypeUuid: 'service-type-uuid-123',
53
+ order: {
54
+ uuid: 'order-uuid-123',
55
+ },
56
+ },
57
+ ],
58
+ billingService: 'Outpatient Services',
59
+ payments: [
60
+ {
61
+ uuid: 'payment-uuid-123',
62
+ instanceType: {
63
+ uuid: 'instance-type-uuid-123',
64
+ name: 'Cash Payment',
65
+ description: 'Standard cash payment',
66
+ retired: false,
67
+ },
68
+ attributes: [],
69
+ amount: 50.0,
70
+ amountTendered: 50.0,
71
+ dateCreated: 1692093000000, // Unix timestamp for 2023-08-15T10:30:00.000Z
72
+ voided: false,
73
+ resourceVersion: '1.0',
74
+ },
75
+ ],
76
+ totalAmount: 50.0,
77
+ tenderedAmount: 50.0,
78
+ display: 'Bill #BILL-001',
79
+ referenceCodes: 'REF-001',
80
+ adjustmentReason: undefined,
81
+ },
82
+ ];
83
+
84
+ const mockUseBillingPrompt = useBillingPrompt as jest.MockedFunction<typeof useBillingPrompt>;
85
+ const mockUseConfig = useConfig as jest.MockedFunction<typeof useConfig>;
86
+ jest.mock('@openmrs/esm-patient-common-lib', () => ({
87
+ getPatientUuidFromStore: jest.fn(() => 'patient-uuid'),
88
+ }));
89
+
90
+ jest.mock('@openmrs/esm-framework', () => ({
91
+ useConfig: jest.fn(),
92
+ navigate: jest.fn(),
93
+ }));
94
+
95
+ jest.mock('./prompt-payment.resource', () => ({
96
+ useBillingPrompt: jest.fn(),
97
+ }));
98
+
99
+ describe('<PromptPaymentModal />', () => {
100
+ beforeEach(() => {
101
+ jest.resetAllMocks();
102
+ });
103
+
104
+ test('should show the prompt payment modal, when `shouldShowBillingPrompt` is true and `enforceBillPayment` is true', async () => {
105
+ const user = userEvent.setup();
106
+ mockUseBillingPrompt.mockReturnValue({
107
+ shouldShowBillingPrompt: true,
108
+ isLoading: false,
109
+ bills: mockMappedBill,
110
+ currentVisit: null,
111
+ error: null,
112
+ });
113
+ mockUseConfig.mockReturnValue({
114
+ enforceBillPayment: true,
115
+ });
116
+ render(<PromptPaymentModal />);
117
+ expect(screen.getByText('Patient Billing Alert')).toBeInTheDocument();
118
+ expect(
119
+ screen.getByText('The current patient has pending bill. Advice patient to settle bill.'),
120
+ ).toBeInTheDocument();
121
+ expect(screen.getByText('Navigate back')).toBeInTheDocument();
122
+ // check if the structured list is rendered
123
+ const structuredListHeaders = ['Item', 'Quantity', 'Unit price', 'Total'];
124
+ structuredListHeaders.forEach((header) => {
125
+ expect(screen.getByText(header)).toBeInTheDocument();
126
+ });
127
+
128
+ // clicking cancel button should close the modal
129
+ const cancelButton = screen.getByText('Cancel');
130
+ await user.click(cancelButton);
131
+ expect(mockNavigate).toHaveBeenCalledWith({ to: `\${openmrsSpaBase}/home` });
132
+ // clicking proceed to care button should close the modal
133
+ const navigateBackButton = screen.getByText('Navigate back');
134
+ await user.click(navigateBackButton);
135
+ expect(mockNavigate).toHaveBeenCalledWith({ to: `\${openmrsSpaBase}/home` });
136
+ });
137
+
138
+ test('should show the prompt payment modal, when `shouldShowBillingPrompt` is true and `enforceBillPayment` is false and not navigate back', async () => {
139
+ const user = userEvent.setup();
140
+ mockUseBillingPrompt.mockReturnValue({
141
+ shouldShowBillingPrompt: true,
142
+ isLoading: false,
143
+ bills: mockMappedBill,
144
+ currentVisit: null,
145
+ error: null,
146
+ });
147
+ mockUseConfig.mockReturnValue({
148
+ enforceBillPayment: false,
149
+ });
150
+
151
+ render(<PromptPaymentModal />);
152
+ expect(screen.getByText('Patient Billing Alert')).toBeInTheDocument();
153
+ expect(
154
+ screen.getByText('The current patient has pending bill. Advice patient to settle bill.'),
155
+ ).toBeInTheDocument();
156
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
157
+ expect(screen.getByText('Proceed to care')).toBeInTheDocument();
158
+
159
+ // clicking proceed to care button should close the modal
160
+ const proceedToCareButton = screen.getByText('Proceed to care');
161
+ await user.click(proceedToCareButton);
162
+ expect(mockNavigate).not.toHaveBeenCalled();
163
+
164
+ // clicking cancel button should close the modal
165
+ const cancelButton = screen.getByText('Cancel');
166
+ await user.click(cancelButton);
167
+ expect(mockNavigate).toHaveBeenCalledWith({ to: `\${openmrsSpaBase}/home` });
168
+ });
169
+
170
+ test('should show the loading state when `isLoading` is true', () => {
171
+ mockUseBillingPrompt.mockReturnValue({
172
+ shouldShowBillingPrompt: true,
173
+ isLoading: true,
174
+ bills: mockMappedBill,
175
+ currentVisit: null,
176
+ error: null,
177
+ });
178
+ mockUseConfig.mockReturnValue({
179
+ enforceBillPayment: true,
180
+ });
181
+ render(<PromptPaymentModal />);
182
+ expect(screen.getByText('Billing status')).toBeInTheDocument();
183
+ expect(screen.getByText('Verifying patient bills')).toBeInTheDocument();
184
+ });
185
+
186
+ test('should not render the modal when `shouldShowBillingPrompt` is false', () => {
187
+ mockUseBillingPrompt.mockReturnValue({
188
+ shouldShowBillingPrompt: false,
189
+ isLoading: false,
190
+ bills: mockMappedBill,
191
+ currentVisit: null,
192
+ error: null,
193
+ });
194
+ mockUseConfig.mockReturnValue({
195
+ enforceBillPayment: true,
196
+ });
197
+ render(<PromptPaymentModal />);
198
+ expect(screen.queryByText('Patient Billing Alert')).not.toBeInTheDocument();
199
+ });
200
+ });