forked from yoone/API
477 lines
14 KiB
TypeScript
477 lines
14 KiB
TypeScript
import { Provide } from '@midwayjs/core';
|
||
import { In, Like, Not, Repository } from 'typeorm';
|
||
import { Product } from '../entity/product.entity';
|
||
import { Category } from '../entity/category.entity';
|
||
import { paginate } from '../utils/paginate.util';
|
||
import { PaginationParams } from '../interface';
|
||
import {
|
||
CreateCategoryDTO,
|
||
CreateFlavorsDTO,
|
||
CreateProductDTO,
|
||
CreateStrengthDTO,
|
||
UpdateCategoryDTO,
|
||
UpdateFlavorsDTO,
|
||
UpdateProductDTO,
|
||
UpdateStrengthDTO,
|
||
} from '../dto/product.dto';
|
||
import {
|
||
CategoryPaginatedResponse,
|
||
FlavorsPaginatedResponse,
|
||
ProductPaginatedResponse,
|
||
StrengthPaginatedResponse,
|
||
} from '../dto/reponse.dto';
|
||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||
import { WpProduct } from '../entity/wp_product.entity';
|
||
import { Variation } from '../entity/variation.entity';
|
||
import { Strength } from '../entity/strength.entity';
|
||
import { Flavors } from '../entity/flavors.entity';
|
||
|
||
@Provide()
|
||
export class ProductService {
|
||
@InjectEntityModel(Product)
|
||
productModel: Repository<Product>;
|
||
|
||
@InjectEntityModel(Category)
|
||
categoryModel: Repository<Category>;
|
||
|
||
@InjectEntityModel(Strength)
|
||
strengthModel: Repository<Strength>;
|
||
|
||
@InjectEntityModel(Flavors)
|
||
flavorsModel: Repository<Flavors>;
|
||
|
||
@InjectEntityModel(WpProduct)
|
||
wpProductModel: Repository<WpProduct>;
|
||
|
||
@InjectEntityModel(Variation)
|
||
variationModel: Repository<Variation>;
|
||
|
||
// async findProductsByName(name: string): Promise<Product[]> {
|
||
// const where: any = {};
|
||
// const nameFilter = name ? name.split(' ').filter(Boolean) : [];
|
||
// if (nameFilter.length > 0) {
|
||
// const nameConditions = nameFilter.map(word => Like(`%${word}%`));
|
||
// where.name = And(...nameConditions);
|
||
// }
|
||
// if(name){
|
||
// where.nameCn = Like(`%${name}%`)
|
||
// }
|
||
// where.sku = Not(IsNull());
|
||
// // 查询 SKU 不为空且 name 包含关键字的产品,最多返回 50 条
|
||
// return this.productModel.find({
|
||
// where,
|
||
// take: 50,
|
||
// });
|
||
// }
|
||
|
||
async findProductsByName(name: string): Promise<Product[]> {
|
||
const nameFilter = name ? name.split(' ').filter(Boolean) : [];
|
||
const query = this.productModel.createQueryBuilder('product');
|
||
|
||
// 保证 sku 不为空
|
||
query.where('product.sku IS NOT NULL');
|
||
|
||
if (nameFilter.length > 0 || name) {
|
||
const params: Record<string, string> = {};
|
||
const conditions: string[] = [];
|
||
|
||
// 英文名关键词全部匹配(AND)
|
||
if (nameFilter.length > 0) {
|
||
const nameConds = nameFilter.map((word, index) => {
|
||
const key = `name${index}`;
|
||
params[key] = `%${word}%`;
|
||
return `product.name LIKE :${key}`;
|
||
});
|
||
conditions.push(`(${nameConds.join(' AND ')})`);
|
||
}
|
||
|
||
// 中文名模糊匹配
|
||
if (name) {
|
||
params['nameCn'] = `%${name}%`;
|
||
conditions.push(`product.nameCn LIKE :nameCn`);
|
||
}
|
||
|
||
// 英文名关键词匹配 OR 中文名匹配
|
||
query.andWhere(`(${conditions.join(' OR ')})`, params);
|
||
}
|
||
|
||
query.take(50);
|
||
|
||
return await query.getMany();
|
||
}
|
||
|
||
async findProductBySku(sku: string): Promise<Product> {
|
||
return this.productModel.findOne({
|
||
where: {
|
||
sku,
|
||
},
|
||
});
|
||
}
|
||
|
||
async getProductList(
|
||
pagination: PaginationParams,
|
||
name?: string,
|
||
categoryId?: number
|
||
): Promise<ProductPaginatedResponse> {
|
||
const nameFilter = name ? name.split(' ').filter(Boolean) : [];
|
||
|
||
const qb = this.productModel
|
||
.createQueryBuilder('product')
|
||
.leftJoin(Category, 'category', 'category.id = product.categoryId')
|
||
.leftJoin(Strength, 'strength', 'strength.id = product.strengthId')
|
||
.leftJoin(Flavors, 'flavors', 'flavors.id = product.flavorsId')
|
||
.select([
|
||
'product.id as id',
|
||
'product.name as name',
|
||
'product.nameCn as nameCn',
|
||
'product.description as description',
|
||
'product.humidity as humidity',
|
||
'product.sku as sku',
|
||
'product.createdAt as createdAt',
|
||
'product.updatedAt as updatedAt',
|
||
'category.name AS categoryName',
|
||
'strength.name AS strengthName',
|
||
'flavors.name AS flavorsName',
|
||
]);
|
||
|
||
// 模糊搜索 name,支持多个关键词
|
||
nameFilter.forEach((word, index) => {
|
||
qb.andWhere(`product.name LIKE :name${index}`, {
|
||
[`name${index}`]: `%${word}%`,
|
||
});
|
||
});
|
||
|
||
// 分类过滤
|
||
if (categoryId) {
|
||
qb.andWhere('product.categoryId = :categoryId', { categoryId });
|
||
}
|
||
|
||
// 分页
|
||
qb.skip((pagination.current - 1) * pagination.pageSize).take(
|
||
pagination.pageSize
|
||
);
|
||
|
||
// 执行查询
|
||
const items = await qb.getRawMany();
|
||
const total = await qb.getCount();
|
||
|
||
return {
|
||
items,
|
||
total,
|
||
...pagination,
|
||
};
|
||
}
|
||
|
||
async createProduct(createProductDTO: CreateProductDTO): Promise<Product> {
|
||
const { name, description, categoryId, strengthId, flavorsId, humidity } =
|
||
createProductDTO;
|
||
const isExit = await this.productModel.findOne({
|
||
where: {
|
||
categoryId,
|
||
strengthId,
|
||
flavorsId,
|
||
humidity,
|
||
},
|
||
});
|
||
if (isExit) throw new Error('产品已存在');
|
||
const product = new Product();
|
||
product.name = name;
|
||
product.description = description;
|
||
product.categoryId = categoryId;
|
||
product.strengthId = strengthId;
|
||
product.flavorsId = flavorsId;
|
||
product.humidity = humidity;
|
||
const categoryKey = (
|
||
await this.categoryModel.findOne({ where: { id: categoryId } })
|
||
).unique_key;
|
||
const strengthKey = (
|
||
await this.strengthModel.findOne({ where: { id: strengthId } })
|
||
).unique_key;
|
||
const flavorsKey = (
|
||
await this.flavorsModel.findOne({ where: { id: flavorsId } })
|
||
).unique_key;
|
||
product.sku = `${categoryKey}-${flavorsKey}-${strengthKey}-${humidity}`;
|
||
return await this.productModel.save(product);
|
||
}
|
||
|
||
async updateProduct(
|
||
id: number,
|
||
updateProductDTO: UpdateProductDTO
|
||
): Promise<Product> {
|
||
// 确认产品是否存在
|
||
const product = await this.productModel.findOneBy({ id });
|
||
if (!product) {
|
||
throw new Error(`产品 ID ${id} 不存在`);
|
||
}
|
||
// 更新产品
|
||
await this.productModel.update(id, updateProductDTO);
|
||
// 返回更新后的产品
|
||
return await this.productModel.findOneBy({ id });
|
||
}
|
||
|
||
async updateProductNameCn(id: number, nameCn: string): Promise<Product> {
|
||
// 确认产品是否存在
|
||
const product = await this.productModel.findOneBy({ id });
|
||
if (!product) {
|
||
throw new Error(`产品 ID ${id} 不存在`);
|
||
}
|
||
// 更新产品
|
||
await this.productModel.update(id, { nameCn });
|
||
// 返回更新后的产品
|
||
return await this.productModel.findOneBy({ id });
|
||
}
|
||
|
||
async deleteProduct(id: number): Promise<boolean> {
|
||
// 检查产品是否存在
|
||
const product = await this.productModel.findOneBy({ id });
|
||
if (!product) {
|
||
throw new Error(`产品 ID ${id} 不存在`);
|
||
}
|
||
const productSku = product.sku;
|
||
|
||
// 查询 wp_product 表中是否存在与该 SKU 关联的产品
|
||
const wpProduct = await this.wpProductModel
|
||
.createQueryBuilder('wp_product')
|
||
.where('JSON_CONTAINS(wp_product.constitution, :sku)', {
|
||
sku: JSON.stringify({ sku: productSku }),
|
||
})
|
||
.getOne();
|
||
if (wpProduct) {
|
||
throw new Error('无法删除,请先删除关联的WP产品');
|
||
}
|
||
|
||
const variation = await this.variationModel
|
||
.createQueryBuilder('variation')
|
||
.where('JSON_CONTAINS(variation.constitution, :sku)', {
|
||
sku: JSON.stringify({ sku: productSku }),
|
||
})
|
||
.getOne();
|
||
|
||
if (variation) {
|
||
console.log(variation);
|
||
throw new Error('无法删除,请先删除关联的WP变体');
|
||
}
|
||
|
||
// 删除产品
|
||
const result = await this.productModel.delete(id);
|
||
return result.affected > 0; // `affected` 表示删除的行数
|
||
}
|
||
|
||
async hasProductsInCategory(categoryId: number): Promise<boolean> {
|
||
const count = await this.productModel.count({
|
||
where: { categoryId },
|
||
});
|
||
return count > 0;
|
||
}
|
||
|
||
async hasCategory(name: string, id?: string): Promise<boolean> {
|
||
const where: any = { name };
|
||
if (id) where.id = Not(id);
|
||
const count = await this.categoryModel.count({
|
||
where,
|
||
});
|
||
return count > 0;
|
||
}
|
||
|
||
async getCategoryList(
|
||
pagination: PaginationParams,
|
||
name?: string
|
||
): Promise<CategoryPaginatedResponse> {
|
||
const where: any = {};
|
||
if (name) {
|
||
where.name = Like(`%${name}%`);
|
||
}
|
||
return await paginate(this.categoryModel, { pagination, where });
|
||
}
|
||
|
||
async getCategoryAll(): Promise<CategoryPaginatedResponse> {
|
||
return await this.categoryModel.find();
|
||
}
|
||
|
||
async createCategory(
|
||
createCategoryDTO: CreateCategoryDTO
|
||
): Promise<Category> {
|
||
const { name, unique_key } = createCategoryDTO;
|
||
const category = new Category();
|
||
category.name = name;
|
||
category.unique_key = unique_key;
|
||
return await this.categoryModel.save(category);
|
||
}
|
||
|
||
async updateCategory(id: number, updateCategory: UpdateCategoryDTO) {
|
||
// 确认产品是否存在
|
||
const category = await this.categoryModel.findOneBy({ id });
|
||
if (!category) {
|
||
throw new Error(`产品分类 ID ${id} 不存在`);
|
||
}
|
||
// 更新产品
|
||
await this.categoryModel.update(id, updateCategory);
|
||
// 返回更新后的产品
|
||
return await this.categoryModel.findOneBy({ id });
|
||
}
|
||
|
||
async deleteCategory(id: number): Promise<boolean> {
|
||
// 检查产品是否存在
|
||
const category = await this.categoryModel.findOneBy({ id });
|
||
if (!category) {
|
||
throw new Error(`产品分类 ID ${id} 不存在`);
|
||
}
|
||
// 删除产品
|
||
const result = await this.categoryModel.delete(id);
|
||
return result.affected > 0; // `affected` 表示删除的行数
|
||
}
|
||
|
||
async hasProductsInFlavors(flavorsId: number): Promise<boolean> {
|
||
const count = await this.productModel.count({
|
||
where: { flavorsId },
|
||
});
|
||
return count > 0;
|
||
}
|
||
|
||
async hasFlavors(name: string, id?: string): Promise<boolean> {
|
||
const where: any = { name };
|
||
if (id) where.id = Not(id);
|
||
const count = await this.flavorsModel.count({
|
||
where,
|
||
});
|
||
return count > 0;
|
||
}
|
||
async getFlavorsList(
|
||
pagination: PaginationParams,
|
||
name?: string
|
||
): Promise<FlavorsPaginatedResponse> {
|
||
const where: any = {};
|
||
if (name) {
|
||
where.name = Like(`%${name}%`);
|
||
}
|
||
return await paginate(this.flavorsModel, { pagination, where });
|
||
}
|
||
|
||
async getFlavorsAll(): Promise<FlavorsPaginatedResponse> {
|
||
return await this.flavorsModel.find();
|
||
}
|
||
|
||
async createFlavors(createFlavorsDTO: CreateFlavorsDTO): Promise<Flavors> {
|
||
const { name, unique_key } = createFlavorsDTO;
|
||
const flavors = new Flavors();
|
||
flavors.name = name;
|
||
flavors.unique_key = unique_key;
|
||
return await this.flavorsModel.save(flavors);
|
||
}
|
||
|
||
async updateFlavors(id: number, updateFlavors: UpdateFlavorsDTO) {
|
||
// 确认产品是否存在
|
||
const flavors = await this.flavorsModel.findOneBy({ id });
|
||
if (!flavors) {
|
||
throw new Error(`口味 ID ${id} 不存在`);
|
||
}
|
||
// 更新产品
|
||
await this.flavorsModel.update(id, updateFlavors);
|
||
// 返回更新后的产品
|
||
return await this.flavorsModel.findOneBy({ id });
|
||
}
|
||
|
||
async deleteFlavors(id: number): Promise<boolean> {
|
||
// 检查产品是否存在
|
||
const flavors = await this.flavorsModel.findOneBy({ id });
|
||
if (!flavors) {
|
||
throw new Error(`口味 ID ${id} 不存在`);
|
||
}
|
||
// 删除产品
|
||
const result = await this.flavorsModel.delete(id);
|
||
return result.affected > 0; // `affected` 表示删除的行数
|
||
}
|
||
async hasProductsInStrength(strengthId: number): Promise<boolean> {
|
||
const count = await this.productModel.count({
|
||
where: { strengthId },
|
||
});
|
||
return count > 0;
|
||
}
|
||
|
||
async hasStrength(name: string, id?: string): Promise<boolean> {
|
||
const where: any = { name };
|
||
if (id) where.id = Not(id);
|
||
const count = await this.strengthModel.count({
|
||
where,
|
||
});
|
||
return count > 0;
|
||
}
|
||
async getStrengthList(
|
||
pagination: PaginationParams,
|
||
name?: string
|
||
): Promise<StrengthPaginatedResponse> {
|
||
const where: any = {};
|
||
if (name) {
|
||
where.name = Like(`%${name}%`);
|
||
}
|
||
return await paginate(this.strengthModel, { pagination, where });
|
||
}
|
||
|
||
async getStrengthAll(): Promise<StrengthPaginatedResponse> {
|
||
return await this.strengthModel.find();
|
||
}
|
||
|
||
async createStrength(
|
||
createStrengthDTO: CreateStrengthDTO
|
||
): Promise<Strength> {
|
||
const { name, unique_key } = createStrengthDTO;
|
||
const strength = new Strength();
|
||
strength.name = name;
|
||
strength.unique_key = unique_key;
|
||
return await this.strengthModel.save(strength);
|
||
}
|
||
|
||
async updateStrength(id: number, updateStrength: UpdateStrengthDTO) {
|
||
// 确认产品是否存在
|
||
const strength = await this.strengthModel.findOneBy({ id });
|
||
if (!strength) {
|
||
throw new Error(`口味 ID ${id} 不存在`);
|
||
}
|
||
// 更新产品
|
||
await this.strengthModel.update(id, updateStrength);
|
||
// 返回更新后的产品
|
||
return await this.strengthModel.findOneBy({ id });
|
||
}
|
||
|
||
async deleteStrength(id: number): Promise<boolean> {
|
||
// 检查产品是否存在
|
||
const strength = await this.strengthModel.findOneBy({ id });
|
||
if (!strength) {
|
||
throw new Error(`口味 ID ${id} 不存在`);
|
||
}
|
||
// 删除产品
|
||
const result = await this.flavorsModel.delete(id);
|
||
return result.affected > 0; // `affected` 表示删除的行数
|
||
}
|
||
|
||
async batchSetSku(skus: { productId: number; sku: string }[]) {
|
||
// 提取所有 sku
|
||
const skuList = skus.map(item => item.sku);
|
||
|
||
// 检查是否存在重复 sku
|
||
const existingProducts = await this.productModel.find({
|
||
where: { sku: In(skuList) },
|
||
});
|
||
|
||
if (existingProducts.length > 0) {
|
||
const existingSkus = existingProducts.map(product => product.sku);
|
||
throw new Error(`以下 SKU 已存在: ${existingSkus.join(', ')}`);
|
||
}
|
||
|
||
// 遍历检查产品 ID 是否存在,并更新 sku
|
||
for (const { productId, sku } of skus) {
|
||
const product = await this.productModel.findOne({
|
||
where: { id: productId },
|
||
});
|
||
if (!product) {
|
||
throw new Error(`产品 ID '${productId}' 不存在`);
|
||
}
|
||
|
||
product.sku = sku;
|
||
await this.productModel.save(product);
|
||
}
|
||
|
||
return `成功更新 ${skus.length} 个 sku`;
|
||
}
|
||
}
|