Skip to content

Commit af45df4

Browse files
authored
Merge pull request Expensify#90719 from Expensify/claude-cancelPaymentOptimisticNextStep
Use SUBMITTED status for cancel payment optimistic data when approvals disabled
2 parents 2aea227 + 13c6d7b commit af45df4

2 files changed

Lines changed: 155 additions & 2 deletions

File tree

src/libs/actions/IOU/PayMoneyRequest.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,16 @@ function cancelPayment(
497497
const stateNum: ValueOf<typeof CONST.REPORT.STATE_NUM> = CONST.REPORT.STATE_NUM.APPROVED;
498498
const statusNum: ValueOf<typeof CONST.REPORT.STATUS_NUM> = approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL ? CONST.REPORT.STATUS_NUM.CLOSED : CONST.REPORT.STATUS_NUM.APPROVED;
499499

500+
// For OPTIONAL approval mode with a connected bank account, the report status is CLOSED but the next step
501+
// should show "waiting to pay", so we use SUBMITTED as the predictedNextStatus which routes through the
502+
// correct next step path. Without a bank account, keep CLOSED which shows "no further action required".
503+
const hasConnectedBankAccount = !!policy?.achAccount?.accountNumber;
504+
const predictedNextStatus = approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL && hasConnectedBankAccount ? CONST.REPORT.STATUS_NUM.SUBMITTED : statusNum;
505+
500506
// buildOptimisticNextStep is used in parallel
501507
const optimisticNextStepDeprecated = buildNextStepNew({
502508
report: expenseReport,
503-
predictedNextStatus: statusNum,
509+
predictedNextStatus,
504510
policy,
505511
currentUserAccountIDParam,
506512
currentUserEmailParam,
@@ -509,7 +515,7 @@ function cancelPayment(
509515
});
510516
const optimisticNextStep = buildOptimisticNextStep({
511517
report: expenseReport,
512-
predictedNextStatus: statusNum,
518+
predictedNextStatus,
513519
policy,
514520
currentUserAccountIDParam,
515521
currentUserEmailParam,

tests/actions/IOUTest/PayMoneyRequestTest.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,153 @@ describe('actions/IOU/PayMoneyRequest', () => {
15661566
},
15671567
});
15681568
});
1569+
1570+
it('optimistic nextStep shows waiting to pay when approvals are disabled and bank account is connected', async () => {
1571+
const adminEmail = 'admin@expensifail.com';
1572+
const adminAccountID = 10;
1573+
const employeeEmail = 'employee@expensifail.com';
1574+
const employeeAccountID = 11;
1575+
1576+
await Onyx.set(ONYXKEYS.SESSION, {email: adminEmail, accountID: adminAccountID});
1577+
await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
1578+
[adminAccountID]: {
1579+
accountID: adminAccountID,
1580+
login: adminEmail,
1581+
displayName: 'Admin User',
1582+
},
1583+
[employeeAccountID]: {
1584+
accountID: employeeAccountID,
1585+
login: employeeEmail,
1586+
displayName: 'Employee User',
1587+
},
1588+
});
1589+
1590+
const policy = {
1591+
id: 'cancelOptionalPolicy',
1592+
name: 'Test Policy',
1593+
role: CONST.POLICY.ROLE.ADMIN,
1594+
owner: adminEmail,
1595+
ownerAccountID: adminAccountID,
1596+
outputCurrency: CONST.CURRENCY.USD,
1597+
isPolicyExpenseChatEnabled: true,
1598+
type: CONST.POLICY.TYPE.CORPORATE,
1599+
approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL,
1600+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
1601+
achAccount: {bankAccountID: 1, accountNumber: '123456789', routingNumber: '011000015', addressName: 'Test User', bankName: 'Test Bank', reimburser: 'admin@expensifail.com'},
1602+
};
1603+
1604+
const expenseReport = {
1605+
reportID: 'cancelOptionalExpense',
1606+
type: CONST.REPORT.TYPE.EXPENSE,
1607+
ownerAccountID: employeeAccountID,
1608+
managerID: adminAccountID,
1609+
policyID: policy.id,
1610+
stateNum: CONST.REPORT.STATE_NUM.APPROVED,
1611+
statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
1612+
total: -1000,
1613+
nonReimbursableTotal: 0,
1614+
currency: 'USD',
1615+
parentReportID: 'cancelOptionalChat',
1616+
chatReportID: 'cancelOptionalChat',
1617+
};
1618+
1619+
const chatReport = {
1620+
reportID: 'cancelOptionalChat',
1621+
isOwnPolicyExpenseChat: true,
1622+
ownerAccountID: employeeAccountID,
1623+
iouReportID: expenseReport.reportID,
1624+
policyID: policy.id,
1625+
type: CONST.REPORT.TYPE.CHAT,
1626+
chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
1627+
};
1628+
1629+
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
1630+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport);
1631+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport);
1632+
1633+
mockFetch?.pause?.();
1634+
1635+
cancelPayment(expenseReport, chatReport, policy, true, adminAccountID, adminEmail, false);
1636+
await waitForBatchedUpdates();
1637+
1638+
const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
1639+
expect(updatedReport?.statusNum).toBe(CONST.REPORT.STATUS_NUM.CLOSED);
1640+
expect(updatedReport?.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
1641+
expect(updatedReport?.nextStep?.messageKey).toBe(CONST.NEXT_STEP.MESSAGE_KEY.WAITING_TO_PAY);
1642+
});
1643+
1644+
it('optimistic nextStep shows no further action when approvals are disabled and no bank account is connected', async () => {
1645+
const adminEmail = 'admin2@expensifail.com';
1646+
const adminAccountID = 20;
1647+
const employeeEmail = 'employee2@expensifail.com';
1648+
const employeeAccountID = 21;
1649+
1650+
await Onyx.set(ONYXKEYS.SESSION, {email: adminEmail, accountID: adminAccountID});
1651+
await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, {
1652+
[adminAccountID]: {
1653+
accountID: adminAccountID,
1654+
login: adminEmail,
1655+
displayName: 'Admin User 2',
1656+
},
1657+
[employeeAccountID]: {
1658+
accountID: employeeAccountID,
1659+
login: employeeEmail,
1660+
displayName: 'Employee User 2',
1661+
},
1662+
});
1663+
1664+
const policy = {
1665+
id: 'cancelOptionalNoBankPolicy',
1666+
name: 'Test Policy No Bank',
1667+
role: CONST.POLICY.ROLE.ADMIN,
1668+
owner: adminEmail,
1669+
ownerAccountID: adminAccountID,
1670+
outputCurrency: CONST.CURRENCY.USD,
1671+
isPolicyExpenseChatEnabled: true,
1672+
type: CONST.POLICY.TYPE.CORPORATE,
1673+
approvalMode: CONST.POLICY.APPROVAL_MODE.OPTIONAL,
1674+
reimbursementChoice: CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_YES,
1675+
};
1676+
1677+
const expenseReport = {
1678+
reportID: 'cancelNoBankExpense',
1679+
type: CONST.REPORT.TYPE.EXPENSE,
1680+
ownerAccountID: employeeAccountID,
1681+
managerID: adminAccountID,
1682+
policyID: policy.id,
1683+
stateNum: CONST.REPORT.STATE_NUM.APPROVED,
1684+
statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED,
1685+
total: -1000,
1686+
nonReimbursableTotal: 0,
1687+
currency: 'USD',
1688+
parentReportID: 'cancelNoBankChat',
1689+
chatReportID: 'cancelNoBankChat',
1690+
};
1691+
1692+
const chatReport = {
1693+
reportID: 'cancelNoBankChat',
1694+
isOwnPolicyExpenseChat: true,
1695+
ownerAccountID: employeeAccountID,
1696+
iouReportID: expenseReport.reportID,
1697+
policyID: policy.id,
1698+
type: CONST.REPORT.TYPE.CHAT,
1699+
chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
1700+
};
1701+
1702+
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy);
1703+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport);
1704+
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport);
1705+
1706+
mockFetch?.pause?.();
1707+
1708+
cancelPayment(expenseReport, chatReport, policy, true, adminAccountID, adminEmail, false);
1709+
await waitForBatchedUpdates();
1710+
1711+
const updatedReport = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`);
1712+
expect(updatedReport?.statusNum).toBe(CONST.REPORT.STATUS_NUM.CLOSED);
1713+
expect(updatedReport?.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED);
1714+
expect(updatedReport?.nextStep?.messageKey).toBe(CONST.NEXT_STEP.MESSAGE_KEY.NO_FURTHER_ACTION);
1715+
});
15691716
});
15701717

15711718
describe('payMoneyRequest', () => {

0 commit comments

Comments
 (0)