Skip to content

Commit d83d0af

Browse files
authored
Merge pull request Expensify#79547 from TaduJR/fix-Report-Total-amount-shown-as-0.00-when-splitting-expense-offline-after-currency-change
fix: Report - Total amount shown as '0,00' when splitting expense offline after currency change
2 parents 01a7ec6 + c9e988d commit d83d0af

2 files changed

Lines changed: 161 additions & 1 deletion

File tree

src/libs/actions/IOU/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3241,8 +3241,17 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma
32413241
}
32423242

32433243
if (isSplitExpense && existingTransaction) {
3244-
const {convertedAmount: omittedConvertedAmount, ...existingTransactionWithoutConvertedAmount} = existingTransaction;
3244+
const {convertedAmount: originalConvertedAmount, ...existingTransactionWithoutConvertedAmount} = existingTransaction;
32453245
optimisticTransaction = fastMerge(existingTransactionWithoutConvertedAmount, optimisticTransaction, false);
3246+
3247+
// Calculate proportional convertedAmount for the split based on the original conversion rate
3248+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- modifiedAmount can be empty string
3249+
const originalAmount = Number(existingTransaction.modifiedAmount) || existingTransaction.amount;
3250+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- modifiedAmount can be empty string
3251+
const splitAmount = Number(optimisticTransaction.modifiedAmount) || optimisticTransaction.amount;
3252+
if (originalConvertedAmount && originalAmount && splitAmount) {
3253+
optimisticTransaction.convertedAmount = Math.round((originalConvertedAmount * splitAmount) / originalAmount);
3254+
}
32463255
}
32473256

32483257
// STEP 4: Build optimistic reportActions. We need:

tests/actions/IOUTest.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3620,6 +3620,157 @@ describe('actions/IOU', () => {
36203620
// Then the description should be the same since it was not changed
36213621
expect(splitTransaction?.comment?.comment).toBe('<h1>test</h1>');
36223622
});
3623+
3624+
it('should calculate proportional convertedAmount for split transactions with foreign currency', async () => {
3625+
jest.setTimeout(10 * 1000);
3626+
3627+
// Given: An expense report with AED currency and a USD transaction with convertedAmount
3628+
const originalAmount = -1000;
3629+
const originalConvertedAmount = -3673;
3630+
const reportID = rand64();
3631+
const originalTransactionID = rand64();
3632+
3633+
const expenseReport: Report = {
3634+
reportID,
3635+
type: CONST.REPORT.TYPE.EXPENSE,
3636+
currency: 'AED',
3637+
ownerAccountID: RORY_ACCOUNT_ID,
3638+
total: originalAmount,
3639+
};
3640+
3641+
const originalTransaction = {
3642+
transactionID: originalTransactionID,
3643+
amount: originalAmount,
3644+
modifiedAmount: '', // Empty string - the edge case that was causing the bug
3645+
currency: 'USD',
3646+
modifiedCurrency: '',
3647+
convertedAmount: originalConvertedAmount,
3648+
created: DateUtils.getDBTime(),
3649+
merchant: 'Test Merchant',
3650+
reportID,
3651+
comment: {},
3652+
} as unknown as Transaction;
3653+
3654+
const transactionThread: Report = {
3655+
reportID: rand64(),
3656+
type: CONST.REPORT.TYPE.CHAT,
3657+
parentReportID: reportID,
3658+
parentReportActionID: rand64(),
3659+
};
3660+
3661+
const iouAction: ReportAction = {
3662+
...buildOptimisticIOUReportAction({
3663+
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
3664+
amount: Math.abs(originalAmount),
3665+
currency: 'USD',
3666+
comment: '',
3667+
participants: [],
3668+
transactionID: originalTransactionID,
3669+
iouReportID: reportID,
3670+
}),
3671+
childReportID: transactionThread.reportID,
3672+
};
3673+
3674+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport);
3675+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${transactionThread.reportID}`, transactionThread);
3676+
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, {
3677+
[iouAction.reportActionID]: iouAction,
3678+
});
3679+
await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${originalTransactionID}`, originalTransaction);
3680+
3681+
const splitExpenses: SplitExpense[] = [
3682+
{
3683+
transactionID: rand64(),
3684+
amount: -500,
3685+
created: DateUtils.getDBTime(),
3686+
merchant: 'Test Merchant',
3687+
},
3688+
{
3689+
transactionID: rand64(),
3690+
amount: -500,
3691+
created: DateUtils.getDBTime(),
3692+
merchant: 'Test Merchant',
3693+
},
3694+
];
3695+
3696+
let allTransactions: OnyxCollection<Transaction>;
3697+
let allReports: OnyxCollection<Report>;
3698+
let allReportNameValuePairs: OnyxCollection<ReportNameValuePairs>;
3699+
await getOnyxData({
3700+
key: ONYXKEYS.COLLECTION.TRANSACTION,
3701+
waitForCollectionCallback: true,
3702+
callback: (value) => {
3703+
allTransactions = value;
3704+
},
3705+
});
3706+
await getOnyxData({
3707+
key: ONYXKEYS.COLLECTION.REPORT,
3708+
waitForCollectionCallback: true,
3709+
callback: (value) => {
3710+
allReports = value;
3711+
},
3712+
});
3713+
await getOnyxData({
3714+
key: ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS,
3715+
waitForCollectionCallback: true,
3716+
callback: (value) => {
3717+
allReportNameValuePairs = value;
3718+
},
3719+
});
3720+
3721+
// When splitting the expense
3722+
updateSplitTransactionsFromSplitExpensesFlow({
3723+
allTransactionsList: allTransactions,
3724+
allReportsList: allReports,
3725+
allReportNameValuePairsList: allReportNameValuePairs,
3726+
transactionData: {
3727+
reportID,
3728+
originalTransactionID,
3729+
splitExpenses,
3730+
splitExpensesTotal: -1000,
3731+
},
3732+
searchContext: {
3733+
currentSearchHash: -1,
3734+
},
3735+
policyCategories: undefined,
3736+
policy: undefined,
3737+
policyRecentlyUsedCategories: [],
3738+
iouReport: expenseReport,
3739+
firstIOU: iouAction,
3740+
isASAPSubmitBetaEnabled: false,
3741+
currentUserPersonalDetails,
3742+
transactionViolations: {},
3743+
policyRecentlyUsedCurrencies: [],
3744+
quickAction: undefined,
3745+
iouReportNextStep: undefined,
3746+
});
3747+
3748+
await waitForBatchedUpdates();
3749+
3750+
// Then each split transaction should have proportional convertedAmount
3751+
// Formula: Math.round((originalConvertedAmount * splitAmount) / originalAmount)
3752+
const expectedProportionalConvertedAmount = -1836;
3753+
3754+
const splitTransactions = await new Promise<Transaction[]>((resolve) => {
3755+
const connection = Onyx.connect({
3756+
key: ONYXKEYS.COLLECTION.TRANSACTION,
3757+
waitForCollectionCallback: true,
3758+
callback: (transactions) => {
3759+
Onyx.disconnect(connection);
3760+
const splits = Object.values(transactions ?? {}).filter(
3761+
(t) => t?.transactionID !== originalTransactionID && t?.comment?.originalTransactionID === originalTransactionID,
3762+
);
3763+
resolve(splits as Transaction[]);
3764+
},
3765+
});
3766+
});
3767+
3768+
expect(splitTransactions.length).toBe(2);
3769+
3770+
for (const splitTransaction of splitTransactions) {
3771+
expect(splitTransaction.convertedAmount).toBe(expectedProportionalConvertedAmount);
3772+
}
3773+
});
36233774
});
36243775

36253776
describe('startSplitBill', () => {

0 commit comments

Comments
 (0)