forked from yoone/API
refactor(shopyy): 重构订单DTO和服务逻辑
fix: 修复本地数据库配置端口和密码 feat(statistics): 支持按日/周/月分组统计订单数据 feat(order): 添加订单导出CSV功能 style: 清理无用代码和注释
This commit is contained in:
parent
361b05117c
commit
2f99e27f0f
|
|
@ -25,7 +25,9 @@ import {
|
||||||
ShopyyVariant,
|
ShopyyVariant,
|
||||||
ShopyyWebhook,
|
ShopyyWebhook,
|
||||||
} from '../dto/shopyy.dto';
|
} from '../dto/shopyy.dto';
|
||||||
|
import {
|
||||||
|
OrderStatus,
|
||||||
|
} from '../enums/base.enum';
|
||||||
export class ShopyyAdapter implements ISiteAdapter {
|
export class ShopyyAdapter implements ISiteAdapter {
|
||||||
constructor(private site: any, private shopyyService: ShopyyService) {
|
constructor(private site: any, private shopyyService: ShopyyService) {
|
||||||
this.mapCustomer = this.mapCustomer.bind(this);
|
this.mapCustomer = this.mapCustomer.bind(this);
|
||||||
|
|
@ -129,6 +131,13 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shopyyOrderAutoNextStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||||
|
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||||||
|
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||||||
|
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||||
|
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||||||
|
}
|
||||||
|
|
||||||
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
// 提取账单和送货地址 如果不存在则为空对象
|
// 提取账单和送货地址 如果不存在则为空对象
|
||||||
const billing = (item as any).billing_address || {};
|
const billing = (item as any).billing_address || {};
|
||||||
|
|
@ -202,9 +211,27 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
quantity: p.quantity,
|
quantity: p.quantity,
|
||||||
total: String(p.price ?? ''),
|
total: String(p.price ?? ''),
|
||||||
sku: p.sku || p.sku_code || '',
|
sku: p.sku || p.sku_code || '',
|
||||||
|
price: String(p.price ?? ''),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currencySymbols: Record<string, string> = {
|
||||||
|
'EUR': '€',
|
||||||
|
'USD': '$',
|
||||||
|
'GBP': '£',
|
||||||
|
'JPY': '¥',
|
||||||
|
'AUD': 'A$',
|
||||||
|
'CAD': 'C$',
|
||||||
|
'CHF': 'CHF',
|
||||||
|
'CNY': '¥',
|
||||||
|
'HKD': 'HK$',
|
||||||
|
'NZD': 'NZ$',
|
||||||
|
'SGD': 'S$'
|
||||||
|
// 可以根据需要添加更多货币代码和符号
|
||||||
|
};
|
||||||
|
const originStatus = item.status;
|
||||||
|
item.status = this.shopyyOrderAutoNextStatusMap[originStatus];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id || item.order_id,
|
id: item.id || item.order_id,
|
||||||
number: item.order_number || item.order_sn,
|
number: item.order_number || item.order_sn,
|
||||||
|
|
@ -222,10 +249,15 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
billing_full_address: formatAddress(billingObj),
|
billing_full_address: formatAddress(billingObj),
|
||||||
shipping_full_address: formatAddress(shippingObj),
|
shipping_full_address: formatAddress(shippingObj),
|
||||||
payment_method: item.payment_method,
|
payment_method: item.payment_method,
|
||||||
shipping_lines: item.shipping_lines,
|
shipping_lines: item.fulfillments || [],
|
||||||
fee_lines: item.fee_lines,
|
fee_lines: item.fee_lines || [],
|
||||||
coupon_lines: item.coupon_lines,
|
coupon_lines: item.coupon_lines || [],
|
||||||
|
date_paid: typeof item.pay_at === 'number'
|
||||||
|
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
|
||||||
|
: null,
|
||||||
|
|
||||||
refunds: [],
|
refunds: [],
|
||||||
|
currency_symbol: (currencySymbols[item.currency] || '$') || '',
|
||||||
date_created:
|
date_created:
|
||||||
typeof item.created_at === 'number'
|
typeof item.created_at === 'number'
|
||||||
? new Date(item.created_at * 1000).toISOString()
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
|
@ -241,7 +273,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||||||
// 处理多地址结构
|
// 处理多地址结构
|
||||||
|
|
|
||||||
|
|
@ -246,17 +246,18 @@ export interface ShopyyOrder {
|
||||||
updated_at?: number | string;
|
updated_at?: number | string;
|
||||||
date_updated?: string;
|
date_updated?: string;
|
||||||
last_modified?: string;
|
last_modified?: string;
|
||||||
|
// 支付时间
|
||||||
|
pay_at?: number ;
|
||||||
// 配送方式
|
// 配送方式
|
||||||
shipping_lines?: Array<UnifiedShippingLineDTO>;
|
shipping_lines?: Array<ShopyyShippingLineDTO>;
|
||||||
// 费用项
|
// 费用项
|
||||||
fee_lines?: Array<UnifiedFeeLineDTO>;
|
fee_lines?: Array<ShopyyFeeLineDTO>;
|
||||||
// 优惠券项
|
// 优惠券项
|
||||||
coupon_lines?: Array<UnifiedCouponLineDTO>;
|
coupon_lines?: Array<ShopyyCouponLineDTO>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class UnifiedShippingLineDTO {
|
export class ShopyyShippingLineDTO {
|
||||||
// 配送方式DTO用于承载统一配送方式数据
|
// 配送方式DTO用于承载统一配送方式数据
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
|
|
||||||
|
|
@ -274,7 +275,7 @@ export class UnifiedShippingLineDTO {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedFeeLineDTO {
|
export class ShopyyFeeLineDTO {
|
||||||
// 费用项DTO用于承载统一费用项数据
|
// 费用项DTO用于承载统一费用项数据
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
|
|
||||||
|
|
@ -293,7 +294,7 @@ export class UnifiedFeeLineDTO {
|
||||||
meta_data?: any[];
|
meta_data?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCouponLineDTO {
|
export class ShopyyCouponLineDTO {
|
||||||
// 优惠券项DTO用于承载统一优惠券项数据
|
// 优惠券项DTO用于承载统一优惠券项数据
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
code?: string;
|
code?: string;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,12 @@ export class UnifiedPaginationDTO<T> {
|
||||||
|
|
||||||
@ApiProperty({ description: '总页数', example: 5 })
|
@ApiProperty({ description: '总页数', example: 5 })
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分页后的数据', required: false })
|
||||||
|
after?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分页前的数据', required: false })
|
||||||
|
before?: string;
|
||||||
}
|
}
|
||||||
export class UnifiedTagDTO {
|
export class UnifiedTagDTO {
|
||||||
// 标签DTO用于承载统一标签数据
|
// 标签DTO用于承载统一标签数据
|
||||||
|
|
@ -258,6 +264,9 @@ export class UnifiedOrderDTO {
|
||||||
@ApiProperty({ description: '货币' })
|
@ApiProperty({ description: '货币' })
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '货币符号' })
|
||||||
|
currency_symbol?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '总金额' })
|
@ApiProperty({ description: '总金额' })
|
||||||
total: string;
|
total: string;
|
||||||
|
|
||||||
|
|
@ -312,6 +321,9 @@ export class UnifiedOrderDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false })
|
@ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false })
|
||||||
coupon_lines?: UnifiedCouponLineDTO[];
|
coupon_lines?: UnifiedCouponLineDTO[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '支付时间', required: false })
|
||||||
|
date_paid?: string ;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedShippingLineDTO {
|
export class UnifiedShippingLineDTO {
|
||||||
|
|
@ -324,7 +336,7 @@ export class UnifiedShippingLineDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '配送方式实例ID' })
|
@ApiProperty({ description: '配送方式实例ID' })
|
||||||
method_id?: string;
|
method_id?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '配送方式金额' })
|
@ApiProperty({ description: '配送方式金额' })
|
||||||
total?: string;
|
total?: string;
|
||||||
|
|
||||||
|
|
@ -336,7 +348,7 @@ export class UnifiedShippingLineDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '配送方式元数据' })
|
@ApiProperty({ description: '配送方式元数据' })
|
||||||
meta_data?: any[];
|
meta_data?: any[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedFeeLineDTO {
|
export class UnifiedFeeLineDTO {
|
||||||
|
|
@ -347,23 +359,23 @@ export class UnifiedFeeLineDTO {
|
||||||
@ApiProperty({ description: '费用项名称' })
|
@ApiProperty({ description: '费用项名称' })
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '税率类' })
|
@ApiProperty({ description: '税率类' })
|
||||||
tax_class?: string;
|
tax_class?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '税率状态' })
|
@ApiProperty({ description: '税率状态' })
|
||||||
tax_status?: string;
|
tax_status?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '总金额' })
|
@ApiProperty({ description: '总金额' })
|
||||||
total?: string;
|
total?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '总税额' })
|
@ApiProperty({ description: '总税额' })
|
||||||
total_tax?: string;
|
total_tax?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '税额详情' })
|
@ApiProperty({ description: '税额详情' })
|
||||||
taxes?: any[];
|
taxes?: any[];
|
||||||
|
|
||||||
@ApiProperty({ description: '元数据' })
|
@ApiProperty({ description: '元数据' })
|
||||||
meta_data?: any[];
|
meta_data?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCouponLineDTO {
|
export class UnifiedCouponLineDTO {
|
||||||
|
|
@ -371,17 +383,17 @@ export class UnifiedCouponLineDTO {
|
||||||
@ApiProperty({ description: '优惠券项ID' })
|
@ApiProperty({ description: '优惠券项ID' })
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
|
|
||||||
@ApiProperty({ description: '优惠券项代码' })
|
@ApiProperty({ description: '优惠券项代码' })
|
||||||
code?: string;
|
code?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '优惠券项折扣' })
|
@ApiProperty({ description: '优惠券项折扣' })
|
||||||
discount?: string;
|
discount?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '优惠券项税额' })
|
@ApiProperty({ description: '优惠券项税额' })
|
||||||
discount_tax?: string;
|
discount_tax?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '优惠券项元数据' })
|
@ApiProperty({ description: '优惠券项元数据' })
|
||||||
meta_data?: any[];
|
meta_data?: any[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -619,6 +631,12 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
||||||
|
|
||||||
@ApiProperty({ description: '过滤条件对象', type: 'object', required: false })
|
@ApiProperty({ description: '过滤条件对象', type: 'object', required: false })
|
||||||
where?: Where;
|
where?: Where;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间后', required: false })
|
||||||
|
after?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间前', required: false })
|
||||||
|
before?: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: '排序对象,例如 { "sku": "desc" }',
|
description: '排序对象,例如 { "sku": "desc" }',
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,9 @@ export class OrderStatisticsParams {
|
||||||
@ApiProperty({ enum: ['all', 'zyn', 'yoone', 'zolt'], default: 'all' })
|
@ApiProperty({ enum: ['all', 'zyn', 'yoone', 'zolt'], default: 'all' })
|
||||||
@Rule(RuleType.string().valid('all', 'zyn', 'yoone', 'zolt'))
|
@Rule(RuleType.string().valid('all', 'zyn', 'yoone', 'zolt'))
|
||||||
brand: string;
|
brand: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: ['day', 'week', 'month'], default: 'day' })
|
||||||
|
@Rule(RuleType.string().valid('day', 'week', 'month'))
|
||||||
|
grouping: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,9 @@ 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 { OrderItemOriginal } from '../entity/order_item_original.entity';
|
||||||
import { SiteApiService } from './site-api.service';
|
import { SiteApiService } from './site-api.service';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
|
|
||||||
|
|
@ -96,8 +98,22 @@ export class OrderService {
|
||||||
siteApiService: SiteApiService;
|
siteApiService: SiteApiService;
|
||||||
|
|
||||||
async syncOrders(siteId: number, params: Record<string, any> = {}) {
|
async syncOrders(siteId: number, params: Record<string, any> = {}) {
|
||||||
|
const daysRange = 7;
|
||||||
|
|
||||||
|
// 获取当前时间和7天前时间
|
||||||
|
const now = new Date();
|
||||||
|
const sevenDaysAgo = new Date();
|
||||||
|
sevenDaysAgo.setDate(now.getDate() - daysRange);
|
||||||
|
|
||||||
|
// 格式化时间为ISO 8601
|
||||||
|
const after = sevenDaysAgo.toISOString();
|
||||||
|
const before = now.toISOString();
|
||||||
// 调用 WooCommerce API 获取订单
|
// 调用 WooCommerce API 获取订单
|
||||||
const result = await (await this.siteApiService.getAdapter(siteId)).getAllOrders(params);
|
const result = await (await this.siteApiService.getAdapter(siteId)).getAllOrders({
|
||||||
|
...params,
|
||||||
|
after,
|
||||||
|
before,
|
||||||
|
});
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let failureCount = 0;
|
let failureCount = 0;
|
||||||
|
|
@ -135,12 +151,7 @@ export class OrderService {
|
||||||
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
|
[OrderStatus.RETURN_CANCELLED]: OrderStatus.REFUNDED // 已取消退款转为 refunded
|
||||||
}
|
}
|
||||||
|
|
||||||
shopyyOrderAutoNextStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
|
||||||
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
|
||||||
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
|
||||||
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
|
||||||
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
|
||||||
}
|
|
||||||
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
|
// 由于 wordpress 订单状态和 我们的订单状态 不一致,需要做转换
|
||||||
async autoUpdateOrderStatus(siteId: number, order: any) {
|
async autoUpdateOrderStatus(siteId: number, order: any) {
|
||||||
console.log('更新订单状态', order)
|
console.log('更新订单状态', order)
|
||||||
|
|
@ -188,28 +199,6 @@ export class OrderService {
|
||||||
await this.orderModel.update({ externalOrderId: order.id, siteId: siteId }, {
|
await this.orderModel.update({ externalOrderId: order.id, siteId: siteId }, {
|
||||||
orderStatus: order.status,
|
orderStatus: order.status,
|
||||||
})
|
})
|
||||||
}else if(site.type === 'shopyy'){
|
|
||||||
const originStatus = orderData.status;
|
|
||||||
orderData.status= this.shopyyOrderAutoNextStatusMap[originStatus];
|
|
||||||
// 根据currency_code获取对应货币符号
|
|
||||||
const currencySymbols: Record<string, string> = {
|
|
||||||
'EUR': '€',
|
|
||||||
'USD': '$',
|
|
||||||
'GBP': '£',
|
|
||||||
'JPY': '¥',
|
|
||||||
'AUD': 'A$',
|
|
||||||
'CAD': 'C$',
|
|
||||||
'CHF': 'CHF',
|
|
||||||
'CNY': '¥',
|
|
||||||
'HKD': 'HK$',
|
|
||||||
'NZD': 'NZ$',
|
|
||||||
'SGD': 'S$'
|
|
||||||
// 可以根据需要添加更多货币代码和符号
|
|
||||||
};
|
|
||||||
if (orderData.currency) {
|
|
||||||
const currencyCode = orderData.currency.toUpperCase();
|
|
||||||
orderData.currency_symbol = currencySymbols[currencyCode] || '$';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const externalOrderId = order.id;
|
const externalOrderId = order.id;
|
||||||
if (
|
if (
|
||||||
|
|
@ -1733,4 +1722,205 @@ export class OrderService {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async exportOrder(ids: number[]) {
|
||||||
|
// 日期 订单号 姓名地址 邮箱 号码 订单内容 盒数 换盒数 换货内容 快递号
|
||||||
|
interface ExportData {
|
||||||
|
'日期': string;
|
||||||
|
'订单号': string;
|
||||||
|
'姓名地址': string;
|
||||||
|
'邮箱': string;
|
||||||
|
'号码': string;
|
||||||
|
'订单内容': string;
|
||||||
|
'盒数': number;
|
||||||
|
'换盒数': number;
|
||||||
|
'换货内容': string;
|
||||||
|
'快递号': string;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 空值检查
|
||||||
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
|
|
||||||
|
// 优化事务使用
|
||||||
|
return await dataSource.transaction(async manager => {
|
||||||
|
// 准备查询条件
|
||||||
|
const whereCondition: any = {};
|
||||||
|
if (ids && ids.length > 0) {
|
||||||
|
whereCondition.id = In(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订单、订单项和物流信息
|
||||||
|
const orders = await manager.getRepository(Order).find({
|
||||||
|
where: whereCondition,
|
||||||
|
relations: ['shipment']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orders.length === 0) {
|
||||||
|
throw new Error('未找到匹配的订单');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有订单ID
|
||||||
|
const orderIds = orders.map(order => order.id);
|
||||||
|
|
||||||
|
// 获取所有订单项
|
||||||
|
const orderItems = await manager.getRepository(OrderItem).find({
|
||||||
|
where: {
|
||||||
|
orderId: In(orderIds)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按订单ID分组订单项
|
||||||
|
const orderItemsByOrderId = orderItems.reduce((acc, item) => {
|
||||||
|
if (!acc[item.orderId]) {
|
||||||
|
acc[item.orderId] = [];
|
||||||
|
}
|
||||||
|
acc[item.orderId].push(item);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<number, OrderItem[]>);
|
||||||
|
|
||||||
|
// 构建导出数据
|
||||||
|
const exportDataList: ExportData[] = orders.map(order => {
|
||||||
|
// 获取订单的订单项
|
||||||
|
const items = orderItemsByOrderId[order.id] || [];
|
||||||
|
|
||||||
|
// 计算总盒数
|
||||||
|
const boxCount = items.reduce((total, item) => total + item.quantity, 0);
|
||||||
|
|
||||||
|
// 构建订单内容
|
||||||
|
const orderContent = items.map(item => `${item.name} (${item.sku || ''}) x ${item.quantity}`).join('; ');
|
||||||
|
|
||||||
|
// 构建姓名地址
|
||||||
|
const shipping = order.shipping;
|
||||||
|
const billing = order.billing;
|
||||||
|
const firstName = shipping?.first_name || billing?.first_name || '';
|
||||||
|
const lastName = shipping?.last_name || billing?.last_name || '';
|
||||||
|
const name = `${firstName} ${lastName}`.trim() || '';
|
||||||
|
const address = shipping?.address_1 || billing?.address_1 || '';
|
||||||
|
const address2 = shipping?.address_2 || billing?.address_2 || '';
|
||||||
|
const city = shipping?.city || billing?.city || '';
|
||||||
|
const state = shipping?.state || billing?.state || '';
|
||||||
|
const postcode = shipping?.postcode || billing?.postcode || '';
|
||||||
|
const country = shipping?.country || billing?.country || '';
|
||||||
|
const nameAddress = `${name} ${address} ${address2} ${city} ${state} ${postcode} ${country}`;
|
||||||
|
|
||||||
|
// 获取电话号码
|
||||||
|
const phone = shipping?.phone || billing?.phone || '';
|
||||||
|
|
||||||
|
// 获取快递号
|
||||||
|
const trackingNumber = order.shipment?.tracking_id || '';
|
||||||
|
|
||||||
|
// 暂时没有换货相关数据,默认为0和空字符串
|
||||||
|
const exchangeBoxCount = 0;
|
||||||
|
const exchangeContent = '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
'日期': order.date_created?.toISOString().split('T')[0] || '',
|
||||||
|
'订单号': order.externalOrderId || '',
|
||||||
|
'姓名地址': nameAddress,
|
||||||
|
'邮箱': order.customer_email || '',
|
||||||
|
'号码': phone,
|
||||||
|
'订单内容': orderContent,
|
||||||
|
'盒数': boxCount,
|
||||||
|
'换盒数': exchangeBoxCount,
|
||||||
|
'换货内容': exchangeContent,
|
||||||
|
'快递号': trackingNumber
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回CSV字符串内容给前端
|
||||||
|
const csvContent = await this.exportToCsv(exportDataList, { type: 'string' });
|
||||||
|
return csvContent;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`导出订单失败:${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出数据为CSV格式
|
||||||
|
* @param {any[]} data 数据数组
|
||||||
|
* @param {Object} options 配置选项
|
||||||
|
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
||||||
|
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
||||||
|
* @param {boolean} [options.writeFile=false] 是否写入文件
|
||||||
|
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
||||||
|
*/
|
||||||
|
async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?: string; writeFile?: boolean } = {}): Promise<string | Buffer> {
|
||||||
|
try {
|
||||||
|
// 检查数据是否为空
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
throw new Error('导出数据不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type = 'string', fileName, writeFile = false } = options;
|
||||||
|
|
||||||
|
// 生成表头
|
||||||
|
const headers = Object.keys(data[0]);
|
||||||
|
let csvContent = headers.join(',') + '\n';
|
||||||
|
|
||||||
|
// 处理数据行
|
||||||
|
data.forEach(item => {
|
||||||
|
const row = headers.map(key => {
|
||||||
|
const value = item[key as keyof any];
|
||||||
|
// 处理特殊字符
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// 转义双引号,将"替换为""
|
||||||
|
const escapedValue = value.replace(/"/g, '""');
|
||||||
|
// 如果包含逗号或换行符,需要用双引号包裹
|
||||||
|
if (escapedValue.includes(',') || escapedValue.includes('\n')) {
|
||||||
|
return `"${escapedValue}"`;
|
||||||
|
}
|
||||||
|
return escapedValue;
|
||||||
|
}
|
||||||
|
// 处理日期类型
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return value.toISOString();
|
||||||
|
}
|
||||||
|
// 处理undefined和null
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}).join(',');
|
||||||
|
csvContent += row + '\n';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果需要写入文件
|
||||||
|
if (writeFile && fileName) {
|
||||||
|
// 获取当前用户目录
|
||||||
|
const userHomeDir = os.homedir();
|
||||||
|
|
||||||
|
// 构建目标路径(下载目录)
|
||||||
|
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
||||||
|
|
||||||
|
// 确保下载目录存在
|
||||||
|
if (!fs.existsSync(downloadsDir)) {
|
||||||
|
fs.mkdirSync(downloadsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(downloadsDir, fileName);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(filePath, csvContent, 'utf8');
|
||||||
|
|
||||||
|
console.log(`数据已成功导出至 ${filePath}`);
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型返回不同结果
|
||||||
|
if (type === 'buffer') {
|
||||||
|
return Buffer.from(csvContent, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return csvContent;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出CSV时出错:', error);
|
||||||
|
throw new Error(`导出CSV文件失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Site } from '../entity/site.entity';
|
||||||
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
||||||
import { ShopyyReview } from '../dto/shopyy.dto';
|
import { ShopyyReview } from '../dto/shopyy.dto';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
|
import { UnifiedSearchParamsDTO } from '../dto/site-api.dto';
|
||||||
/**
|
/**
|
||||||
* ShopYY平台服务实现
|
* ShopYY平台服务实现
|
||||||
*/
|
*/
|
||||||
|
|
@ -286,6 +287,14 @@ export class ShopyyService {
|
||||||
const response = await this.request(site, `products/${productId}/variations/${variationId}`, 'GET');
|
const response = await this.request(site, `products/${productId}/variations/${variationId}`, 'GET');
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
mapOrderSearchParams(params: UnifiedSearchParamsDTO){
|
||||||
|
const { after, before, ...restParams } = params;
|
||||||
|
return {
|
||||||
|
...restParams,
|
||||||
|
...(after ? { created_at_min: after } : {}),
|
||||||
|
...(before ? { created_at_max: before } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取ShopYY订单列表
|
* 获取ShopYY订单列表
|
||||||
|
|
@ -294,14 +303,15 @@ export class ShopyyService {
|
||||||
* @param pageSize 每页数量
|
* @param pageSize 每页数量
|
||||||
* @returns 分页订单列表
|
* @returns 分页订单列表
|
||||||
*/
|
*/
|
||||||
async getOrders(site: any | number, page: number = 1, pageSize: number = 100): Promise<any> {
|
async getOrders(site: any | number, page: number = 1, pageSize: number = 100, params: UnifiedSearchParamsDTO = {}): Promise<any> {
|
||||||
// 如果传入的是站点ID,则获取站点配置
|
// 如果传入的是站点ID,则获取站点配置
|
||||||
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
|
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
|
||||||
|
|
||||||
// ShopYY API: GET /orders
|
// ShopYY API: GET /orders
|
||||||
const response = await this.request(siteConfig, 'orders', 'GET', null, {
|
const response = await this.request(siteConfig, 'orders', 'GET', null, {
|
||||||
page,
|
page,
|
||||||
page_size: pageSize
|
limit: pageSize,
|
||||||
|
...params
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -313,7 +323,7 @@ export class ShopyyService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllOrders(site: any | number, params: Record<string, any> = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
|
async getAllOrders(site: any | number, params: Record<string, any> = {}, maxPages: number = 100, concurrencyLimit: number = 100): Promise<any> {
|
||||||
const firstPage = await this.getOrders(site, 1, 100);
|
const firstPage = await this.getOrders(site, 1, 100);
|
||||||
|
|
||||||
const { items: firstPageItems, totalPages} = firstPage;
|
const { items: firstPageItems, totalPages} = firstPage;
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,13 @@ export class StatisticsService {
|
||||||
orderItemRepository: Repository<OrderItem>;
|
orderItemRepository: Repository<OrderItem>;
|
||||||
|
|
||||||
async getOrderStatistics(params: OrderStatisticsParams) {
|
async getOrderStatistics(params: OrderStatisticsParams) {
|
||||||
const { startDate, endDate, siteId } = params;
|
const { startDate, endDate, siteId ,grouping} = params;
|
||||||
// const keywords = keyword ? keyword.split(' ').filter(Boolean) : [];
|
// const keywords = keyword ? keyword.split(' ').filter(Boolean) : [];
|
||||||
const start = dayjs(startDate).format('YYYY-MM-DD');
|
const start = dayjs(startDate).format('YYYY-MM-DD');
|
||||||
const end = dayjs(endDate).add(1, 'd').format('YYYY-MM-DD');
|
const end = dayjs(endDate).add(1, 'd').format('YYYY-MM-DD');
|
||||||
let sql = `
|
let sql = '';
|
||||||
|
if (!grouping || grouping === 'day') {
|
||||||
|
sql = `
|
||||||
WITH first_order AS (
|
WITH first_order AS (
|
||||||
SELECT customer_email, MIN(date_paid) AS first_purchase_date
|
SELECT customer_email, MIN(date_paid) AS first_purchase_date
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
|
|
@ -214,6 +216,393 @@ export class StatisticsService {
|
||||||
dt.can_total_orders
|
dt.can_total_orders
|
||||||
ORDER BY d.order_date DESC;
|
ORDER BY d.order_date DESC;
|
||||||
`;
|
`;
|
||||||
|
}else if (grouping === 'week') {
|
||||||
|
sql = `WITH first_order AS (
|
||||||
|
SELECT customer_email, MIN(date_paid) AS first_purchase_date
|
||||||
|
FROM \`order\`
|
||||||
|
GROUP BY customer_email
|
||||||
|
),
|
||||||
|
weekly_orders AS (
|
||||||
|
SELECT
|
||||||
|
o.id AS order_id,
|
||||||
|
DATE_FORMAT(o.date_paid, '%Y-%u') AS order_date,
|
||||||
|
o.customer_email,
|
||||||
|
o.total,
|
||||||
|
o.source_type,
|
||||||
|
o.utm_source,
|
||||||
|
o.siteId,
|
||||||
|
CASE
|
||||||
|
WHEN o.date_paid = f.first_purchase_date THEN 'first_purchase'
|
||||||
|
ELSE 'repeat_purchase'
|
||||||
|
END AS purchase_type,
|
||||||
|
CASE
|
||||||
|
WHEN o.source_type = 'utm' AND o.utm_source = 'google' THEN 'cpc'
|
||||||
|
ELSE 'non_cpc'
|
||||||
|
END AS order_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%zyn%' THEN 'zyn' ELSE 'non_zyn' END) AS zyn_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%yoone%' THEN 'yoone' ELSE 'non_yoone' END) AS yoone_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%zex%' THEN 'zex' ELSE 'non_zex' END) AS zex_type
|
||||||
|
FROM \`order\` o
|
||||||
|
LEFT JOIN first_order f ON o.customer_email = f.customer_email
|
||||||
|
LEFT JOIN order_item oi ON o.id = oi.orderId
|
||||||
|
WHERE o.date_paid IS NOT NULL
|
||||||
|
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
||||||
|
AND o.status IN ('processing','completed')
|
||||||
|
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
||||||
|
),
|
||||||
|
order_sales_summary AS (
|
||||||
|
SELECT
|
||||||
|
orderId,
|
||||||
|
SUM(CASE WHEN name LIKE '%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 name LIKE '%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 name LIKE '%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 name LIKE '%yoone%' AND name LIKE '%6%' 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 name LIKE '%yoone%' AND name LIKE '%12%' 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
|
||||||
|
FROM order_sale
|
||||||
|
GROUP BY orderId
|
||||||
|
),
|
||||||
|
order_items_summary AS (
|
||||||
|
SELECT
|
||||||
|
orderId,
|
||||||
|
SUM(CASE WHEN name LIKE '%zyn%' THEN total + total_tax ELSE 0 END) AS zyn_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' THEN total + total_tax ELSE 0 END) AS yoone_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%zex%' THEN total + total_tax ELSE 0 END) AS zex_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%package%' THEN total + total_tax ELSE 0 END) AS yoone_G_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name NOT LIKE '%package%' THEN total + total_tax ELSE 0 END) AS yoone_S_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN total + total_tax ELSE 0 END) AS yoone_3_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN total + total_tax ELSE 0 END) AS yoone_6_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN total + total_tax ELSE 0 END) AS yoone_9_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN total + total_tax ELSE 0 END) AS yoone_12_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN total + total_tax ELSE 0 END) AS yoone_15_amount
|
||||||
|
FROM order_item
|
||||||
|
GROUP BY orderId
|
||||||
|
),
|
||||||
|
weekly_totals AS (
|
||||||
|
SELECT order_date, SUM(total) AS total_amount,
|
||||||
|
SUM(CASE WHEN siteId = 1 THEN total ELSE 0 END) AS togo_total_amount,
|
||||||
|
SUM(CASE WHEN siteId = 2 THEN total ELSE 0 END) AS can_total_amount,
|
||||||
|
COUNT(DISTINCT order_id) AS total_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN siteId = 1 THEN order_id END) AS togo_total_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN siteId = 2 THEN order_id END) AS can_total_orders,
|
||||||
|
SUM(CASE WHEN purchase_type = 'first_purchase' THEN total ELSE 0 END) AS first_purchase_total,
|
||||||
|
SUM(CASE WHEN purchase_type = 'repeat_purchase' THEN total ELSE 0 END) AS repeat_purchase_total,
|
||||||
|
SUM(CASE WHEN order_type = 'cpc' THEN total ELSE 0 END) AS cpc_total,
|
||||||
|
SUM(CASE WHEN order_type = 'non_cpc' THEN total ELSE 0 END) AS non_cpc_total,
|
||||||
|
SUM(CASE WHEN zyn_type = 'zyn' AND order_type = 'cpc' THEN total ELSE 0 END) AS zyn_total,
|
||||||
|
SUM(CASE WHEN zyn_type = 'zyn' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_zyn_total,
|
||||||
|
SUM(CASE WHEN yoone_type = 'yoone' AND order_type = 'cpc' THEN total ELSE 0 END) AS yoone_total,
|
||||||
|
SUM(CASE WHEN yoone_type = 'yoone' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_yoone_total,
|
||||||
|
SUM(CASE WHEN zex_type = 'zex' AND order_type = 'cpc' THEN total ELSE 0 END) AS zex_total,
|
||||||
|
SUM(CASE WHEN zex_type = 'zex' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_zex_total,
|
||||||
|
SUM(CASE WHEN source_type = 'typein' THEN total ELSE 0 END) AS direct_total,
|
||||||
|
SUM(CASE WHEN source_type = 'organic' THEN total ELSE 0 END) AS organic_total
|
||||||
|
FROM weekly_orders
|
||||||
|
GROUP BY order_date
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
wt.order_date,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.purchase_type = 'first_purchase' THEN wo.order_id END) AS first_purchase_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.purchase_type = 'repeat_purchase' THEN wo.order_id END) AS repeat_purchase_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'cpc' THEN wo.order_id END) AS cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'cpc' AND wo.siteId = 1 THEN wo.order_id END) AS togo_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'cpc' AND wo.siteId = 2 THEN wo.order_id END) AS can_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'non_cpc' THEN wo.order_id END) AS non_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'non_cpc' AND wo.siteId = 1 THEN wo.order_id END) AS non_togo_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.order_type = 'non_cpc' AND wo.siteId = 2 THEN wo.order_id END) AS non_can_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.zyn_type = 'zyn' AND wo.order_type = 'cpc' THEN wo.order_id END) AS zyn_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.zyn_type = 'zyn' AND wo.order_type = 'non_cpc' THEN wo.order_id END) AS non_zyn_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.yoone_type = 'yoone' AND wo.order_type = 'cpc' THEN wo.order_id END) AS yoone_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.yoone_type = 'yoone' AND wo.order_type = 'non_cpc' THEN wo.order_id END) AS non_yoone_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.zex_type = 'zex' AND wo.order_type = 'cpc' THEN wo.order_id END) AS zex_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.zex_type = 'zex' AND wo.order_type = 'non_cpc' THEN wo.order_id END) AS non_zex_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.source_type = 'typein' THEN wo.order_id END) AS direct_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN wo.source_type = 'organic' THEN wo.order_id END) AS organic_orders,
|
||||||
|
wt.total_orders,
|
||||||
|
wt.togo_total_orders,
|
||||||
|
wt.can_total_orders,
|
||||||
|
wt.total_amount,
|
||||||
|
wt.togo_total_amount,
|
||||||
|
wt.can_total_amount,
|
||||||
|
wt.first_purchase_total,
|
||||||
|
wt.repeat_purchase_total,
|
||||||
|
wt.cpc_total,
|
||||||
|
wt.non_cpc_total,
|
||||||
|
wt.zyn_total,
|
||||||
|
wt.non_zyn_total,
|
||||||
|
wt.yoone_total,
|
||||||
|
wt.non_yoone_total,
|
||||||
|
wt.zex_total,
|
||||||
|
wt.non_zex_total,
|
||||||
|
wt.direct_total,
|
||||||
|
wt.organic_total,
|
||||||
|
COALESCE(SUM(os.zyn_quantity), 0) AS zyn_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.zyn_quantity ELSE 0 END) AS cpc_zyn_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.zyn_quantity ELSE 0 END) AS non_cpc_zyn_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_quantity), 0) AS yoone_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_quantity ELSE 0 END) AS cpc_yoone_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_quantity ELSE 0 END) AS non_cpc_yoone_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_G_quantity), 0) AS yoone_G_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_G_quantity ELSE 0 END) AS cpc_yoone_G_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_G_quantity ELSE 0 END) AS non_cpc_yoone_G_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_S_quantity), 0) AS yoone_S_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_S_quantity ELSE 0 END) AS cpc_yoone_S_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_S_quantity ELSE 0 END) AS non_cpc_yoone_S_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_3_quantity), 0) AS yoone_3_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_3_quantity ELSE 0 END) AS cpc_yoone_3_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_3_quantity ELSE 0 END) AS non_cpc_yoone_3_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_6_quantity), 0) AS yoone_6_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_6_quantity ELSE 0 END) AS cpc_yoone_6_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_6_quantity ELSE 0 END) AS non_cpc_yoone_6_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_9_quantity), 0) AS yoone_9_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_9_quantity ELSE 0 END) AS cpc_yoone_9_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_9_quantity ELSE 0 END) AS non_cpc_yoone_9_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_12_quantity), 0) AS yoone_12_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_12_quantity ELSE 0 END) AS cpc_yoone_12_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_12_quantity ELSE 0 END) AS non_cpc_yoone_12_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_15_quantity), 0) AS yoone_15_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.yoone_15_quantity ELSE 0 END) AS cpc_yoone_15_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.yoone_15_quantity ELSE 0 END) AS non_cpc_yoone_15_quantity,
|
||||||
|
COALESCE(SUM(os.zex_quantity), 0) AS zex_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'cpc' THEN os.zex_quantity ELSE 0 END) AS cpc_zex_quantity,
|
||||||
|
SUM(CASE WHEN wo.order_type = 'non_cpc' THEN os.zex_quantity ELSE 0 END) AS non_cpc_zex_quantity,
|
||||||
|
COALESCE(SUM(oi.zyn_amount), 0) AS zyn_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_amount), 0) AS yoone_amount,
|
||||||
|
COALESCE(SUM(oi.zex_amount), 0) AS zex_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_G_amount), 0) AS yoone_G_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_S_amount), 0) AS yoone_S_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_3_amount), 0) AS yoone_3_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_6_amount), 0) AS yoone_6_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_9_amount), 0) AS yoone_9_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_12_amount), 0) AS yoone_12_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_15_amount), 0) AS yoone_15_amount,
|
||||||
|
ROUND(COALESCE(wt.total_amount / wt.total_orders, 0), 2) AS avg_total_amount,
|
||||||
|
ROUND(COALESCE(wt.togo_total_amount / wt.togo_total_orders, 0), 2) AS avg_togo_total_amount,
|
||||||
|
ROUND(COALESCE(wt.can_total_amount / wt.can_total_orders, 0), 2) AS avg_can_total_amount
|
||||||
|
FROM weekly_orders wo
|
||||||
|
LEFT JOIN weekly_totals wt ON wo.order_date = wt.order_date
|
||||||
|
LEFT JOIN order_sales_summary os ON wo.order_id = os.orderId
|
||||||
|
LEFT JOIN order_items_summary oi ON wo.order_id = oi.orderId
|
||||||
|
GROUP BY
|
||||||
|
wt.order_date,
|
||||||
|
wt.total_amount,
|
||||||
|
wt.togo_total_amount,
|
||||||
|
wt.can_total_amount,
|
||||||
|
wt.first_purchase_total,
|
||||||
|
wt.repeat_purchase_total,
|
||||||
|
wt.cpc_total,
|
||||||
|
wt.non_cpc_total,
|
||||||
|
wt.zyn_total,
|
||||||
|
wt.non_zyn_total,
|
||||||
|
wt.yoone_total,
|
||||||
|
wt.non_yoone_total,
|
||||||
|
wt.zex_total,
|
||||||
|
wt.non_zex_total,
|
||||||
|
wt.direct_total,
|
||||||
|
wt.organic_total,
|
||||||
|
wt.total_orders,
|
||||||
|
wt.togo_total_orders,
|
||||||
|
wt.can_total_orders
|
||||||
|
ORDER BY wt.order_date DESC;
|
||||||
|
`;
|
||||||
|
}else if (grouping === 'month') {
|
||||||
|
sql = `WITH first_order AS (
|
||||||
|
SELECT customer_email, MIN(date_paid) AS first_purchase_date
|
||||||
|
FROM \`order\`
|
||||||
|
GROUP BY customer_email
|
||||||
|
),
|
||||||
|
monthly_orders AS (
|
||||||
|
SELECT
|
||||||
|
o.id AS order_id,
|
||||||
|
DATE_FORMAT(o.date_paid, '%Y-%m') AS order_date,
|
||||||
|
o.customer_email,
|
||||||
|
o.total,
|
||||||
|
o.source_type,
|
||||||
|
o.utm_source,
|
||||||
|
o.siteId,
|
||||||
|
CASE
|
||||||
|
WHEN o.date_paid = f.first_purchase_date THEN 'first_purchase'
|
||||||
|
ELSE 'repeat_purchase'
|
||||||
|
END AS purchase_type,
|
||||||
|
CASE
|
||||||
|
WHEN o.source_type = 'utm' AND o.utm_source = 'google' THEN 'cpc'
|
||||||
|
ELSE 'non_cpc'
|
||||||
|
END AS order_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%zyn%' THEN 'zyn' ELSE 'non_zyn' END) AS zyn_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%yoone%' THEN 'yoone' ELSE 'non_yoone' END) AS yoone_type,
|
||||||
|
MAX(CASE WHEN oi.name LIKE '%zex%' THEN 'zex' ELSE 'non_zex' END) AS zex_type
|
||||||
|
FROM \`order\` o
|
||||||
|
LEFT JOIN first_order f ON o.customer_email = f.customer_email
|
||||||
|
LEFT JOIN order_item oi ON o.id = oi.orderId
|
||||||
|
WHERE o.date_paid IS NOT NULL
|
||||||
|
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
||||||
|
AND o.status IN ('processing','completed')
|
||||||
|
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
||||||
|
),
|
||||||
|
order_sales_summary AS (
|
||||||
|
SELECT
|
||||||
|
orderId,
|
||||||
|
SUM(CASE WHEN name LIKE '%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 name LIKE '%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 name LIKE '%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 name LIKE '%yoone%' AND name LIKE '%6%' 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 name LIKE '%yoone%' AND name LIKE '%12%' 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
|
||||||
|
FROM order_sale
|
||||||
|
GROUP BY orderId
|
||||||
|
),
|
||||||
|
order_items_summary AS (
|
||||||
|
SELECT
|
||||||
|
orderId,
|
||||||
|
SUM(CASE WHEN name LIKE '%zyn%' THEN total + total_tax ELSE 0 END) AS zyn_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' THEN total + total_tax ELSE 0 END) AS yoone_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%zex%' THEN total + total_tax ELSE 0 END) AS zex_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%package%' THEN total + total_tax ELSE 0 END) AS yoone_G_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name NOT LIKE '%package%' THEN total + total_tax ELSE 0 END) AS yoone_S_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%3%' THEN total + total_tax ELSE 0 END) AS yoone_3_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%6%' THEN total + total_tax ELSE 0 END) AS yoone_6_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%9%' THEN total + total_tax ELSE 0 END) AS yoone_9_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%12%' THEN total + total_tax ELSE 0 END) AS yoone_12_amount,
|
||||||
|
SUM(CASE WHEN name LIKE '%yoone%' AND name LIKE '%15%' THEN total + total_tax ELSE 0 END) AS yoone_15_amount
|
||||||
|
FROM order_item
|
||||||
|
GROUP BY orderId
|
||||||
|
),
|
||||||
|
monthly_totals AS (
|
||||||
|
SELECT order_date, SUM(total) AS total_amount,
|
||||||
|
SUM(CASE WHEN siteId = 1 THEN total ELSE 0 END) AS togo_total_amount,
|
||||||
|
SUM(CASE WHEN siteId = 2 THEN total ELSE 0 END) AS can_total_amount,
|
||||||
|
COUNT(DISTINCT order_id) AS total_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN siteId = 1 THEN order_id END) AS togo_total_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN siteId = 2 THEN order_id END) AS can_total_orders,
|
||||||
|
SUM(CASE WHEN purchase_type = 'first_purchase' THEN total ELSE 0 END) AS first_purchase_total,
|
||||||
|
SUM(CASE WHEN purchase_type = 'repeat_purchase' THEN total ELSE 0 END) AS repeat_purchase_total,
|
||||||
|
SUM(CASE WHEN order_type = 'cpc' THEN total ELSE 0 END) AS cpc_total,
|
||||||
|
SUM(CASE WHEN order_type = 'non_cpc' THEN total ELSE 0 END) AS non_cpc_total,
|
||||||
|
SUM(CASE WHEN zyn_type = 'zyn' AND order_type = 'cpc' THEN total ELSE 0 END) AS zyn_total,
|
||||||
|
SUM(CASE WHEN zyn_type = 'zyn' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_zyn_total,
|
||||||
|
SUM(CASE WHEN yoone_type = 'yoone' AND order_type = 'cpc' THEN total ELSE 0 END) AS yoone_total,
|
||||||
|
SUM(CASE WHEN yoone_type = 'yoone' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_yoone_total,
|
||||||
|
SUM(CASE WHEN zex_type = 'zex' AND order_type = 'cpc' THEN total ELSE 0 END) AS zex_total,
|
||||||
|
SUM(CASE WHEN zex_type = 'zex' AND order_type = 'non_cpc' THEN total ELSE 0 END) AS non_zex_total,
|
||||||
|
SUM(CASE WHEN source_type = 'typein' THEN total ELSE 0 END) AS direct_total,
|
||||||
|
SUM(CASE WHEN source_type = 'organic' THEN total ELSE 0 END) AS organic_total
|
||||||
|
FROM monthly_orders
|
||||||
|
GROUP BY order_date
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
mt.order_date,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.purchase_type = 'first_purchase' THEN mo.order_id END) AS first_purchase_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.purchase_type = 'repeat_purchase' THEN mo.order_id END) AS repeat_purchase_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'cpc' THEN mo.order_id END) AS cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'cpc' AND mo.siteId = 1 THEN mo.order_id END) AS togo_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'cpc' AND mo.siteId = 2 THEN mo.order_id END) AS can_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'non_cpc' THEN mo.order_id END) AS non_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'non_cpc' AND mo.siteId = 1 THEN mo.order_id END) AS non_togo_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.order_type = 'non_cpc' AND mo.siteId = 2 THEN mo.order_id END) AS non_can_cpc_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.zyn_type = 'zyn' AND mo.order_type = 'cpc' THEN mo.order_id END) AS zyn_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.zyn_type = 'zyn' AND mo.order_type = 'non_cpc' THEN mo.order_id END) AS non_zyn_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.yoone_type = 'yoone' AND mo.order_type = 'cpc' THEN mo.order_id END) AS yoone_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.yoone_type = 'yoone' AND mo.order_type = 'non_cpc' THEN mo.order_id END) AS non_yoone_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.zex_type = 'zex' AND mo.order_type = 'cpc' THEN mo.order_id END) AS zex_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.zex_type = 'zex' AND mo.order_type = 'non_cpc' THEN mo.order_id END) AS non_zex_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.source_type = 'typein' THEN mo.order_id END) AS direct_orders,
|
||||||
|
COUNT(DISTINCT CASE WHEN mo.source_type = 'organic' THEN mo.order_id END) AS organic_orders,
|
||||||
|
mt.total_orders,
|
||||||
|
mt.togo_total_orders,
|
||||||
|
mt.can_total_orders,
|
||||||
|
mt.total_amount,
|
||||||
|
mt.togo_total_amount,
|
||||||
|
mt.can_total_amount,
|
||||||
|
mt.first_purchase_total,
|
||||||
|
mt.repeat_purchase_total,
|
||||||
|
mt.cpc_total,
|
||||||
|
mt.non_cpc_total,
|
||||||
|
mt.zyn_total,
|
||||||
|
mt.non_zyn_total,
|
||||||
|
mt.yoone_total,
|
||||||
|
mt.non_yoone_total,
|
||||||
|
mt.zex_total,
|
||||||
|
mt.non_zex_total,
|
||||||
|
mt.direct_total,
|
||||||
|
mt.organic_total,
|
||||||
|
COALESCE(SUM(os.zyn_quantity), 0) AS zyn_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.zyn_quantity ELSE 0 END) AS cpc_zyn_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.zyn_quantity ELSE 0 END) AS non_cpc_zyn_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_quantity), 0) AS yoone_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_quantity ELSE 0 END) AS cpc_yoone_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_quantity ELSE 0 END) AS non_cpc_yoone_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_G_quantity), 0) AS yoone_G_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_G_quantity ELSE 0 END) AS cpc_yoone_G_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_G_quantity ELSE 0 END) AS non_cpc_yoone_G_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_S_quantity), 0) AS yoone_S_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_S_quantity ELSE 0 END) AS cpc_yoone_S_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_S_quantity ELSE 0 END) AS non_cpc_yoone_S_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_3_quantity), 0) AS yoone_3_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_3_quantity ELSE 0 END) AS cpc_yoone_3_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_3_quantity ELSE 0 END) AS non_cpc_yoone_3_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_6_quantity), 0) AS yoone_6_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_6_quantity ELSE 0 END) AS cpc_yoone_6_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_6_quantity ELSE 0 END) AS non_cpc_yoone_6_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_9_quantity), 0) AS yoone_9_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_9_quantity ELSE 0 END) AS cpc_yoone_9_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_9_quantity ELSE 0 END) AS non_cpc_yoone_9_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_12_quantity), 0) AS yoone_12_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_12_quantity ELSE 0 END) AS cpc_yoone_12_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_12_quantity ELSE 0 END) AS non_cpc_yoone_12_quantity,
|
||||||
|
COALESCE(SUM(os.yoone_15_quantity), 0) AS yoone_15_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.yoone_15_quantity ELSE 0 END) AS cpc_yoone_15_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.yoone_15_quantity ELSE 0 END) AS non_cpc_yoone_15_quantity,
|
||||||
|
COALESCE(SUM(os.zex_quantity), 0) AS zex_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'cpc' THEN os.zex_quantity ELSE 0 END) AS cpc_zex_quantity,
|
||||||
|
SUM(CASE WHEN mo.order_type = 'non_cpc' THEN os.zex_quantity ELSE 0 END) AS non_cpc_zex_quantity,
|
||||||
|
COALESCE(SUM(oi.zyn_amount), 0) AS zyn_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_amount), 0) AS yoone_amount,
|
||||||
|
COALESCE(SUM(oi.zex_amount), 0) AS zex_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_G_amount), 0) AS yoone_G_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_S_amount), 0) AS yoone_S_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_3_amount), 0) AS yoone_3_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_6_amount), 0) AS yoone_6_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_9_amount), 0) AS yoone_9_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_12_amount), 0) AS yoone_12_amount,
|
||||||
|
COALESCE(SUM(oi.yoone_15_amount), 0) AS yoone_15_amount,
|
||||||
|
ROUND(COALESCE(mt.total_amount / mt.total_orders, 0), 2) AS avg_total_amount,
|
||||||
|
ROUND(COALESCE(mt.togo_total_amount / mt.togo_total_orders, 0), 2) AS avg_togo_total_amount,
|
||||||
|
ROUND(COALESCE(mt.can_total_amount / mt.can_total_orders, 0), 2) AS avg_can_total_amount
|
||||||
|
FROM monthly_orders mo
|
||||||
|
LEFT JOIN monthly_totals mt ON mo.order_date = mt.order_date
|
||||||
|
LEFT JOIN order_sales_summary os ON mo.order_id = os.orderId
|
||||||
|
LEFT JOIN order_items_summary oi ON mo.order_id = oi.orderId
|
||||||
|
GROUP BY
|
||||||
|
mt.order_date,
|
||||||
|
mt.total_amount,
|
||||||
|
mt.togo_total_amount,
|
||||||
|
mt.can_total_amount,
|
||||||
|
mt.first_purchase_total,
|
||||||
|
mt.repeat_purchase_total,
|
||||||
|
mt.cpc_total,
|
||||||
|
mt.non_cpc_total,
|
||||||
|
mt.zyn_total,
|
||||||
|
mt.non_zyn_total,
|
||||||
|
mt.yoone_total,
|
||||||
|
mt.non_yoone_total,
|
||||||
|
mt.zex_total,
|
||||||
|
mt.non_zex_total,
|
||||||
|
mt.direct_total,
|
||||||
|
mt.organic_total,
|
||||||
|
mt.total_orders,
|
||||||
|
mt.togo_total_orders,
|
||||||
|
mt.can_total_orders
|
||||||
|
ORDER BY mt.order_date DESC;
|
||||||
|
`;}
|
||||||
|
|
||||||
return this.orderRepository.query(sql);
|
return this.orderRepository.query(sql);
|
||||||
}
|
}
|
||||||
// async getOrderStatistics(params: OrderStatisticsParams) {
|
// async getOrderStatistics(params: OrderStatisticsParams) {
|
||||||
|
|
@ -933,7 +1322,8 @@ export class StatisticsService {
|
||||||
user_first_order AS (
|
user_first_order AS (
|
||||||
SELECT
|
SELECT
|
||||||
customer_email,
|
customer_email,
|
||||||
DATE_FORMAT(MIN(date_paid), '%Y-%m') AS first_order_month
|
DATE_FORMAT(MIN(date_paid), '%Y-%m') AS first_order_month,
|
||||||
|
SUM(total) AS first_order_total
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')
|
||||||
GROUP BY customer_email
|
GROUP BY customer_email
|
||||||
|
|
@ -946,7 +1336,7 @@ export class StatisticsService {
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')
|
||||||
),
|
),
|
||||||
filtered_orders AS (
|
filtered_orders AS (
|
||||||
SELECT o.customer_email, o.order_month, u.first_order_month, c.start_month
|
SELECT o.customer_email, o.order_month, u.first_order_month,u.first_order_total, c.start_month
|
||||||
FROM order_months o
|
FROM order_months o
|
||||||
JOIN user_first_order u ON o.customer_email = u.customer_email
|
JOIN user_first_order u ON o.customer_email = u.customer_email
|
||||||
JOIN cutoff_months c ON 1=1
|
JOIN cutoff_months c ON 1=1
|
||||||
|
|
@ -958,14 +1348,16 @@ export class StatisticsService {
|
||||||
CASE
|
CASE
|
||||||
WHEN first_order_month < start_month THEN CONCAT('>', start_month)
|
WHEN first_order_month < start_month THEN CONCAT('>', start_month)
|
||||||
ELSE first_order_month
|
ELSE first_order_month
|
||||||
END AS first_order_month_group
|
END AS first_order_month_group,
|
||||||
|
first_order_total
|
||||||
FROM filtered_orders
|
FROM filtered_orders
|
||||||
),
|
),
|
||||||
final_counts AS (
|
final_counts AS (
|
||||||
SELECT
|
SELECT
|
||||||
order_month,
|
order_month,
|
||||||
first_order_month_group,
|
first_order_month_group,
|
||||||
COUNT(*) AS order_count
|
COUNT(*) AS order_count,
|
||||||
|
SUM(first_order_total) AS total
|
||||||
FROM classified
|
FROM classified
|
||||||
GROUP BY order_month, first_order_month_group
|
GROUP BY order_month, first_order_month_group
|
||||||
)
|
)
|
||||||
|
|
@ -985,7 +1377,8 @@ export class StatisticsService {
|
||||||
SELECT
|
SELECT
|
||||||
customer_email,
|
customer_email,
|
||||||
DATE_FORMAT(date_paid, '%Y-%m') AS order_month,
|
DATE_FORMAT(date_paid, '%Y-%m') AS order_month,
|
||||||
date_paid
|
date_paid,
|
||||||
|
total
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')
|
||||||
),
|
),
|
||||||
|
|
@ -998,7 +1391,8 @@ export class StatisticsService {
|
||||||
monthly_users AS (
|
monthly_users AS (
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
customer_email,
|
customer_email,
|
||||||
order_month
|
order_month,
|
||||||
|
total
|
||||||
FROM filtered_users
|
FROM filtered_users
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -1016,6 +1410,7 @@ export class StatisticsService {
|
||||||
SELECT
|
SELECT
|
||||||
m.customer_email,
|
m.customer_email,
|
||||||
m.order_month,
|
m.order_month,
|
||||||
|
m.total,
|
||||||
CASE
|
CASE
|
||||||
WHEN f.first_order_month = m.order_month THEN 'new'
|
WHEN f.first_order_month = m.order_month THEN 'new'
|
||||||
ELSE 'returning'
|
ELSE 'returning'
|
||||||
|
|
@ -1029,7 +1424,9 @@ export class StatisticsService {
|
||||||
SELECT
|
SELECT
|
||||||
order_month,
|
order_month,
|
||||||
SUM(CASE WHEN customer_type = 'new' THEN 1 ELSE 0 END) AS new_user_count,
|
SUM(CASE WHEN customer_type = 'new' THEN 1 ELSE 0 END) AS new_user_count,
|
||||||
SUM(CASE WHEN customer_type = 'returning' THEN 1 ELSE 0 END) AS old_user_count
|
SUM(CASE WHEN customer_type = 'returning' THEN 1 ELSE 0 END) AS old_user_count,
|
||||||
|
SUM(CASE WHEN customer_type = 'new' THEN total ELSE 0 END) AS new_user_total,
|
||||||
|
SUM(CASE WHEN customer_type = 'returning' THEN total ELSE 0 END) AS old_user_total
|
||||||
FROM labeled_users
|
FROM labeled_users
|
||||||
GROUP BY order_month
|
GROUP BY order_month
|
||||||
),
|
),
|
||||||
|
|
@ -1061,7 +1458,9 @@ export class StatisticsService {
|
||||||
m.order_month,
|
m.order_month,
|
||||||
m.new_user_count,
|
m.new_user_count,
|
||||||
m.old_user_count,
|
m.old_user_count,
|
||||||
COALESCE(i.inactive_user_count, 0) AS inactive_user_count
|
COALESCE(i.inactive_user_count, 0) AS inactive_user_count,
|
||||||
|
m.new_user_total,
|
||||||
|
m.old_user_total
|
||||||
FROM monthly_new_old_counts m
|
FROM monthly_new_old_counts m
|
||||||
LEFT JOIN users_without_future_orders i
|
LEFT JOIN users_without_future_orders i
|
||||||
ON m.order_month = i.order_month
|
ON m.order_month = i.order_month
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue