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

573 lines
20 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
-- 日期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修复时间冲突检查过于严格的问题仅当订单有明确时间范围时才检查时间冲突';