import supa from '@/components/supadb/aksupainstance.uts' import { getCurrentUserId } from '@/utils/store.uts' import { getCurrentUser } from '@/utils/store.uts' import type { UserAddress } from '@/utils/supabaseService.uts' import type { HomeServiceCatalogType } from '@/types/home-service.uts' import type { DeliveryServiceRecordType } from '@/types/delivery.uts' import { getServiceOrderStatusText, normalizeServiceOrderStatus, type ServiceOrderAddressSnapshotType, type ServiceEvidenceFileType, type ServiceExecutionRecordType, type ServiceOrderStatus, type ServiceOrderTimelineItemType, type ServiceOrderType, type ServiceReviewType } from '@/types/service-order.uts' export type CreateServiceOrderParams = { service: HomeServiceCatalogType address: ServiceOrderAddressSnapshotType recipientName: string recipientPhone: string contactName: string contactPhone: string appointmentTime: string remark: string } function nowText(): string { return new Date().toISOString().replace('T', ' ').substring(0, 19) } function nowIso(): string { return new Date().toISOString() } function buildId(prefix: string): string { return prefix + '-' + String(Date.now()) + '-' + String(Math.floor(Math.random() * 100000)).padStart(5, '0') } function buildHex(length: number): string { const chars = '0123456789abcdef' let result = '' for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)) } return result } function buildUuidLike(): string { const segment1 = buildHex(8) const segment2 = buildHex(4) const segment3 = '4' + buildHex(3) const variants = '89ab' const segment4 = variants.charAt(Math.floor(Math.random() * variants.length)) + buildHex(3) const segment5 = buildHex(12) return segment1 + '-' + segment2 + '-' + segment3 + '-' + segment4 + '-' + segment5 } function buildOrderNo(): string { const date = new Date() const y = String(date.getFullYear()) const m = String(date.getMonth() + 1).padStart(2, '0') const d = String(date.getDate()).padStart(2, '0') const h = String(date.getHours()).padStart(2, '0') const i = String(date.getMinutes()).padStart(2, '0') const s = String(date.getSeconds()).padStart(2, '0') return 'HS' + y + m + d + h + i + s + String(Math.floor(Math.random() * 900) + 100) } function isUuidLike(value: string): boolean { return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test(value) } function normalizeUuidOrNull(value: string): string | null { if (value == '') { return null } return isUuidLike(value) ? value : null } function normalizeAppointmentTime(value: string): string | null { const text = value.trim() if (text == '') { return null } if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?$/.test(text)) { return text } if (/^\d{4}-\d{2}-\d{2}(\s+(上午|下午|晚上))$/.test(text)) { return text } if (/^\d{4}-\d{2}-\d{2}T/.test(text)) { const parsed = Date.parse(text) if (!isNaN(parsed)) { const date = new Date(parsed) const year = String(date.getFullYear()) const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hour = String(date.getHours()).padStart(2, '0') const minute = String(date.getMinutes()).padStart(2, '0') return year + '-' + month + '-' + day + ' ' + hour + ':' + minute } return text.replace('T', ' ') } const monthDayMatch = text.match(/(\d{2})\/(\d{2})/) if (monthDayMatch != null) { const month = monthDayMatch[1] ?? '' const day = monthDayMatch[2] ?? '' if (month == '' || day == '') { return text } const year = String(new Date().getFullYear()) const rangeMatch = text.match(/(\d{2}:\d{2})(\s*-\s*\d{2}:\d{2})?/) if (rangeMatch != null) { const startTime = rangeMatch[1] ?? '' const endRange = rangeMatch[2] ?? '' if (startTime != '') { return year + '-' + month + '-' + day + ' ' + startTime + endRange.replace(/\s+/g, '') } } const tailText = text.substring(text.indexOf(month + '/' + day) + 5).trim() return year + '-' + month + '-' + day + (tailText != '' ? ' ' + tailText : '') } const explicitParsed = Date.parse(text) if (!isNaN(explicitParsed) && /^\d{4}\//.test(text)) { const date = new Date(explicitParsed) const year = String(date.getFullYear()) const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hour = String(date.getHours()).padStart(2, '0') const minute = String(date.getMinutes()).padStart(2, '0') return year + '-' + month + '-' + day + ' ' + hour + ':' + minute } const year = String(new Date().getFullYear()) return /^\d{2}-\d{2}\s+\d{2}:\d{2}/.test(text) ? year + '-' + text : text } function safeParseObject(value: string): UTSJSONObject { if (value == '') { return JSON.parse('{}') as UTSJSONObject } return JSON.parse(value) as UTSJSONObject } function safeParseArray(value: string): Array { if (value == '') { return [] as Array } const parsed = JSON.parse(value) as any if (Array.isArray(parsed)) { return parsed as Array } return [] as Array } function safeJsonField(source: any, key: string): string { const plain = JSON.parse(JSON.stringify(source)) as any const value = plain[key] if (value == null) { return '' } return JSON.stringify(value) } function safeFirstJsonField(source: any, keys: Array): string { const plain = JSON.parse(JSON.stringify(source)) as any for (let i = 0; i < keys.length; i++) { const value = plain[keys[i]] if (value != null) { return JSON.stringify(value) } } return '' } function plainObject(source: any): any { return JSON.parse(JSON.stringify(source)) as any } function readString(source: any, key: string): string { const value = plainObject(source)[key] if (value == null) { return '' } return typeof value == 'string' ? value : String(value) } function readNumber(source: any, key: string): number { const value = plainObject(source)[key] if (typeof value == 'number') { return value } if (typeof value == 'string' && value != '') { const parsed = Number(value) return isNaN(parsed) ? 0 : parsed } return 0 } function hasMissingColumnError(error: any, columnName: string): boolean { if (error == null || columnName == '') { return false } const errorText = JSON.stringify(error).toLowerCase() return errorText.indexOf('could not find the') >= 0 && errorText.indexOf(columnName.toLowerCase()) >= 0 } function shouldUseCareTaskPath(orderId: string): boolean { return isUuidLike(orderId) } function isStaffActive(staff: any): boolean { const plain = plainObject(staff) if (plain['deleted_at'] != null && String(plain['deleted_at']) != '') { return false } if (plain['is_active'] === false) { return false } return readNumber(staff, 'status') == 1 } function getStaffPriority(staff: any): number { let score = 0 const onlineStatus = readString(staff, 'online_status') if (onlineStatus == 'online') { score += 30 } else if (onlineStatus == 'resting' || onlineStatus == '') { score += 20 } else if (onlineStatus == 'busy') { score += 10 } if (readString(staff, 'uid') != '') { score += 5 } if (readString(staff, 'station_id') != '') { score += 1 } return score } async function getAutoAssignableStaff(): Promise { const staffResponse = await supa .from('ml_delivery_staff') .select('id, uid, station_id, nickname, phone, status, deleted_at, is_active, online_status, updated_at, created_at') .eq('status', 1) .execute() if (staffResponse.error != null || staffResponse.data == null) { return null } const rawStaffList = staffResponse.data as Array let selected: any = null let bestScore = -1 let bestTime = '' for (let i = 0; i < rawStaffList.length; i++) { const staff = rawStaffList[i] if (!isStaffActive(staff)) { continue } const score = getStaffPriority(staff) const timeMark = readFirstString(staff, ['updated_at', 'created_at']) if (selected == null || score > bestScore || (score == bestScore && timeMark > bestTime)) { selected = staff bestScore = score bestTime = timeMark } } return selected } function buildEcServiceRequestPayload(params: CreateServiceOrderParams, userId: string, requestId: string, createdAt: string, appointmentTime: string | null, useAddressSnapshot: boolean): any { const payload = { id: requestId, user_id: userId, service_catalog_id: params.service.id, service_name: params.service.name, service_category: params.service.category, elder_name: params.recipientName, elder_phone: params.recipientPhone, contact_name: params.contactName, contact_phone: params.contactPhone, scheduled_at: appointmentTime, remark: params.remark, status: 'ORDER_CREATED', created_at: createdAt, updated_at: createdAt } as any if (useAddressSnapshot) { payload.address_snapshot = params.address as any } else { payload.address_snapshot_json = params.address as any } return payload } function buildEcServiceRequestPayloadWithoutAddress(params: CreateServiceOrderParams, userId: string, requestId: string, createdAt: string, appointmentTime: string | null): any { return { id: requestId, user_id: userId, service_catalog_id: params.service.id, service_name: params.service.name, service_category: params.service.category, elder_name: params.recipientName, elder_phone: params.recipientPhone, contact_name: params.contactName, contact_phone: params.contactPhone, scheduled_at: appointmentTime, remark: params.remark, status: 'ORDER_CREATED', created_at: createdAt, updated_at: createdAt } as any } function buildEcCareTaskPayload(params: CreateServiceOrderParams, userId: string, requestId: string, taskId: string, taskNo: string, createdAt: string, appointmentTime: string | null, useAddressSnapshot: boolean): any { const payload = { id: taskId, task_no: taskNo, request_id: requestId, user_id: userId, service_catalog_id: params.service.id, service_name: params.service.name, service_category: params.service.category, service_snapshot_json: params.service as any, elder_name: params.recipientName, elder_phone: params.recipientPhone, contact_name: params.contactName, contact_phone: params.contactPhone, scheduled_at: appointmentTime, remark: params.remark, status: 'ORDER_CREATED', created_at: createdAt, updated_at: createdAt } as any if (useAddressSnapshot) { payload.address_snapshot = params.address as any } else { payload.address_snapshot_json = params.address as any } return payload } function buildEcCareTaskPayloadWithoutAddress(params: CreateServiceOrderParams, userId: string, requestId: string, taskId: string, taskNo: string, createdAt: string, appointmentTime: string | null): any { return { id: taskId, task_no: taskNo, request_id: requestId, user_id: userId, service_catalog_id: params.service.id, service_name: params.service.name, service_category: params.service.category, service_snapshot_json: params.service as any, elder_name: params.recipientName, elder_phone: params.recipientPhone, contact_name: params.contactName, contact_phone: params.contactPhone, scheduled_at: appointmentTime, remark: params.remark, status: 'ORDER_CREATED', created_at: createdAt, updated_at: createdAt } as any } function parseTimeline(item: any): ServiceOrderTimelineItemType { return { id: readString(item, 'id'), orderId: readString(item, 'order_id'), fromStatus: readString(item, 'from_status'), toStatus: normalizeServiceOrderStatus(readString(item, 'to_status')), operatorId: readString(item, 'operator_id'), operatorRole: readString(item, 'operator_role'), remark: readString(item, 'remark'), createdAt: readString(item, 'created_at') } } function parseReview(item: any): ServiceReviewType { return { id: readString(item, 'id'), orderId: readString(item, 'order_id'), userId: readString(item, 'user_id'), rating: readNumber(item, 'rating') == 0 ? 5 : readNumber(item, 'rating'), tags: safeParseArray(safeJsonField(item, 'tags_json')), content: readString(item, 'content'), createdAt: readString(item, 'created_at') } } function parseExecutionRecord(item: any): ServiceExecutionRecordType { return { id: readString(item, 'id'), orderId: readString(item, 'order_id'), assignmentId: readString(item, 'assignment_id'), checkinTime: readString(item, 'checkin_time'), checkinLatitude: readNumber(item, 'checkin_latitude'), checkinLongitude: readNumber(item, 'checkin_longitude'), checkinAddress: readString(item, 'checkin_address'), serviceStartedAt: readString(item, 'service_started_at'), serviceFinishedAt: readString(item, 'service_finished_at'), actualDurationMinutes: readNumber(item, 'actual_duration_minutes'), serviceItemsJson: safeJsonField(item, 'service_items_json'), summary: readString(item, 'summary'), remark: readString(item, 'remark'), trackPointsJson: safeJsonField(item, 'track_points_json'), createdAt: readString(item, 'created_at'), updatedAt: readString(item, 'updated_at') } } function parseEvidenceFile(item: any): ServiceEvidenceFileType { return { id: readString(item, 'id'), orderId: readString(item, 'order_id'), executionRecordId: readString(item, 'execution_record_id'), phase: readString(item, 'phase'), fileType: readString(item, 'file_type'), storagePath: readString(item, 'storage_path'), fileUrl: readString(item, 'file_url'), latitude: readNumber(item, 'latitude'), longitude: readNumber(item, 'longitude'), capturedAt: readString(item, 'captured_at'), createdAt: readString(item, 'created_at') } } function parseServiceOrder(item: any, logs: Array, review: ServiceReviewType | null): ServiceOrderType { const addressSnapshot = safeFirstJsonField(item, ['address_snapshot_json', 'address_snapshot']) const serviceSnapshot = safeJsonField(item, 'service_snapshot_json') const addressObj = plainObject(safeParseObject(addressSnapshot)) const serviceObj = plainObject(safeParseObject(serviceSnapshot)) return { id: readString(item, 'id'), orderNo: readString(item, 'order_no'), userId: readString(item, 'user_id'), serviceId: readString(item, 'service_id'), serviceName: readString(item, 'service_name'), serviceSnapshot: { serviceId: readString(serviceObj, 'serviceId') != '' ? readString(serviceObj, 'serviceId') : readString(item, 'service_id'), serviceName: readString(serviceObj, 'serviceName') != '' ? readString(serviceObj, 'serviceName') : readString(item, 'service_name'), category: readString(serviceObj, 'category'), price: readNumber(serviceObj, 'price'), durationText: readString(serviceObj, 'durationText'), summary: readString(serviceObj, 'summary'), tags: safeParseArray(safeJsonField(serviceObj, 'tags')), suitableFor: readString(serviceObj, 'suitableFor') }, serviceAddressId: readString(item, 'service_address_id'), addressSnapshot: { addressId: readString(addressObj, 'addressId'), contactName: readString(addressObj, 'contactName'), contactPhone: readString(addressObj, 'contactPhone'), province: readString(addressObj, 'province'), city: readString(addressObj, 'city'), district: readString(addressObj, 'district'), detailAddress: readString(addressObj, 'detailAddress'), fullAddress: readString(addressObj, 'fullAddress'), latitude: readNumber(addressObj, 'latitude'), longitude: readNumber(addressObj, 'longitude'), coordinateType: readString(addressObj, 'coordinateType') == '' ? 'gcj02' : readString(addressObj, 'coordinateType'), remark: readString(addressObj, 'remark') }, recipientName: readString(item, 'recipient_name'), recipientPhone: readString(item, 'recipient_phone'), contactName: readString(item, 'contact_name'), contactPhone: readString(item, 'contact_phone'), appointmentTime: readString(item, 'appointment_time'), remark: readString(item, 'remark'), status: normalizeServiceOrderStatus(readString(item, 'status')), currentAssignmentId: readString(item, 'current_assignment_id'), currentStaffId: readString(item, 'current_staff_id'), acceptedAt: readString(item, 'accepted_at'), departedAt: readString(item, 'departed_at'), arrivedAt: readString(item, 'arrived_at'), serviceStartedAt: readString(item, 'service_started_at'), completedAt: readString(item, 'completed_at'), pendingAcceptanceAt: readString(item, 'pending_acceptance_at'), acceptedByUserAt: readString(item, 'accepted_by_user_at'), reviewedAt: readString(item, 'reviewed_at'), createdAt: readString(item, 'created_at'), updatedAt: readString(item, 'updated_at'), staffName: '', staffPhone: '', logs, executionRecord: null, evidenceFiles: [] as Array, review } } async function insertLegacyStatusLog(orderId: string, fromStatus: string, toStatus: ServiceOrderStatus, operatorId: string, operatorRole: string, remark: string): Promise { await supa.from('hss_service_order_status_logs').insert({ id: buildId('slog'), order_id: orderId, from_status: fromStatus, to_status: toStatus, operator_id: operatorId == '' ? null : operatorId, operator_role: operatorRole, remark, created_at: new Date().toISOString() }).execute() } async function insertWorkOrderEvent(taskId: string, fromStatus: string, toStatus: string, actorId: string, actorRole: string, action: string, remark: string): Promise { await supa.from('hc_work_order_events').insert({ id: buildId('hc-event'), task_id: taskId, from_status: fromStatus == '' ? null : fromStatus, to_status: toStatus, actor_id: actorId == '' ? null : actorId, actor_role: actorRole, action, remark, created_at: nowIso() }).execute() } function readFirstString(source: any, keys: Array): string { for (let i = 0; i < keys.length; i++) { const value = readString(source, keys[i]) if (value != '') { return value } } return '' } function readFirstNumber(source: any, keys: Array): number { for (let i = 0; i < keys.length; i++) { const value = readNumber(source, keys[i]) if (value != 0) { return value } } return 0 } function readJsonObjectField(source: any, keys: Array): any { const plain = plainObject(source) for (let i = 0; i < keys.length; i++) { const value = plain[keys[i]] if (value != null) { return value } } return null } function mapCareTaskRowToLegacyOrderRow(item: any): any { const serviceSnapshotValue = readJsonObjectField(item, ['service_snapshot_json']) const addressSnapshotValue = readJsonObjectField(item, ['address_snapshot', 'address_snapshot_json']) let derivedStatus = readString(item, 'status') if (readFirstString(item, ['accepted_by_family_at']) != '') { derivedStatus = 'ACCEPTED' } else if (readFirstString(item, ['acceptance_pending_at']) != '') { derivedStatus = 'ACCEPTANCE_PENDING' } else if (readString(item, 'service_started_at') != '') { derivedStatus = 'ORDER_IN_SERVICE' } else if (readFirstString(item, ['checked_in_at']) != '') { derivedStatus = 'ORDER_CHECKED_IN' } else if (readString(item, 'departed_at') != '') { derivedStatus = 'departed' } else if (readString(item, 'accepted_at') != '') { derivedStatus = 'ORDER_ACCEPTED' } else if (readFirstString(item, ['assigned_to']) != '') { derivedStatus = 'ORDER_ASSIGNED' } const serviceSnapshot = serviceSnapshotValue != null ? serviceSnapshotValue : { serviceId: readFirstString(item, ['service_catalog_id', 'service_id']), serviceName: readString(item, 'service_name'), category: readFirstString(item, ['service_category', 'category']), price: readFirstNumber(item, ['service_price', 'price']), durationText: readFirstString(item, ['service_duration_text', 'duration_text']), summary: readFirstString(item, ['service_summary', 'summary']), tags: [] as Array, suitableFor: readFirstString(item, ['suitable_for']) } return { id: readString(item, 'id'), order_no: readFirstString(item, ['task_no', 'order_no']), user_id: readFirstString(item, ['user_id', 'requester_user_id']), service_id: readFirstString(item, ['service_catalog_id', 'service_id']), service_name: readString(item, 'service_name'), service_snapshot_json: serviceSnapshot, service_address_id: readFirstString(item, ['service_address_id', 'address_id']), address_snapshot_json: addressSnapshotValue != null ? addressSnapshotValue : JSON.parse('{}'), recipient_name: readFirstString(item, ['elder_name', 'recipient_name']), recipient_phone: readFirstString(item, ['elder_phone', 'recipient_phone']), contact_name: readString(item, 'contact_name'), contact_phone: readString(item, 'contact_phone'), appointment_time: readFirstString(item, ['scheduled_at', 'appointment_time']), remark: readString(item, 'remark'), status: derivedStatus, current_assignment_id: readFirstString(item, ['assignment_id', 'current_assignment_id']), current_staff_id: readFirstString(item, ['assigned_to', 'current_staff_id']), accepted_at: readString(item, 'accepted_at'), departed_at: readString(item, 'departed_at'), arrived_at: readFirstString(item, ['checked_in_at', 'arrived_at']), service_started_at: readString(item, 'service_started_at'), completed_at: readFirstString(item, ['service_completed_at', 'completed_at']), pending_acceptance_at: readFirstString(item, ['acceptance_pending_at', 'pending_acceptance_at']), accepted_by_user_at: readFirstString(item, ['accepted_by_family_at', 'accepted_by_user_at']), reviewed_at: readString(item, 'reviewed_at'), created_at: readString(item, 'created_at'), updated_at: readString(item, 'updated_at') } } function mapWorkOrderEventToLegacyLog(item: any): any { return { id: readString(item, 'id'), order_id: readFirstString(item, ['task_id', 'order_id']), from_status: readString(item, 'from_status'), to_status: readFirstString(item, ['to_status', 'status']), operator_id: readFirstString(item, ['actor_id', 'operator_id']), operator_role: readFirstString(item, ['actor_role', 'operator_role']), remark: readString(item, 'remark'), created_at: readString(item, 'created_at') } } function mapCareReviewRecordToLegacyReview(item: any): any { return { id: readString(item, 'id'), order_id: readFirstString(item, ['task_id', 'order_id']), user_id: readFirstString(item, ['created_by', 'user_id']), rating: readFirstNumber(item, ['rating']), tags_json: readJsonObjectField(item, ['tags_json']) != null ? readJsonObjectField(item, ['tags_json']) : [] as Array, content: readFirstString(item, ['content', 'summary', 'remark']), created_at: readString(item, 'created_at') } } function buildLegacyExecutionRecord(taskId: string, records: Array): ServiceExecutionRecordType | null { if (records.length == 0) { return null } let checkinRecord: any = null let serviceRecord: any = null for (let i = 0; i < records.length; i++) { const recordType = readFirstString(records[i], ['record_type', 'care_record_type']) if (recordType == 'review') { continue } if (recordType == 'checkin') { checkinRecord = records[i] continue } if (serviceRecord == null) { serviceRecord = records[i] } } const target = serviceRecord != null ? serviceRecord : (checkinRecord != null ? checkinRecord : records[0]) return parseExecutionRecord({ id: readString(target, 'id'), order_id: taskId, assignment_id: readFirstString(target, ['assignment_id']), checkin_time: checkinRecord != null ? readFirstString(checkinRecord, ['checked_in_at', 'checkin_time', 'started_at']) : readFirstString(target, ['checked_in_at', 'checkin_time']), checkin_latitude: checkinRecord != null ? readFirstNumber(checkinRecord, ['latitude', 'checkin_latitude']) : readFirstNumber(target, ['latitude', 'checkin_latitude']), checkin_longitude: checkinRecord != null ? readFirstNumber(checkinRecord, ['longitude', 'checkin_longitude']) : readFirstNumber(target, ['longitude', 'checkin_longitude']), checkin_address: checkinRecord != null ? readFirstString(checkinRecord, ['location_text', 'checkin_address', 'remark']) : readFirstString(target, ['location_text', 'checkin_address']), service_started_at: readFirstString(target, ['started_at', 'service_started_at']), service_finished_at: readFirstString(target, ['finished_at', 'service_finished_at']), actual_duration_minutes: readFirstNumber(target, ['duration_minutes', 'actual_duration_minutes']), service_items_json: readJsonObjectField(target, ['service_items_json']) != null ? readJsonObjectField(target, ['service_items_json']) : [] as Array, summary: readFirstString(target, ['summary', 'content']), remark: readString(target, 'remark'), track_points_json: readJsonObjectField(target, ['track_points_json']) != null ? readJsonObjectField(target, ['track_points_json']) : [] as Array, created_at: readString(target, 'created_at'), updated_at: readString(target, 'updated_at') }) } async function getCareTaskDetail(taskId: string): Promise { if (!isUuidLike(taskId)) { return null } const taskResponse = await supa.from('ec_care_tasks').select('*').eq('id', taskId).limit(1).execute() if (taskResponse.error != null || taskResponse.data == null) { return null } const taskRows = taskResponse.data as Array if (taskRows.length == 0) { return null } const eventsResponse = await supa.from('hc_work_order_events').select('*').eq('task_id', taskId).order('created_at', { ascending: false }).execute() const recordsResponse = await supa.from('ec_care_records').select('*').eq('task_id', taskId).order('created_at', { ascending: false }).execute() const evidenceResponse = await supa.from('hc_evidence_files').select('*').eq('task_id', taskId).order('created_at', { ascending: false }).execute() const logs = [] as Array if (eventsResponse.data != null) { const rawEvents = eventsResponse.data as Array for (let i = 0; i < rawEvents.length; i++) { logs.push(parseTimeline(mapWorkOrderEventToLegacyLog(rawEvents[i]))) } } let review: ServiceReviewType | null = null const recordRows = recordsResponse.data != null ? recordsResponse.data as Array : [] as Array for (let i = 0; i < recordRows.length; i++) { const recordType = readFirstString(recordRows[i], ['record_type', 'care_record_type']) if (recordType == 'review') { review = parseReview(mapCareReviewRecordToLegacyReview(recordRows[i])) break } } const parsed = parseServiceOrder(mapCareTaskRowToLegacyOrderRow(taskRows[0]), logs, review) parsed.executionRecord = buildLegacyExecutionRecord(taskId, recordRows) if (evidenceResponse.data != null) { const rawEvidence = evidenceResponse.data as Array const evidenceFiles = [] as Array for (let i = 0; i < rawEvidence.length; i++) { evidenceFiles.push(parseEvidenceFile({ id: readString(rawEvidence[i], 'id'), order_id: taskId, execution_record_id: readFirstString(rawEvidence[i], ['care_record_id', 'execution_record_id']), phase: readString(rawEvidence[i], 'phase'), file_type: readString(rawEvidence[i], 'file_type'), storage_path: readString(rawEvidence[i], 'storage_path'), file_url: readString(rawEvidence[i], 'file_url'), latitude: readFirstNumber(rawEvidence[i], ['latitude']), longitude: readFirstNumber(rawEvidence[i], ['longitude']), captured_at: readString(rawEvidence[i], 'captured_at'), created_at: readString(rawEvidence[i], 'created_at') })) } parsed.evidenceFiles = evidenceFiles } if (parsed.currentStaffId != '') { const staffResponse = await supa.from('ml_delivery_staff').select('nickname, phone').eq('uid', parsed.currentStaffId).limit(1).execute() if (staffResponse.data != null) { const rawStaffList = staffResponse.data as Array if (rawStaffList.length > 0) { parsed.staffName = readString(rawStaffList[0], 'nickname') parsed.staffPhone = readString(rawStaffList[0], 'phone') } } } return parsed } async function tryCreateCareTask(params: CreateServiceOrderParams): Promise { const userId = getCurrentUserId() if (userId == '') { return null } const requestId = buildUuidLike() const taskId = buildUuidLike() const taskNo = buildOrderNo() const createdAt = nowIso() const appointmentTime = normalizeAppointmentTime(params.appointmentTime) let requestResponse = await supa.from('ec_service_requests').insert( buildEcServiceRequestPayload(params, userId, requestId, createdAt, appointmentTime, true) ).execute() if (requestResponse.error != null) { requestResponse = await supa.from('ec_service_requests').insert( buildEcServiceRequestPayload(params, userId, requestId, createdAt, appointmentTime, false) ).execute() } if (requestResponse.error != null) { requestResponse = await supa.from('ec_service_requests').insert( buildEcServiceRequestPayloadWithoutAddress(params, userId, requestId, createdAt, appointmentTime) ).execute() } if (requestResponse.error != null) { return null } let taskResponse = await supa.from('ec_care_tasks').insert( buildEcCareTaskPayload(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime, true) ).execute() if (taskResponse.error != null) { taskResponse = await supa.from('ec_care_tasks').insert( buildEcCareTaskPayload(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime, false) ).execute() } if (taskResponse.error != null) { taskResponse = await supa.from('ec_care_tasks').insert( buildEcCareTaskPayloadWithoutAddress(params, userId, requestId, taskId, taskNo, createdAt, appointmentTime) ).execute() } if (taskResponse.error != null) { return null } await insertWorkOrderEvent(taskId, '', 'ORDER_CREATED', userId, 'consumer', 'create_task', '创建服务申请') const assignedStaff = await getAutoAssignableStaff() if (assignedStaff != null) { const assignedUserId = readString(assignedStaff, 'uid') if (assignedUserId != '') { await supa.from('ec_care_tasks').update({ status: 'ORDER_ASSIGNED', assigned_to: assignedUserId, updated_at: createdAt }).eq('id', taskId).execute() await insertWorkOrderEvent(taskId, 'ORDER_CREATED', 'ORDER_ASSIGNED', userId, 'system', 'assign_task', '系统已自动派单') } } return await getCareTaskDetail(taskId) } async function getLegacyServiceOrderDetail(orderId: string): Promise { const orderResponse = await supa.from('hss_service_orders').select('*').eq('id', orderId).limit(1).execute() if (orderResponse.error != null || orderResponse.data == null) { return null } const rawOrders = orderResponse.data as any[] if (rawOrders.length == 0) { return null } const logsResponse = await supa.from('hss_service_order_status_logs').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() const reviewResponse = await supa.from('hss_service_reviews').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() const executionResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).limit(1).execute() const evidenceResponse = await supa.from('hss_service_evidence_files').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() const logs = [] as Array if (logsResponse.data != null) { const rawLogs = logsResponse.data as any[] for (let i = 0; i < rawLogs.length; i++) { logs.push(parseTimeline(rawLogs[i])) } } let review: ServiceReviewType | null = null if (reviewResponse.data != null) { const rawReviews = reviewResponse.data as any[] if (rawReviews.length > 0) { review = parseReview(rawReviews[0]) } } const parsed = parseServiceOrder(rawOrders[0], logs, review) if (executionResponse.data != null) { const rawExecutionList = executionResponse.data as any[] if (rawExecutionList.length > 0) { parsed.executionRecord = parseExecutionRecord(rawExecutionList[0]) } } if (evidenceResponse.data != null) { const rawEvidenceList = evidenceResponse.data as any[] const evidenceFiles = [] as Array for (let i = 0; i < rawEvidenceList.length; i++) { evidenceFiles.push(parseEvidenceFile(rawEvidenceList[i])) } parsed.evidenceFiles = evidenceFiles } if (parsed.currentStaffId != '') { const staffResponse = await supa.from('ml_delivery_staff').select('nickname, phone').eq('id', parsed.currentStaffId).limit(1).execute() if (staffResponse.data != null) { const rawStaffList = staffResponse.data as any[] if (rawStaffList.length > 0) { const staffObj = plainObject(rawStaffList[0]) parsed.staffName = readString(staffObj, 'nickname') parsed.staffPhone = readString(staffObj, 'phone') } } } return parsed } export function buildAddressSnapshot(address: UserAddress, latitude: number, longitude: number): ServiceOrderAddressSnapshotType { return { addressId: address.id, contactName: address.recipient_name, contactPhone: address.phone, province: address.province, city: address.city, district: address.district, detailAddress: address.detail_address, fullAddress: address.province + address.city + address.district + ' ' + address.detail_address, latitude, longitude, coordinateType: 'gcj02', remark: address.label ?? '' } } export async function createServiceOrder(params: CreateServiceOrderParams): Promise { const newTask = await tryCreateCareTask(params) if (newTask != null) { return newTask } const userId = getCurrentUserId() if (userId == '') { return null } const orderId = buildId('so') const orderNo = buildOrderNo() const now = new Date().toISOString() const appointmentTime = normalizeAppointmentTime(params.appointmentTime) const response = await supa.from('hss_service_orders').insert({ id: orderId, order_no: orderNo, user_id: userId, service_id: params.service.id, service_name: params.service.name, service_snapshot_json: params.service as any, service_address_id: normalizeUuidOrNull(params.address.addressId), address_snapshot_json: params.address as any, recipient_name: params.recipientName, recipient_phone: params.recipientPhone, contact_name: params.contactName, contact_phone: params.contactPhone, appointment_time: appointmentTime, remark: params.remark, status: 'created', created_at: now, updated_at: now }).execute() if (response.error != null) { console.error('createServiceOrder failed', response.error) return null } await insertLegacyStatusLog(orderId, '', 'created', userId, 'consumer', '创建服务订单') const staffObj = await getAutoAssignableStaff() if (staffObj != null) { const plainStaff = plainObject(staffObj) const assignmentId = buildId('sa') await supa.from('hss_service_assignments').insert({ id: assignmentId, order_id: orderId, staff_id: readString(plainStaff, 'id'), station_id: readString(plainStaff, 'station_id') == '' ? null : readString(plainStaff, 'station_id'), status: 'assigned', assigned_at: now, created_at: now, updated_at: now }).execute() await supa.from('hss_service_orders').update({ status: 'assigned', current_assignment_id: assignmentId, current_staff_id: readString(plainStaff, 'id'), updated_at: now }).eq('id', orderId).execute() await insertLegacyStatusLog(orderId, 'created', 'assigned', userId, 'system', '系统已自动派单') } return await getLegacyServiceOrderDetail(orderId) } export async function listConsumerServiceOrders(): Promise> { const userId = getCurrentUserId() if (userId == '') { return [] as Array } const careTaskResponse = await supa.from('ec_care_tasks').select('*').eq('user_id', userId).order('created_at', { ascending: false }).execute() if (careTaskResponse.error == null && careTaskResponse.data != null) { const rawTasks = careTaskResponse.data as Array const taskResult = [] as Array for (let i = 0; i < rawTasks.length; i++) { const parsed = await getCareTaskDetail(readString(rawTasks[i], 'id')) if (parsed != null) { taskResult.push(parsed) } } return taskResult } const response = await supa.from('hss_service_orders').select('*').eq('user_id', userId).order('created_at', { ascending: false }).execute() if (response.error != null || response.data == null) { return [] as Array } const list = response.data as any[] const result = [] as Array for (let i = 0; i < list.length; i++) { const parsed = await getLegacyServiceOrderDetail(JSON.parse(JSON.stringify(list[i]))['id'] as string) if (parsed != null) { result.push(parsed) } } return result } export async function getServiceOrderDetail(orderId: string): Promise { if (shouldUseCareTaskPath(orderId)) { const careTask = await getCareTaskDetail(orderId) if (careTask != null) { return careTask } } return await getLegacyServiceOrderDetail(orderId) } export async function saveServiceRecord(orderId: string, record: DeliveryServiceRecordType): Promise { const userId = getCurrentUserId() if (userId == '') { return null } const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null if (careTask != null) { const savedAt = nowIso() const serviceContent = record.serviceContent.length > 0 ? record.serviceContent : record.serviceItems.map(item => item.name) const serviceSummary = record.serviceSummary != '' ? record.serviceSummary : record.processNote const healthMetrics = record.healthMetrics as any const familyConfirmation = record.familyConfirmation as any const insertResponse = await supa.from('ec_care_records').insert({ id: buildId('care-record'), task_id: orderId, record_type: 'service_record', created_by: userId, service_items_json: record.serviceItems as any, service_content_json: serviceContent as any, service_summary: serviceSummary, process_note: record.processNote, elder_status: record.elderStatus, health_metrics_json: healthMetrics, materials_used: record.materialsUsed, abnormal_note: record.abnormalNote, photos_json: record.photos as any, staff_remark: record.staffRemark, family_confirmation_json: familyConfirmation, created_at: savedAt, updated_at: savedAt }).execute() if (insertResponse.error != null) { return null } const updateResponse = await supa.from('ec_care_tasks').update({ updated_at: savedAt }).eq('id', orderId).execute() if (updateResponse.error != null) { return null } await insertWorkOrderEvent(orderId, careTask.status, careTask.status, userId, 'staff', 'save_service_record', serviceSummary == '' ? '服务记录已保存' : serviceSummary) return await getCareTaskDetail(orderId) } const current = await getLegacyServiceOrderDetail(orderId) if (current == null) { return null } const savedAt = nowIso() const serviceSummary = record.serviceSummary != '' ? record.serviceSummary : record.processNote const saveResponse = await supa.from('hss_service_execution_records').insert({ id: buildId('ser'), order_id: orderId, checkin_time: current.executionRecord != null ? current.executionRecord.checkin_time : savedAt, checkin_latitude: current.executionRecord != null ? current.executionRecord.checkin_latitude : 0, checkin_longitude: current.executionRecord != null ? current.executionRecord.checkin_longitude : 0, checkin_address: current.executionRecord != null ? current.executionRecord.checkin_address : '', service_content_json: record.serviceContent.length > 0 ? record.serviceContent as any : record.serviceItems.map(item => item.name) as any, service_summary: serviceSummary, completion_images_json: record.photos as any, signature_image: '', signature_name: record.familyConfirmation.familyMember, created_at: savedAt, updated_at: savedAt }).execute() if (saveResponse.error != null) { return null } await supa.from('hss_service_orders').update({ updated_at: savedAt }).eq('id', orderId).execute() return await getLegacyServiceOrderDetail(orderId) } export async function confirmServiceOrder(orderId: string, rating: number, content: string, tags: Array): Promise { const userId = getCurrentUserId() if (userId == '') { return null } const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null if (careTask != null) { const acceptedAt = nowIso() const updateResponse = await supa.from('ec_care_tasks').update({ status: 'ACCEPTED', accepted_by_family_at: acceptedAt, updated_at: acceptedAt }).eq('id', orderId).execute() if (updateResponse.error == null) { await insertWorkOrderEvent(orderId, 'ACCEPTANCE_PENDING', 'ACCEPTED', userId, 'consumer', 'accept_task', '用户确认验收') if (rating > 0 || content != '' || tags.length > 0) { await supa.from('ec_care_records').insert({ id: buildId('care-review'), task_id: orderId, record_type: 'review', created_by: userId, rating, tags_json: tags as any, content, created_at: acceptedAt, updated_at: acceptedAt }).execute() await insertWorkOrderEvent(orderId, 'ACCEPTED', 'ACCEPTED', userId, 'consumer', 'submit_review', '用户提交评价') } return await getCareTaskDetail(orderId) } } const current = await getServiceOrderDetail(orderId) if (current == null) { return null } const acceptedAt = new Date().toISOString() const updateResponse = await supa.from('hss_service_orders').update({ status: 'accepted_by_user', accepted_by_user_at: acceptedAt, updated_at: acceptedAt }).eq('id', orderId).execute() if (updateResponse.error != null) { return null } await insertLegacyStatusLog(orderId, current.status, 'accepted_by_user', userId, 'consumer', '用户确认验收') await supa.from('hss_service_reviews').insert({ id: buildId('srv'), order_id: orderId, user_id: userId, rating, tags_json: tags as any, content, created_at: acceptedAt }).execute() await supa.from('hss_service_orders').update({ status: 'reviewed', reviewed_at: acceptedAt, updated_at: acceptedAt }).eq('id', orderId).execute() await insertLegacyStatusLog(orderId, 'accepted_by_user', 'reviewed', userId, 'consumer', '用户提交评价') return await getLegacyServiceOrderDetail(orderId) } export async function rejectServiceOrderAcceptance(orderId: string, content: string): Promise { const userId = getCurrentUserId() if (userId == '') { return null } const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null if (careTask != null) { const rejectedAt = nowIso() const updateResponse = await supa.from('ec_care_tasks').update({ status: 'ACCEPTANCE_REJECTED', updated_at: rejectedAt }).eq('id', orderId).execute() if (updateResponse.error == null) { await supa.from('hc_work_order_exceptions').insert({ id: buildId('hc-ex'), task_id: orderId, exception_type: 'acceptance_rejected', description: content == '' ? '用户退回整改' : content, occurred_at: rejectedAt, location_text: '', images_json: [] as Array, need_platform_intervention: false, request_cancel_order: false, request_reschedule: false, created_by: userId, created_at: rejectedAt, updated_at: rejectedAt }).execute() await insertWorkOrderEvent(orderId, 'ACCEPTANCE_PENDING', 'ACCEPTANCE_REJECTED', userId, 'consumer', 'reject_acceptance', content == '' ? '用户退回整改' : content) return await getCareTaskDetail(orderId) } } const current = await getServiceOrderDetail(orderId) if (current == null) { return null } const updateResponse = await supa.from('hss_service_orders').update({ status: 'exception', updated_at: nowIso() }).eq('id', orderId).execute() if (updateResponse.error != null) { return null } await insertLegacyStatusLog(orderId, current.status, 'exception', userId, 'consumer', content == '' ? '用户退回整改' : content) return await getLegacyServiceOrderDetail(orderId) } export async function getCurrentConsumerUser() { return await getCurrentUser() }