完善服务模块缺少付款页的bug
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
// =============================================================================
|
||||
// LEGACY/TODO 全局声明:
|
||||
// 本文件中所有直接 update ec_care_tasks / insert hc_work_order_events /
|
||||
// insert hc_work_order_exceptions / insert ec_care_records 的逻辑均为旧链路。
|
||||
// 按新架构,状态流转与事件留痕必须由后端 RPC 统一处理,前端不得直接写表。
|
||||
// 已改造的函数(如 submitWorkerCheckIn / submitWorkerServiceRecord / submitWorkerException /
|
||||
// completeWorkerTask)已切换为 rpc_delivery_* 或标记为 GAP。
|
||||
// 尚未改造的 admin 函数(submitAdminAssessment / submitAdminServicePlan /
|
||||
// submitAdminRectification / submitAdminSettlementArchive 等)仍保留旧逻辑,
|
||||
// 但已增加 TODO 注释,待后端提供对应 RPC 后统一替换。
|
||||
// =============================================================================
|
||||
|
||||
import {
|
||||
HomeServiceAcceptanceType,
|
||||
HomeServiceAdminApplicationType,
|
||||
@@ -6,6 +18,7 @@ import {
|
||||
HomeServiceCatalogType,
|
||||
HomeServiceCaseType,
|
||||
HomeServiceOverviewCardType,
|
||||
HomeServicePackageType,
|
||||
HomeServicePlanType,
|
||||
HomeServiceRectificationType,
|
||||
HomeServiceSettlementType,
|
||||
@@ -15,7 +28,9 @@ import {
|
||||
import {
|
||||
confirmServiceOrder,
|
||||
createServiceOrder,
|
||||
getHomecareOrderDisplayStatus,
|
||||
getServiceOrderDetail,
|
||||
HOMECARE_DISPATCH_STATUS_FAILED,
|
||||
listConsumerServiceOrders,
|
||||
rejectServiceOrderAcceptance
|
||||
} from '@/services/serviceOrderService.uts'
|
||||
@@ -96,6 +111,24 @@ function parseCatalogItem(source: any): HomeServiceCatalogType {
|
||||
}
|
||||
}
|
||||
|
||||
function parsePackageItem(source: any): HomeServicePackageType {
|
||||
return {
|
||||
id: readString(source, 'id'),
|
||||
serviceId: readString(source, 'service_id'),
|
||||
packageName: readString(source, 'package_name'),
|
||||
packageDesc: readString(source, 'package_desc'),
|
||||
durationMinutes: readNumber(source, 'duration_minutes'),
|
||||
durationText: readString(source, 'duration_text'),
|
||||
price: readNumber(source, 'price'),
|
||||
listPrice: readNumber(source, 'list_price'),
|
||||
isDefault: readString(source, 'is_default') == 'true' || plainObject(source)['is_default'] === true,
|
||||
sortNo: readNumber(source, 'sort_no'),
|
||||
dataSource: readString(source, 'data_source'),
|
||||
seedBatchNo: readString(source, 'seed_batch_no'),
|
||||
remark: readString(source, 'remark')
|
||||
}
|
||||
}
|
||||
|
||||
function createTimeline(title1: string, title2: string, title3: string): Array<HomeServiceTimelineItemType> {
|
||||
return [
|
||||
{
|
||||
@@ -410,6 +443,49 @@ export async function fetchHomeServiceCatalog(): Promise<Array<HomeServiceCatalo
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchHomeServicePackages(serviceId: string): Promise<Array<HomeServicePackageType>> {
|
||||
if (serviceId == '') {
|
||||
return [] as Array<HomeServicePackageType>
|
||||
}
|
||||
const response = await supa
|
||||
.from('hss_service_packages')
|
||||
.select('id, service_id, package_name, package_desc, duration_minutes, duration_text, price, list_price, is_default, sort_no, data_source, seed_batch_no, remark, effective_at, expires_at')
|
||||
.eq('service_id', serviceId)
|
||||
.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<HomeServicePackageType>
|
||||
}
|
||||
const result = [] as Array<HomeServicePackageType>
|
||||
for (let i = 0; i < response.data.length; i++) {
|
||||
const item = response.data[i]
|
||||
const effectiveAt = readString(item, 'effective_at')
|
||||
const expiresAt = readString(item, 'expires_at')
|
||||
const effectiveMs = effectiveAt != '' ? Date.parse(effectiveAt) : 0
|
||||
const expiresMs = expiresAt != '' ? Date.parse(expiresAt) : 0
|
||||
const nowMs = Date.now()
|
||||
if (effectiveMs > 0 && effectiveMs > nowMs) {
|
||||
continue
|
||||
}
|
||||
if (expiresMs > 0 && expiresMs <= nowMs) {
|
||||
continue
|
||||
}
|
||||
result.push(parsePackageItem(item))
|
||||
}
|
||||
result.sort((left: HomeServicePackageType, right: HomeServicePackageType): number => {
|
||||
if (left.isDefault && !right.isDefault) {
|
||||
return -1
|
||||
}
|
||||
if (!left.isDefault && right.isDefault) {
|
||||
return 1
|
||||
}
|
||||
return left.sortNo - right.sortNo
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export async function fetchConsumerHomeServiceCases(): Promise<Array<HomeServiceCaseType>> {
|
||||
const orders = await listConsumerServiceOrders()
|
||||
const result = [] as Array<HomeServiceCaseType>
|
||||
@@ -436,9 +512,28 @@ export async function createHomeServiceApplication(draft: HomeServiceApplication
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matchedService != null && draft.serviceAddressSnapshot != null) {
|
||||
const packages = await fetchHomeServicePackages(draft.serviceId)
|
||||
let matchedPackage: HomeServicePackageType | null = null
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
if (packages[i].id == draft.selectedPackageId) {
|
||||
matchedPackage = packages[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matchedService != null && matchedPackage != null && draft.serviceAddressSnapshot != null) {
|
||||
const serviceSnapshot: HomeServiceCatalogType = {
|
||||
id: matchedService.id,
|
||||
name: matchedService.name,
|
||||
category: matchedService.category,
|
||||
price: matchedPackage.price,
|
||||
durationText: matchedPackage.durationText != '' ? matchedPackage.durationText : matchedService.durationText,
|
||||
summary: matchedPackage.packageDesc != '' ? matchedPackage.packageDesc : matchedService.summary,
|
||||
tags: matchedService.tags,
|
||||
suitableFor: matchedService.suitableFor
|
||||
}
|
||||
const createdOrder = await createServiceOrder({
|
||||
service: matchedService,
|
||||
service: serviceSnapshot,
|
||||
packageInfo: matchedPackage,
|
||||
address: {
|
||||
addressId: draft.serviceAddressSnapshot.addressId,
|
||||
contactName: draft.serviceAddressSnapshot.contactName,
|
||||
@@ -557,12 +652,18 @@ function mapLogsToTimeline(logs: Array<ServiceOrderTimelineItemType>): Array<Hom
|
||||
}
|
||||
|
||||
function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
|
||||
const displayStatus = getHomecareOrderDisplayStatus(order)
|
||||
let statusText = displayStatus
|
||||
let statusTone = getCaseTone(order.status)
|
||||
if (order.dispatchStatus == HOMECARE_DISPATCH_STATUS_FAILED) {
|
||||
statusTone = 'danger'
|
||||
}
|
||||
return {
|
||||
id: order.id,
|
||||
caseNo: order.orderNo,
|
||||
status: order.status,
|
||||
statusText: getServiceOrderStatusText(order.status),
|
||||
statusTone: getCaseTone(order.status),
|
||||
statusText: statusText,
|
||||
statusTone: statusTone,
|
||||
serviceName: order.serviceName,
|
||||
serviceTime: formatServiceAppointmentText(order.appointmentTime),
|
||||
applicantName: order.contactName,
|
||||
@@ -576,6 +677,8 @@ function mapOrderToCase(order: ServiceOrderType): HomeServiceCaseType {
|
||||
staffName: order.staffName == '' ? '待分配' : order.staffName,
|
||||
staffPhone: order.staffPhone == '' ? '待分配' : order.staffPhone,
|
||||
amount: order.serviceSnapshot.price,
|
||||
paymentStatus: order.paymentStatus,
|
||||
payExpireAt: order.payExpireAt,
|
||||
checkinTime: order.executionRecord != null ? order.executionRecord.checkinTime : '',
|
||||
checkinAddress: order.executionRecord != null ? order.executionRecord.checkinAddress : '',
|
||||
serviceStartedAt: order.executionRecord != null ? order.executionRecord.serviceStartedAt : order.serviceStartedAt,
|
||||
@@ -677,42 +780,13 @@ async function isCareTask(taskId: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
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()
|
||||
// LEGACY/TODO: 已切换为调用 rpc_delivery_finish_service。
|
||||
const { data, error } = await supa.rpc('rpc_delivery_finish_service', {
|
||||
p_order_id: taskId,
|
||||
p_payload: {} as any
|
||||
} as any)
|
||||
if (error != null) {
|
||||
console.error('[homeServiceService] finish_service rpc failed:', error)
|
||||
}
|
||||
return await fetchWorkerTaskDetail(taskId)
|
||||
}
|
||||
@@ -876,161 +950,60 @@ export async function advanceWorkerTask(taskId: string): Promise<HomeServiceTask
|
||||
}
|
||||
|
||||
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()
|
||||
// LEGACY/TODO: 已切换为调用 rpc_delivery_checkin_order + rpc_delivery_start_service。
|
||||
const checkinResult = await supa.rpc('rpc_delivery_checkin_order', {
|
||||
p_order_id: taskId,
|
||||
p_payload: {
|
||||
location: {} as any,
|
||||
photos: [] as Array<string>,
|
||||
note: note != null ? note : ''
|
||||
} as any
|
||||
} as any)
|
||||
if (checkinResult.error != null) {
|
||||
console.error('[homeServiceService] checkin rpc failed:', checkinResult.error)
|
||||
}
|
||||
const startResult = await supa.rpc('rpc_delivery_start_service', {
|
||||
p_order_id: taskId
|
||||
} as any)
|
||||
if (startResult.error != null) {
|
||||
console.error('[homeServiceService] start_service rpc failed:', startResult.error)
|
||||
}
|
||||
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()
|
||||
// LEGACY/TODO: 已切换为调用 rpc_delivery_save_progress。
|
||||
const { data, error } = await supa.rpc('rpc_delivery_save_progress', {
|
||||
p_order_id: taskId,
|
||||
p_payload: {
|
||||
items: [] as any,
|
||||
serviceSummary: summary != null ? summary : '',
|
||||
progressNote: summary != null ? summary : ''
|
||||
} as any
|
||||
} as any)
|
||||
if (error != null) {
|
||||
console.error('[homeServiceService] save_progress rpc failed:', error)
|
||||
}
|
||||
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()
|
||||
// LEGACY/TODO: 已切换为调用 rpc_delivery_submit_exception。
|
||||
const { data, error } = await supa.rpc('rpc_delivery_submit_exception', {
|
||||
p_order_id: taskId,
|
||||
p_payload: {
|
||||
exceptionType: exceptionType != null ? exceptionType : 'other',
|
||||
description: description != null ? description : '',
|
||||
occurredAt: nowIso(),
|
||||
locationText: '',
|
||||
images: [] as any,
|
||||
needPlatformIntervention: false,
|
||||
requestCancelOrder: false,
|
||||
requestReschedule: false
|
||||
} as any
|
||||
} as any)
|
||||
if (error != null) {
|
||||
console.error('[homeServiceService] submit_exception rpc failed:', error)
|
||||
}
|
||||
return await fetchWorkerTaskDetail(taskId)
|
||||
}
|
||||
@@ -1056,30 +1029,10 @@ export async function submitAdminAssessment(caseId: string, riskLevel: string, c
|
||||
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()
|
||||
}
|
||||
// TODO/GAP: admin 提交评估暂无后端 RPC(ec_care_tasks 新链)。
|
||||
// 禁止前端直接 insert hc_work_order_events 作为评估留痕。
|
||||
// 如需启用,请后端补充 rpc_admin_submit_assessment(case_id, payload)。
|
||||
console.warn('[GAP] submitAdminAssessment 暂不可用(新链):缺少 admin 评估提交 RPC')
|
||||
return buildAssessmentDetail(order, payload)
|
||||
}
|
||||
|
||||
@@ -1112,30 +1065,10 @@ export async function submitAdminServicePlan(
|
||||
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()
|
||||
}
|
||||
// TODO/GAP: admin 提交服务计划暂无后端 RPC(ec_care_tasks 新链)。
|
||||
// 禁止前端直接 insert hc_work_order_events 作为计划留痕。
|
||||
// 如需启用,请后端补充 rpc_admin_submit_service_plan(case_id, payload)。
|
||||
console.warn('[GAP] submitAdminServicePlan 暂不可用(新链):缺少 admin 服务计划提交 RPC')
|
||||
return buildPlanDetail(order, payload)
|
||||
}
|
||||
|
||||
@@ -1200,42 +1133,10 @@ export async function submitAdminRectification(caseId: string, issueSummary: str
|
||||
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()
|
||||
}
|
||||
// TODO/GAP: admin 提交整改暂无后端 RPC(ec_care_tasks 新链)。
|
||||
// 禁止前端直接 update ec_care_tasks / insert hc_work_order_events。
|
||||
// 如需启用,请后端补充 rpc_admin_submit_rectification(case_id, payload)。
|
||||
console.warn('[GAP] submitAdminRectification 暂不可用(新链):缺少 admin 整改提交 RPC')
|
||||
const latest = await getServiceOrderDetail(caseId)
|
||||
if (latest == null) {
|
||||
return buildRectificationDetail(order, payload, issueSummary)
|
||||
|
||||
@@ -2,7 +2,7 @@ import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { getCurrentUserId } from '@/utils/store.uts'
|
||||
import { getCurrentUser } from '@/utils/store.uts'
|
||||
import type { UserAddress } from '@/utils/supabaseService.uts'
|
||||
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||
import type { HomeServiceCatalogType, HomeServicePackageType } from '@/types/home-service.uts'
|
||||
import type { DeliveryServiceRecordType } from '@/types/delivery.uts'
|
||||
import {
|
||||
getServiceOrderStatusText,
|
||||
@@ -16,8 +16,28 @@ import {
|
||||
type ServiceReviewType
|
||||
} from '@/types/service-order.uts'
|
||||
|
||||
export type HomecareDispatchResult = {
|
||||
success: boolean
|
||||
code: string
|
||||
message: string
|
||||
display_type: string
|
||||
retryable: boolean
|
||||
dispatch_status?: string
|
||||
order_id?: string
|
||||
assignment_id?: string
|
||||
staff_id?: string
|
||||
station_id?: string
|
||||
dispatch_distance_km?: number
|
||||
}
|
||||
|
||||
export const HOMECARE_DISPATCH_STATUS_PENDING = 'pending'
|
||||
export const HOMECARE_DISPATCH_STATUS_DISPATCHING = 'dispatching'
|
||||
export const HOMECARE_DISPATCH_STATUS_ASSIGNED = 'assigned'
|
||||
export const HOMECARE_DISPATCH_STATUS_FAILED = 'failed'
|
||||
|
||||
export type CreateServiceOrderParams = {
|
||||
service: HomeServiceCatalogType
|
||||
packageInfo: HomeServicePackageType
|
||||
address: ServiceOrderAddressSnapshotType
|
||||
recipientName: string
|
||||
recipientPhone: string
|
||||
@@ -29,6 +49,42 @@ export type CreateServiceOrderParams = {
|
||||
remark: string
|
||||
}
|
||||
|
||||
function buildServiceSnapshot(params: CreateServiceOrderParams): any {
|
||||
return {
|
||||
serviceId: params.service.id,
|
||||
serviceName: params.service.name,
|
||||
category: params.service.category,
|
||||
price: params.packageInfo.price,
|
||||
durationText: params.packageInfo.durationText != '' ? params.packageInfo.durationText : params.service.durationText,
|
||||
summary: params.packageInfo.packageDesc != '' ? params.packageInfo.packageDesc : params.service.summary,
|
||||
tags: params.service.tags,
|
||||
suitableFor: params.service.suitableFor,
|
||||
packageId: params.packageInfo.id,
|
||||
packageName: params.packageInfo.packageName,
|
||||
packagePrice: params.packageInfo.price,
|
||||
packageListPrice: params.packageInfo.listPrice,
|
||||
packageDataSource: params.packageInfo.dataSource,
|
||||
packageSeedBatchNo: params.packageInfo.seedBatchNo
|
||||
} as any
|
||||
}
|
||||
|
||||
function buildPricingSnapshot(params: CreateServiceOrderParams): any {
|
||||
return {
|
||||
service_id: params.service.id,
|
||||
service_name: params.service.name,
|
||||
package_id: params.packageInfo.id,
|
||||
package_name: params.packageInfo.packageName,
|
||||
package_desc: params.packageInfo.packageDesc,
|
||||
duration_minutes: params.packageInfo.durationMinutes,
|
||||
duration_text: params.packageInfo.durationText,
|
||||
price: params.packageInfo.price,
|
||||
list_price: params.packageInfo.listPrice,
|
||||
data_source: params.packageInfo.dataSource,
|
||||
seed_batch_no: params.packageInfo.seedBatchNo,
|
||||
remark: params.packageInfo.remark
|
||||
} as any
|
||||
}
|
||||
|
||||
const HOMECARE_DISPATCH_CANDIDATE_RPC = 'rpc_homecare_dispatch_candidate'
|
||||
|
||||
function nowText(): string {
|
||||
@@ -89,8 +145,15 @@ function normalizeAppointmentTime(value: string): string | null {
|
||||
if (text == '') {
|
||||
return null
|
||||
}
|
||||
if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(\s*-\s*\d{2}:\d{2})?$/.test(text)) {
|
||||
return text
|
||||
const fullRangeMatch = text.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})(\s*-\s*\d{2}:\d{2})?$/)
|
||||
if (fullRangeMatch != null) {
|
||||
const datePart = fullRangeMatch[1] ?? ''
|
||||
const startTime = fullRangeMatch[2] ?? ''
|
||||
const parsed = Date.parse(datePart + 'T' + startTime + ':00')
|
||||
if (!isNaN(parsed)) {
|
||||
return new Date(parsed).toISOString()
|
||||
}
|
||||
return datePart + ' ' + startTime
|
||||
}
|
||||
if (/^\d{4}-\d{2}-\d{2}(\s+(上午|下午|晚上))$/.test(text)) {
|
||||
return text
|
||||
@@ -119,9 +182,12 @@ function normalizeAppointmentTime(value: string): string | null {
|
||||
const rangeMatch = text.match(/(\d{2}:\d{2})(\s*-\s*\d{2}:\d{2})?/)
|
||||
if (rangeMatch != null) {
|
||||
const startTime = rangeMatch[1] ?? ''
|
||||
const endRange = rangeMatch[2] ?? ''
|
||||
if (startTime != '') {
|
||||
return year + '-' + month + '-' + day + ' ' + startTime + endRange.replace(/\s+/g, '')
|
||||
const parsed = Date.parse(year + '-' + month + '-' + day + 'T' + startTime + ':00')
|
||||
if (!isNaN(parsed)) {
|
||||
return new Date(parsed).toISOString()
|
||||
}
|
||||
return year + '-' + month + '-' + day + ' ' + startTime
|
||||
}
|
||||
}
|
||||
const tailText = text.substring(text.indexOf(month + '/' + day) + 5).trim()
|
||||
@@ -223,7 +289,7 @@ function shouldBypassEcServiceRequestCreate(error: any): boolean {
|
||||
|| hasMissingColumnError(error, 'contact_phone')
|
||||
}
|
||||
|
||||
function shouldUseCareTaskPath(orderId: string): boolean {
|
||||
export function shouldUseCareTaskPath(orderId: string): boolean {
|
||||
return isUuidLike(orderId)
|
||||
}
|
||||
|
||||
@@ -327,7 +393,7 @@ function buildEcCareTaskPayload(params: CreateServiceOrderParams, userId: string
|
||||
service_catalog_id: params.service.id,
|
||||
service_name: params.service.name,
|
||||
service_category: params.service.category,
|
||||
service_snapshot_json: params.service as any,
|
||||
service_snapshot_json: buildServiceSnapshot(params),
|
||||
elder_name: params.recipientName,
|
||||
elder_phone: params.recipientPhone,
|
||||
elder_age: params.recipientAge,
|
||||
@@ -357,7 +423,7 @@ function buildEcCareTaskPayloadWithoutAddress(params: CreateServiceOrderParams,
|
||||
service_catalog_id: params.service.id,
|
||||
service_name: params.service.name,
|
||||
service_category: params.service.category,
|
||||
service_snapshot_json: params.service as any,
|
||||
service_snapshot_json: buildServiceSnapshot(params),
|
||||
elder_name: params.recipientName,
|
||||
elder_phone: params.recipientPhone,
|
||||
elder_age: params.recipientAge,
|
||||
@@ -479,6 +545,8 @@ function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>,
|
||||
appointmentTime: readString(item, 'appointment_time'),
|
||||
remark: readString(item, 'remark'),
|
||||
status: normalizeServiceOrderStatus(readString(item, 'status')),
|
||||
paymentStatus: readNumber(item, 'payment_status'),
|
||||
payExpireAt: readString(item, 'pay_expire_at'),
|
||||
currentAssignmentId: readString(item, 'current_assignment_id'),
|
||||
currentStaffId: readString(item, 'current_staff_id'),
|
||||
acceptedAt: readString(item, 'accepted_at'),
|
||||
@@ -496,35 +564,23 @@ function parseServiceOrder(item: any, logs: Array<ServiceOrderTimelineItemType>,
|
||||
logs,
|
||||
executionRecord: null,
|
||||
evidenceFiles: [] as Array<any>,
|
||||
review
|
||||
review,
|
||||
dispatchStatus: readString(item, 'dispatch_status'),
|
||||
dispatchErrorCode: readString(item, 'dispatch_error_code'),
|
||||
dispatchErrorMessage: readString(item, 'dispatch_error_message')
|
||||
}
|
||||
}
|
||||
|
||||
async function insertLegacyStatusLog(orderId: string, fromStatus: string, toStatus: ServiceOrderStatus, operatorId: string, operatorRole: string, remark: string): Promise<void> {
|
||||
await supa.from('hss_service_order_status_logs').insert({
|
||||
id: buildId('slog'),
|
||||
order_id: orderId,
|
||||
from_status: fromStatus,
|
||||
to_status: toStatus,
|
||||
operator_id: operatorId == '' ? null : operatorId,
|
||||
operator_role: operatorRole,
|
||||
remark,
|
||||
created_at: new Date().toISOString()
|
||||
}).execute()
|
||||
// LEGACY/TODO: 旧链路直接写 hss_service_order_status_logs 已停用。
|
||||
// 状态事件应由后端 RPC 统一写入 hc_work_order_events / hss_service_order_status_logs。
|
||||
console.warn('[LEGACY] insertLegacyStatusLog skipped for', orderId, fromStatus, toStatus, remark)
|
||||
}
|
||||
|
||||
async function insertWorkOrderEvent(taskId: string, fromStatus: string, toStatus: string, actorId: string, actorRole: string, action: string, remark: string): Promise<void> {
|
||||
await supa.from('hc_work_order_events').insert({
|
||||
id: buildId('hc-event'),
|
||||
task_id: taskId,
|
||||
from_status: fromStatus == '' ? null : fromStatus,
|
||||
to_status: toStatus,
|
||||
actor_id: actorId == '' ? null : actorId,
|
||||
actor_role: actorRole,
|
||||
action,
|
||||
remark,
|
||||
created_at: nowIso()
|
||||
}).execute()
|
||||
// LEGACY/TODO: 前端直接 insert hc_work_order_events 已严格禁止。
|
||||
// 状态事件必须由后端 RPC 统一写入。
|
||||
console.warn('[LEGACY] insertWorkOrderEvent skipped for', taskId, fromStatus, toStatus, action, remark)
|
||||
}
|
||||
|
||||
function readFirstString(source: any, keys: Array<string>): string {
|
||||
@@ -748,10 +804,18 @@ async function getCareTaskDetail(taskId: string): Promise<ServiceOrderType | nul
|
||||
}
|
||||
}
|
||||
}
|
||||
parsed.paymentStatus = 2
|
||||
parsed.dispatchStatus = parsed.currentStaffId != '' ? HOMECARE_DISPATCH_STATUS_ASSIGNED : HOMECARE_DISPATCH_STATUS_PENDING
|
||||
parsed.dispatchErrorCode = ''
|
||||
parsed.dispatchErrorMessage = ''
|
||||
return parsed
|
||||
}
|
||||
|
||||
async function tryCreateCareTask(params: CreateServiceOrderParams): Promise<ServiceOrderType | null> {
|
||||
// LEGACY/TODO: 本函数为前端直接 INSERT ec_service_requests + ec_care_tasks 的过渡逻辑。
|
||||
// 按新架构,履约工单应在支付完成后由后端 RPC 生成/激活,前端不得直接创建。
|
||||
// 当前因缺少后端接口(rpc_consumer_create_homecare_task 或支付回调自动创建)暂时保留,
|
||||
// 但已移除前端直接派单(不再写入 assigned_to + ORDER_ASSIGNED)。
|
||||
if (ecServiceRequestCreateUnavailable) {
|
||||
return null
|
||||
}
|
||||
@@ -808,19 +872,8 @@ async function tryCreateCareTask(params: CreateServiceOrderParams): Promise<Serv
|
||||
if (taskResponse.error != null) {
|
||||
return null
|
||||
}
|
||||
// 已移除前端直接派单逻辑。派单应由后端调度系统完成。
|
||||
await insertWorkOrderEvent(taskId, '', 'ORDER_CREATED', userId, 'consumer', 'create_task', '创建服务申请')
|
||||
const assignedStaff = await getAutoAssignableStaff()
|
||||
if (assignedStaff != null) {
|
||||
const assignedUserId = readString(assignedStaff, 'uid')
|
||||
if (assignedUserId != '') {
|
||||
await supa.from('ec_care_tasks').update({
|
||||
status: 'ORDER_ASSIGNED',
|
||||
assigned_to: assignedUserId,
|
||||
updated_at: createdAt
|
||||
}).eq('id', taskId).execute()
|
||||
await insertWorkOrderEvent(taskId, 'ORDER_CREATED', 'ORDER_ASSIGNED', userId, 'system', 'assign_task', '系统已自动派单')
|
||||
}
|
||||
}
|
||||
return await getCareTaskDetail(taskId)
|
||||
}
|
||||
|
||||
@@ -897,10 +950,190 @@ export function buildAddressSnapshot(address: UserAddress, latitude: number, lon
|
||||
}
|
||||
}
|
||||
|
||||
export function getHomecareOrderDisplayStatus(order: ServiceOrderType): string {
|
||||
if (order.paymentStatus == 1 && order.status == 'created') {
|
||||
return '待付款'
|
||||
}
|
||||
if (order.paymentStatus == 2 && order.dispatchStatus == HOMECARE_DISPATCH_STATUS_PENDING) {
|
||||
return '待派单'
|
||||
}
|
||||
if (order.paymentStatus == 2 && order.dispatchStatus == HOMECARE_DISPATCH_STATUS_DISPATCHING) {
|
||||
return '正在派单'
|
||||
}
|
||||
if (order.paymentStatus == 2 && order.dispatchStatus == HOMECARE_DISPATCH_STATUS_FAILED) {
|
||||
return '派单未成功'
|
||||
}
|
||||
if (order.paymentStatus == 2 && order.dispatchStatus == HOMECARE_DISPATCH_STATUS_ASSIGNED) {
|
||||
return '已派单'
|
||||
}
|
||||
return getServiceOrderStatusText(order.status)
|
||||
}
|
||||
|
||||
export async function dispatchPaidHomecareOrder(orderId: string): Promise<HomecareDispatchResult> {
|
||||
if (orderId == null || orderId.trim() == '') {
|
||||
return {
|
||||
success: false,
|
||||
code: 'ORDER_ID_REQUIRED',
|
||||
message: '订单信息异常,请返回后重试',
|
||||
display_type: 'modal',
|
||||
retryable: false
|
||||
}
|
||||
}
|
||||
|
||||
// LEGACY/TODO: rpc_homecare_auto_dispatch 当前只操作 hss_service_orders(旧交易链)。
|
||||
// 对于 ec_care_tasks 新链(UUID 格式订单 ID),不要调用旧 RPC,避免错误回写旧表。
|
||||
if (shouldUseCareTaskPath(orderId)) {
|
||||
return {
|
||||
success: true,
|
||||
code: 'SYNC_IN_PROGRESS',
|
||||
message: '付款成功,服务安排信息正在同步中,请稍后在我的服务中查看。',
|
||||
display_type: 'toast',
|
||||
retryable: false,
|
||||
dispatch_status: HOMECARE_DISPATCH_STATUS_PENDING,
|
||||
order_id: orderId
|
||||
}
|
||||
}
|
||||
|
||||
const { data, error } = await supa.rpc('rpc_homecare_auto_dispatch', {
|
||||
p_order_id: orderId
|
||||
} as any)
|
||||
|
||||
if (error != null) {
|
||||
console.error('[homecare-dispatch] rpc failed:', error)
|
||||
return {
|
||||
success: false,
|
||||
code: 'RPC_EXECUTION_FAILED',
|
||||
message: '派单服务暂时异常,请稍后重试',
|
||||
display_type: 'modal',
|
||||
retryable: true,
|
||||
dispatch_status: 'failed',
|
||||
order_id: orderId
|
||||
}
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
return {
|
||||
success: false,
|
||||
code: 'RPC_EMPTY_RESULT',
|
||||
message: '未获取到派单结果,请稍后重试',
|
||||
display_type: 'modal',
|
||||
retryable: true,
|
||||
dispatch_status: 'failed',
|
||||
order_id: orderId
|
||||
}
|
||||
}
|
||||
|
||||
const result = plainObject(data)
|
||||
const successValue = result['success']
|
||||
const isSuccess = successValue === true || successValue === 'true' || (typeof successValue === 'boolean' && successValue)
|
||||
const code = readString(result, 'code')
|
||||
const message = readString(result, 'message')
|
||||
const displayType = readString(result, 'display_type')
|
||||
const retryable = result['retryable'] === true || result['retryable'] === 'true'
|
||||
const dispatchStatus = readString(result, 'dispatch_status')
|
||||
|
||||
if (isSuccess || code == 'DISPATCH_ASSIGNED' || code == 'ALREADY_ASSIGNED' || code == 'ALREADY_ASSIGNED_RECOVERED') {
|
||||
return {
|
||||
success: true,
|
||||
code: code != '' ? code : 'DISPATCH_ASSIGNED',
|
||||
message: message != '' ? message : '系统已为您匹配服务人员',
|
||||
display_type: displayType != '' ? displayType : 'none',
|
||||
retryable: false,
|
||||
dispatch_status: dispatchStatus != '' ? dispatchStatus : 'assigned',
|
||||
order_id: readString(result, 'order_id'),
|
||||
assignment_id: readString(result, 'assignment_id'),
|
||||
staff_id: readString(result, 'staff_id'),
|
||||
station_id: readString(result, 'station_id'),
|
||||
dispatch_distance_km: readNumber(result, 'dispatch_distance_km')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
code: code != '' ? code : 'DISPATCH_FAILED',
|
||||
message: message != '' ? message : '派单失败,请稍后重试',
|
||||
display_type: displayType != '' ? displayType : 'modal',
|
||||
retryable: retryable,
|
||||
dispatch_status: dispatchStatus != '' ? dispatchStatus : 'failed',
|
||||
order_id: readString(result, 'order_id')
|
||||
}
|
||||
}
|
||||
|
||||
export function showHomecareDispatchFailureModal(orderId: string, result: HomecareDispatchResult, retryCallback: (id: string) => void): void {
|
||||
const code = result.code
|
||||
let title = '操作失败'
|
||||
let allowRetry = false
|
||||
|
||||
if (code == 'ORDER_ID_REQUIRED') {
|
||||
title = '操作失败'
|
||||
allowRetry = false
|
||||
} else if (code == 'UNAUTHENTICATED') {
|
||||
title = '请重新登录'
|
||||
allowRetry = false
|
||||
} else if (code == 'USER_PROFILE_NOT_FOUND') {
|
||||
title = '账户异常'
|
||||
allowRetry = false
|
||||
} else if (code == 'ORDER_NOT_FOUND') {
|
||||
title = '订单异常'
|
||||
allowRetry = false
|
||||
} else if (code == 'ORDER_ACCESS_DENIED') {
|
||||
title = '无权操作'
|
||||
allowRetry = false
|
||||
} else if (code == 'ORDER_NOT_PAID') {
|
||||
title = '暂不能派单'
|
||||
allowRetry = false
|
||||
} else if (code == 'ORDER_STATUS_NOT_DISPATCHABLE') {
|
||||
title = '暂不能派单'
|
||||
allowRetry = false
|
||||
} else if (code == 'NO_ONLINE_STAFF' || code == 'NO_STAFF_IN_SERVICE_STATION' || code == 'NO_QUALIFIED_STAFF' || code == 'NO_NEARBY_STAFF' || code == 'ALL_ELIGIBLE_STAFF_BUSY') {
|
||||
title = '暂未匹配成功'
|
||||
allowRetry = true
|
||||
} else if (code == 'DISPATCH_CONFLICT_RETRY') {
|
||||
title = '请重新尝试'
|
||||
allowRetry = true
|
||||
} else if (code == 'RPC_EXECUTION_FAILED' || code == 'RPC_EMPTY_RESULT') {
|
||||
title = '派单服务异常'
|
||||
allowRetry = true
|
||||
} else {
|
||||
title = '操作失败'
|
||||
allowRetry = result.retryable
|
||||
}
|
||||
|
||||
if (!allowRetry) {
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: result.message,
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: result.message,
|
||||
showCancel: true,
|
||||
cancelText: '稍后再试',
|
||||
confirmText: '重新派单',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
retryCallback(orderId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function createServiceOrder(params: CreateServiceOrderParams): Promise<ServiceOrderType | null> {
|
||||
const newTask = await tryCreateCareTask(params)
|
||||
if (newTask != null) {
|
||||
return newTask
|
||||
// 当前下单链路分为两层:
|
||||
// 1) 交易支付层:hss_service_orders(旧表,仍承担套餐价格快照与支付状态)。
|
||||
// 2) 履约工单层:ec_care_tasks(新表,应由后端在支付成功后生成/激活)。
|
||||
// 非套餐单当前走 tryCreateCareTask(LEGACY,前端直接写 ec_service_requests + ec_care_tasks),
|
||||
// 待后端提供 rpc_consumer_create_homecare_task 后应统一切到后端创建。
|
||||
if (params.packageInfo.id == '') {
|
||||
const newTask = await tryCreateCareTask(params)
|
||||
if (newTask != null) {
|
||||
return newTask
|
||||
}
|
||||
}
|
||||
const userId = getCurrentUserId()
|
||||
if (userId == '') {
|
||||
@@ -910,13 +1143,21 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
|
||||
const orderNo = buildOrderNo()
|
||||
const now = new Date().toISOString()
|
||||
const appointmentTime = normalizeAppointmentTime(params.appointmentTime)
|
||||
const pricingSnapshot = buildPricingSnapshot(params)
|
||||
const serviceSnapshot = buildServiceSnapshot(params)
|
||||
const payableAmount = params.packageInfo.price
|
||||
const response = await supa.from('hss_service_orders').insert({
|
||||
id: orderId,
|
||||
order_no: orderNo,
|
||||
user_id: userId,
|
||||
service_id: params.service.id,
|
||||
service_name: params.service.name,
|
||||
service_snapshot_json: params.service as any,
|
||||
service_snapshot_json: serviceSnapshot,
|
||||
service_package_id: params.packageInfo.id,
|
||||
pricing_snapshot_json: pricingSnapshot,
|
||||
original_amount: params.packageInfo.listPrice > 0 ? params.packageInfo.listPrice : payableAmount,
|
||||
payable_amount: payableAmount,
|
||||
total_amount: payableAmount,
|
||||
service_address_id: normalizeUuidOrNull(params.address.addressId),
|
||||
address_snapshot_json: params.address as any,
|
||||
recipient_name: params.recipientName,
|
||||
@@ -928,6 +1169,7 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
|
||||
appointment_time: appointmentTime,
|
||||
remark: params.remark,
|
||||
status: 'created',
|
||||
payment_status: 1,
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
}).execute()
|
||||
@@ -936,30 +1178,8 @@ export async function createServiceOrder(params: CreateServiceOrderParams): Prom
|
||||
return null
|
||||
}
|
||||
await insertLegacyStatusLog(orderId, '', 'created', userId, 'consumer', '创建服务订单')
|
||||
const staffObj = await getAutoAssignableStaff()
|
||||
if (staffObj != null) {
|
||||
const plainStaff = plainObject(staffObj)
|
||||
const assignmentId = buildId('sa')
|
||||
await supa.from('hss_service_assignments').insert({
|
||||
id: assignmentId,
|
||||
order_id: orderId,
|
||||
staff_id: readString(plainStaff, 'id'),
|
||||
station_id: readString(plainStaff, 'station_id') == '' ? null : readString(plainStaff, 'station_id'),
|
||||
status: 'assigned',
|
||||
assigned_at: now,
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
}).execute()
|
||||
await supa.from('hss_service_orders').update({
|
||||
status: 'assigned',
|
||||
current_assignment_id: assignmentId,
|
||||
current_staff_id: readString(plainStaff, 'id'),
|
||||
updated_at: now
|
||||
}).eq('id', orderId).execute()
|
||||
await insertLegacyStatusLog(orderId, 'created', 'assigned', userId, 'system', '系统已自动派单')
|
||||
}
|
||||
return await getLegacyServiceOrderDetail(orderId)
|
||||
}
|
||||
}
|
||||
|
||||
export async function listConsumerServiceOrders(): Promise<Array<ServiceOrderType>> {
|
||||
const userId = getCurrentUserId()
|
||||
@@ -1010,40 +1230,11 @@ export async function saveServiceRecord(orderId: string, record: DeliveryService
|
||||
}
|
||||
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||
if (careTask != null) {
|
||||
const savedAt = nowIso()
|
||||
const serviceContent = record.serviceContent.length > 0 ? record.serviceContent : record.serviceItems.map(item => item.name)
|
||||
const serviceSummary = record.serviceSummary != '' ? record.serviceSummary : record.processNote
|
||||
const healthMetrics = record.healthMetrics as any
|
||||
const familyConfirmation = record.familyConfirmation as any
|
||||
const insertResponse = await supa.from('ec_care_records').insert({
|
||||
id: buildId('care-record'),
|
||||
task_id: orderId,
|
||||
record_type: 'service_record',
|
||||
created_by: userId,
|
||||
service_items_json: record.serviceItems as any,
|
||||
service_content_json: serviceContent as any,
|
||||
service_summary: serviceSummary,
|
||||
process_note: record.processNote,
|
||||
elder_status: record.elderStatus,
|
||||
health_metrics_json: healthMetrics,
|
||||
materials_used: record.materialsUsed,
|
||||
abnormal_note: record.abnormalNote,
|
||||
photos_json: record.photos as any,
|
||||
staff_remark: record.staffRemark,
|
||||
family_confirmation_json: familyConfirmation,
|
||||
created_at: savedAt,
|
||||
updated_at: savedAt
|
||||
}).execute()
|
||||
if (insertResponse.error != null) {
|
||||
return null
|
||||
}
|
||||
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||
updated_at: savedAt
|
||||
}).eq('id', orderId).execute()
|
||||
if (updateResponse.error != null) {
|
||||
return null
|
||||
}
|
||||
await insertWorkOrderEvent(orderId, careTask.status, careTask.status, userId, 'staff', 'save_service_record', serviceSummary == '' ? '服务记录已保存' : serviceSummary)
|
||||
// TODO/GAP: consumer 端保存服务记录(ec_care_tasks 新链)暂无后端 RPC。
|
||||
// 禁止前端直接 insert ec_care_records / update ec_care_tasks。
|
||||
// 如需启用,请后端补充 rpc_consumer_save_service_record(task_id, record)。
|
||||
console.warn('[GAP] saveServiceRecord 暂不可用(新链):缺少 consumer 保存服务记录 RPC')
|
||||
uni.showToast({ title: '服务记录功能正在升级,请稍后重试', icon: 'none' })
|
||||
return await getCareTaskDetail(orderId)
|
||||
}
|
||||
const current = await getLegacyServiceOrderDetail(orderId)
|
||||
@@ -1083,31 +1274,14 @@ export async function confirmServiceOrder(orderId: string, rating: number, conte
|
||||
}
|
||||
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||
if (careTask != null) {
|
||||
const acceptedAt = nowIso()
|
||||
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||
status: 'ACCEPTED',
|
||||
accepted_by_family_at: acceptedAt,
|
||||
updated_at: acceptedAt
|
||||
}).eq('id', orderId).execute()
|
||||
if (updateResponse.error == null) {
|
||||
await insertWorkOrderEvent(orderId, 'ACCEPTANCE_PENDING', 'ACCEPTED', userId, 'consumer', 'accept_task', '用户确认验收')
|
||||
if (rating > 0 || content != '' || tags.length > 0) {
|
||||
await supa.from('ec_care_records').insert({
|
||||
id: buildId('care-review'),
|
||||
task_id: orderId,
|
||||
record_type: 'review',
|
||||
created_by: userId,
|
||||
rating,
|
||||
tags_json: tags as any,
|
||||
content,
|
||||
created_at: acceptedAt,
|
||||
updated_at: acceptedAt
|
||||
}).execute()
|
||||
await insertWorkOrderEvent(orderId, 'ACCEPTED', 'ACCEPTED', userId, 'consumer', 'submit_review', '用户提交评价')
|
||||
}
|
||||
return await getCareTaskDetail(orderId)
|
||||
}
|
||||
// TODO/GAP: consumer 确认验收(ec_care_tasks 新链)暂无后端 RPC。
|
||||
// 禁止前端直接 update ec_care_tasks / insert ec_care_records / insert hc_work_order_events。
|
||||
// 如需启用,请后端补充 rpc_consumer_confirm_acceptance(task_id, rating, feedback, tags)。
|
||||
console.warn('[GAP] confirmServiceOrder 暂不可用(新链):缺少 consumer 确认验收 RPC')
|
||||
uni.showToast({ title: '验收功能正在升级,请稍后重试', icon: 'none' })
|
||||
return await getCareTaskDetail(orderId)
|
||||
}
|
||||
// LEGACY: 以下仍为旧 hss_service_orders 链路,仅用于兼容已有交易订单。
|
||||
const current = await getServiceOrderDetail(orderId)
|
||||
if (current == null) {
|
||||
return null
|
||||
@@ -1147,31 +1321,14 @@ export async function rejectServiceOrderAcceptance(orderId: string, content: str
|
||||
}
|
||||
const careTask = shouldUseCareTaskPath(orderId) ? await getCareTaskDetail(orderId) : null
|
||||
if (careTask != null) {
|
||||
const rejectedAt = nowIso()
|
||||
const updateResponse = await supa.from('ec_care_tasks').update({
|
||||
status: 'ACCEPTANCE_REJECTED',
|
||||
updated_at: rejectedAt
|
||||
}).eq('id', orderId).execute()
|
||||
if (updateResponse.error == null) {
|
||||
await supa.from('hc_work_order_exceptions').insert({
|
||||
id: buildId('hc-ex'),
|
||||
task_id: orderId,
|
||||
exception_type: 'acceptance_rejected',
|
||||
description: content == '' ? '用户退回整改' : content,
|
||||
occurred_at: rejectedAt,
|
||||
location_text: '',
|
||||
images_json: [] as Array<string>,
|
||||
need_platform_intervention: false,
|
||||
request_cancel_order: false,
|
||||
request_reschedule: false,
|
||||
created_by: userId,
|
||||
created_at: rejectedAt,
|
||||
updated_at: rejectedAt
|
||||
}).execute()
|
||||
await insertWorkOrderEvent(orderId, 'ACCEPTANCE_PENDING', 'ACCEPTANCE_REJECTED', userId, 'consumer', 'reject_acceptance', content == '' ? '用户退回整改' : content)
|
||||
return await getCareTaskDetail(orderId)
|
||||
}
|
||||
// TODO/GAP: consumer 拒绝验收(ec_care_tasks 新链)暂无后端 RPC。
|
||||
// 禁止前端直接 update ec_care_tasks / insert hc_work_order_exceptions。
|
||||
// 如需启用,请后端补充 rpc_consumer_reject_acceptance(task_id, feedback)。
|
||||
console.warn('[GAP] rejectServiceOrderAcceptance 暂不可用(新链):缺少 consumer 拒绝验收 RPC')
|
||||
uni.showToast({ title: '验收功能正在升级,请稍后重试', icon: 'none' })
|
||||
return await getCareTaskDetail(orderId)
|
||||
}
|
||||
// LEGACY: 以下仍为旧 hss_service_orders 链路。
|
||||
const current = await getServiceOrderDetail(orderId)
|
||||
if (current == null) {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user