feat: assign default sell/purchase tax rates to items (#261)

This commit is contained in:
Ahmed Bouhuolia
2023-10-08 23:55:59 +02:00
committed by GitHub
parent d40de4d22b
commit 1ed1c9ea1d
25 changed files with 400 additions and 18 deletions

View File

@@ -149,6 +149,11 @@ export default class ItemsController extends BaseController {
.trim() .trim()
.escape() .escape()
.isLength({ max: DATATYPES_LENGTH.TEXT }), .isLength({ max: DATATYPES_LENGTH.TEXT }),
check('sell_tax_rate_id').optional({ nullable: true }).isInt().toInt(),
check('purchase_tax_rate_id')
.optional({ nullable: true })
.isInt()
.toInt(),
check('category_id') check('category_id')
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 }) .isInt({ min: 0, max: DATATYPES_LENGTH.INT_10 })
@@ -508,6 +513,28 @@ export default class ItemsController extends BaseController {
], ],
}); });
} }
if (error.errorType === 'PURCHASE_TAX_RATE_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'PURCHASE_TAX_RATE_NOT_FOUND',
message: 'Purchase tax rate has not found.',
code: 410,
},
],
});
}
if (error.errorType === 'SELL_TAX_RATE_NOT_FOUND') {
return res.status(400).send({
errors: [
{
type: 'SELL_TAX_RATE_NOT_FOUND',
message: 'Sell tax rate is not found.',
code: 420,
},
],
});
}
} }
next(error); next(error);
} }

View File

@@ -0,0 +1,18 @@
exports.up = (knex) => {
return knex.schema.table('items', (table) => {
table
.integer('sell_tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
table
.integer('purchase_tax_rate_id')
.unsigned()
.references('id')
.inTable('tax_rates');
});
};
exports.down = (knex) => {
return knex.schema.dropTableIfExists('tax_rates');
};

View File

@@ -22,6 +22,9 @@ export interface IItem {
sellDescription: string; sellDescription: string;
purchaseDescription: string; purchaseDescription: string;
sellTaxRateId: number;
purchaseTaxRateId: number;
quantityOnHand: number; quantityOnHand: number;
note: string; note: string;
@@ -54,6 +57,9 @@ export interface IItemDTO {
sellDescription: string; sellDescription: string;
purchaseDescription: string; purchaseDescription: string;
sellTaxRateId: number;
purchaseTaxRateId: number;
quantityOnHand: number; quantityOnHand: number;
note: string; note: string;

View File

@@ -36,6 +36,7 @@ export interface ITaxRateCreatedPayload {
} }
export interface ITaxRateEditingPayload { export interface ITaxRateEditingPayload {
oldTaxRate: ITaxRate;
editTaxRateDTO: IEditTaxRateDTO; editTaxRateDTO: IEditTaxRateDTO;
tenantId: number; tenantId: number;
trx: Knex.Transaction; trx: Knex.Transaction;

View File

@@ -83,6 +83,7 @@ import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscr
import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber'; import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber';
import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber'; import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber';
import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber'; import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber';
import { SyncItemTaxRateOnEditTaxSubscriber } from '@/services/TaxRates/SyncItemTaxRateOnEditTaxSubscriber';
export default () => { export default () => {
return new EventPublisher(); return new EventPublisher();
@@ -197,5 +198,7 @@ export const susbcribers = () => {
// Tax Rates - Bills // Tax Rates - Bills
BillTaxRateValidateSubscriber, BillTaxRateValidateSubscriber,
WriteBillTaxTransactionsSubscriber, WriteBillTaxTransactionsSubscriber,
SyncItemTaxRateOnEditTaxSubscriber
]; ];
}; };

View File

@@ -65,6 +65,7 @@ export default class Item extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const WarehouseTransferEntry = require('models/WarehouseTransferEntry'); const WarehouseTransferEntry = require('models/WarehouseTransferEntry');
const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry'); const InventoryAdjustmentEntry = require('models/InventoryAdjustmentEntry');
const TaxRate = require('models/TaxRate');
return { return {
/** /**
@@ -178,11 +179,35 @@ export default class Item extends mixin(TenantModel, [
to: 'media.id', to: 'media.id',
}, },
}, },
/**
* Item may has sell tax rate.
*/
sellTaxRate: {
relation: Model.BelongsToOneRelation,
modelClass: TaxRate.default,
join: {
from: 'items.sellTaxRateId',
to: 'tax_rates.id',
},
},
/**
* Item may has purchase tax rate.
*/
purchaseTaxRate: {
relation: Model.BelongsToOneRelation,
modelClass: TaxRate.default,
join: {
from: 'items.purchaseTaxRateId',
to: 'tax_rates.id',
},
},
}; };
} }
/** /**
* *
*/ */
static get secureDeleteRelations() { static get secureDeleteRelations() {
return [ return [

View File

@@ -55,6 +55,18 @@ export class CreateItem {
itemDTO.inventoryAccountId itemDTO.inventoryAccountId
); );
} }
if (itemDTO.purchaseTaxRateId) {
await this.validators.validatePurchaseTaxRateExistance(
tenantId,
itemDTO.purchaseTaxRateId
);
}
if (itemDTO.sellTaxRateId) {
await this.validators.validateSellTaxRateExistance(
tenantId,
itemDTO.sellTaxRateId
);
}
} }
/** /**

View File

@@ -76,6 +76,20 @@ export class EditItem {
itemDTO.inventoryAccountId itemDTO.inventoryAccountId
); );
} }
// Validate the purchase tax rate id existance.
if (itemDTO.purchaseTaxRateId) {
await this.validators.validatePurchaseTaxRateExistance(
tenantId,
itemDTO.purchaseTaxRateId
);
}
// Validate the sell tax rate id existance.
if (itemDTO.sellTaxRateId) {
await this.validators.validateSellTaxRateExistance(
tenantId,
itemDTO.sellTaxRateId
);
}
// Validate inventory account should be modified in inventory item // Validate inventory account should be modified in inventory item
// has inventory transactions. // has inventory transactions.
await this.validators.validateItemInvnetoryAccountModified( await this.validators.validateItemInvnetoryAccountModified(

View File

@@ -27,6 +27,8 @@ export class GetItem {
.withGraphFetched('category') .withGraphFetched('category')
.withGraphFetched('costAccount') .withGraphFetched('costAccount')
.withGraphFetched('itemWarehouses.warehouse') .withGraphFetched('itemWarehouses.warehouse')
.withGraphFetched('sellTaxRate')
.withGraphFetched('purchaseTaxRate')
.throwIfNotFound(); .throwIfNotFound();
return this.transformer.transform(tenantId, item, new ItemTransformer()); return this.transformer.transform(tenantId, item, new ItemTransformer());

View File

@@ -241,4 +241,40 @@ export class ItemsValidators {
throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE); throw new ServiceError(ERRORS.ITEM_CANNOT_CHANGE_INVENTORY_TYPE);
} }
} }
/**
* Validate the purchase tax rate id existance.
* @param {number} tenantId -
* @param {number} taxRateId -
*/
public async validatePurchaseTaxRateExistance(
tenantId: number,
taxRateId: number
) {
const { TaxRate } = this.tenancy.models(tenantId);
const foundTaxRate = await TaxRate.query().findById(taxRateId);
if (!foundTaxRate) {
throw new ServiceError(ERRORS.PURCHASE_TAX_RATE_NOT_FOUND);
}
}
/**
* Validate the sell tax rate id existance.
* @param {number} tenantId
* @param {number} taxRateId
*/
public async validateSellTaxRateExistance(
tenantId: number,
taxRateId: number
) {
const { TaxRate } = this.tenancy.models(tenantId);
const foundTaxRate = await TaxRate.query().findById(taxRateId);
if (!foundTaxRate) {
throw new ServiceError(ERRORS.SELL_TAX_RATE_NOT_FOUND);
}
}
} }

View File

@@ -22,7 +22,10 @@ export const ERRORS = {
TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS', TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED', INVENTORY_ACCOUNT_CANNOT_MODIFIED: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS' ITEM_HAS_ASSOCIATED_TRANSACTIONS: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
PURCHASE_TAX_RATE_NOT_FOUND: 'PURCHASE_TAX_RATE_NOT_FOUND',
SELL_TAX_RATE_NOT_FOUND: 'SELL_TAX_RATE_NOT_FOUND',
}; };
export const DEFAULT_VIEW_COLUMNS = []; export const DEFAULT_VIEW_COLUMNS = [];

View File

@@ -115,6 +115,7 @@ export class EditTaxRateService {
// Triggers `onTaxRateEdited` event. // Triggers `onTaxRateEdited` event.
await this.eventPublisher.emitAsync(events.taxRates.onEdited, { await this.eventPublisher.emitAsync(events.taxRates.onEdited, {
editTaxRateDTO, editTaxRateDTO,
oldTaxRate,
taxRate, taxRate,
tenantId, tenantId,
trx, trx,

View File

@@ -0,0 +1,55 @@
import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
import HasTenancyService from '../Tenancy/TenancyService';
@Service()
export class SyncItemTaxRateOnEditTaxRate {
@Inject()
private tenancy: HasTenancyService;
/**
* Syncs the new tax rate created to item default sell tax rate.
* @param {number} tenantId
* @param {number} itemId
* @param {number} sellTaxRateId
*/
public updateItemSellTaxRate = async (
tenantId: number,
oldSellTaxRateId: number,
sellTaxRateId: number,
trx?: Knex.Transaction
) => {
const { Item } = this.tenancy.models(tenantId);
// Can't continue if the old and new sell tax rate id are equal.
if (oldSellTaxRateId === sellTaxRateId) return;
await Item.query().where('sellTaxRateId', oldSellTaxRateId).update({
sellTaxRateId,
});
};
/**
* Syncs the new tax rate created to item default purchase tax rate.
* @param {number} tenantId
* @param {number} itemId
* @param {number} purchaseTaxRateId
*/
public updateItemPurchaseTaxRate = async (
tenantId: number,
oldPurchaseTaxRateId: number,
purchaseTaxRateId: number,
trx?: Knex.Transaction
) => {
const { Item } = this.tenancy.models(tenantId);
// Can't continue if the old and new sell tax rate id are equal.
if (oldPurchaseTaxRateId === purchaseTaxRateId) return;
await Item.query(trx)
.where('purchaseTaxRateId', oldPurchaseTaxRateId)
.update({
purchaseTaxRateId,
});
};
}

View File

@@ -0,0 +1,45 @@
import { Inject, Service } from 'typedi';
import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
import events from '@/subscribers/events';
import { ITaxRateEditedPayload } from '@/interfaces';
import { runAfterTransaction } from '../UnitOfWork/TransactionsHooks';
@Service()
export class SyncItemTaxRateOnEditTaxSubscriber {
@Inject()
private syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate;
/**
* Attaches events with handles.
*/
public attach(bus) {
bus.subscribe(
events.taxRates.onEdited,
this.handleSyncNewTaxRateToItemTaxRate
);
}
/**
* Syncs the new tax rate created to default item tax rates.
* @param {ITaxRateEditedPayload} payload -
*/
private handleSyncNewTaxRateToItemTaxRate = async ({
taxRate,
tenantId,
oldTaxRate,
trx,
}: ITaxRateEditedPayload) => {
runAfterTransaction(trx, async () => {
await this.syncItemRateOnEdit.updateItemPurchaseTaxRate(
tenantId,
oldTaxRate.id,
taxRate.id
);
await this.syncItemRateOnEdit.updateItemSellTaxRate(
tenantId,
oldTaxRate.id,
taxRate.id
);
});
};
}

View File

@@ -0,0 +1,62 @@
// @ts-nocheck
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { FSelect } from '@/components';
import { DialogsName } from '@/constants/dialogs';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { MenuItem } from '@blueprintjs/core';
// Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => {
return (
<MenuItem
icon="add"
text={intl.get('list.create', { value: `"${query}"` })}
active={active}
onClick={handleClick}
/>
);
};
// Create new item from the given query string.
const createNewItemFromQuery = (name) => ({ name });
/**
* Tax rates select field binded with Formik form.
* @returns {JSX.Element}
*/
function TaxRatesSelectRoot({
// #withDialogActions
openDialog,
// #ownProps
allowCreate,
...restProps
}) {
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDialog(DialogsName.TaxRateForm);
};
return (
<FSelect
valueAccessor={'id'}
labelAccessor={'code'}
textAccessor={'name_formatted'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...restProps}
/>
);
}
export const TaxRatesSelect = R.compose(withDialogActions)(TaxRatesSelectRoot);

View File

@@ -67,6 +67,14 @@ export default function ItemDetailHeader() {
label={intl.get('cost_account_id')} label={intl.get('cost_account_id')}
children={defaultTo(item.cost_account?.name, '-')} children={defaultTo(item.cost_account?.name, '-')}
/> />
<DetailItem
label={intl.get('item.details.sell_tax_rate')}
children={item?.sell_tax_rate?.name}
/>
<DetailItem
label={intl.get('item.details.purchase_tax_rate')}
children={item?.purchase_tax_rate?.name}
/>
<If condition={item.type === 'inventory'}> <If condition={item.type === 'inventory'}>
<DetailItem <DetailItem
label={intl.get('inventory_account')} label={intl.get('inventory_account')}

View File

@@ -8,9 +8,9 @@ import { DataTableEditable } from '@/components';
import { useEditableItemsEntriesColumns } from './components'; import { useEditableItemsEntriesColumns } from './components';
import { import {
useFetchItemRow, useFetchItemRow,
composeRowsOnNewRow,
useComposeRowsOnEditTableCell, useComposeRowsOnEditTableCell,
useComposeRowsOnRemoveTableRow, useComposeRowsOnRemoveTableRow,
useComposeRowsOnNewRow,
} from './utils'; } from './utils';
import { import {
ItemEntriesTableProvider, ItemEntriesTableProvider,
@@ -61,6 +61,7 @@ function ItemEntriesTableRoot() {
currencyCode, currencyCode,
landedCost, landedCost,
taxRates, taxRates,
itemType,
} = useItemEntriesTableContext(); } = useItemEntriesTableContext();
// Editiable items entries columns. // Editiable items entries columns.
@@ -68,11 +69,12 @@ function ItemEntriesTableRoot() {
const composeRowsOnEditCell = useComposeRowsOnEditTableCell(); const composeRowsOnEditCell = useComposeRowsOnEditTableCell();
const composeRowsOnDeleteRow = useComposeRowsOnRemoveTableRow(); const composeRowsOnDeleteRow = useComposeRowsOnRemoveTableRow();
const composeRowsOnNewRow = useComposeRowsOnNewRow();
// Handle the fetch item row details. // Handle the fetch item row details.
const { setItemRow, cellsLoading, isItemFetching } = useFetchItemRow({ const { setItemRow, cellsLoading, isItemFetching } = useFetchItemRow({
landedCost, landedCost,
itemType: null, itemType,
notifyNewRow: (newRow, rowIndex) => { notifyNewRow: (newRow, rowIndex) => {
// Update the rate, description and quantity data of the row. // Update the rate, description and quantity data of the row.
const newRows = composeRowsOnNewRow(rowIndex, newRow, localValue); const newRows = composeRowsOnNewRow(rowIndex, newRow, localValue);

View File

@@ -1,5 +1,5 @@
// @ts-nocheck // @ts-nocheck
import React, { useCallback } from 'react'; import React, { useCallback, useMemo } from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { sumBy, isEmpty, last, keyBy, groupBy } from 'lodash'; import { sumBy, isEmpty, last, keyBy, groupBy } from 'lodash';
import { useItem } from '@/hooks/query'; import { useItem } from '@/hooks/query';
@@ -116,19 +116,20 @@ export function useFetchItemRow({ landedCost, itemType, notifyNewRow }) {
? item.purchase_description ? item.purchase_description
: item.sell_description; : item.sell_description;
// Detarmines whether the landed cost checkbox should be disabled.
const landedCostDisabled = isLandedCostDisabled(item);
const taxRateId = const taxRateId =
itemType === ITEM_TYPE.PURCHASABLE itemType === ITEM_TYPE.PURCHASABLE
? item.purchase_tax_rate_id ? item.purchase_tax_rate_id
: item.sell_tax_rate_id; : item.sell_tax_rate_id;
// Detarmines whether the landed cost checkbox should be disabled.
const landedCostDisabled = isLandedCostDisabled(item);
// The new row. // The new row.
const newRow = { const newRow = {
rate: price, rate: price,
description, description,
quantity: 1, quantity: 1,
tax_rate_id: taxRateId,
...(landedCost ...(landedCost
? { ? {
landed_cost: false, landed_cost: false,
@@ -164,13 +165,21 @@ export const composeRowsOnEditCell = R.curry(
/** /**
* Compose table rows when insert a new row to table rows. * Compose table rows when insert a new row to table rows.
*/ */
export const composeRowsOnNewRow = R.curry((rowIndex, newRow, rows) => { export const useComposeRowsOnNewRow = () => {
return compose( const { taxRates, isInclusiveTax } = useItemEntriesTableContext();
orderingLinesIndexes,
updateItemsEntriesTotal, return React.useMemo(() => {
updateTableRow(rowIndex, newRow), return R.curry((rowIndex, newRow, rows) => {
)(rows); return compose(
}); assignEntriesTaxAmount(isInclusiveTax),
assignEntriesTaxRate(taxRates),
orderingLinesIndexes,
updateItemsEntriesTotal,
updateTableRow(rowIndex, newRow),
)(rows);
});
}, [isInclusiveTax, taxRates]);
};
/** /**
* Associate tax rate to entries. * Associate tax rate to entries.

View File

@@ -29,14 +29,16 @@ import {
costPriceFieldShouldUpdate, costPriceFieldShouldUpdate,
costAccountFieldShouldUpdate, costAccountFieldShouldUpdate,
purchaseDescFieldShouldUpdate, purchaseDescFieldShouldUpdate,
taxRateFieldShouldUpdate,
} from './utils'; } from './utils';
import { compose, inputIntent } from '@/utils'; import { compose, inputIntent } from '@/utils';
import { TaxRatesSelect } from '@/components/TaxRates/TaxRatesSelect';
/** /**
* Item form body. * Item form body.
*/ */
function ItemFormBody({ organization: { base_currency } }) { function ItemFormBody({ organization: { base_currency } }) {
const { accounts } = useItemFormContext(); const { accounts, taxRates } = useItemFormContext();
const { values } = useFormikContext(); const { values } = useFormikContext();
return ( return (
@@ -111,7 +113,20 @@ function ItemFormBody({ organization: { base_currency } }) {
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]} filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
fill={true} fill={true}
allowCreate={true} allowCreate={true}
fastField={true} fastField={true}
/>
</FFormGroup>
{/*------------- Sell Tax Rate ------------- */}
<FFormGroup
name={'sell_tax_rate_id'}
label={'Tax Rate'}
inline={true}
>
<TaxRatesSelect
name={'sell_tax_rate_id'}
items={taxRates}
allowCreate
/> />
</FFormGroup> </FFormGroup>
@@ -213,6 +228,24 @@ function ItemFormBody({ organization: { base_currency } }) {
/> />
</FFormGroup> </FFormGroup>
{/*------------- Purchase Tax Rate ------------- */}
<FFormGroup
name={'purchase_tax_rate_id'}
label={'Tax Rate'}
inline={true}
fastField={true}
shouldUpdateDeps={{ taxRates }}
shouldUpdate={taxRateFieldShouldUpdate}
>
<TaxRatesSelect
name={'purchase_tax_rate_id'}
items={taxRates}
allowCreate={true}
fastField={true}
shouldUpdateDeps={{ taxRates }}
/>
</FFormGroup>
<FastField <FastField
name={'purchase_description'} name={'purchase_description'}
purchasable={values.purchasable} purchasable={values.purchasable}

View File

@@ -10,6 +10,7 @@ import {
useAccounts, useAccounts,
} from '@/hooks/query'; } from '@/hooks/query';
import { useWatchItemError } from './utils'; import { useWatchItemError } from './utils';
import { useTaxRates } from '@/hooks/query/taxRates';
const ItemFormContext = createContext(); const ItemFormContext = createContext();
@@ -30,6 +31,8 @@ function ItemFormProvider({ itemId, ...props }) {
data: { itemsCategories }, data: { itemsCategories },
} = useItemsCategories(); } = useItemsCategories();
const { data: taxRates, isLoading: isTaxRatesLoading } = useTaxRates();
// Fetches the given item details. // Fetches the given item details.
const itemQuery = useItem(itemId || duplicateId, { const itemQuery = useItem(itemId || duplicateId, {
enabled: !!itemId || !!duplicateId, enabled: !!itemId || !!duplicateId,
@@ -69,6 +72,7 @@ function ItemFormProvider({ itemId, ...props }) {
accounts, accounts,
item, item,
itemsCategories, itemsCategories,
taxRates,
submitPayload, submitPayload,
isNewMode, isNewMode,
@@ -76,6 +80,7 @@ function ItemFormProvider({ itemId, ...props }) {
isAccountsLoading, isAccountsLoading,
isItemsCategoriesLoading, isItemsCategoriesLoading,
isItemLoading, isItemLoading,
isTaxRatesLoading,
createItemMutate, createItemMutate,
editItemMutate, editItemMutate,

View File

@@ -23,12 +23,14 @@ const defaultInitialValues = {
sell_price: '', sell_price: '',
cost_account_id: '', cost_account_id: '',
sell_account_id: '', sell_account_id: '',
sell_tax_rate_id: '',
inventory_account_id: '', inventory_account_id: '',
category_id: '', category_id: '',
sellable: 1, sellable: 1,
purchasable: true, purchasable: true,
sell_description: '', sell_description: '',
purchase_description: '', purchase_description: '',
purchase_tax_rate_id: '',
}; };
/** /**
@@ -187,6 +189,13 @@ export const purchaseDescFieldShouldUpdate = (newProps, oldProps) => {
); );
}; };
export const taxRateFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.shouldUpdateDeps.taxRates !== oldProps.shouldUpdateDeps.taxRates ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export function transformItemsTableState(tableState) { export function transformItemsTableState(tableState) {
return { return {
...transformTableStateToQuery(tableState), ...transformTableStateToQuery(tableState),

View File

@@ -5,6 +5,7 @@ import ItemsEntriesTable from '@/containers/Entries/ItemsEntriesTable';
import { useInvoiceFormContext } from './InvoiceFormProvider'; import { useInvoiceFormContext } from './InvoiceFormProvider';
import { entriesFieldShouldUpdate } from './utils'; import { entriesFieldShouldUpdate } from './utils';
import { TaxType } from '@/interfaces/TaxRates'; import { TaxType } from '@/interfaces/TaxRates';
import { ITEM_TYPE } from '@/containers/Entries/utils';
/** /**
* Invoice items entries editor field. * Invoice items entries editor field.
@@ -31,6 +32,7 @@ export default function InvoiceItemsEntriesEditorField() {
}} }}
items={items} items={items}
taxRates={taxRates} taxRates={taxRates}
itemType={ITEM_TYPE.SELLABLE}
errors={error} errors={error}
linesNumber={4} linesNumber={4}
currencyCode={values.currency_code} currencyCode={values.currency_code}

View File

@@ -1,7 +1,7 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { DialogContent } from '@/components'; import { DialogContent } from '@/components';
import { useTaxRate, useTaxRates } from '@/hooks/query/taxRates'; import { useTaxRate } from '@/hooks/query/taxRates';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
const TaxRateFormDialogContext = React.createContext(); const TaxRateFormDialogContext = React.createContext();

View File

@@ -59,6 +59,8 @@ export function useEditTaxRate(props) {
onSuccess: (res, id) => { onSuccess: (res, id) => {
commonInvalidateQueries(queryClient); commonInvalidateQueries(queryClient);
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]); queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
queryClient.invalidateQueries(QUERY_TYPES.ITEM);
queryClient.invalidateQueries(QUERY_TYPES.ITEMS);
}, },
...props, ...props,
}, },

View File

@@ -334,6 +334,8 @@
"currency_name_": "Currency name", "currency_name_": "Currency name",
"cost_account_id": "Cost account", "cost_account_id": "Cost account",
"sell_account_id": "Sell account", "sell_account_id": "Sell account",
"item.details.sell_tax_rate": "Sell Tax Rate",
"item.details.purchase_tax_rate": "Purchase Tax Rate",
"item_type_": "Item type", "item_type_": "Item type",
"item_name_": "Item name", "item_name_": "Item name",
"organization_industry_": "Organization industry", "organization_industry_": "Organization industry",