解决登录显示、首页显示bug
This commit is contained in:
101
docs/sql/10_schema/delivery/ak_delivery_system_v2.sql
Normal file
101
docs/sql/10_schema/delivery/ak_delivery_system_v2.sql
Normal file
@@ -0,0 +1,101 @@
|
||||
-- =====================================================================================
|
||||
-- Schema: 医养执行端 Delivery 基础表升级
|
||||
-- 位置:docs/sql/10_schema/delivery/ak_delivery_system_v2.sql
|
||||
-- 对象类型:ALTER TABLE / INDEX / TRIGGER
|
||||
-- 版本:v2
|
||||
-- 说明:在 v1 基础上最小升级为“医养上门服务执行端”可用模型。
|
||||
-- 补齐 uid 唯一索引、软删除、机构关联、staff_no、在线状态、资质状态。
|
||||
-- =====================================================================================
|
||||
|
||||
-- 0. 通用 updated_at 触发器
|
||||
CREATE OR REPLACE FUNCTION public.tg_set_updated_at()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 1. 配送/执行人员表升级
|
||||
ALTER TABLE public.ml_delivery_staff
|
||||
ADD COLUMN IF NOT EXISTS station_id UUID REFERENCES public.ml_delivery_stations(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS staff_no TEXT,
|
||||
ADD COLUMN IF NOT EXISTS online_status TEXT NOT NULL DEFAULT 'resting',
|
||||
ADD COLUMN IF NOT EXISTS certificate_status TEXT NOT NULL DEFAULT 'pending',
|
||||
ADD COLUMN IF NOT EXISTS certificate_expire_at DATE,
|
||||
ADD COLUMN IF NOT EXISTS service_area TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS skills JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS deleted_by UUID REFERENCES public.ak_users(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE public.ml_delivery_staff
|
||||
DROP CONSTRAINT IF EXISTS chk_ml_delivery_staff_online_status;
|
||||
ALTER TABLE public.ml_delivery_staff
|
||||
ADD CONSTRAINT chk_ml_delivery_staff_online_status
|
||||
CHECK (online_status IN ('online', 'resting', 'busy'));
|
||||
|
||||
ALTER TABLE public.ml_delivery_staff
|
||||
DROP CONSTRAINT IF EXISTS chk_ml_delivery_staff_certificate_status;
|
||||
ALTER TABLE public.ml_delivery_staff
|
||||
ADD CONSTRAINT chk_ml_delivery_staff_certificate_status
|
||||
CHECK (certificate_status IN ('valid', 'expired', 'pending'));
|
||||
|
||||
-- 2. 站点/机构表升级
|
||||
ALTER TABLE public.ml_delivery_stations
|
||||
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS deleted_by UUID REFERENCES public.ak_users(id) ON DELETE SET NULL;
|
||||
|
||||
-- 3. 索引
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_ml_delivery_staff_uid_active
|
||||
ON public.ml_delivery_staff(uid)
|
||||
WHERE uid IS NOT NULL AND deleted_at IS NULL;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_ml_delivery_staff_staff_no_active
|
||||
ON public.ml_delivery_staff(staff_no)
|
||||
WHERE staff_no IS NOT NULL AND deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ml_delivery_staff_station_id
|
||||
ON public.ml_delivery_staff(station_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ml_delivery_staff_deleted_at
|
||||
ON public.ml_delivery_staff(deleted_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ml_delivery_staff_status_active
|
||||
ON public.ml_delivery_staff(status, is_active)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ml_delivery_stations_deleted_at
|
||||
ON public.ml_delivery_stations(deleted_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ml_delivery_stations_status_active
|
||||
ON public.ml_delivery_stations(status)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- 4. 自动维护 updated_at
|
||||
DROP TRIGGER IF EXISTS trg_ml_delivery_staff_set_updated_at ON public.ml_delivery_staff;
|
||||
CREATE TRIGGER trg_ml_delivery_staff_set_updated_at
|
||||
BEFORE UPDATE ON public.ml_delivery_staff
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_ml_delivery_stations_set_updated_at ON public.ml_delivery_stations;
|
||||
CREATE TRIGGER trg_ml_delivery_stations_set_updated_at
|
||||
BEFORE UPDATE ON public.ml_delivery_stations
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.tg_set_updated_at();
|
||||
|
||||
-- 5. 注释
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.station_id IS '所属机构/服务站点 ID';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.staff_no IS '服务人员编号';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.online_status IS '在线状态:online/resting/busy';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.certificate_status IS '资质状态:valid/expired/pending';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.certificate_expire_at IS '主要资质到期日期';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.service_area IS '服务区域描述';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.skills IS '技能标签 JSON 数组';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.deleted_at IS '软删除时间';
|
||||
COMMENT ON COLUMN public.ml_delivery_staff.deleted_by IS '软删除操作人';
|
||||
COMMENT ON COLUMN public.ml_delivery_stations.deleted_at IS '软删除时间';
|
||||
COMMENT ON COLUMN public.ml_delivery_stations.deleted_by IS '软删除操作人';
|
||||
38
docs/sql/20_rls/delivery/ak_delivery_rls_v2.sql
Normal file
38
docs/sql/20_rls/delivery/ak_delivery_rls_v2.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- =====================================================================================
|
||||
-- RLS: 医养执行端 Delivery 安全策略升级
|
||||
-- 位置:docs/sql/20_rls/delivery/ak_delivery_rls_v2.sql
|
||||
-- 对象类型:RLS 策略
|
||||
-- 版本:v2
|
||||
-- 说明:保留管理端通过 SECURITY DEFINER RPC 管理,补充执行人员本人直读自己档案。
|
||||
-- =====================================================================================
|
||||
|
||||
ALTER TABLE public.ml_delivery_staff ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.ml_delivery_stations ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- 清理旧策略
|
||||
DROP POLICY IF EXISTS delivery_staff_self_select ON public.ml_delivery_staff;
|
||||
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
|
||||
|
||||
-- 1. 执行人员本人可直读自己的未删除档案
|
||||
CREATE POLICY delivery_staff_self_select
|
||||
ON public.ml_delivery_staff
|
||||
FOR SELECT
|
||||
TO authenticated
|
||||
USING (
|
||||
deleted_at IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM public.ak_users u
|
||||
WHERE u.id = ml_delivery_staff.uid
|
||||
AND u.auth_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- 2. 提货点/机构对前台保持只读,仅返回启用且未删除数据
|
||||
CREATE POLICY delivery_stations_select_active
|
||||
ON public.ml_delivery_stations
|
||||
FOR SELECT
|
||||
TO anon, authenticated
|
||||
USING (status = 1 AND deleted_at IS NULL);
|
||||
|
||||
-- 3. 其余直连写操作默认不开放,管理端统一走 SECURITY DEFINER RPC
|
||||
110
docs/sql/30_rpc/auth/handle_new_user_v4.sql
Normal file
110
docs/sql/30_rpc/auth/handle_new_user_v4.sql
Normal file
@@ -0,0 +1,110 @@
|
||||
-- =====================================================================================
|
||||
-- Trigger Function: handle_new_user
|
||||
-- Version: v4
|
||||
-- Purpose: auth.users 新用户创建后,优先读取 raw_user_meta_data.user_role 写入 ak_users.role。
|
||||
-- 解决 delivery / merchant 注册时被错误降级为默认 consumer 的问题。
|
||||
-- Security: SECURITY DEFINER + 固定 search_path
|
||||
-- Depends:
|
||||
-- - public.ak_users(auth_id,email,username,role)
|
||||
-- - public.user_roles(user_id,role)
|
||||
-- =====================================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
user_role TEXT;
|
||||
metadata_role TEXT;
|
||||
user_email TEXT := NEW.email;
|
||||
user_name TEXT;
|
||||
ak_user_id UUID;
|
||||
has_user_roles BOOLEAN := FALSE;
|
||||
has_delivery_staff BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
metadata_role := NULLIF(TRIM(COALESCE(NEW.raw_user_meta_data ->> 'user_role', '')), '');
|
||||
|
||||
user_role := CASE
|
||||
WHEN metadata_role IN ('customer', 'merchant', 'delivery', 'service', 'admin') THEN metadata_role
|
||||
WHEN user_email ILIKE '%@admin.%' THEN 'admin'
|
||||
WHEN user_email ILIKE '%@teacher.%' OR user_email ILIKE '%@edu.%' THEN 'customer'
|
||||
ELSE 'customer'
|
||||
END;
|
||||
|
||||
IF user_email IS NOT NULL AND POSITION('@' IN user_email) > 1 THEN
|
||||
user_name := SPLIT_PART(user_email, '@', 1);
|
||||
ELSE
|
||||
user_name := 'user_' || SUBSTRING(NEW.id::text, 1, 8);
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.ak_users (auth_id, email, username, role)
|
||||
VALUES (NEW.id, user_email, user_name, user_role)
|
||||
ON CONFLICT (auth_id)
|
||||
DO UPDATE SET
|
||||
email = COALESCE(EXCLUDED.email, public.ak_users.email),
|
||||
username = COALESCE(EXCLUDED.username, public.ak_users.username),
|
||||
role = COALESCE(NULLIF(public.ak_users.role, ''), EXCLUDED.role),
|
||||
updated_at = now()
|
||||
RETURNING id INTO ak_user_id;
|
||||
|
||||
IF user_role = 'delivery' THEN
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'ml_delivery_staff'
|
||||
) INTO has_delivery_staff;
|
||||
|
||||
IF has_delivery_staff THEN
|
||||
INSERT INTO public.ml_delivery_staff (
|
||||
uid,
|
||||
nickname,
|
||||
phone,
|
||||
status,
|
||||
is_active
|
||||
)
|
||||
SELECT
|
||||
ak_user_id,
|
||||
user_name,
|
||||
'',
|
||||
1,
|
||||
TRUE
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.ml_delivery_staff
|
||||
WHERE uid = ak_user_id
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'user_roles'
|
||||
) INTO has_user_roles;
|
||||
|
||||
IF has_user_roles THEN
|
||||
BEGIN
|
||||
INSERT INTO public.user_roles (user_id, role, created_by)
|
||||
VALUES (NEW.id, user_role, NEW.id)
|
||||
ON CONFLICT DO NOTHING;
|
||||
EXCEPTION
|
||||
WHEN check_violation THEN
|
||||
RAISE NOTICE '[handle_new_user_v4] WARNING: Skipped user_roles insert due to check violation. user_id: %, role: %', NEW.id, user_role;
|
||||
WHEN not_null_violation THEN
|
||||
RAISE NOTICE '[handle_new_user_v4] WARNING: Failed to INSERT into user_roles due to NOT NULL violation. user_id: %, role: %', NEW.id, user_role;
|
||||
WHEN others THEN
|
||||
RAISE NOTICE '[handle_new_user_v4] WARNING: Skipped user_roles insert due to unexpected error. user_id: %, role: %, err: %', NEW.id, user_role, SQLERRM;
|
||||
END;
|
||||
END IF;
|
||||
|
||||
UPDATE auth.users
|
||||
SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) || jsonb_build_object('user_role', user_role)
|
||||
WHERE id = NEW.id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,82 @@
|
||||
-- RPC: rpc_admin_get_delivery_staff_list
|
||||
-- 管理端获取服务人员分页列表(v2)
|
||||
|
||||
DROP FUNCTION IF EXISTS public.rpc_admin_get_delivery_staff_list(TEXT, SMALLINT, INTEGER, INTEGER);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_get_delivery_staff_list(
|
||||
p_search TEXT DEFAULT NULL,
|
||||
p_status SMALLINT DEFAULT NULL,
|
||||
p_page INTEGER DEFAULT 1,
|
||||
p_page_size INTEGER DEFAULT 20
|
||||
)
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_offset INTEGER := (p_page - 1) * p_page_size;
|
||||
v_total BIGINT;
|
||||
v_items JSONB;
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users
|
||||
WHERE auth_id = auth.uid() AND role = 'admin'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Permission denied';
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*) INTO v_total
|
||||
FROM public.ml_delivery_staff s
|
||||
WHERE s.deleted_at IS NULL
|
||||
AND (p_status IS NULL OR s.status = p_status)
|
||||
AND (
|
||||
p_search IS NULL OR p_search = ''
|
||||
OR s.nickname ILIKE '%' || p_search || '%'
|
||||
OR s.phone ILIKE '%' || p_search || '%'
|
||||
OR s.staff_no ILIKE '%' || p_search || '%'
|
||||
);
|
||||
|
||||
SELECT jsonb_agg(t) INTO v_items
|
||||
FROM (
|
||||
SELECT
|
||||
s.id,
|
||||
s.uid,
|
||||
s.station_id,
|
||||
st.name AS station_name,
|
||||
s.staff_no,
|
||||
s.nickname,
|
||||
s.avatar,
|
||||
s.phone,
|
||||
s.status,
|
||||
s.is_active,
|
||||
s.online_status,
|
||||
s.certificate_status,
|
||||
s.certificate_expire_at,
|
||||
s.service_area,
|
||||
s.skills,
|
||||
s.created_at,
|
||||
s.updated_at
|
||||
FROM public.ml_delivery_staff s
|
||||
LEFT JOIN public.ml_delivery_stations st ON st.id = s.station_id AND st.deleted_at IS NULL
|
||||
WHERE s.deleted_at IS NULL
|
||||
AND (p_status IS NULL OR s.status = p_status)
|
||||
AND (
|
||||
p_search IS NULL OR p_search = ''
|
||||
OR s.nickname ILIKE '%' || p_search || '%'
|
||||
OR s.phone ILIKE '%' || p_search || '%'
|
||||
OR s.staff_no ILIKE '%' || p_search || '%'
|
||||
)
|
||||
ORDER BY s.created_at DESC
|
||||
LIMIT p_page_size OFFSET v_offset
|
||||
) t;
|
||||
|
||||
RETURN jsonb_build_object(
|
||||
'total', v_total,
|
||||
'items', COALESCE(v_items, '[]'::jsonb)
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_get_delivery_staff_list(TEXT, SMALLINT, INTEGER, INTEGER) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_get_delivery_staff_list(TEXT, SMALLINT, INTEGER, INTEGER) TO authenticated;
|
||||
@@ -0,0 +1,95 @@
|
||||
-- RPC: rpc_admin_save_delivery_staff
|
||||
-- 管理端新增或更新服务人员信息(v2)
|
||||
|
||||
DROP FUNCTION IF EXISTS public.rpc_admin_save_delivery_staff(UUID, TEXT, TEXT, TEXT, SMALLINT);
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.rpc_admin_save_delivery_staff(
|
||||
p_id UUID DEFAULT NULL,
|
||||
p_uid UUID DEFAULT NULL,
|
||||
p_station_id UUID DEFAULT NULL,
|
||||
p_staff_no TEXT DEFAULT NULL,
|
||||
p_nickname TEXT DEFAULT NULL,
|
||||
p_avatar TEXT DEFAULT NULL,
|
||||
p_phone TEXT DEFAULT NULL,
|
||||
p_status SMALLINT DEFAULT 1,
|
||||
p_online_status TEXT DEFAULT 'resting',
|
||||
p_certificate_status TEXT DEFAULT 'pending',
|
||||
p_certificate_expire_at DATE DEFAULT NULL,
|
||||
p_service_area TEXT DEFAULT '',
|
||||
p_skills JSONB DEFAULT '[]'::jsonb
|
||||
)
|
||||
RETURNS UUID
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_id UUID;
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM public.ak_users
|
||||
WHERE auth_id = auth.uid() AND role = 'admin'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Permission denied';
|
||||
END IF;
|
||||
|
||||
IF p_nickname IS NULL OR p_phone IS NULL THEN
|
||||
RAISE EXCEPTION 'Missing required fields: nickname or phone';
|
||||
END IF;
|
||||
|
||||
IF p_online_status NOT IN ('online', 'resting', 'busy') THEN
|
||||
RAISE EXCEPTION 'Invalid online_status';
|
||||
END IF;
|
||||
|
||||
IF p_certificate_status NOT IN ('valid', 'expired', 'pending') THEN
|
||||
RAISE EXCEPTION 'Invalid certificate_status';
|
||||
END IF;
|
||||
|
||||
IF p_station_id IS NOT NULL AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.ml_delivery_stations s
|
||||
WHERE s.id = p_station_id AND s.deleted_at IS NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Delivery station not found';
|
||||
END IF;
|
||||
|
||||
IF p_id IS NULL THEN
|
||||
INSERT INTO public.ml_delivery_staff (
|
||||
uid, station_id, staff_no, nickname, avatar, phone, status,
|
||||
online_status, certificate_status, certificate_expire_at,
|
||||
service_area, skills
|
||||
) VALUES (
|
||||
p_uid, p_station_id, NULLIF(p_staff_no, ''), p_nickname, p_avatar, p_phone, p_status,
|
||||
p_online_status, p_certificate_status, p_certificate_expire_at,
|
||||
COALESCE(p_service_area, ''), COALESCE(p_skills, '[]'::jsonb)
|
||||
) RETURNING id INTO v_id;
|
||||
ELSE
|
||||
UPDATE public.ml_delivery_staff
|
||||
SET
|
||||
uid = COALESCE(p_uid, uid),
|
||||
station_id = p_station_id,
|
||||
staff_no = CASE WHEN p_staff_no IS NULL OR p_staff_no = '' THEN NULL ELSE p_staff_no END,
|
||||
nickname = COALESCE(p_nickname, nickname),
|
||||
avatar = COALESCE(p_avatar, avatar),
|
||||
phone = COALESCE(p_phone, phone),
|
||||
status = COALESCE(p_status, status),
|
||||
online_status = COALESCE(p_online_status, online_status),
|
||||
certificate_status = COALESCE(p_certificate_status, certificate_status),
|
||||
certificate_expire_at = p_certificate_expire_at,
|
||||
service_area = COALESCE(p_service_area, service_area),
|
||||
skills = COALESCE(p_skills, skills),
|
||||
updated_at = now()
|
||||
WHERE id = p_id AND deleted_at IS NULL
|
||||
RETURNING id INTO v_id;
|
||||
|
||||
IF v_id IS NULL THEN
|
||||
RAISE EXCEPTION 'Delivery staff not found';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN v_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
REVOKE ALL ON FUNCTION public.rpc_admin_save_delivery_staff(UUID, UUID, UUID, TEXT, TEXT, TEXT, TEXT, SMALLINT, TEXT, TEXT, DATE, TEXT, JSONB) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION public.rpc_admin_save_delivery_staff(UUID, UUID, UUID, TEXT, TEXT, TEXT, TEXT, SMALLINT, TEXT, TEXT, DATE, TEXT, JSONB) TO authenticated;
|
||||
Reference in New Issue
Block a user