API/src/service/statistics.service.ts

1128 lines
46 KiB
TypeScript

import { InjectEntityModel } from '@midwayjs/typeorm';
import { Order } from '../entity/order.entity';
import { Provide } from '@midwayjs/core';
import { Repository } from 'typeorm';
import { OrderStatisticsParams } from '../dto/statistics.dto';
import { OrderItem } from '../entity/order_item.entity';
import dayjs = require('dayjs');
@Provide()
export class StatisticsService {
@InjectEntityModel(Order)
orderRepository: Repository<Order>;
@InjectEntityModel(OrderItem)
orderItemRepository: Repository<OrderItem>;
async getOrderStatistics(params: OrderStatisticsParams) {
const { startDate, endDate, siteId } = params;
// const keywords = keyword ? keyword.split(' ').filter(Boolean) : [];
const start = dayjs(startDate).format('YYYY-MM-DD');
const end = dayjs(endDate).add(1, 'd').format('YYYY-MM-DD');
let sql = `
WITH first_order AS (
SELECT customer_email, MIN(date_paid) AS first_purchase_date
FROM \`order\`
GROUP BY customer_email
),
daily_orders AS (
SELECT
o.id AS order_id,
DATE(o.date_paid) 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')
`;
if (siteId) sql += ` AND o.siteId=${siteId}`;
sql += `
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
),
daily_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 daily_orders
GROUP BY order_date
)
SELECT
d.order_date,
COUNT(DISTINCT CASE WHEN d.purchase_type = 'first_purchase' THEN d.order_id END) AS first_purchase_orders,
COUNT(DISTINCT CASE WHEN d.purchase_type = 'repeat_purchase' THEN d.order_id END) AS repeat_purchase_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'cpc' THEN d.order_id END) AS cpc_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'cpc' AND d.siteId = 1 THEN d.order_id END) AS togo_cpc_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'cpc' AND d.siteId = 2 THEN d.order_id END) AS can_cpc_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'non_cpc' THEN d.order_id END) AS non_cpc_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'non_cpc' AND d.siteId = 1 THEN d.order_id END) AS non_togo_cpc_orders,
COUNT(DISTINCT CASE WHEN d.order_type = 'non_cpc' AND d.siteId = 2 THEN d.order_id END) AS non_can_cpc_orders,
COUNT(DISTINCT CASE WHEN d.zyn_type = 'zyn' AND d.order_type = 'cpc' THEN d.order_id END) AS zyn_orders,
COUNT(DISTINCT CASE WHEN d.zyn_type = 'zyn' AND d.order_type = 'non_cpc' THEN d.order_id END) AS non_zyn_orders,
COUNT(DISTINCT CASE WHEN d.yoone_type = 'yoone' AND d.order_type = 'cpc' THEN d.order_id END) AS yoone_orders,
COUNT(DISTINCT CASE WHEN d.yoone_type = 'yoone' AND d.order_type = 'non_cpc' THEN d.order_id END) AS non_yoone_orders,
COUNT(DISTINCT CASE WHEN d.zex_type = 'zex' AND d.order_type = 'cpc' THEN d.order_id END) AS zex_orders,
COUNT(DISTINCT CASE WHEN d.zex_type = 'zex' AND d.order_type = 'non_cpc' THEN d.order_id END) AS non_zex_orders,
COUNT(DISTINCT CASE WHEN d.source_type = 'typein' THEN d.order_id END) AS direct_orders,
COUNT(DISTINCT CASE WHEN d.source_type = 'organic' THEN d.order_id END) AS organic_orders,
dt.total_orders,
dt.togo_total_orders,
dt.can_total_orders,
dt.total_amount,
dt.togo_total_amount,
dt.can_total_amount,
dt.first_purchase_total,
dt.repeat_purchase_total,
dt.cpc_total,
dt.non_cpc_total,
dt.zyn_total,
dt.non_zyn_total,
dt.yoone_total,
dt.non_yoone_total,
dt.zex_total,
dt.non_zex_total,
dt.direct_total,
dt.organic_total,
COALESCE(SUM(os.zyn_quantity), 0) AS zyn_quantity,
SUM(CASE WHEN d.order_type = 'cpc' THEN os.zyn_quantity ELSE 0 END) AS cpc_zyn_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_quantity ELSE 0 END) AS cpc_yoone_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_G_quantity ELSE 0 END) AS cpc_yoone_G_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_S_quantity ELSE 0 END) AS cpc_yoone_S_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_3_quantity ELSE 0 END) AS cpc_yoone_3_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_6_quantity ELSE 0 END) AS cpc_yoone_6_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_9_quantity ELSE 0 END) AS cpc_yoone_9_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_12_quantity ELSE 0 END) AS cpc_yoone_12_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.yoone_15_quantity ELSE 0 END) AS cpc_yoone_15_quantity,
SUM(CASE WHEN d.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 d.order_type = 'cpc' THEN os.zex_quantity ELSE 0 END) AS cpc_zex_quantity,
SUM(CASE WHEN d.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(dt.total_amount / dt.total_orders,0), 2) AS avg_total_amount,
ROUND(COALESCE(dt.togo_total_amount / dt.togo_total_orders,0), 2) AS avg_togo_total_amount,
ROUND(COALESCE(dt.can_total_amount / dt.can_total_orders,0), 2) AS avg_can_total_amount
FROM daily_orders d
LEFT JOIN daily_totals dt ON d.order_date = dt.order_date
LEFT JOIN order_sales_summary os ON d.order_id = os.orderId
LEFT JOIN order_items_summary oi ON d.order_id = oi.orderId
GROUP BY
d.order_date,
dt.total_amount,
dt.togo_total_amount,
dt.can_total_amount,
dt.first_purchase_total,
dt.repeat_purchase_total,
dt.cpc_total,
dt.non_cpc_total,
dt.zyn_total,
dt.non_zyn_total,
dt.yoone_total,
dt.non_yoone_total,
dt.zex_total,
dt.non_zex_total,
dt.direct_total,
dt.organic_total,
dt.total_orders,
dt.togo_total_orders,
dt.can_total_orders
ORDER BY d.order_date DESC;
`;
return this.orderRepository.query(sql);
}
// async getOrderStatistics(params: OrderStatisticsParams) {
// const {
// startDate,
// endDate,
// siteId,
// purchaseType = 'all',
// orderType = 'all',
// brand = 'all',
// } = params;
// const start = dayjs(startDate).format('YYYY-MM-DD');
// const end = dayjs(endDate).add(1, 'day').format('YYYY-MM-DD');
// // 条件拼接
// const siteFilter = siteId ? `AND o.siteId = '${siteId}'` : '';
// const purchaseTypeFilter =
// purchaseType && purchaseType !== 'all'
// ? `AND purchase_type = '${purchaseType}'`
// : '';
// const orderTypeFilter =
// orderType && orderType !== 'all' ? `AND order_type = '${orderType}'` : '';
// const brandFilter = brand !== 'all' ? `WHERE name LIKE '%${brand}%'` : '';
// const sql = `
// WITH first_order AS (
// SELECT customer_email, MIN(date_paid) AS first_purchase_date
// FROM \`order\`
// GROUP BY customer_email
// ),
// all_orders AS (
// SELECT
// o.id AS order_id,
// DATE(o.date_paid) AS order_date,
// o.customer_email,
// o.total,
// o.source_type,
// o.utm_source,
// 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
// FROM \`order\` o
// LEFT JOIN first_order f ON o.customer_email = f.customer_email
// WHERE o.date_paid IS NOT NULL
// AND o.date_paid >= '${start}' AND o.date_paid < '${end}'
// AND o.status IN ('processing','completed')
// ${siteFilter}
// ),
// filtered_orders AS (
// SELECT ao.*
// FROM all_orders ao
// WHERE 1=1
// ${purchaseTypeFilter}
// ${orderTypeFilter}
// ${
// brand !== 'all'
// ? `
// AND EXISTS (
// SELECT 1 FROM order_item oi
// WHERE oi.orderId = ao.order_id
// AND oi.name LIKE '%${brand}%'
// )
// `
// : ''
// }
// ),
// brand_quantity AS (
// SELECT
// orderId,
// SUM(quantity) AS total_quantity
// FROM order_sale
// ${brandFilter}
// GROUP BY orderId
// )
// SELECT
// d.order_date,
// COUNT(DISTINCT d.order_id) AS total_orders,
// ROUND(SUM(d.total), 2) AS total_amount,
// ROUND(SUM(d.total) / COUNT(DISTINCT d.order_id), 2) AS avg_order_amount,
// COALESCE(SUM(bq.total_quantity), 0) AS total_quantity
// FROM filtered_orders d
// LEFT JOIN brand_quantity bq ON d.order_id = bq.orderId
// GROUP BY d.order_date
// ORDER BY d.order_date DESC
// `;
// return this.orderRepository.query(sql);
// }
async getOrderByDate(date: string) {
const startOfDay = new Date(`${date}T00:00:00`);
const endOfDay = new Date(`${date}T23:59:59`);
const sql = `
WITH first_order AS (
SELECT customer_email, MIN(date_paid) AS first_purchase_date
FROM \`order\`
GROUP BY customer_email
),
last_order AS (
SELECT customer_email, MAX(date_paid) AS last_purchase_date
FROM \`order\`
GROUP BY customer_email
),
customer_stats AS (
SELECT
customer_email,
COUNT(o.id) AS order_count,
SUM(o.total) AS total_spent
FROM \`order\` o
WHERE o.status IN ('processing', 'completed')
GROUP BY customer_email
)
SELECT
o.*,
JSON_ARRAYAGG(
JSON_OBJECT('name', oi.name, 'quantity', oi.quantity)
) AS orderItems,
f.first_purchase_date,
l.last_purchase_date,
CASE
WHEN o.date_paid = f.first_purchase_date THEN 'first_purchase'
ELSE 'repeat_purchase'
END AS purchase_type,
cs.order_count,
cs.total_spent,
(
SELECT JSON_ARRAYAGG(tag)
FROM customer_tag ct
WHERE ct.email = o.customer_email
) AS tags
FROM \`order\` o
LEFT JOIN first_order f ON o.customer_email = f.customer_email
LEFT JOIN last_order l ON o.customer_email = l.customer_email
LEFT JOIN order_item oi ON oi.orderId = o.id
LEFT JOIN customer_stats cs ON o.customer_email = cs.customer_email
WHERE o.date_paid BETWEEN ? AND ?
AND o.status IN ('processing', 'completed')
GROUP BY o.id, f.first_purchase_date, cs.order_count, cs.total_spent
`;
const orders = await this.orderRepository.query(sql, [
startOfDay,
endOfDay,
]);
return orders;
}
async getOrderByEmail(email: string) {
const sql = `
SELECT
o.*,
JSON_ARRAYAGG(
JSON_OBJECT('name', oi.name, 'quantity', oi.quantity, 'total', oi.total)
) AS orderItems,
(
SELECT JSON_ARRAYAGG(tag)
FROM customer_tag ct
WHERE ct.email = o.customer_email
) AS tags
FROM \`order\` o
LEFT JOIN order_item oi ON oi.orderId = o.id
WHERE o.customer_email='${email}'
GROUP BY o.id
ORDER BY o.date_paid DESC;
`;
const orders = await this.orderRepository.query(sql, [email]);
return orders;
}
async getCustomerOrders(month) {
const timeWhere = month
? `AND o.date_paid BETWEEN '${dayjs(month[0])
.startOf('month')
.format('YYYY-MM-DD HH:mm:ss')}' AND '${dayjs(month[1])
.endOf('month')
.format('YYYY-MM-DD HH:mm:ss')}'`
: '';
const sql = `WITH orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
WHERE 1=1
${timeWhere}
GROUP BY o.customer_email
),
completed_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
WHERE o.status IN ('completed', 'processing')
${timeWhere}
GROUP BY o.customer_email
),
yoone_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
zyn_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%zyn%'
${timeWhere}
GROUP BY o.customer_email
),
zex_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%zex%'
${timeWhere}
GROUP BY o.customer_email
),
yoone_3mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%3mg%'
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
yoone_6mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%6mg%'
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
yoone_9mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%9mg%'
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
yoone_12mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%12mg%'
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
yoone_15mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%15mg%'
AND os.name LIKE '%yoone%'
${timeWhere}
GROUP BY o.customer_email
),
zyn_3mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%3mg%'
AND os.name LIKE '%zyn%'
${timeWhere}
GROUP BY o.customer_email
),
zyn_6mg_orders AS (
SELECT o.customer_email, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
AND os.name LIKE '%6mg%'
AND os.name LIKE '%zyn%'
${timeWhere}
GROUP BY o.customer_email
)
SELECT
-- 总完成的用户数
(SELECT COUNT(DISTINCT customer_email) FROM orders) AS users,
-- 完成至少一次购买的用户数
(SELECT COUNT(DISTINCT customer_email) FROM completed_orders WHERE order_count >= 1) AS users_one_purchase,
-- 完成至少两次购买的用户数
(SELECT COUNT(DISTINCT customer_email) FROM completed_orders WHERE order_count >= 2) AS users_two_purchases,
-- 完成至少三次购买的用户数
(SELECT COUNT(DISTINCT customer_email) FROM completed_orders WHERE order_count >= 3) AS users_three_purchases,
-- 包含 'yoone' 的订单
(SELECT COUNT(DISTINCT customer_email) FROM yoone_orders WHERE order_count >= 1) AS users_with_yoone_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_orders WHERE order_count >= 2) AS users_with_yoone_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_orders WHERE order_count >= 3) AS users_with_yoone_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_3mg_orders WHERE order_count >= 1) AS users_with_yoone_3mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_3mg_orders WHERE order_count >= 2) AS users_with_yoone_3mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_3mg_orders WHERE order_count >= 3) AS users_with_yoone_3mg_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_6mg_orders WHERE order_count >= 1) AS users_with_yoone_6mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_6mg_orders WHERE order_count >= 2) AS users_with_yoone_6mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_6mg_orders WHERE order_count >= 3) AS users_with_yoone_6mg_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_9mg_orders WHERE order_count >= 1) AS users_with_yoone_9mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_9mg_orders WHERE order_count >= 2) AS users_with_yoone_9mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_9mg_orders WHERE order_count >= 3) AS users_with_yoone_9mg_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_12mg_orders WHERE order_count >= 1) AS users_with_yoone_12mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_12mg_orders WHERE order_count >= 2) AS users_with_yoone_12mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_12mg_orders WHERE order_count >= 3) AS users_with_yoone_12mg_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_15mg_orders WHERE order_count >= 1) AS users_with_yoone_15mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_15mg_orders WHERE order_count >= 2) AS users_with_yoone_15mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM yoone_15mg_orders WHERE order_count >= 3) AS users_with_yoone_15mg_three_purchases,
-- 包含 'zyn' 的订单
(SELECT COUNT(DISTINCT customer_email) FROM zyn_orders WHERE order_count >= 1) AS users_with_zyn_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_orders WHERE order_count >= 2) AS users_with_zyn_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_orders WHERE order_count >= 3) AS users_with_zyn_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_3mg_orders WHERE order_count >= 1) AS users_with_zyn_3mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_3mg_orders WHERE order_count >= 2) AS users_with_zyn_3mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_3mg_orders WHERE order_count >= 3) AS users_with_zyn_3mg_three_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_6mg_orders WHERE order_count >= 1) AS users_with_zyn_6mg_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_6mg_orders WHERE order_count >= 2) AS users_with_zyn_6mg_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zyn_6mg_orders WHERE order_count >= 3) AS users_with_zyn_6mg_three_purchases,
-- 包含 'zex' 的订单
(SELECT COUNT(DISTINCT customer_email) FROM zex_orders WHERE order_count >= 1) AS users_with_zex_one_purchase,
(SELECT COUNT(DISTINCT customer_email) FROM zex_orders WHERE order_count >= 2) AS users_with_zex_two_purchases,
(SELECT COUNT(DISTINCT customer_email) FROM zex_orders WHERE order_count >= 3) AS users_with_zex_three_purchases,
(SELECT SUM(order_count) FROM zyn_3mg_orders) AS zyn_3mg_orders,
(SELECT SUM(order_count) FROM zyn_6mg_orders) AS zyn_6mg_orders,
(SELECT SUM(order_count) FROM zex_orders) AS zex_15mg_orders,
(SELECT SUM(order_count) FROM yoone_3mg_orders) AS order_with_3mg,
(SELECT SUM(order_count) FROM yoone_6mg_orders) AS order_with_6mg,
(SELECT SUM(order_count) FROM yoone_9mg_orders) AS order_with_9mg,
(SELECT SUM(order_count) FROM yoone_12mg_orders) AS order_with_12mg,
(SELECT SUM(order_count) FROM yoone_15mg_orders) AS order_with_15mg
`;
// 执行 SQL 查询
const result = await this.orderRepository.query(sql);
const sql1 = `
WITH product_purchase_counts AS (
SELECT o.customer_email,os.productId, os.name, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
${timeWhere}
GROUP BY o.customer_email, os.productId, os.name
HAVING order_count > 0
)
SELECT
productId,
name,
COUNT(DISTINCT customer_email) AS user_count
FROM product_purchase_counts
GROUP BY productId, name
ORDER BY user_count DESC
LIMIT 30
`;
const sql2 = `
WITH product_purchase_counts AS (
SELECT o.customer_email,os.productId, os.name, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
${timeWhere}
GROUP BY o.customer_email, os.productId, os.name
HAVING order_count > 1
)
SELECT
productId,
name,
COUNT(DISTINCT customer_email) AS user_count
FROM product_purchase_counts
GROUP BY productId, name
ORDER BY user_count DESC
LIMIT 30
`;
const sql3 = `
WITH product_purchase_counts AS (
SELECT o.customer_email,os.productId, os.name, COUNT(DISTINCT o.id) AS order_count
FROM \`order\` o
JOIN order_sale os ON o.id = os.orderId
WHERE o.status IN ('completed', 'processing')
${timeWhere}
GROUP BY o.customer_email, os.productId, os.name
HAVING order_count > 2
)
SELECT
productId,
name,
COUNT(DISTINCT customer_email) AS user_count
FROM product_purchase_counts
GROUP BY productId, name
ORDER BY user_count DESC
LIMIT 30
`;
const first_hot_purchase = await this.orderRepository.query(sql1);
const second_hot_purchase = await this.orderRepository.query(sql2);
const third_hot_purchase = await this.orderRepository.query(sql3);
return {
...result[0],
first_hot_purchase,
second_hot_purchase,
third_hot_purchase,
};
}
async stockForecast(params) {
const { productName, pageSize, current } = params;
const countnameFilter = (
productName ? productName.split(' ').filter(Boolean) : []
)
.map(name => `AND p.name LIKE '%${name}%'`)
.join(' ');
const offset = (current - 1) * pageSize;
const countSql = `
WITH product_list AS (
SELECT DISTINCT s.productSku
FROM stock s
LEFT JOIN stock_point sp ON s.stockPointId = sp.id
LEFT JOIN product p ON s.productSku = p.sku
WHERE sp.ignore = FALSE
${countnameFilter}
)
SELECT COUNT(*) AS total FROM product_list;
`;
const nameFilter = (
productName ? productName.split(' ').filter(Boolean) : []
)
.map(name => `AND pns.productName LIKE '%${name}%'`)
.join(' ');
const sql = `
WITH stock_summary AS (
SELECT
s.productSku,
JSON_ARRAYAGG(JSON_OBJECT('id', sp.id, 'quantity', s.quantity)) AS stockDetails,
SUM(s.quantity) AS totalStock,
SUM(CASE WHEN sp.inCanada THEN s.quantity ELSE 0 END) AS caTotalStock
FROM stock s
JOIN stock_point sp ON s.stockPointId = sp.id
WHERE sp.ignore = FALSE
GROUP BY s.productSku
),
transfer_stock AS (
SELECT
ti.productSku,
SUM(ti.quantity) AS transitStock
FROM transfer_item ti
JOIN transfer t ON ti.transferId = t.id
WHERE t.isCancel = FALSE AND t.isArrived = FALSE
GROUP BY ti.productSku
),
30_sales_summary AS (
SELECT
os.sku AS productSku,
SUM(os.quantity) AS totalSales
FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id
WHERE o.status IN ('completed', 'refunded', 'processing')
AND o.date_paid >= NOW() - INTERVAL 30 DAY
GROUP BY os.sku
),
15_sales_summary AS (
SELECT
os.sku AS productSku,
2 * SUM(os.quantity) AS totalSales
FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id
WHERE o.status IN ('completed', 'refunded', 'processing')
AND o.date_paid >= NOW() - INTERVAL 15 DAY
GROUP BY os.sku
),
sales_max_summary AS (
SELECT
s30.productSku AS productSku,
COALESCE(s30.totalSales, 0) AS totalSales_30,
COALESCE(s15.totalSales, 0) AS totalSales_15,
GREATEST(COALESCE(s30.totalSales, 0), COALESCE(s15.totalSales, 0)) AS maxSales
FROM 30_sales_summary s30
LEFT JOIN 15_sales_summary s15
ON s30.productSku = s15.productSku
UNION ALL
SELECT
s15.productSku AS productSku,
0 AS totalSales_30,
COALESCE(s15.totalSales, 0) AS totalSales_15,
COALESCE(s15.totalSales, 0) AS maxSales
FROM 15_sales_summary s15
LEFT JOIN 30_sales_summary s30
ON s30.productSku = s15.productSku
WHERE s30.productSku IS NULL
),
product_name_summary AS (
SELECT
p.sku AS productSku,
COALESCE(MAX(os.name), MAX(p.name)) AS productName
FROM product p
LEFT JOIN order_sale os ON p.sku = os.sku
GROUP BY p.sku
)
SELECT
ss.productSku,
ss.stockDetails,
COALESCE(ts.transitStock, 0) AS transitStock,
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
sales.totalSales_30,
sales.totalSales_15,
CASE
WHEN sales.maxSales = 0 THEN NULL
ELSE FLOOR((COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) / sales.maxSales * 30)
END AS availableDays,
CASE
WHEN sales.maxSales = 0 THEN NULL
ELSE FLOOR(COALESCE(ss.caTotalStock, 0) / sales.maxSales * 30)
END AS caAvailableDays,
ss.caTotalStock,
sales.maxSales * 4 AS restockQuantity,
pns.productName
FROM stock_summary ss
LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
LEFT JOIN sales_max_summary sales ON ss.productSku = sales.productSku
LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
WHERE 1 = 1
${nameFilter}
ORDER BY caAvailableDays
LIMIT ${pageSize} OFFSET ${offset};
`;
const totalResult = await this.orderRepository.query(countSql);
const total = totalResult[0]?.total || 0;
const items = await this.orderRepository.query(sql);
return {
items,
total,
current,
pageSize,
};
}
async restocking(params) {
const { productName, pageSize, current } = params;
const countnameFilter = (
productName ? productName.split(' ').filter(Boolean) : []
)
.map(name => `AND p.name LIKE '%${name}%'`)
.join(' ');
const offset = (current - 1) * pageSize;
const countSql = `
WITH product_list AS (
SELECT DISTINCT s.productSku
FROM stock s
LEFT JOIN stock_point sp ON s.stockPointId = sp.id
LEFT JOIN product p ON s.productSku = p.sku
WHERE sp.ignore = FALSE
${countnameFilter}
)
SELECT COUNT(*) AS total FROM product_list;
`;
const nameFilter = (
productName ? productName.split(' ').filter(Boolean) : []
)
.map(name => `AND pns.productName LIKE '%${name}%'`)
.join(' ');
const sql = `
WITH stock_summary AS (
SELECT
s.productSku,
SUM(s.quantity) AS totalStock
FROM stock s
JOIN stock_point sp ON s.stockPointId = sp.id
WHERE sp.ignore = FALSE
GROUP BY s.productSku
),
transfer_stock AS (
SELECT
ti.productSku,
SUM(ti.quantity) AS transitStock
FROM transfer_item ti
JOIN transfer t ON ti.transferId = t.id
WHERE t.isCancel = FALSE AND t.isArrived = FALSE
GROUP BY ti.productSku
),
b_sales_data_raw As (
SELECT
sr.productSku,
DATE_FORMAT(sr.createdAt, '%Y-%m') AS month,
SUM(sr.quantityChange) AS sales
FROM stock_record sr
JOIN stock_point sp ON sr.stockPointId = sp.id
WHERE sp.isB
AND sr.createdAt >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
GROUP BY sr.productSku, month
),
sales_data_raw AS (
SELECT
os.sku AS productSku,
DATE_FORMAT(o.date_paid, '%Y-%m') AS month,
SUM(CASE WHEN DAY(o.date_paid) <= 10 THEN os.quantity ELSE 0 END) AS early_sales,
SUM(CASE WHEN DAY(o.date_paid) > 10 AND DAY(o.date_paid) <= 20 THEN os.quantity ELSE 0 END) AS mid_sales,
SUM(CASE WHEN DAY(o.date_paid) > 20 THEN os.quantity ELSE 0 END) AS late_sales
FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id
WHERE o.status IN ('completed', 'refunded', 'processing')
AND o.date_paid >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
GROUP BY os.sku, month
),
monthly_sales_summary AS (
SELECT
sdr.productSku,
JSON_ARRAYAGG(
JSON_OBJECT(
'month', sdr.month,
'early_sales', sdr.early_sales,
'mid_sales', sdr.mid_sales,
'late_sales', sdr.late_sales,
'b_sales', COALESCE(b.sales,0)
)
) AS sales_data
FROM sales_data_raw sdr
LEFT JOIN b_sales_data_raw b ON sdr.productSku = b.productSku AND sdr.month = b.month
GROUP BY sdr.productSku
),
sales_summary AS (
SELECT
os.sku AS productSku,
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 30 DAY THEN os.quantity ELSE 0 END) AS last_30_days_sales,
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 15 DAY THEN os.quantity ELSE 0 END) AS last_15_days_sales,
SUM(CASE WHEN DATE_FORMAT(o.date_paid, '%Y-%m') = DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m') THEN os.quantity ELSE 0 END) AS last_month_sales
FROM order_sale os
JOIN \`order\` o ON os.orderId = o.id
WHERE o.status IN ('completed', 'refunded', 'processing')
AND o.date_paid >= CURDATE() - INTERVAL 2 MONTH
GROUP BY os.sku
),
product_name_summary AS (
SELECT
p.sku AS productSku,
COALESCE(MAX(os.name), MAX(p.name)) AS productName
FROM product p
LEFT JOIN order_sale os ON p.sku = os.sku
GROUP BY p.sku
)
SELECT
ss.productSku,
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
ms.sales_data AS monthlySalesData,
pns.productName,
COALESCE(ssum.last_30_days_sales, 0) AS last30DaysSales,
COALESCE(ssum.last_15_days_sales, 0) AS last15DaysSales,
COALESCE(ssum.last_month_sales, 0) AS lastMonthSales,
CASE
WHEN COALESCE(ssum.last_month_sales, 0) > 0
THEN (COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) / ssum.last_month_sales
ELSE NULL
END AS stock_ratio
FROM stock_summary ss
LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
LEFT JOIN monthly_sales_summary ms ON ss.productSku = ms.productSku
LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
LEFT JOIN sales_summary ssum ON ss.productSku = ssum.productSku
WHERE 1 = 1
${nameFilter}
ORDER BY
stock_ratio IS NULL ASC,
stock_ratio ASC
LIMIT ${pageSize} OFFSET ${offset};
`;
const totalResult = await this.orderRepository.query(countSql);
const total = totalResult[0]?.total || 0;
const items = await this.orderRepository.query(sql);
return {
items,
total,
current,
pageSize,
};
}
async getOrderSorce(params){
const sql = `
WITH cutoff_months AS (
SELECT
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 7 MONTH), '%Y-%m') AS start_month,
DATE_FORMAT(CURDATE(), '%Y-%m') AS end_month
),
user_first_order AS (
SELECT
customer_email,
DATE_FORMAT(MIN(date_paid), '%Y-%m') AS first_order_month
FROM \`order\`
WHERE status IN ('processing', 'completed')
GROUP BY customer_email
),
order_months AS (
SELECT
customer_email,
DATE_FORMAT(date_paid, '%Y-%m') AS order_month
FROM \`order\`
WHERE status IN ('processing', 'completed')
),
filtered_orders AS (
SELECT o.customer_email, o.order_month, u.first_order_month, c.start_month
FROM order_months o
JOIN user_first_order u ON o.customer_email = u.customer_email
JOIN cutoff_months c ON 1=1
WHERE o.order_month >= c.start_month
),
classified AS (
SELECT
order_month,
CASE
WHEN first_order_month < start_month THEN CONCAT('>', start_month)
ELSE first_order_month
END AS first_order_month_group
FROM filtered_orders
),
final_counts AS (
SELECT
order_month,
first_order_month_group,
COUNT(*) AS order_count
FROM classified
GROUP BY order_month, first_order_month_group
)
SELECT * FROM final_counts
ORDER BY order_month DESC, first_order_month_group
`
const inactiveSql = `
WITH
cutoff_months AS (
SELECT
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 7 MONTH), '%Y-%m') AS start_month,
DATE_FORMAT(CURDATE(), '%Y-%m') AS end_month
),
user_orders AS (
SELECT
customer_email,
DATE_FORMAT(date_paid, '%Y-%m') AS order_month,
date_paid
FROM \`order\`
WHERE status IN ('processing', 'completed')
),
filtered_users AS (
SELECT * FROM user_orders u
JOIN cutoff_months c ON u.order_month >= c.start_month
),
monthly_users AS (
SELECT DISTINCT
customer_email,
order_month
FROM filtered_users
),
-- 每个用户的首单月份
first_order_months AS (
SELECT
customer_email,
MIN(DATE_FORMAT(date_paid, '%Y-%m')) AS first_order_month
FROM user_orders
GROUP BY customer_email
),
-- 标注每个用户每月是“新客户”还是“老客户”
labeled_users AS (
SELECT
m.customer_email,
m.order_month,
CASE
WHEN f.first_order_month = m.order_month THEN 'new'
ELSE 'returning'
END AS customer_type
FROM monthly_users m
JOIN first_order_months f ON m.customer_email = f.customer_email
),
-- 每月新老客户统计
monthly_new_old_counts AS (
SELECT
order_month,
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
FROM labeled_users
GROUP BY order_month
),
-- 未来是否下单的检测
user_future_orders AS (
SELECT
u1.customer_email,
u1.order_month AS current_month,
COUNT(u2.order_month) AS future_order_count
FROM monthly_users u1
LEFT JOIN user_orders u2
ON u1.customer_email = u2.customer_email
AND u2.order_month > u1.order_month
GROUP BY u1.customer_email, u1.order_month
),
users_without_future_orders AS (
SELECT
current_month AS order_month,
COUNT(DISTINCT customer_email) AS inactive_user_count
FROM user_future_orders
WHERE future_order_count = 0
GROUP BY current_month
)
-- 最终结果:每月新客户、老客户、未来未复购客户
SELECT
m.order_month,
m.new_user_count,
m.old_user_count,
COALESCE(i.inactive_user_count, 0) AS inactive_user_count
FROM monthly_new_old_counts m
LEFT JOIN users_without_future_orders i
ON m.order_month = i.order_month
ORDER BY m.order_month DESC;
`
const [res, inactiveRes ] = await Promise.all([
this.orderRepository.query(sql),
this.orderRepository.query(inactiveSql),
])
return {
res,inactiveRes
}
}
async getInativeUsersByMonth(month: string) {
const sql = `
WITH current_month_orders AS (
SELECT DISTINCT customer_email
FROM \`order\`
WHERE status IN ('processing', 'completed')
AND DATE_FORMAT(date_paid, '%Y-%m') = '${month}'
),
future_orders AS (
SELECT DISTINCT customer_email
FROM \`order\`
WHERE status IN ('processing', 'completed')
AND DATE_FORMAT(date_paid, '%Y-%m') > '${month}'
),
inactive_customers AS (
SELECT c.customer_email
FROM current_month_orders c
LEFT JOIN future_orders f ON c.customer_email = f.customer_email
WHERE f.customer_email IS NULL
)
SELECT
o.customer_email AS email,
MIN(o.date_paid) AS first_purchase_date,
MAX(o.date_paid) AS last_purchase_date,
COUNT(DISTINCT o.id) AS orders,
SUM(o.total) AS total,
ANY_VALUE(o.shipping) AS shipping,
ANY_VALUE(o.billing) AS billing,
(
SELECT JSON_ARRAYAGG(tag)
FROM customer_tag ct
WHERE ct.email = o.customer_email
) AS tags
FROM \`order\` o
JOIN inactive_customers i ON o.customer_email = i.customer_email
WHERE o.status IN ('processing', 'completed')
GROUP BY o.customer_email
`
const res = await this.orderRepository.query(sql)
return res
}
}