@digihmis/esm-billing-app 1.0.0

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 (115) hide show
  1. package/README.md +4 -0
  2. package/dist/117.js +1 -0
  3. package/dist/117.js.map +1 -0
  4. package/dist/132.js +1 -0
  5. package/dist/132.js.map +1 -0
  6. package/dist/152.js +1 -0
  7. package/dist/152.js.map +1 -0
  8. package/dist/16.js +1 -0
  9. package/dist/16.js.map +1 -0
  10. package/dist/209.js +1 -0
  11. package/dist/209.js.map +1 -0
  12. package/dist/317.js +1 -0
  13. package/dist/317.js.map +1 -0
  14. package/dist/349.js +1 -0
  15. package/dist/349.js.map +1 -0
  16. package/dist/358.js +1 -0
  17. package/dist/358.js.map +1 -0
  18. package/dist/371.js +1 -0
  19. package/dist/371.js.map +1 -0
  20. package/dist/378.js +1 -0
  21. package/dist/378.js.map +1 -0
  22. package/dist/442.js +1 -0
  23. package/dist/442.js.map +1 -0
  24. package/dist/45.js +1 -0
  25. package/dist/45.js.map +1 -0
  26. package/dist/466.js +1 -0
  27. package/dist/466.js.map +1 -0
  28. package/dist/496.js +12 -0
  29. package/dist/496.js.map +1 -0
  30. package/dist/508.js +1 -0
  31. package/dist/508.js.map +1 -0
  32. package/dist/61.js +1 -0
  33. package/dist/61.js.map +1 -0
  34. package/dist/640.js +1 -0
  35. package/dist/640.js.map +1 -0
  36. package/dist/689.js +1 -0
  37. package/dist/689.js.map +1 -0
  38. package/dist/709.js +1 -0
  39. package/dist/709.js.map +1 -0
  40. package/dist/712.js +1 -0
  41. package/dist/712.js.map +1 -0
  42. package/dist/720.js +1 -0
  43. package/dist/720.js.map +1 -0
  44. package/dist/742.js +1 -0
  45. package/dist/742.js.map +1 -0
  46. package/dist/771.js +1 -0
  47. package/dist/771.js.map +1 -0
  48. package/dist/806.js +1 -0
  49. package/dist/806.js.map +1 -0
  50. package/dist/839.js +1 -0
  51. package/dist/839.js.map +1 -0
  52. package/dist/912.js +1 -0
  53. package/dist/912.js.map +1 -0
  54. package/dist/913.js +1 -0
  55. package/dist/913.js.map +1 -0
  56. package/dist/940.js +1 -0
  57. package/dist/940.js.map +1 -0
  58. package/dist/965.js +15 -0
  59. package/dist/965.js.map +1 -0
  60. package/dist/digihmis-esm-billing-app.js +6 -0
  61. package/dist/digihmis-esm-billing-app.js.buildmanifest.json +827 -0
  62. package/dist/digihmis-esm-billing-app.js.map +1 -0
  63. package/dist/main.js +17 -0
  64. package/dist/main.js.map +1 -0
  65. package/dist/routes.json +1 -0
  66. package/jest.config.js +26 -0
  67. package/package.json +51 -0
  68. package/rspack.config.js +1 -0
  69. package/src/billing-checkin-form/billing-checkin-form-schema/index.ts +11 -0
  70. package/src/billing-checkin-form/billing-checkin-form.extension.tsx +179 -0
  71. package/src/billing-checkin-form/billing-checkin-form.resource.ts +42 -0
  72. package/src/billing-checkin-form/billing-checkin-form.scss +82 -0
  73. package/src/billing-checkin-form/billing-patient-type-tag/billing-patient-type-tag.component.tsx +39 -0
  74. package/src/billing-checkin-form/billing-patient-type-tag/billing-patient-type-tag.resource.ts +57 -0
  75. package/src/billing-checkin-form/billing-patient-type-tag/billing-patient-type-tag.scss +4 -0
  76. package/src/billing-checkin-form/helper/index.ts +2 -0
  77. package/src/billing-checkin-form/type/index.ts +42 -0
  78. package/src/billing-checkin-form/visit-attributes/visit-attributes-form.component.tsx +244 -0
  79. package/src/billing-checkin-form/visit-attributes/visit-attributes-form.resource.ts +23 -0
  80. package/src/billing-checkin-form/visit-attributes/visit-attributes-form.scss +186 -0
  81. package/src/billing-dashboard/billing-dashboard.component.tsx +224 -0
  82. package/src/billing-dashboard/billing-dashboard.resource.ts +101 -0
  83. package/src/billing-dashboard/billing-dashboard.scss +166 -0
  84. package/src/billing-dashboard/metrics/metrics.resource.ts +142 -0
  85. package/src/billing-dashboard/type/index.ts +176 -0
  86. package/src/billing-header/page-header/page-header.component.tsx +35 -0
  87. package/src/billing-header/page-header/page-header.scss +76 -0
  88. package/src/billing-header/page-header/page-icon/icon-wrapper.extension.tsx +17 -0
  89. package/src/billing-timesheet-modal/billing-timesheet-modal.scss +56 -0
  90. package/src/billing-timesheet-modal/billing-timesheet-modal.tsx +281 -0
  91. package/src/billing-timesheet-modal/billing-timesheet-resource-modal.ts +37 -0
  92. package/src/bills-table/bills-table.component.tsx +323 -0
  93. package/src/bills-table/bills-table.scss +252 -0
  94. package/src/config-schema.ts +148 -0
  95. package/src/constants/index.ts +2 -0
  96. package/src/dashboard.meta.ts +16 -0
  97. package/src/declarations.d.ts +6 -0
  98. package/src/index.ts +37 -0
  99. package/src/leftpanel/left-panel.component.tsx +98 -0
  100. package/src/leftpanel/left-panel.scss +13 -0
  101. package/src/routes.json +58 -0
  102. package/src/setup-tests.ts +1 -0
  103. package/src/utils/index.ts +270 -0
  104. package/translations/am.json +18 -0
  105. package/translations/ar.json +18 -0
  106. package/translations/en.json +18 -0
  107. package/translations/es.json +18 -0
  108. package/translations/fr.json +18 -0
  109. package/translations/he.json +18 -0
  110. package/translations/km.json +18 -0
  111. package/translations/pt.json +18 -0
  112. package/translations/sw.json +18 -0
  113. package/translations/vi.json +18 -0
  114. package/translations/zh.json +18 -0
  115. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"fhir2":">=1.2","webservices.rest":"^2.24.0"},"extensions":[{"name":"billing-check-in-form","slot":"extra-visit-attribute-slot","component":"billingCheckInForm","order":0},{"name":"payment-type-tag","slot":"patient-banner-tags-slot","component":"paymentTypeTag","online":true,"offline":true},{"name":"billing-db-link","slot":"billing-dashboard-slot","component":"billingQueueleftPanelLink","meta":{"name":"billing","slot":"billing-homepage-dashboard-slot","title":"Billing queue"},"order":1,"online":true,"offline":true},{"name":"care-panel-link","slot":"billing-dashboard-slot","component":"carePanelLink","meta":{"name":"billing/patient/:patientUuid/bill","slot":"patient-bill-dashboard-slot","title":"Patient Bill","showOnlyOnRoute":"/bill"},"order":2,"online":true,"offline":true},{"name":"billing-dashboard","slot":"billing-homepage-dashboard-slot","component":"billingDashboard","online":true,"offline":true}],"modals":[{"name":"billing-timesheet-modal","component":"billingTimesheetModal"}],"version":"1.0.0"}
package/jest.config.js ADDED
@@ -0,0 +1,26 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = {
3
+ clearMocks: true,
4
+ transform: {
5
+ '^.+\\.[jt]sx?$': ['@swc/jest'],
6
+ },
7
+ transformIgnorePatterns: ['/node_modules/(?!@openmrs|.+\\.pnp\\.[^\\/]+$)'],
8
+ moduleNameMapper: {
9
+ '\\.(s?css)$': 'identity-obj-proxy',
10
+ '@openmrs/esm-framework': '@openmrs/esm-framework/mock',
11
+ 'lodash-es': 'lodash',
12
+ '^dexie$': require.resolve('dexie'),
13
+ },
14
+ collectCoverageFrom: [
15
+ '**/src/**/*.tsx',
16
+ '!**/node_modules/**',
17
+ '!**/vendor/**',
18
+ '!**/src/**/*.test.*',
19
+ '!**/src/declarations.d.ts',
20
+ ],
21
+ setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
22
+ testEnvironment: 'jsdom',
23
+ testEnvironmentOptions: {
24
+ url: 'http://localhost/',
25
+ },
26
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@digihmis/esm-billing-app",
3
+ "version": "1.0.0",
4
+ "description": "Billing microfrontend for the OpenMRS SPA",
5
+ "browser": "dist/digihmis-esm-billing-app.js",
6
+ "main": "src/index.ts",
7
+ "source": true,
8
+ "license": "MPL-2.0",
9
+ "homepage": "https://github.com/its-kios09/DigiHMIS-apps#readme",
10
+ "scripts": {
11
+ "analyze": "rspack --mode=production --env analyze=true",
12
+ "build": "rspack --mode=production",
13
+ "coverage": "yarn test --coverage",
14
+ "debug": "npm run serve",
15
+ "extract-translations": "i18next 'src/**/*.component.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js",
16
+ "lint": "eslint src --ext ts,tsx",
17
+ "serve": "rspack serve --mode=development",
18
+ "start": "openmrs develop",
19
+ "test": "cross-env TZ=UTC jest --config jest.config.js --verbose false --passWithNoTests",
20
+ "test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js",
21
+ "typescript": "tsc"
22
+ },
23
+ "browserslist": [
24
+ "extends browserslist-config-openmrs"
25
+ ],
26
+ "keywords": [
27
+ "openmrs"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/its-kios09/DigiHMIS-apps.git#readme"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/its-kios09/DigiHMIS-apps/issues"
38
+ },
39
+ "dependencies": {
40
+ "lodash-es": "^4.17.15",
41
+ "react-to-print": "2.14.13"
42
+ },
43
+ "peerDependencies": {
44
+ "@carbon/react": "1.x",
45
+ "@openmrs/esm-framework": "8.x",
46
+ "react": "^18.1.0",
47
+ "react-i18next": "16.x",
48
+ "react-router-dom": "6.x",
49
+ "swr": "2.x"
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ module.exports = require('../../tools/rs-pack-code-spliting');
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+
3
+ export const visitAttributesFormSchema = z.object({
4
+ isPatientExempted: z.string(),
5
+ paymentMethods: z.string().optional(),
6
+ insuranceScheme: z.string().optional(),
7
+ policyNumber: z.string().optional(),
8
+ exemptionCategory: z.string().optional(),
9
+ });
10
+
11
+ export type VisitAttributesFormValue = z.infer<typeof visitAttributesFormSchema>;
@@ -0,0 +1,179 @@
1
+ import { FilterableMultiSelect, InlineLoading, InlineNotification, Tag } from '@carbon/react';
2
+ import { zodResolver } from '@hookform/resolvers/zod';
3
+ import { showSnackbar, useConfig, useFeatureFlag } from '@openmrs/esm-framework';
4
+ import React, { useCallback, useEffect, useState, useMemo } from 'react';
5
+ import { FormProvider, useForm } from 'react-hook-form';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { BillingConfig } from '../config-schema';
8
+ import styles from './billing-checkin-form.scss';
9
+ import VisitAttributesForm from './visit-attributes/visit-attributes-form.component';
10
+ import { createPatientBill, useBillableItems, useCashPoint } from './billing-checkin-form.resource';
11
+ import { visitAttributesFormSchema, VisitAttributesFormValue } from './billing-checkin-form-schema';
12
+ import { hasPatientBeenExempted } from './helper';
13
+ import { EXEMPTED_PAYMENT_STATUS, PENDING_PAYMENT_STATUS } from '../constants';
14
+ import { useCurrencyFormatting } from '../utils';
15
+
16
+ type BillingCheckInFormProps = {
17
+ patientUuid: string;
18
+ setExtraVisitInfo: (state) => void;
19
+ };
20
+
21
+ const BillingCheckInForm: React.FC<BillingCheckInFormProps> = ({ patientUuid, setExtraVisitInfo }) => {
22
+ const { t } = useTranslation();
23
+ const {
24
+ visitAttributeTypes: { isPatientExempted },
25
+ } = useConfig<BillingConfig>();
26
+ const { cashPoints, isLoading: isLoadingCashPoints, error: cashError } = useCashPoint();
27
+ const { lineItems, isLoading: isLoadingLineItems, error: lineError } = useBillableItems();
28
+ const [attributes, setAttributes] = useState([]);
29
+ const [selectedServices, setSelectedServices] = useState([]);
30
+ const { formatSimple } = useCurrencyFormatting();
31
+
32
+ const formMethods = useForm<VisitAttributesFormValue>({
33
+ mode: 'all',
34
+ defaultValues: {
35
+ isPatientExempted: '',
36
+ paymentMethods: '',
37
+ insuranceScheme: '',
38
+ policyNumber: '',
39
+ exemptionCategory: '',
40
+ },
41
+ resolver: zodResolver(visitAttributesFormSchema),
42
+ });
43
+
44
+ const isPatientExemptedValue = formMethods.watch('isPatientExempted');
45
+ const paymentMethod = formMethods.watch('paymentMethods');
46
+
47
+ const handleCreateBill = useCallback((createBillPayload) => {
48
+ createPatientBill(createBillPayload).then(
49
+ () => {
50
+ showSnackbar({ title: 'Patient Bill', subtitle: 'Patient has been billed successfully', kind: 'success' });
51
+ },
52
+ (error) => {
53
+ const errorMessage = JSON.stringify(error?.responseBody?.error?.message?.replace(/\[/g, '').replace(/\]/g, ''));
54
+ showSnackbar({
55
+ title: 'Patient Bill Error',
56
+ subtitle: `An error has occurred while creating patient bill, Contact system administrator quoting this error ${errorMessage}`,
57
+ kind: 'error',
58
+ isLowContrast: true,
59
+ });
60
+ },
61
+ );
62
+ }, []);
63
+
64
+ const handleBillingService = (selectedItems) => {
65
+ setSelectedServices(selectedItems);
66
+
67
+ const cashPointUuid = cashPoints?.[0]?.uuid ?? '';
68
+ const billStatus = hasPatientBeenExempted(attributes, isPatientExempted)
69
+ ? EXEMPTED_PAYMENT_STATUS
70
+ : PENDING_PAYMENT_STATUS;
71
+
72
+ const lineItems = selectedItems.map((item, index) => {
73
+ const priceForPaymentMode =
74
+ item.servicePrices.find((p) => p.paymentMode?.uuid === paymentMethod) || item?.servicePrices[0];
75
+ return {
76
+ billableService: item?.uuid ?? '',
77
+ quantity: 1,
78
+ price: priceForPaymentMode ? priceForPaymentMode.price : '0.000',
79
+ priceName: 'Default',
80
+ priceUuid: priceForPaymentMode ? priceForPaymentMode.uuid : '',
81
+ lineItemOrder: index,
82
+ paymentStatus: billStatus,
83
+ };
84
+ });
85
+
86
+ const billPayload = {
87
+ lineItems: lineItems,
88
+ cashPoint: cashPointUuid,
89
+ patient: patientUuid,
90
+ status: billStatus,
91
+ payments: [],
92
+ };
93
+
94
+ setExtraVisitInfo({
95
+ handleCreateExtraVisitInfo: () => handleCreateBill(billPayload),
96
+ attributes,
97
+ });
98
+ };
99
+
100
+ useEffect(() => {
101
+ setExtraVisitInfo({
102
+ handleCreateExtraVisitInfo: () => {},
103
+ attributes,
104
+ });
105
+ }, [attributes, setExtraVisitInfo]);
106
+
107
+ if (isLoadingLineItems || isLoadingCashPoints) {
108
+ return (
109
+ <InlineLoading
110
+ status="active"
111
+ iconDescription={t('loading', 'Loading')}
112
+ description={t('loadingBillingServices', 'Loading billing services...')}
113
+ />
114
+ );
115
+ }
116
+
117
+ if (cashError || lineError) {
118
+ return (
119
+ <InlineNotification
120
+ kind="error"
121
+ lowContrast
122
+ title={t('billErrorService', 'Bill service error')}
123
+ subtitle={t('errorLoadingBillServices', 'Error loading bill services')}
124
+ />
125
+ );
126
+ }
127
+
128
+ return (
129
+ <FormProvider {...formMethods}>
130
+ <VisitAttributesForm setAttributes={setAttributes} />
131
+ {paymentMethod && (
132
+ <section className={styles.sectionContainer}>
133
+ <div className={styles.sectionField}>
134
+ {lineItems && lineItems.length > 0 ? (
135
+ <>
136
+ {selectedServices.map((item, index) => {
137
+ const priceForPaymentMode =
138
+ item.servicePrices.find((p) => p.paymentMode?.uuid === paymentMethod) || item?.servicePrices[0];
139
+ const price = priceForPaymentMode ? parseFloat(priceForPaymentMode.price) : 0;
140
+ return (
141
+ <Tag key={index} type="cyan" className={styles.selectedServicesTag}>
142
+ {item.name} - {formatSimple(price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
143
+ </Tag>
144
+ );
145
+ })}
146
+ <FilterableMultiSelect
147
+ key={isPatientExemptedValue}
148
+ id="billing-service"
149
+ titleText={t('searchServices', 'Search services')}
150
+ items={lineItems}
151
+ itemToString={(item) => {
152
+ if (!item) {
153
+ return '';
154
+ }
155
+ const priceForPaymentMode =
156
+ item.servicePrices.find((p) => p.paymentMode?.uuid === paymentMethod) || item?.servicePrices[0];
157
+ const price = priceForPaymentMode ? parseFloat(priceForPaymentMode.price) : 0;
158
+ return `${item.name} - ${formatSimple(price, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
159
+ }}
160
+ onChange={({ selectedItems }) => handleBillingService(selectedItems)}
161
+ disabled={isPatientExemptedValue === ''}
162
+ />
163
+ </>
164
+ ) : (
165
+ <InlineNotification
166
+ kind="info"
167
+ lowContrast
168
+ title={t('noBillableServices', 'No billable services')}
169
+ subtitle={t('noBillableServicesDescription', 'Please configure billable services to continue.')}
170
+ />
171
+ )}
172
+ </div>
173
+ </section>
174
+ )}
175
+ </FormProvider>
176
+ );
177
+ };
178
+
179
+ export default React.memo(BillingCheckInForm);
@@ -0,0 +1,42 @@
1
+ import { openmrsFetch, type OpenmrsResource, restBaseUrl, useConfig } from '@openmrs/esm-framework';
2
+ import { useMemo, useState } from 'react';
3
+ import useSWR from 'swr';
4
+ import { BillingConfig } from '../config-schema';
5
+
6
+ export const useCashPoint = () => {
7
+ const url = `${restBaseUrl}/cashier/cashPoint`;
8
+ const { data, isLoading, error } = useSWR<{ data: { results: Array<OpenmrsResource> } }>(url, openmrsFetch);
9
+
10
+ return { isLoading, error, cashPoints: data?.data?.results ?? [] };
11
+ };
12
+ export const createPatientBill = (payload) => {
13
+ const postUrl = `${restBaseUrl}/cashier/bill`;
14
+ return openmrsFetch(postUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: payload });
15
+ };
16
+
17
+ export const useBillableItems = () => {
18
+ const { registrationServiceType } = useConfig<BillingConfig>();
19
+ const url = `${restBaseUrl}/cashier/billableService?v=custom:(uuid,name,shortName,serviceStatus,serviceType:(uuid,display),servicePrices:(uuid,name,price,paymentMode))`;
20
+ const { data, isLoading, error } = useSWR<{ data: { results: Array<OpenmrsResource> } }>(url, openmrsFetch);
21
+ const [searchTerm, setSearchTerm] = useState('');
22
+
23
+ const filteredItems = useMemo(() => {
24
+ if (!data?.data?.results) {
25
+ return [];
26
+ }
27
+
28
+ const lowerSearchTerm = searchTerm.toLowerCase();
29
+
30
+ return data.data.results.filter(
31
+ (item) => item.serviceType?.uuid === registrationServiceType && item.name.toLowerCase().includes(lowerSearchTerm),
32
+ );
33
+ }, [data?.data?.results, searchTerm, registrationServiceType]);
34
+
35
+ return {
36
+ lineItems: filteredItems,
37
+ isLoading,
38
+ error,
39
+ searchTerm,
40
+ setSearchTerm,
41
+ };
42
+ };
@@ -0,0 +1,82 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@carbon/colors';
4
+
5
+ .sectionContainer {
6
+ flex-direction: row;
7
+ margin-bottom: layout.$spacing-05;
8
+
9
+ :global(.omrs-breakpoint-lt-tablet) & {
10
+ flex-direction: column;
11
+ margin-bottom: layout.$spacing-06;
12
+ }
13
+
14
+ :global(.omrs-breakpoint-lt-desktop) & {
15
+ flex-direction: column;
16
+ margin-bottom: layout.$spacing-05;
17
+ }
18
+ }
19
+
20
+ .sectionTitle {
21
+ @include type.type-style('heading-compact-02');
22
+ color: colors.$gray-70;
23
+ margin: 0 0 layout.$spacing-03 0;
24
+ flex-basis: 30%;
25
+ flex-shrink: 0;
26
+
27
+ :global(.omrs-breakpoint-lt-tablet) & {
28
+ flex-basis: 100%;
29
+ margin-bottom: layout.$spacing-05;
30
+ @include type.type-style('heading-compact-01');
31
+ }
32
+
33
+ :global(.omrs-breakpoint-lt-desktop) & {
34
+ flex-basis: 100%;
35
+ margin-bottom: layout.$spacing-04;
36
+ }
37
+ }
38
+
39
+ .sectionField {
40
+ flex-basis: 70%;
41
+ flex-grow: 1;
42
+
43
+ :global(.omrs-breakpoint-lt-tablet) & {
44
+ flex-basis: 100%;
45
+ }
46
+
47
+ :global(.omrs-breakpoint-lt-desktop) & {
48
+ flex-basis: 100%;
49
+ }
50
+ }
51
+
52
+ .billingSummary {
53
+ margin-top: 1rem;
54
+ padding: 0.75rem;
55
+ background-color: #f4f4f4;
56
+ border-radius: 4px;
57
+
58
+ .summaryTitle {
59
+ font-weight: 600;
60
+ margin-bottom: 0.5rem;
61
+ }
62
+
63
+ .serviceItem {
64
+ display: flex;
65
+ justify-content: space-between;
66
+ padding: 0.5rem 0;
67
+ border-bottom: 1px solid #e0e0e0;
68
+
69
+ &:last-child {
70
+ border-bottom: none;
71
+ }
72
+
73
+ .serviceName {
74
+ color: #161616;
75
+ }
76
+
77
+ .servicePrice {
78
+ font-weight: 500;
79
+ color: #161616;
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Tag, SkeletonText } from '@carbon/react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Currency } from '@carbon/react/icons';
5
+ import styles from './billing-patient-type-tag.scss';
6
+ import { usePatientPaymentType } from './billing-patient-type-tag.resource';
7
+
8
+ interface PaymentTypeTagProps {
9
+ patientUuid: string;
10
+ }
11
+
12
+ const PaymentTypeTag: React.FC<PaymentTypeTagProps> = ({ patientUuid }) => {
13
+ const { t } = useTranslation();
14
+ const { paymentType, isInsurance, insuranceScheme, policyNumber } = usePatientPaymentType(patientUuid);
15
+
16
+ if (!paymentType) {
17
+ return null;
18
+ }
19
+
20
+ if (isInsurance) {
21
+ return (
22
+ <div className={styles.paymentTypeContainer}>
23
+ <Tag type="blue" size="md" renderIcon={Currency} title={policyNumber ? `Policy: ${policyNumber}` : ''}>
24
+ {insuranceScheme || t('insurance', 'Insurance')}
25
+ </Tag>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ return (
31
+ <div className={styles.paymentTypeContainer}>
32
+ <Tag type="green" size="md" renderIcon={Currency}>
33
+ {t('cash', 'Cash')}
34
+ </Tag>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export default PaymentTypeTag;
@@ -0,0 +1,57 @@
1
+ import { useMemo } from 'react';
2
+ import { useConfig, useVisit } from '@openmrs/esm-framework';
3
+ import { BillingConfig } from '../../config-schema';
4
+
5
+ export const usePatientPaymentType = (patientUuid: string) => {
6
+ const { activeVisit } = useVisit(patientUuid);
7
+ const { visitAttributeTypes, insurancePaymentMethod } = useConfig<BillingConfig>();
8
+
9
+ const paymentInfo = useMemo(() => {
10
+ if (!activeVisit?.attributes) {
11
+ return {
12
+ paymentType: null,
13
+ isInsurance: false,
14
+ isCash: false,
15
+ insuranceScheme: null,
16
+ policyNumber: null,
17
+ };
18
+ }
19
+
20
+ const paymentMethodAttr = activeVisit?.attributes.find(
21
+ (attr) => attr?.attributeType?.uuid === visitAttributeTypes?.paymentMethods,
22
+ );
23
+
24
+ const insuranceSchemeAttr = activeVisit?.attributes.find(
25
+ (attr) => attr?.attributeType?.uuid === visitAttributeTypes?.insuranceScheme,
26
+ );
27
+
28
+ const policyNumberAttr = activeVisit?.attributes.find(
29
+ (attr) => attr?.attributeType?.uuid === visitAttributeTypes?.policyNumber,
30
+ );
31
+
32
+ if (!paymentMethodAttr) {
33
+ return {
34
+ paymentType: null,
35
+ isInsurance: false,
36
+ isCash: false,
37
+ insuranceScheme: null,
38
+ policyNumber: null,
39
+ };
40
+ }
41
+
42
+ const paymentMethodValue =
43
+ typeof paymentMethodAttr?.value === 'object' ? paymentMethodAttr?.value?.uuid : paymentMethodAttr?.value;
44
+
45
+ const isInsurance = paymentMethodValue === insurancePaymentMethod;
46
+
47
+ return {
48
+ paymentType: isInsurance ? 'insurance' : 'cash',
49
+ isInsurance,
50
+ isCash: !isInsurance,
51
+ insuranceScheme: insuranceSchemeAttr?.value || null,
52
+ policyNumber: policyNumberAttr?.value || null,
53
+ };
54
+ }, [activeVisit, visitAttributeTypes, insurancePaymentMethod]);
55
+
56
+ return paymentInfo;
57
+ };
@@ -0,0 +1,4 @@
1
+ @use '@carbon/layout';
2
+ .paymentTag {
3
+ margin-left: layout.$spacing-07;
4
+ }
@@ -0,0 +1,2 @@
1
+ export const hasPatientBeenExempted = (attributes: Array<any>, isPatientExempted: string): boolean =>
2
+ attributes.find(({ attributeType }) => attributeType === isPatientExempted)?.value === true;
@@ -0,0 +1,42 @@
1
+ export interface PaymentMethod {
2
+ uuid: string;
3
+ name: string;
4
+ description: string;
5
+ retired: boolean;
6
+ retireReason: null;
7
+ auditInfo: AuditInfo;
8
+ attributeTypes: Array<AttributeType>;
9
+ sortOrder: null;
10
+ resourceVersion: string;
11
+ }
12
+
13
+ interface AttributeType {
14
+ uuid?: string;
15
+ name: string;
16
+ description: string;
17
+ retired: boolean;
18
+ attributeOrder?: number;
19
+ format?: string;
20
+ foreignKey?: string | null;
21
+ regExp?: string | null;
22
+ required: boolean;
23
+ value?: string;
24
+ }
25
+
26
+ export interface AuditInfo {
27
+ creator: Creator;
28
+ dateCreated: string;
29
+ changedBy: null;
30
+ dateChanged: null;
31
+ }
32
+ export interface Link {
33
+ rel: string;
34
+ uri: string;
35
+ resourceAlias: string;
36
+ }
37
+
38
+ export interface Creator {
39
+ uuid: string;
40
+ display: string;
41
+ links: Link[];
42
+ }