API/src/controller/product.controller.ts

754 lines
24 KiB
TypeScript

import {
Body,
ContentType,
Controller,
Del,
Files,
Get,
Inject,
Param,
Post,
Put,
Query,
} from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { ILogger } from '@midwayjs/logger';
import { ApiOkResponse } from '@midwayjs/swagger';
import { UnifiedSearchParamsDTO } from '../dto/api.dto';
import { SyncOperationResultDTO } from '../dto/batch.dto';
import { BatchDeleteProductDTO, BatchUpdateProductDTO, CreateCategoryDTO, CreateProductDTO, ProductWhereFilter, UpdateCategoryDTO, UpdateProductDTO } from '../dto/product.dto';
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
import { BatchSyncProductToSiteDTO, SyncProductToSiteDTO, SyncProductToSiteResultDTO } from '../dto/site-sync.dto';
import { ProductService } from '../service/product.service';
import { errorResponse, successResponse } from '../utils/response.util';
@Controller('/product')
export class ProductController {
@Inject()
productService: ProductService;
@Inject()
ctx: Context;
@Inject()
logger: ILogger;
@ApiOkResponse({
description: '通过name搜索产品',
type: ProductsRes,
})
@Get('/search')
async searchProducts(@Query('name') name: string) {
try {
// 调用服务获取产品数据
const products = await this.productService.findProductsByName(name);
return successResponse(products);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
@ApiOkResponse({
type: ProductRes,
})
@Get('/sku/:sku')
async productBySku(@Param('sku') sku: string) {
try {
// 调用服务获取产品数据
const product = await this.productService.findProductBySku(sku);
return successResponse(product);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
@ApiOkResponse({
description: '成功返回产品列表',
type: ProductListRes,
})
@Get('/list')
async getProductList(
@Query() query: UnifiedSearchParamsDTO<ProductWhereFilter>
): Promise<ProductListRes> {
try {
const data = await this.productService.getProductList(query);
return successResponse(data);
} catch (error) {
this.logger.error('获取产品列表失败', error);
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: ProductRes })
@Post('/')
async createProduct(@Body() productData: CreateProductDTO) {
try {
const data = await this.productService.createProduct(productData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 导出所有产品 CSV
@ApiOkResponse()
@Get('/export')
@ContentType('text/csv')
async exportProductsCSV() {
try {
const csv = await this.productService.exportProductsCSV();
// 设置下载文件名(附件形式)
const date = new Date();
const pad = (n: number) => String(n).padStart(2, '0');
const name = `products-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}.csv`;
this.ctx.set('Content-Disposition', `attachment; filename=${name}`);
return csv;
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 导入产品(CSV 文件)
@ApiOkResponse()
@Post('/import')
async importProductsCSV(@Files() files: any) {
try {
// 条件判断:确保存在文件
const file = files?.[0];
if (!file) return errorResponse('未接收到上传文件');
const result = await this.productService.importProductsCSV(file);
return successResponse(result);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: ProductRes })
@Put('/:id')
async updateProduct(@Param('id') id: number, @Body() productData: UpdateProductDTO) {
try {
const data = await this.productService.updateProduct(id, productData);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: BooleanRes })
@Put('/batch-update')
async batchUpdateProduct(@Body() batchUpdateProductDTO: BatchUpdateProductDTO) {
try {
await this.productService.batchUpdateProduct(batchUpdateProductDTO);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: BooleanRes })
@Post('/batch-delete')
async batchDeleteProduct(@Body() body: BatchDeleteProductDTO) {
try {
const result = await this.productService.batchDeleteProduct(body.ids);
if (result.failed > 0) {
return errorResponse(`成功删除 ${result.success} 个,失败 ${result.failed} 个。首个错误: ${result.errors[0]}`);
}
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: ProductRes })
@Put('updateNameCn/:id/:nameCn')
async updatenameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
try {
const data = await this.productService.updatenameCn(id, nameCn);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 根据站点SKU查询产品
@ApiOkResponse({ type: ProductRes })
@Get('/site-sku/:siteSku')
async getProductBySiteSku(@Param('siteSku') siteSku: string) {
try {
const product = await this.productService.findProductBySiteSku(siteSku);
return successResponse(product);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
// 获取产品的站点SKU绑定
@ApiOkResponse()
@Get('/:id/site-skus')
async getProductSiteSkus(@Param('id') id: number) {
try {
const data = await this.productService.getProductSiteSkus(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 覆盖式绑定产品的站点SKU列表
@ApiOkResponse()
@Post('/:id/site-skus')
async bindProductSiteSkus(@Param('id') id: number, @Body() body: { codes: string[] }) {
try {
const data = await this.productService.bindSiteSkus(id, body?.codes || []);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse({ type: ProductRes })
@Get('/:id')
async getProductById(@Param('id') id: number) {
try {
const product = await this.productService.getProductById(id);
return successResponse(product);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
@ApiOkResponse({ type: BooleanRes })
@Del('/:id')
async deleteProduct(@Param('id') id: number) {
try {
const data = await this.productService.deleteProduct(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 获取产品的库存组成
@ApiOkResponse()
@Get('/:id/components')
async getProductComponents(@Param('id') id: number) {
try {
const data = await this.productService.getProductComponents(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 根据 SKU 自动绑定组成(匹配所有相同 SKU 的库存)
@ApiOkResponse()
@Post('/:id/components/auto')
async autoBindComponents(@Param('id') id: number) {
try {
const data = await this.productService.autoBindComponentsBySku(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 通用属性接口:分页列表
@ApiOkResponse()
@Get('/attribute')
async getAttributeList(
@Query('dictName') dictName: string,
@Query('current') current = 1,
@Query('pageSize') pageSize = 10,
@Query('name') name?: string
) {
try {
const data = await this.productService.getAttributeList(
dictName,
{ current, pageSize },
name
);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 通用属性接口:全部列表
@ApiOkResponse()
@Get('/attributeAll')
async getAttributeAll(@Query('dictName') dictName: string) {
try {
const data = await this.productService.getAttributeAll(dictName);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 通用属性接口:创建
@ApiOkResponse()
@Post('/attribute')
async createAttribute(
@Query('dictName') dictName: string,
@Body() body: { title: string; name: string }
) {
try {
// 调用 getOrCreateAttribute 方法,如果不存在则创建,如果存在则返回
const data = await this.productService.getOrCreateAttribute(dictName, body.title, body.name);
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 = await this.productService.updateAttribute(id, body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 通用属性接口:删除
@ApiOkResponse({ type: BooleanRes })
@Del('/attribute/:id')
async deleteAttribute(@Param('id') id: number) {
try {
await this.productService.deleteAttribute(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 兼容旧接口:品牌
@ApiOkResponse()
@Get('/brandAll')
async compatBrandAll() {
try {
const data = await this.productService.getAttributeAll('brand'); // 返回所有品牌字典项
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/brands')
async compatBrands(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
try {
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; image?: string; shortName?: 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; image?: string; shortName?: 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);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/flavors')
async compatFlavors(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
try {
const data = await this.productService.getAttributeList('flavor', { current, pageSize }, name);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Post('/flavors')
async compatCreateFlavors(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
try {
const has = await this.productService.hasAttribute('flavor', body.name);
if (has) return errorResponse('口味已存在');
const data = await this.productService.createAttribute('flavor', body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Put('/flavors/:id')
async compatUpdateFlavors(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
try {
if (body?.name) {
const has = await this.productService.hasAttribute('flavor', 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('/flavors/:id')
async compatDeleteFlavors(@Param('id') id: number) {
try {
await this.productService.deleteAttribute(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 兼容旧接口:规格
@ApiOkResponse()
@Get('/strengthAll')
async compatStrengthAll() {
try {
const data = await this.productService.getAttributeAll('strength');
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/strength')
async compatStrength(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
try {
const data = await this.productService.getAttributeList('strength', { current, pageSize }, name);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Post('/strength')
async compatCreateStrength(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
try {
const has = await this.productService.hasAttribute('strength', body.name);
if (has) return errorResponse('规格已存在');
const data = await this.productService.createAttribute('strength', body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Put('/strength/:id')
async compatUpdateStrength(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
try {
if (body?.name) {
const has = await this.productService.hasAttribute('strength', 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('/strength/:id')
async compatDeleteStrength(@Param('id') id: number) {
try {
await this.productService.deleteAttribute(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 兼容旧接口:尺寸
@ApiOkResponse()
@Get('/sizeAll')
async compatSizeAll() {
try {
const data = await this.productService.getAttributeAll('size');
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Get('/size')
async compatSize(@Query('current') current = 1, @Query('pageSize') pageSize = 10, @Query('name') name?: string) {
try {
const data = await this.productService.getAttributeList('size', { current, pageSize }, name);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Post('/size')
async compatCreateSize(@Body() body: { title: string; name: string; image?: string; shortName?: string }) {
try {
const has = await this.productService.hasAttribute('size', body.name);
if (has) return errorResponse('尺寸已存在');
const data = await this.productService.createAttribute('size', body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
@ApiOkResponse()
@Put('/size/:id')
async compatUpdateSize(@Param('id') id: number, @Body() body: { title?: string; name?: string; image?: string; shortName?: string }) {
try {
if (body?.name) {
const has = await this.productService.hasAttribute('size', 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('/size/:id')
async compatDeleteSize(@Param('id') id: number) {
try {
await this.productService.deleteAttribute(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 获取所有分类
@ApiOkResponse({ description: '获取所有分类' })
@Get('/categories/all')
async getCategoriesAll() {
try {
const data = await this.productService.getCategoriesAll();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 获取分类下的属性配置
@ApiOkResponse({ description: '获取分类下的属性配置' })
@Get('/category/:id/attributes')
async getCategoryAttributes(@Param('id') id: number) {
try {
const data = await this.productService.getCategoryAttributes(id);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 创建分类
@ApiOkResponse({ description: '创建分类' })
@Post('/category')
async createCategory(@Body() body: CreateCategoryDTO) {
try {
const data = await this.productService.createCategory(body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 更新分类
@ApiOkResponse({ description: '更新分类' })
@Put('/category/:id')
async updateCategory(@Param('id') id: number, @Body() body: UpdateCategoryDTO) {
try {
const data = await this.productService.updateCategory(id, body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 删除分类
@ApiOkResponse({ description: '删除分类' })
@Del('/category/:id')
async deleteCategory(@Param('id') id: number) {
try {
await this.productService.deleteCategory(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 创建分类属性
@ApiOkResponse({ description: '创建分类属性' })
@Post('/category/attribute')
async createCategoryAttribute(@Body() body: { categoryId: number; dictId: number }) {
try {
const data = await this.productService.createCategoryAttribute(body);
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 删除分类属性
@ApiOkResponse({ description: '删除分类属性' })
@Del('/category/attribute/:id')
async deleteCategoryAttribute(@Param('id') id: number) {
try {
await this.productService.deleteCategoryAttribute(id);
return successResponse(true);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 同步库存 SKU 到产品单品
@ApiOkResponse({ description: '同步库存 SKU 到产品单品' })
@Post('/sync-stock')
async syncStockToProduct() {
try {
const data = await this.productService.syncStockToProduct();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 同步单个产品到站点
@ApiOkResponse({ description: '同步单个产品到站点', type: SyncProductToSiteResultDTO })
@Post('/sync-to-site')
async syncToSite(@Body() body: SyncProductToSiteDTO) {
try {
const result = await this.productService.syncToSite(body);
return successResponse(result);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 从站点同步产品到本地
@ApiOkResponse({ description: '从站点同步产品到本地', type: ProductRes })
@Post('/sync-from-site')
async syncProductFromSite(@Body() body: { siteId: number; siteProductId: string | number ,sku: string}) {
try {
const { siteId, siteProductId, sku } = body;
const product = await this.productService.syncProductFromSite(siteId, siteProductId, sku);
return successResponse(product);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 批量从站点同步产品到本地
@ApiOkResponse({ description: '批量从站点同步产品到本地', type: SyncOperationResultDTO })
@Post('/batch-sync-from-site')
async batchSyncFromSite(@Body() body: { siteId: number; siteProductIds: (string | number)[] }) {
try {
throw new Error('批量同步产品到本地暂未实现');
// const { siteId, siteProductIds } = body;
// const result = await this.productService.batchSyncFromSite(siteId, siteProductIds.map((id) => ({ siteProductId: id, sku: '' })));
// // 将服务层返回的结果转换为统一格式
// const errors = result.errors.map((error: string) => {
// // 提取产品ID部分作为标识符
// const match = error.match(/站点产品ID (\d+) /);
// const identifier = match ? match[1] : 'unknown';
// return {
// identifier: identifier,
// error: error
// };
// });
// return successResponse({
// total: siteProductIds.length,
// processed: result.synced + errors.length,
// synced: result.synced,
// errors: errors
// });
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 批量同步产品到站点
@ApiOkResponse({ description: '批量同步产品到站点', type: SyncOperationResultDTO })
@Post('/batch-sync-to-site')
async batchSyncToSite(@Body() body: BatchSyncProductToSiteDTO) {
try {
const { siteId, data } = body;
const result = await this.productService.batchSyncToSite(siteId, data);
return successResponse(result);
} catch (error) {
return errorResponse(error?.message || error);
}
}
}