From 0be62eb16a2cab2bd9e6e6addaa6cf7540ff4ba3 Mon Sep 17 00:00:00 2001 From: Stefan Moraru Date: Thu, 5 Sep 2024 18:53:01 +0300 Subject: [PATCH] Enable setting invoice skipping at 0 for both organizations and customers --- src/components/customers/CustomerSettings.tsx | 113 ++++++++++ ...ustomerFinalizeZeroAmountInvoiceDialog.tsx | 83 +++++++ .../EditFinalizeZeroAmountInvoiceDialog.tsx | 202 ++++++++++++++++++ src/generated/graphql.tsx | 170 ++++++++++++++- src/pages/settings/InvoiceSettings.tsx | 46 ++++ translations/base.json | 21 +- 6 files changed, 632 insertions(+), 3 deletions(-) create mode 100644 src/components/customers/DeleteCustomerFinalizeZeroAmountInvoiceDialog.tsx create mode 100644 src/components/settings/EditFinalizeZeroAmountInvoiceDialog.tsx diff --git a/src/components/customers/CustomerSettings.tsx b/src/components/customers/CustomerSettings.tsx index f37d59417..ec74cc6fe 100644 --- a/src/components/customers/CustomerSettings.tsx +++ b/src/components/customers/CustomerSettings.tsx @@ -2,6 +2,10 @@ import { gql } from '@apollo/client' import { useRef } from 'react' import styled from 'styled-components' +import { + DeleteCustomerFinalizeZeroAmountInvoiceDialog, + DeleteCustomerFinalizeZeroAmountInvoiceDialogRef, +} from '~/components/customers/DeleteCustomerFinalizeZeroAmountInvoiceDialog' import { EditCustomerVatRateDialog, EditCustomerVatRateDialogRef, @@ -17,6 +21,10 @@ import { } from '~/components/designSystem' import { GenericPlaceholder } from '~/components/GenericPlaceholder' import { PremiumWarningDialog, PremiumWarningDialogRef } from '~/components/PremiumWarningDialog' +import { + EditFinalizeZeroAmountInvoiceDialog, + EditFinalizeZeroAmountInvoiceDialogRef, +} from '~/components/settings/EditFinalizeZeroAmountInvoiceDialog' import { EditNetPaymentTermDialog, EditNetPaymentTermDialogRef, @@ -32,6 +40,7 @@ import { EditCustomerDocumentLocaleFragmentDoc, EditCustomerInvoiceGracePeriodFragmentDoc, EditCustomerVatRateFragmentDoc, + FinalizeZeroAmountInvoiceEnum, useGetCustomerSettingsQuery, } from '~/generated/graphql' import { useInternationalization } from '~/hooks/core/useInternationalization' @@ -83,6 +92,8 @@ gql` id invoiceGracePeriod netPaymentTerm + finalizeZeroAmountInvoice + billingConfiguration { id documentLocale @@ -102,6 +113,7 @@ gql` organization { id netPaymentTerm + finalizeZeroAmountInvoice billingConfiguration { id invoiceGracePeriod @@ -143,6 +155,10 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => { const editNetPaymentTermDialogRef = useRef(null) const deleteOrganizationNetPaymentTermDialogRef = useRef(null) + const editFinalizeZeroAmountInvoiceDialogRef = + useRef(null) + const deleteCustomerFinalizeZeroAmountInvoiceDialogRef = + useRef(null) { !!error && !loading && ( @@ -332,6 +348,94 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => { )} + + + {translate('text_1725549671287r9tnu5cuoeu')} + + + {customer?.finalizeZeroAmountInvoice === FinalizeZeroAmountInvoiceEnum.Inherit ? ( + + ) : ( + } + > + {({ closePopper }) => ( + + + + + )} + + )} + + + + {loading ? ( + <> + + + + ) : ( + <> + + {customer?.finalizeZeroAmountInvoice === FinalizeZeroAmountInvoiceEnum.Inherit ? ( + <> + {organization?.finalizeZeroAmountInvoice + ? translate('text_17255500892002nq3iltm03z') + : translate('text_1725549671288zkq9sr0y46l')} + {` ${translate('text_17255500892009uqfqttms4w')}`} + + ) : ( + <> + {customer?.finalizeZeroAmountInvoice === FinalizeZeroAmountInvoiceEnum.Finalize + ? translate('text_17255500892002nq3iltm03z') + : translate('text_1725549671288zkq9sr0y46l')} + + )} + + + + + )} + + {translate('text_638dff9779fb99299bee912e')} @@ -546,6 +650,15 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => { ref={deleteOrganizationNetPaymentTermDialogRef} customer={customer} /> + + )} diff --git a/src/components/customers/DeleteCustomerFinalizeZeroAmountInvoiceDialog.tsx b/src/components/customers/DeleteCustomerFinalizeZeroAmountInvoiceDialog.tsx new file mode 100644 index 000000000..4f8416f40 --- /dev/null +++ b/src/components/customers/DeleteCustomerFinalizeZeroAmountInvoiceDialog.tsx @@ -0,0 +1,83 @@ +import { gql } from '@apollo/client' +import { forwardRef } from 'react' + +import { DialogRef, Typography } from '~/components/designSystem' +import { WarningDialog, WarningDialogRef } from '~/components/WarningDialog' +import { addToast } from '~/core/apolloClient' +import { + DeleteCustomerFinalizeZeroAmountInvoiceFragment, + FinalizeZeroAmountInvoiceEnum, + useDeleteCustomerFinalizeZeroAmountInvoiceMutation, +} from '~/generated/graphql' +import { useInternationalization } from '~/hooks/core/useInternationalization' + +gql` + fragment DeleteCustomerFinalizeZeroAmountInvoice on Customer { + id + externalId + name + finalizeZeroAmountInvoice + } + + mutation deleteCustomerFinalizeZeroAmountInvoice($input: UpdateCustomerInput!) { + updateCustomer(input: $input) { + id + ...DeleteCustomerFinalizeZeroAmountInvoice + } + } +` + +export interface DeleteCustomerFinalizeZeroAmountInvoiceDialogRef extends WarningDialogRef {} + +interface DeleteCustomerFinalizeZeroAmountInvoiceDialogProps { + customer: DeleteCustomerFinalizeZeroAmountInvoiceFragment +} + +export const DeleteCustomerFinalizeZeroAmountInvoiceDialog = forwardRef< + DialogRef, + DeleteCustomerFinalizeZeroAmountInvoiceDialogProps +>(({ customer }: DeleteCustomerFinalizeZeroAmountInvoiceDialogProps, ref) => { + const { translate } = useInternationalization() + + const [deleteCustomerFinalizeZeroAmountInvoice] = + useDeleteCustomerFinalizeZeroAmountInvoiceMutation({ + onCompleted(data) { + if (data && data.updateCustomer) { + addToast({ + message: translate('text_17255496712882bspi9zp0ii'), + severity: 'success', + }) + } + }, + }) + + return ( + + } + onContinue={async () => + await deleteCustomerFinalizeZeroAmountInvoice({ + variables: { + input: { + id: customer?.id, + externalId: customer?.externalId, + name: customer?.name || '', + finalizeZeroAmountInvoice: FinalizeZeroAmountInvoiceEnum.Inherit, + }, + }, + }) + } + continueText={translate('text_63aa085d28b8510cd46441a5')} + /> + ) +}) + +DeleteCustomerFinalizeZeroAmountInvoiceDialog.displayName = + 'DeleteCustomerFinalizeZeroAmountInvoice' diff --git a/src/components/settings/EditFinalizeZeroAmountInvoiceDialog.tsx b/src/components/settings/EditFinalizeZeroAmountInvoiceDialog.tsx new file mode 100644 index 000000000..dc795ea73 --- /dev/null +++ b/src/components/settings/EditFinalizeZeroAmountInvoiceDialog.tsx @@ -0,0 +1,202 @@ +import { gql } from '@apollo/client' +import { useFormik } from 'formik' +import { forwardRef } from 'react' +import styled from 'styled-components' +import { object, string } from 'yup' + +import { Button, Dialog, DialogRef } from '~/components/designSystem' +import { ComboBoxField } from '~/components/form' +import { addToast } from '~/core/apolloClient' +import { + EditCustomerFinalizeZeroAmountInvoiceForDialogFragment, + EditOrganizationFinalizeZeroAmountInvoiceForDialogFragment, + FinalizeZeroAmountInvoiceEnum, + useUpdateCustomerFinalizeZeroAmountInvoiceMutation, + useUpdateOrganizationFinalizeZeroAmountInvoiceMutation, +} from '~/generated/graphql' +import { useInternationalization } from '~/hooks/core/useInternationalization' +import { theme } from '~/styles' + +gql` + fragment EditCustomerFinalizeZeroAmountInvoiceForDialog on Customer { + id + externalId + name + finalizeZeroAmountInvoice + } + + fragment EditOrganizationFinalizeZeroAmountInvoiceForDialog on CurrentOrganization { + id + finalizeZeroAmountInvoice + } + + mutation updateCustomerFinalizeZeroAmountInvoice($input: UpdateCustomerInput!) { + updateCustomer(input: $input) { + id + ...EditCustomerFinalizeZeroAmountInvoiceForDialog + } + } + + mutation updateOrganizationFinalizeZeroAmountInvoice($input: UpdateOrganizationInput!) { + updateOrganization(input: $input) { + id + ...EditOrganizationFinalizeZeroAmountInvoiceForDialog + } + } +` + +type EditFinalizeZeroAmountInvoiceDialogProps = { + entity?: + | EditCustomerFinalizeZeroAmountInvoiceForDialogFragment + | EditOrganizationFinalizeZeroAmountInvoiceForDialogFragment + | null + finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | boolean | null +} + +export interface EditFinalizeZeroAmountInvoiceDialogRef extends DialogRef {} + +export const EditFinalizeZeroAmountInvoiceDialog = forwardRef< + EditFinalizeZeroAmountInvoiceDialogRef, + EditFinalizeZeroAmountInvoiceDialogProps +>(({ entity, finalizeZeroAmountInvoice }: EditFinalizeZeroAmountInvoiceDialogProps, dialogRef) => { + const { translate } = useInternationalization() + + const [updateCustomerFinalizeZeroAmountInvoice] = + useUpdateCustomerFinalizeZeroAmountInvoiceMutation({ + onCompleted(res) { + if (res?.updateCustomer) { + addToast({ + severity: 'success', + translateKey: translate('text_1725549671288cyc585wdz35'), + }) + } + }, + }) + + const [updateOrganizationFinalizeZeroAmountInvoice] = + useUpdateOrganizationFinalizeZeroAmountInvoiceMutation({ + onCompleted(res) { + if (res?.updateOrganization) { + addToast({ + severity: 'success', + translateKey: translate('text_17255496712882bspi9zp0ii'), + }) + } + }, + }) + + const isCustomer = entity?.__typename === 'Customer' + + const initialValue = isCustomer + ? finalizeZeroAmountInvoice === FinalizeZeroAmountInvoiceEnum.Inherit + ? '' + : finalizeZeroAmountInvoice + : finalizeZeroAmountInvoice?.toString() + + const formikProps = useFormik({ + initialValues: { + finalizeZeroAmountInvoice: initialValue, + }, + validationSchema: object().shape({ + finalizeZeroAmountInvoice: string().required(), + }), + validateOnMount: true, + enableReinitialize: true, + onSubmit: async (values) => { + if (!values.finalizeZeroAmountInvoice) { + return + } + + if (isCustomer) { + return await updateCustomerFinalizeZeroAmountInvoice({ + variables: { + input: { + id: entity?.id || '', + externalId: entity.externalId, + name: entity?.name || '', + finalizeZeroAmountInvoice: + values?.finalizeZeroAmountInvoice as FinalizeZeroAmountInvoiceEnum, + }, + }, + }) + } + + return await updateOrganizationFinalizeZeroAmountInvoice({ + variables: { + input: { + finalizeZeroAmountInvoice: values.finalizeZeroAmountInvoice === 'true', + }, + }, + }) + }, + }) + + const comboBoxData = isCustomer + ? [ + { value: 'finalize', label: translate('text_1725549671287ancbf00edxx') }, + { value: 'skip', label: translate('text_1725549671288zkq9sr0y46l') }, + ] + : [ + { value: 'true', label: translate('text_1725549671287ancbf00edxx') }, + { value: 'false', label: translate('text_1725549671288zkq9sr0y46l') }, + ] + + return ( + { + formikProps.resetForm() + }} + actions={({ closeDialog }) => ( + <> + + + + )} + > + + + + + ) +}) + +const ContentWrapper = styled.div` + display: flex; + gap: ${theme.spacing(3)}; + margin-bottom: ${theme.spacing(8)}; + + > * { + flex: 1; + } + + ${theme.breakpoints.down('md')} { + flex-direction: column; + } +` + +EditFinalizeZeroAmountInvoiceDialog.displayName = 'forwardRef' diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index b1b454f36..f5d407e92 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -1094,6 +1094,7 @@ export type CreateCustomerInput = { email?: InputMaybe; externalId: Scalars['String']['input']; externalSalesforceId?: InputMaybe; + finalizeZeroAmountInvoice?: InputMaybe; integrationCustomers?: InputMaybe>; invoiceGracePeriod?: InputMaybe; legalName?: InputMaybe; @@ -1710,6 +1711,7 @@ export type CurrentOrganization = { email?: Maybe; emailSettings?: Maybe>; euTaxManagement: Scalars['Boolean']['output']; + finalizeZeroAmountInvoice: Scalars['Boolean']['output']; gocardlessPaymentProviders?: Maybe>; id: Scalars['ID']['output']; legalName?: Maybe; @@ -1772,6 +1774,8 @@ export type Customer = { email?: Maybe; externalId: Scalars['String']['output']; externalSalesforceId?: Maybe; + /** Options for handling invoices with a zero total amount. */ + finalizeZeroAmountInvoice?: Maybe; /** Define if a customer has an active wallet */ hasActiveWallet: Scalars['Boolean']['output']; /** Define if a customer has any credit note */ @@ -2324,6 +2328,12 @@ export type FinalizeInvoiceInput = { id: Scalars['ID']['input']; }; +export enum FinalizeZeroAmountInvoiceEnum { + Finalize = 'finalize', + Inherit = 'inherit', + Skip = 'skip' +} + export type FinalizedInvoiceCollection = { __typename?: 'FinalizedInvoiceCollection'; amountCents: Scalars['BigInt']['output']; @@ -5072,6 +5082,7 @@ export type UpdateCustomerInput = { email?: InputMaybe; externalId: Scalars['String']['input']; externalSalesforceId?: InputMaybe; + finalizeZeroAmountInvoice?: InputMaybe; id: Scalars['ID']['input']; integrationCustomers?: InputMaybe>; invoiceGracePeriod?: InputMaybe; @@ -5219,6 +5230,7 @@ export type UpdateOrganizationInput = { email?: InputMaybe; emailSettings?: InputMaybe>; euTaxManagement?: InputMaybe; + finalizeZeroAmountInvoice?: InputMaybe; legalName?: InputMaybe; legalNumber?: InputMaybe; logo?: InputMaybe; @@ -5766,7 +5778,7 @@ export type GetCustomerSettingsQueryVariables = Exact<{ }>; -export type GetCustomerSettingsQuery = { __typename?: 'Query', customer?: { __typename?: 'Customer', id: string, invoiceGracePeriod?: number | null, netPaymentTerm?: number | null, name?: string | null, externalId: string, billingConfiguration?: { __typename?: 'CustomerBillingConfiguration', id: string, documentLocale?: string | null } | null, taxes?: Array<{ __typename?: 'Tax', id: string, name: string, code: string, rate: number, autoGenerated: boolean }> | null } | null, organization?: { __typename?: 'CurrentOrganization', id: string, netPaymentTerm: number, billingConfiguration?: { __typename?: 'OrganizationBillingConfiguration', id: string, invoiceGracePeriod: number, documentLocale?: string | null } | null } | null }; +export type GetCustomerSettingsQuery = { __typename?: 'Query', customer?: { __typename?: 'Customer', id: string, invoiceGracePeriod?: number | null, netPaymentTerm?: number | null, finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | null, name?: string | null, externalId: string, billingConfiguration?: { __typename?: 'CustomerBillingConfiguration', id: string, documentLocale?: string | null } | null, taxes?: Array<{ __typename?: 'Tax', id: string, name: string, code: string, rate: number, autoGenerated: boolean }> | null } | null, organization?: { __typename?: 'CurrentOrganization', id: string, netPaymentTerm: number, finalizeZeroAmountInvoice: boolean, billingConfiguration?: { __typename?: 'OrganizationBillingConfiguration', id: string, invoiceGracePeriod: number, documentLocale?: string | null } | null } | null }; export type DeleteCustomerDialogFragment = { __typename?: 'Customer', id: string, name?: string | null }; @@ -5786,6 +5798,15 @@ export type DeleteCustomerDocumentLocaleMutationVariables = Exact<{ export type DeleteCustomerDocumentLocaleMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', id: string, billingConfiguration?: { __typename?: 'CustomerBillingConfiguration', id: string, documentLocale?: string | null } | null } | null }; +export type DeleteCustomerFinalizeZeroAmountInvoiceFragment = { __typename?: 'Customer', id: string, externalId: string, name?: string | null, finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | null }; + +export type DeleteCustomerFinalizeZeroAmountInvoiceMutationVariables = Exact<{ + input: UpdateCustomerInput; +}>; + + +export type DeleteCustomerFinalizeZeroAmountInvoiceMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', id: string, externalId: string, name?: string | null, finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | null } | null }; + export type DeleteCustomerGracePeriodFragment = { __typename?: 'Customer', id: string, name?: string | null }; export type DeleteCustomerGracePeriodMutationVariables = Exact<{ @@ -6311,6 +6332,24 @@ export type UpdateOrganizationDefaultCurrencyMutationVariables = Exact<{ export type UpdateOrganizationDefaultCurrencyMutation = { __typename?: 'Mutation', updateOrganization?: { __typename?: 'CurrentOrganization', id: string, defaultCurrency: CurrencyEnum } | null }; +export type EditCustomerFinalizeZeroAmountInvoiceForDialogFragment = { __typename?: 'Customer', id: string, externalId: string, name?: string | null, finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | null }; + +export type EditOrganizationFinalizeZeroAmountInvoiceForDialogFragment = { __typename?: 'CurrentOrganization', id: string, finalizeZeroAmountInvoice: boolean }; + +export type UpdateCustomerFinalizeZeroAmountInvoiceMutationVariables = Exact<{ + input: UpdateCustomerInput; +}>; + + +export type UpdateCustomerFinalizeZeroAmountInvoiceMutation = { __typename?: 'Mutation', updateCustomer?: { __typename?: 'Customer', id: string, externalId: string, name?: string | null, finalizeZeroAmountInvoice?: FinalizeZeroAmountInvoiceEnum | null } | null }; + +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutationVariables = Exact<{ + input: UpdateOrganizationInput; +}>; + + +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutation = { __typename?: 'Mutation', updateOrganization?: { __typename?: 'CurrentOrganization', id: string, finalizeZeroAmountInvoice: boolean } | null }; + export type EditCustomerNetPaymentTermForDialogFragment = { __typename?: 'Customer', id: string, externalId: string, name?: string | null, netPaymentTerm?: number | null }; export type EditOrganizationNetPaymentTermForDialogFragment = { __typename?: 'CurrentOrganization', id: string, netPaymentTerm: number }; @@ -7824,7 +7863,7 @@ export type GetOrganizationSettingsQueryVariables = Exact<{ }>; -export type GetOrganizationSettingsQuery = { __typename?: 'Query', organization?: { __typename?: 'CurrentOrganization', id: string, netPaymentTerm: number, defaultCurrency: CurrencyEnum, documentNumbering: DocumentNumberingEnum, documentNumberPrefix: string, billingConfiguration?: { __typename?: 'OrganizationBillingConfiguration', id: string, invoiceGracePeriod: number, invoiceFooter?: string | null, documentLocale?: string | null } | null } | null, taxes: { __typename?: 'TaxCollection', collection: Array<{ __typename?: 'Tax', id: string, name: string, code: string, rate: number, appliedToOrganization: boolean }> } }; +export type GetOrganizationSettingsQuery = { __typename?: 'Query', organization?: { __typename?: 'CurrentOrganization', id: string, netPaymentTerm: number, defaultCurrency: CurrencyEnum, documentNumbering: DocumentNumberingEnum, documentNumberPrefix: string, finalizeZeroAmountInvoice: boolean, billingConfiguration?: { __typename?: 'OrganizationBillingConfiguration', id: string, invoiceGracePeriod: number, invoiceFooter?: string | null, documentLocale?: string | null } | null } | null, taxes: { __typename?: 'TaxCollection', collection: Array<{ __typename?: 'Tax', id: string, name: string, code: string, rate: number, appliedToOrganization: boolean }> } }; export type LagoTaxManagementIntegrationsSettingQueryVariables = Exact<{ [key: string]: never; }>; @@ -8214,6 +8253,14 @@ export const DeleteCustomerDocumentLocaleFragmentDoc = gql` externalId } `; +export const DeleteCustomerFinalizeZeroAmountInvoiceFragmentDoc = gql` + fragment DeleteCustomerFinalizeZeroAmountInvoice on Customer { + id + externalId + name + finalizeZeroAmountInvoice +} + `; export const DeleteCustomerGracePeriodFragmentDoc = gql` fragment DeleteCustomerGracePeriod on Customer { id @@ -8528,6 +8575,20 @@ export const EditOrganizationDefaultCurrencyForDialogFragmentDoc = gql` defaultCurrency } `; +export const EditCustomerFinalizeZeroAmountInvoiceForDialogFragmentDoc = gql` + fragment EditCustomerFinalizeZeroAmountInvoiceForDialog on Customer { + id + externalId + name + finalizeZeroAmountInvoice +} + `; +export const EditOrganizationFinalizeZeroAmountInvoiceForDialogFragmentDoc = gql` + fragment EditOrganizationFinalizeZeroAmountInvoiceForDialog on CurrentOrganization { + id + finalizeZeroAmountInvoice +} + `; export const EditCustomerNetPaymentTermForDialogFragmentDoc = gql` fragment EditCustomerNetPaymentTermForDialog on Customer { id @@ -11622,6 +11683,7 @@ export const GetCustomerSettingsDocument = gql` id invoiceGracePeriod netPaymentTerm + finalizeZeroAmountInvoice billingConfiguration { id documentLocale @@ -11638,6 +11700,7 @@ export const GetCustomerSettingsDocument = gql` organization { id netPaymentTerm + finalizeZeroAmountInvoice billingConfiguration { id invoiceGracePeriod @@ -11756,6 +11819,40 @@ export function useDeleteCustomerDocumentLocaleMutation(baseOptions?: Apollo.Mut export type DeleteCustomerDocumentLocaleMutationHookResult = ReturnType; export type DeleteCustomerDocumentLocaleMutationResult = Apollo.MutationResult; export type DeleteCustomerDocumentLocaleMutationOptions = Apollo.BaseMutationOptions; +export const DeleteCustomerFinalizeZeroAmountInvoiceDocument = gql` + mutation deleteCustomerFinalizeZeroAmountInvoice($input: UpdateCustomerInput!) { + updateCustomer(input: $input) { + id + ...DeleteCustomerFinalizeZeroAmountInvoice + } +} + ${DeleteCustomerFinalizeZeroAmountInvoiceFragmentDoc}`; +export type DeleteCustomerFinalizeZeroAmountInvoiceMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteCustomerFinalizeZeroAmountInvoiceMutation__ + * + * To run a mutation, you first call `useDeleteCustomerFinalizeZeroAmountInvoiceMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteCustomerFinalizeZeroAmountInvoiceMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteCustomerFinalizeZeroAmountInvoiceMutation, { data, loading, error }] = useDeleteCustomerFinalizeZeroAmountInvoiceMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useDeleteCustomerFinalizeZeroAmountInvoiceMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteCustomerFinalizeZeroAmountInvoiceDocument, options); + } +export type DeleteCustomerFinalizeZeroAmountInvoiceMutationHookResult = ReturnType; +export type DeleteCustomerFinalizeZeroAmountInvoiceMutationResult = Apollo.MutationResult; +export type DeleteCustomerFinalizeZeroAmountInvoiceMutationOptions = Apollo.BaseMutationOptions; export const DeleteCustomerGracePeriodDocument = gql` mutation deleteCustomerGracePeriod($input: UpdateCustomerInvoiceGracePeriodInput!) { updateCustomerInvoiceGracePeriod(input: $input) { @@ -13913,6 +14010,74 @@ export function useUpdateOrganizationDefaultCurrencyMutation(baseOptions?: Apoll export type UpdateOrganizationDefaultCurrencyMutationHookResult = ReturnType; export type UpdateOrganizationDefaultCurrencyMutationResult = Apollo.MutationResult; export type UpdateOrganizationDefaultCurrencyMutationOptions = Apollo.BaseMutationOptions; +export const UpdateCustomerFinalizeZeroAmountInvoiceDocument = gql` + mutation updateCustomerFinalizeZeroAmountInvoice($input: UpdateCustomerInput!) { + updateCustomer(input: $input) { + id + ...EditCustomerFinalizeZeroAmountInvoiceForDialog + } +} + ${EditCustomerFinalizeZeroAmountInvoiceForDialogFragmentDoc}`; +export type UpdateCustomerFinalizeZeroAmountInvoiceMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateCustomerFinalizeZeroAmountInvoiceMutation__ + * + * To run a mutation, you first call `useUpdateCustomerFinalizeZeroAmountInvoiceMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateCustomerFinalizeZeroAmountInvoiceMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateCustomerFinalizeZeroAmountInvoiceMutation, { data, loading, error }] = useUpdateCustomerFinalizeZeroAmountInvoiceMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUpdateCustomerFinalizeZeroAmountInvoiceMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateCustomerFinalizeZeroAmountInvoiceDocument, options); + } +export type UpdateCustomerFinalizeZeroAmountInvoiceMutationHookResult = ReturnType; +export type UpdateCustomerFinalizeZeroAmountInvoiceMutationResult = Apollo.MutationResult; +export type UpdateCustomerFinalizeZeroAmountInvoiceMutationOptions = Apollo.BaseMutationOptions; +export const UpdateOrganizationFinalizeZeroAmountInvoiceDocument = gql` + mutation updateOrganizationFinalizeZeroAmountInvoice($input: UpdateOrganizationInput!) { + updateOrganization(input: $input) { + id + ...EditOrganizationFinalizeZeroAmountInvoiceForDialog + } +} + ${EditOrganizationFinalizeZeroAmountInvoiceForDialogFragmentDoc}`; +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateOrganizationFinalizeZeroAmountInvoiceMutation__ + * + * To run a mutation, you first call `useUpdateOrganizationFinalizeZeroAmountInvoiceMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateOrganizationFinalizeZeroAmountInvoiceMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateOrganizationFinalizeZeroAmountInvoiceMutation, { data, loading, error }] = useUpdateOrganizationFinalizeZeroAmountInvoiceMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUpdateOrganizationFinalizeZeroAmountInvoiceMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateOrganizationFinalizeZeroAmountInvoiceDocument, options); + } +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutationHookResult = ReturnType; +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutationResult = Apollo.MutationResult; +export type UpdateOrganizationFinalizeZeroAmountInvoiceMutationOptions = Apollo.BaseMutationOptions; export const UpdateCustomerNetPaymentTermDocument = gql` mutation updateCustomerNetPaymentTerm($input: UpdateCustomerInput!) { updateCustomer(input: $input) { @@ -21199,6 +21364,7 @@ export const GetOrganizationSettingsDocument = gql` defaultCurrency documentNumbering documentNumberPrefix + finalizeZeroAmountInvoice billingConfiguration { id invoiceGracePeriod diff --git a/src/pages/settings/InvoiceSettings.tsx b/src/pages/settings/InvoiceSettings.tsx index 69328c2bc..b7c824926 100644 --- a/src/pages/settings/InvoiceSettings.tsx +++ b/src/pages/settings/InvoiceSettings.tsx @@ -26,6 +26,10 @@ import { EditDefaultCurrencyDialog, EditDefaultCurrencyDialogRef, } from '~/components/settings/EditDefaultCurrencyDialog' +import { + EditFinalizeZeroAmountInvoiceDialog, + EditFinalizeZeroAmountInvoiceDialogRef, +} from '~/components/settings/EditFinalizeZeroAmountInvoiceDialog' import { EditNetPaymentTermDialog, EditNetPaymentTermDialogRef, @@ -98,6 +102,7 @@ gql` defaultCurrency documentNumbering documentNumberPrefix + finalizeZeroAmountInvoice billingConfiguration { id invoiceGracePeriod @@ -141,6 +146,8 @@ const InvoiceSettings = () => { const editDefaultCurrencyDialogRef = useRef(null) const editDocumentLanguageDialogRef = useRef(null) const editNetPaymentTermDialogRef = useRef(null) + const editFinalizeZeroAmountInvoiceDialogRef = + useRef(null) const premiumWarningDialogRef = useRef(null) const { data, error, loading } = useGetOrganizationSettingsQuery() const organization = data?.organization @@ -405,6 +412,40 @@ const InvoiceSettings = () => { )} + {/* Finalize empty invoice setting */} + + + {translate('text_1725549671287r9tnu5cuoeu')} + + + + + + {loading ? ( + <> + + + + ) : ( + <> + + {organization?.finalizeZeroAmountInvoice + ? translate('text_1725549671287ancbf00edxx') + : translate('text_1725549671288zkq9sr0y46l')} + + + {translate('text_1725549671288b1qz36o5hyb')} + + + )} + + {/* Tax */} {hasPermissions(['organizationTaxesView']) && ( <> @@ -519,6 +560,11 @@ const InvoiceSettings = () => { ref={editNetPaymentTermDialogRef} description={translate('text_64c7a89b6c67eb6c988980eb')} /> + diff --git a/translations/base.json b/translations/base.json index 7b8d8f6d8..80d843488 100644 --- a/translations/base.json +++ b/translations/base.json @@ -2381,5 +2381,24 @@ "text_1724346450317iqs2rtvx1tp": "Hello, I would like to access your progressive billing add-on. \n\nPlease let me know if you need more info!", "text_1724936123802q74m2xxak3o": "Usage already billed", "text_17250100329108guatmyl9tj": "Unknown", - "text_172535279177716n7p2svtdb": "Anrok synchronisation successfully triggered." + "text_172535279177716n7p2svtdb": "Anrok synchronisation successfully triggered.", + "text_17255383402002zmj6x02fx8": "Edit the empty invoice setting", + "text_1725538340200495slgen6ji": "Determines whether an invoice should be issued if the total amount before deductions is zero.", + "text_1725549671287r9tnu5cuoeu": "Empty invoice setting", + "text_1725549671287ancbf00edxx": "Finalize empty invoices", + "text_1725549671288zkq9sr0y46l": "Skip empty invoices", + "text_1725549671288b1qz36o5hyb": "Determines whether an invoice should be issued if the total amount before deductions is zero.", + "text_1725549671288cyc585wdz35": "Finalize empty invoices successfully deleted", + "text_17255496712882bspi9zp0ii": "Empty invoice setting successfully edited", + "text_1725549671288w2pu90s5rhq": "Edit invoicing rule", + "text_1725549671288gcrvgdn7rml": "Invoicing rule", + "text_1725549671288txz7z4m4qrf": "You’re about to delete the empty invoice setting rule", + "text_17255496712882gafqyniqpc": "After deleting this invoicing rule, {{customerName}}’s upcoming invoices will automatically adopt the one defined at the organization level. Are you sure to delete it?", + "text_1725550089200lfv6ug3vicm": "Add rule", + "text_17255500892002nq3iltm03z": "Issue empty invoices", + "text_17255500892009uqfqttms4w": "(from organization)", + "text_1725550089200omw15t6nh0h": "This customer inherits this rule from the general settings. Define whether to skip or finalize empty invoices for this customer.", + "text_1725550089200svn5rdwfftc": "Empty invoice setting is applied only for this customer.", + "text_1725550661207stz6kovtzkp": "Search or select an invoicing rule", + "text_1725550747818f6v0c35vzdk": "Add invoicing rule" }