From 13d2353d6e771f6ac4ef4d01e525ee8a3a517dd9 Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Mon, 4 May 2026 11:01:57 -0500 Subject: [PATCH 1/3] Swap spouse salary request fields --- .../LandingSalaryCalculations.graphql | 1 + .../LandingTestWrapper.tsx | 3 + .../Landing/useLandingData.ts | 54 ++++++++------ .../SalaryCalculation.graphql | 1 + .../SalaryCalculatorTestWrapper.tsx | 2 + .../Summary/SalarySummaryCard.test.tsx | 72 +++++++++++++----- .../Summary/SalarySummaryCard.tsx | 73 +++++++++++-------- 7 files changed, 137 insertions(+), 69 deletions(-) diff --git a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingSalaryCalculations.graphql b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingSalaryCalculations.graphql index 84c22feec1..2c0ba6af82 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingSalaryCalculations.graphql +++ b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingSalaryCalculations.graphql @@ -17,6 +17,7 @@ query LandingSalaryCalculations { } effectiveCalculation: effectiveSalaryRequest { id + personNumber salary spouseSalary calculations { diff --git a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx index 5280277217..3c02f3aee3 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx +++ b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx @@ -51,6 +51,7 @@ export const LandingTestWrapper: React.FC = ({ staffInfo: { preferredName: 'John', lastName: 'Doe', + personNumber: '000123456', secaStatus: SecaStatusEnum.Seca, peopleGroupSupportType: PeopleGroupSupportTypeEnum.SupportedRmo, @@ -75,6 +76,7 @@ export const LandingTestWrapper: React.FC = ({ staffInfo: { preferredName: 'Jane', lastName: 'Doe', + personNumber: '000123457', secaStatus: SecaStatusEnum.Seca, }, currentSalary: { @@ -98,6 +100,7 @@ export const LandingTestWrapper: React.FC = ({ : null, effectiveCalculation: hasApprovedCalculation ? { + personNumber: '000123456', salary: 50000, spouseSalary: 60000, calculations: { effectiveCap: 60000 }, diff --git a/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts b/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts index 07a716a2a8..fcc37fdef5 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts +++ b/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts @@ -153,36 +153,45 @@ export const useLandingData = (): LandingData => { [accountBalanceData], ); - const salaryCategories = useMemo( - () => [ + const salaryCategories = useMemo(() => { + const ownRequest = + effectiveCalculation?.personNumber === self?.staffInfo.personNumber; + const effectiveCap = ownRequest + ? effectiveCalculation?.calculations.effectiveCap + : effectiveCalculation?.spouseCalculations?.effectiveCap; + const spouseEffectiveCap = ownRequest + ? effectiveCalculation?.spouseCalculations?.effectiveCap + : effectiveCalculation?.calculations.effectiveCap; + const salary = ownRequest + ? effectiveCalculation?.salary + : effectiveCalculation?.spouseSalary; + const spouseSalary = ownRequest + ? effectiveCalculation?.spouseSalary + : effectiveCalculation?.salary; + + return [ { category: t('Maximum Allowable Salary'), - user: effectiveCalculation?.calculations.effectiveCap - ? currencyFormat( - effectiveCalculation.calculations.effectiveCap, - 'USD', - locale, - { showTrailingZeros: true }, - ) + user: effectiveCap + ? currencyFormat(effectiveCap, 'USD', locale, { + showTrailingZeros: true, + }) : 'TBD', - spouse: effectiveCalculation?.spouseCalculations?.effectiveCap - ? currencyFormat( - effectiveCalculation.spouseCalculations?.effectiveCap, - 'USD', - locale, - { showTrailingZeros: true }, - ) + spouse: spouseEffectiveCap + ? currencyFormat(spouseEffectiveCap, 'USD', locale, { + showTrailingZeros: true, + }) : 'TBD', }, { category: t('Requested Salary'), - user: effectiveCalculation?.salary - ? currencyFormat(effectiveCalculation.salary, 'USD', locale, { + user: salary + ? currencyFormat(salary, 'USD', locale, { showTrailingZeros: true, }) : 'TBD', - spouse: effectiveCalculation?.spouseSalary - ? currencyFormat(effectiveCalculation.spouseSalary, 'USD', locale, { + spouse: spouseSalary + ? currencyFormat(spouseSalary, 'USD', locale, { showTrailingZeros: true, }) : 'TBD', @@ -242,9 +251,8 @@ export const useLandingData = (): LandingData => { }), link: '/hrTools/mhaCalculator', }, - ], - [t, salaryData, self, spouse, effectiveCalculation, locale], - ); + ]; + }, [t, salaryData, self, spouse, effectiveCalculation, locale]); return { staffAccountId, diff --git a/src/components/HrTools/SalaryCalculator/SalaryCalculatorContext/SalaryCalculation.graphql b/src/components/HrTools/SalaryCalculator/SalaryCalculatorContext/SalaryCalculation.graphql index bdbbca853f..4a906f73b3 100644 --- a/src/components/HrTools/SalaryCalculator/SalaryCalculatorContext/SalaryCalculation.graphql +++ b/src/components/HrTools/SalaryCalculator/SalaryCalculatorContext/SalaryCalculation.graphql @@ -75,6 +75,7 @@ query SalaryCalculation($id: ID!) { query EffectiveSalaryCalculation { salaryRequest: effectiveSalaryRequest { id + personNumber mhaAmount spouseMhaAmount salary diff --git a/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx b/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx index 8d8d63d0d4..0a4ccaba36 100644 --- a/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx +++ b/src/components/HrTools/SalaryCalculator/SalaryCalculatorTestWrapper.tsx @@ -29,6 +29,7 @@ const hcmMock = gqlMock(HcmDocument, { staffInfo: { preferredName: 'John', lastName: 'Doe', + personNumber: '000123456', city: 'Miami', country: 'US', state: 'FL', @@ -62,6 +63,7 @@ const hcmMock = gqlMock(HcmDocument, { staffInfo: { preferredName: 'Jane', lastName: 'Doe', + personNumber: '000789123', tenure: 1000, primaryPhoneNumber: '555-0124', emailAddress: 'jane.doe@example.com', diff --git a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.test.tsx b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.test.tsx index 76c2aa33c5..92aab54c09 100644 --- a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.test.tsx +++ b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.test.tsx @@ -4,26 +4,31 @@ import { DeepPartial } from 'ts-essentials'; import TestRouter from '__tests__/util/TestRouter'; import { GqlMockedProvider } from '__tests__/util/graphqlMocking'; import { HcmQuery } from '../../Shared/HcmData/Hcm.generated'; -import { SalaryCalculationQuery } from '../SalaryCalculatorContext/SalaryCalculation.generated'; +import { + EffectiveSalaryCalculationQuery, + SalaryCalculationQuery, +} from '../SalaryCalculatorContext/SalaryCalculation.generated'; import { SalaryCalculatorProvider } from '../SalaryCalculatorContext/SalaryCalculatorContext'; import { hcmSpouseMock, hcmUserMock } from '../SalaryCalculatorTestWrapper'; import { SalarySummaryCard } from './SalarySummaryCard'; -const approvedSalaryMock: DeepPartial = - { - salary: 10001, - mhaAmount: 10002, - spouseSalary: 20001, - spouseMhaAmount: 20002, - calculations: { - contributing403bFraction: 0.1, - effectiveCap: 10003, - }, - spouseCalculations: { - contributing403bFraction: 0.2, - effectiveCap: 20003, - }, - }; +const approvedSalaryMock: DeepPartial< + EffectiveSalaryCalculationQuery['salaryRequest'] +> = { + personNumber: hcmUserMock.staffInfo.personNumber, + salary: 10001, + mhaAmount: 10002, + spouseSalary: 20001, + spouseMhaAmount: 20002, + calculations: { + contributing403bFraction: 0.1, + effectiveCap: 10003, + }, + spouseCalculations: { + contributing403bFraction: 0.2, + effectiveCap: 20003, + }, +}; const defaultSalaryMock: DeepPartial = { @@ -45,11 +50,13 @@ const defaultSalaryMock: DeepPartial = interface TestComponentProps { hasSpouse?: boolean; hasApprovedCalculation?: boolean; + hasSpouseApprovedCalculation?: boolean; } const TestComponent: React.FC = ({ hasSpouse = true, hasApprovedCalculation = true, + hasSpouseApprovedCalculation = false, }) => ( = ({ mocks={{ Hcm: { @@ -68,7 +76,14 @@ const TestComponent: React.FC = ({ salaryRequest: defaultSalaryMock, }, EffectiveSalaryCalculation: { - salaryRequest: hasApprovedCalculation ? approvedSalaryMock : null, + salaryRequest: hasApprovedCalculation + ? { + ...approvedSalaryMock, + personNumber: hasSpouseApprovedCalculation + ? hcmSpouseMock.staffInfo.personNumber + : hcmUserMock.staffInfo.personNumber, + } + : null, }, }} > @@ -116,6 +131,29 @@ describe('SalarySummaryCard', () => { ); }); + it('swaps fields when the spouse created the request', async () => { + const { getAllByRole } = render( + , + ); + + const expectedCells = [ + ['Requested Salary', '$20,001.00', '$30,001.00'], + ['MHA', '$20,002.00', '$30,002.00'], + ['403(b) Contribution', '20.00%', '30.00%'], + ['Max Allowable Salary', '$20,003.00', '$30,003.00'], + ['Requested Salary', '$10,001.00', '$40,001.00'], + ['MHA', '$10,002.00', '$40,002.00'], + ['403(b) Contribution', '10.00%', '40.00%'], + ['Max Allowable Salary', '$10,003.00', '$40,003.00'], + ].flat(); + + await waitFor(() => + expect(getAllByRole('cell').map((cell) => cell.textContent)).toEqual( + expectedCells, + ), + ); + }); + describe('no approved calculation', () => { it('should hide the Old column headers', async () => { const { getAllByRole } = render( diff --git a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx index 884bf79d82..2fa7f0d3c3 100644 --- a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx +++ b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx @@ -16,8 +16,16 @@ import { useFormatters } from '../Shared/useFormatters'; export const SalarySummaryCard: React.FC = () => { const { t } = useTranslation(); const { hcmUser, hcmSpouse, calculation } = useSalaryCalculator(); - const { data: approvedData } = useEffectiveSalaryCalculationQuery(); - const effectiveCalculation = approvedData?.salaryRequest; + const { data: effectiveData } = useEffectiveSalaryCalculationQuery(); + const oldCalculation = effectiveData?.salaryRequest; + const ownRequest = + oldCalculation?.personNumber === hcmUser?.staffInfo.personNumber; + const oldCalculations = ownRequest + ? oldCalculation?.calculations + : oldCalculation?.spouseCalculations; + const oldSpouseCalculations = ownRequest + ? oldCalculation?.spouseCalculations + : oldCalculation?.calculations; const { formatCurrency, formatFraction } = useFormatters(); return ( @@ -37,38 +45,42 @@ export const SalarySummaryCard: React.FC = () => { {hcmUser?.staffInfo.preferredName} - {effectiveCalculation && ( - {t('Old')} - )} + {oldCalculation && {t('Old')}} {t('New')} {t('Requested Salary')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency(effectiveCalculation.salary)} + {formatCurrency( + ownRequest + ? oldCalculation.salary + : oldCalculation.spouseSalary, + )} )} {formatCurrency(calculation?.salary)} {t('MHA')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency(effectiveCalculation.mhaAmount)} + {formatCurrency( + ownRequest + ? oldCalculation.mhaAmount + : oldCalculation.spouseMhaAmount, + )} )} {formatCurrency(calculation?.mhaAmount)} {t('403(b) Contribution')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatFraction( - effectiveCalculation.calculations.contributing403bFraction, - )} + {formatFraction(oldCalculations?.contributing403bFraction)} )} @@ -79,11 +91,9 @@ export const SalarySummaryCard: React.FC = () => { {t('Max Allowable Salary')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency( - effectiveCalculation.calculations.effectiveCap, - )} + {formatCurrency(oldCalculations?.effectiveCap)} )} @@ -100,7 +110,7 @@ export const SalarySummaryCard: React.FC = () => { {hcmSpouse?.staffInfo.preferredName} - {effectiveCalculation && ( + {oldCalculation && ( {t('Old')} )} {t('New')} @@ -109,9 +119,13 @@ export const SalarySummaryCard: React.FC = () => { {t('Requested Salary')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency(effectiveCalculation.spouseSalary)} + {formatCurrency( + ownRequest + ? oldCalculation.spouseSalary + : oldCalculation.salary, + )} )} @@ -120,9 +134,13 @@ export const SalarySummaryCard: React.FC = () => { {t('MHA')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency(effectiveCalculation.spouseMhaAmount)} + {formatCurrency( + ownRequest + ? oldCalculation.spouseMhaAmount + : oldCalculation.mhaAmount, + )} )} @@ -131,11 +149,10 @@ export const SalarySummaryCard: React.FC = () => { {t('403(b) Contribution')} - {effectiveCalculation && ( + {oldCalculation && ( {formatFraction( - effectiveCalculation.spouseCalculations - ?.contributing403bFraction, + oldSpouseCalculations?.contributing403bFraction, )} )} @@ -147,11 +164,9 @@ export const SalarySummaryCard: React.FC = () => { {t('Max Allowable Salary')} - {effectiveCalculation && ( + {oldCalculation && ( - {formatCurrency( - effectiveCalculation.spouseCalculations?.effectiveCap, - )} + {formatCurrency(oldSpouseCalculations?.effectiveCap)} )} From 6366a307d61cabe13d47aab5f648d0fa9629cd5f Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Mon, 4 May 2026 11:42:14 -0500 Subject: [PATCH 2/3] Add landing page tests --- .../LandingTestWrapper.tsx | 8 +- .../NewSalaryCalculatorLanding.test.tsx | 80 ++++++++++++------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx index 3c02f3aee3..8c4bf5d868 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx +++ b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/LandingTestWrapper.tsx @@ -18,11 +18,12 @@ import { AccountBalanceQuery } from '../AccountBalance.generated'; import { StaffAccountIdQuery } from '../StaffAccountId.generated'; import { LandingSalaryCalculationsQuery } from './LandingSalaryCalculations.generated'; -interface LandingTestWrapperProps { +export interface LandingTestWrapperProps { onCall?: MockLinkCallHandler; children?: React.ReactNode; hasInProgressCalculation?: boolean; hasApprovedCalculation?: boolean; + hasSpouseApprovedCalculation?: boolean; hasLatestCalculation?: boolean; salaryRequestEligible?: boolean; } @@ -32,6 +33,7 @@ export const LandingTestWrapper: React.FC = ({ children, hasInProgressCalculation = false, hasApprovedCalculation = false, + hasSpouseApprovedCalculation = false, hasLatestCalculation = false, salaryRequestEligible = true, }) => ( @@ -100,7 +102,9 @@ export const LandingTestWrapper: React.FC = ({ : null, effectiveCalculation: hasApprovedCalculation ? { - personNumber: '000123456', + personNumber: hasSpouseApprovedCalculation + ? '000123457' + : '000123456', salary: 50000, spouseSalary: 60000, calculations: { effectiveCap: 60000 }, diff --git a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/NewSalaryCalculatorLanding.test.tsx b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/NewSalaryCalculatorLanding.test.tsx index ad4ca1ac34..3a6cc7cee2 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/NewSalaryCalculatorLanding.test.tsx +++ b/src/components/HrTools/SalaryCalculator/Landing/NewSalaryCalculationLanding/NewSalaryCalculatorLanding.test.tsx @@ -1,27 +1,15 @@ import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LandingTestWrapper } from './LandingTestWrapper'; +import { + LandingTestWrapper, + LandingTestWrapperProps, +} from './LandingTestWrapper'; import { NewSalaryCalculatorLanding } from './NewSalaryCalculatorLanding'; const mutationSpy = jest.fn(); -interface TestComponentProps { - hasInProgressCalculation?: boolean; - hasApprovedCalculation?: boolean; - salaryRequestEligible?: boolean; -} - -const TestComponent: React.FC = ({ - hasInProgressCalculation = false, - hasApprovedCalculation = false, - salaryRequestEligible, -}) => ( - +const TestComponent: React.FC = (props) => ( + ); @@ -45,18 +33,54 @@ describe('NewSalaryCalculatorLanding', () => { expect(await findByTestId('amount-two')).toHaveTextContent('$10,000.00'); }); - it('renders SalaryInformationCard with correct cell data', async () => { - const { findByRole } = render(); + it('renders table with correct cell data', async () => { + const { getAllByRole } = render(); + + const expectedCells = [ + ['Maximum Allowable Salary', '$60,000.00', '$70,000.00'], + ['Requested Salary', '$50,000.00', '$60,000.00'], + ['Tax-deferred 403(b) Contribution', '5%', '6%'], + ['Roth 403(b) Contribution', '12%', '10%'], + ['Security (SECA/FICA) Status', 'Subject to SECA', 'Subject to SECA'], + ['Current Gross Salary', '$55,000.00', '$10,000.00'], + [ + 'Current MHA (Included in Current Gross Salary)', + '$10,000.00View MHA Form', + '$12,000.00', + ], + ].flat(); - // Self - expect( - await findByRole('heading', { name: '$55,000.00' }), - ).toBeInTheDocument(); + await waitFor(() => + expect(getAllByRole('cell').map((cell) => cell.textContent)).toEqual( + expectedCells, + ), + ); + }); - // Spouse - expect( - await findByRole('heading', { name: '$10,000.00' }), - ).toBeInTheDocument(); + it('swaps effective request fields when the spouse created the request', async () => { + const { getAllByRole } = render( + , + ); + + const expectedCells = [ + ['Maximum Allowable Salary', '$70,000.00', '$60,000.00'], + ['Requested Salary', '$60,000.00', '$50,000.00'], + ['Tax-deferred 403(b) Contribution', '5%', '6%'], + ['Roth 403(b) Contribution', '12%', '10%'], + ['Security (SECA/FICA) Status', 'Subject to SECA', 'Subject to SECA'], + ['Current Gross Salary', '$55,000.00', '$10,000.00'], + [ + 'Current MHA (Included in Current Gross Salary)', + '$10,000.00View MHA Form', + '$12,000.00', + ], + ].flat(); + + await waitFor(() => + expect(getAllByRole('cell').map((cell) => cell.textContent)).toEqual( + expectedCells, + ), + ); }); it('renders action button', async () => { From 0a2cffcb0a0eda38ba6ab61fe3666f895565ac2d Mon Sep 17 00:00:00 2001 From: Caleb Cox Date: Mon, 4 May 2026 14:17:19 -0500 Subject: [PATCH 3/3] Add orientSalaryRequest --- .../Landing/useLandingData.ts | 52 ++++++------- .../Shared/orientSalaryRequest.test.ts | 51 +++++++++++++ .../Shared/orientSalaryRequest.ts | 52 +++++++++++++ .../Summary/SalarySummaryCard.tsx | 75 ++++++++----------- 4 files changed, 159 insertions(+), 71 deletions(-) create mode 100644 src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.test.ts create mode 100644 src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.ts diff --git a/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts b/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts index fcc37fdef5..c311041a1b 100644 --- a/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts +++ b/src/components/HrTools/SalaryCalculator/Landing/useLandingData.ts @@ -5,6 +5,7 @@ import { useLocale } from 'src/hooks/useLocale'; import { currencyFormat, percentageFormat } from 'src/lib/intlFormat'; import { type HcmQuery, useHcmQuery } from '../../Shared/HcmData/Hcm.generated'; import { getLocalizedTaxStatus } from '../Shared/getLocalizedTaxStatus'; +import { orientSalaryRequest } from '../Shared/orientSalaryRequest'; import { useAccountBalanceQuery } from './AccountBalance.generated'; import { type LandingSalaryCalculationsQuery, @@ -64,7 +65,6 @@ export const useLandingData = (): LandingData => { const { data: staffAccountIdData, loading: staffAccountIdLoading } = useStaffAccountIdQuery(); - const effectiveCalculation = calculationData?.effectiveCalculation; const inProgressCalculationId = calculationData?.inProgressCalculation?.id ?? null; const latestCalculation = calculationData?.latestCalculation; @@ -154,44 +154,40 @@ export const useLandingData = (): LandingData => { ); const salaryCategories = useMemo(() => { - const ownRequest = - effectiveCalculation?.personNumber === self?.staffInfo.personNumber; - const effectiveCap = ownRequest - ? effectiveCalculation?.calculations.effectiveCap - : effectiveCalculation?.spouseCalculations?.effectiveCap; - const spouseEffectiveCap = ownRequest - ? effectiveCalculation?.spouseCalculations?.effectiveCap - : effectiveCalculation?.calculations.effectiveCap; - const salary = ownRequest - ? effectiveCalculation?.salary - : effectiveCalculation?.spouseSalary; - const spouseSalary = ownRequest - ? effectiveCalculation?.spouseSalary - : effectiveCalculation?.salary; + const effectiveCalculation = orientSalaryRequest( + calculationData?.effectiveCalculation, + self?.staffInfo.personNumber, + ); return [ { category: t('Maximum Allowable Salary'), - user: effectiveCap - ? currencyFormat(effectiveCap, 'USD', locale, { - showTrailingZeros: true, - }) + user: effectiveCalculation?.calculations.effectiveCap + ? currencyFormat( + effectiveCalculation.calculations.effectiveCap, + 'USD', + locale, + { showTrailingZeros: true }, + ) : 'TBD', - spouse: spouseEffectiveCap - ? currencyFormat(spouseEffectiveCap, 'USD', locale, { - showTrailingZeros: true, - }) + spouse: effectiveCalculation?.spouseCalculations?.effectiveCap + ? currencyFormat( + effectiveCalculation.spouseCalculations?.effectiveCap, + 'USD', + locale, + { showTrailingZeros: true }, + ) : 'TBD', }, { category: t('Requested Salary'), - user: salary - ? currencyFormat(salary, 'USD', locale, { + user: effectiveCalculation?.salary + ? currencyFormat(effectiveCalculation.salary, 'USD', locale, { showTrailingZeros: true, }) : 'TBD', - spouse: spouseSalary - ? currencyFormat(spouseSalary, 'USD', locale, { + spouse: effectiveCalculation?.spouseSalary + ? currencyFormat(effectiveCalculation.spouseSalary, 'USD', locale, { showTrailingZeros: true, }) : 'TBD', @@ -252,7 +248,7 @@ export const useLandingData = (): LandingData => { link: '/hrTools/mhaCalculator', }, ]; - }, [t, salaryData, self, spouse, effectiveCalculation, locale]); + }, [t, salaryData, self, spouse, calculationData, locale]); return { staffAccountId, diff --git a/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.test.ts b/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.test.ts new file mode 100644 index 0000000000..7b224d3c12 --- /dev/null +++ b/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.test.ts @@ -0,0 +1,51 @@ +import { orientSalaryRequest } from './orientSalaryRequest'; + +const baseRequest = { + personNumber: 'user-1', + salary: 10001, + spouseSalary: 20001, + mhaAmount: 10002, + spouseMhaAmount: 20003, + salaryCap: 10003, + spouseSalaryCap: 20003, + calculations: { effectiveCap: 10004, contributing403bFraction: 0.15 }, + spouseCalculations: { effectiveCap: 20005, contributing403bFraction: 0.25 }, +}; + +describe('orientSalaryRequest', () => { + it('returns the request unchanged when person numbers match', () => { + expect(orientSalaryRequest(baseRequest, 'user-1')).toEqual(baseRequest); + }); + + it('swaps user and spouse fields when the spouse created the request', () => { + const oriented = orientSalaryRequest(baseRequest, 'user-2'); + + expect(oriented).toEqual({ + personNumber: 'user-2', + salary: 20001, + spouseSalary: 10001, + mhaAmount: 20003, + spouseMhaAmount: 10002, + salaryCap: 20003, + spouseSalaryCap: 10003, + calculations: { + effectiveCap: 20005, + contributing403bFraction: 0.25, + }, + spouseCalculations: { + effectiveCap: 10004, + contributing403bFraction: 0.15, + }, + }); + }); + + it('passes through nullish requests', () => { + expect(orientSalaryRequest(null, 'user-1')).toBeNull(); + expect(orientSalaryRequest(undefined, 'user-1')).toBeUndefined(); + }); + + it('returns the request unchanged when the user person number is missing', () => { + expect(orientSalaryRequest(baseRequest, null)).toEqual(baseRequest); + expect(orientSalaryRequest(baseRequest, undefined)).toEqual(baseRequest); + }); +}); diff --git a/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.ts b/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.ts new file mode 100644 index 0000000000..1ac3ecda0a --- /dev/null +++ b/src/components/HrTools/SalaryCalculator/Shared/orientSalaryRequest.ts @@ -0,0 +1,52 @@ +import type { + SalaryRequest, + SalaryRequestCalculations, +} from 'src/graphql/types.generated'; + +type SwappableSalaryRequest = Partial< + Pick< + SalaryRequest, + | 'personNumber' + | 'salary' + | 'spouseSalary' + | 'mhaAmount' + | 'spouseMhaAmount' + | 'salaryCap' + | 'spouseSalaryCap' + > +> & { + calculations?: Partial | null; + spouseCalculations?: Partial | null; +}; + +/** + * The database stores salary requests from the perspective of the person who created it. This + * helper detects when the request was created by the spouse and swaps the fields to be from the + * user's perspective. + */ +export const orientSalaryRequest = ( + request: Request | null | undefined, + userPersonNumber: string | null | undefined, +): Request | null | undefined => { + if ( + !request?.personNumber || + !userPersonNumber || + request.personNumber === userPersonNumber + ) { + return request; + } + + // The person number of the request doesn't match the user's person number, so swap the fields + return { + ...request, + personNumber: userPersonNumber, + salary: request.spouseSalary, + spouseSalary: request.salary, + mhaAmount: request.spouseMhaAmount, + spouseMhaAmount: request.mhaAmount, + salaryCap: request.spouseSalaryCap, + spouseSalaryCap: request.salaryCap, + calculations: request.spouseCalculations, + spouseCalculations: request.calculations, + }; +}; diff --git a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx index 2fa7f0d3c3..c0234cad04 100644 --- a/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx +++ b/src/components/HrTools/SalaryCalculator/Summary/SalarySummaryCard.tsx @@ -11,21 +11,17 @@ import { useTranslation } from 'react-i18next'; import { useEffectiveSalaryCalculationQuery } from '../SalaryCalculatorContext/SalaryCalculation.generated'; import { useSalaryCalculator } from '../SalaryCalculatorContext/SalaryCalculatorContext'; import { StepCard } from '../Shared/StepCard'; +import { orientSalaryRequest } from '../Shared/orientSalaryRequest'; import { useFormatters } from '../Shared/useFormatters'; export const SalarySummaryCard: React.FC = () => { const { t } = useTranslation(); const { hcmUser, hcmSpouse, calculation } = useSalaryCalculator(); const { data: effectiveData } = useEffectiveSalaryCalculationQuery(); - const oldCalculation = effectiveData?.salaryRequest; - const ownRequest = - oldCalculation?.personNumber === hcmUser?.staffInfo.personNumber; - const oldCalculations = ownRequest - ? oldCalculation?.calculations - : oldCalculation?.spouseCalculations; - const oldSpouseCalculations = ownRequest - ? oldCalculation?.spouseCalculations - : oldCalculation?.calculations; + const effectiveCalculation = orientSalaryRequest( + effectiveData?.salaryRequest, + hcmUser?.staffInfo.personNumber, + ); const { formatCurrency, formatFraction } = useFormatters(); return ( @@ -45,42 +41,38 @@ export const SalarySummaryCard: React.FC = () => { {hcmUser?.staffInfo.preferredName} - {oldCalculation && {t('Old')}} + {effectiveCalculation && ( + {t('Old')} + )} {t('New')} {t('Requested Salary')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency( - ownRequest - ? oldCalculation.salary - : oldCalculation.spouseSalary, - )} + {formatCurrency(effectiveCalculation.salary)} )} {formatCurrency(calculation?.salary)} {t('MHA')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency( - ownRequest - ? oldCalculation.mhaAmount - : oldCalculation.spouseMhaAmount, - )} + {formatCurrency(effectiveCalculation.mhaAmount)} )} {formatCurrency(calculation?.mhaAmount)} {t('403(b) Contribution')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatFraction(oldCalculations?.contributing403bFraction)} + {formatFraction( + effectiveCalculation.calculations.contributing403bFraction, + )} )} @@ -91,9 +83,11 @@ export const SalarySummaryCard: React.FC = () => { {t('Max Allowable Salary')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency(oldCalculations?.effectiveCap)} + {formatCurrency( + effectiveCalculation.calculations.effectiveCap, + )} )} @@ -110,7 +104,7 @@ export const SalarySummaryCard: React.FC = () => { {hcmSpouse?.staffInfo.preferredName} - {oldCalculation && ( + {effectiveCalculation && ( {t('Old')} )} {t('New')} @@ -119,13 +113,9 @@ export const SalarySummaryCard: React.FC = () => { {t('Requested Salary')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency( - ownRequest - ? oldCalculation.spouseSalary - : oldCalculation.salary, - )} + {formatCurrency(effectiveCalculation.spouseSalary)} )} @@ -134,13 +124,9 @@ export const SalarySummaryCard: React.FC = () => { {t('MHA')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency( - ownRequest - ? oldCalculation.spouseMhaAmount - : oldCalculation.mhaAmount, - )} + {formatCurrency(effectiveCalculation.spouseMhaAmount)} )} @@ -149,10 +135,11 @@ export const SalarySummaryCard: React.FC = () => { {t('403(b) Contribution')} - {oldCalculation && ( + {effectiveCalculation && ( {formatFraction( - oldSpouseCalculations?.contributing403bFraction, + effectiveCalculation.spouseCalculations + ?.contributing403bFraction, )} )} @@ -164,9 +151,11 @@ export const SalarySummaryCard: React.FC = () => { {t('Max Allowable Salary')} - {oldCalculation && ( + {effectiveCalculation && ( - {formatCurrency(oldSpouseCalculations?.effectiveCap)} + {formatCurrency( + effectiveCalculation.spouseCalculations?.effectiveCap, + )} )}