feat: add product variant management functionality

- Implemented CreateProductVariantDto and UpdateProductVariantDto for product variant data transfer.
- Developed ProductVariantsController to handle CRUD operations for product variants.
- Created ProductVariantsService to interact with the database using Prisma.
- Added ProductVariantsModule to encapsulate the product variant feature.
- Included response mapping for consistent API responses.
This commit is contained in:
2025-12-06 17:48:10 +03:30
parent 7cb9d7d037
commit a782d61890
68 changed files with 4155 additions and 3334 deletions
+24 -10
View File
@@ -1,15 +1,29 @@
import { Type } from 'class-transformer'
import { IsInt, IsOptional, IsString } from 'class-validator'
export class CreateProductDto {
@IsString()
name: string
description?: string
@IsOptional()
@IsString()
sku?: string
@IsOptional()
@IsString()
barcode?: string
imageUrl?: string
attachmentId?: number
unit: string
discount?: number
quantity?: number
alertQuantity?: number
isActive?: boolean
isFeatured?: boolean
productInfoId: number
@IsOptional()
@IsString()
description?: string
@IsOptional()
@Type(() => Number)
@IsInt()
brandId?: number
@IsOptional()
@Type(() => Number)
@IsInt()
categoryId?: number
}
+25 -10
View File
@@ -1,15 +1,30 @@
import { Type } from 'class-transformer'
import { IsInt, IsOptional, IsString } from 'class-validator'
export class UpdateProductDto {
@IsOptional()
@IsString()
name?: string
description?: string
@IsOptional()
@IsString()
sku?: string
@IsOptional()
@IsString()
barcode?: string
imageUrl?: string
attachmentId?: number
unit?: string
discount?: number
quantity?: number
alertQuantity?: number
isActive?: boolean
isFeatured?: boolean
productInfoId?: number
@IsOptional()
@IsString()
description?: string
@IsOptional()
@Type(() => Number)
@IsInt()
brandId?: number
@IsOptional()
@Type(() => Number)
@IsInt()
categoryId?: number
}
+1
View File
@@ -9,6 +9,7 @@ export class ProductsController {
@Post()
create(@Body() dto: CreateProductDto) {
console.log('[ProductController] create called', dto)
return this.productsService.create(dto)
}
+69 -10
View File
@@ -1,27 +1,86 @@
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from '../common/response/response-mapper'
import { PrismaService } from '../prisma/prisma.service'
import { CreateProductDto } from './dto/create-product.dto'
@Injectable()
export class ProductsService {
constructor(private prisma: PrismaService) {}
create(data: any) {
return this.prisma.product.create({ data })
async create(data: CreateProductDto) {
console.log(data)
// Transform incoming DTO so relation ids are passed as `connect` objects
const payload: any = { ...data }
// If the client supplied brandId/categoryId (even null), remove the scalar
// Prisma expects nested relation objects, so only add `connect` when an id is present.
if (Object.prototype.hasOwnProperty.call(payload, 'brandId')) {
if (payload.brandId !== undefined && payload.brandId !== null) {
payload.brand = { connect: { id: Number(payload.brandId) } }
}
delete payload.brandId
}
if (Object.prototype.hasOwnProperty.call(payload, 'categoryId')) {
if (payload.categoryId !== undefined && payload.categoryId !== null) {
payload.category = { connect: { id: Number(payload.categoryId) } }
}
delete payload.categoryId
}
const item = await this.prisma.product.create({ data: payload })
return ResponseMapper.create(item)
}
findAll() {
return this.prisma.product.findMany()
async findAll() {
const items = await this.prisma.product.findMany({
include: { brand: true, category: true },
})
const mapped = items.map(p => {
const { brandId, categoryId, ...rest } = p as any
return { ...rest, brand: p.brand, category: p.category }
})
return ResponseMapper.list(mapped)
}
findOne(id: number) {
return this.prisma.product.findUnique({ where: { id } })
async findOne(id: number) {
const p = await this.prisma.product.findUnique({
where: { id },
include: { brand: true, category: true },
})
if (!p) return null
console.log('first')
const { brandId, categoryId, ...rest } = p as any
return ResponseMapper.single({ ...rest, brand: p.brand, category: p.category })
}
update(id: number, data: any) {
return this.prisma.product.update({ where: { id }, data })
async update(id: number, data: any) {
const payload: any = { ...data }
if (Object.prototype.hasOwnProperty.call(payload, 'brandId')) {
if (payload.brandId === null) {
payload.brand = { disconnect: true }
} else {
payload.brand = { connect: { id: Number(payload.brandId) } }
}
delete payload.brandId
}
if (Object.prototype.hasOwnProperty.call(payload, 'categoryId')) {
if (payload.categoryId === null) {
payload.category = { disconnect: true }
} else {
payload.category = { connect: { id: Number(payload.categoryId) } }
}
delete payload.categoryId
}
const item = await this.prisma.product.update({ where: { id }, data: payload })
return ResponseMapper.update(item)
}
remove(id: number) {
return this.prisma.product.delete({ where: { id } })
async remove(id: number) {
const item = await this.prisma.product.delete({ where: { id } })
return ResponseMapper.single(item)
}
}