﻿-- ============================================================================
-- 浼樺寲娲惧崟 RPC锛氫慨澶嶆椂闂村啿绐佹鏌ヨ繃浜庝弗鏍肩殑闂
-- 鎵ц鏃堕棿: 2026-06-10
-- 闂: 褰撹鍗?scheduled_start_at/scheduled_end_at 涓?NULL 鏃讹紝
--       浠讳綍鏈夋椿璺冧换鍔＄殑浜哄憳閮戒細琚帓闄わ紝鍗充娇鏃堕棿鏍规湰涓嶅啿绐?
-- ============================================================================

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));

    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 '浼樺寲鐗堟淳鍗昍PC锛氫慨澶嶆椂闂村啿绐佹鏌ヨ繃浜庝弗鏍肩殑闂锛屼粎褰撹鍗曟湁鏄庣‘鏃堕棿鑼冨洿鏃舵墠妫€鏌ユ椂闂村啿绐?;


