diff --git a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx index 1f508b8339..8355678e28 100644 --- a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx +++ b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.test.tsx @@ -17,7 +17,7 @@ describe('PdsGoalCard', () => { , ); - expect(await findByText('$849.44')).toBeInTheDocument(); + expect(await findByText('$8,073.02')).toBeInTheDocument(); }); it('builds the View link with the PDS goal calculator path', async () => { diff --git a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx index 70c2b32d80..14f74e1b03 100644 --- a/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx +++ b/src/components/HrTools/PdsGoalCalculator/GoalCard/PdsGoalCard.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { Chip } from '@mui/material'; import { useTranslation } from 'react-i18next'; import { GoalCard } from 'src/components/Reports/Shared/GoalCard/GoalCard'; @@ -10,10 +10,7 @@ import { useDeletePdsGoalCalculationMutation, } from '../GoalsList/PdsGoalCalculations.generated'; import { useHcmUserQuery } from '../Shared/HCM.generated'; -import { - buildPdsGoalConstants, - calculatePdsGoalTotal, -} from '../calculations/calculatePdsGoalTotal'; +import { usePdsSummaryData } from '../calculations/usePdsSummaryData'; export interface PdsGoalCardProps { goal: PdsGoalCalculationFieldsFragment; @@ -24,23 +21,12 @@ export const PdsGoalCard: React.FC = ({ goal }) => { const accountListId = useAccountListId() ?? ''; const [deletePdsGoalCalculation] = useDeletePdsGoalCalculationMutation(); - const { - goalMiscConstants, - goalGeographicConstantMap, - loading: constantsLoading, - } = useGoalCalculatorConstants(); + const { loading: constantsLoading } = useGoalCalculatorConstants(); const { data: hcmData, loading: hcmLoading } = useHcmUserQuery(); const hcmUser = hcmData?.hcm[0]; - const goalTotal = useMemo(() => { - const constants = buildPdsGoalConstants( - goalMiscConstants, - goalGeographicConstantMap, - goal.geographicLocation, - hcmUser?.fourOThreeB, - ); - return constants ? calculatePdsGoalTotal(goal, constants) : 0; - }, [goal, goalMiscConstants, goalGeographicConstantMap, hcmUser]); + const summaryData = usePdsSummaryData(goal, hcmUser); + const goalTotal = summaryData?.overallTotal ?? 0; const formType = goal.formType ?? DesignationSupportFormType.Detailed; const formTypeBadge = diff --git a/src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx b/src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx index ed9f228020..dfa4f67b90 100644 --- a/src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx +++ b/src/components/HrTools/PdsGoalCalculator/PdsGoalCalculatorTestWrapper.tsx @@ -194,15 +194,15 @@ export const PdsGoalCalculatorTestWrapper: React.FC< mpdGoalGeographicConstants: [ { location: 'None', - percentageMultiplier: 0, + percentageMultiplier: 1, }, { location: 'Orlando, FL', - percentageMultiplier: 0.06, + percentageMultiplier: 1.06, }, { location: 'New York, NY', - percentageMultiplier: 0.12, + percentageMultiplier: 1.12, }, ], mpdGoalMiscConstants: [ diff --git a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.test.tsx b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.test.tsx index 20618995e1..4fc5791113 100644 --- a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.test.tsx +++ b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.test.tsx @@ -45,11 +45,11 @@ describe('AnnualReimbursableSection', () => { expect(getAllByRole('row')).toHaveLength(5); }); - it('renders the info tooltip icon with an accessible label', async () => { - const { findByLabelText } = render(); + it('renders the subtitle describing the annual calculation', async () => { + const { findByText } = render(); expect( - await findByLabelText( + await findByText( 'This annual amount will be divided by 12 when added to the total.', ), ).toBeInTheDocument(); diff --git a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.tsx b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.tsx index fa98617825..41ab391822 100644 --- a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.tsx +++ b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/AnnualReimbursableSection.tsx @@ -30,7 +30,7 @@ export const AnnualReimbursableSection: React.FC = () => { return ( = ({ subtotalValue = 0 }) => ( +const TestComponent: React.FC = ({ + subtotalValue = 0, + subTitle, +}) => ( = ({ subtotalValue = 0 }) => ( > { expect(getAllByRole('row')).toHaveLength(4); }); + it('renders the subtitle when provided', async () => { + const { findByText } = render( + , + ); + + expect(await findByText('Test subtitle text')).toBeInTheDocument(); + }); + it('clears the error after a subsequent valid edit', async () => { const { findByRole, queryByRole } = render(); diff --git a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/ReimbursableExpensesGrid.tsx b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/ReimbursableExpensesGrid.tsx index 6f0e885744..c82689e44a 100644 --- a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/ReimbursableExpensesGrid.tsx +++ b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/ReimbursableExpensesGrid.tsx @@ -1,11 +1,9 @@ import React, { useState } from 'react'; -import InfoIcon from '@mui/icons-material/Info'; import { Box, Card, FormHelperText, Stack, - Tooltip, Typography, styled, } from '@mui/material'; @@ -34,7 +32,7 @@ interface ReimbursableRow { interface ReimbursableExpensesGridProps { title: string; - titleTooltip?: string; + subTitle?: string; fields: ReimbursableField[]; subtotalLabel: string; subtotalValue: number; @@ -58,7 +56,7 @@ export const ReimbursableExpensesGrid: React.FC< ReimbursableExpensesGridProps > = ({ title, - titleTooltip, + subTitle, fields, subtotalLabel, subtotalValue, @@ -162,13 +160,11 @@ export const ReimbursableExpensesGrid: React.FC< return ( <> - + {title} - {titleTooltip && ( - - - - )} + + {subTitle} + { ).toBeInTheDocument(); }); - it('renders the info icon with an accessible label', async () => { - const { findByLabelText } = render(); + it('renders the subtitle describing the $300 minimum floor', async () => { + const { findByText } = render(); expect( - await findByLabelText('Total reimbursable information'), + await findByText( + 'The total is the greater of the $300 minimum or your calculated amount.', + ), ).toBeInTheDocument(); }); - it('shows a tooltip describing the $300 minimum floor on hover', async () => { - const { findByLabelText, findByRole } = render(); - - userEvent.hover(await findByLabelText('Total reimbursable information')); - - expect(await findByRole('tooltip')).toHaveTextContent( - 'The total is the greater of the $300 minimum or your calculated amount.', - ); - }); - it('applies the $300 floor when the calculated amount is below it', async () => { const { getByTestId } = render( , diff --git a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/TotalReimbursableSection.tsx b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/TotalReimbursableSection.tsx index 846a2ca8ba..dedc5677b9 100644 --- a/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/TotalReimbursableSection.tsx +++ b/src/components/HrTools/PdsGoalCalculator/ReimbursableExpenses/TotalReimbursableSection.tsx @@ -1,12 +1,4 @@ -import InfoIcon from '@mui/icons-material/Info'; -import { - Card, - CardContent, - Stack, - Tooltip, - Typography, - styled, -} from '@mui/material'; +import { Card, CardContent, Stack, Typography, styled } from '@mui/material'; import { useTranslation } from 'react-i18next'; import { useLocale } from 'src/hooks/useLocale'; import { currencyFormat } from 'src/lib/intlFormat'; @@ -36,23 +28,18 @@ export const TotalReimbursableSection: React.FC = () => { return ( - + {t('Total Reimbursable Expenses')} - + {t( 'The total is the greater of the {{floor}} minimum or your calculated amount.', { floor: currencyFormat(REIMBURSABLE_FLOOR, 'USD', locale), }, )} - > - - + {currencyFormat(total, 'USD', locale)} diff --git a/src/components/HrTools/PdsGoalCalculator/SupportItem/otherBreakdown.tsx b/src/components/HrTools/PdsGoalCalculator/SupportItem/otherBreakdown.tsx index 7937838ce1..fd6208f74c 100644 --- a/src/components/HrTools/PdsGoalCalculator/SupportItem/otherBreakdown.tsx +++ b/src/components/HrTools/PdsGoalCalculator/SupportItem/otherBreakdown.tsx @@ -126,9 +126,15 @@ export const buildOtherBreakdownRows = ( { id: 'assessment', category: t('Assessment'), - formula: t('(Subtotal + Credit Card Fees + Attrition) × {{rate}}', { - rate: percentageFormat(constants.adminRate, locale), - }), + formula: t( + '(Subtotal + Attrition + Credit Card Fees) ÷ {{divisor}} − (Subtotal + Attrition + Credit Card Fees)', + { + divisor: new Intl.NumberFormat(locale, { + minimumFractionDigits: 2, + maximumFractionDigits: 4, + }).format(1 - constants.adminRate), + }, + ), amount: totals.assessment, testId: 'other-assessment', bold: true, diff --git a/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.test.tsx b/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.test.tsx index 41081d08eb..c12a0a874e 100644 --- a/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.test.tsx +++ b/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.test.tsx @@ -49,7 +49,6 @@ describe('buildSalaryBreakdownRows', () => { expect(rows.map((row) => row.id)).toEqual([ 'pay-rate', 'monthly-base', - 'geographic-multiplier', 'gross-monthly-pay', 'employer-fica', 'total', @@ -69,8 +68,6 @@ describe('buildSalaryBreakdownRows', () => { expect(byId['pay-rate']).toBe(60000); // monthlyBase = 60000 / 12 = 5000 expect(byId['monthly-base']).toBe(5000); - // geographicMultiplier passed through from constants - expect(byId['geographic-multiplier']).toBe(0); // grossMonthlyPay = 5000 * (1 + 0) = 5000 expect(byId['gross-monthly-pay']).toBe(5000); // employerFica = 5000 * 0.08 = 400 @@ -113,7 +110,6 @@ describe('buildSalaryBreakdownRows', () => { 'pay-rate', 'hours-per-week', 'monthly-base', - 'geographic-multiplier', 'gross-monthly-pay', 'employer-fica', 'total', diff --git a/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.tsx b/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.tsx index cd8138c7bc..80c3330382 100644 --- a/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.tsx +++ b/src/components/HrTools/PdsGoalCalculator/SupportItem/salaryBreakdown.tsx @@ -73,16 +73,12 @@ export const buildSalaryBreakdownRows = ( amount: monthlyBase, format: 'currency', }, - { - id: 'geographic-multiplier', - category: t('Geographic Multiplier'), - amount: geographicMultiplier, - format: 'percentage', - }, { id: 'gross-monthly-pay', category: t('Gross Monthly Pay'), - formula: t('Monthly Base × (1 + Geographic Multiplier)'), + formula: t('Monthly Base × {{rate}}', { + rate: percentageFormat(1 + geographicMultiplier, locale), + }), amount: grossMonthlyPay, format: 'currency', testId: 'gross-monthly-pay', diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.test.ts b/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.test.ts index bc6f01250f..485fdf43b5 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.test.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.test.ts @@ -137,19 +137,18 @@ describe('calculateOtherExpenses', () => { }); describe('credit card fees', () => { - it('is 6% of (subtotal + attrition)', () => { + it('grosses up (subtotal + attrition) so that fees are `creditCardFeeRate` of the post-fees total', () => { const result = calculateOtherExpenses(fullTime(), defaultConstants); - // (7400 + 444) * 0.06 - expect(result.creditCardFees).toBeCloseTo(470.64); + // (7400 + 444) / (1 - 0.06) - (7400 + 444) ≈ 500.68 + expect(result.creditCardFees).toBeCloseTo(500.68); }); }); describe('assessment', () => { - it('is (subtotal + creditCardFees + attrition) × adminRate', () => { + it('grosses up (subtotal + creditCardFees + attrition) so that admin is `adminRate` of the post-admin total', () => { const result = calculateOtherExpenses(fullTime(), defaultConstants); - // subtotal=7400, attrition=444, creditCardFees=470.64 - // (7400 + 470.64 + 444) * 0.12 ≈ 997.76 - expect(result.assessment).toBeCloseTo(997.76, 1); + // adminBase=7400+500.68+444=8344.68; assessment = adminBase/0.88 - adminBase ≈ 1137.91 + expect(result.assessment).toBeCloseTo(1137.91, 1); }); it('returns 0 when adminRate is 0', () => { @@ -170,8 +169,8 @@ describe('calculateOtherExpenses', () => { expect(result.benefits).toBe(1500); expect(result.subtotal).toBeCloseTo(7400); expect(result.attrition).toBeCloseTo(444); - expect(result.creditCardFees).toBeCloseTo(470.64); - expect(result.assessment).toBeCloseTo(997.76, 1); + expect(result.creditCardFees).toBeCloseTo(500.68); + expect(result.assessment).toBeCloseTo(1137.91, 1); }); it('produces correct totals for a part-time employee', () => { @@ -179,12 +178,13 @@ describe('calculateOtherExpenses', () => { // reimbursable=500, 403b=400, workComp=4000*0.17=680, benefits=0 // subtotal=5000+500+400+680+0=6580 // attrition=6580*0.06=394.80 - // creditCardFees=(6580+394.80)*0.06=418.49 - // assessment=(6580+418.49+394.80)*0.12≈887.19 + // creditCardFees=(6580+394.80)/(1-0.06)-(6580+394.80)≈445.20 + // adminBase=6580+445.20+394.80=7420 + // assessment = adminBase/0.88 - adminBase ≈ 1011.82 expect(result.subtotal).toBeCloseTo(6580); expect(result.attrition).toBeCloseTo(394.8); - expect(result.creditCardFees).toBeCloseTo(418.49, 1); - expect(result.assessment).toBeCloseTo(887.19, 1); + expect(result.creditCardFees).toBeCloseTo(445.2, 1); + expect(result.assessment).toBeCloseTo(1011.82, 1); }); }); }); diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.ts b/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.ts index c9240ae34b..15068d8a77 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/OtherExpenses.ts @@ -54,9 +54,13 @@ export const calculateOtherExpenses = ( benefits; const attrition = subtotal * constants.attritionRate; - const creditCardFees = (subtotal + attrition) * constants.creditCardFeeRate; - const adminRate = constants.adminRate; - const assessment = (subtotal + creditCardFees + attrition) * adminRate; + const creditCardFees = + (subtotal + attrition) / (1 - constants.creditCardFeeRate) - + (subtotal + attrition); + const adminBase = subtotal + creditCardFees + attrition; + // Admin assessment is `adminRate` of the post-admin total, not a markup on + // `adminBase`, so gross up: assessment / (adminBase + assessment) = adminRate. + const assessment = adminBase / (1 - constants.adminRate) - adminBase; return { reimbursableExpenses, diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/buildPdsGoalConstants.test.ts b/src/components/HrTools/PdsGoalCalculator/calculations/buildPdsGoalConstants.test.ts index 1a398f7b1e..939def989b 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/buildPdsGoalConstants.test.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/buildPdsGoalConstants.test.ts @@ -2,7 +2,7 @@ import { GoalGeographicConstantMap, GoalMiscConstants, } from 'src/hooks/useGoalCalculatorConstants'; -import { buildPdsGoalConstants } from './calculatePdsGoalTotal'; +import { buildPdsGoalConstants } from './pdsGoalConstants'; const makeConstant = (fee: number) => ({ fee, @@ -108,7 +108,7 @@ describe('buildPdsGoalConstants', () => { expect(result).toBeNull(); }); - it('returns geographicMultiplier of 0 for unknown location', () => { + it('defaults geographicMultiplier to 0 (no adjustment) for unknown location', () => { const result = buildPdsGoalConstants( buildMiscConstants(), defaultGeoMap, @@ -119,7 +119,7 @@ describe('buildPdsGoalConstants', () => { expect(result?.geographicMultiplier).toBe(0); }); - it('returns geographicMultiplier of 0 when location is null', () => { + it('defaults geographicMultiplier to 0 (no adjustment) when location is null', () => { const result = buildPdsGoalConstants( buildMiscConstants(), defaultGeoMap, @@ -130,7 +130,7 @@ describe('buildPdsGoalConstants', () => { expect(result?.geographicMultiplier).toBe(0); }); - it('returns geographicMultiplier of 0 when location is undefined', () => { + it('defaults geographicMultiplier to 0 (no adjustment) when location is undefined', () => { const result = buildPdsGoalConstants( buildMiscConstants(), defaultGeoMap, diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.test.ts b/src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.test.ts deleted file mode 100644 index d5c613222a..0000000000 --- a/src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { - DesignationSupportFormType, - DesignationSupportSalaryType, - DesignationSupportStatus, -} from 'src/graphql/types.generated'; -import { - PdsGoalTotalConstants, - PdsGoalTotalFields, - calculatePdsGoalTotal, -} from './calculatePdsGoalTotal'; - -const defaultConstants: PdsGoalTotalConstants = { - employerFicaRate: 0.08, - workCompPercentage: 0.17, - attritionRate: 0.06, - creditCardFeeRate: 0.06, - adminRate: 0.12, - fourOThreeBPercentage: 0.1, - geographicMultiplier: 0, -}; - -const makeGoal = ( - overrides: Partial = {}, -): PdsGoalTotalFields => ({ - hoursWorkedPerWeek: null, - salaryOrHourly: DesignationSupportSalaryType.Salaried, - status: DesignationSupportStatus.FullTime, - payRate: 60000, - benefits: 1500, - geographicLocation: null, - ministryCellPhone: 50, - ministryInternet: 50, - mpdNewsletter: 50, - mpdMiscellaneous: 50, - accountTransfers: 50, - otherMonthlyReimbursements: 50, - conferenceRetreatCosts: 0, - ministryTravelMeals: 0, - otherAnnualReimbursements: 0, - ...overrides, -}); - -describe('calculatePdsGoalTotal', () => { - it('computes the final assessment for a full-time salaried employee', () => { - const goal = makeGoal(); - const result = calculatePdsGoalTotal(goal, defaultConstants); - // assessment ≈ 1038.21 - expect(result).toBeCloseTo(1038.21, 1); - }); - - it('computes the final assessment for a part-time hourly employee', () => { - const goal = makeGoal({ - status: DesignationSupportStatus.PartTime, - salaryOrHourly: DesignationSupportSalaryType.Hourly, - payRate: 25, - hoursWorkedPerWeek: 20, - benefits: null, - }); - const result = calculatePdsGoalTotal(goal, defaultConstants); - expect(result).toBeCloseTo(434.83, 0); - }); - - it('returns a positive value when payRate is null', () => { - const goal = makeGoal({ payRate: null }); - const result = calculatePdsGoalTotal(goal, defaultConstants); - expect(result).toBeGreaterThan(0); - }); - - it('applies geographic multiplier', () => { - const goal = makeGoal(); - const withGeo = calculatePdsGoalTotal(goal, { - ...defaultConstants, - geographicMultiplier: 0.06, - }); - const withoutGeo = calculatePdsGoalTotal(goal, defaultConstants); - expect(withGeo).toBeGreaterThan(withoutGeo); - }); - - it('excludes reimbursable expenses and 403b when formType is Simple', () => { - const baseline = calculatePdsGoalTotal( - makeGoal({ formType: DesignationSupportFormType.Simple }), - { ...defaultConstants, fourOThreeBPercentage: 0.1 }, - ); - - const withDifferentReimbursables = calculatePdsGoalTotal( - makeGoal({ - formType: DesignationSupportFormType.Simple, - ministryCellPhone: 9999, - otherAnnualReimbursements: 9999, - }), - { ...defaultConstants, fourOThreeBPercentage: 0.1 }, - ); - expect(withDifferentReimbursables).toBeCloseTo(baseline); - - const withDifferent403b = calculatePdsGoalTotal( - makeGoal({ formType: DesignationSupportFormType.Simple }), - { ...defaultConstants, fourOThreeBPercentage: 0.5 }, - ); - expect(withDifferent403b).toBeCloseTo(baseline); - }); - - it('includes reimbursable expenses and 403b when formType is Detailed', () => { - const baseline = calculatePdsGoalTotal( - makeGoal({ formType: DesignationSupportFormType.Detailed }), - { ...defaultConstants, fourOThreeBPercentage: 0.1 }, - ); - - const withDifferentReimbursables = calculatePdsGoalTotal( - makeGoal({ - formType: DesignationSupportFormType.Detailed, - ministryCellPhone: 9999, - otherAnnualReimbursements: 9999, - }), - { ...defaultConstants, fourOThreeBPercentage: 0.1 }, - ); - expect(withDifferentReimbursables).toBeGreaterThan(baseline); - - const withDifferent403b = calculatePdsGoalTotal( - makeGoal({ formType: DesignationSupportFormType.Detailed }), - { ...defaultConstants, fourOThreeBPercentage: 0.5 }, - ); - expect(withDifferent403b).toBeGreaterThan(baseline); - }); - - it('treats null formType the same as Detailed (legacy goals)', () => { - const legacy = makeGoal({ formType: null }); - const detailed = makeGoal({ - formType: DesignationSupportFormType.Detailed, - }); - expect(calculatePdsGoalTotal(legacy, defaultConstants)).toBeCloseTo( - calculatePdsGoalTotal(detailed, defaultConstants), - ); - }); -}); diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.ts b/src/components/HrTools/PdsGoalCalculator/calculations/pdsGoalConstants.ts similarity index 70% rename from src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.ts rename to src/components/HrTools/PdsGoalCalculator/calculations/pdsGoalConstants.ts index 36e9aad328..360d557f91 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/calculatePdsGoalTotal.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/pdsGoalConstants.ts @@ -4,24 +4,8 @@ import { GoalMiscConstants, } from 'src/hooks/useGoalCalculatorConstants'; import { HcmUserQuery } from '../Shared/HCM.generated'; -import { - OtherExpensesConstants, - OtherExpensesFields, - calculateOtherExpenses, -} from './OtherExpenses'; -import { - ReimbursableCalculationFields, - calculateReimbursableTotals, -} from './reimbursableExpenses'; -import { - SalaryCalculationFields, - SalaryTotals, - calculateSalaryTotals, -} from './salaryCalculation'; - -export type PdsGoalTotalFields = SalaryCalculationFields & - ReimbursableCalculationFields & - OtherExpensesFields; +import { OtherExpensesConstants } from './OtherExpenses'; +import { SalaryTotals } from './salaryCalculation'; export interface PdsGoalTotalConstants { employerFicaRate: number; @@ -59,7 +43,6 @@ export const buildPdsGoalConstants = ( ) { return null; } - const geographicMultiplier = goalGeographicConstantMap.get(geographicLocation ?? '') ?? 0; @@ -96,27 +79,3 @@ export const buildOtherExpensesConstants = ( adminRate: constants.adminRate, }; }; - -export const calculatePdsGoalTotal = ( - calculation: PdsGoalTotalFields, - constants: PdsGoalTotalConstants, -): number => { - const salaryTotals = calculateSalaryTotals(calculation, { - geographicMultiplier: constants.geographicMultiplier, - employerFicaRate: constants.employerFicaRate, - }); - - const reimbursableTotal = calculateReimbursableTotals(calculation).total; - - const otherExpenses = calculateOtherExpenses( - calculation, - buildOtherExpensesConstants( - calculation.formType ?? DesignationSupportFormType.Detailed, - constants, - salaryTotals, - reimbursableTotal, - ), - ); - - return otherExpenses.assessment; -}; diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/salaryCalculation.test.ts b/src/components/HrTools/PdsGoalCalculator/calculations/salaryCalculation.test.ts index c78088deed..4f3d2cabd6 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/salaryCalculation.test.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/salaryCalculation.test.ts @@ -39,12 +39,12 @@ describe('calculateSalaryTotals', () => { expect(result.grossMonthlyPay).toBe(5000); }); - it('applies geographic multiplier additively', () => { + it('applies geographic multiplier as a delta to the monthly base', () => { const result = calculateSalaryTotals(salaried(), { geographicMultiplier: GEO_MULTIPLIER, employerFicaRate: FICA_RATE, }); - // (60000 / 12) * 1.06 + // (60000 / 12) * (1 + 0.06) expect(result.grossMonthlyPay).toBeCloseTo(5300); }); @@ -72,7 +72,7 @@ describe('calculateSalaryTotals', () => { geographicMultiplier: GEO_MULTIPLIER, employerFicaRate: FICA_RATE, }); - // (25 * 40 * 52 / 12) * 1.06 + // (25 * 40 * 52 / 12) * (1 + 0.06) expect(result.grossMonthlyPay).toBeCloseTo(4593.333, 2); }); }); diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.test.ts b/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.test.ts index 9e58409a7c..d5bfc1e250 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.test.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.test.ts @@ -17,10 +17,6 @@ import { PdsGoalCalculationFieldsFragmentDoc, } from '../GoalsList/PdsGoalCalculations.generated'; import { HcmUserDocument, HcmUserQuery } from '../Shared/HCM.generated'; -import { - PdsGoalTotalConstants, - calculatePdsGoalTotal, -} from './calculatePdsGoalTotal'; import { usePdsSummaryData } from './usePdsSummaryData'; jest.mock('src/hooks/useGoalCalculatorConstants'); @@ -216,14 +212,14 @@ describe('usePdsSummaryData', () => { expect(result.current?.geographicMultiplier).toBe(GEO_MULTIPLIER); }); - it('defaults to 0 when geographicLocation is null', () => { + it('defaults to 0 (no adjustment) when geographicLocation is null', () => { const { result } = renderHook(() => usePdsSummaryData(defaultCalculation, defaultHcmUser), ); expect(result.current?.geographicMultiplier).toBe(0); }); - it('defaults to 0 when geographicLocation is not in the map', () => { + it('defaults to 0 (no adjustment) when geographicLocation is not in the map', () => { const calc = { ...defaultCalculation, geographicLocation: 'Unknown City', @@ -300,8 +296,8 @@ describe('usePdsSummaryData', () => { }); it('computes correct overallTotal for a full-time salaried employee', () => { - // No geographic multiplier, payRate = 60000 - // grossMonthlyPay = 60000 / 12 = 5000 + // Geographic multiplier defaults to 0 (no adjustment), payRate = 60000 + // grossMonthlyPay = 60000 / 12 * (1 + 0) = 5000 // employerFica = 5000 * 0.08 = 400 // salarySubtotal = 5400 // @@ -313,13 +309,14 @@ describe('usePdsSummaryData', () => { // workComp = 0 (full-time) // otherSubtotal = 5400 + 500 + 400 + 0 + 1500 = 7800 // attrition = 7800 * 0.06 = 468 - // creditCardFees = (7800 + 468) * 0.06 = 496.08 - // assessment = (7800 + 468 + 496.08) * 0.12 = 1051.69 - // overallTotal = 7800 + 468 + 496.08 + 1051.69 = 9815.77 + // creditCardFees = (7800 + 468) / (1 - 0.06) - (7800 + 468) ≈ 527.74 + // adminBase = 7800 + 468 + 527.74 ≈ 8795.74 + // assessment = adminBase / 0.88 - adminBase ≈ 1199.42 + // overallTotal = 7800 + 468 + 527.74 + 1199.42 ≈ 9995.16 const { result } = renderHook(() => usePdsSummaryData(defaultCalculation, defaultHcmUser), ); - expect(result.current?.overallTotal).toBeCloseTo(9815.77, 0); + expect(result.current?.overallTotal).toBeCloseTo(9995.16, 0); }); }); @@ -388,34 +385,4 @@ describe('usePdsSummaryData', () => { ); }); }); - - describe('consistency with calculatePdsGoalTotal', () => { - // Mirrors what buildPdsGoalConstants would derive from the mocked - // useGoalCalculatorConstants + defaultHcmUser, so we can call - // calculatePdsGoalTotal directly without the hook. - const directConstants: PdsGoalTotalConstants = { - employerFicaRate: EMPLOYER_FICA_RATE, - workCompPercentage: WORK_COMP_PERCENTAGE, - attritionRate: ATTRITION_RATE, - creditCardFeeRate: CREDIT_CARD_FEE_RATE, - adminRate: ADMIN_RATE, - fourOThreeBPercentage: 0.08, - geographicMultiplier: 0, - }; - - it.each([ - DesignationSupportFormType.Detailed, - DesignationSupportFormType.Simple, - ])( - 'calculatePdsGoalTotal matches usePdsSummaryData.otherTotals.assessment when formType is %s', - (formType) => { - const calc = { ...defaultCalculation, formType }; - const { result } = renderHook(() => - usePdsSummaryData(calc, defaultHcmUser), - ); - const direct = calculatePdsGoalTotal(calc, directConstants); - expect(direct).toBeCloseTo(result.current!.otherTotals.assessment, 5); - }, - ); - }); }); diff --git a/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.ts b/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.ts index 0dc642c228..9e4ff50f8b 100644 --- a/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.ts +++ b/src/components/HrTools/PdsGoalCalculator/calculations/usePdsSummaryData.ts @@ -11,7 +11,7 @@ import { import { buildOtherExpensesConstants, buildPdsGoalConstants, -} from './calculatePdsGoalTotal'; +} from './pdsGoalConstants'; import { ReimbursableTotals, calculateReimbursableTotals, diff --git a/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx b/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx index 540cf55729..c442284174 100644 --- a/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx +++ b/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.test.tsx @@ -138,7 +138,7 @@ describe('DirectionButtons', () => { userEvent.hover(continueButton.parentElement!); expect( - await findByText('Complete all required fields to continue'), + await findByText('Complete all fields to continue'), ).toBeInTheDocument(); }); @@ -152,7 +152,7 @@ describe('DirectionButtons', () => { await waitFor(() => { expect( - queryByText('Complete all required fields to continue'), + queryByText('Complete all fields to continue'), ).not.toBeInTheDocument(); }); }); diff --git a/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx b/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx index b4266f250c..d93eaf700b 100644 --- a/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx +++ b/src/components/HrTools/Shared/CalculationReports/DirectionButtons/DirectionButtons.tsx @@ -142,9 +142,7 @@ export const DirectionButtons: React.FC = ({ ) : (