-- ============================================================ -- 最终修复:rpc_homecare_auto_dispatch_optimized -- 日期:2026-06-10 -- 修复内容: -- 1. operator_type → operator_role(字段名错误) -- 2. 删除所有旧重载,只保留一个函数 -- 3. 修复时间冲突检查逻辑 -- ============================================================ -- 1. 删除所有旧版本重载 DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(TEXT, TEXT); DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(TEXT); DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(UUID, TEXT); DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(UUID); -- 2. 创建修复后的最终版本 CREATE OR REPLACE FUNCTION public.rpc_homecare_auto_dispatch_optimized( p_order_id TEXT DEFAULT NULL ) RETURNS JSONB LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $function$ DECLARE v_now TIMESTAMPTZ := clock_timestamp(); v_current_user_id UUID; v_order public.hss_service_orders%ROWTYPE; v_existing_assignment RECORD; v_candidate RECORD; v_candidate_found BOOLEAN := FALSE; v_assignment_id TEXT; v_status_log_id TEXT; v_attempt_log_id TEXT; v_failure_code TEXT; v_failure_message TEXT; v_retryable BOOLEAN := FALSE; BEGIN -- A. 基础参数与身份校验 IF p_order_id IS NULL OR btrim(p_order_id) = '' THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_ID_REQUIRED', 'message', '订单ID不能为空', 'display_type', 'toast', 'retryable', FALSE ); END IF; IF auth.uid() IS NULL THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'UNAUTHENTICATED', 'message', '登录状态已失效,请重新登录后再操作', 'display_type', 'modal', 'retryable', FALSE ); END IF; SELECT u.id INTO v_current_user_id FROM public.ak_users u WHERE u.auth_id = auth.uid() LIMIT 1; IF v_current_user_id IS NULL THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'USER_PROFILE_NOT_FOUND', 'message', '当前账户信息异常,请重新登录或联系客服', 'display_type', 'modal', 'retryable', FALSE ); END IF; -- B. 锁定订单 SELECT o.* INTO v_order FROM public.hss_service_orders o WHERE o.id = p_order_id AND o.deleted_at IS NULL FOR UPDATE OF o; IF NOT FOUND THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_NOT_FOUND', 'message', '订单不存在或已被删除', 'display_type', 'modal', 'retryable', FALSE ); END IF; IF v_order.user_id IS DISTINCT FROM v_current_user_id THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_ACCESS_DENIED', 'message', '您无权操作该订单', 'display_type', 'modal', 'retryable', FALSE ); END IF; -- C. 幂等处理 IF v_order.status = 'assigned' AND v_order.current_assignment_id IS NOT NULL THEN RETURN jsonb_build_object( 'success', TRUE, 'code', 'ALREADY_ASSIGNED', 'message', '订单已完成派单', 'display_type', 'none', 'retryable', FALSE, 'dispatch_status', 'assigned', 'order_id', p_order_id, 'assignment_id', v_order.current_assignment_id, 'staff_id', v_order.current_staff_id ); END IF; -- D. 支付与订单状态校验 IF COALESCE(v_order.payment_status, 1) <> 2 THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_NOT_PAID', 'message', '订单尚未完成支付,暂时不能安排服务人员', 'display_type', 'modal', 'retryable', FALSE, 'dispatch_status', COALESCE(v_order.dispatch_status, 'pending'), 'order_id', p_order_id ); END IF; IF v_order.status NOT IN ('created', 'paid') THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_STATUS_NOT_DISPATCHABLE', 'message', '当前订单状态不允许重新派单', 'display_type', 'modal', 'retryable', FALSE, 'dispatch_status', COALESCE(v_order.dispatch_status, 'pending'), 'order_id', p_order_id ); END IF; -- E. 兼容历史数据 SELECT a.id, a.staff_id, a.station_id INTO v_existing_assignment FROM public.hss_service_assignments a WHERE a.order_id = p_order_id AND a.deleted_at IS NULL AND a.status IN ('assigned', 'accepted', 'departed', 'arrived', 'serving', 'in_service') ORDER BY a.assigned_at DESC LIMIT 1; IF FOUND THEN UPDATE public.hss_service_orders SET status = 'assigned', dispatch_status = 'assigned', current_assignment_id = v_existing_assignment.id, current_staff_id = v_existing_assignment.staff_id, dispatch_error_code = NULL, dispatch_error_message = NULL, dispatch_failed_at = NULL, updated_at = v_now WHERE id = p_order_id; RETURN jsonb_build_object( 'success', TRUE, 'code', 'ALREADY_ASSIGNED_RECOVERED', 'message', '订单已存在有效派单记录,系统已完成状态同步', 'display_type', 'none', 'retryable', FALSE, 'dispatch_status', 'assigned', 'order_id', p_order_id, 'assignment_id', v_existing_assignment.id, 'staff_id', v_existing_assignment.staff_id, 'station_id', COALESCE(v_existing_assignment.station_id::text, '') ); END IF; -- F. 进入派单中状态 UPDATE public.hss_service_orders SET dispatch_status = 'dispatching', dispatch_attempt_count = COALESCE(dispatch_attempt_count, 0) + 1, dispatch_error_code = NULL, dispatch_error_message = NULL, dispatch_failed_at = NULL, updated_at = v_now WHERE id = p_order_id; -- G. 【核心优化】查找可派单服务人员 -- 关键修复:只有当订单有明确的时间范围时,才检查时间冲突 -- 如果订单没有 scheduled_start_at/scheduled_end_at,则不检查时间冲突 SELECT s.id, s.station_id, distance_calc.distance_km INTO v_candidate FROM public.ml_delivery_staff s CROSS JOIN LATERAL ( SELECT CASE WHEN v_order.service_lat IS NULL OR v_order.service_lng IS NULL OR s.current_lat IS NULL OR s.current_lng IS NULL THEN NULL ELSE ( 6371 * 2 * asin( sqrt( power( sin( radians((s.current_lat - v_order.service_lat)::DOUBLE PRECISION) / 2 ), 2 ) + cos(radians(v_order.service_lat::DOUBLE PRECISION)) * cos(radians(s.current_lat::DOUBLE PRECISION)) * power( sin( radians((s.current_lng - v_order.service_lng)::DOUBLE PRECISION) / 2 ), 2 ) ) ) )::NUMERIC(8,3) END AS distance_km ) distance_calc 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 ( v_order.dispatch_station_id IS NULL OR s.station_id::TEXT = v_order.dispatch_station_id ) AND ( v_order.required_qualification_code IS NULL OR EXISTS ( SELECT 1 FROM public.hc_worker_qualifications q WHERE q.staff_id::TEXT = s.id::TEXT AND q.qualification_code = v_order.required_qualification_code AND q.deleted_at IS NULL AND q.status::TEXT IN ('1', 'active', 'approved', 'valid') AND (q.valid_from IS NULL OR q.valid_from <= v_now) AND (q.valid_until IS NULL OR q.valid_until >= v_now) ) ) AND ( v_order.service_lat IS NULL OR v_order.service_lng IS NULL OR ( s.current_lat IS NOT NULL AND s.current_lng IS NOT NULL AND distance_calc.distance_km <= COALESCE(s.dispatch_radius_km, 20) ) ) -- 【修复点】只有当订单有明确时间范围时,才检查时间冲突 AND ( v_order.scheduled_start_at IS NULL OR v_order.scheduled_end_at IS NULL OR NOT EXISTS ( SELECT 1 FROM public.hss_service_assignments existing_assignment JOIN public.hss_service_orders existing_order ON existing_order.id = existing_assignment.order_id WHERE existing_assignment.staff_id::TEXT = s.id::TEXT AND existing_assignment.deleted_at IS NULL AND existing_assignment.status IN ( 'assigned', 'accepted', 'departed', 'arrived', 'serving', 'in_service' ) AND existing_order.scheduled_start_at IS NOT NULL AND existing_order.scheduled_end_at IS NOT NULL AND tstzrange( existing_order.scheduled_start_at, existing_order.scheduled_end_at, '[)' ) && tstzrange( v_order.scheduled_start_at, v_order.scheduled_end_at, '[)' ) ) ) ORDER BY CASE WHEN v_order.dispatch_station_id IS NOT NULL AND s.station_id::TEXT = v_order.dispatch_station_id THEN 0 ELSE 1 END, distance_calc.distance_km ASC NULLS LAST, COALESCE(s.last_dispatched_at, '-infinity'::TIMESTAMPTZ) ASC, COALESCE(s.updated_at, s.created_at) DESC LIMIT 1 FOR UPDATE OF s; v_candidate_found := FOUND; -- H. 没有找到候选人员时,识别具体失败原因 IF NOT v_candidate_found THEN IF NOT EXISTS ( SELECT 1 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 ) THEN v_failure_code := 'NO_ONLINE_STAFF'; v_failure_message := '当前暂无在线服务人员,请稍后重新派单'; v_retryable := TRUE; ELSIF v_order.dispatch_station_id IS NOT NULL AND NOT EXISTS ( SELECT 1 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 s.station_id::TEXT = v_order.dispatch_station_id ) THEN v_failure_code := 'NO_STAFF_IN_SERVICE_STATION'; v_failure_message := '当前服务区域暂无可派服务人员,请稍后重试或联系客服'; v_retryable := TRUE; ELSIF v_order.required_qualification_code IS NOT NULL AND NOT EXISTS ( SELECT 1 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 ( v_order.dispatch_station_id IS NULL OR s.station_id::TEXT = v_order.dispatch_station_id ) AND EXISTS ( SELECT 1 FROM public.hc_worker_qualifications q WHERE q.staff_id::TEXT = s.id::TEXT AND q.qualification_code = v_order.required_qualification_code AND q.deleted_at IS NULL AND q.status::TEXT IN ('1', 'active', 'approved', 'valid') AND (q.valid_from IS NULL OR q.valid_from <= v_now) AND (q.valid_until IS NULL OR q.valid_until >= v_now) ) ) THEN v_failure_code := 'NO_QUALIFIED_STAFF'; v_failure_message := '当前暂无具备该服务资质的服务人员,请稍后重试或联系客服'; v_retryable := TRUE; ELSIF v_order.service_lat IS NOT NULL AND v_order.service_lng IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM public.ml_delivery_staff s CROSS JOIN LATERAL ( SELECT ( 6371 * 2 * asin( sqrt( power( sin( radians((s.current_lat - v_order.service_lat)::DOUBLE PRECISION) / 2 ), 2 ) + cos(radians(v_order.service_lat::DOUBLE PRECISION)) * cos(radians(s.current_lat::DOUBLE PRECISION)) * power( sin( radians((s.current_lng - v_order.service_lng)::DOUBLE PRECISION) / 2 ), 2 ) ) ) )::NUMERIC(8,3) AS distance_km ) distance_calc 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 s.current_lat IS NOT NULL AND s.current_lng IS NOT NULL AND ( v_order.dispatch_station_id IS NULL OR s.station_id::TEXT = v_order.dispatch_station_id ) AND ( v_order.required_qualification_code IS NULL OR EXISTS ( SELECT 1 FROM public.hc_worker_qualifications q WHERE q.staff_id::TEXT = s.id::TEXT AND q.qualification_code = v_order.required_qualification_code AND q.deleted_at IS NULL AND q.status::TEXT IN ('1', 'active', 'approved', 'valid') AND (q.valid_from IS NULL OR q.valid_from <= v_now) AND (q.valid_until IS NULL OR q.valid_until >= v_now) ) ) AND distance_calc.distance_km <= COALESCE(s.dispatch_radius_km, 20) ) THEN v_failure_code := 'NO_NEARBY_STAFF'; v_failure_message := '附近暂无可上门服务人员,请稍后重试或联系客服'; v_retryable := TRUE; ELSE v_failure_code := 'ALL_ELIGIBLE_STAFF_BUSY'; v_failure_message := '当前可服务人员均在忙,请稍后重新派单'; v_retryable := TRUE; END IF; UPDATE public.hss_service_orders SET dispatch_status = 'failed', dispatch_error_code = v_failure_code, dispatch_error_message = v_failure_message, dispatch_failed_at = v_now, updated_at = v_now WHERE id = p_order_id; v_attempt_log_id := 'dalog-' || floor(extract(epoch FROM v_now) * 1000)::BIGINT::TEXT || '-' || upper(substr(md5(p_order_id || ':' || random()::TEXT), 1, 10)); INSERT INTO public.hss_service_dispatch_attempt_logs ( id, order_id, requested_by_user_id, selected_staff_id, selected_station_id, success, result_code, result_message, retryable, filters_snapshot, created_at ) VALUES ( v_attempt_log_id, p_order_id, v_current_user_id, NULL, v_order.dispatch_station_id, FALSE, v_failure_code, v_failure_message, v_retryable, jsonb_build_object( 'required_qualification_code', v_order.required_qualification_code, 'dispatch_station_id', v_order.dispatch_station_id, 'service_lat', v_order.service_lat, 'service_lng', v_order.service_lng, 'scheduled_start_at', v_order.scheduled_start_at, 'scheduled_end_at', v_order.scheduled_end_at ), v_now ); RETURN jsonb_build_object( 'success', FALSE, 'code', v_failure_code, 'message', v_failure_message, 'display_type', 'modal', 'retryable', v_retryable, 'dispatch_status', 'failed', 'order_id', p_order_id ); END IF; -- I. 派单成功,创建派单记录 v_assignment_id := 'dsa-' || floor(extract(epoch FROM v_now) * 1000)::BIGINT::TEXT || '-' || upper(substr(md5(p_order_id || ':' || v_candidate.id::TEXT), 1, 10)); INSERT INTO public.hss_service_assignments ( id, order_id, staff_id, station_id, status, assigned_at, created_at ) VALUES ( v_assignment_id, p_order_id, v_candidate.id, v_candidate.station_id, 'assigned', v_now, v_now ); UPDATE public.hss_service_orders SET status = 'assigned', dispatch_status = 'assigned', current_assignment_id = v_assignment_id, current_staff_id = v_candidate.id, updated_at = v_now WHERE id = p_order_id; v_status_log_id := 'slg-' || floor(extract(epoch FROM v_now) * 1000)::BIGINT::TEXT || '-' || upper(substr(md5(p_order_id || ':assigned'), 1, 10)); -- 【修复】operator_role 而不是 operator_type INSERT INTO public.hss_service_order_status_logs ( id, order_id, from_status, to_status, operator_role, operator_id, remark, created_at ) VALUES ( v_status_log_id, p_order_id, 'created', 'assigned', 'system', v_current_user_id, '系统自动派单', v_now ); RETURN jsonb_build_object( 'success', TRUE, 'code', 'DISPATCH_ASSIGNED', 'message', '系统已为您匹配服务人员', 'display_type', 'none', 'retryable', FALSE, 'dispatch_status', 'assigned', 'order_id', p_order_id, 'assignment_id', v_assignment_id, 'staff_id', v_candidate.id::TEXT, 'station_id', COALESCE(v_candidate.station_id::TEXT, '') ); END; $function$; COMMENT ON FUNCTION public.rpc_homecare_auto_dispatch_optimized(TEXT) IS '优化版派单RPC:修复时间冲突检查过于严格的问题,仅当订单有明确时间范围时才检查时间冲突';