Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5383c48
add noRoute violation
s77rt Dec 17, 2025
5f7c1ca
empty string if distance is zero
s77rt Dec 17, 2025
94e015c
update displayed reportName if no route was found
s77rt Dec 17, 2025
42e4132
merge main
s77rt Dec 20, 2025
5cec427
update copy
s77rt Dec 20, 2025
06ff539
Merge branch 'main' into allow-invalid-waypoints
s77rt Dec 29, 2025
85e4f88
exclude Null Island from valid waypoints
s77rt Dec 29, 2025
f032cfe
fetch route only if all waypoints are valid
s77rt Dec 29, 2025
3918ade
fix condition
s77rt Dec 29, 2025
5bd8064
display RBR in invalid waypoints
s77rt Dec 29, 2025
44685ba
a waypoint is a null island only if it has explicit lat/lng = 0
s77rt Dec 29, 2025
9d36ca4
remove lat/lng zero-fallback to distinguish pending waypoint vs null …
s77rt Dec 29, 2025
6c1b1b3
Merge branch 'main' into allow-invalid-waypoints
s77rt Dec 30, 2025
a5ea12a
Merge branch 'main' into allow-invalid-waypoints
s77rt Dec 31, 2025
694dd2a
update waypoints properly in Onyx.merge
s77rt Dec 31, 2025
85fb8cf
revert useFetchRoute logic
s77rt Dec 31, 2025
08386e4
merge main
s77rt Jan 7, 2026
23bcbd9
better check if distance is zero
s77rt Jan 7, 2026
2843062
better error messages
s77rt Jan 7, 2026
5106cc2
remove outdated test
s77rt Jan 7, 2026
f479f8e
lint
s77rt Jan 7, 2026
f66f88b
merge main
s77rt Jan 7, 2026
0f4d5f6
Merge branch 'main' into allow-invalid-waypoints
s77rt Jan 8, 2026
6c481fb
optimistically clear noRoute violation
s77rt Jan 8, 2026
5870028
prevent submit if transaction has noRoute violation
s77rt Jan 8, 2026
0c93163
add spanish translation
s77rt Jan 8, 2026
2f973db
update translation
s77rt Jan 8, 2026
db21ee7
pass categories and tags for proper violations calculation
s77rt Jan 8, 2026
1a160d8
lint
s77rt Jan 8, 2026
7c3b293
translations
s77rt Jan 14, 2026
194ab16
merge main
s77rt Jan 14, 2026
fe20313
merge main
s77rt Jan 15, 2026
faf9ab8
ts
s77rt Jan 15, 2026
e37688e
ts
s77rt Jan 15, 2026
8c239fc
merge main
s77rt Jan 15, 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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5806,6 +5806,7 @@ const CONST = {
RECEIPT_GENERATED_WITH_AI: 'receiptGeneratedWithAI',
OVER_TRIP_LIMIT: 'overTripLimit',
COMPANY_CARD_REQUIRED: 'companyCardRequired',
NO_ROUTE: 'noRoute',
MISSING_ATTENDEES: 'missingAttendees',
},
RTER_VIOLATION_TYPES: {
Expand Down
14 changes: 9 additions & 5 deletions src/components/DistanceRequest/DistanceRequestRenderItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import {isWaypointNullIsland} from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import type {WaypointCollection} from '@src/types/onyx/Transaction';

Expand Down Expand Up @@ -32,7 +33,7 @@ type DistanceRequestProps = {

function DistanceRequestRenderItem({waypoints, item = '', onSecondaryInteraction, getIndex, isActive = false, onPress = () => {}, disabled = false}: DistanceRequestProps) {
const theme = useTheme();
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Location']);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Location', 'DotIndicatorUnfilled', 'DotIndicator', 'DragHandles']);
const {translate} = useLocalize();
const numberOfWaypoints = Object.keys(waypoints ?? {}).length;
const lastWaypointIndex = numberOfWaypoints - 1;
Expand All @@ -42,24 +43,25 @@ function DistanceRequestRenderItem({waypoints, item = '', onSecondaryInteraction
let waypointIcon;
if (index === 0) {
descriptionKey += 'start';
waypointIcon = Expensicons.DotIndicatorUnfilled;
waypointIcon = expensifyIcons.DotIndicatorUnfilled;
} else if (index === lastWaypointIndex) {
descriptionKey += 'stop';
waypointIcon = expensifyIcons.Location;
} else {
descriptionKey += 'stop';
waypointIcon = Expensicons.DotIndicator;
waypointIcon = expensifyIcons.DotIndicator;
}

const waypoint = waypoints?.[`waypoint${index}`] ?? {};
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const title = waypoint.name || waypoint.address;
const errorText = isWaypointNullIsland(waypoint) ? translate('violations.noRoute') : undefined;

return (
<MenuItemWithTopDescription
description={translate(descriptionKey as TranslationPaths)}
title={title}
icon={Expensicons.DragHandles}
icon={expensifyIcons.DragHandles}
iconFill={theme.icon}
secondaryIcon={waypointIcon}
secondaryIconFill={theme.icon}
Expand All @@ -69,6 +71,8 @@ function DistanceRequestRenderItem({waypoints, item = '', onSecondaryInteraction
focused={isActive}
key={item}
disabled={disabled}
errorText={errorText}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,8 @@ function MoneyRequestView({
),
);
}}
brickRoadIndicator={getErrorForField('waypoints') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={getErrorForField('waypoints')}
copyValue={distanceCopyValue}
copyable={!!distanceCopyValue}
/>
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useViolations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {TransactionViolation, ViolationName} from '@src/types/onyx';
/**
* Names of Fields where violations can occur.
*/
const validationFields = ['amount', 'billable', 'category', 'comment', 'date', 'merchant', 'receipt', 'tag', 'tax', 'attendees', 'customUnitRateID', 'none'] as const;
const validationFields = ['amount', 'billable', 'category', 'comment', 'date', 'merchant', 'receipt', 'tag', 'tax', 'attendees', 'customUnitRateID', 'waypoints', 'none'] as const;

type ViolationField = TupleToUnion<typeof validationFields>;

Expand Down Expand Up @@ -61,6 +61,7 @@ const violationNameToField: Record<ViolationName, (violation: TransactionViolati
hold: () => 'none',
receiptGeneratedWithAI: () => 'receipt',
companyCardRequired: () => 'none',
noRoute: () => 'waypoints',
};

type ViolationsMap = Map<ViolationField, TransactionViolation[]>;
Expand Down
2 changes: 1 addition & 1 deletion src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Suchen …',
next: 'Weiter',
previous: 'Zurück',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Zurück',
create: 'Erstellen',
add: 'Hinzufügen',
Expand Down Expand Up @@ -7366,6 +7365,7 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard
hold: 'Diese Ausgabe wurde zurückgestellt',
resolvedDuplicates: 'Duplikat behoben',
companyCardRequired: 'Firmenkartenkäufe erforderlich',
noRoute: 'Bitte wählen Sie eine gültige Adresse aus',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} ist erforderlich`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7299,6 +7299,7 @@ const translations = {
hold: 'This expense was put on hold',
resolvedDuplicates: 'resolved the duplicate',
companyCardRequired: 'Company card purchases required',
noRoute: 'Please select a valid address',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} is required`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7451,6 +7451,7 @@ ${amount} para ${merchant} - ${date}`,
hold: 'Este gasto está retenido',
resolvedDuplicates: 'resolvió el duplicado',
companyCardRequired: 'Se requieren compras con la tarjeta de la empresa.',
noRoute: 'Por favor, selecciona una dirección válida',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}) => `${fieldName} es obligatorio`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Rechercher...',
next: 'Suivant',
previous: 'Précédent',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Retour',
create: 'Créer',
add: 'Ajouter',
Expand Down Expand Up @@ -7376,6 +7375,7 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin
hold: 'Cette dépense a été mise en attente',
resolvedDuplicates: 'a résolu le doublon',
companyCardRequired: 'Achats avec carte d’entreprise requis',
noRoute: 'Veuillez sélectionner une adresse valide',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} est requis`,
Expand Down
4 changes: 2 additions & 2 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,7 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Cerca...',
next: 'Avanti',
previous: 'Precedente',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Torna indietro',
goBack: 'Indietro',
create: 'Crea',
add: 'Aggiungi',
resend: 'Reinvia',
Expand Down Expand Up @@ -7352,6 +7351,7 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori
hold: 'Questa spesa è stata messa in sospeso',
resolvedDuplicates: 'ha risolto il duplicato',
companyCardRequired: 'Acquisti con carta aziendale obbligatori',
noRoute: 'Seleziona un indirizzo valido',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} è obbligatorio`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: '検索...',
next: '次へ',
previous: '前へ',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: '戻る',
create: '作成',
add: '追加',
Expand Down Expand Up @@ -7296,6 +7295,7 @@ ${reportName}
hold: 'この経費は保留になっています',
resolvedDuplicates: '重複を解決しました',
companyCardRequired: '法人カードでの購入が必須',
noRoute: '有効な住所を選択してください',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} は必須です`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Zoeken...',
next: 'Volgende',
previous: 'Vorige',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Ga terug',
create: 'Maken',
add: 'Toevoegen',
Expand Down Expand Up @@ -7340,6 +7339,7 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten
hold: 'Deze uitgave is in de wacht gezet',
resolvedDuplicates: 'het duplicaat opgelost',
companyCardRequired: 'Aankopen met bedrijfskaart verplicht',
noRoute: 'Selecteer een geldig adres',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} is verplicht`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Szukaj...',
next: 'Dalej',
previous: 'Wstecz',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Wróć',
create: 'Utwórz',
add: 'Dodaj',
Expand Down Expand Up @@ -7325,6 +7324,7 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
hold: 'Ten wydatek został wstrzymany',
resolvedDuplicates: 'rozwiązano duplikat',
companyCardRequired: 'Wymagane zakupy kartą firmową',
noRoute: 'Wybierz prawidłowy adres',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `Pole ${fieldName} jest wymagane`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: 'Pesquisar...',
next: 'Próximo',
previous: 'Anterior',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: 'Voltar',
create: 'Criar',
add: 'Adicionar',
Expand Down Expand Up @@ -7327,6 +7326,7 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe
hold: 'Esta despesa foi colocada em espera',
resolvedDuplicates: 'duplicata resolvida',
companyCardRequired: 'Compras com cartão da empresa obrigatórias',
noRoute: 'Selecione um endereço válido',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName} é obrigatório`,
Expand Down
2 changes: 1 addition & 1 deletion src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,6 @@ const translations: TranslationDeepObject<typeof en> = {
searchWithThreeDots: '搜索…',
next: '下一步',
previous: '上一步',
// @context Navigation button that returns the user to the previous screen. Should be interpreted as a UI action label.
goBack: '返回',
create: '创建',
add: '添加',
Expand Down Expand Up @@ -7175,6 +7174,7 @@ ${reportName}
hold: '此报销已被搁置',
resolvedDuplicates: '已解决重复项',
companyCardRequired: '需要公司卡消费',
noRoute: '请选择一个有效的地址',
},
reportViolations: {
[CONST.REPORT_VIOLATIONS.FIELD_REQUIRED]: ({fieldName}: RequiredFieldParams) => `${fieldName}为必填项`,
Expand Down
8 changes: 8 additions & 0 deletions src/libs/DistanceRequestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ function getDistanceForDisplay(
return translate('iou.fieldPending');
}

if (!distanceInMeters) {
return '';
}

const distanceInUnits = getRoundedDistanceInUnits(distanceInMeters, unit);
if (useShortFormUnit) {
return `${distanceInUnits} ${unit}`;
Expand Down Expand Up @@ -221,6 +225,10 @@ function getDistanceMerchant(
return translate('iou.fieldPending');
}

if (!distanceInMeters) {
return '';
}

const distanceInUnits = getDistanceForDisplay(hasRoute, distanceInMeters, unit, rate, translate, true);
const ratePerUnit = getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, undefined, true);

Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReportPreviewActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
isReportApproved,
isSettled,
} from './ReportUtils';
import {hasSmartScanFailedViolation, isPending, isScanning} from './TransactionUtils';
import {hasSmartScanFailedOrNoRouteViolation, isPending, isScanning} from './TransactionUtils';

function canSubmit(
report: Report,
Expand All @@ -46,7 +46,7 @@ function canSubmit(

const isAnyReceiptBeingScanned = transactions?.some((transaction) => isScanning(transaction));

if (transactions?.some((transaction) => hasSmartScanFailedViolation(transaction, violations, currentUserEmail, currentUserAccountID, report, policy))) {
if (transactions?.some((transaction) => hasSmartScanFailedOrNoRouteViolation(transaction, violations, currentUserEmail, currentUserAccountID, report, policy))) {
return false;
}

Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReportPrimaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
allHavePendingRTERViolation,
getTransactionViolations,
hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils,
hasSmartScanFailedViolation,
hasSmartScanFailedOrNoRouteViolation,
isDuplicate,
isOnHold as isOnHoldTransactionUtils,
isPending,
Expand Down Expand Up @@ -115,7 +115,7 @@ function isSubmitAction(
}

if (violations && currentUserEmail && currentUserAccountID !== undefined) {
if (reportTransactions.some((transaction) => hasSmartScanFailedViolation(transaction, violations, currentUserEmail, currentUserAccountID, report, policy))) {
if (reportTransactions.some((transaction) => hasSmartScanFailedOrNoRouteViolation(transaction, violations, currentUserEmail, currentUserAccountID, report, policy))) {
return false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
allHavePendingRTERViolation,
getOriginalTransactionWithSplitInfo,
hasReceipt as hasReceiptTransactionUtils,
hasSmartScanFailedViolation,
hasSmartScanFailedOrNoRouteViolation,
isDistanceRequest as isDistanceRequestTransactionUtils,
isDuplicate,
isManagedCardTransaction as isManagedCardTransactionTransactionUtils,
Expand Down Expand Up @@ -195,7 +195,7 @@ function isSubmitAction({
}

if (violations && currentUserLogin && currentUserAccountID !== undefined) {
if (reportTransactions.some((transaction) => hasSmartScanFailedViolation(transaction, violations, currentUserLogin, currentUserAccountID, report, policy))) {
if (reportTransactions.some((transaction) => hasSmartScanFailedOrNoRouteViolation(transaction, violations, currentUserLogin, currentUserAccountID, report, policy))) {
return false;
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ import {
getConvertedAmount,
getCurrency,
getDescription,
getDistanceInMeters,
getFormattedCreated,
getFormattedPostedDate,
getMCCGroup,
Expand All @@ -326,6 +327,7 @@ import {
isExpensifyCardTransaction,
isFetchingWaypointsFromServer,
isManualDistanceRequest as isManualDistanceRequestTransactionUtils,
isMapDistanceRequest,
isOnHold as isOnHoldTransactionUtils,
isPayAtEndExpense,
isPending,
Expand Down Expand Up @@ -5047,6 +5049,12 @@ function getTransactionReportName({
return translateLocal('iou.fieldPending');
}

// The unit does not matter as we are only interested in whether the distance is zero or not
if (isMapDistanceRequest(transaction) && !getDistanceInMeters(transaction, CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS)) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return translateLocal('violations.noRoute');
}

if (isSentMoneyReportAction(reportAction)) {
return getIOUReportActionDisplayMessage(reportAction as ReportAction, transaction);
}
Expand Down
Loading
Loading