@kenyaemr/esm-billing-app 5.3.8-pre.1599 → 5.3.9-pre.1620
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/.turbo/turbo-build.log +57 -57
- package/dist/571.js +1 -1
- package/dist/571.js.map +1 -1
- package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +6 -6
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/billable-services/payment-history/payment-history-table.component.tsx +10 -9
- package/src/config-schema.ts +13 -0
- package/src/helpers/functions.ts +7 -12
- package/src/invoice/invoice-table.component.tsx +7 -3
- package/src/invoice/payments/payments.test.tsx +54 -11
- package/src/invoice/payments/utils.ts +36 -8
- package/src/prompt-payment/prompt-payment-modal.component.tsx +1 -1
- package/src/prompt-payment/prompt-payment.modal.test.tsx +200 -0
- package/src/prompt-payment/prompt-payment.resource.tsx +79 -4
- package/src/types/index.ts +1 -0
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.
|
|
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.9-pre.1620"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-billing-app",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.9-pre.1620",
|
|
4
4
|
"description": "Billing app for KenyaEMR",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-billing-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"webpack": "^5.74.0"
|
|
53
53
|
},
|
|
54
|
-
"stableVersion": "5.3.
|
|
54
|
+
"stableVersion": "5.3.8"
|
|
55
55
|
}
|
|
@@ -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
|
-
'
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
'
|
|
80
|
-
'Payment
|
|
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((
|
|
83
|
-
|
|
84
|
-
)
|
|
85
|
-
.join(' '),
|
|
85
|
+
.map(({ attributes }) => attributes.map(({ value }) => value).join(' '))
|
|
86
|
+
.join(', '),
|
|
86
87
|
};
|
|
87
88
|
});
|
|
88
89
|
|
package/src/config-schema.ts
CHANGED
|
@@ -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
|
};
|
package/src/helpers/functions.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
+
const parts = input.split(':');
|
|
80
|
+
return parts.length > 1 ? parts[1] : input;
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
// cleans the provider display name
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
type DataTableRow,
|
|
22
22
|
} from '@carbon/react';
|
|
23
23
|
import { isDesktop, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
24
|
-
import { LineItem, MappedBill } from '../types';
|
|
24
|
+
import { LineItem, MappedBill, PaymentStatus } from '../types';
|
|
25
25
|
import styles from './invoice-table.scss';
|
|
26
26
|
|
|
27
27
|
type InvoiceTableProps = {
|
|
@@ -155,10 +155,14 @@ const InvoiceTable: React.FC<InvoiceTableProps> = ({ bill, isSelectable = true,
|
|
|
155
155
|
<TableSelectRow
|
|
156
156
|
aria-label="Select row"
|
|
157
157
|
{...getSelectionProps({ row })}
|
|
158
|
-
disabled={
|
|
158
|
+
disabled={
|
|
159
|
+
tableRows[index].status === PaymentStatus.PAID ||
|
|
160
|
+
tableRows[index].status === PaymentStatus.EXEMPTED
|
|
161
|
+
}
|
|
159
162
|
onChange={(checked: boolean) => handleRowSelection(row, checked)}
|
|
160
163
|
checked={
|
|
161
|
-
tableRows[index].status ===
|
|
164
|
+
tableRows[index].status === PaymentStatus.PAID ||
|
|
165
|
+
tableRows[index].status === PaymentStatus.EXEMPTED ||
|
|
162
166
|
Boolean(selectedLineItems?.find((item) => item?.uuid === row?.id))
|
|
163
167
|
}
|
|
164
168
|
/>
|
|
@@ -64,15 +64,63 @@ describe('Payment', () => {
|
|
|
64
64
|
expect(mockProcessBillPayment).toHaveBeenCalledTimes(1);
|
|
65
65
|
expect(mockProcessBillPayment).toHaveBeenCalledWith(
|
|
66
66
|
{
|
|
67
|
-
cashPoint:
|
|
68
|
-
cashier:
|
|
67
|
+
cashPoint: '65dd568e-4124-4e89-a4f8-0b07c58ec6fe',
|
|
68
|
+
cashier: '48b55692-e061-4ffa-b1f2-fd4aaf506224',
|
|
69
69
|
lineItems: [
|
|
70
70
|
{
|
|
71
|
-
billableService: '
|
|
71
|
+
billableService: 'c15d25b9-12bb-441d-9241-cae541dd4575',
|
|
72
72
|
display: 'BillLineItem',
|
|
73
|
-
item: '
|
|
73
|
+
item: 'c15d25b9-12bb-441d-9241-cae541dd4575',
|
|
74
74
|
lineItemOrder: 0,
|
|
75
|
+
order: null,
|
|
76
|
+
paymentStatus: 'PAID',
|
|
77
|
+
price: 500,
|
|
78
|
+
priceName: 'Default',
|
|
79
|
+
priceUuid: '',
|
|
80
|
+
quantity: 1,
|
|
81
|
+
resourceVersion: '1.8',
|
|
82
|
+
uuid: '314c25fd-2c90-4a7f-9f98-c99cd3f153e8',
|
|
83
|
+
voidReason: null,
|
|
84
|
+
voided: false,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
billableService: '04be5832-5440-44d0-83d2-5c0dfd0ac7de',
|
|
88
|
+
display: 'BillLineItem',
|
|
89
|
+
item: '04be5832-5440-44d0-83d2-5c0dfd0ac7de',
|
|
90
|
+
lineItemOrder: 1,
|
|
91
|
+
order: null,
|
|
92
|
+
paymentStatus: 'PAID',
|
|
93
|
+
price: 100,
|
|
94
|
+
priceName: 'Default',
|
|
95
|
+
priceUuid: '',
|
|
96
|
+
quantity: 1,
|
|
97
|
+
resourceVersion: '1.8',
|
|
98
|
+
uuid: '60365e7e-d29e-4f13-b64b-9aecb5d36031',
|
|
99
|
+
voidReason: null,
|
|
100
|
+
voided: false,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
billableService: '3f5d0684-a280-477e-a67b-2a956a1f6dca',
|
|
104
|
+
display: 'BillLineItem',
|
|
105
|
+
item: '3f5d0684-a280-477e-a67b-2a956a1f6dca',
|
|
106
|
+
lineItemOrder: 2,
|
|
107
|
+
order: null,
|
|
75
108
|
paymentStatus: 'PAID',
|
|
109
|
+
price: 50,
|
|
110
|
+
priceName: 'Default',
|
|
111
|
+
priceUuid: '',
|
|
112
|
+
quantity: 1,
|
|
113
|
+
resourceVersion: '1.8',
|
|
114
|
+
uuid: '006ee634-f1cf-4552-b751-f721679527af',
|
|
115
|
+
voidReason: null,
|
|
116
|
+
voided: false,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
billableService: null,
|
|
120
|
+
display: 'BillLineItem',
|
|
121
|
+
item: 'Hemoglobin',
|
|
122
|
+
lineItemOrder: 0,
|
|
123
|
+
paymentStatus: 'PENDING',
|
|
76
124
|
price: 100,
|
|
77
125
|
priceName: '',
|
|
78
126
|
priceUuid: '',
|
|
@@ -85,14 +133,9 @@ describe('Payment', () => {
|
|
|
85
133
|
],
|
|
86
134
|
patient: 'b2fcf02b-7ee3-4d16-a48f-576be2b103aa',
|
|
87
135
|
payments: [
|
|
88
|
-
{
|
|
89
|
-
amount: 100,
|
|
90
|
-
amountTendered: 100,
|
|
91
|
-
attributes: [],
|
|
92
|
-
instanceType: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74',
|
|
93
|
-
},
|
|
136
|
+
{ amount: 100, amountTendered: 100, attributes: [], instanceType: '63eff7a4-6f82-43c4-a333-dbcc58fe9f74' },
|
|
94
137
|
],
|
|
95
|
-
status: '
|
|
138
|
+
status: 'PENDING',
|
|
96
139
|
},
|
|
97
140
|
'6eb8d678-514d-46ad-9554-51e48d96d567',
|
|
98
141
|
);
|
|
@@ -70,6 +70,10 @@ export const determineBillableItemPaymentStatus = (
|
|
|
70
70
|
return fallbackPaymentStatus;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
if (billableItem.paymentStatus === PaymentStatus.EXEMPTED) {
|
|
74
|
+
return PaymentStatus.EXEMPTED;
|
|
75
|
+
}
|
|
76
|
+
|
|
73
77
|
const itemTotalCost = billableItem.price * billableItem.quantity;
|
|
74
78
|
return totalPaidAmount >= itemTotalCost ? PaymentStatus.PAID : PaymentStatus.PENDING;
|
|
75
79
|
};
|
|
@@ -133,16 +137,40 @@ export const createPaymentPayload = (
|
|
|
133
137
|
const consolidatedPayments = [...currentPayments, ...existingPayments];
|
|
134
138
|
const totalPaidAmount = consolidatedPayments.reduce((sum, payment) => sum + payment.amountTendered, 0);
|
|
135
139
|
|
|
136
|
-
// Process
|
|
137
|
-
const
|
|
138
|
-
...
|
|
139
|
-
billableService: extractServiceIdentifier(
|
|
140
|
-
item: extractServiceIdentifier(
|
|
141
|
-
paymentStatus:
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
// Process selected items and update their payment status
|
|
141
|
+
const processedSelectedBillableItems = selectedBillableItems.map((billableItem) => ({
|
|
142
|
+
...billableItem,
|
|
143
|
+
billableService: extractServiceIdentifier(billableItem),
|
|
144
|
+
item: extractServiceIdentifier(billableItem),
|
|
145
|
+
paymentStatus: determineBillableItemPaymentStatus(
|
|
146
|
+
lineItems.length,
|
|
147
|
+
billableItem,
|
|
148
|
+
totalPaidAmount,
|
|
149
|
+
initialPaymentStatus,
|
|
150
|
+
),
|
|
144
151
|
}));
|
|
145
152
|
|
|
153
|
+
// Handle remaining line items based on whether there are selected items
|
|
154
|
+
const remainingLineItems =
|
|
155
|
+
selectedBillableItems.length > 0
|
|
156
|
+
? // If items were selected, exclude them from the original line items
|
|
157
|
+
lineItems.filter((lineItem) => {
|
|
158
|
+
const isItemSelected = processedSelectedBillableItems.some(
|
|
159
|
+
(selectedItem) => selectedItem.uuid === lineItem.uuid,
|
|
160
|
+
);
|
|
161
|
+
return !isItemSelected;
|
|
162
|
+
})
|
|
163
|
+
: // If no items were selected, update payment status for all line items
|
|
164
|
+
lineItems.map((lineItem) => ({
|
|
165
|
+
...lineItem,
|
|
166
|
+
billableService: extractServiceIdentifier(lineItem),
|
|
167
|
+
item: extractServiceIdentifier(lineItem),
|
|
168
|
+
paymentStatus: isBillableItemFullyPaid(totalPaidAmount, lineItem),
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
// Combine selected and remaining items into final processed list
|
|
172
|
+
const processedLineItems = [...processedSelectedBillableItems, ...remainingLineItems];
|
|
173
|
+
|
|
146
174
|
// Determine final bill status
|
|
147
175
|
const hasUnpaidItems = processedLineItems.some((item) => item.paymentStatus === PaymentStatus.PENDING);
|
|
148
176
|
const overallBillStatus = hasUnpaidItems ? PaymentStatus.PENDING : PaymentStatus.PAID;
|
|
@@ -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
|
+
});
|