From 4e53d084973446d4441b0d20798382c908aa0484 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 18 Sep 2023 01:38:38 +0200 Subject: [PATCH] feat(server): wip activate/inactivate tax rate --- .../src/api/controllers/TaxRates/TaxRates.ts | 72 +++++++++++++++++++ .../20230810191606_create_tax_rates.js | 7 +- ...e_balance_column_on_sale_invoices_table.js | 7 ++ packages/server/src/interfaces/TaxRate.ts | 18 +++++ .../src/services/TaxRates/ActivateTaxRate.ts | 64 +++++++++++++++++ .../TaxRates/CommandTaxRatesValidators.ts | 20 ++++++ .../services/TaxRates/InactivateTaxRate.ts | 67 +++++++++++++++++ .../services/TaxRates/TaxRatesApplication.ts | 26 +++++++ .../server/src/services/TaxRates/constants.ts | 2 + packages/server/src/subscribers/events.ts | 6 ++ 10 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 packages/server/src/database/migrations/20230917105226_rename_balance_column_on_sale_invoices_table.js create mode 100644 packages/server/src/services/TaxRates/ActivateTaxRate.ts create mode 100644 packages/server/src/services/TaxRates/InactivateTaxRate.ts diff --git a/packages/server/src/api/controllers/TaxRates/TaxRates.ts b/packages/server/src/api/controllers/TaxRates/TaxRates.ts index b1493ab83..bb80985eb 100644 --- a/packages/server/src/api/controllers/TaxRates/TaxRates.ts +++ b/packages/server/src/api/controllers/TaxRates/TaxRates.ts @@ -33,6 +33,20 @@ export class TaxRatesController extends BaseController { asyncMiddleware(this.editTaxRate.bind(this)), this.handleServiceErrors ); + router.post( + '/:id/active', + [param('id').exists().toInt()], + this.validationResult, + asyncMiddleware(this.activateTaxRate.bind(this)), + this.handleServiceErrors + ); + router.post( + '/:id/inactive', + [param('id').exists().toInt()], + this.validationResult, + asyncMiddleware(this.inactivateTaxRate.bind(this)), + this.handleServiceErrors + ); router.delete( '/:id', [param('id').exists().toInt()], @@ -64,7 +78,9 @@ export class TaxRatesController extends BaseController { body('name').exists(), body('code').exists().isString(), body('rate').exists().isNumeric().toFloat(), + body('description').optional().trim().isString(), body('is_non_recoverable').optional().isBoolean().default(false), + body('is_compound').optional().isBoolean().default(false), body('status').optional().toUpperCase().isIn(['ARCHIVED', 'ACTIVE']), ]; } @@ -173,6 +189,52 @@ export class TaxRatesController extends BaseController { } } + /** + * Inactivates the given tax rate. + * @param req + * @param res + * @param next + * @returns + */ + public async inactivateTaxRate(req: Request, res: Response, next) { + const { tenantId } = req; + const { id: taxRateId } = req.params; + + try { + await this.taxRatesApplication.inactivateTaxRate(tenantId, taxRateId); + + return res.status(200).send({ + id: taxRateId, + message: 'The given tax rate has been inactivated successfully.', + }); + } catch (error) { + next(error); + } + } + + /** + * Inactivates the given tax rate. + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns + */ + public async activateTaxRate(req: Request, res: Response, next) { + const { tenantId } = req; + const { id: taxRateId } = req.params; + + try { + await this.taxRatesApplication.activateTaxRate(tenantId, taxRateId); + + return res.status(200).send({ + id: taxRateId, + message: 'The given tax rate has been activated successfully.', + }); + } catch (error) { + next(error); + } + } + /** * Handles service errors. * @param {Error} error @@ -197,6 +259,16 @@ export class TaxRatesController extends BaseController { errors: [{ type: ERRORS.TAX_RATE_NOT_FOUND, code: 200 }], }); } + if (error.errorType === ERRORS.TAX_RATE_ALREADY_INACTIVE) { + return res.boom.badRequest(null, { + errors: [{ type: ERRORS.TAX_RATE_ALREADY_INACTIVE, code: 300 }], + }); + } + if (error.errorType === ERRORS.TAX_RATE_ALREADY_ACTIVE) { + return res.boom.badRequest(null, { + errors: [{ type: ERRORS.TAX_RATE_ALREADY_ACTIVE, code: 400 }], + }); + } } next(error); } 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 7baf6be4e..d9a115ef9 100644 --- a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js +++ b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js @@ -5,9 +5,11 @@ exports.up = (knex) => { table.string('name'); table.string('code'); table.decimal('rate'); + table.string('description'); table.boolean('is_non_recoverable'); table.boolean('is_compound'); - table.integer('status'); + table.boolean('active').defaultTo(false); + table.date('deleted_at'); table.timestamps(); }) .table('items_entries', (table) => { @@ -43,9 +45,6 @@ exports.up = (knex) => { .references('id') .inTable('tax_rates'); table.decimal('tax_rate').unsigned(); - }) - .table('sales_invoices', (table) => { - table.rename('balance', 'amount'); }); }; diff --git a/packages/server/src/database/migrations/20230917105226_rename_balance_column_on_sale_invoices_table.js b/packages/server/src/database/migrations/20230917105226_rename_balance_column_on_sale_invoices_table.js new file mode 100644 index 000000000..6e34f1839 --- /dev/null +++ b/packages/server/src/database/migrations/20230917105226_rename_balance_column_on_sale_invoices_table.js @@ -0,0 +1,7 @@ +exports.up = function (knex) { + return knex.table('sales_invoices', (table) => { + table.renameColumn('balance', 'amount'); + }); +}; + +exports.down = function (knex) {}; diff --git a/packages/server/src/interfaces/TaxRate.ts b/packages/server/src/interfaces/TaxRate.ts index 4e1275392..73fda18ea 100644 --- a/packages/server/src/interfaces/TaxRate.ts +++ b/packages/server/src/interfaces/TaxRate.ts @@ -5,14 +5,20 @@ export interface ITaxRate { name: string; code: string; rate: number; + description: string; + IsNonRecoverable: boolean; + IsCompound: boolean; + active: boolean; } export interface ICommonTaxRateDTO { name: string; code: string; rate: number; + description: string; IsNonRecoverable: boolean; IsCompound: boolean; + active: boolean; } export interface ICreateTaxRateDTO extends ICommonTaxRateDTO {} export interface IEditTaxRateDTO extends ICommonTaxRateDTO {} @@ -47,6 +53,18 @@ export interface ITaxRateDeletingPayload { tenantId: number; trx: Knex.Transaction; } + +export interface ITaxRateActivatingPayload { + taxRateId: number; + tenantId: number; + trx: Knex.Transaction; +} +export interface ITaxRateActivatedPayload { + taxRateId: number; + tenantId: number; + trx: Knex.Transaction; +} + export interface ITaxRateDeletedPayload { oldTaxRate: ITaxRate; tenantId: number; diff --git a/packages/server/src/services/TaxRates/ActivateTaxRate.ts b/packages/server/src/services/TaxRates/ActivateTaxRate.ts new file mode 100644 index 000000000..99796912d --- /dev/null +++ b/packages/server/src/services/TaxRates/ActivateTaxRate.ts @@ -0,0 +1,64 @@ +import { + ITaxRateActivatedPayload, + ITaxRateActivatingPayload, +} from '@/interfaces'; +import { Inject, Service } 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'; + +@Service() +export class ActivateTaxRateService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private validators: CommandTaxRatesValidators; + + /** + * Edits the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @param {IEditTaxRateDTO} taxRateEditDTO + * @returns {Promise} + */ + public activateTaxRate(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 `onTaxRateActivating` event. + await this.eventPublisher.emitAsync(events.taxRates.onActivating, { + taxRateId, + tenantId, + trx, + } as ITaxRateActivatingPayload); + + const taxRate = await TaxRate.query(trx) + .findById(taxRateId) + .patch({ active: 1 }); + + // Triggers `onTaxRateCreated` event. + await this.eventPublisher.emitAsync(events.taxRates.onActivated, { + taxRateId, + tenantId, + trx, + } as ITaxRateActivatedPayload); + + return taxRate; + }); + } +} diff --git a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts index 74ea0ce14..49583cd56 100644 --- a/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts +++ b/packages/server/src/services/TaxRates/CommandTaxRatesValidators.ts @@ -20,6 +20,26 @@ export class CommandTaxRatesValidators { } } + /** + * Validates the given tax rate active. + * @param {ITaxRate} taxRate + */ + public validateTaxRateNotActive(taxRate: ITaxRate) { + if (taxRate.active) { + throw new ServiceError(ERRORS.TAX_RATE_ALREADY_ACTIVE); + } + } + + /** + * Validates the given tax rate inactive. + * @param {ITaxRate} taxRate + */ + public validateTaxRateNotInactive(taxRate: ITaxRate) { + if (!taxRate.active) { + throw new ServiceError(ERRORS.TAX_RATE_ALREADY_INACTIVE); + } + } + /** * Validates the tax code uniquiness. * @param {number} tenantId diff --git a/packages/server/src/services/TaxRates/InactivateTaxRate.ts b/packages/server/src/services/TaxRates/InactivateTaxRate.ts new file mode 100644 index 000000000..b4fd6a368 --- /dev/null +++ b/packages/server/src/services/TaxRates/InactivateTaxRate.ts @@ -0,0 +1,67 @@ +import { + ITaxRateActivatedPayload, + ITaxRateActivatingPayload, +} from '@/interfaces'; +import { Inject, Service } 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'; + +@Service() +export class InactivateTaxRateService { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private eventPublisher: EventPublisher; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private validators: CommandTaxRatesValidators; + + /** + * Edits the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @param {IEditTaxRateDTO} taxRateEditDTO + * @returns {Promise} + */ + public inactivateTaxRate(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); + + // + this.validators.validateTaxRateNotInactive(oldTaxRate); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onTaxRateActivating` event. + await this.eventPublisher.emitAsync(events.taxRates.onInactivating, { + taxRateId, + tenantId, + trx, + } as ITaxRateActivatingPayload); + + const taxRate = await TaxRate.query(trx) + .findById(taxRateId) + .patch({ active: 0 }); + + // Triggers `onTaxRateCreated` event. + await this.eventPublisher.emitAsync(events.taxRates.onInactivated, { + taxRateId, + tenantId, + trx, + } as ITaxRateActivatedPayload); + + return taxRate; + }); + } +} diff --git a/packages/server/src/services/TaxRates/TaxRatesApplication.ts b/packages/server/src/services/TaxRates/TaxRatesApplication.ts index cf141bd47..c237c7251 100644 --- a/packages/server/src/services/TaxRates/TaxRatesApplication.ts +++ b/packages/server/src/services/TaxRates/TaxRatesApplication.ts @@ -5,6 +5,8 @@ import { DeleteTaxRateService } from './DeleteTaxRate'; import { EditTaxRateService } from './EditTaxRate'; import { GetTaxRateService } from './GetTaxRate'; import { GetTaxRatesService } from './GetTaxRates'; +import { ActivateTaxRateService } from './ActivateTaxRate'; +import { InactivateTaxRateService } from './InactivateTaxRate'; @Service() export class TaxRatesApplication { @@ -23,6 +25,12 @@ export class TaxRatesApplication { @Inject() private getTaxRatesService: GetTaxRatesService; + @Inject() + private activateTaxRateService: ActivateTaxRateService; + + @Inject() + private inactivateTaxRateService: InactivateTaxRateService; + /** * Creates a new tax rate. * @param {number} tenantId @@ -80,4 +88,22 @@ export class TaxRatesApplication { public getTaxRates(tenantId: number) { return this.getTaxRatesService.getTaxRates(tenantId); } + + /** + * Activates the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + */ + public activateTaxRate(tenantId: number, taxRateId: number) { + return this.activateTaxRateService.activateTaxRate(tenantId, taxRateId); + } + + /** + * Inactivates the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + */ + public inactivateTaxRate(tenantId: number, taxRateId: number) { + return this.inactivateTaxRateService.inactivateTaxRate(tenantId, taxRateId); + } } diff --git a/packages/server/src/services/TaxRates/constants.ts b/packages/server/src/services/TaxRates/constants.ts index a38968d93..e1553f4ee 100644 --- a/packages/server/src/services/TaxRates/constants.ts +++ b/packages/server/src/services/TaxRates/constants.ts @@ -3,4 +3,6 @@ export const ERRORS = { TAX_CODE_NOT_UNIQUE: 'TAX_CODE_NOT_UNIQUE', ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND', ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', + TAX_RATE_ALREADY_ACTIVE: 'TAX_RATE_ALREADY_ACTIVE', + TAX_RATE_ALREADY_INACTIVE: 'TAX_RATE_ALREADY_INACTIVE' }; diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index df91245b6..24ca0a0a3 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -570,5 +570,11 @@ export default { onDeleting: 'onTaxRateDeleting', onDeleted: 'onTaxRateDeleted', + + onActivating: 'onTaxRateActivating', + onActivated: 'onTaxRateActivated', + + onInactivating: 'onTaxRateInactivating', + onInactivated: 'onTaxRateInactivated' }, };