mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
Merge pull request #591 from bigcapitalhq/import-export-tax-rates
feat: import and export tax rates
This commit is contained in:
69
packages/server/src/models/TaxRate.settings.ts
Normal file
69
packages/server/src/models/TaxRate.settings.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
export default {
|
||||||
|
defaultSort: {
|
||||||
|
sortOrder: 'DESC',
|
||||||
|
sortField: 'created_at',
|
||||||
|
},
|
||||||
|
exportable: true,
|
||||||
|
importable: true,
|
||||||
|
print: {
|
||||||
|
pageTitle: 'Tax Rates',
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
name: {
|
||||||
|
name: 'Tax Rate Name',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'name',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'Code',
|
||||||
|
type: 'text',
|
||||||
|
accessor: 'code',
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
isNonRecoverable: {
|
||||||
|
name: 'Is Non Recoverable',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: {},
|
||||||
|
fields2: {
|
||||||
|
name: {
|
||||||
|
name: 'Tax name',
|
||||||
|
fieldType: 'name',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
name: 'Code',
|
||||||
|
fieldType: 'code',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
rate: {
|
||||||
|
name: 'Rate',
|
||||||
|
fieldType: 'number',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
name: 'Description',
|
||||||
|
fieldType: 'text',
|
||||||
|
},
|
||||||
|
isNonRecoverable: {
|
||||||
|
name: 'Is Non Recoverable',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
name: 'Active',
|
||||||
|
fieldType: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -2,8 +2,13 @@ import { mixin, Model, raw } from 'objection';
|
|||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSearchable from './ModelSearchable';
|
import ModelSearchable from './ModelSearchable';
|
||||||
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
|
import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
|
||||||
|
import TaxRateMeta from './TaxRate.settings';
|
||||||
|
import ModelSetting from './ModelSetting';
|
||||||
|
|
||||||
export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
export default class TaxRate extends mixin(TenantModel, [
|
||||||
|
ModelSetting,
|
||||||
|
ModelSearchable,
|
||||||
|
]) {
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
*/
|
*/
|
||||||
@@ -25,6 +30,13 @@ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
|
|||||||
return ['createdAt', 'updatedAt'];
|
return ['createdAt', 'updatedAt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tax rate meta.
|
||||||
|
*/
|
||||||
|
static get meta() {
|
||||||
|
return TaxRateMeta;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Virtual attributes.
|
* Virtual attributes.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExporta
|
|||||||
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
|
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
|
||||||
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
|
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
|
||||||
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
|
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
|
||||||
|
import { TaxRatesExportable } from '../TaxRates/TaxRatesExportable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ExportableResources {
|
export class ExportableResources {
|
||||||
@@ -46,6 +47,7 @@ export class ExportableResources {
|
|||||||
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
|
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
|
||||||
{ resource: 'CreditNote', exportable: CreditNotesExportable },
|
{ resource: 'CreditNote', exportable: CreditNotesExportable },
|
||||||
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
|
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
|
||||||
|
{ resource: 'TaxRate', exportable: TaxRatesExportable },
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCredit
|
|||||||
import { PaymentReceivesImportable } from '../Sales/PaymentReceives/PaymentReceivesImportable';
|
import { PaymentReceivesImportable } from '../Sales/PaymentReceives/PaymentReceivesImportable';
|
||||||
import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable';
|
import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable';
|
||||||
import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable';
|
import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable';
|
||||||
|
import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ImportableResources {
|
export class ImportableResources {
|
||||||
@@ -47,7 +48,8 @@ export class ImportableResources {
|
|||||||
{ resource: 'PaymentReceive', importable: PaymentReceivesImportable },
|
{ resource: 'PaymentReceive', importable: PaymentReceivesImportable },
|
||||||
{ resource: 'VendorCredit', importable: VendorCreditsImportable },
|
{ resource: 'VendorCredit', importable: VendorCreditsImportable },
|
||||||
{ resource: 'CreditNote', importable: CreditNotesImportable },
|
{ resource: 'CreditNote', importable: CreditNotesImportable },
|
||||||
{ resource: 'SaleReceipt', importable: SaleReceiptsImportable }
|
{ resource: 'SaleReceipt', importable: SaleReceiptsImportable },
|
||||||
|
{ resource: 'TaxRate', importable: TaxRatesImportable },
|
||||||
];
|
];
|
||||||
|
|
||||||
public get registry() {
|
public get registry() {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { ServiceError } from '@/exceptions';
|
import { Knex } from 'knex';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { difference } from 'lodash';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { IItemEntryDTO, ITaxRate } from '@/interfaces';
|
import { IItemEntryDTO, ITaxRate } from '@/interfaces';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
import { difference } from 'lodash';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CommandTaxRatesValidators {
|
export class CommandTaxRatesValidators {
|
||||||
@@ -44,11 +45,16 @@ export class CommandTaxRatesValidators {
|
|||||||
* Validates the tax code uniquiness.
|
* Validates the tax code uniquiness.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {string} taxCode
|
* @param {string} taxCode
|
||||||
|
* @param {Knex.Transaction} trx -
|
||||||
*/
|
*/
|
||||||
public async validateTaxCodeUnique(tenantId: number, taxCode: string) {
|
public async validateTaxCodeUnique(
|
||||||
|
tenantId: number,
|
||||||
|
taxCode: string,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
const { TaxRate } = this.tenancy.models(tenantId);
|
const { TaxRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const foundTaxCode = await TaxRate.query().findOne({ code: taxCode });
|
const foundTaxCode = await TaxRate.query(trx).findOne({ code: taxCode });
|
||||||
|
|
||||||
if (foundTaxCode) {
|
if (foundTaxCode) {
|
||||||
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);
|
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import {
|
import {
|
||||||
ICreateTaxRateDTO,
|
ICreateTaxRateDTO,
|
||||||
@@ -7,7 +8,6 @@ import {
|
|||||||
import UnitOfWork from '../UnitOfWork';
|
import UnitOfWork from '../UnitOfWork';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
import { Inject, Service } from 'typedi';
|
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
|
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
|
||||||
|
|
||||||
@@ -32,36 +32,41 @@ export class CreateTaxRate {
|
|||||||
*/
|
*/
|
||||||
public async createTaxRate(
|
public async createTaxRate(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
createTaxRateDTO: ICreateTaxRateDTO
|
createTaxRateDTO: ICreateTaxRateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
) {
|
) {
|
||||||
const { TaxRate } = this.tenancy.models(tenantId);
|
const { TaxRate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
// Validates the tax code uniquiness.
|
// Validates the tax code uniquiness.
|
||||||
await this.validators.validateTaxCodeUnique(
|
await this.validators.validateTaxCodeUnique(
|
||||||
tenantId,
|
tenantId,
|
||||||
createTaxRateDTO.code
|
createTaxRateDTO.code,
|
||||||
|
trx
|
||||||
);
|
);
|
||||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
return this.uow.withTransaction(
|
||||||
// Triggers `onTaxRateCreating` event.
|
tenantId,
|
||||||
await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
|
async (trx: Knex.Transaction) => {
|
||||||
createTaxRateDTO,
|
// Triggers `onTaxRateCreating` event.
|
||||||
tenantId,
|
await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
|
||||||
trx,
|
createTaxRateDTO,
|
||||||
} as ITaxRateCreatingPayload);
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ITaxRateCreatingPayload);
|
||||||
|
|
||||||
const taxRate = await TaxRate.query(trx).insertAndFetch({
|
const taxRate = await TaxRate.query(trx).insertAndFetch({
|
||||||
...createTaxRateDTO,
|
...createTaxRateDTO,
|
||||||
});
|
});
|
||||||
|
// Triggers `onTaxRateCreated` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
|
||||||
|
createTaxRateDTO,
|
||||||
|
taxRate,
|
||||||
|
tenantId,
|
||||||
|
trx,
|
||||||
|
} as ITaxRateCreatedPayload);
|
||||||
|
|
||||||
// Triggers `onTaxRateCreated` event.
|
return taxRate;
|
||||||
await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
|
},
|
||||||
createTaxRateDTO,
|
trx
|
||||||
taxRate,
|
);
|
||||||
tenantId,
|
|
||||||
trx,
|
|
||||||
} as ITaxRateCreatedPayload);
|
|
||||||
|
|
||||||
return taxRate;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
packages/server/src/services/TaxRates/TaxRatesExportable.ts
Normal file
18
packages/server/src/services/TaxRates/TaxRatesExportable.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Exportable } from '../Export/Exportable';
|
||||||
|
import { TaxRatesApplication } from './TaxRatesApplication';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class TaxRatesExportable extends Exportable {
|
||||||
|
@Inject()
|
||||||
|
private taxRatesApplication: TaxRatesApplication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accounts data to exportable sheet.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public exportable(tenantId: number) {
|
||||||
|
return this.taxRatesApplication.getTaxRates(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export const TaxRatesSampleData = [
|
||||||
|
{
|
||||||
|
'Tax Name': 'Value Added Tax',
|
||||||
|
Code: 'VAT-STD',
|
||||||
|
Rate: '20',
|
||||||
|
Description: 'Standard VAT rate applied to most goods and services.',
|
||||||
|
'Is Non Recoverable': 'F',
|
||||||
|
Active: 'T',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Tax Name': 'Luxury Goods Tax',
|
||||||
|
Code: 'TAX-LUXURY',
|
||||||
|
Rate: '25',
|
||||||
|
Description: 'Tax imposed on the sale of luxury items.',
|
||||||
|
'Is Non Recoverable': 'T',
|
||||||
|
Active: 'T',
|
||||||
|
},
|
||||||
|
];
|
||||||
46
packages/server/src/services/TaxRates/TaxRatesImportable.ts
Normal file
46
packages/server/src/services/TaxRates/TaxRatesImportable.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { ICreateTaxRateDTO } from '@/interfaces';
|
||||||
|
import { CreateTaxRate } from './CreateTaxRate';
|
||||||
|
import { Importable } from '../Import/Importable';
|
||||||
|
import { TaxRatesSampleData } from './TaxRatesImportable.SampleData';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class TaxRatesImportable extends Importable {
|
||||||
|
@Inject()
|
||||||
|
private createTaxRateService: CreateTaxRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Importing to tax rate creating service.
|
||||||
|
* @param {number} tenantId -
|
||||||
|
* @param {ICreateTaxRateDTO} ICreateTaxRateDTO -
|
||||||
|
* @param {Knex.Transaction} trx -
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public importable(
|
||||||
|
tenantId: number,
|
||||||
|
createAccountDTO: ICreateTaxRateDTO,
|
||||||
|
trx?: Knex.Transaction
|
||||||
|
) {
|
||||||
|
return this.createTaxRateService.createTaxRate(
|
||||||
|
tenantId,
|
||||||
|
createAccountDTO,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concurrrency controlling of the importing process.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
public get concurrency() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the sample data that used to download accounts sample sheet.
|
||||||
|
*/
|
||||||
|
public sampleData(): any[] {
|
||||||
|
return TaxRatesSampleData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,4 +14,5 @@ export const ExportResources = [
|
|||||||
{ value: 'bill', text: 'Bills' },
|
{ value: 'bill', text: 'Bills' },
|
||||||
{ value: 'bill_payment', text: 'Bill Payments' },
|
{ value: 'bill_payment', text: 'Bill Payments' },
|
||||||
{ value: 'vendor_credit', text: 'Vendor Credits' },
|
{ value: 'vendor_credit', text: 'Vendor Credits' },
|
||||||
|
{ value: 'tax_rate', text: 'Tax Rate' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { DashboardInsider } from '@/components';
|
||||||
|
import { ImportView } from '@/containers/Import';
|
||||||
|
|
||||||
|
export default function TaxRatesImport() {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleCancelBtnClick = () => {
|
||||||
|
history.push('/tax-rates');
|
||||||
|
};
|
||||||
|
const handleImportSuccess = () => {
|
||||||
|
history.push('/tax-rates');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider name={'import-tax-rates'}>
|
||||||
|
<ImportView
|
||||||
|
resource={'tax-rate'}
|
||||||
|
onCancelClick={handleCancelBtnClick}
|
||||||
|
onImportSuccess={handleImportSuccess}
|
||||||
|
/>
|
||||||
|
</DashboardInsider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|||||||
|
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tax rates actions bar.
|
* Tax rates actions bar.
|
||||||
@@ -21,11 +22,21 @@ function TaxRatesActionsBar({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
openDialog,
|
openDialog,
|
||||||
}) {
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
// Handle `new item` button click.
|
// Handle `new item` button click.
|
||||||
const onClickNewItem = () => {
|
const onClickNewItem = () => {
|
||||||
openDialog(DialogsName.TaxRateForm);
|
openDialog(DialogsName.TaxRateForm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImportBtnClick = () => {
|
||||||
|
history.push('/tax-rates/import');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExportBtnClick = () => {
|
||||||
|
openDialog(DialogsName.Export, { resource: 'tax_rate' });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -43,11 +54,13 @@ function TaxRatesActionsBar({
|
|||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||||
text={<T id={'import'} />}
|
text={<T id={'import'} />}
|
||||||
|
onClick={handleImportBtnClick}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||||
text={<T id={'export'} />}
|
text={<T id={'export'} />}
|
||||||
|
onClick={handleExportBtnClick}
|
||||||
/>
|
/>
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
</DashboardActionsBar>
|
</DashboardActionsBar>
|
||||||
|
|||||||
@@ -1213,6 +1213,14 @@ export const getDashboardRoutes = () => [
|
|||||||
),
|
),
|
||||||
pageTitle: intl.get('sidebar.projects'),
|
pageTitle: intl.get('sidebar.projects'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tax-rates/import',
|
||||||
|
component: lazy(
|
||||||
|
() => import('@/containers/TaxRates/containers/TaxRatesImport'),
|
||||||
|
),
|
||||||
|
pageTitle: 'Tax Rates',
|
||||||
|
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/tax-rates',
|
path: '/tax-rates',
|
||||||
component: lazy(
|
component: lazy(
|
||||||
|
|||||||
Reference in New Issue
Block a user