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

Enabling Invoices as a feature #46567

Merged
Merged
Show file tree
Hide file tree
Changes from 17 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
8 changes: 8 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2766,6 +2766,10 @@ export default {
title: 'Spend',
subtitle: 'Enable optional functionality that helps you scale your team.',
},
earnSection: {
title: 'Earn',
subtitle: 'Enable optional functionality to streamline your revenue and get paid faster.',
},
organizeSection: {
title: 'Organize',
subtitle: 'Group and analyze spend, record every tax paid.',
Expand Down Expand Up @@ -2799,6 +2803,10 @@ export default {
title: 'Workflows',
subtitle: 'Configure how spend is approved and paid.',
},
invoices: {
title: 'Invoices',
subtitle: 'Send and receive invoices.',
},
categories: {
title: 'Categories',
subtitle: 'Track and organize spend.',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,10 @@ export default {
title: 'Gasto',
subtitle: 'Habilita otras funcionalidades que ayudan a aumentar tu equipo.',
},
earnSection: {
title: 'Gane',
subtitle: 'Habilita funciones opcionales para agilizar tus ingresos y recibir pagos más rápido.',
},
organizeSection: {
title: 'Organizar',
subtitle: 'Agrupa y analiza el gasto, registra cada impuesto pagado.',
Expand Down Expand Up @@ -2851,6 +2855,10 @@ export default {
title: 'Flujos de trabajo',
subtitle: 'Configura cómo se aprueba y paga los gastos.',
},
invoices: {
title: 'Facturas',
subtitle: 'Enviar y recibir facturas.',
},
categories: {
title: 'Categorías',
subtitle: 'Monitoriza y organiza los gastos.',
Expand Down
6 changes: 6 additions & 0 deletions src/libs/API/parameters/EnablePolicyInvoicesParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type EnablePolicyInvoicesParams = {
madmax330 marked this conversation as resolved.
Show resolved Hide resolved
policyID: string;
enabled: boolean;
};

export default EnablePolicyInvoicesParams;
madmax330 marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export type {default as ConnectPolicyToNetSuiteParams} from './ConnectPolicyToNe
export type {default as CreateWorkspaceReportFieldParams} from './CreateWorkspaceReportFieldParams';
export type {default as UpdateWorkspaceReportFieldInitialValueParams} from './UpdateWorkspaceReportFieldInitialValueParams';
export type {default as EnableWorkspaceReportFieldListValueParams} from './EnableWorkspaceReportFieldListValueParams';
export type {default as EnablePolicyInvoicesParams} from './EnablePolicyInvoicesParams';
madmax330 marked this conversation as resolved.
Show resolved Hide resolved
export type {default as CreateWorkspaceReportFieldListValueParams} from './CreateWorkspaceReportFieldListValueParams';
export type {default as RemoveWorkspaceReportFieldListValueParams} from './RemoveWorkspaceReportFieldListValueParams';
export type {default as OpenPolicyExpensifyCardsPageParams} from './OpenPolicyExpensifyCardsPageParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ const WRITE_COMMANDS = {
ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows',
ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields',
ENABLE_POLICY_EXPENSIFY_CARDS: 'EnablePolicyExpensifyCards',
ENABLE_POLICY_INVOICES: 'EnablePolicyInvoices',
madmax330 marked this conversation as resolved.
Show resolved Hide resolved
SET_POLICY_TAXES_CURRENCY_DEFAULT: 'SetPolicyCurrencyDefaultTax',
SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT: 'SetPolicyForeignCurrencyDefaultTax',
SET_POLICY_CUSTOM_TAX_NAME: 'SetPolicyCustomTaxName',
Expand Down Expand Up @@ -515,6 +516,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams;
[WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS]: Parameters.EnablePolicyReportFieldsParams;
[WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS]: Parameters.EnablePolicyExpensifyCardsParams;
[WRITE_COMMANDS.ENABLE_POLICY_INVOICES]: Parameters.EnablePolicyInvoicesParams;
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
Expand Down
2 changes: 2 additions & 0 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2032,6 +2032,8 @@ function getOptions(

const shouldShowInvoiceRoom =
includeInvoiceRooms && ReportUtils.isInvoiceRoom(reportOption.item) && ReportUtils.isPolicyAdmin(reportOption.policyID ?? '', policies) && !reportOption.isArchivedRoom;
// TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
// && PolicyUtils.canSendInvoiceFromWorkspace(reportOption.policyID);

/**
Exclude the report option if it doesn't meet any of the following conditions:
Expand Down
9 changes: 9 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,17 @@ function getActiveAdminWorkspaces(policies: OnyxCollection<Policy> | null): Poli
return activePolicies.filter((policy) => shouldShowPolicy(policy, NetworkStore.isOffline()) && isPolicyAdmin(policy));
}

/** Whether the user can send invoice from the workspace */
function canSendInvoiceFromWorkspace(policyID: string | undefined): boolean {
const policy = getPolicy(policyID);
return policy?.areInvoicesEnabled ?? false;
}

/** Whether the user can send invoice */
function canSendInvoice(policies: OnyxCollection<Policy> | null): boolean {
return getActiveAdminWorkspaces(policies).length > 0;
// TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
// return getActiveAdminWorkspaces(policies).some((policy) => canSendInvoiceFromWorkspace(policy.id));
}

function hasDependentTags(policy: OnyxEntry<Policy>, policyTagList: OnyxEntry<PolicyTagList>) {
Expand Down Expand Up @@ -940,6 +948,7 @@ export {
isTaxTrackingEnabled,
shouldShowPolicy,
getActiveAdminWorkspaces,
canSendInvoiceFromWorkspace,
canSendInvoice,
hasDependentTags,
getXeroTenants,
Expand Down
2 changes: 2 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6086,6 +6086,8 @@ function getMoneyRequestOptions(report: OnyxEntry<Report>, policy: OnyxEntry<Pol
}

if (isInvoiceRoom(report)) {
// TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
// if (PolicyUtils.canSendInvoiceFromWorkspace(policy?.id) && isPolicyAdmin(report?.policyID ?? '-1', allPolicies)) {
if (isPolicyAdmin(report?.policyID ?? '-1', allPolicies)) {
return [CONST.IOU.TYPE.INVOICE];
}
Expand Down
64 changes: 64 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
DeleteWorkspaceParams,
EnablePolicyConnectionsParams,
EnablePolicyExpensifyCardsParams,
EnablePolicyInvoicesParams,
EnablePolicyReportFieldsParams,
EnablePolicyTaxesParams,
EnablePolicyWorkflowsParams,
Expand Down Expand Up @@ -202,13 +203,25 @@ function getPolicy(policyID: string | undefined): OnyxEntry<Policy> {
/**
* Returns a primary policy for the user
*/
// TODO: Use getInvoicePrimaryWorkspace when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
function getPrimaryPolicy(activePolicyID?: OnyxEntry<string>): Policy | undefined {
const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies);
const primaryPolicy: Policy | null | undefined = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID ?? '-1'}`];

return primaryPolicy ?? activeAdminWorkspaces[0];
}

/**
* Returns a primary invoice workspace for the user
*/
function getInvoicePrimaryWorkspace(activePolicyID?: OnyxEntry<string>): Policy | undefined {
if (PolicyUtils.canSendInvoiceFromWorkspace(activePolicyID)) {
return allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID ?? '-1'}`];
}
const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies);
return activeAdminWorkspaces.find((policy) => PolicyUtils.canSendInvoiceFromWorkspace(policy.id));
}

/**
* Check if the user has any active free policies (aka workspaces)
*/
Expand Down Expand Up @@ -2930,6 +2943,55 @@ function enableDistanceRequestTax(policyID: string, customUnitName: string, cust
API.write(WRITE_COMMANDS.ENABLE_DISTANCE_REQUEST_TAX, params, onyxData);
}

function enablePolicyInvoices(policyID: string, enabled: boolean) {
madmax330 marked this conversation as resolved.
Show resolved Hide resolved
const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
areInvoicesEnabled: enabled,
pendingFields: {
areInvoicesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
pendingFields: {
areInvoicesEnabled: null,
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
areInvoicesEnabled: !enabled,
pendingFields: {
areInvoicesEnabled: null,
},
},
},
],
};

const parameters: EnablePolicyInvoicesParams = {policyID, enabled};

API.write(WRITE_COMMANDS.ENABLE_POLICY_INVOICES, parameters, onyxData);
madmax330 marked this conversation as resolved.
Show resolved Hide resolved

// TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
// if (enabled && getIsNarrowLayout()) {
// navigateWhenEnableFeature(policyID);
// }
}

function openPolicyMoreFeaturesPage(policyID: string) {
const params: OpenPolicyMoreFeaturesPageParams = {policyID};

Expand Down Expand Up @@ -3215,6 +3277,7 @@ export {
enablePolicyTaxes,
enablePolicyWorkflows,
enableDistanceRequestTax,
enablePolicyInvoices,
openPolicyMoreFeaturesPage,
openPolicyProfilePage,
openPolicyInitialPage,
Expand All @@ -3231,6 +3294,7 @@ export {
clearPolicyErrorField,
isCurrencySupportedForDirectReimbursement,
getPrimaryPolicy,
getInvoicePrimaryWorkspace,
createDraftWorkspace,
savePreferredExportMethod,
buildPolicyData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const useIsFocused = () => {
return isFocused || (topmostCentralPane?.name === SCREENS.SEARCH.CENTRAL_PANE && shouldUseNarrowLayout);
};

type PolicySelector = Pick<OnyxTypes.Policy, 'type' | 'role' | 'isPolicyExpenseChatEnabled' | 'pendingAction' | 'avatarURL' | 'name' | 'id'>;
type PolicySelector = Pick<OnyxTypes.Policy, 'type' | 'role' | 'isPolicyExpenseChatEnabled' | 'pendingAction' | 'avatarURL' | 'name' | 'id' | 'areInvoicesEnabled'>;

type FloatingActionButtonAndPopoverOnyxProps = {
/** The list of policies the user has access to. */
Expand Down Expand Up @@ -97,6 +97,7 @@ const policySelector = (policy: OnyxEntry<OnyxTypes.Policy>): PolicySelector =>
pendingAction: policy.pendingAction,
avatarURL: policy.avatarURL,
name: policy.name,
areInvoicesEnabled: policy.areInvoicesEnabled,
}) as PolicySelector;

const getQuickActionIcon = (action: QuickActionName): React.FC<SvgProps> => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF
];

if (iouType === CONST.IOU.TYPE.INVOICE) {
// TODO: Use getInvoicePrimaryWorkspace when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
const policyID = option.item && ReportUtils.isInvoiceRoom(option.item) ? option.policyID : Policy.getPrimaryPolicy(activePolicyID)?.id;
newParticipants.push({
policyID,
Expand Down
6 changes: 4 additions & 2 deletions src/pages/iou/request/step/IOURequestStepSendFrom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ function IOURequestStepSendFrom({route, transaction, allPolicies}: IOURequestSte
const selectedWorkspace = useMemo(() => transaction?.participants?.find((participant) => participant.isSender), [transaction]);

const workspaceOptions: WorkspaceListItem[] = useMemo(() => {
const activeAdminWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies);
const availableWorkspaces = PolicyUtils.getActiveAdminWorkspaces(allPolicies);
// TODO: Uncomment the following line when the invoices screen is ready - https://github.com/Expensify/App/issues/45175.
// .filter((policy) => PolicyUtils.canSendInvoiceFromWorkspace(policy.id));

return activeAdminWorkspaces
return availableWorkspaces
.sort((policy1, policy2) => sortWorkspacesBySelected({policyID: policy1.id, name: policy1.name}, {policyID: policy2.id, name: policy2.name}, selectedWorkspace?.policyID))
.map((policy) => ({
text: policy.name,
Expand Down
18 changes: 18 additions & 0 deletions src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
});
}

const earnItems: Item[] = [
{
icon: Illustrations.InvoiceBlue,
titleTranslationKey: 'workspace.moreFeatures.invoices.title',
subtitleTranslationKey: 'workspace.moreFeatures.invoices.subtitle',
isActive: policy?.areInvoicesEnabled ?? false,
pendingAction: policy?.pendingFields?.areInvoicesEnabled,
action: (isEnabled: boolean) => {
Policy.enablePolicyInvoices(policy?.id ?? '-1', isEnabled);
},
},
];

const organizeItems: Item[] = [
{
icon: Illustrations.FolderOpen,
Expand Down Expand Up @@ -247,6 +260,11 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.spendSection.subtitle',
items: spendItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.earnSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.earnSection.subtitle',
items: earnItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.organizeSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.organizeSection.subtitle',
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1492,6 +1492,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback<
/** Whether the Connections feature is enabled */
areConnectionsEnabled?: boolean;

/** Whether the Invoices feature is enabled */
areInvoicesEnabled?: boolean;

/** The verified bank account linked to the policy */
achAccount?: ACHAccount;

Expand Down
Loading