Skip to content

Commit

Permalink
Check in/75754/submit travel claims (#28485)
Browse files Browse the repository at this point in the history
* added hook and api call for multi-travel

* refined plurality of claims vs appointments and minor refactors to naming.

* copy array so count doesnt get maniplulated.

* Removed commented out test.

* Updated mileage page content.

* added tests and fixed error setting to use hook.

* updates to success alert tests.

* Fixed typo ✏️

* added a relational value to sub keys of key.

* claims set to required 🔒

* added difference in day and test for submit hook

* text change for select label.
  • Loading branch information
brianseek committed Mar 13, 2024
1 parent a9ac127 commit 71cf6f2
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 126 deletions.
35 changes: 35 additions & 0 deletions src/applications/check-in/api/versions/v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,41 @@ const v2 = {
...json,
};
},
postTravelPayClaims: async (claims, uuid) => {
const url = '/check_in/v0/travel_claims/';
const headers = { 'Content-Type': 'application/json' };

const posts = claims.map(claim => {
const travelClaimData = {
travelClaims: {
uuid,
appointmentDate: claim.startTime,
},
};
const body = JSON.stringify(travelClaimData);

return {
headers,
body,
method: 'POST',
mode: 'cors',
};
});
const json = await Promise.all(
posts.map(post =>
makeApiCallWithSentry(
apiRequest(`${environment.API_URL}${url}`, post),
'travel-claim-submit-travel-pay-claim',
uuid,
true,
),
),
);

return {
...json,
};
},
};

export { v2 };
75 changes: 75 additions & 0 deletions src/applications/check-in/hooks/usePostTravelClaims.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { differenceInCalendarDays, parseISO } from 'date-fns';
import { api } from '../api';
import { useStorage } from './useStorage';
import { makeSelectForm, makeSelectCurrentContext } from '../selectors';
import { APP_NAMES } from '../utils/appConstants';

const usePostTravelClaims = () => {
const [isLoading, setIsLoading] = useState(false);
const [isComplete, setIsComplete] = useState(false);
const [travelPayClaimError, setTravelPayClaimError] = useState(false);
const selectForm = useMemo(makeSelectForm, []);
const { data } = useSelector(selectForm);
const { facilitiesToFile } = data;
const selectCurrentContext = useMemo(makeSelectCurrentContext, []);
const { token: uuid } = useSelector(selectCurrentContext);
const { setTravelPaySent, getTravelPaySent } = useStorage(
APP_NAMES.TRAVEL_CLAIM,
true,
);
const travelPaySent = getTravelPaySent(window);

const faciltiesToPost = facilitiesToFile.filter(
facility =>
!(facility.stationNo in travelPaySent) ||
differenceInCalendarDays(
Date.now(),
parseISO(travelPaySent[facility.stationNo]),
),
);
useEffect(
() => {
const markTravelPayClaimSent = facilities => {
facilities.forEach(facility => {
travelPaySent[facility.stationNo] = new Date();
setTravelPaySent(window, travelPaySent);
});
};
if (isLoading && !isComplete) {
return;
}
setIsLoading(true);
if (!faciltiesToPost.length) {
setIsLoading(false);
return;
}
api.v2
.postTravelPayClaims(faciltiesToPost, uuid)
.catch(() => {
setTravelPayClaimError(true);
})
.finally(() => {
markTravelPayClaimSent(faciltiesToPost);
setIsLoading(false);
setIsComplete(true);
});
},
[
isLoading,
setTravelPaySent,
uuid,
isComplete,
faciltiesToPost,
travelPaySent,
],
);

return {
travelPayClaimError,
isLoading,
};
};

export { usePostTravelClaims };
13 changes: 8 additions & 5 deletions src/applications/check-in/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,11 @@
"file-a-travel-reimbursement-claim-if-youre-eligible": "File a travel reimbursement claim if you’re eligible for health care travel reimbursement. First, we need your last name and date of birth to make sure it’s you.",
"were-processing-your-travel-claim_one": "We’re processing your travel reimbursement claim",
"were-processing-your-travel-claim_other": "We’re processing your travel reimbursement claims",
"this-claim-is-for-your-appointment_one": "This claim is for your appointment on {{date, long}} at",
"this-claim-is-for-your-appointment_other": "These claims are for appointments on {{date, long}} at",
"this-claim-is-for-your-appointment": "$t(this-claim-is-for-your-appointment-claims, {\"count\": {{claims}} }) for your $t(this-claim-is-for-your-appointment-appointments, {\"count\": {{appointments}} }) on {{date, long}} at",
"this-claim-is-for-your-appointment-claims": "This claim is",
"this-claim-is-for-your-appointment-claims_other": "These claims are",
"this-claim-is-for-your-appointment-appointments": "appointment",
"this-claim-is-for-your-appointment-appointments_other": "appointments",
"well-send-you-a-text-to-let-you-know_one": "We’ll send you a text to let you know the status of your claim.",
"well-send-you-a-text-to-let-you-know_other": "We’ll send you a text to let you know the status of your claims.",
"you-dont-need-to-do-anything-else": "You don’t need to do anything else.",
Expand All @@ -318,14 +321,14 @@
"if-you-need-submit-receipts-other-expenses": "<0>If you need to submit receipts for other expenses like tolls, meals, or lodging, you can’t file a claim in this tool right now.</0> But you can file your claim online, within 30 days, through the Beneficiary Travel Self Service System (BTSSS). Or you can use VA Form 10-3542 to submit a claim by mail or in person.",
"back": "Back",
"if-youre-filing-only-mileage-no-other-file-all-claims-now": "If you’re filing only mileage and no other expenses, you can file all your claims right now for today’s appointments. You’ll need to file separate claims for any appointments that you don’t select.",
"select-one-or-more-appointments": "Select one or more appointments",
"fpo-header": "Placeholder header",
"select-the-appointments-you-want": "Select the appointments you want to file for",
"if-you-traveled-by-bus-train-taxi-or-other-authorized--help-text": "<0>If you traveled by bus, train, taxi, or other authorized public transportation, you can’t file a claim in this tool right now.</0> But you can file your claim online, within 30 days, through the Beneficiary Travel Self Service System (BTSSS). Or you can use VA Form 10-3542 to submit a claim by mail, fax, email, or in person.",
"if-you-traveled-from-a-different-address-you--helptext": "<0>If you traveled from a different address, you can’t file a claim in this tool right now.</0> But you can file your claim online, within 30 days, through the Beneficiary Travel Self Service System (BTSSS). Or you can use VA Form 10-3542 to submit a claim by mail, fax, email, or in person.",
"you-can-submit-your-claim-now-in-this-tool": "You can submit your claim now in this tool or choose to file later.",
"if-you-choose-to-file-later": "If you choose to file later, you can text <0>travel</0> to 54804 until the end of today. Or you can file within 30 days through the Beneficiary Travel Self-Service System (BTSSS).",
"file-claim": "File claim",
"finish-reviewing-your-travel-claim": "Finish reviewing your travel claim",
"by-submitting-this-claim": "By submitting this claim, you agree to the <0>beneficiary travel agreement</0>.",
"review-your-claim-information-now-to-file": "Review your claim information now to file your travel pay claim."
"review-your-claim-information-now-to-file": "Review your claim information now to file your travel pay claim.",
"select-at-least-one-appointment": "Select at least one appointment"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,107 @@
import React from 'react';
import { expect } from 'chai';
import { render } from '@testing-library/react';
import sinon from 'sinon';
import Complete from './index';
import CheckInProvider from '../../../tests/unit/utils/CheckInProvider';
import * as usePostTravelClaimsModule from '../../../hooks/usePostTravelClaims';
import * as useUpdateErrorModule from '../../../hooks/useUpdateError';
import * as useStorageModule from '../../../hooks/useStorage';
import { api } from '../../../api';

describe('Check-in experience', () => {
describe('travel-claim components', () => {
describe('Complete', () => {
it('renders content', () => {
const sandbox = sinon.createSandbox();
const { v2 } = api;
const store = {
app: 'travelClaim',
facilitiesToFile: [
{
stationNo: '500',
startTime: '2024-03-12T10:18:02.422Z',
multipleAppointments: false,
},
],
};
afterEach(() => {
sandbox.restore();
});
it('renders loading while loading', () => {
sandbox.stub(usePostTravelClaimsModule, 'usePostTravelClaims').returns({
travelPayClaimError: false,
isLoading: true,
});
const { getByTestId } = render(
<CheckInProvider>
<Complete />
</CheckInProvider>,
);
expect(getByTestId('loading-indicator')).to.exist;
});
it('renders page when complete', () => {
sandbox.stub(usePostTravelClaimsModule, 'usePostTravelClaims').returns({
travelPayClaimError: false,
isLoading: false,
});
const { getByTestId } = render(
<CheckInProvider store={store}>
<Complete />
</CheckInProvider>,
);
expect(getByTestId('header')).to.exist;
expect(getByTestId('travel-info-external-link')).to.exist;
expect(getByTestId('travel-complete-content')).to.exist;
});
it('calls travel API via hook', () => {
sandbox.stub(v2, 'postTravelPayClaims').resolves({});
render(
<CheckInProvider store={store}>
<Complete />
</CheckInProvider>,
);
sandbox.assert.calledOnce(v2.postTravelPayClaims);
});
it('dispatches error on API error', () => {
const updateErrorSpy = sinon.spy();
sandbox.stub(useUpdateErrorModule, 'useUpdateError').returns({
updateError: updateErrorSpy,
});
sandbox.stub(usePostTravelClaimsModule, 'usePostTravelClaims').returns({
travelPayClaimError: true,
isLoading: false,
});
render(
<CheckInProvider store={store}>
<Complete />
</CheckInProvider>,
);
expect(updateErrorSpy.calledOnce).to.be.true;
});
it('does not call API on reload or already filed', () => {
sandbox.stub(useStorageModule, 'useStorage').returns({
getTravelPaySent: () => ({ 500: '2024-03-12T15:18:02.422Z' }),
});
sandbox.stub(v2, 'postTravelPayClaims').resolves({});
render(
<CheckInProvider store={store}>
<Complete />
</CheckInProvider>,
);
sandbox.assert.notCalled(v2.postTravelPayClaims);
});
it('does call API if station filed before today', () => {
sandbox.stub(useStorageModule, 'useStorage').returns({
getTravelPaySent: () => ({ 500: '2024-03-10T15:18:02.422Z' }),
});
sandbox.stub(v2, 'postTravelPayClaims').resolves({});
render(
<CheckInProvider store={store}>
<Complete />
</CheckInProvider>,
);
sandbox.assert.calledOnce(v2.postTravelPayClaims);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { format } from 'date-fns';
import { getUniqueFacilies } from '../../../utils/appointment';
import { formatList } from '../../../utils/formatters';

const TravelClaimSuccessAlert = props => {
// @TODO refactor this once we have worked out where to derive facilities and times for claims from
const { appointments } = props;
const facilities = getUniqueFacilies(appointments);

const { claims } = props;
const facilities = getUniqueFacilies(claims);
const { t } = useTranslation();

let appointmentCount = claims.length;

if (claims.length === 1) {
appointmentCount = claims[0].multipleAppointments ? 2 : 1;
}
return (
<div className="vads-u-margin-y--4">
<va-alert
Expand All @@ -26,12 +30,15 @@ const TravelClaimSuccessAlert = props => {
className="vads-u-margin-top--0"
data-testid={`travel-pay-${
facilities.length > 1 ? 'multi' : 'single'
}-claim-submitted`}
}-claim-${
appointmentCount > 1 ? 'multi' : 'single'
}-appointment-submitted`}
>
{`${t('this-claim-is-for-your-appointment', {
date: new Date('2024-02-23T11:12:11'),
count: facilities.length,
})} ${formatList(facilities, t('and'))} ${t(
date: format(new Date(), 'MMMM dd, yyyy'),
claims: facilities.length,
appointments: appointmentCount,
})} ${formatList([...facilities], t('and'))} ${t(
'well-send-you-a-text-to-let-you-know',
{
count: facilities.length,
Expand All @@ -45,7 +52,7 @@ const TravelClaimSuccessAlert = props => {
};

TravelClaimSuccessAlert.propTypes = {
appointments: PropTypes.array,
claims: PropTypes.array.isRequired,
};

export default TravelClaimSuccessAlert;
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,68 @@ import { expect } from 'chai';
import { render } from '@testing-library/react';
import TravelClaimSuccessAlert from './TravelClaimSuccessAlert';
import CheckInProvider from '../../../tests/unit/utils/CheckInProvider';
import {
singleFacility,
multiFacility,
singleAppointment,
} from '../travel-intro/testAppointments';

describe('Check-in experience', () => {
describe('travel-claim', () => {
describe('TravelClaimSuccessAlert', () => {
it('renders correct content if only one facility', () => {
const multiFacility = [
{
stationNo: '622',
startTime: '2024-03-12T19:53:22.881Z',
multipleAppointments: false,
facility: 'VA Facility 3',
},
{
stationNo: '530',
startTime: '2024-03-12T15:53:22.881Z',
multipleAppointments: true,
facility: 'Example Veterans Hospital',
},
];
const singleFacilityOneAppointment = [
{
stationNo: '622',
startTime: '2024-03-12T18:28:22.404Z',
multipleAppointments: false,
},
];
const singleFacilityTwoAppointments = [
{
stationNo: '500',
startTime: '2024-03-12T17:55:31.785Z',
multipleAppointments: true,
facility: 'VA Facility 2',
},
];
it('renders correct content if only one facility and one appointment', () => {
const { getByTestId } = render(
<CheckInProvider>
<TravelClaimSuccessAlert appointments={singleFacility} />
<TravelClaimSuccessAlert claims={singleFacilityOneAppointment} />
</CheckInProvider>,
);
expect(getByTestId('travel-pay-single-claim-submitted')).to.exist;
expect(
getByTestId('travel-pay-single-claim-single-appointment-submitted'),
).to.exist;
});
it('renders correct content if only one apppointment', () => {
it('renders correct content if only one facility and multiple apppointments', () => {
const { getByTestId } = render(
<CheckInProvider>
<TravelClaimSuccessAlert appointments={singleAppointment} />
<TravelClaimSuccessAlert claims={singleFacilityTwoAppointments} />
</CheckInProvider>,
);
expect(getByTestId('travel-pay-single-claim-submitted')).to.exist;
expect(
getByTestId('travel-pay-single-claim-multi-appointment-submitted'),
).to.exist;
});
it('renders correct content if more than one facility', () => {
const { getByTestId } = render(
<CheckInProvider>
<TravelClaimSuccessAlert appointments={multiFacility} />
<TravelClaimSuccessAlert claims={multiFacility} />
</CheckInProvider>,
);
expect(getByTestId('travel-pay-multi-claim-submitted')).to.exist;
expect(
getByTestId('travel-pay-multi-claim-multi-appointment-submitted'),
).to.exist;
});
});
});
Expand Down

0 comments on commit 71cf6f2

Please sign in to comment.