-- ============================================================
-- Phase 2 Migration: 参数化函数替换 _april 后缀视图
-- 核心利润链第1批：9个对象
-- 
-- 依赖链：
--   fn_inventory_cost_classified (base)
--     ├── fn_inventory_abnormal_items
--     ├── fn_inventory_finance_category
--     └── fn_store_theoretical_actual_cost (also uses bill_fact)
--          └── fn_store_deep_diagnosis (also uses dish_store_summary, benchmark_composite)
--               └── fn_store_action_priority_deep
--                    └── fn_store_area_efficiency
--
--   fn_dish_sales (base, uses bill_fact + dish_sales_details)
--     └── fn_dish_basket
--          └── fn_dish_store_summary
--
--   fn_store_benchmark_composite (uses v_store_repeat_summary_monthly)
--
-- 向后兼容：保留原 _april 视图，新增函数可并行使用
-- ============================================================

-- 辅助函数：获取月份的起始时间戳（带时区，+08:00）
CREATE OR REPLACE FUNCTION analytics.fn_month_start_ts(p_month date)
RETURNS timestamptz AS $$
BEGIN
  RETURN (p_month::text || ' 00:00:00+08')::timestamptz;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 辅助函数：获取下月起始时间戳
CREATE OR REPLACE FUNCTION analytics.fn_next_month_start_ts(p_month date)
RETURNS timestamptz AS $$
BEGIN
  RETURN ((p_month + INTERVAL '1 month')::date::text || ' 00:00:00+08')::timestamptz;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- ============================================================
-- 1. fn_inventory_cost_classified(p_month)
--    替换 v_inventory_cost_classified_april
--    依赖: v_inventory_cost_operating
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_inventory_cost_classified(p_month date)
RETURNS TABLE (
  record_id bigint,
  report_month date,
  source_file text,
  source_row integer,
  brand text,
  region text,
  cost_unit_name text,
  cost_unit_code text,
  item_name text,
  specification text,
  major_category text,
  minor_category text,
  finance_category text,
  item_code text,
  unit text,
  opening_quantity numeric,
  opening_unit_cost numeric,
  opening_amount numeric,
  opening_tax numeric,
  opening_tax_inclusive numeric,
  purchase_quantity numeric,
  purchase_unit_cost numeric,
  purchase_amount numeric,
  purchase_tax numeric,
  purchase_tax_inclusive numeric,
  ending_quantity numeric,
  ending_unit_cost numeric,
  ending_amount numeric,
  ending_tax numeric,
  ending_tax_inclusive numeric,
  consumption_quantity numeric,
  consumption_unit_cost numeric,
  consumption_amount numeric,
  consumption_tax numeric,
  consumption_tax_inclusive numeric,
  conversion_precision_amount numeric,
  conversion_precision_tax_inclusive numeric,
  return_loss_amount numeric,
  return_loss_tax_inclusive numeric,
  imported_at timestamptz,
  calculated_consumption_amount numeric,
  is_negative_consumption boolean,
  sales_store_code text,
  sales_store_name text,
  unit_type text,
  mapping_method text,
  management_cost_scope text
) AS $$
  SELECT
    i.record_id,
    i.report_month,
    i.source_file,
    i.source_row,
    i.brand,
    i.region,
    i.cost_unit_name,
    i.cost_unit_code,
    i.item_name,
    i.specification,
    i.major_category,
    i.minor_category,
    i.finance_category,
    i.item_code,
    i.unit,
    i.opening_quantity,
    i.opening_unit_cost,
    i.opening_amount,
    i.opening_tax,
    i.opening_tax_inclusive,
    i.purchase_quantity,
    i.purchase_unit_cost,
    i.purchase_amount,
    i.purchase_tax,
    i.purchase_tax_inclusive,
    i.ending_quantity,
    i.ending_unit_cost,
    i.ending_amount,
    i.ending_tax,
    i.ending_tax_inclusive,
    i.consumption_quantity,
    i.consumption_unit_cost,
    i.consumption_amount,
    i.consumption_tax,
    i.consumption_tax_inclusive,
    i.conversion_precision_amount,
    i.conversion_precision_tax_inclusive,
    i.return_loss_amount,
    i.return_loss_tax_inclusive,
    i.imported_at,
    i.calculated_consumption_amount,
    i.is_negative_consumption,
    i.sales_store_code,
    i.sales_store_name,
    i.unit_type,
    i.mapping_method,
    CASE
      WHEN i.major_category = ANY (ARRAY['原料类', '半成品类', '烟酒饮料类', '（新）原料类']) THEN '可比食材成本'
      WHEN i.major_category = '包材类' OR i.finance_category = '食品包装' THEN '食品包装'
      WHEN i.major_category = '易耗品类' THEN '易耗及营运物料'
      WHEN i.major_category = ANY (ARRAY['维修配件类', '电子信息类']) THEN '维修及设备'
      WHEN i.major_category = '新零售' THEN '新零售货品'
      ELSE '其他成本'
    END AS management_cost_scope
  FROM analytics.v_inventory_cost_operating i
  WHERE i.report_month = p_month;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 2. fn_store_theoretical_actual_cost(p_month)
--    替换 v_store_theoretical_actual_cost_april
--    依赖: bill_fact, fn_inventory_cost_classified
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_theoretical_actual_cost(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_count bigint,
  sales_consumption numeric,
  sales_received numeric,
  theoretical_cost numeric,
  theoretical_profit numeric,
  actual_food_cost numeric,
  actual_total_cost numeric,
  packaging_cost numeric,
  operating_supply_cost numeric,
  repair_equipment_cost numeric,
  new_retail_cost numeric,
  ending_inventory_amount numeric,
  negative_item_lines bigint,
  negative_consumption_amount numeric,
  food_cost_variance numeric,
  theoretical_cost_rate_pct numeric,
  actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric,
  cost_rate_gap_pct numeric,
  comparison_status text,
  variance_level text
) AS $$
  WITH theoretical AS (
    SELECT
      b.store_code,
      min(b.store_name) AS store_name,
      count(*) AS bill_count,
      sum(b.consumption) AS sales_consumption,
      sum(b.received_total) AS sales_received,
      sum(b.theoretical_cost) AS theoretical_cost,
      sum(b.theoretical_profit) AS theoretical_profit
    FROM analytics.bill_fact b
    WHERE b.closed_at >= analytics.fn_month_start_ts(p_month)
      AND b.closed_at < analytics.fn_next_month_start_ts(p_month)
    GROUP BY b.store_code
  ), actual AS (
    SELECT
      ic.sales_store_code AS store_code,
      sum(ic.consumption_amount) AS actual_total_cost,
      sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '可比食材成本') AS actual_food_cost,
      sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '食品包装') AS packaging_cost,
      sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '易耗及营运物料') AS operating_supply_cost,
      sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '维修及设备') AS repair_equipment_cost,
      sum(ic.consumption_amount) FILTER (WHERE ic.management_cost_scope = '新零售货品') AS new_retail_cost,
      count(*) FILTER (WHERE ic.is_negative_consumption) AS negative_item_lines,
      sum(ic.consumption_amount) FILTER (WHERE ic.is_negative_consumption) AS negative_consumption_amount,
      sum(ic.ending_amount) AS ending_inventory_amount
    FROM analytics.fn_inventory_cost_classified(p_month) ic
    GROUP BY ic.sales_store_code
  )
  SELECT
    t.store_code,
    t.store_name,
    t.bill_count,
    t.sales_consumption,
    t.sales_received,
    t.theoretical_cost,
    t.theoretical_profit,
    COALESCE(a.actual_food_cost, 0) AS actual_food_cost,
    COALESCE(a.actual_total_cost, 0) AS actual_total_cost,
    COALESCE(a.packaging_cost, 0) AS packaging_cost,
    COALESCE(a.operating_supply_cost, 0) AS operating_supply_cost,
    COALESCE(a.repair_equipment_cost, 0) AS repair_equipment_cost,
    COALESCE(a.new_retail_cost, 0) AS new_retail_cost,
    COALESCE(a.ending_inventory_amount, 0) AS ending_inventory_amount,
    COALESCE(a.negative_item_lines, 0) AS negative_item_lines,
    COALESCE(a.negative_consumption_amount, 0) AS negative_consumption_amount,
    COALESCE(a.actual_food_cost, 0) - t.theoretical_cost AS food_cost_variance,
    round(t.theoretical_cost / NULLIF(t.sales_consumption, 0) * 100, 2) AS theoretical_cost_rate_pct,
    round(COALESCE(a.actual_food_cost, 0) / NULLIF(t.sales_consumption, 0) * 100, 2) AS actual_food_cost_rate_pct,
    round((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / NULLIF(t.theoretical_cost, 0) * 100, 2) AS variance_to_theoretical_pct,
    round((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / NULLIF(t.sales_consumption, 0) * 100, 2) AS cost_rate_gap_pct,
    CASE
      WHEN t.sales_consumption > 0 AND (t.theoretical_cost / t.sales_consumption) >= 0.10 THEN '可比'
      ELSE '理论成本口径异常'
    END AS comparison_status,
    CASE
      WHEN t.sales_consumption = 0 OR (t.theoretical_cost / t.sales_consumption) < 0.10 THEN '灰色-口径异常'
      WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) >= 0.20 THEN '红色-严重超耗'
      WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) >= 0.10 THEN '橙色-明显超耗'
      WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) <= -0.20 THEN '蓝色-倒挤偏低'
      WHEN ((COALESCE(a.actual_food_cost, 0) - t.theoretical_cost) / t.theoretical_cost) <= -0.10 THEN '黄色-低于理论'
      ELSE '绿色-基本正常'
    END AS variance_level
  FROM theoretical t
    JOIN actual a USING (store_code);
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 3. fn_inventory_abnormal_items(p_month)
--    替换 v_inventory_abnormal_items_april
--    依赖: fn_inventory_cost_classified
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_inventory_abnormal_items(p_month date)
RETURNS TABLE (
  record_id bigint,
  report_month date,
  source_file text,
  source_row integer,
  brand text,
  region text,
  cost_unit_name text,
  cost_unit_code text,
  item_name text,
  specification text,
  major_category text,
  minor_category text,
  finance_category text,
  item_code text,
  unit text,
  opening_quantity numeric,
  opening_unit_cost numeric,
  opening_amount numeric,
  opening_tax numeric,
  opening_tax_inclusive numeric,
  purchase_quantity numeric,
  purchase_unit_cost numeric,
  purchase_amount numeric,
  purchase_tax numeric,
  purchase_tax_inclusive numeric,
  ending_quantity numeric,
  ending_unit_cost numeric,
  ending_amount numeric,
  ending_tax numeric,
  ending_tax_inclusive numeric,
  consumption_quantity numeric,
  consumption_unit_cost numeric,
  consumption_amount numeric,
  consumption_tax numeric,
  consumption_tax_inclusive numeric,
  conversion_precision_amount numeric,
  conversion_precision_tax_inclusive numeric,
  return_loss_amount numeric,
  return_loss_tax_inclusive numeric,
  imported_at timestamptz,
  calculated_consumption_amount numeric,
  is_negative_consumption boolean,
  sales_store_code text,
  sales_store_name text,
  unit_type text,
  mapping_method text,
  management_cost_scope text,
  observed_stores bigint,
  median_unit_cost double precision,
  median_abs_quantity double precision,
  abnormal_reason text,
  unit_cost_vs_median numeric,
  quantity_vs_median numeric
) AS $$
  WITH stats AS (
    SELECT
      ic.item_code,
      count(DISTINCT ic.sales_store_code) AS observed_stores,
      percentile_cont(0.5) WITHIN GROUP (ORDER BY (ic.consumption_unit_cost::double precision))
        FILTER (WHERE ic.consumption_unit_cost > 0) AS median_unit_cost,
      percentile_cont(0.5) WITHIN GROUP (ORDER BY (abs(ic.consumption_quantity)::double precision))
        FILTER (WHERE ic.consumption_quantity <> 0) AS median_abs_quantity
    FROM analytics.fn_inventory_cost_classified(p_month) ic
    GROUP BY ic.item_code
  ), flagged AS (
    SELECT
      ic.*,
      s.observed_stores,
      s.median_unit_cost,
      s.median_abs_quantity,
      CASE
        WHEN ic.consumption_amount < 0 THEN '负倒挤成本'
        WHEN ic.consumption_quantity < 0 THEN '负倒挤数量'
        WHEN s.observed_stores >= 5 AND s.median_unit_cost > 0 AND ic.consumption_unit_cost::double precision > (s.median_unit_cost * 1.5) THEN '成本单价显著偏高'
        WHEN s.observed_stores >= 5 AND s.median_unit_cost > 0 AND ic.consumption_unit_cost > 0 AND ic.consumption_unit_cost::double precision < (s.median_unit_cost * 0.5) THEN '成本单价显著偏低'
        WHEN s.observed_stores >= 5 AND s.median_abs_quantity > 0 AND ic.consumption_amount > 1000 AND abs(ic.consumption_quantity)::double precision > (s.median_abs_quantity * 3) THEN '耗用数量显著偏高'
        ELSE NULL
      END AS abnormal_reason
    FROM analytics.fn_inventory_cost_classified(p_month) ic
      JOIN stats s USING (item_code)
  )
  SELECT
    f.record_id, f.report_month, f.source_file, f.source_row,
    f.brand, f.region, f.cost_unit_name, f.cost_unit_code,
    f.item_name, f.specification, f.major_category, f.minor_category,
    f.finance_category, f.item_code, f.unit,
    f.opening_quantity, f.opening_unit_cost, f.opening_amount,
    f.opening_tax, f.opening_tax_inclusive,
    f.purchase_quantity, f.purchase_unit_cost, f.purchase_amount,
    f.purchase_tax, f.purchase_tax_inclusive,
    f.ending_quantity, f.ending_unit_cost, f.ending_amount,
    f.ending_tax, f.ending_tax_inclusive,
    f.consumption_quantity, f.consumption_unit_cost, f.consumption_amount,
    f.consumption_tax, f.consumption_tax_inclusive,
    f.conversion_precision_amount, f.conversion_precision_tax_inclusive,
    f.return_loss_amount, f.return_loss_tax_inclusive,
    f.imported_at, f.calculated_consumption_amount, f.is_negative_consumption,
    f.sales_store_code, f.sales_store_name, f.unit_type, f.mapping_method,
    f.management_cost_scope,
    f.observed_stores, f.median_unit_cost, f.median_abs_quantity,
    f.abnormal_reason,
    round((f.consumption_unit_cost::double precision / NULLIF(f.median_unit_cost, 0))::numeric, 2) AS unit_cost_vs_median,
    round((abs(f.consumption_quantity)::double precision / NULLIF(f.median_abs_quantity, 0))::numeric, 2) AS quantity_vs_median
  FROM flagged f
  WHERE f.abnormal_reason IS NOT NULL;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 4. fn_inventory_finance_category(p_month)
--    替换 v_inventory_finance_category_april
--    依赖: fn_inventory_cost_classified
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_inventory_finance_category(p_month date)
RETURNS TABLE (
  management_cost_scope text,
  finance_category text,
  detail_lines bigint,
  store_count bigint,
  item_count bigint,
  consumption_quantity numeric,
  consumption_amount numeric,
  all_cost_share_pct numeric,
  negative_lines bigint,
  negative_amount numeric,
  ending_inventory_amount numeric
) AS $$
  SELECT
    ic.management_cost_scope,
    ic.finance_category,
    count(*) AS detail_lines,
    count(DISTINCT ic.sales_store_code) AS store_count,
    count(DISTINCT ic.item_code) AS item_count,
    sum(ic.consumption_quantity) AS consumption_quantity,
    sum(ic.consumption_amount) AS consumption_amount,
    round(sum(ic.consumption_amount) / NULLIF(sum(sum(ic.consumption_amount)) OVER (), 0) * 100, 2) AS all_cost_share_pct,
    count(*) FILTER (WHERE ic.is_negative_consumption) AS negative_lines,
    sum(ic.consumption_amount) FILTER (WHERE ic.is_negative_consumption) AS negative_amount,
    sum(ic.ending_amount) AS ending_inventory_amount
  FROM analytics.fn_inventory_cost_classified(p_month) ic
  GROUP BY ic.management_cost_scope, ic.finance_category;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 5. fn_dish_sales(p_month)
--    替换 dish_sales_april
--    依赖: dish_sales_details (public), bill_fact
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_dish_sales(p_month date)
RETURNS TABLE (
  detail_id bigint,
  source_file text,
  source_row integer,
  store_code text,
  store_name text,
  bill_no text,
  business_date date,
  closed_at timestamptz,
  ordered_at timestamp without time zone,
  meal_period text,
  business_type text,
  dish_name text,
  category_level1 text,
  category_level2 text,
  item_type text,
  production_department text,
  unit text,
  preparation_method text,
  sales_quantity numeric,
  unit_price numeric,
  gross_amount numeric,
  received_amount numeric,
  dish_discount_amount numeric,
  bill_guest_count integer,
  bill_consumption numeric,
  bill_received_total numeric,
  bill_discount_total numeric,
  member_id text,
  member_level text,
  marketing_plan text,
  order_tag text,
  bill_theoretical_cost numeric,
  bill_theoretical_profit numeric,
  bill_theoretical_margin numeric,
  meituan_delivery_received numeric,
  taobao_delivery_received numeric,
  jd_delivery_received numeric
) AS $$
  SELECT
    d.detail_id,
    d.source_file,
    d.source_row,
    d.store_code,
    d.store_name,
    d.bill_no,
    b.closed_at::date AS business_date,
    b.closed_at,
    d.ordered_at,
    COALESCE(NULLIF(b.meal_period, ''), d.business_type) AS meal_period,
    d.business_type,
    d.dish_name,
    d.category_level1,
    d.category_level2,
    d.item_type,
    d.production_department,
    d.unit,
    d.preparation_method,
    d.sales_quantity,
    d.unit_price,
    d.gross_amount,
    d.received_amount,
    d.gross_amount - d.received_amount AS dish_discount_amount,
    b.guest_count AS bill_guest_count,
    b.consumption AS bill_consumption,
    b.received_total AS bill_received_total,
    b.discount_total AS bill_discount_total,
    b.member_id,
    b.member_level,
    b.marketing_plan,
    b.order_tag,
    b.theoretical_cost AS bill_theoretical_cost,
    b.theoretical_profit AS bill_theoretical_profit,
    b.theoretical_margin AS bill_theoretical_margin,
    b.meituan_delivery_received,
    b.taobao_delivery_received,
    b.jd_delivery_received
  FROM public.dish_sales_details d
    JOIN (
      SELECT store_code, bill_no, closed_at, meal_period, guest_count, consumption,
             received_total, discount_total, member_id, member_level, marketing_plan,
             order_tag, theoretical_cost, theoretical_profit, theoretical_margin,
             meituan_delivery_received, taobao_delivery_received, jd_delivery_received
      FROM analytics.bill_fact
      WHERE closed_at >= analytics.fn_month_start_ts(p_month)
        AND closed_at < analytics.fn_next_month_start_ts(p_month)
    ) b ON b.store_code = d.store_code AND b.bill_no = d.bill_no
  WHERE d.dish_name IS NOT NULL;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 6. fn_dish_basket(p_month)
--    替换 dish_basket_april
--    依赖: fn_dish_sales
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_dish_basket(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_no text,
  business_date date,
  closed_at timestamptz,
  meal_period text,
  member_id text,
  member_level text,
  guest_count integer,
  bill_consumption numeric,
  bill_received_total numeric,
  bill_discount_total numeric,
  sku_count bigint,
  item_quantity numeric,
  dish_gross_amount numeric,
  dish_received_amount numeric,
  has_noodle boolean,
  has_snack boolean,
  has_drink boolean,
  has_cold_dish boolean,
  has_combo boolean,
  is_delivery boolean
) AS $$
  SELECT
    ds.store_code,
    min(ds.store_name) AS store_name,
    ds.bill_no,
    min(ds.business_date) AS business_date,
    min(ds.closed_at) AS closed_at,
    min(ds.meal_period) AS meal_period,
    min(ds.member_id) AS member_id,
    min(ds.member_level) AS member_level,
    max(ds.bill_guest_count) AS guest_count,
    max(ds.bill_consumption) AS bill_consumption,
    max(ds.bill_received_total) AS bill_received_total,
    max(ds.bill_discount_total) AS bill_discount_total,
    count(DISTINCT ds.dish_name) AS sku_count,
    sum(ds.sales_quantity) AS item_quantity,
    sum(ds.gross_amount) AS dish_gross_amount,
    sum(ds.received_amount) AS dish_received_amount,
    bool_or(ds.category_level1 = '兰州牛肉面') AS has_noodle,
    bool_or(ds.category_level1 = '特色小吃') AS has_snack,
    bool_or(ds.category_level1 = ANY (ARRAY['酒水饮料', '丝路茶饮'])) AS has_drink,
    bool_or(ds.category_level1 = '爽口凉菜') AS has_cold_dish,
    bool_or(ds.item_type ~~ '%套餐%' OR (ds.category_level1 = ANY (ARRAY['堂食套餐', '外卖套餐']))) AS has_combo,
    bool_or(ds.business_type ~~ '%外卖%' OR COALESCE(ds.order_tag, '') ~~ '%外卖%' OR ds.meituan_delivery_received <> 0 OR ds.taobao_delivery_received <> 0 OR ds.jd_delivery_received <> 0) AS is_delivery
  FROM analytics.fn_dish_sales(p_month) ds
  GROUP BY ds.store_code, ds.bill_no;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 7. fn_dish_store_summary(p_month)
--    替换 dish_store_summary_april
--    依赖: fn_dish_basket
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_dish_store_summary(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_count bigint,
  active_days bigint,
  received_amount numeric,
  avg_bill_value numeric,
  items_per_bill numeric,
  skus_per_bill numeric,
  avg_guest_value numeric,
  bill_discount_rate_pct numeric,
  member_bill_share_pct numeric,
  delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric,
  noodle_drink_attach_pct numeric,
  noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric
) AS $$
  SELECT
    db.store_code,
    min(db.store_name) AS store_name,
    count(*) AS bill_count,
    count(DISTINCT db.business_date) AS active_days,
    sum(db.bill_received_total) AS received_amount,
    round(avg(db.bill_received_total), 2) AS avg_bill_value,
    round(sum(db.item_quantity) / NULLIF(count(*), 0), 2) AS items_per_bill,
    round(avg(db.sku_count), 2) AS skus_per_bill,
    round(sum(db.bill_received_total) / NULLIF(sum(db.guest_count), 0), 2) AS avg_guest_value,
    round(sum(db.bill_discount_total) / NULLIF(sum(db.bill_consumption), 0) * 100, 2) AS bill_discount_rate_pct,
    round(count(*) FILTER (WHERE db.member_id IS NOT NULL)::numeric / NULLIF(count(*), 0) * 100, 2) AS member_bill_share_pct,
    round(count(*) FILTER (WHERE db.is_delivery)::numeric / NULLIF(count(*), 0) * 100, 2) AS delivery_bill_share_pct,
    round(count(*) FILTER (WHERE db.has_noodle AND db.has_snack)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_snack_attach_pct,
    round(count(*) FILTER (WHERE db.has_noodle AND db.has_drink)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_drink_attach_pct,
    round(count(*) FILTER (WHERE db.has_noodle AND db.has_cold_dish)::numeric / NULLIF(count(*) FILTER (WHERE db.has_noodle), 0) * 100, 2) AS noodle_cold_attach_pct,
    round(count(*) FILTER (WHERE db.has_combo)::numeric / NULLIF(count(*), 0) * 100, 2) AS combo_bill_share_pct
  FROM analytics.fn_dish_basket(p_month) db
  GROUP BY db.store_code;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 8. fn_store_benchmark_composite(p_month)
--    替换 v_store_benchmark_composite（硬编码 '2026-04-01'）
--    依赖: v_store_benchmark, v_store_repeat_summary_monthly, fn_store_platform_economics
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_benchmark_composite(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  received numeric,
  avg_daily_received numeric,
  avg_bill_value numeric,
  discount_rate_pct numeric,
  theoretical_margin_pct numeric,
  anomaly_rate_pct numeric,
  member_bill_share_pct numeric,
  identified_members bigint,
  repeat_rate_pct numeric,
  avg_orders numeric,
  repeat_revenue_share_pct numeric,
  meituan_cost_rate_pct numeric,
  taobao_cost_rate_pct numeric,
  jd_cost_rate_pct numeric,
  revenue_score double precision,
  margin_score double precision,
  discount_score double precision,
  anomaly_score double precision,
  repeat_score double precision,
  benchmark_score numeric
) AS $$
  WITH eligible AS (
    SELECT
      b.store_code,
      b.store_name,
      b.received,
      b.avg_daily_received,
      b.avg_bill_value,
      b.discount_rate_pct,
      b.theoretical_margin_pct,
      b.anomaly_rate_pct,
      b.member_bill_share_pct,
      r.identified_members,
      r.repeat_rate_pct,
      r.avg_orders,
      r.repeat_revenue_share_pct,
      p.meituan_cost_rate_pct,
      p.taobao_cost_rate_pct,
      p.jd_cost_rate_pct
    FROM analytics.v_store_benchmark b
      JOIN analytics.v_store_repeat_summary_monthly r ON r.store_code = b.store_code AND r.month_start = p_month
      LEFT JOIN analytics.fn_store_platform_economics(p_month) p ON p.store_code = b.store_code
    WHERE b.theoretical_margin_pct >= 60 AND b.theoretical_margin_pct <= 80 AND r.identified_members >= 500
  ), scored AS (
    SELECT
      e.*,
      percent_rank() OVER (ORDER BY e.avg_daily_received) AS revenue_score,
      percent_rank() OVER (ORDER BY e.theoretical_margin_pct) AS margin_score,
      1 - percent_rank() OVER (ORDER BY e.discount_rate_pct) AS discount_score,
      1 - percent_rank() OVER (ORDER BY e.anomaly_rate_pct) AS anomaly_score,
      percent_rank() OVER (ORDER BY e.repeat_rate_pct) AS repeat_score
    FROM eligible e
  )
  SELECT
    s.store_code,
    s.store_name,
    s.received,
    s.avg_daily_received,
    s.avg_bill_value,
    s.discount_rate_pct,
    s.theoretical_margin_pct,
    s.anomaly_rate_pct,
    s.member_bill_share_pct,
    s.identified_members,
    s.repeat_rate_pct,
    s.avg_orders,
    s.repeat_revenue_share_pct,
    s.meituan_cost_rate_pct,
    s.taobao_cost_rate_pct,
    s.jd_cost_rate_pct,
    s.revenue_score,
    s.margin_score,
    s.discount_score,
    s.anomaly_score,
    s.repeat_score,
    round(((s.revenue_score * 0.25 + s.margin_score * 0.25 + s.discount_score * 0.20 + s.anomaly_score * 0.15 + s.repeat_score * 0.15) * 100)::numeric, 2) AS benchmark_score
  FROM scored s;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 9. fn_store_deep_diagnosis(p_month)
--    替换 v_store_deep_diagnosis_april
--    依赖: v_store_scorecard, fn_dish_store_summary, fn_store_theoretical_actual_cost,
--          fn_store_benchmark_composite, fn_store_platform_economics
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_deep_diagnosis(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_count bigint,
  active_days bigint,
  received numeric,
  avg_daily_received numeric,
  avg_bill_value numeric,
  discount_rate_pct numeric,
  theoretical_margin_pct numeric,
  member_bill_share_pct numeric,
  items_per_bill numeric,
  skus_per_bill numeric,
  delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric,
  noodle_drink_attach_pct numeric,
  noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric,
  theoretical_cost numeric,
  actual_food_cost numeric,
  food_cost_variance numeric,
  theoretical_cost_rate_pct numeric,
  actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric,
  comparison_status text,
  variance_level text,
  identified_members bigint,
  repeat_rate_pct numeric,
  repeat_revenue_share_pct numeric,
  benchmark_score numeric,
  meituan_received numeric,
  taobao_received numeric,
  jd_received numeric,
  combined_platform_cost_rate_pct numeric,
  business_type text,
  scale_tier text,
  problem_count integer,
  problem_combination text
) AS $$
  WITH store_base AS (
    SELECT
      s.store_code,
      s.store_name,
      s.bill_count,
      s.active_days,
      s.received,
      s.avg_daily_received,
      s.avg_bill_value,
      s.discount_rate_pct,
      s.theoretical_margin_pct,
      s.member_bill_share_pct,
      d.items_per_bill,
      d.skus_per_bill,
      d.delivery_bill_share_pct,
      d.noodle_snack_attach_pct,
      d.noodle_drink_attach_pct,
      d.noodle_cold_attach_pct,
      d.combo_bill_share_pct,
      c.theoretical_cost,
      c.actual_food_cost,
      c.food_cost_variance,
      c.theoretical_cost_rate_pct,
      c.actual_food_cost_rate_pct,
      c.variance_to_theoretical_pct,
      c.comparison_status,
      c.variance_level,
      b.identified_members,
      b.repeat_rate_pct,
      b.repeat_revenue_share_pct,
      b.benchmark_score,
      p.meituan_received,
      p.taobao_received,
      p.jd_received,
      round((COALESCE(p.meituan_discount, 0) + COALESCE(p.taobao_discount, 0) + COALESCE(p.jd_discount, 0) +
             COALESCE(p.meituan_commission, 0) + COALESCE(p.taobao_commission, 0) + COALESCE(p.jd_commission, 0)) /
            NULLIF(COALESCE(p.meituan_received, 0) + COALESCE(p.taobao_received, 0) + COALESCE(p.jd_received, 0) +
             COALESCE(p.meituan_discount, 0) + COALESCE(p.taobao_discount, 0) + COALESCE(p.jd_discount, 0) +
             COALESCE(p.meituan_commission, 0) + COALESCE(p.taobao_commission, 0) + COALESCE(p.jd_commission, 0), 0) * 100, 2) AS combined_platform_cost_rate_pct,
      CASE
        WHEN s.store_name ~ '机场|火锅|商城|快手|哈马尔罕' THEN '特殊业态'
        ELSE '标准门店'
      END AS business_type
    FROM analytics.v_store_scorecard s
      LEFT JOIN analytics.fn_dish_store_summary(p_month) d USING (store_code)
      LEFT JOIN analytics.fn_store_theoretical_actual_cost(p_month) c USING (store_code)
      LEFT JOIN analytics.fn_store_benchmark_composite(p_month) b USING (store_code)
      LEFT JOIN analytics.fn_store_platform_economics(p_month) p USING (store_code)
  ), tiered AS (
    SELECT
      sb.*,
      CASE
        WHEN sb.business_type = '特殊业态' THEN '特殊业态'
        WHEN percent_rank() OVER (PARTITION BY sb.business_type ORDER BY sb.received) >= 0.67 THEN '高规模'
        WHEN percent_rank() OVER (PARTITION BY sb.business_type ORDER BY sb.received) >= 0.33 THEN '中规模'
        ELSE '低规模'
      END AS scale_tier
    FROM store_base sb
  )
  SELECT
    t.store_code,
    t.store_name,
    t.bill_count,
    t.active_days,
    t.received,
    t.avg_daily_received,
    t.avg_bill_value,
    t.discount_rate_pct,
    t.theoretical_margin_pct,
    t.member_bill_share_pct,
    t.items_per_bill,
    t.skus_per_bill,
    t.delivery_bill_share_pct,
    t.noodle_snack_attach_pct,
    t.noodle_drink_attach_pct,
    t.noodle_cold_attach_pct,
    t.combo_bill_share_pct,
    t.theoretical_cost,
    t.actual_food_cost,
    t.food_cost_variance,
    t.theoretical_cost_rate_pct,
    t.actual_food_cost_rate_pct,
    t.variance_to_theoretical_pct,
    t.comparison_status,
    t.variance_level,
    t.identified_members,
    t.repeat_rate_pct,
    t.repeat_revenue_share_pct,
    t.benchmark_score,
    t.meituan_received,
    t.taobao_received,
    t.jd_received,
    t.combined_platform_cost_rate_pct,
    t.business_type,
    t.scale_tier,
    (CASE WHEN t.comparison_status = '可比' AND t.variance_to_theoretical_pct >= 20 THEN 1 ELSE 0 END +
     CASE WHEN t.discount_rate_pct >= 23.02 THEN 1 ELSE 0 END +
     CASE WHEN t.theoretical_margin_pct < 70 THEN 1 ELSE 0 END +
     CASE WHEN t.identified_members >= 500 AND t.repeat_rate_pct < 30 THEN 1 ELSE 0 END +
     CASE WHEN t.combined_platform_cost_rate_pct >= 40 THEN 1 ELSE 0 END +
     CASE WHEN t.noodle_drink_attach_pct < 12 THEN 1 ELSE 0 END) AS problem_count,
    concat_ws('＋',
      CASE WHEN t.comparison_status = '理论成本口径异常' THEN '成本口径异常' ELSE NULL END,
      CASE WHEN t.comparison_status = '可比' AND t.variance_to_theoretical_pct >= 20 THEN '实际成本严重超耗' ELSE NULL END,
      CASE WHEN t.discount_rate_pct >= 23.02 THEN '优惠偏高' ELSE NULL END,
      CASE WHEN t.theoretical_margin_pct < 70 THEN '理论毛利偏低' ELSE NULL END,
      CASE WHEN t.identified_members >= 500 AND t.repeat_rate_pct < 30 THEN '会员复购偏低' ELSE NULL END,
      CASE WHEN t.combined_platform_cost_rate_pct >= 40 THEN '平台成本偏高' ELSE NULL END,
      CASE WHEN t.noodle_drink_attach_pct < 12 THEN '饮品搭售偏低' ELSE NULL END
    ) AS problem_combination
  FROM tiered t;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 10. fn_store_action_priority_deep(p_month)
--     替换 v_store_action_priority_deep_april
--     依赖: fn_store_deep_diagnosis
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_action_priority_deep(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_count bigint,
  active_days bigint,
  received numeric,
  avg_daily_received numeric,
  avg_bill_value numeric,
  discount_rate_pct numeric,
  theoretical_margin_pct numeric,
  member_bill_share_pct numeric,
  items_per_bill numeric,
  skus_per_bill numeric,
  delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric,
  noodle_drink_attach_pct numeric,
  noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric,
  theoretical_cost numeric,
  actual_food_cost numeric,
  food_cost_variance numeric,
  theoretical_cost_rate_pct numeric,
  actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric,
  comparison_status text,
  variance_level text,
  identified_members bigint,
  repeat_rate_pct numeric,
  repeat_revenue_share_pct numeric,
  benchmark_score numeric,
  meituan_received numeric,
  taobao_received numeric,
  jd_received numeric,
  combined_platform_cost_rate_pct numeric,
  business_type text,
  scale_tier text,
  problem_count integer,
  problem_combination text,
  action_priority text
) AS $$
  SELECT
    d.store_code,
    d.store_name,
    d.bill_count,
    d.active_days,
    d.received,
    d.avg_daily_received,
    d.avg_bill_value,
    d.discount_rate_pct,
    d.theoretical_margin_pct,
    d.member_bill_share_pct,
    d.items_per_bill,
    d.skus_per_bill,
    d.delivery_bill_share_pct,
    d.noodle_snack_attach_pct,
    d.noodle_drink_attach_pct,
    d.noodle_cold_attach_pct,
    d.combo_bill_share_pct,
    d.theoretical_cost,
    d.actual_food_cost,
    d.food_cost_variance,
    d.theoretical_cost_rate_pct,
    d.actual_food_cost_rate_pct,
    d.variance_to_theoretical_pct,
    d.comparison_status,
    d.variance_level,
    d.identified_members,
    d.repeat_rate_pct,
    d.repeat_revenue_share_pct,
    d.benchmark_score,
    d.meituan_received,
    d.taobao_received,
    d.jd_received,
    d.combined_platform_cost_rate_pct,
    d.business_type,
    d.scale_tier,
    d.problem_count,
    d.problem_combination,
    CASE
      WHEN d.business_type = '特殊业态' THEN '特殊业态'
      WHEN d.problem_count >= 4 OR (d.comparison_status = '理论成本口径异常' AND d.problem_count >= 2) THEN 'P0-综合专项整改'
      WHEN d.comparison_status = '理论成本口径异常' THEN 'P0-修复数据口径'
      WHEN d.problem_count >= 2 THEN 'P1-重点整改'
      WHEN d.problem_count = 1 THEN 'P2-单项改善'
      WHEN d.benchmark_score >= 70 THEN '标杆候选'
      ELSE '持续跟踪'
    END AS action_priority
  FROM analytics.fn_store_deep_diagnosis(p_month) d;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 11. fn_store_area_efficiency(p_month)
--     替换 v_store_area_efficiency_april
--     依赖: fn_store_action_priority_deep, v_store_location_operating
-- ============================================================
CREATE OR REPLACE FUNCTION analytics.fn_store_area_efficiency(p_month date)
RETURNS TABLE (
  store_code text,
  store_name text,
  bill_count bigint,
  active_days bigint,
  received numeric,
  avg_daily_received numeric,
  avg_bill_value numeric,
  discount_rate_pct numeric,
  theoretical_margin_pct numeric,
  member_bill_share_pct numeric,
  items_per_bill numeric,
  skus_per_bill numeric,
  delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric,
  noodle_drink_attach_pct numeric,
  noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric,
  theoretical_cost numeric,
  actual_food_cost numeric,
  food_cost_variance numeric,
  theoretical_cost_rate_pct numeric,
  actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric,
  comparison_status text,
  variance_level text,
  identified_members bigint,
  repeat_rate_pct numeric,
  repeat_revenue_share_pct numeric,
  benchmark_score numeric,
  meituan_received numeric,
  taobao_received numeric,
  jd_received numeric,
  combined_platform_cost_rate_pct numeric,
  business_type text,
  scale_tier text,
  problem_count integer,
  problem_combination text,
  action_priority text,
  area_sqm numeric,
  business_address text,
  city text,
  district text,
  latitude_gcj02 double precision,
  longitude_gcj02 double precision,
  open_date date,
  lease_expiry_date date,
  store_age_years numeric,
  monthly_received_per_sqm numeric,
  daily_received_per_sqm numeric,
  estimated_inventory_days numeric
) AS $$
  SELECT
    d.store_code,
    d.store_name,
    d.bill_count,
    d.active_days,
    d.received,
    d.avg_daily_received,
    d.avg_bill_value,
    d.discount_rate_pct,
    d.theoretical_margin_pct,
    d.member_bill_share_pct,
    d.items_per_bill,
    d.skus_per_bill,
    d.delivery_bill_share_pct,
    d.noodle_snack_attach_pct,
    d.noodle_drink_attach_pct,
    d.noodle_cold_attach_pct,
    d.combo_bill_share_pct,
    d.theoretical_cost,
    d.actual_food_cost,
    d.food_cost_variance,
    d.theoretical_cost_rate_pct,
    d.actual_food_cost_rate_pct,
    d.variance_to_theoretical_pct,
    d.comparison_status,
    d.variance_level,
    d.identified_members,
    d.repeat_rate_pct,
    d.repeat_revenue_share_pct,
    d.benchmark_score,
    d.meituan_received,
    d.taobao_received,
    d.jd_received,
    d.combined_platform_cost_rate_pct,
    d.business_type,
    d.scale_tier,
    d.problem_count,
    d.problem_combination,
    d.action_priority,
    loc.area_sqm,
    loc.business_address,
    loc.city,
    loc.district,
    loc.latitude_gcj02,
    loc.longitude_gcj02,
    loc.opened_date AS open_date,
    loc.lease_expiry_date,
    round(EXTRACT(year FROM age(p_month::timestamp with time zone, loc.opened_date::timestamp with time zone)) +
          EXTRACT(day FROM age(p_month::timestamp with time zone, loc.opened_date::timestamp with time zone)) / 365.25, 2) AS store_age_years,
    round(d.received / NULLIF(loc.area_sqm, 0), 2) AS monthly_received_per_sqm,
    round(d.avg_daily_received / NULLIF(loc.area_sqm, 0), 2) AS daily_received_per_sqm,
    NULL::numeric AS estimated_inventory_days
  FROM analytics.fn_store_action_priority_deep(p_month) d
    LEFT JOIN analytics.v_store_location_operating loc ON loc.sales_store_code = d.store_code;
$$ LANGUAGE sql STABLE;

-- ============================================================
-- 验证：对比函数结果与原视图（以2026-04为例）
-- ============================================================
-- SELECT count(*) FROM analytics.fn_inventory_cost_classified('2026-04-01');
-- SELECT count(*) FROM analytics.v_inventory_cost_classified_april;
-- SELECT count(*) FROM analytics.fn_store_theoretical_actual_cost('2026-04-01');
-- SELECT count(*) FROM analytics.v_store_theoretical_actual_cost_april;
-- SELECT count(*) FROM analytics.fn_store_action_priority_deep('2026-04-01');
-- SELECT count(*) FROM analytics.v_store_action_priority_deep_april;
