Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
6be91f14a4
|
|
@ -227,8 +227,10 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
// ========== 订单映射方法 ==========
|
// ========== 订单映射方法 ==========
|
||||||
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
|
// console.log(item)
|
||||||
|
if(!item) throw new Error('订单数据不能为空')
|
||||||
// 提取账单和送货地址 如果不存在则为空对象
|
// 提取账单和送货地址 如果不存在则为空对象
|
||||||
const billing = (item as any).billing_address || {};
|
const billing = (item).bill_address || {};
|
||||||
const shipping = (item as any).shipping_address || {};
|
const shipping = (item as any).shipping_address || {};
|
||||||
|
|
||||||
// 构建账单地址对象
|
// 构建账单地址对象
|
||||||
|
|
@ -313,7 +315,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
quantity: p.quantity,
|
quantity: p.quantity,
|
||||||
total: String(p.price ?? ''),
|
total: String(p.price ?? ''),
|
||||||
sku: p.sku || p.sku_code || '',
|
sku: p.sku_code || '',
|
||||||
price: String(p.price ?? ''),
|
price: String(p.price ?? ''),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
@ -440,7 +442,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新账单地址
|
// 更新账单地址
|
||||||
params.billing_address = params.billing_address || {};
|
params.billing_address = params?.billing_address || {};
|
||||||
if (data.billing.first_name !== undefined) {
|
if (data.billing.first_name !== undefined) {
|
||||||
params.billing_address.first_name = data.billing.first_name;
|
params.billing_address.first_name = data.billing.first_name;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,14 +98,10 @@ export class QueryOrderDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryOrderSalesDTO {
|
export class QueryOrderSalesDTO {
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '是否为原产品还是库存产品' })
|
||||||
@Rule(RuleType.bool().default(false))
|
@Rule(RuleType.bool().default(false))
|
||||||
isSource: boolean;
|
isSource: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.bool().default(false))
|
|
||||||
exceptPackage: boolean;
|
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ example: '1', description: '页码' })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
current: number;
|
current: number;
|
||||||
|
|
@ -114,19 +110,31 @@ export class QueryOrderSalesDTO {
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", sku: "desc" }',type: 'any', required: false })
|
||||||
|
@Rule(RuleType.object().allow(null))
|
||||||
|
orderBy?: Record<string, 'asc' | 'desc'>;
|
||||||
|
// filter
|
||||||
|
@ApiProperty({ description: '是否排除套餐' })
|
||||||
|
@Rule(RuleType.bool().default(false))
|
||||||
|
exceptPackage: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点ID' })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '名称' })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: 'SKU' })
|
||||||
|
@Rule(RuleType.string())
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '开始日期' })
|
||||||
@Rule(RuleType.date())
|
@Rule(RuleType.date())
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '结束日期' })
|
||||||
@Rule(RuleType.date())
|
@Rule(RuleType.date())
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ export interface ShopyyOrder {
|
||||||
customer_email?: string;
|
customer_email?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
// 地址字段
|
// 地址字段
|
||||||
billing_address?: {
|
bill_address?: {
|
||||||
first_name?: string;
|
first_name?: string;
|
||||||
last_name?: string;
|
last_name?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
|
||||||
|
|
@ -272,6 +272,14 @@ export class Order {
|
||||||
@Expose()
|
@Expose()
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'json', nullable: true, description: '订单项列表' })
|
||||||
|
@Expose()
|
||||||
|
orderItems?: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ type: 'json', nullable: true, description: '销售项列表' })
|
||||||
|
@Expose()
|
||||||
|
orderSales?: any[];
|
||||||
|
|
||||||
// 在插入或更新前处理用户代理字符串
|
// 在插入或更新前处理用户代理字符串
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude, Expose } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
BeforeInsert,
|
// BeforeInsert,
|
||||||
BeforeUpdate,
|
// BeforeUpdate,
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
|
@ -22,22 +22,22 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ name:'原始订单ID' })
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
orderId: number; // 订单 ID
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ name:'站点' })
|
||||||
@Column({ nullable: true })
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
siteId: number; // 来源站点唯一标识
|
siteId: number; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({name: "原始订单 itemId"})
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
externalOrderItemId: string; // WooCommerce 订单item ID
|
externalOrderItemId: string; // WooCommerce 订单item ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({name: "产品 ID"})
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
productId: number;
|
productId: number;
|
||||||
|
|
@ -62,25 +62,35 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
isPackage: boolean;
|
isPackage: boolean;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '品牌', type: 'string',nullable: true})
|
||||||
@Column({ default: false })
|
|
||||||
@Expose()
|
@Expose()
|
||||||
isYoone: boolean;
|
@Column({ nullable: true })
|
||||||
|
brand?: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '口味', type: 'string', nullable: true })
|
||||||
@Column({ default: false })
|
|
||||||
@Expose()
|
@Expose()
|
||||||
isZex: boolean;
|
@Column({ nullable: true })
|
||||||
|
flavor?: string;
|
||||||
|
|
||||||
@ApiProperty({ nullable: true })
|
@ApiProperty({ description: '湿度', type: 'string', nullable: true })
|
||||||
@Column({ type: 'int', nullable: true })
|
|
||||||
@Expose()
|
@Expose()
|
||||||
size: number | null;
|
@Column({ nullable: true })
|
||||||
|
humidity?: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({ description: '尺寸', type: 'string', nullable: true })
|
||||||
@Column({ default: false })
|
|
||||||
@Expose()
|
@Expose()
|
||||||
isYooneNew: boolean;
|
@Column({ nullable: true })
|
||||||
|
size?: string;
|
||||||
|
|
||||||
|
@ApiProperty({name: '强度', nullable: true })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
@Expose()
|
||||||
|
strength: string | null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '版本', type: 'string', nullable: true })
|
||||||
|
@Expose()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
version?: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
|
|
@ -97,25 +107,4 @@ export class OrderSale {
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
|
|
||||||
// === 自动计算逻辑 ===
|
|
||||||
@BeforeInsert()
|
|
||||||
@BeforeUpdate()
|
|
||||||
setFlags() {
|
|
||||||
if (!this.name) return;
|
|
||||||
const lower = this.name.toLowerCase();
|
|
||||||
this.isYoone = lower.includes('yoone');
|
|
||||||
this.isZex = lower.includes('zex');
|
|
||||||
this.isYooneNew = this.isYoone && lower.includes('new');
|
|
||||||
let size: number | null = null;
|
|
||||||
const sizes = [3, 6, 9, 12, 15, 18];
|
|
||||||
for (const s of sizes) {
|
|
||||||
if (lower.includes(s.toString())) {
|
|
||||||
size = s;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,17 +75,20 @@ export class SyncUniuniShipmentJob implements IJob{
|
||||||
'255': 'Gateway_To_Gateway_Transit'
|
'255': 'Gateway_To_Gateway_Transit'
|
||||||
};
|
};
|
||||||
async onTick() {
|
async onTick() {
|
||||||
try {
|
|
||||||
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
||||||
shipments.forEach(shipment => {
|
const results = await Promise.all(
|
||||||
this.logisticsService.updateShipmentState(shipment);
|
shipments.map(async shipment => {
|
||||||
});
|
return await this.logisticsService.updateShipmentState(shipment);
|
||||||
} catch (error) {
|
})
|
||||||
this.logger.error(`更新运单状态失败 ${error.message}`);
|
)
|
||||||
}
|
this.logger.info(`更新运单状态完毕 ${JSON.stringify(results)}`);
|
||||||
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete(result: any) {
|
onComplete(result: any) {
|
||||||
|
this.logger.info(`更新运单状态完成 ${result}`);
|
||||||
|
}
|
||||||
|
onError(error: any) {
|
||||||
|
this.logger.error(`更新运单状态失败 ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +125,10 @@ export class LogisticsService {
|
||||||
try {
|
try {
|
||||||
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
|
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
|
||||||
console.log('updateShipmentState data:', data);
|
console.log('updateShipmentState data:', data);
|
||||||
|
// huo
|
||||||
|
if(data.status === 'FAIL'){
|
||||||
|
throw new Error('获取运单状态失败,原因为'+ data.ret_msg)
|
||||||
|
}
|
||||||
shipment.state = data.data[0].state;
|
shipment.state = data.data[0].state;
|
||||||
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
|
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
|
||||||
shipment.finished = true;
|
shipment.finished = true;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -39,6 +39,7 @@ import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { UnifiedOrderDTO } from '../dto/site-api.dto';
|
import { UnifiedOrderDTO } from '../dto/site-api.dto';
|
||||||
import { CustomerService } from './customer.service';
|
import { CustomerService } from './customer.service';
|
||||||
|
import { ProductService } from './product.service';
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
|
|
||||||
|
|
@ -110,6 +111,8 @@ export class OrderService {
|
||||||
|
|
||||||
@Logger()
|
@Logger()
|
||||||
logger; // 注入 Logger 实例
|
logger; // 注入 Logger 实例
|
||||||
|
@Inject()
|
||||||
|
productService: ProductService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量同步订单
|
* 批量同步订单
|
||||||
|
|
@ -281,6 +284,11 @@ export class OrderService {
|
||||||
console.error('更新订单状态失败,原因为:', error)
|
console.error('更新订单状态失败,原因为:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async getOrderByExternalOrderId(siteId: number, externalOrderId: string) {
|
||||||
|
return await this.orderModel.findOne({
|
||||||
|
where: { externalOrderId: String(externalOrderId), siteId },
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 同步单个订单
|
* 同步单个订单
|
||||||
* 流程说明:
|
* 流程说明:
|
||||||
|
|
@ -318,47 +326,28 @@ export class OrderService {
|
||||||
// console.log('同步进单个订单', order)
|
// console.log('同步进单个订单', order)
|
||||||
// 如果订单状态为 AUTO_DRAFT,则跳过处理
|
// 如果订单状态为 AUTO_DRAFT,则跳过处理
|
||||||
if (order.status === OrderStatus.AUTO_DRAFT) {
|
if (order.status === OrderStatus.AUTO_DRAFT) {
|
||||||
|
this.logger.debug('订单状态为 AUTO_DRAFT,跳过处理', siteId, order.id)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检查数据库中是否已存在该订单
|
// 这里其实不用过滤不可编辑的行为,而是应在 save 中做判断
|
||||||
const existingOrder = await this.orderModel.findOne({
|
// if(!order.is_editable && !forceUpdate){
|
||||||
where: { externalOrderId: String(order.id), siteId: siteId },
|
// this.logger.debug('订单不可编辑,跳过处理', siteId, order.id)
|
||||||
});
|
// return;
|
||||||
// 自动更新订单状态(如果需要)
|
// }
|
||||||
|
// 自动转换远程订单的状态(如果需要)
|
||||||
await this.autoUpdateOrderStatus(siteId, order);
|
await this.autoUpdateOrderStatus(siteId, order);
|
||||||
|
// 这里的 saveOrder 已经包括了创建订单和更新订单
|
||||||
if(existingOrder){
|
let orderRecord: Order = await this.saveOrder(siteId, orderData);
|
||||||
// 矫正数据库中的订单数据
|
|
||||||
const updateData: any = { status: order.status };
|
|
||||||
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
|
|
||||||
updateData.orderStatus = this.mapOrderStatus(order.status as any);
|
|
||||||
}
|
|
||||||
// 更新订单主数据
|
|
||||||
await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData);
|
|
||||||
// 更新 fulfillments 数据
|
|
||||||
await this.saveOrderFulfillments({
|
|
||||||
siteId,
|
|
||||||
orderId: existingOrder.id,
|
|
||||||
externalOrderId:order.id,
|
|
||||||
fulfillments: fulfillments,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const externalOrderId = String(order.id);
|
|
||||||
// 如果订单从未完成变为完成状态,则更新库存
|
// 如果订单从未完成变为完成状态,则更新库存
|
||||||
if (
|
if (
|
||||||
existingOrder &&
|
orderRecord &&
|
||||||
existingOrder.orderStatus !== ErpOrderStatus.COMPLETED &&
|
orderRecord.orderStatus !== ErpOrderStatus.COMPLETED &&
|
||||||
orderData.status === OrderStatus.COMPLETED
|
orderData.status === OrderStatus.COMPLETED
|
||||||
) {
|
) {
|
||||||
this.updateStock(existingOrder);
|
await this.updateStock(orderRecord);
|
||||||
// 不再直接返回,继续执行后续的更新操作
|
// 不再直接返回,继续执行后续的更新操作
|
||||||
}
|
}
|
||||||
// 如果订单不可编辑且不强制更新,则跳过处理
|
const externalOrderId = String(order.id);
|
||||||
if (existingOrder && !existingOrder.is_editable && !forceUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 保存订单主数据
|
|
||||||
const orderRecord = await this.saveOrder(siteId, orderData);
|
|
||||||
const orderId = orderRecord.id;
|
const orderId = orderRecord.id;
|
||||||
// 保存订单项
|
// 保存订单项
|
||||||
await this.saveOrderItems({
|
await this.saveOrderItems({
|
||||||
|
|
@ -462,7 +451,8 @@ export class OrderService {
|
||||||
* @param order 订单数据
|
* @param order 订单数据
|
||||||
* @returns 保存后的订单实体
|
* @returns 保存后的订单实体
|
||||||
*/
|
*/
|
||||||
async saveOrder(siteId: number, order: Partial<UnifiedOrderDTO>): Promise<Order> {
|
// 这里 omit 是因为处理在外头了 其实 saveOrder 应该包括 savelineitems 等
|
||||||
|
async saveOrder(siteId: number, order: Omit<UnifiedOrderDTO, 'line_items' | 'refunds'>): Promise<Order> {
|
||||||
// 将外部订单ID转换为字符串
|
// 将外部订单ID转换为字符串
|
||||||
const externalOrderId = String(order.id)
|
const externalOrderId = String(order.id)
|
||||||
delete order.id
|
delete order.id
|
||||||
|
|
@ -711,6 +701,8 @@ export class OrderService {
|
||||||
*
|
*
|
||||||
* @param orderItem 订单项实体
|
* @param orderItem 订单项实体
|
||||||
*/
|
*/
|
||||||
|
// TODO 这里存的是库存商品实际
|
||||||
|
// 所以叫做 orderInventoryItems 可能更合适
|
||||||
async saveOrderSale(orderItem: OrderItem) {
|
async saveOrderSale(orderItem: OrderItem) {
|
||||||
const currentOrderSale = await this.orderSaleModel.find({
|
const currentOrderSale = await this.orderSaleModel.find({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -725,50 +717,53 @@ export class OrderService {
|
||||||
// 从数据库查询产品,关联查询组件
|
// 从数据库查询产品,关联查询组件
|
||||||
const product = await this.productModel.findOne({
|
const product = await this.productModel.findOne({
|
||||||
where: { siteSkus: Like(`%${orderItem.sku}%`) },
|
where: { siteSkus: Like(`%${orderItem.sku}%`) },
|
||||||
relations: ['components'],
|
relations: ['components','attributes','attributes.dict'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!product) return;
|
if (!product) return;
|
||||||
|
const componentDetails: { product: Product, quantity: number }[] = product.components?.length > 0 ? await Promise.all(product.components.map(async comp => {
|
||||||
const orderSales: OrderSale[] = [];
|
return {
|
||||||
|
product: await this.productModel.findOne({
|
||||||
if (product.components && product.components.length > 0) {
|
|
||||||
for (const comp of product.components) {
|
|
||||||
const baseProduct = await this.productModel.findOne({
|
|
||||||
where: { sku: comp.sku },
|
where: { sku: comp.sku },
|
||||||
});
|
relations: ['components', 'attributes','attributes.dict'],
|
||||||
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,
|
quantity: comp.quantity * orderItem.quantity,
|
||||||
sku: comp.sku,
|
|
||||||
isPackage: orderItem.name.toLowerCase().includes('package'),
|
|
||||||
});
|
|
||||||
orderSales.push(orderSaleItem);
|
|
||||||
}
|
}
|
||||||
}
|
})) : [{ product, quantity: orderItem.quantity }]
|
||||||
} else {
|
|
||||||
const orderSaleItem: OrderSale = plainToClass(OrderSale, {
|
const orderSales: OrderSale[] = componentDetails.map(componentDetail => {
|
||||||
|
if (!componentDetail.product) return null
|
||||||
|
const attrsObj = this.productService.getAttributesObject(product.attributes)
|
||||||
|
const orderSale = plainToClass(OrderSale, {
|
||||||
orderId: orderItem.orderId,
|
orderId: orderItem.orderId,
|
||||||
siteId: orderItem.siteId,
|
siteId: orderItem.siteId,
|
||||||
externalOrderItemId: orderItem.externalOrderItemId,
|
externalOrderItemId: orderItem.externalOrderItemId,
|
||||||
productId: product.id,
|
productId: componentDetail.product.id,
|
||||||
name: product.name,
|
name: componentDetail.product.name,
|
||||||
quantity: orderItem.quantity,
|
quantity: componentDetail.quantity * orderItem.quantity,
|
||||||
sku: product.sku,
|
sku: componentDetail.product.sku,
|
||||||
isPackage: orderItem.name.toLowerCase().includes('package'),
|
// 理论上直接存 product 的全部数据才是对的,因为这样我的数据才全面。
|
||||||
|
isPackage: componentDetail.product.type === 'bundle',
|
||||||
|
isYoone: attrsObj?.['brand']?.name === 'yoone',
|
||||||
|
isZyn: attrsObj?.['brand']?.name === 'zyn',
|
||||||
|
isZex: attrsObj?.['brand']?.name === 'zex',
|
||||||
|
isYooneNew: attrsObj?.['brand']?.name === 'yoone' && attrsObj?.['version']?.name === 'new',
|
||||||
|
strength: attrsObj?.['strength']?.name,
|
||||||
});
|
});
|
||||||
orderSales.push(orderSaleItem);
|
return orderSale
|
||||||
}
|
}).filter(v => v !== null)
|
||||||
|
console.log("orderSales",orderSales)
|
||||||
if (orderSales.length > 0) {
|
if (orderSales.length > 0) {
|
||||||
await this.orderSaleModel.save(orderSales);
|
await this.orderSaleModel.save(orderSales);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// // extract stren
|
||||||
|
// extractNumberFromString(str: string): number {
|
||||||
|
// if (!str) return 0;
|
||||||
|
|
||||||
|
// const num = parseInt(str, 10);
|
||||||
|
// return isNaN(num) ? 0 : num;
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存订单退款信息
|
* 保存订单退款信息
|
||||||
|
|
@ -1429,7 +1424,7 @@ export class OrderService {
|
||||||
* @param params 查询参数
|
* @param params 查询参数
|
||||||
* @returns 销售统计和分页信息
|
* @returns 销售统计和分页信息
|
||||||
*/
|
*/
|
||||||
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage }: QueryOrderSalesDTO) {
|
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage, orderBy }: QueryOrderSalesDTO) {
|
||||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||||
const defaultStart = dayjs().subtract(30, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
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');
|
const defaultEnd = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
@ -1582,14 +1577,14 @@ export class OrderService {
|
||||||
`;
|
`;
|
||||||
let yooneSql = `
|
let yooneSql = `
|
||||||
SELECT
|
SELECT
|
||||||
SUM(CASE WHEN os.isYoone = 1 AND os.size = 3 THEN os.quantity ELSE 0 END) AS yoone3Quantity,
|
SUM(CASE WHEN os.brand = 'yoone' AND os.strength = '3mg' 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.brand = 'yoone' AND os.strength = '6mg' 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.brand = 'yoone' AND os.strength = '9mg' 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.brand = 'yoone' AND os.strength = '12mg' 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.brand = 'yoone' AND os.strength = '12mg' 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.brand = 'yoone' AND os.strength = '15mg' 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.brand = 'yoone' AND os.strength = '18mg' THEN os.quantity ELSE 0 END) AS yoone18Quantity,
|
||||||
SUM(CASE WHEN os.isZex = 1 THEN os.quantity ELSE 0 END) AS zexQuantity
|
SUM(CASE WHEN os.brand = 'zex' THEN os.quantity ELSE 0 END) AS zexQuantity
|
||||||
FROM order_sale os
|
FROM order_sale os
|
||||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
WHERE o.date_paid BETWEEN ? AND ?
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
|
|
@ -1645,11 +1640,12 @@ export class OrderService {
|
||||||
* @returns 订单项统计和分页信息
|
* @returns 订单项统计和分页信息
|
||||||
*/
|
*/
|
||||||
async getOrderItems({
|
async getOrderItems({
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
siteId,
|
siteId,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
current,
|
sku,
|
||||||
pageSize,
|
|
||||||
name,
|
name,
|
||||||
}: QueryOrderSalesDTO) {
|
}: QueryOrderSalesDTO) {
|
||||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||||
|
|
@ -2648,6 +2644,8 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
|
||||||
throw new Error(`导出CSV文件失败: ${error.message}`);
|
throw new Error(`导出CSV文件失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除每个分号前面一个左右括号和最后一个左右括号包含的内容(包括括号本身)
|
* 删除每个分号前面一个左右括号和最后一个左右括号包含的内容(包括括号本身)
|
||||||
|
|
@ -2730,3 +2728,5 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
=======
|
||||||
|
>>>>>>> 68574dbc7a74e0f8130f195eba4a28dd3887c485
|
||||||
|
|
|
||||||
|
|
@ -1466,7 +1466,12 @@ export class ProductService {
|
||||||
price: num(rec.price),
|
price: num(rec.price),
|
||||||
promotionPrice: num(rec.promotionPrice),
|
promotionPrice: num(rec.promotionPrice),
|
||||||
type: val(rec.type),
|
type: val(rec.type),
|
||||||
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
siteSkus: rec.siteSkus
|
||||||
|
? String(rec.siteSkus)
|
||||||
|
.split(/[;,]/) // 支持英文分号或英文逗号分隔
|
||||||
|
.map(s => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: undefined,
|
||||||
category, // 添加分类字段
|
category, // 添加分类字段
|
||||||
|
|
||||||
attributes: attributes.length > 0 ? attributes : undefined,
|
attributes: attributes.length > 0 ? attributes : undefined,
|
||||||
|
|
@ -1531,7 +1536,14 @@ export class ProductService {
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
getAttributesObject(attributes:DictItem[]){
|
||||||
|
if(!attributes) return {}
|
||||||
|
const obj:any = {}
|
||||||
|
attributes.forEach(attr=>{
|
||||||
|
obj[attr.dict.name] = attr
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
||||||
// 将单个产品转换为 CSV 行数组
|
// 将单个产品转换为 CSV 行数组
|
||||||
transformProductToCsvRow(
|
transformProductToCsvRow(
|
||||||
p: Product,
|
p: Product,
|
||||||
|
|
|
||||||
|
|
@ -73,16 +73,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
@ -269,16 +269,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
@ -466,16 +466,16 @@ export class StatisticsService {
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
orderId,
|
orderId,
|
||||||
SUM(CASE WHEN name LIKE '%zyn%' THEN quantity ELSE 0 END) AS zyn_quantity,
|
SUM(CASE WHEN brand = 'zyn' THEN quantity ELSE 0 END) AS zyn_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' THEN quantity ELSE 0 END) AS yoone_quantity,
|
SUM(CASE WHEN brand = 'yoone' THEN quantity ELSE 0 END) AS yoone_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%zex%' THEN quantity ELSE 0 END) AS zex_quantity,
|
SUM(CASE WHEN brand = 'zex' THEN quantity ELSE 0 END) AS zex_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 1 THEN quantity ELSE 0 END) AS yoone_G_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND isPackage = 0 THEN quantity ELSE 0 END) AS yoone_S_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '3mg' THEN quantity ELSE 0 END) AS yoone_3_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '6mg' THEN quantity ELSE 0 END) AS yoone_6_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '9mg' THEN quantity ELSE 0 END) AS yoone_9_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
SUM(CASE WHEN brand = 'yoone' AND strength = '12mg' THEN quantity ELSE 0 END) AS yoone_12_quantity,
|
||||||
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
SUM(CASE WHEN brand = 'yoone' AND strength = '15mg' THEN quantity ELSE 0 END) AS yoone_15_quantity
|
||||||
FROM order_sale
|
FROM order_sale
|
||||||
GROUP BY orderId
|
GROUP BY orderId
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue