3 Commits

28 changed files with 7451 additions and 464 deletions

View File

@@ -0,0 +1,465 @@
{
"pages": [
{
"path": "pages/main/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/user/boot",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "登录",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/user/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/forgot-password",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/user/terms",
"style": {
"navigationBarTitleText": "用户协议与隐私政策"
}
},
{
"path": "pages/user/center",
"style": {
"navigationBarTitleText": "用户中心"
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/user/change-password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/user/bind-phone",
"style": {
"navigationBarTitleText": "绑定手机"
}
},
{
"path": "pages/user/bind-email",
"style": {
"navigationBarTitleText": "绑定邮箱"
}
},
{
"path": "pages/main/messages",
"style": {
"navigationBarTitleText": "消息",
"enablePullDownRefresh": true
}
},
{
"path": "pages/main/cart",
"style": {
"navigationBarTitleText": "购物车",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/profile",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/category",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom"
}
}
],
"subPackages": [
{
"root": "pages/mall/consumer",
"pages": [
{
"path": "settings",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "edit-profile",
"style": {
"navigationBarTitleText": "编辑资料"
}
},
{
"path": "wallet",
"style": {
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "withdraw",
"style": {
"navigationBarTitleText": "余额提现"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "channel-detail",
"style": {
"navigationBarTitleText": "频道详情",
"navigationStyle": "custom"
}
},
{
"path": "shop-detail",
"style": {
"navigationBarTitleText": "店铺详情"
}
},
{
"path": "coupons",
"style": {
"navigationBarTitleText": "我的优惠券"
}
},
{
"path": "favorites",
"style": {
"navigationBarTitleText": "我的收藏"
}
},
{
"path": "footprint",
"style": {
"navigationBarTitleText": "我的足迹"
}
},
{
"path": "address",
"style": {
"navigationBarTitleText": "地址"
}
},
{
"path": "address-list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "address-edit",
"style": {
"navigationBarTitleText": "编辑地址"
}
},
{
"path": "checkout",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "payment",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "payment-success",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"backgroundColor": "#f5f5f5"
}
},
{
"path": "order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "review",
"style": {
"navigationBarTitleText": "评价晒单"
}
},
{
"path": "refund",
"style": {
"navigationBarTitleText": "退款/售后"
}
},
{
"path": "apply-refund",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "refund-review",
"style": {
"navigationBarTitleText": "服务评价"
}
},
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天",
"navigationStyle": "custom"
}
},
{
"path": "chat_new",
"style": {
"navigationBarTitleText": "客服聊天(新版)"
}
},
{
"path": "subscription/plan-list",
"style": {
"navigationBarTitleText": "软件订阅"
}
},
{
"path": "subscription/plan-detail",
"style": {
"navigationBarTitleText": "订阅详情"
}
},
{
"path": "subscription/subscribe-checkout",
"style": {
"navigationBarTitleText": "确认订阅"
}
},
{
"path": "subscription/my-subscriptions",
"style": {
"navigationBarTitleText": "我的订阅"
}
},
{
"path": "subscription/followed-shops",
"style": {
"navigationBarTitleText": "关注店铺"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分管理"
}
},
{
"path": "points/signin",
"style": {
"navigationBarTitleText": "签到"
}
},
{
"path": "points/exchange",
"style": {
"navigationBarTitleText": "积分兑换"
}
},
{
"path": "points/exchange-records",
"style": {
"navigationBarTitleText": "兑换记录"
}
},
{
"path": "red-packets/index",
"style": {
"navigationBarTitleText": "我的红包"
}
},
{
"path": "bank-cards/index",
"style": {
"navigationBarTitleText": "银行卡管理"
}
},
{
"path": "bank-cards/add",
"style": {
"navigationBarTitleText": "添加银行卡"
}
},
{
"path": "home-service/index",
"style": {
"navigationBarTitleText": "居家上门服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/apply",
"style": {
"navigationBarTitleText": "提交服务申请",
"navigationStyle": "custom"
}
},
{
"path": "home-service/service-detail",
"style": {
"navigationBarTitleText": "预约服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/order-detail",
"style": {
"navigationBarTitleText": "服务单详情",
"navigationStyle": "custom"
}
},
{
"path": "home-service/feedback",
"style": {
"navigationBarTitleText": "验收反馈",
"navigationStyle": "custom"
}
},
{
"path": "bank-cards/verify",
"style": {
"navigationBarTitleText": "银行卡验证"
}
},
{
"path": "balance/index",
"style": {
"navigationBarTitleText": "余额"
}
},
{
"path": "my-reviews",
"style": {
"navigationBarTitleText": "我的评价"
}
},
{
"path": "message-detail",
"style": {
"navigationBarTitleText": "消息详情"
}
},
{
"path": "member/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "product-reviews",
"style": {
"navigationBarTitleText": "商品评价"
}
}
]
}
],
"tabBar": {
"color": "#999999",
"selectedColor": "#ff5000",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/main/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/main/messages",
"text": "消息",
"iconPath": "static/tabbar/message.png",
"selectedIconPath": "static/tabbar/message.png"
},
{
"pagePath": "pages/main/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart.png"
},
{
"pagePath": "pages/main/profile",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
},
"condition": {
"current": 0,
"list": [
{
"name": "consumer端",
"path": "pages/main/index",
"query": "role=consumer"
}
]
}
}

View File

@@ -0,0 +1,464 @@
{
"pages": [
{
"path": "pages/main/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/user/boot",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "用户登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/user/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/forgot-password",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/user/terms",
"style": {
"navigationBarTitleText": "用户协议与隐私政策"
}
},
{
"path": "pages/user/center",
"style": {
"navigationBarTitleText": "用户中心"
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/user/change-password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/user/bind-phone",
"style": {
"navigationBarTitleText": "绑定手机"
}
},
{
"path": "pages/user/bind-email",
"style": {
"navigationBarTitleText": "绑定邮箱"
}
},
{
"path": "pages/main/messages",
"style": {
"navigationBarTitleText": "消息",
"enablePullDownRefresh": true
}
},
{
"path": "pages/main/cart",
"style": {
"navigationBarTitleText": "购物车",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/profile",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/category",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom"
}
}
],
"subPackages": [
{
"root": "pages/mall/consumer",
"pages": [
{
"path": "settings",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "edit-profile",
"style": {
"navigationBarTitleText": "编辑资料"
}
},
{
"path": "wallet",
"style": {
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "withdraw",
"style": {
"navigationBarTitleText": "余额提现"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "channel-detail",
"style": {
"navigationBarTitleText": "频道详情",
"navigationStyle": "custom"
}
},
{
"path": "shop-detail",
"style": {
"navigationBarTitleText": "店铺详情"
}
},
{
"path": "coupons",
"style": {
"navigationBarTitleText": "我的优惠券"
}
},
{
"path": "favorites",
"style": {
"navigationBarTitleText": "我的收藏"
}
},
{
"path": "footprint",
"style": {
"navigationBarTitleText": "我的足迹"
}
},
{
"path": "address",
"style": {
"navigationBarTitleText": "地址"
}
},
{
"path": "address-list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "address-edit",
"style": {
"navigationBarTitleText": "编辑地址"
}
},
{
"path": "checkout",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "payment",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "payment-success",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"backgroundColor": "#f5f5f5"
}
},
{
"path": "order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "review",
"style": {
"navigationBarTitleText": "评价晒单"
}
},
{
"path": "refund",
"style": {
"navigationBarTitleText": "退款/售后"
}
},
{
"path": "apply-refund",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "refund-review",
"style": {
"navigationBarTitleText": "服务评价"
}
},
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天",
"navigationStyle": "custom"
}
},
{
"path": "chat_new",
"style": {
"navigationBarTitleText": "客服聊天(新版)"
}
},
{
"path": "subscription/plan-list",
"style": {
"navigationBarTitleText": "软件订阅"
}
},
{
"path": "subscription/plan-detail",
"style": {
"navigationBarTitleText": "订阅详情"
}
},
{
"path": "subscription/subscribe-checkout",
"style": {
"navigationBarTitleText": "确认订阅"
}
},
{
"path": "subscription/my-subscriptions",
"style": {
"navigationBarTitleText": "我的订阅"
}
},
{
"path": "subscription/followed-shops",
"style": {
"navigationBarTitleText": "关注店铺"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分管理"
}
},
{
"path": "points/signin",
"style": {
"navigationBarTitleText": "签到"
}
},
{
"path": "points/exchange",
"style": {
"navigationBarTitleText": "积分兑换"
}
},
{
"path": "points/exchange-records",
"style": {
"navigationBarTitleText": "兑换记录"
}
},
{
"path": "red-packets/index",
"style": {
"navigationBarTitleText": "我的红包"
}
},
{
"path": "bank-cards/index",
"style": {
"navigationBarTitleText": "银行卡管理"
}
},
{
"path": "bank-cards/add",
"style": {
"navigationBarTitleText": "添加银行卡"
}
},
{
"path": "home-service/index",
"style": {
"navigationBarTitleText": "居家上门服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/apply",
"style": {
"navigationBarTitleText": "提交服务申请",
"navigationStyle": "custom"
}
},
{
"path": "home-service/service-detail",
"style": {
"navigationBarTitleText": "预约服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/order-detail",
"style": {
"navigationBarTitleText": "服务单详情",
"navigationStyle": "custom"
}
},
{
"path": "home-service/feedback",
"style": {
"navigationBarTitleText": "验收反馈",
"navigationStyle": "custom"
}
},
{
"path": "bank-cards/verify",
"style": {
"navigationBarTitleText": "银行卡验证"
}
},
{
"path": "balance/index",
"style": {
"navigationBarTitleText": "余额"
}
},
{
"path": "my-reviews",
"style": {
"navigationBarTitleText": "我的评价"
}
},
{
"path": "message-detail",
"style": {
"navigationBarTitleText": "消息详情"
}
},
{
"path": "member/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "product-reviews",
"style": {
"navigationBarTitleText": "商品评价"
}
}
]
}
],
"tabBar": {
"color": "#999999",
"selectedColor": "#ff5000",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/main/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/main/messages",
"text": "消息",
"iconPath": "static/tabbar/message.png",
"selectedIconPath": "static/tabbar/message.png"
},
{
"pagePath": "pages/main/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart.png"
},
{
"pagePath": "pages/main/profile",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
},
"condition": {
"current": 0,
"list": [
{
"name": "consumer端",
"path": "pages/main/index",
"query": "role=consumer"
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
-- =====================================================================================
-- Schema: homecare foundation
-- Version: v1
-- Purpose: 为 ec_service_requests / ec_care_tasks / ec_care_records / hc_* 新链补齐最小结构、索引与基础 RLS。
-- Coverage:
-- 1. consumer 下单与自动派单字段
-- 2. consumer 验收 / 退回整改 / 评价字段
-- 3. delivery 执行记录、异常、证据、事件字段
-- =====================================================================================
-- 执行稿见mall_sql/migrations/20260526_homecare_foundation_v1.sql

View File

@@ -11,7 +11,6 @@ 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_staff_assignable_select ON public.ml_delivery_staff;
DROP POLICY IF EXISTS delivery_staff_self_update ON public.ml_delivery_staff;
DROP POLICY IF EXISTS delivery_stations_select_active ON public.ml_delivery_stations;
@@ -30,20 +29,7 @@ CREATE POLICY delivery_staff_self_select
)
);
-- 2. 已登录用户仅可读取可派单的在线服务人员,用于自动派单
CREATE POLICY delivery_staff_assignable_select
ON public.ml_delivery_staff
FOR SELECT
TO authenticated
USING (
deleted_at IS NULL
AND status = 1
AND COALESCE(is_active, true) = true
AND online_status = 'online'
AND uid IS NOT NULL
);
-- 3. 执行人员本人可更新自己的在线状态等自有档案字段
-- 2. 执行人员本人可更新自己的在线状态等自有档案字段
CREATE POLICY delivery_staff_self_update
ON public.ml_delivery_staff
FOR UPDATE
@@ -67,11 +53,12 @@ CREATE POLICY delivery_staff_self_update
)
);
-- 4. 提货点/机构对前台保持只读,仅返回启用且未删除数据
-- 3. 提货点/机构对前台保持只读,仅返回启用且未删除数据
CREATE POLICY delivery_stations_select_active
ON public.ml_delivery_stations
FOR SELECT
TO anon, authenticated
USING (status = 1 AND deleted_at IS NULL);
-- 4. 派单候选人读取统一走 SECURITY DEFINER RPC不再开放公开可派单列表 RLS
-- 5. 其余直连写操作默认不开放,管理端统一走 SECURITY DEFINER RPC

View File

@@ -0,0 +1,11 @@
-- =====================================================================================
-- RPC: rpc_delivery_* homecare actions
-- Version: v1
-- Purpose: 为 delivery 页面当前 api/delivery.uts 已固定的 rpc_delivery_* 契约补齐 SQL 端实现。
-- Notes:
-- 1. 优先兼容 ec/hc 新链,检测不到表或执行失败时回退 hss 旧链。
-- 2. 返回 JSON 结构直接对齐 delivery/types/delivery.uts。
-- 3. message_list 与 record_list 采用最小可用实现,避免前端继续落入 fallback。
-- =====================================================================================
-- 迁移正文见同批文件mall_sql/migrations/20260526_delivery_homecare_rpc_v1.sql

View File

@@ -0,0 +1,54 @@
-- =====================================================================================
-- RPC: rpc_homecare_dispatch_candidate
-- Version: v1
-- Purpose: 为居家上门服务返回单个可派单候选人,替代公开可派单人员 RLS。
-- Security: SECURITY DEFINER + 固定 search_path
-- Depends: public.ak_users, public.ml_delivery_staff
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.rpc_homecare_dispatch_candidate(
p_service_code TEXT DEFAULT NULL,
p_station_id UUID DEFAULT NULL
)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_candidate JSONB;
BEGIN
IF auth.uid() IS NULL OR NOT EXISTS (
SELECT 1
FROM public.ak_users u
WHERE u.auth_id = auth.uid()
) THEN
RAISE EXCEPTION 'Permission denied';
END IF;
SELECT jsonb_build_object(
'id', s.id,
'uid', s.uid,
'station_id', s.station_id,
'status', s.status,
'online_status', s.online_status,
'updated_at', s.updated_at,
'created_at', s.created_at
)
INTO v_candidate
FROM public.ml_delivery_staff s
WHERE s.deleted_at IS NULL
AND s.status = 1
AND COALESCE(s.is_active, TRUE) = TRUE
AND s.online_status = 'online'
AND s.uid IS NOT NULL
AND (p_station_id IS NULL OR s.station_id = p_station_id)
ORDER BY COALESCE(s.updated_at, s.created_at) DESC, s.created_at DESC
LIMIT 1;
RETURN v_candidate;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_homecare_dispatch_candidate(TEXT, UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_homecare_dispatch_candidate(TEXT, UUID) TO authenticated;

View File

@@ -173,4 +173,53 @@ $$;
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;
DROP FUNCTION IF EXISTS public.rpc_homecare_dispatch_candidate(TEXT, UUID);
CREATE OR REPLACE FUNCTION public.rpc_homecare_dispatch_candidate(
p_service_code TEXT DEFAULT NULL,
p_station_id UUID DEFAULT NULL
)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_candidate JSONB;
BEGIN
IF auth.uid() IS NULL OR NOT EXISTS (
SELECT 1
FROM public.ak_users u
WHERE u.auth_id = auth.uid()
) THEN
RAISE EXCEPTION 'Permission denied';
END IF;
SELECT jsonb_build_object(
'id', s.id,
'uid', s.uid,
'station_id', s.station_id,
'status', s.status,
'online_status', s.online_status,
'updated_at', s.updated_at,
'created_at', s.created_at
)
INTO v_candidate
FROM public.ml_delivery_staff s
WHERE s.deleted_at IS NULL
AND s.status = 1
AND COALESCE(s.is_active, TRUE) = TRUE
AND s.online_status = 'online'
AND s.uid IS NOT NULL
AND (p_station_id IS NULL OR s.station_id = p_station_id)
ORDER BY COALESCE(s.updated_at, s.created_at) DESC, s.created_at DESC
LIMIT 1;
RETURN v_candidate;
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_homecare_dispatch_candidate(TEXT, UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_homecare_dispatch_candidate(TEXT, UUID) TO authenticated;
COMMIT;

View File

@@ -79,7 +79,6 @@ 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_staff_assignable_select ON public.ml_delivery_staff;
DROP POLICY IF EXISTS delivery_staff_self_update ON public.ml_delivery_staff;
CREATE POLICY delivery_staff_self_select
ON public.ml_delivery_staff
@@ -95,18 +94,6 @@ CREATE POLICY delivery_staff_self_select
)
);
CREATE POLICY delivery_staff_assignable_select
ON public.ml_delivery_staff
FOR SELECT
TO authenticated
USING (
deleted_at IS NULL
AND status = 1
AND COALESCE(is_active, true) = true
AND online_status = 'online'
AND uid IS NOT NULL
);
CREATE POLICY delivery_staff_self_update
ON public.ml_delivery_staff
FOR UPDATE
@@ -149,4 +136,6 @@ 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 '软删除操作人';
-- 派单候选人读取统一走 SECURITY DEFINER RPC避免公开在线人员列表。
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,336 @@
BEGIN;
-- 与 delivery 侧保持同版,供 consumer/delivery 双端使用同一套 homecare foundation schema。
CREATE TABLE IF NOT EXISTS public.ec_service_requests (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
service_catalog_id TEXT NOT NULL DEFAULT '',
service_name TEXT NOT NULL DEFAULT '',
service_category TEXT NOT NULL DEFAULT '',
elder_name TEXT NOT NULL DEFAULT '',
elder_phone TEXT NOT NULL DEFAULT '',
contact_name TEXT NOT NULL DEFAULT '',
contact_phone TEXT NOT NULL DEFAULT '',
address_snapshot JSONB,
address_snapshot_json JSONB,
scheduled_at TIMESTAMPTZ,
remark TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'ORDER_CREATED',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ec_care_tasks (
id UUID PRIMARY KEY,
task_no TEXT NOT NULL UNIQUE,
request_id UUID REFERENCES public.ec_service_requests(id) ON DELETE SET NULL,
user_id UUID NOT NULL,
assigned_to UUID,
service_catalog_id TEXT NOT NULL DEFAULT '',
service_name TEXT NOT NULL DEFAULT '',
service_category TEXT NOT NULL DEFAULT '',
service_snapshot_json JSONB NOT NULL DEFAULT '{}'::jsonb,
elder_name TEXT NOT NULL DEFAULT '',
elder_phone TEXT NOT NULL DEFAULT '',
contact_name TEXT NOT NULL DEFAULT '',
contact_phone TEXT NOT NULL DEFAULT '',
address_snapshot JSONB,
address_snapshot_json JSONB,
scheduled_at TIMESTAMPTZ,
appointment_time TIMESTAMPTZ,
remark TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'ORDER_CREATED',
reject_reason TEXT NOT NULL DEFAULT '',
accepted_at TIMESTAMPTZ,
departed_at TIMESTAMPTZ,
checked_in_at TIMESTAMPTZ,
service_started_at TIMESTAMPTZ,
service_completed_at TIMESTAMPTZ,
acceptance_pending_at TIMESTAMPTZ,
accepted_by_family_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.ec_care_records (
id TEXT PRIMARY KEY,
task_id UUID NOT NULL REFERENCES public.ec_care_tasks(id) ON DELETE CASCADE,
record_type TEXT NOT NULL DEFAULT 'service_record',
care_record_type TEXT,
created_by UUID,
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
checked_in_at TIMESTAMPTZ,
checkin_time TIMESTAMPTZ,
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION,
location_text TEXT NOT NULL DEFAULT '',
duration_minutes INTEGER NOT NULL DEFAULT 0,
actual_duration_minutes INTEGER NOT NULL DEFAULT 0,
service_items_json JSONB NOT NULL DEFAULT '[]'::jsonb,
service_content_json JSONB NOT NULL DEFAULT '[]'::jsonb,
service_summary TEXT NOT NULL DEFAULT '',
process_note TEXT NOT NULL DEFAULT '',
summary TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
remark TEXT NOT NULL DEFAULT '',
elder_status TEXT NOT NULL DEFAULT '',
health_metrics_json JSONB NOT NULL DEFAULT '{}'::jsonb,
materials_used TEXT NOT NULL DEFAULT '',
abnormal_note TEXT NOT NULL DEFAULT '',
photos_json JSONB NOT NULL DEFAULT '[]'::jsonb,
staff_remark TEXT NOT NULL DEFAULT '',
family_confirmation_json JSONB NOT NULL DEFAULT '{}'::jsonb,
rating INTEGER,
tags_json JSONB NOT NULL DEFAULT '[]'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.hc_work_order_events (
id TEXT PRIMARY KEY,
task_id UUID NOT NULL REFERENCES public.ec_care_tasks(id) ON DELETE CASCADE,
from_status TEXT,
to_status TEXT NOT NULL DEFAULT '',
actor_id UUID,
actor_role TEXT NOT NULL DEFAULT '',
action TEXT NOT NULL DEFAULT '',
remark TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.hc_work_order_exceptions (
id TEXT PRIMARY KEY,
task_id UUID NOT NULL REFERENCES public.ec_care_tasks(id) ON DELETE CASCADE,
exception_type TEXT NOT NULL DEFAULT 'other',
description TEXT NOT NULL DEFAULT '',
occurred_at TIMESTAMPTZ,
location_text TEXT NOT NULL DEFAULT '',
images_json JSONB NOT NULL DEFAULT '[]'::jsonb,
need_platform_intervention BOOLEAN NOT NULL DEFAULT false,
request_cancel_order BOOLEAN NOT NULL DEFAULT false,
request_reschedule BOOLEAN NOT NULL DEFAULT false,
created_by UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.hc_evidence_files (
id TEXT PRIMARY KEY,
task_id UUID NOT NULL REFERENCES public.ec_care_tasks(id) ON DELETE CASCADE,
care_record_id TEXT,
phase TEXT NOT NULL DEFAULT 'service',
file_type TEXT NOT NULL DEFAULT 'image',
storage_path TEXT NOT NULL DEFAULT '',
file_url TEXT NOT NULL DEFAULT '',
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION,
captured_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_ec_service_requests_user_created
ON public.ec_service_requests(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_user_created
ON public.ec_care_tasks(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_assigned_status
ON public.ec_care_tasks(assigned_to, status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ec_care_records_task_created
ON public.ec_care_records(task_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_hc_work_order_events_task_created
ON public.hc_work_order_events(task_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_hc_work_order_exceptions_task_created
ON public.hc_work_order_exceptions(task_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_hc_evidence_files_task_created
ON public.hc_evidence_files(task_id, created_at DESC);
CREATE OR REPLACE FUNCTION public.tg_ec_set_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_ec_service_requests_updated_at ON public.ec_service_requests;
CREATE TRIGGER trg_ec_service_requests_updated_at
BEFORE UPDATE ON public.ec_service_requests
FOR EACH ROW
EXECUTE FUNCTION public.tg_ec_set_updated_at();
DROP TRIGGER IF EXISTS trg_ec_care_tasks_updated_at ON public.ec_care_tasks;
CREATE TRIGGER trg_ec_care_tasks_updated_at
BEFORE UPDATE ON public.ec_care_tasks
FOR EACH ROW
EXECUTE FUNCTION public.tg_ec_set_updated_at();
DROP TRIGGER IF EXISTS trg_ec_care_records_updated_at ON public.ec_care_records;
CREATE TRIGGER trg_ec_care_records_updated_at
BEFORE UPDATE ON public.ec_care_records
FOR EACH ROW
EXECUTE FUNCTION public.tg_ec_set_updated_at();
DROP TRIGGER IF EXISTS trg_hc_work_order_exceptions_updated_at ON public.hc_work_order_exceptions;
CREATE TRIGGER trg_hc_work_order_exceptions_updated_at
BEFORE UPDATE ON public.hc_work_order_exceptions
FOR EACH ROW
EXECUTE FUNCTION public.tg_ec_set_updated_at();
ALTER TABLE public.ec_service_requests ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_care_tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_care_records ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.hc_work_order_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.hc_work_order_exceptions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.hc_evidence_files ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS ec_service_requests_owner_select ON public.ec_service_requests;
CREATE POLICY ec_service_requests_owner_select
ON public.ec_service_requests
FOR SELECT
TO authenticated
USING (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()));
DROP POLICY IF EXISTS ec_service_requests_owner_insert ON public.ec_service_requests;
CREATE POLICY ec_service_requests_owner_insert
ON public.ec_service_requests
FOR INSERT
TO authenticated
WITH CHECK (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()));
DROP POLICY IF EXISTS ec_service_requests_owner_update ON public.ec_service_requests;
CREATE POLICY ec_service_requests_owner_update
ON public.ec_service_requests
FOR UPDATE
TO authenticated
USING (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()))
WITH CHECK (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()));
DROP POLICY IF EXISTS ec_care_tasks_owner_or_staff_select ON public.ec_care_tasks;
CREATE POLICY ec_care_tasks_owner_or_staff_select
ON public.ec_care_tasks
FOR SELECT
TO authenticated
USING (
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
);
DROP POLICY IF EXISTS ec_care_tasks_owner_insert ON public.ec_care_tasks;
CREATE POLICY ec_care_tasks_owner_insert
ON public.ec_care_tasks
FOR INSERT
TO authenticated
WITH CHECK (user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid()));
DROP POLICY IF EXISTS ec_care_tasks_owner_or_staff_update ON public.ec_care_tasks;
CREATE POLICY ec_care_tasks_owner_or_staff_update
ON public.ec_care_tasks
FOR UPDATE
TO authenticated
USING (
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
WITH CHECK (
user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
);
DROP POLICY IF EXISTS ec_care_records_task_access ON public.ec_care_records;
CREATE POLICY ec_care_records_task_access
ON public.ec_care_records
FOR ALL
TO authenticated
USING (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
)
WITH CHECK (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
);
DROP POLICY IF EXISTS hc_work_order_events_task_access ON public.hc_work_order_events;
CREATE POLICY hc_work_order_events_task_access
ON public.hc_work_order_events
FOR ALL
TO authenticated
USING (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
)
WITH CHECK (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
);
DROP POLICY IF EXISTS hc_work_order_exceptions_task_access ON public.hc_work_order_exceptions;
CREATE POLICY hc_work_order_exceptions_task_access
ON public.hc_work_order_exceptions
FOR ALL
TO authenticated
USING (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
)
WITH CHECK (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
);
DROP POLICY IF EXISTS hc_evidence_files_task_access ON public.hc_evidence_files;
CREATE POLICY hc_evidence_files_task_access
ON public.hc_evidence_files
FOR ALL
TO authenticated
USING (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
)
WITH CHECK (
task_id IN (
SELECT t.id
FROM public.ec_care_tasks t
WHERE t.user_id IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
OR t.assigned_to IN (SELECT id FROM public.ak_users WHERE auth_id = auth.uid())
)
);
COMMIT;

View File

@@ -0,0 +1,223 @@
BEGIN;
-- 1. 补充 ec_service_requests / ec_care_tasks / hss_service_orders 缺少的 age / gender 字段
ALTER TABLE public.ec_service_requests
ADD COLUMN IF NOT EXISTS elder_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS elder_gender TEXT NOT NULL DEFAULT '';
ALTER TABLE public.ec_care_tasks
ADD COLUMN IF NOT EXISTS elder_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS elder_gender TEXT NOT NULL DEFAULT '';
ALTER TABLE public.hss_service_orders
ADD COLUMN IF NOT EXISTS recipient_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS recipient_gender TEXT NOT NULL DEFAULT '';
-- 2. 修复 delivery_build_order_json地址 snapshot 增加 address_snapshot fallback elderGender / elderAge 从原始行读取
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', p_raw -> 'address_snapshot', '{}'::jsonb);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 0),
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0),
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', ''),
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb),
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
COMMIT;

View File

@@ -0,0 +1,66 @@
-- ============================================
-- 诊断脚本:定位 delivery 端地址空白问题
-- 请复制全部内容执行,把结果截图发给我
-- ============================================
-- 诊断1确认 delivery_build_order_json 当前版本
SELECT
proname,
CASE
WHEN prosrc LIKE '%NULLIF(p_raw -> ''address_snapshot_json'', ''{}''::jsonb)%' THEN 'FIXED (含 NULLIF 排除空对象)'
WHEN prosrc LIKE '%p_raw -> ''address_snapshot''%' THEN 'HAS_FALLBACK 但缺少 NULLIF'
ELSE 'NO_FALLBACK (只有 address_snapshot_json)'
END AS address_fallback_status,
CASE
WHEN prosrc LIKE '%|| jsonb_build_object%' THEN 'SPLIT (小块拼接)'
ELSE 'SINGLE_BLOCK (单个大块)'
END AS function_structure
FROM pg_proc
WHERE proname = 'delivery_build_order_json';
-- 诊断2查看 ec_care_tasks 最近5条订单的地址数据状态
SELECT
id,
task_no,
elder_name,
assigned_to,
COALESCE(address_snapshot_json::TEXT, 'NULL') AS addr_json_raw,
COALESCE(address_snapshot::TEXT, 'NULL') AS addr_snap_raw,
CASE
WHEN address_snapshot_json IS NULL THEN 'json_null'
WHEN address_snapshot_json = '{}'::jsonb THEN 'json_empty'
WHEN address_snapshot_json = 'null'::jsonb THEN 'json_literal_null'
ELSE 'json_has_data'
END AS json_status,
CASE
WHEN address_snapshot IS NULL THEN 'snap_null'
WHEN address_snapshot = '{}'::jsonb THEN 'snap_empty'
WHEN address_snapshot = 'null'::jsonb THEN 'snap_literal_null'
ELSE 'snap_has_data'
END AS snap_status
FROM public.ec_care_tasks
ORDER BY created_at DESC
LIMIT 5;
-- 诊断3直接测试 delivery_build_order_json 对最近一条 care 订单的输出
-- 如果返回的 address 为空字符串,说明函数内部有问题
SELECT
t.id,
t.task_no,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'address' AS test_address,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'elderGender' AS test_gender,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'elderAge' AS test_age
FROM public.ec_care_tasks t
ORDER BY t.created_at DESC
LIMIT 1;

View File

@@ -0,0 +1,30 @@
-- ============================================
-- 诊断:查看订单的地址数据
-- ============================================
-- 查看 ec_care_tasks 中这个订单的地址字段用订单ID替换 'xxx'
SELECT
id,
task_no,
COALESCE(address_snapshot_json::TEXT, 'NULL') AS address_snapshot_json_raw,
COALESCE(address_snapshot::TEXT, 'NULL') AS address_snapshot_raw,
CASE
WHEN address_snapshot_json IS NULL THEN 'null'
WHEN address_snapshot_json = '{}'::jsonb THEN 'empty_object'
ELSE 'has_data'
END AS json_status,
CASE
WHEN address_snapshot IS NULL THEN 'null'
WHEN address_snapshot = '{}'::jsonb THEN 'empty_object'
ELSE 'has_data'
END AS snapshot_status
FROM public.ec_care_tasks
WHERE task_no LIKE '%20260528%' -- 根据订单号调整
ORDER BY created_at DESC
LIMIT 5;
-- 直接测试 delivery_build_order_json 对这条数据的输出
-- SELECT delivery_build_order_json(
-- (SELECT to_jsonb(t) FROM public.ec_care_tasks t WHERE t.id = '订单UUID'::uuid),
-- '[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
-- ) ->> 'address' AS test_address;

View File

@@ -0,0 +1,267 @@
-- ============================================
-- 诊断脚本:排查 delivery RPC 超时问题
-- 请在 Supabase SQL Editor 中逐条执行
-- ============================================
-- 诊断1确认 delivery_build_order_json 当前是 baseline 还是 patched 版本
SELECT
proname,
CASE
WHEN prosrc LIKE '%p_raw -> ''address_snapshot''%' THEN 'PATCHED (含 address_snapshot fallback)'
WHEN prosrc LIKE '%''elderGender'', COALESCE(NULLIF(p_raw ->> ''elder_gender''%' THEN 'PATCHED (动态读取 gender/age)'
ELSE 'BASELINE (原始版本)'
END AS function_status,
md5(prosrc) AS function_md5,
LENGTH(prosrc) AS src_length
FROM pg_proc
WHERE proname = 'delivery_build_order_json';
-- 诊断2直接测试 delivery_build_order_json 是否能正常执行(不依赖任何表数据)
-- 如果这条语句能立即返回 JSONB 结果,说明函数本身没有问题
SELECT delivery_build_order_json(
'{"id": "test123", "service_name": "测试服务", "status": "assigned", "address_snapshot_json": {"fullAddress": "测试地址"}}'::jsonb,
'[]'::jsonb,
'[]'::jsonb,
'[]'::jsonb,
NULL,
'legacy'
) AS test_result;
-- 诊断3查看 hss_service_orders 有多少条记录、有多少条已删除、有多少条分配给当前登录用户
-- 注意auth.uid() 在 SQL Editor 中为 NULL所以 current_staff_id 匹配会返回空结果
-- 这条只是确认表结构和数据量
SELECT
COUNT(*) AS total_orders,
COUNT(*) FILTER (WHERE deleted_at IS NOT NULL) AS deleted_orders,
COUNT(*) FILTER (WHERE deleted_at IS NULL) AS active_orders,
COUNT(*) FILTER (WHERE current_staff_id IS NOT NULL) AS assigned_orders
FROM public.hss_service_orders;
-- 诊断4查看 ec_care_tasks 有多少条记录(如果存在)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'ec_care_tasks') THEN
RAISE NOTICE 'ec_care_tasks exists, row count: %', (SELECT COUNT(*) FROM public.ec_care_tasks);
ELSE
RAISE NOTICE 'ec_care_tasks does NOT exist';
END IF;
END $$;
-- 诊断5强制刷新 PostgREST schema cache如果怀疑是 schema 缓存问题)
-- 执行后等待 5-10 秒再测试前端
NOTIFY pgrst, 'reload schema';
-- 诊断6如果诊断1显示函数是 PATCHED 版本,执行以下恢复语句(取消注释后运行)
-- 或者如果诊断2卡住/超时,说明 patched 版本有问题,也需要恢复
/*
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', '{}'::jsonb);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', '',
'elderAge', 0,
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0),
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', ''),
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb),
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
*/

View File

@@ -0,0 +1,222 @@
-- ============================================
-- 修复 delivery 端地址读取问题
-- 增强 address_snapshot / address_snapshot_json 的兼容性
-- ============================================
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(
NULLIF(p_raw -> 'address_snapshot_json', '{}'::jsonb),
NULLIF(p_raw -> 'address_snapshot_json', 'null'::jsonb),
NULLIF(p_raw -> 'address_snapshot', '{}'::jsonb),
NULLIF(p_raw -> 'address_snapshot', 'null'::jsonb),
'{}'::jsonb
);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 0),
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0)
) || jsonb_build_object(
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', '')
) || jsonb_build_object(
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb)
) || jsonb_build_object(
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
-- 刷新 PostgREST schema cache
NOTIFY pgrst, 'reload schema';

View File

@@ -0,0 +1,227 @@
-- ============================================
-- 修复 delivery 端地址读取问题v2
-- 根因COALESCE + NULLIF 对 JSONB 类型处理不可靠
-- 改用 CASE WHEN jsonb_typeof() 明确判断有效对象
-- ============================================
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := CASE
WHEN jsonb_typeof(p_raw -> 'address_snapshot_json') = 'object'
AND p_raw -> 'address_snapshot_json' != '{}'::jsonb
AND p_raw -> 'address_snapshot_json' != 'null'::jsonb
THEN p_raw -> 'address_snapshot_json'
WHEN jsonb_typeof(p_raw -> 'address_snapshot') = 'object'
AND p_raw -> 'address_snapshot' != '{}'::jsonb
AND p_raw -> 'address_snapshot' != 'null'::jsonb
THEN p_raw -> 'address_snapshot'
ELSE '{}'::jsonb
END;
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 0),
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0)
) || jsonb_build_object(
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', '')
) || jsonb_build_object(
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb)
) || jsonb_build_object(
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
-- 刷新 PostgREST schema cache
NOTIFY pgrst, 'reload schema';

View File

@@ -0,0 +1,216 @@
-- ============================================
-- 快速恢复:将 delivery_build_order_json 恢复为原始版本
-- 请复制以下全部内容,在 Supabase SQL Editor 中执行
-- ============================================
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', p_raw -> 'address_snapshot', '{}'::jsonb);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 0),
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0)
) || jsonb_build_object(
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', '')
) || jsonb_build_object(
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb)
) || jsonb_build_object(
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
-- 刷新 PostgREST schema cache有时 schema 变更后需要刷新)
NOTIFY pgrst, 'reload schema';

View File

@@ -0,0 +1,18 @@
-- ============================================
-- 重新添加 age / gender 列consumer 端提交预约需要)
-- 不影响 delivery 端链路
-- ============================================
-- care 表
ALTER TABLE public.ec_service_requests
ADD COLUMN IF NOT EXISTS elder_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS elder_gender TEXT NOT NULL DEFAULT '';
ALTER TABLE public.ec_care_tasks
ADD COLUMN IF NOT EXISTS elder_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS elder_gender TEXT NOT NULL DEFAULT '';
-- 旧订单表
ALTER TABLE public.hss_service_orders
ADD COLUMN IF NOT EXISTS recipient_age INTEGER NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS recipient_gender TEXT NOT NULL DEFAULT '';

View File

@@ -0,0 +1,208 @@
BEGIN;
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', '{}'::jsonb);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', '',
'elderAge', 0,
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0),
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', ''),
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb),
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
COMMIT;

View File

@@ -17,9 +17,8 @@
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "登录",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
"navigationBarTitleText": "用户登录",
"navigationStyle": "custom"
}
},
{

View File

@@ -37,6 +37,7 @@
import { ref, onMounted, getCurrentInstance } from 'vue'
import { onShow, onLoad } from '@dcloudio/uni-app'
import { supabaseService, type UserAddress as SupabaseUserAddress } from '@/utils/supabaseService.uts'
import { HomeServiceSelectedAddressType } from '@/types/home-service.uts'
type Address = {
id: string
@@ -170,27 +171,31 @@ const editAddress = (id: string) => {
const selectAddress = (item: Address) => {
if (selectionMode.value) {
uni.$emit('addressSelected', {
id: item.id,
const fullAddressText = getFullAddress(item)
const selectedAddress = {
addressId: item.id,
userId: '',
recipient_name: item.name,
isDefault: item.isDefault,
contactName: item.name,
phone: item.phone,
contactPhone: item.phone,
province: item.province,
city: item.city,
district: item.district,
detail: item.detail,
phone: item.phone,
addressName: `${item.province}${item.city}${item.district}`,
locationName: `${item.province}${item.city}${item.district}`,
addressDetail: item.detail,
locationAddress: `${item.province}${item.city}${item.district}`,
houseNumber: item.detail,
fullAddress: getFullAddress(item),
remark: item.label ?? '',
doorNo: item.detail,
fullAddress: fullAddressText,
latitude: item.latitude ?? 0,
longitude: item.longitude ?? 0,
remark: item.label ?? '',
coordinateType: item.coordinateType ?? 'gcj02',
is_default: item.isDefault
})
createdAt: Date.now(),
updatedAt: Date.now()
} as HomeServiceSelectedAddressType
uni.setStorageSync('hss_selected_service_address', selectedAddress)
uni.$emit('addressSelected', selectedAddress)
uni.navigateBack()
} else {
editAddress(item.id)

View File

@@ -730,7 +730,7 @@ function selectAddress() {
if (!ok) {
return
}
uni.navigateTo({ url: '/pages/address/address-list' })
uni.navigateTo({ url: '/pages/mall/consumer/address-list?selectMode=true' })
})
}
@@ -850,6 +850,7 @@ async function submitBooking() {
applicantName: contactName.value,
elderName: recipientName.value,
age: parsedAge,
gender: recipientGender.value,
phone: contactPhone.value,
address: addressLineText.value,
preferredTime: selectedTimeText.value,

View File

@@ -455,6 +455,8 @@ export async function createHomeServiceApplication(draft: HomeServiceApplication
},
recipientName: draft.elderName,
recipientPhone: draft.phone,
recipientAge: draft.age,
recipientGender: draft.gender,
contactName: draft.applicantName,
contactPhone: draft.phone,
appointmentTime: draft.preferredTime,
@@ -565,7 +567,7 @@ function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
serviceTime: formatServiceAppointmentText(order.appointmentTime),
applicantName: order.contactName,
elderName: order.recipientName,
age: 0,
age: order.recipientAge,
phone: order.contactPhone,
address: order.addressSnapshot.fullAddress,
summary: order.remark,
@@ -645,7 +647,16 @@ async function listWorkerTaskIds(): Promise<Array<string>> {
return result
}
}
const legacyResponse = await supa.from('hss_service_orders').select('id').eq('current_staff_id', userId).order('created_at', { ascending: false }).execute()
let staffProfileId = ''
const staffResponse = await supa.from('ml_delivery_staff').select('id').eq('uid', userId).limit(1).execute()
if (staffResponse.error == null && staffResponse.data != null) {
const staffRows = staffResponse.data as Array<any>
if (staffRows.length > 0) {
staffProfileId = readString(staffRows[0], 'id')
}
}
const legacyStaffId = staffProfileId != '' ? staffProfileId : userId
const legacyResponse = await supa.from('hss_service_orders').select('id').eq('current_staff_id', legacyStaffId).order('created_at', { ascending: false }).execute()
if (legacyResponse.error != null || legacyResponse.data == null) {
return [] as Array<string>
}

View File

@@ -21,12 +21,16 @@ export type CreateServiceOrderParams = {
address: ServiceOrderAddressSnapshotType
recipientName: string
recipientPhone: string
recipientAge: number
recipientGender: string
contactName: string
contactPhone: string
appointmentTime: string
remark: string
}
const HOMECARE_DISPATCH_CANDIDATE_RPC = 'rpc_homecare_dispatch_candidate'
function nowText(): string {
return new Date().toISOString().replace('T', ' ').substring(0, 19)
}
@@ -254,36 +258,16 @@ function getStaffPriority(staff: any): number {
}
async function getAutoAssignableStaff(): Promise<any | null> {
const staffResponse = await supa
.from('ml_delivery_staff')
.select('id, uid, station_id, status, deleted_at, is_active, online_status, updated_at, created_at')
.eq('status', 1)
.eq('online_status', 'online')
.execute()
if (staffResponse.error != null || staffResponse.data == null) {
try {
const rpcResponse: any = await supa.rpc(HOMECARE_DISPATCH_CANDIDATE_RPC, {} as any)
if (rpcResponse == null || rpcResponse.error != null || rpcResponse.data == null) {
return null
}
return rpcResponse.data
} catch (error) {
console.warn('getAutoAssignableStaff rpc failed', error)
return null
}
const rawStaffList = staffResponse.data as Array<any>
let selected: any = null
let bestScore = -1
let bestTime = ''
for (let i = 0; i < rawStaffList.length; i++) {
const staff = rawStaffList[i]
if (!isStaffActive(staff)) {
continue
}
if (readString(staff, 'uid') == '') {
continue
}
const score = getStaffPriority(staff)
const timeMark = readFirstString(staff, ['updated_at', 'created_at'])
if (selected == null || score > bestScore || (score == bestScore && timeMark > bestTime)) {
selected = staff
bestScore = score
bestTime = timeMark
}
}
return selected
}
function buildEcServiceRequestPayload(params: CreateServiceOrderParams, userId: string, requestId: string, createdAt: string, appointmentTime: string | null, useAddressSnapshot: boolean): any {
@@ -295,6 +279,8 @@ function buildEcServiceRequestPayload(params: CreateServiceOrderParams, userId:
service_category: params.service.category,
elder_name: params.recipientName,
elder_phone: params.recipientPhone,
elder_age: params.recipientAge,
elder_gender: params.recipientGender,
contact_name: params.contactName,
contact_phone: params.contactPhone,
scheduled_at: appointmentTime,
@@ -320,6 +306,8 @@ function buildEcServiceRequestPayloadWithoutAddress(params: CreateServiceOrderPa
service_category: params.service.category,
elder_name: params.recipientName,
elder_phone: params.recipientPhone,
elder_age: params.recipientAge,
elder_gender: params.recipientGender,
contact_name: params.contactName,
contact_phone: params.contactPhone,
scheduled_at: appointmentTime,
@@ -342,6 +330,8 @@ function buildEcCareTaskPayload(params: CreateServiceOrderParams, userId: string
service_snapshot_json: params.service as any,
elder_name: params.recipientName,
elder_phone: params.recipientPhone,
elder_age: params.recipientAge,
elder_gender: params.recipientGender,
contact_name: params.contactName,
contact_phone: params.contactPhone,
scheduled_at: appointmentTime,
@@ -370,6 +360,8 @@ function buildEcCareTaskPayloadWithoutAddress(params: CreateServiceOrderParams,
service_snapshot_json: params.service as any,
elder_name: params.recipientName,
elder_phone: params.recipientPhone,
elder_age: params.recipientAge,
elder_gender: params.recipientGender,
contact_name: params.contactName,
contact_phone: params.contactPhone,
scheduled_at: appointmentTime,
@@ -480,6 +472,8 @@ function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>,
},
recipientName: readString(item, 'recipient_name'),
recipientPhone: readString(item, 'recipient_phone'),
recipientAge: readFirstNumber(item, ['recipient_age', 'elder_age']),
recipientGender: readFirstString(item, ['recipient_gender', 'elder_gender']),
contactName: readString(item, 'contact_name'),
contactPhone: readString(item, 'contact_phone'),
appointmentTime: readString(item, 'appointment_time'),
@@ -604,6 +598,8 @@ function mapCareTaskRowToLegacyOrderRow(item: any): any {
address_snapshot_json: addressSnapshotValue != null ? addressSnapshotValue : JSON.parse('{}'),
recipient_name: readFirstString(item, ['elder_name', 'recipient_name']),
recipient_phone: readFirstString(item, ['elder_phone', 'recipient_phone']),
recipient_age: readFirstNumber(item, ['elder_age', 'recipient_age']),
recipient_gender: readFirstString(item, ['elder_gender', 'recipient_gender']),
contact_name: readString(item, 'contact_name'),
contact_phone: readString(item, 'contact_phone'),
appointment_time: readFirstString(item, ['scheduled_at', 'appointment_time']),
@@ -925,6 +921,8 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
address_snapshot_json: params.address as any,
recipient_name: params.recipientName,
recipient_phone: params.recipientPhone,
recipient_age: params.recipientAge,
recipient_gender: params.recipientGender,
contact_name: params.contactName,
contact_phone: params.contactPhone,
appointment_time: appointmentTime,

View File

@@ -92,6 +92,7 @@ export type HomeServiceApplicationDraftType = {
applicantName: string
elderName: string
age: number
gender: string
phone: string
address: string
preferredTime: string

View File

@@ -124,6 +124,8 @@ export type ServiceOrderType = {
addressSnapshot: ServiceOrderAddressSnapshotType
recipientName: string
recipientPhone: string
recipientAge: number
recipientGender: string
contactName: string
contactPhone: string
appointmentTime: string

View File

@@ -1,389 +0,0 @@
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ak_users filter: auth_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ak_users?select=*&auth_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 Profile Load Result: AkReqResponse {status: 200, data: Array(1), headers: Proxy, error: null, total: 1, …}
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_catalog filter: status=eq.1&deleted_at=is.null
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_catalog?select=id%2C%20name%2C%20category%2C%20price%2C%20duration_text%2C%20summary%2C%20tags_json%2C%20suitable_for%2C%20sort_no&order=sort_no.asc&status=eq.1&deleted_at=is.null
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ec_service_requests filter: null
mp.esm.js:529 [ak-req] POST http://119.146.131.237:9126/rest/v1/ec_service_requests
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: return=representation
uni.api.esm.js:1042 POST http://119.146.131.237:9126/rest/v1/ec_service_requests 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ uni.api.esm.js:1042
invokeApi @ uni.api.esm.js:330
promiseApi @ uni.api.esm.js:889
(anonymous) @ ak-req.uts:214
doOnce @ ak-req.uts:213
_loop$ @ ak-req.uts:328
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
s @ regeneratorRuntime.js?forceSync=true:1
_ @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
request @ ak-req.uts:148
_callee19$ @ aksupa.uts:1290
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
requestWithAutoRefresh @ aksupa.uts:1289
_callee13$ @ aksupa.uts:1109
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
insert @ aksupa.uts:1084
_callee$ @ aksupa.uts:469
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
execute @ aksupa.uts:369
_callee5$ @ serviceOrderService.uts:754
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
tryCreateCareTask @ serviceOrderService.uts:742
_callee7$ @ serviceOrderService.uts:874
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createServiceOrder @ serviceOrderService.uts:873
_callee4$ @ homeServiceService.uts:440
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createHomeServiceApplication @ homeServiceService.uts:430
_callee3$ @ service-detail.uvue:860
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
submitBooking @ service-detail.uvue:813
callWithErrorHandling @ vue.runtime.esm.js:1356
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
invoke @ vue.runtime.esm.js:6223
setTimeout (async)
invoker @ vue.runtime.esm.js:6232
Show 28 more frames
mp.esm.js:529 [ak-req] HTTP error response(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:304
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] status: 400(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:305
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] url: http://119.146.131.237:9126/rest/v1/ec_service_requests(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:306
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] body: {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'address_snapshot' column of 'ec_service_requests' in the schema cache"}(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:307
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ec_service_requests filter: null
mp.esm.js:529 [ak-req] POST http://119.146.131.237:9126/rest/v1/ec_service_requests
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: return=representation
uni.api.esm.js:1042 POST http://119.146.131.237:9126/rest/v1/ec_service_requests 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ uni.api.esm.js:1042
invokeApi @ uni.api.esm.js:330
promiseApi @ uni.api.esm.js:889
(anonymous) @ ak-req.uts:214
doOnce @ ak-req.uts:213
_loop$ @ ak-req.uts:328
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
s @ regeneratorRuntime.js?forceSync=true:1
_ @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
request @ ak-req.uts:148
_callee19$ @ aksupa.uts:1290
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
requestWithAutoRefresh @ aksupa.uts:1289
_callee13$ @ aksupa.uts:1109
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
insert @ aksupa.uts:1084
_callee$ @ aksupa.uts:469
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
execute @ aksupa.uts:369
_callee5$ @ serviceOrderService.uts:758
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
tryCreateCareTask @ serviceOrderService.uts:742
_callee7$ @ serviceOrderService.uts:874
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createServiceOrder @ serviceOrderService.uts:873
_callee4$ @ homeServiceService.uts:440
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createHomeServiceApplication @ homeServiceService.uts:430
_callee3$ @ service-detail.uvue:860
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
submitBooking @ service-detail.uvue:813
callWithErrorHandling @ vue.runtime.esm.js:1356
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
invoke @ vue.runtime.esm.js:6223
setTimeout (async)
invoker @ vue.runtime.esm.js:6232
mp.esm.js:529 [ak-req] HTTP error response(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:304
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] status: 400(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:305
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] url: http://119.146.131.237:9126/rest/v1/ec_service_requests(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:306
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] body: {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'address_snapshot_json' column of 'ec_service_requests' in the schema cache"}(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:307
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ec_service_requests filter: null
mp.esm.js:529 [ak-req] POST http://119.146.131.237:9126/rest/v1/ec_service_requests
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: return=representation
uni.api.esm.js:1042 POST http://119.146.131.237:9126/rest/v1/ec_service_requests 400 (Bad Request)(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ uni.api.esm.js:1042
invokeApi @ uni.api.esm.js:330
promiseApi @ uni.api.esm.js:889
(anonymous) @ ak-req.uts:214
doOnce @ ak-req.uts:213
_loop$ @ ak-req.uts:328
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
s @ regeneratorRuntime.js?forceSync=true:1
_ @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
request @ ak-req.uts:148
_callee19$ @ aksupa.uts:1290
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
requestWithAutoRefresh @ aksupa.uts:1289
_callee13$ @ aksupa.uts:1109
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
insert @ aksupa.uts:1084
_callee$ @ aksupa.uts:469
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
execute @ aksupa.uts:369
_callee5$ @ serviceOrderService.uts:763
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
tryCreateCareTask @ serviceOrderService.uts:742
_callee7$ @ serviceOrderService.uts:874
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createServiceOrder @ serviceOrderService.uts:873
_callee4$ @ homeServiceService.uts:440
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
createHomeServiceApplication @ homeServiceService.uts:430
_callee3$ @ service-detail.uvue:860
s @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
fulfilled @ uni.mp.esm.js:1134
Promise.then (async)
step @ uni.mp.esm.js:1134
(anonymous) @ uni.mp.esm.js:1134
__awaiter @ uni.mp.esm.js:1134
submitBooking @ service-detail.uvue:813
callWithErrorHandling @ vue.runtime.esm.js:1356
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
invoke @ vue.runtime.esm.js:6223
setTimeout (async)
invoker @ vue.runtime.esm.js:6232
mp.esm.js:529 [ak-req] HTTP error response(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:304
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] status: 400(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:305
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] url: http://119.146.131.237:9126/rest/v1/ec_service_requests(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:306
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [ak-req] body: {"code":"PGRST204","details":null,"hint":null,"message":"Could not find the 'contact_name' column of 'ec_service_requests' in the schema cache"}(env: Windows,mp,1.06.2504030; lib: 3.16.0)
(anonymous) @ mp.esm.js:529
__f__ @ uni.api.esm.js:590
success @ ak-req.uts:307
(anonymous) @ uni.api.esm.js:946
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_orders filter: null
mp.esm.js:529 [ak-req] POST http://119.146.131.237:9126/rest/v1/hss_service_orders
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: return=representation
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_order_status_logs filter: null
mp.esm.js:529 [ak-req] POST http://119.146.131.237:9126/rest/v1/hss_service_order_status_logs
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: return=representation
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ml_delivery_staff filter: status=eq.1
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ml_delivery_staff?select=id%2C%20uid%2C%20station_id%2C%20nickname%2C%20phone%2C%20status%2C%20deleted_at%2C%20is_active%2C%20online_status%2C%20updated_at%2C%20created_at&status=eq.1
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_orders filter: id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_orders?select=*&limit=1&id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_order_status_logs filter: order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_order_status_logs?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_reviews filter: order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_reviews?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_execution_records filter: order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_execution_records?select=*&limit=1&order=created_at.desc&order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_evidence_files filter: order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_evidence_files?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
[自动热重载] 已开启代码文件保存后自动热重载
2mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: ak_users filter: auth_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ak_users?select=*&auth_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/ak_users?select=*&auth_id=eq.b653fded-7d5e-4950-aa0d-725595543e3c
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 Profile Load Result: AkReqResponse {status: 200, data: Array(1), headers: Proxy, error: null, total: 1, …}
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_orders filter: id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_orders?select=*&limit=1&id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_order_status_logs filter: order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_order_status_logs?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
mp.esm.js:529 Profile Load Result: AkReqResponse {status: 200, data: Array(1), headers: Proxy, error: null, total: 1, …}
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_orders filter: id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_orders?select=*&limit=1&id=eq.so-1779788538646-23864
mp.esm.js:529 [ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
mp.esm.js:529 [AkSupaQueryBuilder] execute - 表: hss_service_reviews filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_reviews?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
[AkSupaQueryBuilder] execute - 表: hss_service_execution_records filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_execution_records?select=*&limit=1&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
[AkSupaQueryBuilder] execute - 表: hss_service_evidence_files filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_evidence_files?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
[pages/mall/consumer/home-service/order-detail] 提示: text 组件包含了长文本,可以考虑增加 user-select 属性,方便用户复制。
[AkSupaQueryBuilder] execute - 表: hss_service_order_status_logs filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_order_status_logs?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
[AkSupaQueryBuilder] execute - 表: hss_service_reviews filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_reviews?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)
[AkSupaQueryBuilder] execute - 表: hss_service_execution_records filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_execution_records?select=*&limit=1&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: count=exact
[AkSupaQueryBuilder] execute - 表: hss_service_evidence_files filter: order_id=eq.so-1779788538646-23864
[ak-req] GET http://119.146.131.237:9126/rest/v1/hss_service_evidence_files?select=*&order=created_at.desc&order_id=eq.so-1779788538646-23864
[ak-req] apikey: eyJhbG...7890 | Authorization: Bearer eyJhbG...2Rsk | auth-mode: pre-set | prefer: (none)