feat(server): wip activate/inactivate tax rate

This commit is contained in:
Ahmed Bouhuolia
2023-09-18 01:38:38 +02:00
parent 2356921f27
commit 4e53d08497
10 changed files with 285 additions and 4 deletions

View File

@@ -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);
}

View File

@@ -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');
});
};

View File

@@ -0,0 +1,7 @@
exports.up = function (knex) {
return knex.table('sales_invoices', (table) => {
table.renameColumn('balance', 'amount');
});
};
exports.down = function (knex) {};

View File

@@ -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;

View File

@@ -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<ITaxRate>}
*/
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;
});
}
}

View File

@@ -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

View File

@@ -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<ITaxRate>}
*/
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;
});
}
}

View File

@@ -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);
}
}

View File

@@ -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'
};

View File

@@ -570,5 +570,11 @@ export default {
onDeleting: 'onTaxRateDeleting',
onDeleted: 'onTaxRateDeleted',
onActivating: 'onTaxRateActivating',
onActivated: 'onTaxRateActivated',
onInactivating: 'onTaxRateInactivating',
onInactivated: 'onTaxRateInactivated'
},
};