refactor: api validation schema

This commit is contained in:
Ahmed Bouhuolia
2025-03-14 23:24:02 +02:00
parent 08de50e2b1
commit fd65ee9428
34 changed files with 628 additions and 102 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Branch[]> => {
return this.getBranchesService.getBranches();
@@ -50,7 +50,7 @@ export class BranchesApplication {
* @returns {Promise<IBranch>}
*/
public createBranch = (
createBranchDTO: ICreateBranchDTO,
createBranchDTO: CreateBranchDto,
): Promise<Branch> => {
return this.createBranchService.createBranch(createBranchDTO);
};
@@ -63,7 +63,7 @@ export class BranchesApplication {
*/
public editBranch = (
branchId: number,
editBranchDTO: IEditBranchDTO,
editBranchDTO: EditBranchDto,
): Promise<Branch> => {
return this.editBranchService.editBranch(branchId, editBranchDTO);
};

View File

@@ -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<IBranch>}
* @param {CreateBranchDto} createBranchDTO
* @returns {Promise<Branch>}
*/
public createBranch = async (
createBranchDTO: ICreateBranchDTO,
createBranchDTO: CreateBranchDto,
): Promise<Branch> => {
// Creates a new branch under unit-of-work.
return this.uow.withTransaction(async (trx: Knex.Transaction) => {

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
export class CreateExpenseDto {}
export class EditExpenseDto {}

View File

@@ -0,0 +1,6 @@
export class CreateItemCategoryDto {}
export class EditItemCategoryDto {}

View File

@@ -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<number>} - The created item id.
*/
public async createItem(
itemDTO: IItemDTO,
itemDTO: CreateItemDto,
trx?: Knex.Transaction,
): Promise<number> {
// Authorize the item before creating.

View File

@@ -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<Item>}
*/
private transformEditItemDTOToModel(
itemDTO: IItemDTO,
itemDTO: EditItemDto,
oldItem: Item,
): Partial<Item> {
return {
@@ -107,7 +108,7 @@ export class EditItemService {
*/
public async editItem(
itemId: number,
itemDTO: IItemDTO,
itemDTO: EditItemDto,
trx?: Knex.Transaction,
): Promise<number> {
// Validates the given item existance on the storage.

View File

@@ -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<number> {
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<number> {
async createItem(@Body() createItemDto: CreateItemDto): Promise<number> {
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<void> {
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({

View File

@@ -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<void>}
*/
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' &&

View File

@@ -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<number>} - The created item id.
*/
async createItem(
createItemDto: IItemDTO,
createItemDto: CreateItemDto,
trx?: Knex.Transaction,
): Promise<number> {
return this.createItemService.createItem(createItemDto);
@@ -47,7 +48,7 @@ export class ItemsApplicationService {
*/
async editItem(
itemId: number,
editItemDto: IItemDTO,
editItemDto: EditItemDto,
trx?: Knex.Transaction,
): Promise<number> {
return this.editItemService.editItem(itemId, editItemDto, trx);

View File

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

View File

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

View File

@@ -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<IManualJournal>}
*/
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,

View File

@@ -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<void | ServiceError> {
@@ -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<any> {
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);

View File

@@ -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<ManualJournal>}
*/
private async transformNewDTOToModel(
manualJournalDTO: IManualJournalDTO,
manualJournalDTO: CreateManualJournalDto,
): Promise<ManualJournal> {
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<ManualJournal> => {
// Authorize manual journal creating.

View File

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

View File

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

View File

@@ -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<string, any>;
}
export class EditPdfTemplateDto {
@IsString()
@IsNotEmpty()
templateName: string;
@IsString()
@IsNotEmpty()
resource: string;
@IsObject()
@IsNotEmpty()
attributes: Record<string, any>;
}

View File

@@ -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<ITaxRate>}
*/
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<ITaxRate>}
*/
public editTaxRate(taxRateId: number, editTaxRateDTO: IEditTaxRateDTO) {
public editTaxRate(taxRateId: number, editTaxRateDTO: EditTaxRateDto) {
return this.editTaxRateService.editTaxRate(taxRateId, editTaxRateDTO);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,16 +7,18 @@ import { TenantModelProxy } from '../System/models/TenantBaseModel';
export class InventoryTransactionsWarehouses {
constructor(
@Inject(AccountTransaction.name)
private readonly accountTransactionModel: TenantModelProxy<typeof AccountTransaction>,
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,

View File

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

View File

@@ -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<IWarehouse>}
*/
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<void>}
*/
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<IWarehouse[]>}
*/
public getWarehouses = () => {
return this.getWarehousesService.getWarehouses();

View File

@@ -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<Warehouse> => {
// Authorize warehouse before creating.
await this.authorize(warehouseDTO);

View File

@@ -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<Warehouse> => {
// Authorize the warehouse DTO before editing.
await this.authorize(warehouseDTO, warehouseId);

View File

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