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

[No QA] Asking for company name and URL during invoice creation #46009

Merged
Merged
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ const ONYXKEYS = {
MONEY_REQUEST_DATE_FORM_DRAFT: 'moneyRequestCreatedFormDraft',
MONEY_REQUEST_HOLD_FORM: 'moneyHoldReasonForm',
MONEY_REQUEST_HOLD_FORM_DRAFT: 'moneyHoldReasonFormDraft',
MONEY_REQUEST_COMPANY_INFO_FORM: 'moneyRequestCompanyInfoForm',
MONEY_REQUEST_COMPANY_INFO_FORM_DRAFT: 'moneyRequestCompanyInfoFormDraft',
NEW_CONTACT_METHOD_FORM: 'newContactMethodForm',
NEW_CONTACT_METHOD_FORM_DRAFT: 'newContactMethodFormDraft',
WAYPOINT_FORM: 'waypointForm',
Expand Down Expand Up @@ -628,6 +630,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.MONEY_REQUEST_AMOUNT_FORM]: FormTypes.MoneyRequestAmountForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_DATE_FORM]: FormTypes.MoneyRequestDateForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM]: FormTypes.MoneyRequestHoldReasonForm;
[ONYXKEYS.FORMS.MONEY_REQUEST_COMPANY_INFO_FORM]: FormTypes.MoneyRequestCompanyInfoForm;
[ONYXKEYS.FORMS.NEW_CONTACT_METHOD_FORM]: FormTypes.NewContactMethodForm;
[ONYXKEYS.FORMS.WAYPOINT_FORM]: FormTypes.WaypointForm;
[ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: FormTypes.SettingsStatusSetForm;
Expand Down
5 changes: 5 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ const ROUTES = {
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`create/${iouType as string}/from/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_COMPANY_INFO: {
route: 'create/:iouType/company-info/:transactionID/:reportID',
getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo = '') =>
getUrlWithBackToParam(`create/${iouType as string}/company-info/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_CONFIRMATION: {
route: ':action/:iouType/confirmation/:transactionID/:reportID',
getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string) =>
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const SCREENS = {
STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate',
STEP_SPLIT_PAYER: 'Money_Request_Step_Split_Payer',
STEP_SEND_FROM: 'Money_Request_Step_Send_From',
STEP_COMPANY_INFO: 'Money_Request_Step_Company_Info',
CURRENCY: 'Money_Request_Currency',
WAYPOINT: 'Money_Request_Waypoint',
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
Expand Down
17 changes: 15 additions & 2 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as ReportUtils from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import * as TransactionUtils from '@libs/TransactionUtils';
import * as IOU from '@userActions/IOU';
import {hasInvoicingDetails} from '@userActions/Policy/Policy';
import type {IOUAction, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
Expand Down Expand Up @@ -364,7 +365,11 @@ function MoneyRequestConfirmationList({
const splitOrRequestOptions: Array<DropdownOption<string>> = useMemo(() => {
let text;
if (isTypeInvoice) {
text = translate('iou.sendInvoice', {amount: formattedAmount});
if (hasInvoicingDetails(policy)) {
text = translate('iou.sendInvoice', {amount: formattedAmount});
} else {
text = translate('common.next');
}
} else if (isTypeTrackExpense) {
text = translate('iou.trackExpense');
} else if (isTypeSplit && iouAmount === 0) {
Expand All @@ -384,7 +389,7 @@ function MoneyRequestConfirmationList({
value: iouType,
},
];
}, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]);
}, [isTypeTrackExpense, isTypeSplit, iouAmount, receiptPath, isTypeRequest, policy, isDistanceRequestWithPendingRoute, iouType, translate, formattedAmount, isTypeInvoice]);

const onSplitShareChange = useCallback(
(accountID: number, value: number) => {
Expand Down Expand Up @@ -669,6 +674,11 @@ function MoneyRequestConfirmationList({
*/
const confirm = useCallback(
(paymentMethod: PaymentMethodType | undefined) => {
if (iouType === CONST.IOU.TYPE.INVOICE && !hasInvoicingDetails(policy)) {
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_COMPANY_INFO.getRoute(iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()));
return;
}

if (selectedParticipants.length === 0) {
return;
}
Expand Down Expand Up @@ -736,6 +746,9 @@ function MoneyRequestConfirmationList({
iouAmount,
onConfirm,
shouldPlaySound,
transactionID,
reportID,
policy,
],
);

Expand Down
7 changes: 7 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,13 @@ export default {
receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.",
receiptScanningFailed: 'Receipt scanning failed. Please enter the details manually.',
transactionPendingDescription: 'Transaction pending. It may take a few days to post.',
companyInfo: 'Company info',
companyInfoDescription: 'We need a few more details before you can send your first invoice.',
yourCompanyName: 'Your company name',
yourCompanyWebsite: 'Your company website',
yourCompanyWebsiteNote: "If you don't have a website, you can provide your company's LinkedIn or social media profile instead.",
invalidDomainError: 'You have entered an invalid domain. To continue, please enter a valid domain.',
publicDomainError: 'You have entered a public domain. To continue, please enter a private domain.',
expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
`${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${
pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,13 @@ export default {
receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.',
receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.',
transactionPendingDescription: 'Transacción pendiente. Puede tardar unos días en contabilizarse.',
companyInfo: 'Información de la empresa',
companyInfoDescription: 'Necesitamos algunos detalles más antes de que pueda enviar su primera factura.',
yourCompanyName: 'Nombre de su empresa',
yourCompanyWebsite: 'Sitio web de su empresa',
yourCompanyWebsiteNote: 'Si no tiene un sitio web, puede proporcionar el perfil de LinkedIn o de las redes sociales de su empresa.',
invalidDomainError: 'Ha introducido un dominio no válido. Para continuar, introduzca un dominio válido.',
publicDomainError: 'Ha introducido un dominio público. Para continuar, introduzca un dominio privado.',
Comment on lines +714 to +720
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've asked for translations approve in the slack: https://expensify.slack.com/archives/C01GTK53T8Q/p1721825016527359

expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
`${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${
pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/parameters/SendInvoiceParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type SendInvoiceParams = RequireAtLeastOne<
reportPreviewReportActionID: string;
transactionID: string;
transactionThreadReportID: string;
companyName?: string;
companyWebsite?: string;
},
'receiverEmail' | 'receiverInvoiceRoomID'
>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator<MoneyRequestNa
[SCREENS.MONEY_REQUEST.STEP_WAYPOINT]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepWaypoint').default,
[SCREENS.MONEY_REQUEST.STEP_SPLIT_PAYER]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepSplitPayer').default,
[SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepSendFrom').default,
[SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: () => require<ReactComponentModule>('../../../../pages/iou/request/step/IOURequestStepCompanyInfo').default,
[SCREENS.MONEY_REQUEST.HOLD]: () => require<ReactComponentModule>('../../../../pages/iou/HoldReasonPage').default,
[SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require<ReactComponentModule>('../../../../pages/AddPersonalBankAccountPage').default,
[SCREENS.IOU_SEND.ADD_DEBIT_CARD]: () => require<ReactComponentModule>('../../../../pages/settings/Wallet/AddDebitCardPage').default,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
},
[SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORIES_ROOT]: ROUTES.SETTINGS_CATEGORIES_ROOT.route,
[SCREENS.MONEY_REQUEST.STEP_SEND_FROM]: ROUTES.MONEY_REQUEST_STEP_SEND_FROM.route,
[SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: ROUTES.MONEY_REQUEST_STEP_COMPANY_INFO.route,
[SCREENS.MONEY_REQUEST.STEP_AMOUNT]: ROUTES.MONEY_REQUEST_STEP_AMOUNT.route,
[SCREENS.MONEY_REQUEST.STEP_CATEGORY]: ROUTES.MONEY_REQUEST_STEP_CATEGORY.route,
[SCREENS.MONEY_REQUEST.STEP_CONFIRMATION]: ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.route,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,12 @@ type MoneyRequestNavigatorParamList = {
reportID: string;
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_COMPANY_INFO]: {
iouType: IOUType;
transactionID: string;
reportID: string;
backTo: Routes;
};
[SCREENS.MONEY_REQUEST.STEP_PARTICIPANTS]: {
action: IOUAction;
iouType: Exclude<IOUType, typeof CONST.IOU.TYPE.REQUEST | typeof CONST.IOU.TYPE.SEND>;
Expand Down
8 changes: 7 additions & 1 deletion src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {addYears, endOfMonth, format, isAfter, isBefore, isSameDay, isValid, isWithinInterval, parse, parseISO, startOfDay, subYears} from 'date-fns';
import {Str, Url} from 'expensify-common';
import {PUBLIC_DOMAINS, Str, Url} from 'expensify-common';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
import type {OnyxCollection} from 'react-native-onyx';
Expand Down Expand Up @@ -242,6 +242,11 @@ function isValidWebsite(url: string): boolean {
return new RegExp(`^${Url.URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i').test(url) && isLowerCase;
}

/** Checks if the domain is public */
function isPublicDomain(domain: string): boolean {
return PUBLIC_DOMAINS.some((publicDomain) => publicDomain === domain);
VickyStash marked this conversation as resolved.
Show resolved Hide resolved
}

function validateIdentity(identity: Record<string, string>): Record<string, boolean> {
const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob'];
const errors: Record<string, boolean> = {};
Expand Down Expand Up @@ -534,4 +539,5 @@ export {
isExistingTaxName,
isValidSubscriptionSize,
isExistingTaxCode,
isPublicDomain,
};
55 changes: 54 additions & 1 deletion src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,8 @@ function buildOnyxDataForInvoice(
policy?: OnyxEntry<OnyxTypes.Policy>,
policyTagList?: OnyxEntry<OnyxTypes.PolicyTagList>,
policyCategories?: OnyxEntry<OnyxTypes.PolicyCategories>,
companyName?: string,
companyWebsite?: string,
): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] {
const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null]));
const optimisticData: OnyxUpdate[] = [
Expand Down Expand Up @@ -1175,6 +1177,49 @@ function buildOnyxDataForInvoice(
},
];

if (companyName && companyWebsite) {
optimisticData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`,
value: {
invoice: {
companyName,
companyWebsite,
pendingFields: {
companyName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
companyWebsite: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
});
successData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`,
value: {
invoice: {
pendingFields: {
companyName: null,
companyWebsite: null,
},
},
},
});
failureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policy?.id}`,
value: {
invoice: {
companyName: undefined,
companyWebsite: undefined,
pendingFields: {
companyName: null,
companyWebsite: null,
},
},
},
});
}

// We don't need to compute violations unless we're on a paid policy
if (!policy || !PolicyUtils.isPaidGroupPolicy(policy)) {
return [optimisticData, successData, failureData];
Expand Down Expand Up @@ -1771,6 +1816,8 @@ function getSendInvoiceInformation(
policy?: OnyxEntry<OnyxTypes.Policy>,
policyTagList?: OnyxEntry<OnyxTypes.PolicyTagList>,
policyCategories?: OnyxEntry<OnyxTypes.PolicyCategories>,
companyName?: string,
companyWebsite?: string,
): SendInvoiceInformation {
const {amount = 0, currency = '', created = '', merchant = '', category = '', tag = '', taxCode = '', taxAmount = 0, billable, comment, participants} = transaction ?? {};
const trimmedComment = (comment?.comment ?? '').trim();
Expand Down Expand Up @@ -1878,6 +1925,8 @@ function getSendInvoiceInformation(
policy,
policyTagList,
policyCategories,
companyName,
companyWebsite,
);

return {
Expand Down Expand Up @@ -3602,9 +3651,11 @@ function sendInvoice(
policy?: OnyxEntry<OnyxTypes.Policy>,
policyTagList?: OnyxEntry<OnyxTypes.PolicyTagList>,
policyCategories?: OnyxEntry<OnyxTypes.PolicyCategories>,
companyName?: string,
companyWebsite?: string,
) {
const {senderWorkspaceID, receiver, invoiceRoom, createdChatReportActionID, invoiceReportID, reportPreviewReportActionID, transactionID, transactionThreadReportID, onyxData} =
getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories);
getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories, companyName, companyWebsite);

const parameters: SendInvoiceParams = {
senderWorkspaceID,
Expand All @@ -3621,6 +3672,8 @@ function sendInvoice(
reportPreviewReportActionID,
transactionID,
transactionThreadReportID,
companyName,
companyWebsite,
...(invoiceChatReport?.reportID ? {receiverInvoiceRoomID: invoiceChatReport.reportID} : {receiverEmail: receiver.login ?? ''}),
};

Expand Down
9 changes: 9 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ function getPrimaryPolicy(activePolicyID?: OnyxEntry<string>): Policy | undefine
return primaryPolicy ?? activeAdminWorkspaces[0];
}

/** Check if the policy has invoicing company details */
// eslint-disable-next-line react/no-unused-prop-types,@typescript-eslint/no-unused-vars
function hasInvoicingDetails(policy: OnyxEntry<Policy>): boolean {
// TODO: uncomment when invoicing details inside a policy are supported.
// return !!policy.invoice.companyName && !!policy.invoice.companyWebsite;
return true;
}

/**
* Check if the user has any active free policies (aka workspaces)
*/
Expand Down Expand Up @@ -3225,6 +3233,7 @@ export {
requestExpensifyCardLimitIncrease,
getAdminPoliciesConnectedToNetSuite,
getAdminPoliciesConnectedToSageIntacct,
hasInvoicingDetails,
};

export type {NewCustomUnit};
Loading
Loading