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

310 lines
13 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.
-- ============================================================
-- 淇锛歳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瀛楁?;