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.shouldShowPayButton(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);

const shouldShowApproveButton = useMemo(() => IOU.shouldShowApproveButton(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 = 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.shouldShowPayButton(iouReport, chatReport, policy), [iouReport, chatReport, policy]);

const shouldShowApproveButton = useMemo(() => IOU.shouldShowApproveButton(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
87 changes: 86 additions & 1 deletion src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3673,10 +3673,64 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency:
Report.notifyNewAction(params.chatReportID, managerID);
}

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

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 shouldShowApproveButton(iouReport: OnyxEntry<OnyxTypes.Report>, chatReport: OnyxEntry<OnyxTypes.Report>, policy: OnyxEntry<OnyxTypes.Policy>) {
const managerID = iouReport?.managerID ?? 0;
const isCurrentUserManager = managerID === userAccountID;
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);

const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
const isApproved = ReportUtils.isReportApproved(iouReport);
const iouSettled = ReportUtils.isSettled(iouReport?.reportID);
const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
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;
}
if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
return false;
}

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

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

return !!Object.values(chatReportActions).find((action) => {
tienifr marked this conversation as resolved.
Show resolved Hide resolved
const iouReport = ReportUtils.getReport(action.childReportID ?? '') as OnyxEntry<OnyxTypes.Report>;
const policy = ReportUtils.getPolicy(iouReport?.policyID) as OnyxEntry<OnyxTypes.Policy>;
tienifr marked this conversation as resolved.
Show resolved Hide resolved

const shouldShowSettlementButton = shouldShowPayButton(iouReport, chatReport, policy) || shouldShowApproveButton(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) as OnyxEntry<OnyxTypes.Report>;
tienifr marked this conversation as resolved.
Show resolved Hide resolved

const optimisticReportActionsData: OnyxUpdate = {
onyxMethod: Onyx.METHOD.MERGE,
Expand All @@ -3699,12 +3753,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 @@ -3728,13 +3791,33 @@ 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}`,
value: currentNextStep,
},
];

if (currentNextStep) {
optimisticData.push({
onyxMethod: Onyx.METHOD.SET,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
value: null,
tienifr marked this conversation as resolved.
Show resolved Hide resolved
});
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
value: currentNextStep,
});
}

const parameters: ApproveMoneyRequestParams = {
reportID: expenseReport.reportID,
approvedReportActionID: optimisticApprovedReportAction.reportActionID,
Expand Down Expand Up @@ -4327,4 +4410,6 @@ export {
cancelPayment,
navigateToStartStepIfScanFileCannotBeRead,
savePreferredPaymentMethod,
shouldShowPayButton,
shouldShowApproveButton,
};
Loading