@kenyaemr/esm-billing-app 5.4.2-pre.2262 → 5.4.2-pre.2269
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 +14 -14
- package/dist/300.js +1 -1
- package/dist/46.js +8 -0
- package/dist/46.js.map +1 -0
- package/dist/{632.js → 611.js} +1 -1
- package/dist/611.js.map +1 -0
- package/dist/kenyaemr-esm-billing-app.js +1 -1
- package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +428 -428
- 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/patient-bills.component.tsx +1 -4
- package/src/billing.resource.ts +2 -0
- package/src/index.ts +2 -0
- package/src/invoice/invoice.component.tsx +40 -1
- package/src/invoice/invoice.resource.ts +41 -0
- package/src/invoice/invoice.scss +14 -5
- package/src/invoice/print-bill-receipt/receipt-print-button.component.tsx +2 -2
- package/src/modal/bill-action.modal.scss +7 -0
- package/src/modal/bill-action.modal.tsx +122 -0
- package/src/past-patient-bills/patient-bills.component.tsx +1 -2
- package/src/payment-modes/payment-mode-dashboard.compont.tsx +2 -2
- package/src/payment-modes/payment-mode.workspace.test.tsx +57 -1
- package/src/payment-modes/payment-mode.workspace.tsx +9 -3
- package/src/payment-modes/usePaymentModeFormSchema.tsx +1 -1
- package/src/prompt-payment/prompt-payment-modal.component.tsx +5 -2
- package/src/prompt-payment/prompt-payment.resource.tsx +1 -1
- package/src/routes.json +4 -0
- package/src/types/index.ts +2 -0
- package/src/utils.ts +4 -0
- package/translations/en.json +2 -0
- package/dist/632.js.map +0 -1
- package/dist/910.js +0 -8
- package/dist/910.js.map +0 -1
- /package/dist/{910.js.LICENSE.txt → 46.js.LICENSE.txt} +0 -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":"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-group-nav-slot"},{"component":"billDepositDashboardLink","name":"bill-deposit-dashboard-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-group-nav-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-group-nav-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-group-nav-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-group-nav-slot"},{"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":"add-deposit-workspace","component":"addDepositWorkspace","title":"Add Deposit","type":"other-form"},{"name":"deposit-transaction-workspace","component":"depositTransactionWorkspace","title":"Deposit Transaction","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":"manage-claim-request-modal","component":"manageClaimRequestModal"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"},{"name":"delete-deposit-modal","component":"deleteDepositModal"},{"name":"reverse-transaction-modal","component":"reverseTransactionModal"},{"name":"print-preview-modal","component":"printPreviewModal"}],"version":"5.4.2-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":"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-group-nav-slot"},{"component":"billDepositDashboardLink","name":"bill-deposit-dashboard-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentHistoryLink","name":"payment-history-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentPointsLink","name":"payment-points-link","slot":"billing-dashboard-group-nav-slot"},{"component":"paymentModesLink","name":"payment-modes-link","slot":"billing-dashboard-group-nav-slot"},{"component":"billManagerLink","name":"bill-manager-link","slot":"billing-dashboard-group-nav-slot"},{"component":"chargeableItemsLink","name":"chargeable-items-link","slot":"billing-dashboard-group-nav-slot"},{"component":"billableExemptionsLink","name":"billable-exemptions-link","slot":"billing-dashboard-group-nav-slot"},{"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":"add-deposit-workspace","component":"addDepositWorkspace","title":"Add Deposit","type":"other-form"},{"name":"deposit-transaction-workspace","component":"depositTransactionWorkspace","title":"Deposit Transaction","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":"manage-claim-request-modal","component":"manageClaimRequestModal"},{"name":"clock-in-modal","component":"clockIn"},{"name":"create-bill-item-modal","component":"createBillItemModal"},{"name":"delete-deposit-modal","component":"deleteDepositModal"},{"name":"reverse-transaction-modal","component":"reverseTransactionModal"},{"name":"print-preview-modal","component":"printPreviewModal"},{"name":"bill-action-modal","component":"billActionModal"}],"version":"5.4.2-pre.2269"}
|
package/package.json
CHANGED
|
@@ -12,8 +12,6 @@ import {
|
|
|
12
12
|
TableExpandRow,
|
|
13
13
|
TableCell,
|
|
14
14
|
TableExpandedRow,
|
|
15
|
-
Tile,
|
|
16
|
-
Button,
|
|
17
15
|
} from '@carbon/react';
|
|
18
16
|
import { convertToCurrency } from '../../helpers';
|
|
19
17
|
import { useTranslation } from 'react-i18next';
|
|
@@ -21,8 +19,7 @@ import { EmptyState } from '@openmrs/esm-patient-common-lib';
|
|
|
21
19
|
import { MappedBill, PaymentStatus } from '../../types';
|
|
22
20
|
import styles from '../../bills-table/bills-table.scss';
|
|
23
21
|
import BillLineItems from './bill-line-items.component';
|
|
24
|
-
import {
|
|
25
|
-
import { ExtensionSlot, launchWorkspace, showModal } from '@openmrs/esm-framework';
|
|
22
|
+
import { ExtensionSlot } from '@openmrs/esm-framework';
|
|
26
23
|
|
|
27
24
|
type PatientBillsProps = {
|
|
28
25
|
bills: Array<MappedBill>;
|
package/src/billing.resource.ts
CHANGED
|
@@ -63,6 +63,7 @@ export const mapBillProperties = (bill: PatientInvoice): MappedBill => {
|
|
|
63
63
|
totalPayments: bill?.totalPayments,
|
|
64
64
|
totalDeposits: bill?.totalDeposits,
|
|
65
65
|
totalExempted: bill?.totalExempted,
|
|
66
|
+
closed: bill?.closed,
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
return mappedBill;
|
|
@@ -142,6 +143,7 @@ export const useBill = (billUuid: string) => {
|
|
|
142
143
|
totalDeposits: bill?.totalDeposits,
|
|
143
144
|
totalExempted: bill?.totalExempted,
|
|
144
145
|
balance: bill?.balance,
|
|
146
|
+
closed: bill?.closed,
|
|
145
147
|
};
|
|
146
148
|
|
|
147
149
|
return mappedBill;
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import RefundLineItem from './billable-services/bill-manager/bill-actions/refund
|
|
|
33
33
|
import WaiveBillActionButton from './billable-services/bill-manager/bill-actions/waive-bill-action-button.component';
|
|
34
34
|
import { DeleteBillModal } from './billable-services/bill-manager/modals/delete-bill.modal';
|
|
35
35
|
import { RefundBillModal } from './billable-services/bill-manager/modals/refund-bill.modal';
|
|
36
|
+
import BillActionModal from './modal/bill-action.modal';
|
|
36
37
|
import DeleteBillableServiceModal from './billable-services/bill-manager/modals/delete-billable-service.modal';
|
|
37
38
|
import CancelBillWorkspace from './billable-services/bill-manager/workspaces/cancel-bill/cancel-bill.workspace';
|
|
38
39
|
import { EditBillForm } from './billable-services/bill-manager/workspaces/edit-bill/edit-bill-form.workspace';
|
|
@@ -188,6 +189,7 @@ export const deleteBillModal = getSyncLifecycle(DeleteBillModal, options);
|
|
|
188
189
|
export const waiveBillForm = getSyncLifecycle(WaiveBillForm, options);
|
|
189
190
|
export const editBillForm = getSyncLifecycle(EditBillForm, options);
|
|
190
191
|
export const refundBillModal = getSyncLifecycle(RefundBillModal, options);
|
|
192
|
+
export const billActionModal = getSyncLifecycle(BillActionModal, options);
|
|
191
193
|
export const cancelBillWorkspace = getSyncLifecycle(CancelBillWorkspace, options);
|
|
192
194
|
export const waiveBillActionButton = getSyncLifecycle(WaiveBillActionButton, options);
|
|
193
195
|
export const deleteBillActionButton = getSyncLifecycle(DeleteBillActionButton, options);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Button, InlineLoading } from '@carbon/react';
|
|
2
|
-
import { BaggageClaim, Printer, Wallet } from '@carbon/react/icons';
|
|
2
|
+
import { BaggageClaim, Close, Printer, Wallet, FolderOpen } from '@carbon/react/icons';
|
|
3
3
|
import {
|
|
4
4
|
defaultVisitCustomRepresentation,
|
|
5
5
|
ExtensionSlot,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
updateVisit,
|
|
14
14
|
useFeatureFlag,
|
|
15
15
|
usePatient,
|
|
16
|
+
UserHasAccess,
|
|
16
17
|
useVisit,
|
|
17
18
|
useVisitContextStore,
|
|
18
19
|
} from '@openmrs/esm-framework';
|
|
@@ -212,10 +213,48 @@ const Invoice: React.FC = () => {
|
|
|
212
213
|
|
|
213
214
|
export function InvoiceSummary({ bill }: { readonly bill: MappedBill }) {
|
|
214
215
|
const { t } = useTranslation();
|
|
216
|
+
const launchBillCloseOrReopenModal = (action: 'close' | 'reopen') => {
|
|
217
|
+
const dispose = showModal('bill-action-modal', {
|
|
218
|
+
closeModal: () => dispose(),
|
|
219
|
+
bill: bill,
|
|
220
|
+
action,
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const shouldCloseBill = bill.balance === 0 && !bill.closed;
|
|
225
|
+
|
|
215
226
|
return (
|
|
216
227
|
<>
|
|
217
228
|
<div className={styles.invoiceSummary}>
|
|
218
229
|
<span className={styles.invoiceSummaryTitle}>{t('invoiceSummary', 'Invoice Summary')}</span>
|
|
230
|
+
<div className="invoiceSummaryActions">
|
|
231
|
+
{shouldCloseBill && (
|
|
232
|
+
<UserHasAccess privilege="Close Cashier Bills">
|
|
233
|
+
<Button
|
|
234
|
+
kind="danger--ghost"
|
|
235
|
+
size="sm"
|
|
236
|
+
renderIcon={Close}
|
|
237
|
+
iconDescription="Add"
|
|
238
|
+
tooltipPosition="right"
|
|
239
|
+
onClick={() => launchBillCloseOrReopenModal('close')}>
|
|
240
|
+
{t('closeBill', 'Close Bill')}
|
|
241
|
+
</Button>
|
|
242
|
+
</UserHasAccess>
|
|
243
|
+
)}
|
|
244
|
+
{bill?.closed && (
|
|
245
|
+
<UserHasAccess privilege="Reopen Cashier Bills">
|
|
246
|
+
<Button
|
|
247
|
+
kind="ghost"
|
|
248
|
+
size="sm"
|
|
249
|
+
renderIcon={FolderOpen}
|
|
250
|
+
iconDescription="Add"
|
|
251
|
+
tooltipPosition="right"
|
|
252
|
+
onClick={() => launchBillCloseOrReopenModal('reopen')}>
|
|
253
|
+
{t('reopen', 'Reopen')}
|
|
254
|
+
</Button>
|
|
255
|
+
</UserHasAccess>
|
|
256
|
+
)}
|
|
257
|
+
</div>
|
|
219
258
|
</div>
|
|
220
259
|
<div className={styles.invoiceSummaryContainer}>
|
|
221
260
|
<div className={styles.invoiceCard}>
|
|
@@ -109,3 +109,44 @@ export const useShaFacilityStatus = () => {
|
|
|
109
109
|
mutate,
|
|
110
110
|
};
|
|
111
111
|
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reopens or closes a bill by making an API call to the billing service.
|
|
115
|
+
*
|
|
116
|
+
* This function allows authorized users to either reopen a closed bill or close an open bill.
|
|
117
|
+
* The action requires a reason to be provided for audit trail purposes.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} billUuid - The unique identifier of the bill to be modified
|
|
120
|
+
* @param {'reopen' | 'close'} action - The action to perform on the bill
|
|
121
|
+
* - 'reopen': Reopens a previously closed bill
|
|
122
|
+
* - 'close': Closes an open bill
|
|
123
|
+
* @param {Object} payload - The payload containing the reason for the action
|
|
124
|
+
* @param {string} payload.reason - A descriptive reason explaining why the bill is being reopened or closed
|
|
125
|
+
*
|
|
126
|
+
* @returns {Promise<FetchResponse>} A promise that resolves to the API response
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* // Reopen a closed bill
|
|
130
|
+
* const result = await reOpenOrCloseBill('bill-uuid-123', 'reopen', {
|
|
131
|
+
* reason: 'Patient returned for additional services'
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Close an open bill
|
|
136
|
+
* const result = await reOpenOrCloseBill('bill-uuid-456', 'close', {
|
|
137
|
+
* reason: 'Services completed and payment received'
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* @throws {Error} When the API call fails or returns an error response
|
|
141
|
+
*/
|
|
142
|
+
export function reOpenOrCloseBill(billUuid: string, action: 'reopen' | 'close', payload: { reason: string }) {
|
|
143
|
+
return openmrsFetch(`${restBaseUrl}/kenyaemr-cashier/bill/${billUuid}/${action}`, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
},
|
|
148
|
+
body: {
|
|
149
|
+
reason: payload.reason,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
package/src/invoice/invoice.scss
CHANGED
|
@@ -130,12 +130,21 @@
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
.invoiceSummary {
|
|
134
|
+
display: flex;
|
|
135
|
+
justify-content: space-between;
|
|
136
|
+
align-items: center;
|
|
136
137
|
margin: layout.$spacing-05 layout.$spacing-05 0 layout.$spacing-05;
|
|
137
138
|
background-color: colors.$white;
|
|
138
|
-
display: block;
|
|
139
139
|
border-bottom: 1px solid colors.$gray-20;
|
|
140
|
-
padding: layout.$spacing-04;
|
|
140
|
+
padding: layout.$spacing-01 layout.$spacing-04;
|
|
141
|
+
|
|
142
|
+
.invoiceSummaryActions {
|
|
143
|
+
display: flex;
|
|
144
|
+
gap: layout.$spacing-04;
|
|
145
|
+
}
|
|
146
|
+
.invoiceSummaryTitle {
|
|
147
|
+
@include type.type-style('heading-01');
|
|
148
|
+
color: colors.$gray-70;
|
|
149
|
+
}
|
|
141
150
|
}
|
|
@@ -42,8 +42,8 @@ const ReceiptPrintButton: React.FC<ReceiptPrintButtonProps> = ({ bill }) => {
|
|
|
42
42
|
* @returns true if printing should be disabled, false otherwise
|
|
43
43
|
*/
|
|
44
44
|
function shouldDisablePrinting(bill: MappedBill): boolean {
|
|
45
|
-
const hasPayments = bill
|
|
46
|
-
const hasExemptedItems = bill
|
|
45
|
+
const hasPayments = bill?.payments?.length > 0;
|
|
46
|
+
const hasExemptedItems = bill?.lineItems?.some((item) => item.paymentStatus === PaymentStatus.EXEMPTED);
|
|
47
47
|
|
|
48
48
|
// If there are exempted items, we need special handling
|
|
49
49
|
if (hasExemptedItems) {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ModalBody, ModalFooter, ModalHeader, Button, TextArea } from '@carbon/react';
|
|
3
|
+
import { type MappedBill } from '../types';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { convertToCurrency } from '../helpers';
|
|
6
|
+
import { reOpenOrCloseBill } from '../invoice/invoice.resource';
|
|
7
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
8
|
+
import { mutate } from 'swr';
|
|
9
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
12
|
+
import styles from './bill-action.modal.scss';
|
|
13
|
+
|
|
14
|
+
type BillActionModalProps = {
|
|
15
|
+
closeModal: () => void;
|
|
16
|
+
bill: MappedBill;
|
|
17
|
+
action: 'close' | 'reopen';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const formSchema = z.object({
|
|
21
|
+
reason: z.string().min(1, { message: 'Reason is required' }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type FormData = z.infer<typeof formSchema>;
|
|
25
|
+
|
|
26
|
+
const BillActionModal: React.FC<BillActionModalProps> = (props) => {
|
|
27
|
+
const { closeModal, bill, action } = props;
|
|
28
|
+
const { t } = useTranslation();
|
|
29
|
+
const formMethod = useForm({
|
|
30
|
+
defaultValues: {
|
|
31
|
+
reason: '',
|
|
32
|
+
},
|
|
33
|
+
resolver: zodResolver(formSchema),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const modalExplanation =
|
|
37
|
+
action === 'close'
|
|
38
|
+
? t('closeBillExplanation', 'Closing this bill will prevent any new items from being added to this bill')
|
|
39
|
+
: t('reopenBillExplanation', 'Reopening this bill will allow new items to be added to this bill');
|
|
40
|
+
|
|
41
|
+
const handleCloseBill = async (data: FormData) => {
|
|
42
|
+
try {
|
|
43
|
+
const response = await reOpenOrCloseBill(bill?.uuid, action, {
|
|
44
|
+
reason: data.reason,
|
|
45
|
+
});
|
|
46
|
+
if (response?.ok) {
|
|
47
|
+
showSnackbar({
|
|
48
|
+
title: t('billClosedSuccessfully', 'Bill {{action}} successfully', { action: action }),
|
|
49
|
+
subtitle: t('billClosedSuccessfullySubtitle', 'The bill has been {{action}} successfully', {
|
|
50
|
+
action: action,
|
|
51
|
+
}),
|
|
52
|
+
kind: 'success',
|
|
53
|
+
timeoutInMs: 3000,
|
|
54
|
+
isLowContrast: true,
|
|
55
|
+
});
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error('Failed to close bill');
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const errorResponseBody = error?.responseBody?.error || t('errorResponseBodyMessage', 'An error occurred');
|
|
61
|
+
|
|
62
|
+
showSnackbar({
|
|
63
|
+
title: t('billClosedFailed', 'Bill closing failed'),
|
|
64
|
+
subtitle: errorResponseBody,
|
|
65
|
+
kind: 'error',
|
|
66
|
+
timeoutInMs: 3000,
|
|
67
|
+
isLowContrast: true,
|
|
68
|
+
});
|
|
69
|
+
} finally {
|
|
70
|
+
const url = `/ws/rest/v1/cashier/bill/${bill.uuid}`;
|
|
71
|
+
mutate((key) => typeof key === 'string' && key.startsWith(url), undefined, { revalidate: true });
|
|
72
|
+
closeModal();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return (
|
|
76
|
+
<form onSubmit={formMethod.handleSubmit(handleCloseBill, (error) => console.error('error', error))}>
|
|
77
|
+
<ModalHeader
|
|
78
|
+
closeModal={closeModal}
|
|
79
|
+
label={t('billActionWithDetails', '{{action}} Bill - {{receiptNumber}} ({{status}}, {{amount}})', {
|
|
80
|
+
action: action === 'close' ? t('close', 'Close') : t('reopen', 'Reopen'),
|
|
81
|
+
receiptNumber: bill?.receiptNumber,
|
|
82
|
+
status: bill?.status,
|
|
83
|
+
amount: bill?.totalAmount ? `${convertToCurrency(bill?.totalAmount)}` : 'N/A',
|
|
84
|
+
})}
|
|
85
|
+
title={t('billAction', '{{action}} Bill', {
|
|
86
|
+
action: action === 'close' ? t('close', 'Close') : t('reopen', 'Reopen'),
|
|
87
|
+
})}
|
|
88
|
+
/>
|
|
89
|
+
<ModalBody>
|
|
90
|
+
<div>
|
|
91
|
+
<p className={styles.modalExplanation}>{modalExplanation}</p>
|
|
92
|
+
</div>
|
|
93
|
+
<Controller
|
|
94
|
+
control={formMethod.control}
|
|
95
|
+
name="reason"
|
|
96
|
+
render={({ field }) => (
|
|
97
|
+
<TextArea
|
|
98
|
+
id="reason"
|
|
99
|
+
labelText={t('reason', 'Reason for {{action}} bill', { action: action })}
|
|
100
|
+
placeholder={t('reason', 'Reason for {{action}} bill', { action: action })}
|
|
101
|
+
rows={4}
|
|
102
|
+
onChange={field.onChange}
|
|
103
|
+
value={field.value}
|
|
104
|
+
invalid={!!formMethod.formState.errors.reason}
|
|
105
|
+
invalidText={formMethod.formState.errors.reason?.message}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
/>
|
|
109
|
+
</ModalBody>
|
|
110
|
+
<ModalFooter>
|
|
111
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
112
|
+
{t('cancel', 'Cancel')}
|
|
113
|
+
</Button>
|
|
114
|
+
<Button disabled={!formMethod.formState.isValid} type="submit" kind="danger">
|
|
115
|
+
{action === 'close' ? t('close', 'Close') : t('reopen', 'Reopen')}
|
|
116
|
+
</Button>
|
|
117
|
+
</ModalFooter>
|
|
118
|
+
</form>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export default BillActionModal;
|
|
@@ -36,7 +36,6 @@ export const patientBillsHeaders = [
|
|
|
36
36
|
export const PatientBills: React.FC<PatientBillsProps> = ({ bills, onCancel, patientUuid }) => {
|
|
37
37
|
const { t } = useTranslation();
|
|
38
38
|
const { patient, isLoading, error } = usePatient(patientUuid);
|
|
39
|
-
|
|
40
39
|
if (isLoading) {
|
|
41
40
|
return <InlineLoading status="active" description={t('loading', 'Loading...')} />;
|
|
42
41
|
}
|
|
@@ -63,7 +62,7 @@ export const PatientBills: React.FC<PatientBillsProps> = ({ bills, onCancel, pat
|
|
|
63
62
|
style={{ textDecoration: 'none', maxWidth: '50%' }}
|
|
64
63
|
to={billingUrl}
|
|
65
64
|
templateParams={{ patientUuid: bill.patientUuid, uuid: bill.uuid }}>
|
|
66
|
-
{bill.lineItems.map((item) => item
|
|
65
|
+
{bill.lineItems.map((item) => item?.billableService?.split(':')[1]).join(', ')}
|
|
67
66
|
</ConfigurableLink>
|
|
68
67
|
),
|
|
69
68
|
totalAmount: convertToCurrency(bill.totalAmount),
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '@carbon/react';
|
|
23
23
|
|
|
24
24
|
import styles from './payment-mode-dashboard.scss';
|
|
25
|
-
import { formatDate, launchWorkspace, showModal, useDebounce } from '@openmrs/esm-framework';
|
|
25
|
+
import { formatDate, launchWorkspace, showModal, useDebounce, useLayoutType } from '@openmrs/esm-framework';
|
|
26
26
|
import { PaymentMode } from '../types';
|
|
27
27
|
import startCase from 'lodash/startCase';
|
|
28
28
|
|
|
@@ -30,7 +30,7 @@ type PaymentModeDashboardProps = {};
|
|
|
30
30
|
|
|
31
31
|
const PaymentModeDashboard: React.FC<PaymentModeDashboardProps> = () => {
|
|
32
32
|
const { t } = useTranslation();
|
|
33
|
-
const size = 'md';
|
|
33
|
+
const size = useLayoutType() === 'tablet' ? 'md' : 'sm';
|
|
34
34
|
const { paymentModes = [], isLoading } = usePaymentModes(false);
|
|
35
35
|
const [searchTerm, setSearchTerm] = useState('');
|
|
36
36
|
|
|
@@ -99,7 +99,7 @@ describe('PaymentModeWorkspace', () => {
|
|
|
99
99
|
// Key in attribute type name, description, required and format
|
|
100
100
|
const attributeTypeNameInput = screen.getByRole('textbox', { name: /Attribute name/i });
|
|
101
101
|
const attributeTypeDescriptionInput = screen.getByRole('textbox', { name: /Attribute description/i });
|
|
102
|
-
const attributeRegExpInput = screen.getByRole('textbox', { name: /
|
|
102
|
+
const attributeRegExpInput = screen.getByRole('textbox', { name: /Regular expression/i });
|
|
103
103
|
const attributeRetiredToggle = screen.getByRole('switch', { name: /Attribute retired/i });
|
|
104
104
|
const attributeRequiredToggle = screen.getByRole('switch', { name: /Attribute required/i });
|
|
105
105
|
|
|
@@ -148,4 +148,60 @@ describe('PaymentModeWorkspace', () => {
|
|
|
148
148
|
'',
|
|
149
149
|
);
|
|
150
150
|
});
|
|
151
|
+
|
|
152
|
+
test('should submit payload with attributeTypes and uuid when in edit mode', async () => {
|
|
153
|
+
const initialPaymentMode = {
|
|
154
|
+
uuid: '123',
|
|
155
|
+
name: 'Test Name',
|
|
156
|
+
description: 'Test Description',
|
|
157
|
+
retired: false,
|
|
158
|
+
attributeTypes: [
|
|
159
|
+
{
|
|
160
|
+
name: 'Test Attribute Name',
|
|
161
|
+
uuid: '456',
|
|
162
|
+
format: 'java.lang.String',
|
|
163
|
+
regExp: null,
|
|
164
|
+
attributeOrder: 0,
|
|
165
|
+
foreignKey: null,
|
|
166
|
+
description: 'Test Attribute Description',
|
|
167
|
+
retired: false,
|
|
168
|
+
required: false,
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const user = userEvent.setup();
|
|
174
|
+
render(<PaymentModeWorkspace {...testProps} initialPaymentMode={initialPaymentMode} />);
|
|
175
|
+
|
|
176
|
+
// make the attribute type required
|
|
177
|
+
const attributeRequiredToggle = screen.getByRole('switch', { name: /Attribute required/i });
|
|
178
|
+
await user.click(attributeRequiredToggle);
|
|
179
|
+
|
|
180
|
+
// click save and close
|
|
181
|
+
const saveAndCloseButton = screen.getByRole('button', { name: /Save & Close/i });
|
|
182
|
+
await user.click(saveAndCloseButton);
|
|
183
|
+
|
|
184
|
+
// should call createPaymentMode with attributeTypes and uuid
|
|
185
|
+
expect(mockCreatePaymentMode).toHaveBeenCalledWith(
|
|
186
|
+
{
|
|
187
|
+
description: 'Test Description',
|
|
188
|
+
name: 'Test Name',
|
|
189
|
+
attributeTypes: [
|
|
190
|
+
{
|
|
191
|
+
name: 'Test Attribute Name',
|
|
192
|
+
uuid: '456',
|
|
193
|
+
description: 'Test Attribute Description',
|
|
194
|
+
retired: false,
|
|
195
|
+
required: true,
|
|
196
|
+
format: 'java.lang.String',
|
|
197
|
+
regExp: null,
|
|
198
|
+
attributeOrder: 0,
|
|
199
|
+
foreignKey: null,
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
retired: false,
|
|
203
|
+
},
|
|
204
|
+
'123',
|
|
205
|
+
);
|
|
206
|
+
});
|
|
151
207
|
});
|
|
@@ -54,16 +54,22 @@ const PaymentModeWorkspace: React.FC<PaymentModeWorkspaceProps> = ({
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
const mappedAttributeTypes = (attributes) => {
|
|
57
|
-
|
|
57
|
+
const uuid = initialPaymentMode?.uuid
|
|
58
|
+
? initialPaymentMode.attributeTypes.find((a) => a.name === attributes.name)?.uuid
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
const attributeType = {
|
|
58
62
|
name: attributes.name,
|
|
59
63
|
description: attributes.description,
|
|
60
64
|
retired: attributes.retired,
|
|
61
65
|
attributeOrder: attributes?.attributeOrder ?? 0,
|
|
62
66
|
format: attributes?.format ?? '',
|
|
63
67
|
foreignKey: attributes?.foreignKey ?? null,
|
|
64
|
-
regExp: attributes?.regExp ??
|
|
68
|
+
regExp: attributes?.regExp ?? null,
|
|
65
69
|
required: attributes.required,
|
|
66
70
|
};
|
|
71
|
+
|
|
72
|
+
return uuid ? { ...attributeType, uuid } : attributeType;
|
|
67
73
|
};
|
|
68
74
|
|
|
69
75
|
const onSubmit = async (data: PaymentModeFormSchema) => {
|
|
@@ -192,7 +198,7 @@ const PaymentModeWorkspace: React.FC<PaymentModeWorkspaceProps> = ({
|
|
|
192
198
|
</Stack>
|
|
193
199
|
</div>
|
|
194
200
|
<ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
|
|
195
|
-
<Button style={{ maxWidth: '50%' }} kind="secondary" onClick={() => closeWorkspace}>
|
|
201
|
+
<Button style={{ maxWidth: '50%' }} kind="secondary" onClick={() => closeWorkspace()}>
|
|
196
202
|
{t('cancel', 'Cancel')}
|
|
197
203
|
</Button>
|
|
198
204
|
<Button
|
|
@@ -8,7 +8,7 @@ const usePaymentModeFormSchema = () => {
|
|
|
8
8
|
retired: z.boolean().default(false),
|
|
9
9
|
retiredReason: z.string().optional(),
|
|
10
10
|
format: z.string().optional(),
|
|
11
|
-
regExp: z.string().optional(),
|
|
11
|
+
regExp: z.union([z.string(), z.null()]).optional(),
|
|
12
12
|
required: z.boolean().default(false),
|
|
13
13
|
})
|
|
14
14
|
.refine(
|
|
@@ -28,7 +28,6 @@ const PromptPaymentModal: React.FC<PromptPaymentModalProps> = () => {
|
|
|
28
28
|
const { shouldShowBillingPrompt, isLoading, bills } = useBillingPrompt(patientUuid, 'patient-chart');
|
|
29
29
|
const [showModal, setShowModal] = useState({ loadingModal: true, billingModal: true });
|
|
30
30
|
const { enforceBillPayment } = useConfig<BillingConfig>();
|
|
31
|
-
|
|
32
31
|
const closeButtonText = enforceBillPayment
|
|
33
32
|
? t('navigateBack', 'Navigate back')
|
|
34
33
|
: t('proceedToCare', 'Proceed to care');
|
|
@@ -42,7 +41,9 @@ const PromptPaymentModal: React.FC<PromptPaymentModalProps> = () => {
|
|
|
42
41
|
const lineItems = bills
|
|
43
42
|
.filter((bill) => bill.status !== 'PAID')
|
|
44
43
|
.flatMap((bill) => bill.lineItems)
|
|
45
|
-
.filter(
|
|
44
|
+
.filter(
|
|
45
|
+
(lineItem) => lineItem?.paymentStatus !== 'EXEMPTED' && lineItem?.paymentStatus !== 'PAID' && !lineItem?.voided,
|
|
46
|
+
);
|
|
46
47
|
|
|
47
48
|
if (!shouldShowBillingPrompt) {
|
|
48
49
|
return null;
|
|
@@ -71,6 +72,7 @@ const PromptPaymentModal: React.FC<PromptPaymentModalProps> = () => {
|
|
|
71
72
|
<StructuredListCell head>{t('item', 'Item')}</StructuredListCell>
|
|
72
73
|
<StructuredListCell head>{t('quantity', 'Quantity')}</StructuredListCell>
|
|
73
74
|
<StructuredListCell head>{t('unitPrice', 'Unit price')}</StructuredListCell>
|
|
75
|
+
<StructuredListCell head>{t('status', 'Status')}</StructuredListCell>
|
|
74
76
|
<StructuredListCell head>{t('total', 'Total')}</StructuredListCell>
|
|
75
77
|
</StructuredListRow>
|
|
76
78
|
</StructuredListHead>
|
|
@@ -81,6 +83,7 @@ const PromptPaymentModal: React.FC<PromptPaymentModalProps> = () => {
|
|
|
81
83
|
<StructuredListCell>{extractString(lineItem.billableService || lineItem.item)}</StructuredListCell>
|
|
82
84
|
<StructuredListCell>{lineItem.quantity}</StructuredListCell>
|
|
83
85
|
<StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
|
|
86
|
+
<StructuredListCell>{lineItem.paymentStatus}</StructuredListCell>
|
|
84
87
|
<StructuredListCell>{convertToCurrency(lineItem.quantity * lineItem.price)}</StructuredListCell>
|
|
85
88
|
</StructuredListRow>
|
|
86
89
|
);
|
|
@@ -71,7 +71,7 @@ const shouldShowPrompt = (
|
|
|
71
71
|
const hasOnlyOrderBills = (bills: Array<MappedBill>): boolean => {
|
|
72
72
|
const flattenedBills = bills.flatMap((bill) => bill.lineItems);
|
|
73
73
|
// check if all line items are orders, line item with order has order not set to null
|
|
74
|
-
return flattenedBills.every((item) => item
|
|
74
|
+
return flattenedBills.every((item) => item?.order);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
/**
|
package/src/routes.json
CHANGED
package/src/types/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface MappedBill {
|
|
|
26
26
|
totalDeposits?: number;
|
|
27
27
|
totalExempted?: number;
|
|
28
28
|
balance?: number;
|
|
29
|
+
closed?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
interface LocationLink {
|
|
@@ -145,6 +146,7 @@ export interface PatientInvoice {
|
|
|
145
146
|
totalDeposits?: number;
|
|
146
147
|
totalExempted?: number;
|
|
147
148
|
balance?: number;
|
|
149
|
+
closed?: boolean;
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
export interface PatientDetails {
|
package/src/utils.ts
CHANGED
|
@@ -162,6 +162,10 @@ export function waitForASecond(): Promise<string> {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
export const computeWaivedAmount = (bill: MappedBill) => {
|
|
165
|
+
if (!bill.payments) {
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
165
169
|
return bill.payments
|
|
166
170
|
.filter((payment) => payment.instanceType.name.toLowerCase() === 'waiver')
|
|
167
171
|
.reduce((curr: number, prev) => curr + Number(prev.amountTendered), 0);
|
package/translations/en.json
CHANGED
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"clearSearch": "Clear search input",
|
|
78
78
|
"clockInTime": "Clocked in on {{clockInDate}}",
|
|
79
79
|
"close": "Close",
|
|
80
|
+
"closeBill": "Close Bill",
|
|
80
81
|
"create": "Create",
|
|
81
82
|
"createClaimError": "Create Claim error",
|
|
82
83
|
"created": "Created",
|
|
@@ -248,6 +249,7 @@
|
|
|
248
249
|
"regExp": "Regular expression",
|
|
249
250
|
"rejected": "Rejected",
|
|
250
251
|
"remove": "Remove",
|
|
252
|
+
"reopen": "Reopen",
|
|
251
253
|
"retryRequest": "Retry request",
|
|
252
254
|
"reverse": "Reverse",
|
|
253
255
|
"saveAndClose": "Save & Close",
|