@kenyaemr/esm-billing-app 5.4.2-pre.2354 → 5.4.2-pre.2358

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.
@@ -1,29 +1,10 @@
1
- import { Button, InlineLoading, Popover, PopoverContent } from '@carbon/react';
2
- import { BaggageClaim, Close, Printer, Wallet, FolderOpen } from '@carbon/react/icons';
3
- import {
4
- defaultVisitCustomRepresentation,
5
- ExtensionSlot,
6
- formatDatetime,
7
- launchWorkspace,
8
- navigate,
9
- parseDate,
10
- restBaseUrl,
11
- showModal,
12
- showSnackbar,
13
- showToast,
14
- updateVisit,
15
- useFeatureFlag,
16
- usePatient,
17
- UserHasAccess,
18
- useVisit,
19
- useVisitContextStore,
20
- } from '@openmrs/esm-framework';
1
+ import { InlineLoading } from '@carbon/react';
2
+ import { ExtensionSlot, formatDatetime, parseDate, usePatient, useVisit } from '@openmrs/esm-framework';
21
3
  import { ErrorState } from '@openmrs/esm-patient-common-lib';
22
4
  import React, { useEffect, useState } from 'react';
23
5
  import { useTranslation } from 'react-i18next';
24
6
  import { useParams } from 'react-router-dom';
25
7
  import { useBill } from '../billing.resource';
26
- import { spaBasePath } from '../constants';
27
8
  import { convertToCurrency } from '../helpers';
28
9
  import { usePaymentsReconciler } from '../hooks/use-payments-reconciler';
29
10
  import { LineItem, MappedBill } from '../types';
@@ -31,27 +12,16 @@ import InvoiceTable from './invoice-table.component';
31
12
  import styles from './invoice.scss';
32
13
  import Payments from './payments/payments.component';
33
14
  import capitalize from 'lodash-es/capitalize';
34
- import { mutate } from 'swr';
35
- import startCase from 'lodash-es/startCase';
36
- import { useCheckShareGnum } from './invoice.resource';
15
+ import { InvoiceActions } from './invoice-actions.component';
37
16
 
38
17
  const Invoice: React.FC = () => {
39
18
  const { t } = useTranslation();
40
- const { checkSHARegNum } = useCheckShareGnum();
41
19
  const { billUuid, patientUuid } = useParams();
42
20
  const { patient, isLoading: isLoadingPatient, error: patientError } = usePatient(patientUuid);
43
21
  const { bill, isLoading: isLoadingBill, error: billingError } = useBill(billUuid);
44
- const isInsurancePayment = (payments) => {
45
- return payments?.some((payment) => payment.instanceType.name === 'Insurance');
46
- };
47
22
  usePaymentsReconciler(billUuid);
48
23
  const { activeVisit, isLoading: isVisitLoading, error: visitError } = useVisit(patientUuid);
49
- const { patientUuid: visitStorePatientUuid, manuallySetVisitUuid } = useVisitContextStore();
50
24
  const [selectedLineItems, setSelectedLineItems] = useState([]);
51
- const isProcessClaimsFormEnabled = useFeatureFlag('healthInformationExchange');
52
-
53
- const isShaFacilityStatusValid =
54
- checkSHARegNum?.registrationNumber && checkSHARegNum.registrationNumber.trim() !== '';
55
25
 
56
26
  const handleSelectItem = (lineItems: Array<LineItem>) => {
57
27
  const paidLineItems = bill?.lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [];
@@ -59,31 +29,6 @@ const Invoice: React.FC = () => {
59
29
  setSelectedLineItems(uniqueLineItems);
60
30
  };
61
31
 
62
- const handlePrint = () => {
63
- const dispose = showModal('print-preview-modal', {
64
- onClose: () => dispose(),
65
- title: `${t('invoice', 'Invoice')} ${bill?.receiptNumber} - ${startCase(bill?.patientName)}`,
66
- documentUrl: `/openmrs${restBaseUrl}/cashier/print?documentType=invoice&billId=${bill?.id}`,
67
- });
68
- };
69
-
70
- const handleBillPayment = () => {
71
- const dispose = showModal('initiate-payment-modal', {
72
- closeModal: () => dispose(),
73
- bill: bill,
74
- selectedLineItems,
75
- });
76
- };
77
-
78
- const mutateClaimForm = async () => {
79
- const activeVisitUrlSuffix = `?patient=${patientUuid}&v=${defaultVisitCustomRepresentation}&includeInactive=false`;
80
- const retrospectiveVisitUuid = patientUuid && visitStorePatientUuid == patientUuid ? manuallySetVisitUuid : null;
81
- const retrospectiveVisitUrlSuffix = `/${retrospectiveVisitUuid}?v=${defaultVisitCustomRepresentation}`;
82
- const activeVisitUrl = `${restBaseUrl}/visit${activeVisitUrlSuffix}`;
83
- const retroVisitUrl = `${restBaseUrl}/visit${retrospectiveVisitUrlSuffix}`;
84
- await mutate((key) => typeof key === 'string' && (key.startsWith(activeVisitUrl) || key.startsWith(retroVisitUrl)));
85
- };
86
-
87
32
  useEffect(() => {
88
33
  const paidLineItems = bill?.lineItems?.filter((item) => item.paymentStatus === 'PAID') ?? [];
89
34
  setSelectedLineItems(paidLineItems);
@@ -114,220 +59,32 @@ const Invoice: React.FC = () => {
114
59
  );
115
60
  }
116
61
 
117
- const handleEndVisit = async () => {
118
- if (activeVisit) {
119
- const endVisitPayload = {
120
- stopDatetime: new Date(),
121
- };
122
-
123
- const abortController = new AbortController();
124
- try {
125
- await updateVisit(activeVisit.uuid, endVisitPayload, abortController);
126
- await mutateClaimForm();
127
- showSnackbar({
128
- isLowContrast: true,
129
- kind: 'success',
130
- subtitle: t('visitEndSuccessssfully', 'visit ended successfully'),
131
- title: t('visitEnded', 'Visit ended'),
132
- });
133
- } catch (error) {
134
- showSnackbar({
135
- title: t('errorEndingVisit', 'Error ending visit'),
136
- kind: 'error',
137
- isLowContrast: false,
138
- subtitle: error?.message,
139
- });
140
- }
141
- }
142
- };
143
-
144
- const handleViewClaims = async () => {
145
- if (!isShaFacilityStatusValid) {
146
- showToast({
147
- critical: true,
148
- kind: 'warning',
149
- title: t('shaFacilityLicenseNumberRequired', 'Facility license number Required'),
150
- description: t(
151
- 'shaFacilityLicenseNumbernRequiredDescription',
152
- 'Facility license number is required to process claims. Please update facility license number details.',
153
- ),
154
- });
155
- return;
156
- }
157
-
158
- if (activeVisit) {
159
- await handleEndVisit();
160
- navigate({ to: `${spaBasePath}/billing/patient/${patientUuid}/${billUuid}/claims` });
161
- } else {
162
- navigate({ to: `${spaBasePath}/billing/patient/${patientUuid}/${billUuid}/claims` });
163
- }
164
- };
165
-
166
62
  return (
167
63
  <div className={styles.invoiceContainer}>
168
64
  {patient && patientUuid && <ExtensionSlot name="patient-header-slot" state={{ patient, patientUuid }} />}
169
- <InvoiceSummary bill={bill} />
170
- <div className={styles.actionArea}>
171
- <Button
172
- onClick={handleBillPayment}
173
- disabled={bill?.balance === 0}
174
- size="sm"
175
- renderIcon={Wallet}
176
- iconDescription="Add"
177
- tooltipPosition="left">
178
- {t('mpesaPayment', 'MPESA Payment')}
179
- </Button>
180
-
181
- {isProcessClaimsFormEnabled && isInsurancePayment(bill?.payments) && (
182
- <Button
183
- onClick={handleViewClaims}
184
- disabled={bill?.status !== 'PAID'}
185
- kind="danger"
186
- size="sm"
187
- renderIcon={BaggageClaim}
188
- iconDescription="Add"
189
- tooltipPosition="bottom">
190
- {activeVisit ? t('endVisitAndClaim', 'End visit and Process claims') : t('claim', 'Process claims')}
191
- </Button>
192
- )}
193
- </div>
194
-
65
+ <InvoiceSummary bill={bill} selectedLineItems={selectedLineItems} activeVisit={activeVisit} />
195
66
  <InvoiceTable bill={bill} isLoadingBill={isLoadingBill} onSelectItem={handleSelectItem} />
196
67
  <Payments bill={bill} selectedLineItems={selectedLineItems} />
197
68
  </div>
198
69
  );
199
70
  };
200
71
 
201
- export function InvoiceSummary({ bill }: { readonly bill: MappedBill }) {
72
+ export function InvoiceSummary({
73
+ bill,
74
+ selectedLineItems,
75
+ activeVisit,
76
+ }: {
77
+ readonly bill: MappedBill;
78
+ readonly selectedLineItems?: LineItem[];
79
+ readonly activeVisit?: any;
80
+ }) {
202
81
  const { t } = useTranslation();
203
- const launchBillCloseOrReopenModal = (action: 'close' | 'reopen') => {
204
- const dispose = showModal('bill-action-modal', {
205
- closeModal: () => dispose(),
206
- bill: bill,
207
- action,
208
- });
209
- };
210
-
211
- const shouldCloseBill = bill.balance === 0 && !bill.closed;
212
- const [isOpen, setIsOpen] = useState(false);
213
-
214
- const handlePrint = (documentType: string, documentTitle: string) => {
215
- const dispose = showModal('print-preview-modal', {
216
- onClose: () => dispose(),
217
- title: documentTitle,
218
- documentUrl: `/openmrs${restBaseUrl}/cashier/print?documentType=${documentType}&billId=${bill?.id}`,
219
- });
220
- };
221
82
 
222
83
  return (
223
84
  <>
224
85
  <div className={styles.invoiceSummary}>
225
86
  <span className={styles.invoiceSummaryTitle}>{t('invoiceSummary', 'Invoice Summary')}</span>
226
- <div className="invoiceSummaryActions">
227
- <Popover
228
- isTabTip
229
- align="bottom-right"
230
- onKeyDown={() => {}}
231
- onRequestClose={() => setIsOpen(false)}
232
- open={isOpen}>
233
- <button
234
- className={styles.printButton}
235
- aria-expanded
236
- aria-label="Settings"
237
- onClick={() => setIsOpen(!isOpen)}
238
- type="button">
239
- <span className={styles.printButtonContent}>
240
- <span className={styles.printButtonText}>{t('print', 'Print')}</span>
241
- <Printer />
242
- </span>
243
- </button>
244
- <PopoverContent>
245
- <div className={styles.popoverContent}>
246
- <Button
247
- kind="ghost"
248
- size="sm"
249
- onClick={() =>
250
- handlePrint(
251
- 'invoice',
252
- `${t('invoice', 'Invoice')} ${bill?.receiptNumber} - ${startCase(bill?.patientName)}`,
253
- )
254
- }
255
- renderIcon={Printer}>
256
- {t('printInvoice', 'Print Invoice')}
257
- </Button>
258
- <Button
259
- kind="ghost"
260
- size="sm"
261
- onClick={() => {
262
- const dispose = showModal('print-preview-modal', {
263
- onClose: () => dispose(),
264
- title: `${t('receipt', 'Receipt')} ${bill?.receiptNumber} - ${startCase(bill?.patientName)}`,
265
- documentUrl: `/openmrs${restBaseUrl}/cashier/receipt?billId=${bill.id}`,
266
- });
267
- }}
268
- renderIcon={Printer}>
269
- {t('printReceipt', 'Print Receipt')}
270
- </Button>
271
- <Button
272
- kind="ghost"
273
- size="sm"
274
- onClick={() =>
275
- handlePrint(
276
- 'billstatement',
277
- `${t('billStatement', 'Bill Statement')} ${bill?.receiptNumber} - ${startCase(
278
- bill?.patientName,
279
- )}`,
280
- )
281
- }
282
- renderIcon={Printer}>
283
- {t('printBillStatement', 'Print Bill Statement')}
284
- </Button>
285
- </div>
286
- </PopoverContent>
287
- </Popover>
288
- {shouldCloseBill && (
289
- <UserHasAccess privilege="Close Cashier Bills">
290
- <Button
291
- kind="danger--ghost"
292
- size="sm"
293
- renderIcon={Close}
294
- iconDescription="Add"
295
- tooltipPosition="right"
296
- onClick={() => launchBillCloseOrReopenModal('close')}>
297
- {t('closeBill', 'Close Bill')}
298
- </Button>
299
- </UserHasAccess>
300
- )}
301
- {bill?.closed && (
302
- <UserHasAccess privilege="Reopen Cashier Bills">
303
- <Button
304
- kind="ghost"
305
- size="sm"
306
- renderIcon={FolderOpen}
307
- iconDescription="Add"
308
- tooltipPosition="right"
309
- onClick={() => launchBillCloseOrReopenModal('reopen')}>
310
- {t('reopen', 'Reopen')}
311
- </Button>
312
- </UserHasAccess>
313
- )}
314
- <Button
315
- kind="ghost"
316
- size="sm"
317
- renderIcon={Wallet}
318
- iconDescription="Add"
319
- tooltipPosition="right"
320
- onClick={() =>
321
- launchWorkspace('payment-workspace', {
322
- bill,
323
- workspaceTitle: t('additionalPayment', 'Additional Payment (Balance {{billBalance}})', {
324
- billBalance: convertToCurrency(bill.balance),
325
- }),
326
- })
327
- }>
328
- {t('additionalPayment', 'Additional Payment')}
329
- </Button>
330
- </div>
87
+ <InvoiceActions bill={bill} selectedLineItems={selectedLineItems} activeVisit={activeVisit} />
331
88
  </div>
332
89
  <div className={styles.invoiceSummaryContainer}>
333
90
  <div className={styles.invoiceCard}>