fix(admin-products): add class-validator ProductDto to allow product creation and updates through ValidationPipe
All checks were successful
Deploy Canina / deploy (push) Successful in 32s
All checks were successful
Deploy Canina / deploy (push) Successful in 32s
This commit is contained in:
parent
4cbde63b89
commit
d5c05b7577
@ -9,7 +9,8 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AdminService, ProductInput, CouponInput } from './admin.service';
|
||||
import { AdminService, CouponInput } from './admin.service';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import { AdminQueryDto } from './dto/admin-query.dto';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import {
|
||||
@ -127,14 +128,14 @@ export class AdminController {
|
||||
|
||||
@Post('products')
|
||||
@ApiOperation({ summary: 'ایجاد محصول جدید' })
|
||||
async createProduct(@Body() data: ProductInput) {
|
||||
async createProduct(@Body() data: ProductDto) {
|
||||
const product = await this.adminService.createProduct(data);
|
||||
return { success: true, data: product };
|
||||
}
|
||||
|
||||
@Put('products/:id')
|
||||
@ApiOperation({ summary: 'ویرایش محصول' })
|
||||
async updateProduct(@Param('id') id: string, @Body() data: ProductInput) {
|
||||
async updateProduct(@Param('id') id: string, @Body() data: ProductDto) {
|
||||
const product = await this.adminService.updateProduct(id, data);
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@ -9,6 +9,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { RedisService } from '../redis/redis.service';
|
||||
import * as bcrypt from 'bcryptjs';
|
||||
import { CreateUserDto, UpdateUserDto } from './dto/user.dto';
|
||||
import { ProductDto } from './dto/product.dto';
|
||||
import { normalizeMobile } from '../common/utils/phone.utils';
|
||||
import { CANONICAL_ALIASES } from '../settings/settings.service';
|
||||
|
||||
@ -21,34 +22,6 @@ export class PaginationQuery {
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export class ProductInput {
|
||||
artNo?: string;
|
||||
nameFa?: string;
|
||||
nameEn?: string;
|
||||
scientificTagline?: string;
|
||||
description?: string;
|
||||
shortDescription?: string;
|
||||
categoryId?: string;
|
||||
categorySlug?: string;
|
||||
priceValue?: number;
|
||||
priceDisplay?: string;
|
||||
unit?: string;
|
||||
packageSize?: number;
|
||||
dosageLogic?: string;
|
||||
suitableFor?: string;
|
||||
imageUrl?: string;
|
||||
images?: string | string[];
|
||||
podcastUrl?: string | null;
|
||||
videoUrl?: string | null;
|
||||
pdfUrl?: string | null;
|
||||
metaTitle?: string;
|
||||
metaDescription?: string;
|
||||
keywords?: string;
|
||||
canonicalUrl?: string;
|
||||
slug?: string;
|
||||
symptoms?: string[];
|
||||
}
|
||||
|
||||
export class CouponTargetInput {
|
||||
targetType!: string;
|
||||
targetId!: string;
|
||||
@ -333,7 +306,7 @@ export class AdminService {
|
||||
}
|
||||
}
|
||||
|
||||
async createProduct(data: ProductInput) {
|
||||
async createProduct(data: ProductDto) {
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
artNo: data.artNo || `ART-${Date.now()}`,
|
||||
@ -344,7 +317,9 @@ export class AdminService {
|
||||
shortDescription: data.shortDescription || '',
|
||||
categoryId: data.categoryId || '',
|
||||
categorySlug: data.categorySlug || 'general',
|
||||
buyPrice: data.buyPrice !== undefined ? data.buyPrice : 0,
|
||||
priceValue: data.priceValue || 0,
|
||||
wholesalePrice: data.wholesalePrice !== undefined ? data.wholesalePrice : null,
|
||||
priceDisplay: data.priceDisplay || '',
|
||||
unit: data.unit || 'عدد',
|
||||
packageSize: data.packageSize || 100,
|
||||
@ -381,7 +356,7 @@ export class AdminService {
|
||||
return product;
|
||||
}
|
||||
|
||||
async updateProduct(id: string, data: ProductInput) {
|
||||
async updateProduct(id: string, data: ProductDto) {
|
||||
const existing = await this.prisma.product.findUnique({ where: { id } });
|
||||
if (!existing) {
|
||||
throw new NotFoundException(`محصولی با شناسه ${id} یافت نشد.`);
|
||||
@ -398,7 +373,9 @@ export class AdminService {
|
||||
shortDescription: data.shortDescription,
|
||||
categoryId: data.categoryId,
|
||||
categorySlug: data.categorySlug,
|
||||
buyPrice: data.buyPrice !== undefined ? data.buyPrice : undefined,
|
||||
priceValue: data.priceValue,
|
||||
wholesalePrice: data.wholesalePrice !== undefined ? data.wholesalePrice : undefined,
|
||||
priceDisplay: data.priceDisplay,
|
||||
unit: data.unit,
|
||||
packageSize: data.packageSize,
|
||||
|
||||
165
backend/src/admin/dto/product.dto.ts
Normal file
165
backend/src/admin/dto/product.dto.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsNumber,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
|
||||
export class ProductDto {
|
||||
@ApiPropertyOptional({ description: 'شماره آرتیکل محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
artNo?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'نام فارسی محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
nameFa?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'نام انگلیسی محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
nameEn?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'شعار علمی محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
scientificTagline?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'توضیحات کامل محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'توضیحات کوتاه محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
shortDescription?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'شناسه دستهبندی' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
categoryId?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'اسلاگ دستهبندی' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
categorySlug?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'قیمت خرید محصول' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
buyPrice?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'قیمت فروش محصول' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
priceValue?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'قیمت عمدهفروشی محصول' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
wholesalePrice?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت مصرفکننده' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
priceValueMarginPercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'درصد سود قیمت عمدهفروشی' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
wholesaleMarginPercent?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'نمایش متنی قیمت' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
priceDisplay?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'واحد شمارش محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
unit?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'اندازه و وزن بسته' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
packageSize?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'منطق دوزبندی و نحوه مصرف' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
dosageLogic?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'مناسب برای گونه حیوان' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
suitableFor?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'لینک تصویر اصلی محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
imageUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'گالری تصاویر محصول' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
images?: string[];
|
||||
|
||||
@ApiPropertyOptional({ description: 'لینک پادکست' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
podcastUrl?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'لینک ویدیو' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
videoUrl?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'لینک فایل PDF' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pdfUrl?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'عنوان سئو' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
metaTitle?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'توضیحات متای سئو' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
metaDescription?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'کلمات کلیدی سئو' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
keywords?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'آدرس کانونیکال سئو' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
canonicalUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'اسلاگ محصول' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
slug?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'علائم درمانی مرتبط' })
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
symptoms?: string[];
|
||||
|
||||
@ApiPropertyOptional({ description: 'آیا پیشخرید فعال است؟' })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isPreorder?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'مبلغ بیعانه پیشخرید' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
preorderDeposit?: string;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user