diff --git a/pages.delivery.json b/pages.delivery.json index 7d0d4a37..22447dbf 100644 --- a/pages.delivery.json +++ b/pages.delivery.json @@ -42,6 +42,20 @@ "navigationStyle": "custom" } }, + { + "path": "pages/mall/delivery/orders/route", + "style": { + "navigationBarTitleText": "出发与导航", + "navigationStyle": "custom" + } + }, + { + "path": "pages/mall/delivery/orders/checkin", + "style": { + "navigationBarTitleText": "到岗签到", + "navigationStyle": "custom" + } + }, { "path": "pages/mall/delivery/service-record/index", "style": { diff --git a/pages/mall/delivery/home/index.uvue b/pages/mall/delivery/home/index.uvue index ed538c1f..08309c5d 100644 --- a/pages/mall/delivery/home/index.uvue +++ b/pages/mall/delivery/home/index.uvue @@ -39,8 +39,8 @@ 服务中 - {{ incomeText }} - 预计收入 + {{ dashboard.completedCount }} + 已完成 @@ -89,7 +89,7 @@ import { computed, ref } from 'vue' import { onShow } from '@dcloudio/uni-app' import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue' import type { DeliveryInfoType } from '@/types/delivery.uts' -import { acceptServiceOrder, getDeliveryDashboardStats, getDeliveryProfile, markDeparted } from '@/services/deliveryService.uts' +import { acceptServiceOrder, getDeliveryDashboardStats, getDeliveryProfile } from '@/services/deliveryService.uts' import { getNextStepText, getPrimaryActionText } from '@/utils/deliveryCareUi.uts' import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts' @@ -117,7 +117,6 @@ const onlineText = computed((): string => { if (profile.value.onlineStatus == 'busy') return '忙碌' return '离线' }) -const incomeText = computed((): string => '¥' + String(dashboard.value.expectedIncome)) const nextActionText = computed((): string => dashboard.value.nextOrder == null ? '查看详情' : getPrimaryActionText(dashboard.value.nextOrder!.status)) const nextStepText = computed((): string => dashboard.value.nextOrder == null ? '暂无' : getNextStepText(dashboard.value.nextOrder!.status)) const nextStatusText = computed((): string => dashboard.value.nextOrder == null ? '' : dashboard.value.nextOrder!.statusText) @@ -172,9 +171,7 @@ async function handleOrderAction(orderId: string) { return } if (order.status == 'accepted' || order.status == 'waiting_departure') { - await markDeparted(orderId) - uni.showToast({ title: '已标记出发', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + orderId }) return } goDetail(orderId) diff --git a/pages/mall/delivery/orders/checkin.uvue b/pages/mall/delivery/orders/checkin.uvue index 2cae6157..8f16959a 100644 --- a/pages/mall/delivery/orders/checkin.uvue +++ b/pages/mall/delivery/orders/checkin.uvue @@ -127,7 +127,7 @@ async function submitCheckin() { checkinMode: 'gps' }) uni.showToast({ title: '签到成功', icon: 'success' }) - uni.navigateTo({ url: '/pages/mall/delivery/orders/execute?id=' + orderId.value }) + uni.redirectTo({ url: '/pages/mall/delivery/service-record/index?id=' + orderId.value }) } finally { submitting.value = false } diff --git a/pages/mall/delivery/orders/detail.uvue b/pages/mall/delivery/orders/detail.uvue index 9e0d39e1..b8bd1c83 100644 --- a/pages/mall/delivery/orders/detail.uvue +++ b/pages/mall/delivery/orders/detail.uvue @@ -92,15 +92,10 @@ import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uv import type { DeliveryOrderType } from '@/types/delivery.uts' import { acceptServiceOrder, - checkInServiceOrder, - completeServiceOrder, getServiceOrderDetail, - markArrived, - markDeparted, - rejectServiceOrder, - startServiceOrder + rejectServiceOrder } from '@/services/deliveryService.uts' -import { getNextStepText, getPrimaryActionText, needsServiceRecord } from '@/utils/deliveryCareUi.uts' +import { getNextStepText, getPrimaryActionText } from '@/utils/deliveryCareUi.uts' import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts' import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts' @@ -129,7 +124,10 @@ function makePhoneCall(phone: string) { } function mockNavigate() { - uni.showToast({ title: '导航为 mock 占位', icon: 'none' }) + if (order.value == null) { + return + } + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + order.value!.id }) } function goException() { @@ -164,27 +162,15 @@ async function handlePrimary() { return } if (status == 'accepted' || status == 'waiting_departure') { - await markDeparted(orderId.value) - uni.showToast({ title: '已出发', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + orderId.value }) return } if (status == 'departed' || status == 'on_the_way') { - await markArrived(orderId.value) - uni.showToast({ title: '已到达', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + orderId.value }) return } if (status == 'arrived') { - await checkInServiceOrder(orderId.value, '详情页签到', null) - uni.showToast({ title: '签到成功', icon: 'success' }) - loadData() - return - } - if (status == 'checked_in') { - await startServiceOrder(orderId.value) - uni.showToast({ title: '开始服务', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/checkin?id=' + orderId.value }) return } if (status == 'in_service' || status == 'serving' || status == 'completed') { @@ -192,14 +178,7 @@ async function handlePrimary() { return } if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') { - if (needsServiceRecord(order.value)) { - uni.showToast({ title: '请先填写服务记录', icon: 'none' }) - goRecord() - return - } - await completeServiceOrder(orderId.value) - uni.showToast({ title: '服务已完成', icon: 'success' }) - loadData() + uni.showToast({ title: '已完成服务,等待用户验收', icon: 'none' }) return } if (status == 'abnormal' || status == 'exception_pending') { diff --git a/pages/mall/delivery/orders/index.uvue b/pages/mall/delivery/orders/index.uvue index e19ede4c..28502a78 100644 --- a/pages/mall/delivery/orders/index.uvue +++ b/pages/mall/delivery/orders/index.uvue @@ -43,15 +43,10 @@ import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uv import type { DeliveryOrderStatus, DeliveryOrderType } from '@/types/delivery.uts' import { acceptServiceOrder, - checkInServiceOrder, - completeServiceOrder, getHistoryServiceOrders, getPendingServiceOrders, getTodayServiceOrders, - markArrived, - markDeparted, - rejectServiceOrder, - startServiceOrder + rejectServiceOrder } from '@/services/deliveryService.uts' import { getDeliveryOrderTabs, getPrimaryActionText } from '@/utils/deliveryCareUi.uts' import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts' @@ -137,27 +132,15 @@ async function handleAction(orderId: string, status: DeliveryOrderStatus) { return } if (status == 'accepted' || status == 'waiting_departure') { - await markDeparted(orderId) - uni.showToast({ title: '已标记出发', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + orderId }) return } if (status == 'departed' || status == 'on_the_way') { - await markArrived(orderId) - uni.showToast({ title: '已标记到达', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/route?id=' + orderId }) return } if (status == 'arrived') { - await checkInServiceOrder(orderId, '已到达并签到', null) - uni.showToast({ title: '签到成功', icon: 'success' }) - loadData() - return - } - if (status == 'checked_in') { - await startServiceOrder(orderId) - uni.showToast({ title: '已开始服务', icon: 'success' }) - loadData() + uni.navigateTo({ url: '/pages/mall/delivery/orders/checkin?id=' + orderId }) return } if (status == 'in_service' || status == 'serving' || status == 'completed') { @@ -165,13 +148,7 @@ async function handleAction(orderId: string, status: DeliveryOrderStatus) { return } if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') { - const result = await completeServiceOrder(orderId) - if (result == null) { - uni.showToast({ title: '请先填写服务记录', icon: 'none' }) - return - } - uni.showToast({ title: '服务已完成', icon: 'success' }) - loadData() + uni.showToast({ title: '已完成服务,等待用户验收', icon: 'none' }) return } goDetail(orderId) diff --git a/pages/mall/delivery/service-record/index.uvue b/pages/mall/delivery/service-record/index.uvue index 9aecc250..6738f1f7 100644 --- a/pages/mall/delivery/service-record/index.uvue +++ b/pages/mall/delivery/service-record/index.uvue @@ -60,7 +60,7 @@ import { computed, ref } from 'vue' import { onLoad } from '@dcloudio/uni-app' import ServicePageScaffold from '@/components/homeService/ServicePageScaffold.uvue' import type { DeliveryOrderType, DeliveryServiceItemType, DeliveryServiceRecordType } from '@/types/delivery.uts' -import { getServiceOrderDetail, submitServiceRecord } from '@/services/deliveryService.uts' +import { completeServiceOrder, getServiceOrderDetail, startServiceOrder, submitServiceRecord } from '@/services/deliveryService.uts' import { requireDeliveryAuth } from '@/utils/deliveryAuth.uts' import { getDeliveryRouteParam } from '@/utils/deliveryRoute.uts' @@ -145,6 +145,9 @@ async function submitRecordAction() { if (!validateRecord()) { return } + if (order.value != null && order.value.status != 'in_service' && order.value.status != 'serving') { + await startServiceOrder(orderId.value) + } const record = { id: 'record-' + orderId.value, orderId: orderId.value, @@ -176,7 +179,8 @@ async function submitRecordAction() { updatedAt: new Date().toISOString().replace('T', ' ').substring(0, 19) } as DeliveryServiceRecordType await submitServiceRecord(orderId.value, record) - uni.showToast({ title: '服务记录已提交', icon: 'success' }) + await completeServiceOrder(orderId.value) + uni.showToast({ title: '服务记录已提交,等待验收', icon: 'success' }) setTimeout(() => { uni.redirectTo({ url: '/pages/mall/delivery/orders/detail?id=' + orderId.value }) }, 300) diff --git a/services/deliveryService.uts b/services/deliveryService.uts index 1033c8be..f31d1403 100644 --- a/services/deliveryService.uts +++ b/services/deliveryService.uts @@ -1,4 +1,16 @@ import { getDeliveryProfileByUserId, loginDelivery as loginDeliveryApi } from '@/api/delivery.uts' +import { + acceptOrder as acceptRealServiceOrder, + arriveOrder as arriveRealServiceOrder, + checkinOrder as checkinRealServiceOrder, + departOrder as departRealServiceOrder, + finishOrder as finishRealServiceOrder, + getDashboard as getRealDashboard, + getOrderDetail as getRealServiceOrderDetail, + getOrdersByTab as getRealOrdersByTab, + saveServiceRecord as saveRealServiceRecord, + startService as startRealService +} from '@/services/serviceOrderService.uts' import { acceptCareOrder, checkInCareOrder, @@ -47,7 +59,7 @@ export async function getDeliveryProfile(): Promise { if (!authResult.ok) { return null } - return getDeliveryCareProfile() + return authResult.deliveryInfo } export async function getDeliveryByUserId(userId: string): Promise { @@ -72,21 +84,21 @@ export async function getDeliveryDashboard(): Promise { } export async function getDeliveryOrders(params: DeliveryOrderQueryType): Promise> { - if (params.tab == 'pending' || params.tab == 'pending_assignment' || params.tab == 'pending_accept') { - return getPendingCareOrders() + if (params.tab == 'pending' || params.tab == 'pending_assignment') { + return await getRealOrdersByTab('pending') } if (params.tab == 'history' || params.tab == 'completed' || params.tab == 'archive') { - return getHistoryCareOrders() + return await getRealOrdersByTab('history') } - return getTodayCareOrders() + return await getRealOrdersByTab('today') } export async function getDeliveryOrderDetail(id: string): Promise { - return getCareOrderDetail(id) + return await getRealServiceOrderDetail(id) } export async function acceptDeliveryOrder(id: string): Promise { - return acceptCareOrder(id) + return await acceptRealServiceOrder(id) } export async function rejectDeliveryOrder(id: string, reason: string): Promise { @@ -94,27 +106,19 @@ export async function rejectDeliveryOrder(id: string, reason: string): Promise { - const order = markCareOrderDeparted(id) - if (order != null) { - order.lastLocation = location - } - return order + return await departRealServiceOrder(id, location) } export async function arriveOrder(id: string, location: DeliveryLocationType): Promise { - const order = markCareOrderArrived(id) - if (order != null) { - order.lastLocation = location - } - return order + return await arriveRealServiceOrder(id, location) } export async function checkinOrder(id: string, payload: DeliveryCheckinPayloadType): Promise { - return checkInCareOrder(id, payload.location, payload.note) + return await checkinRealServiceOrder(id, payload) } export async function startService(id: string): Promise { - return startCareService(id) + return await startRealService(id) } export async function saveServiceProgress(id: string, payload: DeliveryProgressPayloadType): Promise { @@ -194,27 +198,27 @@ export async function updateDeliveryOnlineStatus(status: string): Promise { - return getDeliveryCareDashboard() + return await getRealDashboard() } export async function getPendingServiceOrders(): Promise> { - return getPendingCareOrders() + return await getRealOrdersByTab('pending') } export async function getTodayServiceOrders(): Promise> { - return getTodayCareOrders() + return await getRealOrdersByTab('today') } export async function getHistoryServiceOrders(): Promise> { - return getHistoryCareOrders() + return await getRealOrdersByTab('history') } export async function getServiceOrderDetail(orderId: string): Promise { - return getCareOrderDetail(orderId) + return await getRealServiceOrderDetail(orderId) } export async function acceptServiceOrder(orderId: string): Promise { - return acceptCareOrder(orderId) + return await acceptRealServiceOrder(orderId) } export async function rejectServiceOrder(orderId: string, reason: string): Promise { @@ -222,27 +226,35 @@ export async function rejectServiceOrder(orderId: string, reason: string): Promi } export async function markDeparted(orderId: string): Promise { - return markCareOrderDeparted(orderId) + return await departRealServiceOrder(orderId, null) } export async function markArrived(orderId: string): Promise { - return markCareOrderArrived(orderId) + return await arriveRealServiceOrder(orderId, null) } export async function checkInServiceOrder(orderId: string, note: string, location: DeliveryLocationType | null = null): Promise { - return checkInCareOrder(orderId, location, note) + if (location == null) { + return null + } + return await checkinRealServiceOrder(orderId, { + location, + note, + photos: [] as Array, + checkinMode: 'gps' + }) } export async function startServiceOrder(orderId: string): Promise { - return startCareService(orderId) + return await startRealService(orderId) } export async function submitServiceRecord(orderId: string, record: DeliveryServiceRecordType): Promise { - return submitCareServiceRecord(orderId, record) + return await saveRealServiceRecord(orderId, record) } export async function completeServiceOrder(orderId: string): Promise { - return completeCareOrder(orderId) + return await finishRealServiceOrder(orderId) } export async function submitAbnormalReport(orderId: string, report: DeliveryExceptionPayloadType): Promise { diff --git a/services/serviceOrderService.uts b/services/serviceOrderService.uts new file mode 100644 index 00000000..c2193636 --- /dev/null +++ b/services/serviceOrderService.uts @@ -0,0 +1,500 @@ +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, '服务完成,等待用户验收') + } \ No newline at end of file diff --git a/types/service-order.uts b/types/service-order.uts new file mode 100644 index 00000000..0d0923d7 --- /dev/null +++ b/types/service-order.uts @@ -0,0 +1,81 @@ +export type ServiceOrderStatus = + 'created' | + 'paid' | + 'assigned' | + 'accepted' | + 'rejected' | + 'departed' | + 'arrived' | + 'in_service' | + 'completed' | + 'pending_acceptance' | + 'accepted_by_user' | + 'reviewed' | + 'settled' | + 'cancelled' | + 'exception' + +export const SERVICE_ORDER_STATUS_LIST: Array = [ + 'created', + 'paid', + 'assigned', + 'accepted', + 'rejected', + 'departed', + 'arrived', + 'in_service', + 'completed', + 'pending_acceptance', + 'accepted_by_user', + 'reviewed', + 'settled', + 'cancelled', + 'exception' +] + +export type ServiceOrderTimelineItemType = { + id: string + orderId: string + fromStatus: string + toStatus: ServiceOrderStatus + operatorId: string + operatorRole: string + remark: string + createdAt: string +} + +export function getServiceOrderStatusText(status: ServiceOrderStatus): string { + if (status == 'created') return '待处理' + if (status == 'paid') return '已支付' + if (status == 'assigned') return '已派单' + if (status == 'accepted') return '已接单' + if (status == 'rejected') return '已拒单' + if (status == 'departed') return '已出发' + if (status == 'arrived') return '已到达' + if (status == 'in_service') return '服务中' + if (status == 'completed') return '已完成' + if (status == 'pending_acceptance') return '待验收' + if (status == 'accepted_by_user') return '已验收' + if (status == 'reviewed') return '已评价' + if (status == 'settled') return '已结算' + if (status == 'cancelled') return '已取消' + return '异常' +} + +export function normalizeServiceOrderStatus(status: string): ServiceOrderStatus { + if (status == 'created' || status == 'submitted') return 'created' + if (status == 'paid') return 'paid' + if (status == 'assigned' || status == 'pending_dispatch' || status == 'pending_assignment') return 'assigned' + if (status == 'accepted' || status == 'pending_accept') return 'accepted' + if (status == 'rejected') return 'rejected' + if (status == 'departed' || status == 'waiting_departure' || status == 'on_the_way') return 'departed' + if (status == 'arrived' || status == 'checked_in') return 'arrived' + if (status == 'in_service' || status == 'serving') return 'in_service' + if (status == 'completed') return 'completed' + if (status == 'pending_acceptance' || status == 'pending_confirm' || status == 'pending_submit') return 'pending_acceptance' + if (status == 'accepted_by_user') return 'accepted_by_user' + if (status == 'reviewed') return 'reviewed' + if (status == 'settled') return 'settled' + if (status == 'cancelled') return 'cancelled' + return 'exception' +} \ No newline at end of file diff --git a/utils/deliveryCareUi.uts b/utils/deliveryCareUi.uts index e1e01b5f..405e6d85 100644 --- a/utils/deliveryCareUi.uts +++ b/utils/deliveryCareUi.uts @@ -14,16 +14,14 @@ export function getDeliveryOrderTabs(): Array { } export function isHistoryStatus(status: DeliveryOrderStatus): boolean { - return status == 'completed' || status == 'rejected' || status == 'cancelled' || status == 'abnormal' || status == 'terminated' || status == 'archived' + return status == 'completed' || status == 'pending_acceptance' || status == 'rejected' || status == 'cancelled' || status == 'abnormal' || status == 'terminated' || status == 'archived' } export function getDeliveryStatusLabel(status: DeliveryOrderStatus): string { if (status == 'pending_assignment' || status == 'pending_accept') return '待接单' if (status == 'accepted') return '已接单' - if (status == 'waiting_departure') return '待出发' if (status == 'departed' || status == 'on_the_way') return '已出发' if (status == 'arrived') return '已到达' - if (status == 'checked_in') return '已签到' if (status == 'in_service' || status == 'serving') return '服务中' if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') return '待确认' if (status == 'completed') return '已完成' @@ -36,12 +34,11 @@ export function getDeliveryStatusLabel(status: DeliveryOrderStatus): string { export function getPrimaryActionText(status: DeliveryOrderStatus): string { if (status == 'pending_assignment' || status == 'pending_accept') return '接单' - if (status == 'accepted' || status == 'waiting_departure') return '我已出发' + if (status == 'accepted' || status == 'waiting_departure') return '前往服务' if (status == 'departed' || status == 'on_the_way') return '我已到达' if (status == 'arrived') return '签到' - if (status == 'checked_in') return '开始服务' if (status == 'in_service' || status == 'serving') return '填写记录' - if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') return '完成服务' + if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') return '等待验收' if (status == 'completed') return '查看记录' if (status == 'abnormal' || status == 'exception_pending') return '查看异常' return '查看详情' @@ -49,12 +46,11 @@ export function getPrimaryActionText(status: DeliveryOrderStatus): string { export function getNextStepText(status: DeliveryOrderStatus): string { if (status == 'pending_assignment' || status == 'pending_accept') return '确认是否接单' - if (status == 'accepted' || status == 'waiting_departure') return '前往服务对象地址' + if (status == 'accepted' || status == 'waiting_departure') return '前往服务对象地址并发起导航' if (status == 'departed' || status == 'on_the_way') return '到达服务地点' if (status == 'arrived') return '完成签到确认' - if (status == 'checked_in') return '开始本次上门服务' - if (status == 'in_service' || status == 'serving') return '补充服务记录' - if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') return '确认并完成订单' + if (status == 'in_service' || status == 'serving') return '补充服务记录并完成服务' + if (status == 'pending_confirm' || status == 'pending_acceptance' || status == 'pending_submit') return '等待用户确认验收' if (status == 'completed') return '查看历史记录' if (status == 'abnormal' || status == 'exception_pending') return '跟进异常处置' return '查看订单详情'