forked from yoone/API
1
0
Fork 0
API/src/service/wp_product.service.ts

549 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Product } from './../entity/product.entty';
import { Config, Inject, Provide } from '@midwayjs/core';
import { WPService } from './wp.service';
import { WpSite } from '../interface';
import { WpProduct } from '../entity/wp_product.entity';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { And, Like, Not, Repository } from 'typeorm';
import { Variation } from '../entity/variation.entity';
import {
QueryWpProductDTO,
UpdateVariationDTO,
UpdateWpProductDTO,
} from '../dto/wp_product.dto';
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
@Provide()
export class WpProductService {
@Config('wpSite')
sites: WpSite[];
@Inject()
private readonly wpApiService: WPService;
@InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>;
@InjectEntityModel(Variation)
variationModel: Repository<Variation>;
getSite(id: string): WpSite {
let idx = this.sites.findIndex(item => item.id === id);
return this.sites[idx];
}
async syncAllSites() {
for (const site of this.sites) {
const products = await this.wpApiService.getProducts(site);
for (const product of products) {
const variations =
product.type === 'variable'
? await this.wpApiService.getVariations(site, product.id)
: [];
await this.syncProductAndVariations(site.id, product, variations);
}
}
}
async syncSite(siteId: string) {
const site = this.getSite(siteId);
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
.select([
'wp_product.id ',
'wp_product.externalProductId ',
])
.where('wp_product.siteId = :siteIds ', {
siteIds: siteId,
})
const rawResult = await externalProductIds.getRawMany();
const externalIds = rawResult.map(item => item.externalProductId);
const excludeValues = [];
const products = await this.wpApiService.getProducts(site);
for (const product of products) {
excludeValues.push(String(product.id));
const variations =
product.type === 'variable'
? await this.wpApiService.getVariations(site, product.id)
: [];
await this.syncProductAndVariations(site.id, product, variations);
}
const filteredIds = externalIds.filter(id => !excludeValues.includes(id));
if(filteredIds.length!=0){
await this.variationModel.createQueryBuilder('variation')
.update()
.set({ on_delete: true })
.where(" variation.externalProductId in (:...filteredId) ",{filteredId:filteredIds})
.execute();
this.wpProductModel.createQueryBuilder('wp_product')
.update()
.set({ on_delete: true })
.where(" wp_product.externalProductId in (:...filteredId) ",{filteredId:filteredIds})
.execute();
}
}
// 控制产品上下架
async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) {
const wpProduct = await this.wpProductModel.findOneBy({ id });
const site = await this.getSite(wpProduct.siteId);
wpProduct.status = status;
wpProduct.stockStatus = stock_status;
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
if (res === true) {
this.wpProductModel.save(wpProduct);
return true;
} else {
return res;
}
}
async findProduct(
siteId: string,
externalProductId: string
): Promise<WpProduct | null> {
return await this.wpProductModel.findOne({
where: { siteId, externalProductId },
});
}
async findVariation(
siteId: string,
externalProductId: string,
externalVariationId: string
): Promise<Variation | null> {
return await this.variationModel.findOne({
where: { siteId, externalProductId, externalVariationId },
});
}
async updateWpProduct(
siteId: string,
productId: string,
product: UpdateWpProductDTO
) {
let existingProduct = await this.findProduct(siteId, productId);
if (existingProduct) {
existingProduct.name = product.name;
existingProduct.sku = product.sku;
product.regular_price &&
(existingProduct.regular_price = product.regular_price);
product.sale_price && (existingProduct.sale_price = product.sale_price);
await this.wpProductModel.save(existingProduct);
}
}
async updateWpProductVaritation(
siteId: string,
productId: string,
variationId: string,
variation: UpdateVariationDTO
) {
const existingVariation = await this.findVariation(
siteId,
productId,
variationId
);
if (existingVariation) {
existingVariation.name = variation.name;
existingVariation.sku = variation.sku;
variation.regular_price &&
(existingVariation.regular_price = variation.regular_price);
variation.sale_price &&
(existingVariation.sale_price = variation.sale_price);
await this.variationModel.save(existingVariation);
}
}
async syncProductAndVariations(
siteId: string,
product: WpProduct,
variations: Variation[]
) {
// 1. 处理产品同步
let existingProduct = await this.findProduct(siteId, String(product.id));
if (existingProduct) {
existingProduct.name = product.name;
existingProduct.status = product.status;
existingProduct.type = product.type;
existingProduct.sku = product.sku;
product.regular_price &&
(existingProduct.regular_price = product.regular_price);
product.sale_price && (existingProduct.sale_price = product.sale_price);
existingProduct.on_sale = product.on_sale;
existingProduct.metadata = product.metadata;
await this.wpProductModel.save(existingProduct);
} else {
existingProduct = this.wpProductModel.create({
siteId,
externalProductId: String(product.id),
sku: product.sku,
status: product.status,
name: product.name,
type: product.type,
...(product.regular_price
? { regular_price: product.regular_price }
: {}),
...(product.sale_price ? { sale_price: product.sale_price } : {}),
on_sale: product.on_sale,
metadata: product.metadata,
});
await this.wpProductModel.save(existingProduct);
}
// 2. 处理变体同步
if (product.type === 'variable') {
const currentVariations = await this.variationModel.find({
where: { siteId, externalProductId: String(product.id) },
});
const syncedVariationIds = new Set(variations.map(v => String(v.id)));
const variationsToDelete = currentVariations.filter(
dbVariation =>
!syncedVariationIds.has(String(dbVariation.externalVariationId))
);
if (variationsToDelete.length > 0) {
const idsToDelete = variationsToDelete.map(v => v.id);
await this.variationModel.delete(idsToDelete);
}
for (const variation of variations) {
const existingVariation = await this.findVariation(
siteId,
String(product.id),
String(variation.id)
);
if (existingVariation) {
existingVariation.name = variation.name;
existingVariation.attributes = variation.attributes;
variation.regular_price &&
(existingVariation.regular_price = variation.regular_price);
variation.sale_price &&
(existingVariation.sale_price = variation.sale_price);
existingVariation.on_sale = variation.on_sale;
await this.variationModel.save(existingVariation);
} else {
const newVariation = this.variationModel.create({
siteId,
externalProductId: String(product.id),
externalVariationId: String(variation.id),
productId: existingProduct.id,
sku: variation.sku,
name: variation.name,
...(variation.regular_price
? { regular_price: variation.regular_price }
: {}),
...(variation.sale_price
? { sale_price: variation.sale_price }
: {}),
on_sale: variation.on_sale,
attributes: variation.attributes,
});
await this.variationModel.save(newVariation);
}
}
} else {
// 清理之前的变体
await this.variationModel.delete({
siteId,
externalProductId: String(product.id),
});
}
}
async syncVariation(siteId: string, productId: string, variation: Variation) {
let existingProduct = await this.findProduct(siteId, String(productId));
if (!existingProduct) return;
const existingVariation = await this.findVariation(
siteId,
String(productId),
String(variation.id)
);
if (existingVariation) {
existingVariation.name = variation.name;
existingVariation.attributes = variation.attributes;
variation.regular_price &&
(existingVariation.regular_price = variation.regular_price);
variation.sale_price &&
(existingVariation.sale_price = variation.sale_price);
existingVariation.on_sale = variation.on_sale;
await this.variationModel.save(existingVariation);
} else {
const newVariation = this.variationModel.create({
siteId,
externalProductId: String(productId),
externalVariationId: String(variation.id),
productId: existingProduct.id,
sku: variation.sku,
name: variation.name,
...(variation.regular_price
? { regular_price: variation.regular_price }
: {}),
...(variation.sale_price ? { sale_price: variation.sale_price } : {}),
on_sale: variation.on_sale,
attributes: variation.attributes,
});
await this.variationModel.save(newVariation);
}
}
async getProductList(param: QueryWpProductDTO) {
const { current = 1, pageSize = 10, name, siteId, status } = param;
// 第一步:先查询分页的产品
const where: any = {};
if (siteId) {
where.siteId = siteId;
}
const nameFilter = name ? name.split(' ').filter(Boolean) : [];
if (nameFilter.length > 0) {
const nameConditions = nameFilter.map(word => Like(`%${word}%`));
where.name = And(...nameConditions);
}
if (status) {
where.status = status;
}
where.on_delete = false;
const products = await this.wpProductModel.find({
where,
skip: (current - 1) * pageSize,
take: pageSize,
});
const total = await this.wpProductModel.count({
where,
});
if (products.length === 0) {
return {
items: [],
total,
current,
pageSize,
};
}
const variationQuery = this.wpProductModel
.createQueryBuilder('wp_product')
.leftJoin(Variation, 'variation', 'variation.productId = wp_product.id')
.leftJoin(
Product,
'product',
'JSON_UNQUOTE(JSON_EXTRACT(wp_product.constitution, "$.sku")) = product.sku'
)
.leftJoin(
Product,
'variation_product',
'JSON_UNQUOTE(JSON_EXTRACT(variation.constitution, "$.sku")) = variation_product.sku'
)
.select([
'wp_product.*',
'variation.id as variation_id',
'variation.siteId as variation_siteId',
'variation.externalProductId as variation_externalProductId',
'variation.externalVariationId as variation_externalVariationId',
'variation.productId as variation_productId',
'variation.sku as variation_sku',
'variation.name as variation_name',
'variation.regular_price as variation_regular_price',
'variation.sale_price as variation_sale_price',
'variation.on_sale as variation_on_sale',
'variation.constitution as variation_constitution',
'product.name as product_name', // 关联查询返回 product.name
'variation_product.name as variation_product_name', // 关联查询返回 variation 的产品 name
])
.where('wp_product.id IN (:...ids) AND wp_product.on_delete = false ', {
ids: products.map(product => product.id),
});
const rawResult = await variationQuery.getRawMany();
// 数据转换
const items = rawResult.reduce((acc, row) => {
let product = acc.find(p => p.id === row.id);
if (!product) {
product = {
...Object.keys(row)
.filter(key => !key.startsWith('variation_'))
.reduce((obj, key) => {
obj[key] = row[key];
return obj;
}, {}),
variations: [],
};
acc.push(product);
}
if (row.variation_id) {
const variation: any = Object.keys(row)
.filter(key => key.startsWith('variation_'))
.reduce((obj, key) => {
obj[key.replace('variation_', '')] = row[key];
return obj;
}, {});
variation.constitution =
variation?.constitution?.map(item => {
const product = item.sku
? { ...item, name: row.variation_product_name }
: item;
return product;
}) || [];
product.variations.push(variation);
}
product.constitution =
product?.constitution?.map(item => {
const productWithName = item.sku
? { ...item, name: row.product_name }
: item;
return productWithName;
}) || [];
return acc;
}, []);
return {
items,
total,
current,
pageSize,
};
}
/**
* 检查 SKU 是否重复
* @param sku SKU 编码
* @param excludeSiteId 需要排除的站点 ID
* @param excludeProductId 需要排除的产品 ID
* @param excludeVariationId 需要排除的变体 ID
* @returns 是否重复
*/
async isSkuDuplicate(
sku: string,
excludeSiteId?: string,
excludeProductId?: string,
excludeVariationId?: string
): Promise<boolean> {
if (!sku) return false;
const where: any = { sku };
const varWhere: any = { sku };
if (excludeVariationId) {
varWhere.siteId = Not(excludeSiteId);
varWhere.externalProductId = Not(excludeProductId);
varWhere.externalVariationId = Not(excludeVariationId);
} else {
where.externalProductId = Not(excludeProductId);
where.externalProductId = Not(excludeProductId);
}
const productDuplicate = await this.wpProductModel.findOne({
where,
});
if (productDuplicate) {
return true;
}
const variationDuplicate = await this.variationModel.findOne({
where: varWhere,
});
return !!variationDuplicate;
}
/**
* 设置产品或变体的构成成分
*/
async setConstitution(
id: number,
isProduct: boolean,
constitution: { sku: string; quantity: number }[]
): Promise<void> {
if (isProduct) {
// 更新产品的 constitution
const product = await this.wpProductModel.findOne({ where: { id } });
if (!product) {
throw new Error(`未找到 ID 为 ${id} 的产品`);
}
product.constitution = constitution;
await this.wpProductModel.save(product);
} else {
// 更新变体的 constitution
const variation = await this.variationModel.findOne({ where: { id } });
if (!variation) {
throw new Error(`未找到 ID 为 ${id} 的变体`);
}
variation.constitution = constitution;
await this.variationModel.save(variation);
}
}
async delWpProduct(siteId: string, productId: string) {
const product = await this.wpProductModel.findOne({
where: { siteId, externalProductId: productId },
});
if (!product) throw new Error('未找到该商品');
await this.variationModel.createQueryBuilder('variation')
.update()
.set({ on_delete: true })
.where(" variation.externalProductId = :externalProductId ",{externalProductId:productId})
.execute();
const sums= await this.wpProductModel.createQueryBuilder('wp_product')
.update()
.set({ on_delete: true })
.where(" wp_product.externalProductId = :externalProductId ",{externalProductId:productId})
.execute();
console.log(sums);
//await this.variationModel.delete({ siteId, externalProductId: productId });
//await this.wpProductModel.delete({ siteId, externalProductId: productId });
}
async findProductsByName(name: string): Promise<WpProduct[]> {
const nameFilter = name ? name.split(' ').filter(Boolean) : [];
const query = this.wpProductModel.createQueryBuilder('product');
// 保证 sku 不为空
query.where('product.sku IS NOT NULL AND product.on_delete = false');
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();
}
}