feat(webhook): 添加对shoppy平台webhook的支持
- 在site.entity.ts中添加webhookUrl字段 - 在auth.middleware.ts中添加/shoppy路由到白名单 - 在webhook.controller.ts中实现shoppy平台webhook处理逻辑 fix(webhook): 更新webhook控制器中的密钥值 refactor(entity): 将可选字段明确标记为可选类型 feat(adapter): 公开映射方法以支持统一接口调用 将各适配器中的私有映射方法改为公开,并在接口中定义统一方法签名 修改webhook控制器以使用适配器映射方法处理订单数据 feat: 添加订单支付日期字段并支持国家筛选 - 在ShopyyOrder接口中添加date_paid字段 - 在OrderStatisticsParams中添加country数组字段用于国家筛选 - 修改统计服务以支持按国家筛选订单数据 - 更新数据库配置和同步设置 - 优化订单服务中的类型定义和查询条件 refactor(webhook): 移除未使用的shoppy webhook处理逻辑 fix(订单服务): 修复订单内容括号处理并添加同步日志 添加订单同步过程的调试日志 修复订单内容中括号内容的处理逻辑 修正控制器方法名拼写错误
This commit is contained in:
parent
cdff083940
commit
cb00076bd3
|
|
@ -367,7 +367,6 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
date_paid: typeof item.pay_at === 'number'
|
date_paid: typeof item.pay_at === 'number'
|
||||||
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
|
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
refunds: [],
|
refunds: [],
|
||||||
currency_symbol: (currencySymbols[item.currency] || '$') || '',
|
currency_symbol: (currencySymbols[item.currency] || '$') || '',
|
||||||
date_created:
|
date_created:
|
||||||
|
|
@ -387,6 +386,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
tracking_number: f.tracking_number || '',
|
tracking_number: f.tracking_number || '',
|
||||||
shipping_provider: f.tracking_company || '',
|
shipping_provider: f.tracking_company || '',
|
||||||
shipping_method: f.tracking_company || '',
|
shipping_method: f.tracking_company || '',
|
||||||
|
|
||||||
date_created: typeof f.created_at === 'number'
|
date_created: typeof f.created_at === 'number'
|
||||||
? new Date(f.created_at * 1000).toISOString()
|
? new Date(f.created_at * 1000).toISOString()
|
||||||
: f.created_at || '',
|
: f.created_at || '',
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,15 @@ export default {
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
typeorm: {
|
typeorm: {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
default: {
|
default: {
|
||||||
host: 'localhost',
|
host: '13.212.62.127',
|
||||||
port: "23306",
|
port: "3306",
|
||||||
username: 'root',
|
username: 'root',
|
||||||
password: '12345678',
|
password: 'Yoone!@.2025',
|
||||||
database: 'inventory_v2',
|
database: 'inventory_v2',
|
||||||
|
synchronize: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export class StatisticsController {
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Get('/orderSource')
|
@Get('/orderSource')
|
||||||
async getOrderSorce(@Query() params) {
|
async getOrderSource(@Query() params) {
|
||||||
try {
|
try {
|
||||||
return successResponse(await this.statisticsService.getOrderSorce(params));
|
return successResponse(await this.statisticsService.getOrderSorce(params));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import { SiteService } from '../service/site.service';
|
||||||
import { OrderService } from '../service/order.service';
|
import { OrderService } from '../service/order.service';
|
||||||
import { SiteApiService } from '../service/site-api.service';
|
import { SiteApiService } from '../service/site-api.service';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Controller('/webhook')
|
@Controller('/webhook')
|
||||||
export class WebhookController {
|
export class WebhookController {
|
||||||
private secret = 'YOONE24kd$kjcdjflddd';
|
private secret = 'YOONE24kd$kjcdjflddd';
|
||||||
|
|
@ -177,20 +179,15 @@ export class WebhookController {
|
||||||
console.log('Unhandled event:', topic);
|
console.log('Unhandled event:', topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Webhook processed successfully',
|
message: 'Webhook processed successfully',
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
code: 403,
|
|
||||||
success: false,
|
|
||||||
message: 'Webhook verification failed',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,7 @@ export interface ShopyyOrder {
|
||||||
financial_status?: number;
|
financial_status?: number;
|
||||||
fulfillment_status?: number;
|
fulfillment_status?: number;
|
||||||
// 创建与更新时间可能为时间戳
|
// 创建与更新时间可能为时间戳
|
||||||
|
date_paid?: number | string;
|
||||||
created_at?: number | string;
|
created_at?: number | string;
|
||||||
date_added?: string;
|
date_added?: string;
|
||||||
updated_at?: number | string;
|
updated_at?: number | string;
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ export class UpdateSiteDTO {
|
||||||
skuPrefix?: string;
|
skuPrefix?: string;
|
||||||
|
|
||||||
// 区域
|
// 区域
|
||||||
@ApiProperty({ description: '区域' })
|
@ApiProperty({ description: '区域', required: false })
|
||||||
@Rule(RuleType.array().items(RuleType.string()).optional())
|
@Rule(RuleType.array().items(RuleType.string()).optional())
|
||||||
areas?: string[];
|
areas?: string[];
|
||||||
|
|
||||||
|
|
@ -133,6 +133,10 @@ export class UpdateSiteDTO {
|
||||||
@ApiProperty({ description: '站点网站URL', required: false })
|
@ApiProperty({ description: '站点网站URL', required: false })
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
websiteUrl?: string;
|
websiteUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'Webhook URL', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
webhookUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QuerySiteDTO {
|
export class QuerySiteDTO {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ export class OrderStatisticsParams {
|
||||||
@Rule(RuleType.number().allow(null))
|
@Rule(RuleType.number().allow(null))
|
||||||
siteId?: number;
|
siteId?: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Rule(RuleType.array().allow(null))
|
||||||
|
country?: any[];
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
enum: ['all', 'first_purchase', 'repeat_purchase'],
|
enum: ['all', 'first_purchase', 'repeat_purchase'],
|
||||||
default: 'all',
|
default: 'all',
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ export class OrderService {
|
||||||
updated: 0,
|
updated: 0,
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
|
console.log('开始进入循环同步订单', result.length, '个订单')
|
||||||
// 遍历每个订单进行同步
|
// 遍历每个订单进行同步
|
||||||
for (const order of result) {
|
for (const order of result) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -162,6 +162,7 @@ export class OrderService {
|
||||||
} else {
|
} else {
|
||||||
syncResult.created++;
|
syncResult.created++;
|
||||||
}
|
}
|
||||||
|
// console.log('updated', syncResult.updated, 'created:', syncResult.created)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 记录错误但不中断整个同步过程
|
// 记录错误但不中断整个同步过程
|
||||||
syncResult.errors.push({
|
syncResult.errors.push({
|
||||||
|
|
@ -171,6 +172,8 @@ export class OrderService {
|
||||||
syncResult.processed++;
|
syncResult.processed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('同步完成', syncResult.updated, 'created:', syncResult.created)
|
||||||
|
|
||||||
this.logger.debug('syncOrders result', syncResult)
|
this.logger.debug('syncOrders result', syncResult)
|
||||||
return syncResult;
|
return syncResult;
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +304,7 @@ export class OrderService {
|
||||||
* @param order 订单数据
|
* @param order 订单数据
|
||||||
* @param forceUpdate 是否强制更新
|
* @param forceUpdate 是否强制更新
|
||||||
*/
|
*/
|
||||||
async syncSingleOrder(siteId: number, order: any, forceUpdate = false) {
|
async syncSingleOrder(siteId: number, order: UnifiedOrderDTO, forceUpdate = false) {
|
||||||
// 从订单数据中解构出各个子项
|
// 从订单数据中解构出各个子项
|
||||||
let {
|
let {
|
||||||
line_items,
|
line_items,
|
||||||
|
|
@ -319,7 +322,7 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
// 检查数据库中是否已存在该订单
|
// 检查数据库中是否已存在该订单
|
||||||
const existingOrder = await this.orderModel.findOne({
|
const existingOrder = await this.orderModel.findOne({
|
||||||
where: { externalOrderId: order.id, siteId: siteId },
|
where: { externalOrderId: String(order.id), siteId: siteId },
|
||||||
});
|
});
|
||||||
// 自动更新订单状态(如果需要)
|
// 自动更新订单状态(如果需要)
|
||||||
await this.autoUpdateOrderStatus(siteId, order);
|
await this.autoUpdateOrderStatus(siteId, order);
|
||||||
|
|
@ -328,10 +331,10 @@ export class OrderService {
|
||||||
// 矫正数据库中的订单数据
|
// 矫正数据库中的订单数据
|
||||||
const updateData: any = { status: order.status };
|
const updateData: any = { status: order.status };
|
||||||
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
|
if (this.canUpdateErpStatus(existingOrder.orderStatus)) {
|
||||||
updateData.orderStatus = this.mapOrderStatus(order.status);
|
updateData.orderStatus = this.mapOrderStatus(order.status as any);
|
||||||
}
|
}
|
||||||
// 更新
|
// 更新订单主数据
|
||||||
await this.orderModel.update({ externalOrderId: order.id, siteId: siteId }, updateData);
|
await this.orderModel.update({ externalOrderId: String(order.id), siteId: siteId }, updateData);
|
||||||
// 更新 fulfillments 数据
|
// 更新 fulfillments 数据
|
||||||
await this.saveOrderFulfillments({
|
await this.saveOrderFulfillments({
|
||||||
siteId,
|
siteId,
|
||||||
|
|
@ -340,7 +343,7 @@ export class OrderService {
|
||||||
fulfillments: fulfillments,
|
fulfillments: fulfillments,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const externalOrderId = order.id;
|
const externalOrderId = String(order.id);
|
||||||
// 如果订单从未完成变为完成状态,则更新库存
|
// 如果订单从未完成变为完成状态,则更新库存
|
||||||
if (
|
if (
|
||||||
existingOrder &&
|
existingOrder &&
|
||||||
|
|
@ -361,14 +364,14 @@ export class OrderService {
|
||||||
await this.saveOrderItems({
|
await this.saveOrderItems({
|
||||||
siteId,
|
siteId,
|
||||||
orderId,
|
orderId,
|
||||||
externalOrderId,
|
externalOrderId: String(externalOrderId),
|
||||||
orderItems: line_items,
|
orderItems: line_items,
|
||||||
});
|
});
|
||||||
// 保存退款信息
|
// 保存退款信息
|
||||||
await this.saveOrderRefunds({
|
await this.saveOrderRefunds({
|
||||||
siteId,
|
siteId,
|
||||||
orderId,
|
orderId,
|
||||||
externalOrderId,
|
externalOrderId ,
|
||||||
refunds,
|
refunds,
|
||||||
});
|
});
|
||||||
// 保存费用信息
|
// 保存费用信息
|
||||||
|
|
@ -459,7 +462,7 @@ export class OrderService {
|
||||||
* @param order 订单数据
|
* @param order 订单数据
|
||||||
* @returns 保存后的订单实体
|
* @returns 保存后的订单实体
|
||||||
*/
|
*/
|
||||||
async saveOrder(siteId: number, order: UnifiedOrderDTO): Promise<Order> {
|
async saveOrder(siteId: number, order: Partial<UnifiedOrderDTO>): Promise<Order> {
|
||||||
// 将外部订单ID转换为字符串
|
// 将外部订单ID转换为字符串
|
||||||
const externalOrderId = String(order.id)
|
const externalOrderId = String(order.id)
|
||||||
delete order.id
|
delete order.id
|
||||||
|
|
@ -1234,13 +1237,13 @@ export class OrderService {
|
||||||
parameters.push(siteId);
|
parameters.push(siteId);
|
||||||
}
|
}
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
sqlQuery += ` AND o.date_created >= ?`;
|
sqlQuery += ` AND o.date_paid >= ?`;
|
||||||
totalQuery += ` AND o.date_created >= ?`;
|
totalQuery += ` AND o.date_paid >= ?`;
|
||||||
parameters.push(startDate);
|
parameters.push(startDate);
|
||||||
}
|
}
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
sqlQuery += ` AND o.date_created <= ?`;
|
sqlQuery += ` AND o.date_paid <= ?`;
|
||||||
totalQuery += ` AND o.date_created <= ?`;
|
totalQuery += ` AND o.date_paid <= ?`;
|
||||||
parameters.push(endDate);
|
parameters.push(endDate);
|
||||||
}
|
}
|
||||||
// 支付方式筛选(使用参数化,避免SQL注入)
|
// 支付方式筛选(使用参数化,避免SQL注入)
|
||||||
|
|
@ -1328,7 +1331,7 @@ export class OrderService {
|
||||||
// 添加分页到主查询
|
// 添加分页到主查询
|
||||||
sqlQuery += `
|
sqlQuery += `
|
||||||
GROUP BY o.id
|
GROUP BY o.id
|
||||||
ORDER BY o.date_created DESC
|
ORDER BY o.date_paid DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
`;
|
`;
|
||||||
parameters.push(pageSize, (current - 1) * pageSize);
|
parameters.push(pageSize, (current - 1) * pageSize);
|
||||||
|
|
@ -2545,7 +2548,7 @@ export class OrderService {
|
||||||
'姓名地址': nameAddress,
|
'姓名地址': nameAddress,
|
||||||
'邮箱': order.customer_email || '',
|
'邮箱': order.customer_email || '',
|
||||||
'号码': phone,
|
'号码': phone,
|
||||||
'订单内容': orderContent,
|
'订单内容': this.removeLastParenthesesContent(orderContent),
|
||||||
'盒数': boxCount,
|
'盒数': boxCount,
|
||||||
'换盒数': exchangeBoxCount,
|
'换盒数': exchangeBoxCount,
|
||||||
'换货内容': exchangeContent,
|
'换货内容': exchangeContent,
|
||||||
|
|
@ -2646,6 +2649,84 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除每个分号前面一个左右括号和最后一个左右括号包含的内容(包括括号本身)
|
||||||
|
* @param str 输入字符串
|
||||||
|
* @returns 删除后的字符串
|
||||||
|
*/
|
||||||
|
removeLastParenthesesContent(str: string): string {
|
||||||
|
if (!str || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:删除指定位置的括号对及其内容
|
||||||
|
const removeParenthesesAt = (s: string, leftIndex: number): string => {
|
||||||
|
if (leftIndex === -1) return s;
|
||||||
|
|
||||||
|
let rightIndex = -1;
|
||||||
|
let parenCount = 0;
|
||||||
|
|
||||||
|
for (let i = leftIndex; i < s.length; i++) {
|
||||||
|
const char = s[i];
|
||||||
|
if (char === '(') {
|
||||||
|
parenCount++;
|
||||||
|
} else if (char === ')') {
|
||||||
|
parenCount--;
|
||||||
|
if (parenCount === 0) {
|
||||||
|
rightIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rightIndex !== -1) {
|
||||||
|
return s.substring(0, leftIndex) + s.substring(rightIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. 处理每个分号前面的括号对
|
||||||
|
let result = str;
|
||||||
|
|
||||||
|
// 找出所有分号的位置
|
||||||
|
const semicolonIndices: number[] = [];
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
if (result[i] === ';') {
|
||||||
|
semicolonIndices.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从后向前处理每个分号,避免位置变化影响后续处理
|
||||||
|
for (let i = semicolonIndices.length - 1; i >= 0; i--) {
|
||||||
|
const semicolonIndex = semicolonIndices[i];
|
||||||
|
|
||||||
|
// 从分号位置向前查找最近的左括号
|
||||||
|
let lastLeftParenIndex = -1;
|
||||||
|
for (let j = semicolonIndex - 1; j >= 0; j--) {
|
||||||
|
if (result[j] === '(') {
|
||||||
|
lastLeftParenIndex = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到左括号,删除该括号对及其内容
|
||||||
|
if (lastLeftParenIndex !== -1) {
|
||||||
|
result = removeParenthesesAt(result, lastLeftParenIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 处理整个字符串的最后一个括号对
|
||||||
|
let lastLeftParenIndex = result.lastIndexOf('(');
|
||||||
|
if (lastLeftParenIndex !== -1) {
|
||||||
|
result = removeParenthesesAt(result, lastLeftParenIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,19 @@ export class StatisticsService {
|
||||||
orderItemRepository: Repository<OrderItem>;
|
orderItemRepository: Repository<OrderItem>;
|
||||||
|
|
||||||
async getOrderStatistics(params: OrderStatisticsParams) {
|
async getOrderStatistics(params: OrderStatisticsParams) {
|
||||||
const { startDate, endDate, grouping, siteId } = params;
|
const { startDate, endDate, grouping, siteId, country } = params;
|
||||||
// const keywords = keyword ? keyword.split(' ').filter(Boolean) : [];
|
// const keywords = keyword ? keyword.split(' ').filter(Boolean) : [];
|
||||||
|
|
||||||
|
let siteIds = []
|
||||||
|
if (country) {
|
||||||
|
siteIds = await this.getSiteIds(country)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (siteId) {
|
||||||
|
siteIds.push(siteId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -54,6 +65,8 @@ export class StatisticsService {
|
||||||
AND o.status IN('processing','completed')
|
AND o.status IN('processing','completed')
|
||||||
`;
|
`;
|
||||||
if (siteId) sql += ` AND o.siteId=${siteId}`;
|
if (siteId) sql += ` AND o.siteId=${siteId}`;
|
||||||
|
if (siteIds.length) sql += ` AND o.siteId IN (${siteIds.join(',')})`;
|
||||||
|
|
||||||
sql += `
|
sql += `
|
||||||
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
||||||
),
|
),
|
||||||
|
|
@ -247,7 +260,10 @@ export class StatisticsService {
|
||||||
LEFT JOIN order_item oi ON o.id = oi.orderId
|
LEFT JOIN order_item oi ON o.id = oi.orderId
|
||||||
WHERE o.date_paid IS NOT NULL
|
WHERE o.date_paid IS NOT NULL
|
||||||
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
||||||
AND o.status IN ('processing','completed')
|
AND o.status IN ('processing','completed')`;
|
||||||
|
if (siteId) sql += ` AND o.siteId=${siteId}`;
|
||||||
|
if (siteIds.length) sql += ` AND o.siteId IN (${siteIds.join(',')})`;
|
||||||
|
sql +=`
|
||||||
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
||||||
),
|
),
|
||||||
order_sales_summary AS (
|
order_sales_summary AS (
|
||||||
|
|
@ -440,6 +456,10 @@ export class StatisticsService {
|
||||||
LEFT JOIN order_item oi ON o.id = oi.orderId
|
LEFT JOIN order_item oi ON o.id = oi.orderId
|
||||||
WHERE o.date_paid IS NOT NULL
|
WHERE o.date_paid IS NOT NULL
|
||||||
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
|
||||||
|
`;
|
||||||
|
if (siteId) sql += ` AND o.siteId=${siteId}`;
|
||||||
|
if (siteIds.length) sql += ` AND o.siteId IN (${siteIds.join(',')})`;
|
||||||
|
sql +=`
|
||||||
AND o.status IN ('processing','completed')
|
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
|
GROUP BY o.id, o.date_paid, o.customer_email, o.total, o.source_type, o.siteId, o.utm_source
|
||||||
),
|
),
|
||||||
|
|
@ -1314,7 +1334,14 @@ export class StatisticsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrderSorce(params) {
|
async getOrderSorce(params) {
|
||||||
const sql = `
|
const { country } = params;
|
||||||
|
|
||||||
|
let siteIds = []
|
||||||
|
if (country) {
|
||||||
|
siteIds = await this.getSiteIds(country)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sql = `
|
||||||
WITH cutoff_months AS (
|
WITH cutoff_months AS (
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 7 MONTH), '%Y-%m') AS start_month,
|
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 7 MONTH), '%Y-%m') AS start_month,
|
||||||
|
|
@ -1326,7 +1353,10 @@ export class StatisticsService {
|
||||||
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
|
SUM(total) AS first_order_total
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')`;
|
||||||
|
if (siteIds.length!=0) sql += ` AND siteId IN ('${siteIds.join("','")}')`;
|
||||||
|
else sql += ` AND siteId IS NULL `;
|
||||||
|
sql += `
|
||||||
GROUP BY customer_email
|
GROUP BY customer_email
|
||||||
),
|
),
|
||||||
order_months AS (
|
order_months AS (
|
||||||
|
|
@ -1334,7 +1364,10 @@ export class StatisticsService {
|
||||||
customer_email,
|
customer_email,
|
||||||
DATE_FORMAT(date_paid, '%Y-%m') AS order_month
|
DATE_FORMAT(date_paid, '%Y-%m') AS order_month
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')`;
|
||||||
|
if (siteIds.length!=0) sql += ` AND siteId IN ('${siteIds.join("','")}')`;
|
||||||
|
else sql += ` AND siteId IS NULL `;
|
||||||
|
sql += `
|
||||||
),
|
),
|
||||||
filtered_orders AS (
|
filtered_orders AS (
|
||||||
SELECT o.customer_email, o.order_month, u.first_order_month,u.first_order_total, c.start_month
|
SELECT o.customer_email, o.order_month, u.first_order_month,u.first_order_total, c.start_month
|
||||||
|
|
@ -1366,7 +1399,7 @@ export class StatisticsService {
|
||||||
ORDER BY order_month DESC, first_order_month_group
|
ORDER BY order_month DESC, first_order_month_group
|
||||||
`
|
`
|
||||||
|
|
||||||
const inactiveSql = `
|
let inactiveSql = `
|
||||||
WITH
|
WITH
|
||||||
cutoff_months AS (
|
cutoff_months AS (
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -1381,7 +1414,10 @@ export class StatisticsService {
|
||||||
date_paid,
|
date_paid,
|
||||||
total
|
total
|
||||||
FROM \`order\`
|
FROM \`order\`
|
||||||
WHERE status IN ('processing', 'completed')
|
WHERE status IN ('processing', 'completed')`;
|
||||||
|
if (siteIds.length!=0) inactiveSql += ` AND siteId IN ('${siteIds.join("','")}')`;
|
||||||
|
else inactiveSql += ` AND siteId IS NULL `;
|
||||||
|
inactiveSql += `
|
||||||
),
|
),
|
||||||
|
|
||||||
filtered_users AS (
|
filtered_users AS (
|
||||||
|
|
@ -1524,4 +1560,13 @@ export class StatisticsService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSiteIds(country: any[]) {
|
||||||
|
const sql = `
|
||||||
|
SELECT DISTINCT sa.siteId as site_id FROM area a left join site_areas_area sa on a.id = sa.areaId WHERE a.code IN ('${country.join("','")}')
|
||||||
|
`
|
||||||
|
const res = await this.orderRepository.query(sql)
|
||||||
|
return res.map(item => item.site_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue