import supa from '@/components/supadb/aksupainstance.uts' import { getCurrentUserId } from '@/utils/store.uts' import { getDeliveryProfileByUserId } from '@/api/delivery.uts' import { getServiceOrderStatusText, normalizeServiceOrderStatus, type ServiceOrderStatus } from '@/types/service-order.uts' import type { DeliveryCheckinPayloadType, DeliveryDashboardType, DeliveryLocationType, DeliveryOrderType, DeliveryServiceRecordType } from '@/types/delivery.uts' 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') } async function getCurrentStaffId(): Promise { const userId = getCurrentUserId() if (userId == '') { return '' } const profile = await getDeliveryProfileByUserId(userId) return profile != null ? profile.id : '' } async function insertStatusLog(orderId: string, fromStatus: string, toStatus: ServiceOrderStatus, remark: string): Promise { const userId = getCurrentUserId() await supa.from('hss_service_order_status_logs').insert({ id: buildId('slog'), order_id: orderId, from_status: fromStatus, to_status: toStatus, operator_id: userId == '' ? null : userId, operator_role: 'delivery', remark, created_at: nowIso() }).execute() } function emptyOrder(): DeliveryOrderType { return { id: '', orderNo: '', serviceType: '', serviceName: '', serviceCategory: '', serviceItems: [] as Array, elderId: '', elderName: '', elderNameMasked: '', elderGender: '', elderAge: 0, elderPhone: '', elderPhoneMasked: '', fullElderName: '', fullPhone: '', contactRelation: '家属', addressSummary: '', address: '', addressDetail: '', fullAddress: '', latitude: 0, longitude: 0, appointmentTime: '', appointmentStartTime: '', appointmentEndTime: '', duration: 90, estimatedDuration: 90, price: 0, staffIncome: 0, distance: '', actualStartTime: '', actualEndTime: '', status: 'pending_assignment' as any, statusText: '', statusTone: 'warning', riskTags: [] as Array, healthTags: [] as Array, careLevel: '', needFamilyPresent: false, needMaterials: false, remark: '', merchantId: '', merchantName: '', deliveryStaffId: '', deliveryStaffName: '', acceptTime: '', departTime: '', arriveTime: '', checkinTime: '', finishTime: '', cancelReason: '', exceptionType: '', exceptionDesc: '', evidenceList: [] as Array, signatureUrl: '', signatureName: '', satisfactionStatus: '', settlementStatus: '', archiveStatus: '', createdAt: '', updatedAt: '', contactName: '', contactPhone: '', notices: [] as Array, timeline: [] as Array, statusLog: [] as Array, serviceSummary: '', progressNote: '', distanceKm: '', allowCheckinRadiusMeters: 100, lastLocation: null, trackPoints: [] as Array, serviceRecord: null, abnormalReport: null } as DeliveryOrderType } 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 statusToDeliveryStatus(status: ServiceOrderStatus): string { if (status == 'assigned') return 'pending_assignment' if (status == 'accepted') return 'accepted' if (status == 'departed') return 'departed' if (status == 'arrived') return 'arrived' if (status == 'in_service') return 'in_service' if (status == 'pending_acceptance') return 'pending_acceptance' if (status == 'reviewed' || status == 'accepted_by_user' || status == 'settled') return 'completed' if (status == 'rejected') return 'rejected' if (status == 'cancelled') return 'cancelled' if (status == 'exception') return 'abnormal' return 'pending_assignment' } function statusTone(status: ServiceOrderStatus): string { if (status == 'pending_acceptance' || status == 'assigned') return 'warning' if (status == 'accepted' || status == 'departed' || status == 'arrived' || status == 'in_service') return 'primary' if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 'success' if (status == 'rejected' || status == 'cancelled' || status == 'exception') return 'danger' return 'warning' } async function parseDeliveryOrder(orderId: string, item: any): Promise { const order = emptyOrder() const obj = JSON.parse(JSON.stringify(item)) as UTSJSONObject const addressRaw = safeJsonField(item, 'address_snapshot_json') const serviceRaw = safeJsonField(item, 'service_snapshot_json') const addressObj = JSON.parse(addressRaw == '' ? '{}' : addressRaw) as UTSJSONObject const serviceObj = JSON.parse(serviceRaw == '' ? '{}' : serviceRaw) as UTSJSONObject const normalizedStatus = normalizeServiceOrderStatus(obj.getString('status') ?? '') order.id = obj.getString('id') ?? '' order.orderNo = obj.getString('order_no') ?? '' order.serviceType = serviceObj.getString('category') ?? '居家服务' order.serviceName = obj.getString('service_name') ?? '' order.serviceCategory = serviceObj.getString('category') ?? '' order.elderName = obj.getString('recipient_name') ?? '' order.elderNameMasked = order.elderName order.fullElderName = order.elderName order.elderPhone = obj.getString('recipient_phone') ?? '' order.elderPhoneMasked = order.elderPhone order.fullPhone = order.elderPhone order.contactName = obj.getString('contact_name') ?? '' order.contactPhone = obj.getString('contact_phone') ?? '' order.contactRelation = '家属' order.address = addressObj.getString('fullAddress') ?? '' order.addressDetail = addressObj.getString('detailAddress') ?? '' order.fullAddress = order.address order.latitude = addressObj.getNumber('latitude') ?? 0 order.longitude = addressObj.getNumber('longitude') ?? 0 order.appointmentTime = obj.getString('appointment_time') ?? '' order.appointmentStartTime = order.appointmentTime order.appointmentEndTime = order.appointmentTime order.duration = 90 order.estimatedDuration = 90 order.price = serviceObj.getNumber('price') ?? 0 order.staffIncome = order.price order.status = statusToDeliveryStatus(normalizedStatus) as any order.statusText = getServiceOrderStatusText(normalizedStatus) order.statusTone = statusTone(normalizedStatus) order.remark = obj.getString('remark') ?? '' order.deliveryStaffId = obj.getString('current_staff_id') ?? '' order.acceptTime = obj.getString('accepted_at') ?? '' order.departTime = obj.getString('departed_at') ?? '' order.arriveTime = obj.getString('arrived_at') ?? '' order.actualStartTime = obj.getString('service_started_at') ?? '' order.startServiceTime = order.actualStartTime order.finishTime = obj.getString('completed_at') ?? '' order.createdAt = obj.getString('created_at') ?? '' order.updatedAt = obj.getString('updated_at') ?? '' order.allowCheckinRadiusMeters = 100 const logsResponse = await supa.from('hss_service_order_status_logs').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (logsResponse.data != null) { const rawLogs = logsResponse.data as any[] for (let i = 0; i < rawLogs.length; i++) { const logObj = JSON.parse(JSON.stringify(rawLogs[i])) as UTSJSONObject order.timeline.push({ id: logObj.getString('id') ?? '', title: getServiceOrderStatusText(normalizeServiceOrderStatus(logObj.getString('to_status') ?? 'created')), time: logObj.getString('created_at') ?? '', description: logObj.getString('remark') ?? '' }) } } const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (recordResponse.data != null) { const raw = recordResponse.data as any[] if (raw.length > 0) { const recordObj = JSON.parse(JSON.stringify(raw[0])) as UTSJSONObject order.checkinTime = recordObj.getString('checkin_time') ?? '' order.serviceRecord = { id: recordObj.getString('id') ?? '', orderId: orderId, startTime: recordObj.getString('service_started_at') ?? '', endTime: recordObj.getString('service_finished_at') ?? '', actualDurationMinutes: recordObj.getNumber('actual_duration_minutes') ?? 0, serviceItems: [] as Array, serviceContent: [] as Array, processNote: recordObj.getString('summary') ?? '', elderStatus: '', healthMetrics: { bloodPressure: '', heartRate: '', bloodSugar: '', bloodOxygen: '' }, materialsUsed: '', abnormalNote: '', photos: [] as Array, staffRemark: recordObj.getString('remark') ?? '', familyConfirmation: { method: 'none', code: '', signatureName: '', signatureUrl: '', confirmedAt: '' }, createdAt: recordObj.getString('created_at') ?? '', updatedAt: recordObj.getString('updated_at') ?? '' } as any } } const evidenceResponse = await supa.from('hss_service_evidence_files').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (evidenceResponse.data != null) { const rawEvidence = evidenceResponse.data as any[] for (let i = 0; i < rawEvidence.length; i++) { const evidenceObj = JSON.parse(JSON.stringify(rawEvidence[i])) as UTSJSONObject order.evidenceList.push({ id: evidenceObj.getString('id') ?? '', orderId: orderId, phase: evidenceObj.getString('phase') ?? '', fileType: evidenceObj.getString('file_type') ?? 'image', name: evidenceObj.getString('storage_path') ?? '', url: evidenceObj.getString('file_url') ?? '', localPath: '', status: 'success', progress: 100, retryable: false, createdAt: evidenceObj.getString('created_at') ?? '' }) } } return order } export async function getDashboard(): Promise { const orders = await getOrdersByTab('all') let pending = 0 let today = 0 let serving = 0 let completed = 0 let nextOrder: DeliveryOrderType | null = null for (let i = 0; i < orders.length; i++) { const item = orders[i] if (item.status == 'pending_assignment') pending++ if (item.status == 'pending_assignment' || item.status == 'accepted' || item.status == 'departed' || item.status == 'arrived' || item.status == 'in_service') today++ if (item.status == 'in_service') serving++ if (item.status == 'completed' || item.status == 'pending_acceptance') completed++ if (nextOrder == null && item.status != 'completed' && item.status != 'cancelled' && item.status != 'abnormal') { nextOrder = item } } return { pendingAssignmentCount: pending, pendingAcceptCount: pending, todayOrderCount: today, pendingDepartCount: 0, servingCount: serving, completedCount: completed, exceptionCount: 0, expectedIncome: 0, onlineStatus: 'online', nextOrder, recentOrders: orders.slice(0, 5) } as DeliveryDashboardType } export async function getOrdersByTab(tab: string): Promise> { const staffId = await getCurrentStaffId() if (staffId == '') { return [] as Array } const response = await supa.from('hss_service_orders').select('*').eq('current_staff_id', staffId).order('created_at', { ascending: false }).execute() if (response.error != null || response.data == null) { return [] as Array } const rawOrders = response.data as any[] const result = [] as Array for (let i = 0; i < rawOrders.length; i++) { const orderObj = JSON.parse(JSON.stringify(rawOrders[i])) as UTSJSONObject const normalized = normalizeServiceOrderStatus(orderObj.getString('status') ?? '') let matched = true if (tab == 'pending') { matched = normalized == 'assigned' } else if (tab == 'today') { matched = normalized == 'assigned' || normalized == 'accepted' || normalized == 'departed' || normalized == 'arrived' || normalized == 'in_service' || normalized == 'pending_acceptance' } else if (tab == 'history') { matched = normalized == 'pending_acceptance' || normalized == 'accepted_by_user' || normalized == 'reviewed' || normalized == 'settled' || normalized == 'exception' || normalized == 'cancelled' } if (tab == 'all' || matched) { result.push(await parseDeliveryOrder(orderObj.getString('id') ?? '', rawOrders[i])) } } return result } export async function getOrderDetail(orderId: string): Promise { const response = await supa.from('hss_service_orders').select('*').eq('id', orderId).single().execute() if (response.error != null || response.data == null) { return null } return await parseDeliveryOrder(orderId, response.data) } async function updateOrderStatus(orderId: string, nextStatus: ServiceOrderStatus, updateData: UTSJSONObject, remark: string): Promise { const current = await getOrderDetail(orderId) if (current == null) { return null } const map = JSON.parse('{}') as UTSJSONObject map.set('status', nextStatus) map.set('updated_at', nowIso()) const iterator = updateData.keys() while (iterator.hasNext()) { const key = iterator.next() map.set(key, updateData.get(key)) } const response = await supa.from('hss_service_orders').update(map).eq('id', orderId).execute() if (response.error != null) { console.error('updateOrderStatus failed', response.error) return null } await insertStatusLog(orderId, normalizeServiceOrderStatus(current.status as string), nextStatus, remark) return await getOrderDetail(orderId) } export async function acceptOrder(orderId: string): Promise { const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (assignmentResponse.data != null) { const raw = assignmentResponse.data as any[] if (raw.length > 0) { const assignmentId = JSON.parse(JSON.stringify(raw[0]))['id'] as string await supa.from('hss_service_assignments').update({ status: 'accepted', accepted_at: nowIso(), updated_at: nowIso() }).eq('id', assignmentId).execute() } } const updateData = JSON.parse('{}') as UTSJSONObject updateData.set('accepted_at', nowIso()) return await updateOrderStatus(orderId, 'accepted', updateData, '服务人员接单') } export async function departOrder(orderId: string, location: DeliveryLocationType | null): Promise { const updateData = JSON.parse('{}') as UTSJSONObject updateData.set('departed_at', nowIso()) return await updateOrderStatus(orderId, 'departed', updateData, location == null ? '服务人员出发' : '服务人员出发:' + location.address) } export async function arriveOrder(orderId: string, location: DeliveryLocationType | null): Promise { const updateData = JSON.parse('{}') as UTSJSONObject updateData.set('arrived_at', nowIso()) return await updateOrderStatus(orderId, 'arrived', updateData, location == null ? '服务人员到达' : '服务人员到达:' + location.address) } export async function checkinOrder(orderId: string, payload: DeliveryCheckinPayloadType): Promise { const current = await getOrderDetail(orderId) if (current == null) { return null } let assignmentId = '' const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (assignmentResponse.data != null) { const rawAssignments = assignmentResponse.data as any[] if (rawAssignments.length > 0) { assignmentId = JSON.parse(JSON.stringify(rawAssignments[0]))['id'] as string } } let recordId = buildId('ser') const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (recordResponse.data != null) { const records = recordResponse.data as any[] if (records.length > 0) { recordId = JSON.parse(JSON.stringify(records[0]))['id'] as string await supa.from('hss_service_execution_records').update({ checkin_time: nowIso(), checkin_latitude: payload.location.latitude, checkin_longitude: payload.location.longitude, checkin_address: payload.location.address, remark: payload.note, updated_at: nowIso() }).eq('id', recordId).execute() } else { await supa.from('hss_service_execution_records').insert({ id: recordId, order_id: orderId, assignment_id: assignmentId, checkin_time: nowIso(), checkin_latitude: payload.location.latitude, checkin_longitude: payload.location.longitude, checkin_address: payload.location.address, remark: payload.note, created_at: nowIso(), updated_at: nowIso() }).execute() } } for (let i = 0; i < payload.photos.length; i++) { await supa.from('hss_service_evidence_files').insert({ id: buildId('sef'), order_id: orderId, execution_record_id: recordId, phase: 'checkin', file_type: 'image', storage_path: payload.photos[i], file_url: payload.photos[i], latitude: payload.location.latitude, longitude: payload.location.longitude, captured_at: nowIso(), created_at: nowIso() }).execute() } return current } export async function startService(orderId: string): Promise { let recordId = '' const recordResponse = await supa.from('hss_service_execution_records').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (recordResponse.data != null) { const records = recordResponse.data as any[] if (records.length > 0) { recordId = JSON.parse(JSON.stringify(records[0]))['id'] as string await supa.from('hss_service_execution_records').update({ service_started_at: nowIso(), updated_at: nowIso() }).eq('id', recordId).execute() } } const updateData = JSON.parse('{}') as UTSJSONObject updateData.set('service_started_at', nowIso()) return await updateOrderStatus(orderId, 'in_service', updateData, '开始服务') } export async function saveServiceRecord(orderId: string, record: DeliveryServiceRecordType): Promise { let assignmentId = '' const assignmentResponse = await supa.from('hss_service_assignments').select('*').eq('order_id', orderId).order('created_at', { ascending: false }).execute() if (assignmentResponse.data != null) { const assignments = assignmentResponse.data as any[] if (assignments.length > 0) { assignmentId = JSON.parse(JSON.stringify(assignments[0]))['id'] as string } } await supa.from('hss_service_execution_records').upsert({ id: record.id, order_id: orderId, assignment_id: assignmentId, service_started_at: record.startTime, service_finished_at: record.endTime, actual_duration_minutes: record.actualDurationMinutes, service_items_json: record.serviceItems as any, summary: record.processNote, remark: record.staffRemark, updated_at: nowIso(), created_at: record.createdAt }).execute() for (let i = 0; i < record.photos.length; i++) { await supa.from('hss_service_evidence_files').insert({ id: buildId('sef'), order_id: orderId, execution_record_id: record.id, phase: 'service', file_type: 'image', storage_path: record.photos[i], file_url: record.photos[i], captured_at: nowIso(), created_at: nowIso() }).execute() } return await getOrderDetail(orderId) } export async function finishOrder(orderId: string): Promise { const updateData = JSON.parse('{}') as UTSJSONObject updateData.set('completed_at', nowIso()) updateData.set('pending_acceptance_at', nowIso()) return await updateOrderStatus(orderId, 'pending_acceptance', updateData, '服务完成,等待用户验收') }