diff --git a/src/libs/actions/IOU/SplitTransactionUpdate.ts b/src/libs/actions/IOU/SplitTransactionUpdate.ts index 892df2ab762e..bb5ec2fe53a9 100644 --- a/src/libs/actions/IOU/SplitTransactionUpdate.ts +++ b/src/libs/actions/IOU/SplitTransactionUpdate.ts @@ -211,6 +211,7 @@ function updateSplitTransactions({ created: split.created, merchant: split?.merchant ?? '', transactionID: split.transactionID, + reportID: split.reportID, comment: { comment: currentDescription, }, @@ -225,7 +226,7 @@ function updateSplitTransactions({ }) ?? []; changesInReportTotal -= splitExpensesTotal; - const onyxData: OnyxData = { + const onyxData: OnyxData = { successData: [], failureData: [], optimisticData: [], @@ -476,6 +477,7 @@ function updateSplitTransactions({ comment: currentSplit?.comment?.comment, customUnitRateID: currentSplit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID, } as TransactionChanges; + delete transactionChanges.reportID; const existing = getTransactionDetails(splitTransaction); const oldTransactionChanges = { @@ -1258,6 +1260,29 @@ function updateSplitTransactions({ } } + if (!isCreationOfSplits && !isReverseSplitOperation) { + const splitUpdateFailureDataKeys: Array<`${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}` | `${typeof ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${string}`> = [ + `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${originalTransactionID}`, + `${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`, + ...splitExpenses.filter((splitExpense) => !!splitExpense.transactionID).map((splitExpense) => `${ONYXKEYS.COLLECTION.TRANSACTION}${splitExpense.transactionID}` as const), + ]; + + for (const key of new Set(splitUpdateFailureDataKeys)) { + onyxData.failureData?.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key, + value: {errors: null}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key, + value: {errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericEditFailureMessage')}, + }, + ); + } + } + if (isReverseSplitOperation) { const parameters = { ...splits.at(0), diff --git a/tests/actions/IOUTest/SplitTest.ts b/tests/actions/IOUTest/SplitTest.ts index ec8ad0d6e574..c5b36ff33a4d 100644 --- a/tests/actions/IOUTest/SplitTest.ts +++ b/tests/actions/IOUTest/SplitTest.ts @@ -48,7 +48,7 @@ import {createRandomReport} from '../../utils/collections/reports'; import createRandomTransaction from '../../utils/collections/transaction'; import getOnyxValue from '../../utils/getOnyxValue'; import type {MockFetch} from '../../utils/TestHelper'; -import {getGlobalFetchMock, getOnyxData} from '../../utils/TestHelper'; +import {getGlobalFetchMock, getOnyxData, translateLocal} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../../utils/waitForNetworkPromises'; @@ -4266,6 +4266,84 @@ describe('updateSplitTransactions', () => { return {splitTransactionID1, splitTransactionID2, splitTransactionID3}; }; + it('should send reportID and localized failure fallback when updating existing split transactions', async () => { + const {expenseReport, originalTransactionID} = await createBaseExpense(); + + if (!originalTransactionID || !expenseReport?.reportID) { + throw new Error('Missing original transaction data'); + } + + const iouAction = getIOUActionForReportID(expenseReport.reportID, originalTransactionID); + const {splitTransactionID1, splitTransactionID2} = await splitToTwo(expenseReport, originalTransactionID, iouAction); + const splitTransaction1 = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransactionID1}`); + const splitTransaction2 = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransactionID2}`); + const {allTransactions, allReports, allReportNameValuePairs} = await getCollections(); + const policyTags = await getPolicyTags(expenseReport.reportID); + const reports = getTransactionAndExpenseReports(expenseReport.reportID); + + const APIlib = require('@libs/API') as {write: (...args: unknown[]) => Promise}; + const originalWrite = APIlib.write; + const writeSpy = jest.spyOn(APIlib, 'write').mockImplementation((...args) => originalWrite(...args)); + + updateSplitTransactions({ + allTransactionsList: allTransactions, + allReportsList: allReports, + allReportNameValuePairsList: allReportNameValuePairs, + transactionData: { + reportID: expenseReport.reportID, + originalTransactionID, + splitExpenses: [ + {transactionID: splitTransactionID1, reportID: splitTransaction1?.reportID, amount: amount / 2 + 100, created: DateUtils.getDBTime()}, + {transactionID: splitTransactionID2, reportID: splitTransaction2?.reportID, amount: amount / 2 - 100, created: DateUtils.getDBTime()}, + ], + splitExpensesTotal: amount, + }, + searchContext: {currentSearchHash: -2}, + policyCategories: undefined, + policy: undefined, + policyRecentlyUsedCategories: [], + iouReport: expenseReport, + firstIOU: undefined, + isASAPSubmitBetaEnabled: false, + currentUserPersonalDetails, + transactionViolations: {}, + policyRecentlyUsedCurrencies: [], + quickAction: undefined, + iouReportNextStep: undefined, + betas: [CONST.BETAS.ALL], + policyTags, + personalDetails: {[RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL}}, + transactionReport: reports.transactionReport, + expenseReport: reports.expenseReport, + isOffline: false, + }); + + const updateSplitTransactionCall = writeSpy.mock.calls.find(([command]) => command === WRITE_COMMANDS.UPDATE_SPLIT_TRANSACTION); + writeSpy.mockRestore(); + + expect(updateSplitTransactionCall).toBeDefined(); + const [, params, onyxData] = updateSplitTransactionCall ?? []; + const updateParams = params as Record; + expect(updateParams['splits[0][reportID]']).toBe(splitTransaction1?.reportID); + expect(updateParams['splits[1][reportID]']).toBe(splitTransaction2?.reportID); + + const failureData = (onyxData as {failureData?: Array<{key: string; value?: {errors?: Record | null}}>})?.failureData ?? []; + const getFailureErrorUpdates = (key: string) => + failureData.filter((update) => update.key === key && update.value && Object.hasOwn(update.value, 'errors')).map((update) => update.value?.errors); + + const localizedFallback = translateLocal('iou.error.genericEditFailureMessage'); + const draftFailureErrors = getFailureErrorUpdates(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${originalTransactionID}`); + const originalTransactionFailureErrors = getFailureErrorUpdates(`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`); + const splitTransactionFailureErrors = getFailureErrorUpdates(`${ONYXKEYS.COLLECTION.TRANSACTION}${splitTransactionID1}`); + + expect(draftFailureErrors.slice(-2)).toEqual([null, expect.any(Object)]); + expect(Object.values(draftFailureErrors.at(-1) ?? {}).at(0)).toBe(localizedFallback); + expect(originalTransactionFailureErrors.slice(-2)).toEqual([null, expect.any(Object)]); + expect(Object.values(originalTransactionFailureErrors.at(-1) ?? {}).at(0)).toBe(localizedFallback); + expect(splitTransactionFailureErrors.slice(-2)).toEqual([null, expect.any(Object)]); + expect(Object.values(splitTransactionFailureErrors.at(-1) ?? {}).at(0)).toBe(localizedFallback); + }); + it('should keep all split transactions on hold when splitting a held transaction', async () => { const {expenseReport, transactionThreadReportID, originalTransactionID} = await createBaseExpense(); const {allReports, allReportActions} = await getCollections();