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: customer dunning settings #1823

Merged
merged 11 commits into from
Nov 12, 2024
63 changes: 30 additions & 33 deletions src/components/billableMetrics/CustomExpressionDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { Button, Chip, Drawer, DrawerRef, Icon } from '~/components/designSystem'
import { JsonEditorField } from '~/components/form/JsonEditor'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { Divider } from '~/styles/mainObjectsForm'

type CustomExpressionDrawerState = {
expression?: string | null
Expand Down Expand Up @@ -174,39 +173,37 @@ export const CustomExpressionDrawer = forwardRef<
{translate('text_1729771640162c0o1estqusi')}
</Typography>

<JsonEditorField
name="expression"
disabled={!localData?.isEditable}
label={translate('text_17297736554164pkbpqi0ke8')}
editorMode="text"
customInvalidError={translate('text_1729864793151rrlucly2t6d')}
showHelperOnError={true}
formikProps={formikProps}
placeholder={translate('text_1729771640162kaf49b93e20') + '\n'}
helperText={
<div className="mt-1">
<Typography className="text-sm font-normal text-grey-600">
{translate('text_1729773655417n5w5fu02lbm')}
</Typography>

<div className="mt-1 flex flex-col items-start gap-1">
{CUSTOM_EXPRESSION_EXAMPLES.map((example, index) => (
<Chip
key={`ce-drawer-${index}`}
className="!px-2 !py-0.5"
size="small"
variant="captionCode"
color="grey600"
label={example}
/>
))}
<div className="mb-12 pb-12 shadow-b">
<JsonEditorField
name="expression"
disabled={!localData?.isEditable}
label={translate('text_17297736554164pkbpqi0ke8')}
editorMode="text"
customInvalidError={translate('text_1729864793151rrlucly2t6d')}
showHelperOnError={true}
formikProps={formikProps}
placeholder={translate('text_1729771640162kaf49b93e20') + '\n'}
helperText={
<div className="mt-1">
<Typography className="text-sm font-normal text-grey-600">
{translate('text_1729773655417n5w5fu02lbm')}
</Typography>

<div className="mt-1 flex flex-col items-start gap-1">
{CUSTOM_EXPRESSION_EXAMPLES.map((example, index) => (
<Chip
key={`ce-drawer-${index}`}
className="!px-2 !py-0.5"
size="small"
variant="captionCode"
color="grey600"
label={example}
/>
))}
</div>
</div>
</div>
}
/>

<div className="my-12">
<Divider />
}
/>
</div>

<div className="mb-6">
Expand Down
142 changes: 141 additions & 1 deletion src/components/customers/CustomerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@ import {
EditCustomerVatRateDialog,
EditCustomerVatRateDialogRef,
} from '~/components/customers/EditCustomerVatRateDialog'
import { Avatar, Button, Icon, Popper, Table, Tooltip, Typography } from '~/components/designSystem'
import {
Avatar,
Button,
Chip,
Icon,
Popper,
Table,
TableColumn,
Tooltip,
Typography,
} from '~/components/designSystem'
import { GenericPlaceholder } from '~/components/GenericPlaceholder'
import { PremiumWarningDialog, PremiumWarningDialogRef } from '~/components/PremiumWarningDialog'
import {
Expand All @@ -28,16 +38,20 @@ import {
DeleteCustomerGracePeriodFragmentDoc,
DeleteCustomerNetPaymentTermFragmentDoc,
EditCustomerDocumentLocaleFragmentDoc,
EditCustomerDunningCampaignFragmentDoc,
EditCustomerInvoiceGracePeriodFragmentDoc,
EditCustomerVatRateFragmentDoc,
FinalizeZeroAmountInvoiceEnum,
PremiumIntegrationTypeEnum,
useGetCustomerSettingsQuery,
} from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import { useCurrentUser } from '~/hooks/useCurrentUser'
import { useOrganizationInfos } from '~/hooks/useOrganizationInfos'
import { usePermissions } from '~/hooks/usePermissions'
import ErrorImage from '~/public/images/maneki/error.svg'
import { MenuPopper } from '~/styles'
import { tw } from '~/styles/utils'

import {
DeleteCustomerDocumentLocaleDialog,
Expand All @@ -59,6 +73,10 @@ import {
EditCustomerDocumentLocaleDialog,
EditCustomerDocumentLocaleDialogRef,
} from './EditCustomerDocumentLocaleDialog'
import {
EditCustomerDunningCampaignDialog,
EditCustomerDunningCampaignDialogRef,
} from './EditCustomerDunningCampaignDialog'
import {
EditCustomerInvoiceGracePeriodDialog,
EditCustomerInvoiceGracePeriodDialogRef,
Expand All @@ -84,6 +102,20 @@ gql`
}
}

fragment CustomerAppliedDunningCampaignForSettings on Customer {
currency
appliedDunningCampaign {
id
appliedToOrganization
code
name
thresholds {
currency
}
}
excludeFromDunningCampaign
}

query getCustomerSettings($id: ID!) {
customer(id: $id) {
id
Expand All @@ -97,9 +129,11 @@ gql`
}

...CustomerAppliedTaxRatesForSettings
...CustomerAppliedDunningCampaignForSettings

...EditCustomerVatRate
...EditCustomerDocumentLocale
...EditCustomerDunningCampaign
...EditCustomerInvoiceGracePeriod
...DeleteCustomerGracePeriod
...DeleteCustomerDocumentLocale
Expand All @@ -116,12 +150,22 @@ gql`
invoiceGracePeriod
documentLocale
}
appliedDunningCampaign {
id
name
code
appliedToOrganization
thresholds {
currency
}
}
}
}

${EditCustomerVatRateFragmentDoc}
${EditCustomerInvoiceGracePeriodFragmentDoc}
${EditCustomerDocumentLocaleFragmentDoc}
${EditCustomerDunningCampaignFragmentDoc}
${DeleteCustomerGracePeriodFragmentDoc}
${DeleteCustomerDocumentLocaleFragmentDoc}
${CustomerForDeleteVatRateDialogFragmentDoc}
Expand All @@ -136,6 +180,7 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => {
const { translate } = useInternationalization()
const { isPremium } = useCurrentUser()
const { hasPermissions } = usePermissions()
const { organization: { premiumIntegrations } = {} } = useOrganizationInfos()
const { data, loading, error } = useGetCustomerSettingsQuery({
variables: { id: customerId as string },
skip: !customerId,
Expand All @@ -147,6 +192,7 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => {
const editInvoiceGracePeriodDialogRef = useRef<EditCustomerInvoiceGracePeriodDialogRef>(null)
const deleteGracePeriodDialogRef = useRef<DeleteCustomerGracePeriodeDialogRef>(null)
const editCustomerDocumentLocale = useRef<EditCustomerDocumentLocaleDialogRef>(null)
const editCustomerDunningCampaignDialogRef = useRef<EditCustomerDunningCampaignDialogRef>(null)
const deleteCustomerDocumentLocale = useRef<DeleteCustomerDocumentLocaleDialogRef>(null)
const premiumWarningDialogRef = useRef<PremiumWarningDialogRef>(null)
const editNetPaymentTermDialogRef = useRef<EditNetPaymentTermDialogRef>(null)
Expand All @@ -170,6 +216,16 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => {
)
}

const hasAutoDunningIntegration = premiumIntegrations?.includes(
PremiumIntegrationTypeEnum.AutoDunning,
)
const dunningCampaign =
customer?.appliedDunningCampaign ?? organization?.appliedDunningCampaign ?? undefined

const isDunningCampaignApplicable =
!!dunningCampaign &&
!!dunningCampaign?.thresholds.some((threshold) => threshold.currency === customer?.currency)

return (
<>
<SettingsPaddedContainer className="max-w-full px-0 md:px-0">
Expand Down Expand Up @@ -254,6 +310,86 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => {
</Typography>
</SettingsListItem>

{/* Dunnings campaign */}
{hasAutoDunningIntegration && (
<SettingsListItem
className={tw(
isDunningCampaignApplicable &&
!customer?.excludeFromDunningCampaign &&
'shadow-inherit',
)}
>
<SettingsListItemHeader
label={translate('text_1728584028187fg2ebhssz6r')}
sublabel={translate('text_1729541146351qyno3mh09gi')}
action={
<Button
disabled={loading}
variant="quaternary"
onClick={() => editCustomerDunningCampaignDialogRef?.current?.openDialog()}
>
{translate('text_63e51ef4985f0ebd75c212fc')}
</Button>
}
/>

{!!dunningCampaign && !customer?.excludeFromDunningCampaign ? (
isDunningCampaignApplicable ? (
<Table
name="customer-dunnings-settings"
containerSize={{ default: 0 }}
rowSize={72}
isLoading={loading}
data={[dunningCampaign]}
columns={[
{
key: 'name',
title: translate('text_1729542024833rpf3nsekh42'),
maxSpace: true,
content: ({ name, code }) => (
<div className="flex flex-1 items-center gap-3" data-test={code}>
<Avatar size="big" variant="connector">
<Icon size="medium" name="coin-dollar" color="dark" />
</Avatar>
<div>
<Typography color="textSecondary" variant="bodyHl" noWrap>
{name}
</Typography>
<Typography variant="caption" noWrap>
{code}
</Typography>
</div>
</div>
),
},
...(!customer?.appliedDunningCampaign
? [
{
key: 'appliedToOrganization',
title: translate('text_63ac86d797f728a87b2f9fa7'),
content: () => (
<Chip label={translate('text_1729542098338prhjz7s29kt')} />
),
} as TableColumn<{
appliedToOrganization: boolean
}>,
]
: []),
]}
/>
) : (
<Typography variant="body" color="grey700">
{translate('text_17295411491091t7ii66l5ex')}
</Typography>
)
) : (
<Typography variant="body" color="grey700">
{translate('text_1729541149109r8u8nlsu75e')}
</Typography>
)}
</SettingsListItem>
)}

{/* Finalize empty invoice setting */}
<SettingsListItem>
<SettingsListItemHeader
Expand Down Expand Up @@ -608,6 +744,10 @@ export const CustomerSettings = ({ customerId }: CustomerSettingsProps) => {
invoiceGracePeriod={customer?.invoiceGracePeriod}
/>
<EditCustomerDocumentLocaleDialog ref={editCustomerDocumentLocale} customer={customer} />
<EditCustomerDunningCampaignDialog
ref={editCustomerDunningCampaignDialogRef}
customer={customer}
/>
<DeleteCustomerGracePeriodeDialog ref={deleteGracePeriodDialogRef} customer={customer} />
<DeleteCustomerDocumentLocaleDialog
ref={deleteCustomerDocumentLocale}
Expand Down
Loading
Loading