Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: removed claim modal step and verified SHA number billing check #551

Merged
merged 6 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions packages/esm-billing-app/src/billing-form/hie.resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,41 @@ import { openmrsFetch, restBaseUrl, useConfig, usePatient } from '@openmrs/esm-f
import useSWR from 'swr';
import { BillingConfig } from '../config-schema';

type EligibilityResponse = { message: { eligible: 0 | 1; possible_solution: string; reason: string } };
export interface EligibilityResponse {
requestIdType: number;
requestIdNumber: string;
memberCrNumber: string;
fullName: string;
memberType: string;
coverageStartDate: Date;
coverageEndDate: Date;
status: number;
message: string;
reason: string;
possibleSolution: null;
coverageType: string;
primaryContributor: null;
employerDetails: EmployerDetails;
dependants: Array<unknown>;
active: boolean;
}

export interface EmployerDetails {
employerName: string;
jobGroup: string;
scheme: Scheme;
}

export interface Scheme {
schemeCode: string;
schemeName: string;
schemeCategoryCode: string;
schemeCategoryName: string;
memberPolicyStartDate: string;
memberPolicyEndDate: string;
joinDate: string;
leaveDate: string;
}

type HIEEligibilityResponse = {
insurer: string;
Expand All @@ -11,7 +45,7 @@ type HIEEligibilityResponse = {
eligibility_response: EligibilityResponse;
};

export const useHIEEligibility = (patientUuid: string) => {
export const useSHAEligibility = (patientUuid: string, shaIdentificationNumber?: fhir.Identifier[]) => {
const { patient } = usePatient(patientUuid);
const { nationalIdUUID } = useConfig<BillingConfig>();

Expand All @@ -20,19 +54,18 @@ export const useHIEEligibility = (patientUuid: string) => {
.filter((identifier) => identifier.type.coding.some((coding) => coding.code === nationalIdUUID))
?.at(0)?.value;

const url = `${restBaseUrl}/insuranceclaims/CoverageEligibilityRequest?patientUuid=${patientUuid}&nationalId=${nationalId}`;
const url =
shaIdentificationNumber?.length > 0
? `${restBaseUrl}/insuranceclaims/CoverageEligibilityRequest?patientUuid=${patientUuid}&nationalId=${nationalId}`
: undefined; // this is to avoid making the request if the patient does not have a SHA Id.
const { data, error, isLoading, mutate } = useSWR<{ data: Array<HIEEligibilityResponse> }>(url, openmrsFetch, {
errorRetryCount: 0,
});

return {
data:
data?.data.map((d) => {
return {
...d,
eligibility_response: JSON.parse(d.eligibility_response as unknown as string) as EligibilityResponse,
};
}) ?? [],
data: data
? { ...(JSON.parse(data?.data.at(0)?.eligibility_response as unknown as string) as EligibilityResponse) }
: undefined,
isLoading,
error,
mutate,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ActionableNotification, Form, InlineLoading, InlineNotification, Tooltip } from '@carbon/react';
import { CheckboxCheckedFilled, Information } from '@carbon/react/icons';
import { formatDate, navigate, useConfig, usePatient } from '@openmrs/esm-framework';
import capitalize from 'lodash/capitalize';
import { isWithinInterval } from 'date-fns';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { BillingConfig } from '../../config-schema';
import { useHIEEligibility } from '../hie.resource';
import { useSHAEligibility } from '../hie.resource';
import styles from './sha-number-validity.scss';

type SHANumberValidityProps = {
Expand All @@ -17,36 +17,27 @@ type SHANumberValidityProps = {
const SHANumberValidity: React.FC<SHANumberValidityProps> = ({ paymentMethod, patientUuid }) => {
const { t } = useTranslation();
const { shaIdentificationNumberUUID } = useConfig<BillingConfig>();
const { patient, isLoading } = usePatient(patientUuid);
const { patient, isLoading: isLoadingPatientUuid } = usePatient(patientUuid);
const { watch } = useFormContext();
const isSHA = watch('insuranceScheme')?.includes('SHA');
const { data, isLoading: isLoadingHIEEligibility, error } = useHIEEligibility(patientUuid);

const shaIdentificationNumber = patient?.identifier
?.filter((identifier) => identifier)
.filter((identifier) => identifier.type.coding.some((coding) => coding.code === shaIdentificationNumberUUID));

const isHIEEligible = true;
const { data, isLoading: isLoadingHIEEligibility, error } = useSHAEligibility(patientUuid, shaIdentificationNumber);

if (!isSHA) {
return null;
}
const isRegisteredOnSHA = Boolean(data?.coverageEndDate) && Boolean(data?.coverageStartDate);
const isNotRegisteredOnSHA = data?.active === false;

if (isLoadingHIEEligibility || isLoading) {
return <InlineLoading status="active" description={t('loading', 'Loading ...')} />;
}
const isActive = isRegisteredOnSHA
? isWithinInterval(new Date(), {
start: new Date(data?.coverageStartDate),
end: new Date(data?.coverageEndDate),
})
: false;

if (error) {
return (
<InlineNotification
aria-label="closes notification"
kind="error"
lowContrast={true}
statusIconDescription="notification"
title={t('error', 'Error')}
subtitle={t('errorRetrievingHIESubscription', 'Error retrieving HIE subscription')}
/>
);
if (!isSHA) {
return null;
}

if (shaIdentificationNumber?.length === 0) {
Expand All @@ -68,49 +59,61 @@ const SHANumberValidity: React.FC<SHANumberValidityProps> = ({ paymentMethod, pa
);
}

if (!isHIEEligible) {
if (isLoadingHIEEligibility || isLoadingPatientUuid) {
return <InlineLoading status="active" description={t('loading', 'Loading ...')} />;
}

if (error) {
return (
<InlineNotification
aria-label="closes notification"
kind="error"
lowContrast={true}
statusIconDescription="notification"
title={t('error', 'Error')}
subtitle={t('errorRetrievingHIESubscription', 'Error retrieving HIE subscription')}
/>
);
}

if (isNotRegisteredOnSHA) {
return (
<ActionableNotification
<InlineNotification
title={t('pendingHIEVerification', 'Pending HIE verification')}
subtitle={t('pendingVerificationReason', data[0].eligibility_response.message.reason)}
closeOnEscape
inline={false}
actionButtonLabel={t('verify', 'Verify')}
subtitle={data?.message}
className={styles.missingSHANumber}
onActionButtonClick={() => {
navigate({ to: `\${openmrsSpaBase}/patient/${patientUuid}/edit` });
}}
/>
);
}

return (
<Form className={styles.formContainer}>
{data?.map(({ inforce, insurer, start }, index) => {
return (
<div key={`${index}${insurer}`} className={styles.hieCard}>
<div className={Boolean(inforce) ? styles.hieCardItemActive : styles.hieCardItemInActive}>
<span className={styles.hieInsurerTitle}>{t('insurer', 'Insurer:')}</span>{' '}
<span className={styles.hieInsurerValue}>{capitalize(insurer)}</span>
{start && (
<Tooltip className={styles.tooltip} align="bottom" label={`Active from ${formatDate(new Date(start))}`}>
<button className="sb-tooltip-trigger" type="button">
<Information />
</button>
</Tooltip>
)}
</div>
<div className={Boolean(inforce) ? styles.hieCardItemActive : styles.hieCardItemInActive}>
<CheckboxCheckedFilled />
<span className={Boolean(inforce) ? styles.activeSubscription : styles.inActiveSubscription}>
{inforce ? t('active', 'Active') : t('inactive', 'Inactive')}
</span>
</div>
if (isRegisteredOnSHA) {
return (
<Form className={styles.formContainer}>
<div className={styles.hieCard}>
<div className={isActive ? styles.hieCardItemActive : styles.hieCardItemInActive}>
<span className={styles.hieInsurerTitle}>{t('insurer', 'Insurer:')}</span>{' '}
<span className={styles.hieInsurerValue}>SHA</span>
{isActive && (
<Tooltip
className={styles.tooltip}
align="bottom"
label={`Active from ${formatDate(new Date(data?.coverageStartDate))}`}>
<button className="sb-tooltip-trigger" type="button">
<Information />
</button>
</Tooltip>
)}
</div>
<div className={isActive ? styles.hieCardItemActive : styles.hieCardItemInActive}>
<CheckboxCheckedFilled />
<span className={isActive ? styles.activeSubscription : styles.inActiveSubscription}>
{isActive ? t('active', 'Active') : t('inactive', 'Inactive')}
</span>
</div>
);
})}
</Form>
);
</div>
</Form>
);
}
};

export default SHANumberValidity;
61 changes: 55 additions & 6 deletions packages/esm-billing-app/src/invoice/invoice.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import {
formatDatetime,
navigate,
parseDate,
setCurrentVisit,
showModal,
showSnackbar,
updateVisit,
useFeatureFlag,
usePatient,
useVisit,
Expand All @@ -21,6 +24,7 @@ import { convertToCurrency } from '../helpers';
import { usePaymentsReconciler } from '../hooks/use-payments-reconciler';
import { LineItem } from '../types';
import InvoiceTable from './invoice-table.component';
import { removeQueuedPatient, useVisitQueueEntry } from './invoice.resource';
import styles from './invoice.scss';
import Payments from './payments/payments.component';
import ReceiptPrintButton from './print-bill-receipt/receipt-print-button.component';
Expand All @@ -39,7 +43,14 @@ const Invoice: React.FC = () => {
const { patient, isLoading: isLoadingPatient, error: patientError } = usePatient(patientUuid);
const { bill, isLoading: isLoadingBill, error: billingError } = useBill(billUuid);
usePaymentsReconciler(billUuid);
const { currentVisit, isLoading: isVisitLoading, error: visitError } = useVisit(patientUuid);
const {
currentVisit,
isLoading: isVisitLoading,
error: visitError,
currentVisitIsRetrospective,
mutate: mutateVisit,
} = useVisit(patientUuid);
const { queueEntry } = useVisitQueueEntry(patientUuid, currentVisit?.uuid);
const [selectedLineItems, setSelectedLineItems] = useState([]);
const componentRef = useRef<HTMLDivElement>(null);
const isProcessClaimsFormEnabled = useFeatureFlag('healthInformationExchange');
Expand Down Expand Up @@ -108,12 +119,49 @@ const Invoice: React.FC = () => {
);
}

const handleViewClaims = () => {
const handleEndVisit = async () => {
if (currentVisitIsRetrospective) {
setCurrentVisit(null, null);
} else {
const endVisitPayload = {
stopDatetime: new Date(),
};

const abortController = new AbortController();

updateVisit(currentVisit.uuid, endVisitPayload, abortController)
.then((response) => {
if (queueEntry) {
removeQueuedPatient(
queueEntry.queue.uuid,
queueEntry.queueEntryUuid,
abortController,
response?.data.stopDatetime,
);
}
mutateVisit();
showSnackbar({
isLowContrast: true,
kind: 'success',
subtitle: t('visitEndSuccessfully', `${response?.data?.visitType?.display} ended successfully`),
title: t('visitEnded', 'Visit ended'),
});
})
.catch((error) => {
showSnackbar({
title: t('errorEndingVisit', 'Error ending visit'),
kind: 'error',
isLowContrast: false,
subtitle: error?.message,
});
});
}
};

const handleViewClaims = async () => {
if (currentVisit) {
const dispose = showModal('end-visit-dialog', {
closeModal: () => dispose(),
patientUuid,
});
await handleEndVisit();
navigate({ to: `${spaBasePath}/billing/patient/${patientUuid}/${billUuid}/claims` });
} else {
navigate({ to: `${spaBasePath}/billing/patient/${patientUuid}/${billUuid}/claims` });
}
Expand Down Expand Up @@ -153,6 +201,7 @@ const Invoice: React.FC = () => {
{isProcessClaimsFormEnabled && (
<Button
onClick={handleViewClaims}
disabled={bill?.status !== 'PAID'}
kind="danger"
size="sm"
renderIcon={BaggageClaim}
Expand Down
Loading
Loading