Skip to content

fix(SDK-1285): reject negative additional-earning amounts before submit - #2651

Draft
krisxcrash wants to merge 2 commits into
mainfrom
kw/fix/sdk-1285-correction-amounts-accept-negatives
Draft

fix(SDK-1285): reject negative additional-earning amounts before submit#2651
krisxcrash wants to merge 2 commits into
mainfrom
kw/fix/sdk-1285-correction-amounts-accept-negatives

Conversation

@krisxcrash

@krisxcrash krisxcrash commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes SDK-1285 (Medium, Pay v2 / RRoP Test Fest).

Correction to the ticket

The ticket says the SDK "isn't supplying min={0}". It already does — the correction-payment field and every other additional earning pass min={0} today.

The real gap: a native min only constrains the stepper. It does not stop a typed -50, and the SDK's <form> sets noValidate, so the browser never enforces it either. The form schema accepted any string, so negative amounts reached the platform and came back only as a submit-time error.

"Correction payment" is COMPENSATION_NAME_CORRECTION_PAYMENT, rendered in the same additional-earnings grid as bonus/tips/commission.

Fix

Follows the pattern SDK-1225 established for draft reimbursements: a zod rule plus an errorMessage on the field. useField returns isInvalid ? (errorMessage ?? fieldState.error?.message) : undefined, so a statically-passed errorMessage only surfaces once react-hook-form has actually flagged the field — no conditional logic, and no need to thread t into a module-level schema.

  • superRefine over the fixedCompensations record, adding an issue at ['fixedCompensations', name] for any value parsing to < 0. A record needs superRefine rather than a per-value refine because the keys are dynamic.
  • New validations.negativeAmount alongside the existing validations.reimbursementAmount.
  • Regenerated src/i18n/types.d.ts (CI's derive-check-i18n fails if stale).

Server validation is untouched and remains the backstop, which is the second AC.

Scope

The rule covers every additional earning, not just correction payment — they all share the fixedCompensations record, so one rule and one message is the natural unit rather than special-casing one key. Empty and zero values are untouched.

hourlyCompensations and timeOffCompensations have the same min-without-validation gap, but they are separate fields with different rules and are not touched here.

Testing

Three new cases:

  • a negative correction amount blocks save and shows the inline error
  • a negative bonus does too (proves the rule is not correction-only)
  • empty and zero still save cleanly, with no false positive

I verified the first two fail when the superRefine is removed. The correction-payment case also confirms the error path resolves for a field name containing a space (fixedCompensations.Correction Payment), which was the non-obvious part.

npx vitest run src/components/Payroll/PayrollEditEmployee — 71 pass. npm run tsc, eslint, prettier — clean.

Merge note

SDK-1284 touches the same file and the same schema/builder block. Whichever of the two merges second needs a small rebase there.

krisxcrash and others added 2 commits August 26, 2026 14:59
The fields already pass min={0}, but a native min only constrains the stepper --
it does not stop a typed "-50" -- and the SDK's <form> is noValidate, so the
browser never enforces it either. The form schema accepted any string, so negative
amounts went to the platform and came back only as a submit-time error.

Adds a superRefine over the fixedCompensations record that flags any negative
value at field level, following the pattern SDK-1225 established for draft
reimbursements: a zod rule plus an errorMessage on the field, which useField only
surfaces once react-hook-form has actually flagged it. Server validation stays as
the backstop.

The rule covers every additional earning, not just correction payment: they all
share the fixedCompensations record, so one rule and one message is the natural
unit. Empty and zero values are untouched. hourlyCompensations and
timeOffCompensations have the same min-without-validation gap but are separate
fields with different rules, and are out of scope here.

Note for reviewers: the ticket said the SDK "isn't supplying min={0}". It already
does; the gap is that a native min cannot stop typed input.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant