From 8de7d286c814a77932e67686f9b49583cb261e12 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Jun 2023 16:08:12 +1000 Subject: [PATCH 01/16] Working on tax overview screens --- src/common/interfaces/company.interface.ts | 38 ++++++++++++++++++ .../settings/tax-settings/TaxSettings.tsx | 23 ++++++++++- .../components/CompanyTaxDetails.tsx | 40 +++++++++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx diff --git a/src/common/interfaces/company.interface.ts b/src/common/interfaces/company.interface.ts index 6d0695c107..d3653053bd 100644 --- a/src/common/interfaces/company.interface.ts +++ b/src/common/interfaces/company.interface.ts @@ -57,6 +57,7 @@ export interface Company { invoice_task_locked: boolean; invoice_task_documents: boolean; oauth_password_required: boolean; + origin_tax_data?: OriginTaxData; } export interface CompanyInput { @@ -295,6 +296,43 @@ export interface Settings { enable_e_invoice: boolean; } +export interface OriginTaxData{ + geoPostalCode: string; + geoCity: string; + geoCounty: string; + geoState: string; + taxSales: number; + taxUse: number; + txbService: null | "N" | "Y" | "L"; + txbFreight: null | "Y" | "N"; + stateSalesTax: number; + stateUseTax: number; + citySalesTax: number; + cityUseTax: number; + cityTaxCode: string; + countySalesTax: number; + countyUseTax: number; + countyTaxCode: string; + districtSalesTax: number; + districtUseTax: number; + district1Code: string; + district1SalesTax: number; + district1UseTax: number; + district2Code: string; + district2SalesTax: number; + district2UseTax: number; + district3Code: string; + district3SalesTax: number; + district3UseTax: number; + district4Code: string; + district4SalesTax: number; + district4UseTax: number; + district5Code: string; + district5SalesTax: number; + district5UseTax: number; + originDestination: null | "D" | "O"; +} + export interface TaxData { version: string; seller_subregion: string; diff --git a/src/pages/settings/tax-settings/TaxSettings.tsx b/src/pages/settings/tax-settings/TaxSettings.tsx index 5116614f11..252873bded 100644 --- a/src/pages/settings/tax-settings/TaxSettings.tsx +++ b/src/pages/settings/tax-settings/TaxSettings.tsx @@ -12,12 +12,12 @@ import { useCompanyChanges } from '$app/common/hooks/useCompanyChanges'; import { useInjectCompanyChanges } from '$app/common/hooks/useInjectCompanyChanges'; import { useTitle } from '$app/common/hooks/useTitle'; import { updateChanges } from '$app/common/stores/slices/company-users'; -import { ChangeEvent } from 'react'; +import { ChangeEvent, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { TaxRates } from '..'; import { Card, Element } from '../../../components/cards'; -import { SelectField } from '../../../components/forms'; +import { Button, SelectField } from '../../../components/forms'; import Toggle from '$app/components/forms/Toggle'; import { Settings } from '../../../components/layouts/Settings'; import { useDiscardChanges } from '../common/hooks/useDiscardChanges'; @@ -27,6 +27,7 @@ import { Divider } from '$app/components/cards/Divider'; import { usePaidOrSelfHost } from '$app/common/hooks/usePaidOrSelfhost'; import { CalculateTaxes } from './components/calculate-taxes/CalculateTaxes'; import { useCalculateTaxesRegion } from '$app/common/hooks/useCalculateTaxesRegion'; +import { CompanyTaxDetails } from './components/CompanyTaxDetails'; export function TaxSettings() { const [t] = useTranslation(); @@ -38,6 +39,8 @@ export function TaxSettings() { useInjectCompanyChanges(); useTitle('tax_settings'); + + const [isModalVisible, setIsModalVisible] = useState(false); const dispatch = useDispatch(); const companyChanges = useCompanyChanges(); @@ -137,13 +140,29 @@ export function TaxSettings() { +
handleToggleChange('calculate_taxes', value) } /> + + {companyChanges.calculate_taxes && companyChanges.settings.country_id === '840' &&( + +
+ +
+ )} +
+ + {companyChanges.calculate_taxes && } diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx new file mode 100644 index 0000000000..610218da32 --- /dev/null +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -0,0 +1,40 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; +import { Button } from '$app/components/forms'; +import { Modal } from '$app/components/Modal'; +import { SetStateAction, Dispatch } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; + +interface Props { + isModalOpen: boolean; + setIsModalOpen: Dispatch>; +} + +export function CompanyTaxDetails(props: Props) { + const [t] = useTranslation(); + + const company = useCurrentCompany(); + + return ( + props.setIsModalOpen(false)} + backgroundColor="white" + > + + + + + ); +} \ No newline at end of file From ebcf568e3d97ec7fb141d1bd601ce6ecafd8ddd0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Jun 2023 16:08:50 +1000 Subject: [PATCH 02/16] Working on tax overview screens --- src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 610218da32..08482855d1 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -13,7 +13,6 @@ import { Button } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; interface Props { isModalOpen: boolean; From aebfba59b78ce17eb8d076cddab973367b40747c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Jun 2023 16:09:02 +1000 Subject: [PATCH 03/16] Working on tax overview screens --- .../settings/tax-settings/components/CompanyTaxDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 08482855d1..1f3af66fe9 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -22,7 +22,7 @@ interface Props { export function CompanyTaxDetails(props: Props) { const [t] = useTranslation(); - const company = useCurrentCompany(); + // const company = useCurrentCompany(); return ( Date: Thu, 29 Jun 2023 16:09:17 +1000 Subject: [PATCH 04/16] Working on tax overview screens --- .../settings/tax-settings/components/CompanyTaxDetails.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 1f3af66fe9..8b644ffec7 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -8,7 +8,6 @@ * @license https://www.elastic.co/licensing/elastic-license */ -import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { Button } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; @@ -22,8 +21,6 @@ interface Props { export function CompanyTaxDetails(props: Props) { const [t] = useTranslation(); - // const company = useCurrentCompany(); - return ( Date: Wed, 9 Aug 2023 12:16:06 +1000 Subject: [PATCH 05/16] Merge develop --- src/pages/settings/tax-settings/TaxSettings.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/tax-settings/TaxSettings.tsx b/src/pages/settings/tax-settings/TaxSettings.tsx index 47224cc850..59b728e7b8 100644 --- a/src/pages/settings/tax-settings/TaxSettings.tsx +++ b/src/pages/settings/tax-settings/TaxSettings.tsx @@ -29,6 +29,7 @@ import { CalculateTaxes } from './components/calculate-taxes/CalculateTaxes'; import { useCalculateTaxesRegion } from '$app/common/hooks/useCalculateTaxesRegion'; import { useAtomValue } from 'jotai'; import { companySettingsErrorsAtom } from '../common/atoms'; +import { CompanyTaxDetails } from './components/CompanyTaxDetails'; export function TaxSettings() { const [t] = useTranslation(); @@ -41,7 +42,8 @@ export function TaxSettings() { useInjectCompanyChanges(); useTitle('tax_settings'); const errors = useAtomValue(companySettingsErrorsAtom); - + const [isModalVisible, setIsModalVisible] = useState(false); + const dispatch = useDispatch(); const companyChanges = useCompanyChanges(); From 31573a0d827e567ebad0dd59d437c9a0430e6e8e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 12:43:42 +1000 Subject: [PATCH 06/16] Updates for showing tax details --- .../components/CompanyTaxDetails.tsx | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 8b644ffec7..aa9bc34fef 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -8,11 +8,14 @@ * @license https://www.elastic.co/licensing/elastic-license */ +import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { Button } from '$app/components/forms'; +import { Card, Element } from '$app/components/cards'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; + interface Props { isModalOpen: boolean; setIsModalOpen: Dispatch>; @@ -21,6 +24,13 @@ interface Props { export function CompanyTaxDetails(props: Props) { const [t] = useTranslation(); + const company = useCurrentCompany(); + + const formatTaxRate = (rate: number) => { + + return rate*100 === 0 ? '0%' : `${(rate * 100).toFixed(3)}%`; + }; + return ( props.setIsModalOpen(false)} backgroundColor="white" > - + {company.origin_tax_data && ( +
+ + {company.origin_tax_data.geoPostalCode} + + + {company.origin_tax_data.geoCity} + + + {company.origin_tax_data.geoCounty} + + + {company.origin_tax_data.geoState} + + + {formatTaxRate(company.origin_tax_data.stateSalesTax)} + + + {formatTaxRate(company.origin_tax_data.citySalesTax)} + + + {formatTaxRate(company.origin_tax_data.countySalesTax)} + + + {formatTaxRate(company.origin_tax_data.taxSales)} + + + {company.origin_tax_data.originDestination === 'D' ? 'Destination' : 'Origin'} + + + {company.origin_tax_data.txbService === 'Y' ? 'Yes' : 'No'} + + + {company.origin_tax_data.txbFreight === 'Y' ? 'Yes' : 'No'} + +
+ )}
From 67eae431be4ef91735023dca4d0a602a1544021a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 12:50:01 +1000 Subject: [PATCH 07/16] Updates for showing tax details --- .../settings/tax-settings/components/CompanyTaxDetails.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index aa9bc34fef..96431f158c 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -10,7 +10,7 @@ import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { Button } from '$app/components/forms'; -import { Card, Element } from '$app/components/cards'; +import { Element } from '$app/components/cards'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,7 +38,10 @@ export function CompanyTaxDetails(props: Props) { onClose={() => props.setIsModalOpen(false)} backgroundColor="white" > - {company.origin_tax_data && ( + {company.origin_tax_data?.geoPostalCode === undefined && ( +
+ )} + {company.origin_tax_data?.geoPostalCode && (
Date: Wed, 9 Aug 2023 15:48:11 +1000 Subject: [PATCH 08/16] Tax info --- src/common/interfaces/company.interface.ts | 4 +- .../settings/tax-settings/TaxSettings.tsx | 3 +- .../components/CompanyTaxDetails.tsx | 143 ++++++++---------- .../calculate-taxes/CompanyDetails.tsx | 35 +++++ .../calculate-taxes/EntityTaxData.tsx | 104 +++++++++++++ 5 files changed, 207 insertions(+), 82 deletions(-) create mode 100644 src/pages/settings/tax-settings/components/calculate-taxes/CompanyDetails.tsx create mode 100644 src/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData.tsx diff --git a/src/common/interfaces/company.interface.ts b/src/common/interfaces/company.interface.ts index 6fd019f285..eb8dac2055 100644 --- a/src/common/interfaces/company.interface.ts +++ b/src/common/interfaces/company.interface.ts @@ -57,7 +57,7 @@ export interface Company { invoice_task_locked: boolean; invoice_task_documents: boolean; oauth_password_required: boolean; - origin_tax_data?: OriginTaxData; + origin_tax_data?: TaxData; first_month_of_year: string; company_key: string; fill_products: boolean; @@ -300,7 +300,7 @@ export interface Settings { mark_paid_payment_email: boolean; } -export interface OriginTaxData{ +export interface TaxData{ geoPostalCode: string; geoCity: string; geoCounty: string; diff --git a/src/pages/settings/tax-settings/TaxSettings.tsx b/src/pages/settings/tax-settings/TaxSettings.tsx index 59b728e7b8..d30458574d 100644 --- a/src/pages/settings/tax-settings/TaxSettings.tsx +++ b/src/pages/settings/tax-settings/TaxSettings.tsx @@ -158,7 +158,8 @@ export function TaxSettings() {
+ > + {t('info')}
)}
diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 96431f158c..65704a6c97 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -9,12 +9,21 @@ */ import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; -import { Button } from '$app/components/forms'; -import { Element } from '$app/components/cards'; +import { Button, Link } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; - +import { endpoint } from '$app/common/helpers'; +import { toast } from '$app/common/helpers/toast/toast'; +import { request } from '$app/common/helpers/request'; +import { CompanyDetails } from './calculate-taxes/CompanyDetails'; +import { Divider } from 'antd'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useDispatch } from 'react-redux'; +import { updateRecord } from '$app/common/stores/slices/company-users'; +import { EntityTaxData } from './calculate-taxes/EntityTaxData'; +import { GenericValidationBag } from '$app/common/interfaces/validation-bag'; +import { LoginValidation } from '$app/pages/authentication/common/ValidationInterface'; interface Props { isModalOpen: boolean; @@ -25,10 +34,30 @@ export function CompanyTaxDetails(props: Props) { const [t] = useTranslation(); const company = useCurrentCompany(); - - const formatTaxRate = (rate: number) => { - return rate*100 === 0 ? '0%' : `${(rate * 100).toFixed(3)}%`; + const dispatch = useDispatch(); + + const updateCompanyTaxData = () => { + toast.processing(); + + request( + 'POST', + endpoint('/api/v1/companies/updateOriginTaxData/:id', { + id: company.id, + }), + ).then((response: AxiosResponse) => { + + toast.success('success'); + + dispatch( + updateRecord({ object: 'company', data: response.data.data }) + ); + + }).catch( + (error: AxiosError>) => { + toast.error(error.response?.data?.message as string); + } + ); }; return ( @@ -37,80 +66,36 @@ export function CompanyTaxDetails(props: Props) { visible={props.isModalOpen} onClose={() => props.setIsModalOpen(false)} backgroundColor="white" + size="regular" > - {company.origin_tax_data?.geoPostalCode === undefined && ( -
- )} - {company.origin_tax_data?.geoPostalCode && ( -
- - {company.origin_tax_data.geoPostalCode} - - - {company.origin_tax_data.geoCity} - - - {company.origin_tax_data.geoCounty} - - - {company.origin_tax_data.geoState} - - - {formatTaxRate(company.origin_tax_data.stateSalesTax)} - - - {formatTaxRate(company.origin_tax_data.citySalesTax)} - - - {formatTaxRate(company.origin_tax_data.countySalesTax)} - - - {formatTaxRate(company.origin_tax_data.taxSales)} - - - {company.origin_tax_data.originDestination === 'D' ? 'Destination' : 'Origin'} - - - {company.origin_tax_data.txbService === 'Y' ? 'Yes' : 'No'} - - - {company.origin_tax_data.txbFreight === 'Y' ? 'Yes' : 'No'} - -
- )} + + + + + {company.origin_tax_data?.geoPostalCode === undefined || company.origin_tax_data?.geoPostalCode === "" && ( +
+

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

+ + + + +
+ )} + {company.origin_tax_data?.geoPostalCode && ( +
+ +
+ +
+
+ )} + + +
diff --git a/src/pages/settings/tax-settings/components/calculate-taxes/CompanyDetails.tsx b/src/pages/settings/tax-settings/components/calculate-taxes/CompanyDetails.tsx new file mode 100644 index 0000000000..fd06cb5f4a --- /dev/null +++ b/src/pages/settings/tax-settings/components/calculate-taxes/CompanyDetails.tsx @@ -0,0 +1,35 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { useCountries } from '$app/common/hooks/useCountries'; +import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; +import { useTranslation } from 'react-i18next'; +import { Element } from '$app/components/cards'; + +export function CompanyDetails() { + + const company = useCurrentCompany(); + + const countries = useCountries(); + + const [t] = useTranslation(); + + return ( +
+ {company.settings.name} + {company.settings.address1} + {company.settings.address2} + {`${company.settings.city} ${company.settings?.city.length > 1 ? ', ' : ''} ${company.settings.postal_code}`} + {`${company.settings.state} ${company.settings?.state.length > 1 ? ', ' : ''} + ${countries.find((country) => country.id === company.settings?.country_id)?.name}`} + +
+ ); +} \ No newline at end of file diff --git a/src/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData.tsx b/src/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData.tsx new file mode 100644 index 0000000000..3360a2fbf5 --- /dev/null +++ b/src/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData.tsx @@ -0,0 +1,104 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { Element } from '$app/components/cards'; +import { useTranslation } from 'react-i18next'; + +import { TaxData } from '$app/common/interfaces/company.interface'; + +interface Props { + entity: TaxData +} + +export function EntityTaxData(props: Props) { + + const [t] = useTranslation(); + + const formatTaxRate = (rate: number) => { + return rate * 100 === 0 ? '0%' : `${(rate * 100).toFixed(3)}%`; + }; + + return ( +
+ + {props.entity.geoState} + + + {props.entity.geoCounty} + + + {props.entity.geoCity} + + + {props.entity.geoPostalCode} + + + {formatTaxRate(props.entity.stateSalesTax)} + + + {formatTaxRate(props.entity.countySalesTax)} + + + {formatTaxRate(props.entity.citySalesTax)} + + + {formatTaxRate(props.entity.districtSalesTax)} + + + {formatTaxRate(props.entity.taxSales)} + + + {props.entity.originDestination === 'D' ? 'Destination' : 'Origin'} + + + {props.entity.txbService === 'Y' ? 'Yes' : 'No'} + + + {props.entity.txbFreight === 'Y' ? 'Yes' : 'No'} + +
+ ); +} \ No newline at end of file From c7500db3d974a630c54aff22cef2132bacf127c9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 15:56:34 +1000 Subject: [PATCH 09/16] Clean up for validation logic --- .../tax-settings/components/CompanyTaxDetails.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx index 65704a6c97..7ef0251ba8 100644 --- a/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx +++ b/src/pages/settings/tax-settings/components/CompanyTaxDetails.tsx @@ -60,6 +60,10 @@ export function CompanyTaxDetails(props: Props) { ); }; + const hasInvalidAddress = () => { + return company.settings?.postal_code === "" || company.settings?.city === "" || company.settings?.state === "" + }; + return ( - {company.origin_tax_data?.geoPostalCode === undefined || company.origin_tax_data?.geoPostalCode === "" && ( + {company.origin_tax_data?.geoPostalCode === undefined || + company.origin_tax_data?.geoPostalCode === "" || + hasInvalidAddress() + && (

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

)} - {company.origin_tax_data?.geoPostalCode && ( + {company.origin_tax_data && !hasInvalidAddress() && (
Date: Wed, 9 Aug 2023 16:50:27 +1000 Subject: [PATCH 10/16] Pad Invoice Tax Details --- src/common/interfaces/invoice.ts | 2 + src/common/interfaces/purchase-order.ts | 2 + src/common/interfaces/recurring-invoice.ts | 2 + .../common/components/InvoiceTaxDetails.tsx | 109 ++++++++++++++++++ .../common/components/InvoiceTotals.tsx | 22 +++- 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/pages/invoices/common/components/InvoiceTaxDetails.tsx diff --git a/src/common/interfaces/invoice.ts b/src/common/interfaces/invoice.ts index 7b5c490a77..6777e8bd86 100644 --- a/src/common/interfaces/invoice.ts +++ b/src/common/interfaces/invoice.ts @@ -8,6 +8,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ import { Client } from './client'; +import { TaxData } from './company.interface'; import { InvoiceItem } from './invoice-item'; import { Payment } from './payment'; import { Invitation } from './purchase-order'; @@ -80,6 +81,7 @@ export interface Invoice { client?: Client; activities?: Activity[]; payments?: Payment[]; + tax_info?: TaxData; } export interface Activity { diff --git a/src/common/interfaces/purchase-order.ts b/src/common/interfaces/purchase-order.ts index 41d630f89b..22e09ed116 100644 --- a/src/common/interfaces/purchase-order.ts +++ b/src/common/interfaces/purchase-order.ts @@ -8,6 +8,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ +import { TaxData } from './company.interface'; import { Expense } from './expense'; import { InvoiceItem } from './invoice-item'; import { Vendor } from './vendor'; @@ -77,6 +78,7 @@ export interface PurchaseOrder { documents: any[]; vendor?: Vendor; expense?: Expense; + tax_info?: TaxData; } export interface Invitation { diff --git a/src/common/interfaces/recurring-invoice.ts b/src/common/interfaces/recurring-invoice.ts index bd01ef319e..1d31f556a0 100644 --- a/src/common/interfaces/recurring-invoice.ts +++ b/src/common/interfaces/recurring-invoice.ts @@ -9,6 +9,7 @@ */ import { Client } from './client'; +import { TaxData } from './company.interface'; import { InvoiceItem } from './invoice-item'; export interface RecurringInvoice { @@ -77,4 +78,5 @@ export interface RecurringInvoice { invitations: any[]; documents: any[]; client?: Client; + tax_info?: TaxData; } diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx new file mode 100644 index 0000000000..eb7e8d021e --- /dev/null +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -0,0 +1,109 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; +import { Button } from '$app/components/forms'; +import { Modal } from '$app/components/Modal'; +import { SetStateAction, Dispatch } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Divider } from 'antd'; +import { ProductTableResource } from './ProductsTable'; +import { Client } from '$app/common/interfaces/client'; +import { Address } from '$app/pages/clients/show/components/Address'; +import { Invoice } from '$app/common/interfaces/invoice'; +import { RecurringInvoice } from '$app/common/interfaces/recurring-invoice'; +import { useResolveCountry } from '$app/common/hooks/useResolveCountry'; +import { EntityTaxData } from '$app/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData'; + +interface Props { + isModalOpen: boolean; + setIsModalOpen: Dispatch>; + resource: Invoice | RecurringInvoice; +} + +export function InvoiceTaxDetails(props: Props) { + const [t] = useTranslation(); + + const company = useCurrentCompany(); + const resolveCountry = useResolveCountry(); + + const hasInvalidAddress = () => { + return props.resource?.client?.postal_code === "" || props.resource?.client?.city === "" || props.resource?.client?.state === "" + }; + + return ( + props.setIsModalOpen(false)} + backgroundColor="white" + size="regular" + > + {props.resource.client && ( + <> +
+

{props.resource.client.display_name}

+

+ {props.resource.client.address1.length > 0 && props.resource.client.address1} + {props.resource.client.address1.length > 0 &&
} + {props.resource.client.address2} +

+ +

+ {props.resource.client.city.length > 0 && props.resource.client.city}   + {props.resource.client.postal_code.length > 0 && props.resource.client.postal_code}   + {props.resource.client.state} +

+ +

{resolveCountry(props.resource.client.country_id)?.name}

+
+ + )} + + + {props.resource.tax_info && ( +
+ +
+ )} + {/* {props.resource.tax_info === undefined || + company.origin_tax_data?.geoPostalCode === "" || + hasInvalidAddress() + && ( +
+

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

+ + + + +
+ )} + {company.origin_tax_data && !hasInvalidAddress() && ( +
+ +
+ +
+
+ )} */} + + {/* */} + + + +
+ ); +} \ No newline at end of file diff --git a/src/pages/invoices/common/components/InvoiceTotals.tsx b/src/pages/invoices/common/components/InvoiceTotals.tsx index be2fb89093..693173d0d9 100644 --- a/src/pages/invoices/common/components/InvoiceTotals.tsx +++ b/src/pages/invoices/common/components/InvoiceTotals.tsx @@ -11,7 +11,7 @@ import { Card, Element } from '$app/components/cards'; import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { TaxRate } from '$app/common/interfaces/tax-rate'; -import { Fragment } from 'react'; +import { Fragment, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useResolveTotalVariable } from '../hooks/useResolveTotalVariable'; import { useTotalVariables } from '../hooks/useTotalVariables'; @@ -21,6 +21,10 @@ import { InvoiceSum } from '$app/common/helpers/invoices/invoice-sum'; import { ProductTableResource, RelationType } from './ProductsTable'; import { InvoiceSumInclusive } from '$app/common/helpers/invoices/invoice-sum-inclusive'; import { Entry } from '$app/components/forms/Combobox'; +import { Button } from 'antd'; +import { InvoiceTaxDetails } from './InvoiceTaxDetails'; +import { Invoice } from '$app/common/interfaces/invoice'; +import { RecurringInvoice } from '$app/common/interfaces/recurring-invoice'; interface Props { resource: ProductTableResource; @@ -46,6 +50,8 @@ export function InvoiceTotals(props: Props) { const [t] = useTranslation(); + const [isModalVisible, setIsModalVisible] = useState(false); + return ( {variables.map( @@ -158,6 +164,20 @@ export function InvoiceTotals(props: Props) { onValueChange={(value) => handleChange('custom_surcharge4', value)} /> )} + + {props.resource && props.resource.tax_info && props.resource.client_id && ( + <> +
+ +
+ + + + )}
); } From 6ebbe2ec9bb41df8dfd28dca9884c6d56c92cd46 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 16:55:21 +1000 Subject: [PATCH 11/16] Pad Invoice Tax Details --- .../common/components/InvoiceTaxDetails.tsx | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx index eb7e8d021e..107f6631dc 100644 --- a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -9,14 +9,11 @@ */ import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; -import { Button } from '$app/components/forms'; +import { Button, Link } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; import { Divider } from 'antd'; -import { ProductTableResource } from './ProductsTable'; -import { Client } from '$app/common/interfaces/client'; -import { Address } from '$app/pages/clients/show/components/Address'; import { Invoice } from '$app/common/interfaces/invoice'; import { RecurringInvoice } from '$app/common/interfaces/recurring-invoice'; import { useResolveCountry } from '$app/common/hooks/useResolveCountry'; @@ -33,6 +30,8 @@ export function InvoiceTaxDetails(props: Props) { const company = useCurrentCompany(); const resolveCountry = useResolveCountry(); + const updateClientTaxData = () => { + }; const hasInvalidAddress = () => { return props.resource?.client?.postal_code === "" || props.resource?.client?.city === "" || props.resource?.client?.state === "" @@ -63,11 +62,20 @@ export function InvoiceTaxDetails(props: Props) {

{resolveCountry(props.resource.client.country_id)?.name}

+ + {!hasInvalidAddress() && props.resource.client &&( +
+
+ +
+
+ )}
)} + {props.resource.tax_info && (
)} - {/* {props.resource.tax_info === undefined || - company.origin_tax_data?.geoPostalCode === "" || - hasInvalidAddress() - && ( -
-

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

- - - + {hasInvalidAddress() && props.resource.client && ( +
+

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

+ + + -
- )} - {company.origin_tax_data && !hasInvalidAddress() && ( -
- -
- -
- )} */} - - {/* */} + )} From 0a204e8a537d99a2293883350ed995b3ff9bd257 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 16:56:00 +1000 Subject: [PATCH 12/16] Pad Invoice Tax Details --- src/pages/invoices/common/components/InvoiceTaxDetails.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx index 107f6631dc..18130bad27 100644 --- a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -8,7 +8,6 @@ * @license https://www.elastic.co/licensing/elastic-license */ -import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { Button, Link } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; @@ -28,7 +27,6 @@ interface Props { export function InvoiceTaxDetails(props: Props) { const [t] = useTranslation(); - const company = useCurrentCompany(); const resolveCountry = useResolveCountry(); const updateClientTaxData = () => { }; From fc61b1367b62f7d7c5f802e709106375bbbc2f65 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 17:02:10 +1000 Subject: [PATCH 13/16] Pad Invoice Tax Details --- src/common/interfaces/credit.ts | 2 ++ src/common/interfaces/quote.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/common/interfaces/credit.ts b/src/common/interfaces/credit.ts index dc4b1330c1..5e018a2c05 100644 --- a/src/common/interfaces/credit.ts +++ b/src/common/interfaces/credit.ts @@ -8,6 +8,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ import { Client } from './client'; +import { TaxData } from './company.interface'; import { InvoiceItem } from './invoice-item'; export interface Credit { @@ -76,4 +77,5 @@ export interface Credit { invitations: any[]; documents: any[]; client?: Client; + tax_info?: TaxData; } diff --git a/src/common/interfaces/quote.ts b/src/common/interfaces/quote.ts index f9f15ae84a..46dd780498 100644 --- a/src/common/interfaces/quote.ts +++ b/src/common/interfaces/quote.ts @@ -9,6 +9,7 @@ */ import { Client } from './client'; +import { TaxData } from './company.interface'; import { InvoiceItem } from './invoice-item'; export interface Quote { @@ -77,4 +78,5 @@ export interface Quote { invitations: any[]; documents: any[]; client?: Client; + tax_info?: TaxData; } From bdf898fbfe1fd85296dd625983cfc62cba6d19b4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 17:58:07 +1000 Subject: [PATCH 14/16] Tax decorator --- .../common/components/InvoiceTaxDetails.tsx | 50 +++++++++++++------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx index 18130bad27..c3c1834c76 100644 --- a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -17,6 +17,10 @@ import { Invoice } from '$app/common/interfaces/invoice'; import { RecurringInvoice } from '$app/common/interfaces/recurring-invoice'; import { useResolveCountry } from '$app/common/hooks/useResolveCountry'; import { EntityTaxData } from '$app/pages/settings/tax-settings/components/calculate-taxes/EntityTaxData'; +import { route } from '$app/common/helpers/route'; +import { invalidationQueryAtom } from '$app/common/atoms/data-table'; +import { useSetAtom } from 'jotai'; +import { useNavigate } from 'react-router-dom'; interface Props { isModalOpen: boolean; @@ -27,6 +31,9 @@ interface Props { export function InvoiceTaxDetails(props: Props) { const [t] = useTranslation(); + const navigate = useNavigate(); + const setInvalidationQueryAtom = useSetAtom(invalidationQueryAtom); + const resolveCountry = useResolveCountry(); const updateClientTaxData = () => { }; @@ -35,6 +42,13 @@ export function InvoiceTaxDetails(props: Props) { return props.resource?.client?.postal_code === "" || props.resource?.client?.city === "" || props.resource?.client?.state === "" }; + const redirectToClientEdit = (id: string) => { + setInvalidationQueryAtom(route('/api/v1/invoices')); + navigate(route('/clients/:id/edit', { id: id })); + }; + + console.log(props.resource.client); + return ( - {props.resource.client && ( <> + {props.resource.client && ( +

{props.resource.client.display_name}

@@ -69,32 +84,35 @@ export function InvoiceTaxDetails(props: Props) {

)}
- + )} - + {hasInvalidAddress() && props.resource.client&& ( + <> + + +
+

Minimum required fields are Zip, City, State.

+

For highest accuracy, also include a valid street address.

+ +
+ + )} - {props.resource.tax_info && ( + {!hasInvalidAddress() && props.resource.tax_info && ( + <> + +
- )} - {hasInvalidAddress() && props.resource.client && ( -
-

Minimum required fields are Zip, City, State. For highest accuracy, also include a valid street address.

- - - - -
+ )} - + ); } \ No newline at end of file From 43f850f70afa281bfef2e721bb75c8a210c7f58c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 17:58:38 +1000 Subject: [PATCH 15/16] Tax decorator --- src/pages/invoices/common/components/InvoiceTaxDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx index c3c1834c76..4240606882 100644 --- a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -8,7 +8,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ -import { Button, Link } from '$app/components/forms'; +import { Button } from '$app/components/forms'; import { Modal } from '$app/components/Modal'; import { SetStateAction, Dispatch } from 'react'; import { useTranslation } from 'react-i18next'; From 33c354f330e812d961fd260d799dbcda77e74d8d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 9 Aug 2023 18:22:12 +1000 Subject: [PATCH 16/16] Remove logging --- src/pages/invoices/common/components/InvoiceTaxDetails.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx index 4240606882..f2bd3185b5 100644 --- a/src/pages/invoices/common/components/InvoiceTaxDetails.tsx +++ b/src/pages/invoices/common/components/InvoiceTaxDetails.tsx @@ -47,8 +47,6 @@ export function InvoiceTaxDetails(props: Props) { navigate(route('/clients/:id/edit', { id: id })); }; - console.log(props.resource.client); - return (