-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Migrate the next steps to client side #34450
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
Changes from 43 commits
0230d23
c2f14f8
c2c25b2
04d2c44
24552b0
a32e22e
bbe860d
9387b10
090d8ff
f77cea1
58baa0a
13d2d7f
81616da
003d387
85731fb
9942130
fa2c8fb
dfec8f6
59a036d
ba8e6ff
9ea9a31
8481f66
ae300a4
7467e2c
38bfb70
8703eb1
f2041bf
dba0e00
cf9fcfb
821ee65
9a36327
2e6879c
45a4f7b
ad414b5
5b63982
2b56951
81cca96
d610054
f2e4bc3
77f0019
6aab7ff
9e3ad05
ea00b24
afe058a
bebeea9
bbe1426
4e0bb21
655d9de
e70ec81
12a28c1
b9ac4a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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: number | undefined; | ||
Onyx.connect({ | ||
key: ONYXKEYS.SESSION, | ||
callback: (value) => { | ||
if (!value) { | ||
return; | ||
} | ||
|
||
currentUserAccountID = value.accountID; | ||
}, | ||
}); | ||
|
||
function parseMessage(messages: Message[] | undefined) { | ||
let nextStepHTML = ''; | ||
|
@@ -27,5 +50,270 @@ 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 { | ||
const {policyID = '', ownerAccountID = -1, managerID = -1} = report; | ||
mountiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const policy = ReportUtils.getPolicy(policyID); | ||
const {submitsTo, isHarvestingEnabled, isPreventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy; | ||
mountiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 (isHarvestingEnabled && 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'; | ||
|
||
// Self review & another reviewer | ||
optimisticNextStep = { | ||
type, | ||
title: 'Next Steps:', | ||
message: [ | ||
{ | ||
text: 'Waiting for ', | ||
}, | ||
{ | ||
text: managerDisplayName, | ||
type: 'strong', | ||
}, | ||
{ | ||
text: ' to ', | ||
}, | ||
{ | ||
text: verb, | ||
type: 'strong', | ||
}, | ||
{ | ||
text: ' %expenses.', | ||
}, | ||
], | ||
}; | ||
|
||
// Another owner | ||
if (!isOwner) { | ||
optimisticNextStep.message = [ | ||
{ | ||
mountiny marked this conversation as resolved.
Show resolved
Hide resolved
|
||
text: ownerLogin, | ||
type: 'strong', | ||
}, | ||
{ | ||
text: ' is waiting for ', | ||
}, | ||
{ | ||
text: 'you', | ||
type: 'strong', | ||
}, | ||
{ | ||
text: ' to ', | ||
}, | ||
{ | ||
text: verb, | ||
type: 'strong', | ||
}, | ||
{ | ||
text: ' these %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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Based on a message that we get from the backend, it should be "No further action....". So, looks like it works properly. Approved.movThere was a problem hiding this comment. Choose a reason for hiding this commentThe 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}; |
Uh oh!
There was an error while loading. Please reload this page.