[MPDX-9539] Use approval reason from server#1773
Conversation
|
Preview branch generated at https://9539-approval-reason.d3dytjb8adxkk5.amplifyapp.com |
Bundle sizes [mpdx-react]Compared against 7222699 No significant changes found |
069fadb to
960a3f2
Compare
960a3f2 to
4d875b2
Compare
canac
left a comment
There was a problem hiding this comment.
Multi-Agent Code Review — PR #1773
Mode: standard (6 specialized agents + gap review)
Verdict: APPROVED_WITH_SUGGESTIONS
Risk Score: 10/10 — formula-capped; actual blast radius is small (display-logic refactor)
No critical or high-priority blockers. 5 Important findings (severity 7.0–7.9) are strongly recommended before merge — these cannot be dismissed via /dismiss (severity ≥ 7), so please either address them in this PR or open follow-up tickets.
Summary by agent
| Agent | Critical | High | Important | Medium / Suggestions | Confidence |
|---|---|---|---|---|---|
| Architecture | 0 | 0 | 2 | 3 | M |
| Data Integrity | 0 | 0 | 2 | 3 | H |
| Testing | 0 | 0 | 2 | 4 | H |
| UX | 0 | 0 | 2 | 4 | H/M |
| Standards | 0 | 0 | 0 | 0 | H — all checklist items PASS |
| Financial | 0 | 0 | 1 | 2 | M/H |
| Gap Review | 0 | 0 | 0 | 2 | — |
Cross-cutting observations
{{approvers}}vs{{approver}}(plural vs singular):useSubmitDialogContent.ts:43uses{{approvers}}whileReceipt.tsx,RequestSummaryCard.tsx, andgetCapOverrides.tsuse{{approver}}(singular, mapped fromprogressiveApprovalTier.approver). Likely an inadvertent typo in the new branch.- Copy drift across 4 surfaces for the OverlappingRequests message: four subtly different wordings — "Your spouse" vs "You or your spouse", "This will take" vs "This may take", and only
getCapOverridesmentions "or Salary Request". Each variant is its own i18n key (cost) and a future drift risk. Consider consolidating into a single canonical sentence (or a smalluseApprovalReasonCopy()hook) shared across all four surfaces. - Server contract dependency: SOSA online-submission block (
useSosaBlockOverCap.ts) and several display fallbacks now depend on the server returning the canonicalprogressiveApprovalTierReasonfor each scenario. The previous code computedrequestedGross > effectiveCaplocally and was deterministic. Confirm with the backend team that the enum values are mutually exclusive, thatOverUserCapis the canonical reason whenever a user's individual gross exceeds their individual cap (regardless of combined/overlap status), and that the field is always populated whenprogressiveApprovalTieris set. - Dependency impact: clean — no external callers of removed APIs (
overUserCap, the oldhasBoardCapExceptionshape,additionalApprovalparam ongetCapOverrides).
Findings on Related Files (Not in This PR)
src/components/HrTools/AdditionalSalaryRequest/Shared/HcmData/Hcm.graphql:34—boardCapExceptionfield is now orphaned. Severity 3.5/10 — [Pre-existing]- Flagged by: Architecture Agent
- Recommended action: Consider dropping
boardCapExceptionfrom the Hcm fragment in a follow-up PR; this PR removes the only production consumer (only test mocks still reference it).
This is informational only — it does not block this PR or count toward the verdict.
| subContent = t( | ||
| "You have a Board approved Maximum Allowable Salary (CAP) and your salary request exceeds that amount. As a result we need to get their approval for this request. We'll forward your request to them and get back to you with their decision.", | ||
| ); | ||
| } else if (reason === ProgressiveApprovalTierReasonEnum.OverlappingRequests) { |
There was a problem hiding this comment.
Three concerns on this new OverlappingRequests branch:
- Title doesn't match the branch. The function returns the same
titleandcontentfor every non-default case (lines 61–66): "Your request requires additional approval because your Gross Salary exceeds your Maximum Allowable Salary…". That's false for theOverlappingRequestscase — the user's salary may be fully within cap; the issue is an overlapping pending request. Users will see contradictory copy in the title vs thesubContent. {{approvers}}vs{{approver}}. This branch uses template varapprovers(plural) while the analogous templates inReceipt.tsx,RequestSummaryCard.tsx, andgetCapOverrides.tsuseapprover(singular, mapped fromprogressiveApprovalTier.approver). Likely a typo; would silently substitute the same singular value either way, but the inconsistency complicates translation.- Untested.
useSubmitDialogContent.test.tsxonly adds a test forBoardCapException. The newOverlappingRequestsbranch (and its template substitutions) has no coverage.
Suggested fix: restructure to compute title/content/subContent together inside each if (reason === …) branch (a small lookup or factored helper). Add a colocated test for the OverlappingRequests case asserting both subContent substitution and title correctness.
| const isUserSosa = | ||
| hcmUser?.staffInfo.userPersonType === | ||
| UserPersonTypeEnum.EmployeeStaffNonRmoSpouse; | ||
| const overUserCap = |
There was a problem hiding this comment.
Previously blockOnCap = isUserSosa && (calcs.requestedGross > calcs.effectiveCap) — a deterministic client-side guard that fired whenever the saved gross exceeded the effective cap, by direct comparison. Now it fires only when the server returns progressiveApprovalTierReason === OverUserCap.
This is a behavioral contract change with a real failure mode: if the server classifies a SOSA user's over-cap request with a different reason (e.g. OverCombinedCap for a married SOSA where both individual and combined caps are exceeded, or OverlappingRequests, or returns null transiently), the block disengages and a SOSA user can submit an over-cap request online — exactly the case the block was designed to prevent. The docstring on line 14 still promises "Determine whether a SOSA user's requested gross salary exceeds their effective cap," which the implementation no longer answers.
Recommend one of:
- Confirm with the backend team that
OverUserCapis the canonical reason whenever an individual gross exceeds an individual cap (regardless of other concurrent conditions) — and update the docstring to make the contract dependency explicit. - Keep the local
requestedGross > effectiveCapcheck as a defensive belt-and-suspenders fallback (cheap to maintain, prevents silent regression). - Treat
nullreason for a SOSA user with a tier set as "block, unknown state" rather than "allow submission."
Also add a negative-case regression test to useSosaBlockOverCap.test.tsx: SOSA user, numerically over cap, reason is OverCombinedCap (or null) → assert behavior is what you intend (block or pass).
| const hasBoardCapException = | ||
| hcmUser?.exceptionSalaryCap.boardCapException ?? false; | ||
|
|
||
| if (!approvalRequired) { |
There was a problem hiding this comment.
The early-return on line 28 gates the entire branch chain on the legacy boolean. After this PR, RequestSummaryCard.tsx may render a non-trivial approval message for tier === DivisionHead paired with reason === OverUserCap/OverSpouseCap/OverlappingRequests, but the submit dialog will fall through to the generic "Your request will be sent to HR Services" copy. The two surfaces can disagree about whether approval is needed.
Consider gating on reason (or on reason != null && tier) to keep the submit dialog consistent with the status card the user just read.
| <CardContent> | ||
| <Typography data-testid="ApprovalProcessCard-status"> | ||
| {tier === ProgressiveApprovalTierEnum.DivisionHead ? ( | ||
| {tier === ProgressiveApprovalTierEnum.DivisionHead && |
There was a problem hiding this comment.
The guard changed from tier === DivisionHead ? to tier === DivisionHead && overCapPerson ? — a sound defensive improvement, but the case tier === DivisionHead && !overCapPerson now falls through to the spouseName ? … : … branches. Combined with the second Typography below ("Since your combined request is still within your combined Max Allowable Salary, no additional approvals are required"), a user could see one paragraph saying approval is required and another saying it isn't.
This combination only occurs if the server can return tier === DivisionHead with a reason other than OverUserCap/OverSpouseCap (e.g. OverCombinedCap). If the backend invariant is "DivisionHead ⇒ reason ∈ {OverUserCap, OverSpouseCap}", this is defensive-only and worth a code comment. If not, align the second Typography to also key off reason.
Either way, add a test for tier: DivisionHead with reason: OverCombinedCap (or reason: null).
464f4d9 to
fde3e82
Compare
Description
Builds on #1768 and explains why the overlapping requests require MCC approval, even if the request doesn't exceed the user's caps. The API provides the UI with the reason so that approval logic is centralized on the server.
Depends on https://github.com/CruGlobal/mpdx_api/pull/3325
MPDX-9539
Testing
Checklist:
/pr-reviewcommand locally and fixed any relevant suggestions