Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f6445bf
Add frontend validation for distance expense amounts exceeding backen…
MelvinBot Feb 19, 2026
b21e2a5
Fix: Apply Prettier formatting to Italian translations
MelvinBot Feb 19, 2026
af8f3cd
Add distance amount validation to rate edit flow
MelvinBot Feb 20, 2026
564499b
Fix: Format IOURequestStepDistanceRate.tsx to pass Prettier check
MelvinBot Feb 20, 2026
867d5f1
Fix rate selection UX: keep problematic rate visually selected on val…
MelvinBot Feb 21, 2026
a0d0779
Use context-specific error messages for distance amount validation
MelvinBot Feb 21, 2026
5d20894
Merge remote-tracking branch 'origin/main' into claude-distanceAmount…
MelvinBot Feb 21, 2026
95d35f9
Add maxLength limit to odometer reading inputs
MelvinBot Feb 21, 2026
fd10da4
Add numeric max value validation for odometer readings
neil-marcellini Feb 21, 2026
38e41cf
Fix ODOMETER_MAX_VALUE to 999999.99 to match maxLength
neil-marcellini Feb 22, 2026
7d4d685
Increase odometer max to 9,999,999.9 and allow commas in input
neil-marcellini Feb 22, 2026
6f56802
Add locale-aware number input for odometer readings
neil-marcellini Feb 22, 2026
e7dc018
Fix: Sort imports to match Prettier configuration
MelvinBot Feb 22, 2026
dc5af8b
Auto-format odometer readings with thousand separators
neil-marcellini Feb 22, 2026
061ae92
Fix: Use replaceAll instead of replace to satisfy ESLint unicorn/pref…
MelvinBot Feb 22, 2026
9b9cfdd
Stop modifying odometer input, validate on submit instead
neil-marcellini Feb 22, 2026
2872755
Fix: Move parseOdometerReading definition before its usage
MelvinBot Feb 22, 2026
fa5d703
Reject odometer input that exceeds max value or decimal precision
neil-marcellini Feb 22, 2026
f5f9d36
Use locale-formatted number in odometer max error message
neil-marcellini Feb 22, 2026
21d980c
fix style, blank line above comment
neil-marcellini Feb 22, 2026
aec809b
Consolidate odometer input normalization to fix parsing/validation mi…
MelvinBot Feb 22, 2026
4cf43d9
Consolidate odometer text stripping into shared stripOdometerText helper
MelvinBot Feb 22, 2026
97dc8d2
Merge remote-tracking branch 'origin/claude-distanceAmountValidation'
MelvinBot Feb 22, 2026
187d021
Fix rate unit mismatch in distance validation and allow editing legac…
MelvinBot Feb 22, 2026
07ed7b2
Use selected rate unit for limit validation and allow editing legacy …
MelvinBot Feb 22, 2026
6c8b665
Merge remote changes with review comment fixes
MelvinBot Feb 22, 2026
e54e678
Align isDistanceAmountWithinLimit rounding with getDistanceRequestAmount
neil-marcellini Feb 22, 2026
608ecf7
Fix: Align isDistanceAmountWithinLimit rounding with getDistanceReque…
MelvinBot Feb 22, 2026
b6d8e10
Merge remote branch and resolve conflict in isDistanceAmountWithinLimit
MelvinBot Feb 22, 2026
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
2 changes: 2 additions & 0 deletions src/CONST/index.ts
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3156,6 +3156,8 @@ const CONST = {
},
AMOUNT_MAX_LENGTH: 10,
DISTANCE_REQUEST_AMOUNT_MAX_LENGTH: 14,
ODOMETER_MAX_VALUE: 9999999.9,
MAX_SAFE_AMOUNT: 999999999999,
RECEIPT_STATE: {
SCAN_READY: 'SCANREADY',
OPEN: 'OPEN',
Expand Down
5 changes: 5 additions & 0 deletions src/components/MoneyRequestConfirmationList.tsx
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,11 @@ function MoneyRequestConfirmationList({
return;
}

if (isDistanceRequest && Math.abs(iouAmount) > CONST.IOU.MAX_SAFE_AMOUNT) {
setFormError('iou.error.distanceAmountTooLarge');
return;
}

if (isTimeRequest && !isValidTimeExpenseAmount(iouAmount, iouCurrencyCode, decimals)) {
setFormError('iou.timeTracking.amountTooLargeError');
return;
Expand Down
4 changes: 4 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Bitte gib eine gültige Entfernung ein, bevor du fortfährst',
invalidReadings: 'Bitte geben Sie sowohl Start- als auch Endstand ein',
negativeDistanceNotAllowed: 'Endstand muss größer als Anfangsstand sein',
distanceAmountTooLarge: 'Der Gesamtbetrag ist zu hoch. Verringere die Entfernung oder reduziere den Satz.',
distanceAmountTooLargeReduceDistance: 'Der Gesamtbetrag ist zu hoch. Verringere die Entfernung.',
distanceAmountTooLargeReduceRate: 'Der Gesamtbetrag ist zu hoch. Reduziere den Satz.',
odometerReadingTooLarge: (formattedMax: string) => `Kilometerstände dürfen ${formattedMax} nicht überschreiten.`,
invalidIntegerAmount: 'Bitte gib einen vollen Dollarbetrag ein, bevor du fortfährst',
invalidTaxAmount: (amount: string) => `Der maximale Steuerbetrag ist ${amount}`,
invalidSplit: 'Die Summe der Aufteilungen muss dem Gesamtbetrag entsprechen',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,10 @@ const translations = {
invalidDistance: 'Please enter a valid distance before continuing',
invalidReadings: 'Please enter both start and end readings',
negativeDistanceNotAllowed: 'End reading must be greater than start reading',
distanceAmountTooLarge: 'The total amount is too large. Reduce the distance or lower the rate.',
distanceAmountTooLargeReduceDistance: 'The total amount is too large. Reduce the distance.',
distanceAmountTooLargeReduceRate: 'The total amount is too large. Lower the rate.',
odometerReadingTooLarge: (formattedMax: string) => `Odometer readings cannot exceed ${formattedMax}.`,
invalidIntegerAmount: 'Please enter a whole dollar amount before continuing',
invalidTaxAmount: (amount: string) => `Maximum tax amount is ${amount}`,
invalidSplit: 'The sum of splits must equal the total amount',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Por favor, ingresa una distancia válida antes de continuar',
invalidReadings: 'Por favor ingrese ambas lecturas de inicio y fin',
negativeDistanceNotAllowed: 'La lectura final debe ser mayor que la lectura inicial',
distanceAmountTooLarge: 'El importe total es demasiado alto. Reduce la distancia o disminuye la tarifa.',
distanceAmountTooLargeReduceDistance: 'El importe total es demasiado alto. Reduce la distancia.',
distanceAmountTooLargeReduceRate: 'El importe total es demasiado alto. Disminuye la tarifa.',
odometerReadingTooLarge: (formattedMax: string) => `Las lecturas del odómetro no pueden superar ${formattedMax}.`,
invalidIntegerAmount: 'Por favor, introduce una cantidad entera en dólares antes de continuar',
invalidTaxAmount: (amount) => `El importe máximo del impuesto es ${amount}`,
invalidSplit: 'La suma de las partes debe ser igual al importe total',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Veuillez saisir une distance valide avant de continuer',
invalidReadings: 'Veuillez saisir les relevés de début et de fin',
negativeDistanceNotAllowed: 'Le relevé de fin doit être supérieur au relevé de début',
distanceAmountTooLarge: 'Le montant total est trop élevé. Réduisez la distance ou diminuez le taux.',
distanceAmountTooLargeReduceDistance: 'Le montant total est trop élevé. Réduisez la distance.',
distanceAmountTooLargeReduceRate: 'Le montant total est trop élevé. Diminuez le taux.',
odometerReadingTooLarge: (formattedMax: string) => `Les lectures du compteur kilométrique ne peuvent pas dépasser ${formattedMax}.`,
invalidIntegerAmount: 'Veuillez saisir un montant entier en dollars avant de continuer',
invalidTaxAmount: (amount: string) => `Le montant maximal de taxe est de ${amount}`,
invalidSplit: 'La somme des répartitions doit être égale au montant total',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Inserisci una distanza valida prima di continuare',
invalidReadings: 'Inserisci sia la lettura iniziale che quella finale',
negativeDistanceNotAllowed: 'La lettura finale deve essere maggiore della lettura iniziale',
distanceAmountTooLarge: "L'importo totale è troppo alto. Riduci la distanza o abbassa la tariffa.",
distanceAmountTooLargeReduceDistance: "L'importo totale è troppo alto. Riduci la distanza.",
distanceAmountTooLargeReduceRate: "L'importo totale è troppo alto. Abbassa la tariffa.",
odometerReadingTooLarge: (formattedMax: string) => `Le letture del contachilometri non possono superare ${formattedMax}.`,
invalidIntegerAmount: 'Inserisci un importo in dollari intero prima di continuare',
invalidTaxAmount: (amount: string) => `L’importo massimo dell’imposta è ${amount}`,
invalidSplit: 'La somma delle suddivisioni deve essere uguale all’importo totale',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: '続行する前に有効な距離を入力してください',
invalidReadings: '開始値と終了値の両方を入力してください',
negativeDistanceNotAllowed: '終了値は開始値より大きくなければなりません',
distanceAmountTooLarge: '合計金額が大きすぎます。距離を減らすか、レートを下げてください。',
distanceAmountTooLargeReduceDistance: '合計金額が大きすぎます。距離を減らしてください。',
distanceAmountTooLargeReduceRate: '合計金額が大きすぎます。レートを下げてください。',
odometerReadingTooLarge: (formattedMax: string) => `オドメーターの読み取り値は${formattedMax}を超えることはできません。`,
invalidIntegerAmount: '続行する前にドルの整数金額を入力してください',
invalidTaxAmount: (amount: string) => `最大税額は${amount}です`,
invalidSplit: '分割した金額の合計は合計金額と一致している必要があります',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Voer een geldige afstand in voordat je verdergaat',
invalidReadings: 'Voer zowel de begin- als eindstanden in',
negativeDistanceNotAllowed: 'Eindstand moet hoger zijn dan beginstand',
distanceAmountTooLarge: 'Het totale bedrag is te hoog. Verlaag de afstand of verlaag het tarief.',
distanceAmountTooLargeReduceDistance: 'Het totale bedrag is te hoog. Verlaag de afstand.',
distanceAmountTooLargeReduceRate: 'Het totale bedrag is te hoog. Verlaag het tarief.',
odometerReadingTooLarge: (formattedMax: string) => `Kilometertellerstanden mogen niet hoger zijn dan ${formattedMax}.`,
invalidIntegerAmount: 'Voer een volledig dollarbedrag in voordat je doorgaat',
invalidTaxAmount: (amount: string) => `Maximale belastingbedrag is ${amount}`,
invalidSplit: 'De som van de splitsingen moet gelijk zijn aan het totale bedrag',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Wprowadź prawidłowy dystans przed kontynuowaniem',
invalidReadings: 'Wprowadź zarówno odczyt początkowy, jak i końcowy',
negativeDistanceNotAllowed: 'Końcowy odczyt musi być większy niż początkowy odczyt',
distanceAmountTooLarge: 'Łączna kwota jest zbyt wysoka. Zmniejsz dystans lub obniż stawkę.',
distanceAmountTooLargeReduceDistance: 'Łączna kwota jest zbyt wysoka. Zmniejsz dystans.',
distanceAmountTooLargeReduceRate: 'Łączna kwota jest zbyt wysoka. Obniż stawkę.',
odometerReadingTooLarge: (formattedMax: string) => `Odczyty licznika nie mogą przekraczać ${formattedMax}.`,
invalidIntegerAmount: 'Przed kontynuowaniem wprowadź kwotę w pełnych dolarach',
invalidTaxAmount: (amount: string) => `Maksymalna kwota podatku to ${amount}`,
invalidSplit: 'Suma podziałów musi być równa całkowitej kwocie',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: 'Insira uma distância válida antes de continuar',
invalidReadings: 'Insira as leituras de início e fim',
negativeDistanceNotAllowed: 'A leitura final deve ser maior que a leitura inicial',
distanceAmountTooLarge: 'O valor total é muito alto. Diminua a distância ou reduza a tarifa.',
distanceAmountTooLargeReduceDistance: 'O valor total é muito alto. Diminua a distância.',
distanceAmountTooLargeReduceRate: 'O valor total é muito alto. Reduza a tarifa.',
odometerReadingTooLarge: (formattedMax: string) => `As leituras do hodômetro não podem exceder ${formattedMax}.`,
invalidIntegerAmount: 'Insira um valor inteiro em dólares antes de continuar',
invalidTaxAmount: (amount: string) => `O valor máximo de imposto é ${amount}`,
invalidSplit: 'A soma das divisões deve ser igual ao valor total',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,10 @@ const translations: TranslationDeepObject<typeof en> = {
invalidDistance: '请在继续之前输入有效的距离',
invalidReadings: '请输入起始读数和结束读数',
negativeDistanceNotAllowed: '结束读数必须大于开始读数',
distanceAmountTooLarge: '总金额过大。请减少距离或降低费率。',
distanceAmountTooLargeReduceDistance: '总金额过大。请减少距离。',
distanceAmountTooLargeReduceRate: '总金额过大。请降低费率。',
odometerReadingTooLarge: (formattedMax: string) => `里程表读数不能超过${formattedMax}。`,
invalidIntegerAmount: '请在继续之前输入一个整数美元金额',
invalidTaxAmount: (amount: string) => `最高税额为 ${amount}`,
invalidSplit: '拆分金额之和必须等于总金额',
Expand Down
18 changes: 18 additions & 0 deletions src/libs/DistanceRequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,23 @@ function getRateByCustomUnitRateID({customUnitRateID, policy}: {customUnitRateID
return getMileageRates(policy, true, customUnitRateID)[customUnitRateID];
}

/**
* Returns whether the calculated distance expense amount (distance * rate) is within the backend's safe limit.
* The backend WAF rejects amounts exceeding 12 digits (999,999,999,999 cents).
*
* @param distance - The distance in the unit specified (km or mi), NOT meters
* @param rate - The rate in cents per unit
* @returns true if the amount is within limits, false if it would exceed the backend limit
*/
function isDistanceAmountWithinLimit(distance: number, rate: number): boolean {
// Match the 2-decimal rounding used by getDistanceRequestAmount so boundary
// values produce identical results in both the limit check and the actual
// amount calculation.
const roundedDistance = parseFloat(Math.abs(distance).toFixed(2));
const amount = Math.round(roundedDistance * rate);
return amount <= CONST.IOU.MAX_SAFE_AMOUNT;
}

export default {
getDefaultMileageRate,
getDistanceMerchant,
Expand All @@ -436,6 +453,7 @@ export default {
getRateByCustomUnitRateID,
getDistanceForDisplayLabel,
convertDistanceUnit,
isDistanceAmountWithinLimit,
};

export type {MileageRate};
14 changes: 11 additions & 3 deletions src/pages/iou/request/step/IOURequestStepDistanceManual.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,13 @@
// to make sure the correct distance amount and unit will be shown we use distance unit
// from defaultExpensePolicy or current report's policy instead of from transaction and
// then we use transaction data (distanceUnit and quantity) for conversions
const unit = DistanceRequestUtils.getRate({
const mileageRate = DistanceRequestUtils.getRate({
transaction,
policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy,
useTransactionDistanceUnit: false,
}).unit;
});
const unit = mileageRate.unit;
const rate = mileageRate.rate ?? 0;
const distanceInMeters = getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ? transaction.comment.customUnit.distanceUnit : unit);
const distance = typeof transaction?.comment?.customUnit?.quantity === 'number' ? roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit)) : undefined;
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
Expand Down Expand Up @@ -274,8 +276,14 @@
return;
}

// Validation: Check that distance * rate doesn't exceed the backend's safe amount limit
if (!DistanceRequestUtils.isDistanceAmountWithinLimit(parseFloat(value), rate)) {
setFormError(translate('iou.error.distanceAmountTooLargeReduceDistance'));
return;
}

navigateToNextPage(value);
}, [navigateToNextPage, translate, report, iouType, currentUserAccountIDParam]);
}, [navigateToNextPage, translate, report, iouType, currentUserAccountIDParam, rate]);

Check warning on line 286 in src/pages/iou/request/step/IOURequestStepDistanceManual.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useCallback has unnecessary dependencies: 'currentUserAccountIDParam', 'iouType', and 'report'. Either exclude them or remove the dependency array

useEffect(() => {
if (isLoadingSelectedTab) {
Expand Down
Loading
Loading