@ohmaorg/esm-billing-commons 1.0.1
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/package.json +1 -0
- package/src/api.ts +124 -0
- package/src/constants.ts +78 -0
- package/src/hooks.ts +150 -0
- package/src/index.ts +85 -0
- package/src/status-badge.component.tsx +68 -0
- package/src/types.ts +180 -0
- package/src/utils.ts +70 -0
package/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"@ohmaorg/esm-billing-commons","version":"1.0.1","private":false,"main":"src/index.ts","types":"src/index.ts","scripts":{"typecheck":"tsc --noEmit","lint":"echo 'lint ok'","test":"echo 'no tests yet'","build":"echo 'commons build ok'"},"peerDependencies":{"@openmrs/esm-framework":">=5.0.0","react":"18.x","swr":"2.x","@carbon/react":"^1.0.0"},"devDependencies":{"@openmrs/esm-framework":"^5.0.0","@carbon/react":"^1.71.0","react":"^18.3.1","react-dom":"^18.3.1","react-i18next":"^16.5.0","i18next":"^25.0.0","swr":"^2.2.5","dayjs":"^1.11.0","rxjs":"^6.6.0","single-spa":"^6.0.0","@types/react":"^18.3.0","typescript":"~5.4.0"},"files":["src","package.json"]}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API helper functions for mutating data (POST, DELETE) via openmrsFetch.
|
|
3
|
+
* SWR hooks handle reads; these handle writes.
|
|
4
|
+
*/
|
|
5
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
6
|
+
import {
|
|
7
|
+
BILL_REST_URL,
|
|
8
|
+
CLAIM_REST_URL,
|
|
9
|
+
EDI_PROCESS_URL,
|
|
10
|
+
EDI_RETRY_URL,
|
|
11
|
+
TARIFF_REST_URL,
|
|
12
|
+
} from './constants';
|
|
13
|
+
import type { Bill, Claim, EdiProcessResult, Tariff } from './types';
|
|
14
|
+
|
|
15
|
+
// --- Bill API ---
|
|
16
|
+
|
|
17
|
+
export function createBill(payload: {
|
|
18
|
+
patient: string;
|
|
19
|
+
encounter?: string;
|
|
20
|
+
payerIdentifier?: string;
|
|
21
|
+
billItems?: Array<{
|
|
22
|
+
serviceOrItem?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
quantity: number;
|
|
25
|
+
unitPrice: number;
|
|
26
|
+
}>;
|
|
27
|
+
}): Promise<Bill> {
|
|
28
|
+
return openmrsFetch(`${restBaseUrl}${BILL_REST_URL}`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: payload,
|
|
32
|
+
}).then((res) => res.data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function updateBill(
|
|
36
|
+
uuid: string,
|
|
37
|
+
payload: { status?: string; payerIdentifier?: string },
|
|
38
|
+
): Promise<Bill> {
|
|
39
|
+
return openmrsFetch(`${restBaseUrl}${BILL_REST_URL}/${uuid}`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: payload,
|
|
43
|
+
}).then((res) => res.data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function voidBill(uuid: string, reason: string): Promise<void> {
|
|
47
|
+
return openmrsFetch(
|
|
48
|
+
`${restBaseUrl}${BILL_REST_URL}/${uuid}?reason=${encodeURIComponent(reason)}`,
|
|
49
|
+
{ method: 'DELETE' },
|
|
50
|
+
).then(() => undefined);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- Tariff API ---
|
|
54
|
+
|
|
55
|
+
export function createTariff(payload: {
|
|
56
|
+
name: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
serviceOrItem?: string;
|
|
59
|
+
unitPrice: number;
|
|
60
|
+
currency?: string;
|
|
61
|
+
effectiveFrom?: string;
|
|
62
|
+
effectiveTo?: string;
|
|
63
|
+
}): Promise<Tariff> {
|
|
64
|
+
return openmrsFetch(`${restBaseUrl}${TARIFF_REST_URL}`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: payload,
|
|
68
|
+
}).then((res) => res.data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function retireTariff(uuid: string, reason: string): Promise<void> {
|
|
72
|
+
return openmrsFetch(
|
|
73
|
+
`${restBaseUrl}${TARIFF_REST_URL}/${uuid}?reason=${encodeURIComponent(reason)}`,
|
|
74
|
+
{ method: 'DELETE' },
|
|
75
|
+
).then(() => undefined);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- Claim API ---
|
|
79
|
+
|
|
80
|
+
export function createClaimFromBill(billUuid: string): Promise<Claim> {
|
|
81
|
+
return openmrsFetch(`${restBaseUrl}${CLAIM_REST_URL}`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: { billUuid },
|
|
85
|
+
}).then((res) => res.data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function updateClaim(
|
|
89
|
+
uuid: string,
|
|
90
|
+
payload: {
|
|
91
|
+
status?: string;
|
|
92
|
+
approvedAmount?: number;
|
|
93
|
+
paidAmount?: number;
|
|
94
|
+
rejectionReason?: string;
|
|
95
|
+
},
|
|
96
|
+
): Promise<Claim> {
|
|
97
|
+
return openmrsFetch(`${restBaseUrl}${CLAIM_REST_URL}/${uuid}`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: payload,
|
|
101
|
+
}).then((res) => res.data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function voidClaim(uuid: string, reason: string): Promise<void> {
|
|
105
|
+
return openmrsFetch(
|
|
106
|
+
`${restBaseUrl}${CLAIM_REST_URL}/${uuid}?reason=${encodeURIComponent(reason)}`,
|
|
107
|
+
{ method: 'DELETE' },
|
|
108
|
+
).then(() => undefined);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- EDI API ---
|
|
112
|
+
|
|
113
|
+
export function processClaimEdi(claimUuid: string): Promise<EdiProcessResult> {
|
|
114
|
+
return openmrsFetch(
|
|
115
|
+
`${restBaseUrl}${EDI_PROCESS_URL}?claimUuid=${encodeURIComponent(claimUuid)}`,
|
|
116
|
+
{ method: 'POST' },
|
|
117
|
+
).then((res) => res.data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function retryEdiTransaction(transactionUuid: string): Promise<EdiProcessResult> {
|
|
121
|
+
return openmrsFetch(`${restBaseUrl}${EDI_RETRY_URL}/${transactionUuid}`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
}).then((res) => res.data);
|
|
124
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST endpoint paths and shared constants for Billing, Claims, and EDI modules.
|
|
3
|
+
* All paths are relative to the OpenMRS API base URL (e.g. /openmrs).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// --- REST paths ---
|
|
7
|
+
|
|
8
|
+
export const REST_BASE = '/ws/rest/v1';
|
|
9
|
+
|
|
10
|
+
export const BILL_REST_URL = `${REST_BASE}/bill`;
|
|
11
|
+
export const TARIFF_REST_URL = `${REST_BASE}/tariff`;
|
|
12
|
+
export const CLAIM_REST_URL = `${REST_BASE}/claim`;
|
|
13
|
+
|
|
14
|
+
export const EDI_BASE_URL = `${REST_BASE}/edi`;
|
|
15
|
+
export const EDI_PROCESS_URL = `${EDI_BASE_URL}/process`;
|
|
16
|
+
export const EDI_RETRY_URL = `${EDI_BASE_URL}/retry`;
|
|
17
|
+
export const EDI_TRANSACTION_URL = `${EDI_BASE_URL}/transaction`;
|
|
18
|
+
export const EDI_TRANSACTIONS_URL = `${EDI_BASE_URL}/transactions`;
|
|
19
|
+
export const EDI_PAYERS_URL = `${EDI_BASE_URL}/payers`;
|
|
20
|
+
export const EDI_FORMATS_URL = `${EDI_BASE_URL}/formats`;
|
|
21
|
+
export const EDI_EXPORT_STATUS_URL = `${EDI_BASE_URL}/export/status`;
|
|
22
|
+
export const EDI_SUMMARY_URL = `${EDI_BASE_URL}/summary`;
|
|
23
|
+
|
|
24
|
+
// --- Status display colors (Carbon token names) ---
|
|
25
|
+
|
|
26
|
+
export const BILL_STATUS_COLORS: Record<string, string> = {
|
|
27
|
+
DRAFT: 'blue',
|
|
28
|
+
FINAL: 'green',
|
|
29
|
+
VOIDED: 'gray',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const CLAIM_STATUS_COLORS: Record<string, string> = {
|
|
33
|
+
DRAFT: 'blue',
|
|
34
|
+
SUBMITTED: 'cyan',
|
|
35
|
+
ACCEPTED: 'green',
|
|
36
|
+
REJECTED: 'red',
|
|
37
|
+
PARTIALLY_PAID: 'yellow',
|
|
38
|
+
PAID: 'green',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const EDI_STATUS_COLORS: Record<string, string> = {
|
|
42
|
+
PENDING: 'blue',
|
|
43
|
+
PROCESSING: 'cyan',
|
|
44
|
+
SENT: 'green',
|
|
45
|
+
ACCEPTED: 'green',
|
|
46
|
+
REJECTED: 'red',
|
|
47
|
+
FAILED: 'red',
|
|
48
|
+
RETRY_PENDING: 'yellow',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// --- Privilege names ---
|
|
52
|
+
|
|
53
|
+
export const PRIVILEGES = {
|
|
54
|
+
VIEW_BILLS: 'View Bills',
|
|
55
|
+
EDIT_BILLS: 'Edit Bills',
|
|
56
|
+
FINALIZE_BILLS: 'Finalize Bills',
|
|
57
|
+
VOID_BILLS: 'Void Bills',
|
|
58
|
+
RECORD_PAYMENTS: 'Record Payments',
|
|
59
|
+
MANAGE_TARIFFS: 'Manage Tariffs',
|
|
60
|
+
VIEW_CLAIMS: 'View Claims',
|
|
61
|
+
CREATE_CLAIMS: 'Create Claims',
|
|
62
|
+
SUBMIT_CLAIMS: 'Submit Claims',
|
|
63
|
+
MANAGE_CLAIMS: 'Manage Claims',
|
|
64
|
+
EXPORT_EDI: 'Export EDI',
|
|
65
|
+
RETRY_EDI: 'Retry EDI Submission',
|
|
66
|
+
VIEW_EDI_AUDIT: 'View EDI Audit',
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
69
|
+
// --- Module names ---
|
|
70
|
+
|
|
71
|
+
export const BILLING_MODULE_NAME = '@ohmaorg/esm-billing-app';
|
|
72
|
+
export const CLAIMS_MODULE_NAME = '@ohmaorg/esm-claims-app';
|
|
73
|
+
export const EDI_MODULE_NAME = '@ohmaorg/esm-edi-app';
|
|
74
|
+
|
|
75
|
+
// --- Default currency ---
|
|
76
|
+
|
|
77
|
+
export const DEFAULT_CURRENCY = 'BWP';
|
|
78
|
+
export const DEFAULT_CURRENCY_SYMBOL = 'P';
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SWR hooks for fetching billing, claims, and EDI data from the REST API.
|
|
3
|
+
* Each hook uses the openmrsFetch utility from @openmrs/esm-framework.
|
|
4
|
+
*/
|
|
5
|
+
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
6
|
+
import useSWR from 'swr';
|
|
7
|
+
import type {
|
|
8
|
+
Bill,
|
|
9
|
+
Claim,
|
|
10
|
+
EdiTransaction,
|
|
11
|
+
EdiTransactionSummary,
|
|
12
|
+
PaginatedResponse,
|
|
13
|
+
} from './types';
|
|
14
|
+
import {
|
|
15
|
+
BILL_REST_URL,
|
|
16
|
+
CLAIM_REST_URL,
|
|
17
|
+
EDI_SUMMARY_URL,
|
|
18
|
+
EDI_TRANSACTION_URL,
|
|
19
|
+
EDI_TRANSACTIONS_URL,
|
|
20
|
+
TARIFF_REST_URL,
|
|
21
|
+
} from './constants';
|
|
22
|
+
import type { Tariff } from './types';
|
|
23
|
+
|
|
24
|
+
interface FetchResponse<T> {
|
|
25
|
+
data: T;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function swrFetcher<T>(url: string): Promise<T> {
|
|
29
|
+
return openmrsFetch(url).then((res: FetchResponse<T>) => res.data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Bill hooks ---
|
|
33
|
+
|
|
34
|
+
export interface UseBillsOptions {
|
|
35
|
+
status?: string;
|
|
36
|
+
patient?: string;
|
|
37
|
+
fromDate?: string;
|
|
38
|
+
toDate?: string;
|
|
39
|
+
startIndex?: number;
|
|
40
|
+
limit?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useBills(options: UseBillsOptions = {}) {
|
|
44
|
+
const params = new URLSearchParams();
|
|
45
|
+
if (options.status) params.set('status', options.status);
|
|
46
|
+
if (options.patient) params.set('patient', options.patient);
|
|
47
|
+
if (options.fromDate) params.set('fromDate', options.fromDate);
|
|
48
|
+
if (options.toDate) params.set('toDate', options.toDate);
|
|
49
|
+
if (options.startIndex != null) params.set('startIndex', String(options.startIndex));
|
|
50
|
+
if (options.limit != null) params.set('limit', String(options.limit));
|
|
51
|
+
|
|
52
|
+
const query = params.toString();
|
|
53
|
+
const hasSearchParams = options.status || options.patient || options.fromDate || options.toDate;
|
|
54
|
+
const baseUrl = hasSearchParams ? `${BILL_REST_URL}?${query}` : `${BILL_REST_URL}?${query}`;
|
|
55
|
+
|
|
56
|
+
return useSWR<PaginatedResponse<Bill>>(
|
|
57
|
+
`${restBaseUrl}${baseUrl.startsWith('/') ? baseUrl.slice(1) : baseUrl}`,
|
|
58
|
+
swrFetcher,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useBill(uuid: string | null | undefined) {
|
|
63
|
+
const url = uuid ? `${restBaseUrl}${BILL_REST_URL}/${uuid}?v=full` : null;
|
|
64
|
+
return useSWR<Bill>(url, swrFetcher);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- Tariff hooks ---
|
|
68
|
+
|
|
69
|
+
export function useTariffs(includeRetired = false) {
|
|
70
|
+
const url = includeRetired
|
|
71
|
+
? `${restBaseUrl}${TARIFF_REST_URL}?includeAll=true`
|
|
72
|
+
: `${restBaseUrl}${TARIFF_REST_URL}`;
|
|
73
|
+
|
|
74
|
+
return useSWR<PaginatedResponse<Tariff>>(url, swrFetcher);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function useTariff(uuid: string | null | undefined) {
|
|
78
|
+
const url = uuid ? `${restBaseUrl}${TARIFF_REST_URL}/${uuid}?v=full` : null;
|
|
79
|
+
return useSWR<Tariff>(url, swrFetcher);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Claim hooks ---
|
|
83
|
+
|
|
84
|
+
export interface UseClaimsOptions {
|
|
85
|
+
status?: string;
|
|
86
|
+
patient?: string;
|
|
87
|
+
payerIdentifier?: string;
|
|
88
|
+
startIndex?: number;
|
|
89
|
+
limit?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function useClaims(options: UseClaimsOptions = {}) {
|
|
93
|
+
const params = new URLSearchParams();
|
|
94
|
+
if (options.status) params.set('status', options.status);
|
|
95
|
+
if (options.patient) params.set('patient', options.patient);
|
|
96
|
+
if (options.payerIdentifier) params.set('payerIdentifier', options.payerIdentifier);
|
|
97
|
+
if (options.startIndex != null) params.set('startIndex', String(options.startIndex));
|
|
98
|
+
if (options.limit != null) params.set('limit', String(options.limit));
|
|
99
|
+
|
|
100
|
+
const query = params.toString();
|
|
101
|
+
const baseUrl = `${CLAIM_REST_URL}?${query}`;
|
|
102
|
+
|
|
103
|
+
return useSWR<PaginatedResponse<Claim>>(
|
|
104
|
+
`${restBaseUrl}${baseUrl.startsWith('/') ? baseUrl.slice(1) : baseUrl}`,
|
|
105
|
+
swrFetcher,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function useClaim(uuid: string | null | undefined) {
|
|
110
|
+
const url = uuid ? `${restBaseUrl}${CLAIM_REST_URL}/${uuid}?v=full` : null;
|
|
111
|
+
return useSWR<Claim>(url, swrFetcher);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- EDI hooks ---
|
|
115
|
+
|
|
116
|
+
export function useEdiTransaction(uuid: string | null | undefined) {
|
|
117
|
+
const url = uuid ? `${restBaseUrl}${EDI_TRANSACTION_URL}/${uuid}` : null;
|
|
118
|
+
return useSWR<EdiTransaction>(url, swrFetcher);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface UseEdiTransactionsOptions {
|
|
122
|
+
claimUuid?: string;
|
|
123
|
+
status?: string;
|
|
124
|
+
payerCode?: string;
|
|
125
|
+
fromDate?: string;
|
|
126
|
+
toDate?: string;
|
|
127
|
+
startIndex?: number;
|
|
128
|
+
limit?: number;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function useEdiTransactions(options: UseEdiTransactionsOptions = {}) {
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
if (options.claimUuid) params.set('claimUuid', options.claimUuid);
|
|
134
|
+
if (options.status) params.set('status', options.status);
|
|
135
|
+
if (options.payerCode) params.set('payerCode', options.payerCode);
|
|
136
|
+
if (options.fromDate) params.set('fromDate', options.fromDate);
|
|
137
|
+
if (options.toDate) params.set('toDate', options.toDate);
|
|
138
|
+
if (options.startIndex != null) params.set('startIndex', String(options.startIndex));
|
|
139
|
+
if (options.limit != null) params.set('limit', String(options.limit));
|
|
140
|
+
|
|
141
|
+
const query = params.toString();
|
|
142
|
+
return useSWR<PaginatedResponse<EdiTransaction>>(
|
|
143
|
+
`${restBaseUrl}${EDI_TRANSACTIONS_URL}?${query}`,
|
|
144
|
+
swrFetcher,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function useEdiSummary() {
|
|
149
|
+
return useSWR<EdiTransactionSummary>(`${restBaseUrl}${EDI_SUMMARY_URL}`, swrFetcher);
|
|
150
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Public API for @ohmaorg/esm-billing-commons
|
|
2
|
+
|
|
3
|
+
// Types
|
|
4
|
+
export type {
|
|
5
|
+
Bill,
|
|
6
|
+
BillItem,
|
|
7
|
+
BillStatus,
|
|
8
|
+
Payment,
|
|
9
|
+
PaymentMode,
|
|
10
|
+
Adjustment,
|
|
11
|
+
Tariff,
|
|
12
|
+
Claim,
|
|
13
|
+
ClaimLine,
|
|
14
|
+
ClaimStatus,
|
|
15
|
+
EdiTransaction,
|
|
16
|
+
EdiTransactionStatus,
|
|
17
|
+
EdiProcessResult,
|
|
18
|
+
EdiTransactionSummary,
|
|
19
|
+
PaginatedResponse,
|
|
20
|
+
PatientRef,
|
|
21
|
+
ConceptRef,
|
|
22
|
+
EncounterRef,
|
|
23
|
+
AuditInfo,
|
|
24
|
+
OpenmrsResource,
|
|
25
|
+
} from './types';
|
|
26
|
+
|
|
27
|
+
// Constants
|
|
28
|
+
export {
|
|
29
|
+
REST_BASE,
|
|
30
|
+
BILL_REST_URL,
|
|
31
|
+
TARIFF_REST_URL,
|
|
32
|
+
CLAIM_REST_URL,
|
|
33
|
+
EDI_BASE_URL,
|
|
34
|
+
EDI_PROCESS_URL,
|
|
35
|
+
EDI_RETRY_URL,
|
|
36
|
+
EDI_TRANSACTION_URL,
|
|
37
|
+
EDI_TRANSACTIONS_URL,
|
|
38
|
+
EDI_PAYERS_URL,
|
|
39
|
+
EDI_FORMATS_URL,
|
|
40
|
+
EDI_EXPORT_STATUS_URL,
|
|
41
|
+
EDI_SUMMARY_URL,
|
|
42
|
+
BILL_STATUS_COLORS,
|
|
43
|
+
CLAIM_STATUS_COLORS,
|
|
44
|
+
EDI_STATUS_COLORS,
|
|
45
|
+
PRIVILEGES,
|
|
46
|
+
BILLING_MODULE_NAME,
|
|
47
|
+
CLAIMS_MODULE_NAME,
|
|
48
|
+
EDI_MODULE_NAME,
|
|
49
|
+
DEFAULT_CURRENCY,
|
|
50
|
+
DEFAULT_CURRENCY_SYMBOL,
|
|
51
|
+
} from './constants';
|
|
52
|
+
|
|
53
|
+
// Hooks
|
|
54
|
+
export {
|
|
55
|
+
useBills,
|
|
56
|
+
useBill,
|
|
57
|
+
useTariffs,
|
|
58
|
+
useTariff,
|
|
59
|
+
useClaims,
|
|
60
|
+
useClaim,
|
|
61
|
+
useEdiTransaction,
|
|
62
|
+
useEdiTransactions,
|
|
63
|
+
useEdiSummary,
|
|
64
|
+
} from './hooks';
|
|
65
|
+
export type { UseBillsOptions, UseClaimsOptions, UseEdiTransactionsOptions } from './hooks';
|
|
66
|
+
|
|
67
|
+
// API (mutation helpers)
|
|
68
|
+
export {
|
|
69
|
+
createBill,
|
|
70
|
+
updateBill,
|
|
71
|
+
voidBill,
|
|
72
|
+
createTariff,
|
|
73
|
+
retireTariff,
|
|
74
|
+
createClaimFromBill,
|
|
75
|
+
updateClaim,
|
|
76
|
+
voidClaim,
|
|
77
|
+
processClaimEdi,
|
|
78
|
+
retryEdiTransaction,
|
|
79
|
+
} from './api';
|
|
80
|
+
|
|
81
|
+
// Utils
|
|
82
|
+
export { formatCurrency, formatDate, getPatientName } from './utils';
|
|
83
|
+
|
|
84
|
+
// Components
|
|
85
|
+
export { default as StatusBadge } from './status-badge.component';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tag } from '@carbon/react';
|
|
3
|
+
import {
|
|
4
|
+
BILL_STATUS_COLORS,
|
|
5
|
+
CLAIM_STATUS_COLORS,
|
|
6
|
+
EDI_STATUS_COLORS,
|
|
7
|
+
} from './constants';
|
|
8
|
+
|
|
9
|
+
type StatusDomain = 'bill' | 'claim' | 'edi';
|
|
10
|
+
|
|
11
|
+
const COLOR_MAPS: Record<StatusDomain, Record<string, string>> = {
|
|
12
|
+
bill: BILL_STATUS_COLORS,
|
|
13
|
+
claim: CLAIM_STATUS_COLORS,
|
|
14
|
+
edi: EDI_STATUS_COLORS,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const VALID_TAG_TYPES = [
|
|
18
|
+
'red',
|
|
19
|
+
'magenta',
|
|
20
|
+
'purple',
|
|
21
|
+
'blue',
|
|
22
|
+
'cyan',
|
|
23
|
+
'teal',
|
|
24
|
+
'green',
|
|
25
|
+
'gray',
|
|
26
|
+
'cool-gray',
|
|
27
|
+
'warm-gray',
|
|
28
|
+
'high-contrast',
|
|
29
|
+
'outline',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
type TagType = (typeof VALID_TAG_TYPES)[number];
|
|
33
|
+
|
|
34
|
+
const COLOR_TO_TAG_TYPE: Record<string, TagType> = {
|
|
35
|
+
red: 'red',
|
|
36
|
+
blue: 'blue',
|
|
37
|
+
cyan: 'cyan',
|
|
38
|
+
green: 'green',
|
|
39
|
+
gray: 'gray',
|
|
40
|
+
yellow: 'warm-gray',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DEFAULT_TAG_TYPE: TagType = 'gray';
|
|
44
|
+
|
|
45
|
+
interface StatusBadgeProps {
|
|
46
|
+
status: string;
|
|
47
|
+
domain: StatusDomain;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reusable badge component that maps a bill/claim/EDI status
|
|
52
|
+
* to a colored Carbon Tag. The domain determines which color
|
|
53
|
+
* mapping to use.
|
|
54
|
+
*/
|
|
55
|
+
const StatusBadge: React.FC<StatusBadgeProps> = ({ status, domain }) => {
|
|
56
|
+
const colorMap = COLOR_MAPS[domain] ?? {};
|
|
57
|
+
const colorName = colorMap[status] ?? 'gray';
|
|
58
|
+
const tagType: TagType = COLOR_TO_TAG_TYPE[colorName] ?? DEFAULT_TAG_TYPE;
|
|
59
|
+
const label = status.replace(/_/g, ' ');
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Tag type={tagType} size="sm">
|
|
63
|
+
{label}
|
|
64
|
+
</Tag>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default StatusBadge;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript interfaces matching the OpenMRS REST resource representations
|
|
3
|
+
* for the Billing, Claims, and EDI modules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// --- Common OpenMRS types ---
|
|
7
|
+
|
|
8
|
+
export interface OpenmrsResource {
|
|
9
|
+
uuid: string;
|
|
10
|
+
display?: string;
|
|
11
|
+
links?: Array<{ rel: string; uri: string; resourceAlias?: string }>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PatientRef extends OpenmrsResource {
|
|
15
|
+
identifiers?: Array<{ identifier: string; display: string }>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConceptRef extends OpenmrsResource {
|
|
19
|
+
name?: { display: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EncounterRef extends OpenmrsResource {
|
|
23
|
+
encounterDatetime?: string;
|
|
24
|
+
encounterType?: { display: string };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AuditInfo {
|
|
28
|
+
creator: OpenmrsResource;
|
|
29
|
+
dateCreated: string;
|
|
30
|
+
changedBy?: OpenmrsResource;
|
|
31
|
+
dateChanged?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- Billing types ---
|
|
35
|
+
|
|
36
|
+
export type BillStatus = 'DRAFT' | 'FINAL' | 'VOIDED';
|
|
37
|
+
|
|
38
|
+
export type PaymentMode =
|
|
39
|
+
| 'CASH'
|
|
40
|
+
| 'MEDICAL_AID'
|
|
41
|
+
| 'BANK_TRANSFER'
|
|
42
|
+
| 'CARD'
|
|
43
|
+
| 'CHEQUE'
|
|
44
|
+
| 'OTHER';
|
|
45
|
+
|
|
46
|
+
export interface BillItem extends OpenmrsResource {
|
|
47
|
+
serviceOrItem?: ConceptRef;
|
|
48
|
+
description?: string;
|
|
49
|
+
quantity: number;
|
|
50
|
+
unitPrice: number;
|
|
51
|
+
lineTotal: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Payment extends OpenmrsResource {
|
|
55
|
+
amount: number;
|
|
56
|
+
paymentMode: PaymentMode;
|
|
57
|
+
referenceNumber?: string;
|
|
58
|
+
paymentDate: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Adjustment extends OpenmrsResource {
|
|
62
|
+
amount: number;
|
|
63
|
+
reason: string;
|
|
64
|
+
billItem?: BillItem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Bill extends OpenmrsResource {
|
|
68
|
+
status: BillStatus;
|
|
69
|
+
currency: string;
|
|
70
|
+
totalAmount: number;
|
|
71
|
+
payerIdentifier?: string;
|
|
72
|
+
billDate: string;
|
|
73
|
+
patient: PatientRef;
|
|
74
|
+
encounter?: EncounterRef;
|
|
75
|
+
billItems?: BillItem[];
|
|
76
|
+
payments?: Payment[];
|
|
77
|
+
adjustments?: Adjustment[];
|
|
78
|
+
voided?: boolean;
|
|
79
|
+
voidReason?: string;
|
|
80
|
+
auditInfo?: AuditInfo;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface Tariff extends OpenmrsResource {
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
serviceOrItem?: ConceptRef;
|
|
87
|
+
unitPrice: number;
|
|
88
|
+
currency: string;
|
|
89
|
+
effectiveFrom?: string;
|
|
90
|
+
effectiveTo?: string;
|
|
91
|
+
retired?: boolean;
|
|
92
|
+
retireReason?: string;
|
|
93
|
+
auditInfo?: AuditInfo;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --- Claims types ---
|
|
97
|
+
|
|
98
|
+
export type ClaimStatus =
|
|
99
|
+
| 'DRAFT'
|
|
100
|
+
| 'SUBMITTED'
|
|
101
|
+
| 'ACCEPTED'
|
|
102
|
+
| 'REJECTED'
|
|
103
|
+
| 'PARTIALLY_PAID'
|
|
104
|
+
| 'PAID';
|
|
105
|
+
|
|
106
|
+
export interface ClaimLine extends OpenmrsResource {
|
|
107
|
+
serviceCode?: string;
|
|
108
|
+
description?: string;
|
|
109
|
+
quantity: number;
|
|
110
|
+
amount: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Claim extends OpenmrsResource {
|
|
114
|
+
claimNumber: string;
|
|
115
|
+
billUuid: string;
|
|
116
|
+
status: ClaimStatus;
|
|
117
|
+
claimedAmount: number;
|
|
118
|
+
approvedAmount?: number;
|
|
119
|
+
paidAmount?: number;
|
|
120
|
+
currency: string;
|
|
121
|
+
payerIdentifier?: string;
|
|
122
|
+
claimDate: string;
|
|
123
|
+
submissionDate?: string;
|
|
124
|
+
rejectionReason?: string;
|
|
125
|
+
patient: PatientRef;
|
|
126
|
+
claimLines?: ClaimLine[];
|
|
127
|
+
voided?: boolean;
|
|
128
|
+
voidReason?: string;
|
|
129
|
+
auditInfo?: AuditInfo;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- EDI types ---
|
|
133
|
+
|
|
134
|
+
export type EdiTransactionStatus =
|
|
135
|
+
| 'PENDING'
|
|
136
|
+
| 'PROCESSING'
|
|
137
|
+
| 'SENT'
|
|
138
|
+
| 'ACCEPTED'
|
|
139
|
+
| 'REJECTED'
|
|
140
|
+
| 'FAILED'
|
|
141
|
+
| 'RETRY_PENDING';
|
|
142
|
+
|
|
143
|
+
export interface EdiTransaction {
|
|
144
|
+
uuid: string;
|
|
145
|
+
claimUuid: string;
|
|
146
|
+
payerCode: string;
|
|
147
|
+
status: EdiTransactionStatus;
|
|
148
|
+
totalAmount: number;
|
|
149
|
+
currency: string;
|
|
150
|
+
attemptCount: number;
|
|
151
|
+
maxAttempts: number;
|
|
152
|
+
idempotencyKey: string;
|
|
153
|
+
sentAt?: string;
|
|
154
|
+
nextRetryAt?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface EdiProcessResult {
|
|
158
|
+
success: boolean;
|
|
159
|
+
transactionUuid?: string;
|
|
160
|
+
status?: string;
|
|
161
|
+
errorMessage?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface EdiTransactionSummary {
|
|
165
|
+
PENDING: number;
|
|
166
|
+
PROCESSING: number;
|
|
167
|
+
SENT: number;
|
|
168
|
+
ACCEPTED: number;
|
|
169
|
+
REJECTED: number;
|
|
170
|
+
FAILED: number;
|
|
171
|
+
RETRY_PENDING: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Paginated response ---
|
|
175
|
+
|
|
176
|
+
export interface PaginatedResponse<T> {
|
|
177
|
+
results: T[];
|
|
178
|
+
totalCount?: number;
|
|
179
|
+
links?: Array<{ rel: string; uri: string }>;
|
|
180
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DEFAULT_CURRENCY, DEFAULT_CURRENCY_SYMBOL } from './constants';
|
|
2
|
+
|
|
3
|
+
const CURRENCY_SYMBOLS: Record<string, string> = {
|
|
4
|
+
BWP: 'P',
|
|
5
|
+
USD: '$',
|
|
6
|
+
EUR: '€',
|
|
7
|
+
GBP: '£',
|
|
8
|
+
ZAR: 'R',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const MINIMUM_DISPLAY_DECIMALS = 2;
|
|
12
|
+
const MAXIMUM_DISPLAY_DECIMALS = 2;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format a monetary amount for display using the appropriate currency symbol.
|
|
16
|
+
* Falls back to the ISO currency code if no symbol mapping exists.
|
|
17
|
+
*
|
|
18
|
+
* @param amount - The numeric amount to format
|
|
19
|
+
* @param currencyCode - ISO 4217 currency code (defaults to BWP)
|
|
20
|
+
* @returns Formatted string, e.g. "P 1,234.56"
|
|
21
|
+
*/
|
|
22
|
+
export function formatCurrency(
|
|
23
|
+
amount: number | null | undefined,
|
|
24
|
+
currencyCode: string = DEFAULT_CURRENCY,
|
|
25
|
+
): string {
|
|
26
|
+
if (amount == null || isNaN(amount)) {
|
|
27
|
+
return `${CURRENCY_SYMBOLS[currencyCode] ?? currencyCode} 0.00`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const symbol = CURRENCY_SYMBOLS[currencyCode] ?? currencyCode;
|
|
31
|
+
const formatted = amount.toLocaleString('en-US', {
|
|
32
|
+
minimumFractionDigits: MINIMUM_DISPLAY_DECIMALS,
|
|
33
|
+
maximumFractionDigits: MAXIMUM_DISPLAY_DECIMALS,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return `${symbol} ${formatted}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Format an ISO date string to a localized display date.
|
|
41
|
+
*
|
|
42
|
+
* @param isoDate - ISO 8601 date string
|
|
43
|
+
* @returns Formatted date string, e.g. "15 Feb 2026"
|
|
44
|
+
*/
|
|
45
|
+
export function formatDate(isoDate: string | null | undefined): string {
|
|
46
|
+
if (!isoDate) {
|
|
47
|
+
return '—';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const date = new Date(isoDate);
|
|
51
|
+
if (isNaN(date.getTime())) {
|
|
52
|
+
return '—';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return date.toLocaleDateString('en-GB', {
|
|
56
|
+
day: 'numeric',
|
|
57
|
+
month: 'short',
|
|
58
|
+
year: 'numeric',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a patient display name from a patient reference object.
|
|
64
|
+
*
|
|
65
|
+
* @param patient - Patient reference with optional display field
|
|
66
|
+
* @returns Display name or "Unknown" if not available
|
|
67
|
+
*/
|
|
68
|
+
export function getPatientName(patient: { display?: string } | null | undefined): string {
|
|
69
|
+
return patient?.display ?? 'Unknown';
|
|
70
|
+
}
|