Compare commits
No commits in common. "e4fc195b8d6e5ff69d79481e67fc6c8730e2af8f" and "a5996363c868e4f92a7dfed60c1a7bfefa00fce0" have entirely different histories.
e4fc195b8d
...
a5996363c8
24
output.log
24
output.log
|
|
@ -1,24 +0,0 @@
|
||||||
|
|
||||||
> my-midway-project@1.0.0 dev
|
|
||||||
> cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js
|
|
||||||
|
|
||||||
[2J[3J[H
|
|
||||||
[[90m10:37:17 AM[0m] Starting compilation in watch mode...
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[90m10:37:19 AM[0m] 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
|
|
||||||
|
|
||||||
[32mNode.js server[0m [2mstarted in[0m 732 ms
|
|
||||||
|
|
||||||
[32m➜[0m Local: [36mhttp://127.0.0.1:[1m7001[0m[36m/[0m[0m
|
|
||||||
[32m➜[0m [2mNetwork: http://192.168.5.100:7001/ [0m
|
|
||||||
|
|
||||||
2025-12-01 10:37:20.110 INFO 58678 [SyncShipmentJob] complete job SyncShipmentJob
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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':
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(''))
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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' })
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,19 +1501,19 @@ 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,
|
||||||
sku: sale.sku,
|
sku: sale.sku,
|
||||||
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'],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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/subscriptions(Subscriptions 插件提供),失败时回退 wc/v3/subscriptions。
|
* 优先尝试 wc/v1/subscriptions(Subscriptions 插件提供),失败时回退 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');
|
||||||
|
|
|
||||||
|
|
@ -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,
|
String(productId),
|
||||||
externalProductId: String(productId),
|
String(variation.id)
|
||||||
externalVariationId: 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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue