diff --git a/packages/server-nest/src/modules/App/App.module.ts b/packages/server-nest/src/modules/App/App.module.ts index 65523f431..076693a5d 100644 --- a/packages/server-nest/src/modules/App/App.module.ts +++ b/packages/server-nest/src/modules/App/App.module.ts @@ -34,6 +34,7 @@ import { TransformerModule } from '../Transformer/Transformer.module'; import { AccountsModule } from '../Accounts/Accounts.module'; import { ExpensesModule } from '../Expenses/Expenses.module'; import { ItemCategoryModule } from '../ItemCategories/ItemCategory.module'; +import { TaxRatesModule } from '../TaxRates/TaxRate.module'; @Module({ imports: [ @@ -91,6 +92,7 @@ import { ItemCategoryModule } from '../ItemCategories/ItemCategory.module'; ItemCategoryModule, AccountsModule, ExpensesModule, + TaxRatesModule ], controllers: [AppController], providers: [ diff --git a/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.ts b/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.ts new file mode 100644 index 000000000..2d999def5 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/ItemEntriesTaxTransactions.ts @@ -0,0 +1,72 @@ +// import { Inject, Service } from 'typedi'; +// import { keyBy, sumBy } from 'lodash'; +// import { ItemEntry } from '@/models'; +// import HasTenancyService from '../Tenancy/TenancyService'; +// import { IItem, IItemEntry, IItemEntryDTO } from '@/interfaces'; + +// @Service() +// export class ItemEntriesTaxTransactions { +// @Inject() +// private tenancy: HasTenancyService; + +// /** +// * Associates tax amount withheld to the model. +// * @param model +// * @returns +// */ +// public assocTaxAmountWithheldFromEntries(model: any) { +// const entries = model.entries.map((entry) => ItemEntry.fromJson(entry)); +// const taxAmountWithheld = sumBy(entries, 'taxAmount'); + +// if (taxAmountWithheld) { +// model.taxAmountWithheld = taxAmountWithheld; +// } +// return model; +// } + +// /** +// * Associates tax rate id from tax code to entries. +// * @param {number} tenantId +// * @param {} model +// */ +// public assocTaxRateIdFromCodeToEntries = +// (tenantId: number) => async (entries: any) => { +// const entriesWithCode = entries.filter((entry) => entry.taxCode); +// const taxCodes = entriesWithCode.map((entry) => entry.taxCode); + +// const { TaxRate } = this.tenancy.models(tenantId); +// const foundTaxCodes = await TaxRate.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. +// * @param {number} tenantId +// * @returns {Promise} +// */ +// public assocTaxRateFromTaxIdToEntries = +// (tenantId: number) => async (entries: IItemEntry[]) => { +// const entriesWithId = entries.filter((e) => e.taxRateId); +// const taxRateIds = entriesWithId.map((e) => e.taxRateId); + +// const { TaxRate } = this.tenancy.models(tenantId); +// const foundTaxes = await TaxRate.query().whereIn('id', taxRateIds); + +// const taxRatesMap = keyBy(foundTaxes, 'id'); + +// return entries.map((entry) => { +// if (entry.taxRateId) { +// entry.taxRate = taxRatesMap[entry.taxRateId]?.rate; +// } +// return entry; +// }); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts new file mode 100644 index 000000000..f3ba72384 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxRate.ts @@ -0,0 +1,55 @@ +// import { Knex } from 'knex'; +// import { Inject, Service } from 'typedi'; +// import HasTenancyService from '../Tenancy/TenancyService'; + +// @Service() +// export class SyncItemTaxRateOnEditTaxRate { +// @Inject() +// private tenancy: HasTenancyService; + +// /** +// * Syncs the new tax rate created to item default sell tax rate. +// * @param {number} tenantId +// * @param {number} itemId +// * @param {number} sellTaxRateId +// */ +// public updateItemSellTaxRate = async ( +// tenantId: number, +// oldSellTaxRateId: number, +// sellTaxRateId: number, +// trx?: Knex.Transaction +// ) => { +// const { Item } = this.tenancy.models(tenantId); + +// // Can't continue if the old and new sell tax rate id are equal. +// if (oldSellTaxRateId === sellTaxRateId) return; + +// await Item.query().where('sellTaxRateId', oldSellTaxRateId).update({ +// sellTaxRateId, +// }); +// }; + +// /** +// * Syncs the new tax rate created to item default purchase tax rate. +// * @param {number} tenantId +// * @param {number} itemId +// * @param {number} purchaseTaxRateId +// */ +// public updateItemPurchaseTaxRate = async ( +// tenantId: number, +// oldPurchaseTaxRateId: number, +// purchaseTaxRateId: number, +// trx?: Knex.Transaction +// ) => { +// const { Item } = this.tenancy.models(tenantId); + +// // Can't continue if the old and new sell tax rate id are equal. +// if (oldPurchaseTaxRateId === purchaseTaxRateId) return; + +// await Item.query(trx) +// .where('purchaseTaxRateId', oldPurchaseTaxRateId) +// .update({ +// purchaseTaxRateId, +// }); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts new file mode 100644 index 000000000..e98b4fb65 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/SyncItemTaxRateOnEditTaxSubscriber.ts @@ -0,0 +1,45 @@ +// import { Inject, Service } from 'typedi'; +// import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate'; +// import events from '@/subscribers/events'; +// import { ITaxRateEditedPayload } from '@/interfaces'; +// import { runAfterTransaction } from '../UnitOfWork/TransactionsHooks'; + +// @Service() +// export class SyncItemTaxRateOnEditTaxSubscriber { +// @Inject() +// private syncItemRateOnEdit: SyncItemTaxRateOnEditTaxRate; + +// /** +// * Attaches events with handles. +// */ +// public attach(bus) { +// bus.subscribe( +// events.taxRates.onEdited, +// this.handleSyncNewTaxRateToItemTaxRate +// ); +// } + +// /** +// * Syncs the new tax rate created to default item tax rates. +// * @param {ITaxRateEditedPayload} payload - +// */ +// private handleSyncNewTaxRateToItemTaxRate = async ({ +// taxRate, +// tenantId, +// oldTaxRate, +// trx, +// }: ITaxRateEditedPayload) => { +// runAfterTransaction(trx, async () => { +// await this.syncItemRateOnEdit.updateItemPurchaseTaxRate( +// tenantId, +// oldTaxRate.id, +// taxRate.id +// ); +// await this.syncItemRateOnEdit.updateItemSellTaxRate( +// tenantId, +// oldTaxRate.id, +// taxRate.id +// ); +// }); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts new file mode 100644 index 000000000..93a5c9a22 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts @@ -0,0 +1,89 @@ +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 { GetTaxRatesService } from './queries/GetTaxRates'; +import { ActivateTaxRateService } from './commands/ActivateTaxRate.service'; +import { InactivateTaxRateService } from './commands/InactivateTaxRate'; +import { Injectable } from '@nestjs/common'; +import { ICreateTaxRateDTO, IEditTaxRateDTO } from './TaxRates.types'; + +@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} + */ + public createTaxRate(createTaxRateDTO: ICreateTaxRateDTO) { + return this.createTaxRateService.createTaxRate(createTaxRateDTO); + } + + /** + * Edits the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @param {IEditTaxRateDTO} taxRateEditDTO + * @returns {Promise} + */ + public editTaxRate(taxRateId: number, editTaxRateDTO: IEditTaxRateDTO) { + return this.editTaxRateService.editTaxRate(taxRateId, editTaxRateDTO); + } + + /** + * Deletes the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @returns {Promise} + */ + public deleteTaxRate(taxRateId: number) { + return this.deleteTaxRateService.deleteTaxRate(taxRateId); + } + + /** + * Retrieves the given tax rate. + * @param {number} tenantId + * @param {number} taxRateId + * @returns {Promise} + */ + public getTaxRate(tenantId: number, taxRateId: number) { + return this.getTaxRateService.getTaxRate(taxRateId); + } + + /** + * Retrieves the tax rates list. + * @param {number} tenantId + * @returns {Promise} + */ + public getTaxRates(tenantId: number) { + // return this.getTaxRatesService.getTaxRates(tenantId); + } + + /** + * 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); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts new file mode 100644 index 000000000..3b26d8da1 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts @@ -0,0 +1,59 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, +} from '@nestjs/common'; +import { TaxRatesApplication } from './TaxRate.application'; +import { ICreateTaxRateDTO, IEditTaxRateDTO } from './TaxRates.types'; +import { PublicRoute } from '../Auth/Jwt.guard'; + +@Controller('tax-rates') +@PublicRoute() +export class TaxRatesController { + constructor(private readonly taxRatesApplication: TaxRatesApplication) {} + + @Post() + public createTaxRate(@Body() createTaxRateDTO: ICreateTaxRateDTO) { + return this.taxRatesApplication.createTaxRate(createTaxRateDTO); + } + + @Put(':id') + public editTaxRate( + @Param('id') taxRateId: number, + @Body() editTaxRateDTO: IEditTaxRateDTO, + ) { + return this.taxRatesApplication.editTaxRate(taxRateId, editTaxRateDTO); + } + + @Delete(':id') + public deleteTaxRate(@Param('id') taxRateId: number) { + return this.taxRatesApplication.deleteTaxRate(taxRateId); + } + + @Get(':id') + public getTaxRate( + @Param('tenantId') tenantId: number, + @Param('id') taxRateId: number, + ) { + return this.taxRatesApplication.getTaxRate(tenantId, taxRateId); + } + + @Get() + public getTaxRates(@Param('tenantId') tenantId: number) { + return this.taxRatesApplication.getTaxRates(tenantId); + } + + @Put(':id/activate') + public activateTaxRate(@Param('id') taxRateId: number) { + return this.taxRatesApplication.activateTaxRate(taxRateId); + } + + @Put(':id/inactivate') + public inactivateTaxRate(@Param('id') taxRateId: number) { + return this.taxRatesApplication.inactivateTaxRate(taxRateId); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts new file mode 100644 index 000000000..493baf713 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.module.ts @@ -0,0 +1,30 @@ +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'; + +@Module({ + imports: [], + controllers: [TaxRatesController], + providers: [ + CreateTaxRate, + EditTaxRateService, + DeleteTaxRateService, + GetTaxRateService, + ActivateTaxRateService, + InactivateTaxRateService, + CommandTaxRatesValidators, + TransformerInjectable, + TenancyContext, + TaxRatesApplication + ], +}) +export class TaxRatesModule {} diff --git a/packages/server-nest/src/modules/TaxRates/TaxRates.types.ts b/packages/server-nest/src/modules/TaxRates/TaxRates.types.ts new file mode 100644 index 000000000..57047d185 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRates.types.ts @@ -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', +} diff --git a/packages/server-nest/src/modules/TaxRates/TaxRatesExportable.ts b/packages/server-nest/src/modules/TaxRates/TaxRatesExportable.ts new file mode 100644 index 000000000..079a09637 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRatesExportable.ts @@ -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); +// } +// } diff --git a/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.SampleData.ts b/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.SampleData.ts new file mode 100644 index 000000000..0a7fe389d --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.SampleData.ts @@ -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', + }, +]; diff --git a/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.ts b/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.ts new file mode 100644 index 000000000..eebb3c7c0 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/TaxRatesImportable.ts @@ -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; +// } +// } diff --git a/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts b/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts new file mode 100644 index 000000000..d61d5ae11 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/WriteTaxTransactionsItemEntries.ts @@ -0,0 +1,99 @@ +// import { sumBy, chain, keyBy } from 'lodash'; +// import { IItemEntry, ITaxTransaction } from '@/interfaces'; +// import HasTenancyService from '../Tenancy/TenancyService'; +// import { Inject, Service } from 'typedi'; +// import { Knex } from 'knex'; + +// @Service() +// export class WriteTaxTransactionsItemEntries { +// @Inject() +// private tenancy: HasTenancyService; + +// /** +// * Writes the tax transactions from the given item entries. +// * @param {number} tenantId +// * @param {IItemEntry[]} itemEntries +// */ +// public async writeTaxTransactionsFromItemEntries( +// tenantId: number, +// itemEntries: IItemEntry[], +// trx?: Knex.Transaction +// ) { +// const { TaxRateTransaction, TaxRate } = this.tenancy.models(tenantId); +// const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries); + +// const entriesTaxRateIds = aggregatedEntries.map((entry) => entry.taxRateId); + +// const taxRates = await TaxRate.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 ITaxTransaction[]; + +// await TaxRateTransaction.query(trx).upsertGraph(taxTransactions); +// } + +// /** +// * 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( +// tenantId: number, +// itemEntries: IItemEntry[], +// referenceType: string, +// referenceId: number, +// trx?: Knex.Transaction +// ) { +// await Promise.all([ +// this.removeTaxTransactionsFromItemEntries( +// tenantId, +// referenceId, +// referenceType, +// trx +// ), +// this.writeTaxTransactionsFromItemEntries(tenantId, itemEntries, trx), +// ]); +// } + +// /** +// * Aggregates by tax code id and sums the amount. +// * @param {IItemEntry[]} itemEntries +// * @returns {IItemEntry[]} +// */ +// private aggregateItemEntriesByTaxCode = ( +// itemEntries: IItemEntry[] +// ): IItemEntry[] => { +// 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( +// tenantId: number, +// referenceId: number, +// referenceType: string, +// trx?: Knex.Transaction +// ) { +// const { TaxRateTransaction } = this.tenancy.models(tenantId); + +// await TaxRateTransaction.query(trx) +// .where({ referenceType, referenceId }) +// .delete(); +// } +// } diff --git a/packages/server-nest/src/modules/TaxRates/commands/ActivateTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/ActivateTaxRate.service.ts new file mode 100644 index 000000000..a9f8c0bea --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/ActivateTaxRate.service.ts @@ -0,0 +1,59 @@ +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'; + +@Injectable() +export class ActivateTaxRateService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: CommandTaxRatesValidators, + + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + ) {} + + /** + * Activates the given tax rate. + * @param {number} taxRateId + * @returns {Promise} + */ + 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; + }); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts b/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts new file mode 100644 index 000000000..fb6264116 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/CommandTaxRatesValidator.service.ts @@ -0,0 +1,111 @@ +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'; + +@Injectable() +export class CommandTaxRatesValidators { + constructor( + @Inject(TaxRateModel.name) + private readonly taxRateModel: 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 {number} tenantId + * @param {IItemEntryDTO[]} itemEntriesDTO + * @throws {ServiceError} + */ + // public async validateItemEntriesTaxCode(itemEntriesDTO: IItemEntryDTO[]) { + // 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 {IItemEntryDTO[]} itemEntriesDTO + * @throws {ServiceError} + */ + // public async validateItemEntriesTaxCodeId(itemEntriesDTO: IItemEntryDTO[]) { + // 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); + // } + // } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts new file mode 100644 index 000000000..9c1d630f6 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts @@ -0,0 +1,63 @@ +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'; + +@Injectable() +export class CreateTaxRate { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: CommandTaxRatesValidators, + + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + ) {} + + /** + * Creates a new tax rate. + * @param {ICreateTaxRateDTO} createTaxRateDTO + */ + public async createTaxRate( + createTaxRateDTO: ICreateTaxRateDTO, + 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 + ); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/DeleteTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/DeleteTaxRate.service.ts new file mode 100644 index 000000000..83e461c4a --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/DeleteTaxRate.service.ts @@ -0,0 +1,51 @@ +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'; + +@Injectable() +export class DeleteTaxRateService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: CommandTaxRatesValidators, + + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + ) {} + + /** + * Deletes the given tax rate. + * @param {number} taxRateId + * @returns {Promise} + */ + public async deleteTaxRate(taxRateId: number): Promise { + 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); + }); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts new file mode 100644 index 000000000..86b958e17 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts @@ -0,0 +1,108 @@ +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'; + +@Injectable() +export class EditTaxRateService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: CommandTaxRatesValidators, + + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + ) {} + + /** + * Detarmines whether the tax rate, name or code have been changed. + * @param {ITaxRate} taxRate + * @param {IEditTaxRateDTO} editTaxRateDTO + * @returns {boolean} + */ + private isTaxRateDTOChanged = ( + taxRate: TaxRateModel, + editTaxRateDTO: IEditTaxRateDTO + ) => { + 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} + */ + private async editTaxRateOrCreate( + oldTaxRate: TaxRateModel, + editTaxRateDTO: IEditTaxRateDTO, + 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, + }); + } + } + + public async editTaxRate( + taxRateId: number, + editTaxRateDTO: IEditTaxRateDTO + ) { + 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; + }); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/commands/InactivateTaxRate.ts b/packages/server-nest/src/modules/TaxRates/commands/InactivateTaxRate.ts new file mode 100644 index 000000000..f0dce26a5 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/commands/InactivateTaxRate.ts @@ -0,0 +1,59 @@ +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'; + +@Injectable() +export class InactivateTaxRateService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly uow: UnitOfWork, + private readonly validators: CommandTaxRatesValidators, + + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + ) {} + + /** + * Edits the given tax rate. + * @param {number} taxRateId + * @returns {Promise} + */ + 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; + }); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/constants.ts b/packages/server-nest/src/modules/TaxRates/constants.ts new file mode 100644 index 000000000..e1553f4ee --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/constants.ts @@ -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' +}; diff --git a/packages/server-nest/src/modules/TaxRates/models/TaxRate.model.ts b/packages/server-nest/src/modules/TaxRates/models/TaxRate.model.ts new file mode 100644 index 000000000..4bdd9bab4 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/models/TaxRate.model.ts @@ -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 {}; + } +} diff --git a/packages/server-nest/src/modules/TaxRates/queries/GetTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/queries/GetTaxRate.service.ts new file mode 100644 index 000000000..d287c117d --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/queries/GetTaxRate.service.ts @@ -0,0 +1,33 @@ +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'; + +@Injectable() +export class GetTaxRateService { + constructor( + @Inject(TaxRateModel.name) + private readonly taxRateModel: typeof TaxRateModel, + private readonly validators: CommandTaxRatesValidators, + private readonly transformer: TransformerInjectable, + ) {} + + /** + * Retrieves the given tax rate. + * @param {number} taxRateId + * @returns {Promise} + */ + 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() + ); + } +} diff --git a/packages/server-nest/src/modules/TaxRates/queries/GetTaxRates.service.ts b/packages/server-nest/src/modules/TaxRates/queries/GetTaxRates.service.ts new file mode 100644 index 000000000..a62432ed4 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/queries/GetTaxRates.service.ts @@ -0,0 +1,32 @@ +// import { Inject, Service } from 'typedi'; +// import HasTenancyService from '../Tenancy/TenancyService'; +// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; +// import { TaxRateTransformer } from './TaxRate.transformer'; + +// @Service() +// export class GetTaxRatesService { +// @Inject() +// private tenancy: HasTenancyService; + +// @Inject() +// private transformer: TransformerInjectable; + +// /** +// * Retrieves the tax rates list. +// * @param {number} tenantId +// * @returns {Promise} +// */ +// public async getTaxRates(tenantId: number) { +// const { TaxRate } = this.tenancy.models(tenantId); + +// // Retrieves the tax rates. +// const taxRates = await TaxRate.query().orderBy('name', 'ASC'); + +// // Transforms the tax rates. +// return this.transformer.transform( +// tenantId, +// taxRates, +// new TaxRateTransformer() +// ); +// } +// } diff --git a/packages/server-nest/src/modules/TaxRates/queries/TaxRate.transformer.ts b/packages/server-nest/src/modules/TaxRates/queries/TaxRate.transformer.ts new file mode 100644 index 000000000..659f58dc6 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/queries/TaxRate.transformer.ts @@ -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}%]`; + }; +} diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts new file mode 100644 index 000000000..3a5e54532 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/subscribers/BillTaxRateValidateSubscriber.ts @@ -0,0 +1,89 @@ +// import { Inject, Service } from 'typedi'; +// import { IBillCreatingPayload, IBillEditingPayload } from '@/interfaces'; +// import events from '@/subscribers/events'; +// import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; + +// @Service() +// export class BillTaxRateValidateSubscriber { +// @Inject() +// private taxRateDTOValidator: CommandTaxRatesValidators; + +// /** +// * Attaches events with handlers. +// */ +// public attach(bus) { +// bus.subscribe( +// events.bill.onCreating, +// this.validateBillEntriesTaxCodeExistanceOnCreating +// ); +// bus.subscribe( +// events.bill.onCreating, +// this.validateBillEntriesTaxIdExistanceOnCreating +// ); +// bus.subscribe( +// events.bill.onEditing, +// this.validateBillEntriesTaxCodeExistanceOnEditing +// ); +// bus.subscribe( +// events.bill.onEditing, +// this.validateBillEntriesTaxIdExistanceOnEditing +// ); +// return bus; +// } + +// /** +// * Validate bill entries tax rate code existance when creating. +// * @param {IBillCreatingPayload} +// */ +// private validateBillEntriesTaxCodeExistanceOnCreating = async ({ +// billDTO, +// tenantId, +// }: IBillCreatingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCode( +// tenantId, +// billDTO.entries +// ); +// }; + +// /** +// * Validate the tax rate id existance when creating. +// * @param {IBillCreatingPayload} +// */ +// private validateBillEntriesTaxIdExistanceOnCreating = async ({ +// billDTO, +// tenantId, +// }: IBillCreatingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( +// tenantId, +// billDTO.entries +// ); +// }; + +// /** +// * Validate bill entries tax rate code existance when editing. +// * @param {IBillEditingPayload} +// */ +// private validateBillEntriesTaxCodeExistanceOnEditing = async ({ +// tenantId, +// billDTO, +// }: IBillEditingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCode( +// tenantId, +// billDTO.entries +// ); +// }; + +// /** +// * Validates the bill entries tax rate id existance when editing. +// * @param {ISaleInvoiceEditingPayload} payload - +// */ +// private validateBillEntriesTaxIdExistanceOnEditing = async ({ +// tenantId, +// billDTO, +// }: IBillEditingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( +// tenantId, +// billDTO.entries +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts new file mode 100644 index 000000000..52db3ec0c --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber.ts @@ -0,0 +1,92 @@ +// import { Inject, Service } from 'typedi'; +// import { +// ISaleInvoiceCreatingPaylaod, +// ISaleInvoiceEditingPayload, +// } from '@/interfaces'; +// import events from '@/subscribers/events'; +// import { CommandTaxRatesValidators } from '../commands/CommandTaxRatesValidator.service'; + +// @Service() +// export class SaleInvoiceTaxRateValidateSubscriber { +// @Inject() +// private taxRateDTOValidator: CommandTaxRatesValidators; + +// /** +// * Attaches events with handlers. +// */ +// public attach(bus) { +// bus.subscribe( +// events.saleInvoice.onCreating, +// this.validateSaleInvoiceEntriesTaxCodeExistanceOnCreating +// ); +// bus.subscribe( +// events.saleInvoice.onCreating, +// this.validateSaleInvoiceEntriesTaxIdExistanceOnCreating +// ); +// bus.subscribe( +// events.saleInvoice.onEditing, +// this.validateSaleInvoiceEntriesTaxCodeExistanceOnEditing +// ); +// bus.subscribe( +// events.saleInvoice.onEditing, +// this.validateSaleInvoiceEntriesTaxIdExistanceOnEditing +// ); +// return bus; +// } + +// /** +// * Validate invoice entries tax rate code existance when creating. +// * @param {ISaleInvoiceCreatingPaylaod} +// */ +// private validateSaleInvoiceEntriesTaxCodeExistanceOnCreating = async ({ +// saleInvoiceDTO, +// tenantId, +// }: ISaleInvoiceCreatingPaylaod) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCode( +// tenantId, +// saleInvoiceDTO.entries +// ); +// }; + +// /** +// * Validate the tax rate id existance when creating. +// * @param {ISaleInvoiceCreatingPaylaod} +// */ +// private validateSaleInvoiceEntriesTaxIdExistanceOnCreating = async ({ +// saleInvoiceDTO, +// tenantId, +// }: ISaleInvoiceCreatingPaylaod) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( +// tenantId, +// saleInvoiceDTO.entries +// ); +// }; + +// /** +// * Validate invoice entries tax rate code existance when editing. +// * @param {ISaleInvoiceEditingPayload} +// */ +// private validateSaleInvoiceEntriesTaxCodeExistanceOnEditing = async ({ +// tenantId, +// saleInvoiceDTO, +// }: ISaleInvoiceEditingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCode( +// tenantId, +// saleInvoiceDTO.entries +// ); +// }; + +// /** +// * Validates the invoice entries tax rate id existance when editing. +// * @param {ISaleInvoiceEditingPayload} payload - +// */ +// private validateSaleInvoiceEntriesTaxIdExistanceOnEditing = async ({ +// tenantId, +// saleInvoiceDTO, +// }: ISaleInvoiceEditingPayload) => { +// await this.taxRateDTOValidator.validateItemEntriesTaxCodeId( +// tenantId, +// saleInvoiceDTO.entries +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts new file mode 100644 index 000000000..2968aa413 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber.ts @@ -0,0 +1,87 @@ +// import { Inject, Service } from 'typedi'; +// import { +// IBIllEventDeletedPayload, +// IBillCreatedPayload, +// IBillEditedPayload, +// ISaleInvoiceCreatedPayload, +// ISaleInvoiceDeletedPayload, +// ISaleInvoiceEditedPayload, +// } from '@/interfaces'; +// import events from '@/subscribers/events'; +// import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; + +// @Service() +// export class WriteBillTaxTransactionsSubscriber { +// @Inject() +// private writeTaxTransactions: WriteTaxTransactionsItemEntries; + +// /** +// * Attaches events with handlers. +// */ +// public attach(bus) { +// bus.subscribe( +// events.bill.onCreated, +// this.writeInvoiceTaxTransactionsOnCreated +// ); +// bus.subscribe( +// events.bill.onEdited, +// this.rewriteInvoiceTaxTransactionsOnEdited +// ); +// bus.subscribe( +// events.bill.onDeleted, +// this.removeInvoiceTaxTransactionsOnDeleted +// ); +// return bus; +// } + +// /** +// * Writes the bill tax transactions on invoice created. +// * @param {ISaleInvoiceCreatingPaylaod} +// */ +// private writeInvoiceTaxTransactionsOnCreated = async ({ +// tenantId, +// bill, +// trx, +// }: IBillCreatedPayload) => { +// await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( +// tenantId, +// bill.entries, +// trx +// ); +// }; + +// /** +// * Rewrites the bill tax transactions on invoice edited. +// * @param {IBillEditedPayload} payload - +// */ +// private rewriteInvoiceTaxTransactionsOnEdited = async ({ +// tenantId, +// bill, +// trx, +// }: IBillEditedPayload) => { +// await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( +// tenantId, +// bill.entries, +// 'Bill', +// bill.id, +// trx +// ); +// }; + +// /** +// * Removes the invoice tax transactions on invoice deleted. +// * @param {IBIllEventDeletedPayload} +// */ +// private removeInvoiceTaxTransactionsOnDeleted = async ({ +// tenantId, +// oldBill, +// trx, +// }: IBIllEventDeletedPayload) => { +// await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( +// tenantId, +// oldBill.id, +// 'Bill', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts b/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts new file mode 100644 index 000000000..c1adf3a4c --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber.ts @@ -0,0 +1,84 @@ +// import { Inject, Service } from 'typedi'; +// import { +// ISaleInvoiceCreatedPayload, +// ISaleInvoiceDeletedPayload, +// ISaleInvoiceEditedPayload, +// } from '@/interfaces'; +// import events from '@/subscribers/events'; +// import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries'; + +// @Service() +// export class WriteInvoiceTaxTransactionsSubscriber { +// @Inject() +// private writeTaxTransactions: WriteTaxTransactionsItemEntries; + +// /** +// * Attaches events with handlers. +// */ +// public attach(bus) { +// bus.subscribe( +// events.saleInvoice.onCreated, +// this.writeInvoiceTaxTransactionsOnCreated +// ); +// bus.subscribe( +// events.saleInvoice.onEdited, +// this.rewriteInvoiceTaxTransactionsOnEdited +// ); +// bus.subscribe( +// events.saleInvoice.onDelete, +// this.removeInvoiceTaxTransactionsOnDeleted +// ); +// return bus; +// } + +// /** +// * Writes the invoice tax transactions on invoice created. +// * @param {ISaleInvoiceCreatingPaylaod} +// */ +// private writeInvoiceTaxTransactionsOnCreated = async ({ +// tenantId, +// saleInvoice, +// trx +// }: ISaleInvoiceCreatedPayload) => { +// await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries( +// tenantId, +// saleInvoice.entries, +// trx +// ); +// }; + +// /** +// * Rewrites the invoice tax transactions on invoice edited. +// * @param {ISaleInvoiceEditedPayload} payload - +// */ +// private rewriteInvoiceTaxTransactionsOnEdited = async ({ +// tenantId, +// saleInvoice, +// trx, +// }: ISaleInvoiceEditedPayload) => { +// await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries( +// tenantId, +// saleInvoice.entries, +// 'SaleInvoice', +// saleInvoice.id, +// trx +// ); +// }; + +// /** +// * Removes the invoice tax transactions on invoice deleted. +// * @param {ISaleInvoiceEditingPayload} +// */ +// private removeInvoiceTaxTransactionsOnDeleted = async ({ +// tenantId, +// oldSaleInvoice, +// trx +// }: ISaleInvoiceDeletedPayload) => { +// await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries( +// tenantId, +// oldSaleInvoice.id, +// 'SaleInvoice', +// trx +// ); +// }; +// } diff --git a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts index 57ea4aefe..c453e86a4 100644 --- a/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts +++ b/packages/server-nest/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -9,6 +9,7 @@ import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction import { Expense } from '@/modules/Expenses/models/Expense.model'; import ExpenseCategory from '@/modules/Expenses/models/ExpenseCategory.model'; import { ItemCategory } from '@/modules/ItemCategories/models/ItemCategory.model'; +import { TaxRateModel } from '@/modules/TaxRates/models/TaxRate.model'; const models = [ Item, @@ -18,6 +19,7 @@ const models = [ Expense, ExpenseCategory, ItemCategory, + TaxRateModel ]; const modelProviders = models.map((model) => { diff --git a/packages/server-nest/test/tax-rates.e2e-spec.ts b/packages/server-nest/test/tax-rates.e2e-spec.ts new file mode 100644 index 000000000..307c7fc6e --- /dev/null +++ b/packages/server-nest/test/tax-rates.e2e-spec.ts @@ -0,0 +1,36 @@ +import * as request from 'supertest'; +import { faker } from '@faker-js/faker'; +import { app } from './init-app-test'; + +describe('Item Categories(e2e)', () => { + it('/tax-rates (POST)', () => { + return request(app.getHttpServer()) + .post('/tax-rates') + .set('organization-id', '4064541lv40nhca') + .send({ + name: faker.person.fullName(), + rate: 2, + code: faker.string.uuid(), + active: 1, + }) + .expect(201); + }); + + it('/tax-rates/:id (DELETE)', async () => { + const response = await request(app.getHttpServer()) + .post('/tax-rates') + .set('organization-id', '4064541lv40nhca') + .send({ + name: faker.person.fullName(), + rate: 2, + code: faker.string.uuid(), + active: 1, + }); + const taxRateId = response.body.id; + + return request(app.getHttpServer()) + .delete(`/tax-rates/${taxRateId}`) + .set('organization-id', '4064541lv40nhca') + .expect(200); + }); +});