feat: 支持动态增加 site 配置 #30
|
|
@ -33,6 +33,7 @@ import { Customer } from '../entity/customer.entity';
|
|||
import { DeviceWhitelist } from '../entity/device_whitelist';
|
||||
import { AuthCode } from '../entity/auth_code';
|
||||
import { Subscription } from '../entity/subscription.entity';
|
||||
import { Site } from '../entity/site.entity';
|
||||
|
||||
export default {
|
||||
// use for cookie sign key, should change to your own and keep security
|
||||
|
|
@ -74,6 +75,7 @@ export default {
|
|||
DeviceWhitelist,
|
||||
AuthCode,
|
||||
Subscription,
|
||||
Site,
|
||||
],
|
||||
synchronize: true,
|
||||
logging: false,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import * as crossDomain from '@midwayjs/cross-domain';
|
|||
import * as cron from '@midwayjs/cron';
|
||||
import * as jwt from '@midwayjs/jwt';
|
||||
import { USER_KEY } from './decorator/user.decorator';
|
||||
import { SiteService } from './service/site.service';
|
||||
import { AuthMiddleware } from './middleware/auth.middleware';
|
||||
|
||||
@Configuration({
|
||||
|
|
@ -45,6 +46,9 @@ export class MainConfiguration {
|
|||
@Inject()
|
||||
jwtService: jwt.JwtService; // 注入 JwtService 实例
|
||||
|
||||
@Inject()
|
||||
siteService: SiteService;
|
||||
|
||||
async onReady() {
|
||||
// add middleware
|
||||
this.app.useMiddleware([ReportMiddleware, AuthMiddleware]);
|
||||
|
|
@ -74,5 +78,8 @@ export class MainConfiguration {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
const sites = this.app.getConfig('wpSite') || [];
|
||||
await this.siteService.syncFromConfig(sites);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,75 @@
|
|||
import { Config, Controller, Get } from '@midwayjs/core';
|
||||
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
|
||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||
import { WpSitesResponse } from '../dto/reponse.dto';
|
||||
import { successResponse } from '../utils/response.util';
|
||||
import { WpSite } from '../interface';
|
||||
import { errorResponse, successResponse } from '../utils/response.util';
|
||||
import { SiteService } from '../service/site.service';
|
||||
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
|
||||
|
||||
@Controller('/site')
|
||||
export class SiteController {
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
@Inject()
|
||||
siteService: SiteService;
|
||||
|
||||
@ApiOkResponse({
|
||||
description: '关联网站',
|
||||
type: WpSitesResponse,
|
||||
})
|
||||
@ApiOkResponse({ description: '关联网站', type: WpSitesResponse })
|
||||
@Get('/all')
|
||||
async all() {
|
||||
return successResponse(
|
||||
this.sites.map(v => ({
|
||||
id: v.id,
|
||||
siteName: v.siteName,
|
||||
}))
|
||||
);
|
||||
try {
|
||||
const { items } = await this.siteService.list({ current: 1, pageSize: 1000, isDisabled: false });
|
||||
return successResponse(items.map((v: any) => ({ id: v.id, siteName: v.siteName })));
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/create')
|
||||
async create(@Body() body: CreateSiteDTO) {
|
||||
try {
|
||||
await this.siteService.create(body);
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '创建失败');
|
||||
}
|
||||
}
|
||||
|
||||
@Put('/update/:id')
|
||||
async update(@Param('id') id: string, @Body() body: UpdateSiteDTO) {
|
||||
try {
|
||||
await this.siteService.update(Number(id), body);
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '更新失败');
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/get/:id')
|
||||
async get(@Param('id') id: string) {
|
||||
try {
|
||||
const data = await this.siteService.get(Number(id), false);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/list')
|
||||
async list(@Query() query: QuerySiteDTO) {
|
||||
try {
|
||||
const data = await this.siteService.list(query, false);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询改为使用 /site/list?ids=1,2,3
|
||||
|
||||
@Put('/disable/:id')
|
||||
async disable(@Param('id') id: string, @Body() body: DisableSiteDTO) {
|
||||
try {
|
||||
await this.siteService.disable(Number(id), body.disabled);
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || '更新失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Config, HttpStatus, Inject } from '@midwayjs/core';
|
||||
import { HttpStatus, Inject } from '@midwayjs/core';
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
|
|
@ -11,8 +11,8 @@ import { Context } from '@midwayjs/koa';
|
|||
import * as crypto from 'crypto';
|
||||
import { WpProductService } from '../service/wp_product.service';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { SiteService } from '../service/site.service';
|
||||
import { OrderService } from '../service/order.service';
|
||||
import { WpSite } from '../interface';
|
||||
|
||||
@Controller('/webhook')
|
||||
export class WebhookController {
|
||||
|
|
@ -30,8 +30,10 @@ export class WebhookController {
|
|||
@Inject()
|
||||
ctx: Context;
|
||||
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
|
||||
// 移除配置中的站点数组,来源统一改为数据库
|
||||
|
||||
@Get('/')
|
||||
async test() {
|
||||
|
|
@ -47,9 +49,10 @@ export class WebhookController {
|
|||
const signature = header['x-wc-webhook-signature'];
|
||||
const topic = header['x-wc-webhook-topic'];
|
||||
const source = header['x-wc-webhook-source'];
|
||||
let site = this.sites.find(item => item.id === siteId);
|
||||
// 从数据库获取站点配置
|
||||
const site = await this.siteService.get(Number(siteId), true);
|
||||
|
||||
if (!site || !source.includes(site.wpApiUrl)) {
|
||||
if (!site || !source.includes(site.apiUrl)) {
|
||||
console.log('domain not match');
|
||||
return {
|
||||
code: HttpStatus.BAD_REQUEST,
|
||||
|
|
@ -94,13 +97,13 @@ export class WebhookController {
|
|||
? await this.wpApiService.getVariations(site, body.id)
|
||||
: [];
|
||||
await this.wpProductService.syncProductAndVariations(
|
||||
site.id,
|
||||
String(site.id),
|
||||
body,
|
||||
variations
|
||||
);
|
||||
break;
|
||||
case 'product.deleted':
|
||||
await this.wpProductService.delWpProduct(site.id, body.id);
|
||||
await this.wpProductService.delWpProduct(String(site.id), body.id);
|
||||
break;
|
||||
case 'order.created':
|
||||
case 'order.updated':
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
Query,
|
||||
Put,
|
||||
Body,
|
||||
Config,
|
||||
} from '@midwayjs/core';
|
||||
import { WpProductService } from '../service/wp_product.service';
|
||||
import { errorResponse, successResponse } from '../utils/response.util';
|
||||
|
|
@ -20,22 +19,13 @@ import {
|
|||
UpdateWpProductDTO,
|
||||
} from '../dto/wp_product.dto';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { WpSite } from '../interface';
|
||||
import { SiteService } from '../service/site.service';
|
||||
import {
|
||||
ProductsRes,
|
||||
} from '../dto/reponse.dto';
|
||||
@Controller('/wp_product')
|
||||
export class WpProductController {
|
||||
@Inject()
|
||||
wpService: WPService;
|
||||
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
|
||||
getSite(id: string): WpSite {
|
||||
let idx = this.sites.findIndex(item => item.id === id);
|
||||
return this.sites[idx];
|
||||
}
|
||||
// 移除控制器内的配置站点引用,统一由服务层处理站点数据
|
||||
|
||||
@Inject()
|
||||
private readonly wpProductService: WpProductService;
|
||||
|
|
@ -43,6 +33,9 @@ export class WpProductController {
|
|||
@Inject()
|
||||
private readonly wpApiService: WPService;
|
||||
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
|
||||
@ApiOkResponse({
|
||||
type: BooleanRes,
|
||||
})
|
||||
|
|
@ -127,7 +120,7 @@ export class WpProductController {
|
|||
if (isDuplicate) {
|
||||
return errorResponse('SKU已存在');
|
||||
}
|
||||
const site = await this.wpProductService.getSite(siteId);
|
||||
const site = await this.siteService.get(Number(siteId), true);
|
||||
const result = await this.wpApiService.updateProduct(
|
||||
site,
|
||||
productId,
|
||||
|
|
@ -167,7 +160,7 @@ export class WpProductController {
|
|||
if (isDuplicate) {
|
||||
return errorResponse('SKU已存在');
|
||||
}
|
||||
const site = await this.wpProductService.getSite(siteId);
|
||||
const site = await this.siteService.get(Number(siteId), true);
|
||||
const result = await this.wpApiService.updateVariation(
|
||||
site,
|
||||
productId,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export class SiteConfig {
|
|||
|
||||
@ApiProperty({ description: '站点 URL' })
|
||||
@Rule(RuleType.string())
|
||||
wpApiUrl: string;
|
||||
apiUrl: string;
|
||||
|
||||
@ApiProperty({ description: '站点 rest key' })
|
||||
@Rule(RuleType.string())
|
||||
|
|
@ -22,11 +22,61 @@ export class SiteConfig {
|
|||
@Rule(RuleType.string())
|
||||
siteName: string;
|
||||
|
||||
@ApiProperty({ description: '站点邮箱' })
|
||||
@Rule(RuleType.string())
|
||||
email?: string;
|
||||
@ApiProperty({ description: '平台类型', enum: ['woocommerce', 'shopyy'] })
|
||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy'))
|
||||
type: string;
|
||||
|
||||
@ApiProperty({ description: '站点邮箱密码' })
|
||||
@ApiProperty({ description: 'SKU 前缀' })
|
||||
@Rule(RuleType.string())
|
||||
emailPswd?: string;
|
||||
skuPrefix: string;
|
||||
}
|
||||
|
||||
export class CreateSiteDTO {
|
||||
@Rule(RuleType.string().optional())
|
||||
apiUrl?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
consumerKey?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
consumerSecret?: string;
|
||||
@Rule(RuleType.string())
|
||||
siteName: string;
|
||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||
type?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
skuPrefix?: string;
|
||||
}
|
||||
|
||||
export class UpdateSiteDTO {
|
||||
@Rule(RuleType.string().optional())
|
||||
apiUrl?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
consumerKey?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
consumerSecret?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
siteName?: string;
|
||||
@Rule(RuleType.boolean().optional())
|
||||
isDisabled?: boolean;
|
||||
@Rule(RuleType.string().valid('woocommerce', 'shopyy').optional())
|
||||
type?: string;
|
||||
@Rule(RuleType.string().optional())
|
||||
skuPrefix?: string;
|
||||
}
|
||||
|
||||
export class QuerySiteDTO {
|
||||
@Rule(RuleType.number().optional())
|
||||
current?: number;
|
||||
@Rule(RuleType.number().optional())
|
||||
pageSize?: number;
|
||||
@Rule(RuleType.string().optional())
|
||||
keyword?: string;
|
||||
@Rule(RuleType.boolean().optional())
|
||||
isDisabled?: boolean;
|
||||
@Rule(RuleType.string().optional())
|
||||
ids?: string;
|
||||
}
|
||||
|
||||
export class DisableSiteDTO {
|
||||
@Rule(RuleType.boolean())
|
||||
disabled: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('site')
|
||||
export class Site {
|
||||
@PrimaryGeneratedColumn({ type: 'int' })
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
apiUrl: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
consumerKey: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
consumerSecret: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
siteName: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, default: 'woocommerce' })
|
||||
type: string; // 平台类型:woocommerce | shopyy
|
||||
|
||||
@Column({ type: 'varchar', length: 64, nullable: true })
|
||||
skuPrefix: string;
|
||||
|
||||
@Column({ type: 'tinyint', default: 0 })
|
||||
isDisabled: number;
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ export enum ShipmentType {
|
|||
}
|
||||
|
||||
export enum staticValue {
|
||||
// 万能验证码
|
||||
STATIC_CAPTCHA = 'yoone2025!@YOONE0923'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Config, Inject, Provide, sleep } from '@midwayjs/core';
|
||||
import { Inject, Provide, sleep } from '@midwayjs/core';
|
||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
import { Service } from '../entity/service.entity';
|
||||
import { In, IsNull, Like, Repository } from 'typeorm';
|
||||
|
|
@ -21,7 +21,6 @@ import { StockRecord } from '../entity/stock_record.entity';
|
|||
import { Stock } from '../entity/stock.entity';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { WPService } from './wp.service';
|
||||
import { WpSite } from '../interface';
|
||||
// import { Product } from '../entity/product.entty';
|
||||
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
||||
import { CanadaPostService } from './canadaPost.service';
|
||||
|
|
@ -31,6 +30,7 @@ import { UniExpressService } from './uni_express.service';
|
|||
import { StockPoint } from '../entity/stock_point.entity';
|
||||
import { OrderService } from './order.service';
|
||||
import { convertKeysFromCamelToSnake } from '../utils/object-transform.util';
|
||||
import { SiteService } from './site.service';
|
||||
|
||||
@Provide()
|
||||
export class LogisticsService {
|
||||
|
|
@ -82,13 +82,8 @@ export class LogisticsService {
|
|||
@Inject()
|
||||
dataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
|
||||
getSite(id: string): WpSite {
|
||||
let idx = this.sites.findIndex(item => item.id === id);
|
||||
return this.sites[idx];
|
||||
}
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
|
||||
async getServiceList(param: QueryServiceDTO) {
|
||||
const { pageSize, current, carrier_name, isActive } = param;
|
||||
|
|
@ -263,7 +258,7 @@ export class LogisticsService {
|
|||
|
||||
try {
|
||||
// 同步订单状态到woocommerce
|
||||
const site = await this.getSite(order.siteId);
|
||||
const site = await this.siteService.get(Number(order.siteId), true);
|
||||
if (order.status === OrderStatus.COMPLETED) {
|
||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||
status: OrderStatus.PROCESSING,
|
||||
|
|
@ -367,7 +362,7 @@ export class LogisticsService {
|
|||
const tracking_provider = 'UniUni'; // todo: id未确定,后写进常数
|
||||
|
||||
// 同步物流信息到woocommerce
|
||||
const site = await this.getSite(order.siteId);
|
||||
const site = await this.siteService.get(Number(order.siteId), true);
|
||||
const res = await this.wpService.createShipment(site, order.externalOrderId, {
|
||||
tracking_number: resShipmentOrder.data.tno,
|
||||
tracking_provider: tracking_provider,
|
||||
|
|
@ -493,7 +488,7 @@ export class LogisticsService {
|
|||
const order = await this.orderModel.findOneBy({
|
||||
id: orderShipment.order_id,
|
||||
});
|
||||
const site = this.getSite(order.siteId);
|
||||
const site = await this.siteService.get(Number(order.siteId), true);
|
||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||
status: OrderStatus.COMPLETED,
|
||||
});
|
||||
|
|
@ -563,7 +558,10 @@ export class LogisticsService {
|
|||
},
|
||||
});
|
||||
|
||||
const siteMap = new Map(this.sites.map(site => [site.id, site.siteName]));
|
||||
// 从数据库批量获取站点信息,构建映射以避免 N+1 查询
|
||||
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
|
||||
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
|
||||
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.siteName]));
|
||||
|
||||
return orders.map(order => ({
|
||||
...order,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Config, Inject, Provide } from '@midwayjs/core';
|
||||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import { WPService } from './wp.service';
|
||||
import { WpSite } from '../interface';
|
||||
import { Order } from '../entity/order.entity';
|
||||
import { In, Like, Repository } from 'typeorm';
|
||||
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||
|
|
@ -27,7 +28,7 @@ import dayjs = require('dayjs');
|
|||
import { OrderDetailRes } from '../dto/reponse.dto';
|
||||
import { OrderNote } from '../entity/order_note.entity';
|
||||
import { User } from '../entity/user.entity';
|
||||
import { WpSite } from '../interface';
|
||||
import { SiteService } from './site.service';
|
||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||
import { UpdateStockDTO } from '../dto/stock.dto';
|
||||
import { StockService } from './stock.service';
|
||||
|
|
@ -35,8 +36,6 @@ import { OrderSaleOriginal } from '../entity/order_item_original.entity';
|
|||
|
||||
@Provide()
|
||||
export class OrderService {
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
|
||||
@Inject()
|
||||
wpService: WPService;
|
||||
|
|
@ -101,6 +100,9 @@ export class OrderService {
|
|||
@InjectEntityModel(Customer)
|
||||
customerModel: Repository<Customer>;
|
||||
|
||||
@Inject()
|
||||
siteService: SiteService;
|
||||
|
||||
async syncOrders(siteId: string) {
|
||||
const orders = await this.wpService.getOrders(siteId); // 调用 WooCommerce API 获取订单
|
||||
for (const order of orders) {
|
||||
|
|
@ -127,12 +129,9 @@ export class OrderService {
|
|||
return
|
||||
}
|
||||
try {
|
||||
const site = this.sites.find(v => v.id === siteId);
|
||||
if (!site) {
|
||||
throw new Error(`更新订单信息,但失败,原因为 ${siteId} 的站点信息不存在`)
|
||||
}
|
||||
// 同步更新回 wordpress 的 order 状态
|
||||
await this.wpService.updateOrder(site, order.id, { status: order.status });
|
||||
const site = await this.siteService.get(siteId);
|
||||
// 将订单状态同步到 WooCommerce,然后切换至下一状态
|
||||
await this.wpService.updateOrder(site, String(order.id), { status: order.status });
|
||||
order.status = this.orderAutoNextStatusMap[originStatus];
|
||||
} catch (error) {
|
||||
console.error('更新订单状态失败,原因为:', error)
|
||||
|
|
@ -1262,7 +1261,7 @@ export class OrderService {
|
|||
}
|
||||
async getOrderDetail(id: number): Promise<OrderDetailRes> {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
const site = this.sites.find(site => site.id === order.siteId);
|
||||
const site = await this.siteService.get(Number(order.siteId), true);
|
||||
const items = await this.orderItemModel.find({ where: { orderId: id } });
|
||||
const sales = await this.orderSaleModel.find({ where: { orderId: id } });
|
||||
const refunds = await this.orderRefundModel.find({
|
||||
|
|
@ -1352,7 +1351,8 @@ export class OrderService {
|
|||
return {
|
||||
...order,
|
||||
siteName: site?.siteName,
|
||||
email: site?.email,
|
||||
// Site 实体无邮箱字段,这里返回空字符串保持兼容
|
||||
email: '',
|
||||
items,
|
||||
sales,
|
||||
refundItems,
|
||||
|
|
@ -1415,20 +1415,22 @@ export class OrderService {
|
|||
]),
|
||||
},
|
||||
});
|
||||
return orders.map(order => {
|
||||
return {
|
||||
externalOrderId: order.externalOrderId,
|
||||
id: order.id,
|
||||
siteName:
|
||||
this.sites.find(site => site.id === order.siteId)?.siteName || '',
|
||||
};
|
||||
});
|
||||
// 批量获取订单涉及的站点名称,避免使用配置文件
|
||||
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
|
||||
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
|
||||
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.siteName]));
|
||||
return orders.map(order => ({
|
||||
externalOrderId: order.externalOrderId,
|
||||
id: order.id,
|
||||
siteName: siteMap.get(order.siteId) || '',
|
||||
}));
|
||||
}
|
||||
|
||||
async cancelOrder(id: number) {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||
const site = this.wpService.geSite(order.siteId);
|
||||
const s: any = await this.siteService.get(Number(order.siteId), true);
|
||||
const site = { id: String(s.id), wpApiUrl: s.apiUrl, consumerKey: s.consumerKey, consumerSecret: s.consumerSecret, siteName: s.siteName, email: '', emailPswd: '' } as WpSite;
|
||||
if (order.status !== OrderStatus.CANCEL) {
|
||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||
status: OrderStatus.CANCEL,
|
||||
|
|
@ -1442,7 +1444,7 @@ export class OrderService {
|
|||
async refundOrder(id: number) {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||
const site = this.wpService.geSite(order.siteId);
|
||||
const site = await this.siteService.get(Number(order.siteId), true);
|
||||
if (order.status !== OrderStatus.REFUNDED) {
|
||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||
status: OrderStatus.REFUNDED,
|
||||
|
|
@ -1456,7 +1458,7 @@ export class OrderService {
|
|||
async completedOrder(id: number) {
|
||||
const order = await this.orderModel.findOne({ where: { id } });
|
||||
if (!order) throw new Error(`订单 ${id}不存在`);
|
||||
const site = this.wpService.geSite(order.siteId);
|
||||
const site = await this.siteService.get(order.siteId);
|
||||
if (order.status !== OrderStatus.COMPLETED) {
|
||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||
status: OrderStatus.COMPLETED,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository, Like, In } from 'typeorm';
|
||||
import { Site } from '../entity/site.entity';
|
||||
import { WpSite } from '../interface';
|
||||
import { UpdateSiteDTO } from '../dto/site.dto';
|
||||
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class SiteService {
|
||||
@InjectEntityModel(Site)
|
||||
siteModel: Repository<Site>;
|
||||
|
||||
async syncFromConfig(sites: WpSite[] = []) {
|
||||
// 将配置中的 WpSite 同步到数据库 Site 表(用于一次性导入或初始化)
|
||||
for (const siteConfig of sites) {
|
||||
// 按站点名称查询是否已存在记录
|
||||
const exist = await this.siteModel.findOne({ where: { siteName: siteConfig.siteName } });
|
||||
// 将 WpSite 字段映射为 Site 实体字段
|
||||
const payload: Partial<Site> = {
|
||||
siteName: siteConfig.siteName,
|
||||
apiUrl: (siteConfig as any).wpApiUrl,
|
||||
consumerKey: (siteConfig as any).consumerKey,
|
||||
consumerSecret: (siteConfig as any).consumerSecret,
|
||||
type: 'woocommerce',
|
||||
};
|
||||
// 存在则更新,不存在则插入新记录
|
||||
if (exist) await this.siteModel.update({ id: exist.id }, payload);
|
||||
else await this.siteModel.insert(payload as Site);
|
||||
}
|
||||
}
|
||||
|
||||
async create(data: Partial<Site>) {
|
||||
// 创建新的站点记录
|
||||
await this.siteModel.insert(data as Site);
|
||||
return true;
|
||||
}
|
||||
|
||||
async update(id: string | number, data: UpdateSiteDTO) {
|
||||
// 更新指定站点记录,将布尔 isDisabled 转换为数值 0/1
|
||||
const payload: Partial<Site> = {
|
||||
...data,
|
||||
isDisabled:
|
||||
data.isDisabled === undefined // 未传入则不更新该字段
|
||||
? undefined
|
||||
: data.isDisabled // true -> 1, false -> 0
|
||||
? 1
|
||||
: 0,
|
||||
} as any;
|
||||
await this.siteModel.update({ id: Number(id) }, payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
async get(id: string | number, includeSecret = false) {
|
||||
// 根据主键获取站点;includeSecret 为 true 时返回密钥字段
|
||||
const site = await this.siteModel.findOne({ where: { id: Number(id) } });
|
||||
if (!site) return null;
|
||||
if (includeSecret) return site;
|
||||
// 默认不返回密钥,进行字段脱敏
|
||||
const { consumerKey, consumerSecret, ...rest } = site;
|
||||
return rest;
|
||||
}
|
||||
|
||||
async list(param: { current?: number; pageSize?: number; keyword?: string; isDisabled?: boolean; ids?: string }, includeSecret = false) {
|
||||
// 分页查询站点列表,支持关键字、禁用状态与 ID 列表过滤
|
||||
const { current = 1, pageSize = 10, keyword, isDisabled, ids } = (param || {}) as any;
|
||||
const where: any = {};
|
||||
// 按名称模糊查询
|
||||
if (keyword) where.siteName = Like(`%${keyword}%`);
|
||||
// 按禁用状态过滤(布尔转数值)
|
||||
if (typeof isDisabled === 'boolean') where.isDisabled = isDisabled ? 1 : 0;
|
||||
if (ids) {
|
||||
// 解析逗号分隔的 ID 字符串为数字数组,并过滤非法值
|
||||
const numIds = String(ids)
|
||||
.split(',')
|
||||
.filter(Boolean)
|
||||
.map((i) => Number(i))
|
||||
.filter((v) => !Number.isNaN(v));
|
||||
if (numIds.length > 0) where.id = In(numIds);
|
||||
}
|
||||
// 进行分页查询(skip/take)并返回总条数
|
||||
const [items, total] = await this.siteModel.findAndCount({ where, skip: (current - 1) * pageSize, take: pageSize });
|
||||
// 根据 includeSecret 决定是否脱敏返回密钥字段
|
||||
const data = includeSecret ? items : items.map((item: any) => {
|
||||
const { consumerKey, consumerSecret, ...rest } = item;
|
||||
return rest;
|
||||
});
|
||||
return { items: data, total, current, pageSize };
|
||||
}
|
||||
|
||||
async disable(id: string | number, disabled: boolean) {
|
||||
// 设置站点禁用状态(true -> 1, false -> 0)
|
||||
await this.siteModel.update({ id: Number(id) }, { isDisabled: disabled ? 1 : 0 });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import { Config, Provide } from '@midwayjs/core';
|
||||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api';
|
||||
import { WpSite } from '../interface';
|
||||
import { WpProduct } from '../entity/wp_product.entity';
|
||||
import { Variation } from '../entity/variation.entity';
|
||||
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
|
||||
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||
import { SiteService } from './site.service';
|
||||
|
||||
@Provide()
|
||||
export class WPService {
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
|
||||
/**
|
||||
* 构建 URL,自动规范各段的斜杠,避免出现多 / 或少 / 导致请求失败
|
||||
|
|
@ -35,12 +35,11 @@ export class WPService {
|
|||
* @param site 站点配置
|
||||
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
||||
*/
|
||||
private createApi(site: WpSite, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||
return new WooCommerceRestApi({
|
||||
url: site.wpApiUrl,
|
||||
url: site.apiUrl,
|
||||
consumerKey: site.consumerKey,
|
||||
consumerSecret: site.consumerSecret,
|
||||
// SDK 的版本字段有联合类型限制,这里兼容插件命名空间(例如 wcs/v1)
|
||||
version: namespace,
|
||||
});
|
||||
}
|
||||
|
|
@ -52,15 +51,19 @@ export class WPService {
|
|||
const page = params.page ?? 1;
|
||||
const per_page = params.per_page ?? 100;
|
||||
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
|
||||
if (res?.headers?.['content-type']?.includes('text/html')) {
|
||||
throw new Error('接口返回了 text/html,可能为 WordPress 登录页或错误页,请检查站点配置或权限');
|
||||
}
|
||||
const data = res.data as T[];
|
||||
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
|
||||
return { items: data, totalPages, page, per_page };
|
||||
const total = Number(res.headers?.['x-wp-total']?? 1)
|
||||
return { items: data, total, totalPages, page, per_page };
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 SDK 聚合分页数据,返回全部数据
|
||||
*/
|
||||
private async sdkGetAll<T>(api: any, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> {
|
||||
private async sdkGetAll<T>(api: WooCommerceRestApi, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> {
|
||||
const result: T[] = [];
|
||||
for (let page = 1; page <= maxPages; page++) {
|
||||
const { items, totalPages } = await this.sdkGetPage<T>(api, resource, { ...params, page });
|
||||
|
|
@ -78,20 +81,18 @@ export class WPService {
|
|||
* @param consumerSecret WooCommerce 的消费者密钥
|
||||
*/
|
||||
|
||||
geSite(id: string): WpSite {
|
||||
let idx = this.sites.findIndex(item => item.id === id);
|
||||
return this.sites[idx];
|
||||
}
|
||||
|
||||
|
||||
async fetchData<T>(
|
||||
endpoint: string,
|
||||
site: WpSite,
|
||||
site: any,
|
||||
param: Record<string, any> = {}
|
||||
): Promise<T> {
|
||||
try {
|
||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||
const apiUrl = site.apiUrl;
|
||||
const { consumerKey, consumerSecret } = site;
|
||||
// 构建 URL,规避多/或少/问题
|
||||
const url = this.buildURL(wpApiUrl, '/wp-json', endpoint);
|
||||
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
|
||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
|
@ -111,22 +112,22 @@ export class WPService {
|
|||
|
||||
async fetchPagedData<T>(
|
||||
endpoint: string,
|
||||
site: WpSite,
|
||||
site: any,
|
||||
page: number = 1,
|
||||
perPage: number = 100
|
||||
): Promise<T[]> {
|
||||
const allData: T[] = [];
|
||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||
const { apiUrl, consumerKey, consumerSecret } = site;
|
||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||
'base64'
|
||||
);
|
||||
console.log(`!!!wpApiUrl, consumerKey, consumerSecret, auth`,wpApiUrl, consumerKey, consumerSecret, auth)
|
||||
console.log(`!!!wpApiUrl, consumerKey, consumerSecret, auth`,site.apiUrl, consumerKey, consumerSecret, auth)
|
||||
let hasMore = true;
|
||||
while (hasMore) {
|
||||
const config: AxiosRequestConfig = {
|
||||
method: 'GET',
|
||||
// 构建 URL,规避多/或少/问题
|
||||
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
|
||||
url: this.buildURL(apiUrl, '/wp-json', endpoint),
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
|
|
@ -156,18 +157,18 @@ export class WPService {
|
|||
return allData;
|
||||
}
|
||||
|
||||
async getProducts(site: WpSite): Promise<WpProduct[]> {
|
||||
async getProducts(site: any): Promise<WpProduct[]> {
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<WpProduct>(api, 'products');
|
||||
}
|
||||
|
||||
async getVariations(site: WpSite, productId: number): Promise<Variation[]> {
|
||||
async getVariations(site: any, productId: number): Promise<Variation[]> {
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<Variation>(api, `products/${productId}/variations`);
|
||||
}
|
||||
|
||||
async getVariation(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
productId: number,
|
||||
variationId: number
|
||||
): Promise<Variation> {
|
||||
|
|
@ -180,13 +181,13 @@ export class WPService {
|
|||
siteId: string,
|
||||
orderId: string
|
||||
): Promise<Record<string, any>> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
const res = await api.get(`orders/${orderId}`);
|
||||
return res.data as Record<string, any>;
|
||||
}
|
||||
async getOrders(siteId: string): Promise<Record<string, any>[]> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<Record<string, any>>(api, 'orders');
|
||||
}
|
||||
|
|
@ -197,7 +198,7 @@ export class WPService {
|
|||
* 返回所有分页合并后的订阅数组。
|
||||
*/
|
||||
async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
// 优先使用 Subscriptions 命名空间 wcs/v1,失败回退 wc/v3
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<Record<string, any>>(api, 'subscriptions');
|
||||
|
|
@ -209,7 +210,7 @@ export class WPService {
|
|||
orderId: string,
|
||||
refundId: number
|
||||
): Promise<Record<string, any>> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
const res = await api.get(`orders/${orderId}/refunds/${refundId}`);
|
||||
return res.data as Record<string, any>;
|
||||
|
|
@ -219,7 +220,7 @@ export class WPService {
|
|||
siteId: string,
|
||||
orderId: number
|
||||
): Promise<Record<string, any>[]> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/refunds`);
|
||||
}
|
||||
|
|
@ -229,7 +230,7 @@ export class WPService {
|
|||
orderId: number,
|
||||
noteId: number
|
||||
): Promise<Record<string, any>> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
const res = await api.get(`orders/${orderId}/notes/${noteId}`);
|
||||
return res.data as Record<string, any>;
|
||||
|
|
@ -239,24 +240,25 @@ export class WPService {
|
|||
siteId: string,
|
||||
orderId: number
|
||||
): Promise<Record<string, any>[]> {
|
||||
const site = this.geSite(siteId);
|
||||
const site = await this.siteService.get(siteId);
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetAll<Record<string, any>>(api, `orders/${orderId}/notes`);
|
||||
}
|
||||
|
||||
async updateData<T>(
|
||||
endpoint: string,
|
||||
site: WpSite,
|
||||
site: any,
|
||||
data: Record<string, any>
|
||||
): Promise<Boolean> {
|
||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||
const apiUrl = site.apiUrl;
|
||||
const { consumerKey, consumerSecret } = site;
|
||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||
'base64'
|
||||
);
|
||||
const config: AxiosRequestConfig = {
|
||||
method: 'PUT',
|
||||
// 构建 URL,规避多/或少/问题
|
||||
url: this.buildURL(wpApiUrl, '/wp-json', endpoint),
|
||||
url: this.buildURL(apiUrl, '/wp-json', endpoint),
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
|
|
@ -276,7 +278,7 @@ export class WPService {
|
|||
* @param data 更新的数据
|
||||
*/
|
||||
async updateProduct(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
productId: string,
|
||||
data: UpdateWpProductDTO
|
||||
): Promise<Boolean> {
|
||||
|
|
@ -295,7 +297,7 @@ export class WPService {
|
|||
* @param stock_status 上下架状态
|
||||
*/
|
||||
async updateProductStatus(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
productId: string,
|
||||
status: ProductStatus,
|
||||
stock_status: ProductStockStatus
|
||||
|
|
@ -315,7 +317,7 @@ export class WPService {
|
|||
* @param data 更新的数据
|
||||
*/
|
||||
async updateVariation(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
productId: string,
|
||||
variationId: string,
|
||||
data: UpdateVariationDTO
|
||||
|
|
@ -336,7 +338,7 @@ export class WPService {
|
|||
* 更新 Order
|
||||
*/
|
||||
async updateOrder(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
orderId: string,
|
||||
data: Record<string, any>
|
||||
): Promise<Boolean> {
|
||||
|
|
@ -344,11 +346,12 @@ export class WPService {
|
|||
}
|
||||
|
||||
async createShipment(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
orderId: string,
|
||||
data: Record<string, any>
|
||||
) {
|
||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||
const apiUrl = site.apiUrl;
|
||||
const { consumerKey, consumerSecret } = site;
|
||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
|
@ -356,7 +359,7 @@ export class WPService {
|
|||
method: 'POST',
|
||||
// 构建 URL,规避多/或少/问题
|
||||
url: this.buildURL(
|
||||
wpApiUrl,
|
||||
apiUrl,
|
||||
'/wp-json',
|
||||
'wc-ast/v3/orders',
|
||||
orderId,
|
||||
|
|
@ -371,11 +374,12 @@ export class WPService {
|
|||
}
|
||||
|
||||
async deleteShipment(
|
||||
site: WpSite,
|
||||
site: any,
|
||||
orderId: string,
|
||||
trackingId: string,
|
||||
): Promise<Boolean> {
|
||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||
const apiUrl = site.apiUrl;
|
||||
const { consumerKey, consumerSecret } = site;
|
||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
|
@ -386,7 +390,7 @@ export class WPService {
|
|||
method: 'DELETE',
|
||||
// 构建 URL,规避多/或少/问题
|
||||
url: this.buildURL(
|
||||
wpApiUrl,
|
||||
apiUrl,
|
||||
'/wp-json',
|
||||
'wc-ast/v3/orders',
|
||||
orderId,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Product } from '../entity/product.entity';
|
||||
import { Config, Inject, Provide } from '@midwayjs/core';
|
||||
import { 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';
|
||||
|
|
@ -12,89 +11,91 @@ import {
|
|||
UpdateWpProductDTO,
|
||||
} from '../dto/wp_product.dto';
|
||||
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||
import { SiteService } from './site.service';
|
||||
|
||||
@Provide()
|
||||
export class WpProductService {
|
||||
@Config('wpSite')
|
||||
sites: WpSite[];
|
||||
// 移除配置中的站点数组,统一从数据库获取站点信息
|
||||
|
||||
@Inject()
|
||||
private readonly wpApiService: WPService;
|
||||
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
|
||||
@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 { items: sites } = await this.siteService.list({ current: 1, pageSize: Infinity, isDisabled: false }, true);
|
||||
for (const site of 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);
|
||||
await this.syncProductAndVariations(String(site.id), product, variations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 同步一个网站
|
||||
async syncSite(siteId: string) {
|
||||
const site = this.getSite(siteId);
|
||||
// 通过数据库获取站点并转换为 WpSite,用于后续 WooCommerce 同步
|
||||
const site = await this.siteService.get(Number(siteId), true);
|
||||
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
|
||||
.select([
|
||||
'wp_product.id ',
|
||||
.select([
|
||||
'wp_product.id ',
|
||||
'wp_product.externalProductId ',
|
||||
])
|
||||
.where('wp_product.siteId = :siteIds ', {
|
||||
.where('wp_product.siteId = :siteIds ', {
|
||||
siteIds: siteId,
|
||||
})
|
||||
const rawResult = await externalProductIds.getRawMany();
|
||||
const rawResult = await externalProductIds.getRawMany();
|
||||
|
||||
const externalIds = rawResult.map(item => item.externalProductId);
|
||||
const externalIds = rawResult.map(item => item.externalProductId);
|
||||
|
||||
const excludeValues = [];
|
||||
const excludeValues = [];
|
||||
|
||||
const products = await this.wpApiService.getProducts(site);
|
||||
for (const product of products) {
|
||||
excludeValues.push(String(product.id));
|
||||
const variations =
|
||||
excludeValues.push(String(product.id));
|
||||
const variations =
|
||||
product.type === 'variable'
|
||||
? await this.wpApiService.getVariations(site, product.id)
|
||||
: [];
|
||||
|
||||
await this.syncProductAndVariations(site.id, product, variations);
|
||||
await this.syncProductAndVariations(String(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();
|
||||
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();
|
||||
}
|
||||
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);
|
||||
const site = await this.siteService.get(Number(wpProduct.siteId), true);
|
||||
wpProduct.status = status;
|
||||
wpProduct.stockStatus = stock_status;
|
||||
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
|
||||
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
|
||||
if (res === true) {
|
||||
this.wpProductModel.save(wpProduct);
|
||||
return true;
|
||||
|
|
@ -310,7 +311,7 @@ const excludeValues = [];
|
|||
if (status) {
|
||||
where.status = status;
|
||||
}
|
||||
where.on_delete = false;
|
||||
where.on_delete = false;
|
||||
|
||||
const products = await this.wpProductModel.find({
|
||||
where,
|
||||
|
|
@ -492,16 +493,16 @@ const excludeValues = [];
|
|||
if (!product) throw new Error('未找到该商品');
|
||||
|
||||
await this.variationModel.createQueryBuilder('variation')
|
||||
.update()
|
||||
.set({ on_delete: true })
|
||||
.where(" variation.externalProductId = :externalProductId ",{externalProductId:productId})
|
||||
.execute();
|
||||
.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();
|
||||
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 });
|
||||
|
|
|
|||
Loading…
Reference in New Issue