From 04d134806ba2aeaafb4386d01db67406bfc90e48 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 11 Aug 2023 01:31:52 +0200 Subject: [PATCH] feat(server): wip tax rates service --- .../src/api/controllers/TaxRates/TaxRates.ts | 108 ++++++++++++++++++ .../src/api/controllers/TaxRates/index.ts | 0 .../20230810191606_create_tax_rates.js | 14 +++ packages/server/src/interfaces/TaxRate.ts | 48 ++++++++ packages/server/src/interfaces/index.ts | 1 + packages/server/src/loaders/tenantModels.ts | 2 + packages/server/src/models/TaxRate.ts | 40 +++++++ .../TaxRates/CommandTaxRatesValidators.ts | 16 +++ .../src/services/TaxRates/CreateTaxRate.ts | 53 +++++++++ .../src/services/TaxRates/DeleteTaxRate.ts | 54 +++++++++ .../src/services/TaxRates/EditTaxRate.ts | 68 +++++++++++ .../src/services/TaxRates/GetTaxRate.ts | 28 +++++ .../src/services/TaxRates/GetTaxRates.ts | 21 ++++ .../services/TaxRates/TaxRatesApplication.ts | 82 +++++++++++++ .../server/src/services/TaxRates/constants.ts | 3 + packages/server/src/subscribers/events.ts | 13 ++- 16 files changed, 550 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/api/controllers/TaxRates/TaxRates.ts create mode 100644 packages/server/src/api/controllers/TaxRates/index.ts create mode 100644 packages/server/src/database/migrations/20230810191606_create_tax_rates.js create mode 100644 packages/server/src/interfaces/TaxRate.ts create mode 100644 packages/server/src/models/TaxRate.ts create mode 100644 packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts create mode 100644 packages/server/src/services/TaxRates/CreateTaxRate.ts create mode 100644 packages/server/src/services/TaxRates/DeleteTaxRate.ts create mode 100644 packages/server/src/services/TaxRates/EditTaxRate.ts create mode 100644 packages/server/src/services/TaxRates/GetTaxRate.ts create mode 100644 packages/server/src/services/TaxRates/GetTaxRates.ts create mode 100644 packages/server/src/services/TaxRates/TaxRatesApplication.ts create mode 100644 packages/server/src/services/TaxRates/constants.ts diff --git a/packages/server/src/api/controllers/TaxRates/TaxRates.ts b/packages/server/src/api/controllers/TaxRates/TaxRates.ts new file mode 100644 index 000000000..d9752dede --- /dev/null +++ b/packages/server/src/api/controllers/TaxRates/TaxRates.ts @@ -0,0 +1,108 @@ +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 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'; + +@Service() +export class TaxRatesController extends BaseController { + @Inject() + private taxRatesApplication: TaxRatesApplication; + + /** + * Router constructor. + */ + public router() { + const router = Router(); + + router.post( + '/', + this.taxRateValidationSchema, + this.validationResult, + asyncMiddleware(this.createTaxRate.bind(this)) + ); + router.post( + '/:tax_rate_id', + this.taxRateValidationSchema, + this.validationResult, + asyncMiddleware(this.editTaxRate.bind(this)) + ); + router.delete( + '/:tax_rate_id', + this.taxRateValidationSchema, + this.validationResult, + asyncMiddleware(this.deleteTaxRate.bind(this)) + ); + router.get( + '/:tax_rate_id', + this.taxRateValidationSchema, + this.validationResult, + asyncMiddleware(this.getTaxRate.bind(this)) + ); + router.get( + '/', + this.taxRateValidationSchema, + this.validationResult, + asyncMiddleware(this.getTaxRates.bind(this)) + ); + return router; + } + + /** + * Save settings validation schema. + */ + private get taxRateValidationSchema() { + return [ + body('rate').exists().isNumeric().toFloat(), + body('is_non_recoverable').exists().isBoolean().default(false), + ]; + } + + /** + * + * @param {Request} req - + * @param {Response} res - + */ + public async createTaxRate(req: Request, res: Response, next) { + const taxRate = await this.taxRatesApplication.createTaxRate() + } + + /** + * + * @param {Request} req - + * @param {Response} res - + */ + public async editTaxRate(req: Request, res: Response, next) { + const taxRate = await this.taxRatesApplication.editTaxRate(); + + } + + /** + * + * @param {Request} req - + * @param {Response} res - + */ + public async deleteTaxRate(req: Request, res: Response, next) { + await this.taxRatesApplication.deleteTaxRate(); + } + + /** + * + * @param {Request} req - + * @param {Response} res - + */ + public async getTaxRate(req: Request, res: Response, next) {} + + /** + * + * @param {Request} req - + * @param {Response} res - + */ + public async getTaxRates(req: Request, res: Response, next) {} +} diff --git a/packages/server/src/api/controllers/TaxRates/index.ts b/packages/server/src/api/controllers/TaxRates/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js new file mode 100644 index 000000000..3cdbf1098 --- /dev/null +++ b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js @@ -0,0 +1,14 @@ +exports.up = (knex) => { + return knex.schema.createTable('tax_rates', (table) => { + table.increments(); + table.string('name'); + table.decimal('rate'); + table.boolean('is_non_recoverable'); + table.boolean('is_compound'); + table.timestamps(); + }); +}; + +exports.down = (knex) => { + return knex.schema.dropTableIfExists('tax_rates'); +}; diff --git a/packages/server/src/interfaces/TaxRate.ts b/packages/server/src/interfaces/TaxRate.ts new file mode 100644 index 000000000..4514a732a --- /dev/null +++ b/packages/server/src/interfaces/TaxRate.ts @@ -0,0 +1,48 @@ +import { Knex } from 'knex'; + +export interface ITaxRate {} + +export interface ICommonTaxRateDTO { + name: string; + rate: number; + IsNonRecoverable: boolean; + IsCompound: boolean; +} +export interface ICreateTaxRateDTO extends ICommonTaxRateDTO {} +export interface IEditTaxRateDTO extends ICommonTaxRateDTO {} + +export interface ITaxRateCreatingPayload { + createTaxRateDTO: ICreateTaxRateDTO; + tenantId: number; + trx: Knex.Transaction; +} +export interface ITaxRateCreatedPayload { + createTaxRateDTO: ICreateTaxRateDTO; + taxRate: ITaxRate; + tenantId: number; + trx: Knex.Transaction; +} + +export interface ITaxRateEditingPayload { + editTaxRateDTO: IEditTaxRateDTO; + tenantId: number; + trx: Knex.Transaction; +} +export interface ITaxRateEditedPayload { + editTaxRateDTO: IEditTaxRateDTO; + oldTaxRate: ITaxRate; + taxRate: ITaxRate; + tenantId: number; + trx: Knex.Transaction; +} + +export interface ITaxRateDeletingPayload { + oldTaxRate: ITaxRate; + tenantId: number; + trx: Knex.Transaction; +} +export interface ITaxRateDeletedPayload { + oldTaxRate: ITaxRate; + tenantId: number; + trx: Knex.Transaction; +} diff --git a/packages/server/src/interfaces/index.ts b/packages/server/src/interfaces/index.ts index 7cd789457..1b23eedd3 100644 --- a/packages/server/src/interfaces/index.ts +++ b/packages/server/src/interfaces/index.ts @@ -73,6 +73,7 @@ export * from './Project'; export * from './Tasks'; export * from './Times'; export * from './ProjectProfitabilitySummary'; +export * from './TaxRate'; export interface I18nService { __: (input: string) => string; diff --git a/packages/server/src/loaders/tenantModels.ts b/packages/server/src/loaders/tenantModels.ts index 5e07ff3dd..3c7865922 100644 --- a/packages/server/src/loaders/tenantModels.ts +++ b/packages/server/src/loaders/tenantModels.ts @@ -58,6 +58,7 @@ import ItemWarehouseQuantity from 'models/ItemWarehouseQuantity'; import Project from 'models/Project'; import Time from 'models/Time'; import Task from 'models/Task'; +import TaxRate from 'models/TaxRate'; export default (knex) => { const models = { @@ -119,6 +120,7 @@ export default (knex) => { Project, Time, Task, + TaxRate, }; return mapValues(models, (model) => model.bindKnex(knex)); }; diff --git a/packages/server/src/models/TaxRate.ts b/packages/server/src/models/TaxRate.ts new file mode 100644 index 000000000..b137a24d2 --- /dev/null +++ b/packages/server/src/models/TaxRate.ts @@ -0,0 +1,40 @@ +import { mixin, Model, raw } from 'objection'; +import TenantModel from 'models/TenantModel'; +import ModelSearchable from './ModelSearchable'; + +export default class TaxRate extends mixin(TenantModel, [ModelSearchable]) { + /** + * Table name + */ + static get tableName() { + return 'tax_rates'; + } + + /** + * Timestamps columns. + */ + get timestamps() { + return ['created_at', 'updated_at']; + } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return []; + } + + /** + * Model modifiers. + */ + static get modifiers() { + return {}; + } + + /** + * Relationship mapping. + */ + static get relationMappings() { + return {}; + } +} diff --git a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts new file mode 100644 index 000000000..1b25cabee --- /dev/null +++ b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts @@ -0,0 +1,16 @@ +import { ServiceError } from '@/exceptions'; +import TaxRate from '@/models/TaxRate'; +import { Service } from 'typedi'; + +@Service() +export class CommandTaxRatesValidators { + /** + * + * @param {} taxRate + */ + public validateTaxRateExistance(taxRate: TaxRate | undefined | null) { + if (!taxRate) { + throw new ServiceError(ERRORS.TAX_RATE_NOT_FOUND); + } + } +} diff --git a/packages/server/src/services/TaxRates/CreateTaxRate.ts b/packages/server/src/services/TaxRates/CreateTaxRate.ts new file mode 100644 index 000000000..1414b5654 --- /dev/null +++ b/packages/server/src/services/TaxRates/CreateTaxRate.ts @@ -0,0 +1,53 @@ +import { Knex } from 'knex'; +import { + ICreateTaxRateDTO, + ITaxRateCreatedPayload, + ITaxRateCreatingPayload, +} from '@/interfaces'; +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'; + +@Service() +export class CreateTaxRate { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private uow: UnitOfWork; + + /** + * Creates a new tax rate. + * @param {number} tenantId + * @param {ICreateTaxRateDTO} createTaxRateDTO + */ + public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { + const { TaxRate } = this.tenancy.models(tenantId); + + 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).insert({ ...createTaxRateDTO }); + + // Triggers `onTaxRateCreated` event. + await this.eventPublisher.emitAsync(events.taxRates.onCreated, { + createTaxRateDTO, + taxRate, + tenantId, + trx, + } as ITaxRateCreatedPayload); + + return taxRate; + }); + } +} diff --git a/packages/server/src/services/TaxRates/DeleteTaxRate.ts b/packages/server/src/services/TaxRates/DeleteTaxRate.ts new file mode 100644 index 000000000..17f3a339e --- /dev/null +++ b/packages/server/src/services/TaxRates/DeleteTaxRate.ts @@ -0,0 +1,54 @@ +import { Inject, Service } from 'typedi'; +import { Knex } from 'knex'; +import { ITaxRateDeletedPayload, ITaxRateDeletingPayload } from '@/interfaces'; +import UnitOfWork from '../UnitOfWork'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { CommandTaxRatesValidators } from './CommandTaxRatesValidators'; +import events from '@/subscribers/events'; + +@Service() +export class DeleteTaxRateService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private validators: CommandTaxRatesValidators; + + /** + * + * @param tenantId + * @param taxRateId + */ + public deleteTaxRate(tenantId: number, taxRateId: number) { + const { TaxRate } = this.tenancy.models(tenantId); + + const oldTaxRate = TaxRate.query().findById(taxRateId); + + this.validators.validateTaxRateExistance(oldTaxRate); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onSaleInvoiceCreating` event. + await this.eventPublisher.emitAsync(events.taxRates.onDeleting, { + oldTaxRate, + tenantId, + trx, + } as ITaxRateDeletingPayload); + + await TaxRate.query(trx).findById(taxRateId).delete(); + + // + await this.eventPublisher.emitAsync(events.taxRates.onDeleted, { + oldTaxRate, + tenantId, + trx, + } as ITaxRateDeletedPayload); + }); + } +} diff --git a/packages/server/src/services/TaxRates/EditTaxRate.ts b/packages/server/src/services/TaxRates/EditTaxRate.ts new file mode 100644 index 000000000..a6b502388 --- /dev/null +++ b/packages/server/src/services/TaxRates/EditTaxRate.ts @@ -0,0 +1,68 @@ +import { + IEditTaxRateDTO, + ITaxRateCreatingPayload, + ITaxRateEditedPayload, + ITaxRateEditingPayload, +} from '@/interfaces'; +import { Inject } from 'typedi'; +import UnitOfWork from '../UnitOfWork'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { Knex } from 'knex'; +import { CommandTaxRatesValidators } from './CommandTaxRatesValidators'; +import events from '@/subscribers/events'; + +export class EditTaxRateService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private validators: CommandTaxRatesValidators; + + /** + * + * @param {number} tenantId + * @param {number} taxRateId + * @param {IEditTaxRateDTO} taxRateEditDTO + */ + public editTaxRate( + tenantId: number, + taxRateId: number, + editTaxRateDTO: IEditTaxRateDTO + ) { + const { TaxRate } = this.tenancy.models(tenantId); + + const oldTaxRate = TaxRate.query().findById(taxRateId); + + this.validators.validateTaxRateExistance(oldTaxRate); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onTaxRateCreating` event. + await this.eventPublisher.emitAsync(events.taxRates.onCreating, { + editTaxRateDTO, + tenantId, + trx, + } as ITaxRateEditingPayload); + + const taxRate = await TaxRate.query(trx) + .findById(taxRateId) + .patch({ ...editTaxRateDTO }); + + // Triggers `onTaxRateCreated` event. + await this.eventPublisher.emitAsync(events.taxRates.onCreated, { + editTaxRateDTO, + taxRate, + tenantId, + trx, + } as ITaxRateEditedPayload); + + return taxRate; + }); + } +} diff --git a/packages/server/src/services/TaxRates/GetTaxRate.ts b/packages/server/src/services/TaxRates/GetTaxRate.ts new file mode 100644 index 000000000..536741b3e --- /dev/null +++ b/packages/server/src/services/TaxRates/GetTaxRate.ts @@ -0,0 +1,28 @@ +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { CommandTaxRatesValidators } from './CommandTaxRatesValidators'; + +@Service() +export class GetTaxRateService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private validators: CommandTaxRatesValidators; + + /** + * + * @param {number} tenantId + * @param {number} taxRateId + * @returns + */ + public async getTaxRate(tenantId: number, taxRateId: number) { + const { TaxRate } = this.tenancy.models(tenantId); + + const taxRate = await TaxRate.query().findById(taxRateId); + + this.validators.validateTaxRateExistance(taxRate); + + return taxRate; + } +} diff --git a/packages/server/src/services/TaxRates/GetTaxRates.ts b/packages/server/src/services/TaxRates/GetTaxRates.ts new file mode 100644 index 000000000..6344d4eed --- /dev/null +++ b/packages/server/src/services/TaxRates/GetTaxRates.ts @@ -0,0 +1,21 @@ +import { Inject, Service } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; + +@Service() +export class GetTaxRatesService { + @Inject() + private tenancy: HasTenancyService; + + /** + * + * @param {number} tenantId + * @returns + */ + public async getTaxRates(tenantId: number) { + const { TaxRate } = this.tenancy.models(tenantId); + + const taxRates = await TaxRate.query(); + + return taxRates; + } +} diff --git a/packages/server/src/services/TaxRates/TaxRatesApplication.ts b/packages/server/src/services/TaxRates/TaxRatesApplication.ts new file mode 100644 index 000000000..63190c83b --- /dev/null +++ b/packages/server/src/services/TaxRates/TaxRatesApplication.ts @@ -0,0 +1,82 @@ +import { Inject, Service } from 'typedi'; +import { ICreateTaxRateDTO, IEditTaxRateDTO } from '@/interfaces'; +import { CreateTaxRate } from './CreateTaxRate'; +import { DeleteTaxRateService } from './DeleteTaxRate'; +import { EditTaxRateService } from './EditTaxRate'; +import { GetTaxRateService } from './GetTaxRate'; +import { GetTaxRatesService } from './GetTaxRates'; + +@Service() +export class TaxRatesApplication { + @Inject() + private createTaxRateService: CreateTaxRate; + + @Inject() + private editTaxRateService: EditTaxRateService; + + @Inject() + private deleteTaxRateService: DeleteTaxRateService; + + @Inject() + private getTaxRateService: GetTaxRateService; + + @Inject() + private getTaxRatesService: GetTaxRatesService; + + /** + * Creates a new tax rate. + * @param {number} tenantId + * @param {ICreateTaxRateDTO} createTaxRateDTO + * @returns + */ + public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { + return this.createTaxRateService.createTaxRate(tenantId, createTaxRateDTO); + } + + /** + * Edits the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @param {IEditTaxRateDTO} taxRateEditDTO + */ + public editTaxRate( + tenantId: number, + taxRateId: number, + editTaxRateDTO: IEditTaxRateDTO + ) { + return this.editTaxRateService.editTaxRate( + tenantId, + taxRateId, + editTaxRateDTO + ); + } + + /** + * Deletes the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @returns {Promise} + */ + public deleteTaxRate(tenantId: number, taxRateId: number) { + return this.deleteTaxRateService.deleteTaxRate(tenantId, taxRateId); + } + + /** + * Retrieves the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @returns + */ + public getTaxRate(tenantId: number, taxRateId: number) { + return this.getTaxRateService.getTaxRate(tenantId, taxRateId); + } + + /** + * Retrieves the tax rates list. + * @param {number} tenantId + * @returns + */ + 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 new file mode 100644 index 000000000..3ceceeab4 --- /dev/null +++ b/packages/server/src/services/TaxRates/constants.ts @@ -0,0 +1,3 @@ +const ERRORS = { + TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND', +}; diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index 19416b65d..f8653238a 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -13,7 +13,7 @@ export default { sendResetPassword: 'onSendResetPassword', resetPassword: 'onResetPassword', - resetingPassword: 'onResetingPassword' + resetingPassword: 'onResetingPassword', }, /** @@ -557,4 +557,15 @@ export default { onDeleting: 'onProjectTimeDeleting', onDeleted: 'onProjectTimeDeleted', }, + + taxRates: { + onCreating: 'onTaxRateCreating', + onCreated: 'onTaxRateCreated', + + onEditing: 'onTaxRateEditing', + onEdited: 'onTaxRateEdited', + + onDeleting: 'onTaxRateDeleting', + onDeleted: 'onTaxRateDeleted', + }, };