Skip to content

Commit 6afd52e

Browse files
committed
on payment screen, add fee schedule information
1 parent 22dca79 commit 6afd52e

File tree

6 files changed

+156
-14
lines changed

6 files changed

+156
-14
lines changed

apps/ehr/src/components/PatientPaymentsList.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
Box,
44
Button,
55
capitalize,
6+
CircularProgress,
7+
Container,
68
FormControlLabel,
79
Paper,
810
Radio,
@@ -22,13 +24,16 @@ import { DocumentReference, Encounter, Patient } from 'fhir/r4b';
2224
import { DateTime } from 'luxon';
2325
import { enqueueSnackbar } from 'notistack';
2426
import { FC, Fragment, ReactElement, useEffect, useState } from 'react';
27+
import { useOystehrAPIClient } from 'src/features/visits/shared/hooks/useOystehrAPIClient';
2528
import { useApiClients } from 'src/hooks/useAppClients';
2629
import { useGetEncounter } from 'src/hooks/useEncounter';
30+
import { useGetPatientAccount, useGetPatientCoverages } from 'src/hooks/useGetPatient';
2731
import { useGetPatientPaymentsList } from 'src/hooks/useGetPatientPaymentsList';
2832
import {
2933
APIError,
3034
APIErrorCode,
3135
CashOrCardPayment,
36+
FHIR_EXTENSION,
3237
getPaymentVariantFromEncounter,
3338
isApiError,
3439
PatientPaymentDTO,
@@ -68,6 +73,7 @@ export default function PatientPaymentList({
6873
responsibleParty,
6974
}: PaymentListProps): ReactElement {
7075
const { oystehr, oystehrZambda } = useApiClients();
76+
const apiClient = useOystehrAPIClient();
7177
const theme = useTheme();
7278
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
7379
const [sendReceiptByEmailDialogOpen, setSendReceiptByEmailDialogOpen] = useState(false);
@@ -88,6 +94,16 @@ export default function PatientPaymentList({
8894
encounterId,
8995
disabled: !encounterId || !patient?.id,
9096
});
97+
const { data: insuranceData } = useGetPatientAccount({
98+
apiClient,
99+
patientId: patient?.id ?? null,
100+
});
101+
102+
const { data: insuranceCoverages } = useGetPatientCoverages({
103+
apiClient,
104+
patientId: patient?.id ?? null,
105+
});
106+
91107
const payments = paymentData?.payments ?? []; // Replace with actual payments when available
92108

93109
const stripeCustomerDeletedError =
@@ -233,6 +249,26 @@ export default function PatientPaymentList({
233249
return null;
234250
})();
235251

252+
const insurance = insuranceCoverages?.coverages?.primary?.identifier?.find(
253+
(temp) => temp.type?.coding?.find((temp) => temp.code === 'MB')
254+
)?.assigner;
255+
const insuranceOrganization = insuranceCoverages?.insuranceOrgs?.find(
256+
(organization) => organization.id === insurance?.reference?.replace('Organization/', '')
257+
);
258+
const insuranceName = insuranceOrganization?.name;
259+
const insuranceNotes = insuranceOrganization?.extension?.find(
260+
(extensionTemp) => extensionTemp.url === FHIR_EXTENSION.InsurancePlan.notes.url
261+
)?.valueString;
262+
263+
const copayAmount = insuranceData?.coverageChecks?.[0]?.copay?.find(
264+
(copay) =>
265+
copay.code === 'UC' && copay.coverageCode === 'B' && copay.levelCode === 'IND' && copay.periodCode === '27'
266+
)?.amountInUSD;
267+
const remainingDeductibleAmount = insuranceData?.coverageChecks?.[0]?.deductible?.find(
268+
(copay) =>
269+
copay.code === '30' && copay.coverageCode === 'C' && copay.levelCode === 'IND' && copay.periodCode === '29'
270+
)?.amountInUSD;
271+
236272
return (
237273
<Paper
238274
sx={{
@@ -269,6 +305,64 @@ export default function PatientPaymentList({
269305
label="Self-pay"
270306
/>
271307
</RadioGroup>
308+
<Container
309+
style={{
310+
backgroundColor: theme.palette.background.default,
311+
borderRadius: 4,
312+
paddingTop: 10,
313+
paddingBottom: 10,
314+
}}
315+
>
316+
<Typography variant="h5" sx={{ color: theme.palette.primary.dark }}>
317+
Payment Considerations
318+
</Typography>
319+
{insuranceData ? (
320+
<>
321+
<Table style={{ tableLayout: 'fixed' }}>
322+
<TableBody>
323+
<TableRow>
324+
<TableCell style={{ fontSize: '16px' }}>Insurance Carrier</TableCell>
325+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
326+
{insuranceName ? insuranceName : 'Unknown'}
327+
</TableCell>
328+
</TableRow>
329+
<TableRow>
330+
<TableCell style={{ fontSize: '16px' }}>Copay</TableCell>
331+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
332+
{copayAmount ? `$${copayAmount}` : 'Unknown'}
333+
</TableCell>
334+
</TableRow>
335+
<TableRow sx={{ '&:last-child td': { borderBottom: 'none' } }}>
336+
<TableCell style={{ fontSize: '16px' }}>Remaining Deductible</TableCell>
337+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
338+
{remainingDeductibleAmount ? `$${remainingDeductibleAmount}` : 'Unknown'}
339+
</TableCell>
340+
</TableRow>
341+
</TableBody>
342+
</Table>
343+
{insuranceNotes && (
344+
<Container
345+
style={{
346+
backgroundColor: '#2169F514',
347+
borderRadius: 4,
348+
marginTop: 5,
349+
paddingTop: 10,
350+
paddingBottom: 10,
351+
}}
352+
>
353+
<Typography variant="body1" sx={{ color: theme.palette.primary.dark, fontWeight: 'bold' }}>
354+
Notes
355+
</Typography>
356+
<Typography variant="body1" style={{ whiteSpace: 'pre' }}>
357+
{insuranceNotes}
358+
</Typography>
359+
</Container>
360+
)}
361+
</>
362+
) : (
363+
<CircularProgress />
364+
)}
365+
</Container>
272366
{stripeCustomerDeletedError && <StripeErrorAlert />}
273367
{!stripeCustomerDeletedError && (
274368
<>

apps/ehr/src/features/visits/telemed/components/telemed-admin/EditInsurance.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export type InsuranceData = InsuranceSettingsBooleans & {
5252
active: Organization['active'];
5353
identifier?: Identifier[];
5454
address?: Address[];
55+
notes?: string;
5556
};
5657

5758
type InsuranceForm = Omit<InsuranceData, 'id' | 'active'>;
@@ -106,6 +107,7 @@ export default function EditInsurance(): JSX.Element {
106107
reset({
107108
payor: insuranceDetails,
108109
displayName: alias || insuranceDetails.name,
110+
notes: insuranceDetails.extension?.find((ext) => ext.url === FHIR_EXTENSION.InsurancePlan.notes.url)?.valueString,
109111
// TODO: uncomment when insurance settings will be applied to patient paperwork step with filling insurance data
110112
// ...settingsMap,
111113
});
@@ -129,6 +131,7 @@ export default function EditInsurance(): JSX.Element {
129131
const submitSnackbarText = isNew
130132
? `${data.displayName} was successfully created`
131133
: `${data.displayName} was updated successfully`;
134+
132135
try {
133136
const mutateResp = await mutateInsurance(data);
134137
enqueueSnackbar(`${submitSnackbarText}`, {
@@ -262,6 +265,23 @@ export default function EditInsurance(): JSX.Element {
262265
)}
263266
/>
264267

268+
<Controller
269+
name="notes"
270+
control={control}
271+
render={({ field: { onChange, value } }) => (
272+
<TextField
273+
id="notes"
274+
label="Notes"
275+
value={value || ''}
276+
onChange={onChange}
277+
sx={{ marginTop: 1, marginBottom: 1, width: '100%' }}
278+
multiline
279+
minRows={2}
280+
margin="dense"
281+
/>
282+
)}
283+
/>
284+
265285
{/* TODO: uncomment when insurance settings will be applied to patient paperwork step with filling insurance data */}
266286
{/* <Controller
267287
name={ENABLE_ELIGIBILITY_CHECK_KEY}

apps/ehr/src/features/visits/telemed/components/telemed-admin/telemed-admin.queries.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,30 @@ export const useInsuranceMutation = (
151151
identifier: insurancePlan?.identifier || data?.identifier,
152152
address: insurancePlan?.address || data?.address,
153153
};
154+
155+
if (data.notes) {
156+
const noteExt = {
157+
url: FHIR_EXTENSION.InsurancePlan.notes.url,
158+
valueString: data.notes,
159+
};
160+
161+
const existingExtIndex = resourceExtensions.findIndex(
162+
(ext) => ext.url === FHIR_EXTENSION.InsurancePlan.notes.url
163+
);
164+
if (existingExtIndex >= 0) {
165+
resourceExtensions[existingExtIndex] = noteExt;
166+
} else {
167+
resourceExtensions.push(noteExt);
168+
}
169+
} else {
170+
const existingExtIndex = resourceExtensions.findIndex(
171+
(ext) => ext.url === FHIR_EXTENSION.InsurancePlan.notes.url
172+
);
173+
if (existingExtIndex >= 0) {
174+
resourceExtensions.splice(existingExtIndex, 1);
175+
}
176+
}
177+
154178
// TODO: uncomment when insurance settings will be applied to patient paperwork step with filling insurance data
155179
// if (!requirementSettingsExistingExtensions) {
156180
// resourceExtensions?.push({

packages/utils/lib/fhir/billing.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,15 @@ export const parseCoverageEligibilityResponse = (
121121
(e) => e.url === 'https://extensions.fhir.oystehr.com/raw-response'
122122
)?.valueString;
123123
let copay: PatientPaymentBenefit[] | undefined;
124+
let deductible: PatientPaymentBenefit[] | undefined;
124125
if (fullBenefitJSON) {
125126
try {
126127
// cSpell:disable-next eligibility
127128
const benefitList = JSON.parse(fullBenefitJSON)?.elig?.benefit;
128-
copay = parseObjectsToCopayBenefits(benefitList);
129+
copay = parseObjectsToCopayBenefits(benefitList).filter(
130+
(benefit) => benefit.coverageCode === 'A' || benefit.coverageCode === 'B'
131+
);
132+
deductible = parseObjectsToCopayBenefits(benefitList).filter((benefit) => benefit.coverageCode === 'C');
129133
} catch (error) {
130134
console.error('Error parsing fullBenefitJSON', error);
131135
}
@@ -134,6 +138,7 @@ export const parseCoverageEligibilityResponse = (
134138
status: InsuranceEligibilityCheckStatus.eligibilityConfirmed,
135139
dateISO,
136140
copay,
141+
deductible,
137142
errors: coverageResponse.error,
138143
};
139144
} else {
@@ -154,17 +159,9 @@ export const parseCoverageEligibilityResponse = (
154159
};
155160

156161
export const parseObjectsToCopayBenefits = (input: any[]): PatientPaymentBenefit[] => {
157-
const filteredInputs = input.filter((item) => {
158-
return (
159-
item &&
160-
typeof item === 'object' &&
161-
(item['benefit_coverage_code'] === 'B' || item['benefit_coverage_code'] === 'A')
162-
);
163-
});
164-
165-
return filteredInputs
162+
return input
166163
.map((item) => {
167-
const benefitCoverageCode = item['benefit_coverage_code'] as 'A' | 'B';
164+
const benefitCoverageCode = item['benefit_coverage_code'] as 'A' | 'B' | 'C';
168165
const CP: PatientPaymentBenefit = {
169166
amountInUSD: item['benefit_amount'] ?? 0,
170167
percentage: item['benefit_percent'] ?? 0,

packages/utils/lib/fhir/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ export const FHIR_EXTENSION = {
123123
insuranceRequirements: {
124124
url: `${PUBLIC_EXTENSION_BASE_URL}/insurance-requirements`,
125125
},
126+
notes: {
127+
url: `${PRIVATE_EXTENSION_BASE_URL}/notes`,
128+
},
126129
},
127130
QuestionnaireResponse: {
128131
ipAddress: {

packages/utils/lib/types/data/telemed/eligibility.types.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,17 +188,21 @@ export interface CoverageBenefitInfo {
188188

189189
inNetwork: boolean;
190190
}
191+
export interface CoinsuranceBenefit extends CoverageBenefitInfo {
192+
coverageCode: 'A';
193+
}
191194
export interface CopayBenefit extends CoverageBenefitInfo {
192195
coverageCode: 'B';
193196
}
194-
export interface CoinsuranceBenefit extends CoverageBenefitInfo {
195-
coverageCode: 'A';
197+
export interface DeductibleBenefit extends CoverageBenefitInfo {
198+
coverageCode: 'C';
196199
}
197-
export type PatientPaymentBenefit = CopayBenefit | CoinsuranceBenefit;
200+
export type PatientPaymentBenefit = CopayBenefit | CoinsuranceBenefit | DeductibleBenefit;
198201
export interface InsuranceCheckStatusWithDate {
199202
status: InsuranceEligibilityCheckStatus;
200203
dateISO: string;
201204
copay?: PatientPaymentBenefit[];
205+
deductible?: PatientPaymentBenefit[];
202206
errors?: { code: CodeableConcept }[];
203207
}
204208

0 commit comments

Comments
 (0)