feat: 配送物化视图优化查询性能 + 饼图统一处理 + SKU长尾治理明细 + 地图初始聚焦北京

This commit is contained in:
freedakgmail
2026-08-02 08:53:21 +08:00
parent ed7beb7e47
commit 73f12f40d1
20 changed files with 288 additions and 103 deletions
+69 -26
View File
@@ -1597,6 +1597,13 @@ router.get('/central-kitchen/dashboard', async (req: AuthRequest, res) => {
router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
try {
const month = parseMonth(req)
// 检查配送物化视图是否包含当前月份数据
const distMvCheck = await query(`SELECT 1 FROM mv_distribution_monthly WHERE report_month = $1::date LIMIT 1`, [month])
if (distMvCheck.rows.length === 0) {
await query(`REFRESH MATERIALIZED VIEW CONCURRENTLY mv_distribution_monthly`)
}
const [summary, storeReconciliation, topVariances, unmatchedItems, categoryReconciliation] = await Promise.all([
// 汇总
query(`
@@ -1605,9 +1612,8 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
round(sum(d.total_quantity)::numeric,2) AS dist_qty,
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt,
round(sum(d.cost_excl_tax_amount)::numeric,2) AS dist_cost_excl_tax
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY d.store_code, d.item_code
),
inv AS (
@@ -1658,9 +1664,8 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
round(sum(d.total_quantity)::numeric,2) AS dist_qty,
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt,
round(sum(d.cost_excl_tax_amount)::numeric,2) AS dist_cost_excl_tax
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY d.store_code
),
inv AS (
@@ -1674,8 +1679,8 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
GROUP BY f.store_code
),
store_names AS (
SELECT DISTINCT store_code, store_name FROM distribution_detail_records
WHERE business_date >= $1::date AND business_date < ($1::date + INTERVAL '1 month')
SELECT DISTINCT store_code, store_name FROM mv_distribution_monthly
WHERE report_month = $1::date
)
SELECT COALESCE(d.store_code, i.store_code) AS store_code,
sn.store_name,
@@ -1699,9 +1704,8 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
SELECT d.store_code, d.item_code, d.item_name, d.unit, d.major_category, d.minor_category,
round(sum(d.total_quantity)::numeric,2) AS dist_qty,
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY d.store_code, d.item_code, d.item_name, d.unit, d.major_category, d.minor_category
),
inv AS (
@@ -1733,9 +1737,8 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
round(sum(d.total_quantity)::numeric,2) AS dist_qty,
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt,
count(DISTINCT d.store_code) AS store_count
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY d.item_code, d.item_name, d.unit, d.major_category, d.minor_category
),
inv_items AS (
@@ -1757,16 +1760,14 @@ router.get('/distribution/reconciliation', async (req: AuthRequest, res) => {
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt,
round(sum(d.cost_excl_tax_amount)::numeric,2) AS dist_cost_excl_tax,
count(DISTINCT d.item_code) AS item_count
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY COALESCE(NULLIF(d.minor_category,''),'未分类')
),
item_cat AS (
SELECT DISTINCT item_code, COALESCE(NULLIF(minor_category,''),'未分类') AS minor_category
FROM distribution_detail_records
WHERE business_date >= $1::date AND business_date < ($1::date + INTERVAL '1 month')
AND item_code IS NOT NULL
FROM mv_distribution_monthly
WHERE report_month = $1::date
),
inv AS (
SELECT ic.minor_category,
@@ -2080,6 +2081,14 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
await client.query(`REFRESH MATERIALIZED VIEW CONCURRENTLY mv_dish_sales_monthly`)
}
// 检查配送物化视图是否包含当前月份数据
const distMvCheck = await client.query(`
SELECT 1 FROM mv_distribution_monthly WHERE report_month = $1::date LIMIT 1
`, [month])
if (distMvCheck.rows.length === 0) {
await client.query(`REFRESH MATERIALIZED VIEW CONCURRENTLY mv_distribution_monthly`)
}
// 从物化视图创建临时表(索引扫描,毫秒级)
await client.query(`
CREATE TEMP TABLE IF NOT EXISTS tmp_sales_agg AS
@@ -2159,9 +2168,8 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
LEFT JOIN analytics.dim_material m ON b.material_code = m.material_code
LEFT JOIN (
SELECT d.item_name, round(sum(d.outbound_total_amount)::numeric / NULLIF(sum(d.total_quantity), 0), 4) AS unit_price
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_name IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
GROUP BY d.item_name
) ic ON m.material_name = ic.item_name
GROUP BY s.store_code, b.material_code, m.material_name, b.unit
@@ -2279,12 +2287,33 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
SELECT d.item_code, d.item_name,
round(sum(d.total_quantity)::numeric,4) AS dist_qty,
round(sum(d.outbound_total_amount)::numeric,2) AS dist_amt
FROM distribution_detail_records d
WHERE d.business_date >= $1::date AND d.business_date < ($1::date + INTERVAL '1 month')
AND NOT d.is_return AND d.item_code IS NOT NULL
FROM mv_distribution_monthly d
WHERE d.report_month = $1::date
AND d.distribution_center_code = '2'
GROUP BY d.item_code, d.item_name
),
ck_inventory AS (
SELECT f.material_code,
round(sum(f.consumption_quantity)::numeric,4) AS consumption_qty,
round(sum(f.ending_quantity)::numeric,4) AS ending_qty
FROM analytics.fact_inventory_snapshot f
WHERE f.snapshot_date >= $1::date AND f.snapshot_date < ($1::date + INTERVAL '1 month')
AND f.store_code = '2'
GROUP BY f.material_code
),
other_wh_inventory AS (
SELECT f.material_code,
round(sum(f.consumption_quantity)::numeric,4) AS consumption_qty,
round(sum(f.ending_quantity)::numeric,4) AS ending_qty
FROM analytics.fact_inventory_snapshot f
WHERE f.snapshot_date >= $1::date AND f.snapshot_date < ($1::date + INTERVAL '1 month')
AND f.store_code IN (
SELECT cost_unit_code FROM inventory_store_mapping
WHERE sales_store_code IS NULL OR sales_store_code = ''
)
AND f.store_code != '2'
GROUP BY f.material_code
),
store_consumption AS (
SELECT f.material_code,
round(sum(f.consumption_quantity)::numeric,4) AS consumption_qty,
@@ -2293,6 +2322,10 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
round(sum(f.ending_amount)::numeric,2) AS ending_amt
FROM analytics.fact_inventory_snapshot f
WHERE f.snapshot_date >= $1::date AND f.snapshot_date < ($1::date + INTERVAL '1 month')
AND f.store_code NOT IN (
SELECT cost_unit_code FROM inventory_store_mapping
WHERE sales_store_code IS NULL OR sales_store_code = ''
)
GROUP BY f.material_code
)
SELECT COALESCE(p.product_code, dist.item_code) AS product_code,
@@ -2302,6 +2335,10 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
COALESCE(p.theoretical_cost, 0) AS theoretical_cost,
COALESCE(dist.dist_qty, 0) AS dist_qty,
COALESCE(dist.dist_amt, 0) AS dist_amt,
COALESCE(ck.consumption_qty, 0) AS ck_consumption_qty,
COALESCE(ck.ending_qty, 0) AS ck_ending_qty,
COALESCE(owh.consumption_qty, 0) AS other_wh_consumption_qty,
COALESCE(owh.ending_qty, 0) AS other_wh_ending_qty,
COALESCE(sc.consumption_qty, 0) AS consumption_qty,
COALESCE(sc.consumption_amt, 0) AS consumption_amt,
COALESCE(sc.ending_qty, 0) AS ending_qty,
@@ -2319,6 +2356,8 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
ELSE NULL END AS inventory_accumulation_rate
FROM ck_production p
FULL OUTER JOIN ck_distribution dist ON p.product_code = dist.item_code
FULL OUTER JOIN ck_inventory ck ON COALESCE(p.product_code, dist.item_code) = ck.material_code
FULL OUTER JOIN other_wh_inventory owh ON COALESCE(p.product_code, dist.item_code) = owh.material_code
FULL OUTER JOIN store_consumption sc ON COALESCE(p.product_code, dist.item_code) = sc.material_code
ORDER BY COALESCE(p.actual_cost, 0) DESC
LIMIT 50
@@ -2384,6 +2423,10 @@ router.get('/sales-driven/production-plan', async (req: AuthRequest, res) => {
theoretical_cost: parseNum(row.theoretical_cost),
dist_qty: parseNum(row.dist_qty),
dist_amt: parseNum(row.dist_amt),
ck_consumption_qty: parseNum(row.ck_consumption_qty),
ck_ending_qty: parseNum(row.ck_ending_qty),
other_wh_consumption_qty: parseNum(row.other_wh_consumption_qty),
other_wh_ending_qty: parseNum(row.other_wh_ending_qty),
consumption_qty: parseNum(row.consumption_qty),
consumption_amt: parseNum(row.consumption_amt),
ending_qty: parseNum(row.ending_qty),