310 lines
13 KiB
PL/PgSQL
310 lines
13 KiB
PL/PgSQL
-- ============================================================
|
||
-- 淇锛歳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瀛楁鍚?;
|
||
|