diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index b7a55e95a9a0..c9f3008cfd2e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -66,6 +66,7 @@ import { getTagForDisplay, getTaxName, hasMissingSmartscanFields, + hasReceipt, hasReservationList, hasRoute as hasRouteTransactionUtils, isCardTransaction as isCardTransactionTransactionUtils, @@ -194,9 +195,15 @@ function MoneyRequestView({ // Use the updated transaction amount in merge flow to have correct positive/negative sign const actualAmount = isFromMergeTransaction && updatedTransaction ? updatedTransaction.amount : transactionAmount; const actualCurrency = updatedTransaction ? getCurrency(updatedTransaction) : transactionCurrency; - const shouldDisplayTransactionAmount = ((isDistanceRequest && hasRoute) || !!actualAmount) && actualAmount !== undefined; + const shouldDisplayTransactionAmount = useMemo(() => { + return ( + ((isDistanceRequest && hasRoute) || (!!actualAmount && hasReceipt(updatedTransaction ?? transaction)) || !hasReceipt(updatedTransaction ?? transaction)) && + actualAmount !== undefined + ); + }, [isDistanceRequest, hasRoute, actualAmount, updatedTransaction, transaction]); const formattedTransactionAmount = shouldDisplayTransactionAmount ? convertToDisplayString(actualAmount, actualCurrency) : ''; - const formattedPerAttendeeAmount = shouldDisplayTransactionAmount ? convertToDisplayString(actualAmount / (transactionAttendees?.length ?? 1), actualCurrency) : ''; + const formattedPerAttendeeAmount = + shouldDisplayTransactionAmount && actualAmount !== undefined ? convertToDisplayString(actualAmount / (transactionAttendees?.length ?? 1), actualCurrency) : ''; const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); const isCardTransaction = isCardTransactionTransactionUtils(transaction); @@ -298,7 +305,7 @@ function MoneyRequestView({ const rateToDisplay = isCustomUnitOutOfPolicy ? translate('common.rateOutOfPolicy') : DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; - let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : ''; + let amountTitle = formattedTransactionAmount?.toString() || ''; if (isTransactionScanning) { merchantTitle = translate('iou.receiptStatusTitle'); amountTitle = translate('iou.receiptStatusTitle'); @@ -371,10 +378,6 @@ function MoneyRequestView({ // Checks applied when creating a new expense // NOTE: receipt field can return multiple violations, so we need to handle it separately const fieldChecks: Partial> = { - amount: { - isError: transactionAmount === 0, - translationPath: canEditAmount ? 'common.error.enterAmount' : 'common.error.missingAmount', - }, merchant: { isError: !isSettled && !isCancelled && isPolicyExpenseChat && isEmptyMerchant, translationPath: canEditMerchant ? 'common.error.enterMerchant' : 'common.error.missingMerchantName', @@ -413,7 +416,6 @@ function MoneyRequestView({ return ''; }, [ - transactionAmount, isSettled, isCancelled, isPolicyExpenseChat, @@ -424,7 +426,6 @@ function MoneyRequestView({ hasViolations, translate, getViolationsForField, - canEditAmount, canEditDate, canEditMerchant, canEdit, diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index a6257dc7b233..b7efdbb7dd4f 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -31,6 +31,7 @@ import { hasMissingSmartscanFields, hasNoticeTypeViolation, hasPendingRTERViolation, + hasReceipt, hasViolation, hasWarningTypeViolation, isAmountMissing, @@ -237,7 +238,7 @@ function getTransactionPreviewTextAndTranslationPaths({ if (hasFieldErrors && RBRMessage === undefined) { const merchantMissing = isMerchantMissing(transaction); - const amountMissing = isAmountMissing(transaction); + const amountMissing = isAmountMissing(transaction) && hasReceipt(transaction); if (amountMissing && merchantMissing) { RBRMessage = {translationPath: 'violations.reviewRequired'}; } else if (amountMissing) { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 69507a651d9f..e08c5090dddb 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -445,7 +445,8 @@ function areRequiredFieldsEmpty(transaction: OnyxEntry, reportTrans const isFromExpenseReport = parentReport?.type === CONST.REPORT.TYPE.EXPENSE; const isSplitPolicyExpenseChat = !!transaction?.comment?.splits?.some((participant) => allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`]?.isOwnPolicyExpenseChat); const isMerchantRequired = isFromExpenseReport || isSplitPolicyExpenseChat; - return (isMerchantRequired && isMerchantMissing(transaction)) || isAmountMissing(transaction) || isCreatedMissing(transaction); + const isAmountRequired = hasReceipt(transaction); + return (isMerchantRequired && isMerchantMissing(transaction)) || (isAmountMissing(transaction) && isAmountRequired) || isCreatedMissing(transaction); } /** diff --git a/src/pages/iou/MoneyRequestAmountForm.tsx b/src/pages/iou/MoneyRequestAmountForm.tsx index 1a50af10a360..f49bd6b55f35 100644 --- a/src/pages/iou/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/MoneyRequestAmountForm.tsx @@ -54,7 +54,7 @@ type MoneyRequestAmountFormProps = Omit !amount.length || parseFloat(amount) < 0.01; +const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean, currency: string) => isTaxAmountForm && Number.parseFloat(currentAmount) > convertToFrontendAmountAsInteger(Math.abs(taxAmount), currency); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index bf1302f72131..693a8620e0e2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -297,7 +297,7 @@ function IOURequestStepDistanceManual({ const submitAndNavigateToNextPage = useCallback(() => { const value = numberFormRef.current?.getNumber() ?? ''; - if (!value.length || parseFloat(value) < 0.01) { + if (!value.length || parseFloat(value) < 0) { setFormError(translate('iou.error.invalidDistance')); return; } diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index c3346c726922..0951faa403ba 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1250,8 +1250,9 @@ describe('DebugUtils', () => { }; await Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + amount: 100, + created: '', + modifiedCreated: '', }, [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, @@ -1310,8 +1311,9 @@ describe('DebugUtils', () => { }; await Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + amount: 100, + created: '', + modifiedCreated: '', }, [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, @@ -1369,8 +1371,9 @@ describe('DebugUtils', () => { }; await Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + amount: 100, + created: '', + modifiedCreated: '', }, [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, @@ -1434,8 +1437,9 @@ describe('DebugUtils', () => { }; await Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + amount: 100, + created: '', + modifiedCreated: '', }, [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, @@ -1493,8 +1497,9 @@ describe('DebugUtils', () => { }; await Onyx.multiSet({ [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + amount: 100, + created: '', + modifiedCreated: '', }, [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index c2736c703b2e..50068b833989 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -7133,7 +7133,8 @@ describe('ReportUtils', () => { const transaction: Transaction = { ...createRandomTransaction(12345), reportID: parentReport.reportID, - amount: 0, + created: '', + modifiedCreated: '', }; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); await waitForBatchedUpdates(); @@ -7158,7 +7159,8 @@ describe('ReportUtils', () => { const transaction: Transaction = { ...createRandomTransaction(12345), reportID: parentReport.reportID, - amount: 0, + created: '', + modifiedCreated: '', }; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, transaction); await waitForBatchedUpdates(); diff --git a/tests/unit/TransactionPreviewUtils.test.ts b/tests/unit/TransactionPreviewUtils.test.ts index 4331f657be60..aae0b40701a2 100644 --- a/tests/unit/TransactionPreviewUtils.test.ts +++ b/tests/unit/TransactionPreviewUtils.test.ts @@ -104,12 +104,20 @@ describe('TransactionPreviewUtils', () => { expect(result.displayAmountText.text).toEqual('$0.00'); }); - it('returns merchant missing and amount missing message when appropriate', () => { + it('returns violations.reviewRequired when transaction has receipt with missing merchant and amount', async () => { + // Set up a proper expense report in Onyx + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, { + reportID: '1', + type: 'expense', + }); + await waitForBatchedUpdates(); + const functionArgs = { ...basicProps, - transaction: {...basicProps.transaction, merchant: '', amount: 0}, + transaction: {...basicProps.transaction, merchant: '', amount: 0, receipt: {source: 'test-receipt.jpg', state: CONST.IOU.RECEIPT_STATE.SCAN_COMPLETE}}, originalTransaction: undefined, shouldShowRBR: true, + isReportAPolicyExpenseChat: true, }; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.RBRMessage.translationPath).toEqual('violations.reviewRequired');