Skip to content
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
9 changes: 5 additions & 4 deletions .claude/rules/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ MPDX displays and calculates donation/partner-giving aggregations across dozens
**Trigger conditions:**

- Any file under `src/components/Reports/**`
- Any file under `src/components/Reports/GoalCalculator/**` or `src/components/Reports/PdsGoalCalculator/**`
- Any file under `src/components/Reports/SalaryCalculator/**`
- Any file under `src/components/HrTools/**`
- Any file under `src/components/Reports/GoalCalculator/**`, `src/components/HrTools/GoalCalculator/**`, or `src/components/HrTools/PdsGoalCalculator/**`
- Any file under `src/components/Reports/SalaryCalculator/**` or `src/components/HrTools/SalaryCalculator/**`
- Any file under `src/components/EditDonationModal/**`
- Any file under `src/components/Reports/AdditionalSalaryRequest/**`, `src/components/Reports/MinisterHousingAllowance/**`
- Any file under `src/components/Reports/AdditionalSalaryRequest/**`, `src/components/HrTools/AdditionalSalaryRequest/**`, or `src/components/HrTools/MinisterHousingAllowance/**`
- Any file under `src/components/Dashboard/MonthlyGoal/**`, `src/components/Dashboard/DonationHistories/**`
- Diff content contains any of: `amount`, `currency`, `convertedAmount`, `pledgeAmount`, `goal`, `balance`, `total`, `sum(`, `reduce((`, `.toFixed(`, `Math.round(`, `Number(`, `parseFloat(`, `parseInt(` inside `src/components/Reports/**` or goal/donation components
- Diff content contains any of: `amount`, `currency`, `convertedAmount`, `pledgeAmount`, `goal`, `balance`, `total`, `sum(`, `reduce((`, `.toFixed(`, `Math.round(`, `Number(`, `parseFloat(`, `parseInt(` inside `src/components/Reports/**`, `src/components/HrTools/**`, or other goal/donation components

**Focus areas:**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,51 @@ describe('buildOtherBreakdownRows', () => {
]);
});

it('renders the credit-card-fees formula with the configured fee rate, not a rounded percent', () => {
const fractionalRateConstants: OtherExpensesConstants = {
...constants,
creditCardFeeRate: 0.006,
};

const rows = buildOtherBreakdownRows(
fullTimeCalculation,
fractionalRateConstants,
'en-US',
Comment thread
wjames111 marked this conversation as resolved.
i18n.t,
);

const creditCardFees = rows.find((row) => row.id === 'credit-card-fees');
expect(creditCardFees?.formula).toBe(
'(Subtotal + Attrition) ÷ (1 - 0.60%) - (Subtotal + Attrition)',
Comment thread
wjames111 marked this conversation as resolved.
);
});

it('renders the attrition formula with the rate as a decimal value', () => {
const rows = buildOtherBreakdownRows(
fullTimeCalculation,
{ ...constants, attritionRate: 0.06 },
'en-US',
i18n.t,
);

const attrition = rows.find((row) => row.id === 'attrition');
expect(attrition?.formula).toBe('Subtotal × 0.06%');
});

it('renders the assessment formula with the admin rate as a decimal value', () => {
const rows = buildOtherBreakdownRows(
fullTimeCalculation,
{ ...constants, adminRate: 0.12 },
'en-US',
i18n.t,
);

const assessment = rows.find((row) => row.id === 'assessment');
expect(assessment?.formula).toBe(
'(Subtotal + Attrition + Credit Card Fees) ÷ (1 − 0.12%) − (Subtotal + Attrition + Credit Card Fees)',
);
});

it('uses a subtotal formula without reimbursable/403b in Simple form', () => {
const simpleCalculation: OtherExpensesFields = {
...fullTimeCalculation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
DesignationSupportFormType,
DesignationSupportStatus,
} from 'src/graphql/types.generated';
import { currencyFormat, percentageFormat } from 'src/lib/intlFormat';
import {
currencyFormat,
numberFormat,
percentageFormat,
} from 'src/lib/intlFormat';
import {
OtherExpensesConstants,
OtherExpensesFields,
Expand Down Expand Up @@ -106,33 +110,41 @@
{
id: 'attrition',
category: t('Attrition'),
formula: t('Subtotal × {{rate}}', {
rate: percentageFormat(constants.attritionRate, locale),
formula: t('Subtotal × {{rate}}%', {
rate: numberFormat(constants.attritionRate, locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 4,
}),
}),
amount: totals.attrition,
testId: 'other-attrition',
bold: true,
},
{
id: 'credit-card-fees',
category: t('Credit Card Fees'),
formula: t(
'(Subtotal + Attrition) ÷ (1 - {{rate}}) - (Subtotal + Attrition)',
{
rate: percentageFormat(constants.creditCardFeeRate, locale),
rate: percentageFormat(constants.creditCardFeeRate, locale, {
fractionDigits: 2,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] **Sub-basis-point precision is truncated (FYI)** — `fractionDigits: 2` will round anything below 0.005% to `"0.00%"`. For typical credit-card fee rates this is the right trade-off (and is exactly the project's "round at the display boundary" rule), but worth noting that if PDS ever configures rates with more than basis-point precision, the displayed rate won't exactly match the un-rounded value used in the `amount` calculation. No action needed.

}),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Medium] **Cross-rate consistency** — 3 agents (Architecture, UX, Financial Reporting) all flag the same point: the sibling `attritionRate` (line 110) and `adminRate` (line 137) still call `percentageFormat` without `{ fractionDigits: 2 }`. They share the same upstream shape (`fee: Float!` in `schema.graphql`) and the same source in `pdsGoalConstants.ts`, so the same misrepresentation bug class can recur there (e.g., a 6.5% attrition rate would render as `7%`). The sibling `PdsSummaryTable.tsx:69` already standardizes on 2-decimal precision for PDS rates.

Out of scope for MPDX-9617 (the ticket is scoped to Credit Card Fees), but recommended as a small follow-up to close the bug class. If you keep the scope tight, a tiny helper at the top of the file would prevent option-object duplication when you do extend it:

const formatRate = (value: number, locale: string) =>
  percentageFormat(value, locale, { fractionDigits: 2 });

},
),
amount: totals.creditCardFees,
testId: 'other-credit-card-fees',
bold: true,
},
{
id: 'assessment',
category: t('Assessment'),
formula: t(
'(Subtotal + Attrition + Credit Card Fees) ÷ (1 − {{rate}}) − (Subtotal + Attrition + Credit Card Fees)',
'(Subtotal + Attrition + Credit Card Fees) ÷ (1 − {{rate}}%) − (Subtotal + Attrition + Credit Card Fees)',
{
rate: percentageFormat(constants.adminRate, locale),
rate: numberFormat(constants.adminRate, locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 4,
}),

Check warning on line 147 in src/components/HrTools/PdsGoalCalculator/SupportItem/otherBreakdown.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ Getting worse: Large Method

buildOtherBreakdownRows increases from 108 to 116 lines of code, threshold = 100. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.
},
),
amount: totals.assessment,
Expand Down
Loading