API/src/service/order.service.ts

1696 lines
55 KiB
TypeScript

import { Inject, Provide } from '@midwayjs/core';
import { WPService } from './wp.service';
import { Order } from '../entity/order.entity';
import { In, Like, Repository } from 'typeorm';
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { plainToClass } from 'class-transformer';
import { OrderItem } from '../entity/order_item.entity';
import { OrderSale } from '../entity/order_sale.entity';
import { Product } from '../entity/product.entity';
import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity';
import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderCoupon } from '../entity/order_coupon.entity';
import { OrderShipping } from '../entity/order_shipping.entity';
import { Shipment } from '../entity/shipment.entity';
import { Customer } from '../entity/customer.entity';
import {
ErpOrderStatus,
OrderStatus,
StockRecordOperationType,
} from '../enums/base.enum';
import { CreateOrderNoteDTO, QueryOrderSalesDTO } from '../dto/order.dto';
import dayjs = require('dayjs');
import { OrderDetailRes } from '../dto/reponse.dto';
import { OrderNote } from '../entity/order_note.entity';
import { User } from '../entity/user.entity';
import { SiteService } from './site.service';
import { ShipmentItem } from '../entity/shipment_item.entity';
import { UpdateStockDTO } from '../dto/stock.dto';
import { StockService } from './stock.service';
import { OrderItemOriginal } from '../entity/order_item_original.entity';
@Provide()
export class OrderService {
@Inject()
wpService: WPService;
@Inject()
stockService: StockService;
@InjectEntityModel(Order)
orderModel: Repository<Order>;
@InjectEntityModel(User)
userModel: Repository<User>;
@InjectEntityModel(OrderItem)
orderItemModel: Repository<OrderItem>;
@InjectEntityModel(OrderItem)
orderItemOriginalModel: Repository<OrderItemOriginal>;
@InjectEntityModel(OrderSale)
orderSaleModel: Repository<OrderSale>;
@InjectEntityModel(Product)
productModel: Repository<Product>;
@InjectEntityModel(OrderFee)
orderFeeModel: Repository<OrderFee>;
@InjectEntityModel(OrderRefund)
orderRefundModel: Repository<OrderRefund>;
@InjectEntityModel(OrderRefundItem)
orderRefundItemModel: Repository<OrderRefundItem>;
@InjectEntityModel(OrderCoupon)
orderCouponModel: Repository<OrderCoupon>;
@InjectEntityModel(OrderShipping)
orderShippingModel: Repository<OrderShipping>;
@InjectEntityModel(Shipment)
shipmentModel: Repository<Shipment>;
@InjectEntityModel(ShipmentItem)
shipmentItemModel: Repository<ShipmentItem>;
@InjectEntityModel(OrderNote)
orderNoteModel: Repository<OrderNote>;
@Inject()
dataSourceManager: TypeORMDataSourceManager;
@InjectEntityModel(Customer)
customerModel: Repository<Customer>;
@Inject()
siteService: SiteService;
async syncOrders(siteId: number, params: Record<string, any> = {}) {
// 调用 WooCommerce API 获取订单
const orders = await this.wpService.getOrders(siteId, params);
let successCount = 0;
let failureCount = 0;
for (const order of orders) {
try {
await this.syncSingleOrder(siteId, order);
successCount++;
} catch (error) {
console.error(`同步订单 ${order.id} 失败:`, error);
failureCount++;
}
}
return {
success: failureCount === 0,
successCount,
failureCount,
message: `同步完成: 成功 ${successCount}, 失败 ${failureCount}`,
};
}
async syncOrderById(siteId: number, orderId: string) {
try {
// 调用 WooCommerce API 获取订单
const order = await this.wpService.getOrder(siteId, orderId);
await this.syncSingleOrder(siteId, order, true);
return { success: true, message: '同步成功' };
} catch (error) {
console.error(`同步订单 ${orderId} 失败:`, error);
return { success: false, message: `同步失败: ${error.message}` };
}
}
// 订单状态切换表
orderAutoNextStatusMap = {
[OrderStatus.RETURN_APPROVED]: OrderStatus.ON_HOLD, // 退款申请已通过转为 on-hold
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
}
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
async autoUpdateOrderStatus(siteId: number, order: any) {
console.log('更新订单状态', order)
// 其他状态保持不变
const originStatus = order.status;
// 如果有值就赋值
if (!this.orderAutoNextStatusMap[originStatus]) {
return
}
try {
const site = await this.siteService.get(siteId);
// 将订单状态同步到 WooCommerce,然后切换至下一状态
await this.wpService.updateOrder(site, String(order.id), { status: order.status });
order.status = this.orderAutoNextStatusMap[originStatus];
} catch (error) {
console.error('更新订单状态失败,原因为:', error)
}
}
// wordpress 发来,
async syncSingleOrder(siteId: number, order: any, forceUpdate = false) {
let {
line_items,
shipping_lines,
fee_lines,
coupon_lines,
refunds,
...orderData
} = order;
console.log('同步进单个订单', order)
const existingOrder = await this.orderModel.findOne({
where: { externalOrderId: order.id, siteId: siteId },
});
// 矫正状态
await this.autoUpdateOrderStatus(siteId, order);
if (order.status === OrderStatus.AUTO_DRAFT) {
return;
}
// 更新订单
if (existingOrder) {
await this.orderModel.update({ id: existingOrder.id }, { orderStatus: this.mapOrderStatus(order.status) });
}
const externalOrderId = order.id;
if (
existingOrder &&
existingOrder.orderStatus !== ErpOrderStatus.COMPLETED &&
orderData.status === OrderStatus.COMPLETED
) {
this.updateStock(existingOrder);
return;
}
if (existingOrder && !existingOrder.is_editable && !forceUpdate) {
return;
}
const orderRecord = await this.saveOrder(siteId, orderData);
const orderId = orderRecord.id;
await this.saveOrderItems({
siteId,
orderId,
externalOrderId,
orderItems: line_items,
});
await this.saveOrderRefunds({
siteId,
orderId,
externalOrderId,
refunds,
});
await this.saveOrderFees({
siteId,
orderId,
externalOrderId,
fee_lines,
});
await this.saveOrderCoupons({
siteId,
orderId,
externalOrderId,
coupon_lines,
});
await this.saveOrderShipping({
siteId,
orderId,
externalOrderId,
shipping_lines,
});
}
async updateStock(existingOrder: Order) {
const items = await this.orderSaleModel.find({
where: { orderId: existingOrder.id },
});
if (!items) return;
const stockPointId = 2;
// ['YT', 'NT', 'BC', 'AB', 'SK'].some(
// v =>
// v.toLowerCase() ===
// (
// existingOrder?.shipping?.state || existingOrder?.billing?.state
// ).toLowerCase()
// )
// ? 3
// : 2;
for (const item of items) {
const updateStock = new UpdateStockDTO();
updateStock.stockPointId = stockPointId;
updateStock.sku = item.sku;
updateStock.quantityChange = item.quantity;
updateStock.operationType = StockRecordOperationType.OUT;
updateStock.operatorId = 1;
updateStock.note = `订单${existingOrder.externalOrderId} 出库`;
await this.stockService.updateStock(updateStock);
}
}
async saveOrder(siteId: number, order: Order): Promise<Order> {
order.externalOrderId = String(order.id);
order.siteId = siteId;
delete order.id;
order.device_type =
order?.meta_data?.find(
el => el.key === '_wc_order_attribution_device_type'
)?.value || '';
order.source_type =
order?.meta_data?.find(
el => el.key === '_wc_order_attribution_source_type'
)?.value || '';
order.utm_source =
order?.meta_data?.find(
el => el.key === '_wc_order_attribution_utm_source'
)?.value || '';
order.customer_email = order?.billing?.email || order?.shipping?.email;
// order.billing_phone = order?.billing?.phone || order?.shipping?.phone;
const entity = plainToClass(Order, order);
const existingOrder = await this.orderModel.findOne({
where: { externalOrderId: order.externalOrderId, siteId: siteId },
});
if (existingOrder) {
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
entity.orderStatus = this.mapOrderStatus(entity.status);
}
await this.orderModel.update(existingOrder.id, entity);
entity.id = existingOrder.id;
return entity;
}
entity.orderStatus = this.mapOrderStatus(entity.status);
const customer = await this.customerModel.findOne({
where: { email: order.customer_email },
});
if (!customer) {
await this.customerModel.save({
email: order.customer_email,
rate: 0,
});
}
return await this.orderModel.save(entity);
}
canUpdateErpStatus(currentErpStatus: string): boolean {
const nonOverridableStatuses = [
'AFTER_SALE_PROCESSING',
'PENDING_RESHIPMENT',
'PENDING_REFUND',
];
// 如果当前 ERP 状态不可覆盖,则禁止更新
return !nonOverridableStatuses.includes(currentErpStatus);
}
mapOrderStatus(status: OrderStatus): ErpOrderStatus {
switch (status) {
case OrderStatus.PENDING:
return ErpOrderStatus.PENDING;
case OrderStatus.PROCESSING:
return ErpOrderStatus.PROCESSING;
case OrderStatus.COMPLETED:
return ErpOrderStatus.COMPLETED;
case OrderStatus.CANCEL:
return ErpOrderStatus.CANCEL;
case OrderStatus.REFUNDED:
return ErpOrderStatus.REFUNDED;
case OrderStatus.FAILED:
return ErpOrderStatus.FAILED;
case OrderStatus.RETURN_REQUESTED:
return ErpOrderStatus.RETURN_REQUESTED;
case OrderStatus.RETURN_APPROVED:
return ErpOrderStatus.RETURN_APPROVED;
case OrderStatus.RETURN_CANCELLED:
return ErpOrderStatus.RETURN_CANCELLED;
default:
return ErpOrderStatus.PENDING;
}
}
async saveOrderItems(params: {
siteId: number;
orderId: number;
externalOrderId: string;
orderItems: Record<string, any>[];
}) {
console.log('saveOrderItems params',params)
const { siteId, orderId, externalOrderId, orderItems } = params;
const currentOrderItems = await this.orderItemModel.find({
where: { siteId, externalOrderId: externalOrderId },
});
const syncedOrderItemIds = new Set(orderItems.map(v => String(v.id)));
const orderItemToDelete = currentOrderItems.filter(
db => !syncedOrderItemIds.has(String(db.externalOrderItemId))
);
if (orderItemToDelete.length > 0) {
const idsToDelete = orderItemToDelete.map(v => v.id);
await this.orderItemModel.delete(idsToDelete);
const itemIdsToDelete = orderItemToDelete.map(v => v.externalOrderItemId);
await this.orderSaleModel.delete({
siteId,
externalOrderItemId: In(itemIdsToDelete),
});
}
for (const orderItem of orderItems) {
orderItem.siteId = siteId;
orderItem.externalOrderId = externalOrderId;
orderItem.externalOrderItemId = String(orderItem.id);
orderItem.orderId = orderId;
orderItem.externalProductId = String(orderItem.product_id);
orderItem.externalVariationId = String(orderItem.variation_id);
delete orderItem.id;
const entity = plainToClass(OrderItem, orderItem);
await this.saveOrderItem(entity);
await this.saveOrderSale(entity);
}
}
async saveOrderItem(orderItem: OrderItem) {
const existingOrderItem = await this.orderItemModel.findOne({
where: {
externalOrderId: orderItem.externalOrderId,
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
},
});
if (
existingOrderItem &&
existingOrderItem.quantity === orderItem.quantity
) {
return;
}
if (existingOrderItem) {
await this.orderItemModel.update(existingOrderItem.id, orderItem);
} else {
await this.orderItemModel.save(orderItem);
}
}
async saveOrderItemsG(orderItem: OrderItem) {
const existingOrderItem = await this.orderItemModel.findOne({
where: {
externalOrderId: orderItem.externalOrderId,
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
},
});
if (
existingOrderItem &&
existingOrderItem.quantity === orderItem.quantity
) {
return;
}
if (existingOrderItem) {
await this.orderItemModel.update(existingOrderItem.id, orderItem);
} else {
await this.orderItemModel.save(orderItem);
}
}
async saveOrderSale(orderItem: OrderItem) {
const currentOrderSale = await this.orderSaleModel.find({
where: {
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
},
});
if (currentOrderSale.length > 0) {
await this.orderSaleModel.delete(currentOrderSale.map(v => v.id));
}
if (!orderItem.sku) return;
const product = await this.productModel.findOne({
where: { sku: orderItem.sku },
relations: ['components'],
});
if (!product) return;
const orderSales: OrderSale[] = [];
if (product.components && product.components.length > 0) {
for (const comp of product.components) {
const baseProduct = await this.productModel.findOne({
where: { sku: comp.sku },
});
if (baseProduct) {
const orderSaleItem: OrderSale = plainToClass(OrderSale, {
orderId: orderItem.orderId,
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
productId: baseProduct.id,
name: baseProduct.name,
quantity: comp.quantity * orderItem.quantity,
sku: comp.sku,
isPackage: orderItem.name.toLowerCase().includes('package'),
});
orderSales.push(orderSaleItem);
}
}
} else {
const orderSaleItem: OrderSale = plainToClass(OrderSale, {
orderId: orderItem.orderId,
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
productId: product.id,
name: product.name,
quantity: orderItem.quantity,
sku: product.sku,
isPackage: orderItem.name.toLowerCase().includes('package'),
});
orderSales.push(orderSaleItem);
}
if (orderSales.length > 0) {
await this.orderSaleModel.save(orderSales);
}
}
async saveOrderRefunds({
siteId,
orderId,
externalOrderId,
refunds,
}: {
siteId: number;
orderId: number;
externalOrderId: string;
refunds: Record<string, any>[];
}) {
for (const item of refunds) {
const refund = await this.wpService.getOrderRefund(
String(siteId),
externalOrderId,
item.id
);
const refundItems = refund.line_items;
refund.orderId = orderId;
refund.siteId = siteId;
refund.externalOrderId = externalOrderId;
refund.externalRefundId = item.id;
delete refund.id;
const entity = plainToClass(OrderRefund, refund);
const existingOrderRefund = await this.orderRefundModel.findOne({
where: {
siteId,
externalOrderId: externalOrderId,
externalRefundId: item.id,
},
});
let refundId;
if (existingOrderRefund) {
await this.orderRefundModel.update(existingOrderRefund.id, entity);
refundId = existingOrderRefund.id;
} else {
refundId = (await this.orderRefundModel.save(entity)).id;
}
this.saveOrderRefundItems({
refundItems,
siteId,
refundId,
externalRefundId: item.id,
});
}
}
async saveOrderRefundItems({
refundItems,
siteId,
refundId,
externalRefundId,
}) {
for (const item of refundItems) {
item.externalRefundItemId = item.id;
item.externalProductId = item.product_id;
item.externalVariationId = item.variation_id;
delete item.id;
const refundItem = plainToClass(OrderRefundItem, {
refundId,
siteId,
externalRefundId,
...item,
});
const existingOrderRefundItem = await this.orderRefundItemModel.findOne({
where: {
siteId,
externalRefundId,
externalRefundItemId: refundItem.externalRefundItemId,
},
});
if (existingOrderRefundItem) {
await this.orderRefundItemModel.update(
existingOrderRefundItem.id,
refundItem
);
} else {
await this.orderRefundItemModel.save(refundItem);
}
}
}
async saveOrderFees({ siteId, orderId, externalOrderId, fee_lines }) {
const currentOrderFees = await this.orderFeeModel.find({
where: { siteId, externalOrderId },
});
const syncedFeeIds = new Set(fee_lines.map(v => String(v.id)));
const toDeleteIds = currentOrderFees
.filter(db => !syncedFeeIds.has(db.externalOrderFeeId))
.map(db => db.id);
if (toDeleteIds.length > 0) {
await this.orderFeeModel.delete(toDeleteIds);
}
for (const fee of fee_lines) {
fee.externalOrderFeeId = String(fee.id);
delete fee.id;
const entity = plainToClass(OrderFee, {
siteId,
orderId,
externalOrderId,
...fee,
});
const db = await this.orderFeeModel.findOne({
where: {
siteId,
externalOrderId,
externalOrderFeeId: entity.externalOrderFeeId,
},
});
if (db) {
await this.orderFeeModel.update(db.id, entity);
} else {
await this.orderFeeModel.save(entity);
}
}
}
async saveOrderCoupons({ siteId, externalOrderId, orderId, coupon_lines }) {
for (const item of coupon_lines) {
item.externalOrderCouponId = item.id;
item.siteId = siteId;
item.orderId = orderId;
item.externalOrderId = externalOrderId;
delete item.id;
const coupon = plainToClass(OrderCoupon, item);
const existingOrderCoupon = await this.orderCouponModel.findOne({
where: {
siteId,
externalOrderId,
externalOrderCouponId: coupon.externalOrderCouponId,
},
});
if (existingOrderCoupon) {
await this.orderCouponModel.update(existingOrderCoupon.id, coupon);
} else {
await this.orderCouponModel.save(coupon);
}
}
}
async saveOrderShipping({
siteId,
orderId,
externalOrderId,
shipping_lines,
}) {
for (const item of shipping_lines) {
item.externalOrderShippingId = item.id;
item.siteId = siteId;
item.orderId = orderId;
item.externalOrderId = externalOrderId;
delete item.id;
const shipping = plainToClass(OrderShipping, item);
const existingOrderShipping = await this.orderShippingModel.findOne({
where: {
siteId,
externalOrderId,
externalOrderShippingId: shipping.externalOrderShippingId,
},
});
if (existingOrderShipping) {
await this.orderShippingModel.update(
existingOrderShipping.id,
shipping
);
} else {
await this.orderShippingModel.save(shipping);
}
}
}
async getOrders({
externalOrderId,
siteId,
startDate,
endDate,
status,
keyword,
current,
pageSize,
customer_email,
payment_method,
billing_phone,
isSubscriptionOnly = false,
}, userId = undefined) {
const parameters: any[] = [];
// 基础查询
let sqlQuery = `
SELECT
o.id as id,
o.externalOrderId as externalOrderId,
o.siteId as siteId,
o.date_paid as date_paid,
o.total as total,
o.date_created as date_created,
o.customer_email as customer_email,
o.exchange_frequency as exchange_frequency,
o.transaction_id as transaction_id,
o.orderStatus as orderStatus,
o.customer_ip_address as customer_ip_address,
o.device_type as device_type,
o.source_type as source_type,
o.utm_source as utm_source,
o.customer_note as customer_note,
o.shipping as shipping,
o.billing as billing,
o.payment_method as payment_method,
cs.order_count as order_count,
cs.total_spent as total_spent,
CASE WHEN EXISTS (
SELECT 1 FROM subscription s
WHERE s.siteId = o.siteId AND s.parent_id = o.externalOrderId
) THEN 1 ELSE 0 END AS isSubscription,
(
SELECT COALESCE(
JSON_ARRAYAGG(
JSON_OBJECT(
'id', s.id,
'externalSubscriptionId', s.externalSubscriptionId,
'status', s.status,
'currency', s.currency,
'total', s.total,
'billing_period', s.billing_period,
'billing_interval', s.billing_interval,
'customer_id', s.customer_id,
'customer_email', s.customer_email,
'parent_id', s.parent_id,
'start_date', s.start_date,
'trial_end', s.trial_end,
'next_payment_date', s.next_payment_date,
'end_date', s.end_date,
'line_items', s.line_items,
'meta_data', s.meta_data
)
),
JSON_ARRAY()
)
FROM subscription s
WHERE s.siteId = o.siteId AND s.parent_id = o.externalOrderId
) AS related,
COALESCE(
JSON_ARRAYAGG(
CASE WHEN s.id IS NOT NULL THEN JSON_OBJECT(
'state', s.state,
'tracking_provider', s.tracking_provider,
'primary_tracking_number', s.primary_tracking_number
) END
),
JSON_ARRAY()
) as shipmentList
FROM \`order\` o
LEFT JOIN (
SELECT
o.customer_email AS customer_email,
COUNT(o.id) AS order_count,
SUM(o.total) AS total_spent
FROM \`order\` o
WHERE o.status IN ('processing', 'completed')
GROUP BY o.customer_email
) cs ON cs.customer_email = o.customer_email
LEFT JOIN order_shipment os ON os.order_id = o.id
LEFT JOIN shipment s ON s.id = os.shipment_id
WHERE 1=1
`;
// 计算总数
let totalQuery = `
SELECT COUNT(*) as total
FROM \`order\` o
LEFT JOIN (
SELECT o.customer_email AS customer_email
FROM \`order\` o
WHERE o.status IN ('processing', 'completed')
GROUP BY o.customer_email
) cs ON cs.customer_email = o.customer_email
WHERE 1=1
`;
// 动态添加过滤条件
if (externalOrderId) {
sqlQuery += ` AND o.externalOrderId = ?`;
totalQuery += ` AND o.externalOrderId = ?`;
parameters.push(externalOrderId);
}
if (siteId) {
sqlQuery += ` AND o.siteId = ?`;
totalQuery += ` AND o.siteId = ?`;
parameters.push(siteId);
}
if (startDate) {
sqlQuery += ` AND o.date_created >= ?`;
totalQuery += ` AND o.date_created >= ?`;
parameters.push(startDate);
}
if (endDate) {
sqlQuery += ` AND o.date_created <= ?`;
totalQuery += ` AND o.date_created <= ?`;
parameters.push(endDate);
}
// 支付方式筛选(使用参数化,避免SQL注入)
if (payment_method) {
sqlQuery += ` AND o.payment_method LIKE ?`;
totalQuery += ` AND o.payment_method LIKE ?`;
parameters.push(`%${payment_method}%`);
}
const user = await this.userModel.findOneBy({ id: userId });
if (user?.permissions?.includes('order-10-days') && !startDate && !endDate) {
sqlQuery += ` AND o.date_created >= ?`;
totalQuery += ` AND o.date_created >= ?`;
const tenDaysAgo = new Date();
tenDaysAgo.setDate(tenDaysAgo.getDate() - 10);
parameters.push(tenDaysAgo.toISOString());
}
// 处理 status 参数
if (status) {
if (Array.isArray(status)) {
sqlQuery += ` AND o.orderStatus IN (${status
.map(() => '?')
.join(', ')})`;
totalQuery += ` AND o.orderStatus IN (${status
.map(() => '?')
.join(', ')})`;
parameters.push(...status);
} else {
sqlQuery += ` AND o.orderStatus = ?`;
totalQuery += ` AND o.orderStatus = ?`;
parameters.push(status);
}
}
// 仅订阅订单过滤:父订阅订单 或 行项目包含订阅相关元数据(兼容 JSON 与字符串存储)
if (isSubscriptionOnly) {
const subCond = `
AND (
EXISTS (
SELECT 1 FROM subscription s
WHERE s.siteId = o.siteId AND s.parent_id = o.externalOrderId
)
)
`;
sqlQuery += subCond;
totalQuery += subCond;
}
if (customer_email) {
sqlQuery += ` AND o.customer_email LIKE ?`;
totalQuery += ` AND o.customer_email LIKE ?`;
parameters.push(`%${customer_email}%`);
}
if (billing_phone) {
sqlQuery += ` AND o.billing_phone LIKE ?`;
totalQuery += ` AND o.billing_phone LIKE ?`;
parameters.push(`%${billing_phone}%`);
}
// 关键字搜索
if (keyword) {
sqlQuery += `
AND EXISTS (
SELECT 1 FROM order_item oi
WHERE oi.orderId = o.id
AND oi.name LIKE ?
)
`;
totalQuery += `
AND EXISTS (
SELECT 1 FROM order_item oi
WHERE oi.orderId = o.id
AND oi.name LIKE ?
)
`;
parameters.push(`%${keyword}%`);
}
// 执行获取总数的查询
const totalResult = await this.orderModel.query(totalQuery, parameters);
const total = totalResult[0]?.total || 0;
// 添加分页到主查询
sqlQuery += `
GROUP BY o.id
ORDER BY o.date_created DESC
LIMIT ? OFFSET ?
`;
parameters.push(pageSize, (current - 1) * pageSize);
// 执行查询
const orders = await this.orderModel.query(sqlQuery, parameters);
return { items: orders, total, current, pageSize };
}
async getOrderStatus({
externalOrderId,
siteId,
startDate,
endDate,
keyword,
customer_email,
billing_phone,
isSubscriptionOnly = false,
}: any) {
const query = this.orderModel
.createQueryBuilder('order')
.select('order.orderStatus', 'status')
.addSelect('COUNT(*)', 'count')
.groupBy('order.orderStatus');
if (externalOrderId) {
query.andWhere('order.externalOrderId = :externalOrderId', {
externalOrderId,
});
}
if (siteId) {
query.andWhere('order.siteId = :siteId', { siteId });
}
if (startDate) {
query.andWhere('order.date_created >= :startDate', { startDate });
}
if (endDate) {
query.andWhere('order.date_created <= :endDate', { endDate });
}
if (customer_email)
query.andWhere('order.customer_email LIKE :customer_email', {
customer_email: `%${customer_email}%`,
});
// 🔥 关键字搜索:检查 order_item.name 是否包含 keyword
if (keyword) {
query.andWhere(
`EXISTS (
SELECT 1 FROM order_item oi
WHERE oi.orderId = order.id
AND oi.name LIKE :keyword
)`,
{ keyword: `%${keyword}%` }
);
}
if (isSubscriptionOnly) {
query.andWhere(`(
EXISTS (
SELECT 1 FROM subscription s
WHERE s.siteId = order.siteId AND s.parent_id = order.externalOrderId
)
)`);
}
return await query.getRawMany();
}
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage }: QueryOrderSalesDTO) {
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
const defaultStart = dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
const defaultEnd = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
startDate = (startDate as any) || defaultStart as any;
endDate = (endDate as any) || defaultEnd as any;
const offset = (current - 1) * pageSize;
// -------------------------
// 1. 查询总条数
// -------------------------
const countParams: any[] = [startDate, endDate];
let countSql = `
SELECT COUNT(DISTINCT os.productId) AS totalCount
FROM order_sale os
INNER JOIN \`order\` o ON o.id = os.orderId
WHERE o.date_paid BETWEEN ? AND ?
AND o.status IN ('completed','processing')
`;
if (siteId) {
countSql += ' AND os.siteId = ?';
countParams.push(siteId);
}
if (nameKeywords.length > 0) {
countSql += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
countParams.push(...nameKeywords.map(w => `%${w}%`));
}
const [countResult] = await this.orderSaleModel.query(countSql, countParams);
const totalCount = Number(countResult?.totalCount || 0);
// -------------------------
// 2. 分页查询 product 基础信息
// -------------------------
const itemParams: any[] = [startDate, endDate];
let nameCondition = '';
if (nameKeywords.length > 0) {
nameCondition = ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
itemParams.push(...nameKeywords.map(w => `%${w}%`));
}
let itemSql = `
SELECT os.productId, os.name, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
FROM order_sale os
INNER JOIN \`order\` o ON o.id = os.orderId
WHERE o.date_paid BETWEEN ? AND ?
AND o.status IN ('completed','processing')
`;
if (siteId) {
itemSql += ' AND os.siteId = ?';
itemParams.push(siteId);
}
if (exceptPackage) {
itemSql += `
AND os.orderId IN (
SELECT orderId
FROM order_sale
GROUP BY orderId
HAVING COUNT(*) = 1
)
`;
}
itemSql += nameCondition;
itemSql += `
GROUP BY os.productId, os.name
ORDER BY totalQuantity DESC
LIMIT ? OFFSET ?
`;
itemParams.push(pageSize, offset);
const items = await this.orderSaleModel.query(itemSql, itemParams);
// -------------------------
// 3. 批量统计当前页 product 历史复购
// -------------------------
if (items.length > 0) {
const productIds = items.map(i => i.productId);
const pcParams: any[] = [...productIds, startDate, endDate];
if (siteId) pcParams.push(siteId);
let pcSql = `
SELECT
os.productId,
SUM(CASE WHEN t.purchaseIndex = 1 THEN os.quantity ELSE 0 END) AS firstOrderYOONEBoxCount,
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 1 THEN os.orderId END) AS firstOrderCount,
SUM(CASE WHEN t.purchaseIndex = 2 THEN os.quantity ELSE 0 END) AS secondOrderYOONEBoxCount,
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 2 THEN os.orderId END) AS secondOrderCount,
SUM(CASE WHEN t.purchaseIndex = 3 THEN os.quantity ELSE 0 END) AS thirdOrderYOONEBoxCount,
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 3 THEN os.orderId END) AS thirdOrderCount,
SUM(CASE WHEN t.purchaseIndex > 3 THEN os.quantity ELSE 0 END) AS moreThirdOrderYOONEBoxCount,
COUNT(DISTINCT CASE WHEN t.purchaseIndex > 3 THEN os.orderId END) AS moreThirdOrderCount
FROM order_sale os
INNER JOIN (
SELECT o2.id AS orderId,
@idx := IF(@prev_email = o2.customer_email, @idx + 1, 1) AS purchaseIndex,
@prev_email := o2.customer_email
FROM \`order\` o2
CROSS JOIN (SELECT @idx := 0, @prev_email := '') vars
WHERE o2.status IN ('completed','processing')
ORDER BY o2.customer_email, o2.date_paid
) t ON t.orderId = os.orderId
WHERE os.productId IN (${productIds.map(() => '?').join(',')})
AND os.orderId IN (
SELECT id FROM \`order\`
WHERE date_paid BETWEEN ? AND ?
${siteId ? 'AND siteId = ?' : ''}
)
`;
if (exceptPackage) {
pcSql += `
AND os.orderId IN (
SELECT orderId
FROM order_sale
GROUP BY orderId
HAVING COUNT(*) = 1
)
`;
}
pcSql += `
GROUP BY os.productId
`;
console.log('------3.5-----', pcSql, pcParams, exceptPackage);
const pcResults = await this.orderSaleModel.query(pcSql, pcParams);
const pcMap = new Map<number, any>();
pcResults.forEach(r => pcMap.set(r.productId, r));
items.forEach(i => {
const r = pcMap.get(i.productId) || {};
i.firstOrderYOONEBoxCount = Number(r.firstOrderYOONEBoxCount || 0);
i.firstOrderCount = Number(r.firstOrderCount || 0);
i.secondOrderYOONEBoxCount = Number(r.secondOrderYOONEBoxCount || 0);
i.secondOrderCount = Number(r.secondOrderCount || 0);
i.thirdOrderYOONEBoxCount = Number(r.thirdOrderYOONEBoxCount || 0);
i.thirdOrderCount = Number(r.thirdOrderCount || 0);
i.moreThirdOrderYOONEBoxCount = Number(r.moreThirdOrderYOONEBoxCount || 0);
i.moreThirdOrderCount = Number(r.moreThirdOrderCount || 0);
});
}
// -------------------------
// 4. 总量统计(时间段 + siteId)
// -------------------------
const totalParams: any[] = [startDate, endDate];
const yooneParams: any[] = [startDate, endDate];
let totalSql = `
SELECT
SUM(os.quantity) AS totalQuantity
FROM order_sale os
INNER JOIN \`order\` o ON o.id = os.orderId
WHERE o.date_paid BETWEEN ? AND ?
AND o.status IN ('completed','processing')
`;
let yooneSql = `
SELECT
SUM(CASE WHEN os.isYoone = 1 AND os.size = 3 THEN os.quantity ELSE 0 END) AS yoone3Quantity,
SUM(CASE WHEN os.isYoone = 1 AND os.size = 6 THEN os.quantity ELSE 0 END) AS yoone6Quantity,
SUM(CASE WHEN os.isYoone = 1 AND os.size = 9 THEN os.quantity ELSE 0 END) AS yoone9Quantity,
SUM(CASE WHEN os.isYoone = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12Quantity,
SUM(CASE WHEN os.isYooneNew = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12QuantityNew,
SUM(CASE WHEN os.isYoone = 1 AND os.size = 15 THEN os.quantity ELSE 0 END) AS yoone15Quantity,
SUM(CASE WHEN os.isYoone = 1 AND os.size = 18 THEN os.quantity ELSE 0 END) AS yoone18Quantity,
SUM(CASE WHEN os.isZex = 1 THEN os.quantity ELSE 0 END) AS zexQuantity
FROM order_sale os
INNER JOIN \`order\` o ON o.id = os.orderId
WHERE o.date_paid BETWEEN ? AND ?
AND o.status IN ('completed','processing')
`;
if (siteId) {
totalSql += ' AND os.siteId = ?';
totalParams.push(siteId);
yooneSql += ' AND os.siteId = ?';
yooneParams.push(siteId);
}
if (nameKeywords.length > 0) {
totalSql += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
totalParams.push(...nameKeywords.map(w => `%${w}%`));
}
const [totalResult] = await this.orderSaleModel.query(totalSql, totalParams);
const [yooneResult] = await this.orderSaleModel.query(yooneSql, yooneParams);
return {
items,
total: totalCount, // ✅ 总条数
totalQuantity: Number(totalResult.totalQuantity || 0),
yoone3Quantity: Number(yooneResult.yoone3Quantity || 0),
yoone6Quantity: Number(yooneResult.yoone6Quantity || 0),
yoone9Quantity: Number(yooneResult.yoone9Quantity || 0),
yoone12Quantity: Number(yooneResult.yoone12Quantity || 0),
yoone12QuantityNew: Number(yooneResult.yoone12QuantityNew || 0),
yoone15Quantity: Number(yooneResult.yoone15Quantity || 0),
yoone18Quantity: Number(yooneResult.yoone18Quantity || 0),
zexQuantity: Number(yooneResult.zexQuantity || 0),
current,
pageSize,
};
}
async getOrderItems({
siteId,
startDate,
endDate,
current,
pageSize,
name,
}: QueryOrderSalesDTO) {
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
const defaultStart = dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
const defaultEnd = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
startDate = (startDate as any) || defaultStart as any;
endDate = (endDate as any) || defaultEnd as any;
// 分页查询
let sqlQuery = `
WITH product_purchase_counts AS (
SELECT o.customer_email,oi.siteId,oi.externalProductId,oi.externalVariationId, oi.name, COUNT(DISTINCT o.id,oi.siteId,oi.externalProductId,oi.externalVariationId) AS order_count
FROM \`order\` o
JOIN order_item oi ON o.id = oi.orderId
WHERE o.status IN ('completed', 'processing')
GROUP BY o.customer_email, oi.siteId,oi.externalProductId,oi.externalVariationId, oi.name
)
SELECT
oi.externalProductId AS externalProductId,
oi.externalVariationId AS externalVariationId,
oi.name AS name,
SUM(oi.quantity) AS totalQuantity,
COUNT(distinct oi.orderId) AS totalOrders,
COUNT(DISTINCT CASE WHEN pc.order_count = 1 THEN o.id END) AS firstOrderCount,
COUNT(DISTINCT CASE WHEN pc.order_count = 2 THEN o.id END) AS secondOrderCount,
COUNT(DISTINCT CASE WHEN pc.order_count = 3 THEN o.id END) AS thirdOrderCount,
COUNT(DISTINCT CASE WHEN pc.order_count > 3 THEN o.id END) AS moreThirdOrderCount
FROM order_item oi
INNER JOIN \`order\` o ON o.id = oi.orderId
INNER JOIN product_purchase_counts pc ON pc.customer_email = o.customer_email AND pc.externalProductId = oi.externalProductId AND pc.externalVariationId = oi.externalVariationId
WHERE o.date_created BETWEEN ? AND ?
AND o.status IN ('processing', 'completed')
`;
const parameters: any[] = [startDate, endDate];
if (siteId) {
sqlQuery += ' AND oi.siteId = ?';
parameters.push(siteId);
}
if (nameKeywords.length > 0) {
sqlQuery +=
' AND ' + nameKeywords.map(() => `oi.name LIKE ?`).join(' AND ');
parameters.push(...nameKeywords.map(word => `%${word}%`));
}
sqlQuery += `
GROUP BY oi.siteId,oi.externalProductId,oi.externalVariationId, oi.name
ORDER BY totalQuantity DESC
`;
sqlQuery += ' LIMIT ? OFFSET ?';
parameters.push(pageSize, (current - 1) * pageSize);
// 执行查询并传递参数
const items = await this.orderSaleModel.query(sqlQuery, parameters);
let totalCountQuery = `
SELECT COUNT(DISTINCT oi.siteId,oi.externalProductId,oi.externalVariationId) AS totalCount
FROM order_item oi
INNER JOIN \`order\` o ON o.id = oi.orderId
WHERE o.date_created BETWEEN ? AND ?
AND o.status IN ('processing', 'completed')
`;
const totalCountParameters: any[] = [startDate, endDate];
if (siteId) {
totalCountQuery += ' AND oi.siteId = ?';
totalCountParameters.push(siteId);
}
if (nameKeywords.length > 0) {
totalCountQuery +=
' AND ' + nameKeywords.map(() => `oi.name LIKE ?`).join(' AND ');
totalCountParameters.push(...nameKeywords.map(word => `%${word}%`));
}
const totalCountResult = await this.orderSaleModel.query(
totalCountQuery,
totalCountParameters
);
let totalQuantityQuery = `
SELECT SUM(oi.quantity) AS totalQuantity
FROM order_item oi
INNER JOIN \`order\` o ON o.id = oi.orderId
WHERE o.date_created BETWEEN ? AND ?
AND o.status IN ('processing', 'completed')
`;
const totalQuantityParameters: any[] = [startDate, endDate];
if (siteId) {
totalQuantityQuery += ' AND oi.siteId = ?';
totalQuantityParameters.push(siteId);
}
if (nameKeywords.length > 0) {
totalQuantityQuery +=
' AND ' + nameKeywords.map(() => `oi.name LIKE ?`).join(' AND ');
totalQuantityParameters.push(...nameKeywords.map(word => `%${word}%`));
}
const totalQuantityResult = await this.orderSaleModel.query(
totalQuantityQuery,
totalQuantityParameters
);
return {
items,
total: totalCountResult[0]?.totalCount,
totalQuantity: Number(
totalQuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
),
current,
pageSize,
};
}
async getOrderItemList({
siteId,
startDate,
endDate,
current,
pageSize,
name,
externalProductId,
externalVariationId,
}: any) {
const params: any[] = [];
let sql = `
SELECT
oi.*,
o.id AS orderId,
o.externalOrderId AS orderExternalOrderId,
o.date_created AS orderDateCreated,
o.customer_email AS orderCustomerEmail,
o.orderStatus AS orderStatus,
o.siteId AS orderSiteId,
CASE WHEN
JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"is_subscription"')
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcs_bought_as_subscription"')
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_wcsatt_scheme"')
OR JSON_CONTAINS(JSON_EXTRACT(oi.meta_data, '$[*].key'), '"_subscription"')
THEN 1 ELSE 0 END AS isSubscriptionItem
FROM order_item oi
INNER JOIN \`order\` o ON o.id = oi.orderId
WHERE 1=1
`;
let countSql = `
SELECT COUNT(*) AS total
FROM order_item oi
INNER JOIN \`order\` o ON o.id = oi.orderId
WHERE 1=1
`;
const pushFilter = (cond: string, value: any) => {
sql += cond; countSql += cond; params.push(value);
};
if (startDate) pushFilter(' AND o.date_created >= ?', startDate);
if (endDate) pushFilter(' AND o.date_created <= ?', endDate);
if (siteId) pushFilter(' AND oi.siteId = ?', siteId);
if (name) {
pushFilter(' AND oi.name LIKE ?', `%${name}%`);
}
if (externalProductId) pushFilter(' AND oi.externalProductId = ?', externalProductId);
if (externalVariationId) pushFilter(' AND oi.externalVariationId = ?', externalVariationId);
sql += ' ORDER BY o.date_created DESC LIMIT ? OFFSET ?';
const listParams = [...params, pageSize, (current - 1) * pageSize];
const items = await this.orderItemModel.query(sql, listParams);
const [countRow] = await this.orderItemModel.query(countSql, params);
const total = Number(countRow?.total || 0);
return { items, total, current, pageSize };
}
async getOrderDetail(id: number): Promise<OrderDetailRes> {
const order = await this.orderModel.findOne({ where: { id } });
const site = await this.siteService.get(Number(order.siteId), true);
const items = await this.orderItemModel.find({ where: { orderId: id } });
const sales = await this.orderSaleModel.find({ where: { orderId: id } });
const refunds = await this.orderRefundModel.find({
where: { orderId: id },
});
const refundItems = await this.orderRefundItemModel.find({
where: { refundId: In(refunds.map(refund => refund.id)) },
});
const notes = await this.orderNoteModel
.createQueryBuilder('order_note')
.leftJoin(User, 'u', 'u.id=order_note.userId')
.select(['order_note.*', 'u.username as username'])
.where('order_note.orderId=:orderId', { orderId: id })
.getRawMany();
const getShipmentSql = `
SELECT
s.*,
CAST(CONCAT('[', GROUP_CONCAT(DISTINCT o.externalOrderId SEPARATOR ','), ']') AS JSON) AS orderIds
FROM
shipment s
JOIN
order_shipment os ON os.shipment_id = s.id
JOIN
\`order\` o ON os.order_id = o.id
WHERE
s.id IN (
SELECT shipment_id FROM order_shipment WHERE order_id = ?
)
GROUP BY
s.id;
`;
const shipment = await this.shipmentModel.query(getShipmentSql, [id, id]);
if (shipment && shipment.length) {
for (const v of shipment) {
v.items = await this.shipmentItemModel.findBy({ shipment_id: v.id });
}
}
// 关联数据:订阅与相关订单(用于前端关联展示)
let relatedList: any[] = [];
try {
const related = await this.getRelatedByOrder(id);
const subs = Array.isArray(related?.subscriptions) ? related.subscriptions : [];
const ords = Array.isArray(related?.orders) ? related.orders : [];
const seen = new Set<string>();
const merge = [...subs, ...ords];
for (const it of merge) {
const key = it?.externalSubscriptionId
? `sub:${it.externalSubscriptionId}`
: it?.externalOrderId
? `ord:${it.externalOrderId}`
: `id:${it?.id}`;
if (!seen.has(key)) {
seen.add(key);
relatedList.push(it);
}
}
} catch (error) {
// 关联查询失败不影响详情返回
}
return {
...order,
name: site?.name,
// Site 实体无邮箱字段,这里返回空字符串保持兼容
email: '',
items,
sales,
refundItems,
notes,
shipment,
related: relatedList,
};
}
async getRelatedByOrder(orderId: number) {
const order = await this.orderModel.findOne({ where: { id: orderId } });
if (!order) throw new Error('订单不存在');
const siteId = order.siteId;
const subSql = `
SELECT * FROM subscription s
WHERE s.siteId = ? AND s.parent_id = ?
`;
const subscriptions = await this.orderModel.query(subSql, [siteId, order.externalOrderId]);
return {
order,
subscriptions,
orders: [],
};
}
async delOrder(id: number) {
const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error('订单不存在');
await this.orderShippingModel.delete({ orderId: id });
await this.orderSaleModel.delete({ orderId: id });
const refunds = await this.orderRefundModel.find({
where: { orderId: id },
});
if (refunds.length > 0) {
for (const refund of refunds) {
await this.orderRefundItemModel.delete({ refundId: refund.id });
await this.orderRefundModel.delete({ id: refund.id });
}
}
await this.orderItemModel.delete({ orderId: id });
await this.orderFeeModel.delete({ orderId: id });
await this.orderCouponModel.delete({ orderId: id });
await this.orderModel.delete({ id });
}
async createNote(userId: number, data: CreateOrderNoteDTO) {
return await this.orderNoteModel.save({
...data,
userId,
});
}
async getOrderByNumber(id: string) {
const orders = await this.orderModel.find({
where: {
externalOrderId: Like(id),
orderStatus: In([
ErpOrderStatus.PROCESSING,
ErpOrderStatus.PENDING_RESHIPMENT,
]),
},
});
// 批量获取订单涉及的站点名称,避免使用配置文件
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.name]));
return orders.map(order => ({
externalOrderId: order.externalOrderId,
id: order.id,
name: siteMap.get(String(order.siteId)) || '',
}));
}
async cancelOrder(id: number) {
const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error(`订单 ${id}不存在`);
const site = await this.siteService.get(Number(order.siteId), true);
if (order.status !== OrderStatus.CANCEL) {
await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.CANCEL,
});
order.status = OrderStatus.CANCEL;
}
order.orderStatus = ErpOrderStatus.CANCEL;
await this.orderModel.save(order);
}
async refundOrder(id: number) {
const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error(`订单 ${id}不存在`);
const site = await this.siteService.get(Number(order.siteId), true);
if (order.status !== OrderStatus.REFUNDED) {
await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.REFUNDED,
});
order.status = OrderStatus.REFUNDED;
}
order.orderStatus = ErpOrderStatus.REFUNDED;
await this.orderModel.save(order);
}
async completedOrder(id: number) {
const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error(`订单 ${id}不存在`);
const site = await this.siteService.get(order.siteId);
if (order.status !== OrderStatus.COMPLETED) {
await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.COMPLETED,
});
order.status = OrderStatus.COMPLETED;
}
order.orderStatus = ErpOrderStatus.COMPLETED;
await this.orderModel.save(order);
}
async changeStatus(id: number, status: ErpOrderStatus) {
const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error(`订单 ${id}不存在`);
order.orderStatus = status;
await this.orderModel.save(order);
}
async createOrder(data: Record<string, any>) {
// 从数据中解构出需要用的属性
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 now = new Date();
// 在事务中处理订单创建
return dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale);
const productRepo = manager.getRepository(Product);
// 保存订单信息
const order = await orderRepo.save({
siteId,
externalOrderId: '-1',
status: OrderStatus.PROCESSING,
orderStatus: ErpOrderStatus.PROCESSING,
currency: 'CAD',
currency_symbol: '$',
date_created: now,
date_paid: now,
total,
customer_email,
billing_phone,
billing,
shipping: billing,
});
// 遍历销售项目并保存
for (const sale of sales) {
const product = await productRepo.findOne({ where: { sku: sale.sku } });
const saleItem = {
orderId: order.id,
siteId: order.siteId,
externalOrderItemId: '-1',
productId: product.id,
name: product.name,
sku: sale.sku,
quantity: sale.quantity,
};
await orderSaleRepo.save(saleItem);
}
});
}
async pengdingItems(data: Record<string, any>) {
const { current = 1, pageSize = 10 } = data;
const sql = `
SELECT
os.name,
SUM(os.quantity) AS quantity,
JSON_ARRAYAGG(os.orderId) AS numbers
FROM \`order\` o
INNER JOIN order_sale os ON os.orderId = o.id
WHERE o.status = 'processing'
GROUP BY os.name
LIMIT ${pageSize} OFFSET ${(current - 1) * pageSize}
`;
const countSql = `
SELECT COUNT(*) AS total FROM (
SELECT 1
FROM \`order\` o
INNER JOIN order_sale os ON os.orderId = o.id
WHERE o.status = 'processing'
GROUP BY os.name
) AS temp
`;
const [items, countResult] = await Promise.all([
this.orderModel.query(sql),
this.orderModel.query(countSql),
]);
const total = countResult[0]?.total || 0;
return {
items,
total,
current,
pageSize,
};
}
async updateOrderSales(orderId: number, sales: OrderSale[]) {
try {
const dataSource = this.dataSourceManager.getDataSource('default');
let transactionError = undefined;
await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale);
const productRepo = manager.getRepository(Product);
const order = await orderRepo.findOneBy({ id: orderId });
let product: Product;
await orderSaleRepo.delete({ orderId });
for (const sale of sales) {
product = await productRepo.findOneBy({ sku: sale.sku });
await orderSaleRepo.save({
orderId,
siteId: order.siteId,
productId: product.id,
name: product.name,
sku: sale.sku,
quantity: sale.quantity,
// externalOrderItemId:
});
};
}).catch(error => {
transactionError = error;
});
if (transactionError !== undefined) {
throw new Error(`更新物流信息错误:${transactionError.message}`);
}
return true;
} catch (error) {
throw new Error(`更新发货产品失败:${error.message}`);
}
}
//换货确认按钮改成调用这个方法
//换货功能更新OrderSale和Orderitem数据
async updateExchangeOrder(orderId: number, data: any) {
throw new Error('暂未实现')
// try {
// const dataSource = this.dataSourceManager.getDataSource('default');
// let transactionError = undefined;
// await dataSource.transaction(async manager => {
// const orderRepo = manager.getRepository(Order);
// const orderSaleRepo = manager.getRepository(OrderSale);
// const orderItemRepo = manager.getRepository(OrderItem);
// const productRepo = manager.getRepository(ProductV2);
// const order = await orderRepo.findOneBy({ id: orderId });
// let product: ProductV2;
// await orderSaleRepo.delete({ orderId });
// await orderItemRepo.delete({ orderId });
// for (const sale of data['sales']) {
// product = await productRepo.findOneBy({ sku: sale['sku'] });
// await orderSaleRepo.save({
// orderId,
// siteId: order.siteId,
// productId: product.id,
// name: product.name,
// sku: sale['sku'],
// quantity: sale['quantity'],
// });
// };
// for (const item of data['items']) {
// product = await productRepo.findOneBy({ sku: item['sku'] });
// await orderItemRepo.save({
// orderId,
// siteId: order.siteId,
// productId: product.id,
// name: product.name,
// externalOrderId: order.externalOrderId,
// externalProductId: product.externalProductId,
// sku: item['sku'],
// quantity: item['quantity'],
// });
// };
// //将是否换货状态改为true
// await orderRepo.update(
// order.id
// , {
// is_exchange: true
// });
// //查询这个用户换过多少次货
// const counts = await orderRepo.countBy({
// is_editable: true,
// customer_email: order.customer_email,
// });
// //批量更新当前用户换货次数
// await orderRepo.update({
// customer_email: order.customer_email
// }, {
// exchange_frequency: counts
// });
// }).catch(error => {
// transactionError = error;
// });
// if (transactionError !== undefined) {
// throw new Error(`更新物流信息错误:${transactionError.message}`);
// }
// return true;
// } catch (error) {
// throw new Error(`更新发货产品失败:${error.message}`);
// }
}
}