Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Migrate the next steps to client side #34450

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0230d23
add a new report flag
rezkiy37 Jan 12, 2024
c2f14f8
draft implementation of buildNextStep
rezkiy37 Jan 12, 2024
c2c25b2
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 15, 2024
04d2c44
cover all basic scenarios
rezkiy37 Jan 16, 2024
24552b0
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 17, 2024
a32e22e
rename const
rezkiy37 Jan 17, 2024
bbe860d
clear todo
rezkiy37 Jan 17, 2024
9387b10
integrate timestamp handling
rezkiy37 Jan 17, 2024
090d8ff
add a case with random for manual approval
rezkiy37 Jan 17, 2024
f77cea1
add comment
rezkiy37 Jan 17, 2024
58baa0a
integrate predictedNextStatus argument
rezkiy37 Jan 18, 2024
13d2d7f
replace review with approve once submit
rezkiy37 Jan 18, 2024
81616da
handle manager case
rezkiy37 Jan 18, 2024
003d387
improve adding the random for manual approval message
rezkiy37 Jan 18, 2024
85731fb
integrate optimistic next step generation
rezkiy37 Jan 18, 2024
9942130
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 18, 2024
fa2c8fb
minor improvements
rezkiy37 Jan 19, 2024
dfec8f6
remove log
rezkiy37 Jan 19, 2024
59a036d
improve and fix tests
rezkiy37 Jan 19, 2024
ba8e6ff
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 22, 2024
9ea9a31
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 24, 2024
8481f66
integrate last business day calculation
rezkiy37 Jan 24, 2024
ae300a4
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 25, 2024
7467e2c
prettify codebase
rezkiy37 Jan 25, 2024
38bfb70
remove one case for now
rezkiy37 Jan 25, 2024
8703eb1
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 26, 2024
f2041bf
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 28, 2024
dba0e00
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 30, 2024
cf9fcfb
add new harvesting field of policy
rezkiy37 Jan 30, 2024
821ee65
clarify comments
rezkiy37 Jan 30, 2024
9a36327
integrate new harvesting field
rezkiy37 Jan 30, 2024
2e6879c
Revert "integrate new harvesting field"
rezkiy37 Jan 31, 2024
45a4f7b
Revert "clarify comments"
rezkiy37 Jan 31, 2024
ad414b5
Revert "add new harvesting field of policy"
rezkiy37 Jan 31, 2024
5b63982
rename a var
rezkiy37 Jan 31, 2024
2b56951
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Jan 31, 2024
81cca96
improve object keys
rezkiy37 Jan 31, 2024
d610054
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 5, 2024
f2e4bc3
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 6, 2024
77f0019
remove redundant prop
rezkiy37 Feb 6, 2024
6aab7ff
use long name for manager
rezkiy37 Feb 6, 2024
9e3ad05
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 6, 2024
ea00b24
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 7, 2024
afe058a
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 8, 2024
bebeea9
remove isHarvestingEnabled ans use another default value for accountID
rezkiy37 Feb 8, 2024
bbe1426
integrate harvesting value
rezkiy37 Feb 8, 2024
4e0bb21
Merge branch 'main' of https://github.com/rezkiy37/Expensify into fea…
rezkiy37 Feb 9, 2024
655d9de
use merge method
rezkiy37 Feb 9, 2024
e70ec81
check if a report created as submitted
rezkiy37 Feb 9, 2024
12a28c1
check if a report is expense
rezkiy37 Feb 9, 2024
b9ac4a3
flip a condition once submit
rezkiy37 Feb 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,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',
Expand Down
4 changes: 2 additions & 2 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,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)];
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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?.harvesting?.enabled,
[chatReport?.isOwnPolicyExpenseChat, policy?.harvesting?.enabled],
);

const getDisplayAmount = (): string => {
Expand Down
296 changes: 294 additions & 2 deletions src/libs/NextStepUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
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';
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 type {EmptyObject} from '@src/types/utils/EmptyObject';
import DateUtils from './DateUtils';
import EmailUtils from './EmailUtils';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as ReportUtils from './ReportUtils';

let currentUserAccountID = -1;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (value) => {
if (!value) {
return;
}

currentUserAccountID = value?.accountID ?? -1;
},
});

function parseMessage(messages: Message[] | undefined) {
let nextStepHTML = '';
Expand All @@ -27,5 +50,274 @@ function parseMessage(messages: Message[] | undefined) {
return `<next-step>${formattedHtml}</next-step>`;
}

// eslint-disable-next-line import/prefer-default-export
export {parseMessage};
type BuildNextStepParameters = {
isPaidWithWallet?: boolean;
};

/**
* Generates an optimistic nextStep based on a current report status and other properties.
*
* @param report
* @param predictedNextStatus - a next expected status of the report
* @param parameters.isPaidWithWallet - Whether a report has been paid with the wallet or outside of Expensify
* @returns nextStep
*/
function buildNextStep(report: Report | EmptyObject, predictedNextStatus: ValueOf<typeof CONST.REPORT.STATUS_NUM>, {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;
const isOwner = currentUserAccountID === ownerAccountID;
const isManager = currentUserAccountID === managerID;
const isSelfApproval = currentUserAccountID === submitsTo;
const ownerLogin = PersonalDetailsUtils.getLoginsByAccountIDs([ownerAccountID])[0] ?? '';
const managerDisplayName = isSelfApproval ? 'you' : ReportUtils.getDisplayNameForParticipant(submitsTo) ?? '';
const type: ReportNextStep['type'] = 'neutral';
let optimisticNextStep: ReportNextStep | null;

switch (predictedNextStatus) {
// Generates an optimistic nextStep once a report has been opened
case CONST.REPORT.STATUS_NUM.OPEN:
// Self review
optimisticNextStep = {
type,
title: 'Next Steps:',
message: [
{
text: 'Waiting for ',
},
{
text: 'you',
type: 'strong',
},
{
text: ' to ',
},
{
text: 'submit',
type: 'strong',
},
{
text: ' these expenses.',
},
],
};

// Scheduled submit enabled
if (harvesting?.enabled && autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL) {
optimisticNextStep.message = [
{
text: 'These expenses are scheduled to ',
},
];
let harvestingSuffix = '';

if (autoReportingFrequency) {
const currentDate = new Date();
let autoSubmissionDate: Date | null = null;
let formattedDate = '';

if (autoReportingOffset === CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_DAY_OF_MONTH) {
autoSubmissionDate = lastDayOfMonth(currentDate);
} else if (autoReportingOffset === CONST.POLICY.AUTO_REPORTING_OFFSET.LAST_BUSINESS_DAY_OF_MONTH) {
const lastBusinessDayOfMonth = DateUtils.getLastBusinessDayOfMonth(currentDate);
autoSubmissionDate = setDate(currentDate, lastBusinessDayOfMonth);
} else if (autoReportingOffset !== undefined) {
autoSubmissionDate = setDate(currentDate, autoReportingOffset);
}

if (autoSubmissionDate) {
formattedDate = format(autoSubmissionDate, CONST.DATE.ORDINAL_DAY_OF_MONTH);
}

const harvestingSuffixes: Record<DeepValueOf<typeof CONST.POLICY.AUTO_REPORTING_FREQUENCIES>, 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]) {
harvestingSuffix = ` ${harvestingSuffixes[autoReportingFrequency]}`;
}
}

optimisticNextStep.message.push(
{
text: `automatically submit${harvestingSuffix}!`,
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_NUM.SUBMITTED: {
const verb = isManager ? 'review' : 'approve';

// Another owner
optimisticNextStep = {
type,
title: 'Next Steps:',
message: [
{
text: ownerLogin,
type: 'strong',
},
{
text: ' is waiting for ',
},
{
text: 'you',
type: 'strong',
},
{
text: ' to ',
},
{
text: verb,
type: 'strong',
},
{
text: ' these %expenses.',
},
],
};

// Self review & another reviewer
if (isOwner) {
optimisticNextStep.message = [
{
mountiny marked this conversation as resolved.
Show resolved Hide resolved
text: 'Waiting for ',
},
{
text: managerDisplayName,
type: 'strong',
},
{
text: ' to ',
},
{
text: verb,
type: 'strong',
},
{
text: ' %expenses.',
},
];
}

break;
}

// Generates an optimistic nextStep once a report has been approved
case CONST.REPORT.STATUS_NUM.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!',
},
];
}
Comment on lines +273 to +281
Copy link
Contributor

Choose a reason for hiding this comment

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

An approver could also be an admin who still needs to reimburse I think, how would this work in this case? It would show them they are done but they still need to pay it, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An approver could also be an admin...
Yes, could be. I think it is always an admin, it is not?

Based on a message that we get from the backend, it should be "No further action....". So, looks like it works properly.

Approved.mov

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok lets go with this and see if some issues will be reported!


break;

// Generates an optimistic nextStep once a report has been paid
case CONST.REPORT.STATUS_NUM.REIMBURSED:
// Paid with wallet
optimisticNextStep = {
type,
title: 'Finished!',
message: [
{
text: 'You',
type: 'strong',
},
{
text: ' have marked these expenses as ',
},
{
text: 'paid',
type: 'strong',
},
],
};

// Paid outside of Expensify
if (isPaidWithWallet === false) {
optimisticNextStep.message?.push({text: ' outside of Expensify'});
}

optimisticNextStep.message?.push({text: '.'});

break;

// Resets a nextStep
default:
optimisticNextStep = null;
}

return optimisticNextStep;
}

export {parseMessage, buildNextStep};
Loading
Loading