-- ============================================================ -- 淇锛歳pc_homecare_auto_dispatch_optimized 瀛楁鍚嶉敊璇? -- 鏃ユ湡锛?026-06-10 -- 闂锛欼NSERT INTO hss_service_order_status_logs 浣跨敤浜嗕笉瀛樺湪鐨勫瓧娈?operator_type -- 淇锛氬皢 operator_type 鏀逛负 operator_role锛堣〃涓疄闄呭瓧娈靛悕锛? -- ============================================================ -- 鍒犻櫎鏃х増鏈嚱鏁帮紙蹇呴』鎸囧畾鍙傛暟鍒楄〃锛? DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(TEXT, TEXT); DROP FUNCTION IF EXISTS public.rpc_homecare_auto_dispatch_optimized(UUID, TEXT); -- 鍒涘缓淇鍚庣殑浼樺寲鐗堣嚜鍔ㄦ淳鍗?RPC CREATE OR REPLACE FUNCTION public.rpc_homecare_auto_dispatch_optimized( p_order_id TEXT, p_operator_type TEXT DEFAULT 'system' ) RETURNS JSONB LANGUAGE plpgsql AS $$ DECLARE v_order RECORD; v_assignment RECORD; v_assignment_id TEXT; v_staff_id UUID; v_current_user_id UUID; v_now TIMESTAMPTZ; v_failure_code TEXT; v_failure_message TEXT; v_retryable BOOLEAN; v_status_log_id TEXT; v_attempt_log_id TEXT; v_distance_km NUMERIC; v_order_lat NUMERIC; v_order_lng NUMERIC; v_staff_lat NUMERIC; v_staff_lng NUMERIC; v_scheduled_start_at TIMESTAMPTZ; v_scheduled_end_at TIMESTAMPTZ; BEGIN v_now := now(); BEGIN v_current_user_id := gen_random_uuid(); EXCEPTION WHEN OTHERS THEN v_current_user_id := NULL; END; SELECT * INTO v_order FROM public.hss_service_orders WHERE id = p_order_id AND deleted_at IS NULL AND status = 'paid' AND dispatch_status = 'pending'; IF NOT FOUND THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_NOT_FOUND_OR_NOT_ELIGIBLE', 'message', '璁㈠崟涓嶅瓨鍦ㄦ垨涓嶇鍚堟淳鍗曟潯浠?, 'display_type', 'toast', 'retryable', FALSE ); END IF; v_order_lat := v_order.service_lat; v_order_lng := v_order.service_lng; IF v_order_lat IS NULL OR v_order_lng IS NULL THEN RETURN jsonb_build_object( 'success', FALSE, 'code', 'ORDER_MISSING_COORDINATES', 'message', '璁㈠崟缂哄皯浣嶇疆淇℃伅锛岃鑱旂郴瀹㈡湇', 'display_type', 'toast', 'retryable', FALSE ); END IF; IF v_order.dispatch_station_id IS NOT NULL THEN SELECT * INTO v_assignment FROM ( SELECT s.id AS staff_id, s.staff_name, s.online_status, s.station_id, s.latitude AS staff_lat, s.longitude AS staff_lng, distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) AS distance_km, ROW_NUMBER() OVER (ORDER BY distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) ASC) AS rn FROM public.ml_delivery_staff s WHERE s.station_id::TEXT = v_order.dispatch_station_id AND s.online_status = 'online' AND s.latitude IS NOT NULL AND s.longitude IS NOT NULL 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(s.latitude, s.longitude, v_order_lat, v_order_lng) <= COALESCE(s.dispatch_radius_km, 20) AND NOT EXISTS ( SELECT 1 FROM public.hss_service_assignments sa WHERE sa.staff_id = s.id AND sa.status IN ('assigned', 'accepted', 'in_progress') AND sa.deleted_at IS NULL AND sa.start_time <= v_now AND sa.end_time > v_now ) ) AS ranked_candidates WHERE rn = 1 LIMIT 1; ELSE SELECT * INTO v_assignment FROM ( SELECT s.id AS staff_id, s.staff_name, s.online_status, s.station_id, s.latitude AS staff_lat, s.longitude AS staff_lng, distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) AS distance_km, ROW_NUMBER() OVER (ORDER BY distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) ASC) AS rn FROM public.ml_delivery_staff s WHERE s.online_status = 'online' AND s.latitude IS NOT NULL AND s.longitude 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(s.latitude, s.longitude, v_order_lat, v_order_lng) <= COALESCE(s.dispatch_radius_km, 20) AND NOT EXISTS ( SELECT 1 FROM public.hss_service_assignments sa WHERE sa.staff_id = s.id AND sa.status IN ('assigned', 'accepted', 'in_progress') AND sa.deleted_at IS NULL AND sa.start_time <= v_now AND sa.end_time > v_now ) ) AS ranked_candidates WHERE rn = 1 LIMIT 1; END IF; IF FOUND AND v_assignment.staff_id IS NOT NULL THEN v_staff_id := v_assignment.staff_id; v_assignment_id := 'asgn-' || floor(extract(epoch FROM v_now) * 1000)::BIGINT::TEXT || '-' || upper(substr(md5(p_order_id || ':' || v_staff_id::TEXT), 1, 10)); INSERT INTO public.hss_service_assignments ( id, order_id, staff_id, status, assigned_at, start_time, end_time, created_at ) VALUES ( v_assignment_id, p_order_id, v_staff_id, 'assigned', v_now, v_now, v_now + INTERVAL '2 hours', v_now ); UPDATE public.hss_service_orders SET dispatch_status = 'assigned', current_staff_id = v_staff_id, assigned_at = v_now, 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)); 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 ); v_attempt_log_id := 'dalog-' || floor(extract(epoch FROM v_now) * 1000)::BIGINT::TEXT || '-' || upper(substr(md5(p_order_id || ':attempt'), 1, 10)); INSERT INTO public.hss_service_dispatch_attempt_logs ( id, order_id, requested_by_user_id, selected_staff_id, selected_station_id, success, assigned_at, created_at ) VALUES ( v_attempt_log_id, p_order_id, v_current_user_id, v_staff_id, v_assignment.station_id::TEXT, TRUE, v_now, 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_name', v_assignment.staff_name, 'distance_km', v_assignment.distance_km ); END IF; v_failure_code := 'NO_ONLINE_STAFF'; v_failure_message := '鏆傛棤鍦ㄧ嚎鏈嶅姟浜哄憳锛岃绋嶅悗閲嶈瘯'; v_retryable := TRUE; IF NOT EXISTS ( SELECT 1 FROM public.ml_delivery_staff WHERE online_status = 'online' AND deleted_at IS NULL AND latitude IS NOT NULL AND longitude IS NOT NULL ) THEN v_failure_code := 'NO_ONLINE_STAFF'; v_failure_message := '鏆傛棤鍦ㄧ嚎浜哄憳锛岃绋嶅悗閲嶈瘯鎴栬仈绯诲鏈?; ELSIF v_order.dispatch_station_id IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM public.ml_delivery_staff s WHERE s.station_id::TEXT = v_order.dispatch_station_id AND s.online_status = 'online' AND s.deleted_at IS NULL ) THEN v_failure_code := 'NO_STAFF_IN_SERVICE_STATION'; v_failure_message := '褰撳墠鍖哄煙鏆傛棤浜哄憳锛岃绋嶅悗閲嶈瘯鎴栬仈绯诲鏈?; ELSIF v_order.required_qualification_code IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM public.hc_worker_qualifications q WHERE 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 := '鏆傛棤鍏峰璇ヨ祫璐ㄧ殑鏈嶅姟浜哄憳锛岃绋嶅悗閲嶈瘯鎴栬仈绯诲鏈?; ELSIF NOT EXISTS ( SELECT 1 FROM public.ml_delivery_staff s WHERE s.online_status = 'online' AND s.latitude IS NOT NULL AND s.longitude IS NOT NULL AND distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) <= COALESCE(s.dispatch_radius_km, 20) ) THEN v_failure_code := 'NO_NEARBY_STAFF'; v_failure_message := '闄勮繎鏆傛棤鍙笂闂ㄦ湇鍔′汉鍛橈紝璇风◢鍚庨噸璇曟垨鑱旂郴瀹㈡湇'; ELSIF EXISTS ( SELECT 1 FROM public.ml_delivery_staff s WHERE s.online_status = 'online' AND s.latitude IS NOT NULL AND s.longitude IS NOT NULL AND distance_calc.distance_km(s.latitude, s.longitude, v_order_lat, v_order_lng) <= COALESCE(s.dispatch_radius_km, 20) AND EXISTS ( SELECT 1 FROM public.hss_service_assignments sa WHERE sa.staff_id = s.id AND sa.status IN ('assigned', 'accepted', 'in_progress') AND sa.deleted_at IS NULL AND sa.start_time <= v_now AND sa.end_time > v_now ) ) THEN v_failure_code := 'ALL_ELIGIBLE_STAFF_BUSY'; v_failure_message := '鍙湇鍔′汉鍛樺潎鍦ㄥ繖锛岃绋嶅悗閲嶆柊娲惧崟'; 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 || ':failed'), 1, 10)); INSERT INTO public.hss_service_dispatch_attempt_logs ( id, order_id, requested_by_user_id, selected_staff_id, selected_station_id, success, failure_code, failure_message, retryable, created_at ) VALUES ( v_attempt_log_id, p_order_id, v_current_user_id, NULL, NULL, FALSE, v_failure_code, v_failure_message, v_retryable, 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; $$; COMMENT ON FUNCTION public.rpc_homecare_auto_dispatch_optimized IS '浼樺寲鐗堝眳瀹舵湇鍔¤嚜鍔ㄦ淳鍗昍PC - 淇鏃堕棿鍐茬獊妫€鏌ラ€昏緫鍜宱perator_role瀛楁鍚?;