@@ -23,16 +23,18 @@ import { useMutation } from '@tanstack/react-query';
2323import { Appointment , DocumentReference , Encounter , Organization , Patient } from 'fhir/r4b' ;
2424import { DateTime } from 'luxon' ;
2525import { enqueueSnackbar } from 'notistack' ;
26- import { FC , Fragment , ReactElement , useEffect , useState } from 'react' ;
26+ import { FC , Fragment , ReactElement , useState } from 'react' ;
27+ import { getEligibilityCheckDetailsForCoverage } from 'src/features/visits/shared/components/patient/InsuranceSection' ;
2728import { useOystehrAPIClient } from 'src/features/visits/shared/hooks/useOystehrAPIClient' ;
2829import { useApiClients } from 'src/hooks/useAppClients' ;
29- import { useGetEncounter } from 'src/hooks/useEncounter' ;
30+ import { useEncounterReceipt , useGetEncounter } from 'src/hooks/useEncounter' ;
3031import { useGetPatientAccount } from 'src/hooks/useGetPatient' ;
3132import { useGetPatientPaymentsList } from 'src/hooks/useGetPatientPaymentsList' ;
3233import {
3334 APIError ,
3435 APIErrorCode ,
3536 CashOrCardPayment ,
37+ CoverageCheckWithDetails ,
3638 FHIR_EXTENSION ,
3739 getCoding ,
3840 getPaymentVariantFromEncounter ,
@@ -42,7 +44,6 @@ import {
4244 PatientPaymentDTO ,
4345 PaymentVariant ,
4446 PostPatientPaymentInput ,
45- RECEIPT_CODE ,
4647 SendReceiptByEmailZambdaInput ,
4748 SERVICE_CATEGORY_SYSTEM ,
4849 updateEncounterPaymentVariantExtension ,
@@ -88,13 +89,19 @@ export default function PatientPaymentList({
8889 const theme = useTheme ( ) ;
8990 const [ paymentDialogOpen , setPaymentDialogOpen ] = useState ( false ) ;
9091 const [ sendReceiptByEmailDialogOpen , setSendReceiptByEmailDialogOpen ] = useState ( false ) ;
91- const [ receiptDocRefId , setReceiptDocRefId ] = useState < string | undefined > ( ) ;
92+
9293 const {
9394 data : encounter ,
9495 refetch : refetchEncounter ,
9596 isRefetching : isEncounterRefetching ,
9697 } = useGetEncounter ( { encounterId } ) ;
9798
99+ const {
100+ data : receiptDocRef ,
101+ refetch : refetchReceipt ,
102+ isFetching : isReceiptFetching ,
103+ } = useEncounterReceipt ( { encounterId } ) ;
104+
98105 const {
99106 data : paymentData ,
100107 refetch : refetchPaymentList ,
@@ -121,15 +128,21 @@ export default function PatientPaymentList({
121128 code : string ;
122129 coverageCode : string ;
123130 levelCode : string ;
124- periodCode : string ;
125- } ) : number | undefined {
126- return coverage ?. find (
131+ periodCode : string | undefined ;
132+ } ) : PatientPaymentBenefit | undefined {
133+ if ( ! periodCode ) {
134+ return coverage . find (
135+ ( item ) => item . code === code && item . coverageCode === coverageCode && item . levelCode === levelCode
136+ ) ;
137+ }
138+
139+ return coverage . find (
127140 ( item ) =>
128141 item . code === code &&
129142 item . coverageCode === coverageCode &&
130143 item . levelCode === levelCode &&
131144 item . periodCode === periodCode
132- ) ?. amountInUSD ;
145+ ) ;
133146 }
134147
135148 const payments = paymentData ?. payments ?? [ ] ; // Replace with actual payments when available
@@ -139,30 +152,7 @@ export default function PatientPaymentList({
139152 ? ( paymentListError as APIError ) . code === APIErrorCode . STRIPE_CUSTOMER_ID_DOES_NOT_EXIST
140153 : false ;
141154
142- useEffect ( ( ) => {
143- if ( oystehr && encounterId ) {
144- void oystehr . fhir
145- . search < DocumentReference > ( {
146- resourceType : 'DocumentReference' ,
147- params : [
148- {
149- name : 'type' ,
150- value : RECEIPT_CODE ,
151- } ,
152- {
153- name : 'encounter' ,
154- value : 'Encounter/' + encounterId ,
155- } ,
156- ] ,
157- } )
158- . then ( ( response ) => {
159- const docRef = response . unbundle ( ) [ 0 ] ;
160- if ( docRef ) {
161- setReceiptDocRefId ( docRef . id ) ;
162- }
163- } ) ;
164- }
165- } , [ encounterId , oystehr , paymentData ] ) ;
155+ const receiptDocRefId = receiptDocRef ?. id ;
166156
167157 const sendReceipt = async ( formData : SendReceiptFormData ) : Promise < void > => {
168158 if ( ! oystehr ) return ;
@@ -214,17 +204,34 @@ export default function PatientPaymentList({
214204
215205 const createNewPayment = useMutation ( {
216206 mutationFn : async ( input : PostPatientPaymentInput ) => {
217- if ( oystehrZambda && input ) {
218- return oystehrZambda . zambda
219- . execute ( {
220- id : 'patient-payments-post' ,
221- ...input ,
222- } )
223- . then ( async ( ) => {
224- await refetchPaymentList ( ) ;
225- setPaymentDialogOpen ( false ) ;
226- } ) ;
227- }
207+ if ( ! oystehrZambda ) return ;
208+
209+ await oystehrZambda . zambda . execute ( {
210+ id : 'patient-payments-post' ,
211+ ...input ,
212+ } ) ;
213+ } ,
214+ onSuccess : async ( ) => {
215+ await refetchPaymentList ( ) ;
216+ const waitForReceipt = async ( ) : Promise < void > => {
217+ let receipt : DocumentReference | null = null ;
218+ const maxTries = 15 ;
219+ let tries = 0 ;
220+
221+ while ( ! receipt && tries < maxTries ) {
222+ const result = await refetchReceipt ( ) ;
223+
224+ receipt = result . data ?? null ;
225+ if ( ! receipt ) {
226+ await new Promise ( ( res ) => setTimeout ( res , 2000 ) ) ;
227+ }
228+ tries += 1 ;
229+ }
230+ } ;
231+
232+ await waitForReceipt ( ) ;
233+
234+ setPaymentDialogOpen ( false ) ;
228235 } ,
229236 retry : 0 ,
230237 } ) ;
@@ -288,16 +295,24 @@ export default function PatientPaymentList({
288295 ( extensionTemp ) => extensionTemp . url === FHIR_EXTENSION . InsurancePlan . notes . url
289296 ) ?. valueString ;
290297
298+ let coverageCheck : CoverageCheckWithDetails | undefined = undefined ;
299+ if ( insuranceCoverages ?. coverages ?. primary && insuranceData ?. coverageChecks ) {
300+ coverageCheck = getEligibilityCheckDetailsForCoverage (
301+ insuranceCoverages ?. coverages ?. primary ,
302+ insuranceData ?. coverageChecks
303+ ) ;
304+ }
305+
291306 const copayAmount = getPaymentAmountFromPatientBenefit ( {
292- coverage : insuranceData ?. coverageChecks ?. [ 0 ] ?. copay || [ ] ,
307+ coverage : coverageCheck ?. copay ?. filter ( ( item ) => item . inNetwork === true ) || [ ] ,
293308 code : 'UC' ,
294309 coverageCode : 'B' ,
295310 levelCode : 'IND' ,
296- periodCode : '27' ,
311+ periodCode : undefined ,
297312 } ) ;
298313
299314 const remainingDeductibleAmount = getPaymentAmountFromPatientBenefit ( {
300- coverage : insuranceData ?. coverageChecks ?. [ 0 ] ?. deductible || [ ] ,
315+ coverage : coverageCheck ?. deductible ?. filter ( ( item ) => item . inNetwork === true ) || [ ] ,
301316 code : '30' ,
302317 coverageCode : 'C' ,
303318 levelCode : 'IND' ,
@@ -379,13 +394,13 @@ export default function PatientPaymentList({
379394 < TableRow >
380395 < TableCell style = { { fontSize : '16px' } } > Copay</ TableCell >
381396 < TableCell style = { { fontSize : '16px' , fontWeight : 'bold' , textAlign : 'right' } } >
382- { copayAmount ? `$${ copayAmount } ` : 'Unknown' }
397+ { copayAmount ? `$${ copayAmount ?. amountInUSD } / ${ copayAmount ?. periodDescription } ` : 'Unknown' }
383398 </ TableCell >
384399 </ TableRow >
385400 < TableRow sx = { { '&:last-child td' : { borderBottom : 'none' } } } >
386401 < TableCell style = { { fontSize : '16px' } } > Remaining Deductible</ TableCell >
387402 < TableCell style = { { fontSize : '16px' , fontWeight : 'bold' , textAlign : 'right' } } >
388- { remainingDeductibleAmount ? `$${ remainingDeductibleAmount } ` : 'Unknown' }
403+ { remainingDeductibleAmount ? `$${ remainingDeductibleAmount ?. amountInUSD } ` : 'Unknown' }
389404 </ TableCell >
390405 </ TableRow >
391406 </ TableBody >
@@ -494,7 +509,7 @@ export default function PatientPaymentList({
494509 < span >
495510 < Button
496511 sx = { { mt : 2 , ml : 2 } }
497- disabled = { ! receiptDocRefId }
512+ disabled = { ! receiptDocRefId || isReceiptFetching }
498513 onClick = { ( ) => setSendReceiptByEmailDialogOpen ( true ) }
499514 variant = "contained"
500515 color = "primary"
0 commit comments