From fd65ee94281b603b2ab2cbdea9df03372b0fda75 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 14 Mar 2025 23:24:02 +0200 Subject: [PATCH] refactor: api validation schema --- .../src/common/pipes/ClassValidation.pipe.ts | 29 ++++ packages/server-nest/src/main.ts | 4 + .../modules/Accounts/Accounts.controller.ts | 2 - .../modules/Branches/Branches.controller.ts | 6 +- .../Branches/BranchesApplication.service.ts | 8 +- .../Branches/commands/CreateBranch.service.ts | 13 +- .../Branches/commands/EditBranch.service.ts | 9 +- .../src/modules/Branches/dtos/Branch.dto.ts | 55 ++++++++ .../src/modules/Expenses/dtos/Expense.dto.ts | 3 + .../ItemCategories/dtos/ItemCategory.dto.ts | 6 + .../src/modules/Items/CreateItem.service.ts | 9 +- .../src/modules/Items/EditItem.service.ts | 7 +- .../src/modules/Items/Item.controller.ts | 10 +- .../modules/Items/ItemValidator.service.ts | 15 +- .../modules/Items/ItemsApplication.service.ts | 5 +- .../src/modules/Items/dtos/Item.dto.ts | 109 +++++++++++++++ .../ManualJournals.controller.ts | 5 +- .../ManualJournalsApplication.service.ts | 5 +- .../CommandManualJournalValidators.service.ts | 41 +++--- .../commands/CreateManualJournal.service.ts | 7 +- .../commands/EditManualJournal.service.ts | 7 +- .../ManualJournals/dtos/ManualJournal.dto.ts | 130 ++++++++++++++++++ .../PdfTemplate/dtos/PdfTemplate.dto.ts | 30 ++++ .../modules/TaxRates/TaxRate.application.ts | 7 +- .../modules/TaxRates/TaxRate.controller.ts | 6 +- .../commands/CreateTaxRate.service.ts | 3 +- .../TaxRates/commands/EditTaxRate.service.ts | 7 +- .../src/modules/TaxRates/dtos/TaxRate.dto.ts | 94 +++++++++++++ .../AccountsTransactionsWarehouses.ts | 8 +- .../Warehouses/Warehouses.controller.ts | 5 +- .../WarehousesApplication.service.ts | 20 ++- .../commands/CreateWarehouse.service.ts | 5 +- .../commands/EditWarehouse.service.ts | 5 +- .../modules/Warehouses/dtos/Warehouse.dto.ts | 55 ++++++++ 34 files changed, 628 insertions(+), 102 deletions(-) create mode 100644 packages/server-nest/src/common/pipes/ClassValidation.pipe.ts create mode 100644 packages/server-nest/src/modules/Branches/dtos/Branch.dto.ts create mode 100644 packages/server-nest/src/modules/Expenses/dtos/Expense.dto.ts create mode 100644 packages/server-nest/src/modules/ItemCategories/dtos/ItemCategory.dto.ts create mode 100644 packages/server-nest/src/modules/Items/dtos/Item.dto.ts create mode 100644 packages/server-nest/src/modules/ManualJournals/dtos/ManualJournal.dto.ts create mode 100644 packages/server-nest/src/modules/PdfTemplate/dtos/PdfTemplate.dto.ts create mode 100644 packages/server-nest/src/modules/TaxRates/dtos/TaxRate.dto.ts create mode 100644 packages/server-nest/src/modules/Warehouses/dtos/Warehouse.dto.ts diff --git a/packages/server-nest/src/common/pipes/ClassValidation.pipe.ts b/packages/server-nest/src/common/pipes/ClassValidation.pipe.ts new file mode 100644 index 000000000..90549a615 --- /dev/null +++ b/packages/server-nest/src/common/pipes/ClassValidation.pipe.ts @@ -0,0 +1,29 @@ +import { + PipeTransform, + Injectable, + ArgumentMetadata, + BadRequestException, +} from '@nestjs/common'; +import { validate } from 'class-validator'; +import { plainToInstance } from 'class-transformer'; + +@Injectable() +export class ValidationPipe implements PipeTransform { + async transform(value: any, { metatype }: ArgumentMetadata) { + if (!metatype || !this.toValidate(metatype)) { + return value; + } + const object = plainToInstance(metatype, value); + const errors = await validate(object); + + if (errors.length > 0) { + throw new BadRequestException(errors); + } + return value; + } + + private toValidate(metatype: Function): boolean { + const types: Function[] = [String, Boolean, Number, Array, Object]; + return !types.includes(metatype); + } +} diff --git a/packages/server-nest/src/main.ts b/packages/server-nest/src/main.ts index 9179f2661..b9597aea5 100644 --- a/packages/server-nest/src/main.ts +++ b/packages/server-nest/src/main.ts @@ -4,6 +4,7 @@ import { ClsMiddleware } from 'nestjs-cls'; import './utils/moment-mysql'; import { AppModule } from './modules/App/App.module'; import { ServiceErrorFilter } from './common/filters/service-error.filter'; +import { ValidationPipe } from './common/pipes/ClassValidation.pipe'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -12,6 +13,9 @@ async function bootstrap() { // create and mount the middleware manually here app.use(new ClsMiddleware({}).use); + // use the validation pipe globally + app.useGlobalPipes(new ValidationPipe()); + const config = new DocumentBuilder() .setTitle('Bigcapital') .setDescription('Financial accounting software') diff --git a/packages/server-nest/src/modules/Accounts/Accounts.controller.ts b/packages/server-nest/src/modules/Accounts/Accounts.controller.ts index 53d278f13..eef64a5bb 100644 --- a/packages/server-nest/src/modules/Accounts/Accounts.controller.ts +++ b/packages/server-nest/src/modules/Accounts/Accounts.controller.ts @@ -14,8 +14,6 @@ import { EditAccountDTO } from './EditAccount.dto'; import { PublicRoute } from '../Auth/Jwt.guard'; import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types'; import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; -// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types'; -// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe'; @Controller('accounts') @ApiTags('accounts') diff --git a/packages/server-nest/src/modules/Branches/Branches.controller.ts b/packages/server-nest/src/modules/Branches/Branches.controller.ts index acb448b3f..ff99595b1 100644 --- a/packages/server-nest/src/modules/Branches/Branches.controller.ts +++ b/packages/server-nest/src/modules/Branches/Branches.controller.ts @@ -8,7 +8,7 @@ import { Param, } from '@nestjs/common'; import { BranchesApplication } from './BranchesApplication.service'; -import { ICreateBranchDTO, IEditBranchDTO } from './Branches.types'; +import { CreateBranchDto, EditBranchDto } from './dtos/Branch.dto'; import { PublicRoute } from '../Auth/Jwt.guard'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; @@ -46,7 +46,7 @@ export class BranchesController { description: 'The branch has been successfully created.', }) @ApiResponse({ status: 404, description: 'The branch not found.' }) - createBranch(@Body() createBranchDTO: ICreateBranchDTO) { + createBranch(@Body() createBranchDTO: CreateBranchDto) { return this.branchesApplication.createBranch(createBranchDTO); } @@ -57,7 +57,7 @@ export class BranchesController { description: 'The branch has been successfully edited.', }) @ApiResponse({ status: 404, description: 'The branch not found.' }) - editBranch(@Param('id') id: string, @Body() editBranchDTO: IEditBranchDTO) { + editBranch(@Param('id') id: string, @Body() editBranchDTO: EditBranchDto) { return this.branchesApplication.editBranch(Number(id), editBranchDTO); } diff --git a/packages/server-nest/src/modules/Branches/BranchesApplication.service.ts b/packages/server-nest/src/modules/Branches/BranchesApplication.service.ts index 7d67f72cc..0ca78cbcc 100644 --- a/packages/server-nest/src/modules/Branches/BranchesApplication.service.ts +++ b/packages/server-nest/src/modules/Branches/BranchesApplication.service.ts @@ -12,6 +12,7 @@ import { GetBranchesService } from './queries/GetBranches.service'; import { MarkBranchAsPrimaryService } from './commands/MarkBranchAsPrimary.service'; import { Branch } from './models/Branch.model'; import { Injectable } from '@nestjs/common'; +import { CreateBranchDto, EditBranchDto } from './dtos/Branch.dto'; @Injectable() export class BranchesApplication { @@ -27,8 +28,7 @@ export class BranchesApplication { /** * Retrieves branches list. - * @param {number} tenantId - * @returns {IBranch} + * @returns {Branch[]} */ public getBranches = (): Promise => { return this.getBranchesService.getBranches(); @@ -50,7 +50,7 @@ export class BranchesApplication { * @returns {Promise} */ public createBranch = ( - createBranchDTO: ICreateBranchDTO, + createBranchDTO: CreateBranchDto, ): Promise => { return this.createBranchService.createBranch(createBranchDTO); }; @@ -63,7 +63,7 @@ export class BranchesApplication { */ public editBranch = ( branchId: number, - editBranchDTO: IEditBranchDTO, + editBranchDTO: EditBranchDto, ): Promise => { return this.editBranchService.editBranch(branchId, editBranchDTO); }; diff --git a/packages/server-nest/src/modules/Branches/commands/CreateBranch.service.ts b/packages/server-nest/src/modules/Branches/commands/CreateBranch.service.ts index 60767ffb7..2e60fac52 100644 --- a/packages/server-nest/src/modules/Branches/commands/CreateBranch.service.ts +++ b/packages/server-nest/src/modules/Branches/commands/CreateBranch.service.ts @@ -1,15 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; -import { - IBranchCreatedPayload, - IBranchCreatePayload, - ICreateBranchDTO, -} from '../Branches.types'; +import { IBranchCreatedPayload, IBranchCreatePayload } from '../Branches.types'; import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Branch } from '../models/Branch.model'; import { events } from '@/common/events/events'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { CreateBranchDto } from '../dtos/Branch.dto'; @Injectable() export class CreateBranchService { @@ -28,11 +25,11 @@ export class CreateBranchService { /** * Creates a new branch. - * @param {ICreateBranchDTO} createBranchDTO - * @returns {Promise} + * @param {CreateBranchDto} createBranchDTO + * @returns {Promise} */ public createBranch = async ( - createBranchDTO: ICreateBranchDTO, + createBranchDTO: CreateBranchDto, ): Promise => { // Creates a new branch under unit-of-work. return this.uow.withTransaction(async (trx: Knex.Transaction) => { diff --git a/packages/server-nest/src/modules/Branches/commands/EditBranch.service.ts b/packages/server-nest/src/modules/Branches/commands/EditBranch.service.ts index ae59d4857..2d4ee63ab 100644 --- a/packages/server-nest/src/modules/Branches/commands/EditBranch.service.ts +++ b/packages/server-nest/src/modules/Branches/commands/EditBranch.service.ts @@ -1,15 +1,12 @@ import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; -import { - IBranchEditedPayload, - IBranchEditPayload, - IEditBranchDTO, -} from '../Branches.types'; +import { IBranchEditedPayload, IBranchEditPayload } from '../Branches.types'; import { Branch } from '../models/Branch.model'; import { UnitOfWork } from '../../Tenancy/TenancyDB/UnitOfWork.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { events } from '@/common/events/events'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { EditBranchDto } from '../dtos/Branch.dto'; @Injectable() export class EditBranchService { @@ -27,7 +24,7 @@ export class EditBranchService { */ public editBranch = async ( branchId: number, - editBranchDTO: IEditBranchDTO, + editBranchDTO: EditBranchDto, ) => { // Retrieves the old branch or throw not found service error. const oldBranch = await this.branchModel() diff --git a/packages/server-nest/src/modules/Branches/dtos/Branch.dto.ts b/packages/server-nest/src/modules/Branches/dtos/Branch.dto.ts new file mode 100644 index 000000000..bb1e5ba43 --- /dev/null +++ b/packages/server-nest/src/modules/Branches/dtos/Branch.dto.ts @@ -0,0 +1,55 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsEmail, + IsNotEmpty, + IsOptional, + IsString, + IsUrl, +} from 'class-validator'; + +class CommandBranchDto { + @ApiProperty({ description: 'Branch name' }) + @IsNotEmpty() + @IsString() + name: string; + + @ApiPropertyOptional({ description: 'Branch code' }) + @IsOptional() + @IsString() + code?: string; + + @ApiPropertyOptional({ description: 'Branch address' }) + @IsOptional() + @IsString() + address?: string; + + @ApiPropertyOptional({ description: 'Branch city' }) + @IsOptional() + @IsString() + city?: string; + + @ApiPropertyOptional({ description: 'Branch country' }) + @IsOptional() + @IsString() + country?: string; + + @ApiPropertyOptional({ description: 'Branch phone number' }) + @IsOptional() + @IsString() + phone_number?: string; + + @ApiPropertyOptional({ description: 'Branch email' }) + @IsOptional() + @IsEmail() + @IsString() + email?: string; + + @ApiPropertyOptional({ description: 'Branch website' }) + @IsOptional() + @IsUrl() + @IsString() + website?: string; +} + +export class CreateBranchDto extends CommandBranchDto {} +export class EditBranchDto extends CommandBranchDto {} diff --git a/packages/server-nest/src/modules/Expenses/dtos/Expense.dto.ts b/packages/server-nest/src/modules/Expenses/dtos/Expense.dto.ts new file mode 100644 index 000000000..d2427e95a --- /dev/null +++ b/packages/server-nest/src/modules/Expenses/dtos/Expense.dto.ts @@ -0,0 +1,3 @@ +export class CreateExpenseDto {} + +export class EditExpenseDto {} diff --git a/packages/server-nest/src/modules/ItemCategories/dtos/ItemCategory.dto.ts b/packages/server-nest/src/modules/ItemCategories/dtos/ItemCategory.dto.ts new file mode 100644 index 000000000..921f6e27e --- /dev/null +++ b/packages/server-nest/src/modules/ItemCategories/dtos/ItemCategory.dto.ts @@ -0,0 +1,6 @@ + + + +export class CreateItemCategoryDto {} + +export class EditItemCategoryDto {} diff --git a/packages/server-nest/src/modules/Items/CreateItem.service.ts b/packages/server-nest/src/modules/Items/CreateItem.service.ts index 82dcbee3f..0b58ad272 100644 --- a/packages/server-nest/src/modules/Items/CreateItem.service.ts +++ b/packages/server-nest/src/modules/Items/CreateItem.service.ts @@ -8,6 +8,7 @@ import { ItemsValidators } from './ItemValidator.service'; import { Item } from './models/Item'; import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { CreateItemDto } from './dtos/Item.dto'; @Injectable({ scope: Scope.REQUEST }) export class CreateItemService { @@ -32,7 +33,7 @@ export class CreateItemService { * @param {number} tenantId * @param {IItemDTO} itemDTO */ - async authorize(itemDTO: IItemDTO) { + async authorize(itemDTO: CreateItemDto) { // Validate whether the given item name already exists on the storage. await this.validators.validateItemNameUniquiness(itemDTO.name); @@ -76,10 +77,10 @@ export class CreateItemService { /** * Transforms the item DTO to model. - * @param {IItemDTO} itemDTO - Item DTO. + * @param {CreateItemDto} itemDTO - Item DTO. * @return {IItem} */ - private transformNewItemDTOToModel(itemDTO: IItemDTO) { + private transformNewItemDTOToModel(itemDTO: CreateItemDto) { return { ...itemDTO, active: defaultTo(itemDTO.active, 1), @@ -93,7 +94,7 @@ export class CreateItemService { * @return {Promise} - The created item id. */ public async createItem( - itemDTO: IItemDTO, + itemDTO: CreateItemDto, trx?: Knex.Transaction, ): Promise { // Authorize the item before creating. diff --git a/packages/server-nest/src/modules/Items/EditItem.service.ts b/packages/server-nest/src/modules/Items/EditItem.service.ts index 43abb4fd7..d6e443c55 100644 --- a/packages/server-nest/src/modules/Items/EditItem.service.ts +++ b/packages/server-nest/src/modules/Items/EditItem.service.ts @@ -7,6 +7,7 @@ import { ItemsValidators } from './ItemValidator.service'; import { Item } from './models/Item'; import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { EditItemDto } from './dtos/Item.dto'; @Injectable() export class EditItemService { @@ -31,7 +32,7 @@ export class EditItemService { * @param {IItemDTO} itemDTO * @param {Item} oldItem */ - async authorize(itemDTO: IItemDTO, oldItem: Item) { + async authorize(itemDTO: EditItemDto, oldItem: Item) { // Validate edit item type from inventory type. this.validators.validateEditItemFromInventory(itemDTO, oldItem); @@ -86,7 +87,7 @@ export class EditItemService { * @return {Partial} */ private transformEditItemDTOToModel( - itemDTO: IItemDTO, + itemDTO: EditItemDto, oldItem: Item, ): Partial { return { @@ -107,7 +108,7 @@ export class EditItemService { */ public async editItem( itemId: number, - itemDTO: IItemDTO, + itemDTO: EditItemDto, trx?: Knex.Transaction, ): Promise { // Validates the given item existance on the storage. diff --git a/packages/server-nest/src/modules/Items/Item.controller.ts b/packages/server-nest/src/modules/Items/Item.controller.ts index 101f7d2db..160e00980 100644 --- a/packages/server-nest/src/modules/Items/Item.controller.ts +++ b/packages/server-nest/src/modules/Items/Item.controller.ts @@ -23,6 +23,7 @@ import { } from '@nestjs/swagger'; import { IItemsFilter } from './types/Items.types'; import { IItemDTO } from '@/interfaces/Item'; +import { CreateItemDto, EditItemDto } from './dtos/Item.dto'; @Controller('/items') @UseGuards(SubscriptionGuard) @@ -120,7 +121,7 @@ export class ItemsController extends TenantController { // @UsePipes(new ZodValidationPipe(createItemSchema)) async editItem( @Param('id') id: string, - @Body() editItemDto: IItemDTO, + @Body() editItemDto: EditItemDto, ): Promise { const itemId = parseInt(id, 10); return this.itemsApplication.editItem(itemId, editItemDto); @@ -138,7 +139,7 @@ export class ItemsController extends TenantController { description: 'The item has been successfully created.', }) // @UsePipes(new ZodValidationPipe(createItemSchema)) - async createItem(@Body() createItemDto: IItemDTO): Promise { + async createItem(@Body() createItemDto: CreateItemDto): Promise { return this.itemsApplication.createItem(createItemDto); } @@ -182,8 +183,6 @@ export class ItemsController extends TenantController { description: 'The item id', }) async inactivateItem(@Param('id') id: string): Promise { - console.log(id, 'XXXXXX'); - const itemId = parseInt(id, 10); return this.itemsApplication.inactivateItem(itemId); } @@ -243,7 +242,8 @@ export class ItemsController extends TenantController { }) @ApiResponse({ status: 200, - description: 'The item associated invoices transactions have been successfully retrieved.', + description: + 'The item associated invoices transactions have been successfully retrieved.', }) @ApiResponse({ status: 404, description: 'The item not found.' }) @ApiParam({ diff --git a/packages/server-nest/src/modules/Items/ItemValidator.service.ts b/packages/server-nest/src/modules/Items/ItemValidator.service.ts index d325e0ca4..2e4ba63fa 100644 --- a/packages/server-nest/src/modules/Items/ItemValidator.service.ts +++ b/packages/server-nest/src/modules/Items/ItemValidator.service.ts @@ -5,7 +5,6 @@ import { ACCOUNT_TYPE, } from '@/constants/accounts'; import { ServiceError } from './ServiceError'; -import { IItemDTO } from '@/interfaces/Item'; import { ERRORS } from './Items.constants'; import { Item } from './models/Item'; import { Account } from '../Accounts/models/Account.model'; @@ -15,6 +14,7 @@ import { ItemCategory } from '../ItemCategories/models/ItemCategory.model'; import { AccountTransaction } from '../Accounts/models/AccountTransaction.model'; import { InventoryAdjustment } from '../InventoryAdjutments/models/InventoryAdjustment'; import { TenantModelProxy } from '../System/models/TenantBaseModel'; +import { CreateItemDto, EditItemDto } from './dtos/Item.dto'; @Injectable() export class ItemsValidators { @@ -229,7 +229,7 @@ export class ItemsValidators { */ public async validateEditItemTypeToInventory( oldItem: Item, - newItemDTO: IItemDTO, + newItemDTO: CreateItemDto | EditItemDto, ) { // We have no problem in case the item type not modified. if (newItemDTO.type === oldItem.type || oldItem.type === 'inventory') { @@ -254,12 +254,12 @@ export class ItemsValidators { * Validate the item inventory account whether modified and item * has associated inventory transactions. * @param {Item} oldItem - Old item. - * @param {IItemDTO} newItemDTO - New item DTO. + * @param {CreateItemDto | EditItemDto} newItemDTO - New item DTO. * @returns {Promise} */ async validateItemInvnetoryAccountModified( oldItem: Item, - newItemDTO: IItemDTO, + newItemDTO: CreateItemDto | EditItemDto, ) { if ( newItemDTO.type !== 'inventory' || @@ -279,10 +279,13 @@ export class ItemsValidators { /** * Validate edit item type from inventory to another type that not allowed. - * @param {IItemDTO} itemDTO - Item DTO. + * @param {CreateItemDto | EditItemDto} itemDTO - Item DTO. * @param {IItem} oldItem - Old item. */ - public validateEditItemFromInventory(itemDTO: IItemDTO, oldItem: Item) { + public validateEditItemFromInventory( + itemDTO: CreateItemDto | EditItemDto, + oldItem: Item, + ) { if ( itemDTO.type && oldItem.type === 'inventory' && diff --git a/packages/server-nest/src/modules/Items/ItemsApplication.service.ts b/packages/server-nest/src/modules/Items/ItemsApplication.service.ts index 709eb3e93..0bd822714 100644 --- a/packages/server-nest/src/modules/Items/ItemsApplication.service.ts +++ b/packages/server-nest/src/modules/Items/ItemsApplication.service.ts @@ -11,6 +11,7 @@ import { ItemTransactionsService } from './ItemTransactions.service'; import { Injectable } from '@nestjs/common'; import { GetItemsService } from './GetItems.service'; import { IItemsFilter } from './types/Items.types'; +import { EditItemDto, CreateItemDto } from './dtos/Item.dto'; @Injectable() export class ItemsApplicationService { @@ -32,7 +33,7 @@ export class ItemsApplicationService { * @return {Promise} - The created item id. */ async createItem( - createItemDto: IItemDTO, + createItemDto: CreateItemDto, trx?: Knex.Transaction, ): Promise { return this.createItemService.createItem(createItemDto); @@ -47,7 +48,7 @@ export class ItemsApplicationService { */ async editItem( itemId: number, - editItemDto: IItemDTO, + editItemDto: EditItemDto, trx?: Knex.Transaction, ): Promise { return this.editItemService.editItem(itemId, editItemDto, trx); diff --git a/packages/server-nest/src/modules/Items/dtos/Item.dto.ts b/packages/server-nest/src/modules/Items/dtos/Item.dto.ts new file mode 100644 index 000000000..3d95fadae --- /dev/null +++ b/packages/server-nest/src/modules/Items/dtos/Item.dto.ts @@ -0,0 +1,109 @@ +import { + IsString, + IsIn, + IsOptional, + IsBoolean, + IsNumber, + IsInt, + IsArray, + ValidateIf, + MaxLength, + Min, + Max, + IsNotEmpty, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +export class CommandItemDto { + @IsString() + @IsNotEmpty() + @MaxLength(255) + name: string; + + @IsString() + @IsIn(['service', 'non-inventory', 'inventory']) + type: 'service' | 'non-inventory' | 'inventory'; + + @IsOptional() + @IsString() + @MaxLength(255) + code?: string; + + // Purchase attributes + @IsOptional() + @IsBoolean() + purchasable?: boolean; + + @IsOptional() + @IsNumber({ maxDecimalPlaces: 3 }) + @Min(0) + @ValidateIf((o) => o.purchasable === true) + costPrice?: number; + + @IsOptional() + @IsInt() + @Min(0) + @ValidateIf((o) => o.purchasable === true) + costAccountId?: number; + + // Sell attributes + @IsOptional() + @IsBoolean() + sellable?: boolean; + + @IsOptional() + @IsNumber({ maxDecimalPlaces: 3 }) + @Min(0) + @ValidateIf((o) => o.sellable === true) + sellPrice?: number; + + @IsOptional() + @IsInt() + @Min(0) + @ValidateIf((o) => o.sellable === true) + sellAccountId?: number; + + @IsOptional() + @IsInt() + @Min(0) + @ValidateIf((o) => o.type === 'inventory') + inventoryAccountId?: number; + + @IsOptional() + @IsString() + sellDescription?: string; + + @IsOptional() + @IsString() + purchaseDescription?: string; + + @IsOptional() + @IsInt() + sellTaxRateId?: number; + + @IsOptional() + @IsInt() + purchaseTaxRateId?: number; + + @IsOptional() + @IsInt() + @Min(0) + categoryId?: number; + + @IsOptional() + @IsString() + note?: string; + + @IsOptional() + @IsBoolean() + active?: boolean; + + @IsOptional() + @IsArray() + @Type(() => Number) + @IsInt({ each: true }) + mediaIds?: number[]; +} + +export class CreateItemDto extends CommandItemDto {} +export class EditItemDto extends CommandItemDto {} diff --git a/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts b/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts index 67a49c05c..688f28597 100644 --- a/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts +++ b/packages/server-nest/src/modules/ManualJournals/ManualJournals.controller.ts @@ -11,6 +11,7 @@ import { ManualJournalsApplication } from './ManualJournalsApplication.service'; import { IManualJournalDTO } from './types/ManualJournals.types'; import { PublicRoute } from '../Auth/Jwt.guard'; import { ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { CreateManualJournalDto, EditManualJournalDto } from './dtos/ManualJournal.dto'; @Controller('manual-journals') @ApiTags('manual-journals') @@ -20,7 +21,7 @@ export class ManualJournalsController { @Post() @ApiOperation({ summary: 'Create a new manual journal.' }) - public createManualJournal(@Body() manualJournalDTO: IManualJournalDTO) { + public createManualJournal(@Body() manualJournalDTO: CreateManualJournalDto) { return this.manualJournalsApplication.createManualJournal(manualJournalDTO); } @@ -39,7 +40,7 @@ export class ManualJournalsController { }) public editManualJournal( @Param('id') manualJournalId: number, - @Body() manualJournalDTO: IManualJournalDTO, + @Body() manualJournalDTO: EditManualJournalDto ) { return this.manualJournalsApplication.editManualJournal( manualJournalId, diff --git a/packages/server-nest/src/modules/ManualJournals/ManualJournalsApplication.service.ts b/packages/server-nest/src/modules/ManualJournals/ManualJournalsApplication.service.ts index a727cc329..49ba23a93 100644 --- a/packages/server-nest/src/modules/ManualJournals/ManualJournalsApplication.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/ManualJournalsApplication.service.ts @@ -5,6 +5,7 @@ import { PublishManualJournal } from './commands/PublishManualJournal.service'; import { GetManualJournal } from './queries/GetManualJournal.service'; import { DeleteManualJournalService } from './commands/DeleteManualJournal.service'; import { IManualJournalDTO, } from './types/ManualJournals.types'; +import { CreateManualJournalDto, EditManualJournalDto } from './dtos/ManualJournal.dto'; // import { GetManualJournals } from './queries/GetManualJournals'; @Injectable() @@ -24,7 +25,7 @@ export class ManualJournalsApplication { * @param {IManualJournalDTO} manualJournalDTO * @returns {Promise} */ - public createManualJournal = (manualJournalDTO: IManualJournalDTO) => { + public createManualJournal = (manualJournalDTO: CreateManualJournalDto) => { return this.createManualJournalService.makeJournalEntries(manualJournalDTO); }; @@ -35,7 +36,7 @@ export class ManualJournalsApplication { */ public editManualJournal = ( manualJournalId: number, - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: EditManualJournalDto, ) => { return this.editManualJournalService.editJournalEntries( manualJournalId, diff --git a/packages/server-nest/src/modules/ManualJournals/commands/CommandManualJournalValidators.service.ts b/packages/server-nest/src/modules/ManualJournals/commands/CommandManualJournalValidators.service.ts index b69669eee..dee9b6370 100644 --- a/packages/server-nest/src/modules/ManualJournals/commands/CommandManualJournalValidators.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/commands/CommandManualJournalValidators.service.ts @@ -1,15 +1,16 @@ import { Inject, Injectable } from '@nestjs/common'; import { difference, isEmpty, round, sumBy } from 'lodash'; -import { - IManualJournalDTO, - IManualJournalEntryDTO, -} from '../types/ManualJournals.types'; import { ERRORS } from '../constants'; import { ServiceError } from '@/modules/Items/ServiceError'; import { Account } from '@/modules/Accounts/models/Account.model'; import { ManualJournal } from '../models/ManualJournal'; import { Contact } from '@/modules/Contacts/models/Contact'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { + CreateManualJournalDto, + EditManualJournalDto, + ManualJournalEntryDto, +} from '../dtos/ManualJournal.dto'; @Injectable() export class CommandManualJournalValidators { @@ -26,9 +27,11 @@ export class CommandManualJournalValidators { /** * Validate manual journal credit and debit should be equal. - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO */ - public valdiateCreditDebitTotalEquals(manualJournalDTO: IManualJournalDTO) { + public valdiateCreditDebitTotalEquals( + manualJournalDTO: CreateManualJournalDto | EditManualJournalDto, + ) { const totalCredit = round( sumBy(manualJournalDTO.entries, (entry) => entry.credit || 0), 2, @@ -48,9 +51,11 @@ export class CommandManualJournalValidators { /** * Validate manual entries accounts existance on the storage. * @param {number} tenantId - - * @param {IManualJournalDTO} manualJournalDTO - + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO - */ - public async validateAccountsExistance(manualJournalDTO: IManualJournalDTO) { + public async validateAccountsExistance( + manualJournalDTO: CreateManualJournalDto | EditManualJournalDto, + ) { const manualAccountsIds = manualJournalDTO.entries.map((e) => e.accountId); const accounts = await this.accountModel() .query() @@ -66,7 +71,7 @@ export class CommandManualJournalValidators { /** * Validate manual journal number unique. * @param {number} tenantId - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO */ public async validateManualJournalNoUnique( journalNumber: string, @@ -91,12 +96,12 @@ export class CommandManualJournalValidators { /** * Validate accounts with contact type. * @param {number} tenantId - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO * @param {string} accountBySlug * @param {string} contactType */ public async validateAccountWithContactType( - entriesDTO: IManualJournalEntryDTO[], + entriesDTO: ManualJournalEntryDto[], accountBySlug: string, contactType: string, ): Promise { @@ -147,10 +152,10 @@ export class CommandManualJournalValidators { /** * Dynamic validates accounts with contacts. * @param {number} tenantId - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO */ public async dynamicValidateAccountsWithContactType( - entriesDTO: IManualJournalEntryDTO[], + entriesDTO: ManualJournalEntryDto[], ): Promise { return Promise.all([ this.validateAccountWithContactType( @@ -182,9 +187,11 @@ export class CommandManualJournalValidators { /** * Validate entries contacts existance. - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO */ - public async validateContactsExistance(manualJournalDTO: IManualJournalDTO) { + public async validateContactsExistance( + manualJournalDTO: CreateManualJournalDto | EditManualJournalDto, + ) { // Filters the entries that have contact only. const entriesContactPairs = manualJournalDTO.entries.filter( (entry) => entry.contactId, @@ -268,10 +275,10 @@ export class CommandManualJournalValidators { /** * - * @param {IManualJournalDTO} manualJournalDTO + * @param {CreateManualJournalDto | EditManualJournalDto} manualJournalDTO */ public validateJournalCurrencyWithAccountsCurrency = async ( - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: CreateManualJournalDto | EditManualJournalDto, baseCurrency: string, ) => { const accountsIds = manualJournalDTO.entries.map((e) => e.accountId); diff --git a/packages/server-nest/src/modules/ManualJournals/commands/CreateManualJournal.service.ts b/packages/server-nest/src/modules/ManualJournals/commands/CreateManualJournal.service.ts index a4d35fe35..4804d2597 100644 --- a/packages/server-nest/src/modules/ManualJournals/commands/CreateManualJournal.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/commands/CreateManualJournal.service.ts @@ -18,6 +18,7 @@ import { assocItemEntriesDefaultIndex } from '@/utils/associate-item-entries-ind import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { ManualJournalBranchesDTOTransformer } from '@/modules/Branches/integrations/ManualJournals/ManualJournalDTOTransformer.service'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { CreateManualJournalDto } from '../dtos/ManualJournal.dto'; @Injectable() export class CreateManualJournalService { @@ -39,7 +40,7 @@ export class CreateManualJournalService { * @returns {Promise} */ private async transformNewDTOToModel( - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: CreateManualJournalDto, ): Promise { const amount = sumBy(manualJournalDTO.entries, 'credit') || 0; const date = moment(manualJournalDTO.date).format('YYYY-MM-DD'); @@ -84,7 +85,7 @@ export class CreateManualJournalService { * @param {IManualJournalDTO} manualJournalDTO * @param {ISystemUser} authorizedUser */ - private authorize = async (manualJournalDTO: IManualJournalDTO) => { + private authorize = async (manualJournalDTO: CreateManualJournalDto) => { const tenant = await this.tenancyContext.getTenant(true); // Validate the total credit should equals debit. @@ -124,7 +125,7 @@ export class CreateManualJournalService { * @param {ISystemUser} authorizedUser */ public makeJournalEntries = async ( - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: CreateManualJournalDto, trx?: Knex.Transaction, ): Promise => { // Authorize manual journal creating. diff --git a/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts b/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts index d3ded951f..e652ae402 100644 --- a/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts +++ b/packages/server-nest/src/modules/ManualJournals/commands/EditManualJournal.service.ts @@ -13,6 +13,7 @@ import { CommandManualJournalValidators } from './CommandManualJournalValidators import { events } from '@/common/events/events'; import { ManualJournal } from '../models/ManualJournal'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { EditManualJournalDto } from '../dtos/ManualJournal.dto'; @Injectable() export class EditManualJournal { @@ -32,7 +33,7 @@ export class EditManualJournal { */ private authorize = async ( manualJournalId: number, - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: EditManualJournalDto, ) => { // Validates the total credit and debit to be equals. this.validator.valdiateCreditDebitTotalEquals(manualJournalDTO); @@ -62,7 +63,7 @@ export class EditManualJournal { * @param {IManualJournal} oldManualJournal */ private transformEditDTOToModel = ( - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: EditManualJournalDto, oldManualJournal: ManualJournal, ) => { const amount = sumBy(manualJournalDTO.entries, 'credit') || 0; @@ -86,7 +87,7 @@ export class EditManualJournal { */ public async editJournalEntries( manualJournalId: number, - manualJournalDTO: IManualJournalDTO, + manualJournalDTO: EditManualJournalDto, ): Promise<{ manualJournal: ManualJournal; oldManualJournal: ManualJournal; diff --git a/packages/server-nest/src/modules/ManualJournals/dtos/ManualJournal.dto.ts b/packages/server-nest/src/modules/ManualJournals/dtos/ManualJournal.dto.ts new file mode 100644 index 000000000..d8b584da1 --- /dev/null +++ b/packages/server-nest/src/modules/ManualJournals/dtos/ManualJournal.dto.ts @@ -0,0 +1,130 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsArray, + IsBoolean, + IsDate, + IsInt, + IsNumber, + IsOptional, + IsPositive, + IsString, + MaxLength, + Min, + ValidateNested, +} from 'class-validator'; + +export class ManualJournalEntryDto { + @ApiProperty({ description: 'Entry index' }) + @IsInt() + index: number; + + @ApiPropertyOptional({ description: 'Credit amount' }) + @IsOptional() + @IsNumber() + @Min(0) + credit?: number; + + @ApiPropertyOptional({ description: 'Debit amount' }) + @IsOptional() + @IsNumber() + @Min(0) + debit?: number; + + @ApiProperty({ description: 'Account ID' }) + @IsInt() + accountId: number; + + @ApiPropertyOptional({ description: 'Entry note' }) + @IsOptional() + @IsString() + note?: string; + + @ApiPropertyOptional({ description: 'Contact ID' }) + @IsOptional() + @IsInt() + contactId?: number; + + @ApiPropertyOptional({ description: 'Branch ID' }) + @IsOptional() + @IsInt() + branchId?: number; + + @ApiPropertyOptional({ description: 'Project ID' }) + @IsOptional() + @IsInt() + projectId?: number; +} + +class AttachmentDto { + @ApiProperty({ description: 'Attachment key' }) + @IsString() + key: string; +} + +export class CommandManualJournalDto { + @ApiProperty({ description: 'Journal date' }) + @IsDate() + @Type(() => Date) + date: Date; + + @ApiPropertyOptional({ description: 'Currency code' }) + @IsOptional() + @IsString() + currencyCode?: string; + + @ApiPropertyOptional({ description: 'Exchange rate' }) + @IsOptional() + @IsNumber() + @IsPositive() + exchangeRate?: number; + + @ApiPropertyOptional({ description: 'Journal number' }) + @IsOptional() + @IsString() + @MaxLength(255) + journalNumber?: string; + + @ApiPropertyOptional({ description: 'Journal type' }) + @IsOptional() + @IsString() + @MaxLength(255) + journalType?: string; + + @ApiPropertyOptional({ description: 'Reference' }) + @IsOptional() + @IsString() + @MaxLength(255) + reference?: string; + + @ApiPropertyOptional({ description: 'Description' }) + @IsOptional() + @IsString() + description?: string; + + @ApiPropertyOptional({ description: 'Branch ID' }) + @IsOptional() + @IsInt() + branchId?: number; + + @ApiPropertyOptional({ description: 'Publish status' }) + @IsOptional() + @IsBoolean() + publish?: boolean; + + @ApiProperty({ description: 'Journal entries', type: [ManualJournalEntryDto] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ManualJournalEntryDto) + entries: ManualJournalEntryDto[]; + + @ApiPropertyOptional({ description: 'Attachments', type: [AttachmentDto] }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => AttachmentDto) + attachments?: AttachmentDto[]; +} + +export class CreateManualJournalDto extends CommandManualJournalDto {} +export class EditManualJournalDto extends CommandManualJournalDto {} \ No newline at end of file diff --git a/packages/server-nest/src/modules/PdfTemplate/dtos/PdfTemplate.dto.ts b/packages/server-nest/src/modules/PdfTemplate/dtos/PdfTemplate.dto.ts new file mode 100644 index 000000000..a26fa162e --- /dev/null +++ b/packages/server-nest/src/modules/PdfTemplate/dtos/PdfTemplate.dto.ts @@ -0,0 +1,30 @@ +import { IsObject, IsString } from 'class-validator'; +import { IsNotEmpty } from 'class-validator'; + +export class CreatePdfTemplateDto { + @IsString() + @IsNotEmpty() + templateName: string; + + @IsString() + @IsNotEmpty() + resource: string; + + @IsObject() + @IsNotEmpty() + attributes: Record; +} + +export class EditPdfTemplateDto { + @IsString() + @IsNotEmpty() + templateName: string; + + @IsString() + @IsNotEmpty() + resource: string; + + @IsObject() + @IsNotEmpty() + attributes: Record; +} diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts index a10ca1832..2159c7d33 100644 --- a/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.application.ts @@ -2,12 +2,11 @@ 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'; import { GetTaxRatesService } from './queries/GetTaxRates.service'; +import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto'; @Injectable() export class TaxRatesApplication { @@ -26,7 +25,7 @@ export class TaxRatesApplication { * @param {ICreateTaxRateDTO} createTaxRateDTO * @returns {Promise} */ - public createTaxRate(createTaxRateDTO: ICreateTaxRateDTO) { + public createTaxRate(createTaxRateDTO: CreateTaxRateDto) { return this.createTaxRateService.createTaxRate(createTaxRateDTO); } @@ -37,7 +36,7 @@ export class TaxRatesApplication { * @param {IEditTaxRateDTO} taxRateEditDTO * @returns {Promise} */ - public editTaxRate(taxRateId: number, editTaxRateDTO: IEditTaxRateDTO) { + public editTaxRate(taxRateId: number, editTaxRateDTO: EditTaxRateDto) { return this.editTaxRateService.editTaxRate(taxRateId, editTaxRateDTO); } diff --git a/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts b/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts index 2e8a1de6a..47dd6388e 100644 --- a/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts +++ b/packages/server-nest/src/modules/TaxRates/TaxRate.controller.ts @@ -8,9 +8,9 @@ import { Put, } from '@nestjs/common'; import { TaxRatesApplication } from './TaxRate.application'; -import { ICreateTaxRateDTO, IEditTaxRateDTO } from './TaxRates.types'; import { PublicRoute } from '../Auth/Jwt.guard'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CreateTaxRateDto, EditTaxRateDto } from './dtos/TaxRate.dto'; @Controller('tax-rates') @ApiTags('tax-rates') @@ -20,7 +20,7 @@ export class TaxRatesController { @Post() @ApiOperation({ summary: 'Create a new tax rate.' }) - public createTaxRate(@Body() createTaxRateDTO: ICreateTaxRateDTO) { + public createTaxRate(@Body() createTaxRateDTO: CreateTaxRateDto) { return this.taxRatesApplication.createTaxRate(createTaxRateDTO); } @@ -28,7 +28,7 @@ export class TaxRatesController { @ApiOperation({ summary: 'Edit the given tax rate.' }) public editTaxRate( @Param('id') taxRateId: number, - @Body() editTaxRateDTO: IEditTaxRateDTO, + @Body() editTaxRateDTO: EditTaxRateDto, ) { return this.taxRatesApplication.editTaxRate(taxRateId, editTaxRateDTO); } diff --git a/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts index 9b29fd6b7..3bc72f7e5 100644 --- a/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts +++ b/packages/server-nest/src/modules/TaxRates/commands/CreateTaxRate.service.ts @@ -11,6 +11,7 @@ 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 { @@ -34,7 +35,7 @@ export class CreateTaxRate { * @param {ICreateTaxRateDTO} createTaxRateDTO */ public async createTaxRate( - createTaxRateDTO: ICreateTaxRateDTO, + createTaxRateDTO: CreateTaxRateDto, trx?: Knex.Transaction, ) { // Validates the tax code uniquiness. diff --git a/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts b/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts index 336707765..badd391d0 100644 --- a/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts +++ b/packages/server-nest/src/modules/TaxRates/commands/EditTaxRate.service.ts @@ -12,6 +12,7 @@ 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 { @@ -38,7 +39,7 @@ export class EditTaxRateService { */ private isTaxRateDTOChanged = ( taxRate: TaxRateModel, - editTaxRateDTO: IEditTaxRateDTO, + editTaxRateDTO: EditTaxRateDto, ) => { return ( taxRate.rate !== editTaxRateDTO.rate || @@ -57,7 +58,7 @@ export class EditTaxRateService { */ private async editTaxRateOrCreate( oldTaxRate: TaxRateModel, - editTaxRateDTO: IEditTaxRateDTO, + editTaxRateDTO: EditTaxRateDto, trx?: Knex.Transaction, ) { const isTaxDTOChanged = this.isTaxRateDTOChanged( @@ -90,7 +91,7 @@ export class EditTaxRateService { * @param {IEditTaxRateDTO} editTaxRateDTO - The tax rate data to edit. * @returns {Promise} */ - public async editTaxRate(taxRateId: number, editTaxRateDTO: IEditTaxRateDTO) { + public async editTaxRate(taxRateId: number, editTaxRateDTO: EditTaxRateDto) { const oldTaxRate = await this.taxRateModel().query().findById(taxRateId); // Validates the tax rate existance. diff --git a/packages/server-nest/src/modules/TaxRates/dtos/TaxRate.dto.ts b/packages/server-nest/src/modules/TaxRates/dtos/TaxRate.dto.ts new file mode 100644 index 000000000..07cb69b91 --- /dev/null +++ b/packages/server-nest/src/modules/TaxRates/dtos/TaxRate.dto.ts @@ -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 {} diff --git a/packages/server-nest/src/modules/Warehouses/AccountsTransactionsWarehouses.ts b/packages/server-nest/src/modules/Warehouses/AccountsTransactionsWarehouses.ts index 2face488f..42ef06460 100644 --- a/packages/server-nest/src/modules/Warehouses/AccountsTransactionsWarehouses.ts +++ b/packages/server-nest/src/modules/Warehouses/AccountsTransactionsWarehouses.ts @@ -7,16 +7,18 @@ import { TenantModelProxy } from '../System/models/TenantBaseModel'; export class InventoryTransactionsWarehouses { constructor( @Inject(AccountTransaction.name) - private readonly accountTransactionModel: TenantModelProxy, + private readonly accountTransactionModel: TenantModelProxy< + typeof AccountTransaction + >, ) {} - + /** * Updates all accounts transctions with the priamry branch. * @param {number} primaryBranchId - The primary branch id. */ public updateTransactionsWithWarehouse = async ( primaryBranchId: number, - trx?: Knex.Transaction + trx?: Knex.Transaction, ) => { await this.accountTransactionModel().query(trx).update({ branchId: primaryBranchId, diff --git a/packages/server-nest/src/modules/Warehouses/Warehouses.controller.ts b/packages/server-nest/src/modules/Warehouses/Warehouses.controller.ts index bf2876d45..4a0501910 100644 --- a/packages/server-nest/src/modules/Warehouses/Warehouses.controller.ts +++ b/packages/server-nest/src/modules/Warehouses/Warehouses.controller.ts @@ -11,6 +11,7 @@ import { WarehousesApplication } from './WarehousesApplication.service'; import { ICreateWarehouseDTO, IEditWarehouseDTO } from './Warehouse.types'; import { PublicRoute } from '../Auth/Jwt.guard'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CreateWarehouseDto, EditWarehouseDto } from './dtos/Warehouse.dto'; @Controller('warehouses') @ApiTags('warehouses') @@ -20,14 +21,14 @@ export class WarehousesController { @Post() @ApiOperation({ summary: 'Create a warehouse' }) - createWarehouse(@Body() createWarehouseDTO: ICreateWarehouseDTO) { + createWarehouse(@Body() createWarehouseDTO: CreateWarehouseDto) { return this.warehousesApplication.createWarehouse(createWarehouseDTO); } @Put(':id') editWarehouse( @Param('id') warehouseId: string, - @Body() editWarehouseDTO: IEditWarehouseDTO, + @Body() editWarehouseDTO: EditWarehouseDto, ) { return this.warehousesApplication.editWarehouse( Number(warehouseId), diff --git a/packages/server-nest/src/modules/Warehouses/WarehousesApplication.service.ts b/packages/server-nest/src/modules/Warehouses/WarehousesApplication.service.ts index 1ca6d0047..25b439173 100644 --- a/packages/server-nest/src/modules/Warehouses/WarehousesApplication.service.ts +++ b/packages/server-nest/src/modules/Warehouses/WarehousesApplication.service.ts @@ -1,6 +1,4 @@ import { - ICreateWarehouseDTO, - IEditWarehouseDTO, IWarehouse, } from './Warehouse.types'; import { ActivateWarehousesService } from './commands/ActivateWarehouses.service'; @@ -12,6 +10,7 @@ import { GetWarehouses } from './queries/GetWarehouses'; import { GetItemWarehouses } from './Items/GetItemWarehouses'; import { WarehouseMarkPrimary } from './commands/WarehouseMarkPrimary.service'; import { Injectable } from '@nestjs/common'; +import { CreateWarehouseDto, EditWarehouseDto } from './dtos/Warehouse.dto'; @Injectable() export class WarehousesApplication { @@ -31,20 +30,19 @@ export class WarehousesApplication { * @param {ICreateWarehouseDTO} createWarehouseDTO * @returns {Promise} */ - public createWarehouse = (createWarehouseDTO: ICreateWarehouseDTO) => { + public createWarehouse = (createWarehouseDTO: CreateWarehouseDto) => { return this.createWarehouseService.createWarehouse(createWarehouseDTO); }; /** * Edits the given warehouse. - * @param {number} tenantId - * @param {number} warehouseId - * @param {IEditWarehouseDTO} editWarehouseDTO + * @param {number} warehouseId + * @param {EditWarehouseDto} editWarehouseDTO * @returns {Promise} */ public editWarehouse = ( warehouseId: number, - editWarehouseDTO: IEditWarehouseDTO, + editWarehouseDTO: EditWarehouseDto, ) => { return this.editWarehouseService.editWarehouse( warehouseId, @@ -54,7 +52,6 @@ export class WarehousesApplication { /** * Deletes the given warehouse. - * @param {number} tenantId * @param {number} warehouseId */ public deleteWarehouse = (warehouseId: number) => { @@ -63,7 +60,7 @@ export class WarehousesApplication { /** * Retrieves the specific warehouse. - * @param {number} warehouseId + * @param {number} warehouseId * @returns */ public getWarehouse = (warehouseId: number) => { @@ -71,9 +68,8 @@ export class WarehousesApplication { }; /** - * - * @param {number} tenantId - * @returns + * Retrieves the warehouses list. + * @returns {Promise} */ public getWarehouses = () => { return this.getWarehousesService.getWarehouses(); diff --git a/packages/server-nest/src/modules/Warehouses/commands/CreateWarehouse.service.ts b/packages/server-nest/src/modules/Warehouses/commands/CreateWarehouse.service.ts index 70107ae26..17e2d31e9 100644 --- a/packages/server-nest/src/modules/Warehouses/commands/CreateWarehouse.service.ts +++ b/packages/server-nest/src/modules/Warehouses/commands/CreateWarehouse.service.ts @@ -11,6 +11,7 @@ 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 { CreateWarehouseDto } from '../dtos/Warehouse.dto'; @Injectable() export class CreateWarehouse { @@ -33,7 +34,7 @@ export class CreateWarehouse { * Authorize the warehouse before creating. * @param {ICreateWarehouseDTO} warehouseDTO - */ - public authorize = async (warehouseDTO: ICreateWarehouseDTO) => { + public authorize = async (warehouseDTO: CreateWarehouseDto) => { if (warehouseDTO.code) { await this.validator.validateWarehouseCodeUnique(warehouseDTO.code); } @@ -44,7 +45,7 @@ export class CreateWarehouse { * @param {ICreateWarehouseDTO} warehouseDTO */ public createWarehouse = async ( - warehouseDTO: ICreateWarehouseDTO, + warehouseDTO: CreateWarehouseDto, ): Promise => { // Authorize warehouse before creating. await this.authorize(warehouseDTO); diff --git a/packages/server-nest/src/modules/Warehouses/commands/EditWarehouse.service.ts b/packages/server-nest/src/modules/Warehouses/commands/EditWarehouse.service.ts index 9227c36e7..90f772fed 100644 --- a/packages/server-nest/src/modules/Warehouses/commands/EditWarehouse.service.ts +++ b/packages/server-nest/src/modules/Warehouses/commands/EditWarehouse.service.ts @@ -7,6 +7,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { Warehouse } from '../models/Warehouse.model'; import { events } from '@/common/events/events'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; +import { EditWarehouseDto } from '../dtos/Warehouse.dto'; @Injectable() export class EditWarehouse { @@ -29,7 +30,7 @@ export class EditWarehouse { * Authorize the warehouse before deleting. */ public authorize = async ( - warehouseDTO: IEditWarehouseDTO, + warehouseDTO: EditWarehouseDto, warehouseId: number, ) => { if (warehouseDTO.code) { @@ -47,7 +48,7 @@ export class EditWarehouse { */ public editWarehouse = async ( warehouseId: number, - warehouseDTO: IEditWarehouseDTO, + warehouseDTO: EditWarehouseDto, ): Promise => { // Authorize the warehouse DTO before editing. await this.authorize(warehouseDTO, warehouseId); diff --git a/packages/server-nest/src/modules/Warehouses/dtos/Warehouse.dto.ts b/packages/server-nest/src/modules/Warehouses/dtos/Warehouse.dto.ts new file mode 100644 index 000000000..885a5dde9 --- /dev/null +++ b/packages/server-nest/src/modules/Warehouses/dtos/Warehouse.dto.ts @@ -0,0 +1,55 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsBoolean, IsEmail, IsOptional, IsUrl } from "class-validator"; +import { IsNotEmpty } from "class-validator"; +import { IsString } from "class-validator"; + + +export class CommandWarehouseDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ description: 'The name of the warehouse' }) + name: string; + + @IsBoolean() + @IsOptional() + @ApiProperty({ description: 'Whether the warehouse is primary' }) + primary?: boolean; + + @IsString() + @IsOptional() + @ApiProperty({ description: 'The code of the warehouse' }) + code?: string; + + @IsString() + @IsOptional() + @ApiProperty({ description: 'The address of the warehouse' }) + address?: string; + + @IsString() + @IsOptional() + @ApiProperty({ description: 'The city of the warehouse' }) + city?: string; + + @IsString() + @IsOptional() + @ApiProperty({ description: 'The country of the warehouse' }) + country?: string; + + @IsString() + @IsOptional() + @ApiProperty({ description: 'The phone number of the warehouse' }) + phoneNumber?: string; + + @IsEmail() + @IsOptional() + @ApiProperty({ description: 'The email of the warehouse' }) + email?: string; + + @IsUrl() + @IsOptional() + @ApiProperty({ description: 'The website of the warehouse' }) + website?: string; +} + +export class CreateWarehouseDto extends CommandWarehouseDto {} +export class EditWarehouseDto extends CommandWarehouseDto {} \ No newline at end of file