From d6f56568a30037ec37c371d0d8a168dbe8c81741 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 11 Aug 2023 16:00:39 +0200 Subject: [PATCH] feat: tax rates crud service --- .../src/api/controllers/TaxRates/TaxRates.ts | 155 ++++++++++++++---- packages/server/src/api/index.ts | 2 + .../20230810191606_create_tax_rates.js | 2 + packages/server/src/interfaces/TaxRate.ts | 1 + .../TaxRates/CommandTaxRatesValidators.ts | 30 +++- .../src/services/TaxRates/CreateTaxRate.ts | 14 +- .../src/services/TaxRates/DeleteTaxRate.ts | 12 +- .../src/services/TaxRates/EditTaxRate.ts | 4 +- .../src/services/TaxRates/GetTaxRate.ts | 5 +- .../src/services/TaxRates/GetTaxRates.ts | 4 +- .../services/TaxRates/TaxRatesApplication.ts | 7 +- .../server/src/services/TaxRates/constants.ts | 3 +- 12 files changed, 189 insertions(+), 50 deletions(-) diff --git a/packages/server/src/api/controllers/TaxRates/TaxRates.ts b/packages/server/src/api/controllers/TaxRates/TaxRates.ts index d9752dede..b1493ab83 100644 --- a/packages/server/src/api/controllers/TaxRates/TaxRates.ts +++ b/packages/server/src/api/controllers/TaxRates/TaxRates.ts @@ -1,14 +1,12 @@ import { Inject, Service } from 'typedi'; import { Router, Request, Response } from 'express'; -import { body, query } from 'express-validator'; -import { pick } from 'lodash'; -import { IOptionDTO, IOptionsDTO } from '@/interfaces'; +import { body, param } from 'express-validator'; import BaseController from '@/api/controllers/BaseController'; import asyncMiddleware from '@/api/middleware/asyncMiddleware'; -import { AbilitySubject, PreferencesAction } from '@/interfaces'; -import SettingsService from '@/services/Settings/SettingsService'; -import CheckPolicies from '@/api/middleware/CheckPolicies'; import { TaxRatesApplication } from '@/services/TaxRates/TaxRatesApplication'; +import { HookNextFunction } from 'mongoose'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from '@/services/TaxRates/constants'; @Service() export class TaxRatesController extends BaseController { @@ -25,84 +23,181 @@ export class TaxRatesController extends BaseController { '/', this.taxRateValidationSchema, this.validationResult, - asyncMiddleware(this.createTaxRate.bind(this)) + asyncMiddleware(this.createTaxRate.bind(this)), + this.handleServiceErrors ); router.post( - '/:tax_rate_id', - this.taxRateValidationSchema, + '/:id', + [param('id').exists().toInt(), ...this.taxRateValidationSchema], this.validationResult, - asyncMiddleware(this.editTaxRate.bind(this)) + asyncMiddleware(this.editTaxRate.bind(this)), + this.handleServiceErrors ); router.delete( - '/:tax_rate_id', - this.taxRateValidationSchema, + '/:id', + [param('id').exists().toInt()], this.validationResult, - asyncMiddleware(this.deleteTaxRate.bind(this)) + asyncMiddleware(this.deleteTaxRate.bind(this)), + this.handleServiceErrors ); router.get( - '/:tax_rate_id', - this.taxRateValidationSchema, + '/:id', + [param('id').exists().toInt()], this.validationResult, - asyncMiddleware(this.getTaxRate.bind(this)) + asyncMiddleware(this.getTaxRate.bind(this)), + this.handleServiceErrors ); router.get( '/', - this.taxRateValidationSchema, this.validationResult, - asyncMiddleware(this.getTaxRates.bind(this)) + asyncMiddleware(this.getTaxRates.bind(this)), + this.handleServiceErrors ); return router; } /** - * Save settings validation schema. + * Tax rate validation schema. */ private get taxRateValidationSchema() { return [ + body('name').exists(), + body('code').exists().isString(), body('rate').exists().isNumeric().toFloat(), - body('is_non_recoverable').exists().isBoolean().default(false), + body('is_non_recoverable').optional().isBoolean().default(false), + body('status').optional().toUpperCase().isIn(['ARCHIVED', 'ACTIVE']), ]; } /** - * + * Creates a new tax rate. * @param {Request} req - * @param {Response} res - */ public async createTaxRate(req: Request, res: Response, next) { - const taxRate = await this.taxRatesApplication.createTaxRate() + const { tenantId } = req; + const createTaxRateDTO = this.matchedBodyData(req); + + try { + const taxRate = await this.taxRatesApplication.createTaxRate( + tenantId, + createTaxRateDTO + ); + return res.status(200).send({ + data: taxRate, + }); + } catch (error) { + next(error); + } } /** - * + * Edits the given tax rate. * @param {Request} req - * @param {Response} res - */ public async editTaxRate(req: Request, res: Response, next) { - const taxRate = await this.taxRatesApplication.editTaxRate(); + const { tenantId } = req; + const editTaxRateDTO = this.matchedBodyData(req); + const { id: taxRateId } = req.params; + try { + const taxRate = await this.taxRatesApplication.editTaxRate( + tenantId, + taxRateId, + editTaxRateDTO + ); + return res.status(200).send({ + data: taxRate, + }); + } catch (error) { + next(error); + } } /** - * + * Deletes the given tax rate. * @param {Request} req - * @param {Response} res - */ public async deleteTaxRate(req: Request, res: Response, next) { - await this.taxRatesApplication.deleteTaxRate(); + const { tenantId } = req; + const { id: taxRateId } = req.params; + + try { + await this.taxRatesApplication.deleteTaxRate(tenantId, taxRateId); + + return res.status(200).send({ + code: 200, + message: 'The tax rate has been deleted successfully.', + }); + } catch (error) { + next(error); + } } /** - * + * Retrieves the given tax rate. * @param {Request} req - * @param {Response} res - */ - public async getTaxRate(req: Request, res: Response, next) {} + public async getTaxRate(req: Request, res: Response, next) { + const { tenantId } = req; + const { id: taxRateId } = req.params; + + try { + const taxRate = await this.taxRatesApplication.getTaxRate( + tenantId, + taxRateId + ); + return res.status(200).send({ data: taxRate }); + } catch (error) { + next(error); + } + } /** - * + * Retrieves the tax rates list. * @param {Request} req - * @param {Response} res - */ - public async getTaxRates(req: Request, res: Response, next) {} + public async getTaxRates(req: Request, res: Response, next) { + const { tenantId } = req; + + try { + const taxRates = await this.taxRatesApplication.getTaxRates(tenantId); + + return res.status(200).send({ data: taxRates }); + } catch (error) { + next(error); + } + } + + /** + * Handles service errors. + * @param {Error} error + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ + private handleServiceErrors( + error: Error, + req: Request, + res: Response, + next: HookNextFunction + ) { + if (error instanceof ServiceError) { + if (error.errorType === ERRORS.TAX_CODE_NOT_UNIQUE) { + return res.boom.badRequest(null, { + errors: [{ type: ERRORS.TAX_CODE_NOT_UNIQUE, code: 100 }], + }); + } + if (error.errorType === ERRORS.TAX_RATE_NOT_FOUND) { + return res.boom.badRequest(null, { + errors: [{ type: ERRORS.TAX_RATE_NOT_FOUND, code: 200 }], + }); + } + } + next(error); + } } diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index 727a9e0ba..6a41c8304 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -55,6 +55,7 @@ import { InventoryItemsCostController } from './controllers/Inventory/Inventorty import { ProjectsController } from './controllers/Projects/Projects'; import { ProjectTasksController } from './controllers/Projects/Tasks'; import { ProjectTimesController } from './controllers/Projects/Times'; +import { TaxRatesController } from './controllers/TaxRates/TaxRates'; export default () => { const app = Router(); @@ -129,6 +130,7 @@ export default () => { ); dashboard.use('/warehouses', Container.get(WarehousesController).router()); dashboard.use('/projects', Container.get(ProjectsController).router()); + dashboard.use('/tax-rates', Container.get(TaxRatesController).router()); dashboard.use('/', Container.get(ProjectTasksController).router()); dashboard.use('/', Container.get(ProjectTimesController).router()); diff --git a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js index 3cdbf1098..802c275e4 100644 --- a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js +++ b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js @@ -2,9 +2,11 @@ exports.up = (knex) => { return knex.schema.createTable('tax_rates', (table) => { table.increments(); table.string('name'); + table.string('code'); table.decimal('rate'); table.boolean('is_non_recoverable'); table.boolean('is_compound'); + table.integer('status'); table.timestamps(); }); }; diff --git a/packages/server/src/interfaces/TaxRate.ts b/packages/server/src/interfaces/TaxRate.ts index 4514a732a..b65a03851 100644 --- a/packages/server/src/interfaces/TaxRate.ts +++ b/packages/server/src/interfaces/TaxRate.ts @@ -4,6 +4,7 @@ export interface ITaxRate {} export interface ICommonTaxRateDTO { name: string; + code: string; rate: number; IsNonRecoverable: boolean; IsCompound: boolean; diff --git a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts index 1b25cabee..9d3c07229 100644 --- a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts +++ b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts @@ -1,16 +1,36 @@ import { ServiceError } from '@/exceptions'; -import TaxRate from '@/models/TaxRate'; -import { Service } from 'typedi'; +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { ITaxRate } from '@/interfaces'; +import { ERRORS } from './constants'; @Service() export class CommandTaxRatesValidators { + @Inject() + private tenancy: HasTenancyService; + /** - * - * @param {} taxRate + * Validates the tax rate existance. + * @param {TaxRate | undefined | null} taxRate */ - public validateTaxRateExistance(taxRate: TaxRate | undefined | null) { + public validateTaxRateExistance(taxRate: ITaxRate | undefined | null) { if (!taxRate) { throw new ServiceError(ERRORS.TAX_RATE_NOT_FOUND); } } + + /** + * Validates the tax code uniquiness. + * @param {number} tenantId + * @param {string} taxCode + */ + public async validateTaxCodeUnique(tenantId: number, taxCode: string) { + const { TaxRate } = this.tenancy.models(tenantId); + + const foundTaxCode = await TaxRate.query().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 1414b5654..b7bc19fc8 100644 --- a/packages/server/src/services/TaxRates/CreateTaxRate.ts +++ b/packages/server/src/services/TaxRates/CreateTaxRate.ts @@ -9,6 +9,7 @@ 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'; @Service() export class CreateTaxRate { @@ -21,14 +22,25 @@ export class CreateTaxRate { @Inject() private uow: UnitOfWork; + @Inject() + private validators: CommandTaxRatesValidators; + /** * Creates a new tax rate. * @param {number} tenantId * @param {ICreateTaxRateDTO} createTaxRateDTO */ - public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { + public async createTaxRate( + tenantId: number, + createTaxRateDTO: ICreateTaxRateDTO + ) { const { TaxRate } = this.tenancy.models(tenantId); + // Validates the tax code uniquiness. + await this.validators.validateTaxCodeUnique( + tenantId, + createTaxRateDTO.code + ); return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { // Triggers `onTaxRateCreating` event. await this.eventPublisher.emitAsync(events.taxRates.onCreating, { diff --git a/packages/server/src/services/TaxRates/DeleteTaxRate.ts b/packages/server/src/services/TaxRates/DeleteTaxRate.ts index 17f3a339e..27c104de1 100644 --- a/packages/server/src/services/TaxRates/DeleteTaxRate.ts +++ b/packages/server/src/services/TaxRates/DeleteTaxRate.ts @@ -22,19 +22,21 @@ export class DeleteTaxRateService { private validators: CommandTaxRatesValidators; /** - * - * @param tenantId - * @param taxRateId + * Deletes the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @returns {Promise} */ public deleteTaxRate(tenantId: number, taxRateId: number) { const { TaxRate } = this.tenancy.models(tenantId); const oldTaxRate = TaxRate.query().findById(taxRateId); + // Validates the tax rate existance. this.validators.validateTaxRateExistance(oldTaxRate); return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - // Triggers `onSaleInvoiceCreating` event. + // Triggers `onTaxRateDeleting` event. await this.eventPublisher.emitAsync(events.taxRates.onDeleting, { oldTaxRate, tenantId, @@ -43,7 +45,7 @@ export class DeleteTaxRateService { await TaxRate.query(trx).findById(taxRateId).delete(); - // + // Triggers `onTaxRateDeleted` event. await this.eventPublisher.emitAsync(events.taxRates.onDeleted, { oldTaxRate, tenantId, diff --git a/packages/server/src/services/TaxRates/EditTaxRate.ts b/packages/server/src/services/TaxRates/EditTaxRate.ts index a6b502388..7ae9bbfd7 100644 --- a/packages/server/src/services/TaxRates/EditTaxRate.ts +++ b/packages/server/src/services/TaxRates/EditTaxRate.ts @@ -26,10 +26,11 @@ export class EditTaxRateService { private validators: CommandTaxRatesValidators; /** - * + * Edits the given tax rate. * @param {number} tenantId * @param {number} taxRateId * @param {IEditTaxRateDTO} taxRateEditDTO + * @returns {Promise} */ public editTaxRate( tenantId: number, @@ -40,6 +41,7 @@ export class EditTaxRateService { const oldTaxRate = TaxRate.query().findById(taxRateId); + // Validates the tax rate existance. this.validators.validateTaxRateExistance(oldTaxRate); return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { diff --git a/packages/server/src/services/TaxRates/GetTaxRate.ts b/packages/server/src/services/TaxRates/GetTaxRate.ts index 536741b3e..5dacd3d3f 100644 --- a/packages/server/src/services/TaxRates/GetTaxRate.ts +++ b/packages/server/src/services/TaxRates/GetTaxRate.ts @@ -11,16 +11,17 @@ export class GetTaxRateService { private validators: CommandTaxRatesValidators; /** - * + * Retrieves the given tax rate. * @param {number} tenantId * @param {number} taxRateId - * @returns + * @returns {Promise} */ public async getTaxRate(tenantId: number, taxRateId: number) { const { TaxRate } = this.tenancy.models(tenantId); const taxRate = await TaxRate.query().findById(taxRateId); + // Validates the tax rate existance. this.validators.validateTaxRateExistance(taxRate); return taxRate; diff --git a/packages/server/src/services/TaxRates/GetTaxRates.ts b/packages/server/src/services/TaxRates/GetTaxRates.ts index 6344d4eed..8086dd121 100644 --- a/packages/server/src/services/TaxRates/GetTaxRates.ts +++ b/packages/server/src/services/TaxRates/GetTaxRates.ts @@ -7,9 +7,9 @@ export class GetTaxRatesService { private tenancy: HasTenancyService; /** - * + * Retrieves the tax rates list. * @param {number} tenantId - * @returns + * @returns {Promise} */ public async getTaxRates(tenantId: number) { const { TaxRate } = this.tenancy.models(tenantId); diff --git a/packages/server/src/services/TaxRates/TaxRatesApplication.ts b/packages/server/src/services/TaxRates/TaxRatesApplication.ts index 63190c83b..cf141bd47 100644 --- a/packages/server/src/services/TaxRates/TaxRatesApplication.ts +++ b/packages/server/src/services/TaxRates/TaxRatesApplication.ts @@ -27,7 +27,7 @@ export class TaxRatesApplication { * Creates a new tax rate. * @param {number} tenantId * @param {ICreateTaxRateDTO} createTaxRateDTO - * @returns + * @returns {Promise} */ public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { return this.createTaxRateService.createTaxRate(tenantId, createTaxRateDTO); @@ -38,6 +38,7 @@ export class TaxRatesApplication { * @param {number} tenantId * @param {number} taxRateId * @param {IEditTaxRateDTO} taxRateEditDTO + * @returns {Promise} */ public editTaxRate( tenantId: number, @@ -65,7 +66,7 @@ export class TaxRatesApplication { * Retrieves the given tax rate. * @param {number} tenantId * @param {number} taxRateId - * @returns + * @returns {Promise} */ public getTaxRate(tenantId: number, taxRateId: number) { return this.getTaxRateService.getTaxRate(tenantId, taxRateId); @@ -74,7 +75,7 @@ export class TaxRatesApplication { /** * Retrieves the tax rates list. * @param {number} tenantId - * @returns + * @returns {Promise} */ public getTaxRates(tenantId: number) { return this.getTaxRatesService.getTaxRates(tenantId); diff --git a/packages/server/src/services/TaxRates/constants.ts b/packages/server/src/services/TaxRates/constants.ts index 3ceceeab4..71cf5e07e 100644 --- a/packages/server/src/services/TaxRates/constants.ts +++ b/packages/server/src/services/TaxRates/constants.ts @@ -1,3 +1,4 @@ -const ERRORS = { +export const ERRORS = { TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND', + TAX_CODE_NOT_UNIQUE: 'TAX_CODE_NOT_UNIQUE', };