Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function useFooterDerivedFlags({
isTypeInvoice,
shouldShowSmartScanFields,
}: UseFooterDerivedFlagsParams) {
const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses();
const {policyForMovingExpenses, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();

const transaction = useTransactionSelector(transactionID, derivedFlagsSliceSelector);

Expand Down Expand Up @@ -95,7 +95,6 @@ function useFooterDerivedFlags({
const shouldShowBillable = isBillableEnabledOnPolicy(policy);
const shouldShowReimbursable =
(isPolicyExpenseChat || isTrackExpense) && !!policy && policy?.disabledFields?.reimbursable !== true && !isManagedCardTransaction(transaction) && !isTypeInvoice;
const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy;
const shouldShowTimeRequestFields = isTimeRequest && action === CONST.IOU.ACTION.CREATE;

return {
Expand Down
3 changes: 1 addition & 2 deletions src/components/MoneyRequestHeaderSecondaryActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,7 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money
const defaultExpensePolicy = useDefaultExpensePolicy();
const [defaultPolicyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(defaultExpensePolicy?.id)}`);
const [defaultPolicyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(defaultExpensePolicy?.id)}`);
const {policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(isPerDiemRequest(transaction));
const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy;
const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(isPerDiemRequest(transaction));
const {deleteTransactions, shouldOpenSplitExpenseEditFlowOnDelete} = useDeleteTransactions({report: parentReport, reportActions: parentReportAction ? [parentReportAction] : [], policy});
const {iouReport, chatReport: chatIOUReport, isChatIOUReportArchived} = useGetIOUReportFromReportAction(parentReportAction);
const isParentReportArchived = useReportIsArchived(report?.parentReportID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ function QuickCreationActionsBar() {
const {isBetaEnabled} = usePermissions();
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? '');
const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses();
const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy;
const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();
const isSubmit2026BetaEnabled = isBetaEnabled(CONST.BETAS.SUBMIT_2026);
const groupPoliciesWithChatEnabledSelector = (policies: OnyxCollection<OnyxTypes.Policy>) => getGroupPoliciesWhereReportCanBeCreated(policies, isSubmit2026BetaEnabled, email);
const [groupPoliciesWithChatEnabled = CONST.EMPTY_ARRAY] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: groupPoliciesWithChatEnabledSelector}, [email, isSubmit2026BetaEnabled]);
Expand Down
3 changes: 1 addition & 2 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ function MoneyRequestView({
const transaction = updatedTransaction ?? onyxTransaction;
const isExpenseUnreported = isExpenseUnreportedTransactionUtils(transaction);
const personalPolicy = usePersonalPolicy();
const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses();
const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();
const isTimeRequest = isTimeRequestTransactionUtils(transaction);

const [policiesWithPerDiem] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
Expand Down Expand Up @@ -583,7 +583,6 @@ function MoneyRequestView({
amountTitle = translate('iou.receiptStatusTitle');
}

const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy;
const updatedTransactionDescription = getDescription(updatedTransaction) || undefined;
const shouldHideEmptyDescription = (isFromReviewDuplicates || isFromMergeTransaction) && !(updatedTransactionDescription ?? transactionDescription);
const isEmptyUpdatedMerchant = isInvalidMerchantValue(updatedTransaction?.modifiedMerchant);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ function SearchActionsBarCreateButton() {
const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END);
const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END);
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses();
const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy;
const {shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();
const defaultChatEnabledPolicy = useMemo(
() => getDefaultChatEnabledPolicy(groupPoliciesWithChatEnabled as Array<OnyxEntry<OnyxTypes.Policy>>, activePolicy),
[activePolicy, groupPoliciesWithChatEnabled],
Expand Down
25 changes: 19 additions & 6 deletions src/hooks/usePolicyForMovingExpenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ function getPolicyQualificationResult(
return {singlePolicyID, isMemberOfMoreThanOnePolicy, validExpensePolicyID};
}

function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: boolean, expensePolicyID?: string) {
type PolicyForMovingExpenses = {
policyForMovingExpensesID: string | undefined;
policyForMovingExpenses: OnyxEntry<Policy>;
shouldSelectPolicy: boolean;
shouldNavigateToUpgradePath: boolean;
};

function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?: boolean, expensePolicyID?: string, isUnreportedManagedCardTransaction?: boolean): PolicyForMovingExpenses {
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {
selector: activePolicySelector,
Expand All @@ -104,24 +111,30 @@ function usePolicyForMovingExpenses(isPerDiemRequest?: boolean, isTimeRequest?:
const resolvedPolicyID = validExpensePolicyID ?? singlePolicyID;
const [resolvedPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${resolvedPolicyID}`);

// If this is an employee's card transaction that we manage, then we should report it to their default policy
// which we don't know. Sending an empty `policyID` instructs the backend to auto-select the preferred policy.
if (isUnreportedManagedCardTransaction) {
return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false};
Comment thread
s77rt marked this conversation as resolved.
}

// If an expense policy ID is provided and valid, prefer it over the active policy
if (validExpensePolicyID) {
return {policyForMovingExpensesID: validExpensePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false};
return {policyForMovingExpensesID: validExpensePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false};
}

if (activePolicy && (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(activePolicy)) && (!isTimeRequest || isTimeTrackingEnabled(activePolicy))) {
return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false};
return {policyForMovingExpensesID: activePolicyID, policyForMovingExpenses: activePolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false};
}

if (singlePolicyID && !isMemberOfMoreThanOnePolicy) {
return {policyForMovingExpensesID: singlePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false};
return {policyForMovingExpensesID: singlePolicyID, policyForMovingExpenses: resolvedPolicy, shouldSelectPolicy: false, shouldNavigateToUpgradePath: false};
}

if (isMemberOfMoreThanOnePolicy) {
return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: true};
return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: true, shouldNavigateToUpgradePath: false};
}

return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false};
return {policyForMovingExpensesID: undefined, policyForMovingExpenses: undefined, shouldSelectPolicy: false, shouldNavigateToUpgradePath: true};
}

export default usePolicyForMovingExpenses;
5 changes: 5 additions & 0 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2959,6 +2959,10 @@ function isExpenseUnreported(transaction?: Transaction): transaction is Unreport
return transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID;
}

function isUnreportedManagedCardTransaction(transaction?: Transaction): boolean {
return isExpenseUnreported(transaction) && isManagedCardTransaction(transaction);
}

/**
* Returns true if the violation should block report submission.
*/
Expand Down Expand Up @@ -3225,4 +3229,5 @@ export {
hasSmartScanFailedWithMissingFields,
isDeletedTransaction,
getDistanceRequestType,
isUnreportedManagedCardTransaction,
};
53 changes: 29 additions & 24 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4104,28 +4104,30 @@ function buildNewReportOptimisticData(
value: {[reportActionID]: optimisticCreateAction},
},
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`,
value: optimisticNextStepDeprecated,
},
];
if (parentReport) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`,
value: {[reportPreviewReportActionID]: optimisticReportPreview},
},
{
});
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`,
key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
value: {iouReportID: reportID, ...outstandingChildRequest},
},
{
});
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${parentReport?.reportID}`,
key: `${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${parentReport.reportID}`,
value: {
hasOnceLoadedReportActions: true,
},
},
{
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${reportID}`,
value: optimisticNextStepDeprecated,
},
];
});
}

optimisticData.push(...updateTitleFieldToMatchPolicy(reportID, policy));

Expand Down Expand Up @@ -4172,13 +4174,14 @@ function buildNewReportOptimisticData(
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
value: {[reportActionID]: {errors: {createReport: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage')}}},
},

{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`,
value: {hasOutstandingChildRequest: parentReport?.hasOutstandingChildRequest},
},
];
if (parentReport) {
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`,
value: {hasOutstandingChildRequest: parentReport.hasOutstandingChildRequest},
});
}

const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS>> = [
{
Expand All @@ -4203,17 +4206,19 @@ function buildNewReportOptimisticData(
},
},
},
{
];
if (parentReport) {
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`,
value: {
[reportPreviewReportActionID]: {
pendingAction: null,
errors: null,
},
},
},
];
});
}

return {
optimisticReportName: optimisticReportData.reportName,
Expand Down
14 changes: 11 additions & 3 deletions src/pages/Search/SearchTransactionsChangeReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import setNavigationActionToMicrotaskQueue from '@libs/Navigation/helpers/setNav
import Navigation from '@libs/Navigation/Navigation';
import {generateReportID, getPersonalDetailsForAccountID, getReportOrDraftReport, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils';
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
import {isUnreportedManagedCardTransaction} from '@libs/TransactionUtils';

import IOURequestEditReportCommon from '@pages/iou/request/step/IOURequestEditReportCommon';

Expand Down Expand Up @@ -51,6 +52,7 @@ function SearchTransactionsChangeReport() {
const [betas] = useOnyx(ONYXKEYS.BETAS);
const [allPolicyTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
const hasPerDiemTransactions = useHasPerDiemTransactions(selectedTransactionsKeys);
const hasUnreportedManagedCardTransactions = transactions.some((transaction) => isUnreportedManagedCardTransaction(transaction));
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const {isBetaEnabled} = usePermissions();
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
Expand All @@ -66,7 +68,12 @@ function SearchTransactionsChangeReport() {
// Get the policyID from the selected transactions' report to pass to usePolicyForMovingExpenses
// This ensures the "Create report" button shows the correct workspace instead of the user's default
const selectedReportPolicyID = selectedReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selectedReportID}`]?.policyID : undefined;
const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(hasPerDiemTransactions, undefined, selectedReportPolicyID);
const {policyForMovingExpensesID, shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses(
hasPerDiemTransactions,
undefined,
selectedReportPolicyID,
hasUnreportedManagedCardTransactions,
);
const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined;
const areAllTransactionsUnreported =
selectedTransactionsKeys.length > 0 && selectedTransactionsKeys.every((transactionKey) => selectedTransactions[transactionKey]?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID);
Expand Down Expand Up @@ -173,7 +180,7 @@ function SearchTransactionsChangeReport() {
});

const createReport = () => {
if (!policyForMovingExpensesID && !shouldSelectPolicy && selectedTransactionsKeys.length > 0) {
if (shouldNavigateToUpgradePath && selectedTransactionsKeys.length > 0) {
const firstTransactionID = selectedTransactionsKeys.at(0);
if (firstTransactionID) {
Navigation.navigate(
Expand All @@ -193,7 +200,7 @@ function SearchTransactionsChangeReport() {
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute(true)));
return;
}
if (!policyForMovingExpensesID) {
if (shouldNavigateToUpgradePath) {
Navigation.navigate(
ROUTES.MONEY_REQUEST_UPGRADE.getRoute({
action: CONST.IOU.ACTION.CREATE,
Expand Down Expand Up @@ -274,6 +281,7 @@ function SearchTransactionsChangeReport() {
targetOwnerAccountID={targetOwnerAccountID}
transactionPolicyID={selectedReportPolicyID}
isPerDiemRequest={hasPerDiemTransactions}
isUnreportedManagedCardTransaction={hasUnreportedManagedCardTransactions}
/>
);
}
Expand Down
3 changes: 1 addition & 2 deletions src/pages/iou/SplitExpenseEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) {

// Detect selfDM splits whose source workspace is gone: nothing for the Rate step to render.
const hasAnyPaidWorkspace = hasAnyPaidPolicy(allPolicies ?? {});
const {policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses();
const shouldNavigateToUpgradePath = !policyForMovingExpenses && !shouldSelectPolicy;
const {shouldSelectPolicy, shouldNavigateToUpgradePath} = usePolicyForMovingExpenses();

const effectivePolicyID = effectivePolicy?.id;

Expand Down
Loading
Loading