-- ===================================================================================== -- 20260610_fix_delivery_order_list_pending_blank.sql -- Purpose: -- 修复配送端"待接单"Tab 订单卡片显示空白的问题。 -- 根因: -- 1. hss_service_orders 表缺少 request_id / price / staff_income / service_category 等列 -- 2. delivery_build_order_json 对空字段的 COALESCE 兜底不足,返回 "" / 0 -- 3. delivery_order_matches 对 pending tab 的过滤逻辑正确,但返回的订单核心字段为空 -- 4. 前端 enrichOrderWithRequestFallback 依赖 request_id 补全,但 hss_service_orders 无此列 -- 5. pending_assignment / pending_accept 不在原表 CHECK 约束中 -- ===================================================================================== BEGIN; -- ============================================================================ -- 1. 为 hss_service_orders 补齐缺失的核心列 -- ============================================================================ ALTER TABLE public.hss_service_orders ADD COLUMN IF NOT EXISTS request_id TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS service_category TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS price NUMERIC(10, 2) NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS staff_income NUMERIC(10, 2) NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS elder_name TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS elder_phone TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS contact_name TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS contact_phone TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS scheduled_at TIMESTAMPTZ, ADD COLUMN IF NOT EXISTS dispatch_status TEXT NOT NULL DEFAULT 'pending', ADD COLUMN IF NOT EXISTS dispatch_attempt_count INTEGER NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS dispatch_error_code TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS dispatch_error_message TEXT NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS dispatch_failed_at TIMESTAMPTZ, ADD COLUMN IF NOT EXISTS service_price NUMERIC(10, 2) NOT NULL DEFAULT 0, ADD COLUMN IF NOT EXISTS duration_minutes INTEGER NOT NULL DEFAULT 90; -- 扩展 CHECK 约束:允许 pending_assignment / pending_accept 状态 -- 由于 PostgreSQL 不支持直接修改 CHECK,需要 DROP + CREATE ALTER TABLE public.hss_service_orders DROP CONSTRAINT IF EXISTS chk_hss_service_orders_status; ALTER TABLE public.hss_service_orders ADD CONSTRAINT chk_hss_service_orders_status CHECK ( status IN ( 'created', 'paid', 'assigned', 'accepted', 'rejected', 'departed', 'arrived', 'in_service', 'completed', 'pending_acceptance', 'accepted_by_user', 'reviewed', 'settled', 'cancelled', 'exception', 'pending_assignment', 'pending_accept', 'waiting_departure', 'on_the_way', 'serving', 'pending_confirm', 'pending_submit', 'abnormal', 'exception_pending', 'archived' ) ); -- ============================================================================ -- 2. 修复 delivery_build_order_json:所有展示字段必须有兜底值 -- ============================================================================ DROP FUNCTION IF EXISTS public.delivery_build_order_json(JSONB, JSONB, JSONB, JSONB, JSONB, TEXT); 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, COALESCE((p_raw ->> 'price')::NUMERIC, 0)))); v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', COALESCE(p_raw -> 'address_snapshot', '{}'::jsonb)); v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'pending_assignment'); 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; v_service_name TEXT; v_address_text TEXT; v_address_detail TEXT; v_full_address TEXT; v_appointment_time TEXT; v_staff_income NUMERIC; v_price NUMERIC; BEGIN -- 优先从 service_snapshot_json 获取 category/price IF v_service ->> 'category' = '' THEN v_service := v_service || jsonb_build_object( 'category', COALESCE(NULLIF(p_raw ->> 'service_category', ''), '居家服务'), 'price', COALESCE( NULLIF(p_raw ->> 'price', '')::NUMERIC, NULLIF(p_raw ->> 'service_price', '')::NUMERIC, 0 ) ); END IF; 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 'pending_assignment' 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; -- 【核心修复】serviceName 多级兜底 v_service_name := COALESCE( NULLIF(p_raw ->> 'service_name', ''), COALESCE(v_service ->> 'category', ''), COALESCE(p_raw ->> 'service_category', ''), '居家服务订单' ); -- 【核心修复】address 多级兜底 v_address_text := COALESCE( NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), NULLIF(v_address ->> 'name', ''), '' ); v_address_detail := COALESCE( NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), '' ); IF v_address_text = '' THEN v_address_text := '地址待补充'; END IF; v_full_address := v_address_text; IF v_address_detail != '' AND v_full_address !~ v_address_detail THEN v_full_address := v_full_address || ' ' || v_address_detail; END IF; -- 【核心修复】appointmentTime 多级兜底 v_appointment_time := COALESCE( NULLIF(p_raw ->> 'appointment_time', ''), NULLIF(p_raw ->> 'scheduled_at', ''), NULLIF(p_raw ->> 'appointment_start_time', ''), COALESCE(to_char((p_raw ->> 'created_at')::timestamptz, 'YYYY-MM-DD HH24:MI:SS'), ''), '时间待补充' ); -- 【核心修复】staffIncome / price 多级兜底 v_price := COALESCE( NULLIF(v_service ->> 'price', '')::NUMERIC, NULLIF(p_raw ->> 'price', '')::NUMERIC, NULLIF(p_raw ->> 'service_price', '')::NUMERIC, 0 ); v_staff_income := COALESCE( NULLIF(p_raw ->> 'staff_income', '')::NUMERIC, v_price, 0 ); 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', v_service_name, 'serviceCategory', COALESCE(NULLIF(p_raw ->> 'service_category', ''), 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 ->> 'elder_name', ''), NULLIF(p_raw ->> 'recipient_name', ''), '服务对象待补充' ), 'elderNameMasked', COALESCE( NULLIF(p_raw ->> 'elder_name', ''), NULLIF(p_raw ->> 'recipient_name', ''), '服务对象待补充' ), 'elderGender', '', 'elderAge', 0, 'elderPhone', COALESCE( NULLIF(p_raw ->> 'elder_phone', ''), NULLIF(p_raw ->> 'recipient_phone', ''), '' ), 'elderPhoneMasked', COALESCE( NULLIF(p_raw ->> 'elder_phone', ''), NULLIF(p_raw ->> 'recipient_phone', ''), '' ), 'fullElderName', COALESCE( NULLIF(p_raw ->> 'elder_name', ''), NULLIF(p_raw ->> 'recipient_name', ''), '服务对象待补充' ), 'fullPhone', COALESCE( NULLIF(p_raw ->> 'elder_phone', ''), NULLIF(p_raw ->> 'recipient_phone', ''), '' ), 'contactRelation', '家属', 'addressSummary', v_address_text, 'address', v_address_text, 'addressDetail', v_address_detail, 'fullAddress', v_full_address, 'latitude', COALESCE(NULLIF(v_address ->> 'latitude', '')::NUMERIC, 0), 'longitude', COALESCE(NULLIF(v_address ->> 'longitude', '')::NUMERIC, 0), 'appointmentTime', v_appointment_time, 'appointmentStartTime', v_appointment_time, 'appointmentEndTime', v_appointment_time, 'duration', COALESCE( NULLIF(p_raw ->> 'duration_minutes', '')::INTEGER, 90 ), 'estimatedDuration', COALESCE( NULLIF(p_raw ->> 'duration_minutes', '')::INTEGER, 90 ), 'price', v_price, 'staffIncome', v_staff_income, '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', '') ) || jsonb_build_object( '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( NULLIF(p_raw ->> 'contact_name', ''), '家属待补充' ), 'contactPhone', COALESCE(NULLIF(p_raw ->> 'contact_phone', ''), ''), 'requestId', COALESCE(NULLIF(p_raw ->> 'request_id', ''), '') ) || jsonb_build_object( '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; $$; REVOKE ALL ON FUNCTION public.delivery_build_order_json(JSONB, JSONB, JSONB, JSONB, JSONB, TEXT) FROM PUBLIC; REVOKE ALL ON FUNCTION public.delivery_build_order_json(JSONB, JSONB, JSONB, JSONB, JSONB, TEXT) FROM anon; GRANT EXECUTE ON FUNCTION public.delivery_build_order_json(JSONB, JSONB, JSONB, JSONB, JSONB, TEXT) TO authenticated; -- ============================================================================ -- 3. 修复 delivery_get_legacy_order_json:确保所有状态统一返回完整字段 -- ============================================================================ CREATE OR REPLACE FUNCTION public.delivery_get_legacy_order_json(p_order_id TEXT) RETURNS JSONB LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public AS $$ DECLARE v_staff_id UUID := public.delivery_current_staff_id(); v_raw JSONB; v_logs JSONB := '[]'::jsonb; v_records JSONB := '[]'::jsonb; v_evidence JSONB := '[]'::jsonb; BEGIN SELECT to_jsonb(o) INTO v_raw FROM public.hss_service_orders o WHERE o.id = p_order_id AND o.deleted_at IS NULL AND (v_staff_id IS NULL OR o.current_staff_id = v_staff_id) LIMIT 1; IF v_raw IS NULL THEN RETURN NULL; END IF; SELECT COALESCE(jsonb_agg(to_jsonb(l) ORDER BY l.created_at DESC), '[]'::jsonb) INTO v_logs FROM public.hss_service_order_status_logs l WHERE l.order_id = p_order_id; SELECT COALESCE(jsonb_agg(to_jsonb(r) ORDER BY r.created_at DESC), '[]'::jsonb) INTO v_records FROM public.hss_service_execution_records r WHERE r.order_id = p_order_id; SELECT COALESCE(jsonb_agg(to_jsonb(e) ORDER BY e.created_at DESC), '[]'::jsonb) INTO v_evidence FROM public.hss_service_evidence_files e WHERE e.order_id = p_order_id; RETURN public.delivery_build_order_json(v_raw, v_logs, v_records, v_evidence, NULL, 'legacy'); END; $$; REVOKE ALL ON FUNCTION public.delivery_get_legacy_order_json(TEXT) FROM PUBLIC; REVOKE ALL ON FUNCTION public.delivery_get_legacy_order_json(TEXT) FROM anon; GRANT EXECUTE ON FUNCTION public.delivery_get_legacy_order_json(TEXT) TO authenticated; -- ============================================================================ -- 4. 修复 rpc_delivery_order_list:pending tab 必须包含 pending_assignment + pending_accept -- ============================================================================ CREATE OR REPLACE FUNCTION public.rpc_delivery_order_list( p_staff_id UUID DEFAULT NULL, p_tab TEXT DEFAULT 'all', p_keyword TEXT DEFAULT '' ) RETURNS JSONB LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = pg_catalog, public, pg_temp AS $$ DECLARE v_user_id UUID := public.delivery_current_user_id(); v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id()); v_result JSONB := '[]'::jsonb; v_order JSONB; v_order_id TEXT; v_order_key TEXT; v_seen_order_ids TEXT[] := ARRAY[]::TEXT[]; v_max_rows INTEGER := 200; BEGIN IF v_user_id IS NULL THEN RAISE EXCEPTION USING ERRCODE = '42501', MESSAGE = 'delivery user context is required'; END IF; IF v_staff_id IS NULL THEN RAISE EXCEPTION USING ERRCODE = '42501', MESSAGE = 'delivery staff context is required'; END IF; PERFORM public.delivery_assert_staff_access(v_staff_id); -- 查询 hss_service_orders IF public.delivery_table_exists('hss_service_orders') THEN FOR v_order_id IN SELECT o.id::text FROM public.hss_service_orders o WHERE o.deleted_at IS NULL AND o.current_staff_id = v_staff_id AND o.status IN ( 'pending_assignment', 'pending_accept', 'accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived', 'in_service', 'serving', 'pending_acceptance', 'completed', 'settled', 'archived', 'abnormal', 'exception_pending' ) ORDER BY COALESCE(o.updated_at, o.created_at) DESC, o.created_at DESC LIMIT v_max_rows LOOP v_order := public.delivery_get_legacy_order_json(v_order_id); IF v_order IS NULL THEN CONTINUE; END IF; v_order_key := COALESCE(v_order ->> 'id', v_order_id); IF v_order_key = ANY(v_seen_order_ids) THEN CONTINUE; END IF; v_seen_order_ids := array_append(v_seen_order_ids, v_order_key); v_result := public.delivery_append_if_match( COALESCE(v_result, '[]'::jsonb), v_order, COALESCE(p_tab, 'all'), COALESCE(p_keyword, '') ); END LOOP; END IF; -- 兼容 ec_care_tasks 新链 IF public.delivery_table_exists('ec_care_tasks') THEN BEGIN FOR v_order_id IN EXECUTE 'SELECT t.id::text FROM public.ec_care_tasks t WHERE t.assigned_to = $1 AND t.status NOT IN (''ORDER_COMPLETED'', ''ORDER_CANCELLED'') ORDER BY t.created_at DESC LIMIT $2' USING v_user_id, v_max_rows LOOP v_order := public.delivery_get_care_order_json(v_order_id); IF v_order IS NULL THEN CONTINUE; END IF; v_order_key := COALESCE(v_order ->> 'id', v_order_id); IF v_order_key = ANY(v_seen_order_ids) THEN CONTINUE; END IF; v_seen_order_ids := array_append(v_seen_order_ids, v_order_key); v_result := public.delivery_append_if_match( COALESCE(v_result, '[]'::jsonb), v_order, COALESCE(p_tab, 'all'), COALESCE(p_keyword, '') ); END LOOP; EXCEPTION WHEN undefined_table OR undefined_column OR undefined_function THEN RAISE NOTICE 'Skip legacy ec_care_tasks compatibility query: %', SQLERRM; END; END IF; RETURN COALESCE(v_result, '[]'::jsonb); END; $$; REVOKE ALL ON FUNCTION public.rpc_delivery_order_list(UUID, TEXT, TEXT) FROM PUBLIC; REVOKE ALL ON FUNCTION public.rpc_delivery_order_list(UUID, TEXT, TEXT) FROM anon; GRANT EXECUTE ON FUNCTION public.rpc_delivery_order_list(UUID, TEXT, TEXT) TO authenticated; -- ============================================================================ -- 5. 修复 rpc_delivery_dashboard:同步更新 -- ============================================================================ CREATE OR REPLACE FUNCTION public.rpc_delivery_dashboard(p_staff_id UUID DEFAULT NULL) RETURNS JSONB LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = pg_catalog, public, pg_temp AS $$ DECLARE v_user_id UUID := public.delivery_current_user_id(); v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id()); v_orders JSONB := '[]'::jsonb; v_pending_assignment_count INTEGER := 0; v_pending_accept_count INTEGER := 0; v_today_order_count INTEGER := 0; v_pending_depart_count INTEGER := 0; v_serving_count INTEGER := 0; v_completed_count INTEGER := 0; v_exception_count INTEGER := 0; v_next_order JSONB; v_item JSONB; v_order_id TEXT; v_order_key TEXT; v_seen_order_ids TEXT[] := ARRAY[]::TEXT[]; v_max_rows INTEGER := 200; BEGIN IF v_user_id IS NULL THEN RAISE EXCEPTION USING ERRCODE = '42501', MESSAGE = 'delivery user context is required'; END IF; IF v_staff_id IS NULL THEN RAISE EXCEPTION USING ERRCODE = '42501', MESSAGE = 'delivery staff context is required'; END IF; PERFORM public.delivery_assert_staff_access(v_staff_id); IF public.delivery_table_exists('hss_service_orders') THEN FOR v_order_id IN SELECT o.id::text FROM public.hss_service_orders o WHERE o.deleted_at IS NULL AND o.current_staff_id = v_staff_id AND o.status IN ( 'pending_assignment', 'pending_accept', 'accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived', 'in_service', 'serving', 'pending_acceptance', 'completed', 'settled', 'archived', 'abnormal', 'exception_pending' ) ORDER BY COALESCE(o.updated_at, o.created_at) DESC, o.created_at DESC LIMIT v_max_rows LOOP v_item := public.delivery_get_legacy_order_json(v_order_id); IF v_item IS NULL THEN CONTINUE; END IF; v_order_key := COALESCE(v_item ->> 'id', v_order_id); IF v_order_key = ANY(v_seen_order_ids) THEN CONTINUE; END IF; v_seen_order_ids := array_append(v_seen_order_ids, v_order_key); v_orders := COALESCE(v_orders, '[]'::jsonb) || jsonb_build_array(v_item); END LOOP; END IF; IF public.delivery_table_exists('ec_care_tasks') THEN BEGIN FOR v_order_id IN EXECUTE 'SELECT t.id::text FROM public.ec_care_tasks t WHERE t.assigned_to = $1 AND t.status NOT IN (''ORDER_COMPLETED'', ''ORDER_CANCELLED'') ORDER BY t.created_at DESC LIMIT $2' USING v_user_id, v_max_rows LOOP v_item := public.delivery_get_care_order_json(v_order_id); IF v_item IS NULL THEN CONTINUE; END IF; v_order_key := COALESCE(v_item ->> 'id', v_order_id); IF v_order_key = ANY(v_seen_order_ids) THEN CONTINUE; END IF; v_seen_order_ids := array_append(v_seen_order_ids, v_order_key); v_orders := COALESCE(v_orders, '[]'::jsonb) || jsonb_build_array(v_item); END LOOP; EXCEPTION WHEN undefined_table OR undefined_column OR undefined_function THEN RAISE NOTICE 'Skip legacy ec_care_tasks compatibility query: %', SQLERRM; END; END IF; FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb)) LOOP IF v_item IS NULL THEN CONTINUE; END IF; IF COALESCE(v_item ->> 'status', '') = 'pending_assignment' THEN v_pending_assignment_count := v_pending_assignment_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') = 'pending_accept' THEN v_pending_accept_count := v_pending_accept_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') IN ( 'pending_assignment', 'pending_accept', 'accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived', 'in_service', 'serving', 'pending_acceptance' ) THEN v_today_order_count := v_today_order_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') IN ( 'accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived' ) THEN v_pending_depart_count := v_pending_depart_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') IN ('in_service', 'serving') THEN v_serving_count := v_serving_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') IN ('completed', 'pending_acceptance', 'settled', 'archived') THEN v_completed_count := v_completed_count + 1; END IF; IF COALESCE(v_item ->> 'status', '') IN ('abnormal', 'exception_pending') THEN v_exception_count := v_exception_count + 1; END IF; IF v_next_order IS NULL AND COALESCE(v_item ->> 'status', '') NOT IN ( 'completed', 'settled', 'archived', 'cancelled', 'rejected', 'abnormal', 'exception_pending' ) THEN v_next_order := v_item; END IF; END LOOP; RETURN jsonb_build_object( 'pendingAssignmentCount', v_pending_assignment_count, 'pendingAcceptCount', v_pending_accept_count, 'todayOrderCount', v_today_order_count, 'pendingDepartCount', v_pending_depart_count, 'servingCount', v_serving_count, 'completedCount', v_completed_count, 'exceptionCount', v_exception_count, 'expectedIncome', 0, 'onlineStatus', COALESCE(public.delivery_build_staff_info(v_staff_id) ->> 'onlineStatus', 'resting'), 'nextOrder', v_next_order, 'recentOrders', COALESCE( ( SELECT jsonb_agg(value) FROM ( SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb)) LIMIT 5 ) q ), '[]'::jsonb ) ); END; $$; REVOKE ALL ON FUNCTION public.rpc_delivery_dashboard(UUID) FROM PUBLIC; REVOKE ALL ON FUNCTION public.rpc_delivery_dashboard(UUID) FROM anon; GRANT EXECUTE ON FUNCTION public.rpc_delivery_dashboard(UUID) TO authenticated; COMMIT;