Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Approving a report causes the workspace chat to “flash” in the LHN before disappearing #35928

Merged
merged 13 commits into from
Mar 8, 2024
25 changes: 5 additions & 20 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -51,13 +50,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
const {translate} = useLocalize();
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport);
const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport);
const policyType = policy?.type;
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy);
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport);
const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && session?.accountID === moneyRequestReport.managerID;
const isPayer = ReportUtils.isPayer(session, moneyRequestReport);
const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
Expand All @@ -70,22 +65,12 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
setIsConfirmModalVisible(false);
}, [moneyRequestReport, chatReport]);

const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
const shouldShowPayButton = useMemo(
() => isPayer && !isDraft && !isSettled && !moneyRequestReport.isWaitingOnBankAccount && reimbursableSpend !== 0 && !ReportUtils.isArchivedRoom(chatReport) && !isAutoReimbursable,
[isPayer, isDraft, isSettled, moneyRequestReport, reimbursableSpend, chatReport, isAutoReimbursable],
);
const shouldShowApproveButton = useMemo(() => {
if (!isPaidGroupPolicy) {
return false;
}
if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
return false;
}
return isManager && !isDraft && !isApproved && !isSettled;
}, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy]);
const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);

const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);

const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;

const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length;
Expand Down
34 changes: 5 additions & 29 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import ControlSelection from '@libs/ControlSelection';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReceiptUtils from '@libs/ReceiptUtils';
import * as ReportActionUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
Expand All @@ -30,7 +29,7 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, Session, Transaction, TransactionViolations} from '@src/types/onyx';
import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import ReportActionItemImages from './ReportActionItemImages';

Expand All @@ -44,9 +43,6 @@ type ReportPreviewOnyxProps = {
/** Active IOU Report for current report */
iouReport: OnyxEntry<Report>;

/** Session info for the currently logged in user. */
session: OnyxEntry<Session>;

/** All the transactions, used to update ReportPreview label and status */
transactions: OnyxCollection<Transaction>;

Expand Down Expand Up @@ -85,7 +81,6 @@ type ReportPreviewProps = ReportPreviewOnyxProps & {

function ReportPreview({
iouReport,
session,
policy,
iouReportID,
policyID,
Expand Down Expand Up @@ -118,12 +113,9 @@ function ReportPreview({
);

const managerID = iouReport?.managerID ?? 0;
const isCurrentUserManager = managerID === session?.accountID;
const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);

const iouSettled = ReportUtils.isSettled(iouReportID);
const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(action);
const moneyRequestComment = action?.childLastMoneyRequestComment ?? '';
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
Expand Down Expand Up @@ -208,23 +200,10 @@ function ReportPreview({

const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);

const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
const isPayer = ReportUtils.isPayer(session, iouReport);
const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
const shouldShowPayButton = useMemo(
() => isPayer && !isDraftExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable,
[isPayer, isDraftExpenseReport, iouSettled, reimbursableSpend, iouCanceled, isAutoReimbursable, iouReport],
);
const shouldShowApproveButton = useMemo(() => {
if (!isPaidGroupPolicy) {
return false;
}
if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
return false;
}
return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled;
}, [isPaidGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy, iouSettled]);
const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(iouReport, chatReport, policy), [iouReport, chatReport, policy]);

const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, chatReport, policy), [iouReport, chatReport, policy]);

const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;

/*
Expand Down Expand Up @@ -353,9 +332,6 @@ export default withOnyx<ReportPreviewProps, ReportPreviewOnyxProps>({
iouReport: {
key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`,
},
session: {
key: ONYXKEYS.SESSION,
},
transactions: {
key: ONYXKEYS.COLLECTION.TRANSACTION,
},
Expand Down
2 changes: 1 addition & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ function isInstantSubmitEnabled(policy: OnyxEntry<Policy> | EmptyObject): boolea
/**
* Checks if policy's approval mode is "optional", a.k.a. "Submit & Close"
*/
function isSubmitAndClose(policy: OnyxEntry<Policy>): boolean {
function isSubmitAndClose(policy: OnyxEntry<Policy> | EmptyObject): boolean {
return policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL;
}

Expand Down
4 changes: 2 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5160,8 +5160,8 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined): Ances
return allAncestorIDs;
}

function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null): boolean {
if (!policy) {
function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> | EmptyObject): boolean {
if (isEmptyObject(policy)) {
return false;
}
type CurrencyType = (typeof CONST.DIRECT_REIMBURSEMENT_CURRENCIES)[number];
Expand Down
83 changes: 82 additions & 1 deletion src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3677,10 +3677,73 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency:
Report.notifyNewAction(params.chatReportID, managerID);
}

function canIOUBePaid(iouReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, policy: OnyxEntry<OnyxTypes.Policy> | EmptyObject) {
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
const iouCanceled = ReportUtils.isArchivedRoom(chatReport);

if (isEmptyObject(iouReport)) {
return false;
}

const isPayer = ReportUtils.isPayer(
{
email: currentUserEmail,
accountID: userAccountID,
},
iouReport,
);

const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
const iouSettled = ReportUtils.isSettled(iouReport?.reportID);

const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);
return isPayer && !isDraftExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable;
}

function canApproveIOU(iouReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, policy: OnyxEntry<OnyxTypes.Policy> | EmptyObject) {
if (isEmptyObject(chatReport)) {
return false;
}
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
if (!isPaidGroupPolicy) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how costly the methods above are, but maybe consider moving this if to be right at the top?

return false;
}

const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
return false;
}

const managerID = iouReport?.managerID ?? 0;
const isCurrentUserManager = managerID === userAccountID;
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);

const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
const isApproved = ReportUtils.isReportApproved(iouReport);
const iouSettled = ReportUtils.isSettled(iouReport?.reportID);

return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled;
}

function hasIOUToApproveOrPay(chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, excludedIOUReportID: string): boolean {
const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? '');

return Object.values(chatReportActions).some((action) => {
const iouReport = ReportUtils.getReport(action.childReportID ?? '');
const policy = ReportUtils.getPolicy(iouReport?.policyID);

const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy);
return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && shouldShowSettlementButton;
});
}

function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID);
const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED);
const chatReport = ReportUtils.getReport(expenseReport.chatReportID);

const optimisticReportActionsData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -3703,12 +3766,21 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
},
};

const optimisticChatReportData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`,
value: {
hasOutstandingChildRequest: hasIOUToApproveOrPay(chatReport, expenseReport?.reportID ?? ''),
Copy link
Contributor

Choose a reason for hiding this comment

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

We missed to check if the report was fully approved before passing expenseReport?.reportID, this caused the bug #43014

},
};

const optimisticNextStepData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
value: optimisticNextStep,
};
const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData];
const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData, optimisticChatReportData];

const successData: OnyxUpdate[] = [
{
Expand All @@ -3732,6 +3804,13 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
},
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.chatReportID}`,
value: {
hasOutstandingChildRequest: chatReport?.hasOutstandingChildRequest,
},
},
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
Expand Down Expand Up @@ -4331,4 +4410,6 @@ export {
cancelPayment,
navigateToStartStepIfScanFileCannotBeRead,
savePreferredPaymentMethod,
canIOUBePaid,
canApproveIOU,
};
Loading