Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0f8c971
Recalculate report totals after moving expenses and hide Pay button w…
nabi-ebrahimi Dec 11, 2025
9217fc4
removed unused variable
nabi-ebrahimi Dec 11, 2025
ef5eeeb
refine the code a little bit
nabi-ebrahimi Dec 11, 2025
c52a49a
applied bot suggested refinement
nabi-ebrahimi Dec 11, 2025
bfa1e41
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Dec 12, 2025
29d9ac7
refine unit tests
nabi-ebrahimi Dec 12, 2025
1cc8909
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Dec 17, 2025
559184c
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Dec 24, 2025
3bcb4cd
grey out the values report rows and report titles Backend calculation
nabi-ebrahimi Dec 26, 2025
daecf24
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Dec 26, 2025
0a95c03
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Dec 27, 2025
ec47602
fixed type error
nabi-ebrahimi Dec 27, 2025
3de1c21
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Jan 6, 2026
15b3c80
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Jan 7, 2026
1686ad2
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Jan 7, 2026
6e97a34
fixed type error in unit tests
nabi-ebrahimi Jan 7, 2026
5c0f8b9
Merge branch 'main' into fix/reports-old-amount-and-pay-button-after-…
nabi-ebrahimi Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/AddUnreportedExpenseFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, repor
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true});
const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true});
const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true});

const handleConfirm = () => {
Expand Down Expand Up @@ -73,6 +74,7 @@ function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, repor
email: session?.email ?? '',
newReport: reportToConfirm,
policy,
allSnapshots,
reportNextStep,
policyCategories,
allTransactions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@

const addExpenseDropdownOptions = useMemo(
() => getAddExpenseDropdownOptions(expensifyIcons, report?.reportID, policy, undefined, undefined, lastDistanceExpenseType),
[report?.reportID, policy, lastDistanceExpenseType, expensifyIcons.Location],

Check warning on line 194 in src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

React Hook useMemo has a missing dependency: 'expensifyIcons'. Either include it or remove the dependency array

Check warning on line 194 in src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useMemo has a missing dependency: 'expensifyIcons'. Either include it or remove the dependency array
);

const hasPendingAction = useMemo(() => {
Expand Down Expand Up @@ -641,12 +641,14 @@
</View>
)}

<MoneyRequestReportTotalSpend
isEmptyTransactions={isEmptyTransactions}
totalDisplaySpend={totalDisplaySpend}
report={report}
hasPendingAction={hasPendingAction}
/>
<OfflineWithFeedback pendingAction={report?.pendingFields?.total}>
<MoneyRequestReportTotalSpend
isEmptyTransactions={isEmptyTransactions}
totalDisplaySpend={totalDisplaySpend}
report={report}
hasPendingAction={hasPendingAction}
/>
</OfflineWithFeedback>
</View>
</View>
<Modal
Expand Down
13 changes: 12 additions & 1 deletion src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ function getReportSections({
}

if (shouldShow) {
const reportPendingAction = reportItem?.pendingAction ?? reportItem?.pendingFields?.preview;
const reportPendingAction = reportItem?.pendingAction ?? reportItem?.pendingFields?.total ?? reportItem?.pendingFields?.createReport ?? reportItem?.pendingFields?.preview;
const shouldShowBlankTo = !reportItem || isOpenExpenseReport(reportItem);
const allActions = getActions(data, allViolations, key, currentSearch, currentUserEmail, bankAccountList, actions);

Expand Down Expand Up @@ -3511,6 +3511,16 @@ function getTableMinWidth(columns: SearchColumnType[]) {
return minWidth;
}

type OnyxSnapshotKey = `${typeof ONYXKEYS.COLLECTION.SNAPSHOT}${string}`;

function getSnapshotKeys(allSnapshots: OnyxCollection<OnyxTypes.SearchResults>) {
if (!allSnapshots) {
return [];
}

return Object.keys(allSnapshots || {}) as OnyxSnapshotKey[];
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need the snapshot keys? How will it impact performance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sobitneupane Thanks for the review. We need to update transactions and reports within the searched data, which requires retrieving all current snapshot keys and iterating over them to update the relevant transaction or report across all search result snapshots. I don’t believe this will have a significant performance impact, since we already perform optimistic updates throughout the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sobitneupane, I think we should wait for this pr to get merged. then we don't need this function anymore. thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Could you please add Hold on the title with the PR number?

export {
getSuggestedSearches,
getDefaultActionableSearchMenuItem,
Expand Down Expand Up @@ -3556,6 +3566,7 @@ export {
getSettlementStatus,
getSettlementStatusBadgeProps,
getTransactionFromTransactionListItem,
getSnapshotKeys,
getSearchColumnTranslationKey,
getTableMinWidth,
getCustomColumns,
Expand Down
215 changes: 205 additions & 10 deletions src/libs/actions/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
hasViolations as hasViolationsReportUtils,
shouldEnableNegative,
} from '@libs/ReportUtils';
import {getSnapshotKeys} from '@libs/SearchUIUtils';
import {isManagedCardTransaction, isOnHold, shouldClearConvertedAmount, waypointHasValidAddress} from '@libs/TransactionUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import CONST from '@src/CONST';
Expand All @@ -48,6 +49,7 @@
ReportAction,
ReportNextStepDeprecated,
ReviewDuplicates,
SearchResults,
Transaction,
TransactionViolation,
TransactionViolations,
Expand All @@ -59,7 +61,7 @@
import {getPolicyTagsData} from './Policy/Tag';
import {getCurrentUserAccountID} from './Report';

let allTransactionDrafts: OnyxCollection<Transaction> = {};

Check warning on line 64 in src/libs/actions/Transaction.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION_DRAFT,
waitForCollectionCallback: true,
Expand All @@ -68,7 +70,7 @@
},
});

let allReports: OnyxCollection<Report> = {};

Check warning on line 73 in src/libs/actions/Transaction.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
Expand All @@ -80,7 +82,7 @@
},
});

const allTransactionViolation: OnyxCollection<TransactionViolation[]> = {};

Check warning on line 85 in src/libs/actions/Transaction.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
callback: (transactionViolation, key) => {
Expand All @@ -92,7 +94,7 @@
},
});

let allTransactionViolations: TransactionViolations = [];

Check warning on line 97 in src/libs/actions/Transaction.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
callback: (val) => (allTransactionViolations = val ?? []),
Expand Down Expand Up @@ -701,6 +703,7 @@
isASAPSubmitBetaEnabled: boolean;
accountID: number;
email: string;
allSnapshots: OnyxCollection<SearchResults>;
newReport?: OnyxEntry<Report>;
policy?: OnyxEntry<Policy>;
reportNextStep?: OnyxEntry<ReportNextStepDeprecated>;
Expand All @@ -715,6 +718,7 @@
email,
newReport,
policy,
allSnapshots,
reportNextStep,
policyCategories,
allTransactions,
Expand Down Expand Up @@ -742,6 +746,7 @@
| typeof ONYXKEYS.COLLECTION.TRANSACTION
| typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS
| typeof ONYXKEYS.COLLECTION.NEXT_STEP
| typeof ONYXKEYS.COLLECTION.SNAPSHOT
>
> = [];
const successData: Array<
Expand All @@ -751,6 +756,7 @@
| typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS
| typeof ONYXKEYS.COLLECTION.TRANSACTION
| typeof ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS
| typeof ONYXKEYS.COLLECTION.SNAPSHOT
>
> = [];

Expand Down Expand Up @@ -1006,16 +1012,122 @@
const targetReportID = isUnreported ? selfDMReportID : reportID;
const {amount: transactionAmount = 0, currency: transactionCurrency} = getTransactionDetails(transaction, undefined, undefined, allowNegative) ?? {};
const oldReportTotal = oldReport?.total ?? 0;
const updatedReportTotal = transactionAmount < 0 ? oldReportTotal - transactionAmount : oldReportTotal + transactionAmount;

if (oldReport && oldReport.currency === transactionCurrency) {
updatedReportTotals[oldReportID] = updatedReportTotals[oldReportID] ? updatedReportTotals[oldReportID] : updatedReportTotal;
updatedReportNonReimbursableTotals[oldReportID] =
(updatedReportNonReimbursableTotals[oldReportID] ? updatedReportNonReimbursableTotals[oldReportID] : (oldReport?.nonReimbursableTotal ?? 0)) +
(transaction?.reimbursable ? 0 : transactionAmount);
updatedReportUnheldNonReimbursableTotals[oldReportID] =
(updatedReportUnheldNonReimbursableTotals[oldReportID] ? updatedReportUnheldNonReimbursableTotals[oldReportID] : (oldReport?.unheldNonReimbursableTotal ?? 0)) +
(transaction?.reimbursable && !isOnHold(transaction) ? 0 : transactionAmount);

if (oldReport) {
const oldReportCurrency = oldReport.currency;
const remainingTransactions = getReportTransactions(oldReportID).filter(
(t) => t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && !transactionIDs.includes(t.transactionID),
);

const willBeEmpty = remainingTransactions.length === 0;

if (willBeEmpty) {
updatedReportTotals[oldReportID] = 0;
updatedReportNonReimbursableTotals[oldReportID] = 0;
updatedReportUnheldNonReimbursableTotals[oldReportID] = 0;
} else if (oldReportCurrency === transactionCurrency) {
const baseTotal = updatedReportTotals[oldReportID] ?? oldReportTotal;

const baseNonReimb = updatedReportNonReimbursableTotals[oldReportID] ?? oldReport?.nonReimbursableTotal ?? 0;

const baseUnheld = updatedReportUnheldNonReimbursableTotals[oldReportID] ?? oldReport?.unheldNonReimbursableTotal ?? 0;

let addToNonReimb = 0;
let addToUnheld = 0;

if (!transaction?.reimbursable) {
addToNonReimb = transactionAmount;

if (!isOnHold(transaction)) {
addToUnheld = transactionAmount;
}
}

updatedReportTotals[oldReportID] = baseTotal + transactionAmount;
updatedReportNonReimbursableTotals[oldReportID] = baseNonReimb + addToNonReimb;
updatedReportUnheldNonReimbursableTotals[oldReportID] = baseUnheld + addToUnheld;
} else {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`,
value: {
pendingFields: {
preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
total: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
});

failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`,
value: {
pendingFields: {
preview: null,
total: null,
},
},
});

successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`,
value: {
pendingFields: {
preview: null,
total: null,
},
},
});

const allSnapshotKeys = getSnapshotKeys(allSnapshots);

if (allSnapshotKeys?.length && allSnapshotKeys.length > 0) {
for (const key of allSnapshotKeys) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`]: {
pendingFields: {
total: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
} as Partial<Report>,
});

failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`]: {
pendingFields: {
total: null,
},
},
},
} as Partial<Report>,
});

successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${oldReport.reportID}`]: {
pendingFields: {
total: null,
},
},
},
} as Partial<Report>,
});
}
}
}
}

if (targetReportID) {
Expand Down Expand Up @@ -1044,6 +1156,89 @@
const currentUnheldNonReimbursableTotal = updatedReportUnheldNonReimbursableTotals[targetReportID] ?? targetReport?.unheldNonReimbursableTotal ?? 0;
updatedReportUnheldNonReimbursableTotals[targetReportID] = currentUnheldNonReimbursableTotal + (transactionReimbursable && !isOnHold(transaction) ? 0 : convertedAmount);
}

if (transactionCurrency !== targetReport?.currency) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`,
value: {
pendingFields: {
preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
total: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
});

failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`,
value: {
pendingFields: {
preview: null,
total: null,
},
},
});

successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`,
value: {
pendingFields: {
preview: null,
total: null,
},
},
});

const allSnapshotKeys = getSnapshotKeys(allSnapshots);

if (allSnapshotKeys?.length && allSnapshotKeys.length > 0) {
for (const key of allSnapshotKeys) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`]: {
pendingFields: {
total: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
} as Partial<Report>,
});

failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`]: {
pendingFields: {
total: null,
},
},
},
} as Partial<Report>,
});

successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key,
value: {
data: {
[`${ONYXKEYS.COLLECTION.REPORT}${targetReportID}`]: {
pendingFields: {
total: null,
},
},
},
} as Partial<Report>,
});
}
}
}
}

// 4. Optimistically update the IOU action reportID
Expand Down
4 changes: 3 additions & 1 deletion src/pages/NewReportWorkspaceSelectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag
const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, accountID ?? CONST.DEFAULT_NUMBER_ID, email ?? '');
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
const [hasDismissedEmptyReportsConfirmation] = useOnyx(ONYXKEYS.NVP_EMPTY_REPORTS_CONFIRMATION_DISMISSED, {canBeMissing: true});

const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true});
const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true});
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
Expand Down Expand Up @@ -136,6 +136,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag
accountID: currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
email: currentUserPersonalDetails?.email ?? '',
newReport: optimisticReport,
allSnapshots,
policy: policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`],
reportNextStep,
policyCategories: undefined,
Expand Down Expand Up @@ -170,6 +171,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag
navigateToNewReport,
allReportNextSteps,
backTo,
allSnapshots,
allTransactions,
activePolicyID,
clearSelectedTransactions,
Expand Down
4 changes: 4 additions & 0 deletions src/pages/Search/SearchTransactionsChangeReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function SearchTransactionsChangeReport() {
const hasPerDiemTransactions = useHasPerDiemTransactions(selectedTransactionsKeys);
const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(hasPerDiemTransactions);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true});
const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
const {isBetaEnabled} = usePermissions();
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
Expand Down Expand Up @@ -80,6 +81,7 @@ function SearchTransactionsChangeReport() {
changeTransactionsReport({
transactionIDs: selectedTransactionsKeys,
isASAPSubmitBetaEnabled,
allSnapshots,
accountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
email: session?.email ?? '',
newReport: optimisticReport,
Expand Down Expand Up @@ -125,6 +127,7 @@ function SearchTransactionsChangeReport() {
accountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
email: session?.email ?? '',
newReport: destinationReport,
allSnapshots,
policy: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.policyID}`],
reportNextStep,
policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${item.policyID}`],
Expand All @@ -145,6 +148,7 @@ function SearchTransactionsChangeReport() {
changeTransactionsReport({
transactionIDs: selectedTransactionsKeys,
isASAPSubmitBetaEnabled,
allSnapshots,
accountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
email: session?.email ?? '',
allTransactions,
Expand Down
Loading
Loading