From 0230d2351e64d4ecbfd6c0f7d53d134ea81eb6c6 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Jan 2024 17:22:20 +0100 Subject: [PATCH 01/35] add a new report flag --- src/types/onyx/Report.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index f6af87038d00..0c2970f4d3f6 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -160,6 +160,9 @@ type Report = { /** If the report contains reportFields, save the field id and its value */ reportFields?: Record; + + /** Whether the user can do self approve or submit of an expense report */ + isPreventSelfApprovalEnabled?: boolean; }; export default Report; From c2f14f83e5fe41318b054dee0f582865403905f1 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 12 Jan 2024 17:22:42 +0100 Subject: [PATCH 02/35] draft implementation of buildNextStep --- src/libs/NextStepUtils.ts | 129 ++++++++++++- tests/unit/NextStepUtilsTest.ts | 315 ++++++++++++++++++++++++++++++++ 2 files changed, 442 insertions(+), 2 deletions(-) create mode 100644 tests/unit/NextStepUtilsTest.ts diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index a76a7d3c75c4..c45ccec43e24 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,6 +1,23 @@ import Str from 'expensify-common/lib/str'; +import Onyx from 'react-native-onyx'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report, ReportNextStep} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportNextStep'; import EmailUtils from './EmailUtils'; +import * as ReportUtils from './ReportUtils'; + +let currentUserAccountID: number | undefined; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (value) => { + if (!value) { + return; + } + + currentUserAccountID = value.accountID; + }, +}); function parseMessage(messages: Message[] | undefined) { let nextStepHTML = ''; @@ -27,5 +44,113 @@ function parseMessage(messages: Message[] | undefined) { return `${formattedHtml}`; } -// eslint-disable-next-line import/prefer-default-export -export {parseMessage}; +/** + * + * @param report + * @param isPaidWithWallet - Whether a report has been paid with wallet or outside of Expensify + * @returns next step + */ +function buildNextStep(report: Report, isPaidWithWallet = false): ReportNextStep | null { + const { + statusNum = CONST.REPORT.STATUS.OPEN, + // TODO: Clarify default value + isPreventSelfApprovalEnabled = false, + } = report; + const policy = ReportUtils.getPolicy(report.policyID ?? ''); + const isSelfApproval = policy.submitsTo === currentUserAccountID; + const submitterDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(policy.submitsTo, true) ?? ''; + const type: ReportNextStep['type'] = 'neutral'; + let optimisticNextStep: ReportNextStep | null; + + switch (statusNum) { + case CONST.REPORT.STATUS.OPEN: { + const message = [ + { + text: 'Waiting for', + }, + { + text: submitterDisplayName, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: 'these expenses.', + }, + ]; + const preventedSelfApprovalMessage = [ + { + text: "Oops! Looks like you're submitting to ", + }, + { + text: 'yourself', + type: 'strong', + }, + { + text: '. Approving your own reports is ', + }, + { + text: 'forbidden', + type: 'strong', + }, + { + text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', + }, + ]; + + optimisticNextStep = { + type, + title: 'Next Steps:', + message: isPreventSelfApprovalEnabled && isSelfApproval ? preventedSelfApprovalMessage : message, + }; + break; + } + + case CONST.REPORT.STATUS.SUBMITTED: + optimisticNextStep = { + type, + title: 'Next Steps:', + message: [{text: 'Waiting for'}, {text: submitterDisplayName, type: 'strong'}, {text: 'to'}, {text: 'review', type: 'strong'}, {text: ' %expenses.'}], + }; + break; + + case CONST.REPORT.STATUS.APPROVED: { + const message = [ + { + text: isSelfApproval ? Str.recapitalize(submitterDisplayName) : submitterDisplayName, + type: 'strong', + }, + { + text: 'have marked these expenses as', + }, + { + text: 'paid', + type: 'strong', + }, + ]; + + if (!isPaidWithWallet) { + message.push({text: 'outside of Expensify.'}); + } + + optimisticNextStep = { + type, + title: 'Finished!', + message, + }; + break; + } + + default: + optimisticNextStep = null; + } + + return optimisticNextStep; +} + +export {parseMessage, buildNextStep}; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts new file mode 100644 index 000000000000..5de218911375 --- /dev/null +++ b/tests/unit/NextStepUtilsTest.ts @@ -0,0 +1,315 @@ +import Str from 'expensify-common/lib/str'; +import CONST from '@src/CONST'; +import type {ReportNextStep} from '@src/types/onyx'; +import * as NextStepUtils from '../../src/libs/NextStepUtils'; +import * as ReportUtils from '../../src/libs/ReportUtils'; + +describe('libs/NextStepUtils', () => { + describe('buildNextStep', () => { + const fakeSubmitterEmail = 'submitter@expensify.com'; + const fakeSelfSubmitterEmail = 'you'; + const fakeChatReportID = '1'; + const fakePolicyID = '2'; + const fakePayeeAccountID = 3; + const report = ReportUtils.buildOptimisticExpenseReport(fakeChatReportID, fakePolicyID, fakePayeeAccountID, -500, CONST.CURRENCY.USD); + + const optimisticNextStep: ReportNextStep = { + type: 'neutral', + title: '', + message: [], + }; + + beforeEach(() => { + report.statusNum = CONST.REPORT.STATUS.OPEN; + optimisticNextStep.title = ''; + optimisticNextStep.message = []; + }); + + it('generates an optimistic nextStep once a report has been opened', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: 'these expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been self opened', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSelfSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: 'these expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been opened with prevented self submitting', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: "Oops! Looks like you're submitting to ", + }, + { + text: 'yourself', + type: 'strong', + }, + { + text: '. Approving your own reports is ', + }, + { + text: 'forbidden', + type: 'strong', + }, + { + text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been submitted', () => { + report.statusNum = CONST.REPORT.STATUS.SUBMITTED; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been self submitted', () => { + report.statusNum = CONST.REPORT.STATUS.SUBMITTED; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSelfSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been approved', () => { + report.statusNum = CONST.REPORT.STATUS.APPROVED; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been self approved', () => { + report.statusNum = CONST.REPORT.STATUS.APPROVED; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for', + }, + { + text: fakeSelfSubmitterEmail, + type: 'strong', + }, + { + text: 'to', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been paid with wallet', () => { + report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: fakeSubmitterEmail, + type: 'strong', + }, + { + text: 'have marked these expenses as', + }, + { + text: 'paid', + type: 'strong', + }, + ]; + + const result = NextStepUtils.buildNextStep(report, true); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been self paid with wallet', () => { + report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: Str.recapitalize(fakeSelfSubmitterEmail), + type: 'strong', + }, + { + text: 'have marked these expenses as', + }, + { + text: 'paid', + type: 'strong', + }, + ]; + + const result = NextStepUtils.buildNextStep(report, true); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been paid outside of Expensify', () => { + report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: fakeSubmitterEmail, + type: 'strong', + }, + { + text: 'have marked these expenses as', + }, + { + text: 'paid', + type: 'strong', + }, + { + text: 'outside of Expensify.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + it('generates an optimistic nextStep once a report has been paid self outside of Expensify', () => { + report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: Str.recapitalize(fakeSelfSubmitterEmail), + type: 'strong', + }, + { + text: 'have marked these expenses as', + }, + { + text: 'paid', + type: 'strong', + }, + { + text: 'outside of Expensify.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); +}); From 04d2c446e47d8fb86d86690a06b5df12ad3a80a5 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 16 Jan 2024 18:45:31 +0100 Subject: [PATCH 03/35] cover all basic scenarios --- src/libs/NextStepUtils.ts | 254 +++++++++---- tests/unit/NextStepUtilsTest.ts | 612 +++++++++++++++++--------------- 2 files changed, 512 insertions(+), 354 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index c45ccec43e24..901f1ffee96e 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -5,6 +5,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, ReportNextStep} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportNextStep'; import EmailUtils from './EmailUtils'; +import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportUtils from './ReportUtils'; let currentUserAccountID: number | undefined; @@ -44,108 +45,229 @@ function parseMessage(messages: Message[] | undefined) { return `${formattedHtml}`; } +type BuildNextStepParameters = { + isPaidWithWallet?: boolean; +}; + /** + * Generates an optimistic nextStep based on a current report status and other properties. * * @param report - * @param isPaidWithWallet - Whether a report has been paid with wallet or outside of Expensify - * @returns next step + * @param parameters + * @param parameters.isPaidWithWallet - Whether a report has been paid with wallet or outside of Expensify + * @returns nextStep */ -function buildNextStep(report: Report, isPaidWithWallet = false): ReportNextStep | null { +function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const { statusNum = CONST.REPORT.STATUS.OPEN, // TODO: Clarify default value isPreventSelfApprovalEnabled = false, + ownerAccountID = -1, } = report; const policy = ReportUtils.getPolicy(report.policyID ?? ''); - const isSelfApproval = policy.submitsTo === currentUserAccountID; + const isOwner = currentUserAccountID === ownerAccountID; + const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; + const isSelfApproval = currentUserAccountID === policy.submitsTo; const submitterDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(policy.submitsTo, true) ?? ''; const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; switch (statusNum) { - case CONST.REPORT.STATUS.OPEN: { - const message = [ - { - text: 'Waiting for', - }, - { - text: submitterDisplayName, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'submit', - type: 'strong', - }, - { - text: 'these expenses.', - }, - ]; - const preventedSelfApprovalMessage = [ - { - text: "Oops! Looks like you're submitting to ", - }, - { - text: 'yourself', - type: 'strong', - }, - { - text: '. Approving your own reports is ', - }, - { - text: 'forbidden', - type: 'strong', - }, - { - text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', - }, - ]; - + // Generates an optimistic nextStep once a report has been opened + case CONST.REPORT.STATUS.OPEN: + // Self review optimisticNextStep = { type, title: 'Next Steps:', - message: isPreventSelfApprovalEnabled && isSelfApproval ? preventedSelfApprovalMessage : message, + message: [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: ' these expenses. This report may be selected at random for manual approval.', + }, + ], }; + + // TODO: Clarify date + // Scheduled submit enabled + if (policy.isHarvestingEnabled) { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + } + + // Prevented self submitting + if (isPreventSelfApprovalEnabled && isSelfApproval) { + optimisticNextStep.message = [ + { + text: "Oops! Looks like you're submitting to ", + }, + { + text: 'yourself', + type: 'strong', + }, + { + text: '. Approving your own reports is ', + }, + { + text: 'forbidden', + type: 'strong', + }, + { + text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', + }, + ]; + } + break; - } + // Generates an optimistic nextStep once a report has been submitted case CONST.REPORT.STATUS.SUBMITTED: + // Self review & another reviewer optimisticNextStep = { type, title: 'Next Steps:', - message: [{text: 'Waiting for'}, {text: submitterDisplayName, type: 'strong'}, {text: 'to'}, {text: 'review', type: 'strong'}, {text: ' %expenses.'}], + message: [ + { + text: 'Waiting for ', + }, + { + text: submitterDisplayName, + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ], }; + + // Another owner + if (!isOwner) { + optimisticNextStep.message = [ + { + text: ownerLogin, + type: 'strong', + }, + { + text: ' is waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' these %expenses.', + }, + ]; + } + break; - case CONST.REPORT.STATUS.APPROVED: { - const message = [ - { - text: isSelfApproval ? Str.recapitalize(submitterDisplayName) : submitterDisplayName, - type: 'strong', - }, - { - text: 'have marked these expenses as', - }, - { - text: 'paid', - type: 'strong', - }, - ]; - - if (!isPaidWithWallet) { - message.push({text: 'outside of Expensify.'}); + // Generates an optimistic nextStep once a report has been approved + case CONST.REPORT.STATUS.APPROVED: + // Self review + optimisticNextStep = { + type, + title: 'Next Steps:', + message: [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ], + }; + + // Another owner + if (!isOwner) { + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: 'No further action required!', + }, + ]; } + break; + + // Generates an optimistic nextStep once a report has been paid + case CONST.REPORT.STATUS.REIMBURSED: + // Paid with wallet optimisticNextStep = { type, title: 'Finished!', - message, + message: [ + { + text: 'You', + type: 'strong', + }, + { + text: ' have marked these expenses as ', + }, + { + text: 'paid', + type: 'strong', + }, + ], }; + + // Paid outside of Expensify + if (typeof isPaidWithWallet === 'boolean' && !isPaidWithWallet) { + optimisticNextStep.message?.push({text: ' outside of Expensify'}); + } + + optimisticNextStep.message?.push({text: '.'}); + break; - } + // Resets a nextStep default: optimisticNextStep = null; } diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 5de218911375..6fe5d1dd31f1 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -1,17 +1,35 @@ -import Str from 'expensify-common/lib/str'; +import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; -import type {ReportNextStep} from '@src/types/onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Policy, Report, ReportNextStep} from '@src/types/onyx'; import * as NextStepUtils from '../../src/libs/NextStepUtils'; import * as ReportUtils from '../../src/libs/ReportUtils'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +Onyx.init({keys: ONYXKEYS}); describe('libs/NextStepUtils', () => { describe('buildNextStep', () => { - const fakeSubmitterEmail = 'submitter@expensify.com'; - const fakeSelfSubmitterEmail = 'you'; - const fakeChatReportID = '1'; - const fakePolicyID = '2'; - const fakePayeeAccountID = 3; - const report = ReportUtils.buildOptimisticExpenseReport(fakeChatReportID, fakePolicyID, fakePayeeAccountID, -500, CONST.CURRENCY.USD); + const currentUserEmail = 'current-user@expensify.com'; + const currentUserAccountID = 37; + const strangeEmail = 'stranger@expensify.com'; + const strangeAccountID = 50; + const policyID = '1'; + const policy: Policy = { + // Important props + id: policyID, + owner: currentUserEmail, + submitsTo: currentUserAccountID, + isHarvestingEnabled: false, + // Required props + name: 'Policy', + role: 'admin', + type: 'team', + outputCurrency: CONST.CURRENCY.USD, + areChatRoomsEnabled: true, + isPolicyExpenseChatEnabled: true, + }; + const report = ReportUtils.buildOptimisticExpenseReport('fake-chat-report-id-1', policyID, 1, -500, CONST.CURRENCY.USD) as Report; const optimisticNextStep: ReportNextStep = { type: 'neutral', @@ -19,297 +37,315 @@ describe('libs/NextStepUtils', () => { message: [], }; + beforeAll(() => { + // @ts-expect-error Preset necessary values + Onyx.multiSet({ + [ONYXKEYS.SESSION]: {email: currentUserEmail, accountID: currentUserAccountID}, + [`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]: policy, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: { + [strangeAccountID]: { + accountID: strangeAccountID, + login: strangeEmail, + avatar: '', + }, + }, + }).then(waitForBatchedUpdates); + }); + beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS.OPEN; + report.ownerAccountID = currentUserAccountID; optimisticNextStep.title = ''; optimisticNextStep.message = []; }); - it('generates an optimistic nextStep once a report has been opened', () => { - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'submit', - type: 'strong', - }, - { - text: 'these expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); + describe('it generates an optimistic nextStep once a report has been opened', () => { + beforeEach(() => { + report.statusNum = CONST.REPORT.STATUS.OPEN; + }); + + test('self review', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: ' these expenses. This report may be selected at random for manual approval.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + // TODO: Clarify date + test('scheduled submit enabled', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('prevented self submitting', () => { + report.isPreventSelfApprovalEnabled = true; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: "Oops! Looks like you're submitting to ", + }, + { + text: 'yourself', + type: 'strong', + }, + { + text: '. Approving your own reports is ', + }, + { + text: 'forbidden', + type: 'strong', + }, + { + text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', + }, + ]; + + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + submitsTo: currentUserAccountID, + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); }); - it('generates an optimistic nextStep once a report has been self opened', () => { - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSelfSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'submit', - type: 'strong', - }, - { - text: 'these expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); + describe('it generates an optimistic nextStep once a report has been submitted', () => { + beforeEach(() => { + report.statusNum = CONST.REPORT.STATUS.SUBMITTED; + }); + + test('self review', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + test('another reviewer', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: strangeEmail, + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + submitsTo: strangeAccountID, + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('another owner', () => { + report.ownerAccountID = strangeAccountID; + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: strangeEmail, + type: 'strong', + }, + { + text: ' is waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' these %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); }); - it('generates an optimistic nextStep once a report has been opened with prevented self submitting', () => { - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: "Oops! Looks like you're submitting to ", - }, - { - text: 'yourself', - type: 'strong', - }, - { - text: '. Approving your own reports is ', - }, - { - text: 'forbidden', - type: 'strong', - }, - { - text: ' by your policy. Please submit this report to someone else or contact your admin to change the person you submit to.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been submitted', () => { - report.statusNum = CONST.REPORT.STATUS.SUBMITTED; - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'review', - type: 'strong', - }, - { - text: ' %expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); + describe('it generates an optimistic nextStep once a report has been approved', () => { + beforeEach(() => { + report.statusNum = CONST.REPORT.STATUS.APPROVED; + }); + + test('self review', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'review', + type: 'strong', + }, + { + text: ' %expenses.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + test('another owner', () => { + report.ownerAccountID = strangeAccountID; + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: 'No further action required!', + }, + ]; + + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); }); - it('generates an optimistic nextStep once a report has been self submitted', () => { - report.statusNum = CONST.REPORT.STATUS.SUBMITTED; - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSelfSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'review', - type: 'strong', - }, - { - text: ' %expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been approved', () => { - report.statusNum = CONST.REPORT.STATUS.APPROVED; - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'review', - type: 'strong', - }, - { - text: ' %expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been self approved', () => { - report.statusNum = CONST.REPORT.STATUS.APPROVED; - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for', - }, - { - text: fakeSelfSubmitterEmail, - type: 'strong', - }, - { - text: 'to', - }, - { - text: 'review', - type: 'strong', - }, - { - text: ' %expenses.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been paid with wallet', () => { - report.statusNum = CONST.REPORT.STATUS.REIMBURSED; - optimisticNextStep.title = 'Finished!'; - optimisticNextStep.message = [ - { - text: fakeSubmitterEmail, - type: 'strong', - }, - { - text: 'have marked these expenses as', - }, - { - text: 'paid', - type: 'strong', - }, - ]; - - const result = NextStepUtils.buildNextStep(report, true); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been self paid with wallet', () => { - report.statusNum = CONST.REPORT.STATUS.REIMBURSED; - optimisticNextStep.title = 'Finished!'; - optimisticNextStep.message = [ - { - text: Str.recapitalize(fakeSelfSubmitterEmail), - type: 'strong', - }, - { - text: 'have marked these expenses as', - }, - { - text: 'paid', - type: 'strong', - }, - ]; - - const result = NextStepUtils.buildNextStep(report, true); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been paid outside of Expensify', () => { - report.statusNum = CONST.REPORT.STATUS.REIMBURSED; - optimisticNextStep.title = 'Finished!'; - optimisticNextStep.message = [ - { - text: fakeSubmitterEmail, - type: 'strong', - }, - { - text: 'have marked these expenses as', - }, - { - text: 'paid', - type: 'strong', - }, - { - text: 'outside of Expensify.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); - }); - - it('generates an optimistic nextStep once a report has been paid self outside of Expensify', () => { - report.statusNum = CONST.REPORT.STATUS.REIMBURSED; - optimisticNextStep.title = 'Finished!'; - optimisticNextStep.message = [ - { - text: Str.recapitalize(fakeSelfSubmitterEmail), - type: 'strong', - }, - { - text: 'have marked these expenses as', - }, - { - text: 'paid', - type: 'strong', - }, - { - text: 'outside of Expensify.', - }, - ]; - - const result = NextStepUtils.buildNextStep(report); - - expect(result).toStrictEqual(optimisticNextStep); + describe('it generates an optimistic nextStep once a report has been paid', () => { + beforeEach(() => { + report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + }); + + test('paid with wallet', () => { + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: 'You', + type: 'strong', + }, + { + text: ' have marked these expenses as ', + }, + { + text: 'paid', + type: 'strong', + }, + { + text: '.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report, {isPaidWithWallet: true}); + + expect(result).toStrictEqual(optimisticNextStep); + }); + + test('paid outside of Expensify', () => { + optimisticNextStep.title = 'Finished!'; + optimisticNextStep.message = [ + { + text: 'You', + type: 'strong', + }, + { + text: ' have marked these expenses as ', + }, + { + text: 'paid', + type: 'strong', + }, + { + text: ' outside of Expensify', + }, + { + text: '.', + }, + ]; + + const result = NextStepUtils.buildNextStep(report, {isPaidWithWallet: false}); + + expect(result).toStrictEqual(optimisticNextStep); + }); }); }); }); From a32e22e830f8592f22025242d7ac4eb386f95212 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Jan 2024 12:14:17 +0100 Subject: [PATCH 04/35] rename const --- src/libs/NextStepUtils.ts | 10 +++++----- tests/unit/NextStepUtilsTest.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 901f1ffee96e..e5a3afad4966 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -59,7 +59,7 @@ type BuildNextStepParameters = { */ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const { - statusNum = CONST.REPORT.STATUS.OPEN, + statusNum = CONST.REPORT.STATUS_NUM.OPEN, // TODO: Clarify default value isPreventSelfApprovalEnabled = false, ownerAccountID = -1, @@ -74,7 +74,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete switch (statusNum) { // Generates an optimistic nextStep once a report has been opened - case CONST.REPORT.STATUS.OPEN: + case CONST.REPORT.STATUS_NUM.OPEN: // Self review optimisticNextStep = { type, @@ -143,7 +143,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete break; // Generates an optimistic nextStep once a report has been submitted - case CONST.REPORT.STATUS.SUBMITTED: + case CONST.REPORT.STATUS_NUM.SUBMITTED: // Self review & another reviewer optimisticNextStep = { type, @@ -199,7 +199,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete break; // Generates an optimistic nextStep once a report has been approved - case CONST.REPORT.STATUS.APPROVED: + case CONST.REPORT.STATUS_NUM.APPROVED: // Self review optimisticNextStep = { type, @@ -238,7 +238,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete break; // Generates an optimistic nextStep once a report has been paid - case CONST.REPORT.STATUS.REIMBURSED: + case CONST.REPORT.STATUS_NUM.REIMBURSED: // Paid with wallet optimisticNextStep = { type, diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 6fe5d1dd31f1..e2637a3bdb85 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -60,7 +60,7 @@ describe('libs/NextStepUtils', () => { describe('it generates an optimistic nextStep once a report has been opened', () => { beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS.OPEN; + report.statusNum = CONST.REPORT.STATUS_NUM.OPEN; }); test('self review', () => { @@ -150,7 +150,7 @@ describe('libs/NextStepUtils', () => { describe('it generates an optimistic nextStep once a report has been submitted', () => { beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS.SUBMITTED; + report.statusNum = CONST.REPORT.STATUS_NUM.SUBMITTED; }); test('self review', () => { @@ -246,7 +246,7 @@ describe('libs/NextStepUtils', () => { describe('it generates an optimistic nextStep once a report has been approved', () => { beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS.APPROVED; + report.statusNum = CONST.REPORT.STATUS_NUM.APPROVED; }); test('self review', () => { @@ -293,7 +293,7 @@ describe('libs/NextStepUtils', () => { describe('it generates an optimistic nextStep once a report has been paid', () => { beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS.REIMBURSED; + report.statusNum = CONST.REPORT.STATUS_NUM.REIMBURSED; }); test('paid with wallet', () => { From bbe860da7e34094a932aa4538109f2234ffbce74 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Jan 2024 12:14:37 +0100 Subject: [PATCH 05/35] clear todo --- src/libs/NextStepUtils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index e5a3afad4966..13ae204c0296 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -58,12 +58,7 @@ type BuildNextStepParameters = { * @returns nextStep */ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { - const { - statusNum = CONST.REPORT.STATUS_NUM.OPEN, - // TODO: Clarify default value - isPreventSelfApprovalEnabled = false, - ownerAccountID = -1, - } = report; + const {statusNum = CONST.REPORT.STATUS_NUM.OPEN, isPreventSelfApprovalEnabled = false, ownerAccountID = -1} = report; const policy = ReportUtils.getPolicy(report.policyID ?? ''); const isOwner = currentUserAccountID === ownerAccountID; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; From 9387b10f451560091d7655a97dcac1af87163c5a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Jan 2024 17:35:13 +0100 Subject: [PATCH 06/35] integrate timestamp handling --- src/libs/NextStepUtils.ts | 57 +++++++-- src/types/onyx/Policy.ts | 3 + tests/unit/NextStepUtilsTest.ts | 200 ++++++++++++++++++++++++++++---- 3 files changed, 231 insertions(+), 29 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 13ae204c0296..8c44b84ed734 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,3 +1,4 @@ +import {format, lastDayOfMonth} from 'date-fns'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; @@ -90,28 +91,66 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete type: 'strong', }, { - text: ' these expenses. This report may be selected at random for manual approval.', + text: ' these expenses.', }, ], }; - // TODO: Clarify date // Scheduled submit enabled if (policy.isHarvestingEnabled) { optimisticNextStep.message = [ { text: 'These expenses are scheduled to ', }, - { - text: 'automatically submit!', - type: 'strong', - }, - { - text: ' No further action required!', - }, ]; + + if (policy.autoReportingFrequency) { + const currentDate = new Date(); + + let autoSubmissionDate: string; + + if (policy.autoReportingOffset === 'lastDayOfMonth') { + const currentDateWithLastDayOfMonth = lastDayOfMonth(currentDate); + + autoSubmissionDate = format(currentDateWithLastDayOfMonth, 'do'); + } else if (policy.autoReportingOffset === 'lastBusinessDayOfMonth') { + // TODO: Get from the backend + // const currentLastBusinessDayOfMonth = + autoSubmissionDate = ''; + } else if (Number.isNaN(Number(policy.autoReportingOffset))) { + autoSubmissionDate = ''; + } else { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + autoSubmissionDate = format(currentDate.setDate(+policy.autoReportingOffset!), 'do'); + } + + const harvestingSuffixes = { + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: 'later today', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: 'on Sunday', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: 'on the 1st and 16th of each month', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: `on the ${autoSubmissionDate} of each month`, + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'at the end of your trip', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: '', + }; + + optimisticNextStep.message.push({ + text: `automatically submit ${harvestingSuffixes[policy.autoReportingFrequency]}!`, + type: 'strong', + }); + } else { + optimisticNextStep.message.push({ + text: `automatically submit!`, + type: 'strong', + }); + } + + optimisticNextStep.message.push({ + text: ' No further action required!', + }); } + // TODO add "This report may be selected at random for manual approval." + // Prevented self submitting if (isPreventSelfApprovalEnabled && isSelfApproval) { optimisticNextStep.message = [ diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index da4522487a7a..0be879fbfd68 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -74,6 +74,9 @@ type Policy = { /** The scheduled submit frequency set up on the this policy */ autoReportingFrequency?: ValueOf; + /** The scheduled submission date */ + autoReportingOffset?: string; + /** Whether the scheduled submit is enabled */ isHarvestingEnabled?: boolean; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index e2637a3bdb85..33089b26ea0e 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -1,3 +1,4 @@ +import {format, lastDayOfMonth} from 'date-fns'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -81,7 +82,7 @@ describe('libs/NextStepUtils', () => { type: 'strong', }, { - text: ' these expenses. This report may be selected at random for manual approval.', + text: ' these expenses.', }, ]; @@ -90,28 +91,187 @@ describe('libs/NextStepUtils', () => { expect(result).toStrictEqual(optimisticNextStep); }); - // TODO: Clarify date - test('scheduled submit enabled', () => { + describe('scheduled submit enabled', () => { optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'These expenses are scheduled to ', - }, - { - text: 'automatically submit!', - type: 'strong', - }, - { - text: ' No further action required!', - }, - ]; - return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, - }).then(() => { - const result = NextStepUtils.buildNextStep(report); + test('daily', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit later today!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'immediate', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); - expect(result).toStrictEqual(optimisticNextStep); + test('weekly', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit on Sunday!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'weekly', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('twice a month', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit on the 1st and 16th of each month!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'semimonthly', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('monthly on the 2nd', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit on the 2nd of each month!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'monthly', + autoReportingOffset: '2', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('monthly on the last day', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: `automatically submit on the ${format(lastDayOfMonth(new Date()), 'do')} of each month!`, + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'monthly', + autoReportingOffset: 'lastDayOfMonth', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('trip', () => { + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: 'automatically submit at the end of your trip!', + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'trip', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + + test('manual', () => { + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: ' these expenses.', + }, + { + text: ' This report may be selected at random for manual approval.', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'manual', + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); }); }); From 090d8ff545547c63a2940b9e83af02ceb8ec7cba Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Jan 2024 17:42:31 +0100 Subject: [PATCH 07/35] add a case with random for manual approval --- src/libs/NextStepUtils.ts | 6 +++++- src/types/onyx/Policy.ts | 3 +++ tests/unit/NextStepUtilsTest.ts | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 8c44b84ed734..4014bcd549fc 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -149,7 +149,11 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete }); } - // TODO add "This report may be selected at random for manual approval." + if (isOwner && policy.isAutoApprovalEnabled) { + optimisticNextStep.message?.push({ + text: ' This report may be selected at random for manual approval.', + }); + } // Prevented self submitting if (isPreventSelfApprovalEnabled && isSelfApproval) { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 0be879fbfd68..08a13fa12e38 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -65,6 +65,9 @@ type Policy = { /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ areChatRoomsEnabled: boolean; + /** Whether auto approval enabled */ + isAutoApprovalEnabled?: boolean; + /** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */ isPolicyExpenseChatEnabled: boolean; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 33089b26ea0e..9eb3e0a14555 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -22,6 +22,7 @@ describe('libs/NextStepUtils', () => { owner: currentUserEmail, submitsTo: currentUserAccountID, isHarvestingEnabled: false, + isAutoApprovalEnabled: false, // Required props name: 'Policy', role: 'admin', @@ -91,6 +92,40 @@ describe('libs/NextStepUtils', () => { expect(result).toStrictEqual(optimisticNextStep); }); + test('self review and auto approval enabled', () => { + optimisticNextStep.title = 'Next Steps:'; + optimisticNextStep.message = [ + { + text: 'Waiting for ', + }, + { + text: 'you', + type: 'strong', + }, + { + text: ' to ', + }, + { + text: 'submit', + type: 'strong', + }, + { + text: ' these expenses.', + }, + { + text: ' This report may be selected at random for manual approval.', + }, + ]; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isAutoApprovalEnabled: true, + }).then(() => { + const result = NextStepUtils.buildNextStep(report); + + expect(result).toStrictEqual(optimisticNextStep); + }); + }); + describe('scheduled submit enabled', () => { optimisticNextStep.title = 'Next Steps:'; From f77cea193bd5775f668dfd8e67a9852a7dc6df40 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 17 Jan 2024 17:48:16 +0100 Subject: [PATCH 08/35] add comment --- src/libs/NextStepUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 4014bcd549fc..a0880c8a96e6 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -149,6 +149,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete }); } + // Self review and auto approval enabled if (isOwner && policy.isAutoApprovalEnabled) { optimisticNextStep.message?.push({ text: ' This report may be selected at random for manual approval.', From 58baa0afd7821999d980a71c471ebc1ee20b2a06 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 18 Jan 2024 13:54:40 +0100 Subject: [PATCH 09/35] integrate predictedNextStatus argument --- src/libs/NextStepUtils.ts | 9 +++--- tests/unit/NextStepUtilsTest.ts | 53 +++++++++++---------------------- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index a0880c8a96e6..4cebc1325b27 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,6 +1,7 @@ import {format, lastDayOfMonth} from 'date-fns'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, ReportNextStep} from '@src/types/onyx'; @@ -54,12 +55,12 @@ type BuildNextStepParameters = { * Generates an optimistic nextStep based on a current report status and other properties. * * @param report - * @param parameters + * @param predictedNextStatus - a next expected status of the report * @param parameters.isPaidWithWallet - Whether a report has been paid with wallet or outside of Expensify * @returns nextStep */ -function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { - const {statusNum = CONST.REPORT.STATUS_NUM.OPEN, isPreventSelfApprovalEnabled = false, ownerAccountID = -1} = report; +function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { + const {isPreventSelfApprovalEnabled = false, ownerAccountID = -1} = report; const policy = ReportUtils.getPolicy(report.policyID ?? ''); const isOwner = currentUserAccountID === ownerAccountID; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; @@ -68,7 +69,7 @@ function buildNextStep(report: Report, {isPaidWithWallet}: BuildNextStepParamete const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; - switch (statusNum) { + switch (predictedNextStatus) { // Generates an optimistic nextStep once a report has been opened case CONST.REPORT.STATUS_NUM.OPEN: // Self review diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 9eb3e0a14555..bd4c7c9a0834 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -31,13 +31,12 @@ describe('libs/NextStepUtils', () => { areChatRoomsEnabled: true, isPolicyExpenseChatEnabled: true, }; - const report = ReportUtils.buildOptimisticExpenseReport('fake-chat-report-id-1', policyID, 1, -500, CONST.CURRENCY.USD) as Report; - const optimisticNextStep: ReportNextStep = { type: 'neutral', title: '', message: [], }; + const report = ReportUtils.buildOptimisticExpenseReport('fake-chat-report-id-1', policyID, 1, -500, CONST.CURRENCY.USD) as Report; beforeAll(() => { // @ts-expect-error Preset necessary values @@ -61,10 +60,6 @@ describe('libs/NextStepUtils', () => { }); describe('it generates an optimistic nextStep once a report has been opened', () => { - beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS_NUM.OPEN; - }); - test('self review', () => { optimisticNextStep.title = 'Next Steps:'; optimisticNextStep.message = [ @@ -87,7 +82,7 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -120,7 +115,7 @@ describe('libs/NextStepUtils', () => { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isAutoApprovalEnabled: true, }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -147,7 +142,7 @@ describe('libs/NextStepUtils', () => { isHarvestingEnabled: true, autoReportingFrequency: 'immediate', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -171,7 +166,7 @@ describe('libs/NextStepUtils', () => { isHarvestingEnabled: true, autoReportingFrequency: 'weekly', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -195,7 +190,7 @@ describe('libs/NextStepUtils', () => { isHarvestingEnabled: true, autoReportingFrequency: 'semimonthly', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -220,7 +215,7 @@ describe('libs/NextStepUtils', () => { autoReportingFrequency: 'monthly', autoReportingOffset: '2', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -245,7 +240,7 @@ describe('libs/NextStepUtils', () => { autoReportingFrequency: 'monthly', autoReportingOffset: 'lastDayOfMonth', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -269,7 +264,7 @@ describe('libs/NextStepUtils', () => { isHarvestingEnabled: true, autoReportingFrequency: 'trip', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -303,7 +298,7 @@ describe('libs/NextStepUtils', () => { isHarvestingEnabled: true, autoReportingFrequency: 'manual', }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -336,7 +331,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { submitsTo: currentUserAccountID, }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); expect(result).toStrictEqual(optimisticNextStep); }); @@ -344,10 +339,6 @@ describe('libs/NextStepUtils', () => { }); describe('it generates an optimistic nextStep once a report has been submitted', () => { - beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS_NUM.SUBMITTED; - }); - test('self review', () => { optimisticNextStep.title = 'Next Steps:'; optimisticNextStep.message = [ @@ -370,7 +361,7 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); expect(result).toStrictEqual(optimisticNextStep); }); @@ -400,7 +391,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { submitsTo: strangeAccountID, }).then(() => { - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); expect(result).toStrictEqual(optimisticNextStep); }); @@ -433,17 +424,13 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); expect(result).toStrictEqual(optimisticNextStep); }); }); describe('it generates an optimistic nextStep once a report has been approved', () => { - beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS_NUM.APPROVED; - }); - test('self review', () => { optimisticNextStep.title = 'Next Steps:'; optimisticNextStep.message = [ @@ -466,7 +453,7 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); expect(result).toStrictEqual(optimisticNextStep); }); @@ -480,17 +467,13 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); expect(result).toStrictEqual(optimisticNextStep); }); }); describe('it generates an optimistic nextStep once a report has been paid', () => { - beforeEach(() => { - report.statusNum = CONST.REPORT.STATUS_NUM.REIMBURSED; - }); - test('paid with wallet', () => { optimisticNextStep.title = 'Finished!'; optimisticNextStep.message = [ @@ -510,7 +493,7 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report, {isPaidWithWallet: true}); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithWallet: true}); expect(result).toStrictEqual(optimisticNextStep); }); @@ -537,7 +520,7 @@ describe('libs/NextStepUtils', () => { }, ]; - const result = NextStepUtils.buildNextStep(report, {isPaidWithWallet: false}); + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithWallet: false}); expect(result).toStrictEqual(optimisticNextStep); }); From 13d2d7f83bf0843b1368b38e5e554b00a110aafd Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 18 Jan 2024 14:55:51 +0100 Subject: [PATCH 10/35] replace review with approve once submit --- src/libs/NextStepUtils.ts | 4 ++-- tests/unit/NextStepUtilsTest.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 4cebc1325b27..321d72187545 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -200,7 +200,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { text: ' to ', }, { - text: 'review', + text: 'approve', type: 'strong', }, { @@ -380,7 +380,7 @@ describe('libs/NextStepUtils', () => { text: ' to ', }, { - text: 'review', + text: 'approve', type: 'strong', }, { @@ -416,7 +416,7 @@ describe('libs/NextStepUtils', () => { text: ' to ', }, { - text: 'review', + text: 'approve', type: 'strong', }, { From 81616da30daeb78c9a77067812cf0206459a2c22 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 18 Jan 2024 17:05:07 +0100 Subject: [PATCH 11/35] handle manager case --- src/libs/NextStepUtils.ts | 12 ++++++++---- tests/unit/NextStepUtilsTest.ts | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 321d72187545..cdc7c625b189 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -60,8 +60,9 @@ type BuildNextStepParameters = { * @returns nextStep */ function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { - const {isPreventSelfApprovalEnabled = false, ownerAccountID = -1} = report; + const {isPreventSelfApprovalEnabled = false, ownerAccountID = -1, managerID} = report; const policy = ReportUtils.getPolicy(report.policyID ?? ''); + const isManager = currentUserAccountID === managerID; const isOwner = currentUserAccountID === ownerAccountID; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; const isSelfApproval = currentUserAccountID === policy.submitsTo; @@ -183,7 +184,9 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { beforeEach(() => { report.ownerAccountID = currentUserAccountID; + report.managerID = currentUserAccountID; optimisticNextStep.title = ''; optimisticNextStep.message = []; }); @@ -353,7 +354,7 @@ describe('libs/NextStepUtils', () => { text: ' to ', }, { - text: 'approve', + text: 'review', type: 'strong', }, { @@ -367,6 +368,7 @@ describe('libs/NextStepUtils', () => { }); test('another reviewer', () => { + report.managerID = strangeAccountID; optimisticNextStep.title = 'Next Steps:'; optimisticNextStep.message = [ { @@ -416,7 +418,7 @@ describe('libs/NextStepUtils', () => { text: ' to ', }, { - text: 'approve', + text: 'review', type: 'strong', }, { From 003d387200ff20b92e897bae0796e603591e802c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 18 Jan 2024 18:05:10 +0100 Subject: [PATCH 12/35] improve adding the random for manual approval message --- src/libs/NextStepUtils.ts | 14 +++++++------- tests/unit/NextStepUtilsTest.ts | 2 ++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index cdc7c625b189..43569f4c8106 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -151,13 +151,6 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { report.managerID = currentUserAccountID; optimisticNextStep.title = ''; optimisticNextStep.message = []; + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, policy).then(waitForBatchedUpdates); }); describe('it generates an optimistic nextStep once a report has been opened', () => { From 85731fbdaadb584ddc1b8ac8f20a25bcec4d2075 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 18 Jan 2024 18:31:33 +0100 Subject: [PATCH 13/35] integrate optimistic next step generation --- src/libs/actions/IOU.js | 107 +++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 430d88b98569..66e804cf76ab 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -14,6 +14,7 @@ import * as IOUUtils from '@libs/IOUUtils'; import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as Localize from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; +import * as NextStepUtils from '@libs/NextStepUtils'; import * as NumberUtils from '@libs/NumberUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Permissions from '@libs/Permissions'; @@ -330,6 +331,7 @@ function getReceiptError(receipt, filename, isScanRequest = true) { * @param {Array} policyTags * @param {Array} policyCategories * @param {Boolean} hasOutstandingChildRequest + * @param {Object} [optimisticNextStep] * @returns {Array} - An array containing the optimistic data, success data, and failure data. */ function buildOnyxDataForMoneyRequest( @@ -349,6 +351,7 @@ function buildOnyxDataForMoneyRequest( policyTags, policyCategories, hasOutstandingChildRequest = false, + optimisticNextStep, ) { const isScanRequest = TransactionUtils.isScanRequest(transaction); const optimisticData = [ @@ -431,6 +434,14 @@ function buildOnyxDataForMoneyRequest( }); } + if (!_.isEmpty(optimisticNextStep)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, + value: optimisticNextStep, + }); + } + const successData = [ ...(isNewChatReport ? [ @@ -810,6 +821,10 @@ function getMoneyRequestInformation( // so the employee has to submit to their manager manually. const hasOutstandingChildRequest = isPolicyExpenseChat && needsToBeManuallySubmitted; + const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.OPEN); + // eslint-disable-next-line no-console + console.log('optimisticNextStep', optimisticNextStep); + // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( chatReport, @@ -828,6 +843,7 @@ function getMoneyRequestInformation( policyTags, policyCategories, hasOutstandingChildRequest, + optimisticNextStep, ); return { @@ -3024,6 +3040,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho } const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, null); + const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithWallet: paymentMethodType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY}); const optimisticData = [ { @@ -3065,6 +3082,11 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho key: ONYXKEYS.NVP_LAST_PAYMENT_METHOD, value: {[iouReport.policyID]: paymentMethodType}, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, + value: optimisticNextStep, + }, ]; const successData = [ @@ -3099,20 +3121,12 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, value: chatReport, }, - ]; - - if (!_.isNull(currentNextStep)) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, - value: null, - }); - failureData.push({ + { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, value: currentNextStep, - }); - } + }, + ]; // In case the report preview action is loaded locally, let's update it. if (optimisticReportPreviewAction) { @@ -3184,9 +3198,9 @@ function sendMoneyWithWallet(report, amount, currency, comment, managerID, recip } function approveMoneyRequest(expenseReport) { - const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null); - const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID); + const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null); + const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED); const optimisticReportActionsData = { onyxMethod: Onyx.METHOD.MERGE, @@ -3209,7 +3223,15 @@ function approveMoneyRequest(expenseReport) { statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, }; - const optimisticData = [optimisticIOUReportData, optimisticReportActionsData]; + const optimisticData = [ + optimisticIOUReportData, + optimisticReportActionsData, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: optimisticNextStep, + }, + ]; const successData = [ { @@ -3233,20 +3255,12 @@ function approveMoneyRequest(expenseReport) { }, }, }, - ]; - - if (!_.isNull(currentNextStep)) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, - value: null, - }); - failureData.push({ + { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, value: currentNextStep, - }); - } + }, + ]; API.write('ApproveMoneyRequest', {reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID}, {optimisticData, successData, failureData}); } @@ -3255,11 +3269,11 @@ function approveMoneyRequest(expenseReport) { * @param {Object} expenseReport */ function submitReport(expenseReport) { - const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null); - const optimisticSubmittedReportAction = ReportUtils.buildOptimisticSubmittedReportAction(expenseReport.total, expenseReport.currency, expenseReport.reportID); const parentReport = ReportUtils.getReport(expenseReport.parentReportID); const isCurrentUserManager = currentUserPersonalDetails.accountID === expenseReport.managerID; + const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null); + const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.SUBMITTED); const optimisticData = [ { @@ -3283,6 +3297,11 @@ function submitReport(expenseReport) { statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: optimisticNextStep, + }, ...(parentReport.reportID ? [ { @@ -3330,6 +3349,11 @@ function submitReport(expenseReport) { stateNum: CONST.REPORT.STATE_NUM.OPEN, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: currentNextStep, + }, ...(parentReport.reportID ? [ { @@ -3344,19 +3368,6 @@ function submitReport(expenseReport) { : []), ]; - if (!_.isNull(currentNextStep)) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, - value: null, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, - value: currentNextStep, - }); - } - API.write( 'SubmitReport', { @@ -3376,6 +3387,10 @@ function cancelPayment(expenseReport, chatReport) { const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(expenseReport.reportID); const policy = ReportUtils.getPolicy(chatReport.policyID); const isFree = policy && policy.type === CONST.POLICY.TYPE.FREE; + const statusNum = isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN; + const currentNextStep = lodashGet(allNextSteps, `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, null); + const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, statusNum); + const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -3395,9 +3410,14 @@ function cancelPayment(expenseReport, chatReport) { lastMessageText: lodashGet(optimisticReportAction, 'message.0.text', ''), lastMessageHtml: lodashGet(optimisticReportAction, 'message.0.html', ''), stateNum: isFree ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN, - statusNum: isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN, + statusNum, }, }, + { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: optimisticNextStep, + }, ...(chatReport.reportID ? [ { @@ -3442,6 +3462,11 @@ function cancelPayment(expenseReport, chatReport) { statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, + value: currentNextStep, + }, ...(chatReport.reportID ? [ { From fa2c8fb084c825c2af6c34cf0d10e10d265fdc17 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 19 Jan 2024 14:00:09 +0100 Subject: [PATCH 14/35] minor improvements --- src/CONST.ts | 1 + src/libs/NextStepUtils.ts | 36 ++++++++++++++++----------------- src/types/onyx/Policy.ts | 3 --- tests/unit/NextStepUtilsTest.ts | 2 +- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index e3cce4b613af..5de9b9f29a28 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -182,6 +182,7 @@ const CONST = { UNIX_EPOCH: '1970-01-01 00:00:00.000', MAX_DATE: '9999-12-31', MIN_DATE: '0001-01-01', + ORDINAL_DAY_OF_MONTH: 'do', }, SMS: { DOMAIN: '@expensify.sms', diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 43569f4c8106..df477a887926 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -105,47 +105,45 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf; - /** The scheduled submission date */ - autoReportingOffset?: string; - /** Whether the scheduled submit is enabled */ isHarvestingEnabled?: boolean; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 6b6b046fd427..820ffb937450 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -216,7 +216,7 @@ describe('libs/NextStepUtils', () => { Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'monthly', - autoReportingOffset: '2', + autoReportingOffset: 2, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); From dfec8f60dbce63a758a5fa5ab3171e2aa0fca8c2 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 19 Jan 2024 14:01:25 +0100 Subject: [PATCH 15/35] remove log --- src/libs/actions/IOU.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index ba6227512f7a..f5858a234823 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -822,8 +822,6 @@ function getMoneyRequestInformation( const hasOutstandingChildRequest = isPolicyExpenseChat && needsToBeManuallySubmitted; const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.OPEN); - // eslint-disable-next-line no-console - console.log('optimisticNextStep', optimisticNextStep); // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( From 59a036dede1a9d8e7d0e91898ec3d0312b326061 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 19 Jan 2024 15:03:01 +0100 Subject: [PATCH 16/35] improve and fix tests --- src/libs/NextStepUtils.ts | 3 +- tests/unit/NextStepUtilsTest.ts | 65 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index df477a887926..371ef7cda8ec 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -99,7 +99,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); test('self review and auto approval enabled', () => { @@ -115,17 +115,19 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isAutoApprovalEnabled: true, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); describe('scheduled submit enabled', () => { - optimisticNextStep.title = 'Next Steps:'; + beforeEach(() => { + optimisticNextStep.title = 'Next Steps:'; + }); test('daily', () => { optimisticNextStep.message = [ @@ -141,13 +143,13 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'immediate', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -165,13 +167,13 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'weekly', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -189,13 +191,13 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'semimonthly', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -213,14 +215,14 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'monthly', autoReportingOffset: 2, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -238,14 +240,14 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'monthly', autoReportingOffset: 'lastDayOfMonth', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -263,13 +265,13 @@ describe('libs/NextStepUtils', () => { }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'trip', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -292,18 +294,15 @@ describe('libs/NextStepUtils', () => { { text: ' these expenses.', }, - { - text: ' This report may be selected at random for manual approval.', - }, ]; - Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, autoReportingFrequency: 'manual', }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); }); @@ -336,7 +335,7 @@ describe('libs/NextStepUtils', () => { }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); }); @@ -366,7 +365,7 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); test('another reviewer', () => { @@ -397,7 +396,7 @@ describe('libs/NextStepUtils', () => { }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -430,7 +429,7 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.SUBMITTED); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -459,7 +458,7 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); test('another owner', () => { @@ -473,7 +472,7 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.APPROVED); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); }); @@ -499,7 +498,7 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithWallet: true}); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); }); test('paid outside of Expensify', () => { @@ -526,7 +525,15 @@ describe('libs/NextStepUtils', () => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.REIMBURSED, {isPaidWithWallet: false}); - expect(result).toStrictEqual(optimisticNextStep); + expect(result).toMatchObject(optimisticNextStep); + }); + }); + + describe('it generates a nullable optimistic nextStep', () => { + test('closed status', () => { + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.CLOSED); + + expect(result).toBeNull(); }); }); }); From 8481f664890c039efd7ee81011848f5a49399011 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 24 Jan 2024 17:18:30 +0100 Subject: [PATCH 17/35] integrate last business day calculation --- src/libs/NextStepUtils.ts | 11 +++++------ tests/unit/NextStepUtilsTest.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 371ef7cda8ec..9a5ec66db948 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -1,4 +1,4 @@ -import {format, lastDayOfMonth} from 'date-fns'; +import {format, lastDayOfMonth, setDate} from 'date-fns'; import Str from 'expensify-common/lib/str'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, ReportNextStep} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportNextStep'; +import DateUtils from './DateUtils'; import EmailUtils from './EmailUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportUtils from './ReportUtils'; @@ -113,12 +114,10 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { text: 'These expenses are scheduled to ', }, { - text: `automatically submit on the ${format(lastDayOfMonth(new Date()), 'do')} of each month!`, + text: `automatically submit on the ${format(lastDayOfMonth(new Date()), CONST.DATE.ORDINAL_DAY_OF_MONTH)} of each month!`, type: 'strong', }, { @@ -251,6 +252,32 @@ describe('libs/NextStepUtils', () => { }); }); + test('monthly on the last business day', () => { + const lastBusinessDayOfMonth = DateUtils.getLastBusinessDayOfMonth(new Date()); + optimisticNextStep.message = [ + { + text: 'These expenses are scheduled to ', + }, + { + text: `automatically submit on the ${format(new Date().setDate(lastBusinessDayOfMonth), CONST.DATE.ORDINAL_DAY_OF_MONTH)} of each month!`, + type: 'strong', + }, + { + text: ' No further action required!', + }, + ]; + + return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { + isHarvestingEnabled: true, + autoReportingFrequency: 'monthly', + autoReportingOffset: 'lastBusinessDayOfMonth', + }).then(() => { + const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); + + expect(result).toMatchObject(optimisticNextStep); + }); + }); + test('trip', () => { optimisticNextStep.message = [ { From 7467e2c5a4fa037024c7a13b38898eca0d704436 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 25 Jan 2024 17:29:11 +0100 Subject: [PATCH 18/35] prettify codebase --- src/libs/NextStepUtils.ts | 64 +++++++++++++++++---------------- src/types/onyx/Report.ts | 3 -- tests/unit/NextStepUtilsTest.ts | 26 +++++++------- 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 9a5ec66db948..f606afbc6702 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -57,17 +57,18 @@ type BuildNextStepParameters = { * * @param report * @param predictedNextStatus - a next expected status of the report - * @param parameters.isPaidWithWallet - Whether a report has been paid with wallet or outside of Expensify + * @param parameters.isPaidWithWallet - Whether a report has been paid with the wallet or outside of Expensify * @returns nextStep */ function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { - const {isPreventSelfApprovalEnabled = false, ownerAccountID = -1, managerID} = report; const policy = ReportUtils.getPolicy(report.policyID ?? ''); - const isManager = currentUserAccountID === managerID; + const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, isAutoApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {ownerAccountID = -1, managerID = -1} = report; const isOwner = currentUserAccountID === ownerAccountID; + const isManager = currentUserAccountID === managerID; + const isSelfApproval = currentUserAccountID === submitsTo; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; - const isSelfApproval = currentUserAccountID === policy.submitsTo; - const submitterDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(policy.submitsTo, true) ?? ''; + const submitterDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo, true) ?? ''; const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; @@ -100,7 +101,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf; - - /** Whether the user can do self approve or submit of an expense report */ - isPreventSelfApprovalEnabled?: boolean; }; export default Report; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 5c6aef46a87f..5de6f539ea53 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -1,4 +1,4 @@ -import {format, lastDayOfMonth} from 'date-fns'; +import {format, lastDayOfMonth, setDate} from 'date-fns'; import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -146,7 +146,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'immediate', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -170,7 +170,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'weekly', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -194,7 +194,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'semimonthly', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -218,7 +218,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'monthly', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: 2, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -243,8 +243,8 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'monthly', - autoReportingOffset: 'lastDayOfMonth', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, + autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -259,7 +259,7 @@ describe('libs/NextStepUtils', () => { text: 'These expenses are scheduled to ', }, { - text: `automatically submit on the ${format(new Date().setDate(lastBusinessDayOfMonth), CONST.DATE.ORDINAL_DAY_OF_MONTH)} of each month!`, + text: `automatically submit on the ${format(setDate(new Date(), lastBusinessDayOfMonth), CONST.DATE.ORDINAL_DAY_OF_MONTH)} of each month!`, type: 'strong', }, { @@ -269,8 +269,8 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'monthly', - autoReportingOffset: 'lastBusinessDayOfMonth', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, + autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -294,7 +294,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'trip', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -325,7 +325,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { isHarvestingEnabled: true, - autoReportingFrequency: 'manual', + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -335,7 +335,6 @@ describe('libs/NextStepUtils', () => { }); test('prevented self submitting', () => { - report.isPreventSelfApprovalEnabled = true; optimisticNextStep.title = 'Next Steps:'; optimisticNextStep.message = [ { @@ -359,6 +358,7 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { submitsTo: currentUserAccountID, + isPreventSelfApprovalEnabled: true, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); From 38bfb704fc7a94d0c3e15ef5b9bb89a576876619 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 25 Jan 2024 17:50:22 +0100 Subject: [PATCH 19/35] remove one case for now --- src/libs/NextStepUtils.ts | 9 +-------- src/types/onyx/Policy.ts | 3 --- tests/unit/NextStepUtilsTest.ts | 35 --------------------------------- 3 files changed, 1 insertion(+), 46 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index f606afbc6702..dec6acaecec2 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -62,7 +62,7 @@ type BuildNextStepParameters = { */ function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const policy = ReportUtils.getPolicy(report.policyID ?? ''); - const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, isAutoApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; const {ownerAccountID = -1, managerID = -1} = report; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; @@ -174,13 +174,6 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { owner: currentUserEmail, submitsTo: currentUserAccountID, isHarvestingEnabled: false, - isAutoApprovalEnabled: false, // Required props name: 'Policy', role: 'admin', @@ -91,40 +90,6 @@ describe('libs/NextStepUtils', () => { expect(result).toMatchObject(optimisticNextStep); }); - test('self review and auto approval enabled', () => { - optimisticNextStep.title = 'Next Steps:'; - optimisticNextStep.message = [ - { - text: 'Waiting for ', - }, - { - text: 'you', - type: 'strong', - }, - { - text: ' to ', - }, - { - text: 'submit', - type: 'strong', - }, - { - text: ' these expenses.', - }, - { - text: ' This report may be selected at random for manual approval.', - }, - ]; - - return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isAutoApprovalEnabled: true, - }).then(() => { - const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); - - expect(result).toMatchObject(optimisticNextStep); - }); - }); - describe('scheduled submit enabled', () => { beforeEach(() => { optimisticNextStep.title = 'Next Steps:'; From cf9fcfbdc671b8c3d9155a56747db761900bc244 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 30 Jan 2024 14:46:15 +0100 Subject: [PATCH 20/35] add new harvesting field of policy --- src/types/onyx/Policy.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index eca7e9d1ee06..ffa619291516 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -88,9 +88,14 @@ type Policy = { /** The scheduled submit frequency set up on the this policy */ autoReportingFrequency?: ValueOf; - /** Whether the scheduled submit is enabled */ + /** @deprecated Whether the scheduled submit is enabled */ isHarvestingEnabled?: boolean; + /** Whether the scheduled submit is enabled */ + harvesting?: { + enabled: boolean; + }; + /** Whether the scheduled submit is enabled */ isPreventSelfApprovalEnabled?: boolean; From 821ee65b43a8c2add86e75184b848c3403c03362 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 30 Jan 2024 14:47:17 +0100 Subject: [PATCH 21/35] clarify comments --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ffa619291516..719e0ba1fb9d 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -96,7 +96,7 @@ type Policy = { enabled: boolean; }; - /** Whether the scheduled submit is enabled */ + /** Whether the self approval or submitting is enabled */ isPreventSelfApprovalEnabled?: boolean; /** When the monthly scheduled submit should happen */ From 9a36327265853796e28ebb4caaa3c6c177a9d8fa Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 30 Jan 2024 14:59:12 +0100 Subject: [PATCH 22/35] integrate new harvesting field --- src/components/MoneyReportHeader.tsx | 4 +-- .../ReportActionItem/ReportPreview.tsx | 4 +-- src/libs/NextStepUtils.ts | 4 +-- src/libs/actions/IOU.js | 2 +- tests/unit/NextStepUtilsTest.ts | 36 ++++++++++++++----- tests/utils/LHNTestUtils.js | 4 ++- tests/utils/collections/policies.ts | 4 ++- 7 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 4b4e3915f969..c2e6ff341416 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -86,8 +86,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !policy.isHarvestingEnabled, - [chatReport?.isOwnPolicyExpenseChat, policy.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !(policy.harvesting?.enabled ?? policy.isHarvestingEnabled), + [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled, policy.isHarvestingEnabled], ); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b2fece085f57..52e9d94eaefd 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -159,8 +159,8 @@ function ReportPreview({ // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !policy?.isHarvestingEnabled, - [chatReport?.isOwnPolicyExpenseChat, policy?.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled), + [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled, policy?.isHarvestingEnabled], ); const getDisplayAmount = (): string => { diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index dec6acaecec2..85986e57057e 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -62,7 +62,7 @@ type BuildNextStepParameters = { */ function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const policy = ReportUtils.getPolicy(report.policyID ?? ''); - const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, harvesting, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; const {ownerAccountID = -1, managerID = -1} = report; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; @@ -101,7 +101,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { id: policyID, owner: currentUserEmail, submitsTo: currentUserAccountID, - isHarvestingEnabled: false, + harvesting: { + enabled: false, + }, // Required props name: 'Policy', role: 'admin', @@ -110,7 +112,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -134,7 +138,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -158,7 +164,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -182,7 +190,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: 2, }).then(() => { @@ -207,7 +217,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, }).then(() => { @@ -233,7 +245,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, }).then(() => { @@ -258,7 +272,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -289,7 +305,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 6c72558e5df3..04246c1c438a 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -256,7 +256,9 @@ function getFakePolicy(id = 1, name = 'Workspace-Test-001') { lastModified: 1697323926777105, autoReporting: true, autoReportingFrequency: 'immediate', - isHarvestingEnabled: true, + harvesting: { + enabled: true, + }, autoReportingOffset: 1, isPreventSelfApprovalEnabled: true, submitsTo: 123456, diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index 8547c171c7a7..4223c7e41941 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -11,7 +11,9 @@ export default function createRandomPolicy(index: number): Policy { autoReporting: randBoolean(), isPolicyExpenseChatEnabled: randBoolean(), autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)), - isHarvestingEnabled: randBoolean(), + harvesting: { + enabled: randBoolean(), + }, autoReportingOffset: 1, isPreventSelfApprovalEnabled: randBoolean(), submitsTo: index, From 2e6879c362f58bd757507dd58392ab68dfd1e52a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 10:33:39 +0100 Subject: [PATCH 23/35] Revert "integrate new harvesting field" This reverts commit 9a36327265853796e28ebb4caaa3c6c177a9d8fa. --- src/components/MoneyReportHeader.tsx | 4 +-- .../ReportActionItem/ReportPreview.tsx | 4 +-- src/libs/NextStepUtils.ts | 4 +-- src/libs/actions/IOU.js | 2 +- tests/unit/NextStepUtilsTest.ts | 36 +++++-------------- tests/utils/LHNTestUtils.js | 4 +-- tests/utils/collections/policies.ts | 4 +-- 7 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index c2e6ff341416..4b4e3915f969 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -86,8 +86,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !(policy.harvesting?.enabled ?? policy.isHarvestingEnabled), - [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled, policy.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !policy.isHarvestingEnabled, + [chatReport?.isOwnPolicyExpenseChat, policy.isHarvestingEnabled], ); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 52e9d94eaefd..b2fece085f57 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -159,8 +159,8 @@ function ReportPreview({ // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled), - [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled, policy?.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !policy?.isHarvestingEnabled, + [chatReport?.isOwnPolicyExpenseChat, policy?.isHarvestingEnabled], ); const getDisplayAmount = (): string => { diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 85986e57057e..dec6acaecec2 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -62,7 +62,7 @@ type BuildNextStepParameters = { */ function buildNextStep(report: Report, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const policy = ReportUtils.getPolicy(report.policyID ?? ''); - const {submitsTo, harvesting, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; const {ownerAccountID = -1, managerID = -1} = report; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; @@ -101,7 +101,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf { id: policyID, owner: currentUserEmail, submitsTo: currentUserAccountID, - harvesting: { - enabled: false, - }, + isHarvestingEnabled: false, // Required props name: 'Policy', role: 'admin', @@ -112,9 +110,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -138,9 +134,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -164,9 +158,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -190,9 +182,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: 2, }).then(() => { @@ -217,9 +207,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, }).then(() => { @@ -245,9 +233,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, }).then(() => { @@ -272,9 +258,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -305,9 +289,7 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 04246c1c438a..6c72558e5df3 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -256,9 +256,7 @@ function getFakePolicy(id = 1, name = 'Workspace-Test-001') { lastModified: 1697323926777105, autoReporting: true, autoReportingFrequency: 'immediate', - harvesting: { - enabled: true, - }, + isHarvestingEnabled: true, autoReportingOffset: 1, isPreventSelfApprovalEnabled: true, submitsTo: 123456, diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index 4223c7e41941..8547c171c7a7 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -11,9 +11,7 @@ export default function createRandomPolicy(index: number): Policy { autoReporting: randBoolean(), isPolicyExpenseChatEnabled: randBoolean(), autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)), - harvesting: { - enabled: randBoolean(), - }, + isHarvestingEnabled: randBoolean(), autoReportingOffset: 1, isPreventSelfApprovalEnabled: randBoolean(), submitsTo: index, From 45a4f7b006e622f8baf2785b5d108292fd5d6a02 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 10:33:45 +0100 Subject: [PATCH 24/35] Revert "clarify comments" This reverts commit 821ee65b43a8c2add86e75184b848c3403c03362. --- src/types/onyx/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 719e0ba1fb9d..ffa619291516 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -96,7 +96,7 @@ type Policy = { enabled: boolean; }; - /** Whether the self approval or submitting is enabled */ + /** Whether the scheduled submit is enabled */ isPreventSelfApprovalEnabled?: boolean; /** When the monthly scheduled submit should happen */ From ad414b54f2576e0512a63d4e9f7c34cea74f5f5e Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 10:33:49 +0100 Subject: [PATCH 25/35] Revert "add new harvesting field of policy" This reverts commit cf9fcfbdc671b8c3d9155a56747db761900bc244. --- src/types/onyx/Policy.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index ffa619291516..eca7e9d1ee06 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -88,13 +88,8 @@ type Policy = { /** The scheduled submit frequency set up on the this policy */ autoReportingFrequency?: ValueOf; - /** @deprecated Whether the scheduled submit is enabled */ - isHarvestingEnabled?: boolean; - /** Whether the scheduled submit is enabled */ - harvesting?: { - enabled: boolean; - }; + isHarvestingEnabled?: boolean; /** Whether the scheduled submit is enabled */ isPreventSelfApprovalEnabled?: boolean; From 5b639828bbc90403415a2776f881d284419b724c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Wed, 31 Jan 2024 14:45:11 +0100 Subject: [PATCH 26/35] rename a var --- src/libs/NextStepUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index dec6acaecec2..5ba254779475 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -68,7 +68,7 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf Date: Wed, 31 Jan 2024 15:17:44 +0100 Subject: [PATCH 27/35] improve object keys --- src/libs/NextStepUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 5ba254779475..ed0e13b4f192 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -6,6 +6,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, ReportNextStep} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportNextStep'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import DateUtils from './DateUtils'; import EmailUtils from './EmailUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; @@ -127,12 +128,14 @@ function buildNextStep(report: Report, predictedNextStatus: ValueOf, string> = { [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE]: 'later today', [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY]: 'on Sunday', [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY]: 'on the 1st and 16th of each month', [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY]: formattedDate ? `on the ${formattedDate} of each month` : '', [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP]: 'at the end of your trip', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT]: '', + [CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL]: '', }; if (harvestingSuffixes[autoReportingFrequency]) { From 77f0019c1ecac5681ec96688f61ce9a457840f5a Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 6 Feb 2024 11:56:53 +0100 Subject: [PATCH 28/35] remove redundant prop --- tests/unit/NextStepUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index bec69d08a745..60b51e90cabc 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -28,7 +28,6 @@ describe('libs/NextStepUtils', () => { role: 'admin', type: 'team', outputCurrency: CONST.CURRENCY.USD, - areChatRoomsEnabled: true, isPolicyExpenseChatEnabled: true, }; const optimisticNextStep: ReportNextStep = { From 6aab7ff6173005b65e4f432ed427e8353b389df7 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 6 Feb 2024 11:57:05 +0100 Subject: [PATCH 29/35] use long name for manager --- src/libs/NextStepUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 3ef21a55b6a7..3becffb46a28 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -70,7 +70,7 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO const isManager = currentUserAccountID === managerID; const isSelfApproval = currentUserAccountID === submitsTo; const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? ''; - const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo, true) ?? ''; + const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo) ?? ''; const type: ReportNextStep['type'] = 'neutral'; let optimisticNextStep: ReportNextStep | null; From bebeea94afe9f3d72471eaae9e203c0941a023cf Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 8 Feb 2024 10:48:20 +0100 Subject: [PATCH 30/35] remove isHarvestingEnabled ans use another default value for accountID --- src/components/MoneyReportHeader.tsx | 4 ++-- src/components/ReportActionItem/ReportPreview.tsx | 4 ++-- src/libs/NextStepUtils.ts | 8 ++++---- src/libs/actions/IOU.ts | 2 +- src/types/onyx/Policy.ts | 3 --- tests/unit/NextStepUtilsTest.ts | 9 --------- 6 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 3f5a3c50f6cc..78e9357afd65 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -86,8 +86,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !(policy.harvesting?.enabled ?? policy.isHarvestingEnabled), - [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled, policy.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !policy.harvesting?.enabled, + [chatReport?.isOwnPolicyExpenseChat, policy.harvesting?.enabled], ); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f12c6d9bea31..1a5f51f192f6 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -158,8 +158,8 @@ function ReportPreview({ // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( - () => chatReport?.isOwnPolicyExpenseChat && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled), - [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled, policy?.isHarvestingEnabled], + () => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled, + [chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled], ); const getDisplayAmount = (): string => { diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 3becffb46a28..e29d6c24c3a1 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -13,7 +13,7 @@ import EmailUtils from './EmailUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as ReportUtils from './ReportUtils'; -let currentUserAccountID: number | undefined; +let currentUserAccountID = -1; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { @@ -21,7 +21,7 @@ Onyx.connect({ return; } - currentUserAccountID = value.accountID; + currentUserAccountID = value?.accountID ?? -1; }, }); @@ -65,7 +65,7 @@ type BuildNextStepParameters = { function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const {policyID = '', ownerAccountID = -1, managerID = -1} = report; const policy = ReportUtils.getPolicy(policyID); - const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; const isSelfApproval = currentUserAccountID === submitsTo; @@ -103,7 +103,7 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO }; // Scheduled submit enabled - if (isHarvestingEnabled && autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) { + if (autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) { optimisticNextStep.message = [ { text: 'These expenses are scheduled to ', diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8ff9815e6ddd..94884eecc118 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -758,7 +758,7 @@ function getMoneyRequestInformation( isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy ?? null); // If the scheduled submit is turned off on the policy, user needs to manually submit the report which is indicated by GBR in LHN - needsToBeManuallySubmitted = isFromPaidPolicy && !(policy?.harvesting?.enabled ?? policy?.isHarvestingEnabled); + needsToBeManuallySubmitted = isFromPaidPolicy && !policy?.harvesting?.enabled; // If the linked expense report on paid policy is not draft, we need to create a new draft expense report if (iouReport && isFromPaidPolicy && !ReportUtils.isDraftExpenseReport(iouReport)) { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 784cd546a961..6b339d6e3ed4 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -85,9 +85,6 @@ type Policy = { /** The scheduled submit frequency set up on this policy */ autoReportingFrequency?: ValueOf; - /** @deprecated Whether the scheduled submit is enabled */ - isHarvestingEnabled?: boolean; - /** Whether the scheduled submit is enabled */ harvesting?: { enabled: boolean; diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 60b51e90cabc..325bf02030b6 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -22,7 +22,6 @@ describe('libs/NextStepUtils', () => { id: policyID, owner: currentUserEmail, submitsTo: currentUserAccountID, - isHarvestingEnabled: false, // Required props name: 'Policy', role: 'admin', @@ -109,7 +108,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -133,7 +131,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -157,7 +154,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -181,7 +177,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: 2, }).then(() => { @@ -206,7 +201,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, }).then(() => { @@ -232,7 +226,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, }).then(() => { @@ -257,7 +250,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -288,7 +280,6 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - isHarvestingEnabled: true, autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); From bbe1426c4d167afb7a0fd336868f4a813b669861 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 8 Feb 2024 13:06:10 +0100 Subject: [PATCH 31/35] integrate harvesting value --- src/libs/NextStepUtils.ts | 4 ++-- tests/unit/NextStepUtilsTest.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index e29d6c24c3a1..5922552150f6 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -65,7 +65,7 @@ type BuildNextStepParameters = { function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { const {policyID = '', ownerAccountID = -1, managerID = -1} = report; const policy = ReportUtils.getPolicy(policyID); - const {submitsTo, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; + const {submitsTo, harvesting, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; const isOwner = currentUserAccountID === ownerAccountID; const isManager = currentUserAccountID === managerID; const isSelfApproval = currentUserAccountID === submitsTo; @@ -103,7 +103,7 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO }; // Scheduled submit enabled - if (autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) { + if (harvesting?.enabled && autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) { optimisticNextStep.message = [ { text: 'These expenses are scheduled to ', diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index 325bf02030b6..568c641d2ac5 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -22,6 +22,9 @@ describe('libs/NextStepUtils', () => { id: policyID, owner: currentUserEmail, submitsTo: currentUserAccountID, + harvesting: { + enabled: false, + }, // Required props name: 'Policy', role: 'admin', @@ -109,6 +112,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -132,6 +138,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -155,6 +164,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.SEMI_MONTHLY, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -179,6 +191,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: 2, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -203,6 +218,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -228,6 +246,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY, autoReportingOffset: CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -251,6 +272,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.TRIP, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); @@ -281,6 +305,9 @@ describe('libs/NextStepUtils', () => { return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, + harvesting: { + enabled: true, + }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); From 655d9deeaa07a90b480b75401ccf447af2f3c229 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 9 Feb 2024 13:48:54 +0100 Subject: [PATCH 32/35] use merge method --- src/libs/actions/IOU.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4be5314fa0d6..09ca2a15a483 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -504,7 +504,7 @@ function buildOnyxDataForMoneyRequest( if (!isEmptyObject(optimisticNextStep)) { optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, value: optimisticNextStep, }); @@ -3188,7 +3188,7 @@ function getPayMoneyRequestParams(chatReport: OnyxTypes.Report, iouReport: OnyxT value: {[iouReport.policyID ?? '']: paymentMethodType}, }, { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${iouReport.reportID}`, value: optimisticNextStep, }, @@ -3331,7 +3331,7 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) { }, }; const optimisticNextStepData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, value: optimisticNextStep, }; @@ -3405,7 +3405,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { }, }, { - onyxMethod: Onyx.METHOD.SET, + onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`, value: optimisticNextStep, }, From e70ec81d5af44142a3b978bcdd45a1a5bd399e03 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 9 Feb 2024 13:49:24 +0100 Subject: [PATCH 33/35] check if a report created as submitted --- src/libs/actions/IOU.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 09ca2a15a483..05fd97f494b7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -873,7 +873,8 @@ function getMoneyRequestInformation( } : {}; - const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, CONST.REPORT.STATUS_NUM.OPEN); + const optimisticNextStep = NextStepUtils.buildNextStep(iouReport, needsToBeManuallySubmitted ? CONST.REPORT.STATUS_NUM.OPEN : CONST.REPORT.STATUS_NUM.SUBMITTED); + // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( chatReport, From 12a28c12821a9c76db3038c64241e4f53101ceaf Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 9 Feb 2024 13:53:23 +0100 Subject: [PATCH 34/35] check if a report is expense --- src/libs/NextStepUtils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 5922552150f6..d75095315bd5 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -63,6 +63,10 @@ type BuildNextStepParameters = { * @returns nextStep */ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueOf, {isPaidWithWallet}: BuildNextStepParameters = {}): ReportNextStep | null { + if (!ReportUtils.isExpenseReport(report)) { + return null; + } + const {policyID = '', ownerAccountID = -1, managerID = -1} = report; const policy = ReportUtils.getPolicy(policyID); const {submitsTo, harvesting, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; From b9ac4a3438739ff0f29853f5e31ade1c8ddbfb02 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Fri, 9 Feb 2024 13:58:47 +0100 Subject: [PATCH 35/35] flip a condition once submit --- src/libs/NextStepUtils.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index d75095315bd5..3b42382b10f9 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -188,16 +188,20 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO case CONST.REPORT.STATUS_NUM.SUBMITTED: { const verb = isManager ? 'review' : 'approve'; - // Self review & another reviewer + // Another owner optimisticNextStep = { type, title: 'Next Steps:', message: [ { - text: 'Waiting for ', + text: ownerLogin, + type: 'strong', }, { - text: managerDisplayName, + text: ' is waiting for ', + }, + { + text: 'you', type: 'strong', }, { @@ -208,23 +212,19 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO type: 'strong', }, { - text: ' %expenses.', + text: ' these %expenses.', }, ], }; - // Another owner - if (!isOwner) { + // Self review & another reviewer + if (isOwner) { optimisticNextStep.message = [ { - text: ownerLogin, - type: 'strong', - }, - { - text: ' is waiting for ', + text: 'Waiting for ', }, { - text: 'you', + text: managerDisplayName, type: 'strong', }, { @@ -235,7 +235,7 @@ function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueO type: 'strong', }, { - text: ' these %expenses.', + text: ' %expenses.', }, ]; }