Files
medical-mall/services/homeServiceService.uts

1281 lines
43 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
HomeServiceAcceptanceType,
HomeServiceAdminApplicationType,
HomeServiceAssessmentType,
HomeServiceApplicationDraftType,
HomeServiceCatalogType,
HomeServiceCaseType,
HomeServiceOverviewCardType,
HomeServicePlanType,
HomeServiceRectificationType,
HomeServiceSettlementType,
HomeServiceTaskType,
HomeServiceTimelineItemType
} from '@/types/home-service.uts'
import {
confirmServiceOrder,
createServiceOrder,
getServiceOrderDetail,
listConsumerServiceOrders,
rejectServiceOrderAcceptance
} from '@/services/serviceOrderService.uts'
import supa from '@/components/supadb/aksupainstance.uts'
import { getCurrentUserId } from '@/utils/store.uts'
import {
getServiceOrderStatusText,
type ServiceOrderStatus,
type ServiceOrderTimelineItemType,
type ServiceOrderType
} from '@/types/service-order.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')
}
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
}
const parsed = Number(value)
return isNaN(parsed) ? 0 : parsed
}
function readStringArray(source: any, key: string): Array<string> {
const value = plainObject(source)[key]
if (Array.isArray(value)) {
const result = [] as Array<string>
for (let i = 0; i < value.length; i++) {
result.push(typeof value[i] == 'string' ? value[i] : String(value[i]))
}
return result
}
if (typeof value == 'string' && value != '') {
try {
const parsed = JSON.parse(value) as any
if (Array.isArray(parsed)) {
const result = [] as Array<string>
for (let i = 0; i < parsed.length; i++) {
result.push(typeof parsed[i] == 'string' ? parsed[i] : String(parsed[i]))
}
return result
}
} catch (error) {
return [] as Array<string>
}
}
return [] as Array<string>
}
function parseCatalogItem(source: any): HomeServiceCatalogType {
return {
id: readString(source, 'id'),
name: readString(source, 'name'),
category: readString(source, 'category'),
price: readNumber(source, 'price'),
durationText: readString(source, 'duration_text'),
summary: readString(source, 'summary'),
tags: readStringArray(source, 'tags_json'),
suitableFor: readString(source, 'suitable_for')
}
}
function createTimeline(title1: string, title2: string, title3: string): Array<HomeServiceTimelineItemType> {
return [
{
id: 'tl-1',
title: title1,
time: '2026-05-12 09:00',
description: '系统已建立服务申请档案。'
},
{
id: 'tl-2',
title: title2,
time: '2026-05-12 11:30',
description: '评估员已完成初步核对与上门安排。'
},
{
id: 'tl-3',
title: title3,
time: '2026-05-13 08:20',
description: '当前环节已进入待执行状态。'
}
]
}
const CASE_STORE: Array<HomeServiceCaseType> = [
{
id: 'case-001',
caseNo: 'HS202605130001',
status: 'pending_dispatch',
statusText: '待派单',
statusTone: 'warning',
serviceName: '基础上门护理',
serviceTime: '2026-05-14 09:00-11:00',
applicantName: '李晓兰',
elderName: '李奶奶',
age: 78,
phone: '13800138000',
address: '梅州市梅江区学海路 18 号 2 栋 602',
summary: '老人近期行动困难,需要基础照护与生命体征监测。',
currentStep: 3,
totalSteps: 8,
staffName: '待分配',
staffPhone: '待分配',
amount: 168,
timeline: createTimeline('已提交申请', '待上门评估', '待派单')
},
{
id: 'case-002',
caseNo: 'HS202605130002',
status: 'in_service',
statusText: '服务中',
statusTone: 'primary',
serviceName: '慢病健康随访',
serviceTime: '2026-05-13 15:00-16:30',
applicantName: '张春梅',
elderName: '张爷爷',
age: 82,
phone: '13900139000',
address: '梅州市梅县区华侨城康宁路 66 号',
summary: '随访血压血糖,核对用药并记录健康建议。',
currentStep: 6,
totalSteps: 8,
staffName: '陈护理',
staffPhone: '13688886666',
amount: 128,
timeline: createTimeline('已提交申请', '已生成服务方案', '服务执行中')
}
]
const TASK_STORE: Array<HomeServiceTaskType> = [
{
id: 'task-001',
caseId: 'case-001',
caseNo: 'HS202605130001',
status: 'pending_visit',
statusText: '待上门',
statusTone: 'warning',
serviceName: '基础上门护理',
elderName: '李奶奶',
address: '梅州市梅江区学海路 18 号 2 栋 602',
appointmentTime: '2026-05-14 09:00',
checkInStatus: '未签到',
recordSummary: '待填写服务记录',
staffName: '黄护理',
staffPhone: '13777770001',
actionText: '签到开始',
timeline: createTimeline('调度已派单', '护理员已接单', '等待上门签到')
},
{
id: 'task-002',
caseId: 'case-002',
caseNo: 'HS202605130002',
status: 'serving',
statusText: '服务中',
statusTone: 'primary',
serviceName: '慢病健康随访',
elderName: '张爷爷',
address: '梅州市梅县区华侨城康宁路 66 号',
appointmentTime: '2026-05-13 15:00',
checkInStatus: '已签到',
recordSummary: '已完成血压血糖记录,待补充宣教备注。',
staffName: '陈护理',
staffPhone: '13688886666',
actionText: '完成提交',
timeline: createTimeline('调度已派单', '已到岗签到', '服务执行中')
}
]
const ADMIN_APPLICATIONS: Array<HomeServiceAdminApplicationType> = [
{
id: 'admin-app-001',
caseId: 'case-001',
caseNo: 'HS202605130001',
status: 'pending_assessment',
statusText: '待评估',
statusTone: 'warning',
elderName: '李奶奶',
serviceName: '基础上门护理',
preferredTime: '2026-05-14 上午',
assessmentResult: '待评估员上门',
dispatcherName: '刘调度',
staffName: '待分配'
},
{
id: 'admin-app-002',
caseId: 'case-002',
caseNo: 'HS202605130002',
status: 'pending_acceptance',
statusText: '待验收',
statusTone: 'success',
elderName: '张爷爷',
serviceName: '慢病健康随访',
preferredTime: '2026-05-13 下午',
assessmentResult: '评估通过,按标准随访执行',
dispatcherName: '刘调度',
staffName: '陈护理'
}
]
const ADMIN_OVERVIEW: Array<HomeServiceOverviewCardType> = [
{ id: 'overview-1', label: '待评估申请', value: '06', tone: 'warning' },
{ id: 'overview-2', label: '待派单工单', value: '03', tone: 'primary' },
{ id: 'overview-3', label: '服务中任务', value: '08', tone: 'success' },
{ id: 'overview-4', label: '待验收任务', value: '04', tone: 'neutral' }
]
const ADMIN_ASSESSMENTS: Array<HomeServiceAssessmentType> = [
{
caseId: 'case-001',
caseNo: 'HS202605130001',
elderName: '李奶奶',
serviceName: '基础上门护理',
riskLevel: '中风险',
careLevel: '护理二级',
visitTime: '2026-05-14 09:00',
assessmentSummary: '行动缓慢,需重点关注跌倒风险和晨间血压波动。',
requirementTags: ['血压监测', '基础照护', '跌倒风险提醒']
},
{
caseId: 'case-002',
caseNo: 'HS202605130002',
elderName: '张爷爷',
serviceName: '慢病健康随访',
riskLevel: '低风险',
careLevel: '随访管理',
visitTime: '2026-05-13 15:00',
assessmentSummary: '生命体征稳定,重点跟踪慢病用药执行情况。',
requirementTags: ['血糖记录', '用药核对', '家属宣教']
}
]
const ADMIN_PLANS: Array<HomeServicePlanType> = [
{
caseId: 'case-001',
caseNo: 'HS202605130001',
elderName: '李奶奶',
serviceName: '基础上门护理',
planTitle: '基础护理 7 日方案',
serviceFrequency: '每周 3 次',
serviceCycle: '2026-05-14 至 2026-05-21',
executorAdvice: '优先安排熟悉慢病照护的护理员,首次上门同步评估跌倒风险。',
billingSummary: '基础护理 ¥168/次,计划总额 ¥504',
planSummary: '围绕晨间照护、生命体征监测和风险提醒开展服务。'
},
{
caseId: 'case-002',
caseNo: 'HS202605130002',
elderName: '张爷爷',
serviceName: '慢病健康随访',
planTitle: '慢病随访连续方案',
serviceFrequency: '每周 2 次',
serviceCycle: '2026-05-13 至 2026-05-27',
executorAdvice: '执行人员需同步上传血压血糖记录并完成家属宣教。',
billingSummary: '随访服务 ¥128/次,计划总额 ¥512',
planSummary: '持续随访血压血糖、用药和饮食管理情况。'
}
]
const ADMIN_RECTIFICATIONS: Array<HomeServiceRectificationType> = [
{
caseId: 'case-001',
caseNo: 'HS202605130001',
elderName: '李奶奶',
serviceName: '基础上门护理',
issueSummary: '家属要求补充护理动作说明和现场照片留痕。',
deadline: '2026-05-14 18:00',
ownerName: '黄护理',
status: 'pending',
statusText: '待整改'
},
{
caseId: 'case-002',
caseNo: 'HS202605130002',
elderName: '张爷爷',
serviceName: '慢病健康随访',
issueSummary: '记录完整,无需整改。',
deadline: '2026-05-15 12:00',
ownerName: '陈护理',
status: 'closed',
statusText: '已关闭'
}
]
const ADMIN_SETTLEMENTS: Array<HomeServiceSettlementType> = [
{
caseId: 'case-001',
caseNo: 'HS202605130001',
elderName: '李奶奶',
serviceName: '基础上门护理',
billingPeriod: '2026-05-14 至 2026-05-21',
totalAmount: '¥504',
insuranceAmount: '¥300',
selfPayAmount: '¥204',
archiveStatus: 'pending',
archiveStatusText: '待归档'
},
{
caseId: 'case-002',
caseNo: 'HS202605130002',
elderName: '张爷爷',
serviceName: '慢病健康随访',
billingPeriod: '2026-05-13 至 2026-05-27',
totalAmount: '¥512',
insuranceAmount: '¥256',
selfPayAmount: '¥256',
archiveStatus: 'pending',
archiveStatusText: '待归档'
}
]
function delay(): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, 120)
})
}
function cloneCase(item: HomeServiceCaseType): HomeServiceCaseType {
return {
...item,
timeline: item.timeline.map((log) => ({ ...log }))
}
}
function cloneTask(item: HomeServiceTaskType): HomeServiceTaskType {
return {
...item,
timeline: item.timeline.map((log) => ({ ...log }))
}
}
function cloneAssessment(item: HomeServiceAssessmentType): HomeServiceAssessmentType {
return {
...item,
requirementTags: item.requirementTags.slice(0)
}
}
function clonePlan(item: HomeServicePlanType): HomeServicePlanType {
return {
...item
}
}
function cloneRectification(item: HomeServiceRectificationType): HomeServiceRectificationType {
return {
...item
}
}
function cloneSettlement(item: HomeServiceSettlementType): HomeServiceSettlementType {
return {
...item
}
}
export async function fetchHomeServiceCatalog(): Promise<Array<HomeServiceCatalogType>> {
const response = await supa
.from('hss_service_catalog')
.select('id, name, category, price, duration_text, summary, tags_json, suitable_for, sort_no')
.eq('status', 1)
.is('deleted_at', null)
.order('sort_no', { ascending: true })
.execute()
if (response.error != null || response.data == null || !Array.isArray(response.data)) {
return [] as Array<HomeServiceCatalogType>
}
const result = [] as Array<HomeServiceCatalogType>
for (let i = 0; i < response.data.length; i++) {
result.push(parseCatalogItem(response.data[i]))
}
return result
}
export async function fetchConsumerHomeServiceCases(): Promise<Array<HomeServiceCaseType>> {
const orders = await listConsumerServiceOrders()
const result = [] as Array<HomeServiceCaseType>
for (let i = 0; i < orders.length; i++) {
result.push(mapOrderToCase(orders[i]))
}
return result
}
export async function fetchConsumerHomeServiceCaseDetail(caseId: string): Promise<HomeServiceCaseType | null> {
const detail = await getServiceOrderDetail(caseId)
if (detail != null) {
return mapOrderToCase(detail)
}
return null
}
export async function createHomeServiceApplication(draft: HomeServiceApplicationDraftType): Promise<HomeServiceCaseType | null> {
const catalog = await fetchHomeServiceCatalog()
let matchedService: HomeServiceCatalogType | null = null
for (let i = 0; i < catalog.length; i++) {
if (catalog[i].id == draft.serviceId) {
matchedService = catalog[i]
break
}
}
if (matchedService != null && draft.serviceAddressSnapshot != null) {
const createdOrder = await createServiceOrder({
service: matchedService,
address: {
addressId: draft.serviceAddressSnapshot.addressId,
contactName: draft.serviceAddressSnapshot.contactName,
contactPhone: draft.serviceAddressSnapshot.contactPhone,
province: '',
city: '',
district: '',
detailAddress: draft.serviceAddressSnapshot.addressDetail,
fullAddress: draft.serviceAddressSnapshot.fullAddress,
latitude: draft.serviceAddressSnapshot.latitude,
longitude: draft.serviceAddressSnapshot.longitude,
coordinateType: draft.serviceAddressSnapshot.coordinateType,
remark: draft.serviceAddressSnapshot.remark
},
recipientName: draft.elderName,
recipientPhone: draft.phone,
contactName: draft.applicantName,
contactPhone: draft.phone,
appointmentTime: draft.preferredTime,
remark: draft.demandSummary
})
if (createdOrder != null) {
return mapOrderToCase(createdOrder)
}
}
return null
}
function getCaseStep(status: ServiceOrderStatus): number {
if (status == 'created') return 1
if (status == 'assigned') return 3
if (status == 'accepted') return 4
if (status == 'departed') return 5
if (status == 'arrived' || status == 'in_service') return 6
if (status == 'pending_acceptance') return 7
if (status == 'accepted_by_user' || status == 'reviewed' || status == 'settled') return 8
if (status == 'rejected' || status == 'cancelled' || status == 'exception') return 7
return 2
}
function getCaseTone(status: ServiceOrderStatus): string {
if (status == 'created' || status == 'assigned' || status == 'pending_acceptance') 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 == 'exception' || status == 'cancelled' || status == 'rejected') return 'danger'
return 'neutral'
}
function getTimelineDescription(log: ServiceOrderTimelineItemType): string {
if (log.remark != '') {
return log.remark
}
if (log.toStatus == 'created') return '平台已接收预约申请,等待后续处理。'
if (log.toStatus == 'assigned') return '系统已完成派单,正在通知服务人员。'
if (log.toStatus == 'accepted') return '服务人员已接单,正在准备上门。'
if (log.toStatus == 'departed') return '服务人员已出发,正在前往服务地点。'
if (log.toStatus == 'arrived') return '服务人员已到达服务地点。'
if (log.toStatus == 'in_service') return '服务已开始执行,请留意后续进度。'
if (log.toStatus == 'completed') return '服务执行已完成,正在整理结果。'
if (log.toStatus == 'pending_acceptance') return '服务记录已提交,等待家属验收。'
if (log.toStatus == 'accepted_by_user') return '家属已确认本次服务结果。'
if (log.toStatus == 'reviewed') return '家属已完成评价反馈。'
if (log.toStatus == 'settled') return '本次服务已完成结算归档。'
if (log.toStatus == 'cancelled') return '服务单已取消。'
if (log.toStatus == 'rejected') return '服务人员未接受该工单。'
return '服务过程状态已更新。'
}
function formatServiceAppointmentText(value: string): string {
if (value == '') {
return ''
}
if (value.indexOf('上午') >= 0 || value.indexOf('下午') >= 0 || value.indexOf('晚上') >= 0) {
return value
}
const parsed = Date.parse(value)
if (!isNaN(parsed)) {
const date = new Date(parsed)
let year = date.getFullYear()
const currentYear = new Date().getFullYear()
if (year < currentYear - 1) {
year = currentYear
}
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 monthDayMatch = value.match(/(\d{2})\/(\d{2})/)
const timeMatch = value.match(/(\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?)/)
if (monthDayMatch != null) {
const month = monthDayMatch[1] ?? ''
const day = monthDayMatch[2] ?? ''
const timeText = timeMatch != null ? (timeMatch[1] ?? '') : ''
if (month != '' && day != '') {
return String(new Date().getFullYear()) + '-' + month + '-' + day + (timeText != '' ? ' ' + timeText.replace(/\s+/g, '') : '')
}
}
return value.replace('T', ' ')
}
function mapLogsToTimeline(logs: Array<ServiceOrderTimelineItemType>): Array<HomeServiceTimelineItemType> {
const timeline = [] as Array<HomeServiceTimelineItemType>
for (let i = 0; i < logs.length; i++) {
timeline.push({
id: logs[i].id,
title: getServiceOrderStatusText(logs[i].toStatus),
time: logs[i].createdAt,
description: getTimelineDescription(logs[i])
})
}
return timeline
}
function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
return {
id: order.id,
caseNo: order.orderNo,
status: order.status,
statusText: getServiceOrderStatusText(order.status),
statusTone: getCaseTone(order.status),
serviceName: order.serviceName,
serviceTime: formatServiceAppointmentText(order.appointmentTime),
applicantName: order.contactName,
elderName: order.recipientName,
age: 0,
phone: order.contactPhone,
address: order.addressSnapshot.fullAddress,
summary: order.remark,
currentStep: getCaseStep(order.status),
totalSteps: 8,
staffName: order.staffName == '' ? '待分配' : order.staffName,
staffPhone: order.staffPhone == '' ? '待分配' : order.staffPhone,
amount: order.serviceSnapshot.price,
checkinTime: order.executionRecord != null ? order.executionRecord.checkinTime : '',
checkinAddress: order.executionRecord != null ? order.executionRecord.checkinAddress : '',
serviceStartedAt: order.executionRecord != null ? order.executionRecord.serviceStartedAt : order.serviceStartedAt,
serviceFinishedAt: order.executionRecord != null ? order.executionRecord.serviceFinishedAt : order.completedAt,
executionSummary: order.executionRecord != null ? (order.executionRecord.summary != '' ? order.executionRecord.summary : order.executionRecord.remark) : '',
evidenceCount: order.evidenceFiles.length,
serviceAddressSnapshot: null,
timeline: mapLogsToTimeline(order.logs)
}
}
function mapOrderToWorkerTask(order: ServiceOrderType): HomeServiceTaskType {
let taskStatus = 'pending_visit'
let taskStatusText = '待上门'
let taskStatusTone = 'warning'
let actionText = '签到开始'
if (order.status == 'arrived' || order.status == 'in_service') {
taskStatus = 'serving'
taskStatusText = '服务中'
taskStatusTone = 'primary'
actionText = '完成提交'
} else if (order.status == 'pending_acceptance' || order.status == 'accepted_by_user' || order.status == 'reviewed' || order.status == 'settled') {
taskStatus = 'completed'
taskStatusText = '待验收'
taskStatusTone = 'success'
actionText = '已提交'
} else if (order.status == 'exception' || order.status == 'rejected' || order.status == 'cancelled') {
taskStatus = 'exception'
taskStatusText = getServiceOrderStatusText(order.status)
taskStatusTone = 'warning'
actionText = '已上报'
}
return {
id: order.id,
caseId: order.id,
caseNo: order.orderNo,
status: taskStatus,
statusText: taskStatusText,
statusTone: taskStatusTone,
serviceName: order.serviceName,
elderName: order.recipientName,
address: order.addressSnapshot.fullAddress,
appointmentTime: formatServiceAppointmentText(order.appointmentTime),
checkInStatus: order.executionRecord != null && order.executionRecord.checkinTime != '' ? '已签到' : '未签到',
recordSummary: order.executionRecord != null ? (order.executionRecord.summary != '' ? order.executionRecord.summary : order.executionRecord.remark) : '待填写服务记录',
staffName: order.staffName == '' ? '待分配' : order.staffName,
staffPhone: order.staffPhone == '' ? '待分配' : order.staffPhone,
actionText,
timeline: mapLogsToTimeline(order.logs)
}
}
async function listWorkerTaskIds(): Promise<Array<string>> {
const userId = getCurrentUserId()
if (userId == '') {
return [] as Array<string>
}
const careTaskResponse = await supa.from('ec_care_tasks').select('id').eq('assigned_to', userId).order('created_at', { ascending: false }).execute()
if (careTaskResponse.error == null && careTaskResponse.data != null) {
const result = [] as Array<string>
const rows = careTaskResponse.data as Array<any>
for (let i = 0; i < rows.length; i++) {
const id = readString(rows[i], 'id')
if (id != '') {
result.push(id)
}
}
if (result.length > 0) {
return result
}
}
let staffProfileId = ''
const staffResponse = await supa.from('ml_delivery_staff').select('id').eq('uid', userId).limit(1).execute()
if (staffResponse.error == null && staffResponse.data != null) {
const staffRows = staffResponse.data as Array<any>
if (staffRows.length > 0) {
staffProfileId = readString(staffRows[0], 'id')
}
}
const legacyStaffId = staffProfileId != '' ? staffProfileId : userId
const legacyResponse = await supa.from('hss_service_orders').select('id').eq('current_staff_id', legacyStaffId).order('created_at', { ascending: false }).execute()
if (legacyResponse.error != null || legacyResponse.data == null) {
return [] as Array<string>
}
const legacyIds = [] as Array<string>
const legacyRows = legacyResponse.data as Array<any>
for (let i = 0; i < legacyRows.length; i++) {
const id = readString(legacyRows[i], 'id')
if (id != '') {
legacyIds.push(id)
}
}
return legacyIds
}
async function isCareTask(taskId: string): Promise<boolean> {
const response = await supa.from('ec_care_tasks').select('id').eq('id', taskId).limit(1).execute()
return response.error == null && response.data != null && (response.data as Array<any>).length > 0
}
async function completeWorkerTask(taskId: string): Promise<HomeServiceTaskType | null> {
const completedAt = nowIso()
if (await isCareTask(taskId)) {
await supa.from('ec_care_tasks').update({
status: 'ACCEPTANCE_PENDING',
service_completed_at: completedAt,
acceptance_pending_at: completedAt,
updated_at: completedAt
}).eq('id', taskId).execute()
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: taskId,
from_status: 'ORDER_IN_SERVICE',
to_status: 'ACCEPTANCE_PENDING',
actor_id: getCurrentUserId(),
actor_role: 'merchant',
action: 'finish_service',
remark: '服务记录和凭证已经提交。',
created_at: completedAt
}).execute()
} else {
await supa.from('hss_service_orders').update({
status: 'pending_acceptance',
completed_at: completedAt,
pending_acceptance_at: completedAt,
updated_at: completedAt
}).eq('id', taskId).execute()
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: taskId,
from_status: 'in_service',
to_status: 'pending_acceptance',
operator_id: getCurrentUserId(),
operator_role: 'merchant',
remark: '服务记录和凭证已经提交。',
created_at: completedAt
}).execute()
}
return await fetchWorkerTaskDetail(taskId)
}
const ADMIN_ASSESSMENT_PREFIX = '[admin_assessment]'
const ADMIN_PLAN_PREFIX = '[admin_plan]'
const ADMIN_RECTIFICATION_PREFIX = '[admin_rectification]'
function encodeAdminRemark(prefix: string, payload: any): string {
return prefix + JSON.stringify(payload)
}
function parseAdminRemark(remark: string, prefix: string): any | null {
if (remark == '' || remark.indexOf(prefix) != 0) {
return null
}
const body = remark.substring(prefix.length)
if (body == '') {
return null
}
try {
return JSON.parse(body) as any
} catch (error) {
return null
}
}
function getDefaultCareLevel(order: ServiceOrderType): string {
if (order.serviceName.indexOf('随访') >= 0) {
return '随访管理'
}
if (order.serviceName.indexOf('护理') >= 0) {
return '护理二级'
}
return '护理一级'
}
function getOrderLatestRemark(order: ServiceOrderType): string {
for (let i = 0; i < order.logs.length; i++) {
if (order.logs[i].remark != '') {
return order.logs[i].remark
}
}
return ''
}
async function listAdminEventRows(caseId: string): Promise<Array<any>> {
if (await isCareTask(caseId)) {
const response = await supa.from('hc_work_order_events').select('*').eq('task_id', caseId).order('created_at', { ascending: false }).execute()
return response.data != null ? response.data as Array<any> : [] as Array<any>
}
const response = await supa.from('hss_service_order_status_logs').select('*').eq('order_id', caseId).order('created_at', { ascending: false }).execute()
return response.data != null ? response.data as Array<any> : [] as Array<any>
}
async function readAdminPayload(caseId: string, prefix: string): Promise<any | null> {
const rows = await listAdminEventRows(caseId)
for (let i = 0; i < rows.length; i++) {
const payload = parseAdminRemark(readString(rows[i], 'remark'), prefix)
if (payload != null) {
return payload
}
}
return null
}
async function readLatestExceptionSummary(caseId: string, order: ServiceOrderType): Promise<string> {
if (await isCareTask(caseId)) {
const response = await supa.from('hc_work_order_exceptions').select('description').eq('task_id', caseId).order('created_at', { ascending: false }).limit(1).execute()
if (response.data != null && (response.data as Array<any>).length > 0) {
const rows = response.data as Array<any>
const text = readString(rows[0], 'description')
if (text != '') {
return text
}
}
}
const latestRemark = getOrderLatestRemark(order)
if (latestRemark != '') {
return latestRemark
}
if (order.review != null && order.review.content != '') {
return order.review.content
}
return '待补充整改说明。'
}
function buildAssessmentDetail(order: ServiceOrderType, payload: any | null): HomeServiceAssessmentType {
const requirementTags = payload != null ? readStringArray(payload, 'requirementTags') : [] as Array<string>
return {
caseId: order.id,
caseNo: order.orderNo,
elderName: order.recipientName,
serviceName: order.serviceName,
riskLevel: payload != null && readString(payload, 'riskLevel') != '' ? readString(payload, 'riskLevel') : '中风险',
careLevel: payload != null && readString(payload, 'careLevel') != '' ? readString(payload, 'careLevel') : getDefaultCareLevel(order),
visitTime: formatServiceAppointmentText(order.appointmentTime),
assessmentSummary: payload != null && readString(payload, 'assessmentSummary') != '' ? readString(payload, 'assessmentSummary') : (order.remark != '' ? order.remark : '待补充上门评估结论。'),
requirementTags: requirementTags.length > 0 ? requirementTags : order.serviceSnapshot.tags
}
}
function buildPlanDetail(order: ServiceOrderType, payload: any | null): HomeServicePlanType {
const serviceCycle = payload != null && readString(payload, 'serviceCycle') != '' ? readString(payload, 'serviceCycle') : formatServiceAppointmentText(order.appointmentTime)
const amountText = '¥' + String(order.serviceSnapshot.price)
return {
caseId: order.id,
caseNo: order.orderNo,
elderName: order.recipientName,
serviceName: order.serviceName,
planTitle: payload != null && readString(payload, 'planTitle') != '' ? readString(payload, 'planTitle') : (order.serviceName + '执行方案'),
serviceFrequency: payload != null && readString(payload, 'serviceFrequency') != '' ? readString(payload, 'serviceFrequency') : '按预约执行',
serviceCycle: serviceCycle,
executorAdvice: payload != null && readString(payload, 'executorAdvice') != '' ? readString(payload, 'executorAdvice') : (order.staffName != '' ? ('优先由' + order.staffName + '承接本次服务并补全留痕。') : '优先安排熟悉该服务类型的执行人员。'),
billingSummary: payload != null && readString(payload, 'billingSummary') != '' ? readString(payload, 'billingSummary') : ('本次服务金额 ' + amountText),
planSummary: payload != null && readString(payload, 'planSummary') != '' ? readString(payload, 'planSummary') : (order.remark != '' ? order.remark : '待补充服务目标、执行重点和验收口径。')
}
}
function buildRectificationDetail(order: ServiceOrderType, payload: any | null, issueSummary: string): HomeServiceRectificationType {
const status = payload != null && readString(payload, 'status') != '' ? readString(payload, 'status') : (order.status == 'exception' ? 'pending' : 'closed')
return {
caseId: order.id,
caseNo: order.orderNo,
elderName: order.recipientName,
serviceName: order.serviceName,
issueSummary: payload != null && readString(payload, 'issueSummary') != '' ? readString(payload, 'issueSummary') : issueSummary,
deadline: payload != null && readString(payload, 'deadline') != '' ? readString(payload, 'deadline') : (order.pendingAcceptanceAt != '' ? order.pendingAcceptanceAt : formatServiceAppointmentText(order.appointmentTime)),
ownerName: payload != null && readString(payload, 'ownerName') != '' ? readString(payload, 'ownerName') : (order.staffName != '' ? order.staffName : '待分配'),
status: status,
statusText: status == 'closed' ? '已关闭' : '待整改'
}
}
export async function fetchWorkerTasks(): Promise<Array<HomeServiceTaskType>> {
const taskIds = await listWorkerTaskIds()
const result = [] as Array<HomeServiceTaskType>
for (let i = 0; i < taskIds.length; i++) {
const detail = await getServiceOrderDetail(taskIds[i])
if (detail != null) {
result.push(mapOrderToWorkerTask(detail))
}
}
return result
}
export async function fetchWorkerTaskDetail(taskId: string): Promise<HomeServiceTaskType | null> {
const detail = await getServiceOrderDetail(taskId)
return detail == null ? null : mapOrderToWorkerTask(detail)
}
export async function advanceWorkerTask(taskId: string): Promise<HomeServiceTaskType | null> {
const detail = await fetchWorkerTaskDetail(taskId)
if (detail == null) {
return null
}
if (detail.status == 'serving') {
return await completeWorkerTask(taskId)
}
return detail
}
export async function submitWorkerCheckIn(taskId: string, note: string): Promise<HomeServiceTaskType | null> {
const checkedInAt = nowIso()
if (await isCareTask(taskId)) {
const recordId = buildId('care-checkin')
await supa.from('ec_care_records').insert({
id: recordId,
task_id: taskId,
record_type: 'checkin',
started_at: checkedInAt,
checked_in_at: checkedInAt,
location_text: note,
remark: note,
created_at: checkedInAt,
updated_at: checkedInAt
}).execute()
await supa.from('ec_care_tasks').update({
status: 'ORDER_IN_SERVICE',
checked_in_at: checkedInAt,
service_started_at: checkedInAt,
updated_at: checkedInAt
}).eq('id', taskId).execute()
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: taskId,
from_status: 'ORDER_ACCEPTED',
to_status: 'ORDER_IN_SERVICE',
actor_id: getCurrentUserId(),
actor_role: 'merchant',
action: 'checkin_task',
remark: note == '' ? '已完成签到,开始执行服务。' : note,
created_at: checkedInAt
}).execute()
} else {
const recordId = buildId('ser')
await supa.from('hss_service_execution_records').upsert({
id: recordId,
order_id: taskId,
assignment_id: '',
checkin_time: checkedInAt,
checkin_address: note,
service_started_at: checkedInAt,
remark: note,
created_at: checkedInAt,
updated_at: checkedInAt
}).execute()
await supa.from('hss_service_orders').update({
status: 'in_service',
arrived_at: checkedInAt,
service_started_at: checkedInAt,
updated_at: checkedInAt
}).eq('id', taskId).execute()
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: taskId,
from_status: 'accepted',
to_status: 'in_service',
operator_id: getCurrentUserId(),
operator_role: 'merchant',
remark: note == '' ? '已完成签到,开始执行服务。' : note,
created_at: checkedInAt
}).execute()
}
return await fetchWorkerTaskDetail(taskId)
}
export async function submitWorkerServiceRecord(taskId: string, summary: string): Promise<HomeServiceTaskType | null> {
const detail = await getServiceOrderDetail(taskId)
if (detail == null) {
return null
}
const now = nowIso()
const recordId = detail.executionRecord != null && detail.executionRecord.id != '' ? detail.executionRecord.id : buildId('worker-rec')
if (await isCareTask(taskId)) {
await supa.from('ec_care_records').upsert({
id: recordId,
task_id: taskId,
record_type: 'service',
started_at: detail.executionRecord != null ? detail.executionRecord.serviceStartedAt : detail.serviceStartedAt,
summary: summary,
remark: summary,
created_at: detail.executionRecord != null && detail.executionRecord.createdAt != '' ? detail.executionRecord.createdAt : now,
updated_at: now
}).execute()
await supa.from('ec_care_tasks').update({
status: 'ORDER_IN_SERVICE',
updated_at: now
}).eq('id', taskId).execute()
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: taskId,
from_status: 'ORDER_IN_SERVICE',
to_status: 'ORDER_IN_SERVICE',
actor_id: getCurrentUserId(),
actor_role: 'merchant',
action: 'save_record',
remark: summary == '' ? '已保存服务记录。' : summary,
created_at: now
}).execute()
} else {
await supa.from('hss_service_execution_records').upsert({
id: recordId,
order_id: taskId,
assignment_id: detail.currentAssignmentId,
service_started_at: detail.executionRecord != null ? detail.executionRecord.serviceStartedAt : detail.serviceStartedAt,
summary: summary,
remark: summary,
created_at: detail.executionRecord != null && detail.executionRecord.createdAt != '' ? detail.executionRecord.createdAt : now,
updated_at: now
}).execute()
}
return await fetchWorkerTaskDetail(taskId)
}
export async function submitWorkerException(taskId: string, exceptionType: string, description: string): Promise<HomeServiceTaskType | null> {
const now = nowIso()
if (await isCareTask(taskId)) {
await supa.from('hc_work_order_exceptions').insert({
id: buildId('hc-ex'),
task_id: taskId,
exception_type: exceptionType,
description: description,
occurred_at: now,
created_by: getCurrentUserId(),
created_at: now,
updated_at: now
}).execute()
await supa.from('ec_care_tasks').update({
status: 'ORDER_EXCEPTION',
updated_at: now
}).eq('id', taskId).execute()
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: taskId,
from_status: '',
to_status: 'ORDER_EXCEPTION',
actor_id: getCurrentUserId(),
actor_role: 'merchant',
action: 'report_exception',
remark: exceptionType + '' + description,
created_at: now
}).execute()
} else {
await supa.from('hss_service_orders').update({
status: 'exception',
updated_at: now
}).eq('id', taskId).execute()
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: taskId,
from_status: 'in_service',
to_status: 'exception',
operator_id: getCurrentUserId(),
operator_role: 'merchant',
remark: exceptionType + '' + description,
created_at: now
}).execute()
}
return await fetchWorkerTaskDetail(taskId)
}
export async function fetchAdminAssessmentDetail(caseId: string): Promise<HomeServiceAssessmentType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = await readAdminPayload(caseId, ADMIN_ASSESSMENT_PREFIX)
return buildAssessmentDetail(order, payload)
}
export async function submitAdminAssessment(caseId: string, riskLevel: string, careLevel: string, assessmentSummary: string): Promise<HomeServiceAssessmentType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = {
riskLevel,
careLevel,
assessmentSummary,
requirementTags: order.serviceSnapshot.tags,
updatedAt: nowIso()
}
if (await isCareTask(caseId)) {
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: caseId,
from_status: order.status,
to_status: order.status,
actor_id: getCurrentUserId(),
actor_role: 'admin',
action: 'submit_assessment',
remark: encodeAdminRemark(ADMIN_ASSESSMENT_PREFIX, payload),
created_at: nowIso()
}).execute()
} else {
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: caseId,
from_status: order.status,
to_status: order.status,
operator_id: getCurrentUserId(),
operator_role: 'admin',
remark: encodeAdminRemark(ADMIN_ASSESSMENT_PREFIX, payload),
created_at: nowIso()
}).execute()
}
return buildAssessmentDetail(order, payload)
}
export async function fetchAdminServicePlanDetail(caseId: string): Promise<HomeServicePlanType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = await readAdminPayload(caseId, ADMIN_PLAN_PREFIX)
return buildPlanDetail(order, payload)
}
export async function submitAdminServicePlan(
caseId: string,
planTitle: string,
serviceFrequency: string,
serviceCycle: string,
planSummary: string
): Promise<HomeServicePlanType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = {
planTitle,
serviceFrequency,
serviceCycle,
executorAdvice: order.staffName != '' ? ('优先由' + order.staffName + '承接本次服务并补全留痕。') : '优先安排熟悉该服务类型的执行人员。',
billingSummary: '本次服务金额 ¥' + String(order.serviceSnapshot.price),
planSummary,
updatedAt: nowIso()
}
if (await isCareTask(caseId)) {
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: caseId,
from_status: order.status,
to_status: order.status,
actor_id: getCurrentUserId(),
actor_role: 'admin',
action: 'submit_plan',
remark: encodeAdminRemark(ADMIN_PLAN_PREFIX, payload),
created_at: nowIso()
}).execute()
} else {
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: caseId,
from_status: order.status,
to_status: order.status,
operator_id: getCurrentUserId(),
operator_role: 'admin',
remark: encodeAdminRemark(ADMIN_PLAN_PREFIX, payload),
created_at: nowIso()
}).execute()
}
return buildPlanDetail(order, payload)
}
export async function fetchConsumerAcceptanceDetail(caseId: string): Promise<HomeServiceAcceptanceType | null> {
const detail = await getServiceOrderDetail(caseId)
if (detail != null) {
return {
caseId: detail.id,
caseNo: detail.orderNo,
elderName: detail.recipientName,
serviceName: detail.serviceName,
acceptanceStatus: detail.status,
acceptanceStatusText: getServiceOrderStatusText(detail.status),
rating: detail.review != null ? detail.review.rating : 5,
feedback: detail.review != null ? detail.review.content : '',
tags: detail.review != null ? detail.review.tags : [] as Array<string>
}
}
return null
}
export async function submitConsumerAcceptance(
caseId: string,
approved: boolean,
rating: number,
feedback: string,
tags: Array<string>
): Promise<HomeServiceAcceptanceType | null> {
if (approved) {
const result = await confirmServiceOrder(caseId, rating, feedback, tags)
if (result != null) {
return await fetchConsumerAcceptanceDetail(caseId)
}
return null
}
const rejected = await rejectServiceOrderAcceptance(caseId, feedback)
if (rejected != null) {
return await fetchConsumerAcceptanceDetail(caseId)
}
return null
}
export async function fetchAdminRectificationDetail(caseId: string): Promise<HomeServiceRectificationType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = await readAdminPayload(caseId, ADMIN_RECTIFICATION_PREFIX)
const issueSummary = await readLatestExceptionSummary(caseId, order)
return buildRectificationDetail(order, payload, issueSummary)
}
export async function submitAdminRectification(caseId: string, issueSummary: string): Promise<HomeServiceRectificationType | null> {
const order = await getServiceOrderDetail(caseId)
if (order == null) {
return null
}
const payload = {
issueSummary,
deadline: order.pendingAcceptanceAt != '' ? order.pendingAcceptanceAt : formatServiceAppointmentText(order.appointmentTime),
ownerName: order.staffName != '' ? order.staffName : '待分配',
status: 'closed',
updatedAt: nowIso()
}
if (await isCareTask(caseId)) {
const reopenedAt = nowIso()
await supa.from('ec_care_tasks').update({
status: 'ACCEPTANCE_PENDING',
acceptance_pending_at: reopenedAt,
updated_at: reopenedAt
}).eq('id', caseId).execute()
await supa.from('hc_work_order_events').insert({
id: buildId('hc-event'),
task_id: caseId,
from_status: 'ACCEPTANCE_REJECTED',
to_status: 'ACCEPTANCE_PENDING',
actor_id: getCurrentUserId(),
actor_role: 'admin',
action: 'submit_rectification',
remark: encodeAdminRemark(ADMIN_RECTIFICATION_PREFIX, payload),
created_at: reopenedAt
}).execute()
} else {
const reopenedAt = nowIso()
await supa.from('hss_service_orders').update({
status: 'pending_acceptance',
pending_acceptance_at: reopenedAt,
updated_at: reopenedAt
}).eq('id', caseId).execute()
await supa.from('hss_service_order_status_logs').insert({
id: buildId('slog'),
order_id: caseId,
from_status: order.status,
to_status: 'pending_acceptance',
operator_id: getCurrentUserId(),
operator_role: 'admin',
remark: encodeAdminRemark(ADMIN_RECTIFICATION_PREFIX, payload),
created_at: reopenedAt
}).execute()
}
const latest = await getServiceOrderDetail(caseId)
if (latest == null) {
return buildRectificationDetail(order, payload, issueSummary)
}
return buildRectificationDetail(latest, payload, issueSummary)
}
export async function fetchAdminSettlementDetail(caseId: string): Promise<HomeServiceSettlementType | null> {
await delay()
const target = ADMIN_SETTLEMENTS.find((item) => item.caseId == caseId)
return target == null ? null : cloneSettlement(target)
}
export async function submitAdminSettlementArchive(caseId: string): Promise<HomeServiceSettlementType | null> {
await delay()
const target = ADMIN_SETTLEMENTS.find((item) => item.caseId == caseId)
if (target == null) {
return null
}
target.archiveStatus = 'archived'
target.archiveStatusText = '已归档'
const relatedCase = CASE_STORE.find((item) => item.id == caseId)
if (relatedCase != null) {
relatedCase.timeline.unshift({
id: 'case-settlement-' + String(relatedCase.timeline.length + 1),
title: '结算归档完成',
time: '2026-05-13 18:30',
description: '结算单和执行材料已完成归档。'
})
}
return cloneSettlement(target)
}
export async function fetchAdminHomeServiceApplications(): Promise<Array<HomeServiceAdminApplicationType>> {
await delay()
return ADMIN_APPLICATIONS.map((item) => ({ ...item }))
}
export async function fetchAdminHomeServiceOverview(): Promise<Array<HomeServiceOverviewCardType>> {
await delay()
return ADMIN_OVERVIEW.map((item) => ({ ...item }))
}