From 72678bb936563881ce15966fb8f332b8c12152b1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 11 Aug 2024 19:51:16 +0200 Subject: [PATCH] feat: import and export tax rates --- .../server/src/models/TaxRate.settings.ts | 69 +++++++++++++++++++ packages/server/src/models/TaxRate.ts | 14 +++- .../src/services/Export/ExportResources.ts | 2 + .../services/Import/ImportableResources.ts | 4 +- .../TaxRates/CommandTaxRatesValidators.ts | 14 ++-- .../src/services/TaxRates/CreateTaxRate.ts | 51 +++++++------- .../services/TaxRates/TaxRatesExportable.ts | 18 +++++ .../TaxRates/TaxRatesImportable.SampleData.ts | 18 +++++ .../services/TaxRates/TaxRatesImportable.ts | 46 +++++++++++++ .../Dialogs/ExportDialog/constants.ts | 1 + .../TaxRates/containers/TaxRatesImport.tsx | 25 +++++++ .../containers/TaxRatesLandingActionsBar.tsx | 13 ++++ packages/webapp/src/routes/dashboard.tsx | 8 +++ 13 files changed, 254 insertions(+), 29 deletions(-) create mode 100644 packages/server/src/models/TaxRate.settings.ts create mode 100644 packages/server/src/services/TaxRates/TaxRatesExportable.ts create mode 100644 packages/server/src/services/TaxRates/TaxRatesImportable.SampleData.ts create mode 100644 packages/server/src/services/TaxRates/TaxRatesImportable.ts create mode 100644 packages/webapp/src/containers/TaxRates/containers/TaxRatesImport.tsx 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} />