@kenyaemr/esm-billing-app 5.4.1-pre.2075 → 5.4.1-pre.2083
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 +83 -90
- package/dist/421.js +1 -0
- package/dist/421.js.map +1 -0
- package/dist/746.js +2 -2
- package/dist/kenyaemr-esm-billing-app.js +1 -1
- package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +32 -32
- package/dist/kenyaemr-esm-billing-app.js.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/billable-services/bill-manager/workspaces/create-bill/create-bill.workspace.test.tsx +271 -0
- package/src/billable-services/bill-manager/workspaces/create-bill/create-bill.workspace.tsx +174 -40
- package/src/billable-services/billables/charge-summary.resource.tsx +2 -2
- package/src/billable-services/billiable-item/order-actions/components/medication-order-button.component.tsx +6 -11
- package/src/billable-services/billiable-item/order-actions/components/order-action-button.component.tsx +3 -12
- package/src/billable-services/billiable-item/order-actions/hooks/useMedicationOrderAction.ts +5 -2
- package/src/index.ts +77 -53
- package/dist/22.js +0 -1
- package/dist/22.js.map +0 -1
- package/src/billable-services/bill-manager/modals/create-bill-item-modal.component.tsx +0 -208
- package/src/billable-services/bill-manager/modals/create-bill-item-modal.scss +0 -26
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":"order-action-button","component":"orderActionButton","slots":["prescription-action-button-slot","imaging-orders-action","procedure-orders-action","tests-ordered-actions-slot"],"order":0},{"component":"billingOverviewLink","name":"billing-overview-link","order":0,"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","order":0,"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":"create-bill-workspace","component":"createBillWorkspace","title":"Create Bill Workspace","type":"other-form"},{"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"},{"name":"bill-deposit-workspace","component":"billDepositWorkspace","title":"Bill Deposit Workspace","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"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"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"}],"version":"5.4.1-pre.
|
|
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":"order-action-button","component":"orderActionButton","slots":["prescription-action-button-slot","imaging-orders-action","procedure-orders-action","tests-ordered-actions-slot"],"order":0},{"component":"billingOverviewLink","name":"billing-overview-link","order":0,"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","order":0,"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":"create-bill-workspace","component":"createBillWorkspace","title":"Create Bill Workspace","type":"other-form"},{"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"},{"name":"bill-deposit-workspace","component":"billDepositWorkspace","title":"Bill Deposit Workspace","type":"other-form"}],"modals":[{"name":"create-payment-point","component":"createPaymentPoint"},{"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"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"}],"version":"5.4.1-pre.2083"}
|
package/package.json
CHANGED
package/src/billable-services/bill-manager/workspaces/create-bill/create-bill.workspace.test.tsx
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { useConfig, useLayoutType } from '@openmrs/esm-framework';
|
|
5
|
+
import { type Order, type OrderAction } from '@openmrs/esm-patient-common-lib';
|
|
6
|
+
import { processBillItems } from '../../../../billing.resource';
|
|
7
|
+
import CreateBillWorkspace from './create-bill.workspace';
|
|
8
|
+
import { useBillableItem } from '../../../billiable-item/useBillableItem';
|
|
9
|
+
|
|
10
|
+
jest.mock('react-i18next', () => ({
|
|
11
|
+
useTranslation: () => ({
|
|
12
|
+
t: (key, defaultText, options) => {
|
|
13
|
+
if (options) {
|
|
14
|
+
return defaultText.replace(/\{\{(\w+)\}\}/g, (_, key) => options[key] || '');
|
|
15
|
+
}
|
|
16
|
+
return defaultText || key;
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
22
|
+
useConfig: jest.fn(),
|
|
23
|
+
useLayoutType: jest.fn(),
|
|
24
|
+
showSnackbar: jest.fn(),
|
|
25
|
+
ResponsiveWrapper: ({ children }) => <div>{children}</div>,
|
|
26
|
+
restBaseUrl: '/openmrs/ws/rest/v1',
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
jest.mock('../../../billiable-item/useBillableItem', () => ({
|
|
30
|
+
useBillableItem: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock('../../../../billing.resource', () => ({
|
|
34
|
+
processBillItems: jest.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
jest.mock('swr', () => ({
|
|
38
|
+
mutate: jest.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
describe('CreateBillWorkspace', () => {
|
|
42
|
+
// Mock data
|
|
43
|
+
const mockOrder: Order = {
|
|
44
|
+
uuid: 'order-uuid',
|
|
45
|
+
orderNumber: 'ORD-123',
|
|
46
|
+
concept: {
|
|
47
|
+
uuid: 'concept-uuid',
|
|
48
|
+
display: 'Test Service',
|
|
49
|
+
},
|
|
50
|
+
action: 'NEW' as OrderAction,
|
|
51
|
+
asNeeded: false,
|
|
52
|
+
autoExpireDate: null,
|
|
53
|
+
careSetting: {
|
|
54
|
+
uuid: 'care-setting-uuid',
|
|
55
|
+
display: 'Outpatient',
|
|
56
|
+
},
|
|
57
|
+
dateActivated: '2024-01-01',
|
|
58
|
+
dateStopped: null,
|
|
59
|
+
drug: null,
|
|
60
|
+
dosingType: 'org.openmrs.FreeTextDosingInstructions',
|
|
61
|
+
dose: null,
|
|
62
|
+
doseUnits: null,
|
|
63
|
+
frequency: null,
|
|
64
|
+
instructions: null,
|
|
65
|
+
numRefills: 0,
|
|
66
|
+
orderType: {
|
|
67
|
+
uuid: 'order-type-uuid',
|
|
68
|
+
display: 'Test Order Type',
|
|
69
|
+
conceptClasses: [],
|
|
70
|
+
description: 'Test Order Type Description',
|
|
71
|
+
name: 'Test Order Type',
|
|
72
|
+
parent: 'parent-uuid',
|
|
73
|
+
retired: false,
|
|
74
|
+
},
|
|
75
|
+
orderer: {
|
|
76
|
+
uuid: 'orderer-uuid',
|
|
77
|
+
display: 'Test Orderer',
|
|
78
|
+
person: {
|
|
79
|
+
display: 'Test Person',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
previousOrder: null,
|
|
83
|
+
quantity: 1,
|
|
84
|
+
quantityUnits: null,
|
|
85
|
+
route: null,
|
|
86
|
+
scheduledDate: null,
|
|
87
|
+
scheduleDate: null,
|
|
88
|
+
numberOfRepeats: '0',
|
|
89
|
+
urgency: 'ROUTINE',
|
|
90
|
+
accessionNumber: null,
|
|
91
|
+
auditInfo: {
|
|
92
|
+
creator: {
|
|
93
|
+
uuid: 'creator-uuid',
|
|
94
|
+
display: 'Test Creator',
|
|
95
|
+
},
|
|
96
|
+
dateCreated: '2024-01-01',
|
|
97
|
+
changedBy: null,
|
|
98
|
+
dateChanged: null,
|
|
99
|
+
},
|
|
100
|
+
fulfillerStatus: null,
|
|
101
|
+
fulfillerComment: null,
|
|
102
|
+
specimenSource: null,
|
|
103
|
+
laterality: null,
|
|
104
|
+
clinicalHistory: null,
|
|
105
|
+
commentToFulfiller: null,
|
|
106
|
+
display: 'Test Order',
|
|
107
|
+
type: 'order',
|
|
108
|
+
dispenseAsWritten: false,
|
|
109
|
+
dosingInstructions: null,
|
|
110
|
+
duration: null,
|
|
111
|
+
durationUnits: null,
|
|
112
|
+
brandName: null,
|
|
113
|
+
orderReason: null,
|
|
114
|
+
orderReasonNonCoded: null,
|
|
115
|
+
encounter: {
|
|
116
|
+
uuid: 'encounter-uuid',
|
|
117
|
+
display: 'Test Encounter',
|
|
118
|
+
},
|
|
119
|
+
patient: {
|
|
120
|
+
uuid: 'patient-uuid',
|
|
121
|
+
display: 'Test Patient',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const mockBillableItem = {
|
|
126
|
+
uuid: 'billable-uuid',
|
|
127
|
+
name: 'Test Service',
|
|
128
|
+
servicePrices: [
|
|
129
|
+
{
|
|
130
|
+
uuid: 'price-uuid-1',
|
|
131
|
+
price: 100,
|
|
132
|
+
paymentMode: { name: 'Cash' },
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
uuid: 'price-uuid-2',
|
|
136
|
+
price: 150,
|
|
137
|
+
paymentMode: { name: 'Insurance' },
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const defaultProps = {
|
|
143
|
+
patientUuid: 'patient-uuid',
|
|
144
|
+
order: mockOrder,
|
|
145
|
+
closeWorkspace: jest.fn(),
|
|
146
|
+
closeWorkspaceWithSavedChanges: jest.fn(),
|
|
147
|
+
promptBeforeClosing: jest.fn(),
|
|
148
|
+
setTitle: jest.fn(),
|
|
149
|
+
closeModal: jest.fn(),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Reset mocks before each test
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
jest.clearAllMocks();
|
|
155
|
+
|
|
156
|
+
// Setup default mock implementations
|
|
157
|
+
(useConfig as jest.Mock).mockReturnValue({
|
|
158
|
+
cashPointUuid: 'cash-point-uuid',
|
|
159
|
+
cashierUuid: 'cashier-uuid',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
(useLayoutType as jest.Mock).mockReturnValue('desktop');
|
|
163
|
+
|
|
164
|
+
(useBillableItem as jest.Mock).mockReturnValue({
|
|
165
|
+
billableItem: mockBillableItem,
|
|
166
|
+
isLoading: false,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
(processBillItems as jest.Mock).mockResolvedValue({});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('renders the form with correct initial state', async () => {
|
|
173
|
+
render(<CreateBillWorkspace {...defaultProps} />);
|
|
174
|
+
|
|
175
|
+
// Check that the title and description are rendered
|
|
176
|
+
expect(screen.getByText(/Create bill for order Test Service/)).toBeInTheDocument();
|
|
177
|
+
expect(screen.getByText(/Order Bill Creation ORD-123/)).toBeInTheDocument();
|
|
178
|
+
|
|
179
|
+
// Check form elements are rendered
|
|
180
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /quantity/i });
|
|
181
|
+
expect(quantityInput).toHaveValue(1);
|
|
182
|
+
|
|
183
|
+
const priceDropdown = screen.getByRole('combobox', { name: /Unit Price/i });
|
|
184
|
+
expect(priceDropdown).toBeInTheDocument();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('submits the form successfully', async () => {
|
|
188
|
+
const user = userEvent.setup();
|
|
189
|
+
|
|
190
|
+
render(<CreateBillWorkspace {...defaultProps} />);
|
|
191
|
+
|
|
192
|
+
// Get form elements
|
|
193
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /quantity/i });
|
|
194
|
+
const priceDropdown = screen.getByRole('combobox', { name: /Unit Price/i });
|
|
195
|
+
|
|
196
|
+
// Set quantity
|
|
197
|
+
await user.clear(quantityInput);
|
|
198
|
+
await user.type(quantityInput, '2');
|
|
199
|
+
|
|
200
|
+
// Open and select from dropdown
|
|
201
|
+
await user.click(priceDropdown);
|
|
202
|
+
|
|
203
|
+
// Since Carbon dropdown items might be rendered in a portal or with complex structure,
|
|
204
|
+
// we need to find them by their text content regardless of their exact structure
|
|
205
|
+
const dropdownItem = await screen.findByText(/Cash - Ksh 100.00/);
|
|
206
|
+
await user.click(dropdownItem);
|
|
207
|
+
|
|
208
|
+
// Submit form
|
|
209
|
+
const submitButton = screen.getByRole('button', { name: /save & close/i });
|
|
210
|
+
|
|
211
|
+
// Wait for button to be enabled after form validation
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(submitButton).not.toBeDisabled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await user.click(submitButton);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test('handles loading state correctly', () => {
|
|
220
|
+
// Mock loading state
|
|
221
|
+
(useBillableItem as jest.Mock).mockReturnValue({
|
|
222
|
+
billableItem: null,
|
|
223
|
+
isLoading: true,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
render(<CreateBillWorkspace {...defaultProps} />);
|
|
227
|
+
|
|
228
|
+
expect(screen.getByText(/Loading billable items/i)).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('handles form errors correctly', async () => {
|
|
232
|
+
const user = userEvent.setup();
|
|
233
|
+
|
|
234
|
+
// Mock API error
|
|
235
|
+
const mockError = new Error('Failed to process bill');
|
|
236
|
+
(processBillItems as jest.Mock).mockRejectedValue(mockError);
|
|
237
|
+
|
|
238
|
+
render(<CreateBillWorkspace {...defaultProps} />);
|
|
239
|
+
|
|
240
|
+
// Fill form
|
|
241
|
+
const quantityInput = screen.getByRole('spinbutton', { name: /quantity/i });
|
|
242
|
+
const priceDropdown = screen.getByRole('combobox', { name: /Unit Price/i });
|
|
243
|
+
|
|
244
|
+
await user.clear(quantityInput);
|
|
245
|
+
await user.type(quantityInput, '2');
|
|
246
|
+
|
|
247
|
+
await user.click(priceDropdown);
|
|
248
|
+
const dropdownItem = await screen.findByText(/Cash - Ksh 100.00/);
|
|
249
|
+
await user.click(dropdownItem);
|
|
250
|
+
|
|
251
|
+
// Submit form
|
|
252
|
+
const submitButton = screen.getByRole('button', { name: /save & close/i });
|
|
253
|
+
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
expect(submitButton).not.toBeDisabled();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await user.click(submitButton);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('cancel button closes workspace', async () => {
|
|
262
|
+
const user = userEvent.setup();
|
|
263
|
+
|
|
264
|
+
render(<CreateBillWorkspace {...defaultProps} />);
|
|
265
|
+
|
|
266
|
+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
|
|
267
|
+
await user.click(cancelButton);
|
|
268
|
+
|
|
269
|
+
expect(defaultProps.closeWorkspace).toHaveBeenCalled();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -1,49 +1,162 @@
|
|
|
1
1
|
import React, { useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { type Order } from '@openmrs/esm-patient-common-lib';
|
|
4
|
-
import { useBillableItem } from '../../../billiable-item/useBillableItem';
|
|
5
|
-
import styles from './create-bill.style.scss';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
6
3
|
import { Controller, useForm } from 'react-hook-form';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import classNames from 'classnames';
|
|
6
|
+
import {
|
|
7
|
+
Dropdown,
|
|
8
|
+
Button,
|
|
9
|
+
ButtonSet,
|
|
10
|
+
InlineLoading,
|
|
11
|
+
InlineNotification,
|
|
12
|
+
NumberInput,
|
|
13
|
+
Stack,
|
|
14
|
+
Column,
|
|
15
|
+
} from '@carbon/react';
|
|
7
16
|
import {
|
|
8
17
|
type DefaultWorkspaceProps,
|
|
9
18
|
ResponsiveWrapper,
|
|
10
19
|
restBaseUrl,
|
|
11
20
|
useConfig,
|
|
12
21
|
useLayoutType,
|
|
22
|
+
showSnackbar,
|
|
13
23
|
} from '@openmrs/esm-framework';
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
24
|
+
import { type Order } from '@openmrs/esm-patient-common-lib';
|
|
25
|
+
import { useBillableItem } from '../../../billiable-item/useBillableItem';
|
|
26
|
+
import styles from './create-bill.style.scss';
|
|
16
27
|
import { convertToCurrency } from '../../../../helpers';
|
|
17
28
|
import { BillingConfig } from '../../../../config-schema';
|
|
18
|
-
import { z } from 'zod';
|
|
19
29
|
import { processBillItems } from '../../../../billing.resource';
|
|
20
30
|
import { mutate } from 'swr';
|
|
21
31
|
|
|
22
32
|
type CreateBillWorkspaceProps = DefaultWorkspaceProps & {
|
|
23
33
|
patientUuid: string;
|
|
24
34
|
order: Order;
|
|
35
|
+
closeModal: () => void;
|
|
36
|
+
medicationRequestBundle?: {
|
|
37
|
+
request: fhir.MedicationRequest;
|
|
38
|
+
};
|
|
25
39
|
};
|
|
26
40
|
|
|
27
41
|
const createBillFormSchema = z.object({
|
|
28
42
|
id: z.string().min(1),
|
|
29
43
|
text: z.string().min(1),
|
|
30
44
|
unitPrice: z.string().min(1),
|
|
45
|
+
quantity: z.number().min(1),
|
|
31
46
|
});
|
|
32
47
|
|
|
33
48
|
type CreateBillFormSchema = z.infer<typeof createBillFormSchema>;
|
|
34
49
|
|
|
50
|
+
interface BillFormProps {
|
|
51
|
+
billableItem: any;
|
|
52
|
+
quantityToDispense: number;
|
|
53
|
+
createBillForm: any;
|
|
54
|
+
errors: any;
|
|
55
|
+
calculateTotal: () => number;
|
|
56
|
+
comboBoxItems: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
text: string;
|
|
59
|
+
unitPrice: number;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface MedicationBillFormProps extends Omit<BillFormProps, 'quantityToDispense'> {
|
|
64
|
+
medicationRequestBundle: {
|
|
65
|
+
request: fhir.MedicationRequest;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const BillForm: React.FC<BillFormProps> = ({
|
|
70
|
+
billableItem,
|
|
71
|
+
quantityToDispense,
|
|
72
|
+
createBillForm,
|
|
73
|
+
errors,
|
|
74
|
+
calculateTotal,
|
|
75
|
+
comboBoxItems,
|
|
76
|
+
}) => {
|
|
77
|
+
const { t } = useTranslation();
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Stack gap={4}>
|
|
81
|
+
<Column>
|
|
82
|
+
<div className={styles.formField}>
|
|
83
|
+
<label className={styles.label}>{t('item', 'Item')}</label>
|
|
84
|
+
<div className={styles.value}>{billableItem?.name ?? 'Service Not Found'}</div>
|
|
85
|
+
</div>
|
|
86
|
+
</Column>
|
|
87
|
+
<Column>
|
|
88
|
+
<Controller
|
|
89
|
+
control={createBillForm.control}
|
|
90
|
+
name="quantity"
|
|
91
|
+
render={({ field }) => (
|
|
92
|
+
<NumberInput
|
|
93
|
+
{...field}
|
|
94
|
+
id="quantity"
|
|
95
|
+
label={t('quantity', 'Quantity')}
|
|
96
|
+
min={1}
|
|
97
|
+
max={quantityToDispense}
|
|
98
|
+
value={field.value}
|
|
99
|
+
onChange={(e, { value }) => field.onChange(value)}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
</Column>
|
|
104
|
+
<Column>
|
|
105
|
+
<Controller
|
|
106
|
+
control={createBillForm.control}
|
|
107
|
+
name="unitPrice"
|
|
108
|
+
render={({ field }) => (
|
|
109
|
+
<Dropdown
|
|
110
|
+
{...field}
|
|
111
|
+
onChange={({ selectedItem }) => {
|
|
112
|
+
field.onChange(selectedItem?.unitPrice?.toString() ?? '');
|
|
113
|
+
createBillForm.setValue('id', selectedItem?.id ?? '');
|
|
114
|
+
createBillForm.setValue('text', selectedItem?.text ?? '');
|
|
115
|
+
}}
|
|
116
|
+
id="unit-price"
|
|
117
|
+
itemToString={(item) => item?.text ?? ''}
|
|
118
|
+
items={comboBoxItems}
|
|
119
|
+
label={t('selectUnitPrice', 'Select Unit Price')}
|
|
120
|
+
titleText={t('unitPrice', 'Unit Price')}
|
|
121
|
+
type="default"
|
|
122
|
+
invalid={!!errors.unitPrice}
|
|
123
|
+
invalidText={errors.unitPrice?.message}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
/>
|
|
127
|
+
</Column>
|
|
128
|
+
<Column>
|
|
129
|
+
<div className={styles.formField}>
|
|
130
|
+
<label className={styles.label}>{t('total', 'Total')}</label>
|
|
131
|
+
<div className={styles.value}>{convertToCurrency(calculateTotal())}</div>
|
|
132
|
+
</div>
|
|
133
|
+
</Column>
|
|
134
|
+
</Stack>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const MedicationBillForm: React.FC<MedicationBillFormProps> = ({ medicationRequestBundle, ...props }) => {
|
|
139
|
+
const quantityToDispense = medicationRequestBundle?.request?.dispenseRequest?.quantity?.value ?? 1;
|
|
140
|
+
return <BillForm {...props} quantityToDispense={quantityToDispense} />;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const StandardBillForm: React.FC<Omit<BillFormProps, 'quantityToDispense'>> = (props) => {
|
|
144
|
+
return <BillForm {...props} quantityToDispense={1} />;
|
|
145
|
+
};
|
|
146
|
+
|
|
35
147
|
const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
36
148
|
patientUuid,
|
|
37
149
|
order,
|
|
38
150
|
closeWorkspace,
|
|
39
151
|
closeWorkspaceWithSavedChanges,
|
|
40
152
|
promptBeforeClosing,
|
|
153
|
+
medicationRequestBundle,
|
|
41
154
|
}) => {
|
|
42
155
|
const { t } = useTranslation();
|
|
43
156
|
const defaultPaymentStatus = 'PENDING';
|
|
44
157
|
const isTablet = useLayoutType() === 'tablet';
|
|
45
158
|
const { cashPointUuid, cashierUuid } = useConfig<BillingConfig>();
|
|
46
|
-
const { billableItem, isLoading } = useBillableItem(order
|
|
159
|
+
const { billableItem, isLoading } = useBillableItem(order?.concept?.uuid ?? order?.drug?.concept?.uuid);
|
|
47
160
|
|
|
48
161
|
const comboBoxItems =
|
|
49
162
|
billableItem?.servicePrices?.map((item) => ({
|
|
@@ -57,14 +170,22 @@ const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
|
57
170
|
id: '',
|
|
58
171
|
text: '',
|
|
59
172
|
unitPrice: '0',
|
|
173
|
+
quantity: medicationRequestBundle?.request?.dispenseRequest?.quantity?.value ?? 1,
|
|
60
174
|
},
|
|
61
175
|
});
|
|
62
176
|
|
|
63
177
|
const {
|
|
64
178
|
handleSubmit,
|
|
65
179
|
formState: { isValid, isDirty, isSubmitting, errors },
|
|
180
|
+
watch,
|
|
66
181
|
} = createBillForm;
|
|
67
182
|
|
|
183
|
+
const calculateTotal = () => {
|
|
184
|
+
const price = parseFloat(watch('unitPrice')) || 0;
|
|
185
|
+
const quantity = watch('quantity') || 1;
|
|
186
|
+
return price * quantity;
|
|
187
|
+
};
|
|
188
|
+
|
|
68
189
|
const handleCreateBill = async (formData: CreateBillFormSchema) => {
|
|
69
190
|
const unitPrice = parseFloat(formData.unitPrice);
|
|
70
191
|
const createBillPayload = {
|
|
@@ -76,21 +197,37 @@ const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
|
76
197
|
{
|
|
77
198
|
billableService: billableItem?.uuid,
|
|
78
199
|
lineItemOrder: 0,
|
|
79
|
-
quantity:
|
|
200
|
+
quantity: formData.quantity,
|
|
80
201
|
price: unitPrice,
|
|
81
202
|
paymentStatus: defaultPaymentStatus,
|
|
82
|
-
priceUuid:
|
|
203
|
+
priceUuid: formData.id,
|
|
83
204
|
priceName: formData.text,
|
|
84
205
|
order: order.uuid,
|
|
85
206
|
},
|
|
86
207
|
],
|
|
87
208
|
payments: [],
|
|
88
209
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
await processBillItems(createBillPayload);
|
|
213
|
+
showSnackbar({
|
|
214
|
+
title: t('billItems', 'Save Bill'),
|
|
215
|
+
subtitle: 'Bill processing has been successful',
|
|
216
|
+
kind: 'success',
|
|
217
|
+
timeoutInMs: 3000,
|
|
218
|
+
});
|
|
219
|
+
mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/cashier/bill`), undefined, {
|
|
220
|
+
revalidate: true,
|
|
221
|
+
});
|
|
222
|
+
closeWorkspaceWithSavedChanges();
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Bill processing error:', error);
|
|
225
|
+
showSnackbar({
|
|
226
|
+
title: 'Bill processing error',
|
|
227
|
+
kind: 'error',
|
|
228
|
+
subtitle: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
94
231
|
};
|
|
95
232
|
|
|
96
233
|
useEffect(() => {
|
|
@@ -99,8 +236,22 @@ const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
|
99
236
|
}
|
|
100
237
|
}, [isDirty, promptBeforeClosing]);
|
|
101
238
|
|
|
239
|
+
if (isLoading) {
|
|
240
|
+
return <InlineLoading description={t('loadingBillableItems', 'Loading billable items...')} />;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const commonFormProps = {
|
|
244
|
+
billableItem,
|
|
245
|
+
createBillForm,
|
|
246
|
+
errors,
|
|
247
|
+
calculateTotal,
|
|
248
|
+
comboBoxItems,
|
|
249
|
+
};
|
|
250
|
+
|
|
102
251
|
return (
|
|
103
|
-
<
|
|
252
|
+
<form
|
|
253
|
+
className={styles.form}
|
|
254
|
+
onSubmit={handleSubmit(handleCreateBill, (errors) => console.error('errors', errors))}>
|
|
104
255
|
<div className={styles.formContainer}>
|
|
105
256
|
<ResponsiveWrapper>
|
|
106
257
|
<InlineNotification
|
|
@@ -109,34 +260,17 @@ const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
|
109
260
|
lowContrast
|
|
110
261
|
statusIconDescription="notification"
|
|
111
262
|
subtitle={t('createBillForOrder', 'Create bill for order {{order}} by selecting the correct unit price', {
|
|
112
|
-
order: order
|
|
263
|
+
order: order?.concept?.display ?? order?.drug?.display,
|
|
113
264
|
})}
|
|
114
265
|
title={t('orderBillCreation', 'Order Bill Creation {{orderNumber}}', { orderNumber: order.orderNumber })}
|
|
115
266
|
/>
|
|
116
267
|
</ResponsiveWrapper>
|
|
117
268
|
<ResponsiveWrapper>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{...field}
|
|
124
|
-
onChange={({ selectedItem }) => {
|
|
125
|
-
field.onChange(selectedItem?.unitPrice?.toString() || '');
|
|
126
|
-
createBillForm.setValue('id', selectedItem?.id || '');
|
|
127
|
-
createBillForm.setValue('text', selectedItem?.text || '');
|
|
128
|
-
}}
|
|
129
|
-
id="unit-price"
|
|
130
|
-
itemToString={(item) => item?.text || ''}
|
|
131
|
-
items={comboBoxItems}
|
|
132
|
-
label={t('selectUnitPrice', 'Select Unit Price')}
|
|
133
|
-
titleText={t('unitPrice', 'Unit Price')}
|
|
134
|
-
type="default"
|
|
135
|
-
invalid={!!errors.unitPrice}
|
|
136
|
-
invalidText={errors.unitPrice?.message}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
/>
|
|
269
|
+
{medicationRequestBundle ? (
|
|
270
|
+
<MedicationBillForm {...commonFormProps} medicationRequestBundle={medicationRequestBundle} />
|
|
271
|
+
) : (
|
|
272
|
+
<StandardBillForm {...commonFormProps} />
|
|
273
|
+
)}
|
|
140
274
|
</ResponsiveWrapper>
|
|
141
275
|
</div>
|
|
142
276
|
<ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
|
|
@@ -151,7 +285,7 @@ const CreateBillWorkspace: React.FC<CreateBillWorkspaceProps> = ({
|
|
|
151
285
|
)}
|
|
152
286
|
</Button>
|
|
153
287
|
</ButtonSet>
|
|
154
|
-
</
|
|
288
|
+
</form>
|
|
155
289
|
);
|
|
156
290
|
};
|
|
157
291
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
1
|
+
import { openmrsFetch, restBaseUrl, type OpenmrsResource } from '@openmrs/esm-framework';
|
|
2
2
|
import useSWR from 'swr';
|
|
3
3
|
|
|
4
4
|
export type ChargeAble = {
|
|
@@ -6,7 +6,7 @@ export type ChargeAble = {
|
|
|
6
6
|
name: string;
|
|
7
7
|
shortName: string;
|
|
8
8
|
serviceStatus: 'ENABLED' | 'DISABLED';
|
|
9
|
-
stockItem:
|
|
9
|
+
stockItem: OpenmrsResource;
|
|
10
10
|
serviceType: {
|
|
11
11
|
uuid: string;
|
|
12
12
|
display: string;
|