feat(nestjs): migrate to NestJS

This commit is contained in:
Ahmed Bouhuolia
2025-04-07 11:51:24 +02:00
parent f068218a16
commit 55fcc908ef
3779 changed files with 631 additions and 195332 deletions

View File

@@ -0,0 +1,75 @@
import { Inject, Injectable } from '@nestjs/common';
import { keyBy, sumBy } from 'lodash';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { TaxRateModel } from './models/TaxRate.model';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class ItemEntriesTaxTransactions {
constructor(
@Inject(ItemEntry.name)
private itemEntryModel: TenantModelProxy<typeof ItemEntry>,
@Inject(TaxRateModel.name)
private taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Associates tax amount withheld to the model.
* @param model
* @returns
*/
public assocTaxAmountWithheldFromEntries = (model: any) => {
const entries = model.entries.map((entry) =>
this.itemEntryModel().fromJson(entry),
);
const taxAmountWithheld = sumBy(entries, 'taxAmount');
if (taxAmountWithheld) {
model.taxAmountWithheld = taxAmountWithheld;
}
return model;
};
/**
* Associates tax rate id from tax code to entries.
* @param {any} entries
*/
public assocTaxRateIdFromCodeToEntries = async (entries: any) => {
const entriesWithCode = entries.filter((entry) => entry.taxCode);
const taxCodes = entriesWithCode.map((entry) => entry.taxCode);
const foundTaxCodes = await this.taxRateModel()
.query()
.whereIn('code', taxCodes);
const taxCodesMap = keyBy(foundTaxCodes, 'code');
return entries.map((entry) => {
if (entry.taxCode) {
entry.taxRateId = taxCodesMap[entry.taxCode]?.id;
}
return entry;
});
};
/**
* Associates tax rate from tax id to entries.
* @returns {Promise<ItemEntry[]>}
*/
public assocTaxRateFromTaxIdToEntries = async (entries: ItemEntry[]) => {
const entriesWithId = entries.filter((e) => e.taxRateId);
const taxRateIds = entriesWithId.map((e) => e.taxRateId);
const foundTaxes = await this.taxRateModel()
.query()
.whereIn('id', taxRateIds);
const taxRatesMap = keyBy(foundTaxes, 'id');
return entries.map((entry) => {
if (entry.taxRateId) {
entry.taxRate = taxRatesMap[entry.taxRateId]?.rate;
}
return entry;
});
};
}

View File

@@ -0,0 +1,54 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { Item } from '../Items/models/Item';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
export class SyncItemTaxRateOnEditTaxRate {
constructor(
@Inject(Item.name)
private readonly itemModel: TenantModelProxy<typeof Item>,
) {}
/**
* Syncs the new tax rate created to item default sell tax rate.
* @param {number} itemId - Item id.
* @param {number} sellTaxRateId - Sell tax rate id.
*/
public updateItemSellTaxRate = async (
oldSellTaxRateId: number,
sellTaxRateId: number,
trx?: Knex.Transaction,
) => {
// Can't continue if the old and new sell tax rate id are equal.
if (oldSellTaxRateId === sellTaxRateId) return;
await this.itemModel()
.query()
.where('sellTaxRateId', oldSellTaxRateId)
.update({
sellTaxRateId,
});
};
/**
* Syncs the new tax rate created to item default purchase tax rate.
* @param {number} itemId
* @param {number} purchaseTaxRateId
*/
public updateItemPurchaseTaxRate = async (
oldPurchaseTaxRateId: number,
purchaseTaxRateId: number,
trx?: Knex.Transaction,
) => {
// Can't continue if the old and new sell tax rate id are equal.
if (oldPurchaseTaxRateId === purchaseTaxRateId) return;
await this.itemModel()
.query(trx)
.where('purchaseTaxRateId', oldPurchaseTaxRateId)
.update({
purchaseTaxRateId,
});
};
}

View File

@@ -0,0 +1,88 @@
import { CreateTaxRate } from './commands/CreateTaxRate.service';
import { DeleteTaxRateService } from './commands/DeleteTaxRate.service';
import { EditTaxRateService } from './commands/EditTaxRate.service';
import { GetTaxRateService } from './queries/GetTaxRate.service';
import { ActivateTaxRateService } from './commands/ActivateTaxRate.service';
import { InactivateTaxRateService } from './commands/InactivateTaxRate';
import { Injectable } from '@nestjs/common';
import { GetTaxRatesService } from './queries/GetTaxRates.service';
import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto';
@Injectable()
export class TaxRatesApplication {
constructor(
private readonly createTaxRateService: CreateTaxRate,
private readonly editTaxRateService: EditTaxRateService,
private readonly deleteTaxRateService: DeleteTaxRateService,
private readonly getTaxRateService: GetTaxRateService,
private readonly activateTaxRateService: ActivateTaxRateService,
private readonly inactivateTaxRateService: InactivateTaxRateService,
private readonly getTaxRatesService: GetTaxRatesService,
) {}
/**
* Creates a new tax rate.
* @param {ICreateTaxRateDTO} createTaxRateDTO
* @returns {Promise<ITaxRate>}
*/
public createTaxRate(createTaxRateDTO: CreateTaxRateDto) {
return this.createTaxRateService.createTaxRate(createTaxRateDTO);
}
/**
* Edits the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
* @param {IEditTaxRateDTO} taxRateEditDTO
* @returns {Promise<ITaxRate>}
*/
public editTaxRate(taxRateId: number, editTaxRateDTO: EditTaxRateDto) {
return this.editTaxRateService.editTaxRate(taxRateId, editTaxRateDTO);
}
/**
* Deletes the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
* @returns {Promise<void>}
*/
public deleteTaxRate(taxRateId: number) {
return this.deleteTaxRateService.deleteTaxRate(taxRateId);
}
/**
* Retrieves the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
* @returns {Promise<ITaxRate>}
*/
public getTaxRate(taxRateId: number) {
return this.getTaxRateService.getTaxRate(taxRateId);
}
/**
* Retrieves the tax rates list.
* @returns {Promise<ITaxRate[]>}
*/
public getTaxRates() {
return this.getTaxRatesService.getTaxRates();
}
/**
* Activates the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
*/
public activateTaxRate(taxRateId: number) {
return this.activateTaxRateService.activateTaxRate(taxRateId);
}
/**
* Inactivates the given tax rate.
* @param {number} tenantId
* @param {number} taxRateId
*/
public inactivateTaxRate(taxRateId: number) {
return this.inactivateTaxRateService.inactivateTaxRate(taxRateId);
}
}

View File

@@ -0,0 +1,63 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
} from '@nestjs/common';
import { TaxRatesApplication } from './TaxRate.application';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto';
@Controller('tax-rates')
@ApiTags('tax-rates')
export class TaxRatesController {
constructor(private readonly taxRatesApplication: TaxRatesApplication) {}
@Post()
@ApiOperation({ summary: 'Create a new tax rate.' })
public createTaxRate(@Body() createTaxRateDTO: CreateTaxRateDto) {
return this.taxRatesApplication.createTaxRate(createTaxRateDTO);
}
@Put(':id')
@ApiOperation({ summary: 'Edit the given tax rate.' })
public editTaxRate(
@Param('id') taxRateId: number,
@Body() editTaxRateDTO: EditTaxRateDto,
) {
return this.taxRatesApplication.editTaxRate(taxRateId, editTaxRateDTO);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete the given tax rate.' })
public deleteTaxRate(@Param('id') taxRateId: number) {
return this.taxRatesApplication.deleteTaxRate(taxRateId);
}
@Get(':id')
@ApiOperation({ summary: 'Retrieves the tax rate details.' })
public getTaxRate(@Param('id') taxRateId: number) {
return this.taxRatesApplication.getTaxRate(taxRateId);
}
@Get()
@ApiOperation({ summary: 'Retrieves the tax rates.' })
public getTaxRates() {
return this.taxRatesApplication.getTaxRates();
}
@Put(':id/activate')
@ApiOperation({ summary: 'Activate the given tax rate.' })
public activateTaxRate(@Param('id') taxRateId: number) {
return this.taxRatesApplication.activateTaxRate(taxRateId);
}
@Put(':id/inactivate')
@ApiOperation({ summary: 'Inactivate the given tax rate.' })
public inactivateTaxRate(@Param('id') taxRateId: number) {
return this.taxRatesApplication.inactivateTaxRate(taxRateId);
}
}

View File

@@ -0,0 +1,53 @@
import { Module } from '@nestjs/common';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TaxRatesController } from './TaxRate.controller';
import { CreateTaxRate } from './commands/CreateTaxRate.service';
import { InactivateTaxRateService } from './commands/InactivateTaxRate';
import { ActivateTaxRateService } from './commands/ActivateTaxRate.service';
import { GetTaxRateService } from './queries/GetTaxRate.service';
import { DeleteTaxRateService } from './commands/DeleteTaxRate.service';
import { EditTaxRateService } from './commands/EditTaxRate.service';
import { CommandTaxRatesValidators } from './commands/CommandTaxRatesValidator.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TaxRatesApplication } from './TaxRate.application';
import { ItemEntriesTaxTransactions } from './ItemEntriesTaxTransactions.service';
import { GetTaxRatesService } from './queries/GetTaxRates.service';
import { WriteBillTaxTransactionsSubscriber } from './subscribers/WriteBillTaxTransactionsSubscriber';
import { WriteInvoiceTaxTransactionsSubscriber } from './subscribers/WriteInvoiceTaxTransactionsSubscriber';
import { BillTaxRateValidateSubscriber } from './subscribers/BillTaxRateValidateSubscriber';
import { SaleInvoiceTaxRateValidateSubscriber } from './subscribers/SaleInvoiceTaxRateValidateSubscriber';
import { SyncItemTaxRateOnEditTaxSubscriber } from './subscribers/SyncItemTaxRateOnEditTaxSubscriber';
import { WriteTaxTransactionsItemEntries } from './WriteTaxTransactionsItemEntries';
import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { TaxRateTransaction } from './models/TaxRateTransaction.model';
const models = [RegisterTenancyModel(TaxRateTransaction)];
@Module({
imports: [...models],
controllers: [TaxRatesController],
providers: [
CreateTaxRate,
EditTaxRateService,
DeleteTaxRateService,
GetTaxRateService,
GetTaxRatesService,
ActivateTaxRateService,
InactivateTaxRateService,
CommandTaxRatesValidators,
TransformerInjectable,
TenancyContext,
TaxRatesApplication,
ItemEntriesTaxTransactions,
WriteBillTaxTransactionsSubscriber,
WriteInvoiceTaxTransactionsSubscriber,
BillTaxRateValidateSubscriber,
SaleInvoiceTaxRateValidateSubscriber,
SyncItemTaxRateOnEditTaxSubscriber,
WriteTaxTransactionsItemEntries,
SyncItemTaxRateOnEditTaxRate,
],
exports: [ItemEntriesTaxTransactions, ...models],
})
export class TaxRatesModule {}

View File

@@ -0,0 +1,90 @@
import { Knex } from 'knex';
import { TaxRateModel } from './models/TaxRate.model';
export interface ITaxRate {
id?: number;
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 {}
export interface ITaxRateCreatingPayload {
createTaxRateDTO: ICreateTaxRateDTO;
// tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxRateCreatedPayload {
createTaxRateDTO: ICreateTaxRateDTO;
taxRate: TaxRateModel;
// tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxRateEditingPayload {
oldTaxRate: TaxRateModel;
editTaxRateDTO: IEditTaxRateDTO;
// tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxRateEditedPayload {
editTaxRateDTO: IEditTaxRateDTO;
oldTaxRate: TaxRateModel;
taxRate: TaxRateModel;
// tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxRateDeletingPayload {
oldTaxRate: TaxRateModel;
// 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: TaxRateModel;
// tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxTransaction {
id?: number;
taxRateId: number;
referenceType: string;
referenceId: number;
rate: number;
taxAccountId: number;
}
export enum TaxRateAction {
CREATE = 'Create',
EDIT = 'Edit',
DELETE = 'Delete',
VIEW = 'View',
}

View File

@@ -0,0 +1,18 @@
// import { Inject, Service } from 'typedi';
// import { Exportable } from '../Export/Exportable';
// import { TaxRatesApplication } from './TaxRate.application';
// @Service()
// export class TaxRatesExportable extends Exportable {
// @Inject()
// private taxRatesApplication: TaxRatesApplication;
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId
// * @returns
// */
// public exportable(tenantId: number) {
// return this.taxRatesApplication.getTaxRates(tenantId);
// }
// }

View File

@@ -0,0 +1,18 @@
export const TaxRatesSampleData = [
{
'Tax Name': 'Value Added Tax',
Code: 'VAT-STD',
Rate: '20',
Description: 'Standard VAT rate applied to most goods and services.',
'Is Non Recoverable': 'F',
Active: 'T',
},
{
'Tax Name': 'Luxury Goods Tax',
Code: 'TAX-LUXURY',
Rate: '25',
Description: 'Tax imposed on the sale of luxury items.',
'Is Non Recoverable': 'T',
Active: 'T',
},
];

View File

@@ -0,0 +1,46 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { ICreateTaxRateDTO } from '@/interfaces';
// import { CreateTaxRate } from './commands/CreateTaxRate.service';
// import { Importable } from '../Import/Importable';
// import { TaxRatesSampleData } from './TaxRatesImportable.SampleData';
// @Service()
// export class TaxRatesImportable extends Importable {
// @Inject()
// private createTaxRateService: CreateTaxRate;
// /**
// * Importing to tax rate creating service.
// * @param {number} tenantId -
// * @param {ICreateTaxRateDTO} ICreateTaxRateDTO -
// * @param {Knex.Transaction} trx -
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: ICreateTaxRateDTO,
// trx?: Knex.Transaction
// ) {
// return this.createTaxRateService.createTaxRate(
// tenantId,
// createAccountDTO,
// trx
// );
// }
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return TaxRatesSampleData;
// }
// }

View File

@@ -0,0 +1,105 @@
import { sumBy, chain, keyBy } from 'lodash';
import { Knex } from 'knex';
import { ModelObject } from 'objection';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { TaxRateModel } from './models/TaxRate.model';
import { Inject, Injectable } from '@nestjs/common';
import { ItemEntry } from '../TransactionItemEntry/models/ItemEntry';
import { TaxRateTransaction } from './models/TaxRateTransaction.model';
@Injectable()
export class WriteTaxTransactionsItemEntries {
constructor(
@Inject(TaxRateTransaction.name)
private readonly taxRateTransactionModel: TenantModelProxy<
typeof TaxRateTransaction
>,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Writes the tax transactions from the given item entries.
* @param {number} tenantId
* @param {IItemEntry[]} itemEntries
*/
public async writeTaxTransactionsFromItemEntries(
itemEntries: ModelObject<ItemEntry>[],
trx?: Knex.Transaction,
) {
const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries);
const entriesTaxRateIds = aggregatedEntries.map((entry) => entry.taxRateId);
const taxRates = await this.taxRateModel()
.query(trx)
.whereIn('id', entriesTaxRateIds);
const taxRatesById = keyBy(taxRates, 'id');
const taxTransactions = aggregatedEntries.map((entry) => ({
taxRateId: entry.taxRateId,
referenceType: entry.referenceType,
referenceId: entry.referenceId,
rate: entry.taxRate || (taxRatesById[entry.taxRateId]?.rate as number),
}));
await this.taxRateTransactionModel()
.query(trx)
.upsertGraph(taxTransactions as ModelObject<TaxRateTransaction>[]);
}
/**
* Rewrites the tax rate transactions from the given item entries.
* @param {number} tenantId
* @param {IItemEntry[]} itemEntries
* @param {string} referenceType
* @param {number} referenceId
* @param {Knex.Transaction} trx
*/
public async rewriteTaxRateTransactionsFromItemEntries(
itemEntries: ModelObject<ItemEntry>[],
referenceType: string,
referenceId: number,
trx?: Knex.Transaction,
) {
await Promise.all([
this.removeTaxTransactionsFromItemEntries(
referenceId,
referenceType,
trx,
),
this.writeTaxTransactionsFromItemEntries(itemEntries, trx),
]);
}
/**
* Aggregates by tax code id and sums the amount.
* @param {IItemEntry[]} itemEntries
* @returns {IItemEntry[]}
*/
private aggregateItemEntriesByTaxCode = (
itemEntries: ModelObject<ItemEntry>[],
): ModelObject<ItemEntry>[] => {
return chain(itemEntries.filter((item) => item.taxRateId))
.groupBy((item) => item.taxRateId)
.values()
.map((group) => ({ ...group[0], amount: sumBy(group, 'amount') }))
.value();
};
/**
* Removes the tax transactions from the given item entries.
* @param {number} tenantId - Tenant id.
* @param {string} referenceType - Reference type.
* @param {number} referenceId - Reference id.
*/
public async removeTaxTransactionsFromItemEntries(
referenceId: number,
referenceType: string,
trx?: Knex.Transaction,
) {
await this.taxRateTransactionModel()
.query(trx)
.where({ referenceType, referenceId })
.delete();
}
}

View File

@@ -0,0 +1,66 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ITaxRateActivatedPayload,
ITaxRateActivatingPayload,
} from '../TaxRates.types';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidator.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class ActivateTaxRateService {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {typeof TaxRateModel} taxRateModel - The tax rate model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: CommandTaxRatesValidators,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Activates the given tax rate.
* @param {number} taxRateId
* @returns {Promise<ITaxRate>}
*/
public async activateTaxRate(taxRateId: number) {
const oldTaxRate = await this.taxRateModel().query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate);
// Validates the tax rate inactive.
this.validators.validateTaxRateNotActive(oldTaxRate);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onTaxRateActivating` event.
await this.eventEmitter.emitAsync(events.taxRates.onActivating, {
taxRateId,
trx,
} as ITaxRateActivatingPayload);
const taxRate = await this.taxRateModel()
.query(trx)
.findById(taxRateId)
.patch({ active: true });
// Triggers `onTaxRateCreated` event.
await this.eventEmitter.emitAsync(events.taxRates.onActivated, {
taxRateId,
trx,
} as ITaxRateActivatedPayload);
return taxRate;
});
}
}

View File

@@ -0,0 +1,116 @@
import { Knex } from 'knex';
import { difference } from 'lodash';
// import { IItemEntryDTO } from '@/modules/Items/';
import { ERRORS } from '../constants';
import { TaxRateModel } from '../models/TaxRate.model';
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { ServiceError } from '@/modules/Items/ServiceError';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
@Injectable()
export class CommandTaxRatesValidators {
/**
* @param {TenantModelProxy<typeof TaxRateModel>} taxRateModel - The tax rate model.
*/
constructor(
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Validates the tax rate existance.
* @param {TaxRate | undefined | null} taxRate
*/
public validateTaxRateExistance(taxRate: TaxRateModel | undefined | null) {
if (!taxRate) {
throw new ServiceError(ERRORS.TAX_RATE_NOT_FOUND);
}
}
/**
* Validates the given tax rate active.
* @param {TaxRateModel} taxRate
*/
public validateTaxRateNotActive(taxRate: TaxRateModel) {
if (taxRate.active) {
throw new ServiceError(ERRORS.TAX_RATE_ALREADY_ACTIVE);
}
}
/**
* Validates the given tax rate inactive.
* @param {TaxRateModel} taxRate
*/
public validateTaxRateNotInactive(taxRate: TaxRateModel) {
if (!taxRate.active) {
throw new ServiceError(ERRORS.TAX_RATE_ALREADY_INACTIVE);
}
}
/**
* Validates the tax code uniquiness.
* @param {number} tenantId
* @param {string} taxCode
* @param {Knex.Transaction} trx -
*/
public async validateTaxCodeUnique(taxCode: string, trx?: Knex.Transaction) {
const foundTaxCode = await this.taxRateModel()
.query(trx)
.findOne({ code: taxCode });
if (foundTaxCode) {
throw new ServiceError(ERRORS.TAX_CODE_NOT_UNIQUE);
}
}
/**
* Validates the tax codes of the given item entries DTO.
* @param {IItemEntryDTO[]} itemEntriesDTO
* @throws {ServiceError}
*/
public async validateItemEntriesTaxCode(itemEntriesDTO: ItemEntryDto[]) {
const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxCode);
const taxCodes = filteredTaxEntries.map((e) => e.taxCode);
// Can't validate if there is no tax codes.
if (taxCodes.length === 0) return;
const foundTaxCodes = await this.taxRateModel()
.query()
.whereIn('code', taxCodes);
const foundCodes = foundTaxCodes.map((tax) => tax.code);
const notFoundTaxCodes = difference(taxCodes, foundCodes);
if (notFoundTaxCodes.length > 0) {
throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND);
}
}
/**
* Validates the tax rate id of the given item entries DTO.
* @param {ItemEntryDto[]} itemEntriesDTO
* @throws {ServiceError}
*/
public async validateItemEntriesTaxCodeId(itemEntriesDTO: ItemEntryDto[]) {
const filteredTaxEntries = itemEntriesDTO.filter((e) => e.taxRateId);
const taxRatesIds = filteredTaxEntries.map((e) => e.taxRateId);
// Can't validate if there is no tax codes.
if (taxRatesIds.length === 0) return;
const foundTaxCodes = await this.taxRateModel()
.query()
.whereIn('id', taxRatesIds);
const foundTaxRatesIds = foundTaxCodes.map((tax) => tax.id);
const notFoundTaxCodes = difference(taxRatesIds, foundTaxRatesIds);
if (notFoundTaxCodes.length > 0) {
throw new ServiceError(ERRORS.ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,67 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ICreateTaxRateDTO,
ITaxRateCreatedPayload,
ITaxRateCreatingPayload,
} from '../TaxRates.types';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidator.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { CreateTaxRateDto } from '../dtos/TaxRate.dto';
@Injectable()
export class CreateTaxRate {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {typeof TaxRateModel} taxRateModel - The tax rate model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: CommandTaxRatesValidators,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Creates a new tax rate.
* @param {ICreateTaxRateDTO} createTaxRateDTO
*/
public async createTaxRate(
createTaxRateDTO: CreateTaxRateDto,
trx?: Knex.Transaction,
) {
// Validates the tax code uniquiness.
await this.validators.validateTaxCodeUnique(createTaxRateDTO.code, trx);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onTaxRateCreating` event.
await this.eventEmitter.emitAsync(events.taxRates.onCreating, {
createTaxRateDTO,
trx,
} as ITaxRateCreatingPayload);
const taxRate = await this.taxRateModel()
.query(trx)
.insertAndFetch({
...createTaxRateDTO,
});
// Triggers `onTaxRateCreated` event.
await this.eventEmitter.emitAsync(events.taxRates.onCreated, {
createTaxRateDTO,
taxRate,
trx,
} as ITaxRateCreatedPayload);
return taxRate;
}, trx);
}
}

View File

@@ -0,0 +1,58 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import {
ITaxRateDeletedPayload,
ITaxRateDeletingPayload,
} from '../TaxRates.types';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidator.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class DeleteTaxRateService {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {typeof TaxRateModel} taxRateModel - The tax rate model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: CommandTaxRatesValidators,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Deletes the given tax rate.
* @param {number} taxRateId
* @returns {Promise<void>}
*/
public async deleteTaxRate(taxRateId: number): Promise<void> {
const oldTaxRate = await this.taxRateModel().query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onTaxRateDeleting` event.
await this.eventEmitter.emitAsync(events.taxRates.onDeleting, {
oldTaxRate,
trx,
} as ITaxRateDeletingPayload);
await this.taxRateModel().query(trx).findById(taxRateId).delete();
// Triggers `onTaxRateDeleted` event.
await this.eventEmitter.emitAsync(events.taxRates.onDeleted, {
oldTaxRate,
trx,
} as ITaxRateDeletedPayload);
});
}
}

View File

@@ -0,0 +1,123 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { omit } from 'lodash';
import {
IEditTaxRateDTO,
ITaxRateEditedPayload,
ITaxRateEditingPayload,
} from '../TaxRates.types';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidator.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { EditTaxRateDto } from '../dtos/TaxRate.dto';
@Injectable()
export class EditTaxRateService {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {TenantModelProxy<typeof TaxRateModel>} taxRateModel - The tax rate model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: CommandTaxRatesValidators,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Determines whether the tax rate, name or code have been changed.
* @param {ITaxRate} taxRate
* @param {IEditTaxRateDTO} editTaxRateDTO
* @returns {boolean}
*/
private isTaxRateDTOChanged = (
taxRate: TaxRateModel,
editTaxRateDTO: EditTaxRateDto,
) => {
return (
taxRate.rate !== editTaxRateDTO.rate ||
taxRate.name !== editTaxRateDTO.name ||
taxRate.code !== editTaxRateDTO.code
);
};
/**
* Edits the given tax rate or creates a new if the rate or name have been changed.
* @param {number} tenantId
* @param {ITaxRate} oldTaxRate
* @param {IEditTaxRateDTO} editTaxRateDTO
* @param {Knex.Transaction} trx
* @returns {Promise<ITaxRate>}
*/
private async editTaxRateOrCreate(
oldTaxRate: TaxRateModel,
editTaxRateDTO: EditTaxRateDto,
trx?: Knex.Transaction,
) {
const isTaxDTOChanged = this.isTaxRateDTOChanged(
oldTaxRate,
editTaxRateDTO,
);
if (isTaxDTOChanged) {
// Soft deleting the old tax rate.
await this.taxRateModel().query(trx).findById(oldTaxRate.id).delete();
// Create a new tax rate with new edited data.
return this.taxRateModel()
.query(trx)
.insertAndFetch({
...omit(oldTaxRate, ['id']),
...editTaxRateDTO,
});
} else {
return this.taxRateModel()
.query(trx)
.patchAndFetchById(oldTaxRate.id, {
...editTaxRateDTO,
});
}
}
/**
* Edits the given tax rate.
* @param {number} taxRateId - The tax rate id.
* @param {IEditTaxRateDTO} editTaxRateDTO - The tax rate data to edit.
* @returns {Promise<ITaxRate>}
*/
public async editTaxRate(taxRateId: number, editTaxRateDTO: EditTaxRateDto) {
const oldTaxRate = await this.taxRateModel().query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onTaxRateEditing` event.
await this.eventEmitter.emitAsync(events.taxRates.onEditing, {
editTaxRateDTO,
trx,
} as ITaxRateEditingPayload);
const taxRate = await this.editTaxRateOrCreate(
oldTaxRate,
editTaxRateDTO,
trx,
);
// Triggers `onTaxRateEdited` event.
await this.eventEmitter.emitAsync(events.taxRates.onEdited, {
editTaxRateDTO,
oldTaxRate,
taxRate,
trx,
} as ITaxRateEditedPayload);
return taxRate;
});
}
}

View File

@@ -0,0 +1,66 @@
import { Inject, Injectable } from '@nestjs/common';
import {
ITaxRateActivatedPayload,
ITaxRateActivatingPayload,
} from '../TaxRates.types';
import { Knex } from 'knex';
import { CommandTaxRatesValidators } from './CommandTaxRatesValidator.service';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class InactivateTaxRateService {
/**
* @param {EventEmitter2} eventEmitter - The event emitter.
* @param {UnitOfWork} uow - The unit of work.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {typeof TaxRateModel} taxRateModel - The tax rate model.
*/
constructor(
private readonly eventEmitter: EventEmitter2,
private readonly uow: UnitOfWork,
private readonly validators: CommandTaxRatesValidators,
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Edits the given tax rate.
* @param {number} taxRateId
* @returns {Promise<ITaxRate>}
*/
public async inactivateTaxRate(taxRateId: number) {
const oldTaxRate = await this.taxRateModel().query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(oldTaxRate);
// Validates the tax rate active.
this.validators.validateTaxRateNotInactive(oldTaxRate);
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Triggers `onTaxRateActivating` event.
await this.eventEmitter.emitAsync(events.taxRates.onInactivating, {
taxRateId,
trx,
} as ITaxRateActivatingPayload);
const taxRate = await this.taxRateModel()
.query(trx)
.findById(taxRateId)
.patch({ active: false });
// Triggers `onTaxRateCreated` event.
await this.eventEmitter.emitAsync(events.taxRates.onInactivated, {
taxRateId,
trx,
} as ITaxRateActivatedPayload);
return taxRate;
});
}
}

View File

@@ -0,0 +1,8 @@
export const ERRORS = {
TAX_RATE_NOT_FOUND: 'TAX_RATE_NOT_FOUND',
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

@@ -0,0 +1,94 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsBoolean,
IsNumber,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator';
export class CommandTaxRateDto {
/**
* Tax rate name.
*/
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The name of the tax rate.',
example: 'VAT',
})
name: string;
/**
* Tax rate code.
*/
@IsString()
@IsNotEmpty()
@ApiProperty({
description: 'The code of the tax rate.',
example: 'VAT',
})
code: string;
/**
* Tax rate percentage.
*/
@IsNumber()
@IsNotEmpty()
@ApiProperty({
description: 'The rate of the tax rate.',
example: 10,
})
rate: number;
/**
* Tax rate description.
*/
@IsString()
@IsOptional()
@ApiProperty({
description: 'The description of the tax rate.',
example: 'VAT',
})
description?: string;
/**
* Whether the tax is non-recoverable.
*/
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value ?? false)
@ApiProperty({
description: 'Whether the tax is non-recoverable.',
example: false,
})
isNonRecoverable?: boolean;
/**
* Whether the tax is compound.
*/
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value ?? false)
@ApiProperty({
description: 'Whether the tax is compound.',
example: false,
})
isCompound?: boolean;
/**
* Whether the tax rate is active.
*/
@IsBoolean()
@IsOptional()
@Transform(({ value }) => value ?? false)
@ApiProperty({
description: 'Whether the tax rate is active.',
example: false,
})
active?: boolean;
}
export class CreateTaxRateDto extends CommandTaxRateDto {}
export class EditTaxRateDto extends CommandTaxRateDto {}

View File

@@ -0,0 +1,64 @@
import { mixin, Model, raw } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSearchable from './ModelSearchable';
// import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder';
// import TaxRateMeta from './TaxRate.settings';
// import ModelSetting from './ModelSetting';
import { BaseModel } from '@/models/Model';
export class TaxRateModel extends BaseModel {
active!: boolean;
code!: string;
name!: string;
rate!: number;
description?: string;
/**
* Table name
*/
static get tableName() {
return 'tax_rates';
}
/**
* Soft delete query builder.
*/
// static get QueryBuilder() {
// return SoftDeleteQueryBuilder;
// }
/**
* Timestamps columns.
*/
get timestamps() {
return ['createdAt', 'updatedAt'];
}
/**
* Retrieves the tax rate meta.
*/
// static get meta() {
// return TaxRateMeta;
// }
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Model modifiers.
*/
static get modifiers() {
return {};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
return {};
}
}

View File

@@ -0,0 +1,62 @@
import { mixin, Model, raw } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
export class TaxRateTransaction extends BaseModel {
public id: number;
public taxRateId: number;
public referenceType: string;
public referenceId: string;
public rate: number;
public taxAccountId?: number;
/**
* Table name
*/
static get tableName() {
return 'tax_rate_transactions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Model modifiers.
*/
static get modifiers() {
return {};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { TaxRateModel } = require('./TaxRate.model');
return {
/**
* Belongs to the tax rate.
*/
taxRate: {
relation: Model.BelongsToOneRelation,
modelClass: TaxRateModel,
join: {
from: 'tax_rate_transactions.taxRateId',
to: 'tax_rates.id',
},
},
};
}
}

View File

@@ -0,0 +1,36 @@
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TaxRateTransformer } from './TaxRate.transformer';
import { TaxRateModel } from '../models/TaxRate.model';
import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetTaxRateService {
/**
* @param {typeof TaxRateModel} taxRateModel - The tax rate model.
* @param {CommandTaxRatesValidators} validators - The tax rates validators.
* @param {TransformerInjectable} transformer - The transformer.
*/
constructor(
@Inject(TaxRateModel.name)
private readonly taxRateModel: TenantModelProxy<typeof TaxRateModel>,
private readonly validators: CommandTaxRatesValidators,
private readonly transformer: TransformerInjectable,
) {}
/**
* Retrieves the given tax rate.
* @param {number} taxRateId
* @returns {Promise<ITaxRate>}
*/
public async getTaxRate(taxRateId: number) {
const taxRate = await this.taxRateModel().query().findById(taxRateId);
// Validates the tax rate existance.
this.validators.validateTaxRateExistance(taxRate);
// Transforms the tax rate.
return this.transformer.transform(taxRate, new TaxRateTransformer());
}
}

View File

@@ -0,0 +1,26 @@
import { Inject, Injectable } from '@nestjs/common';
import { TaxRateTransformer } from './TaxRate.transformer';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { TaxRateModel } from '../models/TaxRate.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class GetTaxRatesService {
constructor(
private transformer: TransformerInjectable,
@Inject(TaxRateModel.name)
private taxRateModel: TenantModelProxy<typeof TaxRateModel>,
) {}
/**
* Retrieves the tax rates list.
* @returns {Promise<ITaxRate[]>}
*/
public async getTaxRates() {
// Retrieves the tax rates.
const taxRates = await this.taxRateModel().query().orderBy('name', 'ASC');
// Transforms the tax rates.
return this.transformer.transform(taxRates, new TaxRateTransformer());
}
}

View File

@@ -0,0 +1,30 @@
import { Transformer } from '@/modules/Transformer/Transformer';
import { TaxRateModel } from '../models/TaxRate.model';
export class TaxRateTransformer extends Transformer {
/**
* Include these attributes to tax rate object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['nameFormatted', 'rateFormatted'];
};
/**
* Retrieve the formatted rate.
* @param taxRate
* @returns {string}
*/
public rateFormatted = (taxRate: TaxRateModel): string => {
return `${taxRate.rate}%`;
};
/**
* Formats the tax rate name.
* @param taxRate
* @returns {string}
*/
protected nameFormatted = (taxRate: TaxRateModel): string => {
return `${taxRate.name} [${taxRate.rate}%]`;
};
}

View File

@@ -0,0 +1,61 @@
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service';
import { Injectable } from '@nestjs/common';
import { IBillCreatingPayload } from '@/modules/Bills/Bills.types';
import { IBillEditingPayload } from '@/modules/Bills/Bills.types';
@Injectable()
export class BillTaxRateValidateSubscriber {
constructor(
private readonly taxRateDTOValidator: CommandTaxRatesValidators,
) {}
/**
* Validate bill entries tax rate code existance when creating.
* @param {IBillCreatingPayload}
*/
@OnEvent(events.bill.onCreating)
async validateBillEntriesTaxCodeExistanceOnCreating({
billDTO,
}: IBillCreatingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(billDTO.entries);
}
/**
* Validate the tax rate id existance when creating.
* @param {IBillCreatingPayload}
*/
@OnEvent(events.bill.onCreating)
async validateBillEntriesTaxIdExistanceOnCreating({
billDTO,
}: IBillCreatingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
billDTO.entries,
);
}
/**
* Validate bill entries tax rate code existance when editing.
* @param {IBillEditingPayload}
*/
@OnEvent(events.bill.onEditing)
async validateBillEntriesTaxCodeExistanceOnEditing({
billDTO,
}: IBillEditingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(billDTO.entries);
}
/**
* Validates the bill entries tax rate id existance when editing.
* @param {ISaleInvoiceEditingPayload} payload -
*/
@OnEvent(events.bill.onEditing)
async validateBillEntriesTaxIdExistanceOnEditing({
billDTO,
}: IBillEditingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
billDTO.entries,
);
}
}

View File

@@ -0,0 +1,67 @@
import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service';
import { OnEvent } from '@nestjs/event-emitter';
import { Injectable } from '@nestjs/common';
import {
ISaleInvoiceCreatingPaylaod,
ISaleInvoiceEditingPayload,
} from '@/modules/SaleInvoices/SaleInvoice.types';
import { events } from '@/common/events/events';
@Injectable()
export class SaleInvoiceTaxRateValidateSubscriber {
constructor(
private readonly taxRateDTOValidator: CommandTaxRatesValidators,
) {}
/**
* Validate invoice entries tax rate code existance when creating.
* @param {ISaleInvoiceCreatingPaylaod}
*/
@OnEvent(events.saleInvoice.onCreating)
async validateSaleInvoiceEntriesTaxCodeExistanceOnCreating({
saleInvoiceDTO,
}: ISaleInvoiceCreatingPaylaod) {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
saleInvoiceDTO.entries,
);
}
/**
* Validate the tax rate id existance when creating.
* @param {ISaleInvoiceCreatingPaylaod}
*/
@OnEvent(events.saleInvoice.onCreating)
async validateSaleInvoiceEntriesTaxIdExistanceOnCreating({
saleInvoiceDTO,
}: ISaleInvoiceCreatingPaylaod) {
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
saleInvoiceDTO.entries,
);
}
/**
* Validate invoice entries tax rate code existance when editing.
* @param {ISaleInvoiceEditingPayload}
*/
@OnEvent(events.saleInvoice.onEditing)
async validateSaleInvoiceEntriesTaxCodeExistanceOnEditing({
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
saleInvoiceDTO.entries,
);
}
/**
* Validates the invoice entries tax rate id existance when editing.
* @param {ISaleInvoiceEditingPayload} payload -
*/
@OnEvent(events.saleInvoice.onEditing)
async validateSaleInvoiceEntriesTaxIdExistanceOnEditing({
saleInvoiceDTO,
}: ISaleInvoiceEditingPayload) {
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
saleInvoiceDTO.entries,
);
}
}

View File

@@ -0,0 +1,35 @@
import { Injectable } from '@nestjs/common';
import { ITaxRateEditedPayload } from '../TaxRates.types';
import { runAfterTransaction } from '@/modules/Tenancy/TenancyDB/TransactionsHooks';
import { events } from '@/common/events/events';
import { SyncItemTaxRateOnEditTaxRate } from '../SyncItemTaxRateOnEditTaxRate';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class SyncItemTaxRateOnEditTaxSubscriber {
constructor(
private readonly syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate,
) {}
/**
* Syncs the new tax rate created to default item tax rates.
* @param {ITaxRateEditedPayload} payload -
*/
@OnEvent(events.taxRates.onEdited)
async handleSyncNewTaxRateToItemTaxRate({
taxRate,
oldTaxRate,
trx,
}: ITaxRateEditedPayload) {
runAfterTransaction(trx, async () => {
await this.syncItemRateOnEdit.updateItemPurchaseTaxRate(
oldTaxRate.id,
taxRate.id,
);
await this.syncItemRateOnEdit.updateItemSellTaxRate(
oldTaxRate.id,
taxRate.id,
);
});
}
}

View File

@@ -0,0 +1,65 @@
import {
IBIllEventDeletedPayload,
IBillCreatedPayload,
IBillEditedPayload,
} from '@/modules/Bills/Bills.types';
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
import { OnEvent } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { Inject, Injectable } from '@nestjs/common';
@Injectable()
export class WriteBillTaxTransactionsSubscriber {
constructor(
@Inject()
private readonly writeTaxTransactions: WriteTaxTransactionsItemEntries,
) {}
/**
* Writes the bill tax transactions on invoice created.
* @param {ISaleInvoiceCreatingPaylaod}
*/
@OnEvent(events.bill.onCreated)
async writeInvoiceTaxTransactionsOnCreated({
bill,
trx,
}: IBillCreatedPayload) {
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
bill.entries,
trx,
);
}
/**
* Rewrites the bill tax transactions on invoice edited.
* @param {IBillEditedPayload} payload -
*/
@OnEvent(events.bill.onEdited)
async rewriteInvoiceTaxTransactionsOnEdited({
bill,
trx,
}: IBillEditedPayload) {
await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries(
bill.entries,
'Bill',
bill.id,
trx,
);
}
/**
* Removes the invoice tax transactions on invoice deleted.
* @param {IBIllEventDeletedPayload}
*/
@OnEvent(events.bill.onDeleted)
async removeInvoiceTaxTransactionsOnDeleted({
oldBill,
trx,
}: IBIllEventDeletedPayload) {
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
oldBill.id,
'Bill',
trx,
);
}
}

View File

@@ -0,0 +1,64 @@
import {
ISaleInvoiceDeletedPayload,
ISaleInvoiceEditedPayload,
} from '@/modules/SaleInvoices/SaleInvoice.types';
import { OnEvent } from '@nestjs/event-emitter';
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
import { events } from '@/common/events/events';
import { ISaleInvoiceCreatedPayload } from '@/modules/SaleInvoices/SaleInvoice.types';
import { Injectable } from '@nestjs/common';
@Injectable()
export class WriteInvoiceTaxTransactionsSubscriber {
constructor(
private readonly writeTaxTransactions: WriteTaxTransactionsItemEntries,
) {}
/**
* Writes the invoice tax transactions on invoice created.
* @param {ISaleInvoiceCreatingPaylaod}
*/
@OnEvent(events.saleInvoice.onCreated)
async writeInvoiceTaxTransactionsOnCreated({
saleInvoice,
trx,
}: ISaleInvoiceCreatedPayload) {
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
saleInvoice.entries,
trx,
);
}
/**
* Rewrites the invoice tax transactions on invoice edited.
* @param {ISaleInvoiceEditedPayload} payload -
*/
@OnEvent(events.saleInvoice.onEdited)
async rewriteInvoiceTaxTransactionsOnEdited({
saleInvoice,
trx,
}: ISaleInvoiceEditedPayload) {
await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries(
saleInvoice.entries,
'SaleInvoice',
saleInvoice.id,
trx,
);
}
/**
* Removes the invoice tax transactions on invoice deleted.
* @param {ISaleInvoiceEditingPayload}
*/
@OnEvent(events.saleInvoice.onDelete)
async removeInvoiceTaxTransactionsOnDeleted({
oldSaleInvoice,
trx,
}: ISaleInvoiceDeletedPayload) {
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
oldSaleInvoice.id,
'SaleInvoice',
trx,
);
}
}

View File

@@ -0,0 +1,19 @@
/**
* Get inclusive tax amount.
* @param {number} amount
* @param {number} taxRate
* @returns {number}
*/
export const getInclusiveTaxAmount = (amount: number, taxRate: number) => {
return (amount * taxRate) / (100 + taxRate);
};
/**
* Get exclusive tax amount.
* @param {number} amount
* @param {number} taxRate
* @returns {number}
*/
export const getExlusiveTaxAmount = (amount: number, taxRate: number) => {
return (amount * taxRate) / 100;
};