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

[Feat] Make it possible to submit expenses to any workspace, whether or not there is a workspace chat #51341

Closed
1 change: 1 addition & 0 deletions src/libs/API/parameters/CreateDistanceRequestParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type CreateDistanceRequestParams = {
payerEmail?: string;
splits?: string;
chatType?: string;
policyID?: string;
};

export default CreateDistanceRequestParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/RequestMoneyParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type RequestMoneyParams = {
transactionThreadReportID: string;
createdReportActionIDForThread: string;
reimbursible?: boolean;
policyID?: string;
};

export default RequestMoneyParams;
14 changes: 12 additions & 2 deletions src/libs/DistanceRequestUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx';
import type {Unit} from '@src/types/onyx/Policy';
import type Policy from '@src/types/onyx/Policy';
import type Report from '@src/types/onyx/Report';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import * as CurrencyUtils from './CurrencyUtils';
import * as PolicyUtils from './PolicyUtils';
Expand All @@ -30,6 +31,13 @@ Onyx.connect({
},
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
});

const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters
const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter

Expand Down Expand Up @@ -279,7 +287,9 @@ function convertToDistanceInMeters(distance: number, unit: Unit): number {
*/
function getCustomUnitRateID(reportID: string, shouldUseDefault?: boolean) {
const allReports = ReportConnection.getAllReports();
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
const reportReal = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
const reportDraft = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
const report = reportReal ?? reportDraft;
const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
const policy = PolicyUtils.getPolicy(report?.policyID ?? parentReport?.policyID ?? '-1');
let customUnitRateID: string = CONST.CUSTOM_UNITS.FAKE_P2P_ID;
Expand Down
62 changes: 62 additions & 0 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type DeepValueOf from '@src/types/utils/DeepValueOf';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import times from '@src/utils/times';
import {createDraftReportForPolicyExpenseChat} from './actions/Report';
import Timing from './actions/Timing';
import filterArrayByMatch from './filterArrayByMatch';
import localeCompare from './LocaleCompare';
Expand All @@ -61,6 +62,7 @@ import * as UserUtils from './UserUtils';

type SearchOption<T> = ReportUtils.OptionData & {
item: T;
isOptimisticReportOption?: boolean;
};

type OptionList = {
Expand Down Expand Up @@ -181,6 +183,7 @@ type GetOptionsConfig = {
shouldAcceptName?: boolean;
recentAttendees?: Attendee[];
shouldBoldTitleByDefault?: boolean;
includePoliciesWithoutExpenseChats?: boolean;
};

type GetUserToInviteConfig = {
Expand Down Expand Up @@ -246,6 +249,13 @@ Onyx.connect({
},
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
});

let loginList: OnyxEntry<Login>;
Onyx.connect({
key: ONYXKEYS.LOGIN_LIST,
Expand Down Expand Up @@ -1505,6 +1515,7 @@ function isReportSelected(reportOption: ReportUtils.OptionData, selectedOptions:
function createOptionList(personalDetails: OnyxEntry<PersonalDetailsList>, reports?: OnyxCollection<Report>) {
const reportMapForAccountIDs: Record<number, Report> = {};
const allReportOptions: Array<SearchOption<Report>> = [];
const policyToReportForPolicyExpenseChats: Record<string, Report> = {};

if (reports) {
Object.values(reports).forEach((report) => {
Expand All @@ -1520,6 +1531,10 @@ function createOptionList(personalDetails: OnyxEntry<PersonalDetailsList>, repor
return;
}

if (ReportUtils.isPolicyExpenseChat(report) && report.policyID) {
policyToReportForPolicyExpenseChats[report.policyID] = report;
}

// Save the report in the map if this is a single participant so we can associate the reportID with the
// personal detail option later. Individuals should not be associated with single participant
// policyExpenseChats or chatRooms since those are not people.
Expand All @@ -1534,6 +1549,46 @@ function createOptionList(personalDetails: OnyxEntry<PersonalDetailsList>, repor
});
}

const policiesWithoutExpenseChats = Object.values(policies ?? {}).filter((policy) => {
if (policy?.type === CONST.POLICY.TYPE.PERSONAL) {
return false;
}
return !policyToReportForPolicyExpenseChats[policy?.id ?? ''];
});

// go through each policy and create a optimistic report option for it
if (policiesWithoutExpenseChats && policiesWithoutExpenseChats.length > 0) {
policiesWithoutExpenseChats.forEach((policy) => {
// check for draft report exist in allreportDrafts for the policy
let draftReport = Object.values(allReportsDraft ?? {})?.find((reportDraft) => reportDraft?.policyID === policy?.id);
if (!draftReport) {
draftReport = ReportUtils.buildOptimisticChatReport(
[currentUserAccountID ?? -1],
'',
CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
policy?.id,
currentUserAccountID,
true,
policy?.name,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
);
createDraftReportForPolicyExpenseChat({...draftReport, isOptimisticReport: true});
}
const accountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(draftReport);
allReportOptions.push({
item: draftReport,
isOptimisticReportOption: true,
...createOption(accountIDs, personalDetails, draftReport, {}),
});
});
}
const allPersonalDetailsOptions = Object.values(personalDetails ?? {}).map((personalDetail) => ({
item: personalDetail,
...createOption([personalDetail?.accountID ?? -1], personalDetails, reportMapForAccountIDs[personalDetail?.accountID ?? -1], {}, {showPersonalDetails: true}),
Expand Down Expand Up @@ -1732,6 +1787,7 @@ function getOptions(
action,
recentAttendees,
shouldBoldTitleByDefault = true,
includePoliciesWithoutExpenseChats = false,
}: GetOptionsConfig,
): Options {
if (includeCategories) {
Expand Down Expand Up @@ -1796,6 +1852,9 @@ function getOptions(

// Filter out all the reports that shouldn't be displayed
const filteredReportOptions = options.reports.filter((option) => {
if (option.isOptimisticReportOption && !includePoliciesWithoutExpenseChats) {
return;
}
const report = option.item;
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);

Expand Down Expand Up @@ -2148,6 +2207,7 @@ type FilteredOptionsParams = {
includeInvoiceRooms?: boolean;
action?: IOUAction;
sortByReportTypeInSearch?: boolean;
includePoliciesWithoutExpenseChats?: boolean;
};

// It is not recommended to pass a search value to getFilteredOptions when passing reports and personalDetails.
Expand Down Expand Up @@ -2189,6 +2249,7 @@ function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue
includeInvoiceRooms = false,
action,
sortByReportTypeInSearch = false,
includePoliciesWithoutExpenseChats = false,
} = params;
return getOptions(
{reports, personalDetails},
Expand Down Expand Up @@ -2218,6 +2279,7 @@ function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue
includeInvoiceRooms,
action,
sortByReportTypeInSearch,
includePoliciesWithoutExpenseChats,
},
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ type OptimisticChatReport = Pick<
| 'chatReportID'
| 'iouReportID'
| 'isOwnPolicyExpenseChat'
| 'isPolicyExpenseChat'
| 'isPinned'
| 'lastActorAccountID'
| 'lastMessageTranslationKey'
Expand Down Expand Up @@ -5344,6 +5345,7 @@ function buildOptimisticChatReport(
chatType,
isOwnPolicyExpenseChat,
isPinned: isNewlyCreatedWorkspaceChat,
isPolicyExpenseChat: chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
lastActorAccountID: 0,
lastMessageTranslationKey: '',
lastMessageHtml: '',
Expand Down
4 changes: 4 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3623,6 +3623,7 @@ function requestMoney(
attendees,
);
const activeReportID = isMoneyRequestReport ? report?.reportID : chatReport.reportID;
const isOptimisticPolicyExpenseChat = report?.isOptimisticReport && ReportUtils.isPolicyExpenseChat(report);

switch (action) {
case CONST.IOU.ACTION.SUBMIT: {
Expand Down Expand Up @@ -3683,6 +3684,7 @@ function requestMoney(
transactionThreadReportID,
createdReportActionIDForThread,
reimbursible,
policyID: isOptimisticPolicyExpenseChat ? policy?.id : undefined,
};

// eslint-disable-next-line rulesdir/no-multiple-api-calls
Expand Down Expand Up @@ -5192,6 +5194,7 @@ function createDistanceRequest(
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
const currentChatReport = isMoneyRequestReport ? ReportUtils.getReportOrDraftReport(report?.chatReportID) : report;
const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : '';
const isOptimisticPolicyExpenseChat = report?.isOptimisticReport && ReportUtils.isPolicyExpenseChat(report);

const optimisticReceipt: Receipt = {
source: ReceiptGeneric as ReceiptSource,
Expand Down Expand Up @@ -5306,6 +5309,7 @@ function createDistanceRequest(
createdReportActionIDForThread,
payerEmail,
customUnitRateID,
policyID: isOptimisticPolicyExpenseChat ? policy?.id : undefined,
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ import processReportIDDeeplink from '@libs/processReportIDDeeplink';
import * as Pusher from '@libs/Pusher/pusher';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportConnection from '@libs/ReportConnection';
import type {OptimisticAddCommentReportAction} from '@libs/ReportUtils';
import type {OptimisticAddCommentReportAction, OptimisticChatReport} from '@libs/ReportUtils';
import * as ReportUtils from '@libs/ReportUtils';
import {doesReportBelongToWorkspace} from '@libs/ReportUtils';
import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation';
Expand Down Expand Up @@ -1372,6 +1372,7 @@ function handleReportChanged(report: OnyxEntry<Report>) {
if (report?.reportID && report.preexistingReportID) {
let callback = () => {
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, null);
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, null);
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${report.reportID}`, null);
};
// Only re-route them if they are still looking at the optimistically created report
Expand Down Expand Up @@ -4136,6 +4137,10 @@ function markAsManuallyExported(reportID: string, connectionName: ConnectionName
API.write(WRITE_COMMANDS.MARK_AS_EXPORTED, params, {optimisticData, successData, failureData});
}

function createDraftReportForPolicyExpenseChat(draftReport: OptimisticChatReport) {
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${draftReport.reportID}`, draftReport);
}

function exportReportToCSV({reportID, transactionIDList}: ExportReportCSVParams, onDownloadFailed: () => void) {
const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_REPORT_TO_CSV, {
reportID,
Expand Down Expand Up @@ -4243,4 +4248,5 @@ export {
updateReportName,
updateRoomVisibility,
updateWriteCapability,
createDraftReportForPolicyExpenseChat,
};
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function MoneyRequestParticipantsSelector({
sortByReportTypeInSearch: isPaidGroupPolicy,
searchValue: '',
maxRecentReportsToShow: 0,
includePoliciesWithoutExpenseChats: true,
});

return optionList;
Expand Down
14 changes: 9 additions & 5 deletions src/pages/iou/request/step/IOURequestStepCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ function IOURequestStepCategory({
transaction,
}: IOURequestStepCategoryProps) {
const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID ?? '-1'}`);
const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);

const policyIDForReal = IOU.getIOURequestPolicyID(transaction, reportReal ?? reportDraft);
const policyIDForDraft = IOU.getIOURequestPolicyID(transaction, reportDraft);
const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyIDForReal}`);
const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyIDForDraft}`);
const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyIDForReal}`);
const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyIDForDraft}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyIDForReal}`);

let reportID = '-1';
if (action === CONST.IOU.ACTION.EDIT && reportReal) {
if (iouType === CONST.IOU.TYPE.SPLIT) {
Expand Down
13 changes: 8 additions & 5 deletions src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ function IOURequestStepConfirmation({
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;

const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${IOU.getIOURequestPolicyID(transaction, reportDraft)}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${IOU.getIOURequestPolicyID(transaction, reportReal)}`);
const policyIDForReal = IOU.getIOURequestPolicyID(transaction, reportReal ?? reportDraft);
const policyIDForDraft = IOU.getIOURequestPolicyID(transaction, reportDraft);

const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyIDForReal}`);
const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyIDForDraft}`);
const [policyCategoriesReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyIDForReal}`);
const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyIDForDraft}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyIDForReal}`);

const report = reportReal ?? reportDraft;
const policy = policyReal ?? policyDraft;
Expand Down
4 changes: 3 additions & 1 deletion src/pages/iou/request/step/IOURequestStepDistance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ type IOURequestStepDistanceProps = WithCurrentUserPersonalDetailsProps &
};

function IOURequestStepDistance({
report,
report: reportReal,
reportDraft,
route: {
params: {action, iouType, reportID, transactionID, backTo},
},
Expand All @@ -63,6 +64,7 @@ function IOURequestStepDistance({
const styles = useThemeStyles();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
const report = reportReal ?? reportDraft;
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`);
const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${transactionID}`);
const policy = usePolicy(report?.policyID);
Expand Down
14 changes: 9 additions & 5 deletions src/pages/iou/request/step/IOURequestStepDistanceRate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,24 @@ type IOURequestStepDistanceRateProps = WithWritableReportOrNotFoundProps<typeof
};

function IOURequestStepDistanceRate({
report,
report: reportReal,
reportDraft,
route: {
params: {action, reportID, backTo, transactionID},
},
transaction,
}: IOURequestStepDistanceRateProps) {
const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft) ?? '-1'}`);
const policyIDForReal = IOU.getIOURequestPolicyID(transaction, reportReal ?? reportDraft);
const policyIDForDraft = IOU.getIOURequestPolicyID(transaction, reportDraft);

const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyIDForDraft}`);
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID || '-1'}`);
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report?.policyID || '-1'}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${report?.policyID || '-1'}`);
const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyIDForReal || '-1'}`);
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyIDForReal || '-1'}`);
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyIDForReal || '-1'}`);
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */

const report = reportReal ?? reportDraft;
const policy = policyReal ?? policyDraft;

const styles = useThemeStyles();
Expand Down
Loading
Loading