From 77d0deb2d78a358004dc7e1b5b128aa127facacb Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 10 Jun 2025 15:57:21 +0200 Subject: [PATCH 1/5] Fix multiple expenses creation --- src/libs/ReportUtils.ts | 8 +++++-- src/libs/actions/IOU.ts | 22 ++++++++++++++++--- .../step/IOURequestStepConfirmation.tsx | 7 ++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e7112c0b7d3c..62506ab7dc43 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5522,6 +5522,7 @@ function buildOptimisticSelfDMReport(created: string): Report { * @param currency - IOU currency. * @param isSendingMoney - If we pay someone the IOU should be created as settled * @param parentReportActionID - The parent report action ID of the IOU report + * @param optimisticIOUReportID - Optimistic IOU report id */ function buildOptimisticIOUReport( @@ -5532,6 +5533,7 @@ function buildOptimisticIOUReport( currency: string, isSendingMoney = false, parentReportActionID?: string, + optimisticIOUReportID?: string, ): OptimisticIOUReport { const formattedTotal = convertToDisplayString(total, currency); const personalDetails = getPersonalDetailsForAccountID(payerAccountID); @@ -5553,7 +5555,7 @@ function buildOptimisticIOUReport( managerID: payerAccountID, ownerAccountID: payeeAccountID, participants, - reportID: generateReportID(), + reportID: optimisticIOUReportID ?? generateReportID(), stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, total, @@ -5680,6 +5682,7 @@ function getExpenseReportStateAndStatus(policy: OnyxEntry, isEmptyOptimi * @param currency * @param reimbursable – Whether the expense is reimbursable * @param parentReportActionID – The parent ReportActionID of the PolicyExpenseChat + * @param optimisticIOUReportID – Optimistic IOU report id */ function buildOptimisticExpenseReport( chatReportID: string | undefined, @@ -5689,6 +5692,7 @@ function buildOptimisticExpenseReport( currency: string, nonReimbursableTotal = 0, parentReportActionID?: string, + optimisticIOUReportID?: string, ): OptimisticExpenseReport { // The amount for Expense reports are stored as negative value in the database const storedTotal = total * -1; @@ -5703,7 +5707,7 @@ function buildOptimisticExpenseReport( const {stateNum, statusNum} = getExpenseReportStateAndStatus(policy); const expenseReport: OptimisticExpenseReport = { - reportID: generateReportID(), + reportID: optimisticIOUReportID ?? generateReportID(), chatReportID, policyID, type: CONST.REPORT.TYPE.EXPENSE, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 75b2d76270ee..da72ce6d3acc 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -426,6 +426,9 @@ type RequestMoneyInformation = { shouldPlaySound?: boolean; shouldHandleNavigation?: boolean; backToReport?: string; + optimisticChatReportID?: string; + optimisticIOUReportID?: string; + optimisticReportPreviewActionID?: string; }; type MoneyRequestInformationParams = { @@ -439,6 +442,9 @@ type MoneyRequestInformationParams = { retryParams?: StartSplitBilActionParams | CreateTrackExpenseParams | RequestMoneyInformation | ReplaceReceipt; isSplitExpense?: boolean; testDriveCommentReportActionID?: string; + optimisticChatReportID?: string; + optimisticIOUReportID?: string; + optimisticReportPreviewActionID?: string; }; type MoneyRequestOptimisticParams = { @@ -3240,6 +3246,9 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma retryParams, isSplitExpense, testDriveCommentReportActionID, + optimisticChatReportID, + optimisticIOUReportID, + optimisticReportPreviewActionID, } = moneyRequestInformation; const {payeeAccountID = userAccountID, payeeEmail = currentUserEmail, participant} = participantParams; const {policy, policyCategories, policyTagList} = policyParams; @@ -3283,6 +3292,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma isNewChatReport = true; chatReport = buildOptimisticChatReport({ participantList: [payerAccountID, payeeAccountID], + optimisticReportID: optimisticChatReportID, }); } @@ -3299,8 +3309,8 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma if (!iouReport || shouldCreateNewMoneyRequestReport) { iouReport = isPolicyExpenseChat - ? buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeAccountID, amount, currency) - : buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency); + ? buildOptimisticExpenseReport(chatReport.reportID, chatReport.policyID, payeeAccountID, amount, currency, undefined, undefined, optimisticIOUReportID) + : buildOptimisticIOUReport(payeeAccountID, payerAccountID, amount, chatReport.reportID, currency, undefined, undefined, optimisticIOUReportID); } else if (isPolicyExpenseChat) { // Splitting doesn’t affect the amount, so no adjustment is needed // The amount remains constant after the split @@ -3387,7 +3397,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma if (reportPreviewAction) { reportPreviewAction = updateReportPreview(iouReport, reportPreviewAction, false, comment, optimisticTransaction); } else { - reportPreviewAction = buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction); + reportPreviewAction = buildOptimisticReportPreview(chatReport, iouReport, comment, optimisticTransaction, undefined, optimisticReportPreviewActionID); chatReport.lastVisibleActionCreated = reportPreviewAction.created; // Generated ReportPreview action is a parent report action of the iou report. @@ -5188,6 +5198,9 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { shouldHandleNavigation = true, backToReport, shouldPlaySound = true, + optimisticChatReportID, + optimisticIOUReportID, + optimisticReportPreviewActionID, } = requestMoneyInformation; const {payeeAccountID} = participantParams; const parsedComment = getParsedComment(transactionParams.comment ?? ''); @@ -5266,6 +5279,9 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { existingTransaction: isDistanceRequestTransactionUtils(existingTransaction) ? existingTransaction : undefined, retryParams, testDriveCommentReportActionID, + optimisticChatReportID, + optimisticIOUReportID, + optimisticReportPreviewActionID, }); const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport.reportID; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index c6c7c3fb52b4..8180d7ab088f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -477,6 +477,10 @@ function IOURequestStepConfirmation({ return; } + const optimisticChatReportID = generateReportID(); + const optimisticIOUReportID = generateReportID(); + const optimisticReportPreviewActionID = generateReportID(); + transactions.forEach((item, index) => { const receipt = receiptFiles[item.transactionID]; const isTestReceipt = receipt?.isTestReceipt ?? false; @@ -488,6 +492,9 @@ function IOURequestStepConfirmation({ requestMoneyIOUActions({ report, + optimisticChatReportID, + optimisticIOUReportID, + optimisticReportPreviewActionID, participantParams: { payeeEmail: currentUserPersonalDetails.login, payeeAccountID: currentUserPersonalDetails.accountID, From af332c14a226a9833121388a1a876c2aff193d3a Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 17 Jun 2025 14:30:44 +0200 Subject: [PATCH 2/5] Update canUseMultiScan flag --- .../iou/request/step/IOURequestStepScan/index.native.tsx | 6 +++--- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 5fe47973e939..ca5c934dbb2f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -29,6 +29,7 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -102,9 +103,8 @@ function IOURequestStepScan({ const hasFlash = !!device?.hasFlash; const camera = useRef(null); const [flash, setFlash] = useState(false); - // TODO: use correct canUseMultiScan value when all multi-scan functionality is implemented - // const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; - const canUseMultiScan = false; + const {isBetaEnabled} = usePermissions(); + const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false); const [receiptFiles, setReceiptFiles] = useState([]); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true}); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 83b95aadf1d3..a6a7a8b6f1c6 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -133,9 +133,7 @@ function IOURequestStepScan({ const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [isLoadingReceipt, setIsLoadingReceipt] = useState(false); const isEditing = action === CONST.IOU.ACTION.EDIT; - // TODO: use correct canUseMultiScan value when all multi-scan functionality is implemented - // const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; - const canUseMultiScan = false; + const canUseMultiScan = isBetaEnabled(CONST.BETAS.NEWDOT_MULTI_SCAN) && !isEditing && iouType !== CONST.IOU.TYPE.SPLIT && !backTo && !backToReport; const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, { selector: (items) => Object.values(items ?? {}), From ad320b91a79418a456b3673b850d1e211b668f39 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 17 Jun 2025 14:37:23 +0200 Subject: [PATCH 3/5] Remove unnecessary return --- src/components/MoneyRequestConfirmationList.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 0f2499ca50aa..72b8cc287784 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -849,12 +849,6 @@ function MoneyRequestConfirmationList({ */ const confirm = useCallback( (paymentMethod: PaymentMethodType | undefined) => { - if (expensesNumber > 1) { - // TODO: remove early return when bulk expense creation is implemented - // https://github.com/Expensify/App/issues/61184 - return; - } - if (!!routeError || !transactionID) { return; } From ac07e22678fc975c9426859e84d88d19d36375e8 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 17 Jun 2025 14:43:18 +0200 Subject: [PATCH 4/5] Lint fix --- src/components/MoneyRequestConfirmationList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 72b8cc287784..47e2cc25a325 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -917,7 +917,6 @@ function MoneyRequestConfirmationList({ isMerchantEmpty, shouldDisplayFieldError, transaction, - expensesNumber, iouCategory.length, formError, iouType, From a9f2489cdfdbc8e5bff1aabe289ecff345314aac Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 17 Jun 2025 15:30:04 +0200 Subject: [PATCH 5/5] Fix created report action duplicates --- src/libs/ReportUtils.ts | 8 +++++--- src/libs/actions/IOU.ts | 6 ++++++ src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4c093feb3887..e2b3deb26d08 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -625,6 +625,7 @@ type OptimisticMoneyRequestEntities = { isPersonalTrackingExpense?: boolean; existingTransactionThreadReportID?: string; linkedTrackedExpenseReportAction?: ReportAction; + optimisticCreatedReportActionID?: string; }; type OptimisticTaskReport = SetRequired< @@ -6758,9 +6759,9 @@ function buildOptimisticGroupChatReport( * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param [created] - Action created time */ -function buildOptimisticCreatedReportAction(emailCreatingAction: string, created = DateUtils.getDBTime()): OptimisticCreatedReportAction { +function buildOptimisticCreatedReportAction(emailCreatingAction: string, created = DateUtils.getDBTime(), optimisticReportActionID?: string): OptimisticCreatedReportAction { return { - reportActionID: rand64(), + reportActionID: optimisticReportActionID ?? rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, actorAccountID: currentUserAccountID, @@ -7522,8 +7523,9 @@ function buildOptimisticMoneyRequestEntities({ isPersonalTrackingExpense, existingTransactionThreadReportID, linkedTrackedExpenseReportAction, + optimisticCreatedReportActionID, }: OptimisticMoneyRequestEntities): [OptimisticCreatedReportAction, OptimisticCreatedReportAction, OptimisticIOUReportAction, OptimisticChatReport, OptimisticCreatedReportAction | null] { - const createdActionForChat = buildOptimisticCreatedReportAction(payeeEmail); + const createdActionForChat = buildOptimisticCreatedReportAction(payeeEmail, undefined, optimisticCreatedReportActionID); // The `CREATED` action must be optimistically generated before the IOU action so that it won't appear after the IOU action in the chat. const iouActionCreationTime = DateUtils.getDBTime(); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index f82d2f18e1fa..fc5472e1d25d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -428,6 +428,7 @@ type RequestMoneyInformation = { shouldHandleNavigation?: boolean; backToReport?: string; optimisticChatReportID?: string; + optimisticCreatedReportActionID?: string; optimisticIOUReportID?: string; optimisticReportPreviewActionID?: string; }; @@ -444,6 +445,7 @@ type MoneyRequestInformationParams = { isSplitExpense?: boolean; testDriveCommentReportActionID?: string; optimisticChatReportID?: string; + optimisticCreatedReportActionID?: string; optimisticIOUReportID?: string; optimisticReportPreviewActionID?: string; }; @@ -3246,6 +3248,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma isSplitExpense, testDriveCommentReportActionID, optimisticChatReportID, + optimisticCreatedReportActionID, optimisticIOUReportID, optimisticReportPreviewActionID, } = moneyRequestInformation; @@ -3388,6 +3391,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma transactionID: optimisticTransaction.transactionID, paymentType: isSelectedManagerMcTest(participant.login) || transactionParams.receipt?.isTestDriveReceipt ? CONST.IOU.PAYMENT_TYPE.ELSEWHERE : undefined, existingTransactionThreadReportID: linkedTrackedExpenseReportAction?.childReportID, + optimisticCreatedReportActionID, linkedTrackedExpenseReportAction, }); @@ -5198,6 +5202,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { backToReport, shouldPlaySound = true, optimisticChatReportID, + optimisticCreatedReportActionID, optimisticIOUReportID, optimisticReportPreviewActionID, } = requestMoneyInformation; @@ -5279,6 +5284,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { retryParams, testDriveCommentReportActionID, optimisticChatReportID, + optimisticCreatedReportActionID, optimisticIOUReportID, optimisticReportPreviewActionID, }); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 70d9b4e52edb..23898ae0f546 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -32,6 +32,7 @@ import {isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpen import Log from '@libs/Log'; import navigateAfterInteraction from '@libs/Navigation/navigateAfterInteraction'; import Navigation from '@libs/Navigation/Navigation'; +import {rand64} from '@libs/NumberUtils'; import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import {generateReportID, getBankAccountRoute, getReportOrDraftReport, isProcessingReport, isReportOutstanding, isSelectedManagerMcTest} from '@libs/ReportUtils'; @@ -484,8 +485,9 @@ function IOURequestStepConfirmation({ } const optimisticChatReportID = generateReportID(); + const optimisticCreatedReportActionID = rand64(); const optimisticIOUReportID = generateReportID(); - const optimisticReportPreviewActionID = generateReportID(); + const optimisticReportPreviewActionID = rand64(); transactions.forEach((item, index) => { const receipt = receiptFiles[item.transactionID]; @@ -499,6 +501,7 @@ function IOURequestStepConfirmation({ requestMoneyIOUActions({ report, optimisticChatReportID, + optimisticCreatedReportActionID, optimisticIOUReportID, optimisticReportPreviewActionID, participantParams: {