Files
medical-mall/mall_sql/migrations/20260610_cleanup_dispatch_rpc_overload.sql

338 lines
14 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================
-- 娓呯悊锛氬垹闄?rpc_homecare_auto_dispatch_optimized 鎵€鏈夐噸杞?
-- 鏃ユ湡锛?026-06-10
-- 闂锛歅ostgREST 鎶ラ敊 PGRST203 - 鏃犳硶閫夋嫨姝g‘鐨勫嚱鏁伴噸杞?
-- 鍘熷洜锛氬瓨鍦ㄤ袱涓悓鍚嶅嚱鏁帮紝鍙傛暟鍒楄〃涓嶅悓
-- 瑙e喅锛氬垹闄ゆ墍鏈夐噸杞斤紝鍙繚鐣欎慨澶嶅悗鐨勬柊鐗堟湰
-- ============================================================
-- 鏌ョ湅褰撳墠鎵€鏈夐噸杞?
SELECT routine_name, argument_list, data_type
FROM information_schema.routines
WHERE routine_name = 'rpc_homecare_auto_dispatch_optimized';
-- 鍒犻櫎鎵€鏈夊彲鑳界殑閲嶈浇锛堟寜鍙傛暟鏁伴噺浠庡鍒板皯锛?
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);
-- 纭宸插垹闄?
SELECT routine_name
FROM information_schema.routines
WHERE routine_name = 'rpc_homecare_auto_dispatch_optimized';
-- 搴旇杩斿洖 0 琛?
-- 鐜板湪鍒涘缓淇鍚庣殑鏂扮増鏈?
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;
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;
-- 璁板綍鐘舵€佹棩蹇楋紙浣跨敤姝g‘鐨?operator_role 瀛楁锛?
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 - ';