Compare commits

..

No commits in common. "e4fc195b8d6e5ff69d79481e67fc6c8730e2af8f" and "a5996363c868e4f92a7dfed60c1a7bfefa00fce0" have entirely different histories.

36 changed files with 268 additions and 291 deletions

View File

@ -1,24 +0,0 @@
> my-midway-project@1.0.0 dev
> cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js

[10:37:17 AM] Starting compilation in watch mode...
[10:37:19 AM] Found 0 errors. Watching for file changes.
2025-12-01 10:37:20.106 INFO 58678 [SyncProductJob] start job SyncProductJob
2025-12-01 10:37:20.106 INFO 58678 [SyncShipmentJob] start job SyncShipmentJob
2025-12-01 10:37:20.109 INFO 58678 [SyncProductJob] complete job SyncProductJob
Node.js server started in 732 ms
➜ Local: http://127.0.0.1:7001/
➜ Network: http://192.168.5.100:7001/ 
2025-12-01 10:37:20.110 INFO 58678 [SyncShipmentJob] complete job SyncShipmentJob

8
package-lock.json generated
View File

@ -27,7 +27,7 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"csv-parse": "^6.1.0", "csv-parse": "^6.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"mysql2": "^3.15.3", "mysql2": "^3.11.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",
"npm-check-updates": "^19.1.2", "npm-check-updates": "^19.1.2",
"swagger-ui-dist": "^5.18.2", "swagger-ui-dist": "^5.18.2",
@ -3388,9 +3388,9 @@
} }
}, },
"node_modules/mysql2": { "node_modules/mysql2": {
"version": "3.15.3", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.15.0.tgz",
"integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", "integrity": "sha512-tT6pomf5Z/I7Jzxu8sScgrYBMK9bUFWd7Kbo6Fs1L0M13OOIJ/ZobGKS3Z7tQ8Re4lj+LnLXIQVZZxa3fhYKzA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"aws-ssl-profiles": "^1.1.1", "aws-ssl-profiles": "^1.1.1",

View File

@ -22,7 +22,7 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"csv-parse": "^6.1.0", "csv-parse": "^6.1.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"mysql2": "^3.15.3", "mysql2": "^3.11.5",
"nodemailer": "^7.0.5", "nodemailer": "^7.0.5",
"npm-check-updates": "^19.1.2", "npm-check-updates": "^19.1.2",
"swagger-ui-dist": "^5.18.2", "swagger-ui-dist": "^5.18.2",

View File

@ -15,7 +15,7 @@ import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity'; import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_refund_item.entity'; import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { OrderItemOriginal } from '../entity/order_item_original.entity'; import { OrderSaleOriginal } from '../entity/order_item_original.entity';
import { OrderShipping } from '../entity/order_shipping.entity'; import { OrderShipping } from '../entity/order_shipping.entity';
import { Service } from '../entity/service.entity'; import { Service } from '../entity/service.entity';
import { ShippingAddress } from '../entity/shipping_address.entity'; import { ShippingAddress } from '../entity/shipping_address.entity';
@ -61,7 +61,7 @@ export default {
OrderRefund, OrderRefund,
OrderRefundItem, OrderRefundItem,
OrderSale, OrderSale,
OrderItemOriginal, OrderSaleOriginal,
OrderShipment, OrderShipment,
ShipmentItem, ShipmentItem,
Shipment, Shipment,

View File

@ -36,7 +36,7 @@ export class OrderController {
type: BooleanRes, type: BooleanRes,
}) })
@Post('/syncOrder/:siteId') @Post('/syncOrder/:siteId')
async syncOrder(@Param('siteId') siteId: number) { async syncOrder(@Param('siteId') siteId: string) {
try { try {
await this.orderService.syncOrders(siteId); await this.orderService.syncOrders(siteId);
return successResponse(true); return successResponse(true);
@ -51,7 +51,7 @@ export class OrderController {
}) })
@Post('/syncOrder/:siteId/order/:orderId') @Post('/syncOrder/:siteId/order/:orderId')
async syncOrderById( async syncOrderById(
@Param('siteId') siteId: number, @Param('siteId') siteId: string,
@Param('orderId') orderId: string @Param('orderId') orderId: string
) { ) {
try { try {

View File

@ -13,7 +13,7 @@ export class SubscriptionController {
// 同步订阅:根据站点 ID 拉取并更新本地订阅数据 // 同步订阅:根据站点 ID 拉取并更新本地订阅数据
@ApiOkResponse({ type: BooleanRes }) @ApiOkResponse({ type: BooleanRes })
@Post('/sync/:siteId') @Post('/sync/:siteId')
async sync(@Param('siteId') siteId: number) { async sync(@Param('siteId') siteId: string) {
try { try {
await this.subscriptionService.syncSubscriptions(siteId); await this.subscriptionService.syncSubscriptions(siteId);
return successResponse(true); return successResponse(true);

View File

@ -43,15 +43,14 @@ export class WebhookController {
@Post('/woocommerce') @Post('/woocommerce')
async handleWooWebhook( async handleWooWebhook(
@Body() body: any, @Body() body: any,
@Query('siteId') siteIdStr: string, @Query('siteId') siteId: string,
@Headers() header: any @Headers() header: any
) { ) {
const signature = header['x-wc-webhook-signature']; const signature = header['x-wc-webhook-signature'];
const topic = header['x-wc-webhook-topic']; const topic = header['x-wc-webhook-topic'];
const source = header['x-wc-webhook-source']; const source = header['x-wc-webhook-source'];
const siteId = Number(siteIdStr);
// 从数据库获取站点配置 // 从数据库获取站点配置
const site = await this.siteService.get(siteId, true); const site = await this.siteService.get(Number(siteId), true);
if (!site || !source.includes(site.apiUrl)) { if (!site || !source.includes(site.apiUrl)) {
console.log('domain not match'); console.log('domain not match');
@ -98,13 +97,13 @@ export class WebhookController {
? await this.wpApiService.getVariations(site, body.id) ? await this.wpApiService.getVariations(site, body.id)
: []; : [];
await this.wpProductService.syncProductAndVariations( await this.wpProductService.syncProductAndVariations(
site.id, String(site.id),
body, body,
variations variations
); );
break; break;
case 'product.deleted': case 'product.deleted':
await this.wpProductService.delWpProduct(site.id, body.id); await this.wpProductService.delWpProduct(String(site.id), body.id);
break; break;
case 'order.created': case 'order.created':
case 'order.updated': case 'order.updated':

View File

@ -40,7 +40,7 @@ export class WpProductController {
type: BooleanRes, type: BooleanRes,
}) })
@Post('/sync/:siteId') @Post('/sync/:siteId')
async syncProducts(@Param('siteId') siteId: number) { async syncProducts(@Param('siteId') siteId: string) {
try { try {
await this.wpProductService.syncSite(siteId); await this.wpProductService.syncSite(siteId);
return successResponse(true); return successResponse(true);
@ -107,7 +107,7 @@ export class WpProductController {
}) })
@Put('/siteId/:siteId/products/:productId') @Put('/siteId/:siteId/products/:productId')
async updateProduct( async updateProduct(
@Param('siteId') siteId: number, @Param('siteId') siteId: string,
@Param('productId') productId: string, @Param('productId') productId: string,
@Body() body: UpdateWpProductDTO @Body() body: UpdateWpProductDTO
) { ) {
@ -120,8 +120,7 @@ export class WpProductController {
if (isDuplicate) { if (isDuplicate) {
return errorResponse('SKU已存在'); return errorResponse('SKU已存在');
} }
const site = await this.siteService.get(Number(siteId), true);
const site = await this.siteService.get(siteId, true);
const result = await this.wpApiService.updateProduct( const result = await this.wpApiService.updateProduct(
site, site,
productId, productId,
@ -146,7 +145,7 @@ export class WpProductController {
*/ */
@Put('/siteId/:siteId/products/:productId/variations/:variationId') @Put('/siteId/:siteId/products/:productId/variations/:variationId')
async updateVariation( async updateVariation(
@Param('siteId') siteId: number, @Param('siteId') siteId: string,
@Param('productId') productId: string, @Param('productId') productId: string,
@Param('variationId') variationId: string, @Param('variationId') variationId: string,
@Body() body: UpdateVariationDTO @Body() body: UpdateVariationDTO
@ -161,7 +160,7 @@ export class WpProductController {
if (isDuplicate) { if (isDuplicate) {
return errorResponse('SKU已存在'); return errorResponse('SKU已存在');
} }
const site = await this.siteService.get(siteId, true); const site = await this.siteService.get(Number(siteId), true);
const result = await this.wpApiService.updateVariation( const result = await this.wpApiService.updateVariation(
site, site,
productId, productId,

View File

@ -61,8 +61,8 @@ export class QueryOrderDTO {
externalOrderId: string; externalOrderId: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.number()) @Rule(RuleType.string())
siteId: number; siteId: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string().allow('')) @Rule(RuleType.string().allow(''))
@ -115,8 +115,8 @@ export class QueryOrderSalesDTO {
pageSize: number; pageSize: number;
@ApiProperty() @ApiProperty()
@Rule(RuleType.number()) @Rule(RuleType.string())
siteId: number; siteId: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
@ -156,8 +156,8 @@ export class QueryOrderItemDTO {
pageSize: number; pageSize: number;
@ApiProperty() @ApiProperty()
@Rule(RuleType.number().allow('')) @Rule(RuleType.string().allow(''))
siteId: number; siteId: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string().allow('')) @Rule(RuleType.string().allow(''))

View File

@ -24,13 +24,13 @@ export class QueryStockDTO {
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
sku: string; productSku: string;
@ApiProperty({ description: '按库存点ID排序', required: false }) @ApiProperty({ description: '按库存点ID排序', required: false })
@Rule(RuleType.number().allow(null)) @Rule(RuleType.number().allow(null))
sortPointId?: number; sortPointId?: number;
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", sku: "desc" }', required: false }) @ApiProperty({ description: '排序对象,格式如 { productName: "asc", productSku: "desc" }', required: false })
@Rule(RuleType.object().allow(null)) @Rule(RuleType.object().allow(null))
order?: Record<string, 'asc' | 'desc'>; order?: Record<string, 'asc' | 'desc'>;
} }
@ -58,7 +58,7 @@ export class QueryStockRecordDTO {
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
sku: string; productSku: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
@ -132,7 +132,7 @@ export class UpdateStockDTO {
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
sku: string; productSku: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.number()) @Rule(RuleType.number())

View File

@ -24,9 +24,9 @@ export class Order {
id: number; id: number;
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 来源站点唯一标识 siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -22,9 +22,9 @@ export class OrderCoupon {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column( )
@Expose() @Expose()
siteId: number; // 站点ID siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -22,9 +22,9 @@ export class OrderFee {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 站点ID siteId: string;
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -22,9 +22,9 @@ export class OrderItem {
name: string; name: string;
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 来源站点唯一标识 siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -11,9 +11,9 @@ import {
} from 'typeorm'; } from 'typeorm';
import { Order } from './order.entity'; import { Order } from './order.entity';
@Entity('order_item_original') @Entity('order_sale_original')
@Exclude() @Exclude()
export class OrderItemOriginal { export class OrderSaleOriginal {
@ApiProperty() @ApiProperty()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@Expose() @Expose()
@ -27,9 +27,9 @@ export class OrderItemOriginal {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 站点ID siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column({ nullable: true })

View File

@ -22,9 +22,9 @@ export class OrderRefund {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 站点ID siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -22,9 +22,9 @@ export class OrderRefundItem {
refundId: number; // 订单 refund ID refundId: number; // 订单 refund ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 站点ID siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -24,9 +24,9 @@ export class OrderSale {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 来源站点唯一标识 siteId: string; // 来源站点唯一标识
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column({ nullable: true })

View File

@ -22,9 +22,9 @@ export class OrderShipping {
orderId: number; // 订单 ID orderId: number; // 订单 ID
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; // 站点ID siteId: string;
@ApiProperty() @ApiProperty()
@Column() @Column()

View File

@ -23,11 +23,6 @@ export class Product {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
// 类型 主要用来区分混装和单品 单品死
@ApiProperty({ description: '类型' })
@Column({ length: 16, default: 'single' })
type: string;
@ApiProperty({ @ApiProperty({
example: 'ZYN 6MG WINTERGREEN', example: 'ZYN 6MG WINTERGREEN',
description: '产品名称', description: '产品名称',
@ -54,7 +49,10 @@ export class Product {
@ApiProperty({ description: '价格', example: 99.99 }) @ApiProperty({ description: '价格', example: 99.99 })
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 }) @Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
price: number; price: number;
// 类型 主要用来区分混装和单品 单品死
@ApiProperty({ description: '类型' })
@Column({ length: 16, default: 'simple' })
type: string;
// 促销价格 // 促销价格
@ApiProperty({ description: '促销价格', example: 99.99 }) @ApiProperty({ description: '促销价格', example: 99.99 })
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 }) @Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })

View File

@ -14,7 +14,7 @@ export class ProductStockComponent {
@ApiProperty({ description: '组件所关联的 SKU', type: 'string' }) @ApiProperty({ description: '组件所关联的 SKU', type: 'string' })
@Column({ type: 'varchar', length: 64 }) @Column({ type: 'varchar', length: 64 })
sku: string; productSku: string;
@ApiProperty({ type: Number, description: '组成数量' }) @ApiProperty({ type: Number, description: '组成数量' })
@Column({ type: 'int', default: 1 }) @Column({ type: 'int', default: 1 })

View File

@ -10,7 +10,7 @@ export class PurchaseOrderItem {
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()
sku: string; productSku: string;
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()

View File

@ -20,7 +20,7 @@ export class Stock {
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()
sku: string; productSku: string;
@ApiProperty({ type: Number }) @ApiProperty({ type: Number })
@Column() @Column()

View File

@ -20,7 +20,7 @@ export class StockRecord {
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()
sku: string; productSku: string;
@ApiProperty({ type: StockRecordOperationType }) @ApiProperty({ type: StockRecordOperationType })
@Column({ type: 'enum', enum: StockRecordOperationType }) @Column({ type: 'enum', enum: StockRecordOperationType })

View File

@ -20,9 +20,9 @@ export class Subscription {
// 站点唯一标识,用于区分不同来源站点 // 站点唯一标识,用于区分不同来源站点
@ApiProperty({ description: '来源站点唯一标识' }) @ApiProperty({ description: '来源站点唯一标识' })
@Column({ nullable: true }) @Column()
@Expose() @Expose()
siteId: number; siteId: string;
// WooCommerce 订阅的原始 ID字符串化用于幂等更新 // WooCommerce 订阅的原始 ID字符串化用于幂等更新
@ApiProperty({ description: 'WooCommerce 订阅 ID' }) @ApiProperty({ description: 'WooCommerce 订阅 ID' })

View File

@ -9,7 +9,7 @@ export class TransferItem {
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()
sku: string; productSku: string;
@ApiProperty({ type: String }) @ApiProperty({ type: String })
@Column() @Column()

View File

@ -21,12 +21,13 @@ export class Variation {
id: number; id: number;
@ApiProperty({ @ApiProperty({
description: '站点 id', example: '1',
example: 1, description: 'wp网站ID',
required: false type: 'string',
required: true,
}) })
@Column({ nullable: true }) @Column()
siteId: number; siteId: string; // 来源站点唯一标识
@ApiProperty({ @ApiProperty({
example: '1', example: '1',

View File

@ -1,4 +1,3 @@
import { Site } from './site.entity';
import { import {
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
Column, Column,
@ -6,8 +5,6 @@ import {
UpdateDateColumn, UpdateDateColumn,
Unique, Unique,
Entity, Entity,
ManyToOne,
JoinColumn,
} from 'typeorm'; } from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger'; import { ApiProperty } from '@midwayjs/swagger';
import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum'; import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum';
@ -25,17 +22,13 @@ export class WpProduct {
id: number; id: number;
@ApiProperty({ @ApiProperty({
example: 1, example: '1',
description: 'wp网站ID', description: 'wp网站ID',
type: 'number', type: 'string',
required: true, required: true,
}) })
@Column({ type: 'int', nullable: true }) @Column()
siteId: number; siteId: string;
@ManyToOne(() => Site)
@JoinColumn({ name: 'siteId', referencedColumnName: 'id' })
site: Site;
@ApiProperty({ @ApiProperty({
example: '1', example: '1',

View File

@ -527,14 +527,14 @@ export class LogisticsService {
const stock = await stockRepo.findOne({ const stock = await stockRepo.findOne({
where: { where: {
stockPointId: orderShipments[0].stockPointId, stockPointId: orderShipments[0].stockPointId,
sku: item.sku, productSku: item.sku,
}, },
}); });
stock.quantity += item.quantity; stock.quantity += item.quantity;
await stockRepo.save(stock); await stockRepo.save(stock);
await stockRecordRepo.save({ await stockRecordRepo.save({
stockPointId: orderShipments[0].stockPointId, stockPointId: orderShipments[0].stockPointId,
sku: item.sku, productSku: item.sku,
operationType: StockRecordOperationType.IN, operationType: StockRecordOperationType.IN,
quantityChange: item.quantity, quantityChange: item.quantity,
operatorId: userId, operatorId: userId,
@ -561,7 +561,7 @@ export class LogisticsService {
// 从数据库批量获取站点信息,构建映射以避免 N+1 查询 // 从数据库批量获取站点信息,构建映射以避免 N+1 查询
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean))); 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 { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
const siteMap = new Map(sites.map((s: any) => [s.id, s.name])); const siteMap = new Map(sites.map((s: any) => [String(s.id), s.name]));
return orders.map(order => ({ return orders.map(order => ({
...order, ...order,

View File

@ -6,7 +6,7 @@ import { In, Like, Repository } from 'typeorm';
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm'; import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderItemOriginal } from '../entity/order_items_original.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { WpProduct } from '../entity/wp_product.entity'; import { WpProduct } from '../entity/wp_product.entity';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
@ -32,7 +32,7 @@ import { SiteService } from './site.service';
import { ShipmentItem } from '../entity/shipment_item.entity'; import { ShipmentItem } from '../entity/shipment_item.entity';
import { UpdateStockDTO } from '../dto/stock.dto'; import { UpdateStockDTO } from '../dto/stock.dto';
import { StockService } from './stock.service'; import { StockService } from './stock.service';
import { OrderItemOriginal } from '../entity/order_item_original.entity'; import { OrderSaleOriginal } from '../entity/order_item_original.entity';
@Provide() @Provide()
export class OrderService { export class OrderService {
@ -58,6 +58,8 @@ export class OrderService {
@InjectEntityModel(OrderSale) @InjectEntityModel(OrderSale)
orderSaleModel: Repository<OrderSale>; orderSaleModel: Repository<OrderSale>;
@InjectEntityModel(OrderSaleOriginal)
orderSaleOriginalModel: Repository<OrderSaleOriginal>;
@InjectEntityModel(WpProduct) @InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>; wpProductModel: Repository<WpProduct>;
@ -101,17 +103,15 @@ export class OrderService {
@Inject() @Inject()
siteService: SiteService; siteService: SiteService;
async syncOrders(siteId: number) { async syncOrders(siteId: string) {
// 调用 WooCommerce API 获取订单 const orders = await this.wpService.getOrders(siteId); // 调用 WooCommerce API 获取订单
const orders = await this.wpService.getOrders(siteId);
for (const order of orders) { for (const order of orders) {
await this.syncSingleOrder(siteId, order); await this.syncSingleOrder(siteId, order);
} }
} }
async syncOrderById(siteId: number, orderId: string) { async syncOrderById(siteId: string, orderId: string) {
// 调用 WooCommerce API 获取订单 const order = await this.wpService.getOrder(siteId, orderId);
const order = await this.wpService.getOrder(String(siteId), orderId);
await this.syncSingleOrder(siteId, order, true); await this.syncSingleOrder(siteId, order, true);
} }
// 订单状态切换表 // 订单状态切换表
@ -120,7 +120,7 @@ export class OrderService {
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded [OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
} }
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换 // 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
async autoUpdateOrderStatus(siteId: number, order: any) { async autoUpdateOrderStatus(siteId: string, order: any) {
console.log('更新订单状态', order) console.log('更新订单状态', order)
// 其他状态保持不变 // 其他状态保持不变
const originStatus = order.status; const originStatus = order.status;
@ -138,7 +138,7 @@ export class OrderService {
} }
} }
// wordpress 发来, // wordpress 发来,
async syncSingleOrder(siteId: number, order: any, forceUpdate = false) { async syncSingleOrder(siteId: string, order: any, forceUpdate = false) {
let { let {
line_items, line_items,
shipping_lines, shipping_lines,
@ -217,7 +217,7 @@ export class OrderService {
for (const item of items) { for (const item of items) {
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = stockPointId; updateStock.stockPointId = stockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.sku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.OUT; updateStock.operationType = StockRecordOperationType.OUT;
updateStock.operatorId = 1; updateStock.operatorId = 1;
@ -226,7 +226,7 @@ export class OrderService {
} }
} }
async saveOrder(siteId: number, order: Order): Promise<Order> { async saveOrder(siteId: string, order: Order): Promise<Order> {
order.externalOrderId = String(order.id); order.externalOrderId = String(order.id);
order.siteId = siteId; order.siteId = siteId;
delete order.id; delete order.id;
@ -305,7 +305,7 @@ export class OrderService {
} }
async saveOrderItems(params: { async saveOrderItems(params: {
siteId: number; siteId: string;
orderId: number; orderId: number;
externalOrderId: string; externalOrderId: string;
orderItems: Record<string, any>[]; orderItems: Record<string, any>[];
@ -438,14 +438,14 @@ export class OrderService {
externalOrderId, externalOrderId,
refunds, refunds,
}: { }: {
siteId: number; siteId: string;
orderId: number; orderId: number;
externalOrderId: string; externalOrderId: string;
refunds: Record<string, any>[]; refunds: Record<string, any>[];
}) { }) {
for (const item of refunds) { for (const item of refunds) {
const refund = await this.wpService.getOrderRefund( const refund = await this.wpService.getOrderRefund(
String(siteId), siteId,
externalOrderId, externalOrderId,
item.id item.id
); );
@ -1302,7 +1302,28 @@ export class OrderService {
} }
} }
// update order_sale_origin if not exist
try {
const order_sale_origin_count = await this.orderSaleOriginalModel.countBy({ orderId: id });
if (order_sale_origin_count === 0) {
sales.forEach(async sale => {
const { id: saleId, ...saleData } = sale;
await this.orderSaleOriginalModel.save(saleData);
});
}
// update order_item_origin if not exist
const order_item_origin_count = await this.orderItemOriginalModel.countBy({ orderId: id });
if (order_item_origin_count === 0 && items.length != 0) {
items.forEach(async sale => {
const { id: saleId, ...saleData } = sale;
await this.orderItemOriginalModel.save(saleData);
});
}
} catch (error) {
console.log('create order sale origin error: ', error.message);
}
// 关联数据:订阅与相关订单(用于前端关联展示) // 关联数据:订阅与相关订单(用于前端关联展示)
let relatedList: any[] = []; let relatedList: any[] = [];
@ -1401,7 +1422,7 @@ export class OrderService {
return orders.map(order => ({ return orders.map(order => ({
externalOrderId: order.externalOrderId, externalOrderId: order.externalOrderId,
id: order.id, id: order.id,
name: siteMap.get(String(order.siteId)) || '', name: siteMap.get(order.siteId) || '',
})); }));
} }
@ -1457,23 +1478,16 @@ export class OrderService {
async createOrder(data: Record<string, any>) { async createOrder(data: Record<string, any>) {
// 从数据中解构出需要用的属性 const { sales, total, billing, customer_email, billing_phone } = data;
const { siteId, sales, total, billing, customer_email, billing_phone } = data;
// 如果没有 siteId则抛出错误
if (!siteId) {
throw new Error('siteId is required');
}
// 获取默认数据源
const dataSource = this.dataSourceManager.getDataSource('default'); const dataSource = this.dataSourceManager.getDataSource('default');
const now = new Date(); const now = new Date();
// 在事务中处理订单创建
return dataSource.transaction(async manager => { return dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order); const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale); const orderSaleRepo = manager.getRepository(OrderSale);
const OrderSaleOriginalRepo = manager.getRepository(OrderSaleOriginal);
const productRepo = manager.getRepository(Product); const productRepo = manager.getRepository(Product);
// 保存订单信息
const order = await orderRepo.save({ const order = await orderRepo.save({
siteId, siteId: '-1',
externalOrderId: '-1', externalOrderId: '-1',
status: OrderStatus.PROCESSING, status: OrderStatus.PROCESSING,
orderStatus: ErpOrderStatus.PROCESSING, orderStatus: ErpOrderStatus.PROCESSING,
@ -1487,12 +1501,11 @@ export class OrderService {
billing, billing,
shipping: billing, shipping: billing,
}); });
// 遍历销售项目并保存
for (const sale of sales) { for (const sale of sales) {
const product = await productRepo.findOne({ where: { sku: sale.sku } }); const product = await productRepo.findOne({ where: { sku: sale.sku } });
const saleItem = { const saleItem = {
orderId: order.id, orderId: order.id,
siteId: order.siteId, siteId: '-1',
externalOrderItemId: '-1', externalOrderItemId: '-1',
productId: product.id, productId: product.id,
name: product.name, name: product.name,
@ -1500,6 +1513,7 @@ export class OrderService {
quantity: sale.quantity, quantity: sale.quantity,
}; };
await orderSaleRepo.save(saleItem); await orderSaleRepo.save(saleItem);
await OrderSaleOriginalRepo.save(saleItem);
} }
}); });
} }
@ -1592,7 +1606,7 @@ export class OrderService {
const orderRepo = manager.getRepository(Order); const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale); const orderSaleRepo = manager.getRepository(OrderSale);
const orderItemRepo = manager.getRepository(OrderItem); const orderItemRepo = manager.getRepository(OrderItem);
const OrderItemOriginalRepo = manager.getRepository(OrderItemOriginal);
const productRepo = manager.getRepository(Product); const productRepo = manager.getRepository(Product);
const WpProductRepo = manager.getRepository(WpProduct); const WpProductRepo = manager.getRepository(WpProduct);
@ -1600,7 +1614,7 @@ export class OrderService {
const order = await orderRepo.findOneBy({ id: orderId }); const order = await orderRepo.findOneBy({ id: orderId });
let product: Product; let product: Product;
let wpProduct: WpProduct; let wpProduct: WpProduct;
let wpOrderItemOriginal: OrderItemOriginal;
await orderSaleRepo.delete({ orderId }); await orderSaleRepo.delete({ orderId });
await orderItemRepo.delete({ orderId }); await orderItemRepo.delete({ orderId });
for (const sale of data['sales']) { for (const sale of data['sales']) {
@ -1617,8 +1631,13 @@ export class OrderService {
for (const item of data['items']) { for (const item of data['items']) {
wpProduct = await WpProductRepo.findOneBy({ sku: item['sku'] }); wpProduct = await WpProductRepo.findOneBy({ sku: item['sku'] });
wpOrderItemOriginal = await OrderItemOriginalRepo.findOneBy({ sku: item['sku'] });
let externalVariationId = wpOrderItemOriginal?.externalVariationId;
let price = wpOrderItemOriginal?.price;
if (wpOrderItemOriginal == null) {
externalVariationId = '0';
price = 0;
}
await orderItemRepo.save({ await orderItemRepo.save({
orderId, orderId,
siteId: order.siteId, siteId: order.siteId,
@ -1626,7 +1645,8 @@ export class OrderService {
name: wpProduct.name, name: wpProduct.name,
externalOrderId: order.externalOrderId, externalOrderId: order.externalOrderId,
externalProductId: wpProduct.externalProductId, externalProductId: wpProduct.externalProductId,
externalVariationId: externalVariationId,
price: price,
sku: item['sku'], sku: item['sku'],
quantity: item['quantity'], quantity: item['quantity'],
}); });

View File

@ -177,19 +177,17 @@ export class ProductService {
const [items, total] = await qb.getManyAndCount(); const [items, total] = await qb.getManyAndCount();
// 中文注释:根据类型填充组成信息 // 中文注释:根据类型填充组成信息
for (const product of items) { for (const p of items) {
if (product.type === 'single') { if (p.type === 'simple') {
// 中文注释:单品不持久化组成,这里仅返回一个基于 SKU 的虚拟组成 // 中文注释:单品不持久化组成,这里仅返回一个基于 SKU 的虚拟组成
const component = new ProductStockComponent(); const comp = new ProductStockComponent();
component.productId = product.id; comp.productId = p.id;
component.sku = product.sku; comp.productSku = p.sku;
component.quantity = 1; comp.quantity = 1;
product.components = [component]; p.components = [comp];
} else { } else {
// 中文注释:混装商品返回持久化的 SKU 组成 // 中文注释:混装商品返回持久化的 SKU 组成
product.components = await this.productStockComponentModel.find({ p.components = await this.productStockComponentModel.find({ where: { productId: p.id } });
where: { productId: product.id },
});
} }
} }
@ -275,7 +273,7 @@ export class ProductService {
product.description = description; product.description = description;
product.attributes = resolvedAttributes; product.attributes = resolvedAttributes;
// 条件判断(中文注释:设置商品类型,默认 simple // 条件判断(中文注释:设置商品类型,默认 simple
product.type = (createProductDTO.type as any) || 'single'; product.type = (createProductDTO.type as any) || 'simple';
// 生成或设置 SKU中文注释基于属性字典项的 name 生成) // 生成或设置 SKU中文注释基于属性字典项的 name 生成)
if (sku) { if (sku) {
@ -389,10 +387,10 @@ export class ProductService {
let components: ProductStockComponent[] = []; let components: ProductStockComponent[] = [];
// 条件判断(中文注释:单品 simple 不持久化组成,按 SKU 动态返回单条组成) // 条件判断(中文注释:单品 simple 不持久化组成,按 SKU 动态返回单条组成)
if (product.type === 'single') { if (product.type === 'simple') {
const comp = new ProductStockComponent(); const comp = new ProductStockComponent();
comp.productId = productId; comp.productId = productId;
comp.sku = product.sku; comp.productSku = product.sku;
comp.quantity = 1; comp.quantity = 1;
components = [comp]; components = [comp];
} else { } else {
@ -401,14 +399,14 @@ export class ProductService {
} }
// 中文注释:获取所有组件的 SKU 列表 // 中文注释:获取所有组件的 SKU 列表
const skus = components.map(c => c.sku); const skus = components.map(c => c.productSku);
if (skus.length === 0) { if (skus.length === 0) {
return components; return components;
} }
// 中文注释:查询这些 SKU 的库存信息 // 中文注释:查询这些 SKU 的库存信息
const stocks = await this.stockModel.find({ const stocks = await this.stockModel.find({
where: { sku: In(skus) }, where: { productSku: In(skus) },
}); });
// 中文注释:获取所有相关的库存点 ID // 中文注释:获取所有相关的库存点 ID
@ -421,12 +419,12 @@ export class ProductService {
// 中文注释:将库存信息按 SKU 分组 // 中文注释:将库存信息按 SKU 分组
const stockMap = stocks.reduce((map, stock) => { const stockMap = stocks.reduce((map, stock) => {
if (!map[stock.sku]) { if (!map[stock.productSku]) {
map[stock.sku] = []; map[stock.productSku] = [];
} }
const stockPoint = stockPointMap[stock.stockPointId]; const stockPoint = stockPointMap[stock.stockPointId];
if (stockPoint) { if (stockPoint) {
map[stock.sku].push({ map[stock.productSku].push({
name: stockPoint.name, name: stockPoint.name,
quantity: stock.quantity, quantity: stock.quantity,
}); });
@ -438,7 +436,7 @@ export class ProductService {
const componentsWithStock = components.map(comp => { const componentsWithStock = components.map(comp => {
return { return {
...comp, ...comp,
stock: stockMap[comp.sku] || [], stock: stockMap[comp.productSku] || [],
}; };
}); });
@ -454,7 +452,7 @@ export class ProductService {
const product = await this.productModel.findOne({ where: { id: productId } }); const product = await this.productModel.findOne({ where: { id: productId } });
if (!product) throw new Error(`产品 ID ${productId} 不存在`); if (!product) throw new Error(`产品 ID ${productId} 不存在`);
// 条件判断(中文注释:单品 simple 不允许手动设置组成) // 条件判断(中文注释:单品 simple 不允许手动设置组成)
if (product.type === 'single') { if (product.type === 'simple') {
throw new Error('单品无需设置组成'); throw new Error('单品无需设置组成');
} }
@ -474,7 +472,7 @@ export class ProductService {
} }
const comp = new ProductStockComponent(); const comp = new ProductStockComponent();
comp.productId = productId; comp.productId = productId;
comp.sku = i.sku; comp.productSku = i.sku;
comp.quantity = i.quantity; comp.quantity = i.quantity;
created.push(await this.productStockComponentModel.save(comp)); created.push(await this.productStockComponentModel.save(comp));
} }
@ -488,19 +486,19 @@ export class ProductService {
if (!product) throw new Error(`产品 ID ${productId} 不存在`); if (!product) throw new Error(`产品 ID ${productId} 不存在`);
// 中文注释:按 SKU 自动绑定 // 中文注释:按 SKU 自动绑定
// 条件判断simple 类型不持久化组成,直接返回单条基于 SKU 的组成 // 条件判断simple 类型不持久化组成,直接返回单条基于 SKU 的组成
if (product.type === 'single') { if (product.type === 'simple') {
const comp = new ProductStockComponent(); const comp = new ProductStockComponent();
comp.productId = productId; comp.productId = productId;
comp.sku = product.sku; comp.productSku = product.sku;
comp.quantity = 1; // 默认数量 1 comp.quantity = 1; // 默认数量 1
return [comp]; return [comp];
} }
// bundle 类型:若不存在则持久化一条基于 SKU 的组成 // bundle 类型:若不存在则持久化一条基于 SKU 的组成
const exist = await this.productStockComponentModel.findOne({ where: { productId, sku: product.sku } }); const exist = await this.productStockComponentModel.findOne({ where: { productId, productSku: product.sku } });
if (!exist) { if (!exist) {
const comp = new ProductStockComponent(); const comp = new ProductStockComponent();
comp.productId = productId; comp.productId = productId;
comp.sku = product.sku; comp.productSku = product.sku;
comp.quantity = 1; comp.quantity = 1;
await this.productStockComponentModel.save(comp); await this.productStockComponentModel.save(comp);
} }
@ -527,13 +525,13 @@ export class ProductService {
if (!product) { if (!product) {
throw new Error(`产品 ID ${id} 不存在`); throw new Error(`产品 ID ${id} 不存在`);
} }
const sku = product.sku; const productSku = product.sku;
// 查询 wp_product 表中是否存在与该 SKU 关联的产品 // 查询 wp_product 表中是否存在与该 SKU 关联的产品
const wpProduct = await this.wpProductModel const wpProduct = await this.wpProductModel
.createQueryBuilder('wp_product') .createQueryBuilder('wp_product')
.where('JSON_CONTAINS(wp_product.constitution, :sku)', { .where('JSON_CONTAINS(wp_product.constitution, :sku)', {
sku: JSON.stringify({ sku: sku }), sku: JSON.stringify({ sku: productSku }),
}) })
.getOne(); .getOne();
if (wpProduct) { if (wpProduct) {
@ -543,7 +541,7 @@ export class ProductService {
const variation = await this.variationModel const variation = await this.variationModel
.createQueryBuilder('variation') .createQueryBuilder('variation')
.where('JSON_CONTAINS(variation.constitution, :sku)', { .where('JSON_CONTAINS(variation.constitution, :sku)', {
sku: JSON.stringify({ sku: sku }), sku: JSON.stringify({ sku: productSku }),
}) })
.getOne(); .getOne();

View File

@ -656,10 +656,10 @@ export class StatisticsService {
const offset = (current - 1) * pageSize; const offset = (current - 1) * pageSize;
const countSql = ` const countSql = `
WITH product_list AS ( WITH product_list AS (
SELECT DISTINCT s.sku SELECT DISTINCT s.productSku
FROM stock s FROM stock s
LEFT JOIN stock_point sp ON s.stockPointId = sp.id LEFT JOIN stock_point sp ON s.stockPointId = sp.id
LEFT JOIN product p ON s.sku = p.sku LEFT JOIN product p ON s.productSku = p.sku
WHERE sp.ignore = FALSE WHERE sp.ignore = FALSE
${countnameFilter} ${countnameFilter}
) )
@ -674,27 +674,27 @@ export class StatisticsService {
const sql = ` const sql = `
WITH stock_summary AS ( WITH stock_summary AS (
SELECT SELECT
s.sku, s.productSku,
JSON_ARRAYAGG(JSON_OBJECT('id', sp.id, 'quantity', s.quantity)) AS stockDetails, JSON_ARRAYAGG(JSON_OBJECT('id', sp.id, 'quantity', s.quantity)) AS stockDetails,
SUM(s.quantity) AS totalStock, SUM(s.quantity) AS totalStock,
SUM(CASE WHEN sp.inCanada THEN s.quantity ELSE 0 END) AS caTotalStock SUM(CASE WHEN sp.inCanada THEN s.quantity ELSE 0 END) AS caTotalStock
FROM stock s FROM stock s
JOIN stock_point sp ON s.stockPointId = sp.id JOIN stock_point sp ON s.stockPointId = sp.id
WHERE sp.ignore = FALSE WHERE sp.ignore = FALSE
GROUP BY s.sku GROUP BY s.productSku
), ),
transfer_stock AS ( transfer_stock AS (
SELECT SELECT
ti.sku, ti.productSku,
SUM(ti.quantity) AS transitStock SUM(ti.quantity) AS transitStock
FROM transfer_item ti FROM transfer_item ti
JOIN transfer t ON ti.transferId = t.id JOIN transfer t ON ti.transferId = t.id
WHERE t.isCancel = FALSE AND t.isArrived = FALSE WHERE t.isCancel = FALSE AND t.isArrived = FALSE
GROUP BY ti.sku GROUP BY ti.productSku
), ),
30_sales_summary AS ( 30_sales_summary AS (
SELECT SELECT
os.sku AS sku, os.sku AS productSku,
SUM(os.quantity) AS totalSales SUM(os.quantity) AS totalSales
FROM order_sale os FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id JOIN \`order\` o ON os.orderId = o.id
@ -704,7 +704,7 @@ export class StatisticsService {
), ),
15_sales_summary AS ( 15_sales_summary AS (
SELECT SELECT
os.sku AS sku, os.sku AS productSku,
2 * SUM(os.quantity) AS totalSales 2 * SUM(os.quantity) AS totalSales
FROM order_sale os FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id JOIN \`order\` o ON os.orderId = o.id
@ -714,36 +714,36 @@ export class StatisticsService {
), ),
sales_max_summary AS ( sales_max_summary AS (
SELECT SELECT
s30.sku AS sku, s30.productSku AS productSku,
COALESCE(s30.totalSales, 0) AS totalSales_30, COALESCE(s30.totalSales, 0) AS totalSales_30,
COALESCE(s15.totalSales, 0) AS totalSales_15, COALESCE(s15.totalSales, 0) AS totalSales_15,
GREATEST(COALESCE(s30.totalSales, 0), COALESCE(s15.totalSales, 0)) AS maxSales GREATEST(COALESCE(s30.totalSales, 0), COALESCE(s15.totalSales, 0)) AS maxSales
FROM 30_sales_summary s30 FROM 30_sales_summary s30
LEFT JOIN 15_sales_summary s15 LEFT JOIN 15_sales_summary s15
ON s30.sku = s15.sku ON s30.productSku = s15.productSku
UNION ALL UNION ALL
SELECT SELECT
s15.sku AS sku, s15.productSku AS productSku,
0 AS totalSales_30, 0 AS totalSales_30,
COALESCE(s15.totalSales, 0) AS totalSales_15, COALESCE(s15.totalSales, 0) AS totalSales_15,
COALESCE(s15.totalSales, 0) AS maxSales COALESCE(s15.totalSales, 0) AS maxSales
FROM 15_sales_summary s15 FROM 15_sales_summary s15
LEFT JOIN 30_sales_summary s30 LEFT JOIN 30_sales_summary s30
ON s30.sku = s15.sku ON s30.productSku = s15.productSku
WHERE s30.sku IS NULL WHERE s30.productSku IS NULL
), ),
product_name_summary AS ( product_name_summary AS (
SELECT SELECT
p.sku AS sku, p.sku AS productSku,
COALESCE(MAX(os.name), MAX(p.name)) AS productName COALESCE(MAX(os.name), MAX(p.name)) AS productName
FROM product p FROM product p
LEFT JOIN order_sale os ON p.sku = os.sku LEFT JOIN order_sale os ON p.sku = os.sku
GROUP BY p.sku GROUP BY p.sku
) )
SELECT SELECT
ss.sku, ss.productSku,
ss.stockDetails, ss.stockDetails,
COALESCE(ts.transitStock, 0) AS transitStock, COALESCE(ts.transitStock, 0) AS transitStock,
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock, (COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
@ -761,9 +761,9 @@ export class StatisticsService {
sales.maxSales * 4 AS restockQuantity, sales.maxSales * 4 AS restockQuantity,
pns.productName pns.productName
FROM stock_summary ss FROM stock_summary ss
LEFT JOIN transfer_stock ts ON ss.sku = ts.sku LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
LEFT JOIN sales_max_summary sales ON ss.sku = sales.sku LEFT JOIN sales_max_summary sales ON ss.productSku = sales.productSku
LEFT JOIN product_name_summary pns ON ss.sku = pns.sku LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
WHERE 1 = 1 WHERE 1 = 1
${nameFilter} ${nameFilter}
ORDER BY caAvailableDays ORDER BY caAvailableDays
@ -791,10 +791,10 @@ export class StatisticsService {
const offset = (current - 1) * pageSize; const offset = (current - 1) * pageSize;
const countSql = ` const countSql = `
WITH product_list AS ( WITH product_list AS (
SELECT DISTINCT s.sku SELECT DISTINCT s.productSku
FROM stock s FROM stock s
LEFT JOIN stock_point sp ON s.stockPointId = sp.id LEFT JOIN stock_point sp ON s.stockPointId = sp.id
LEFT JOIN product p ON s.sku = p.sku LEFT JOIN product p ON s.productSku = p.sku
WHERE sp.ignore = FALSE WHERE sp.ignore = FALSE
${countnameFilter} ${countnameFilter}
) )
@ -810,36 +810,36 @@ export class StatisticsService {
const sql = ` const sql = `
WITH stock_summary AS ( WITH stock_summary AS (
SELECT SELECT
s.sku, s.productSku,
SUM(s.quantity) AS totalStock SUM(s.quantity) AS totalStock
FROM stock s FROM stock s
JOIN stock_point sp ON s.stockPointId = sp.id JOIN stock_point sp ON s.stockPointId = sp.id
WHERE sp.ignore = FALSE WHERE sp.ignore = FALSE
GROUP BY s.sku GROUP BY s.productSku
), ),
transfer_stock AS ( transfer_stock AS (
SELECT SELECT
ti.sku, ti.productSku,
SUM(ti.quantity) AS transitStock SUM(ti.quantity) AS transitStock
FROM transfer_item ti FROM transfer_item ti
JOIN transfer t ON ti.transferId = t.id JOIN transfer t ON ti.transferId = t.id
WHERE t.isCancel = FALSE AND t.isArrived = FALSE WHERE t.isCancel = FALSE AND t.isArrived = FALSE
GROUP BY ti.sku GROUP BY ti.productSku
), ),
b_sales_data_raw As ( b_sales_data_raw As (
SELECT SELECT
sr.sku, sr.productSku,
DATE_FORMAT(sr.createdAt, '%Y-%m') AS month, DATE_FORMAT(sr.createdAt, '%Y-%m') AS month,
SUM(sr.quantityChange) AS sales SUM(sr.quantityChange) AS sales
FROM stock_record sr FROM stock_record sr
JOIN stock_point sp ON sr.stockPointId = sp.id JOIN stock_point sp ON sr.stockPointId = sp.id
WHERE sp.isB WHERE sp.isB
AND sr.createdAt >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01') AND sr.createdAt >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
GROUP BY sr.sku, month GROUP BY sr.productSku, month
), ),
sales_data_raw AS ( sales_data_raw AS (
SELECT SELECT
os.sku AS sku, os.sku AS productSku,
DATE_FORMAT(o.date_paid, '%Y-%m') AS month, DATE_FORMAT(o.date_paid, '%Y-%m') AS month,
SUM(CASE WHEN DAY(o.date_paid) <= 10 THEN os.quantity ELSE 0 END) AS early_sales, SUM(CASE WHEN DAY(o.date_paid) <= 10 THEN os.quantity ELSE 0 END) AS early_sales,
SUM(CASE WHEN DAY(o.date_paid) > 10 AND DAY(o.date_paid) <= 20 THEN os.quantity ELSE 0 END) AS mid_sales, SUM(CASE WHEN DAY(o.date_paid) > 10 AND DAY(o.date_paid) <= 20 THEN os.quantity ELSE 0 END) AS mid_sales,
@ -852,7 +852,7 @@ export class StatisticsService {
), ),
monthly_sales_summary AS ( monthly_sales_summary AS (
SELECT SELECT
sdr.sku, sdr.productSku,
JSON_ARRAYAGG( JSON_ARRAYAGG(
JSON_OBJECT( JSON_OBJECT(
'month', sdr.month, 'month', sdr.month,
@ -863,12 +863,12 @@ export class StatisticsService {
) )
) AS sales_data ) AS sales_data
FROM sales_data_raw sdr FROM sales_data_raw sdr
LEFT JOIN b_sales_data_raw b ON sdr.sku = b.sku AND sdr.month = b.month LEFT JOIN b_sales_data_raw b ON sdr.productSku = b.productSku AND sdr.month = b.month
GROUP BY sdr.sku GROUP BY sdr.productSku
), ),
sales_summary AS ( sales_summary AS (
SELECT SELECT
os.sku AS sku, os.sku AS productSku,
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 30 DAY THEN os.quantity ELSE 0 END) AS last_30_days_sales, SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 30 DAY THEN os.quantity ELSE 0 END) AS last_30_days_sales,
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 15 DAY THEN os.quantity ELSE 0 END) AS last_15_days_sales, SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 15 DAY THEN os.quantity ELSE 0 END) AS last_15_days_sales,
SUM(CASE WHEN DATE_FORMAT(o.date_paid, '%Y-%m') = DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m') THEN os.quantity ELSE 0 END) AS last_month_sales SUM(CASE WHEN DATE_FORMAT(o.date_paid, '%Y-%m') = DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m') THEN os.quantity ELSE 0 END) AS last_month_sales
@ -880,14 +880,14 @@ export class StatisticsService {
), ),
product_name_summary AS ( product_name_summary AS (
SELECT SELECT
p.sku AS sku, p.sku AS productSku,
COALESCE(MAX(os.name), MAX(p.name)) AS productName COALESCE(MAX(os.name), MAX(p.name)) AS productName
FROM product p FROM product p
LEFT JOIN order_sale os ON p.sku = os.sku LEFT JOIN order_sale os ON p.sku = os.sku
GROUP BY p.sku GROUP BY p.sku
) )
SELECT SELECT
ss.sku, ss.productSku,
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock, (COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
ms.sales_data AS monthlySalesData, ms.sales_data AS monthlySalesData,
pns.productName, pns.productName,
@ -900,10 +900,10 @@ export class StatisticsService {
ELSE NULL ELSE NULL
END AS stock_ratio END AS stock_ratio
FROM stock_summary ss FROM stock_summary ss
LEFT JOIN transfer_stock ts ON ss.sku = ts.sku LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
LEFT JOIN monthly_sales_summary ms ON ss.sku = ms.sku LEFT JOIN monthly_sales_summary ms ON ss.productSku = ms.productSku
LEFT JOIN product_name_summary pns ON ss.sku = pns.sku LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
LEFT JOIN sales_summary ssum ON ss.sku = ssum.sku LEFT JOIN sales_summary ssum ON ss.productSku = ssum.productSku
WHERE 1 = 1 WHERE 1 = 1
${nameFilter} ${nameFilter}
ORDER BY ORDER BY

View File

@ -167,7 +167,7 @@ export class StockService {
qb qb
.select([ .select([
'poi.purchaseOrderId AS purchaseOrderId', 'poi.purchaseOrderId AS purchaseOrderId',
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'productName', poi.productName,'sku', poi.sku, 'quantity', poi.quantity, 'price', poi.price)) AS items", "JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'productName', poi.productName,'productSku', poi.productSku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
]) ])
.from(PurchaseOrderItem, 'poi') .from(PurchaseOrderItem, 'poi')
.groupBy('poi.purchaseOrderId'), .groupBy('poi.purchaseOrderId'),
@ -191,7 +191,7 @@ export class StockService {
async hasStockBySku(sku: string): Promise<boolean> { async hasStockBySku(sku: string): Promise<boolean> {
const count = await this.stockModel const count = await this.stockModel
.createQueryBuilder('stock') .createQueryBuilder('stock')
.where('stock.sku = :sku', { sku }) .where('stock.productSku = :sku', { sku })
.andWhere('stock.quantity > 0') .andWhere('stock.quantity > 0')
.getCount(); .getCount();
return count > 0; return count > 0;
@ -217,7 +217,7 @@ export class StockService {
for (const item of items) { for (const item of items) {
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = purchaseOrder.stockPointId; updateStock.stockPointId = purchaseOrder.stockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.IN; updateStock.operationType = StockRecordOperationType.IN;
updateStock.operatorId = userId; updateStock.operatorId = userId;
@ -240,7 +240,7 @@ export class StockService {
// 获取库存列表 // 获取库存列表
async getStocks(query: QueryStockDTO) { async getStocks(query: QueryStockDTO) {
const { current = 1, pageSize = 10, productName, sku } = query; const { current = 1, pageSize = 10, productName, productSku } = query;
const nameKeywords = productName const nameKeywords = productName
? productName.split(' ').filter(Boolean) ? productName.split(' ').filter(Boolean)
: []; : [];
@ -249,31 +249,31 @@ export class StockService {
.createQueryBuilder('stock') .createQueryBuilder('stock')
.select([ .select([
// 'stock.id as id', // 'stock.id as id',
'stock.sku as sku', 'stock.productSku as productSku',
'product.name as productName', 'product.name as productName',
'product.nameCn as productNameCn', 'product.nameCn as productNameCn',
'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint', 'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint',
'MIN(stock.updatedAt) as updatedAt', 'MIN(stock.updatedAt) as updatedAt',
'MAX(stock.createdAt) as createdAt', 'MAX(stock.createdAt) as createdAt',
]) ])
.leftJoin(Product, 'product', 'product.sku = stock.sku') .leftJoin(Product, 'product', 'product.sku = stock.productSku')
.groupBy('stock.sku') .groupBy('stock.productSku')
.addGroupBy('product.name') .addGroupBy('product.name')
.addGroupBy('product.nameCn'); .addGroupBy('product.nameCn');
let totalQueryBuilder = this.stockModel let totalQueryBuilder = this.stockModel
.createQueryBuilder('stock') .createQueryBuilder('stock')
.select('COUNT(DISTINCT stock.sku)', 'count') .select('COUNT(DISTINCT stock.productSku)', 'count')
.leftJoin(Product, 'product', 'product.sku = stock.sku'); .leftJoin(Product, 'product', 'product.sku = stock.productSku');
if (sku) { if (productSku) {
queryBuilder.andWhere('stock.sku = :sku', { sku }); queryBuilder.andWhere('stock.productSku = :productSku', { productSku });
totalQueryBuilder.andWhere('stock.sku = :sku', { sku }); totalQueryBuilder.andWhere('stock.productSku = :productSku', { productSku });
} }
if (nameKeywords.length) { if (nameKeywords.length) {
nameKeywords.forEach((name, index) => { nameKeywords.forEach((name, index) => {
queryBuilder.andWhere( queryBuilder.andWhere(
`EXISTS ( `EXISTS (
SELECT 1 FROM product p SELECT 1 FROM product p
WHERE p.sku = stock.sku WHERE p.sku = stock.productSku
AND p.name LIKE :name${index} AND p.name LIKE :name${index}
)`, )`,
{ [`name${index}`]: `%${name}%` } { [`name${index}`]: `%${name}%` }
@ -281,7 +281,7 @@ export class StockService {
totalQueryBuilder.andWhere( totalQueryBuilder.andWhere(
`EXISTS ( `EXISTS (
SELECT 1 FROM product p SELECT 1 FROM product p
WHERE p.sku = stock.sku WHERE p.sku = stock.productSku
AND p.name LIKE :name${index} AND p.name LIKE :name${index}
)`, )`,
{ [`name${index}`]: `%${name}%` } { [`name${index}`]: `%${name}%` }
@ -291,7 +291,7 @@ export class StockService {
if (query.order) { if (query.order) {
const sortFieldMap: Record<string, string> = { const sortFieldMap: Record<string, string> = {
productName: 'product.name', productName: 'product.name',
sku: 'stock.sku', productSku: 'stock.productSku',
updatedAt: 'updatedAt', updatedAt: 'updatedAt',
createdAt: 'createdAt', createdAt: 'createdAt',
}; };
@ -335,15 +335,15 @@ export class StockService {
const transfer = await this.transferModel const transfer = await this.transferModel
.createQueryBuilder('t') .createQueryBuilder('t')
.select(['ti.sku as sku', 'SUM(ti.quantity) as quantity']) .select(['ti.productSku as productSku', 'SUM(ti.quantity) as quantity'])
.leftJoin(TransferItem, 'ti', 'ti.transferId = t.id') .leftJoin(TransferItem, 'ti', 'ti.transferId = t.id')
.where('!t.isArrived and !t.isCancel and !t.isLost') .where('!t.isArrived and !t.isCancel and !t.isLost')
.groupBy('ti.sku') .groupBy('ti.productSku')
.getRawMany(); .getRawMany();
for (const item of items) { for (const item of items) {
item.inTransitQuantity = item.inTransitQuantity =
transfer.find(t => t.sku === item.sku)?.quantity || 0; transfer.find(t => t.productSku === item.productSku)?.quantity || 0;
} }
return { return {
@ -361,10 +361,10 @@ export class StockService {
const stocks = await this.stockModel const stocks = await this.stockModel
.createQueryBuilder('stock') .createQueryBuilder('stock')
.select('stock.sku', 'sku') .select('stock.productSku', 'productSku')
.addSelect('SUM(stock.quantity)', 'totalQuantity') .addSelect('SUM(stock.quantity)', 'totalQuantity')
.where('stock.sku IN (:...skus)', { skus }) .where('stock.productSku IN (:...skus)', { skus })
.groupBy('stock.sku') .groupBy('stock.productSku')
.getRawMany(); .getRawMany();
return stocks; return stocks;
@ -374,7 +374,7 @@ export class StockService {
async updateStock(data: UpdateStockDTO) { async updateStock(data: UpdateStockDTO) {
const { const {
stockPointId, stockPointId,
sku, productSku,
quantityChange, quantityChange,
operationType, operationType,
operatorId, operatorId,
@ -383,13 +383,13 @@ export class StockService {
const stock = await this.stockModel.findOneBy({ const stock = await this.stockModel.findOneBy({
stockPointId, stockPointId,
sku, productSku,
}); });
if (!stock) { if (!stock) {
// 如果库存不存在,则直接新增 // 如果库存不存在,则直接新增
const newStock = this.stockModel.create({ const newStock = this.stockModel.create({
stockPointId, stockPointId,
sku, productSku,
quantity: operationType === 'in' ? quantityChange : -quantityChange, quantity: operationType === 'in' ? quantityChange : -quantityChange,
}); });
await this.stockModel.save(newStock); await this.stockModel.save(newStock);
@ -406,7 +406,7 @@ export class StockService {
// 记录库存变更日志 // 记录库存变更日志
const stockRecord = this.stockRecordModel.create({ const stockRecord = this.stockRecordModel.create({
stockPointId, stockPointId,
sku, productSku,
operationType, operationType,
quantityChange, quantityChange,
operatorId, operatorId,
@ -421,7 +421,7 @@ export class StockService {
current = 1, current = 1,
pageSize = 10, pageSize = 10,
stockPointId, stockPointId,
sku, productSku,
productName, productName,
operationType, operationType,
startDate, startDate,
@ -430,14 +430,14 @@ export class StockService {
const where: any = {}; const where: any = {};
if (stockPointId) where.stockPointId = stockPointId; if (stockPointId) where.stockPointId = stockPointId;
if (sku) where.sku = sku; if (productSku) where.productSku = productSku;
if (operationType) where.operationType = operationType; if (operationType) where.operationType = operationType;
if (startDate) where.createdAt = MoreThan(startDate); if (startDate) where.createdAt = MoreThan(startDate);
if (endDate) where.createdAt = LessThan(endDate); if (endDate) where.createdAt = LessThan(endDate);
if (startDate && endDate) where.createdAt = Between(startDate, endDate); if (startDate && endDate) where.createdAt = Between(startDate, endDate);
const queryBuilder = this.stockRecordModel const queryBuilder = this.stockRecordModel
.createQueryBuilder('stock_record') .createQueryBuilder('stock_record')
.leftJoin(Product, 'product', 'product.sku = stock_record.sku') .leftJoin(Product, 'product', 'product.sku = stock_record.productSku')
.leftJoin(User, 'user', 'stock_record.operatorId = user.id') .leftJoin(User, 'user', 'stock_record.operatorId = user.id')
.leftJoin(StockPoint, 'sp', 'sp.id = stock_record.stockPointId') .leftJoin(StockPoint, 'sp', 'sp.id = stock_record.stockPointId')
.select([ .select([
@ -470,7 +470,7 @@ export class StockService {
// for (const item of items) { // for (const item of items) {
// const stock = await this.stockModel.findOneBy({ // const stock = await this.stockModel.findOneBy({
// stockPointId: sourceStockPointId, // stockPointId: sourceStockPointId,
// sku: item.sku, // productSku: item.productSku,
// }); // });
// if (!stock || stock.quantity < item.quantity) // if (!stock || stock.quantity < item.quantity)
// throw new Error(`${item.productName} 库存不足`); // throw new Error(`${item.productName} 库存不足`);
@ -496,7 +496,7 @@ export class StockService {
item.transferId = transfer.id; item.transferId = transfer.id;
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = sourceStockPointId; updateStock.stockPointId = sourceStockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.OUT; updateStock.operationType = StockRecordOperationType.OUT;
updateStock.operatorId = userId; updateStock.operatorId = userId;
@ -530,7 +530,7 @@ export class StockService {
qb qb
.select([ .select([
'ti.transferId AS transferId', 'ti.transferId AS transferId',
"JSON_ARRAYAGG(JSON_OBJECT('id', ti.id, 'productName', ti.productName,'sku', ti.sku, 'quantity', ti.quantity)) AS items", "JSON_ARRAYAGG(JSON_OBJECT('id', ti.id, 'productName', ti.productName,'productSku', ti.productSku, 'quantity', ti.quantity)) AS items",
]) ])
.from(TransferItem, 'ti') .from(TransferItem, 'ti')
.groupBy('ti.transferId'), .groupBy('ti.transferId'),
@ -561,7 +561,7 @@ export class StockService {
for (const item of items) { for (const item of items) {
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = transfer.sourceStockPointId; updateStock.stockPointId = transfer.sourceStockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.IN; updateStock.operationType = StockRecordOperationType.IN;
updateStock.operatorId = userId; updateStock.operatorId = userId;
@ -584,7 +584,7 @@ export class StockService {
for (const item of items) { for (const item of items) {
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = transfer.destStockPointId; updateStock.stockPointId = transfer.destStockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.IN; updateStock.operationType = StockRecordOperationType.IN;
updateStock.operatorId = userId; updateStock.operatorId = userId;
@ -619,7 +619,7 @@ export class StockService {
item.transferId = transfer.id; item.transferId = transfer.id;
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = sourceStockPointId; updateStock.stockPointId = sourceStockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.IN; updateStock.operationType = StockRecordOperationType.IN;
updateStock.operatorId = userId; updateStock.operatorId = userId;
@ -632,7 +632,7 @@ export class StockService {
item.transferId = transfer.id; item.transferId = transfer.id;
const updateStock = new UpdateStockDTO(); const updateStock = new UpdateStockDTO();
updateStock.stockPointId = sourceStockPointId; updateStock.stockPointId = sourceStockPointId;
updateStock.sku = item.sku; updateStock.productSku = item.productSku;
updateStock.quantityChange = item.quantity; updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.OUT; updateStock.operationType = StockRecordOperationType.OUT;
updateStock.operatorId = userId; updateStock.operatorId = userId;

View File

@ -19,7 +19,7 @@ export class SubscriptionService {
* *
* - WooCommerce / * - WooCommerce /
*/ */
async syncSubscriptions(siteId: number) { async syncSubscriptions(siteId: string) {
const subs = await this.wpService.getSubscriptions(siteId); const subs = await this.wpService.getSubscriptions(siteId);
for (const sub of subs) { for (const sub of subs) {
await this.syncSingleSubscription(siteId, sub); await this.syncSingleSubscription(siteId, sub);
@ -31,7 +31,7 @@ export class SubscriptionService {
* - externalSubscriptionId * - externalSubscriptionId
* - * -
*/ */
async syncSingleSubscription(siteId: number, sub: any) { async syncSingleSubscription(siteId: string, sub: any) {
const { line_items, ...raw } = sub; const { line_items, ...raw } = sub;
const entity: Partial<Subscription> = { const entity: Partial<Subscription> = {
...raw, ...raw,
@ -43,7 +43,7 @@ export class SubscriptionService {
}; };
delete (entity as any).id; delete (entity as any).id;
const existing = await this.subscriptionModel.findOne({ const existing = await this.subscriptionModel.findOne({
where: { externalSubscriptionId: String(sub.id), siteId: siteId }, where: { externalSubscriptionId: String(sub.id), siteId },
}); });
const saveEntity = plainToClass(Subscription, entity); const saveEntity = plainToClass(Subscription, entity);
if (existing) { if (existing) {

View File

@ -186,7 +186,7 @@ export class WPService {
const res = await api.get(`orders/${orderId}`); const res = await api.get(`orders/${orderId}`);
return res.data as Record<string, any>; return res.data as Record<string, any>;
} }
async getOrders(siteId: number): Promise<Record<string, any>[]> { async getOrders(siteId: string): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId); const site = await this.siteService.get(siteId);
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
return await this.sdkGetAll<Record<string, any>>(api, 'orders'); return await this.sdkGetAll<Record<string, any>>(api, 'orders');
@ -197,7 +197,7 @@ export class WPService {
* wc/v1/subscriptionsSubscriptions 退 wc/v3/subscriptions * wc/v1/subscriptionsSubscriptions 退 wc/v3/subscriptions
* *
*/ */
async getSubscriptions(siteId: number): Promise<Record<string, any>[]> { async getSubscriptions(siteId: string): Promise<Record<string, any>[]> {
const site = await this.siteService.get(siteId); const site = await this.siteService.get(siteId);
// 优先使用 Subscriptions 命名空间 wcs/v1失败回退 wc/v3 // 优先使用 Subscriptions 命名空间 wcs/v1失败回退 wc/v3
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');

View File

@ -40,21 +40,21 @@ export class WpProductService {
product.type === 'variable' product.type === 'variable'
? await this.wpApiService.getVariations(site, product.id) ? 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: number) { async syncSite(siteId: string) {
// 通过数据库获取站点并转换为 WpSite用于后续 WooCommerce 同步 // 通过数据库获取站点并转换为 WpSite用于后续 WooCommerce 同步
const site = await this.siteService.get(siteId, true); const site = await this.siteService.get(Number(siteId), true);
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product') const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
.select([ .select([
'wp_product.id ', 'wp_product.id ',
'wp_product.externalProductId ', 'wp_product.externalProductId ',
]) ])
.where('wp_product.siteId = :siteId', { .where('wp_product.siteId = :siteIds ', {
siteId, siteIds: siteId,
}) })
const rawResult = await externalProductIds.getRawMany(); const rawResult = await externalProductIds.getRawMany();
@ -70,7 +70,7 @@ export class WpProductService {
? await this.wpApiService.getVariations(site, product.id) ? 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)); const filteredIds = externalIds.filter(id => !excludeValues.includes(id));
@ -78,13 +78,13 @@ export class WpProductService {
await this.variationModel.createQueryBuilder('variation') await this.variationModel.createQueryBuilder('variation')
.update() .update()
.set({ on_delete: true }) .set({ on_delete: true })
.where('variation.siteId = :siteId AND variation.externalProductId IN (:...filteredId)', { siteId, filteredId: filteredIds }) .where(" variation.externalProductId in (:...filteredId) ", { filteredId: filteredIds })
.execute(); .execute();
this.wpProductModel.createQueryBuilder('wp_product') this.wpProductModel.createQueryBuilder('wp_product')
.update() .update()
.set({ on_delete: true }) .set({ on_delete: true })
.where('wp_product.siteId = :siteId AND wp_product.externalProductId IN (:...filteredId)', { siteId, filteredId: filteredIds }) .where(" wp_product.externalProductId in (:...filteredId) ", { filteredId: filteredIds })
.execute(); .execute();
} }
} }
@ -92,7 +92,7 @@ export class WpProductService {
// 控制产品上下架 // 控制产品上下架
async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) { async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) {
const wpProduct = await this.wpProductModel.findOneBy({ id }); const wpProduct = await this.wpProductModel.findOneBy({ id });
const site = await this.siteService.get(wpProduct.siteId, true); const site = await this.siteService.get(Number(wpProduct.siteId), true);
wpProduct.status = status; wpProduct.status = status;
wpProduct.stockStatus = stock_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);
@ -105,7 +105,7 @@ export class WpProductService {
} }
async findProduct( async findProduct(
siteId: number, siteId: string,
externalProductId: string externalProductId: string
): Promise<WpProduct | null> { ): Promise<WpProduct | null> {
return await this.wpProductModel.findOne({ return await this.wpProductModel.findOne({
@ -114,17 +114,17 @@ export class WpProductService {
} }
async findVariation( async findVariation(
siteId: number, siteId: string,
externalProductId: string, externalProductId: string,
externalVariationId: string externalVariationId: string
): Promise<Variation | null> { ): Promise<Variation | null> {
return await this.variationModel.findOne({ return await this.variationModel.findOne({
where: { siteId, externalProductId, externalVariationId, on_delete: false }, where: { siteId, externalProductId, externalVariationId },
}); });
} }
async updateWpProduct( async updateWpProduct(
siteId: number, siteId: string,
productId: string, productId: string,
product: UpdateWpProductDTO product: UpdateWpProductDTO
) { ) {
@ -140,7 +140,7 @@ export class WpProductService {
} }
async updateWpProductVaritation( async updateWpProductVaritation(
siteId: number, siteId: string,
productId: string, productId: string,
variationId: string, variationId: string,
variation: UpdateVariationDTO variation: UpdateVariationDTO
@ -163,7 +163,7 @@ export class WpProductService {
} }
async syncProductAndVariations( async syncProductAndVariations(
siteId: number, siteId: string,
product: WpProduct, product: WpProduct,
variations: Variation[] variations: Variation[]
) { ) {
@ -180,8 +180,6 @@ export class WpProductService {
product.sale_price && (existingProduct.sale_price = product.sale_price); product.sale_price && (existingProduct.sale_price = product.sale_price);
existingProduct.on_sale = product.on_sale; existingProduct.on_sale = product.on_sale;
existingProduct.metadata = product.metadata; existingProduct.metadata = product.metadata;
existingProduct.tags = product.tags;
existingProduct.categories = product.categories;
await this.wpProductModel.save(existingProduct); await this.wpProductModel.save(existingProduct);
} else { } else {
existingProduct = this.wpProductModel.create({ existingProduct = this.wpProductModel.create({
@ -197,8 +195,6 @@ export class WpProductService {
...(product.sale_price ? { sale_price: product.sale_price } : {}), ...(product.sale_price ? { sale_price: product.sale_price } : {}),
on_sale: product.on_sale, on_sale: product.on_sale,
metadata: product.metadata, metadata: product.metadata,
tags: product.tags,
categories: product.categories,
}); });
await this.wpProductModel.save(existingProduct); await this.wpProductModel.save(existingProduct);
} }
@ -206,7 +202,7 @@ export class WpProductService {
// 2. 处理变体同步 // 2. 处理变体同步
if (product.type === 'variable') { if (product.type === 'variable') {
const currentVariations = await this.variationModel.find({ const currentVariations = await this.variationModel.find({
where: { siteId, externalProductId: String(product.id), on_delete: false }, where: { siteId, externalProductId: String(product.id) },
}); });
const syncedVariationIds = new Set(variations.map(v => String(v.id))); const syncedVariationIds = new Set(variations.map(v => String(v.id)));
const variationsToDelete = currentVariations.filter( const variationsToDelete = currentVariations.filter(
@ -256,23 +252,21 @@ export class WpProductService {
} }
} else { } else {
// 清理之前的变体 // 清理之前的变体
await this.variationModel.update( await this.variationModel.delete({
{ siteId, externalProductId: String(product.id) }, siteId,
{ on_delete: true } externalProductId: String(product.id),
); });
} }
} }
async syncVariation(siteId: number, productId: string, variation: Variation) { async syncVariation(siteId: string, productId: string, variation: Variation) {
let existingProduct = await this.findProduct(siteId, String(productId)); let existingProduct = await this.findProduct(siteId, String(productId));
if (!existingProduct) return; if (!existingProduct) return;
const existingVariation = await this.variationModel.findOne({ const existingVariation = await this.findVariation(
where: {
siteId, siteId,
externalProductId: String(productId), String(productId),
externalVariationId: String(variation.id), String(variation.id)
}, );
});
if (existingVariation) { if (existingVariation) {
existingVariation.name = variation.name; existingVariation.name = variation.name;
@ -320,7 +314,6 @@ export class WpProductService {
where.on_delete = false; where.on_delete = false;
const products = await this.wpProductModel.find({ const products = await this.wpProductModel.find({
relations: ['site'],
where, where,
skip: (current - 1) * pageSize, skip: (current - 1) * pageSize,
take: pageSize, take: pageSize,
@ -435,7 +428,7 @@ export class WpProductService {
*/ */
async isSkuDuplicate( async isSkuDuplicate(
sku: string, sku: string,
excludeSiteId?: number, excludeSiteId?: string,
excludeProductId?: string, excludeProductId?: string,
excludeVariationId?: string excludeVariationId?: string
): Promise<boolean> { ): Promise<boolean> {
@ -446,8 +439,8 @@ export class WpProductService {
varWhere.siteId = Not(excludeSiteId); varWhere.siteId = Not(excludeSiteId);
varWhere.externalProductId = Not(excludeProductId); varWhere.externalProductId = Not(excludeProductId);
varWhere.externalVariationId = Not(excludeVariationId); varWhere.externalVariationId = Not(excludeVariationId);
} else if (excludeProductId) { } else {
where.siteId = Not(excludeSiteId); where.externalProductId = Not(excludeProductId);
where.externalProductId = Not(excludeProductId); where.externalProductId = Not(excludeProductId);
} }
@ -493,7 +486,7 @@ export class WpProductService {
} }
} }
async delWpProduct(siteId: number, productId: string) { async delWpProduct(siteId: string, productId: string) {
const product = await this.wpProductModel.findOne({ const product = await this.wpProductModel.findOne({
where: { siteId, externalProductId: productId }, where: { siteId, externalProductId: productId },
}); });
@ -502,13 +495,13 @@ export class WpProductService {
await this.variationModel.createQueryBuilder('variation') await this.variationModel.createQueryBuilder('variation')
.update() .update()
.set({ on_delete: true }) .set({ on_delete: true })
.where('variation.siteId = :siteId AND variation.externalProductId = :externalProductId', { siteId, externalProductId: productId }) .where(" variation.externalProductId = :externalProductId ", { externalProductId: productId })
.execute(); .execute();
const sums = await this.wpProductModel.createQueryBuilder('wp_product') const sums = await this.wpProductModel.createQueryBuilder('wp_product')
.update() .update()
.set({ on_delete: true }) .set({ on_delete: true })
.where('wp_product.siteId = :siteId AND wp_product.externalProductId = :externalProductId', { siteId, externalProductId: productId }) .where(" wp_product.externalProductId = :externalProductId ", { externalProductId: productId })
.execute(); .execute();
console.log(sums); console.log(sums);