-- ============================================================
-- 连锁餐饮企业数字化运营平台 — 本体标准数据库
-- 创建时间: 2026-07-26
-- 说明: 维度主数据表 + 事实表 + 指标标准 + 枚举标准
-- ============================================================

-- ============================================================
-- 一、维度主数据表 (Dimension Tables)
-- ============================================================

-- 1. 门店主数据 (扩展现有 dim_store)
ALTER TABLE analytics.dim_store 
  ADD COLUMN IF NOT EXISTS brand TEXT DEFAULT '马兰拉面',
  ADD COLUMN IF NOT EXISTS comparable BOOLEAN DEFAULT true,
  ADD COLUMN IF NOT EXISTS management_quadrant TEXT,
  ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now();

-- 2. SKU主数据
CREATE TABLE IF NOT EXISTS analytics.dim_sku (
  sku_code        TEXT PRIMARY KEY,
  standard_name   TEXT NOT NULL,
  pos_code        TEXT,
  meituan_code    TEXT,
  taobao_code     TEXT,
  jd_code         TEXT,
  historical_names TEXT[],
  sku_type        TEXT NOT NULL DEFAULT '单品',  -- 单品/套餐主品/套餐组件/赠品/包装/服务项目
  category_l1     TEXT,
  category_l2     TEXT,
  status          TEXT NOT NULL DEFAULT '在售',  -- 在售/停用/季节停/待定
  effective_date  DATE,
  discontinued_date DATE,
  brand           TEXT DEFAULT '马兰拉面',
  business_type   TEXT,  -- 兰州牛肉面/夜市烧烤/火锅等
  applicable_stores TEXT[],  -- NULL表示全部门店
  tags            TEXT[],  -- 新品/季节品/区域品/战略品
  abc_class       TEXT,  -- A-核心/B-成长/C-长尾
  unit            TEXT,
  created_at      TIMESTAMPTZ DEFAULT now(),
  updated_at      TIMESTAMPTZ DEFAULT now()
);

-- 3. 原料主数据
CREATE TABLE IF NOT EXISTS analytics.dim_material (
  material_code    TEXT PRIMARY KEY,
  material_name    TEXT NOT NULL,
  specification    TEXT,
  major_category   TEXT,  -- 牛肉类/面粉类/蔬菜类/调味料类等
  minor_category   TEXT,
  finance_category TEXT,  -- 原材料/辅料/包装物/低值易耗品
  base_unit        TEXT NOT NULL,  -- kg/个/包/瓶
  conversion_ratio JSONB,  -- {"kg": 1, "斤": 2}
  supplier_code    TEXT,
  status           TEXT DEFAULT '启用',
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- 4. 会员主数据
CREATE TABLE IF NOT EXISTS analytics.dim_member (
  member_id        TEXT PRIMARY KEY,
  phone_hash       TEXT,  -- 脱敏手机号
  register_channel TEXT,  -- 堂食/美团/淘宝/京东/小程序
  register_store   TEXT,
  register_date    DATE,
  member_level     TEXT DEFAULT '普通',  -- 普通/银卡/金卡/钻石
  total_orders     INTEGER DEFAULT 0,
  total_revenue    NUMERIC DEFAULT 0,
  last_order_date  DATE,
  status           TEXT DEFAULT '活跃',  -- 活跃/沉睡/流失
  tags             TEXT[],
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- 5. 员工主数据
CREATE TABLE IF NOT EXISTS analytics.dim_employee (
  employee_id      TEXT PRIMARY KEY,
  employee_name    TEXT NOT NULL,
  position         TEXT,  -- 店长/副店长/厨师长/厨师/服务员/收银员/配送员
  store_code       TEXT,
  hire_date        DATE,
  leave_date       DATE,
  status           TEXT DEFAULT '在职',  -- 在职/离职/休假
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- 6. 渠道主数据
CREATE TABLE IF NOT EXISTS analytics.dim_channel (
  channel_code     TEXT PRIMARY KEY,
  channel_name     TEXT NOT NULL,
  channel_group    TEXT NOT NULL,  -- 堂食/外卖/支付
  channel_type     TEXT,  -- 堂食/美团到店/美团外卖/支付宝/微信/现金/银联/抖音/挂账/京东到家/淘宝外卖
  platform         TEXT,  -- 美团/淘宝/京东/抖音/自有
  commission_rate  NUMERIC,  -- 平台佣金率%
  is_delivery      BOOLEAN DEFAULT false,
  sort_order       INTEGER DEFAULT 0,
  status           TEXT DEFAULT '启用',
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 7. 活动主数据
CREATE TABLE IF NOT EXISTS analytics.dim_promotion (
  promotion_id     TEXT PRIMARY KEY,
  promotion_name   TEXT NOT NULL,
  channel          TEXT,  -- 美团/淘宝/京东/堂食/全渠道
  start_date       DATE,
  end_date         DATE,
  applicable_stores TEXT[],  -- NULL表示全部
  applicable_skus  TEXT[],  -- NULL表示全部
  platform_bear    NUMERIC DEFAULT 0,  -- 平台承担金额
  company_bear     NUMERIC DEFAULT 0,  -- 公司承担金额
  store_bear       NUMERIC DEFAULT 0,  -- 门店承担金额
  target_audience  TEXT,  -- 新客/老客/全客
  budget           NUMERIC,
  status           TEXT DEFAULT '进行中',  -- 计划中/进行中/已结束/已暂停
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- 8. 日历主数据
CREATE TABLE IF NOT EXISTS analytics.dim_calendar (
  date_value       DATE PRIMARY KEY,
  year             INTEGER NOT NULL,
  month            INTEGER NOT NULL,
  day              INTEGER NOT NULL,
  weekday_no       INTEGER NOT NULL,  -- 1=周一, 7=周日
  weekday_name     TEXT NOT NULL,
  is_weekend       BOOLEAN NOT NULL,
  is_holiday       BOOLEAN DEFAULT false,
  holiday_name     TEXT,
  is_operating_day BOOLEAN DEFAULT true,
  weather          TEXT,  -- 晴/阴/雨/雪/雾
  temperature      NUMERIC,
  special_event    TEXT,  -- 商圈活动/门店周边事件
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 9. 供应商主数据
CREATE TABLE IF NOT EXISTS analytics.dim_supplier (
  supplier_code    TEXT PRIMARY KEY,
  supplier_name    TEXT NOT NULL,
  supplier_type    TEXT,  -- 原料/包装/设备/服务
  contact_person   TEXT,
  contact_phone    TEXT,
  region           TEXT,
  status           TEXT DEFAULT '合作中',
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- ============================================================
-- 二、事实表 (Fact Tables)
-- ============================================================

-- 1. 账单事实表 (物化视图已存在 analytics.bill_fact，此处建规范事实表)
CREATE TABLE IF NOT EXISTS analytics.fact_bill (
  record_id        BIGINT PRIMARY KEY,
  source_file      TEXT,
  source_row       INTEGER,
  store_code       TEXT NOT NULL,
  store_name       TEXT,
  meal_period      TEXT,  -- 午市/晚市/夜宵/外卖
  bill_no          TEXT,
  business_area    TEXT,
  table_type       TEXT,
  table_no         TEXT,
  business_date    DATE GENERATED ALWAYS AS ((closed_at AT TIME ZONE 'Asia/Shanghai')::date) STORED,
  consumption      NUMERIC DEFAULT 0,
  discount_total   NUMERIC DEFAULT 0,
  received_total   NUMERIC DEFAULT 0,
  guest_count      NUMERIC DEFAULT 0,
  table_count      NUMERIC DEFAULT 0,
  theoretical_cost NUMERIC DEFAULT 0,
  theoretical_profit NUMERIC DEFAULT 0,
  theoretical_margin NUMERIC,
  third_party_discount NUMERIC DEFAULT 0,
  marketing_plan   TEXT,
  member_id        TEXT,
  member_level     TEXT,
  waiter           TEXT,
  cashier          TEXT,
  shift_name       TEXT,
  bill_status      TEXT,
  order_tag        TEXT,
  opened_at        TIMESTAMPTZ,
  closed_at        TIMESTAMPTZ,
  duration_minutes NUMERIC,
  -- 支付渠道金额
  cash_received       NUMERIC DEFAULT 0,
  alipay_received     NUMERIC DEFAULT 0,
  wechat_received     NUMERIC DEFAULT 0,
  meituan_received    NUMERIC DEFAULT 0,
  unionpay_received   NUMERIC DEFAULT 0,
  douyin_received     NUMERIC DEFAULT 0,
  credit_received     NUMERIC DEFAULT 0,
  jd_delivery_received NUMERIC DEFAULT 0,
  meituan_delivery_received NUMERIC DEFAULT 0,
  taobao_delivery_received NUMERIC DEFAULT 0,
  -- 优惠明细
  points_discount        NUMERIC DEFAULT 0,
  member_coupon_discount NUMERIC DEFAULT 0,
  member_prestore_discount NUMERIC DEFAULT 0,
  douyin_discount        NUMERIC DEFAULT 0,
  -- 平台佣金
  meituan_delivery_commission NUMERIC DEFAULT 0,
  taobao_delivery_commission  NUMERIC DEFAULT 0,
  jd_delivery_commission      NUMERIC DEFAULT 0,
  -- 异常标记
  anomaly_reason    TEXT,
  is_anomaly        BOOLEAN DEFAULT false,
  is_zero_received BOOLEAN DEFAULT false,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 2. 账单明细事实表
CREATE TABLE IF NOT EXISTS analytics.fact_bill_item (
  detail_id        BIGINT PRIMARY KEY,
  source_file      TEXT,
  source_row       INTEGER,
  store_code       TEXT NOT NULL,
  store_name       TEXT,
  bill_no          TEXT NOT NULL,
  business_type    TEXT,
  table_or_pickup_no TEXT,
  guest_count      NUMERIC,
  opened_at        TIMESTAMP,
  closed_at        TIMESTAMP,
  ordered_at       TIMESTAMP,
  dish_name        TEXT NOT NULL,
  sku_code         TEXT,  -- 关联 dim_sku
  category_l1      TEXT,
  category_l2      TEXT,
  item_type        TEXT,  -- 单品/套餐/赠品/加料/包装
  production_department TEXT,
  unit             TEXT,
  preparation_method TEXT,
  sales_quantity   NUMERIC DEFAULT 0,
  unit_price       NUMERIC DEFAULT 0,
  gross_amount     NUMERIC DEFAULT 0,
  received_amount  NUMERIC DEFAULT 0,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 3. 支付事实表 (一张账单一种支付方式一行)
CREATE TABLE IF NOT EXISTS analytics.fact_payment (
  id               BIGSERIAL PRIMARY KEY,
  bill_record_id   BIGINT NOT NULL,  -- 关联 fact_bill.record_id
  store_code       TEXT NOT NULL,
  bill_no          TEXT,
  business_date    DATE,
  channel_code     TEXT NOT NULL,  -- 关联 dim_channel
  amount           NUMERIC DEFAULT 0,
  commission       NUMERIC DEFAULT 0,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 4. 优惠使用事实表
CREATE TABLE IF NOT EXISTS analytics.fact_promotion_usage (
  id               BIGSERIAL PRIMARY KEY,
  bill_record_id   BIGINT,
  store_code       TEXT NOT NULL,
  bill_no          TEXT,
  business_date    DATE,
  promotion_id     TEXT,  -- 关联 dim_promotion
  discount_amount  NUMERIC DEFAULT 0,
  platform_bear    NUMERIC DEFAULT 0,
  company_bear     NUMERIC DEFAULT 0,
  store_bear       NUMERIC DEFAULT 0,
  is_new_customer  BOOLEAN,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 5. 平台订单损益表
CREATE TABLE IF NOT EXISTS analytics.fact_platform_order (
  id               BIGSERIAL PRIMARY KEY,
  platform_order_no TEXT NOT NULL,
  platform         TEXT NOT NULL,  -- 美团/淘宝/京东
  store_code       TEXT NOT NULL,
  business_date    DATE NOT NULL,
  gross_amount     NUMERIC DEFAULT 0,
  discount_amount  NUMERIC DEFAULT 0,
  received_amount  NUMERIC DEFAULT 0,
  commission       NUMERIC DEFAULT 0,
  delivery_fee     NUMERIC DEFAULT 0,
  refund_amount    NUMERIC DEFAULT 0,
  net_revenue      NUMERIC DEFAULT 0,
  theoretical_cost NUMERIC DEFAULT 0,
  gross_profit     NUMERIC DEFAULT 0,
  is_cancelled     BOOLEAN DEFAULT false,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 6. 库存快照事实表
CREATE TABLE IF NOT EXISTS analytics.fact_inventory_snapshot (
  id               BIGSERIAL PRIMARY KEY,
  store_code       TEXT NOT NULL,
  material_code    TEXT NOT NULL,
  snapshot_date    DATE NOT NULL,
  opening_quantity NUMERIC DEFAULT 0,
  opening_amount   NUMERIC DEFAULT 0,
  purchase_quantity NUMERIC DEFAULT 0,
  purchase_amount  NUMERIC DEFAULT 0,
  consumption_quantity NUMERIC DEFAULT 0,
  consumption_amount NUMERIC DEFAULT 0,
  ending_quantity  NUMERIC DEFAULT 0,
  ending_amount    NUMERIC DEFAULT 0,
  waste_quantity   NUMERIC DEFAULT 0,
  waste_amount     NUMERIC DEFAULT 0,
  transfer_in_quantity NUMERIC DEFAULT 0,
  transfer_out_quantity NUMERIC DEFAULT 0,
  inventory_days   NUMERIC,  -- 库存天数
  is_negative      BOOLEAN DEFAULT false,
  created_at       TIMESTAMPTZ DEFAULT now(),
  UNIQUE(store_code, material_code, snapshot_date)
);

-- 7. 采购收货事实表
CREATE TABLE IF NOT EXISTS analytics.fact_purchase_receipt (
  id               BIGSERIAL PRIMARY KEY,
  receipt_no       TEXT,
  store_code       TEXT NOT NULL,
  supplier_code    TEXT,
  material_code    TEXT NOT NULL,
  receipt_date     DATE NOT NULL,
  quantity         NUMERIC DEFAULT 0,
  unit_price_excl_tax NUMERIC DEFAULT 0,
  unit_price_incl_tax NUMERIC DEFAULT 0,
  tax_rate         NUMERIC DEFAULT 0,
  amount_excl_tax  NUMERIC DEFAULT 0,
  tax_amount       NUMERIC DEFAULT 0,
  amount_incl_tax  NUMERIC DEFAULT 0,
  return_quantity  NUMERIC DEFAULT 0,
  batch_no         TEXT,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 8. 调拨事实表
CREATE TABLE IF NOT EXISTS analytics.fact_transfer (
  id               BIGSERIAL PRIMARY KEY,
  transfer_no      TEXT,
  from_store_code  TEXT NOT NULL,
  to_store_code    TEXT NOT NULL,
  material_code    TEXT NOT NULL,
  transfer_date    DATE NOT NULL,
  quantity         NUMERIC DEFAULT 0,
  amount           NUMERIC DEFAULT 0,
  operator         TEXT,
  approver         TEXT,
  reason           TEXT,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 9. 报损事实表
CREATE TABLE IF NOT EXISTS analytics.fact_waste (
  id               BIGSERIAL PRIMARY KEY,
  waste_no         TEXT,
  store_code       TEXT NOT NULL,
  material_code    TEXT NOT NULL,
  waste_date       DATE NOT NULL,
  quantity         NUMERIC DEFAULT 0,
  amount           NUMERIC DEFAULT 0,
  reason           TEXT,  -- 过期/损坏/变质/操作失误/盘点差异
  operator         TEXT,
  approver         TEXT,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 10. BOM配方事实表
CREATE TABLE IF NOT EXISTS analytics.fact_recipe_bom (
  id               BIGSERIAL PRIMARY KEY,
  sku_code         TEXT NOT NULL,
  material_code    TEXT NOT NULL,
  bom_version      TEXT NOT NULL DEFAULT 'v1',
  standard_gross_quantity NUMERIC NOT NULL,  -- 标准毛料量
  standard_net_quantity NUMERIC NOT NULL,  -- 标准净料量
  unit             TEXT NOT NULL,
  yield_rate       NUMERIC,  -- 出成率%
  waste_rate       NUMERIC,  -- 损耗率%
  substitute_material TEXT,  -- 替代原料
  effective_date   DATE NOT NULL DEFAULT '2026-01-01',
  expiry_date      DATE,
  applicable_stores TEXT[],
  created_at       TIMESTAMPTZ DEFAULT now(),
  UNIQUE(sku_code, material_code, bom_version)
);

-- 11. 员工排班事实表
CREATE TABLE IF NOT EXISTS analytics.fact_employee_shift (
  id               BIGSERIAL PRIMARY KEY,
  employee_id      TEXT NOT NULL,
  employee_name    TEXT,
  store_code       TEXT NOT NULL,
  shift_date       DATE NOT NULL,
  shift_name       TEXT,  -- 早班/中班/晚班/全天
  planned_hours    NUMERIC DEFAULT 0,
  actual_hours     NUMERIC DEFAULT 0,
  overtime_hours   NUMERIC DEFAULT 0,
  wage             NUMERIC DEFAULT 0,
  overtime_pay     NUMERIC DEFAULT 0,
  meal_period      TEXT,  -- 午市/晚市/夜宵
  created_at       TIMESTAMPTZ DEFAULT now(),
  UNIQUE(employee_id, store_code, shift_date, shift_name)
);

-- 12. 会员触达事实表
CREATE TABLE IF NOT EXISTS analytics.fact_customer_contact (
  id               BIGSERIAL PRIMARY KEY,
  member_id        TEXT NOT NULL,
  contact_type     TEXT NOT NULL,  -- 短信/推送/券/电话/企微
  contact_date     DATE NOT NULL,
  store_code       TEXT,
  campaign_id      TEXT,
  channel          TEXT,
  is_responded     BOOLEAN DEFAULT false,
  response_date    DATE,
  is_converted     BOOLEAN DEFAULT false,  -- 是否转化为消费
  conversion_date  DATE,
  conversion_amount NUMERIC DEFAULT 0,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- 13. 门店任务事实表 (已存在 analytics.store_task，此处建规范版)
CREATE TABLE IF NOT EXISTS analytics.fact_store_task (
  task_id          INTEGER PRIMARY KEY,
  plan_month       DATE,
  store_code       TEXT NOT NULL,
  priority         TEXT NOT NULL,  -- P0/P1/P2/P3
  problem_indicator TEXT NOT NULL,
  problem_module   TEXT,  -- 收入/成本/优惠/会员/库存/风险
  current_value    NUMERIC,
  benchmark_value  NUMERIC,
  target_value     NUMERIC,
  problem_description TEXT,
  action_required  TEXT,
  owner            TEXT,
  collaborators    TEXT,
  deadline         DATE,
  status           TEXT DEFAULT '待启动',
  process_evidence TEXT,
  verification_indicator TEXT,
  verification_result TEXT,
  incomplete_reason TEXT,
  next_step        TEXT,
  created_at       TIMESTAMPTZ DEFAULT now(),
  updated_at       TIMESTAMPTZ DEFAULT now()
);

-- ============================================================
-- 三、指标标准表 (Metric Dictionary)
-- ============================================================

-- 扩展现有 indicator_dictionary 表
ALTER TABLE analytics.indicator_dictionary
  ADD COLUMN IF NOT EXISTS metric_code TEXT,
  ADD COLUMN IF NOT EXISTS metric_category TEXT,  -- 收入/成本/优惠/会员/库存/风险/人效
  ADD COLUMN IF NOT EXISTS calculation_sql TEXT,
  ADD COLUMN IF NOT EXISTS direction TEXT DEFAULT 'higher_better',  -- higher_better/lower_better
  ADD COLUMN IF NOT EXISTS unit TEXT,  -- 元/%/个/天
  ADD COLUMN IF NOT EXISTS granularity TEXT,  -- 门店/区域/公司
  ADD COLUMN IF NOT EXISTS version TEXT DEFAULT 'v1.0',
  ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true;

-- ============================================================
-- 四、枚举标准表 (Enum Tables)
-- ============================================================

-- 1. 风险等级枚举
CREATE TABLE IF NOT EXISTS analytics.enum_risk_level (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  color   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 2. 任务优先级枚举
CREATE TABLE IF NOT EXISTS analytics.enum_priority (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  response_time TEXT,
  sort_order INTEGER DEFAULT 0
);

-- 3. 任务状态枚举
CREATE TABLE IF NOT EXISTS analytics.enum_task_status (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 4. 验收结果枚举
CREATE TABLE IF NOT EXISTS analytics.enum_verification_result (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 5. SKU类型枚举
CREATE TABLE IF NOT EXISTS analytics.enum_sku_type (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 6. SKU状态枚举
CREATE TABLE IF NOT EXISTS analytics.enum_sku_status (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 7. 业态枚举
CREATE TABLE IF NOT EXISTS analytics.enum_business_type (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 8. 渠道组枚举
CREATE TABLE IF NOT EXISTS analytics.enum_channel_group (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 9. 指标方向枚举
CREATE TABLE IF NOT EXISTS analytics.enum_metric_direction (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL
);

-- 10. 会员状态枚举
CREATE TABLE IF NOT EXISTS analytics.enum_member_status (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 11. 报损原因枚举
CREATE TABLE IF NOT EXISTS analytics.enum_waste_reason (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- 12. 管理象限枚举
CREATE TABLE IF NOT EXISTS analytics.enum_management_quadrant (
  code    TEXT PRIMARY KEY,
  label   TEXT NOT NULL,
  sort_order INTEGER DEFAULT 0
);

-- ============================================================
-- 五、创建索引
-- ============================================================

CREATE INDEX IF NOT EXISTS idx_dim_sku_status ON analytics.dim_sku(status);
CREATE INDEX IF NOT EXISTS idx_dim_sku_type ON analytics.dim_sku(sku_type);
CREATE INDEX IF NOT EXISTS idx_dim_sku_category ON analytics.dim_sku(category_l1, category_l2);
CREATE INDEX IF NOT EXISTS idx_dim_material_category ON analytics.dim_material(major_category);
CREATE INDEX IF NOT EXISTS idx_dim_member_status ON analytics.dim_member(status);
CREATE INDEX IF NOT EXISTS idx_dim_employee_store ON analytics.dim_employee(store_code);
CREATE INDEX IF NOT EXISTS idx_dim_channel_group ON analytics.dim_channel(channel_group);
CREATE INDEX IF NOT EXISTS idx_dim_promotion_status ON analytics.dim_promotion(status);
CREATE INDEX IF NOT EXISTS idx_dim_calendar_date ON analytics.dim_calendar(date_value);

CREATE INDEX IF NOT EXISTS idx_fact_bill_store_date ON analytics.fact_bill(store_code, business_date);
CREATE INDEX IF NOT EXISTS idx_fact_bill_no ON analytics.fact_bill(bill_no);
CREATE INDEX IF NOT EXISTS idx_fact_bill_anomaly ON analytics.fact_bill(is_anomaly) WHERE is_anomaly = true;
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_store ON analytics.fact_bill_item(store_code);
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_bill_no ON analytics.fact_bill_item(bill_no);
CREATE INDEX IF NOT EXISTS idx_fact_bill_item_dish ON analytics.fact_bill_item(dish_name);
CREATE INDEX IF NOT EXISTS idx_fact_payment_bill ON analytics.fact_payment(bill_record_id);
CREATE INDEX IF NOT EXISTS idx_fact_payment_channel ON analytics.fact_payment(channel_code);
CREATE INDEX IF NOT EXISTS idx_fact_inventory_store_date ON analytics.fact_inventory_snapshot(store_code, snapshot_date);
CREATE INDEX IF NOT EXISTS idx_fact_inventory_negative ON analytics.fact_inventory_snapshot(is_negative) WHERE is_negative = true;
CREATE INDEX IF NOT EXISTS idx_fact_purchase_store_date ON analytics.fact_purchase_receipt(store_code, receipt_date);
CREATE INDEX IF NOT EXISTS idx_fact_waste_store_date ON analytics.fact_waste(store_code, waste_date);
CREATE INDEX IF NOT EXISTS idx_fact_bom_sku ON analytics.fact_recipe_bom(sku_code);
CREATE INDEX IF NOT EXISTS idx_fact_shift_store_date ON analytics.fact_employee_shift(store_code, shift_date);
CREATE INDEX IF NOT EXISTS idx_fact_contact_member ON analytics.fact_customer_contact(member_id);
CREATE INDEX IF NOT EXISTS idx_fact_task_store ON analytics.fact_store_task(store_code);
CREATE INDEX IF NOT EXISTS idx_fact_task_priority ON analytics.fact_store_task(priority);

-- ============================================================
-- 六、初始化枚举数据
-- ============================================================

INSERT INTO analytics.enum_risk_level (code, label, color, sort_order) VALUES
  ('红色', '红色', '#ef4444', 1),
  ('黄色', '黄色', '#eab308', 2),
  ('绿色', '绿色', '#22c55e', 3)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_priority (code, label, response_time, sort_order) VALUES
  ('P0', 'P0-紧急', '当日确认、7天内形成方案', 1),
  ('P1', 'P1-重点', '48小时确认、当月整改', 2),
  ('P2', 'P2-改善', '一周内纳入计划', 3),
  ('P3', 'P3-观察', '持续监控', 4)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_task_status (code, label, sort_order) VALUES
  ('待启动', '待启动', 1),
  ('进行中', '进行中', 2),
  ('待验收', '待验收', 3),
  ('已验收', '已验收', 4),
  ('已回滚', '已回滚', 5)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_verification_result (code, label, sort_order) VALUES
  ('达标', '达标', 1),
  ('改善中', '改善中', 2),
  ('未达标', '未达标', 3),
  ('待验收', '待验收', 4)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_sku_type (code, label, sort_order) VALUES
  ('单品', '单品', 1),
  ('套餐主品', '套餐主品', 2),
  ('套餐组件', '套餐组件', 3),
  ('赠品', '赠品', 4),
  ('包装', '包装', 5),
  ('服务项目', '服务项目', 6)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_sku_status (code, label, sort_order) VALUES
  ('在售', '在售', 1),
  ('停用', '停用', 2),
  ('季节停', '季节停', 3),
  ('待定', '待定', 4)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_business_type (code, label, sort_order) VALUES
  ('标准门店', '标准门店', 1),
  ('特殊业态', '特殊业态', 2),
  ('旗舰店', '旗舰店', 3),
  ('社区店', '社区店', 4),
  ('交通枢纽店', '交通枢纽店', 5)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_channel_group (code, label, sort_order) VALUES
  ('堂食', '堂食', 1),
  ('外卖', '外卖', 2),
  ('支付', '支付', 3)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_metric_direction (code, label) VALUES
  ('higher_better', '越高越好'),
  ('lower_better', '越低越好')
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_member_status (code, label, sort_order) VALUES
  ('活跃', '活跃', 1),
  ('沉睡', '沉睡(30天未消费)', 2),
  ('流失', '流失(90天未消费)', 3)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_waste_reason (code, label, sort_order) VALUES
  ('过期', '过期', 1),
  ('损坏', '损坏', 2),
  ('变质', '变质', 3),
  ('操作失误', '操作失误', 4),
  ('盘点差异', '盘点差异', 5)
ON CONFLICT (code) DO NOTHING;

INSERT INTO analytics.enum_management_quadrant (code, label, sort_order) VALUES
  ('明星', '明星门店', 1),
  ('金牛', '金牛门店', 2),
  ('问题', '问题门店', 3),
  ('瘦狗', '瘦狗门店', 4)
ON CONFLICT (code) DO NOTHING;

-- ============================================================
-- 七、初始化渠道主数据
-- ============================================================

INSERT INTO analytics.dim_channel (channel_code, channel_name, channel_group, channel_type, platform, is_delivery, sort_order, status) VALUES
  ('cash', '现金', '支付', '现金', NULL, false, 1, '启用'),
  ('alipay', '支付宝', '支付', '支付宝', '支付宝', false, 2, '启用'),
  ('wechat', '微信', '支付', '微信', '微信', false, 3, '启用'),
  ('unionpay', '银联', '支付', '银联', '银联', false, 4, '启用'),
  ('credit', '挂账', '支付', '挂账', NULL, false, 5, '启用'),
  ('meituan', '美团到店', '堂食', '美团到店', '美团', false, 6, '启用'),
  ('meituan_delivery', '美团外卖', '外卖', '美团外卖', '美团', true, 7, '启用'),
  ('taobao_delivery', '淘宝外卖', '外卖', '淘宝外卖', '淘宝', true, 8, '启用'),
  ('jd_delivery', '京东到家', '外卖', '京东到家', '京东', true, 9, '启用'),
  ('douyin', '抖音', '堂食', '抖音', '抖音', false, 10, '启用')
ON CONFLICT (channel_code) DO NOTHING;

-- ============================================================
-- 八、初始化日历主数据 (2026年全年)
-- ============================================================

INSERT INTO analytics.dim_calendar (date_value, year, month, day, weekday_no, weekday_name, is_weekend, is_holiday, holiday_name, is_operating_day)
SELECT
  d::date,
  EXTRACT(year FROM d)::int,
  EXTRACT(month FROM d)::int,
  EXTRACT(day FROM d)::int,
  EXTRACT(dow FROM d)::int,
  CASE EXTRACT(dow FROM d)::int
    WHEN 0 THEN '周日' WHEN 1 THEN '周一' WHEN 2 THEN '周二'
    WHEN 3 THEN '周三' WHEN 4 THEN '周四' WHEN 5 THEN '周五'
    WHEN 6 THEN '周六'
  END,
  EXTRACT(dow FROM d)::int IN (0, 6),
  false, NULL, true
FROM generate_series('2026-01-01'::date, '2026-12-31'::date, '1 day'::interval) AS d
ON CONFLICT (date_value) DO NOTHING;

-- 标记2026年主要节假日
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '元旦' WHERE date_value = '2026-01-01';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '春节' WHERE date_value BETWEEN '2026-02-17' AND '2026-02-23';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '清明节' WHERE date_value BETWEEN '2026-04-04' AND '2026-04-06';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '劳动节' WHERE date_value BETWEEN '2026-05-01' AND '2026-05-05';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '端午节' WHERE date_value BETWEEN '2026-06-19' AND '2026-06-21';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '中秋节' WHERE date_value BETWEEN '2026-09-25' AND '2026-09-27';
UPDATE analytics.dim_calendar SET is_holiday = true, holiday_name = '国庆节' WHERE date_value BETWEEN '2026-10-01' AND '2026-10-07';

-- ============================================================
-- 九、初始化指标标准 (扩展现有 indicator_dictionary)
-- ============================================================

UPDATE analytics.indicator_dictionary SET
  metric_code = 'received_total',
  metric_category = '收入',
  direction = 'higher_better',
  unit = '元',
  granularity = '门店/区域/公司',
  version = 'v1.0',
  is_active = true
WHERE indicator_name = '实收' AND metric_code IS NULL;

UPDATE analytics.indicator_dictionary SET
  metric_code = 'avg_bill_value',
  metric_category = '收入',
  direction = 'higher_better',
  unit = '元',
  granularity = '门店/区域/公司',
  version = 'v1.0',
  is_active = true
WHERE indicator_name = '客单价' AND metric_code IS NULL;

UPDATE analytics.indicator_dictionary SET
  metric_code = 'discount_rate',
  metric_category = '优惠',
  direction = 'lower_better',
  unit = '%',
  granularity = '门店/区域/公司',
  version = 'v1.0',
  is_active = true
WHERE indicator_name = '优惠率' AND metric_code IS NULL;

-- 补充缺失的核心指标 (upsert: 已有的更新metric_code等字段，新的插入)
INSERT INTO analytics.indicator_dictionary (indicator_name, business_definition, formula, data_source, update_frequency, owner, scope, yellow_threshold, red_threshold, version_date, metric_code, metric_category, direction, unit, granularity, version, is_active)
SELECT * FROM (VALUES
  ('账单数', '有效账单总数', 'count(*)', 'bill_fact', '日', '运营部', '门店/区域/公司', NULL, NULL, '2026-04-01'::date, 'bill_count', '收入', 'higher_better', '个', '门店/区域/公司', 'v1.0', true),
  ('理论成本率', '理论成本占实收比例', 'sum(theoretical_cost)/sum(received_total)*100', 'bill_fact', '日', '财务部', '门店/区域/公司', 40, 45, '2026-04-01'::date, 'theoretical_cost_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
  ('实际成本率', '盘点倒挤成本占实收比例', 'sum(consumption_amount)/sum(received_total)*100', 'inventory_cost_records', '月', '财务部', '门店/区域/公司', 38, 42, '2026-04-01'::date, 'actual_cost_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
  ('成本差异率', '实际成本率与理论成本率差异', 'actual_cost_rate - theoretical_cost_rate', '计算指标', '月', '财务部', '门店/区域/公司', 3, 5, '2026-04-01'::date, 'cost_variance_rate', '成本', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
  ('平台合并加权成本率', '三平台(美团+淘宝+京东)加权成本率', '(sum(commission)+sum(discount))/sum(received)*100', 'v_store_platform_economics', '月', '运营部', '门店/区域/公司', 25, 30, '2026-04-01'::date, 'platform_weighted_cost_rate', '优惠', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
  ('会员识别率', '会员账单占总账单比例', 'count(member_id IS NOT NULL)/count(*)*100', 'bill_fact', '日', '会员部', '门店/区域/公司', 15, 10, '2026-04-01'::date, 'member_identify_rate', '会员', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
  ('30日复购率', '30天内二次消费会员占比', 'count(repeat_members)/count(identified_members)*100', 'v_store_repeat_summary_monthly', '月', '会员部', '门店/区域/公司', 30, 20, '2026-04-01'::date, 'repeat_rate_30d', '会员', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
  ('异常账单率', '异常账单数占总账单比例', 'count(is_anomaly)/count(*)*100', 'bill_fact', '日', '运营部', '门店/区域/公司', 0.1, 0.5, '2026-04-01'::date, 'anomaly_bill_rate', '风险', 'lower_better', '%', '门店/区域/公司', 'v1.0', true),
  ('零实收账单数', '实收为0的账单数量', 'count(received_total=0)', 'bill_fact', '日', '运营部', '门店', 5, 10, '2026-04-01'::date, 'zero_received_count', '风险', 'lower_better', '个', '门店', 'v1.0', true),
  ('库存天数', '期末库存可消耗天数', 'ending_amount/(consumption_amount/30)', 'inventory_cost_records', '月', '供应链', '门店', 7, 14, '2026-04-01'::date, 'inventory_days', '库存', 'lower_better', '天', '门店', 'v1.0', true),
  ('负耗用货品数', '消耗量为负的货品数量', 'count(consumption_quantity<0)', 'inventory_cost_records', '月', '财务部', '门店', 0, 3, '2026-04-01'::date, 'negative_consumption_count', '库存', 'lower_better', '个', '门店', 'v1.0', true),
  ('理论毛利率', '理论毛利占消费额比例', 'sum(theoretical_profit)/sum(consumption)*100', 'bill_fact', '日', '财务部', '门店/区域/公司', 65, 60, '2026-04-01'::date, 'theoretical_margin_rate', '收入', 'higher_better', '%', '门店/区域/公司', 'v1.0', true),
  ('活动新客率', '活动期间新客占活动用户比例', 'count(is_new_customer)/count(*)*100', 'fact_promotion_usage', '月', '会员部', '门店/活动', 30, 15, '2026-04-01'::date, 'campaign_new_customer_rate', '优惠', 'higher_better', '%', '门店/活动', 'v1.0', true),
  ('SKU有效数', '在售且月实收>1000的SKU数量', 'count(sku_code WHERE status=在售 AND received_amount>1000)', 'fact_bill_item JOIN dim_sku', '月', '商品部', '公司/区域', 900, 700, '2026-04-01'::date, 'effective_sku_count', '商品', 'lower_better', '个', '公司/区域', 'v1.0', true),
  ('长尾SKU占比', 'C类SKU数占总SKU数比例', 'count(abc_class=C)/count(*)*100', 'dim_sku', '月', '商品部', '公司', 50, 60, '2026-04-01'::date, 'longtail_sku_ratio', '商品', 'lower_better', '%', '公司', 'v1.0', true)
) AS t(indicator_name, business_definition, formula, data_source, update_frequency, owner, scope, yellow_threshold, red_threshold, version_date, metric_code, metric_category, direction, unit, granularity, version, is_active)
ON CONFLICT (indicator_name) DO UPDATE SET
  metric_code = EXCLUDED.metric_code,
  metric_category = EXCLUDED.metric_category,
  direction = EXCLUDED.direction,
  unit = EXCLUDED.unit,
  granularity = EXCLUDED.granularity,
  version = EXCLUDED.version,
  is_active = EXCLUDED.is_active,
  business_definition = EXCLUDED.business_definition,
  formula = EXCLUDED.formula,
  yellow_threshold = EXCLUDED.yellow_threshold,
  red_threshold = EXCLUDED.red_threshold;

-- ============================================================
-- 十、创建数据质量检查日志表
-- ============================================================

CREATE TABLE IF NOT EXISTS analytics.data_quality_log (
  id               BIGSERIAL PRIMARY KEY,
  check_date       DATE NOT NULL DEFAULT CURRENT_DATE,
  check_type       TEXT NOT NULL,  -- bill/dish/inventory/mapping
  total_records    BIGINT,
  issue_count      INTEGER DEFAULT 0,
  issue_details    JSONB,
  status           TEXT DEFAULT '通过',  -- 通过/警告/失败
  checked_by       TEXT DEFAULT 'system',
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- ============================================================
-- 十一、创建口径变更日志表
-- ============================================================

CREATE TABLE IF NOT EXISTS analytics.metric_version_log (
  id               BIGSERIAL PRIMARY KEY,
  metric_code      TEXT NOT NULL,
  version          TEXT NOT NULL,
  change_date      DATE NOT NULL DEFAULT CURRENT_DATE,
  change_reason    TEXT NOT NULL,
  old_formula      TEXT,
  new_formula      TEXT,
  impact_amount    NUMERIC,
  changed_by       TEXT,
  created_at       TIMESTAMPTZ DEFAULT now()
);

-- ============================================================
-- 完成提示
-- ============================================================
DO $$
BEGIN
  RAISE NOTICE '本体标准数据库表创建完成';
END $$;
