diff --git a/packages/server/src/models/TaxRate.settings.ts b/packages/server/src/models/TaxRate.settings.ts
new file mode 100644
index 000000000..a25ce2940
--- /dev/null
+++ b/packages/server/src/models/TaxRate.settings.ts
@@ -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',
+ },
+ },
+};
diff --git a/packages/server/src/models/TaxRate.ts b/packages/server/src/models/TaxRate.ts
index e294b897a..13863e233 100644
--- a/packages/server/src/models/TaxRate.ts
+++ b/packages/server/src/models/TaxRate.ts
@@ -2,8 +2,13 @@ import { mixin, Model, raw } from 'objection';
import TenantModel from 'models/TenantModel';
import ModelSearchable from './ModelSearchable';
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
*/
@@ -25,6 +30,13 @@ export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) {
return ['createdAt', 'updatedAt'];
}
+ /**
+ * Retrieves the tax rate meta.
+ */
+ static get meta() {
+ return TaxRateMeta;
+ }
+
/**
* Virtual attributes.
*/
diff --git a/packages/server/src/services/Export/ExportResources.ts b/packages/server/src/services/Export/ExportResources.ts
index b252bfab1..e7308e4dc 100644
--- a/packages/server/src/services/Export/ExportResources.ts
+++ b/packages/server/src/services/Export/ExportResources.ts
@@ -15,6 +15,7 @@ import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExporta
import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
+import { TaxRatesExportable } from '../TaxRates/TaxRatesExportable';
@Service()
export class ExportableResources {
@@ -46,6 +47,7 @@ export class ExportableResources {
{ resource: 'ManualJournal', exportable: ManualJournalsExportable },
{ resource: 'CreditNote', exportable: CreditNotesExportable },
{ resource: 'VendorCredit', exportable: VendorCreditsExportable },
+ { resource: 'TaxRate', exportable: TaxRatesExportable },
];
/**
diff --git a/packages/server/src/services/Import/ImportableResources.ts b/packages/server/src/services/Import/ImportableResources.ts
index 33f2f10f3..1a0d6cbf0 100644
--- a/packages/server/src/services/Import/ImportableResources.ts
+++ b/packages/server/src/services/Import/ImportableResources.ts
@@ -16,6 +16,7 @@ import { VendorCreditsImportable } from '../Purchases/VendorCredits/VendorCredit
import { PaymentReceivesImportable } from '../Sales/PaymentReceives/PaymentReceivesImportable';
import { CreditNotesImportable } from '../CreditNotes/CreditNotesImportable';
import { SaleReceiptsImportable } from '../Sales/Receipts/SaleReceiptsImportable';
+import { TaxRatesImportable } from '../TaxRates/TaxRatesImportable';
@Service()
export class ImportableResources {
@@ -47,7 +48,8 @@ export class ImportableResources {
{ resource: 'PaymentReceive', importable: PaymentReceivesImportable },
{ resource: 'VendorCredit', importable: VendorCreditsImportable },
{ resource: 'CreditNote', importable: CreditNotesImportable },
- { resource: 'SaleReceipt', importable: SaleReceiptsImportable }
+ { resource: 'SaleReceipt', importable: SaleReceiptsImportable },
+ { resource: 'TaxRate', importable: TaxRatesImportable },
];
public get registry() {
diff --git a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts
index 9d7c2558e..decd41857 100644
--- a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts
+++ b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts
@@ -1,9 +1,10 @@
-import { ServiceError } from '@/exceptions';
+import { Knex } from 'knex';
import { Inject, Service } from 'typedi';
+import { difference } from 'lodash';
+import { ServiceError } from '@/exceptions';
import HasTenancyService from '../Tenancy/TenancyService';
import { IItemEntryDTO, ITaxRate } from '@/interfaces';
import { ERRORS } from './constants';
-import { difference } from 'lodash';
@Service()
export class CommandTaxRatesValidators {
@@ -44,11 +45,16 @@ export class CommandTaxRatesValidators {
* Validates the tax code uniquiness.
* @param {number} tenantId
* @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 foundTaxCode = await TaxRate.query().findOne({ code: taxCode });
+ const foundTaxCode = await TaxRate.query(trx).findOne({ code: taxCode });
if (foundTaxCode) {
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);
diff --git a/packages/server/src/services/TaxRates/CreateTaxRate.ts b/packages/server/src/services/TaxRates/CreateTaxRate.ts
index a7795a3c7..9da05dded 100644
--- a/packages/server/src/services/TaxRates/CreateTaxRate.ts
+++ b/packages/server/src/services/TaxRates/CreateTaxRate.ts
@@ -1,3 +1,4 @@
+import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import {
ICreateTaxRateDTO,
@@ -7,7 +8,6 @@ import {
import UnitOfWork from '../UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '../Tenancy/TenancyService';
-import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
@@ -32,36 +32,41 @@ export class CreateTaxRate {
*/
public async createTaxRate(
tenantId: number,
- createTaxRateDTO: ICreateTaxRateDTO
+ createTaxRateDTO: ICreateTaxRateDTO,
+ trx?: Knex.Transaction
) {
const { TaxRate } = this.tenancy.models(tenantId);
// Validates the tax code uniquiness.
await this.validators.validateTaxCodeUnique(
tenantId,
- createTaxRateDTO.code
+ createTaxRateDTO.code,
+ trx
);
- return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
- // Triggers `onTaxRateCreating` event.
- await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
- createTaxRateDTO,
- tenantId,
- trx,
- } as ITaxRateCreatingPayload);
+ return this.uow.withTransaction(
+ tenantId,
+ async (trx: Knex.Transaction) => {
+ // Triggers `onTaxRateCreating` event.
+ await this.eventPublisher.emitAsync(events.taxRates.onCreating, {
+ createTaxRateDTO,
+ tenantId,
+ trx,
+ } as ITaxRateCreatingPayload);
- const taxRate = await TaxRate.query(trx).insertAndFetch({
- ...createTaxRateDTO,
- });
+ const taxRate = await TaxRate.query(trx).insertAndFetch({
+ ...createTaxRateDTO,
+ });
+ // Triggers `onTaxRateCreated` event.
+ await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
+ createTaxRateDTO,
+ taxRate,
+ tenantId,
+ trx,
+ } as ITaxRateCreatedPayload);
- // Triggers `onTaxRateCreated` event.
- await this.eventPublisher.emitAsync(events.taxRates.onCreated, {
- createTaxRateDTO,
- taxRate,
- tenantId,
- trx,
- } as ITaxRateCreatedPayload);
-
- return taxRate;
- });
+ return taxRate;
+ },
+ trx
+ );
}
}
diff --git a/packages/server/src/services/TaxRates/TaxRatesExportable.ts b/packages/server/src/services/TaxRates/TaxRatesExportable.ts
new file mode 100644
index 000000000..096a36698
--- /dev/null
+++ b/packages/server/src/services/TaxRates/TaxRatesExportable.ts
@@ -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);
+ }
+}
diff --git a/packages/server/src/services/TaxRates/TaxRatesImportable.SampleData.ts b/packages/server/src/services/TaxRates/TaxRatesImportable.SampleData.ts
new file mode 100644
index 000000000..0a7fe389d
--- /dev/null
+++ b/packages/server/src/services/TaxRates/TaxRatesImportable.SampleData.ts
@@ -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',
+ },
+];
diff --git a/packages/server/src/services/TaxRates/TaxRatesImportable.ts b/packages/server/src/services/TaxRates/TaxRatesImportable.ts
new file mode 100644
index 000000000..5f0ab4185
--- /dev/null
+++ b/packages/server/src/services/TaxRates/TaxRatesImportable.ts
@@ -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;
+ }
+}
diff --git a/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts b/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts
index b971a8433..d13466c3a 100644
--- a/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts
+++ b/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts
@@ -14,4 +14,5 @@ export const ExportResources = [
{ value: 'bill', text: 'Bills' },
{ value: 'bill_payment', text: 'Bill Payments' },
{ value: 'vendor_credit', text: 'Vendor Credits' },
+ { value: 'tax_rate', text: 'Tax Rate' },
];
diff --git a/packages/webapp/src/containers/TaxRates/containers/TaxRatesImport.tsx b/packages/webapp/src/containers/TaxRates/containers/TaxRatesImport.tsx
new file mode 100644
index 000000000..e2b5c66ff
--- /dev/null
+++ b/packages/webapp/src/containers/TaxRates/containers/TaxRatesImport.tsx
@@ -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 (
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/TaxRates/containers/TaxRatesLandingActionsBar.tsx b/packages/webapp/src/containers/TaxRates/containers/TaxRatesLandingActionsBar.tsx
index 9631ae97c..1cd369ed3 100644
--- a/packages/webapp/src/containers/TaxRates/containers/TaxRatesLandingActionsBar.tsx
+++ b/packages/webapp/src/containers/TaxRates/containers/TaxRatesLandingActionsBar.tsx
@@ -13,6 +13,7 @@ import withDialogActions from '@/containers/Dialog/withDialogActions';
import { DialogsName } from '@/constants/dialogs';
import { compose } from '@/utils';
+import { useHistory } from 'react-router-dom';
/**
* Tax rates actions bar.
@@ -21,11 +22,21 @@ function TaxRatesActionsBar({
// #withDialogActions
openDialog,
}) {
+ const history = useHistory();
+
// Handle `new item` button click.
const onClickNewItem = () => {
openDialog(DialogsName.TaxRateForm);
};
+ const handleImportBtnClick = () => {
+ history.push('/tax-rates/import');
+ };
+
+ const handleExportBtnClick = () => {
+ openDialog(DialogsName.Export, { resource: 'tax_rate' });
+ };
+
return (
@@ -43,11 +54,13 @@ function TaxRatesActionsBar({
className={Classes.MINIMAL}
icon={}
text={}
+ onClick={handleImportBtnClick}
/>
}
text={}
+ onClick={handleExportBtnClick}
/>
diff --git a/packages/webapp/src/routes/dashboard.tsx b/packages/webapp/src/routes/dashboard.tsx
index b1b4cb1d4..25765554f 100644
--- a/packages/webapp/src/routes/dashboard.tsx
+++ b/packages/webapp/src/routes/dashboard.tsx
@@ -1213,6 +1213,14 @@ export const getDashboardRoutes = () => [
),
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',
component: lazy(