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

Update logic for categorising expenses #40840

Merged
merged 16 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
21 changes: 21 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,27 @@ const CONST = {
DISABLE: 'disable',
ENABLE: 'enable',
},
DEFAULT_CATEGORIES: [
'Advertising',
'Benefits',
'Car',
'Equipment',
'Fees',
'Home Office',
'Insurance',
'Interest',
'Labor',
'Maintenance',
'Materials',
'Meals and Entertainment',
'Office Supplies',
'Other',
'Professional Services',
'Rent',
'Taxes',
'Travel',
'Utilities',
],
OWNERSHIP_ERRORS: {
NO_BILLING_CARD: 'noBillingCard',
AMOUNT_OWED: 'amountOwed',
Expand Down
18 changes: 17 additions & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
import * as IOU from './actions/IOU';
import * as PolicyActions from './actions/Policy';
import * as store from './actions/ReimbursementAccount/store';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
Expand Down Expand Up @@ -6116,7 +6117,22 @@ function createDraftTransactionAndNavigateToParticipantSelector(transactionID: s
mccGroup,
} as Transaction);

Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SUBMIT, transactionID, reportID, undefined, actionName));
Copy link
Contributor

Choose a reason for hiding this comment

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

@allroundexperts You forgot to keep this route for the Submit it to someone flow. And this is a blocker for this PR.

if (allPolicies && Object.values(allPolicies).filter((p) => p?.type !== 'personal').length > 0) {
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_PARTICIPANTS.getRoute(CONST.IOU.TYPE.SUBMIT, transactionID, reportID, undefined, actionName));
return;
}

const {expenseChatReportID} = PolicyActions.createWorkspace();
const isCategorizing = actionName === CONST.IOU.ACTION.CATEGORIZE;

IOU.setMoneyRequestTag(transactionID, '');
IOU.setMoneyRequestCategory(transactionID, '');
const iouConfirmationPageRoute = ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, expenseChatReportID);
if (isCategorizing) {
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(actionName, CONST.IOU.TYPE.SUBMIT, transactionID, expenseChatReportID, iouConfirmationPageRoute));
} else {
Navigation.navigate(iouConfirmationPageRoute);
}
}

/**
Expand Down
122 changes: 80 additions & 42 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,69 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol
Onyx.update(optimisticData);
}

function buildOptimisticPolicyCategories(policyID: string, categories: readonly string[]) {
const optimisticCategoryMap = categories.reduce(
(acc, category) => ({
...acc,
[category]: {
name: category,
enabled: true,
errors: null,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
}),
{},
);

const successCategoryMap = categories.reduce(
(acc, category) => ({
...acc,
[category]: {
errors: null,
pendingAction: null,
},
}),
{},
);

const failureCategoryMap = categories.reduce(
(acc, category) => ({
...acc,
[category]: {
errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.createFailureMessage'),
pendingAction: null,
},
}),
{},
);

const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: optimisticCategoryMap,
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: successCategoryMap,
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: failureCategoryMap,
},
],
};

return onyxData;
}

/**
* Optimistically creates a new workspace and default workspace chats
*
Expand All @@ -2083,7 +2146,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol
* @param [policyName] custom policy name we will use for created workspace
* @param [policyID] custom policy id we will use for created workspace
*/
function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): string {
function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID()): CreateWorkspaceParams {
const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail);

const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits();
Expand All @@ -2103,6 +2166,8 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
expenseCreatedReportActionID,
} = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName);

const optimisticCategoriesData = buildOptimisticPolicyCategories(policyID, CONST.POLICY.DEFAULT_CATEGORIES);

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.SET,
Expand Down Expand Up @@ -2293,6 +2358,18 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
},
];

if (optimisticCategoriesData.optimisticData) {
optimisticData.push(...optimisticCategoriesData.optimisticData);
}

if (optimisticCategoriesData.failureData) {
failureData.push(...optimisticCategoriesData.failureData);
}

if (optimisticCategoriesData.successData) {
successData.push(...optimisticCategoriesData.successData);
}

const params: CreateWorkspaceParams = {
policyID,
announceChatReportID,
Expand All @@ -2311,7 +2388,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName

API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData});

return adminsChatReportID;
return params;
}

function openWorkspaceReimburseView(policyID: string) {
Expand Down Expand Up @@ -3085,46 +3162,7 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor
}

function createPolicyCategory(policyID: string, categoryName: string) {
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: {
[categoryName]: {
name: categoryName,
enabled: true,
errors: null,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: {
[categoryName]: {
errors: null,
pendingAction: null,
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
value: {
[categoryName]: {
errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.createFailureMessage'),
pendingAction: null,
},
},
},
],
};
const onyxData = buildOptimisticPolicyCategories(policyID, [categoryName]);

const parameters = {
policyID,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/OnboardingWork/BaseOnboardingWork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function BaseOnboardingWork({currentUserPersonalDetails, shouldUseNativeStyles,

const work = values.work.trim();

const adminsChatReportID = Policy.createWorkspace(undefined, true, work);
const {adminsChatReportID} = Policy.createWorkspace(undefined, true, work);

Report.completeOnboarding(
onboardingPurposeSelected,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import lodashEvery from 'lodash/every';
import lodashGet from 'lodash/get';
import lodashIsEqual from 'lodash/isEqual';
import lodashMap from 'lodash/map';
Expand All @@ -9,11 +8,8 @@ import lodashValues from 'lodash/values';
import PropTypes from 'prop-types';
import React, {memo, useCallback, useEffect, useMemo} from 'react';
import {useOnyx} from 'react-native-onyx';
import BlockingView from '@components/BlockingViews/BlockingView';
import Button from '@components/Button';
import FormHelpMessage from '@components/FormHelpMessage';
import * as Illustrations from '@components/Icon/Illustrations';
import OfflineIndicator from '@components/OfflineIndicator';
import {usePersonalDetails} from '@components/OnyxProvider';
import {useOptionsList} from '@components/OptionListContextProvider';
import ReferralProgramCTA from '@components/ReferralProgramCTA';
Expand All @@ -26,15 +22,11 @@ import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

const propTypes = {
/** Callback to request parent modal to go to next step, which should be split */
Expand Down Expand Up @@ -87,7 +79,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF
const offlineMessage = isOffline ? [`${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}`, {isTranslated: true}] : '';

const maxParticipantsReached = participants.length === CONST.REPORT.MAXIMUM_PARTICIPANTS;
const {isSmallScreenWidth} = useWindowDimensions();

const isIOUSplit = iouType === CONST.IOU.TYPE.SPLIT;
const isCategorizeOrShareAction = [CONST.IOU.ACTION.CATEGORIZE, CONST.IOU.ACTION.SHARE].includes(action);
Expand Down Expand Up @@ -335,31 +326,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({participants, onF
);
}, [handleConfirmSelection, participants.length, isDismissed, referralContentType, shouldShowSplitBillErrorMessage, styles, translate]);

const renderEmptyWorkspaceView = () => (
<>
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyWorkspaceIconWidth}
iconHeight={variables.emptyWorkspaceIconHeight}
title={translate('workspace.emptyWorkspace.notFound')}
shouldShowLink={false}
/>
<Button
success
large
text={translate('footer.learnMore')}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_WORKSPACES)}
style={[styles.mh5, styles.mb5]}
/>
{isSmallScreenWidth && <OfflineIndicator />}
</>
);

const isAllSectionsEmpty = lodashEvery(sections, (section) => section.data.length === 0);
if (isCategorizeOrShareAction && isAllSectionsEmpty && didScreenTransitionEnd && debouncedSearchTerm.trim() === '' && areOptionsInitialized) {
return renderEmptyWorkspaceView();
}

return (
<SelectionList
onConfirm={handleConfirmSelection}
Expand Down
Loading