@kenyaemr/esm-billing-app 5.4.1-pre.1801 → 5.4.1-pre.1821

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.
Files changed (37) hide show
  1. package/.turbo/turbo-build.log +65 -58
  2. package/dist/300.js +1 -1
  3. package/dist/{475.js → 548.js} +1 -1
  4. package/dist/548.js.map +1 -0
  5. package/dist/kenyaemr-esm-billing-app.js +1 -1
  6. package/dist/kenyaemr-esm-billing-app.js.buildmanifest.json +26 -26
  7. package/dist/kenyaemr-esm-billing-app.js.map +1 -1
  8. package/dist/main.js +2 -2
  9. package/dist/main.js.map +1 -1
  10. package/dist/routes.json +1 -1
  11. package/package.json +1 -1
  12. package/src/benefits-package/forms/package-interventions.component.tsx +11 -16
  13. package/src/billable-services/bill-manager/modals/create-bill-item-modal.component.tsx +208 -0
  14. package/src/billable-services/bill-manager/modals/create-bill-item-modal.scss +26 -0
  15. package/src/billable-services/bill-manager/modals/refund-bill.modal.tsx +1 -1
  16. package/src/billable-services/bill-manager/workspaces/edit-bill/edit-bill-form.workspace.tsx +1 -1
  17. package/src/billable-services/bill-manager/workspaces/waive-bill/waive-bill-form.workspace.tsx +1 -1
  18. package/src/billable-services/billable-exemptions/billable-exemptions-viewer.component.tsx +12 -9
  19. package/src/billable-services/billable-service.resource.tsx +1 -1
  20. package/src/billable-services/billables/commodity/commodity-form.workspace.tsx +1 -1
  21. package/src/billable-services/billables/services/service-form.workspace.tsx +1 -1
  22. package/src/billable-services/billiable-item/drug-order/drug-order.component.tsx +0 -1
  23. package/src/billable-services/billiable-item/test-order/test-order-action.component.tsx +56 -13
  24. package/src/billable-services/billiable-item/test-order/test-order-action.resource.tsx +14 -3
  25. package/src/billable-services/billiable-item/test-order/test-order-action.test.tsx +51 -2
  26. package/src/billable-services/billiable-item/useBillableItem.tsx +16 -1
  27. package/src/billing-form/visit-attributes/visit-attributes-form.component.tsx +9 -3
  28. package/src/billing.resource.ts +1 -0
  29. package/src/claims/claims-management/main/claims-pre-auth-main.component.tsx +1 -1
  30. package/src/claims/claims-management/table/claims-list-table.component.tsx +1 -1
  31. package/src/claims/dashboard/form/claims-form.component.tsx +16 -15
  32. package/src/index.ts +3 -1
  33. package/src/invoice/invoice.component.tsx +1 -1
  34. package/src/invoice/payments/payment-form/payment-form.component.tsx +3 -5
  35. package/src/routes.json +4 -0
  36. package/translations/en.json +3 -0
  37. package/dist/475.js.map +0 -1
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","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":"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-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"}],"version":"5.4.1-pre.1801"}
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","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":"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-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.1821"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-billing-app",
3
- "version": "5.4.1-pre.1801",
3
+ "version": "5.4.1-pre.1821",
4
4
  "description": "Billing app for KenyaEMR",
5
5
  "browser": "dist/kenyaemr-esm-billing-app.js",
6
6
  "main": "src/index.ts",
@@ -1,9 +1,9 @@
1
+ import React from 'react';
1
2
  import { InlineLoading, InlineNotification, MultiSelect } from '@carbon/react';
2
- import React, { useEffect, useState } from 'react';
3
3
  import { Controller, useFormContext } from 'react-hook-form';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { z } from 'zod';
6
- import { InterventionsFilter, toQueryParams, useInterventions } from '../../hooks/useInterventions';
6
+ import { InterventionsFilter, useInterventions } from '../../hooks/useInterventions';
7
7
  import { eligibilityRequestShema } from '../benefits-package.resources';
8
8
  import { usePatient } from '@openmrs/esm-framework';
9
9
 
@@ -14,25 +14,16 @@ type PackageInterventionsProps = {
14
14
  patientUuid: string;
15
15
  };
16
16
  const PackageInterventions: React.FC<PackageInterventionsProps> = ({ category, patientUuid }) => {
17
- const [filters, setFilters] = useState<InterventionsFilter>({ package_code: category });
18
17
  const { error: patientError, isLoading: isPatientLoading, patient } = usePatient(patientUuid);
18
+ const filters: InterventionsFilter = {
19
+ package_code: category,
20
+ applicable_gender: patient?.gender === 'male' ? 'MALE' : 'FEMALE',
21
+ };
19
22
  const { error, interventions, isLoading } = useInterventions(filters);
20
23
 
21
24
  const form = useFormContext<EligibilityRequest>();
22
25
  const { t } = useTranslation();
23
26
 
24
- useEffect(() => {
25
- setFilters((state) => ({
26
- ...state,
27
- package_code: category,
28
- applicable_gender: patient?.gender === 'male' ? 'MALE' : patient?.gender === 'female' ? 'FEMALE' : undefined,
29
- }));
30
- }, [category, patient]);
31
-
32
- useEffect(() => {
33
- form.setValue('interventions', []);
34
- }, [category]);
35
-
36
27
  if (isLoading || isPatientLoading) {
37
28
  return (
38
29
  <InlineLoading
@@ -56,6 +47,10 @@ const PackageInterventions: React.FC<PackageInterventionsProps> = ({ category, p
56
47
  );
57
48
  }
58
49
 
50
+ if (interventions.length === 0) {
51
+ return null;
52
+ }
53
+
59
54
  return (
60
55
  <Controller
61
56
  control={form.control}
@@ -71,7 +66,7 @@ const PackageInterventions: React.FC<PackageInterventionsProps> = ({ category, p
71
66
  field.onChange(e.selectedItems);
72
67
  }}
73
68
  initialSelectedItems={field.value}
74
- label="Choose option"
69
+ label={t('chooseInterventions', 'Choose interventions')}
75
70
  items={interventions.map((r) => r.interventionCode)}
76
71
  itemToString={(item) => interventions.find((r) => r.interventionCode === item)?.interventionName ?? ''}
77
72
  />
@@ -0,0 +1,208 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ Button,
5
+ ModalBody,
6
+ ModalFooter,
7
+ ModalHeader,
8
+ Column,
9
+ Dropdown,
10
+ Stack,
11
+ Table,
12
+ TableBody,
13
+ TableCell,
14
+ TableHead,
15
+ TableHeader,
16
+ TableRow,
17
+ } from '@carbon/react';
18
+ import { z } from 'zod';
19
+ import { Controller, useForm } from 'react-hook-form';
20
+ import { zodResolver } from '@hookform/resolvers/zod';
21
+ import { useBillableServices } from '../../billable-service.resource';
22
+ import { showSnackbar, useConfig } from '@openmrs/esm-framework';
23
+ import { BillingConfig } from '../../../config-schema';
24
+ import { billingFormSchema, processBillItems } from '../../../billing.resource';
25
+ import styles from './create-bill-item-modal.scss';
26
+ import { useStockItemQuantity } from '../../billiable-item/useBillableItem';
27
+
28
+ interface CreateBillItemModalProps {
29
+ closeModal: () => void;
30
+ medicationRequestBundle: {
31
+ request: fhir.MedicationRequest;
32
+ };
33
+ }
34
+
35
+ type FormType = z.infer<typeof billingFormSchema>;
36
+
37
+ const CreateBillItemModal: React.FC<CreateBillItemModalProps> = ({ closeModal, medicationRequestBundle }) => {
38
+ const { t } = useTranslation();
39
+ const { billableServices, error, isLoading } = useBillableServices();
40
+ const { cashPointUuid, cashierUuid } = useConfig<BillingConfig>();
41
+
42
+ const patientUuid = medicationRequestBundle?.request?.subject?.reference?.split('/')[1];
43
+ const orderUuid = medicationRequestBundle?.request?.id;
44
+ const quantityToDispense = medicationRequestBundle?.request?.dispenseRequest?.quantity?.value;
45
+ const drugUuid = medicationRequestBundle?.request?.medicationReference?.reference?.split('/')[1];
46
+ const { stockItemUuid } = useStockItemQuantity(drugUuid);
47
+
48
+ const billableItem =
49
+ billableServices?.filter((service) => {
50
+ const stockItem = service?.stockItem.split(':')[0];
51
+ return stockItem === stockItemUuid;
52
+ }) || [];
53
+ const defaultPaymentStatus = 'PENDING';
54
+
55
+ const form = useForm<FormType>({
56
+ mode: 'onChange',
57
+ resolver: zodResolver(billingFormSchema),
58
+ defaultValues: {
59
+ cashPoint: cashPointUuid,
60
+ cashier: cashierUuid,
61
+ patient: patientUuid,
62
+ status: 'PENDING',
63
+ lineItems: billableItem.map((service) => ({
64
+ billableService: service.uuid,
65
+ lineItemOrder: 0,
66
+ quantity: quantityToDispense || 1,
67
+ price: 0,
68
+ paymentStatus: defaultPaymentStatus,
69
+ priceUuid: '',
70
+ priceName: '',
71
+ order: orderUuid,
72
+ })),
73
+ payments: [],
74
+ },
75
+ });
76
+
77
+ const handleSubmit = async (data: FormType) => {
78
+ const validatedLineItems = data.lineItems.map((item, index) => ({
79
+ ...item,
80
+ billableService: billableItem[index]?.uuid,
81
+ order: orderUuid,
82
+ paymentStatus: defaultPaymentStatus,
83
+ }));
84
+
85
+ const validatedData = {
86
+ ...data,
87
+ lineItems: validatedLineItems,
88
+ };
89
+
90
+ try {
91
+ await processBillItems(validatedData);
92
+ showSnackbar({
93
+ title: t('billItems', 'Save Bill'),
94
+ subtitle: 'Bill processing has been successful',
95
+ kind: 'success',
96
+ timeoutInMs: 3000,
97
+ });
98
+ // TODO: mutate the bill
99
+ closeModal();
100
+ } catch (error) {
101
+ console.error('Bill processing error:', error);
102
+ showSnackbar({
103
+ title: 'Bill processing error',
104
+ kind: 'error',
105
+ subtitle: error instanceof Error ? error.message : 'Unknown error occurred',
106
+ });
107
+ }
108
+ };
109
+
110
+ const calculateTotal = (index: number) => {
111
+ const price = form.watch(`lineItems.${index}.price`) || 0;
112
+ const quantity = form.watch(`lineItems.${index}.quantity`) || 1;
113
+ return price * quantity;
114
+ };
115
+
116
+ return (
117
+ <div>
118
+ <form onSubmit={form.handleSubmit(handleSubmit)}>
119
+ <ModalHeader closeModal={closeModal} title={t('billing', 'Billing')} />
120
+ <ModalBody>
121
+ <Stack gap={4} className={styles.grid}>
122
+ <Column className={styles.billingItem}>
123
+ <Table aria-label="billing items table">
124
+ <TableHead>
125
+ <TableRow>
126
+ <TableHeader>Item</TableHeader>
127
+ <TableHeader>Quantity</TableHeader>
128
+ <TableHeader>PaymentMethod</TableHeader>
129
+ <TableHeader>Price</TableHeader>
130
+ <TableHeader>Total</TableHeader>
131
+ </TableRow>
132
+ </TableHead>
133
+ <TableBody>
134
+ {billableItem.map((service, index) => (
135
+ <TableRow key={service.uuid}>
136
+ <TableCell>{service?.name || 'Service Not Found'}</TableCell>
137
+ <TableCell>
138
+ <Controller
139
+ control={form.control}
140
+ name={`lineItems.${index}.quantity`}
141
+ defaultValue={quantityToDispense}
142
+ render={({ field }) => (
143
+ <input
144
+ {...field}
145
+ type="number"
146
+ className="form-control"
147
+ min={1}
148
+ max={quantityToDispense}
149
+ onChange={(e) => {
150
+ const value = parseInt(e.target.value, 10);
151
+ field.onChange(value);
152
+ }}
153
+ />
154
+ )}
155
+ />
156
+ </TableCell>
157
+ <TableCell>
158
+ <Controller
159
+ control={form.control}
160
+ name={`lineItems.${index}.priceUuid`}
161
+ render={({ field }) => (
162
+ <Dropdown
163
+ id={`priceUuid-${index}`}
164
+ invalid={!!form.formState.errors.lineItems?.[index]?.priceUuid}
165
+ invalidText={form.formState.errors.lineItems?.[index]?.priceUuid?.message}
166
+ onChange={(e) => {
167
+ const selectedPrice = service?.servicePrices.find((p) => p.uuid === e.selectedItem);
168
+ if (selectedPrice) {
169
+ field.onChange(e.selectedItem);
170
+ form.setValue(`lineItems.${index}.price`, selectedPrice.price);
171
+ form.setValue(`lineItems.${index}.priceName`, selectedPrice.name);
172
+ form.setValue(`lineItems.${index}.billableService`, service.uuid);
173
+ form.setValue(`lineItems.${index}.order`, orderUuid);
174
+ form.setValue(`lineItems.${index}.paymentStatus`, defaultPaymentStatus);
175
+ }
176
+ }}
177
+ selectedItem={field.value}
178
+ label="Choose method"
179
+ items={service?.servicePrices.map((r) => r.uuid) ?? []}
180
+ itemToString={(item) => service?.servicePrices.find((r) => r.uuid === item)?.name ?? ''}
181
+ direction="top"
182
+ />
183
+ )}
184
+ />
185
+ </TableCell>
186
+ <TableCell>{form.watch(`lineItems.${index}.price`) ?? ' '}</TableCell>
187
+ <TableCell>{calculateTotal(index)}</TableCell>
188
+ </TableRow>
189
+ ))}
190
+ </TableBody>
191
+ </Table>
192
+ </Column>
193
+ </Stack>
194
+ </ModalBody>
195
+ <ModalFooter>
196
+ <Button className={styles.button} kind="secondary" onClick={closeModal}>
197
+ {t('discard', 'Discard')}
198
+ </Button>
199
+ <Button className={styles.button} kind="primary" type="submit" disabled={form.formState.isSubmitting}>
200
+ {t('saveAndClose', 'Save & Close')}
201
+ </Button>
202
+ </ModalFooter>
203
+ </form>
204
+ </div>
205
+ );
206
+ };
207
+
208
+ export default CreateBillItemModal;
@@ -0,0 +1,26 @@
1
+ @use '@carbon/type';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/colors';
4
+
5
+ .heading {
6
+ @include type.type-style('heading-compact-01');
7
+ margin: layout.$spacing-05 0 layout.$spacing-05;
8
+ }
9
+
10
+ .grid {
11
+ margin: layout.$spacing-05 layout.$spacing-05;
12
+ padding-top: layout.$spacing-05;
13
+ }
14
+
15
+ .billingItem {
16
+ margin-top: layout.$spacing-10;
17
+ overflow-x: auto;
18
+ }
19
+
20
+ .button {
21
+ height: layout.$spacing-10;
22
+ display: flex;
23
+ align-content: flex-start;
24
+ align-items: baseline;
25
+ min-width: 50%;
26
+ }
@@ -71,7 +71,7 @@ export const RefundBillModal: React.FC<RefundBillModalProps> = ({ onClose, bill,
71
71
  } finally {
72
72
  setIsLoading(false);
73
73
  }
74
- }, [bill, lineItem, onClose, t]);
74
+ }, [bill, lineItem, onClose, t, billableServiceUuid, itemUuid]);
75
75
 
76
76
  return (
77
77
  <>
@@ -60,7 +60,7 @@ export const EditBillForm: React.FC<EditBillFormProps> = ({
60
60
 
61
61
  useEffect(() => {
62
62
  promptBeforeClosing(() => isDirty);
63
- }, [isDirty]);
63
+ }, [isDirty, promptBeforeClosing]);
64
64
 
65
65
  const onSubmit: SubmitHandler<EditBillFormData> = async (formData) => {
66
66
  const updateBill = createEditBillPayload(lineItem, formData, bill, formData.adjustmentReason);
@@ -89,7 +89,7 @@ export const WaiveBillForm: React.FC<BillWaiverFormProps> = ({
89
89
 
90
90
  useEffect(() => {
91
91
  promptBeforeClosing(() => isDirty);
92
- }, [isDirty]);
92
+ }, [isDirty, promptBeforeClosing]);
93
93
 
94
94
  if (lineItems?.length === 0) {
95
95
  return null;
@@ -62,17 +62,20 @@ export const BillableExemptionsViewer = () => {
62
62
  setInvalidJsonErrorMessage(t('invalidJsonError', 'Invalid JSON input.'));
63
63
  }
64
64
  },
65
- [resetErrorMessage],
65
+ [resetErrorMessage, t],
66
66
  );
67
67
 
68
- const updateSchema = useCallback((updatedSchema: Schema) => {
69
- try {
70
- setSchema(updatedSchema);
71
- setStringifiedSchema(JSON.stringify(updatedSchema, null, 2));
72
- } catch (error) {
73
- setInvalidJsonErrorMessage(t('saveError', 'Failed to save schema.'));
74
- }
75
- }, []);
68
+ const updateSchema = useCallback(
69
+ (updatedSchema: Schema) => {
70
+ try {
71
+ setSchema(updatedSchema);
72
+ setStringifiedSchema(JSON.stringify(updatedSchema, null, 2));
73
+ } catch (error) {
74
+ setInvalidJsonErrorMessage(t('saveError', 'Failed to save schema.'));
75
+ }
76
+ },
77
+ [t],
78
+ );
76
79
 
77
80
  useEffect(() => {
78
81
  if (billableExceptionSchema) {
@@ -7,7 +7,7 @@ type ResponseObject = {
7
7
  };
8
8
 
9
9
  export const useBillableServices = () => {
10
- const url = `/ws/rest/v1/cashier/billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,paymentMode,price),concept:(uuid,display))`;
10
+ const url = `/ws/rest/v1/cashier/billableService?v=custom:(uuid,name,shortName,stockItem:(uuid, display),serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,paymentMode,price),concept:(uuid,display))`;
11
11
  const { data, isLoading, isValidating, error, mutate } = useSWR<{ data: ResponseObject }>(url, openmrsFetch, {});
12
12
  return { billableServices: data?.data.results ?? [], isLoading, isValidating, error, mutate };
13
13
  };
@@ -52,7 +52,7 @@ const CommodityForm: React.FC<CommodityFormProps> = ({
52
52
  if (initialValues) {
53
53
  trigger();
54
54
  }
55
- }, [initialValues]);
55
+ }, [initialValues, trigger]);
56
56
 
57
57
  const onSubmit = async (formValues: BillableFormSchema) => {
58
58
  const payload = formatBillableServicePayloadForSubmission(formValues, initialValues?.['uuid']);
@@ -66,7 +66,7 @@ const AddServiceForm: React.FC<AddServiceFormProps> = ({
66
66
  setConceptToLookup(initialValues.concept?.concept?.display);
67
67
  trigger();
68
68
  }
69
- }, [initialValues]);
69
+ }, [initialValues, trigger]);
70
70
 
71
71
  const {
72
72
  fields: servicePriceFields,
@@ -28,7 +28,6 @@ const DrugOrder: React.FC<DrugOrderProps> = ({ order }) => {
28
28
  if (isLoading || isLoadingInventory) {
29
29
  return null;
30
30
  }
31
-
32
31
  return (
33
32
  <div className={styles.drugOrderContainer}>
34
33
  {stockItem && stockItem.length > 0 ? (
@@ -1,12 +1,14 @@
1
1
  import { InlineLoading, Button } from '@carbon/react';
2
- import { Order } from '@openmrs/esm-patient-common-lib';
3
2
  import React, { useCallback } from 'react';
4
3
  import { useTranslation } from 'react-i18next';
5
- import { useTestOrderBillStatus } from './test-order-action.resource';
6
- import { launchWorkspace, showModal } from '@openmrs/esm-framework';
7
4
  import { mutate } from 'swr';
8
- import styles from './test-order-action.scss';
5
+ import { useOrderBill, useTestOrderBillStatus } from './test-order-action.resource';
6
+ import { launchWorkspace, showModal } from '@openmrs/esm-framework';
7
+ import { Order } from '@openmrs/esm-patient-common-lib';
9
8
  import { createMedicationDispenseProps } from './dispense.resource';
9
+ import { useStockItemQuantity } from '../useBillableItem';
10
+ import { useBillableServices } from '../../billable-service.resource';
11
+ import styles from './test-order-action.scss';
10
12
 
11
13
  type TestOrderProps = {
12
14
  order?: Order;
@@ -32,9 +34,16 @@ const TestOrderAction: React.FC<TestOrderProps> = (props) => {
32
34
  const dispenseFormProps = isDispenseOrder ? createMedicationDispenseProps(props) : null;
33
35
  const orderUuid = order?.uuid ?? medicationRequestBundle?.request?.id;
34
36
  const patientUuid = order?.patient?.uuid ?? medicationRequestBundle?.request?.subject?.reference?.split('/')[1];
35
-
36
37
  const { isLoading, hasPendingPayment } = useTestOrderBillStatus(orderUuid, patientUuid);
37
-
38
+ const drugUuid = medicationRequestBundle?.request?.medicationReference?.reference?.split('/')[1];
39
+ const { billableServices } = useBillableServices();
40
+ const { stockItemQuantity, stockItemUuid } = useStockItemQuantity(drugUuid);
41
+ const billableItem =
42
+ billableServices?.filter((service) => {
43
+ const stockItem = service?.stockItem.split(':')[0];
44
+ return stockItem === stockItemUuid;
45
+ }) || [];
46
+ const { itemHasBill } = useOrderBill(patientUuid, orderUuid);
38
47
  // Handle modal close and revalidation
39
48
  const handleModalClose = useCallback(() => {
40
49
  mutate((key) => typeof key === 'string' && key.startsWith(additionalProps?.mutateUrl as string), undefined, {
@@ -43,6 +52,17 @@ const TestOrderAction: React.FC<TestOrderProps> = (props) => {
43
52
  }, [additionalProps?.mutateUrl]);
44
53
 
45
54
  const launchModal = useCallback(() => {
55
+ if (stockItemQuantity > 0 && itemHasBill.length < 1 && billableItem.length > 0) {
56
+ const disposeBill = showModal(modalName ?? 'create-bill-item-modal', {
57
+ closeModal: () => {
58
+ handleModalClose();
59
+ disposeBill();
60
+ },
61
+ medicationRequestBundle,
62
+ });
63
+ return;
64
+ }
65
+
46
66
  if (isDispenseOrder) {
47
67
  launchWorkspace('dispense-workspace', dispenseFormProps);
48
68
  return;
@@ -56,7 +76,18 @@ const TestOrderAction: React.FC<TestOrderProps> = (props) => {
56
76
  order,
57
77
  ...(additionalProps && { additionalProps }),
58
78
  });
59
- }, [isDispenseOrder, modalName, order, additionalProps, dispenseFormProps, handleModalClose]);
79
+ }, [
80
+ isDispenseOrder,
81
+ modalName,
82
+ order,
83
+ additionalProps,
84
+ dispenseFormProps,
85
+ handleModalClose,
86
+ medicationRequestBundle,
87
+ stockItemQuantity,
88
+ billableItem.length,
89
+ itemHasBill.length,
90
+ ]);
60
91
 
61
92
  if (isLoading) {
62
93
  return (
@@ -70,18 +101,30 @@ const TestOrderAction: React.FC<TestOrderProps> = (props) => {
70
101
  return null;
71
102
  }
72
103
 
73
- const buttonText = hasPendingPayment
74
- ? t('unsettledBill', 'Unsettled bill')
75
- : isDispenseOrder
76
- ? actionText ?? t('dispense', 'Dispense')
77
- : actionText ?? t('pickLabRequest', 'Pick Lab Request');
104
+ const buttonText = (() => {
105
+ if (hasPendingPayment) {
106
+ return t('unsettledBill', 'Unsettled bill');
107
+ }
108
+
109
+ if (stockItemQuantity < 1) {
110
+ return t('outOfStock', 'Out of Stock');
111
+ }
112
+
113
+ if (stockItemQuantity > 0 && itemHasBill.length === 0 && billableItem.length > 0) {
114
+ return t('bill', 'Bill');
115
+ }
116
+
117
+ return isDispenseOrder
118
+ ? actionText ?? t('dispense', 'Dispense')
119
+ : actionText ?? t('pickLabRequest', 'Pick Lab Request');
120
+ })();
78
121
 
79
122
  return (
80
123
  <Button
81
124
  kind="primary"
82
125
  className={!isDispenseOrder ? styles.actionButton : ''}
83
126
  size={!isDispenseOrder ? 'md' : ''}
84
- disabled={hasPendingPayment}
127
+ disabled={hasPendingPayment || stockItemQuantity < 1}
85
128
  onClick={launchModal}>
86
129
  {buttonText}
87
130
  </Button>
@@ -9,7 +9,7 @@ export const useTestOrderBillStatus = (orderUuid: string, patientUuid: string) =
9
9
  const config = useConfig<BillingConfig>();
10
10
  const { currentVisit } = useVisit(patientUuid);
11
11
  const { isEmergencyPatient, isLoading: isLoadingQueue } = usePatientQueue(patientUuid);
12
- const { isLoading: isLoadingBill, hasPendingPayment } = useOrderPendingPaymentStatus(patientUuid, orderUuid);
12
+ const { isLoading, hasPendingPayment } = useOrderPendingPaymentStatus(patientUuid, orderUuid);
13
13
 
14
14
  // We want to check if the payment method is in the excluded list this includes insurances, where patient do not need to pay immediately
15
15
  const isExcludedPaymentMethod = checkPaymentMethodExclusion(
@@ -18,7 +18,7 @@ export const useTestOrderBillStatus = (orderUuid: string, patientUuid: string) =
18
18
  );
19
19
 
20
20
  return useMemo(() => {
21
- if (isLoadingBill || isLoadingQueue) {
21
+ if (isLoading || isLoadingQueue) {
22
22
  return { hasPendingPayment: false, isLoading: true };
23
23
  }
24
24
 
@@ -33,7 +33,7 @@ export const useTestOrderBillStatus = (orderUuid: string, patientUuid: string) =
33
33
  return { hasPendingPayment, isLoading: false };
34
34
  }, [
35
35
  isLoadingQueue,
36
- isLoadingBill,
36
+ isLoading,
37
37
  currentVisit?.visitType?.uuid,
38
38
  config?.inPatientVisitTypeUuid,
39
39
  isExcludedPaymentMethod,
@@ -74,3 +74,14 @@ export const useOrderPendingPaymentStatus = (patientUuid: string, orderUuid: str
74
74
 
75
75
  return useMemo(() => ({ hasPendingPayment, isLoading, error }), [hasPendingPayment, isLoading, error]);
76
76
  };
77
+
78
+ export const useOrderBill = (patientUuid: string, orderUuid: string) => {
79
+ const { patientBills, isLoading, error } = usePatientBills(patientUuid);
80
+ const itemHasBill = useMemo(() => {
81
+ return patientBills
82
+ ?.map((bill) => bill.lineItems)
83
+ .flat()
84
+ .filter((lineItem) => lineItem.order && lineItem.order.uuid === orderUuid);
85
+ }, [patientBills, orderUuid]);
86
+ return { itemHasBill };
87
+ };