diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index be9103a30421..5ed7dcfac96d 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -38,7 +38,15 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getRateFromMerchant} from '@libs/MergeTransactionUtils'; import {hasEnabledOptions} from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; -import {canSubmitPerDiemExpenseFromWorkspace, getLengthOfTag, getTagLists, hasDependentTags as hasDependentTagsPolicyUtils, isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import { + canSubmitPerDiemExpenseFromWorkspace, + getLengthOfTag, + getPerDiemCustomUnit, + getPolicyByCustomUnitID, + getTagLists, + hasDependentTags as hasDependentTagsPolicyUtils, + isTaxTrackingEnabled, +} from '@libs/PolicyUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {getReportName} from '@libs/ReportNameUtils'; import {isSplitAction} from '@libs/ReportSecondaryActionUtils'; @@ -90,6 +98,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {TransactionPendingFieldsKey} from '@src/types/onyx/Transaction'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import MoneyRequestReceiptView from './MoneyRequestReceiptView'; type MoneyRequestViewProps = { @@ -118,6 +127,17 @@ type MoneyRequestViewProps = { mergeTransactionID?: string; }; +const perDiemPoliciesSelector = (policies: OnyxCollection) => { + return Object.fromEntries( + Object.entries(policies ?? {}).filter(([, policy]) => { + const perDiemCustomUnit = getPerDiemCustomUnit(policy); + const hasPolicyPerDiemRates = !isEmptyObject(perDiemCustomUnit?.rates); + + return policy?.arePerDiemRatesEnabled && hasPolicyPerDiemRates; + }), + ); +}; + function MoneyRequestView({ allReports, report, @@ -158,9 +178,28 @@ function MoneyRequestView({ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(linkedTransactionID)}`, {canBeMissing: true}); const isExpenseUnreported = isExpenseUnreportedTransactionUtils(updatedTransaction ?? transaction); const {policyForMovingExpensesID, policyForMovingExpenses, shouldSelectPolicy} = usePolicyForMovingExpenses(); - // If the expense is unreported the policy should be the user's default policy, otherwise it should be the policy the expense was made for - const policy = isExpenseUnreported ? policyForMovingExpenses : expensePolicy; - const policyID = isExpenseUnreported ? policyForMovingExpensesID : report?.policyID; + + const [policiesWithPerDiem] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { + selector: perDiemPoliciesSelector, + canBeMissing: true, + }); + const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction); + const perDiemOriginalPolicy = getPolicyByCustomUnitID(transaction, policiesWithPerDiem); + + let policy; + let policyID; + // If the expense is unreported the policy should be the user's default policy, if the expense is a per diem request and is unreported + // the policy should be the one where the per diem rates are enabled, otherwise it should be the expense's report policy + if (isExpenseUnreported && !isPerDiemRequest) { + policy = policyForMovingExpenses; + policyID = policyForMovingExpensesID; + } else if (isExpenseUnreported && isPerDiemRequest) { + policy = perDiemOriginalPolicy; + policyID = perDiemOriginalPolicy?.id; + } else { + policy = expensePolicy; + policyID = report?.policyID; + } const allPolicyCategories = usePolicyCategories(); const policyCategories = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; @@ -220,7 +259,6 @@ function MoneyRequestView({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); const isMapDistanceRequest = isDistanceRequest && !isManualDistanceRequest; - const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction); const isTransactionScanning = isScanning(updatedTransaction ?? transaction); const hasRoute = hasRouteTransactionUtils(transactionBackup ?? transaction, isDistanceRequest); @@ -271,13 +309,10 @@ function MoneyRequestView({ const canEditDate = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE, undefined, isChatReportArchived); const canEditDistance = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE, undefined, isChatReportArchived); const canEditDistanceRate = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE, undefined, isChatReportArchived); - const canEditReport = useMemo( - () => - isEditable && - canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.REPORT, undefined, isChatReportArchived, outstandingReportsByPolicyID) && - (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(policy)), - [isEditable, parentReportAction, isChatReportArchived, outstandingReportsByPolicyID, isPerDiemRequest, policy], - ); + const canEditReport = + isEditable && + canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.REPORT, undefined, isChatReportArchived, outstandingReportsByPolicyID) && + (!isPerDiemRequest || canSubmitPerDiemExpenseFromWorkspace(policy) || (isExpenseUnreported && !!perDiemOriginalPolicy)); // A flag for verifying that the current report is a sub-report of a expense chat // if the policy of the report is either Collect or Control, then this report must be tied to expense chat diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 44140eaa44fc..82d36f82f03e 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm'; -import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate} from '@src/types/onyx'; +import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate, Transaction} from '@src/types/onyx'; import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type { ConnectionLastSync, @@ -182,6 +182,24 @@ function getPerDiemCustomUnit(policy: OnyxEntry): CustomUnit | undefined return Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_PER_DIEM_INTERNATIONAL); } +/** + * Finds a policy that contains the customUnitID from the transaction + */ +function getPolicyByCustomUnitID(transaction: OnyxEntry, policies: OnyxCollection): OnyxEntry { + const customUnitID = transaction?.comment?.customUnit?.customUnitID; + + if (!customUnitID || !policies) { + return undefined; + } + + return Object.values(policies).find((policy) => { + if (!policy?.customUnits || !policy?.arePerDiemRatesEnabled) { + return false; + } + return customUnitID in policy.customUnits; + }); +} + /** * Retrieves custom unit rate object from the given customUnitRateID */ @@ -1677,6 +1695,7 @@ export { getSageIntacctBankAccounts, getDistanceRateCustomUnit, getPerDiemCustomUnit, + getPolicyByCustomUnitID, getDistanceRateCustomUnitRate, getPerDiemRateCustomUnitRate, sortWorkspacesBySelected, diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 15c08bfd16b2..3ad546f060ed 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -98,11 +98,12 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { }; const createReportForPolicy = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!policyForMovingExpensesID) { + if (!hasPerDiemTransactions && !policyForMovingExpensesID) { return; } - const optimisticReport = createNewReport(currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, policyForMovingExpensesID, false, shouldDismissEmptyReportsConfirmation); + const policyForNewReportID = hasPerDiemTransactions ? selectedReport?.policyID : policyForMovingExpensesID; + const optimisticReport = createNewReport(currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, policyForNewReportID, false, shouldDismissEmptyReportsConfirmation); selectReport({value: optimisticReport.reportID}, optimisticReport); }; @@ -114,7 +115,11 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { }); const createReport = () => { - if (!policyForMovingExpensesID && !shouldSelectPolicy) { + if (hasPerDiemTransactions) { + handleCreateReport(); + return; + } + if (!hasPerDiemTransactions && !policyForMovingExpensesID && !shouldSelectPolicy) { return; } if (shouldSelectPolicy) { diff --git a/src/pages/iou/request/step/IOURequestEditReportCommon.tsx b/src/pages/iou/request/step/IOURequestEditReportCommon.tsx index 0358f27f99c9..c7117b57ecbd 100644 --- a/src/pages/iou/request/step/IOURequestEditReportCommon.tsx +++ b/src/pages/iou/request/step/IOURequestEditReportCommon.tsx @@ -16,8 +16,9 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; import useReportTransactions from '@hooks/useReportTransactions'; +import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Navigation from '@libs/Navigation/Navigation'; -import {canSubmitPerDiemExpenseFromWorkspace, getPersonalPolicy, isPolicyAdmin} from '@libs/PolicyUtils'; +import {canSubmitPerDiemExpenseFromWorkspace, getPersonalPolicy, getPolicyByCustomUnitID, isPolicyAdmin} from '@libs/PolicyUtils'; import { canAddTransaction, getOutstandingReportsForUser, @@ -79,6 +80,7 @@ function IOURequestEditReportCommon({ const {options} = useOptionsList(); const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true}); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const [firstTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionIDs?.at(0))}`, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [selectedReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${selectedReportID}`, {canBeMissing: true}); const resolvedReportOwnerAccountID = useMemo(() => { @@ -95,6 +97,7 @@ function IOURequestEditReportCommon({ const reportPolicy = usePolicy(selectedReport?.policyID); const {policyForMovingExpenses} = usePolicyForMovingExpenses(isPerDiemRequest); + const perDiemOriginalPolicy = getPolicyByCustomUnitID(firstTransaction, allPolicies); const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); const [allPoliciesID] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: policiesSelector, canBeMissing: false}); @@ -114,8 +117,8 @@ function IOURequestEditReportCommon({ } return reportTransactions - .filter((transaction) => transactionIDs.includes(transaction.transactionID)) - .some((transaction) => transaction?.comment?.liabilityType === CONST.TRANSACTION.LIABILITY_TYPE.RESTRICT); + .filter((reportTransaction) => transactionIDs.includes(reportTransaction.transactionID)) + .some((reportTransaction) => reportTransaction?.comment?.liabilityType === CONST.TRANSACTION.LIABILITY_TYPE.RESTRICT); }, [transactionIDs, selectedReport, reportTransactions]); const shouldShowRemoveFromReport = @@ -164,6 +167,9 @@ function IOURequestEditReportCommon({ .filter((report) => !debouncedSearchValue || report?.reportName?.toLowerCase().includes(debouncedSearchValue.toLowerCase())) .filter((report): report is NonNullable => report !== undefined) .filter((report) => { + if (isPerDiemRequest && report?.policyID !== perDiemOriginalPolicy?.id) { + return false; + } if (isPerDiemRequest && report?.policyID && selectedReportID !== report?.reportID) { const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`]; return canSubmitPerDiemExpenseFromWorkspace(policy); @@ -206,6 +212,7 @@ function IOURequestEditReportCommon({ allPolicies, isPerDiemRequest, currentUserPersonalDetails.accountID, + perDiemOriginalPolicy?.id, ]); const navigateBack = () => { @@ -223,11 +230,11 @@ function IOURequestEditReportCommon({ ); - }, [icons.Document, createReport, isEditing, isOwner, translate, policyForMovingExpenses?.name]); + }, [icons.Document, createReport, isEditing, isOwner, translate, policyForMovingExpenses?.name, perDiemOriginalPolicy?.name, isPerDiemRequest]); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useMemo(() => { diff --git a/src/pages/iou/request/step/IOURequestStepReport.tsx b/src/pages/iou/request/step/IOURequestStepReport.tsx index 2b4051b6d879..7aa1449e3627 100644 --- a/src/pages/iou/request/step/IOURequestStepReport.tsx +++ b/src/pages/iou/request/step/IOURequestStepReport.tsx @@ -14,6 +14,7 @@ import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import {createNewReport} from '@libs/actions/Report'; import {changeTransactionsReport, setTransactionReport} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; +import {getPolicyByCustomUnitID} from '@libs/PolicyUtils'; import {getReportOrDraftReport, hasViolations as hasViolationsReportUtils, isPolicyExpenseChat, isReportOutstanding} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {isPerDiemRequest} from '@libs/TransactionUtils'; @@ -61,6 +62,8 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined; useRestartOnReceiptFailure(transaction, reportIDFromRoute, iouType, action); + const isPerDiemTransaction = isPerDiemRequest(transaction); + const perDiemOriginalPolicy = getPolicyByCustomUnitID(transaction, allPolicies); const handleGoBack = () => { if (isEditing) { @@ -175,11 +178,12 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const shouldShowNotFoundPage = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, reportOrDraftReport, transaction); const createReportForPolicy = (shouldDismissEmptyReportsConfirmation?: boolean) => { - if (!policyForMovingExpensesID) { + if (!isPerDiemTransaction && !policyForMovingExpensesID) { return; } - const optimisticReport = createNewReport(currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, policyForMovingExpensesID, false, shouldDismissEmptyReportsConfirmation); + const policyForNewReport = isPerDiemTransaction && perDiemOriginalPolicy ? perDiemOriginalPolicy.id : policyForMovingExpensesID; + const optimisticReport = createNewReport(currentUserPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, policyForNewReport, false, shouldDismissEmptyReportsConfirmation); handleRegularReportSelection({value: optimisticReport.reportID}, optimisticReport); }; @@ -191,7 +195,11 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { }); const createReport = () => { - if (!policyForMovingExpensesID && !shouldSelectPolicy) { + if (isPerDiemTransaction) { + handleCreateReport(); + return; + } + if (!isPerDiemTransaction && !policyForMovingExpensesID && !shouldSelectPolicy) { return; } if (shouldSelectPolicy) { @@ -220,7 +228,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { isUnreported={isUnreported} shouldShowNotFoundPage={shouldShowNotFoundPage} isPerDiemRequest={transaction ? isPerDiemRequest(transaction) : false} - createReport={action === CONST.IOU.ACTION.EDIT && (policyForMovingExpensesID || shouldSelectPolicy) ? createReport : undefined} + createReport={action === CONST.IOU.ACTION.EDIT && (policyForMovingExpensesID || shouldSelectPolicy || isPerDiemTransaction) ? createReport : undefined} /> );