测试delivery接单后数据获取链路,解决订单地址获取异常bug

This commit is contained in:
2026-05-28 18:08:39 +08:00
parent 83be5b3c66
commit c3324d459a
5 changed files with 3525 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
-- ============================================
-- 诊断脚本:定位 delivery 端地址空白问题
-- 请复制全部内容执行,把结果截图发给我
-- ============================================
-- 诊断1确认 delivery_build_order_json 当前版本
SELECT
proname,
CASE
WHEN prosrc LIKE '%NULLIF(p_raw -> ''address_snapshot_json'', ''{}''::jsonb)%' THEN 'FIXED (含 NULLIF 排除空对象)'
WHEN prosrc LIKE '%p_raw -> ''address_snapshot''%' THEN 'HAS_FALLBACK 但缺少 NULLIF'
ELSE 'NO_FALLBACK (只有 address_snapshot_json)'
END AS address_fallback_status,
CASE
WHEN prosrc LIKE '%|| jsonb_build_object%' THEN 'SPLIT (小块拼接)'
ELSE 'SINGLE_BLOCK (单个大块)'
END AS function_structure
FROM pg_proc
WHERE proname = 'delivery_build_order_json';
-- 诊断2查看 ec_care_tasks 最近5条订单的地址数据状态
SELECT
id,
task_no,
elder_name,
assigned_to,
COALESCE(address_snapshot_json::TEXT, 'NULL') AS addr_json_raw,
COALESCE(address_snapshot::TEXT, 'NULL') AS addr_snap_raw,
CASE
WHEN address_snapshot_json IS NULL THEN 'json_null'
WHEN address_snapshot_json = '{}'::jsonb THEN 'json_empty'
WHEN address_snapshot_json = 'null'::jsonb THEN 'json_literal_null'
ELSE 'json_has_data'
END AS json_status,
CASE
WHEN address_snapshot IS NULL THEN 'snap_null'
WHEN address_snapshot = '{}'::jsonb THEN 'snap_empty'
WHEN address_snapshot = 'null'::jsonb THEN 'snap_literal_null'
ELSE 'snap_has_data'
END AS snap_status
FROM public.ec_care_tasks
ORDER BY created_at DESC
LIMIT 5;
-- 诊断3直接测试 delivery_build_order_json 对最近一条 care 订单的输出
-- 如果返回的 address 为空字符串,说明函数内部有问题
SELECT
t.id,
t.task_no,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'address' AS test_address,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'elderGender' AS test_gender,
delivery_build_order_json(
to_jsonb(t),
'[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
) ->> 'elderAge' AS test_age
FROM public.ec_care_tasks t
ORDER BY t.created_at DESC
LIMIT 1;

View File

@@ -0,0 +1,30 @@
-- ============================================
-- 诊断:查看订单的地址数据
-- ============================================
-- 查看 ec_care_tasks 中这个订单的地址字段用订单ID替换 'xxx'
SELECT
id,
task_no,
COALESCE(address_snapshot_json::TEXT, 'NULL') AS address_snapshot_json_raw,
COALESCE(address_snapshot::TEXT, 'NULL') AS address_snapshot_raw,
CASE
WHEN address_snapshot_json IS NULL THEN 'null'
WHEN address_snapshot_json = '{}'::jsonb THEN 'empty_object'
ELSE 'has_data'
END AS json_status,
CASE
WHEN address_snapshot IS NULL THEN 'null'
WHEN address_snapshot = '{}'::jsonb THEN 'empty_object'
ELSE 'has_data'
END AS snapshot_status
FROM public.ec_care_tasks
WHERE task_no LIKE '%20260528%' -- 根据订单号调整
ORDER BY created_at DESC
LIMIT 5;
-- 直接测试 delivery_build_order_json 对这条数据的输出
-- SELECT delivery_build_order_json(
-- (SELECT to_jsonb(t) FROM public.ec_care_tasks t WHERE t.id = '订单UUID'::uuid),
-- '[]'::jsonb, '[]'::jsonb, '[]'::jsonb, NULL, 'care'
-- ) ->> 'address' AS test_address;

View File

@@ -0,0 +1,222 @@
-- ============================================
-- 修复 delivery 端地址读取问题
-- 增强 address_snapshot / address_snapshot_json 的兼容性
-- ============================================
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(
NULLIF(p_raw -> 'address_snapshot_json', '{}'::jsonb),
NULLIF(p_raw -> 'address_snapshot_json', 'null'::jsonb),
NULLIF(p_raw -> 'address_snapshot', '{}'::jsonb),
NULLIF(p_raw -> 'address_snapshot', 'null'::jsonb),
'{}'::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', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 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)
) || jsonb_build_object(
'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', '')
) || jsonb_build_object(
'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 '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'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)
) || jsonb_build_object(
'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;
$$;
-- 刷新 PostgREST schema cache
NOTIFY pgrst, 'reload schema';

View File

@@ -0,0 +1,227 @@
-- ============================================
-- 修复 delivery 端地址读取问题v2
-- 根因COALESCE + NULLIF 对 JSONB 类型处理不可靠
-- 改用 CASE WHEN jsonb_typeof() 明确判断有效对象
-- ============================================
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 := CASE
WHEN jsonb_typeof(p_raw -> 'address_snapshot_json') = 'object'
AND p_raw -> 'address_snapshot_json' != '{}'::jsonb
AND p_raw -> 'address_snapshot_json' != 'null'::jsonb
THEN p_raw -> 'address_snapshot_json'
WHEN jsonb_typeof(p_raw -> 'address_snapshot') = 'object'
AND p_raw -> 'address_snapshot' != '{}'::jsonb
AND p_raw -> 'address_snapshot' != 'null'::jsonb
THEN p_raw -> 'address_snapshot'
ELSE '{}'::jsonb
END;
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', COALESCE(NULLIF(p_raw ->> 'elder_gender', ''), p_raw ->> 'recipient_gender', ''),
'elderAge', COALESCE((p_raw ->> 'elder_age')::INTEGER, (p_raw ->> 'recipient_age')::INTEGER, 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)
) || jsonb_build_object(
'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', '')
) || jsonb_build_object(
'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 '待验收' WHEN v_front_status = 'completed' THEN '已验收' ELSE '待评价' END,
'settlementStatus', CASE WHEN v_front_status = 'completed' THEN '待结算' ELSE '待确认' END,
'archiveStatus', '未归档',
'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)
) || jsonb_build_object(
'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;
$$;
-- 刷新 PostgREST schema cache
NOTIFY pgrst, 'reload schema';