Skip to content

Commit dd5bd5d

Browse files
authored
Merge pull request Expensify#79197 from Expensify/revert-79145-arosiclair-revert-79085-77238
[BETA] Allow zero-amount expenses - ROUND 5
2 parents 3852554 + 4a760ca commit dd5bd5d

43 files changed

Lines changed: 297 additions & 137 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

__mocks__/reportData/transactions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const transactionR14932: Transaction = {
3030
reimbursable: true,
3131
hasEReceipt: true,
3232
cardID: 0,
33-
modifiedAmount: 0,
33+
modifiedAmount: '',
3434
originalAmount: 0,
3535
comment: {},
3636
bank: '',
@@ -59,7 +59,7 @@ const transactionR98765: Transaction = {
5959
hasEReceipt: true,
6060
managedCard: false,
6161
billable: false,
62-
modifiedAmount: 0,
62+
modifiedAmount: '',
6363
cardID: 0,
6464
originalAmount: 0,
6565
comment: {},

src/CONST/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ const CONST = {
734734
NO_OPTIMISTIC_TRANSACTION_THREADS: 'noOptimisticTransactionThreads',
735735
UBER_FOR_BUSINESS: 'uberForBusiness',
736736
CUSTOM_REPORT_NAMES: 'newExpensifyCustomReportNames',
737+
ZERO_EXPENSES: 'zeroExpenses',
737738
NEW_DOT_DEW: 'newDotDEW',
738739
GPS_MILEAGE: 'gpsMileage',
739740
},

src/components/MoneyReportHeader.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1753,6 +1753,7 @@ function MoneyReportHeader({
17531753
paymentType={paymentType}
17541754
chatReport={chatReport}
17551755
moneyRequestReport={moneyRequestReport}
1756+
hasNonHeldExpenses={!hasOnlyHeldExpenses}
17561757
startAnimation={() => {
17571758
if (requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE) {
17581759
startApprovedAnimation();

src/components/MoneyRequestConfirmationList.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,12 @@ function MoneyRequestConfirmationList({
562562
if (iouAmount !== 0) {
563563
text = translate('iou.createExpenseWithAmount', {amount: formattedAmount});
564564
}
565+
} else if (isTypeSplit) {
566+
text = translate('iou.splitAmount', {amount: formattedAmount});
567+
} else if (iouAmount === 0) {
568+
text = translate('iou.createExpense');
565569
} else {
566-
const translationKey = isTypeSplit ? 'iou.splitAmount' : 'iou.createExpenseWithAmount';
567-
text = translate(translationKey, {amount: formattedAmount});
570+
text = translate('iou.createExpenseWithAmount', {amount: formattedAmount});
568571
}
569572
return [
570573
{
@@ -973,6 +976,11 @@ function MoneyRequestConfirmationList({
973976
return;
974977
}
975978

979+
if (isEditingSplitBill && iouAmount === 0) {
980+
setFormError('iou.error.invalidAmount');
981+
return;
982+
}
983+
976984
if (formError) {
977985
return;
978986
}

src/components/ProcessMoneyReportHoldMenu.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ type ProcessMoneyReportHoldMenuProps = {
4747

4848
/** Callback for displaying payment animation on IOU preview component */
4949
startAnimation?: () => void;
50+
51+
/** Whether the report has non held expenses */
52+
hasNonHeldExpenses?: boolean;
5053
};
5154

5255
function ProcessMoneyReportHoldMenu({
5356
requestType,
54-
nonHeldAmount,
57+
nonHeldAmount = '0',
5558
fullAmount,
5659
onClose,
5760
isVisible,
@@ -60,6 +63,7 @@ function ProcessMoneyReportHoldMenu({
6063
moneyRequestReport,
6164
transactionCount,
6265
startAnimation,
66+
hasNonHeldExpenses,
6367
}: ProcessMoneyReportHoldMenuProps) {
6468
const {translate} = useLocalize();
6569
const isApprove = requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE;
@@ -102,19 +106,19 @@ function ProcessMoneyReportHoldMenu({
102106
};
103107

104108
const promptText = useMemo(() => {
105-
if (nonHeldAmount) {
109+
if (hasNonHeldExpenses) {
106110
return translate(isApprove ? 'iou.confirmApprovalAmount' : 'iou.confirmPayAmount');
107111
}
108112
return translate(isApprove ? 'iou.confirmApprovalAllHoldAmount' : 'iou.confirmPayAllHoldAmount', {count: transactionCount});
109-
}, [nonHeldAmount, transactionCount, translate, isApprove]);
113+
}, [hasNonHeldExpenses, transactionCount, translate, isApprove]);
110114

111115
return (
112116
<DecisionModal
113117
title={translate(isApprove ? 'iou.confirmApprove' : 'iou.confirmPay')}
114118
onClose={onClose}
115119
isVisible={isVisible}
116120
prompt={promptText}
117-
firstOptionText={nonHeldAmount ? `${translate(isApprove ? 'iou.approveOnly' : 'iou.payOnly')} ${nonHeldAmount}` : undefined}
121+
firstOptionText={hasNonHeldExpenses ? `${translate(isApprove ? 'iou.approveOnly' : 'iou.payOnly')} ${nonHeldAmount}` : undefined}
118122
secondOptionText={`${translate(isApprove ? 'iou.approve' : 'iou.pay')} ${fullAmount}`}
119123
onFirstOptionSubmit={() => onSubmit(false)}
120124
onSecondOptionSubmit={() => onSubmit(true)}

src/components/ReportActionItem/MoneyRequestReportPreview/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function MoneyRequestReportPreview({
9797
}
9898

9999
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
100-
return transactions.some((transaction) => (transaction?.modifiedAmount || transaction?.amount) < 0);
100+
return transactions.some((transaction) => (Number(transaction?.modifiedAmount) || transaction?.amount) < 0);
101101
}, [transactions, action.childType, iouReport]);
102102

103103
const openReportFromPreview = useCallback(() => {

src/components/ReportActionItem/MoneyRequestView.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ function MoneyRequestView({
240240
const currentUserEmailParam = currentUserPersonalDetails.login ?? '';
241241
const {isBetaEnabled} = usePermissions();
242242
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
243+
const isZeroExpensesBetaEnabled = isBetaEnabled(CONST.BETAS.ZERO_EXPENSES);
243244

244245
const moneyRequestReport = parentReport;
245246
const isApproved = isReportApproved({report: moneyRequestReport});
@@ -275,7 +276,9 @@ function MoneyRequestView({
275276
postedDate: transactionPostedDate,
276277
convertedAmount: transactionConvertedAmount,
277278
} = getTransactionDetails(transaction, undefined, undefined, allowNegativeAmount, false, currentUserPersonalDetails) ?? {};
278-
const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
279+
const isZeroTransactionAmount = transactionAmount === 0;
280+
const isEmptyMerchant =
281+
transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transactionMerchant === CONST.TRANSACTION.DEFAULT_MERCHANT;
279282
const isDistanceRequest = isDistanceRequestTransactionUtils(transaction);
280283
const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction, !!mergeTransactionID);
281284
const isOdometerDistanceRequest = isOdometerDistanceRequestTransactionUtils(transaction);
@@ -288,9 +291,10 @@ function MoneyRequestView({
288291
// Use the updated transaction amount in merge flow to have correct positive/negative sign
289292
const actualAmount = isFromMergeTransaction && updatedTransaction ? updatedTransaction.amount : transactionAmount;
290293
const actualCurrency = updatedTransaction ? getCurrency(updatedTransaction) : transactionCurrency;
291-
const shouldDisplayTransactionAmount = ((isDistanceRequest && hasRoute) || !!actualAmount) && actualAmount !== undefined;
294+
const shouldDisplayTransactionAmount = (isDistanceRequest && hasRoute) || !isDistanceRequest;
292295
const formattedTransactionAmount = shouldDisplayTransactionAmount ? convertToDisplayString(actualAmount, actualCurrency) : '';
293-
const formattedPerAttendeeAmount = shouldDisplayTransactionAmount ? convertToDisplayString(actualAmount / (actualAttendees?.length ?? 1), actualCurrency) : '';
296+
const formattedPerAttendeeAmount =
297+
shouldDisplayTransactionAmount && actualAmount !== undefined ? convertToDisplayString(actualAmount / (transactionAttendees?.length ?? 1), actualCurrency) : '';
294298

295299
const transactionOriginalAmount = transaction && getOriginalAmountForDisplay(transaction, isExpenseReport(moneyRequestReport));
296300
const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
@@ -398,16 +402,18 @@ function MoneyRequestView({
398402
let rateToDisplay = isCustomUnitOutOfPolicy ? translate('common.rateOutOfPolicy') : DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline);
399403
const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate);
400404
let merchantTitle = isEmptyMerchant ? '' : transactionMerchant;
401-
let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : '';
405+
let amountTitle = formattedTransactionAmount?.toString() || '';
402406
if (isTransactionScanning) {
403407
merchantTitle = translate('iou.receiptStatusTitle');
404408
amountTitle = translate('iou.receiptStatusTitle');
405409
}
406410

407411
const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy;
408-
409412
const updatedTransactionDescription = getDescription(updatedTransaction) || undefined;
410-
const isEmptyUpdatedMerchant = updatedTransaction?.modifiedMerchant === '' || updatedTransaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
413+
const isEmptyUpdatedMerchant =
414+
updatedTransaction?.modifiedMerchant === '' ||
415+
updatedTransaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ||
416+
updatedTransaction?.modifiedMerchant === CONST.TRANSACTION.DEFAULT_MERCHANT;
411417
const updatedMerchantTitle = isEmptyUpdatedMerchant ? '' : (updatedTransaction?.modifiedMerchant ?? merchantTitle);
412418

413419
const shouldShowConvertedAmount =
@@ -503,7 +509,7 @@ function MoneyRequestView({
503509
// NOTE: receipt field can return multiple violations, so we need to handle it separately
504510
const fieldChecks: Partial<Record<ViolationField, {isError: boolean; translationPath: TranslationPaths}>> = {
505511
amount: {
506-
isError: transactionAmount === 0,
512+
isError: isZeroTransactionAmount && !isZeroExpensesBetaEnabled,
507513
translationPath: canEditAmount ? 'common.error.enterAmount' : 'common.error.missingAmount',
508514
},
509515
merchant: {

src/components/ReportActionItem/TransactionPreview/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function TransactionPreview(props: TransactionPreviewProps) {
9393

9494
// See description of `transactionRawAmount` prop for more context
9595
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
96-
const transactionRawAmount = (transaction?.modifiedAmount || transaction?.amount) ?? 0;
96+
const transactionRawAmount = (Number(transaction?.modifiedAmount) || transaction?.amount) ?? 0;
9797

9898
const shouldDisableOnPress = isBillSplit && isEmptyObject(transaction);
9999
const isTransactionMadeWithCard = isManagedCardTransaction(transaction);

src/components/Search/index.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
shouldShowYear as shouldShowYearUtil,
5959
} from '@libs/SearchUIUtils';
6060
import {cancelSpan, endSpan, startSpan} from '@libs/telemetry/activeSpans';
61-
import {getOriginalTransactionWithSplitInfo, isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils';
61+
import {getOriginalTransactionWithSplitInfo, hasValidModifiedAmount, isOnHold, isTransactionPendingDelete} from '@libs/TransactionUtils';
6262
import Navigation, {navigationRef} from '@navigation/Navigation';
6363
import type {SearchFullscreenNavigatorParamList} from '@navigation/types';
6464
import EmptySearchView from '@pages/Search/EmptySearchView';
@@ -124,7 +124,7 @@ function mapTransactionItemToSelectedEntry(
124124
groupExchangeRate: item.groupExchangeRate,
125125
reportID: item.reportID,
126126
policyID: item.report?.policyID,
127-
amount: item.modifiedAmount ?? item.amount,
127+
amount: hasValidModifiedAmount(item) ? Number(item.modifiedAmount) : item.amount,
128128
groupAmount: item.groupAmount,
129129
currency: item.currency,
130130
isFromOneTransactionReport: isOneTransactionReport(item.report),
@@ -174,8 +174,7 @@ function prepareTransactionsList(
174174
action: item.action,
175175
reportID: item.reportID,
176176
policyID: item.policyID,
177-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
178-
amount: Math.abs(item.modifiedAmount || item.amount),
177+
amount: Math.abs(hasValidModifiedAmount(item) ? Number(item.modifiedAmount) : item.amount),
179178
groupAmount: item.groupAmount,
180179
groupCurrency: item.groupCurrency,
181180
groupExchangeRate: item.groupExchangeRate,
@@ -505,7 +504,7 @@ function Search({
505504
canReject: canRejectRequest,
506505
reportID: transactionItem.reportID,
507506
policyID: transactionItem.report?.policyID,
508-
amount: transactionItem.modifiedAmount ?? transactionItem.amount,
507+
amount: hasValidModifiedAmount(transactionItem) ? Number(transactionItem.modifiedAmount) : transactionItem.amount,
509508
groupAmount: transactionItem.groupAmount,
510509
groupCurrency: transactionItem.groupCurrency,
511510
groupExchangeRate: transactionItem.groupExchangeRate,
@@ -558,7 +557,7 @@ function Search({
558557
canReject: canRejectRequest,
559558
reportID: transactionItem.reportID,
560559
policyID: transactionItem.report?.policyID,
561-
amount: transactionItem.modifiedAmount ?? transactionItem.amount,
560+
amount: hasValidModifiedAmount(transactionItem) ? Number(transactionItem.modifiedAmount) : transactionItem.amount,
562561
groupAmount: transactionItem.groupAmount,
563562
groupCurrency: transactionItem.groupCurrency,
564563
groupExchangeRate: transactionItem.groupExchangeRate,

src/components/TransactionItemRow/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ function getMerchantName(transactionItem: TransactionWithOptionalSearchFields, t
145145
}
146146

147147
const merchantName = StringUtils.getFirstLine(merchant);
148-
return merchantName !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? merchantName : '';
148+
return merchantName !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchantName !== CONST.TRANSACTION.DEFAULT_MERCHANT ? merchantName : '';
149149
}
150150

151151
function TransactionItemRow({

0 commit comments

Comments
 (0)