822 lines
27 KiB
Python
Executable File
822 lines
27 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
import argparse
|
||
import subprocess
|
||
import sys
|
||
|
||
|
||
def _sql_escape(value: str) -> str:
|
||
return value.replace("'", "''")
|
||
|
||
|
||
def build_sql(keyword: str, days: int, limit: int) -> str:
|
||
kw = _sql_escape(keyword.strip())
|
||
return f"""
|
||
USE [JSDataMiddlePlatform];
|
||
SET NOCOUNT ON;
|
||
|
||
DECLARE @kw nvarchar(100) = N'{kw}';
|
||
DECLARE @days int = {days};
|
||
DECLARE @limit int = {limit};
|
||
DECLARE @start_time datetime2 = DATEADD(day, -@days, GETDATE());
|
||
DECLARE @end_time datetime2 = GETDATE();
|
||
DECLARE @has_exact bit = 0;
|
||
|
||
IF EXISTS (SELECT 1 FROM dbo.jst_sku WITH (NOLOCK) WHERE sku_id = @kw)
|
||
SET @has_exact = 1;
|
||
|
||
IF OBJECT_ID('tempdb..#matched_sku') IS NOT NULL
|
||
DROP TABLE #matched_sku;
|
||
|
||
SELECT TOP (300)
|
||
s.sku_id,
|
||
s.i_id AS style_code,
|
||
s.other_1 AS spu,
|
||
s.name AS sku_name,
|
||
s.short_name,
|
||
s.properties_value,
|
||
s.color,
|
||
s.item_type,
|
||
s.category,
|
||
s.brand,
|
||
s.vc_name,
|
||
s.supplier_name,
|
||
s.supplier_sku_id,
|
||
s.sku_code,
|
||
s.sale_price,
|
||
s.cost_price,
|
||
s.purchase_price,
|
||
s.market_price,
|
||
s.weight,
|
||
s.l,
|
||
s.w,
|
||
s.h,
|
||
s.unit,
|
||
s.labels,
|
||
s.other_2 AS board_type,
|
||
s.other_10 AS board_material,
|
||
s.other_3 AS default_warehouse,
|
||
s.other_5 AS sku_status,
|
||
s.other_7 AS product_no,
|
||
s.pic AS preview_pic_url,
|
||
s.pic_big AS preview_pic_big_url,
|
||
s.created,
|
||
s.modified,
|
||
CASE
|
||
WHEN s.sku_id = @kw THEN 0
|
||
WHEN s.i_id = @kw THEN 1
|
||
WHEN s.other_1 = @kw THEN 2
|
||
WHEN s.other_7 = @kw THEN 3
|
||
WHEN s.sku_id LIKE @kw + '%' THEN 4
|
||
WHEN s.name LIKE @kw + '%' THEN 5
|
||
ELSE 9
|
||
END AS match_rank
|
||
INTO #matched_sku
|
||
FROM dbo.jst_sku AS s WITH (NOLOCK)
|
||
WHERE ISNULL(NULLIF(@kw, ''), '') <> ''
|
||
AND (
|
||
(@has_exact = 1 AND s.sku_id = @kw)
|
||
OR (
|
||
@has_exact = 0
|
||
AND (
|
||
s.sku_id LIKE '%' + @kw + '%'
|
||
OR s.i_id LIKE '%' + @kw + '%'
|
||
OR s.other_1 LIKE '%' + @kw + '%'
|
||
OR s.name LIKE '%' + @kw + '%'
|
||
OR s.short_name LIKE '%' + @kw + '%'
|
||
OR s.other_7 LIKE '%' + @kw + '%'
|
||
)
|
||
)
|
||
)
|
||
ORDER BY
|
||
CASE
|
||
WHEN s.sku_id = @kw THEN 0
|
||
WHEN s.i_id = @kw THEN 1
|
||
WHEN s.other_1 = @kw THEN 2
|
||
WHEN s.other_7 = @kw THEN 3
|
||
WHEN s.sku_id LIKE @kw + '%' THEN 4
|
||
WHEN s.name LIKE @kw + '%' THEN 5
|
||
ELSE 9
|
||
END,
|
||
s.modified DESC;
|
||
|
||
-- 1) 产品基础信息(含SKU名称/成本/材质/预览图 + 扩展字段)
|
||
SELECT TOP (@limit)
|
||
sku_id,
|
||
sku_name,
|
||
short_name,
|
||
style_code,
|
||
spu,
|
||
product_no,
|
||
sku_code,
|
||
properties_value,
|
||
color,
|
||
item_type,
|
||
category,
|
||
brand,
|
||
supplier_name,
|
||
supplier_sku_id,
|
||
cost_price,
|
||
purchase_price,
|
||
sale_price,
|
||
market_price,
|
||
board_type,
|
||
board_material,
|
||
default_warehouse,
|
||
sku_status,
|
||
labels,
|
||
weight,
|
||
l,
|
||
w,
|
||
h,
|
||
unit,
|
||
preview_pic_url,
|
||
preview_pic_big_url,
|
||
created,
|
||
modified
|
||
FROM #matched_sku
|
||
ORDER BY match_rank ASC, modified DESC;
|
||
|
||
IF OBJECT_ID('tempdb..#afs') IS NOT NULL
|
||
DROP TABLE #afs;
|
||
|
||
SELECT
|
||
NULLIF(a.undefined10, '') AS sku_id,
|
||
ISNULL(NULLIF(a.undefined36, ''), 'unknown') AS sku_name,
|
||
ISNULL(NULLIF(a.undefined8, ''), 'unknown') AS shop_name,
|
||
NULLIF(a.undefined2, '') AS order_id,
|
||
ISNULL(NULLIF(a.undefined56, ''), '未标注') AS afs_type,
|
||
ISNULL(NULLIF(a.undefined58, ''), '未标注') AS afs_result,
|
||
ISNULL(NULLIF(a.undefined57, ''), '') AS native_reason,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined20, '')) AS order_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined30, '')) AS shipped_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined37, '')) AS pre_ship_apply_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined40, '')) AS pre_ship_return_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined46, '')) AS post_ship_apply_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined49, '')) AS post_ship_return_qty,
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined52, '')) AS refund_amount
|
||
INTO #afs
|
||
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
|
||
WHERE a.createdAt >= @start_time
|
||
AND a.createdAt < @end_time
|
||
AND ISNULL(NULLIF(a.undefined2, ''), '') <> ''
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%配件%'
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%特殊单%'
|
||
AND NULLIF(a.undefined10, '') IN (SELECT sku_id FROM #matched_sku);
|
||
|
||
IF OBJECT_ID('tempdb..#afs_orders') IS NOT NULL
|
||
DROP TABLE #afs_orders;
|
||
|
||
SELECT DISTINCT order_id
|
||
INTO #afs_orders
|
||
FROM #afs
|
||
WHERE ISNULL(order_id, '') <> '';
|
||
|
||
IF OBJECT_ID('tempdb..#chat_latest') IS NOT NULL
|
||
DROP TABLE #chat_latest;
|
||
|
||
WITH chat_rank AS (
|
||
SELECT
|
||
NULLIF(c.orderNo, '') AS order_id,
|
||
ISNULL(NULLIF(c.mainReason, ''), '') AS main_reason,
|
||
ISNULL(NULLIF(c.secondReason, ''), '') AS second_reason,
|
||
ISNULL(NULLIF(c.thirdReason, ''), '') AS third_reason,
|
||
c.createdAt,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY NULLIF(c.orderNo, '')
|
||
ORDER BY c.createdAt DESC, c.id DESC
|
||
) AS rn
|
||
FROM dbo.pc_kfChatLog AS c WITH (NOLOCK)
|
||
INNER JOIN #afs_orders AS o
|
||
ON o.order_id = NULLIF(c.orderNo, '')
|
||
)
|
||
SELECT
|
||
order_id,
|
||
main_reason,
|
||
second_reason,
|
||
third_reason
|
||
INTO #chat_latest
|
||
FROM chat_rank
|
||
WHERE rn = 1;
|
||
|
||
IF OBJECT_ID('tempdb..#afs_enriched') IS NOT NULL
|
||
DROP TABLE #afs_enriched;
|
||
|
||
SELECT
|
||
af.*,
|
||
CASE
|
||
WHEN ISNULL(NULLIF(af.native_reason, ''), '') <> '' THEN af.native_reason
|
||
WHEN ISNULL(NULLIF(cl.main_reason, ''), '') <> '' THEN cl.main_reason
|
||
ELSE '未标注'
|
||
END AS reason_l1,
|
||
CASE
|
||
WHEN ISNULL(NULLIF(cl.second_reason, ''), '') <> '' THEN cl.second_reason
|
||
ELSE '未标注'
|
||
END AS reason_l2,
|
||
CASE
|
||
WHEN ISNULL(NULLIF(cl.third_reason, ''), '') <> '' THEN cl.third_reason
|
||
ELSE '未标注'
|
||
END AS reason_l3,
|
||
CASE
|
||
WHEN ISNULL(NULLIF(af.native_reason, ''), '') <> '' THEN 'afs_reason'
|
||
WHEN ISNULL(NULLIF(cl.main_reason, ''), '') <> '' THEN 'chat_main_reason'
|
||
ELSE 'unknown'
|
||
END AS reason_source
|
||
INTO #afs_enriched
|
||
FROM #afs AS af
|
||
LEFT JOIN #chat_latest AS cl
|
||
ON cl.order_id = af.order_id;
|
||
|
||
-- 2) 最近N天产品出货/售后件数(件数优先)
|
||
SELECT TOP (@limit)
|
||
ae.sku_id,
|
||
MAX(COALESCE(ms.sku_name, ae.sku_name)) AS sku_name,
|
||
COUNT(*) AS afs_record_count,
|
||
COUNT(DISTINCT ae.order_id) AS order_count,
|
||
SUM(ISNULL(ae.order_qty, 0)) AS order_qty_sum,
|
||
SUM(ISNULL(ae.shipped_qty, 0)) AS shipped_qty_sum,
|
||
SUM(CASE WHEN ISNULL(ae.refund_amount, 0) > 0 THEN 1 ELSE 0 END) AS refund_order_count
|
||
FROM #afs_enriched AS ae
|
||
LEFT JOIN #matched_sku AS ms
|
||
ON ms.sku_id = ae.sku_id
|
||
GROUP BY ae.sku_id
|
||
ORDER BY shipped_qty_sum DESC, order_count DESC;
|
||
|
||
-- 3) 最近N天该产品的店铺分布(件数)
|
||
SELECT TOP (@limit)
|
||
ae.shop_name,
|
||
COUNT(DISTINCT ae.order_id) AS order_count,
|
||
SUM(ISNULL(ae.order_qty, 0)) AS order_qty_sum,
|
||
SUM(ISNULL(ae.shipped_qty, 0)) AS shipped_qty_sum,
|
||
SUM(CASE WHEN ISNULL(ae.refund_amount, 0) > 0 THEN 1 ELSE 0 END) AS refund_order_count
|
||
FROM #afs_enriched AS ae
|
||
GROUP BY ae.shop_name
|
||
ORDER BY shipped_qty_sum DESC, order_count DESC;
|
||
|
||
IF OBJECT_ID('tempdb..#freight') IS NOT NULL
|
||
DROP TABLE #freight;
|
||
|
||
SELECT
|
||
NULLIF(f.sku_id, '') AS sku_id,
|
||
NULLIF(f.so_id, '') AS so_id,
|
||
ISNULL(NULLIF(f.receiver_state, ''), '未知') AS receiver_state,
|
||
CASE
|
||
WHEN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(f.receiver_state,''),'未知'),'省',''),'市',''),'壮族自治区',''),'回族自治区',''),'维吾尔自治区',''),'自治区',''),'特别行政区',''),'地区','')
|
||
IN ('上海','江苏','浙江','安徽') THEN '一区'
|
||
WHEN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(f.receiver_state,''),'未知'),'省',''),'市',''),'壮族自治区',''),'回族自治区',''),'维吾尔自治区',''),'自治区',''),'特别行政区',''),'地区','')
|
||
IN ('北京','天津','河北','山东','河南','湖北','湖南','江西','福建','广东') THEN '二区'
|
||
WHEN REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(f.receiver_state,''),'未知'),'省',''),'市',''),'壮族自治区',''),'回族自治区',''),'维吾尔自治区',''),'自治区',''),'特别行政区',''),'地区','')
|
||
IN ('辽宁','吉林','黑龙江','山西','陕西','四川','重庆','贵州','云南','广西','海南','内蒙古','甘肃','宁夏','青海','新疆','西藏') THEN '三区'
|
||
ELSE '未分区'
|
||
END AS freight_zone,
|
||
ISNULL(NULLIF(f.expressName, ''), 'unknown') AS express_name,
|
||
TRY_CONVERT(decimal(18, 2), f.freightCost) AS freight_cost,
|
||
TRY_CONVERT(decimal(18, 2), f.weight) AS freight_weight,
|
||
f.send_date
|
||
INTO #freight
|
||
FROM dbo.fys_jstFreightCostDetail AS f WITH (NOLOCK)
|
||
WHERE f.send_date >= @start_time
|
||
AND f.send_date < @end_time
|
||
AND ISNULL(NULLIF(f.sku_id, ''), '') <> ''
|
||
AND NULLIF(f.sku_id, '') IN (SELECT sku_id FROM #matched_sku);
|
||
|
||
-- 4) 最近N天一区/二区/三区运费(按SKU)
|
||
SELECT TOP (@limit)
|
||
fr.sku_id,
|
||
fr.freight_zone,
|
||
COUNT(*) AS freight_record_count,
|
||
COUNT(DISTINCT fr.so_id) AS order_count,
|
||
SUM(ISNULL(fr.freight_cost, 0)) AS freight_cost_sum,
|
||
AVG(ISNULL(fr.freight_cost, 0)) AS avg_freight_cost,
|
||
AVG(ISNULL(fr.freight_weight, 0)) AS avg_weight
|
||
FROM #freight AS fr
|
||
GROUP BY fr.sku_id, fr.freight_zone
|
||
ORDER BY
|
||
CASE fr.freight_zone WHEN '一区' THEN 1 WHEN '二区' THEN 2 WHEN '三区' THEN 3 ELSE 9 END,
|
||
order_count DESC;
|
||
|
||
-- 5) 最近N天省份运费TOP(按SKU)
|
||
SELECT TOP (@limit)
|
||
fr.sku_id,
|
||
fr.receiver_state,
|
||
COUNT(DISTINCT fr.so_id) AS order_count,
|
||
SUM(ISNULL(fr.freight_cost, 0)) AS freight_cost_sum,
|
||
AVG(ISNULL(fr.freight_cost, 0)) AS avg_freight_cost,
|
||
AVG(ISNULL(fr.freight_weight, 0)) AS avg_weight
|
||
FROM #freight AS fr
|
||
GROUP BY fr.sku_id, fr.receiver_state
|
||
ORDER BY order_count DESC, freight_cost_sum DESC;
|
||
|
||
-- 6) 最近N天售后总览(该SKU)
|
||
SELECT TOP (@limit)
|
||
ae.sku_id,
|
||
COUNT(*) AS afs_record_count,
|
||
COUNT(DISTINCT ae.order_id) AS afs_order_count,
|
||
SUM(ISNULL(ae.shipped_qty, 0)) AS shipped_qty_sum,
|
||
SUM(ISNULL(ae.pre_ship_apply_qty, 0) + ISNULL(ae.post_ship_apply_qty, 0)) AS apply_qty_sum,
|
||
SUM(ISNULL(ae.pre_ship_return_qty, 0) + ISNULL(ae.post_ship_return_qty, 0)) AS return_qty_sum,
|
||
SUM(CASE WHEN ISNULL(ae.refund_amount, 0) > 0 THEN 1 ELSE 0 END) AS refund_order_count,
|
||
SUM(ISNULL(ae.refund_amount, 0)) AS refund_amount_sum
|
||
FROM #afs_enriched AS ae
|
||
GROUP BY ae.sku_id
|
||
ORDER BY afs_record_count DESC;
|
||
|
||
-- 7) 最近N天售后类型/结果矩阵(该SKU)
|
||
SELECT TOP (@limit)
|
||
ae.sku_id,
|
||
ae.afs_type,
|
||
ae.afs_result,
|
||
COUNT(*) AS afs_record_count,
|
||
COUNT(DISTINCT ae.order_id) AS order_count,
|
||
SUM(ISNULL(ae.shipped_qty, 0)) AS shipped_qty_sum,
|
||
SUM(CASE WHEN ISNULL(ae.refund_amount, 0) > 0 THEN 1 ELSE 0 END) AS refund_order_count
|
||
FROM #afs_enriched AS ae
|
||
GROUP BY ae.sku_id, ae.afs_type, ae.afs_result
|
||
ORDER BY afs_record_count DESC, order_count DESC;
|
||
|
||
-- 8) 最近N天售后原因TOP(该SKU)
|
||
SELECT TOP (@limit)
|
||
ae.sku_id,
|
||
ae.reason_l1,
|
||
ae.reason_l2,
|
||
ae.reason_l3,
|
||
ae.reason_source,
|
||
COUNT(*) AS afs_record_count,
|
||
COUNT(DISTINCT ae.order_id) AS order_count,
|
||
SUM(ISNULL(ae.shipped_qty, 0)) AS shipped_qty_sum,
|
||
SUM(CASE WHEN ISNULL(ae.refund_amount, 0) > 0 THEN 1 ELSE 0 END) AS refund_order_count
|
||
FROM #afs_enriched AS ae
|
||
GROUP BY ae.sku_id, ae.reason_l1, ae.reason_l2, ae.reason_l3, ae.reason_source
|
||
ORDER BY afs_record_count DESC, order_count DESC;
|
||
|
||
IF OBJECT_ID('tempdb..#inv_latest') IS NOT NULL
|
||
DROP TABLE #inv_latest;
|
||
|
||
WITH inv_rank AS (
|
||
SELECT
|
||
NULLIF(i.sku_id, '') AS sku_id,
|
||
TRY_CONVERT(decimal(18, 2), i.qty) AS stock_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.order_lock) AS order_lock_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.pick_lock) AS pick_lock_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.lock_qty) AS lock_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.virtual_qty) AS virtual_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.defective_qty) AS defective_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.purchase_qty) AS purchase_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.return_qty) AS return_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.in_qty) AS in_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.min_qty) AS min_qty,
|
||
TRY_CONVERT(decimal(18, 2), i.max_qty) AS max_qty,
|
||
i.wms_co_id,
|
||
i.name AS inventory_note,
|
||
COALESCE(i.updateTime, i.modified, i.createTime) AS inventory_snapshot_time,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY NULLIF(i.sku_id, '')
|
||
ORDER BY COALESCE(i.updateTime, i.modified, i.createTime) DESC, i.Id DESC
|
||
) AS rn
|
||
FROM dbo.jst_inventoryInfo AS i WITH (NOLOCK)
|
||
WHERE NULLIF(i.sku_id, '') IN (SELECT sku_id FROM #matched_sku)
|
||
)
|
||
SELECT
|
||
sku_id,
|
||
stock_qty,
|
||
order_lock_qty,
|
||
pick_lock_qty,
|
||
lock_qty,
|
||
virtual_qty,
|
||
defective_qty,
|
||
purchase_qty,
|
||
return_qty,
|
||
in_qty,
|
||
min_qty,
|
||
max_qty,
|
||
wms_co_id,
|
||
inventory_note,
|
||
inventory_snapshot_time,
|
||
CASE
|
||
WHEN ISNULL(stock_qty, 0) - ISNULL(order_lock_qty, 0) - ISNULL(pick_lock_qty, 0) - ISNULL(lock_qty, 0) < 0
|
||
THEN CAST(0 AS decimal(18, 2))
|
||
ELSE ISNULL(stock_qty, 0) - ISNULL(order_lock_qty, 0) - ISNULL(pick_lock_qty, 0) - ISNULL(lock_qty, 0)
|
||
END AS available_qty
|
||
INTO #inv_latest
|
||
FROM inv_rank
|
||
WHERE rn = 1;
|
||
|
||
IF OBJECT_ID('tempdb..#ship_base') IS NOT NULL
|
||
DROP TABLE #ship_base;
|
||
|
||
SELECT
|
||
NULLIF(a.undefined10, '') AS sku_id,
|
||
NULLIF(a.undefined2, '') AS order_id,
|
||
NULLIF(a.undefined12, '') AS product_id,
|
||
NULLIF(a.undefined11, '') AS style_code_from_afs,
|
||
NULLIF(a.undefined96, '') AS product_no_from_afs,
|
||
ISNULL(NULLIF(a.undefined8, ''), 'unknown') AS shop_name,
|
||
CASE
|
||
WHEN CHARINDEX('-', ISNULL(NULLIF(a.undefined8, ''), '')) > 0
|
||
THEN LEFT(ISNULL(NULLIF(a.undefined8, ''), ''), CHARINDEX('-', ISNULL(NULLIF(a.undefined8, ''), '')) - 1)
|
||
ELSE ISNULL(NULLIF(a.undefined8, ''), '未知渠道')
|
||
END AS shop_channel,
|
||
ISNULL(NULLIF(a.undefined66, ''), '') AS ship_warehouse_name,
|
||
COALESCE(
|
||
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 120),
|
||
TRY_CONVERT(datetime2, REPLACE(NULLIF(a.undefined6, ''), '/', '-'), 120),
|
||
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 111),
|
||
TRY_CONVERT(datetime2, a.createdAt)
|
||
) AS ship_dt,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined30, '')),
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined20, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS shipped_qty
|
||
INTO #ship_base
|
||
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
|
||
WHERE NULLIF(a.undefined10, '') IN (SELECT sku_id FROM #matched_sku)
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%配件%'
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%特殊单%';
|
||
|
||
IF OBJECT_ID('tempdb..#ship_daily') IS NOT NULL
|
||
DROP TABLE #ship_daily;
|
||
|
||
SELECT
|
||
sb.sku_id,
|
||
CAST(sb.ship_dt AS date) AS ship_date,
|
||
COUNT(DISTINCT sb.order_id) AS order_count,
|
||
SUM(ISNULL(sb.shipped_qty, 0)) AS shipped_qty_sum
|
||
INTO #ship_daily
|
||
FROM #ship_base AS sb
|
||
WHERE sb.ship_dt >= @start_time
|
||
AND sb.ship_dt < @end_time
|
||
GROUP BY sb.sku_id, CAST(sb.ship_dt AS date);
|
||
|
||
IF OBJECT_ID('tempdb..#ship_stats') IS NOT NULL
|
||
DROP TABLE #ship_stats;
|
||
|
||
SELECT
|
||
sd.sku_id,
|
||
SUM(CASE WHEN sd.ship_date >= CAST(DATEADD(day, -7, @end_time) AS date) THEN ISNULL(sd.shipped_qty_sum, 0) ELSE 0 END) AS shipped_qty_7d,
|
||
SUM(CASE WHEN sd.ship_date >= CAST(DATEADD(day, -30, @end_time) AS date) THEN ISNULL(sd.shipped_qty_sum, 0) ELSE 0 END) AS shipped_qty_30d,
|
||
MAX(sd.ship_date) AS last_ship_date
|
||
INTO #ship_stats
|
||
FROM #ship_daily AS sd
|
||
GROUP BY sd.sku_id;
|
||
|
||
IF OBJECT_ID('tempdb..#sku_owner') IS NOT NULL
|
||
DROP TABLE #sku_owner;
|
||
|
||
SELECT
|
||
ms.sku_id,
|
||
COALESCE(
|
||
NULLIF(pc_exact.purchaser, ''),
|
||
NULLIF(pc_style.purchaser, ''),
|
||
NULLIF(pc_base.purchaser, ''),
|
||
'未分配'
|
||
) AS purchaser_name,
|
||
COALESCE(
|
||
NULLIF(po_exact.oper, ''),
|
||
NULLIF(po_style.oper, ''),
|
||
NULLIF(po_base.oper, ''),
|
||
NULLIF(pc_exact.oper, ''),
|
||
NULLIF(pc_style.oper, ''),
|
||
NULLIF(pc_base.oper, ''),
|
||
'未分配'
|
||
) AS spu_oper_name
|
||
INTO #sku_owner
|
||
FROM #matched_sku AS ms
|
||
OUTER APPLY (
|
||
SELECT TOP 1 c.purchaser, c.oper
|
||
FROM dbo.fys_SkuDataLibrary_SpuConfig AS c WITH (NOLOCK)
|
||
WHERE NULLIF(c.spu, '') = NULLIF(ms.spu, '')
|
||
ORDER BY c.Id DESC
|
||
) AS pc_exact
|
||
OUTER APPLY (
|
||
SELECT TOP 1 c.purchaser, c.oper
|
||
FROM dbo.fys_SkuDataLibrary_SpuConfig AS c WITH (NOLOCK)
|
||
WHERE NULLIF(c.spu, '') = NULLIF(ms.style_code, '')
|
||
ORDER BY c.Id DESC
|
||
) AS pc_style
|
||
OUTER APPLY (
|
||
SELECT TOP 1 c.purchaser, c.oper
|
||
FROM dbo.fys_SkuDataLibrary_SpuConfig AS c WITH (NOLOCK)
|
||
WHERE NULLIF(c.spu, '') = CASE
|
||
WHEN CHARINDEX('K', ISNULL(ms.spu, '')) > 0
|
||
THEN LEFT(ms.spu, CHARINDEX('K', ms.spu) - 1)
|
||
ELSE NULL
|
||
END
|
||
ORDER BY c.Id DESC
|
||
) AS pc_base
|
||
OUTER APPLY (
|
||
SELECT TOP 1 o.oper
|
||
FROM dbo.op_operManage AS o WITH (NOLOCK)
|
||
WHERE NULLIF(o.spu, '') = NULLIF(ms.spu, '')
|
||
ORDER BY o.Id DESC
|
||
) AS po_exact
|
||
OUTER APPLY (
|
||
SELECT TOP 1 o.oper
|
||
FROM dbo.op_operManage AS o WITH (NOLOCK)
|
||
WHERE NULLIF(o.spu, '') = NULLIF(ms.style_code, '')
|
||
ORDER BY o.Id DESC
|
||
) AS po_style
|
||
OUTER APPLY (
|
||
SELECT TOP 1 o.oper
|
||
FROM dbo.op_operManage AS o WITH (NOLOCK)
|
||
WHERE NULLIF(o.spu, '') = CASE
|
||
WHEN CHARINDEX('K', ISNULL(ms.spu, '')) > 0
|
||
THEN LEFT(ms.spu, CHARINDEX('K', ms.spu) - 1)
|
||
ELSE NULL
|
||
END
|
||
ORDER BY o.Id DESC
|
||
) AS po_base;
|
||
|
||
IF OBJECT_ID('tempdb..#tm_oper') IS NOT NULL
|
||
DROP TABLE #tm_oper;
|
||
|
||
WITH tm_rank AS (
|
||
SELECT
|
||
NULLIF(t.shopproid, '') AS product_id,
|
||
ISNULL(NULLIF(t.shopname, ''), 'unknown') AS shop_name,
|
||
NULLIF(t.oper, '') AS oper_name,
|
||
NULLIF(t.dept, '') AS dept_name,
|
||
ROW_NUMBER() OVER (
|
||
PARTITION BY NULLIF(t.shopproid, ''), ISNULL(NULLIF(t.shopname, ''), 'unknown')
|
||
ORDER BY t.Id DESC
|
||
) AS rn
|
||
FROM dbo.op_TargetManagement AS t WITH (NOLOCK)
|
||
WHERE NULLIF(t.shopproid, '') IS NOT NULL
|
||
)
|
||
SELECT
|
||
product_id,
|
||
shop_name,
|
||
oper_name,
|
||
dept_name
|
||
INTO #tm_oper
|
||
FROM tm_rank
|
||
WHERE rn = 1;
|
||
|
||
IF OBJECT_ID('tempdb..#tm_oper_pid') IS NOT NULL
|
||
DROP TABLE #tm_oper_pid;
|
||
|
||
SELECT
|
||
product_id,
|
||
MAX(oper_name) AS oper_name,
|
||
MAX(dept_name) AS dept_name
|
||
INTO #tm_oper_pid
|
||
FROM #tm_oper
|
||
GROUP BY product_id;
|
||
|
||
IF OBJECT_ID('tempdb..#product_id_contact') IS NOT NULL
|
||
DROP TABLE #product_id_contact;
|
||
|
||
SELECT
|
||
sb.sku_id,
|
||
ms.sku_name,
|
||
ISNULL(NULLIF(sb.product_id, ''), '(未标注)') AS product_id,
|
||
sb.shop_channel,
|
||
sb.shop_name,
|
||
COALESCE(
|
||
NULLIF(tm_shop.oper_name, ''),
|
||
NULLIF(tm_pid.oper_name, ''),
|
||
NULLIF(ow.spu_oper_name, ''),
|
||
'未分配'
|
||
) AS operator_name,
|
||
COALESCE(NULLIF(ow.purchaser_name, ''), '未分配') AS purchaser_name,
|
||
COALESCE(NULLIF(ms.supplier_name, ''), '未标注') AS factory_name,
|
||
COALESCE(NULLIF(sb.ship_warehouse_name, ''), NULLIF(ms.default_warehouse, ''), NULLIF(inv.inventory_note, ''), '未标注') AS warehouse_name,
|
||
COUNT(DISTINCT sb.order_id) AS order_count_30d,
|
||
SUM(ISNULL(sb.shipped_qty, 0)) AS shipped_qty_sum_30d
|
||
INTO #product_id_contact
|
||
FROM #ship_base AS sb
|
||
INNER JOIN #matched_sku AS ms
|
||
ON ms.sku_id = sb.sku_id
|
||
LEFT JOIN #sku_owner AS ow
|
||
ON ow.sku_id = sb.sku_id
|
||
LEFT JOIN #tm_oper AS tm_shop
|
||
ON tm_shop.product_id = NULLIF(sb.product_id, '')
|
||
AND tm_shop.shop_name = sb.shop_name
|
||
LEFT JOIN #tm_oper_pid AS tm_pid
|
||
ON tm_pid.product_id = NULLIF(sb.product_id, '')
|
||
LEFT JOIN #inv_latest AS inv
|
||
ON inv.sku_id = sb.sku_id
|
||
WHERE sb.ship_dt >= @start_time
|
||
AND sb.ship_dt < @end_time
|
||
AND ISNULL(NULLIF(sb.product_id, ''), '') <> ''
|
||
GROUP BY
|
||
sb.sku_id,
|
||
ms.sku_name,
|
||
ISNULL(NULLIF(sb.product_id, ''), '(未标注)'),
|
||
sb.shop_channel,
|
||
sb.shop_name,
|
||
COALESCE(
|
||
NULLIF(tm_shop.oper_name, ''),
|
||
NULLIF(tm_pid.oper_name, ''),
|
||
NULLIF(ow.spu_oper_name, ''),
|
||
'未分配'
|
||
),
|
||
COALESCE(NULLIF(ow.purchaser_name, ''), '未分配'),
|
||
COALESCE(NULLIF(ms.supplier_name, ''), '未标注'),
|
||
COALESCE(NULLIF(sb.ship_warehouse_name, ''), NULLIF(ms.default_warehouse, ''), NULLIF(inv.inventory_note, ''), '未标注');
|
||
|
||
IF OBJECT_ID('tempdb..#risk_eval') IS NOT NULL
|
||
DROP TABLE #risk_eval;
|
||
|
||
SELECT
|
||
ms.sku_id,
|
||
ms.sku_name,
|
||
inv.stock_qty,
|
||
inv.order_lock_qty,
|
||
inv.pick_lock_qty,
|
||
inv.lock_qty,
|
||
inv.available_qty,
|
||
inv.virtual_qty,
|
||
inv.defective_qty,
|
||
inv.purchase_qty,
|
||
inv.return_qty,
|
||
inv.in_qty,
|
||
inv.min_qty,
|
||
inv.max_qty,
|
||
inv.wms_co_id,
|
||
inv.inventory_snapshot_time,
|
||
ss.last_ship_date,
|
||
CAST(ISNULL(ss.shipped_qty_7d, 0) / 7.0 AS decimal(18, 2)) AS avg_daily_shipped_7d,
|
||
CAST(ISNULL(ss.shipped_qty_30d, 0) / 30.0 AS decimal(18, 2)) AS avg_daily_shipped_30d,
|
||
CAST(
|
||
CASE
|
||
WHEN ISNULL(ss.shipped_qty_30d, 0) <= 0 THEN NULL
|
||
ELSE inv.available_qty / NULLIF(ISNULL(ss.shipped_qty_30d, 0) / 30.0, 0)
|
||
END AS decimal(18, 2)
|
||
) AS estimated_stockout_days,
|
||
10 AS replenishment_lead_days,
|
||
CAST(
|
||
CASE
|
||
WHEN ISNULL(ss.shipped_qty_30d, 0) <= 0 THEN 0
|
||
WHEN inv.available_qty >= (ISNULL(ss.shipped_qty_30d, 0) / 30.0) * 10 THEN 0
|
||
ELSE ((ISNULL(ss.shipped_qty_30d, 0) / 30.0) * 10) - inv.available_qty
|
||
END AS decimal(18, 2)
|
||
) AS suggest_replenish_qty_to_10d,
|
||
CASE
|
||
WHEN inv.sku_id IS NULL THEN '无库存快照'
|
||
WHEN ISNULL(ss.shipped_qty_30d, 0) <= 0 THEN '近30天无发货'
|
||
WHEN inv.available_qty <= 0 THEN '缺货/可用库存<=0'
|
||
WHEN (inv.available_qty / NULLIF(ISNULL(ss.shipped_qty_30d, 0) / 30.0, 0)) <= 10 THEN '补货紧急(<=10天)'
|
||
WHEN (inv.available_qty / NULLIF(ISNULL(ss.shipped_qty_30d, 0) / 30.0, 0)) <= 15 THEN '补货预警(10-15天)'
|
||
ELSE '库存安全'
|
||
END AS stock_risk_level,
|
||
CASE
|
||
WHEN inv.sku_id IS NULL THEN 0
|
||
WHEN ISNULL(ss.shipped_qty_30d, 0) <= 0 THEN 0
|
||
WHEN inv.available_qty <= 0 THEN 1
|
||
WHEN (inv.available_qty / NULLIF(ISNULL(ss.shipped_qty_30d, 0) / 30.0, 0)) <= 15 THEN 1
|
||
ELSE 0
|
||
END AS has_stock_risk
|
||
INTO #risk_eval
|
||
FROM #matched_sku AS ms
|
||
LEFT JOIN #inv_latest AS inv
|
||
ON inv.sku_id = ms.sku_id
|
||
LEFT JOIN #ship_stats AS ss
|
||
ON ss.sku_id = ms.sku_id;
|
||
|
||
-- 9) 最近N天每日总发货件数(按SKU)
|
||
SELECT TOP (@limit)
|
||
sd.sku_id,
|
||
ms.sku_name,
|
||
sd.ship_date,
|
||
sd.order_count,
|
||
sd.shipped_qty_sum
|
||
FROM #ship_daily AS sd
|
||
LEFT JOIN #matched_sku AS ms
|
||
ON ms.sku_id = sd.sku_id
|
||
ORDER BY sd.ship_date DESC, sd.shipped_qty_sum DESC;
|
||
|
||
-- 10) 库存风险预估(按SKU,补货安全线=10天)
|
||
SELECT TOP (@limit)
|
||
re.sku_id,
|
||
re.sku_name,
|
||
re.stock_qty,
|
||
re.order_lock_qty,
|
||
re.pick_lock_qty,
|
||
re.lock_qty,
|
||
re.available_qty,
|
||
re.virtual_qty,
|
||
re.defective_qty,
|
||
re.purchase_qty,
|
||
re.return_qty,
|
||
re.in_qty,
|
||
re.min_qty,
|
||
re.max_qty,
|
||
re.wms_co_id,
|
||
re.inventory_snapshot_time,
|
||
re.last_ship_date,
|
||
re.avg_daily_shipped_7d,
|
||
re.avg_daily_shipped_30d,
|
||
re.estimated_stockout_days,
|
||
re.replenishment_lead_days,
|
||
re.suggest_replenish_qty_to_10d,
|
||
re.stock_risk_level
|
||
FROM #risk_eval AS re
|
||
ORDER BY
|
||
CASE
|
||
WHEN re.stock_qty IS NULL THEN 9
|
||
WHEN re.avg_daily_shipped_30d <= 0 THEN 8
|
||
WHEN re.available_qty <= 0 THEN 1
|
||
WHEN re.estimated_stockout_days <= 10 THEN 2
|
||
WHEN re.estimated_stockout_days <= 15 THEN 3
|
||
ELSE 4
|
||
END,
|
||
re.estimated_stockout_days ASC,
|
||
re.sku_id ASC;
|
||
|
||
-- 11) 近30天在售商品ID数量(按店铺渠道)
|
||
SELECT TOP (@limit)
|
||
sb.sku_id,
|
||
ms.sku_name,
|
||
sb.shop_channel,
|
||
COUNT(DISTINCT sb.product_id) AS sold_product_id_count_30d,
|
||
COUNT(DISTINCT sb.shop_name) AS shop_count_30d,
|
||
COUNT(DISTINCT sb.order_id) AS order_count_30d,
|
||
SUM(ISNULL(sb.shipped_qty, 0)) AS shipped_qty_sum_30d
|
||
FROM #ship_base AS sb
|
||
INNER JOIN #matched_sku AS ms
|
||
ON ms.sku_id = sb.sku_id
|
||
WHERE sb.ship_dt >= @start_time
|
||
AND sb.ship_dt < @end_time
|
||
AND ISNULL(NULLIF(sb.product_id, ''), '') <> ''
|
||
GROUP BY sb.sku_id, ms.sku_name, sb.shop_channel
|
||
ORDER BY sold_product_id_count_30d DESC, shipped_qty_sum_30d DESC, sb.shop_channel ASC;
|
||
|
||
-- 12) 商品ID对应店铺/渠道/运营人(用于通知)
|
||
SELECT TOP (@limit)
|
||
pic.sku_id,
|
||
pic.sku_name,
|
||
pic.product_id,
|
||
pic.shop_channel,
|
||
pic.shop_name,
|
||
pic.operator_name,
|
||
pic.purchaser_name,
|
||
pic.factory_name,
|
||
pic.warehouse_name,
|
||
pic.order_count_30d,
|
||
pic.shipped_qty_sum_30d
|
||
FROM #product_id_contact AS pic
|
||
ORDER BY pic.shipped_qty_sum_30d DESC, pic.order_count_30d DESC, pic.product_id ASC;
|
||
|
||
-- 13) 缺货风险通知清单(带采购/工厂/仓库/店铺/运营)
|
||
SELECT TOP (@limit)
|
||
re.sku_id,
|
||
re.sku_name,
|
||
re.stock_risk_level,
|
||
re.available_qty,
|
||
re.avg_daily_shipped_30d,
|
||
re.estimated_stockout_days,
|
||
re.suggest_replenish_qty_to_10d,
|
||
pic.product_id,
|
||
pic.shop_channel,
|
||
pic.shop_name,
|
||
pic.operator_name,
|
||
pic.purchaser_name,
|
||
pic.factory_name,
|
||
pic.warehouse_name,
|
||
pic.shipped_qty_sum_30d
|
||
FROM #risk_eval AS re
|
||
LEFT JOIN #product_id_contact AS pic
|
||
ON pic.sku_id = re.sku_id
|
||
WHERE re.has_stock_risk = 1
|
||
ORDER BY
|
||
CASE
|
||
WHEN re.available_qty <= 0 THEN 1
|
||
WHEN re.estimated_stockout_days <= 10 THEN 2
|
||
WHEN re.estimated_stockout_days <= 15 THEN 3
|
||
ELSE 9
|
||
END,
|
||
re.estimated_stockout_days ASC,
|
||
pic.shipped_qty_sum_30d DESC;
|
||
"""
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(description="Company product info lookup (SQLServer)")
|
||
parser.add_argument("keyword", help="SKU/SPU/款式/产品名关键词")
|
||
parser.add_argument("--days", type=int, default=30, help="lookback window, default 30")
|
||
parser.add_argument("--limit", type=int, default=20, help="max rows per result set")
|
||
args = parser.parse_args()
|
||
|
||
kw = args.keyword.strip()
|
||
if not kw:
|
||
print("keyword is required", file=sys.stderr)
|
||
return 2
|
||
|
||
days = max(1, min(args.days, 365))
|
||
limit = max(1, min(args.limit, 100))
|
||
|
||
sql = build_sql(kw, days, limit)
|
||
cmd = ["company-db", "query", "--sql", sql]
|
||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||
if proc.returncode != 0:
|
||
err = (proc.stderr or proc.stdout or "company-db failed").strip()
|
||
print(err, file=sys.stderr)
|
||
return proc.returncode
|
||
|
||
print(proc.stdout.rstrip())
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|