Files
medical-mall/mall_sql/migrations/20260526_delivery_homecare_rpc_v1.sql

1536 lines
65 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.
BEGIN;
-- =====================================================================================
-- Delivery homecare action RPCs
-- Purpose: <20>?delivery 端补<E7ABAF>?rpc_delivery_* 契约优先兼<E58588>?ec/hc 新链,回退 hss 旧链<E697A7>?
-- =====================================================================================
CREATE OR REPLACE FUNCTION public.delivery_current_user_id()
RETURNS UUID
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
SELECT u.id
FROM public.ak_users u
WHERE u.auth_id = auth.uid()
LIMIT 1
$$;
CREATE OR REPLACE FUNCTION public.delivery_current_staff_id()
RETURNS UUID
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
SELECT s.id
FROM public.ml_delivery_staff s
WHERE s.uid = public.delivery_current_user_id()
AND s.deleted_at IS NULL
ORDER BY COALESCE(s.updated_at, s.created_at) DESC, s.created_at DESC
LIMIT 1
$$;
CREATE OR REPLACE FUNCTION public.delivery_table_exists(p_table_name TEXT)
RETURNS BOOLEAN
LANGUAGE sql
STABLE
SET search_path = public
AS $$
SELECT to_regclass('public.' || p_table_name) IS NOT NULL
$$;
CREATE OR REPLACE FUNCTION public.delivery_front_status(p_normalized_status TEXT, p_raw JSONB)
RETURNS TEXT
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
IF p_normalized_status = 'assigned' THEN
RETURN 'pending_assignment';
END IF;
IF p_normalized_status = 'accepted' THEN
IF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
RETURN 'departed';
END IF;
RETURN 'accepted';
END IF;
IF p_normalized_status = 'departed' THEN
RETURN 'departed';
END IF;
IF p_normalized_status = 'arrived' THEN
RETURN 'arrived';
END IF;
IF p_normalized_status = 'in_service' THEN
RETURN 'in_service';
END IF;
IF p_normalized_status = 'pending_acceptance' THEN
RETURN 'pending_acceptance';
END IF;
IF p_normalized_status IN ('reviewed', 'accepted_by_user', 'settled', 'completed') THEN
RETURN 'completed';
END IF;
IF p_normalized_status = 'rejected' THEN
RETURN 'rejected';
END IF;
IF p_normalized_status = 'cancelled' THEN
RETURN 'cancelled';
END IF;
IF p_normalized_status = 'exception' THEN
RETURN 'abnormal';
END IF;
RETURN 'pending_assignment';
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_status_text(p_front_status TEXT)
RETURNS TEXT
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
CASE p_front_status
WHEN 'pending_assignment' THEN RETURN '待接<EFBFBD>?;
WHEN 'pending_accept' THEN RETURN '<EFBFBD>?;
WHEN 'accepted' THEN RETURN '待出<EFBFBD>?;
WHEN 'waiting_departure' THEN RETURN '<EFBFBD>?;
WHEN 'departed' THEN RETURN '已出<EFBFBD>?;
WHEN 'on_the_way' THEN RETURN '';
WHEN 'arrived' THEN RETURN '<EFBFBD>?;
WHEN 'checked_in' THEN RETURN '已签<EFBFBD>?;
WHEN 'in_service' THEN RETURN '<EFBFBD>?;
WHEN 'serving' THEN RETURN '服务<EFBFBD>?;
WHEN 'pending_confirm' THEN RETURN '<EFBFBD>?;
WHEN 'pending_submit' THEN RETURN '待提<EFBFBD>?;
WHEN 'pending_acceptance' THEN RETURN '<EFBFBD>?;
WHEN 'completed' THEN RETURN '已完<EFBFBD>?;
WHEN 'rejected' THEN RETURN '<EFBFBD>?;
WHEN 'abnormal' THEN RETURN '异常处理<EFBFBD>?;
WHEN 'cancelled' THEN RETURN '<EFBFBD>?;
WHEN 'settled' THEN RETURN '已结<EFBFBD>?;
WHEN 'archived' THEN RETURN '<EFBFBD>?;
ELSE RETURN '待接<EFBFBD>?;
END CASE;
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_status_tone(p_front_status TEXT)
RETURNS TEXT
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
IF p_front_status IN ('pending_assignment', 'pending_accept', 'pending_acceptance', 'pending_confirm', 'pending_submit') THEN
RETURN 'warning';
END IF;
IF p_front_status IN ('accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived', 'checked_in', 'in_service', 'serving') THEN
RETURN 'primary';
END IF;
IF p_front_status IN ('completed', 'settled', 'archived') THEN
RETURN 'success';
END IF;
IF p_front_status IN ('rejected', 'cancelled', 'abnormal', 'exception_pending', 'terminated') THEN
RETURN 'danger';
END IF;
RETURN 'warning';
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_default_service_items(p_order_id TEXT, p_service_name TEXT)
RETURNS JSONB
LANGUAGE sql
IMMUTABLE
AS $$
SELECT jsonb_build_array(
jsonb_build_object('id', p_order_id || '-item-1', 'name', '', 'required', true, 'completed', false, 'incompleteReason', '', 'remark', '', 'updatedAt', ''),
jsonb_build_object('id', p_order_id || '-item-2', 'name', CASE WHEN position('' IN COALESCE(p_service_name, '')) > 0 THEN '' WHEN position('访' IN COALESCE(p_service_name, '')) > 0 THEN '' ELSE '' END, 'required', true, 'completed', false, 'incompleteReason', '', 'remark', '', 'updatedAt', ''),
jsonb_build_object('id', p_order_id || '-item-3', 'name', '', 'required', true, 'completed', false, 'incompleteReason', '', 'remark', '', 'updatedAt', ''),
jsonb_build_object('id', p_order_id || '-item-4', 'name', '<EFBFBD>?, 'required', true, 'completed', false, 'incompleteReason', '', 'remark', '', 'updatedAt', '')
)
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_timeline(p_logs JSONB)
RETURNS JSONB
LANGUAGE sql
IMMUTABLE
AS $$
SELECT COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'id', COALESCE(item ->> 'id', md5(COALESCE(item::TEXT, ''))),
'title', public.delivery_status_text(
public.delivery_front_status(
CASE
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_ACCEPTED') THEN 'accepted'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_CHECKED_IN') THEN 'arrived'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_IN_SERVICE') THEN 'in_service'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ACCEPTANCE_PENDING') THEN 'pending_acceptance'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_EXCEPTION') THEN 'exception'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_REJECTED') THEN 'rejected'
ELSE lower(COALESCE(item ->> 'to_status', item ->> 'status', 'assigned'))
END,
item
)
),
'time', COALESCE(item ->> 'created_at', ''),
'description', COALESCE(item ->> 'remark', '')
)
ORDER BY COALESCE(item ->> 'created_at', '') DESC
)
FROM jsonb_array_elements(COALESCE(p_logs, '[]'::jsonb)) item
),
'[]'::jsonb
)
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_status_logs(p_logs JSONB, p_order_id TEXT)
RETURNS JSONB
LANGUAGE sql
IMMUTABLE
AS $$
SELECT COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'id', COALESCE(item ->> 'id', md5(COALESCE(item::TEXT, ''))),
'orderId', p_order_id,
'fromStatus', public.delivery_front_status(lower(COALESCE(item ->> 'from_status', 'assigned')), item),
'toStatus', public.delivery_front_status(
CASE
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_ACCEPTED') THEN 'accepted'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_CHECKED_IN') THEN 'arrived'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_IN_SERVICE') THEN 'in_service'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ACCEPTANCE_PENDING') THEN 'pending_acceptance'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_EXCEPTION') THEN 'exception'
WHEN COALESCE(item ->> 'to_status', item ->> 'status', '') IN ('ORDER_REJECTED') THEN 'rejected'
ELSE lower(COALESCE(item ->> 'to_status', item ->> 'status', 'assigned'))
END,
item
),
'operatorRole', COALESCE(item ->> 'operator_role', item ->> 'actor_role', ''),
'operatorId', COALESCE(item ->> 'operator_id', item ->> 'actor_id', ''),
'remark', COALESCE(item ->> 'remark', ''),
'createdAt', COALESCE(item ->> 'created_at', '')
)
ORDER BY COALESCE(item ->> 'created_at', '') DESC
)
FROM jsonb_array_elements(COALESCE(p_logs, '[]'::jsonb)) item
),
'[]'::jsonb
)
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_evidence(p_evidence JSONB, p_order_id TEXT)
RETURNS JSONB
LANGUAGE sql
IMMUTABLE
AS $$
SELECT COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'id', COALESCE(item ->> 'id', md5(COALESCE(item::TEXT, ''))),
'orderId', p_order_id,
'phase', COALESCE(item ->> 'phase', 'service'),
'fileType', COALESCE(NULLIF(item ->> 'file_type', ''), 'image'),
'name', COALESCE(NULLIF(item ->> 'storage_path', ''), item ->> 'file_url', ''),
'url', COALESCE(item ->> 'file_url', ''),
'localPath', '',
'status', 'success',
'progress', 100,
'retryable', false,
'createdAt', COALESCE(item ->> 'created_at', '')
)
ORDER BY COALESCE(item ->> 'created_at', '') DESC
)
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
),
'[]'::jsonb
)
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_abnormal(p_exception JSONB, p_order_id TEXT)
RETURNS JSONB
LANGUAGE sql
IMMUTABLE
AS $$
SELECT CASE
WHEN p_exception IS NULL THEN NULL
ELSE jsonb_build_object(
'id', COALESCE(p_exception ->> 'id', ''),
'orderId', p_order_id,
'type', COALESCE(p_exception ->> 'exception_type', 'other'),
'description', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'occurredAt', COALESCE(p_exception ->> 'occurred_at', ''),
'locationText', COALESCE(p_exception ->> 'location_text', ''),
'images', COALESCE(p_exception -> 'images_json', '[]'::jsonb),
'needPlatformIntervention', COALESCE((p_exception ->> 'need_platform_intervention')::BOOLEAN, false),
'requestCancelOrder', COALESCE((p_exception ->> 'request_cancel_order')::BOOLEAN, false),
'requestReschedule', COALESCE((p_exception ->> 'request_reschedule')::BOOLEAN, false),
'createdAt', COALESCE(p_exception ->> 'created_at', '')
)
END
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_order_json(
p_raw JSONB,
p_logs JSONB DEFAULT '[]'::jsonb,
p_records JSONB DEFAULT '[]'::jsonb,
p_evidence JSONB DEFAULT '[]'::jsonb,
p_exception JSONB DEFAULT NULL,
p_source TEXT DEFAULT 'legacy'
)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_service JSONB := COALESCE(p_raw -> 'service_snapshot_json', jsonb_build_object('category', COALESCE(p_raw ->> 'service_category', ''), 'price', COALESCE((p_raw ->> 'service_price')::NUMERIC, 0)));
v_address JSONB := COALESCE(p_raw -> 'address_snapshot_json', '{}'::jsonb);
v_raw_status TEXT := COALESCE(p_raw ->> 'status', 'assigned');
v_normalized_status TEXT;
v_front_status TEXT;
v_checkin_record JSONB;
v_service_record JSONB;
v_service_items JSONB;
v_record_json JSONB;
v_timeline JSONB;
v_status_logs JSONB;
v_evidence_list JSONB;
BEGIN
IF p_source = 'care' THEN
IF COALESCE(p_raw ->> 'accepted_by_family_at', '') <> '' THEN
v_normalized_status := 'completed';
ELSIF COALESCE(p_raw ->> 'acceptance_pending_at', '') <> '' THEN
v_normalized_status := 'pending_acceptance';
ELSIF COALESCE(p_raw ->> 'service_started_at', '') <> '' THEN
v_normalized_status := 'in_service';
ELSIF COALESCE(p_raw ->> 'checked_in_at', '') <> '' THEN
v_normalized_status := 'arrived';
ELSIF COALESCE(p_raw ->> 'departed_at', '') <> '' THEN
v_normalized_status := 'departed';
ELSIF COALESCE(p_raw ->> 'accepted_at', '') <> '' THEN
v_normalized_status := 'accepted';
ELSE
v_normalized_status := CASE v_raw_status
WHEN 'ORDER_ACCEPTED' THEN 'accepted'
WHEN 'ORDER_CHECKED_IN' THEN 'arrived'
WHEN 'ORDER_IN_SERVICE' THEN 'in_service'
WHEN 'ACCEPTANCE_PENDING' THEN 'pending_acceptance'
WHEN 'ORDER_EXCEPTION' THEN 'exception'
WHEN 'ORDER_REJECTED' THEN 'rejected'
WHEN 'ORDER_CANCELLED' THEN 'cancelled'
WHEN 'ORDER_COMPLETED' THEN 'completed'
ELSE 'assigned'
END;
END IF;
ELSE
v_normalized_status := lower(v_raw_status);
END IF;
v_front_status := public.delivery_front_status(v_normalized_status, p_raw);
v_timeline := public.delivery_build_timeline(p_logs);
v_status_logs := public.delivery_build_status_logs(p_logs, COALESCE(p_raw ->> 'id', ''));
v_evidence_list := public.delivery_build_evidence(p_evidence, COALESCE(p_raw ->> 'id', ''));
SELECT item INTO v_checkin_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') = 'checkin'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
SELECT item INTO v_service_record
FROM jsonb_array_elements(COALESCE(p_records, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'checkin'
AND COALESCE(item ->> 'record_type', item ->> 'care_record_type', '') <> 'review'
ORDER BY COALESCE(item ->> 'created_at', '') DESC
LIMIT 1;
IF v_service_record IS NULL THEN
v_service_record := v_checkin_record;
END IF;
v_service_items := COALESCE(v_service_record -> 'service_items_json', public.delivery_default_service_items(COALESCE(p_raw ->> 'id', ''), COALESCE(p_raw ->> 'service_name', '')));
IF v_service_record IS NULL THEN
v_record_json := NULL;
ELSE
v_record_json := jsonb_build_object(
'id', COALESCE(v_service_record ->> 'id', ''),
'orderId', COALESCE(p_raw ->> 'id', ''),
'startTime', COALESCE(v_service_record ->> 'started_at', v_service_record ->> 'service_started_at', ''),
'endTime', COALESCE(v_service_record ->> 'finished_at', v_service_record ->> 'service_finished_at', ''),
'actualDurationMinutes', COALESCE((v_service_record ->> 'duration_minutes')::INTEGER, (v_service_record ->> 'actual_duration_minutes')::INTEGER, 0),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'serviceContent', COALESCE(
(
SELECT jsonb_agg(elem ->> 'name')
FROM jsonb_array_elements(COALESCE(v_service_items, '[]'::jsonb)) elem
WHERE COALESCE((elem ->> 'completed')::BOOLEAN, false)
),
'[]'::jsonb
),
'processNote', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'elderStatus', '',
'healthMetrics', jsonb_build_object('bloodPressure', '', 'heartRate', '', 'bloodSugar', '', 'bloodOxygen', ''),
'materialsUsed', '',
'abnormalNote', '',
'photos', COALESCE(
(
SELECT jsonb_agg(item ->> 'file_url')
FROM jsonb_array_elements(COALESCE(p_evidence, '[]'::jsonb)) item
WHERE COALESCE(item ->> 'phase', '') = 'service'
),
'[]'::jsonb
),
'staffRemark', COALESCE(v_service_record ->> 'remark', ''),
'familyConfirmation', jsonb_build_object('method', 'none', 'code', '', 'signatureName', '', 'signatureUrl', '', 'confirmedAt', ''),
'createdAt', COALESCE(v_service_record ->> 'created_at', ''),
'updatedAt', COALESCE(v_service_record ->> 'updated_at', '')
);
END IF;
RETURN jsonb_build_object(
'id', COALESCE(p_raw ->> 'id', ''),
'orderNo', COALESCE(NULLIF(p_raw ->> 'task_no', ''), p_raw ->> 'order_no', ''),
'serviceType', COALESCE(NULLIF(v_service ->> 'category', ''), '居家服务'),
'serviceName', COALESCE(p_raw ->> 'service_name', ''),
'serviceCategory', COALESCE(v_service ->> 'category', ''),
'serviceItems', COALESCE(v_service_items, '[]'::jsonb),
'elderId', COALESCE(p_raw ->> 'elder_id', p_raw ->> 'user_id', ''),
'elderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderNameMasked', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'elderGender', '',
'elderAge', 0,
'elderPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'elderPhoneMasked', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'fullElderName', COALESCE(NULLIF(p_raw ->> 'recipient_name', ''), p_raw ->> 'elder_name', ''),
'fullPhone', COALESCE(NULLIF(p_raw ->> 'recipient_phone', ''), p_raw ->> 'elder_phone', ''),
'contactRelation', '家属',
'addressSummary', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'address', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'addressDetail', COALESCE(NULLIF(v_address ->> 'detailAddress', ''), NULLIF(v_address ->> 'detail_address', ''), ''),
'fullAddress', COALESCE(NULLIF(v_address ->> 'fullAddress', ''), NULLIF(v_address ->> 'full_address', ''), NULLIF(v_address ->> 'address', ''), ''),
'latitude', COALESCE((v_address ->> 'latitude')::NUMERIC, 0),
'longitude', COALESCE((v_address ->> 'longitude')::NUMERIC, 0),
'appointmentTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentStartTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'appointmentEndTime', COALESCE(NULLIF(p_raw ->> 'appointment_time', ''), p_raw ->> 'scheduled_at', ''),
'duration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'estimatedDuration', COALESCE((p_raw ->> 'duration_minutes')::INTEGER, 90),
'price', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'staffIncome', COALESCE((v_service ->> 'price')::NUMERIC, 0),
'distance', '',
'actualStartTime', COALESCE(p_raw ->> 'service_started_at', ''),
'actualEndTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'status', v_front_status,
'statusText', public.delivery_status_text(v_front_status),
'statusTone', public.delivery_status_tone(v_front_status),
'riskTags', '[]'::jsonb,
'healthTags', '[]'::jsonb,
'careLevel', COALESCE(v_service ->> 'category', ''),
'needFamilyPresent', false,
'needMaterials', false,
'remark', COALESCE(p_raw ->> 'remark', ''),
'merchantId', COALESCE(p_raw ->> 'merchant_id', ''),
'merchantName', COALESCE(p_raw ->> 'merchant_name', ''),
'deliveryStaffId', COALESCE(NULLIF(p_raw ->> 'current_staff_id', ''), p_raw ->> 'assigned_to', ''),
'deliveryStaffName', COALESCE(p_raw ->> 'delivery_staff_name', ''),
'acceptTime', COALESCE(p_raw ->> 'accepted_at', ''),
'departTime', COALESCE(p_raw ->> 'departed_at', ''),
'arriveTime', COALESCE(NULLIF(p_raw ->> 'arrived_at', ''), p_raw ->> 'checked_in_at', ''),
'checkinTime', COALESCE(NULLIF(p_raw ->> 'checked_in_at', ''), p_raw ->> 'arrived_at', ''),
'startServiceTime', COALESCE(p_raw ->> 'service_started_at', ''),
'finishTime', COALESCE(NULLIF(p_raw ->> 'completed_at', ''), p_raw ->> 'service_completed_at', ''),
'cancelReason', COALESCE(p_raw ->> 'cancel_reason', ''),
'exceptionType', COALESCE(p_exception ->> 'exception_type', ''),
'exceptionDesc', COALESCE(p_exception ->> 'description', p_exception ->> 'remark', ''),
'evidenceList', COALESCE(v_evidence_list, '[]'::jsonb),
'signatureUrl', '',
'signatureName', '',
'satisfactionStatus', CASE WHEN v_front_status = 'pending_acceptance' THEN '待验<EFBFBD>? WHEN v_front_status = 'completed' THEN '<EFBFBD>? ELSE '待评<EFBFBD>? END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '<EFBFBD>? ELSE '待确<EFBFBD>? END,
'archiveStatus', '<EFBFBD>?,
'createdAt', COALESCE(p_raw ->> 'created_at', ''),
'updatedAt', COALESCE(p_raw ->> 'updated_at', ''),
'contactName', COALESCE(p_raw ->> 'contact_name', ''),
'contactPhone', COALESCE(p_raw ->> 'contact_phone', ''),
'notices', '[]'::jsonb,
'timeline', COALESCE(v_timeline, '[]'::jsonb),
'statusLog', COALESCE(v_status_logs, '[]'::jsonb),
'serviceSummary', COALESCE(v_service_record ->> 'summary', v_service_record ->> 'content', ''),
'progressNote', COALESCE(v_service_record ->> 'remark', ''),
'distanceKm', '',
'allowCheckinRadiusMeters', 100,
'lastLocation', CASE
WHEN v_checkin_record IS NULL THEN NULL
ELSE jsonb_build_object(
'latitude', COALESCE((v_checkin_record ->> 'latitude')::NUMERIC, (v_checkin_record ->> 'checkin_latitude')::NUMERIC, 0),
'longitude', COALESCE((v_checkin_record ->> 'longitude')::NUMERIC, (v_checkin_record ->> 'checkin_longitude')::NUMERIC, 0),
'address', COALESCE(v_checkin_record ->> 'location_text', v_checkin_record ->> 'checkin_address', ''),
'time', COALESCE(v_checkin_record ->> 'checked_in_at', v_checkin_record ->> 'checkin_time', '')
)
END,
'trackPoints', COALESCE(v_service_record -> 'track_points_json', '[]'::jsonb),
'serviceRecord', v_record_json,
'abnormalReport', public.delivery_build_abnormal(p_exception, COALESCE(p_raw ->> 'id', ''))
);
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_get_legacy_order_json(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_staff_id UUID := public.delivery_current_staff_id();
v_raw JSONB;
v_logs JSONB := '[]'::jsonb;
v_records JSONB := '[]'::jsonb;
v_evidence JSONB := '[]'::jsonb;
BEGIN
SELECT to_jsonb(o)
INTO v_raw
FROM public.hss_service_orders o
WHERE o.id = p_order_id
AND o.deleted_at IS NULL
AND (v_staff_id IS NULL OR o.current_staff_id = v_staff_id)
LIMIT 1;
IF v_raw IS NULL THEN
RETURN NULL;
END IF;
SELECT COALESCE(jsonb_agg(to_jsonb(l) ORDER BY l.created_at DESC), '[]'::jsonb)
INTO v_logs
FROM public.hss_service_order_status_logs l
WHERE l.order_id = p_order_id;
SELECT COALESCE(jsonb_agg(to_jsonb(r) ORDER BY r.created_at DESC), '[]'::jsonb)
INTO v_records
FROM public.hss_service_execution_records r
WHERE r.order_id = p_order_id;
SELECT COALESCE(jsonb_agg(to_jsonb(e) ORDER BY e.created_at DESC), '[]'::jsonb)
INTO v_evidence
FROM public.hss_service_evidence_files e
WHERE e.order_id = p_order_id;
RETURN public.delivery_build_order_json(v_raw, v_logs, v_records, v_evidence, NULL, 'legacy');
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_get_care_order_json(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id UUID := public.delivery_current_user_id();
v_raw JSONB;
v_logs JSONB := '[]'::jsonb;
v_records JSONB := '[]'::jsonb;
v_evidence JSONB := '[]'::jsonb;
v_exception JSONB;
BEGIN
IF NOT public.delivery_table_exists('ec_care_tasks') THEN
RETURN NULL;
END IF;
BEGIN
EXECUTE 'SELECT to_jsonb(t) FROM public.ec_care_tasks t WHERE t.id = $1::uuid AND ($2 IS NULL OR t.assigned_to = $2) LIMIT 1'
INTO v_raw
USING p_order_id, v_user_id;
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
IF v_raw IS NULL THEN
RETURN NULL;
END IF;
BEGIN
IF public.delivery_table_exists('hc_work_order_events') THEN
EXECUTE 'SELECT COALESCE(jsonb_agg(to_jsonb(e) ORDER BY e.created_at DESC), ''[]''::jsonb) FROM public.hc_work_order_events e WHERE e.task_id = $1::uuid'
INTO v_logs
USING p_order_id;
END IF;
IF public.delivery_table_exists('ec_care_records') THEN
EXECUTE 'SELECT COALESCE(jsonb_agg(to_jsonb(r) ORDER BY r.created_at DESC), ''[]''::jsonb) FROM public.ec_care_records r WHERE r.task_id = $1::uuid'
INTO v_records
USING p_order_id;
END IF;
IF public.delivery_table_exists('hc_evidence_files') THEN
EXECUTE 'SELECT COALESCE(jsonb_agg(to_jsonb(f) ORDER BY f.created_at DESC), ''[]''::jsonb) FROM public.hc_evidence_files f WHERE f.task_id = $1::uuid'
INTO v_evidence
USING p_order_id;
END IF;
IF public.delivery_table_exists('hc_work_order_exceptions') THEN
EXECUTE 'SELECT to_jsonb(x) FROM public.hc_work_order_exceptions x WHERE x.task_id = $1::uuid ORDER BY x.created_at DESC LIMIT 1'
INTO v_exception
USING p_order_id;
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
RETURN public.delivery_build_order_json(v_raw, v_logs, v_records, v_evidence, v_exception, 'care');
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_get_order_json(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_order JSONB;
BEGIN
v_order := public.delivery_get_care_order_json(p_order_id);
IF v_order IS NOT NULL THEN
RETURN v_order;
END IF;
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_order_matches(p_order JSONB, p_tab TEXT, p_keyword TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
v_status TEXT := COALESCE(p_order ->> 'status', 'pending_assignment');
v_keyword TEXT := btrim(COALESCE(p_keyword, ''));
BEGIN
IF COALESCE(p_tab, '') NOT IN ('', 'all') THEN
CASE p_tab
WHEN 'pending_accept' THEN
IF v_status NOT IN ('pending_assignment', 'pending_accept') THEN RETURN FALSE; END IF;
WHEN 'accepted' THEN
IF v_status NOT IN ('accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived') THEN RETURN FALSE; END IF;
WHEN 'serving' THEN
IF v_status NOT IN ('in_service', 'serving') THEN RETURN FALSE; END IF;
WHEN 'pending_submit' THEN
IF v_status NOT IN ('pending_confirm', 'pending_submit', 'pending_acceptance') THEN RETURN FALSE; END IF;
WHEN 'completed' THEN
IF v_status NOT IN ('completed', 'settled', 'archived') THEN RETURN FALSE; END IF;
WHEN 'exception' THEN
IF v_status NOT IN ('abnormal', 'exception_pending') THEN RETURN FALSE; END IF;
END CASE;
END IF;
IF v_keyword <> '' THEN
IF COALESCE(p_order ->> 'orderNo', '') NOT ILIKE '%' || v_keyword || '%'
AND COALESCE(p_order ->> 'serviceName', '') NOT ILIKE '%' || v_keyword || '%'
AND COALESCE(p_order ->> 'elderNameMasked', '') NOT ILIKE '%' || v_keyword || '%'
AND COALESCE(p_order ->> 'addressSummary', '') NOT ILIKE '%' || v_keyword || '%' THEN
RETURN FALSE;
END IF;
END IF;
RETURN TRUE;
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_append_if_match(p_result JSONB, p_order JSONB, p_tab TEXT, p_keyword TEXT)
RETURNS JSONB
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
IF p_order IS NULL THEN
RETURN COALESCE(p_result, '[]'::jsonb);
END IF;
IF public.delivery_order_matches(p_order, p_tab, p_keyword) THEN
RETURN COALESCE(p_result, '[]'::jsonb) || jsonb_build_array(p_order);
END IF;
RETURN COALESCE(p_result, '[]'::jsonb);
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_build_staff_info(p_staff_id UUID)
RETURNS JSONB
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
SELECT jsonb_build_object(
'id', s.id,
'userId', COALESCE(s.uid::TEXT, ''),
'staffNo', COALESCE(NULLIF(s.staff_no, ''), 'DEL-' || upper(substr(s.id::TEXT, 1, 8))),
'name', COALESCE(NULLIF(s.nickname, ''), '服务人员'),
'phone', COALESCE(s.phone, ''),
'role', 'delivery',
'status', CASE WHEN s.status = 0 OR COALESCE(s.is_active, true) = false THEN 'disabled' ELSE 'active' END,
'organizationId', COALESCE(s.station_id::TEXT, ''),
'organizationName', COALESCE(st.name, COALESCE(s.station_id::TEXT, '')),
'certificates', '[]'::jsonb,
'certificateStatus', CASE WHEN COALESCE(s.certificate_status, '') IN ('valid', 'expired') THEN s.certificate_status ELSE 'pending' END,
'certificateExpireAt', COALESCE(to_char(s.certificate_expire_at, 'YYYY-MM-DD'), ''),
'onlineStatus', CASE WHEN s.online_status IN ('online', 'busy') THEN s.online_status ELSE 'resting' END,
'serviceArea', COALESCE(s.service_area, ''),
'skills', CASE WHEN COALESCE(s.skills, '') = '' THEN '[]'::jsonb ELSE COALESCE(s.skills::jsonb, '[]'::jsonb) END,
'avatarUrl', COALESCE(s.avatar, ''),
'todayAccepted', 0,
'todayServing', 0,
'todayCompleted', 0,
'usesMock', false
)
FROM public.ml_delivery_staff s
LEFT JOIN public.ml_delivery_stations st ON st.id = s.station_id
WHERE s.id = p_staff_id
AND s.deleted_at IS NULL
LIMIT 1
$$;
CREATE OR REPLACE FUNCTION public.delivery_assert_staff_access(p_staff_id UUID)
RETURNS VOID
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_current_staff_id UUID := public.delivery_current_staff_id();
BEGIN
IF auth.uid() IS NULL THEN
RAISE EXCEPTION 'Permission denied';
END IF;
IF p_staff_id IS NOT NULL AND v_current_staff_id IS NOT NULL AND p_staff_id <> v_current_staff_id THEN
RAISE EXCEPTION 'Permission denied';
END IF;
END;
$$;
CREATE OR REPLACE FUNCTION public.delivery_try_update_care_task(p_order_id TEXT, p_status TEXT, p_patch JSONB, p_action TEXT, p_remark TEXT)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id UUID := public.delivery_current_user_id();
v_now TIMESTAMPTZ := now();
BEGIN
IF NOT public.delivery_table_exists('ec_care_tasks') THEN
RETURN NULL;
END IF;
BEGIN
EXECUTE $sql$
UPDATE public.ec_care_tasks
SET status = COALESCE($2, status),
accepted_at = COALESCE(($3 ->> 'accepted_at')::timestamptz, accepted_at),
departed_at = COALESCE(($3 ->> 'departed_at')::timestamptz, departed_at),
checked_in_at = COALESCE(($3 ->> 'checked_in_at')::timestamptz, checked_in_at),
service_started_at = COALESCE(($3 ->> 'service_started_at')::timestamptz, service_started_at),
service_completed_at = COALESCE(($3 ->> 'service_completed_at')::timestamptz, service_completed_at),
acceptance_pending_at = COALESCE(($3 ->> 'acceptance_pending_at')::timestamptz, acceptance_pending_at),
reject_reason = COALESCE($3 ->> 'reject_reason', reject_reason),
updated_at = COALESCE(($3 ->> 'updated_at')::timestamptz, $4)
WHERE id = $1::uuid
AND ($5 IS NULL OR assigned_to = $5)
$sql$
USING p_order_id, p_status, COALESCE(p_patch, '{}'::jsonb), v_now, v_user_id;
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
BEGIN
IF public.delivery_table_exists('hc_work_order_events') THEN
EXECUTE $sql$
INSERT INTO public.hc_work_order_events(id, task_id, from_status, to_status, actor_id, actor_role, action, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), $1::uuid, NULL, $2, $3, 'delivery', $4, $5, $6)
$sql$
USING p_order_id, p_status, v_user_id, COALESCE(p_action, ''), COALESCE(p_remark, ''), v_now;
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
RETURN public.delivery_get_care_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_order_detail(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
IF auth.uid() IS NULL THEN
RAISE EXCEPTION 'Permission denied';
END IF;
RETURN public.delivery_get_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_order_list(
p_staff_id UUID DEFAULT NULL,
p_tab TEXT DEFAULT 'all',
p_keyword TEXT DEFAULT ''
)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_user_id UUID := public.delivery_current_user_id();
v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id());
v_result JSONB := '[]'::jsonb;
v_task JSONB;
v_order JSONB;
v_order_id TEXT;
BEGIN
PERFORM public.delivery_assert_staff_access(v_staff_id);
IF public.delivery_table_exists('ec_care_tasks') THEN
BEGIN
FOR v_task IN EXECUTE 'SELECT to_jsonb(t) FROM public.ec_care_tasks t WHERE ($1 IS NULL OR t.assigned_to = $1) ORDER BY t.created_at DESC'
USING v_user_id
LOOP
v_order := public.delivery_get_care_order_json(v_task ->> 'id');
v_result := public.delivery_append_if_match(v_result, v_order, p_tab, p_keyword);
END LOOP;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
END IF;
FOR v_order_id IN
SELECT o.id
FROM public.hss_service_orders o
WHERE o.deleted_at IS NULL
AND (v_staff_id IS NULL OR o.current_staff_id = v_staff_id)
ORDER BY COALESCE(o.updated_at, o.created_at) DESC, o.created_at DESC
LOOP
v_order := public.delivery_get_legacy_order_json(v_order_id);
v_result := public.delivery_append_if_match(v_result, v_order, p_tab, p_keyword);
END LOOP;
RETURN COALESCE(v_result, '[]'::jsonb);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_dashboard(p_staff_id UUID DEFAULT NULL)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id());
v_orders JSONB := public.rpc_delivery_order_list(v_staff_id, 'all', '');
v_item JSONB;
v_pending_assignment_count INTEGER := 0;
v_pending_accept_count INTEGER := 0;
v_today_order_count INTEGER := 0;
v_pending_depart_count INTEGER := 0;
v_serving_count INTEGER := 0;
v_completed_count INTEGER := 0;
v_exception_count INTEGER := 0;
v_next_order JSONB;
BEGIN
PERFORM public.delivery_assert_staff_access(v_staff_id);
FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb))
LOOP
IF COALESCE(v_item ->> 'status', '') IN ('pending_assignment', 'pending_accept') THEN
v_pending_assignment_count := v_pending_assignment_count + 1;
v_pending_accept_count := v_pending_accept_count + 1;
END IF;
IF COALESCE(v_item ->> 'status', '') IN ('accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived') THEN
v_pending_depart_count := v_pending_depart_count + 1;
END IF;
IF COALESCE(v_item ->> 'status', '') IN ('pending_assignment', 'pending_accept', 'accepted', 'waiting_departure', 'departed', 'on_the_way', 'arrived', 'in_service', 'serving', 'pending_acceptance') THEN
v_today_order_count := v_today_order_count + 1;
END IF;
IF COALESCE(v_item ->> 'status', '') IN ('in_service', 'serving') THEN
v_serving_count := v_serving_count + 1;
END IF;
IF COALESCE(v_item ->> 'status', '') IN ('completed', 'pending_acceptance', 'settled', 'archived') THEN
v_completed_count := v_completed_count + 1;
END IF;
IF COALESCE(v_item ->> 'status', '') IN ('abnormal', 'exception_pending') THEN
v_exception_count := v_exception_count + 1;
END IF;
IF v_next_order IS NULL AND COALESCE(v_item ->> 'status', '') NOT IN ('completed', 'settled', 'archived', 'cancelled', 'rejected', 'abnormal') THEN
v_next_order := v_item;
END IF;
END LOOP;
RETURN jsonb_build_object(
'pendingAssignmentCount', v_pending_assignment_count,
'pendingAcceptCount', v_pending_accept_count,
'todayOrderCount', v_today_order_count,
'pendingDepartCount', v_pending_depart_count,
'servingCount', v_serving_count,
'completedCount', v_completed_count,
'exceptionCount', v_exception_count,
'expectedIncome', 0,
'onlineStatus', COALESCE(public.delivery_build_staff_info(v_staff_id) ->> 'onlineStatus', 'resting'),
'nextOrder', v_next_order,
'recentOrders', COALESCE((SELECT jsonb_agg(value) FROM (SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb)) LIMIT 5) q), '[]'::jsonb)
);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_accept_order(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_ACCEPTED',
jsonb_build_object('accepted_at', v_now, 'updated_at', v_now),
'accept_task',
'服务人员接单'
);
END IF;
UPDATE public.hss_service_assignments
SET status = 'accepted', accepted_at = v_now, updated_at = v_now
WHERE order_id = p_order_id
AND (v_staff_id IS NULL OR staff_id = v_staff_id);
UPDATE public.hss_service_orders
SET status = 'accepted', accepted_at = v_now, updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'assigned', 'accepted', public.delivery_current_user_id(), 'delivery', '服务人员接单', v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_reject_order(p_order_id TEXT, p_reason TEXT DEFAULT '')
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
v_reason TEXT := COALESCE(p_reason, '');
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_REJECTED',
jsonb_build_object('reject_reason', v_reason, 'updated_at', v_now),
'reject_task',
CASE WHEN v_reason = '' THEN '服务人员拒单' ELSE v_reason END
);
END IF;
UPDATE public.hss_service_assignments
SET status = 'rejected', rejected_at = v_now, reject_reason = v_reason, updated_at = v_now
WHERE order_id = p_order_id
AND (v_staff_id IS NULL OR staff_id = v_staff_id);
UPDATE public.hss_service_orders
SET status = 'rejected', updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'assigned', 'rejected', public.delivery_current_user_id(), 'delivery', CASE WHEN v_reason = '' THEN '服务人员拒单' ELSE v_reason END, v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_start_depart(p_order_id TEXT, p_location JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
v_remark TEXT := CASE WHEN COALESCE(p_location ->> 'address', '') = '' THEN '服务人员出发' ELSE '服务人员出发<EFBFBD>? || (p_location ->> 'address') END;
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_ACCEPTED',
jsonb_build_object('departed_at', v_now, 'updated_at', v_now),
'depart_task',
v_remark
);
END IF;
UPDATE public.hss_service_orders
SET status = 'departed', departed_at = v_now, updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'accepted', 'departed', public.delivery_current_user_id(), 'delivery', v_remark, v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_arrive_order(p_order_id TEXT, p_location JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
v_remark TEXT := CASE WHEN COALESCE(p_location ->> 'address', '') = '' THEN '' ELSE '<EFBFBD>? || (p_location ->> 'address') END;
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_CHECKED_IN',
jsonb_build_object('checked_in_at', v_now, 'updated_at', v_now),
'arrive_task',
v_remark
);
END IF;
UPDATE public.hss_service_orders
SET status = 'arrived', arrived_at = v_now, updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'departed', 'arrived', public.delivery_current_user_id(), 'delivery', v_remark, v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_checkin_order(p_order_id TEXT, p_payload JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
v_assignment_id TEXT := '';
v_record_id TEXT := 'ser-' || replace(gen_random_uuid()::TEXT, '-', '');
v_file TEXT;
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
BEGIN
IF public.delivery_table_exists('ec_care_records') THEN
EXECUTE $sql$
INSERT INTO public.ec_care_records(id, task_id, record_type, started_at, checked_in_at, latitude, longitude, location_text, remark, created_at, updated_at)
VALUES ($1, $2::uuid, 'checkin', $3, $3, ($4 -> 'location' ->> 'latitude')::double precision, ($4 -> 'location' ->> 'longitude')::double precision, COALESCE($4 -> 'location' ->> 'address', ''), COALESCE($4 ->> 'note', ''), $3, $3)
$sql$
USING 'care-checkin-' || replace(gen_random_uuid()::TEXT, '-', ''), p_order_id, v_now, COALESCE(p_payload, '{}'::jsonb);
END IF;
IF public.delivery_table_exists('hc_evidence_files') THEN
FOR v_file IN SELECT jsonb_array_elements_text(COALESCE(p_payload -> 'photos', '[]'::jsonb))
LOOP
EXECUTE $sql$
INSERT INTO public.hc_evidence_files(id, task_id, care_record_id, phase, file_type, storage_path, file_url, latitude, longitude, captured_at, created_at)
VALUES ($1, $2::uuid, NULL, 'checkin', 'image', $3, $3, ($4 -> 'location' ->> 'latitude')::double precision, ($4 -> 'location' ->> 'longitude')::double precision, $5, $5)
$sql$
USING 'hc-ef-' || replace(gen_random_uuid()::TEXT, '-', ''), p_order_id, v_file, COALESCE(p_payload, '{}'::jsonb), v_now;
END LOOP;
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_CHECKED_IN',
jsonb_build_object('checked_in_at', v_now, 'updated_at', v_now),
'checkin_task',
COALESCE(NULLIF(p_payload ->> 'note', ''), '到场签到')
);
END IF;
SELECT COALESCE(a.id, '') INTO v_assignment_id
FROM public.hss_service_assignments a
WHERE a.order_id = p_order_id
AND (v_staff_id IS NULL OR a.staff_id = v_staff_id)
ORDER BY a.created_at DESC
LIMIT 1;
INSERT INTO public.hss_service_execution_records(
id, order_id, assignment_id, checkin_time, checkin_latitude, checkin_longitude, checkin_address, remark, created_at, updated_at
) VALUES (
v_record_id, p_order_id, v_assignment_id, v_now,
COALESCE((p_payload -> 'location' ->> 'latitude')::double precision, 0),
COALESCE((p_payload -> 'location' ->> 'longitude')::double precision, 0),
COALESCE(p_payload -> 'location' ->> 'address', ''),
COALESCE(p_payload ->> 'note', ''),
v_now, v_now
) ON CONFLICT (id) DO UPDATE SET
checkin_time = EXCLUDED.checkin_time,
checkin_latitude = EXCLUDED.checkin_latitude,
checkin_longitude = EXCLUDED.checkin_longitude,
checkin_address = EXCLUDED.checkin_address,
remark = EXCLUDED.remark,
updated_at = EXCLUDED.updated_at;
FOR v_file IN SELECT jsonb_array_elements_text(COALESCE(p_payload -> 'photos', '[]'::jsonb))
LOOP
INSERT INTO public.hss_service_evidence_files(id, order_id, execution_record_id, phase, file_type, storage_path, file_url, latitude, longitude, captured_at, created_at)
VALUES (
'sef-' || replace(gen_random_uuid()::TEXT, '-', ''),
p_order_id,
v_record_id,
'checkin',
'image',
v_file,
v_file,
COALESCE((p_payload -> 'location' ->> 'latitude')::double precision, 0),
COALESCE((p_payload -> 'location' ->> 'longitude')::double precision, 0),
v_now,
v_now
);
END LOOP;
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_start_service(p_order_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_IN_SERVICE',
jsonb_build_object('service_started_at', v_now, 'updated_at', v_now),
'start_service',
'开始服<EFBFBD>?
);
END IF;
UPDATE public.hss_service_execution_records
SET service_started_at = v_now, updated_at = v_now
WHERE order_id = p_order_id;
UPDATE public.hss_service_orders
SET status = 'in_service', service_started_at = v_now, updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'arrived', 'in_service', public.delivery_current_user_id(), 'delivery', '<EFBFBD>?, v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_save_progress(p_order_id TEXT, p_payload JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
v_assignment_id TEXT := '';
v_record_id TEXT := 'progress-' || replace(gen_random_uuid()::TEXT, '-', '');
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
BEGIN
IF public.delivery_table_exists('ec_care_records') THEN
EXECUTE $sql$
INSERT INTO public.ec_care_records(id, task_id, record_type, started_at, finished_at, duration_minutes, service_items_json, summary, remark, created_at, updated_at)
VALUES ($1, $2::uuid, 'service', now(), NULL, 0, COALESCE($3 -> 'items', '[]'::jsonb), COALESCE($3 ->> 'serviceSummary', ''), COALESCE($3 ->> 'progressNote', ''), $4, $4)
ON CONFLICT (id) DO UPDATE SET
service_items_json = EXCLUDED.service_items_json,
summary = EXCLUDED.summary,
remark = EXCLUDED.remark,
updated_at = EXCLUDED.updated_at
$sql$
USING v_record_id, p_order_id, COALESCE(p_payload, '{}'::jsonb), v_now;
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
RETURN public.delivery_get_care_order_json(p_order_id);
END IF;
SELECT COALESCE(a.id, '') INTO v_assignment_id
FROM public.hss_service_assignments a
WHERE a.order_id = p_order_id
AND (v_staff_id IS NULL OR a.staff_id = v_staff_id)
ORDER BY a.created_at DESC
LIMIT 1;
INSERT INTO public.hss_service_execution_records(
id, order_id, assignment_id, service_started_at, service_finished_at, actual_duration_minutes, service_items_json, summary, remark, created_at, updated_at
) VALUES (
v_record_id, p_order_id, v_assignment_id, v_now, NULL, 0,
COALESCE(p_payload -> 'items', '[]'::jsonb),
COALESCE(p_payload ->> 'serviceSummary', ''),
COALESCE(p_payload ->> 'progressNote', ''),
v_now, v_now
) ON CONFLICT (id) DO UPDATE SET
service_items_json = EXCLUDED.service_items_json,
summary = EXCLUDED.summary,
remark = EXCLUDED.remark,
updated_at = EXCLUDED.updated_at;
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_upload_evidence(
p_order_id TEXT,
p_phase TEXT DEFAULT 'service',
p_files TEXT[] DEFAULT ARRAY[]::TEXT[]
)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_file TEXT;
v_result JSONB := '[]'::jsonb;
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
BEGIN
IF public.delivery_table_exists('hc_evidence_files') THEN
FOREACH v_file IN ARRAY COALESCE(p_files, ARRAY[]::TEXT[])
LOOP
EXECUTE $sql$
INSERT INTO public.hc_evidence_files(id, task_id, care_record_id, phase, file_type, storage_path, file_url, captured_at, created_at)
VALUES ($1, $2::uuid, NULL, $3, 'image', $4, $4, $5, $5)
$sql$
USING 'hc-ef-' || replace(gen_random_uuid()::TEXT, '-', ''), p_order_id, COALESCE(p_phase, 'service'), v_file, v_now;
END LOOP;
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
v_result := COALESCE(public.delivery_get_care_order_json(p_order_id) -> 'evidenceList', '[]'::jsonb);
RETURN v_result;
END IF;
FOREACH v_file IN ARRAY COALESCE(p_files, ARRAY[]::TEXT[])
LOOP
INSERT INTO public.hss_service_evidence_files(id, order_id, execution_record_id, phase, file_type, storage_path, file_url, captured_at, created_at)
VALUES (
'sef-' || replace(gen_random_uuid()::TEXT, '-', ''),
p_order_id,
NULL,
COALESCE(p_phase, 'service'),
'image',
v_file,
v_file,
v_now,
v_now
);
END LOOP;
RETURN COALESCE(public.delivery_get_legacy_order_json(p_order_id) -> 'evidenceList', '[]'::jsonb);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_retry_evidence(p_order_id TEXT, p_evidence_id TEXT)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_order JSONB := public.delivery_get_order_json(p_order_id);
v_item JSONB;
BEGIN
FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(v_order -> 'evidenceList', '[]'::jsonb))
LOOP
IF COALESCE(v_item ->> 'id', '') = COALESCE(p_evidence_id, '') THEN
RETURN v_item;
END IF;
END LOOP;
RETURN NULL;
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_submit_exception(p_order_id TEXT, p_payload JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
BEGIN
IF public.delivery_table_exists('hc_work_order_exceptions') THEN
EXECUTE $sql$
INSERT INTO public.hc_work_order_exceptions(id, task_id, exception_type, description, occurred_at, location_text, images_json, need_platform_intervention, request_cancel_order, request_reschedule, created_by, created_at, updated_at)
VALUES ($1, $2::uuid, COALESCE($3 ->> 'exceptionType', 'other'), COALESCE($3 ->> 'description', ''), COALESCE(($3 ->> 'occurredAt')::timestamptz, $4), COALESCE($3 ->> 'locationText', ''), COALESCE($3 -> 'images', '[]'::jsonb), COALESCE(($3 ->> 'needPlatformIntervention')::boolean, false), COALESCE(($3 ->> 'requestCancelOrder')::boolean, false), COALESCE(($3 ->> 'requestReschedule')::boolean, false), $5, $4, $4)
$sql$
USING 'hc-ex-' || replace(gen_random_uuid()::TEXT, '-', ''), p_order_id, COALESCE(p_payload, '{}'::jsonb), v_now, public.delivery_current_user_id();
END IF;
EXCEPTION WHEN OTHERS THEN
NULL;
END;
RETURN public.delivery_try_update_care_task(
p_order_id,
'ORDER_EXCEPTION',
jsonb_build_object('updated_at', v_now),
'report_exception',
COALESCE(NULLIF(p_payload ->> 'description', ''), '异常上报')
);
END IF;
UPDATE public.hss_service_orders
SET status = 'exception', updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'in_service', 'exception', public.delivery_current_user_id(), 'delivery', COALESCE(NULLIF(p_payload ->> 'description', ''), '异常上报'), v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_finish_service(p_order_id TEXT, p_payload JSONB DEFAULT '{}'::jsonb)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now TIMESTAMPTZ := now();
v_staff_id UUID := public.delivery_current_staff_id();
BEGIN
IF public.delivery_get_care_order_json(p_order_id) IS NOT NULL THEN
RETURN public.delivery_try_update_care_task(
p_order_id,
'ACCEPTANCE_PENDING',
jsonb_build_object('service_completed_at', v_now, 'acceptance_pending_at', v_now, 'updated_at', v_now),
'finish_service',
'服务完成等待用户验<EFBFBD>?
);
END IF;
UPDATE public.hss_service_execution_records
SET service_finished_at = v_now, updated_at = v_now
WHERE order_id = p_order_id;
UPDATE public.hss_service_orders
SET status = 'pending_acceptance', completed_at = v_now, pending_acceptance_at = v_now, updated_at = v_now
WHERE id = p_order_id
AND deleted_at IS NULL
AND (v_staff_id IS NULL OR current_staff_id = v_staff_id);
INSERT INTO public.hss_service_order_status_logs(id, order_id, from_status, to_status, operator_id, operator_role, remark, created_at)
VALUES (md5(random()::text || clock_timestamp()::text), p_order_id, 'in_service', 'pending_acceptance', public.delivery_current_user_id(), 'delivery', '<EFBFBD>?, v_now);
RETURN public.delivery_get_legacy_order_json(p_order_id);
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_record_list(p_staff_id UUID DEFAULT NULL)
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id());
v_orders JSONB := public.rpc_delivery_order_list(v_staff_id, 'all', '');
v_result JSONB := '[]'::jsonb;
v_item JSONB;
BEGIN
PERFORM public.delivery_assert_staff_access(v_staff_id);
FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb))
LOOP
IF COALESCE(v_item ->> 'status', '') IN ('pending_acceptance', 'completed', 'settled', 'archived', 'abnormal', 'exception_pending', 'cancelled', 'rejected') THEN
v_result := v_result || jsonb_build_array(jsonb_build_object(
'id', 'record-' || COALESCE(v_item ->> 'id', ''),
'orderId', COALESCE(v_item ->> 'id', ''),
'orderNo', COALESCE(v_item ->> 'orderNo', ''),
'serviceName', COALESCE(v_item ->> 'serviceName', ''),
'elderName', COALESCE(v_item ->> 'elderName', ''),
'elderNameMasked', COALESCE(v_item ->> 'elderNameMasked', ''),
'status', COALESCE(v_item ->> 'status', ''),
'statusText', COALESCE(v_item ->> 'statusText', ''),
'appointmentStartTime', COALESCE(v_item ->> 'appointmentStartTime', ''),
'appointmentTime', COALESCE(v_item ->> 'appointmentTime', ''),
'actualStartTime', COALESCE(v_item ->> 'actualStartTime', ''),
'actualEndTime', COALESCE(v_item ->> 'actualEndTime', COALESCE(v_item ->> 'updatedAt', '')),
'staffIncome', COALESCE((v_item ->> 'staffIncome')::NUMERIC, 0),
'settlementStatus', COALESCE(v_item ->> 'settlementStatus', ''),
'acceptanceStatus', COALESCE(v_item ->> 'satisfactionStatus', ''),
'exceptionDesc', COALESCE(v_item ->> 'exceptionDesc', ''),
'ratingText', CASE WHEN COALESCE(v_item ->> 'status', '') IN ('completed', 'settled', 'archived') THEN '已验<EFBFBD>? WHEN COALESCE(v_item ->> 'status', '') = 'pending_acceptance' THEN '<EFBFBD>? ELSE '待补<EFBFBD>? END,
'hasServiceRecord', (v_item -> 'serviceRecord') IS NOT NULL
));
END IF;
END LOOP;
RETURN v_result;
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_message_list()
RETURNS JSONB
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_orders JSONB := public.rpc_delivery_order_list(public.delivery_current_staff_id(), 'all', '');
v_result JSONB := '[]'::jsonb;
v_item JSONB;
BEGIN
FOR v_item IN SELECT value FROM jsonb_array_elements(COALESCE(v_orders, '[]'::jsonb)) LIMIT 8
LOOP
v_result := v_result || jsonb_build_array(jsonb_build_object(
'id', 'msg-' || COALESCE(v_item ->> 'id', ''),
'title', COALESCE(v_item ->> 'statusText', ''),
'content', COALESCE(v_item ->> 'serviceName', '') || '' || COALESCE(v_item ->> 'elderNameMasked', '') || '' || COALESCE(v_item ->> 'appointmentStartTime', ''),
'type', 'order',
'createdAt', COALESCE(v_item ->> 'updatedAt', v_item ->> 'createdAt', ''),
'read', false,
'orderId', COALESCE(v_item ->> 'id', '')
));
END LOOP;
RETURN v_result;
END;
$$;
CREATE OR REPLACE FUNCTION public.rpc_delivery_set_online_status(p_staff_id UUID, p_status TEXT)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_staff_id UUID := COALESCE(p_staff_id, public.delivery_current_staff_id());
v_info JSONB;
BEGIN
PERFORM public.delivery_assert_staff_access(v_staff_id);
UPDATE public.ml_delivery_staff
SET online_status = CASE WHEN p_status IN ('online', 'busy') THEN p_status ELSE 'resting' END,
updated_at = now()
WHERE id = v_staff_id
AND deleted_at IS NULL;
v_info := public.delivery_build_staff_info(v_staff_id);
IF v_info IS NULL THEN
RETURN NULL;
END IF;
RETURN v_info || jsonb_build_object('onlineStatus', CASE WHEN p_status IN ('online', 'busy') THEN p_status ELSE 'resting' END);
END;
$$;
REVOKE ALL ON FUNCTION public.rpc_delivery_order_detail(TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_order_detail(TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_order_list(UUID, TEXT, TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_order_list(UUID, TEXT, TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_dashboard(UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_dashboard(UUID) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_accept_order(TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_accept_order(TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_reject_order(TEXT, TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_reject_order(TEXT, TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_start_depart(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_start_depart(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_arrive_order(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_arrive_order(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_checkin_order(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_checkin_order(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_start_service(TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_start_service(TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_save_progress(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_save_progress(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_upload_evidence(TEXT, TEXT, TEXT[]) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_upload_evidence(TEXT, TEXT, TEXT[]) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_retry_evidence(TEXT, TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_retry_evidence(TEXT, TEXT) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_submit_exception(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_submit_exception(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_finish_service(TEXT, JSONB) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_finish_service(TEXT, JSONB) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_record_list(UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_record_list(UUID) TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_message_list() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_message_list() TO authenticated;
REVOKE ALL ON FUNCTION public.rpc_delivery_set_online_status(UUID, TEXT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_delivery_set_online_status(UUID, TEXT) TO authenticated;
COMMIT;