-- ============================================================
-- 03_create_views.sql
-- 周度检查视图 + 月度验收视图 + 闭环健康度视图 + 数据质量视图
-- ============================================================

-- 周度检查视图
CREATE OR REPLACE VIEW analytics.v_task_weekly_check AS
SELECT
  w.id, w.task_id, w.store_code, w.store_name, w.problem_indicator,
  w.iso_week,
  round(w.this_week_value, 2) AS this_week_value,
  round(w.last_week_value, 2) AS last_week_value,
  w.change_direction,
  w.consecutive_no_improve_weeks,
  w.check_comment,
  w.checked_by,
  w.checked_at,
  t.plan_month,
  t.priority,
  t.status AS task_status,
  t.target_value,
  CASE
    WHEN w.consecutive_no_improve_weeks >= 2 THEN '需重新判断原因'
    WHEN w.change_direction = 'up' AND t.problem_indicator IN ('优惠率','异常率','实际成本率','平台加权成本率') THEN '改善中'
    WHEN w.change_direction = 'down' AND t.problem_indicator IN ('优惠率','异常率','实际成本率','平台加权成本率') THEN '需关注'
    WHEN w.change_direction = 'up' AND t.problem_indicator IN ('理论毛利率','会员复购率','饮品搭售率','经营稳定性') THEN '改善中'
    WHEN w.change_direction = 'down' AND t.problem_indicator IN ('理论毛利率','会员复购率','饮品搭售率','经营稳定性') THEN '需关注'
    ELSE '观察中'
  END AS improvement_status
FROM analytics.task_weekly_check w
JOIN analytics.store_task t ON w.task_id = t.task_id;

-- 月度验收视图
CREATE OR REPLACE VIEW analytics.v_task_monthly_review AS
SELECT
  r.id, r.task_id, r.store_code, r.store_name, r.plan_month,
  r.problem_indicator,
  round(r.baseline_value, 2) AS baseline_value,
  round(r.target_value, 2) AS target_value,
  round(r.actual_value, 2) AS actual_value,
  r.review_result,
  r.revenue_stable,
  r.margin_improved,
  r.customer_stable,
  r.anomaly_decreased,
  CASE
    WHEN r.review_result = '达标' THEN true
    WHEN r.review_result = '改善中' AND r.revenue_stable AND r.customer_stable THEN true
    ELSE false
  END AS can_promote,
  t.priority,
  t.owner,
  t.action_required,
  t.process_evidence,
  t.incomplete_reason,
  t.next_step
FROM analytics.task_monthly_review r
JOIN analytics.store_task t ON r.task_id = t.task_id;

-- 闭环健康度视图
CREATE OR REPLACE VIEW analytics.v_loop_health AS
WITH task_stats AS (
  SELECT
    count(DISTINCT store_code) AS total_stores,
    count(DISTINCT CASE WHEN status != '待启动' THEN store_code END) AS executed_stores,
    count(*) AS total_tasks,
    count(*) FILTER (WHERE status = '已验收') AS verified_tasks,
    count(*) FILTER (WHERE status = '已回滚') AS rolled_back_tasks
  FROM analytics.store_task
  WHERE plan_month = date_trunc('month', COALESCE(
    (SELECT max(plan_month) FROM analytics.store_task), CURRENT_DATE))
),
  weekly_stats AS (
  SELECT
    count(DISTINCT t.store_code) AS stores_with_weekly_check
  FROM analytics.task_weekly_check w
  JOIN analytics.store_task t ON w.task_id = t.task_id
  WHERE t.plan_month = date_trunc('month', COALESCE(
    (SELECT max(plan_month) FROM analytics.store_task), CURRENT_DATE))
  AND w.checked_at >= date_trunc('month', COALESCE(
    (SELECT max(plan_month) FROM analytics.store_task), CURRENT_DATE))
),
  practice_stats AS (
  SELECT
    count(*) AS total_practices,
    count(*) FILTER (WHERE status = '已推广') AS promoted_practices
  FROM analytics.standardized_practice
),
  store_count AS (
  SELECT count(DISTINCT store_code) AS total FROM analytics.v_store_scorecard
)
SELECT
  round(COALESCE(ts.total_stores::numeric / NULLIF(sc.total, 0) * 100, 0), 1) AS task_generation_rate,
  round(COALESCE(ts.executed_stores::numeric / NULLIF(ts.total_stores, 0) * 100, 0), 1) AS store_execution_rate,
  round(COALESCE(ws.stores_with_weekly_check::numeric / NULLIF(ts.total_stores, 0) * 100, 0), 1) AS weekly_check_rate,
  round(COALESCE(ts.verified_tasks::numeric / NULLIF(ts.total_tasks, 0) * 100, 0), 1) AS monthly_review_rate,
  round(COALESCE(ps.promoted_practices::numeric / NULLIF(ps.total_practices, 0) * 100, 0), 1) AS practice_promotion_rate
FROM task_stats ts
CROSS JOIN weekly_stats ws
CROSS JOIN practice_stats ps
CROSS JOIN store_count sc;

-- 数据质量检查视图
CREATE OR REPLACE VIEW analytics.v_data_quality_check AS
WITH bill_stats AS (
  SELECT
    count(*) AS total_bills,
    count(*) FILTER (WHERE NULLIF(c005, '') IS NULL) AS missing_bill_no,
    count(*) FILTER (WHERE NULLIF(c002, '') IS NULL) AS missing_store_code,
    count(*) FILTER (WHERE c009::numeric <= 0 OR c009 IS NULL) AS zero_consumption,
    count(*) FILTER (WHERE c114::numeric < 0) AS negative_received,
    count(DISTINCT NULLIF(c002, '')) AS store_count,
    min(c175::timestamp) AS min_date,
    max(c176::timestamp) AS max_date
  FROM bill_records
),
  dish_stats AS (
  SELECT
    count(*) AS total_dish_records,
    count(*) FILTER (WHERE store_code IS NULL OR store_code = '') AS missing_store,
    count(*) FILTER (WHERE dish_name IS NULL OR dish_name = '') AS missing_dish
  FROM dish_sales_details
)
SELECT
  b.total_bills,
  b.missing_bill_no,
  b.missing_store_code,
  b.zero_consumption,
  b.negative_received,
  b.store_count,
  b.min_date,
  b.max_date,
  d.total_dish_records,
  d.missing_store AS dish_missing_store,
  d.missing_dish AS dish_missing_dish,
  CASE
    WHEN b.missing_bill_no > 0 THEN '有账单缺失单号'
    WHEN b.missing_store_code > 0 THEN '有账单缺失门店编码'
    WHEN b.negative_received > 0 THEN '有负实收账单'
    ELSE '数据完整性正常'
  END AS bill_quality_status,
  CASE
    WHEN d.missing_store > 0 OR d.missing_dish > 0 THEN '菜品明细有缺失字段'
    ELSE '菜品明细完整性正常'
  END AS dish_quality_status
FROM bill_stats b CROSS JOIN dish_stats d;

-- 店长日卡视图
CREATE OR REPLACE VIEW analytics.v_store_daily_card AS
WITH latest_date AS (
  SELECT max(closed_at)::date AS business_date FROM analytics.bill_fact WHERE closed_at IS NOT NULL
),
  -- 收入模块
  revenue AS (
    SELECT bf.store_code, bf.store_name,
      '收入' AS module,
      jsonb_build_array(
        jsonb_build_object('metric', '实收', 'value', round(sum(bf.received_total), 2),
          'baseline', round(avg(sw.received), 2), 'is_anomaly',
          sum(bf.received_total) < avg(sw.received) * 0.8),
        jsonb_build_object('metric', '账单数', 'value', count(*),
          'baseline', round(avg(sw.bill_count), 0), 'is_anomaly',
          count(*) < avg(sw.bill_count) * 0.8),
        jsonb_build_object('metric', '客单价', 'value', round(sum(bf.received_total)/count(*), 2),
          'baseline', round(avg(sw.avg_bill), 2), 'is_anomaly',
          sum(bf.received_total)/count(*) < avg(sw.avg_bill) * 0.9)
      ) AS anomalies
    FROM analytics.bill_fact bf
    CROSS JOIN latest_date ld
    LEFT JOIN LATERAL (
      SELECT sum(r.received_total) AS received, count(*) AS bill_count,
        sum(r.received_total)/count(*) AS avg_bill
      FROM analytics.bill_fact r
      WHERE r.store_code = bf.store_code
        AND r.closed_at::date >= ld.business_date - 28
        AND r.closed_at::date < ld.business_date
        AND extract(isodow FROM r.closed_at) = extract(isodow FROM ld.business_date)
    ) sw ON true
    WHERE bf.closed_at::date = ld.business_date
    GROUP BY bf.store_code, bf.store_name
  ),
  -- 优惠模块
  discount AS (
    SELECT bf.store_code, bf.store_name,
      '优惠' AS module,
      jsonb_build_array(
        jsonb_build_object('metric', '优惠率', 'value',
          round(sum(bf.discount_total)/nullif(sum(bf.consumption), 0) * 100, 2),
          'baseline', 20.23, 'is_anomaly',
          sum(bf.discount_total)/nullif(sum(bf.consumption), 0) * 100 > 25),
        jsonb_build_object('metric', '异常优惠账单', 'value',
          count(*) FILTER (WHERE bf.discount_total > bf.consumption AND bf.consumption > 0),
          'baseline', 0, 'is_anomaly',
          count(*) FILTER (WHERE bf.discount_total > bf.consumption AND bf.consumption > 0) > 5)
      ) AS anomalies
    FROM analytics.bill_fact bf
    CROSS JOIN latest_date ld
    WHERE bf.closed_at::date = ld.business_date
    GROUP BY bf.store_code, bf.store_name
  ),
  -- 风险模块
  risk AS (
    SELECT bf.store_code, bf.store_name,
      '风险' AS module,
      jsonb_build_array(
        jsonb_build_object('metric', '零实收账单', 'value',
          count(*) FILTER (WHERE bf.received_total = 0), 'baseline', 0, 'is_anomaly',
          count(*) FILTER (WHERE bf.received_total = 0) > 3),
        jsonb_build_object('metric', '撤单/退款', 'value',
          count(*) FILTER (WHERE bf.bill_status IN ('撤单', '退款')), 'baseline', 0, 'is_anomaly',
          count(*) FILTER (WHERE bf.bill_status IN ('撤单', '退款')) > 2)
      ) AS anomalies
    FROM analytics.bill_fact bf
    CROSS JOIN latest_date ld
    WHERE bf.closed_at::date = ld.business_date
    GROUP BY bf.store_code, bf.store_name
  )
SELECT
  COALESCE(r.store_code, d.store_code, rk.store_code) AS store_code,
  COALESCE(r.store_name, d.store_name, rk.store_name) AS store_name,
  COALESCE(r.module, d.module, rk.module) AS module,
  COALESCE(r.anomalies, d.anomalies, rk.anomalies) AS anomalies
FROM revenue r
FULL JOIN discount d USING (store_code, store_name)
FULL JOIN risk rk USING (store_code, store_name);
