Skip to content

Commit ef1f387

Browse files
authored
Insurance otr 1373 (#5255)
* on payment screen, add fee schedule information * update comments --------- Signed-off-by: Jonathan Saewitz <48358905+saewitz@users.noreply.github.com>
1 parent ee93530 commit ef1f387

File tree

6 files changed

+185
-15
lines changed

6 files changed

+185
-15
lines changed

apps/ehr/src/components/PatientPaymentsList.tsx

Lines changed: 124 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,15 +24,19 @@ 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,
39+
PatientPaymentBenefit,
3440
PatientPaymentDTO,
3541
PaymentVariant,
3642
PostPatientPaymentInput,
@@ -68,6 +74,7 @@ export default function PatientPaymentList({
6874
responsibleParty,
6975
}: PaymentListProps): ReactElement {
7076
const { oystehr, oystehrZambda } = useApiClients();
77+
const apiClient = useOystehrAPIClient();
7178
const theme = useTheme();
7279
const [paymentDialogOpen, setPaymentDialogOpen] = useState(false);
7380
const [sendReceiptByEmailDialogOpen, setSendReceiptByEmailDialogOpen] = useState(false);
@@ -88,6 +95,38 @@ export default function PatientPaymentList({
8895
encounterId,
8996
disabled: !encounterId || !patient?.id,
9097
});
98+
const { data: insuranceData } = useGetPatientAccount({
99+
apiClient,
100+
patientId: patient?.id ?? null,
101+
});
102+
103+
const { data: insuranceCoverages } = useGetPatientCoverages({
104+
apiClient,
105+
patientId: patient?.id ?? null,
106+
});
107+
108+
function getPaymentAmountFromPatientBenefit({
109+
coverage,
110+
code,
111+
coverageCode,
112+
levelCode,
113+
periodCode,
114+
}: {
115+
coverage: PatientPaymentBenefit[];
116+
code: string;
117+
coverageCode: string;
118+
levelCode: string;
119+
periodCode: string;
120+
}): number | undefined {
121+
return coverage?.find(
122+
(item) =>
123+
item.code === code &&
124+
item.coverageCode === coverageCode &&
125+
item.levelCode === levelCode &&
126+
item.periodCode === periodCode
127+
)?.amountInUSD;
128+
}
129+
91130
const payments = paymentData?.payments ?? []; // Replace with actual payments when available
92131

93132
const stripeCustomerDeletedError =
@@ -233,6 +272,33 @@ export default function PatientPaymentList({
233272
return null;
234273
})();
235274

275+
const insurance = insuranceCoverages?.coverages?.primary?.identifier?.find(
276+
(temp) => temp.type?.coding?.find((temp) => temp.code === 'MB')
277+
)?.assigner;
278+
const insuranceOrganization = insuranceCoverages?.insuranceOrgs?.find(
279+
(organization) => organization.id === insurance?.reference?.replace('Organization/', '')
280+
);
281+
const insuranceName = insuranceOrganization?.name;
282+
const insuranceNotes = insuranceOrganization?.extension?.find(
283+
(extensionTemp) => extensionTemp.url === FHIR_EXTENSION.InsurancePlan.notes.url
284+
)?.valueString;
285+
286+
const copayAmount = getPaymentAmountFromPatientBenefit({
287+
coverage: insuranceData?.coverageChecks?.[0]?.copay || [],
288+
code: 'UC',
289+
coverageCode: 'B',
290+
levelCode: 'IND',
291+
periodCode: '27',
292+
});
293+
294+
const remainingDeductibleAmount = getPaymentAmountFromPatientBenefit({
295+
coverage: insuranceData?.coverageChecks?.[0]?.deductible || [],
296+
code: '30',
297+
coverageCode: 'C',
298+
levelCode: 'IND',
299+
periodCode: '29',
300+
});
301+
236302
return (
237303
<Paper
238304
sx={{
@@ -269,6 +335,64 @@ export default function PatientPaymentList({
269335
label="Self-pay"
270336
/>
271337
</RadioGroup>
338+
<Container
339+
style={{
340+
backgroundColor: theme.palette.background.default,
341+
borderRadius: 4,
342+
paddingTop: 10,
343+
paddingBottom: 10,
344+
}}
345+
>
346+
<Typography variant="h5" sx={{ color: theme.palette.primary.dark }}>
347+
Payment Considerations
348+
</Typography>
349+
{insuranceData ? (
350+
<>
351+
<Table style={{ tableLayout: 'fixed' }}>
352+
<TableBody>
353+
<TableRow>
354+
<TableCell style={{ fontSize: '16px' }}>Insurance Carrier</TableCell>
355+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
356+
{insuranceName ? insuranceName : 'Unknown'}
357+
</TableCell>
358+
</TableRow>
359+
<TableRow>
360+
<TableCell style={{ fontSize: '16px' }}>Copay</TableCell>
361+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
362+
{copayAmount ? `$${copayAmount}` : 'Unknown'}
363+
</TableCell>
364+
</TableRow>
365+
<TableRow sx={{ '&:last-child td': { borderBottom: 'none' } }}>
366+
<TableCell style={{ fontSize: '16px' }}>Remaining Deductible</TableCell>
367+
<TableCell style={{ fontSize: '16px', fontWeight: 'bold', textAlign: 'right' }}>
368+
{remainingDeductibleAmount ? `$${remainingDeductibleAmount}` : 'Unknown'}
369+
</TableCell>
370+
</TableRow>
371+
</TableBody>
372+
</Table>
373+
{insuranceNotes && (
374+
<Container
375+
style={{
376+
backgroundColor: '#2169F514',
377+
borderRadius: 4,
378+
marginTop: 5,
379+
paddingTop: 10,
380+
paddingBottom: 10,
381+
}}
382+
>
383+
<Typography variant="body1" sx={{ color: theme.palette.primary.dark, fontWeight: 'bold' }}>
384+
Notes
385+
</Typography>
386+
<Typography variant="body1" style={{ whiteSpace: 'pre' }}>
387+
{insuranceNotes}
388+
</Typography>
389+
</Container>
390+
)}
391+
</>
392+
) : (
393+
<CircularProgress />
394+
)}
395+
</Container>
272396
{stripeCustomerDeletedError && <StripeErrorAlert />}
273397
{!stripeCustomerDeletedError && (
274398
<>

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: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ 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 {
126-
// cSpell:disable-next eligibility
127127
const benefitList = JSON.parse(fullBenefitJSON)?.elig?.benefit;
128-
copay = parseObjectsToCopayBenefits(benefitList);
128+
const benefitsTemp = parseObjectsToCopayBenefits(benefitList);
129+
copay = benefitsTemp.filter((benefit) => benefit.coverageCode === 'A' || benefit.coverageCode === 'B');
130+
deductible = benefitsTemp.filter((benefit) => benefit.coverageCode === 'C');
129131
} catch (error) {
130132
console.error('Error parsing fullBenefitJSON', error);
131133
}
@@ -134,6 +136,7 @@ export const parseCoverageEligibilityResponse = (
134136
status: InsuranceEligibilityCheckStatus.eligibilityConfirmed,
135137
dateISO,
136138
copay,
139+
deductible,
137140
errors: coverageResponse.error,
138141
};
139142
} else {
@@ -154,17 +157,9 @@ export const parseCoverageEligibilityResponse = (
154157
};
155158

156159
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
160+
return input
166161
.map((item) => {
167-
const benefitCoverageCode = item['benefit_coverage_code'] as 'A' | 'B';
162+
const benefitCoverageCode = item['benefit_coverage_code'] as 'A' | 'B' | 'C';
168163
const CP: PatientPaymentBenefit = {
169164
amountInUSD: item['benefit_amount'] ?? 0,
170165
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
@@ -124,6 +124,9 @@ export const FHIR_EXTENSION = {
124124
insuranceRequirements: {
125125
url: `${PUBLIC_EXTENSION_BASE_URL}/insurance-requirements`,
126126
},
127+
notes: {
128+
url: `${PRIVATE_EXTENSION_BASE_URL}/notes`,
129+
},
127130
},
128131
QuestionnaireResponse: {
129132
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)