diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index 0ac2878b6857..59f46b429698 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -79,7 +79,8 @@ function buildNextStep(report: OnyxEntry, predictedNextStatus: ValueOf): boolean { return policy?.type === CONST.POLICY.TYPE.FREE || (policy?.autoReporting === true && policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT); } +/** + * This gets a "corrected" value for autoReportingFrequency. The purpose of this function is to encapsulate some logic around the "immediate" frequency. + * + * - "immediate" is actually not immediate. For that you want "instant". + * - (immediate && harvesting.enabled) === daily + * - (immediate && !harvesting.enabled) === manual + * + * Note that "daily" and "manual" only exist as options for the API, not in the database or Onyx. + */ +function getCorrectedAutoReportingFrequency(policy: OnyxInputOrEntry): ValueOf | undefined { + if (policy?.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE) { + return policy?.autoReportingFrequency; + } + + if (policy?.harvesting?.enabled) { + // This is actually not really "immediate". It's "daily". Surprise! + return CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE; + } + + // "manual" is really just "immediate" (aka "daily") with harvesting disabled + return CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL; +} + /** * Checks if policy's approval mode is "optional", a.k.a. "Submit & Close" */ @@ -793,6 +816,7 @@ export { isDeletedPolicyEmployee, isFreeGroupPolicy, isInstantSubmitEnabled, + getCorrectedAutoReportingFrequency, isPaidGroupPolicy, isPendingDeletePolicy, isPolicyAdmin, diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 561c98362e27..e6d2e35dd8c1 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -343,13 +343,32 @@ function deleteWorkspace(policyID: string, policyName: string) { function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf) { const policy = getPolicy(policyID); + const wasPolicyOnManualReporting = PolicyUtils.getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL; + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { - autoReportingFrequency: frequency, + // Recall that the "daily" and "manual" frequencies don't actually exist in Onyx or the DB (see PolicyUtils.getCorrectedAutoReportingFrequency) + autoReportingFrequency: frequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL ? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE : frequency, pendingFields: {autoReportingFrequency: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + + // To set the frequency to "manual", we really must set it to "immediate" with harvesting disabled + ...(frequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL && { + harvesting: { + enabled: false, + }, + }), + + // If the policy was on manual reporting before, and now will be auto-reported, + // then we must re-enable harvesting + ...(wasPolicyOnManualReporting && + frequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL && { + harvesting: { + enabled: true, + }, + }), }, }, ]; @@ -360,6 +379,7 @@ function setWorkspaceAutoReportingFrequency(policyID: string, frequency: ValueOf key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { autoReportingFrequency: policy?.autoReportingFrequency ?? null, + harvesting: policy?.harvesting ?? null, pendingFields: {autoReportingFrequency: null}, errorFields: {autoReportingFrequency: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workflowsDelayedSubmissionPage.autoReportingFrequencyErrorMessage')}, }, @@ -3066,8 +3086,11 @@ function upgradeToCorporate(policyID: string, featureName: string) { glCodes: true, ...(PolicyUtils.isInstantSubmitEnabled(policy) && { autoReporting: true, - autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, }), + harvesting: { + enabled: false, + }, }, }, ]; @@ -3095,6 +3118,7 @@ function upgradeToCorporate(policyID: string, featureName: string) { glCodes: policy?.glCodes ?? null, autoReporting: policy?.autoReporting ?? null, autoReportingFrequency: policy?.autoReportingFrequency ?? null, + harvesting: policy?.harvesting ?? null, }, }, ]; diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx index 6f6e5fcff15d..704f1def7ad3 100644 --- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx @@ -48,9 +48,11 @@ const getAutoReportingFrequencyDisplayNames = (locale: Locale): AutoReportingFre }); function WorkspaceAutoReportingFrequencyPage({policy, route}: WorkspaceAutoReportingFrequencyPageProps) { + const autoReportingFrequency = PolicyUtils.getCorrectedAutoReportingFrequency(policy); + const {translate, preferredLocale, toLocaleOrdinal} = useLocalize(); const styles = useThemeStyles(); - const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(policy?.autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); + const [isMonthlyFrequency, setIsMonthlyFrequency] = useState(autoReportingFrequency === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY); const onSelectAutoReportingFrequency = (item: WorkspaceAutoReportingFrequencyPageItem) => { Policy.setWorkspaceAutoReportingFrequency(policy?.id ?? '-1', item.keyForList as AutoReportingFrequencyKey); @@ -97,16 +99,12 @@ function WorkspaceAutoReportingFrequencyPage({policy, route}: WorkspaceAutoRepor ); - const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageItem[] = Object.keys(getAutoReportingFrequencyDisplayNames(preferredLocale)).map((frequencyKey) => { - const isSelected = policy?.autoReportingFrequency === frequencyKey; - - return { - text: getAutoReportingFrequencyDisplayNames(preferredLocale)[frequencyKey as AutoReportingFrequencyKey] || '', - keyForList: frequencyKey, - isSelected, - footerContent: isMonthlyFrequency && frequencyKey === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY ? monthlyFrequencyDetails() : null, - }; - }); + const autoReportingFrequencyItems: WorkspaceAutoReportingFrequencyPageItem[] = Object.keys(getAutoReportingFrequencyDisplayNames(preferredLocale)).map((frequencyKey) => ({ + text: getAutoReportingFrequencyDisplayNames(preferredLocale)[frequencyKey as AutoReportingFrequencyKey] || '', + keyForList: frequencyKey, + isSelected: frequencyKey === autoReportingFrequency, + footerContent: isMonthlyFrequency && frequencyKey === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MONTHLY ? monthlyFrequencyDetails() : null, + })); return ( diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 372044d4bb12..11c2d370fc3d 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -119,7 +119,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr // Instant submit is the equivalent of delayed submissions being turned off, so we show the feature as disabled if the frequency is instant description={ getAutoReportingFrequencyDisplayNames(preferredLocale)[ - (policy?.autoReportingFrequency as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY + (PolicyUtils.getCorrectedAutoReportingFrequency(policy) as AutoReportingFrequencyKey) ?? CONST.POLICY.AUTO_REPORTING_FREQUENCIES.WEEKLY ] } shouldShowRightIcon @@ -128,7 +128,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr brickRoadIndicator={hasDelayedSubmissionError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} /> ), - isActive: (policy?.harvesting?.enabled && policy.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT && !hasDelayedSubmissionError) ?? false, + isActive: (policy?.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT && !hasDelayedSubmissionError) ?? false, pendingAction: policy?.pendingFields?.autoReporting, errors: ErrorUtils.getLatestErrorField(policy ?? {}, CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING), onCloseError: () => Policy.clearPolicyErrorField(policy?.id ?? '-1', CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING), diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 014901c6982a..ba26a36638e4 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1355,8 +1355,12 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the auto reporting is enabled */ autoReporting?: boolean; - /** The scheduled submit frequency set up on this policy */ - autoReportingFrequency?: ValueOf; + /** + * The scheduled submit frequency set up on this policy. + * Note that manual does not exist in the DB and thus should not exist in Onyx, only as a param for the API. + * "manual" really means "immediate" (aka "daily") && harvesting.enabled === false + */ + autoReportingFrequency?: Exclude, typeof CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL>; /** Scheduled submit data */ harvesting?: { diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts index f92bc7a9dfee..fce66fbb1249 100644 --- a/tests/unit/NextStepUtilsTest.ts +++ b/tests/unit/NextStepUtilsTest.ts @@ -311,9 +311,9 @@ describe('libs/NextStepUtils', () => { ]; return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, harvesting: { - enabled: true, + enabled: false, }, }).then(() => { const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN); diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts index d34a2f6474b5..47bf996afb7e 100644 --- a/tests/utils/collections/policies.ts +++ b/tests/utils/collections/policies.ts @@ -1,4 +1,5 @@ import {rand, randAvatar, randBoolean, randCurrencyCode, randEmail, randPastDate, randWord} from '@ngneat/falso'; +import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import type {Policy} from '@src/types/onyx'; @@ -9,7 +10,12 @@ export default function createRandomPolicy(index: number): Policy { type: rand(Object.values(CONST.POLICY.TYPE)), autoReporting: randBoolean(), isPolicyExpenseChatEnabled: randBoolean(), - autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)), + autoReportingFrequency: rand( + Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES).filter( + (frequency): frequency is Exclude, typeof CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL> => + frequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL, + ), + ), harvesting: { enabled: randBoolean(), },