Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
48f6c11
Implement core "Duplicate report" action logic.
Krishna2323 Mar 2, 2026
ae47408
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 5, 2026
c280547
Implement duplicate report action with proper transaction routing
Krishna2323 Mar 5, 2026
7bafa0f
fix ESLint.
Krishna2323 Mar 6, 2026
5a45141
Fix duplicate report preview, remove navigation, and suppress multipl…
Krishna2323 Mar 6, 2026
bdd2c7d
Rename DUPLICATE to DUPLICATE_EXPENSE and add duplicate report UI fee…
Krishna2323 Mar 6, 2026
e2eebe2
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 6, 2026
5715b18
Return iouReport from createDistanceRequest and submitPerDiemExpense …
Krishna2323 Mar 6, 2026
67c0c1d
address codex review.
Krishna2323 Mar 6, 2026
596552b
add more tests.
Krishna2323 Mar 6, 2026
3c85160
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 10, 2026
9fa3dce
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 10, 2026
b2a8595
fix tests.
Krishna2323 Mar 10, 2026
58e368e
Merge branch 'Expensify:main' into krishna2323/issue/82153
Krishna2323 Mar 12, 2026
2713647
fix split distance transaction case.
Krishna2323 Mar 12, 2026
213517b
minor fix.
Krishna2323 Mar 12, 2026
80a7aa7
add more tests.
Krishna2323 Mar 12, 2026
4d21ec2
fix: pass existingIOUReport through distance and per-diem expense cre…
Krishna2323 Mar 12, 2026
aaf3f2a
Merge branch 'Expensify:main' into krishna2323/issue/82153
Krishna2323 Mar 13, 2026
972a418
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 15, 2026
f6a99b2
fix prettier.
Krishna2323 Mar 15, 2026
efc1b0d
refactor: extract shared helpers from duplicateExpenseTransaction and…
Krishna2323 Mar 15, 2026
649b74b
fix: ensure positive amounts and clear stale currency fields when dup…
Krishna2323 Mar 15, 2026
88923cd
fix: add eligibility guards and hide duplicate report when no target …
Krishna2323 Mar 15, 2026
98a482f
fix: defer duplicate report action to prevent popover menu from closi…
Krishna2323 Mar 15, 2026
a240a75
fix: correct cross-workspace detection and update test for missing ta…
Krishna2323 Mar 15, 2026
1a1a67a
fix: pass parentChatReport from UI and align report duplication with …
Krishna2323 Mar 16, 2026
af7e206
update translations.
Krishna2323 Mar 16, 2026
1fcc680
Merge remote-tracking branch 'origin/main' into krishna2323/issue/82153
Krishna2323 Mar 16, 2026
a778643
Merge remote-tracking branch 'upstream/main' into krishna2323/issue/8…
Krishna2323 Mar 16, 2026
7681dd2
Merge branch 'main' into krishna2323/issue/82153
Krishna2323 Mar 17, 2026
38cb70a
Merge branch 'Expensify:main' into krishna2323/issue/82153
Krishna2323 Mar 17, 2026
f9d54ba
Close popover after duplicate report animation completes
Krishna2323 Mar 17, 2026
b191499
Fix popover auto-closing on reopen after duplicate report action
Krishna2323 Mar 18, 2026
5e049e5
Use source report's workspace for duplicate report instead of default
Krishna2323 Mar 18, 2026
9611b6b
pass sourceReport from UI
Krishna2323 Mar 18, 2026
6485987
fix tests.
Krishna2323 Mar 18, 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: 1 addition & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,7 @@ const CONST = {
EXPORT: 'export',
PAY: 'pay',
MERGE: 'merge',
DUPLICATE: 'duplicate',
DUPLICATE_EXPENSE: 'duplicateExpense',
DUPLICATE_REPORT: 'duplicateReport',
MOVE_EXPENSE: 'moveExpense',
},
Expand Down
73 changes: 62 additions & 11 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useThrottledButtonState from '@hooks/useThrottledButtonState';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
import useTransactionViolations from '@hooks/useTransactionViolations';
import {duplicateExpenseTransaction as duplicateTransactionAction} from '@libs/actions/IOU/Duplicate';
import {duplicateReport as duplicateReportAction, duplicateExpenseTransaction as duplicateTransactionAction} from '@libs/actions/IOU/Duplicate';
import {openOldDotLink} from '@libs/actions/Link';
import {setupMergeTransactionDataAndNavigate} from '@libs/actions/MergeTransaction';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
Expand Down Expand Up @@ -237,7 +237,8 @@ function MoneyReportHeader({
| PlatformStackRouteProp<RightModalNavigatorParamList, typeof SCREENS.RIGHT_MODAL.SEARCH_MONEY_REQUEST_REPORT>
| PlatformStackRouteProp<RightModalNavigatorParamList, typeof SCREENS.RIGHT_MODAL.SEARCH_REPORT>
>();
const {login: currentUserLogin, accountID, email} = useCurrentUserPersonalDetails();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const {login: currentUserLogin, accountID, email} = currentUserPersonalDetails;
const personalDetails = usePersonalDetails();
const defaultExpensePolicy = useDefaultExpensePolicy();
const activePolicyExpenseChat = getPolicyExpenseChat(accountID, defaultExpensePolicy?.id);
Expand Down Expand Up @@ -421,7 +422,21 @@ function MoneyReportHeader({
const shouldShowSplitIndicator = isExpenseSplit && (hasMultipleSplits || isReportOpen);

const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [isDuplicateReportActive, temporarilyDisableDuplicateReportAction] = useThrottledButtonState();
const dropdownMenuRef = useRef<ButtonWithDropdownMenuRef>(null);
const wasDuplicateReportTriggered = useRef(false);

const handleOptionsMenuHide = useCallback(() => {
wasDuplicateReportTriggered.current = false;
}, []);

useEffect(() => {
if (!isDuplicateReportActive || !wasDuplicateReportTriggered.current) {
return;
}
wasDuplicateReportTriggered.current = false;
dropdownMenuRef.current?.setIsMenuVisible(false);
Comment on lines +437 to +438
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we need to clean up this effect? Otherwise, we have this issue

Screen.Recording.2026-03-18.at.13.29.37.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

}, [isDuplicateReportActive]);

const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);
const [paymentType, setPaymentType] = useState<PaymentMethodType>();
Expand Down Expand Up @@ -1867,11 +1882,11 @@ function MoneyReportHeader({
setupMergeTransactionDataAndNavigate(currentTransaction.transactionID, [currentTransaction], localeCompare);
},
},
[CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE]: {
[CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE_EXPENSE]: {
text: isDuplicateActive ? translate('common.duplicateExpense') : translate('common.duplicated'),
icon: isDuplicateActive ? expensifyIcons.ExpenseCopy : expensifyIcons.Checkmark,
iconFill: isDuplicateActive ? undefined : theme.icon,
value: CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE,
value: CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE_EXPENSE,
onSelected: () => {
if (hasCustomUnitOutOfPolicyViolation) {
setRateErrorModalVisible(true);
Expand Down Expand Up @@ -1899,15 +1914,49 @@ function MoneyReportHeader({
shouldCloseModalOnSelect: shouldDuplicateCloseModalOnSelect,
},
[CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE_REPORT]: {
text: translate('common.duplicateReport'),
icon: expensifyIcons.ReportCopy,
text: isDuplicateReportActive ? translate('common.duplicateReport') : translate('common.duplicated'),
icon: isDuplicateReportActive ? expensifyIcons.ReportCopy : expensifyIcons.Checkmark,
iconFill: isDuplicateReportActive ? undefined : theme.icon,
value: CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE_REPORT,
sentryLabel: CONST.SENTRY_LABEL.MORE_MENU.DUPLICATE_REPORT,
// To be implemented in https://github.com/Expensify/App/issues/82153
// onSelected: () => {
// },
// Remove after implementation
shouldShow: false,
shouldShow: !!defaultExpensePolicy,
shouldCloseModalOnSelect: false,
onSelected: () => {
if (!isDuplicateReportActive) {
return;
}

temporarilyDisableDuplicateReportAction();
wasDuplicateReportTriggered.current = true;

const targetPolicyForDuplicate = policy ?? defaultExpensePolicy;
const targetChatForDuplicate = policy ? chatReport : activePolicyExpenseChat;
const activePolicyCategories = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${targetPolicyForDuplicate?.id}`] ?? {};

// eslint-disable-next-line @typescript-eslint/no-deprecated
InteractionManager.runAfterInteractions(() => {
duplicateReportAction({
sourceReport: moneyRequestReport,
sourceReportTransactions: nonPendingDeleteTransactions,
sourceReportName: moneyRequestReport?.reportName ?? '',
targetPolicy: targetPolicyForDuplicate ?? undefined,
targetPolicyCategories: activePolicyCategories,
targetPolicyTags: allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${targetPolicyForDuplicate?.id}`] ?? {},
parentChatReport: targetChatForDuplicate,
ownerPersonalDetails: currentUserPersonalDetails,
isASAPSubmitBetaEnabled,
betas,
personalDetails,
quickAction,
policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [],
draftTransactionIDs,
isSelfTourViewed,
transactionViolations: allTransactionViolations,
translate,
recentWaypoints: recentWaypoints ?? [],
});
});
},
},
[CONST.REPORT.SECONDARY_ACTIONS.CHANGE_WORKSPACE]: {
text: translate('iou.changeWorkspace'),
Expand Down Expand Up @@ -2416,6 +2465,7 @@ function MoneyReportHeader({
primaryAction={primaryAction}
applicableSecondaryActions={applicableSecondaryActions}
dropdownMenuRef={dropdownMenuRef}
onOptionsMenuHide={handleOptionsMenuHide}
ref={kycWallRef}
/>
)}
Expand All @@ -2438,6 +2488,7 @@ function MoneyReportHeader({
primaryAction={primaryAction}
applicableSecondaryActions={applicableSecondaryActions}
dropdownMenuRef={dropdownMenuRef}
onOptionsMenuHide={handleOptionsMenuHide}
ref={kycWallRef}
/>
)}
Expand Down
5 changes: 5 additions & 0 deletions src/components/MoneyReportHeaderKYCDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type MoneyReportHeaderKYCDropdownProps = Omit<KYCWallProps, 'children' | 'enable

/** Ref for the inner ButtonWithDropdownMenu */
dropdownMenuRef?: React.Ref<ButtonWithDropdownMenuRef>;

/** Callback fired when the dropdown menu hides */
onOptionsMenuHide?: () => void;
};

function MoneyReportHeaderKYCDropdown({
Expand All @@ -39,6 +42,7 @@ function MoneyReportHeaderKYCDropdown({
customText,
shouldShowSuccessStyle,
dropdownMenuRef,
onOptionsMenuHide,
ref,
...props
}: MoneyReportHeaderKYCDropdownProps) {
Expand Down Expand Up @@ -84,6 +88,7 @@ function MoneyReportHeaderKYCDropdown({
isSplitButton={false}
wrapperStyle={shouldDisplayNarrowVersion && [!primaryAction && !customText && styles.flex1, !!customText && styles.w100]}
shouldUseModalPaddingStyle
onOptionsMenuHide={onOptionsMenuHide}
sentryLabel={CONST.SENTRY_LABEL.MORE_MENU.MORE_BUTTON}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -534,11 +534,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
setupMergeTransactionDataAndNavigate(transaction.transactionID, [transaction], localeCompare, [], false, isOnSearch);
},
},
[CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE]: {
[CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.DUPLICATE]: {
text: isDuplicateActive ? translate('common.duplicateExpense') : translate('common.duplicated'),
icon: isDuplicateActive ? expensifyIcons.ExpenseCopy : expensifyIcons.Checkmark,
iconFill: isDuplicateActive ? undefined : theme.icon,
value: CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE,
value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.DUPLICATE,
onSelected: () => {
if (hasCustomUnitOutOfPolicyViolation) {
showConfirmModal({
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Hallo, wie kann ich helfen?', showHistory: 'Verlauf anzeigen'},
duplicateReport: 'Duplizierten Bericht',
approver: 'Genehmiger',
copyOfReportName: (reportName: string) => `Kopie von ${reportName}`,
},
socials: {
podcast: 'Folgen Sie uns auf Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ const translations = {
duplicated: 'Duplicated',
duplicateExpense: 'Duplicate expense',
duplicateReport: 'Duplicate report',
copyOfReportName: (reportName: string) => `Copy of ${reportName}`,
exchangeRate: 'Exchange rate',
reimbursableTotal: 'Reimbursable total',
nonReimbursableTotal: 'Non-reimbursable total',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ const translations: TranslationDeepObject<typeof en> = {
duplicated: 'Duplicado',
duplicateExpense: 'Duplicar gasto',
duplicateReport: 'Duplicar informe',
copyOfReportName: (reportName: string) => `Copia de ${reportName}`,
exchangeRate: 'Tipo de cambio',
reimbursableTotal: 'Total reembolsable',
nonReimbursableTotal: 'Total no reembolsable',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Bonjour, comment puis-je vous aider ?', showHistory: 'Afficher l’historique'},
duplicateReport: 'Note de frais en double',
approver: 'Approbateur',
copyOfReportName: (reportName: string) => `Copie de ${reportName}`,
},
socials: {
podcast: 'Suivez-nous sur Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Ciao, come posso aiutarti?', showHistory: 'Mostra cronologia'},
duplicateReport: 'Report duplicato',
approver: 'Approvante',
copyOfReportName: (reportName: string) => `Copia di ${reportName}`,
},
socials: {
podcast: 'Seguici su Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'こんにちは、どのようにお手伝いできますか?', showHistory: '履歴を表示'},
duplicateReport: 'レポートを複製',
approver: '承認者',
copyOfReportName: (reportName: string) => `${reportName} のコピー`,
},
socials: {
podcast: 'ポッドキャストでフォロー',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Hoi, waarmee kan ik je helpen?', showHistory: 'Geschiedenis weergeven'},
duplicateReport: 'Dubbel rapport',
approver: 'Fiatteur',
copyOfReportName: (reportName: string) => `Kopie van ${reportName}`,
},
socials: {
podcast: 'Volg ons op Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Cześć, w czym mogę pomóc?', showHistory: 'Pokaż historię'},
duplicateReport: 'Zduplikowany raport',
approver: 'Osoba zatwierdzająca',
copyOfReportName: (reportName: string) => `Kopia raportu ${reportName}`,
},
socials: {
podcast: 'Śledź nas na Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: 'Oi, como posso ajudar?', showHistory: 'Mostrar histórico'},
duplicateReport: 'Duplicar relatório',
approver: 'Aprovador',
copyOfReportName: (reportName: string) => `Cópia de ${reportName}`,
},
socials: {
podcast: 'Siga-nos no Podcast',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ const translations: TranslationDeepObject<typeof en> = {
concierge: {sidePanelGreeting: '你好,我能帮你做什么?', showHistory: '显示历史'},
duplicateReport: '重复报销单',
approver: '审批人',
copyOfReportName: (reportName: string) => `${reportName} 的副本`,
},
socials: {
podcast: '在播客上关注我们',
Expand Down
1 change: 1 addition & 0 deletions src/libs/API/parameters/CreateAppReportParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ type CreateAppReportParams = {
reportPreviewReportActionID: string;
ownerEmail?: string;
shouldDismissEmptyReportsConfirmation?: boolean;
reportName?: string;
};
export default CreateAppReportParams;
2 changes: 1 addition & 1 deletion src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ function getSecondaryReportActions({
}

if (isDuplicateAction(report, reportTransactions)) {
options.push(CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE);
options.push(CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE_EXPENSE);
}

if (isDuplicateReportAction(report)) {
Expand Down
Loading
Loading