consumer模块完成度95%,检查消费者前端bug并修复

This commit is contained in:
cyh666666
2026-03-09 17:20:59 +08:00
parent 7b5801a72b
commit 2262d1bfd9
128 changed files with 13485 additions and 1670 deletions

View File

@@ -2,22 +2,29 @@
-- 推销模式功能 - 数据库脚本(第一阶段)
-- 包含:用户余额系统、分享免单系统、会员等级系统
-- 创建日期: 2026-03-06
-- 注意请在Supabase SQL编辑器中分段执行
-- =====================================================
-- =====================================================
-- 0. 启用必要扩展
-- =====================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- =====================================================
-- 一、用户余额系统
-- =====================================================
-- 1. 用户余额表
CREATE TABLE IF NOT EXISTS ml_user_balance (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
DROP TABLE IF EXISTS ml_user_balance CASCADE;
CREATE TABLE ml_user_balance (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
balance DECIMAL(10,2) DEFAULT 0,
frozen_balance DECIMAL(10,2) DEFAULT 0,
total_earned DECIMAL(10,2) DEFAULT 0,
total_withdrawn DECIMAL(10,2) DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id)
);
@@ -30,16 +37,17 @@ COMMENT ON COLUMN ml_user_balance.total_earned IS '累计获得';
COMMENT ON COLUMN ml_user_balance.total_withdrawn IS '累计提现';
-- 2. 余额变动记录表
CREATE TABLE IF NOT EXISTS ml_balance_records (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
DROP TABLE IF EXISTS ml_balance_records CASCADE;
CREATE TABLE ml_balance_records (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
type VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
balance_before DECIMAL(10,2) NOT NULL DEFAULT 0,
balance_after DECIMAL(10,2) NOT NULL DEFAULT 0,
related_id UUID,
description VARCHAR(200),
operator_id UUID REFERENCES auth.users(id),
operator_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
@@ -55,11 +63,12 @@ COMMENT ON COLUMN ml_balance_records.type IS '类型free_order-免单奖励
-- =====================================================
-- 1. 分享记录表
CREATE TABLE IF NOT EXISTS ml_share_records (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
product_id UUID NOT NULL REFERENCES ml_products(id),
order_id UUID NOT NULL REFERENCES ml_orders(id),
DROP TABLE IF EXISTS ml_share_records CASCADE;
CREATE TABLE ml_share_records (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
product_id UUID NOT NULL,
order_id UUID NOT NULL,
order_item_id UUID,
share_code VARCHAR(20) NOT NULL UNIQUE,
product_name VARCHAR(200),
@@ -82,15 +91,15 @@ COMMENT ON TABLE ml_share_records IS '分享免单记录表';
COMMENT ON COLUMN ml_share_records.status IS '状态0-进行中1-已完成2-已失效3-已过期';
-- 2. 二级购买记录表
CREATE TABLE IF NOT EXISTS ml_secondary_purchases (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
share_record_id UUID NOT NULL REFERENCES ml_share_records(id) ON DELETE CASCADE,
buyer_id UUID NOT NULL REFERENCES auth.users(id),
order_id UUID NOT NULL REFERENCES ml_orders(id),
DROP TABLE IF EXISTS ml_secondary_purchases CASCADE;
CREATE TABLE ml_secondary_purchases (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
share_record_id UUID NOT NULL,
buyer_id UUID NOT NULL,
order_id UUID NOT NULL,
quantity INT DEFAULT 1,
unit_price DECIMAL(10,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(order_id, share_record_id)
);
@@ -100,15 +109,16 @@ CREATE INDEX IF NOT EXISTS idx_secondary_purchases_buyer_id ON ml_secondary_purc
COMMENT ON TABLE ml_secondary_purchases IS '二级用户购买记录表';
-- 3. 免单奖励记录表
CREATE TABLE IF NOT EXISTS ml_free_order_rewards (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
share_record_id UUID NOT NULL REFERENCES ml_share_records(id),
DROP TABLE IF EXISTS ml_free_order_rewards CASCADE;
CREATE TABLE ml_free_order_rewards (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
share_record_id UUID NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status INT DEFAULT 0,
balance_record_id UUID REFERENCES ml_balance_records(id),
balance_record_id UUID,
cleared_at TIMESTAMPTZ,
cleared_by UUID REFERENCES auth.users(id),
cleared_by UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
@@ -123,7 +133,8 @@ COMMENT ON COLUMN ml_free_order_rewards.status IS '状态0-待发放1-已
-- =====================================================
-- 1. 会员等级配置表
CREATE TABLE IF NOT EXISTS ml_member_levels (
DROP TABLE IF EXISTS ml_member_levels CASCADE;
CREATE TABLE ml_member_levels (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
min_amount DECIMAL(10,2) DEFAULT 0,
@@ -146,29 +157,17 @@ INSERT INTO ml_member_levels (id, name, min_amount, discount, description, sort_
(2, '银牌会员', 2000, 0.9500, '累计消费2000元升级', 2),
(3, '金牌会员', 5000, 0.9200, '累计消费5000元升级', 3),
(4, '钻石会员', 10000, 0.8800, '累计消费10000元升级', 4),
(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5)
ON CONFLICT (id) DO NOTHING;
(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5);
-- 2. 用户会员信息扩展字段(添加到 ml_user_profiles
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS member_level INT DEFAULT 0;
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS total_spent DECIMAL(10,2) DEFAULT 0;
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS level_updated_at TIMESTAMPTZ;
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level BOOLEAN DEFAULT FALSE;
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level_by UUID;
ALTER TABLE ml_user_profiles ADD COLUMN IF NOT EXISTS manual_level_at TIMESTAMPTZ;
COMMENT ON COLUMN ml_user_profiles.member_level IS '当前会员等级';
COMMENT ON COLUMN ml_user_profiles.total_spent IS '累计消费金额';
COMMENT ON COLUMN ml_user_profiles.manual_level IS '是否手动设置等级';
-- 3. 会员等级变更记录表
CREATE TABLE IF NOT EXISTS ml_member_level_logs (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id),
-- 2. 会员等级变更记录表
DROP TABLE IF EXISTS ml_member_level_logs CASCADE;
CREATE TABLE ml_member_level_logs (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
old_level INT DEFAULT 0,
new_level INT NOT NULL,
reason VARCHAR(200),
operator_id UUID REFERENCES auth.users(id),
operator_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
@@ -183,16 +182,19 @@ COMMENT ON TABLE ml_member_level_logs IS '会员等级变更记录表';
-- 用户余额表 RLS
ALTER TABLE ml_user_balance ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own balance" ON ml_user_balance;
CREATE POLICY "Users can view own balance"
ON ml_user_balance FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own balance" ON ml_user_balance;
CREATE POLICY "Users can insert own balance"
ON ml_user_balance FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can update own balance" ON ml_user_balance;
CREATE POLICY "Users can update own balance"
ON ml_user_balance FOR UPDATE
TO authenticated
@@ -201,11 +203,13 @@ USING (auth.uid() = user_id);
-- 余额记录表 RLS
ALTER TABLE ml_balance_records ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own balance records" ON ml_balance_records;
CREATE POLICY "Users can view own balance records"
ON ml_balance_records FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own balance records" ON ml_balance_records;
CREATE POLICY "Users can insert own balance records"
ON ml_balance_records FOR INSERT
TO authenticated
@@ -214,22 +218,26 @@ WITH CHECK (auth.uid() = user_id);
-- 分享记录表 RLS
ALTER TABLE ml_share_records ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own share records" ON ml_share_records;
CREATE POLICY "Users can view own share records"
ON ml_share_records FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own share records" ON ml_share_records;
CREATE POLICY "Users can insert own share records"
ON ml_share_records FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can update own share records" ON ml_share_records;
CREATE POLICY "Users can update own share records"
ON ml_share_records FOR UPDATE
TO authenticated
USING (auth.uid() = user_id);
-- 允许通过分享码查询(用于验证分享码)
-- 允许通过分享码查询
DROP POLICY IF EXISTS "Anyone can view by share code" ON ml_share_records;
CREATE POLICY "Anyone can view by share code"
ON ml_share_records FOR SELECT
TO authenticated, anon
@@ -238,6 +246,7 @@ USING (share_code IS NOT NULL);
-- 二级购买记录表 RLS
ALTER TABLE ml_secondary_purchases ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own secondary purchases" ON ml_secondary_purchases;
CREATE POLICY "Users can view own secondary purchases"
ON ml_secondary_purchases FOR SELECT
TO authenticated
@@ -245,6 +254,7 @@ USING (buyer_id = auth.uid() OR EXISTS (
SELECT 1 FROM ml_share_records WHERE id = share_record_id AND user_id = auth.uid()
));
DROP POLICY IF EXISTS "Users can insert secondary purchases" ON ml_secondary_purchases;
CREATE POLICY "Users can insert secondary purchases"
ON ml_secondary_purchases FOR INSERT
TO authenticated
@@ -253,14 +263,16 @@ WITH CHECK (true);
-- 免单奖励记录表 RLS
ALTER TABLE ml_free_order_rewards ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own rewards" ON ml_free_order_rewards;
CREATE POLICY "Users can view own rewards"
ON ml_free_order_rewards FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- 会员等级配置表 RLS(所有人可查看)
-- 会员等级配置表 RLS
ALTER TABLE ml_member_levels ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Anyone can view member levels" ON ml_member_levels;
CREATE POLICY "Anyone can view member levels"
ON ml_member_levels FOR SELECT
TO authenticated, anon
@@ -269,302 +281,8 @@ USING (status = 1);
-- 会员等级变更记录表 RLS
ALTER TABLE ml_member_level_logs ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own level logs" ON ml_member_level_logs;
CREATE POLICY "Users can view own level logs"
ON ml_member_level_logs FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- =====================================================
-- 五、数据库函数
-- =====================================================
-- 1. 生成分享码函数
CREATE OR REPLACE FUNCTION generate_share_code()
RETURNS VARCHAR(20) AS $$
DECLARE
chars TEXT := 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
result VARCHAR(20) := '';
i INT;
BEGIN
FOR i IN 1..8 LOOP
result := result || substr(chars, floor(random() * length(chars) + 1)::int, 1);
END LOOP;
RETURN result;
END;
$$ LANGUAGE plpgsql;
-- 2. 增加余额函数
CREATE OR REPLACE FUNCTION add_user_balance(
p_user_id UUID,
p_amount DECIMAL(10,2),
p_type VARCHAR(50),
p_related_id UUID DEFAULT NULL,
p_description VARCHAR(200) DEFAULT NULL,
p_operator_id UUID DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
v_balance_before DECIMAL(10,2);
v_balance_after DECIMAL(10,2);
BEGIN
-- 获取当前余额
SELECT COALESCE(balance, 0) INTO v_balance_before
FROM ml_user_balance
WHERE user_id = p_user_id;
IF v_balance_before IS NULL THEN
-- 创建余额记录
INSERT INTO ml_user_balance (user_id, balance, total_earned)
VALUES (p_user_id, p_amount, p_amount);
v_balance_before := 0;
v_balance_after := p_amount;
ELSE
-- 更新余额
UPDATE ml_user_balance
SET balance = balance + p_amount,
total_earned = total_earned + CASE WHEN p_amount > 0 THEN p_amount ELSE 0 END,
updated_at = NOW()
WHERE user_id = p_user_id;
v_balance_after := v_balance_before + p_amount;
END IF;
-- 记录变动
INSERT INTO ml_balance_records (user_id, type, amount, balance_before, balance_after, related_id, description, operator_id)
VALUES (p_user_id, p_type, p_amount, v_balance_before, v_balance_after, p_related_id, p_description, p_operator_id);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 3. 清零用户余额函数
CREATE OR REPLACE FUNCTION clear_user_balance(
p_user_id UUID,
p_operator_id UUID,
p_description VARCHAR(200) DEFAULT '商家清零余额'
)
RETURNS BOOLEAN AS $$
DECLARE
v_balance_before DECIMAL(10,2);
BEGIN
-- 获取当前余额
SELECT balance INTO v_balance_before
FROM ml_user_balance
WHERE user_id = p_user_id;
IF v_balance_before IS NULL OR v_balance_before = 0 THEN
RETURN FALSE;
END IF;
-- 清零余额
UPDATE ml_user_balance
SET balance = 0,
updated_at = NOW()
WHERE user_id = p_user_id;
-- 记录变动
INSERT INTO ml_balance_records (user_id, type, amount, balance_before, balance_after, description, operator_id)
VALUES (p_user_id, 'clear', -v_balance_before, v_balance_before, 0, p_description, p_operator_id);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 4. 检查并升级会员等级函数
CREATE OR REPLACE FUNCTION check_and_upgrade_member_level(p_user_id UUID)
RETURNS INT AS $$
DECLARE
v_total_spent DECIMAL(10,2);
v_current_level INT;
v_new_level INT;
v_manual_level BOOLEAN;
BEGIN
-- 获取用户当前信息
SELECT member_level, total_spent, manual_level
INTO v_current_level, v_total_spent, v_manual_level
FROM ml_user_profiles
WHERE user_id = p_user_id;
-- 如果是手动设置的等级,不自动升级
IF v_manual_level = TRUE THEN
RETURN v_current_level;
END IF;
-- 根据消费金额计算新等级
SELECT id INTO v_new_level
FROM ml_member_levels
WHERE status = 1 AND min_amount <= COALESCE(v_total_spent, 0)
ORDER BY min_amount DESC
LIMIT 1;
IF v_new_level IS NULL THEN
v_new_level := 0;
END IF;
-- 如果等级有变化,更新并记录
IF v_new_level > COALESCE(v_current_level, 0) THEN
UPDATE ml_user_profiles
SET member_level = v_new_level,
level_updated_at = NOW()
WHERE user_id = p_user_id;
INSERT INTO ml_member_level_logs (user_id, old_level, new_level, reason)
VALUES (p_user_id, COALESCE(v_current_level, 0), v_new_level, '累计消费自动升级');
RETURN v_new_level;
END IF;
RETURN COALESCE(v_current_level, 0);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 5. 处理分享购买函数
CREATE OR REPLACE FUNCTION process_share_purchase(
p_share_code VARCHAR(20),
p_buyer_id UUID,
p_order_id UUID,
p_quantity INT,
p_unit_price DECIMAL(10,2)
)
RETURNS BOOLEAN AS $$
DECLARE
v_share_record ml_share_records%ROWTYPE;
v_required_count INT;
v_reward_amount DECIMAL(10,2);
BEGIN
-- 查找分享记录
SELECT * INTO v_share_record
FROM ml_share_records
WHERE share_code = p_share_code AND status = 0;
IF NOT FOUND THEN
RETURN FALSE;
END IF;
-- 不能购买自己分享的商品
IF v_share_record.user_id = p_buyer_id THEN
RETURN FALSE;
END IF;
-- 检查是否已购买过(同一用户对同一分享记录只算一次)
IF EXISTS (SELECT 1 FROM ml_secondary_purchases WHERE share_record_id = v_share_record.id AND buyer_id = p_buyer_id) THEN
RETURN FALSE;
END IF;
-- 记录购买
INSERT INTO ml_secondary_purchases (share_record_id, buyer_id, order_id, quantity, unit_price)
VALUES (v_share_record.id, p_buyer_id, p_order_id, p_quantity, p_unit_price);
-- 更新计数
UPDATE ml_share_records
SET current_count = current_count + 1
WHERE id = v_share_record.id;
-- 检查是否达标
SELECT current_count, required_count INTO v_required_count, v_required_count
FROM ml_share_records WHERE id = v_share_record.id;
IF v_share_record.current_count + 1 >= v_share_record.required_count THEN
-- 计算奖励金额
v_reward_amount := v_share_record.product_price;
-- 更新分享记录状态
UPDATE ml_share_records
SET status = 1, completed_at = NOW(), reward_amount = v_reward_amount
WHERE id = v_share_record.id;
-- 创建奖励记录
INSERT INTO ml_free_order_rewards (user_id, share_record_id, amount)
VALUES (v_share_record.user_id, v_share_record.id, v_reward_amount);
-- 增加用户余额
PERFORM add_user_balance(
v_share_record.user_id,
v_reward_amount,
'free_order',
v_share_record.id,
'分享免单奖励:' || v_share_record.product_name
);
-- 更新奖励记录状态
UPDATE ml_free_order_rewards
SET status = 1
WHERE share_record_id = v_share_record.id;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- 六、触发器
-- =====================================================
-- 订单完成后更新用户累计消费并检查会员等级
CREATE OR REPLACE FUNCTION update_user_total_spent()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.status = 4 AND (OLD.status != 4 OR OLD.status IS NULL) THEN
-- 更新累计消费
UPDATE ml_user_profiles
SET total_spent = COALESCE(total_spent, 0) + NEW.total_amount
WHERE user_id = NEW.user_id;
-- 检查会员等级
PERFORM check_and_upgrade_member_level(NEW.user_id);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器(如果不存在)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_update_user_total_spent'
) THEN
CREATE TRIGGER trigger_update_user_total_spent
AFTER UPDATE ON ml_orders
FOR EACH ROW
EXECUTE FUNCTION update_user_total_spent();
END IF;
END $$;
-- =====================================================
-- 七、视图
-- =====================================================
-- 用户余额概览视图
CREATE OR REPLACE VIEW ml_user_balance_overview AS
SELECT
ub.user_id,
ub.balance,
ub.frozen_balance,
ub.total_earned,
ub.total_withdrawn,
up.member_level,
ml.name as member_level_name,
ml.discount as member_discount,
up.total_spent
FROM ml_user_balance ub
LEFT JOIN ml_user_profiles up ON ub.user_id = up.user_id
LEFT JOIN ml_member_levels ml ON up.member_level = ml.id;
-- 分享进度视图
CREATE OR REPLACE VIEW ml_share_progress_view AS
SELECT
sr.id,
sr.user_id,
sr.product_id,
sr.product_name,
sr.product_image,
sr.product_price,
sr.share_code,
sr.required_count,
sr.current_count,
sr.status,
sr.reward_amount,
sr.created_at,
sr.completed_at,
sr.required_count - sr.current_count as remaining_count
FROM ml_share_records sr;

View File

@@ -0,0 +1,453 @@
-- =====================================================
-- 推销模式功能 - 数据库脚本 V2商家级别
-- 包含:商家推销配置、用户余额系统、分享免单系统、会员等级系统
-- 创建日期: 2026-03-09
-- 重要变更:推销模式改为商家级别,每个商家独立配置
-- =====================================================
-- =====================================================
-- 0. 启用必要扩展
-- =====================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- =====================================================
-- 一、商家推销配置表
-- =====================================================
DROP TABLE IF EXISTS ml_merchant_promotion_config CASCADE;
CREATE TABLE ml_merchant_promotion_config (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
merchant_id UUID NOT NULL UNIQUE,
promotion_enabled BOOLEAN DEFAULT FALSE,
share_free_enabled BOOLEAN DEFAULT FALSE,
distribution_enabled BOOLEAN DEFAULT FALSE,
required_count INT DEFAULT 4,
reward_type VARCHAR(20) DEFAULT 'product_price',
fixed_reward_amount DECIMAL(10,2) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_merchant_promotion_merchant_id ON ml_merchant_promotion_config(merchant_id);
COMMENT ON TABLE ml_merchant_promotion_config IS '商家推销模式配置表';
COMMENT ON COLUMN ml_merchant_promotion_config.promotion_enabled IS '是否开启推销模式';
COMMENT ON COLUMN ml_merchant_promotion_config.share_free_enabled IS '是否开启分享免单';
COMMENT ON COLUMN ml_merchant_promotion_config.distribution_enabled IS '是否开启经销点返利';
COMMENT ON COLUMN ml_merchant_promotion_config.required_count IS '分享免单所需购买数';
COMMENT ON COLUMN ml_merchant_promotion_config.reward_type IS '奖励类型product_price-商品价格fixed-固定金额';
-- =====================================================
-- 二、用户余额系统(按商家区分)
-- =====================================================
-- 1. 商家用户余额表
DROP TABLE IF EXISTS ml_merchant_user_balance CASCADE;
CREATE TABLE ml_merchant_user_balance (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
merchant_id UUID NOT NULL,
user_id UUID NOT NULL,
balance DECIMAL(10,2) DEFAULT 0,
frozen_balance DECIMAL(10,2) DEFAULT 0,
total_earned DECIMAL(10,2) DEFAULT 0,
total_withdrawn DECIMAL(10,2) DEFAULT 0,
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(merchant_id, user_id)
);
CREATE INDEX IF NOT EXISTS idx_merchant_user_balance_merchant_id ON ml_merchant_user_balance(merchant_id);
CREATE INDEX IF NOT EXISTS idx_merchant_user_balance_user_id ON ml_merchant_user_balance(user_id);
COMMENT ON TABLE ml_merchant_user_balance IS '商家用户余额表(每个商家独立余额)';
-- 2. 余额变动记录表
DROP TABLE IF EXISTS ml_balance_records CASCADE;
CREATE TABLE ml_balance_records (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
merchant_id UUID NOT NULL,
user_id UUID NOT NULL,
type VARCHAR(50) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
balance_before DECIMAL(10,2) NOT NULL DEFAULT 0,
balance_after DECIMAL(10,2) NOT NULL DEFAULT 0,
related_id UUID,
description VARCHAR(200),
operator_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_balance_records_merchant_id ON ml_balance_records(merchant_id);
CREATE INDEX IF NOT EXISTS idx_balance_records_user_id ON ml_balance_records(user_id);
CREATE INDEX IF NOT EXISTS idx_balance_records_type ON ml_balance_records(type);
CREATE INDEX IF NOT EXISTS idx_balance_records_created_at ON ml_balance_records(created_at);
COMMENT ON TABLE ml_balance_records IS '余额变动记录表';
COMMENT ON COLUMN ml_balance_records.type IS '类型free_order-免单奖励rebate-返利withdraw-提现clear-清零manual-手动调整';
-- =====================================================
-- 三、分享免单系统
-- =====================================================
-- 1. 分享记录表
DROP TABLE IF EXISTS ml_share_records CASCADE;
CREATE TABLE ml_share_records (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
merchant_id UUID NOT NULL,
user_id UUID NOT NULL,
product_id UUID NOT NULL,
order_id UUID NOT NULL,
order_item_id UUID,
share_code VARCHAR(20) NOT NULL UNIQUE,
product_name VARCHAR(200),
product_image VARCHAR(500),
product_price DECIMAL(10,2),
required_count INT DEFAULT 4,
current_count INT DEFAULT 0,
status INT DEFAULT 0,
reward_amount DECIMAL(10,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
completed_at TIMESTAMPTZ,
expired_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_share_records_merchant_id ON ml_share_records(merchant_id);
CREATE INDEX IF NOT EXISTS idx_share_records_user_id ON ml_share_records(user_id);
CREATE INDEX IF NOT EXISTS idx_share_records_share_code ON ml_share_records(share_code);
CREATE INDEX IF NOT EXISTS idx_share_records_status ON ml_share_records(status);
COMMENT ON TABLE ml_share_records IS '分享免单记录表';
COMMENT ON COLUMN ml_share_records.status IS '状态0-进行中1-已完成2-已失效3-已过期';
-- 2. 二级购买记录表
DROP TABLE IF EXISTS ml_secondary_purchases CASCADE;
CREATE TABLE ml_secondary_purchases (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
share_record_id UUID NOT NULL,
buyer_id UUID NOT NULL,
order_id UUID NOT NULL,
quantity INT DEFAULT 1,
unit_price DECIMAL(10,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(order_id, share_record_id)
);
CREATE INDEX IF NOT EXISTS idx_secondary_purchases_share_record_id ON ml_secondary_purchases(share_record_id);
CREATE INDEX IF NOT EXISTS idx_secondary_purchases_buyer_id ON ml_secondary_purchases(buyer_id);
COMMENT ON TABLE ml_secondary_purchases IS '二级用户购买记录表';
-- 3. 免单奖励记录表
DROP TABLE IF EXISTS ml_free_order_rewards CASCADE;
CREATE TABLE ml_free_order_rewards (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
merchant_id UUID NOT NULL,
user_id UUID NOT NULL,
share_record_id UUID NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status INT DEFAULT 0,
balance_record_id UUID,
cleared_at TIMESTAMPTZ,
cleared_by UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_free_order_rewards_merchant_id ON ml_free_order_rewards(merchant_id);
CREATE INDEX IF NOT EXISTS idx_free_order_rewards_user_id ON ml_free_order_rewards(user_id);
CREATE INDEX IF NOT EXISTS idx_free_order_rewards_status ON ml_free_order_rewards(status);
COMMENT ON TABLE ml_free_order_rewards IS '免单奖励记录表';
COMMENT ON COLUMN ml_free_order_rewards.status IS '状态0-待发放1-已发放2-已清零';
-- =====================================================
-- 四、会员等级系统(全局)
-- =====================================================
-- 1. 会员等级配置表
DROP TABLE IF EXISTS ml_member_levels CASCADE;
CREATE TABLE ml_member_levels (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
min_amount DECIMAL(10,2) DEFAULT 0,
discount DECIMAL(5,4) DEFAULT 1.0000,
icon VARCHAR(200),
description TEXT,
sort_order INT DEFAULT 0,
status INT DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
COMMENT ON TABLE ml_member_levels IS '会员等级配置表';
COMMENT ON COLUMN ml_member_levels.discount IS '折扣率0.85表示85折';
-- 初始化会员等级数据
INSERT INTO ml_member_levels (id, name, min_amount, discount, description, sort_order) VALUES
(0, '普通会员', 0, 1.0000, '注册即可成为普通会员', 0),
(1, '铜牌会员', 500, 0.9800, '累计消费500元升级', 1),
(2, '银牌会员', 2000, 0.9500, '累计消费2000元升级', 2),
(3, '金牌会员', 5000, 0.9200, '累计消费5000元升级', 3),
(4, '钻石会员', 10000, 0.8800, '累计消费10000元升级', 4),
(5, 'VIP会员', 0, 0.8500, '商家特邀会员', 5);
-- 2. 会员等级变更记录表
DROP TABLE IF EXISTS ml_member_level_logs CASCADE;
CREATE TABLE ml_member_level_logs (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL,
old_level INT DEFAULT 0,
new_level INT NOT NULL,
reason VARCHAR(200),
operator_id UUID,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_member_level_logs_user_id ON ml_member_level_logs(user_id);
COMMENT ON TABLE ml_member_level_logs IS '会员等级变更记录表';
-- =====================================================
-- 五、RLS 策略
-- =====================================================
-- 商家推销配置表 RLS
ALTER TABLE ml_merchant_promotion_config ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Anyone can view merchant promotion config" ON ml_merchant_promotion_config;
CREATE POLICY "Anyone can view merchant promotion config"
ON ml_merchant_promotion_config FOR SELECT
TO authenticated, anon
USING (true);
-- 商家用户余额表 RLS
ALTER TABLE ml_merchant_user_balance ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own merchant balance" ON ml_merchant_user_balance;
CREATE POLICY "Users can view own merchant balance"
ON ml_merchant_user_balance FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own merchant balance" ON ml_merchant_user_balance;
CREATE POLICY "Users can insert own merchant balance"
ON ml_merchant_user_balance FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can update own merchant balance" ON ml_merchant_user_balance;
CREATE POLICY "Users can update own merchant balance"
ON ml_merchant_user_balance FOR UPDATE
TO authenticated
USING (auth.uid() = user_id);
-- 余额记录表 RLS
ALTER TABLE ml_balance_records ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own balance records" ON ml_balance_records;
CREATE POLICY "Users can view own balance records"
ON ml_balance_records FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own balance records" ON ml_balance_records;
CREATE POLICY "Users can insert own balance records"
ON ml_balance_records FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- 分享记录表 RLS
ALTER TABLE ml_share_records ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own share records" ON ml_share_records;
CREATE POLICY "Users can view own share records"
ON ml_share_records FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can insert own share records" ON ml_share_records;
CREATE POLICY "Users can insert own share records"
ON ml_share_records FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
DROP POLICY IF EXISTS "Users can update own share records" ON ml_share_records;
CREATE POLICY "Users can update own share records"
ON ml_share_records FOR UPDATE
TO authenticated
USING (auth.uid() = user_id);
-- 允许通过分享码查询
DROP POLICY IF EXISTS "Anyone can view by share code" ON ml_share_records;
CREATE POLICY "Anyone can view by share code"
ON ml_share_records FOR SELECT
TO authenticated, anon
USING (share_code IS NOT NULL);
-- 二级购买记录表 RLS
ALTER TABLE ml_secondary_purchases ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own secondary purchases" ON ml_secondary_purchases;
CREATE POLICY "Users can view own secondary purchases"
ON ml_secondary_purchases FOR SELECT
TO authenticated
USING (buyer_id = auth.uid() OR EXISTS (
SELECT 1 FROM ml_share_records WHERE id = share_record_id AND user_id = auth.uid()
));
DROP POLICY IF EXISTS "Users can insert secondary purchases" ON ml_secondary_purchases;
CREATE POLICY "Users can insert secondary purchases"
ON ml_secondary_purchases FOR INSERT
TO authenticated
WITH CHECK (true);
-- 免单奖励记录表 RLS
ALTER TABLE ml_free_order_rewards ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own rewards" ON ml_free_order_rewards;
CREATE POLICY "Users can view own rewards"
ON ml_free_order_rewards FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- 会员等级配置表 RLS
ALTER TABLE ml_member_levels ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Anyone can view member levels" ON ml_member_levels;
CREATE POLICY "Anyone can view member levels"
ON ml_member_levels FOR SELECT
TO authenticated, anon
USING (status = 1);
-- 会员等级变更记录表 RLS
ALTER TABLE ml_member_level_logs ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS "Users can view own level logs" ON ml_member_level_logs;
CREATE POLICY "Users can view own level logs"
ON ml_member_level_logs FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- =====================================================
-- 六、数据库函数
-- =====================================================
-- 1. 获取商家推销配置
CREATE OR REPLACE FUNCTION get_merchant_promotion_config(p_merchant_id UUID)
RETURNS TABLE (
promotion_enabled BOOLEAN,
share_free_enabled BOOLEAN,
distribution_enabled BOOLEAN,
required_count INT,
reward_type VARCHAR,
fixed_reward_amount DECIMAL
) AS $$
BEGIN
RETURN QUERY
SELECT
mpc.promotion_enabled,
mpc.share_free_enabled,
mpc.distribution_enabled,
mpc.required_count,
mpc.reward_type,
mpc.fixed_reward_amount
FROM ml_merchant_promotion_config mpc
WHERE mpc.merchant_id = p_merchant_id;
IF NOT FOUND THEN
RETURN QUERY SELECT FALSE, FALSE, FALSE, 4, 'product_price'::VARCHAR, 0::DECIMAL;
END IF;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 2. 检查商家是否开启分享免单
CREATE OR REPLACE FUNCTION is_share_free_enabled(p_merchant_id UUID)
RETURNS BOOLEAN AS $$
DECLARE
v_enabled BOOLEAN;
BEGIN
SELECT promotion_enabled AND share_free_enabled INTO v_enabled
FROM ml_merchant_promotion_config
WHERE merchant_id = p_merchant_id;
RETURN COALESCE(v_enabled, FALSE);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 3. 增加商家用户余额
CREATE OR REPLACE FUNCTION add_merchant_user_balance(
p_merchant_id UUID,
p_user_id UUID,
p_amount DECIMAL(10,2),
p_type VARCHAR(50),
p_related_id UUID DEFAULT NULL,
p_description VARCHAR(200) DEFAULT NULL,
p_operator_id UUID DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
v_balance_before DECIMAL(10,2);
v_balance_after DECIMAL(10,2);
BEGIN
SELECT COALESCE(balance, 0) INTO v_balance_before
FROM ml_merchant_user_balance
WHERE merchant_id = p_merchant_id AND user_id = p_user_id;
IF v_balance_before IS NULL THEN
INSERT INTO ml_merchant_user_balance (merchant_id, user_id, balance, total_earned)
VALUES (p_merchant_id, p_user_id, p_amount, p_amount);
v_balance_before := 0;
v_balance_after := p_amount;
ELSE
UPDATE ml_merchant_user_balance
SET balance = balance + p_amount,
total_earned = total_earned + CASE WHEN p_amount > 0 THEN p_amount ELSE 0 END,
updated_at = NOW()
WHERE merchant_id = p_merchant_id AND user_id = p_user_id;
v_balance_after := v_balance_before + p_amount;
END IF;
INSERT INTO ml_balance_records (merchant_id, user_id, type, amount, balance_before, balance_after, related_id, description, operator_id)
VALUES (p_merchant_id, p_user_id, p_type, p_amount, v_balance_before, v_balance_after, p_related_id, p_description, p_operator_id);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 4. 清零商家用户余额
CREATE OR REPLACE FUNCTION clear_merchant_user_balance(
p_merchant_id UUID,
p_user_id UUID,
p_operator_id UUID,
p_description VARCHAR(200) DEFAULT '商家清零余额'
)
RETURNS BOOLEAN AS $$
DECLARE
v_balance_before DECIMAL(10,2);
BEGIN
SELECT balance INTO v_balance_before
FROM ml_merchant_user_balance
WHERE merchant_id = p_merchant_id AND user_id = p_user_id;
IF v_balance_before IS NULL OR v_balance_before = 0 THEN
RETURN FALSE;
END IF;
UPDATE ml_merchant_user_balance
SET balance = 0,
updated_at = NOW()
WHERE merchant_id = p_merchant_id AND user_id = p_user_id;
INSERT INTO ml_balance_records (merchant_id, user_id, type, amount, balance_before, balance_after, description, operator_id)
VALUES (p_merchant_id, p_user_id, 'clear', -v_balance_before, v_balance_before, 0, p_description, p_operator_id);
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =====================================================
-- 七、测试数据(可选)
-- =====================================================
-- 为某个商家开启推销模式替换YOUR_MERCHANT_ID为实际商家ID
-- INSERT INTO ml_merchant_promotion_config (merchant_id, promotion_enabled, share_free_enabled, required_count)
-- VALUES ('YOUR_MERCHANT_ID', TRUE, TRUE, 4);

View File

@@ -0,0 +1,143 @@
-- =====================================================
-- 分享免单通知消息 - 数据库执行语句
-- =====================================================
-- 1. 添加分享免单相关的通知类型说明
-- ml_notifications 表的 type 字段可包含以下类型:
-- 'system' - 系统通知
-- 'promotion' - 优惠活动
-- 'order' - 订单通知
-- 'share_free' - 分享免单通知 (新增)
-- 2. 创建分享免单通知的函数
-- 当分享免单成功时,自动发送通知给用户
CREATE OR REPLACE FUNCTION notify_share_free_success(
p_user_id UUID,
p_share_record_id UUID,
p_product_name VARCHAR,
p_reward_amount DECIMAL(10,2),
p_share_code VARCHAR
)
RETURNS VOID AS $$
BEGIN
INSERT INTO ml_notifications (
user_id,
type,
title,
content,
icon_url,
link_url,
extra_data,
created_at
) VALUES (
p_user_id,
'share_free',
'恭喜!您已获得免单奖励',
'您分享的商品【' || p_product_name || '】已有4位好友购买获得免单奖励 ¥' || p_reward_amount || '!分享码:' || p_share_code,
'/static/icons/gift.png',
'/pages/mall/consumer/share/detail?id=' || p_share_record_id,
jsonb_build_object(
'share_record_id', p_share_record_id,
'product_name', p_product_name,
'reward_amount', p_reward_amount,
'share_code', p_share_code
),
NOW()
);
END;
$$ LANGUAGE plpgsql;
-- 3. 创建二级购买进度通知函数
-- 当有人通过分享码购买时,发送进度通知
CREATE OR REPLACE FUNCTION notify_share_purchase_progress(
p_user_id UUID,
p_share_record_id UUID,
p_product_name VARCHAR,
p_current_count INT,
p_required_count INT,
p_share_code VARCHAR
)
RETURNS VOID AS $$
BEGIN
INSERT INTO ml_notifications (
user_id,
type,
title,
content,
icon_url,
link_url,
extra_data,
created_at
) VALUES (
p_user_id,
'share_free',
'分享免单进度更新',
'您分享的商品【' || p_product_name || '】已有 ' || p_current_count || '/' || p_required_count || ' 人购买,再邀请 ' || (p_required_count - p_current_count) || ' 人即可免单!',
'/static/icons/share.png',
'/pages/mall/consumer/share/detail?id=' || p_share_record_id,
jsonb_build_object(
'share_record_id', p_share_record_id,
'product_name', p_product_name,
'current_count', p_current_count,
'required_count', p_required_count,
'share_code', p_share_code
),
NOW()
);
END;
$$ LANGUAGE plpgsql;
-- 4. 创建分享创建成功通知函数
-- 当用户创建分享时,发送确认通知
CREATE OR REPLACE FUNCTION notify_share_created(
p_user_id UUID,
p_share_record_id UUID,
p_product_name VARCHAR,
p_share_code VARCHAR
)
RETURNS VOID AS $$
BEGIN
INSERT INTO ml_notifications (
user_id,
type,
title,
content,
icon_url,
link_url,
extra_data,
created_at
) VALUES (
p_user_id,
'share_free',
'分享创建成功',
'您已成功分享商品【' || p_product_name || '】,分享码:' || p_share_code || '。邀请4位好友购买即可免单',
'/static/icons/share-success.png',
'/pages/mall/consumer/share/detail?id=' || p_share_record_id,
jsonb_build_object(
'share_record_id', p_share_record_id,
'product_name', p_product_name,
'share_code', p_share_code
),
NOW()
);
END;
$$ LANGUAGE plpgsql;
-- 5. 示例:手动插入测试通知(可选)
-- 替换 'YOUR_USER_ID' 为实际用户ID
-- INSERT INTO ml_notifications (user_id, type, title, content, icon_url, link_url, extra_data, created_at)
-- VALUES (
-- 'YOUR_USER_ID',
-- 'share_free',
-- '恭喜!您已获得免单奖励',
-- '您分享的商品【测试商品】已有4位好友购买获得免单奖励 ¥99.00分享码ABCD1234',
-- '/static/icons/gift.png',
-- '/pages/mall/consumer/share/detail?id=test-share-id',
-- '{"share_record_id": "test-share-id", "product_name": "测试商品", "reward_amount": 99.00, "share_code": "ABCD1234"}',
-- NOW()
-- );
-- 6. 查询用户分享免单相关通知
-- SELECT * FROM ml_notifications
-- WHERE type = 'share_free'
-- ORDER BY created_at DESC;