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

Company tax overview #1029

Draft
wants to merge 18 commits into
base: develop
Choose a base branch
from
38 changes: 38 additions & 0 deletions src/common/interfaces/company.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface Company {
invoice_task_locked: boolean;
invoice_task_documents: boolean;
oauth_password_required: boolean;
origin_tax_data?: TaxData;
first_month_of_year: string;
company_key: string;
fill_products: boolean;
Expand Down Expand Up @@ -300,6 +301,43 @@ export interface Settings {
mark_paid_payment_email: boolean;
}

export interface TaxData{
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;
Expand Down
2 changes: 2 additions & 0 deletions src/common/interfaces/credit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -76,4 +77,5 @@ export interface Credit {
invitations: any[];
documents: any[];
client?: Client;
tax_info?: TaxData;
}
2 changes: 2 additions & 0 deletions src/common/interfaces/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface Invoice {
client?: Client;
activities?: Activity[];
payments?: Payment[];
tax_info?: TaxData;
reminder_schedule?: string;
}

Expand Down
2 changes: 2 additions & 0 deletions src/common/interfaces/purchase-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,6 +78,7 @@ export interface PurchaseOrder {
documents: any[];
vendor?: Vendor;
expense?: Expense;
tax_info?: TaxData;
}

export interface Invitation {
Expand Down
2 changes: 2 additions & 0 deletions src/common/interfaces/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import { Client } from './client';
import { TaxData } from './company.interface';
import { InvoiceItem } from './invoice-item';

export interface Quote {
Expand Down Expand Up @@ -77,4 +78,5 @@ export interface Quote {
invitations: any[];
documents: any[];
client?: Client;
tax_info?: TaxData;
}
2 changes: 2 additions & 0 deletions src/common/interfaces/recurring-invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import { Client } from './client';
import { TaxData } from './company.interface';
import { InvoiceItem } from './invoice-item';

export interface RecurringInvoice {
Expand Down Expand Up @@ -77,4 +78,5 @@ export interface RecurringInvoice {
invitations: any[];
documents: any[];
client?: Client;
tax_info?: TaxData;
}
116 changes: 116 additions & 0 deletions src/pages/invoices/common/components/InvoiceTaxDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* 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 { 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 { 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;
setIsModalOpen: Dispatch<SetStateAction<boolean>>;
resource: Invoice | RecurringInvoice;
}

export function InvoiceTaxDetails(props: Props) {
const [t] = useTranslation();

const navigate = useNavigate();
const setInvalidationQueryAtom = useSetAtom(invalidationQueryAtom);

const resolveCountry = useResolveCountry();
const updateClientTaxData = () => {
};

const hasInvalidAddress = () => {
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 }));
};

return (
<Modal
title="Invoice Tax Details"
visible={props.isModalOpen}
onClose={() => props.setIsModalOpen(false)}
backgroundColor="white"
size="regular"
>
<>
{props.resource.client && (

<div className="col-span-12 lg:col-span-3">
<p>{props.resource.client.display_name}</p>
<p>
{props.resource.client.address1.length > 0 && props.resource.client.address1}
{props.resource.client.address1.length > 0 && <br />}
{props.resource.client.address2}
</p>

<p>
{props.resource.client.city.length > 0 && props.resource.client.city} &nbsp;
{props.resource.client.postal_code.length > 0 && props.resource.client.postal_code} &nbsp;
{props.resource.client.state}
</p>

<p>{resolveCountry(props.resource.client.country_id)?.name}</p>

{!hasInvalidAddress() && props.resource.client &&(
<div className="flex flex-col">
<div className='flex justify-center items-center'>
<Button onClick={updateClientTaxData} className='mt-5 mb-5'>Refresh Client Tax Data</Button>
</div>
</div>
)}
</div>

)}

{hasInvalidAddress() && props.resource.client&& (
<>
<Divider />

<div className='flex flex-col items-center '>
<p className="text-center">Minimum required fields are Zip, City, State.</p>
<p className="text-center">For highest accuracy, also include a valid street address.</p>
<Button className='mt-5 mb-5' onClick={() => redirectToClientEdit(props.resource.client?.id ?? '')}>{t('edit_client')}</Button>
</div>
</>
)}

{!hasInvalidAddress() && props.resource.tax_info && (
<>
<Divider />

<div className="flex flex-col">
<EntityTaxData
entity={props.resource.tax_info}
/>
</div>
</>
)}

<Button onClick={() => props.setIsModalOpen(false)}>{t('close')}</Button>
</>
</Modal>
);
}
22 changes: 21 additions & 1 deletion src/pages/invoices/common/components/InvoiceTotals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -46,6 +50,8 @@ export function InvoiceTotals(props: Props) {

const [t] = useTranslation();

const [isModalVisible, setIsModalVisible] = useState<boolean>(false);

return (
<Card className="col-span-12 xl:col-span-4 h-max">
{variables.map(
Expand Down Expand Up @@ -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 && (
<>
<div className='flex justify-center items-center'>
<Button onClick={() => setIsModalVisible(true)} className='mt-5 mb-5'>{t('tax_details')}</Button>
</div>

<InvoiceTaxDetails
isModalOpen={isModalVisible}
setIsModalOpen={setIsModalVisible}
resource={props.resource as Invoice | RecurringInvoice}
/>
</>
)}
</Card>
);
}
26 changes: 22 additions & 4 deletions src/pages/settings/tax-settings/TaxSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();
Expand All @@ -40,9 +41,9 @@ export function TaxSettings() {

useInjectCompanyChanges();
useTitle('tax_settings');

const errors = useAtomValue(companySettingsErrorsAtom);

const [isModalVisible, setIsModalVisible] = useState<boolean>(false);

const dispatch = useDispatch();
const companyChanges = useCompanyChanges();

Expand Down Expand Up @@ -144,13 +145,30 @@ export function TaxSettings() {
<Divider />

<Element leftSide={t('calculate_taxes')}>
<div className="flex">
<Toggle
checked={companyChanges?.calculate_taxes}
onChange={(value: boolean) =>
handleToggleChange('calculate_taxes', value)
}
/>

{companyChanges.calculate_taxes && companyChanges.settings.country_id === '840' &&(

<div className="mx-5 my-1">
<Button type="minimal" behavior="button"
onClick={() => setIsModalVisible(true)}
>
{t('info')}</Button>
</div>
)}
</div>
</Element>

<CompanyTaxDetails
isModalOpen={isModalVisible}
setIsModalOpen={setIsModalVisible}
/>

{companyChanges.calculate_taxes && <CalculateTaxes />}
</>
Expand Down
Loading