feat(产品): 重构产品属性管理为通用字典项实现
重构产品相关的品牌、口味、规格和尺寸属性管理,改为基于通用字典项的实现方式: 1. 新增 AttributeInputDTO 用于统一处理属性输入 2. 实现通用的字典项增删改查接口 3. 保留旧接口作为兼容层 4. 优化产品创建和更新逻辑以支持新属性结构
This commit is contained in:
parent
bc575840b2
commit
64b8468df8
|
|
@ -11,35 +11,9 @@ import {
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
import { ProductService } from '../service/product.service';
|
import { ProductService } from '../service/product.service';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import {
|
import { CreateProductDTO, QueryProductDTO, UpdateProductDTO } from '../dto/product.dto';
|
||||||
BatchSetSkuDTO,
|
|
||||||
CreateBrandDTO,
|
|
||||||
CreateFlavorsDTO,
|
|
||||||
CreateProductDTO,
|
|
||||||
CreateStrengthDTO,
|
|
||||||
CreateSizeDTO,
|
|
||||||
QueryBrandDTO,
|
|
||||||
QueryFlavorsDTO,
|
|
||||||
QueryProductDTO,
|
|
||||||
QueryStrengthDTO,
|
|
||||||
QuerySizeDTO,
|
|
||||||
UpdateBrandDTO,
|
|
||||||
UpdateFlavorsDTO,
|
|
||||||
UpdateProductDTO,
|
|
||||||
UpdateStrengthDTO,
|
|
||||||
UpdateSizeDTO,
|
|
||||||
} from '../dto/product.dto';
|
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import {
|
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
||||||
BooleanRes,
|
|
||||||
ProductBrandListRes,
|
|
||||||
ProductBrandRes,
|
|
||||||
ProductSizeListRes,
|
|
||||||
ProductSizeRes,
|
|
||||||
ProductListRes,
|
|
||||||
ProductRes,
|
|
||||||
ProductsRes,
|
|
||||||
} from '../dto/reponse.dto';
|
|
||||||
|
|
||||||
@Controller('/product')
|
@Controller('/product')
|
||||||
export class ProductController {
|
export class ProductController {
|
||||||
|
|
@ -98,9 +72,7 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: ProductRes })
|
||||||
type: ProductRes,
|
|
||||||
})
|
|
||||||
@Post('/')
|
@Post('/')
|
||||||
async createProduct(@Body() productData: CreateProductDTO) {
|
async createProduct(@Body() productData: CreateProductDTO) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -111,14 +83,9 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: ProductRes })
|
||||||
type: ProductRes,
|
|
||||||
})
|
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
async updateProduct(
|
async updateProduct(@Param('id') id: number, @Body() productData: UpdateProductDTO) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() productData: UpdateProductDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const data = this.productService.updateProduct(id, productData);
|
const data = this.productService.updateProduct(id, productData);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
|
|
@ -127,14 +94,9 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: ProductRes })
|
||||||
type: ProductRes,
|
|
||||||
})
|
|
||||||
@Put('updateNameCn/:id/:nameCn')
|
@Put('updateNameCn/:id/:nameCn')
|
||||||
async updateProductNameCn(
|
async updateProductNameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Param('nameCn') nameCn: string
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const data = this.productService.updateProductNameCn(id, nameCn);
|
const data = this.productService.updateProductNameCn(id, nameCn);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
|
|
@ -143,9 +105,7 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Del('/:id')
|
@Del('/:id')
|
||||||
async deleteProduct(@Param('id') id: number) {
|
async deleteProduct(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -156,14 +116,19 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: ProductBrandListRes,
|
// 通用属性接口:分页列表
|
||||||
})
|
@ApiOkResponse()
|
||||||
@Get('/brands')
|
@Get('/attribute')
|
||||||
async getBrands(@Query() query: QueryBrandDTO) {
|
async getAttributeList(
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
@Query('dictName') dictName: string,
|
||||||
|
@Query('current') current = 1,
|
||||||
|
@Query('pageSize') pageSize = 10,
|
||||||
|
@Query('name') name?: string
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getBrandList(
|
const data = await this.productService.getAttributeList(
|
||||||
|
dictName,
|
||||||
{ current, pageSize },
|
{ current, pageSize },
|
||||||
name
|
name
|
||||||
);
|
);
|
||||||
|
|
@ -173,95 +138,142 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通用属性接口:全部列表
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/brandAll')
|
@Get('/attributeAll')
|
||||||
async getBrandAll() {
|
async getAttributeAll(@Query('dictName') dictName: string) {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getBrandAll();
|
const data = await this.productService.getAttributeAll(dictName);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 通用属性接口:创建
|
||||||
type: ProductBrandRes,
|
@ApiOkResponse()
|
||||||
})
|
@Post('/attribute')
|
||||||
@Post('/brand')
|
async createAttribute(
|
||||||
async createBrand(@Body() brandData: CreateBrandDTO) {
|
@Query('dictName') dictName: string,
|
||||||
try {
|
@Body() body: { title: string; name: string }
|
||||||
const hasBrand = await this.productService.hasAttribute(
|
|
||||||
'brand',
|
|
||||||
brandData.name
|
|
||||||
);
|
|
||||||
if (hasBrand) {
|
|
||||||
return errorResponse('品牌已存在');
|
|
||||||
}
|
|
||||||
let data = await this.productService.createBrand(brandData);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error?.message || error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOkResponse({
|
|
||||||
type: ProductBrandRes,
|
|
||||||
})
|
|
||||||
@Put('/brand/:id')
|
|
||||||
async updateBrand(
|
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() brandData: UpdateBrandDTO
|
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const hasBrand = await this.productService.hasAttribute(
|
const hasItem = await this.productService.hasAttribute(
|
||||||
'brand',
|
dictName,
|
||||||
brandData.name,
|
body.name
|
||||||
id
|
|
||||||
);
|
);
|
||||||
if (hasBrand) {
|
if (hasItem) return errorResponse('字典项已存在');
|
||||||
return errorResponse('品牌已存在');
|
const data = await this.productService.createAttribute(dictName, body);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性接口:更新
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/attribute/:id')
|
||||||
|
async updateAttribute(
|
||||||
|
@Param('id') id: number,
|
||||||
|
@Query('dictName') dictName: string,
|
||||||
|
@Body() body: { title?: string; name?: string }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (body?.name) {
|
||||||
|
const hasItem = await this.productService.hasAttribute(
|
||||||
|
dictName,
|
||||||
|
body.name,
|
||||||
|
id
|
||||||
|
);
|
||||||
|
if (hasItem) return errorResponse('字典项已存在');
|
||||||
}
|
}
|
||||||
const data = this.productService.updateBrand(id, brandData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
// 通用属性接口:删除
|
||||||
type: BooleanRes,
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
})
|
@Del('/attribute/:id')
|
||||||
@Del('/brand/:id')
|
async deleteAttribute(@Param('id') id: number) {
|
||||||
async deleteBrand(@Param('id') id: number) {
|
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInAttribute(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该品牌下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteBrand(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/batchSetSku')
|
// 兼容旧接口:品牌
|
||||||
@ApiOkResponse({
|
@ApiOkResponse()
|
||||||
description: '批量设置 sku 的响应结果',
|
@Get('/brandAll')
|
||||||
type: BooleanRes,
|
async compatBrandAll() {
|
||||||
})
|
|
||||||
async batchSetSku(@Body() body: BatchSetSkuDTO) {
|
|
||||||
try {
|
try {
|
||||||
const result = await this.productService.batchSetSku(body.skus);
|
const data = await this.productService.getAttributeAll('brand'); // 中文注释:返回所有品牌字典项
|
||||||
return successResponse(result, '批量设置 sku 成功');
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message, 400);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/flavorsAll')
|
@Get('/brands')
|
||||||
async getFlavorsAll() {
|
async compatBrands(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getFlavorsAll();
|
const data = await this.productService.getAttributeList('brand', { current, pageSize }, name); // 中文注释:分页品牌列表
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/brand')
|
||||||
|
async compatCreateBrand(@Body() body: { title: string; name: string }) {
|
||||||
|
try {
|
||||||
|
const has = await this.productService.hasAttribute('brand', body.name); // 中文注释:唯一性校验
|
||||||
|
if (has) return errorResponse('品牌已存在');
|
||||||
|
const data = await this.productService.createAttribute('brand', body); // 中文注释:创建品牌字典项
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Put('/brand/:id')
|
||||||
|
async compatUpdateBrand(@Param('id') id: number, @Body() body: { title?: string; name?: string }) {
|
||||||
|
try {
|
||||||
|
if (body?.name) {
|
||||||
|
const has = await this.productService.hasAttribute('brand', body.name, id); // 中文注释:唯一性校验(排除自身)
|
||||||
|
if (has) return errorResponse('品牌已存在');
|
||||||
|
}
|
||||||
|
const data = await this.productService.updateAttribute(id, body); // 中文注释:更新品牌字典项
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
|
@Del('/brand/:id')
|
||||||
|
async compatDeleteBrand(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
await this.productService.deleteAttribute(id); // 中文注释:删除品牌字典项
|
||||||
|
return successResponse(true);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧接口:口味
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/flavorsAll')
|
||||||
|
async compatFlavorsAll() {
|
||||||
|
try {
|
||||||
|
const data = await this.productService.getAttributeAll('flavor');
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -270,13 +282,9 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/flavors')
|
@Get('/flavors')
|
||||||
async getFlavors(@Query() query: QueryFlavorsDTO) {
|
async compatFlavors(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getFlavorsList(
|
const data = await this.productService.getAttributeList('flavor', { current, pageSize }, name);
|
||||||
{ current, pageSize },
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -285,13 +293,11 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/flavors')
|
@Post('/flavors')
|
||||||
async createFlavors(@Body() flavorsData: CreateFlavorsDTO) {
|
async compatCreateFlavors(@Body() body: { title: string; name: string }) {
|
||||||
try {
|
try {
|
||||||
const hasFlavors = await this.productService.hasAttribute('flavor', flavorsData.name);
|
const has = await this.productService.hasAttribute('flavor', body.name);
|
||||||
if (hasFlavors) {
|
if (has) return errorResponse('口味已存在');
|
||||||
return errorResponse('口味已存在');
|
const data = await this.productService.createAttribute('flavor', body);
|
||||||
}
|
|
||||||
let data = await this.productService.createFlavors(flavorsData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -300,42 +306,36 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Put('/flavors/:id')
|
@Put('/flavors/:id')
|
||||||
async updateFlavors(
|
async compatUpdateFlavors(@Param('id') id: number, @Body() body: { title?: string; name?: string }) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() flavorsData: UpdateFlavorsDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const hasFlavors = await this.productService.hasAttribute('flavor', flavorsData.name, id);
|
if (body?.name) {
|
||||||
if (hasFlavors) {
|
const has = await this.productService.hasAttribute('flavor', body.name, id);
|
||||||
return errorResponse('口味已存在');
|
if (has) return errorResponse('口味已存在');
|
||||||
}
|
}
|
||||||
const data = this.productService.updateFlavors(id, flavorsData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Del('/flavors/:id')
|
@Del('/flavors/:id')
|
||||||
async deleteFlavors(@Param('id') id: number) {
|
async compatDeleteFlavors(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInAttribute(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该口味下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteFlavors(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 兼容旧接口:规格
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/strengthAll')
|
@Get('/strengthAll')
|
||||||
async getStrengthAll() {
|
async compatStrengthAll() {
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getStrengthAll();
|
const data = await this.productService.getAttributeAll('strength');
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -344,13 +344,9 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/strength')
|
@Get('/strength')
|
||||||
async getStrength(@Query() query: QueryStrengthDTO) {
|
async compatStrength(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
let data = await this.productService.getStrengthList(
|
const data = await this.productService.getAttributeList('strength', { current, pageSize }, name);
|
||||||
{ current, pageSize },
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -359,16 +355,11 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/strength')
|
@Post('/strength')
|
||||||
async createStrength(@Body() strengthData: CreateStrengthDTO) {
|
async compatCreateStrength(@Body() body: { title: string; name: string }) {
|
||||||
try {
|
try {
|
||||||
const hasStrength = await this.productService.hasAttribute(
|
const has = await this.productService.hasAttribute('strength', body.name);
|
||||||
'strength',
|
if (has) return errorResponse('规格已存在');
|
||||||
strengthData.name
|
const data = await this.productService.createAttribute('strength', body);
|
||||||
);
|
|
||||||
if (hasStrength) {
|
|
||||||
return errorResponse('规格已存在');
|
|
||||||
}
|
|
||||||
let data = await this.productService.createStrength(strengthData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -377,109 +368,75 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Put('/strength/:id')
|
@Put('/strength/:id')
|
||||||
async updateStrength(
|
async compatUpdateStrength(@Param('id') id: number, @Body() body: { title?: string; name?: string }) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() strengthData: UpdateStrengthDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const hasStrength = await this.productService.hasAttribute(
|
if (body?.name) {
|
||||||
'strength',
|
const has = await this.productService.hasAttribute('strength', body.name, id);
|
||||||
strengthData.name,
|
if (has) return errorResponse('规格已存在');
|
||||||
id
|
|
||||||
);
|
|
||||||
if (hasStrength) {
|
|
||||||
return errorResponse('规格已存在');
|
|
||||||
}
|
}
|
||||||
const data = this.productService.updateStrength(id, strengthData);
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
type: BooleanRes,
|
|
||||||
})
|
|
||||||
@Del('/strength/:id')
|
@Del('/strength/:id')
|
||||||
async deleteStrength(@Param('id') id: number) {
|
async compatDeleteStrength(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
const hasProducts = await this.productService.hasProductsInAttribute(id);
|
await this.productService.deleteAttribute(id);
|
||||||
if (hasProducts) throw new Error('该规格下有商品,无法删除');
|
return successResponse(true);
|
||||||
const data = await this.productService.deleteStrength(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// size 路由与增删改查
|
// 兼容旧接口:尺寸
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/sizeAll')
|
@Get('/sizeAll')
|
||||||
async getSizeAll() {
|
async compatSizeAll() {
|
||||||
try {
|
try {
|
||||||
// 中文注释:获取所有尺寸项
|
const data = await this.productService.getAttributeAll('size');
|
||||||
const data = await this.productService.getSizeAll();
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: ProductSizeListRes })
|
@ApiOkResponse()
|
||||||
@Get('/size')
|
@Get('/size')
|
||||||
async getSize(@Query() query: QuerySizeDTO) {
|
async compatSize(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
|
||||||
// 中文注释:解析分页与关键字
|
|
||||||
const { current = 1, pageSize = 10, name } = query;
|
|
||||||
try {
|
try {
|
||||||
// 中文注释:分页查询尺寸列表
|
const data = await this.productService.getAttributeList('size', { current, pageSize }, name);
|
||||||
const data = await this.productService.getSizeList(
|
|
||||||
{ current, pageSize },
|
|
||||||
name
|
|
||||||
);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: ProductSizeRes })
|
@ApiOkResponse()
|
||||||
@Post('/size')
|
@Post('/size')
|
||||||
async createSize(@Body() sizeData: CreateSizeDTO) {
|
async compatCreateSize(@Body() body: { title: string; name: string }) {
|
||||||
try {
|
try {
|
||||||
// 条件判断(中文注释:唯一性校验,禁止重复)
|
const has = await this.productService.hasAttribute('size', body.name);
|
||||||
const hasSize = await this.productService.hasAttribute(
|
if (has) return errorResponse('尺寸已存在');
|
||||||
'size',
|
const data = await this.productService.createAttribute('size', body);
|
||||||
sizeData.name
|
|
||||||
);
|
|
||||||
if (hasSize) {
|
|
||||||
return errorResponse('尺寸已存在');
|
|
||||||
}
|
|
||||||
// 调用服务创建(中文注释:新增尺寸项)
|
|
||||||
const data = await this.productService.createSize(sizeData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: ProductSizeRes })
|
@ApiOkResponse()
|
||||||
@Put('/size/:id')
|
@Put('/size/:id')
|
||||||
async updateSize(
|
async compatUpdateSize(@Param('id') id: number, @Body() body: { title?: string; name?: string }) {
|
||||||
@Param('id') id: number,
|
|
||||||
@Body() sizeData: UpdateSizeDTO
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
// 条件判断(中文注释:唯一性校验,排除自身)
|
if (body?.name) {
|
||||||
const hasSize = await this.productService.hasAttribute(
|
const has = await this.productService.hasAttribute('size', body.name, id);
|
||||||
'size',
|
if (has) return errorResponse('尺寸已存在');
|
||||||
sizeData.name,
|
|
||||||
id
|
|
||||||
);
|
|
||||||
if (hasSize) {
|
|
||||||
return errorResponse('尺寸已存在');
|
|
||||||
}
|
}
|
||||||
// 调用服务更新(中文注释:提交变更)
|
const data = await this.productService.updateAttribute(id, body);
|
||||||
const data = await this.productService.updateSize(id, sizeData);
|
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -488,14 +445,10 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse({ type: BooleanRes })
|
@ApiOkResponse({ type: BooleanRes })
|
||||||
@Del('/size/:id')
|
@Del('/size/:id')
|
||||||
async deleteSize(@Param('id') id: number) {
|
async compatDeleteSize(@Param('id') id: number) {
|
||||||
try {
|
try {
|
||||||
// 条件判断(中文注释:若有商品关联则不可删除)
|
await this.productService.deleteAttribute(id);
|
||||||
const hasProducts = await this.productService.hasProductsInAttribute(id);
|
return successResponse(true);
|
||||||
if (hasProducts) throw new Error('该尺寸下有商品,无法删除');
|
|
||||||
// 调用服务删除(中文注释:返回是否成功)
|
|
||||||
const data = await this.productService.deleteSize(id);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,9 @@ export class UpdateDictItemDTO {
|
||||||
title?: string; // 字典项标题 (可选)
|
title?: string; // 字典项标题 (可选)
|
||||||
|
|
||||||
@Rule(RuleType.string().allow(null))
|
@Rule(RuleType.string().allow(null))
|
||||||
value?: string; // 字典项值 (可选)
|
titleCN?: string; // 字典项中文标题 (可选)
|
||||||
|
|
||||||
@Rule(RuleType.string().allow(null))
|
@Rule(RuleType.string().allow(null))
|
||||||
titleCN?: string; // 字典项中文标题 (可选)
|
value?: string; // 字典项值 (可选)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,35 +21,52 @@ export class CreateProductDTO {
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
sku?: string;
|
sku?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '品牌 ID', type: 'number' })
|
// 通用属性输入(中文注释:通过 attributes 统一提交品牌/口味/强度/尺寸/干湿等)
|
||||||
@Rule(RuleType.number().required())
|
@ApiProperty({ description: '属性列表', type: 'array' })
|
||||||
brandId: number;
|
@Rule(RuleType.array().required())
|
||||||
|
attributes: AttributeInputDTO[];
|
||||||
@ApiProperty({ description: '规格 ID', type: 'number' })
|
|
||||||
@Rule(RuleType.number().required())
|
|
||||||
strengthId: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '口味 ID', type: 'number' })
|
|
||||||
@Rule(RuleType.number().required())
|
|
||||||
flavorsId: number;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
humidity: string;
|
|
||||||
|
|
||||||
// 商品价格
|
// 商品价格
|
||||||
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
price?: number;
|
price?: number;
|
||||||
|
|
||||||
|
// 促销价格
|
||||||
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
promotionPrice?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 用于更新产品
|
* DTO 用于更新产品
|
||||||
*/
|
*/
|
||||||
export class UpdateProductDTO extends CreateProductDTO {
|
export class UpdateProductDTO {
|
||||||
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称' })
|
@ApiProperty({ example: 'ZYN 6MG WINTERGREEN', description: '产品名称' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string;
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '产品描述', description: '产品描述' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品 SKU', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
sku?: string;
|
||||||
|
|
||||||
|
// 商品价格
|
||||||
|
@ApiProperty({ description: '价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
price?: number;
|
||||||
|
|
||||||
|
// 促销价格
|
||||||
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
// 属性更新(中文注释:可选,支持增量替换指定字典的属性项)
|
||||||
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
|
@Rule(RuleType.array())
|
||||||
|
attributes?: AttributeInputDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,6 +90,25 @@ export class QueryProductDTO {
|
||||||
brandId: number;
|
brandId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 属性输入项(中文注释:用于在创建/更新产品时传递字典项信息)
|
||||||
|
export class AttributeInputDTO {
|
||||||
|
@ApiProperty({ description: '字典名称', example: 'brand' })
|
||||||
|
@Rule(RuleType.string().required())
|
||||||
|
dictName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '字典项 ID', required: false })
|
||||||
|
@Rule(RuleType.number())
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '字典项显示名称', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '字典项唯一标识', required: false })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DTO 用于创建品牌
|
* DTO 用于创建品牌
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -164,57 +164,14 @@ export class ProductService {
|
||||||
|
|
||||||
const [items, total] = await qb.getManyAndCount();
|
const [items, total] = await qb.getManyAndCount();
|
||||||
|
|
||||||
// 获取所有 SKU 的库存信息
|
|
||||||
const skus = items.map(item => item.sku).filter(Boolean);
|
|
||||||
const stocks = await this.stockService.getStocksBySkus(skus);
|
|
||||||
|
|
||||||
// 将库存信息映射到 SKU
|
|
||||||
const stockMap = stocks.reduce((map, stock) => {
|
|
||||||
map[stock.productSku] = stock.totalQuantity;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// 格式化返回的数据(中文注释:将品牌/口味/规格/尺寸均以 DictItem 对象形式返回,并保留 attributes 列表)
|
|
||||||
const formattedItems = items.map(product => {
|
|
||||||
// 函数(中文注释:按字典名称获取对应的属性对象)
|
|
||||||
const getAttributeByDict = (dictName: string) =>
|
|
||||||
product.attributes.find(a => a.dict?.name === dictName) || null;
|
|
||||||
// 条件判断(中文注释:从属性中取出各类维度对象)
|
|
||||||
const brand = getAttributeByDict('brand');
|
|
||||||
const flavors = getAttributeByDict('flavor');
|
|
||||||
const strength = getAttributeByDict('strength');
|
|
||||||
const size = getAttributeByDict('size');
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: product.id,
|
|
||||||
name: product.name,
|
|
||||||
nameCn: product.nameCn,
|
|
||||||
description: product.description,
|
|
||||||
sku: product.sku,
|
|
||||||
stock: stockMap[product.sku] || 0, // 中文注释:库存使用聚合库存值
|
|
||||||
price: product.price,
|
|
||||||
promotionPrice: product.promotionPrice,
|
|
||||||
source: product.source, // 中文注释:返回产品来源字段
|
|
||||||
createdAt: product.createdAt,
|
|
||||||
updatedAt: product.updatedAt,
|
|
||||||
// 单列属性(中文注释:直接返回 DictItem 对象,方便前端展示与使用)
|
|
||||||
brand,
|
|
||||||
flavors,
|
|
||||||
strength,
|
|
||||||
size,
|
|
||||||
// 全量属性列表(中文注释:保留原 attributes,包含字典关系)
|
|
||||||
attributes: product.attributes,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: formattedItems,
|
items,
|
||||||
total,
|
total,
|
||||||
...pagination,
|
...pagination,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreateDictItem(
|
async getOrCreateAttribute(
|
||||||
dictName: string,
|
dictName: string,
|
||||||
itemTitle: string,
|
itemTitle: string,
|
||||||
itemName?: string
|
itemName?: string
|
||||||
|
|
@ -243,25 +200,32 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createProduct(createProductDTO: CreateProductDTO): Promise<Product> {
|
async createProduct(createProductDTO: CreateProductDTO): Promise<Product> {
|
||||||
const { name, description, brandId, flavorsId, strengthId, humidity, sku, price } =
|
const { name, description, attributes, sku, price } = createProductDTO;
|
||||||
createProductDTO;
|
|
||||||
|
|
||||||
// 获取或创建品牌、口味、规格
|
// 条件判断(中文注释:校验属性输入)
|
||||||
const brandItem = await this.dictItemModel.findOne({ where: { id: brandId } });
|
if (!Array.isArray(attributes) || attributes.length === 0) {
|
||||||
if (!brandItem) throw new Error('品牌不存在');
|
throw new Error('属性列表不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
const flavorItem = await this.dictItemModel.findOne({ where: { id: flavorsId } });
|
// 解析属性输入(中文注释:按 id 或 dictName 创建/关联字典项)
|
||||||
if (!flavorItem) throw new Error('口味不存在');
|
const resolvedAttributes: DictItem[] = [];
|
||||||
|
for (const attr of attributes) {
|
||||||
|
if (!attr?.dictName) throw new Error('属性项缺少字典名称');
|
||||||
|
let item: DictItem | null = null;
|
||||||
|
if (attr.id) {
|
||||||
|
item = await this.dictItemModel.findOne({ where: { id: attr.id }, relations: ['dict'] });
|
||||||
|
if (!item) throw new Error(`字典项 ID ${attr.id} 不存在`);
|
||||||
|
} else {
|
||||||
|
const titleOrName = attr.title || attr.name;
|
||||||
|
if (!titleOrName) throw new Error('新建字典项需要提供 title 或 name');
|
||||||
|
item = await this.getOrCreateAttribute(attr.dictName, titleOrName, attr.name);
|
||||||
|
}
|
||||||
|
resolvedAttributes.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
const strengthItem = await this.dictItemModel.findOne({ where: { id: strengthId } });
|
// 检查完全相同属性组合是否已存在(中文注释:避免重复)
|
||||||
if (!strengthItem) throw new Error('规格不存在');
|
|
||||||
|
|
||||||
const humidityItem = await this.getOrCreateDictItem('humidity', humidity);
|
|
||||||
|
|
||||||
// 检查具有完全相同属性组合的产品是否已存在
|
|
||||||
const attributesToMatch = [brandItem, flavorItem, strengthItem, humidityItem];
|
|
||||||
const qb = this.productModel.createQueryBuilder('product');
|
const qb = this.productModel.createQueryBuilder('product');
|
||||||
attributesToMatch.forEach((attr, index) => {
|
resolvedAttributes.forEach((attr, index) => {
|
||||||
qb.innerJoin(
|
qb.innerJoin(
|
||||||
'product.attributes',
|
'product.attributes',
|
||||||
`attr${index}`,
|
`attr${index}`,
|
||||||
|
|
@ -269,30 +233,32 @@ export class ProductService {
|
||||||
{ [`attrId${index}`]: attr.id }
|
{ [`attrId${index}`]: attr.id }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const isExit = await qb.getOne();
|
const isExist = await qb.getOne();
|
||||||
|
if (isExist) throw new Error('产品已存在');
|
||||||
|
|
||||||
if (isExit) throw new Error('产品已存在');
|
// 创建新产品实例(中文注释:绑定属性与基础字段)
|
||||||
|
|
||||||
// 创建新产品实例
|
|
||||||
const product = new Product();
|
const product = new Product();
|
||||||
product.name = name;
|
product.name = name;
|
||||||
product.description = description;
|
product.description = description;
|
||||||
product.attributes = attributesToMatch;
|
product.attributes = resolvedAttributes;
|
||||||
|
|
||||||
// 如果用户提供了 sku,则直接使用;否则,通过模板引擎生成
|
// 生成或设置 SKU(中文注释:基于属性字典项的 name 生成)
|
||||||
if (sku) {
|
if (sku) {
|
||||||
product.sku = sku;
|
product.sku = sku;
|
||||||
} else {
|
} else {
|
||||||
// 生成 SKU
|
const attributeMap: Record<string, string> = {};
|
||||||
|
for (const a of resolvedAttributes) {
|
||||||
|
if (a?.dict?.name && a?.name) attributeMap[a.dict.name] = a.name;
|
||||||
|
}
|
||||||
product.sku = await this.templateService.render('product_sku', {
|
product.sku = await this.templateService.render('product_sku', {
|
||||||
brand: brandItem.name,
|
brand: attributeMap['brand'] || '',
|
||||||
flavor: flavorItem.name,
|
flavor: attributeMap['flavor'] || '',
|
||||||
strength: strengthItem.name,
|
strength: attributeMap['strength'] || '',
|
||||||
humidity: humidityItem.name,
|
humidity: attributeMap['humidity'] || '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 价格与促销价
|
// 价格与促销价(中文注释:可选字段)
|
||||||
if (price !== undefined) {
|
if (price !== undefined) {
|
||||||
product.price = Number(price);
|
product.price = Number(price);
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +267,6 @@ export class ProductService {
|
||||||
product.promotionPrice = Number(promotionPrice);
|
product.promotionPrice = Number(promotionPrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存产品
|
|
||||||
return await this.productModel.save(product);
|
return await this.productModel.save(product);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,40 +305,31 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理属性更新(品牌/口味/强度/干湿)
|
// 处理属性更新(中文注释:若传入 attributes 则按字典名称替换对应项)
|
||||||
const nextAttributes: DictItem[] = [...(product.attributes || [])];
|
if (Array.isArray(updateProductDTO.attributes) && updateProductDTO.attributes.length > 0) {
|
||||||
|
const nextAttributes: DictItem[] = [...(product.attributes || [])];
|
||||||
|
|
||||||
// 根据 dict.name 查找或替换已有属性
|
const replaceAttr = (dictName: string, item: DictItem) => {
|
||||||
const replaceAttr = (dictName: string, item: DictItem) => {
|
const idx = nextAttributes.findIndex(a => a.dict?.name === dictName);
|
||||||
const idx = nextAttributes.findIndex(a => a.dict?.name === dictName);
|
if (idx >= 0) nextAttributes[idx] = item; else nextAttributes.push(item);
|
||||||
if (idx >= 0) nextAttributes[idx] = item; else nextAttributes.push(item);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// 品牌
|
for (const attr of updateProductDTO.attributes) {
|
||||||
if (updateProductDTO.brandId !== undefined) {
|
if (!attr?.dictName) throw new Error('属性项缺少字典名称');
|
||||||
const brandItem = await this.dictItemModel.findOne({ where: { id: updateProductDTO.brandId }, relations: ['dict'] });
|
let item: DictItem | null = null;
|
||||||
if (!brandItem) throw new Error('品牌不存在');
|
if (attr.id) {
|
||||||
replaceAttr('brand', brandItem);
|
item = await this.dictItemModel.findOne({ where: { id: attr.id }, relations: ['dict'] });
|
||||||
}
|
if (!item) throw new Error(`字典项 ID ${attr.id} 不存在`);
|
||||||
// 口味
|
} else {
|
||||||
if (updateProductDTO.flavorsId !== undefined) {
|
const titleOrName = attr.title || attr.name;
|
||||||
const flavorItem = await this.dictItemModel.findOne({ where: { id: updateProductDTO.flavorsId }, relations: ['dict'] });
|
if (!titleOrName) throw new Error('新建字典项需要提供 title 或 name');
|
||||||
if (!flavorItem) throw new Error('口味不存在');
|
item = await this.getOrCreateAttribute(attr.dictName, titleOrName, attr.name);
|
||||||
replaceAttr('flavor', flavorItem);
|
}
|
||||||
}
|
replaceAttr(attr.dictName, item);
|
||||||
// 强度
|
}
|
||||||
if (updateProductDTO.strengthId !== undefined) {
|
|
||||||
const strengthItem = await this.dictItemModel.findOne({ where: { id: updateProductDTO.strengthId }, relations: ['dict'] });
|
|
||||||
if (!strengthItem) throw new Error('规格不存在');
|
|
||||||
replaceAttr('strength', strengthItem);
|
|
||||||
}
|
|
||||||
// 干湿(按 title 获取或创建)
|
|
||||||
if (updateProductDTO.humidity !== undefined) {
|
|
||||||
const humidityItem = await this.getOrCreateDictItem('humidity', updateProductDTO.humidity);
|
|
||||||
replaceAttr('humidity', humidityItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
product.attributes = nextAttributes;
|
product.attributes = nextAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
// 保存更新后的产品
|
// 保存更新后的产品
|
||||||
const saved = await this.productModel.save(product);
|
const saved = await this.productModel.save(product);
|
||||||
|
|
@ -752,6 +708,75 @@ export class ProductService {
|
||||||
return await this.dictItemModel.save(strength);
|
return await this.dictItemModel.save(strength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 通用属性:分页获取指定字典的字典项
|
||||||
|
async getAttributeList(
|
||||||
|
dictName: string,
|
||||||
|
pagination: PaginationParams,
|
||||||
|
name?: string
|
||||||
|
): Promise<BrandPaginatedResponse> {
|
||||||
|
const dict = await this.dictModel.findOne({ where: { name: dictName } });
|
||||||
|
if (!dict) return { items: [], total: 0, ...pagination } as any;
|
||||||
|
const where: any = { dict: { id: dict.id } };
|
||||||
|
if (name) where.title = Like(`%${name}%`);
|
||||||
|
const [items, total] = await this.dictItemModel.findAndCount({
|
||||||
|
where,
|
||||||
|
skip: (pagination.current - 1) * pagination.pageSize,
|
||||||
|
take: pagination.pageSize,
|
||||||
|
order: { sort: 'ASC', id: 'DESC' },
|
||||||
|
relations: ['dict'],
|
||||||
|
});
|
||||||
|
return { items, total, ...pagination } as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性:获取指定字典的全部字典项
|
||||||
|
async getAttributeAll(dictName: string): Promise<DictItem[]> {
|
||||||
|
const dict = await this.dictModel.findOne({ where: { name: dictName } });
|
||||||
|
if (!dict) return [];
|
||||||
|
return this.dictItemModel.find({
|
||||||
|
where: { dict: { id: dict.id } },
|
||||||
|
order: { sort: 'ASC', id: 'DESC' },
|
||||||
|
relations: ['dict'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性:创建字典项
|
||||||
|
async createAttribute(
|
||||||
|
dictName: string,
|
||||||
|
payload: { title: string; name: string }
|
||||||
|
): Promise<DictItem> {
|
||||||
|
const dict = await this.dictModel.findOne({ where: { name: dictName } });
|
||||||
|
if (!dict) throw new Error(`字典 ${dictName} 不存在`);
|
||||||
|
const exists = await this.dictItemModel.findOne({
|
||||||
|
where: { name: payload.name, dict: { id: dict.id } },
|
||||||
|
relations: ['dict'],
|
||||||
|
});
|
||||||
|
if (exists) throw new Error('字典项已存在');
|
||||||
|
const item = new DictItem();
|
||||||
|
item.title = payload.title;
|
||||||
|
item.name = payload.name;
|
||||||
|
item.dict = dict;
|
||||||
|
return await this.dictItemModel.save(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性:更新字典项
|
||||||
|
async updateAttribute(
|
||||||
|
id: number,
|
||||||
|
payload: { title?: string; name?: string }
|
||||||
|
): Promise<DictItem> {
|
||||||
|
const item = await this.dictItemModel.findOne({ where: { id } });
|
||||||
|
if (!item) throw new Error('字典项不存在');
|
||||||
|
if (payload.title !== undefined) item.title = payload.title;
|
||||||
|
if (payload.name !== undefined) item.name = payload.name;
|
||||||
|
return await this.dictItemModel.save(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用属性:删除字典项(若存在产品关联则禁止删除)
|
||||||
|
async deleteAttribute(id: number): Promise<void> {
|
||||||
|
const hasProducts = await this.hasProductsInAttribute(id);
|
||||||
|
if (hasProducts) throw new Error('当前字典项存在关联产品,无法删除');
|
||||||
|
await this.dictItemModel.delete({ id });
|
||||||
|
}
|
||||||
|
|
||||||
async updateStrength(id: number, updateStrength: UpdateStrengthDTO) {
|
async updateStrength(id: number, updateStrength: UpdateStrengthDTO) {
|
||||||
const strength = await this.dictItemModel.findOneBy({ id });
|
const strength = await this.dictItemModel.findOneBy({ id });
|
||||||
if (!strength) {
|
if (!strength) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue