feat: tax rates crud service

This commit is contained in:
Ahmed Bouhuolia
2023-08-11 16:00:39 +02:00
parent 04d134806b
commit d6f56568a3
12 changed files with 189 additions and 50 deletions

View File

@@ -1,14 +1,12 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { body, query } from 'express-validator'; import { body, param } from 'express-validator';
import { pick } from 'lodash';
import { IOptionDTO, IOptionsDTO } from '@/interfaces';
import BaseController from '@/api/controllers/BaseController'; import BaseController from '@/api/controllers/BaseController';
import asyncMiddleware from '@/api/middleware/asyncMiddleware'; 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 { TaxRatesApplication } from '@/services/TaxRates/TaxRatesApplication';
import { HookNextFunction } from 'mongoose';
import { ServiceError } from '@/exceptions';
import { ERRORS } from '@/services/TaxRates/constants';
@Service() @Service()
export class TaxRatesController extends BaseController { export class TaxRatesController extends BaseController {
@@ -25,84 +23,181 @@ export class TaxRatesController extends BaseController {
'/', '/',
this.taxRateValidationSchema, this.taxRateValidationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.createTaxRate.bind(this)) asyncMiddleware(this.createTaxRate.bind(this)),
this.handleServiceErrors
); );
router.post( router.post(
'/:tax_rate_id', '/:id',
this.taxRateValidationSchema, [param('id').exists().toInt(), ...this.taxRateValidationSchema],
this.validationResult, this.validationResult,
asyncMiddleware(this.editTaxRate.bind(this)) asyncMiddleware(this.editTaxRate.bind(this)),
this.handleServiceErrors
); );
router.delete( router.delete(
'/:tax_rate_id', '/:id',
this.taxRateValidationSchema, [param('id').exists().toInt()],
this.validationResult, this.validationResult,
asyncMiddleware(this.deleteTaxRate.bind(this)) asyncMiddleware(this.deleteTaxRate.bind(this)),
this.handleServiceErrors
); );
router.get( router.get(
'/:tax_rate_id', '/:id',
this.taxRateValidationSchema, [param('id').exists().toInt()],
this.validationResult, this.validationResult,
asyncMiddleware(this.getTaxRate.bind(this)) asyncMiddleware(this.getTaxRate.bind(this)),
this.handleServiceErrors
); );
router.get( router.get(
'/', '/',
this.taxRateValidationSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.getTaxRates.bind(this)) asyncMiddleware(this.getTaxRates.bind(this)),
this.handleServiceErrors
); );
return router; return router;
} }
/** /**
* Save settings validation schema. * Tax rate validation schema.
*/ */
private get taxRateValidationSchema() { private get taxRateValidationSchema() {
return [ return [
body('name').exists(),
body('code').exists().isString(),
body('rate').exists().isNumeric().toFloat(), 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 {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
public async createTaxRate(req: Request, res: Response, next) { 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 {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
public async editTaxRate(req: Request, res: Response, next) { 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 {Request} req -
* @param {Response} res - * @param {Response} res -
*/ */
public async deleteTaxRate(req: Request, res: Response, next) { 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 {Request} req -
* @param {Response} res - * @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 {Request} req -
* @param {Response} res - * @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);
}
} }

View File

@@ -55,6 +55,7 @@ import { InventoryItemsCostController } from './controllers/Inventory/Inventorty
import { ProjectsController } from './controllers/Projects/Projects'; import { ProjectsController } from './controllers/Projects/Projects';
import { ProjectTasksController } from './controllers/Projects/Tasks'; import { ProjectTasksController } from './controllers/Projects/Tasks';
import { ProjectTimesController } from './controllers/Projects/Times'; import { ProjectTimesController } from './controllers/Projects/Times';
import { TaxRatesController } from './controllers/TaxRates/TaxRates';
export default () => { export default () => {
const app = Router(); const app = Router();
@@ -129,6 +130,7 @@ export default () => {
); );
dashboard.use('/warehouses', Container.get(WarehousesController).router()); dashboard.use('/warehouses', Container.get(WarehousesController).router());
dashboard.use('/projects', Container.get(ProjectsController).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(ProjectTasksController).router());
dashboard.use('/', Container.get(ProjectTimesController).router()); dashboard.use('/', Container.get(ProjectTimesController).router());

View File

@@ -2,9 +2,11 @@ exports.up = (knex) => {
return knex.schema.createTable('tax_rates', (table) => { return knex.schema.createTable('tax_rates', (table) => {
table.increments(); table.increments();
table.string('name'); table.string('name');
table.string('code');
table.decimal('rate'); table.decimal('rate');
table.boolean('is_non_recoverable'); table.boolean('is_non_recoverable');
table.boolean('is_compound'); table.boolean('is_compound');
table.integer('status');
table.timestamps(); table.timestamps();
}); });
}; };

View File

@@ -4,6 +4,7 @@ export interface ITaxRate {}
export interface ICommonTaxRateDTO { export interface ICommonTaxRateDTO {
name: string; name: string;
code: string;
rate: number; rate: number;
IsNonRecoverable: boolean; IsNonRecoverable: boolean;
IsCompound: boolean; IsCompound: boolean;

View File

@@ -1,16 +1,36 @@
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import TaxRate from '@/models/TaxRate'; import { Inject, Service } from 'typedi';
import { Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService';
import { ITaxRate } from '@/interfaces';
import { ERRORS } from './constants';
@Service() @Service()
export class CommandTaxRatesValidators { export class CommandTaxRatesValidators {
@Inject()
private tenancy: HasTenancyService;
/** /**
* * Validates the tax rate existance.
* @param {} taxRate * @param {TaxRate | undefined | null} taxRate
*/ */
public validateTaxRateExistance(taxRate: TaxRate | undefined | null) { public validateTaxRateExistance(taxRate: ITaxRate | undefined | null) {
if (!taxRate) { if (!taxRate) {
throw new ServiceError(ERRORS.TAX_RATE_NOT_FOUND); 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);
}
}
} }

View File

@@ -9,6 +9,7 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '../Tenancy/TenancyService'; import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidators';
@Service() @Service()
export class CreateTaxRate { export class CreateTaxRate {
@@ -21,14 +22,25 @@ export class CreateTaxRate {
@Inject() @Inject()
private uow: UnitOfWork; private uow: UnitOfWork;
@Inject()
private validators: CommandTaxRatesValidators;
/** /**
* Creates a new tax rate. * Creates a new tax rate.
* @param {number} tenantId * @param {number} tenantId
* @param {ICreateTaxRateDTO} createTaxRateDTO * @param {ICreateTaxRateDTO} createTaxRateDTO
*/ */
public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { public async createTaxRate(
tenantId: number,
createTaxRateDTO: ICreateTaxRateDTO
) {
const { TaxRate } = this.tenancy.models(tenantId); 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) => { return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onTaxRateCreating` event. // Triggers `onTaxRateCreating` event.
await this.eventPublisher.emitAsync(events.taxRates.onCreating, { await this.eventPublisher.emitAsync(events.taxRates.onCreating, {

View File

@@ -22,19 +22,21 @@ export class DeleteTaxRateService {
private validators: CommandTaxRatesValidators; private validators: CommandTaxRatesValidators;
/** /**
* * Deletes the given tax rate.
* @param tenantId * @param {number} tenantId
* @param taxRateId * @param {number} taxRateId
* @returns {Promise<void>}
*/ */
public deleteTaxRate(tenantId: number, taxRateId: number) { public deleteTaxRate(tenantId: number, taxRateId: number) {
const { TaxRate } = this.tenancy.models(tenantId); const { TaxRate } = this.tenancy.models(tenantId);
const oldTaxRate = TaxRate.query().findById(taxRateId); const oldTaxRate = TaxRate.query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate); this.validators.validateTaxRateExistance(oldTaxRate);
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onSaleInvoiceCreating` event. // Triggers `onTaxRateDeleting` event.
await this.eventPublisher.emitAsync(events.taxRates.onDeleting, { await this.eventPublisher.emitAsync(events.taxRates.onDeleting, {
oldTaxRate, oldTaxRate,
tenantId, tenantId,
@@ -43,7 +45,7 @@ export class DeleteTaxRateService {
await TaxRate.query(trx).findById(taxRateId).delete(); await TaxRate.query(trx).findById(taxRateId).delete();
// // Triggers `onTaxRateDeleted` event.
await this.eventPublisher.emitAsync(events.taxRates.onDeleted, { await this.eventPublisher.emitAsync(events.taxRates.onDeleted, {
oldTaxRate, oldTaxRate,
tenantId, tenantId,

View File

@@ -26,10 +26,11 @@ export class EditTaxRateService {
private validators: CommandTaxRatesValidators; private validators: CommandTaxRatesValidators;
/** /**
* * Edits the given tax rate.
* @param {number} tenantId * @param {number} tenantId
* @param {number} taxRateId * @param {number} taxRateId
* @param {IEditTaxRateDTO} taxRateEditDTO * @param {IEditTaxRateDTO} taxRateEditDTO
* @returns {Promise<ITaxRate>}
*/ */
public editTaxRate( public editTaxRate(
tenantId: number, tenantId: number,
@@ -40,6 +41,7 @@ export class EditTaxRateService {
const oldTaxRate = TaxRate.query().findById(taxRateId); const oldTaxRate = TaxRate.query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate); this.validators.validateTaxRateExistance(oldTaxRate);
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {

View File

@@ -11,16 +11,17 @@ export class GetTaxRateService {
private validators: CommandTaxRatesValidators; private validators: CommandTaxRatesValidators;
/** /**
* * Retrieves the given tax rate.
* @param {number} tenantId * @param {number} tenantId
* @param {number} taxRateId * @param {number} taxRateId
* @returns * @returns {Promise<ITaxRate>}
*/ */
public async getTaxRate(tenantId: number, taxRateId: number) { public async getTaxRate(tenantId: number, taxRateId: number) {
const { TaxRate } = this.tenancy.models(tenantId); const { TaxRate } = this.tenancy.models(tenantId);
const taxRate = await TaxRate.query().findById(taxRateId); const taxRate = await TaxRate.query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(taxRate); this.validators.validateTaxRateExistance(taxRate);
return taxRate; return taxRate;

View File

@@ -7,9 +7,9 @@ export class GetTaxRatesService {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
/** /**
* * Retrieves the tax rates list.
* @param {number} tenantId * @param {number} tenantId
* @returns * @returns {Promise<ITaxRate[]>}
*/ */
public async getTaxRates(tenantId: number) { public async getTaxRates(tenantId: number) {
const { TaxRate } = this.tenancy.models(tenantId); const { TaxRate } = this.tenancy.models(tenantId);

View File

@@ -27,7 +27,7 @@ export class TaxRatesApplication {
* Creates a new tax rate. * Creates a new tax rate.
* @param {number} tenantId * @param {number} tenantId
* @param {ICreateTaxRateDTO} createTaxRateDTO * @param {ICreateTaxRateDTO} createTaxRateDTO
* @returns * @returns {Promise<ITaxRate>}
*/ */
public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) { public createTaxRate(tenantId: number, createTaxRateDTO: ICreateTaxRateDTO) {
return this.createTaxRateService.createTaxRate(tenantId, createTaxRateDTO); return this.createTaxRateService.createTaxRate(tenantId, createTaxRateDTO);
@@ -38,6 +38,7 @@ export class TaxRatesApplication {
* @param {number} tenantId * @param {number} tenantId
* @param {number} taxRateId * @param {number} taxRateId
* @param {IEditTaxRateDTO} taxRateEditDTO * @param {IEditTaxRateDTO} taxRateEditDTO
* @returns {Promise<ITaxRate>}
*/ */
public editTaxRate( public editTaxRate(
tenantId: number, tenantId: number,
@@ -65,7 +66,7 @@ export class TaxRatesApplication {
* Retrieves the given tax rate. * Retrieves the given tax rate.
* @param {number} tenantId * @param {number} tenantId
* @param {number} taxRateId * @param {number} taxRateId
* @returns * @returns {Promise<ITaxRate>}
*/ */
public getTaxRate(tenantId: number, taxRateId: number) { public getTaxRate(tenantId: number, taxRateId: number) {
return this.getTaxRateService.getTaxRate(tenantId, taxRateId); return this.getTaxRateService.getTaxRate(tenantId, taxRateId);
@@ -74,7 +75,7 @@ export class TaxRatesApplication {
/** /**
* Retrieves the tax rates list. * Retrieves the tax rates list.
* @param {number} tenantId * @param {number} tenantId
* @returns * @returns {Promise<ITaxRate[]>}
*/ */
public getTaxRates(tenantId: number) { public getTaxRates(tenantId: number) {
return this.getTaxRatesService.getTaxRates(tenantId); return this.getTaxRatesService.getTaxRates(tenantId);

View File

@@ -1,3 +1,4 @@
const ERRORS = { export const ERRORS = {
TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND', TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND',
TAX_CODE_NOT_UNIQUE: 'TAX_CODE_NOT_UNIQUE',
}; };