CREATE DATABASE IF NOT EXISTS lifanghe_education DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE lifanghe_education; CREATE TABLE IF NOT EXISTS roles ( role VARCHAR(40) PRIMARY KEY, name VARCHAR(80) NOT NULL, permissions JSON NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS users ( id VARCHAR(40) PRIMARY KEY, username VARCHAR(80) NOT NULL UNIQUE, password_hash CHAR(64) NULL, role VARCHAR(40) NOT NULL, role_name VARCHAR(80) NOT NULL, name VARCHAR(80) NOT NULL, phone VARCHAR(30) NULL UNIQUE, wechat_openid VARCHAR(80) NULL UNIQUE, wechat_unionid VARCHAR(80) NULL UNIQUE, wechat_nickname VARCHAR(80) NULL, wechat_avatar VARCHAR(500) NULL, login_provider VARCHAR(30) NOT NULL DEFAULT 'password', phone_bound TINYINT(1) NOT NULL DEFAULT 1, phone_bound_at VARCHAR(32) NULL, wechat_bound_at VARCHAR(32) NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', last_login_at VARCHAR(32) NULL, inviter_code VARCHAR(40) NULL UNIQUE, child_id VARCHAR(40) NULL, child_name VARCHAR(80) NULL, child_grade VARCHAR(40) NULL, child_focus VARCHAR(80) NULL, trial_booking_limit INT NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_users_role (role), INDEX idx_users_phone (phone), INDEX idx_users_login_provider (login_provider), CONSTRAINT fk_users_role FOREIGN KEY (role) REFERENCES roles(role) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS point_accounts ( user_id VARCHAR(40) PRIMARY KEY, available INT NOT NULL DEFAULT 0, pending INT NOT NULL DEFAULT 0, frozen INT NOT NULL DEFAULT 0, expiring INT NOT NULL DEFAULT 0, CONSTRAINT fk_point_accounts_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS point_transactions ( id VARCHAR(40) PRIMARY KEY, user_id VARCHAR(40) NOT NULL, title VARCHAR(200) NOT NULL, amount INT NOT NULL, status VARCHAR(30) NOT NULL, reference_type VARCHAR(40) NULL, reference_id VARCHAR(60) NULL, created_at VARCHAR(32) NOT NULL, INDEX idx_point_transactions_user (user_id), INDEX idx_point_transactions_reference (reference_type, reference_id), CONSTRAINT fk_point_transactions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS point_expiry_rule ( id INT PRIMARY KEY DEFAULT 1, title VARCHAR(100) NOT NULL, valid_days INT NOT NULL, description VARCHAR(500) NOT NULL, reminder VARCHAR(500) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS point_expiry_schedules ( id VARCHAR(40) PRIMARY KEY, user_id VARCHAR(40) NOT NULL, points INT NOT NULL, expire_at VARCHAR(32) NOT NULL, source VARCHAR(200) NOT NULL, INDEX idx_point_expiry_user (user_id), CONSTRAINT fk_point_expiry_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS referral_rules ( event VARCHAR(40) PRIMARY KEY, title VARCHAR(100) NOT NULL, inviter INT NOT NULL, invitee INT NOT NULL, sort_order INT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS referrals ( id VARCHAR(40) PRIMARY KEY, inviter_id VARCHAR(40) NOT NULL, invitee_id VARCHAR(40) NULL, name VARCHAR(80) NOT NULL, phone VARCHAR(30) NOT NULL, child_grade VARCHAR(40) NOT NULL, status VARCHAR(40) NOT NULL, reward INT NOT NULL DEFAULT 0, channel VARCHAR(80) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_referrals_inviter (inviter_id), CONSTRAINT fk_referrals_inviter FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS courses ( id VARCHAR(40) PRIMARY KEY, name VARCHAR(120) NOT NULL, tags JSON NOT NULL, next_time VARCHAR(32) NOT NULL, points_back INT NOT NULL DEFAULT 0, seats INT NOT NULL DEFAULT 0, campus VARCHAR(80) NOT NULL, teacher VARCHAR(80) NOT NULL, trial_minutes INT NOT NULL DEFAULT 45, status VARCHAR(30) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS appointments ( id VARCHAR(40) PRIMARY KEY, user_id VARCHAR(40) NOT NULL, child_id VARCHAR(40) NULL, course_id VARCHAR(40) NOT NULL, status VARCHAR(30) NOT NULL, created_at VARCHAR(40) NOT NULL, INDEX idx_appointments_user (user_id), INDEX idx_appointments_course (course_id), CONSTRAINT fk_appointments_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_appointments_course FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS rewards ( id VARCHAR(40) PRIMARY KEY, name VARCHAR(120) NOT NULL, cost INT NOT NULL, type VARCHAR(60) NOT NULL, stock INT NOT NULL DEFAULT 0, description VARCHAR(500) NOT NULL DEFAULT '' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS redemption_orders ( id VARCHAR(40) PRIMARY KEY, user_id VARCHAR(40) NOT NULL, item_id VARCHAR(40) NOT NULL, status VARCHAR(40) NOT NULL, cost_points INT NOT NULL DEFAULT 0, voucher_code VARCHAR(60) NULL UNIQUE, voucher_status VARCHAR(40) NOT NULL DEFAULT 'not_issued', created_at VARCHAR(40) NOT NULL, reviewed_by VARCHAR(40) NULL, reviewed_at VARCHAR(40) NULL, rejected_reason VARCHAR(255) NULL, expire_at VARCHAR(40) NULL, used_at VARCHAR(40) NULL, used_by VARCHAR(40) NULL, verify_note VARCHAR(255) NULL, INDEX idx_redemption_user (user_id), INDEX idx_redemption_status (status), INDEX idx_redemption_voucher (voucher_code), CONSTRAINT fk_redemption_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_redemption_reward FOREIGN KEY (item_id) REFERENCES rewards(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS leads ( id VARCHAR(40) PRIMARY KEY, referral_id VARCHAR(40) NULL, invitee_id VARCHAR(40) NULL, parent VARCHAR(80) NOT NULL, source VARCHAR(100) NOT NULL, stage VARCHAR(40) NOT NULL, advisor VARCHAR(80) NOT NULL, risk VARCHAR(40) NOT NULL DEFAULT '正常', channel VARCHAR(80) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_leads_referral (referral_id), INDEX idx_leads_invitee (invitee_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS channel_stats ( channel VARCHAR(80) PRIMARY KEY, leads INT NOT NULL DEFAULT 0, attended INT NOT NULL DEFAULT 0, purchased INT NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO roles (role, name, permissions) VALUES ('parent', '家长', JSON_ARRAY()), ('super_admin', '超级管理员', JSON_ARRAY('*:*:*')), ('campus_admin', '校区管理员', JSON_ARRAY('dashboard:overview:view', 'system:user:view', 'system:role:view', 'leads:referral:view', 'leads:referral:advance', 'points:account:view', 'points:rule:edit', 'points:redemption:view', 'points:redemption:review', 'points:voucher:verify', 'courses:trial:view', 'courses:trial:add', 'courses:trial:edit', 'statistics:channel:view')), ('advisor', '课程顾问', JSON_ARRAY('dashboard:overview:view', 'leads:referral:view', 'leads:referral:advance', 'courses:trial:view', 'points:account:view', 'points:redemption:view')), ('teacher', '老师', JSON_ARRAY('dashboard:overview:view', 'leads:referral:view', 'courses:trial:view')), ('finance', '财务', JSON_ARRAY('dashboard:overview:view', 'points:account:view', 'points:account:adjust', 'points:rule:edit', 'points:reward:edit', 'points:redemption:view', 'points:redemption:review', 'points:voucher:verify', 'statistics:channel:view')) ON DUPLICATE KEY UPDATE name = VALUES(name), permissions = VALUES(permissions); INSERT INTO users ( id, username, password_hash, role, role_name, name, phone, wechat_openid, wechat_unionid, wechat_nickname, wechat_avatar, login_provider, phone_bound, phone_bound_at, wechat_bound_at, status, last_login_at, inviter_code, child_id, child_name, child_grade, child_focus ) VALUES ('admin_001', 'admin', '240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9', 'super_admin', '超级管理员', '系统管理员', '13800009999', NULL, NULL, NULL, NULL, 'password', 1, NULL, NULL, 'active', NULL, NULL, NULL, NULL, NULL, NULL) ON DUPLICATE KEY UPDATE username = VALUES(username), password_hash = VALUES(password_hash), role = VALUES(role), role_name = VALUES(role_name), name = VALUES(name), phone = VALUES(phone), login_provider = VALUES(login_provider), phone_bound = VALUES(phone_bound), status = VALUES(status); INSERT INTO point_expiry_rule (id, title, valid_days, description, reminder) VALUES (1, '积分有效期说明', 180, '积分自实际到账日起 180 天内有效,仅可兑换课程、测评和学习服务权益,不可提现或转赠。', '系统会在到期前 30 天进入“将过期”口径,运营可通过积分管理页提醒家长尽快兑换。') ON DUPLICATE KEY UPDATE title = VALUES(title), valid_days = VALUES(valid_days), description = VALUES(description), reminder = VALUES(reminder); INSERT INTO referral_rules (event, title, inviter, invitee, sort_order) VALUES ('registered', '新家长注册', 20, 20, 1), ('booked', '预约试听', 50, 50, 2), ('attended', '确认到课', 150, 100, 3), ('purchased', '报名正价课', 800, 500, 4) ON DUPLICATE KEY UPDATE title = VALUES(title), inviter = VALUES(inviter), invitee = VALUES(invitee), sort_order = VALUES(sort_order); INSERT INTO rewards (id, name, cost, type, stock, description) VALUES ('reward_001', '精品体验课 1 次', 800, '课程权益', 30, '适用于校区公开试听课,审核通过后生成课程券码,30 天内到店核销。'), ('reward_002', '学习规划咨询', 500, '服务权益', 50, '由课程顾问提供一次学习规划沟通,适合新生测评后使用。'), ('reward_003', '正价课抵扣券 100 元', 1000, '抵扣券', 20, '报名正价课程时抵扣 100 元,不可叠加现金活动使用。'), ('reward_004', '阶段测评报告', 300, '测评', 80, '兑换后安排一次阶段测评报告解读,帮助家长了解学习状态。') ON DUPLICATE KEY UPDATE name = VALUES(name), cost = VALUES(cost), type = VALUES(type), stock = VALUES(stock), description = VALUES(description);