完善服务模块缺少付款页的bug

This commit is contained in:
2026-06-02 11:35:31 +08:00
parent c3324d459a
commit 881262940c
35 changed files with 29069 additions and 557 deletions

View File

@@ -0,0 +1,268 @@
// ============================================================
// 居家上门服务预约时间工具
// 负责:动态日期生成、常用/自定义时间段生成、时间合法性校验
// 约束:所有计算基于真实当前时间,不写死任何展示文本
// ============================================================
export const BOOKING_DAYS_LIMIT = 7
export const MIN_ADVANCE_MINUTES = 30
export const CUSTOM_SLOT_DURATION_MINUTES = 60
export const CUSTOM_SLOT_STEP_MINUTES = 30
export const PRESET_SLOT_RANGES = [
'09:00-10:00',
'10:00-11:00',
'14:00-15:00',
'15:00-16:00',
'18:00-19:00'
]
export type BookingDayOptionType = {
id: string
dateKey: string
label: string
dateText: string
weekday: string
timestamp: number
available: boolean
disabledReason: string
}
export type BookingTimeSlotType = {
id: string
label: string
startAt: number
endAt: number
source: string
available: boolean
disabledReason: string
}
export type BookingValidationResultType = {
valid: boolean
message: string
}
const WEEKDAY_LABELS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
function padLeft(num: number, length: number): string {
let result = String(num)
while (result.length < length) {
result = '0' + result
}
return result
}
function parseDateKey(dateKey: string): { year: number, month: number, day: number } | null {
if (dateKey.length != 10) {
return null
}
const year = parseInt(dateKey.substring(0, 4))
const month = parseInt(dateKey.substring(5, 7)) - 1
const day = parseInt(dateKey.substring(8, 10))
if (isNaN(year) || isNaN(month) || isNaN(day)) {
return null
}
return { year, month, day }
}
function parseTimeRange(range: string): { startH: number, startM: number, endH: number, endM: number } | null {
const parts = range.split('-')
if (parts.length != 2) {
return null
}
const startParts = parts[0].split(':')
const endParts = parts[1].split(':')
if (startParts.length != 2 || endParts.length != 2) {
return null
}
const startH = parseInt(startParts[0])
const startM = parseInt(startParts[1])
const endH = parseInt(endParts[0])
const endM = parseInt(endParts[1])
if (isNaN(startH) || isNaN(startM) || isNaN(endH) || isNaN(endM)) {
return null
}
return { startH, startM, endH, endM }
}
export function buildBookingDays(now: Date): Array<BookingDayOptionType> {
const result: Array<BookingDayOptionType> = []
const base = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)
for (let i = 0; i < BOOKING_DAYS_LIMIT; i++) {
const d = new Date(base.getTime() + i * 24 * 60 * 60 * 1000)
const year = d.getFullYear()
const month = d.getMonth() + 1
const day = d.getDate()
const weekdayIndex = d.getDay()
const dateKey = year + '-' + padLeft(month, 2) + '-' + padLeft(day, 2)
let label = ''
if (i == 0) {
label = '今天'
} else if (i == 1) {
label = '明天'
} else if (i == 2) {
label = '后天'
} else {
label = WEEKDAY_LABELS[weekdayIndex]
}
const dateText = padLeft(month, 2) + '月' + padLeft(day, 2) + '日'
const weekday = WEEKDAY_LABELS[weekdayIndex]
result.push({
id: 'day-' + String(i),
dateKey,
label,
dateText,
weekday,
timestamp: d.getTime(),
available: true,
disabledReason: ''
})
}
return result
}
export function buildPresetSlots(selectedDateKey: string, now: Date): Array<BookingTimeSlotType> {
const result: Array<BookingTimeSlotType> = []
const parsed = parseDateKey(selectedDateKey)
if (parsed == null) {
return result
}
const minStartTime = now.getTime() + MIN_ADVANCE_MINUTES * 60 * 1000
for (let i = 0; i < PRESET_SLOT_RANGES.length; i++) {
const range = PRESET_SLOT_RANGES[i]
const time = parseTimeRange(range)
if (time == null) {
continue
}
const startDate = new Date(parsed.year, parsed.month, parsed.day, time.startH, time.startM, 0, 0)
const endDate = new Date(parsed.year, parsed.month, parsed.day, time.endH, time.endM, 0, 0)
const startAt = startDate.getTime()
const endAt = endDate.getTime()
const available = startAt >= minStartTime
result.push({
id: 'preset-' + String(i),
label: range,
startAt,
endAt,
source: 'preset',
available,
disabledReason: available ? '' : '该时段距离当前不足30分钟无法保证服务人员按时到达'
})
}
return result
}
export function buildCustomSlots(selectedDateKey: string, now: Date): Array<BookingTimeSlotType> {
const result: Array<BookingTimeSlotType> = []
const parsed = parseDateKey(selectedDateKey)
if (parsed == null) {
return result
}
const minStartTime = now.getTime() + MIN_ADVANCE_MINUTES * 60 * 1000
for (let h = 0; h < 24; h++) {
for (let m = 0; m < 60; m += CUSTOM_SLOT_STEP_MINUTES) {
const startDate = new Date(parsed.year, parsed.month, parsed.day, h, m, 0, 0)
const endDate = new Date(parsed.year, parsed.month, parsed.day, h, m + CUSTOM_SLOT_DURATION_MINUTES, 0, 0)
const startAt = startDate.getTime()
const endAt = endDate.getTime()
const available = startAt >= minStartTime
const startLabel = padLeft(h, 2) + ':' + padLeft(m, 2)
const endLabel = padLeft(endDate.getHours(), 2) + ':' + padLeft(endDate.getMinutes(), 2)
result.push({
id: 'custom-' + startLabel + '-' + endLabel,
label: startLabel + '-' + endLabel,
startAt,
endAt,
source: 'custom',
available,
disabledReason: available ? '' : '该时段距离当前不足30分钟无法保证服务人员按时到达'
})
}
}
return result
}
export function hasAnyAvailableSlots(dateKey: string, now: Date): boolean {
const presets = buildPresetSlots(dateKey, now)
for (let i = 0; i < presets.length; i++) {
if (presets[i].available) {
return true
}
}
const customs = buildCustomSlots(dateKey, now)
for (let i = 0; i < customs.length; i++) {
if (customs[i].available) {
return true
}
}
return false
}
export function validateSelectedAppointmentTime(
selectedDay: BookingDayOptionType | null,
selectedSlot: BookingTimeSlotType | null,
now: Date
): BookingValidationResultType {
if (selectedDay == null) {
return { valid: false, message: '请选择预约日期' }
}
if (selectedSlot == null) {
return { valid: false, message: '请选择预约时间段' }
}
const slotStartDate = new Date(selectedSlot.startAt)
const dayDate = new Date(selectedDay.timestamp)
const sameDay = slotStartDate.getFullYear() == dayDate.getFullYear()
&& slotStartDate.getMonth() == dayDate.getMonth()
&& slotStartDate.getDate() == dayDate.getDate()
if (!sameDay) {
return { valid: false, message: '所选时间段不属于当前选中的日期' }
}
const minStartTime = now.getTime() + MIN_ADVANCE_MINUTES * 60 * 1000
if (selectedSlot.startAt < minStartTime) {
return { valid: false, message: '当前选择的上门时间已失效,请重新选择稍晚的时间段' }
}
const maxDate = new Date(now.getTime() + BOOKING_DAYS_LIMIT * 24 * 60 * 60 * 1000)
const maxDateStart = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 0, 0, 0, 0)
if (selectedDay.timestamp > maxDateStart.getTime()) {
return { valid: false, message: '预约日期超出可预约范围' }
}
return { valid: true, message: '' }
}
export function formatAppointmentDisplayText(
day: BookingDayOptionType | null,
slot: BookingTimeSlotType | null
): string {
if (day == null || slot == null) {
return '请选择上门时间'
}
return day.label + ' ' + day.dateText + ' ' + day.weekday + ' ' + slot.label
}
export function formatStandardAppointmentTime(
day: BookingDayOptionType | null,
slot: BookingTimeSlotType | null
): string {
if (day == null || slot == null) {
return ''
}
return new Date(slot.startAt).toISOString()
}

View File

@@ -1,4 +1,8 @@
import { HomeServiceCatalogType } from '@/types/home-service.uts'
import {
BookingDayOptionType,
BookingTimeSlotType
} from '@/utils/homeServiceBookingTime.uts'
// TODO: 接入真实服务分类、机构与可预约时段接口后替换这些 UI mock。
@@ -37,19 +41,6 @@ export type HomeServiceItemType = {
suitableFor: string
}
export type BookingTimeSlotType = {
id: string
label: string
available: boolean
}
export type BookingDayOptionType = {
id: string
label: string
dateText: string
weekday: string
}
export type HomeServiceAgencyType = {
id: string
name: string
@@ -97,21 +88,6 @@ const FALLBACK_HOME_SERVICE_ITEMS: Array<HomeServiceItemType> = [
{ id: 'svc-ui-004', title: '慢病随访关怀', subtitle: '血压血糖记录 用药核对 健康宣教', category: 'chronic-follow', price: 118, unit: '次', tags: ['可预约', '记录留档', '明码标价'], imageText: '随访', suitableFor: '慢病 长者 家庭' }
]
const BOOKING_DAY_OPTIONS: Array<BookingDayOptionType> = [
{ id: 'day-1', label: '今天', dateText: '05/18', weekday: '周一' },
{ id: 'day-2', label: '明天', dateText: '05/19', weekday: '周二' },
{ id: 'day-3', label: '后天', dateText: '05/20', weekday: '周三' },
{ id: 'day-4', label: '周四', dateText: '05/21', weekday: '周四' }
]
const BOOKING_TIME_SLOTS: Array<BookingTimeSlotType> = [
{ id: 'slot-1', label: '09:00-10:00', available: true },
{ id: 'slot-2', label: '10:00-11:00', available: true },
{ id: 'slot-3', label: '14:00-15:00', available: true },
{ id: 'slot-4', label: '15:00-16:00', available: true },
{ id: 'slot-5', label: '18:00-19:00', available: false }
]
const SERVICE_GUARANTEES: Array<HomeServiceGuaranteeItemType> = [
{ id: 'guarantee-1', label: '平台认证' },
{ id: 'guarantee-2', label: '明码标价' },
@@ -188,14 +164,6 @@ export function getHomeServiceItems(catalog: Array<HomeServiceCatalogType>): Arr
return result
}
export function getBookingDayOptions(): Array<BookingDayOptionType> {
return BOOKING_DAY_OPTIONS.map((item) => ({ ...item }))
}
export function getBookingTimeSlots(): Array<BookingTimeSlotType> {
return BOOKING_TIME_SLOTS.map((item) => ({ ...item }))
}
export function getServiceGuarantees(): Array<HomeServiceGuaranteeItemType> {
return SERVICE_GUARANTEES.map((item) => ({ ...item }))
}

161
utils/homecareStatus.uts Normal file
View File

@@ -0,0 +1,161 @@
// =============================================================================
// 居家服务统一状态常量与映射
// 说明:
// - 本文件对齐数据库 ec_care_tasks.status 口径与 ORDER_* 枚举。
// - delivery / consumer 两端共用同一套状态常量,禁止各自维护不同文案。
// - 状态流转必须由后端 RPC 推导,前端只做展示与权限判断。
// =============================================================================
// 工单主状态(来自 ec_care_tasks.status
export const ORDER_CREATED = 'ORDER_CREATED'
export const ORDER_ASSIGNED = 'ORDER_ASSIGNED'
export const ORDER_ACCEPTED = 'ORDER_ACCEPTED'
export const ORDER_REJECTED = 'ORDER_REJECTED'
export const ORDER_CHECKED_IN = 'ORDER_CHECKED_IN'
export const ORDER_IN_SERVICE = 'ORDER_IN_SERVICE'
export const ORDER_EXCEPTION = 'ORDER_EXCEPTION'
export const ORDER_COMPLETED = 'ORDER_COMPLETED'
export const ACCEPTANCE_PENDING = 'ACCEPTANCE_PENDING'
export const ACCEPTED = 'ACCEPTED'
export const ACCEPTANCE_REJECTED = 'ACCEPTANCE_REJECTED'
export const SETTLEMENT_READY = 'SETTLEMENT_READY'
export const ARCHIVED = 'ARCHIVED'
export const ORDER_CANCELLED = 'ORDER_CANCELLED'
export const ORDER_CLOSED = 'ORDER_CLOSED'
// 合法状态集合(用于校验)
export const HOMEcare_TASK_STATUSES: Array<string> = [
ORDER_CREATED,
ORDER_ASSIGNED,
ORDER_ACCEPTED,
ORDER_REJECTED,
ORDER_CHECKED_IN,
ORDER_IN_SERVICE,
ORDER_EXCEPTION,
ORDER_COMPLETED,
ACCEPTANCE_PENDING,
ACCEPTED,
ACCEPTANCE_REJECTED,
SETTLEMENT_READY,
ARCHIVED,
ORDER_CANCELLED,
ORDER_CLOSED
]
// consumer 端展示文案
export function getHomecareTaskStatusText(status: string): string {
if (status == ORDER_CREATED) return '待安排服务'
if (status == ORDER_ASSIGNED) return '已安排服务人员,等待接单'
if (status == ORDER_ACCEPTED) return '服务人员已接单,等待上门'
if (status == ORDER_REJECTED) return '正在重新安排人员'
if (status == ORDER_CHECKED_IN) return '服务人员已到达'
if (status == ORDER_IN_SERVICE) return '服务进行中'
if (status == ORDER_EXCEPTION) return '服务处理异常中'
if (status == ORDER_COMPLETED) return '服务已完成,待验收'
if (status == ACCEPTANCE_PENDING) return '待验收'
if (status == ACCEPTED) return '已验收'
if (status == ACCEPTANCE_REJECTED) return '验收未通过'
if (status == SETTLEMENT_READY) return '待结算'
if (status == ARCHIVED) return '已归档'
if (status == ORDER_CANCELLED) return '已取消'
if (status == ORDER_CLOSED) return '已关闭'
return status
}
// 主题色/标签色(用于 UI 标签)
export function getHomecareTaskStatusTheme(status: string): string {
if (status == ORDER_CREATED) return 'warning'
if (status == ORDER_ASSIGNED) return 'primary'
if (status == ORDER_ACCEPTED) return 'primary'
if (status == ORDER_REJECTED) return 'danger'
if (status == ORDER_CHECKED_IN) return 'success'
if (status == ORDER_IN_SERVICE) return 'success'
if (status == ORDER_EXCEPTION) return 'danger'
if (status == ORDER_COMPLETED) return 'primary'
if (status == ACCEPTANCE_PENDING) return 'warning'
if (status == ACCEPTED) return 'success'
if (status == ACCEPTANCE_REJECTED) return 'danger'
if (status == SETTLEMENT_READY) return 'warning'
if (status == ARCHIVED) return 'info'
if (status == ORDER_CANCELLED) return 'info'
if (status == ORDER_CLOSED) return 'info'
return 'default'
}
// 给定角色与状态,返回允许的前端操作标识
export function getHomecareTaskAllowedActions(status: string, role: string): Array<string> {
const actions = [] as Array<string>
if (role == 'FAMILY_USER' || role == 'consumer') {
if (status == ORDER_COMPLETED || status == ACCEPTANCE_PENDING) {
actions.push('confirm_acceptance')
actions.push('reject_acceptance')
}
}
if (role == 'HOMECARE_WORKER' || role == 'delivery' || role == 'staff') {
if (status == ORDER_ASSIGNED) {
actions.push('accept')
actions.push('reject')
}
if (status == ORDER_ACCEPTED) {
actions.push('checkin')
actions.push('report_exception')
}
if (status == ORDER_CHECKED_IN) {
actions.push('start_service')
actions.push('report_exception')
}
if (status == ORDER_IN_SERVICE) {
actions.push('save_record')
actions.push('upload_evidence')
actions.push('report_exception')
actions.push('finish_service')
}
}
if (role == 'HOMECARE_DISPATCHER' || role == 'dispatcher') {
if (status == ORDER_CREATED || status == ORDER_REJECTED || status == ORDER_EXCEPTION) {
actions.push('dispatch')
}
}
return actions
}
// 将旧 hss / delivery 前端状态归一化为 ORDER_* 状态
export function normalizeToOrderStatus(raw: string): string {
const s = raw.trim().toUpperCase()
if (s == 'CREATED' || s == 'SUBMITTED') return ORDER_CREATED
if (s == 'PAID') return ORDER_CREATED
if (s == 'ASSIGNED' || s == 'PENDING_ASSIGNMENT' || s == 'PENDING_DISPATCH' || s == 'PENDING_ACCEPT') return ORDER_ASSIGNED
if (s == 'ACCEPTED' || s == 'PENDING_ACCEPT') return ORDER_ACCEPTED
if (s == 'REJECTED') return ORDER_REJECTED
if (s == 'DEPARTED' || s == 'ON_THE_WAY' || s == 'WAITING_DEPARTURE') return ORDER_ACCEPTED
if (s == 'ARRIVED' || s == 'CHECKED_IN') return ORDER_CHECKED_IN
if (s == 'IN_SERVICE' || s == 'SERVING') return ORDER_IN_SERVICE
if (s == 'COMPLETED') return ORDER_COMPLETED
if (s == 'PENDING_ACCEPTANCE' || s == 'PENDING_CONFIRM' || s == 'PENDING_SUBMIT') return ACCEPTANCE_PENDING
if (s == 'ACCEPTED_BY_USER') return ACCEPTED
if (s == 'REVIEWED') return ACCEPTED
if (s == 'SETTLED') return SETTLEMENT_READY
if (s == 'EXCEPTION' || s == 'ABNORMAL' || s == 'EXCEPTION_PENDING') return ORDER_EXCEPTION
if (s == 'CANCELLED') return ORDER_CANCELLED
if (s == 'ARCHIVED') return ARCHIVED
if (s == 'ORDER_CREATED') return ORDER_CREATED
if (s == 'ORDER_ASSIGNED') return ORDER_ASSIGNED
if (s == 'ORDER_ACCEPTED') return ORDER_ACCEPTED
if (s == 'ORDER_REJECTED') return ORDER_REJECTED
if (s == 'ORDER_CHECKED_IN') return ORDER_CHECKED_IN
if (s == 'ORDER_IN_SERVICE') return ORDER_IN_SERVICE
if (s == 'ORDER_EXCEPTION') return ORDER_EXCEPTION
if (s == 'ORDER_COMPLETED') return ORDER_COMPLETED
if (s == 'ACCEPTANCE_PENDING') return ACCEPTANCE_PENDING
if (s == 'ACCEPTED') return ACCEPTED
if (s == 'ACCEPTANCE_REJECTED') return ACCEPTANCE_REJECTED
if (s == 'SETTLEMENT_READY') return SETTLEMENT_READY
if (s == 'ORDER_CANCELLED') return ORDER_CANCELLED
if (s == 'ORDER_CLOSED') return ORDER_CLOSED
return raw
}
// 判断是否为已关闭/终态
export function isHomecareTaskClosed(status: string): boolean {
return status == ARCHIVED || status == ORDER_CANCELLED || status == ORDER_CLOSED || status == ACCEPTED || status == SETTLEMENT_READY
}

View File

@@ -4695,16 +4695,26 @@ class SupabaseService {
return 3
}
private matchesServiceStatusTab(status: string, statusTab: string): boolean {
private matchesServiceStatusTab(status: string, statusTab: string, dispatchStatus: string = ''): boolean {
if (statusTab == 'all') return true
const normalizedStatus = this.normalizeServiceStatus(status)
if (statusTab == 'pending') return normalizedStatus == 'created'
if (statusTab == 'accepted') return normalizedStatus == 'paid' || normalizedStatus == 'assigned'
if (statusTab == 'accepted') {
if (normalizedStatus == 'paid' && dispatchStatus != 'assigned' && dispatchStatus != 'failed') {
return true
}
return normalizedStatus == 'assigned'
}
if (statusTab == 'scheduled') return normalizedStatus == 'accepted' || normalizedStatus == 'departed'
if (statusTab == 'inservice') return normalizedStatus == 'arrived' || normalizedStatus == 'in_service'
if (statusTab == 'completed') return normalizedStatus == 'completed' || normalizedStatus == 'pending_acceptance' || normalizedStatus == 'accepted_by_user' || normalizedStatus == 'reviewed' || normalizedStatus == 'settled'
if (statusTab == 'aftersale') return false
if (statusTab == 'inprogress') return normalizedStatus == 'paid' || normalizedStatus == 'assigned' || normalizedStatus == 'accepted' || normalizedStatus == 'departed' || normalizedStatus == 'arrived' || normalizedStatus == 'in_service'
if (statusTab == 'inprogress') {
if (normalizedStatus == 'paid' && dispatchStatus != 'failed') {
return true
}
return normalizedStatus == 'assigned' || normalizedStatus == 'accepted' || normalizedStatus == 'departed' || normalizedStatus == 'arrived' || normalizedStatus == 'in_service'
}
return true
}
@@ -4746,8 +4756,10 @@ class SupabaseService {
const orderObj = JSON.parse(JSON.stringify(rawOrder)) as UTSJSONObject
const addressSnapshotRaw = orderObj.get('address_snapshot_json')
const serviceSnapshotRaw = orderObj.get('service_snapshot_json')
const pricingSnapshotRaw = orderObj.get('pricing_snapshot_json')
let addressObj: UTSJSONObject | null = null
let serviceObj: UTSJSONObject | null = null
let pricingObj: UTSJSONObject | null = null
try {
if (addressSnapshotRaw != null) {
@@ -4775,11 +4787,33 @@ class SupabaseService {
serviceObj = null
}
try {
if (pricingSnapshotRaw != null) {
const pricingText = JSON.stringify(pricingSnapshotRaw)
if (pricingText.startsWith('"')) {
pricingObj = JSON.parse(orderObj.getString('pricing_snapshot_json') ?? '{}') as UTSJSONObject
} else {
pricingObj = JSON.parse(pricingText) as UTSJSONObject
}
}
} catch (e) {
pricingObj = null
}
const normalizedStatus = this.getUnifiedServiceStatusNumber(orderObj.getString('status') ?? '')
const fullAddress = addressObj != null ? (addressObj.getString('fullAddress') ?? '') : ''
const providerName = orderObj.getString('staff_name') ?? ''
const serviceName = orderObj.getString('service_name') ?? (serviceObj != null ? (serviceObj.getString('serviceName') ?? '') : '')
const servicePrice = serviceObj != null ? (serviceObj.getNumber('price') ?? 0) : 0
let servicePrice = orderObj.getNumber('total_amount') ?? 0
if (servicePrice <= 0 && orderObj.getNumber('payable_amount') != null) {
servicePrice = orderObj.getNumber('payable_amount') ?? 0
}
if (servicePrice <= 0 && pricingObj != null) {
servicePrice = pricingObj.getNumber('price') ?? 0
}
if (servicePrice <= 0 && serviceObj != null) {
servicePrice = serviceObj.getNumber('price') ?? 0
}
const serviceInfo = new UTSJSONObject()
serviceInfo.set('service_name', serviceName)
@@ -4789,6 +4823,8 @@ class SupabaseService {
serviceInfo.set('contact_name', orderObj.getString('contact_name') ?? '')
serviceInfo.set('contact_phone', orderObj.getString('contact_phone') ?? '')
serviceInfo.set('provider_name', providerName)
serviceInfo.set('package_name', pricingObj != null ? (pricingObj.getString('package_name') ?? '') : '')
serviceInfo.set('pricing_data_source', pricingObj != null ? (pricingObj.getString('data_source') ?? '') : '')
const unifiedOrder = new UTSJSONObject()
const rawPaymentStatus = orderObj.getNumber('payment_status')
@@ -4817,10 +4853,16 @@ class SupabaseService {
unifiedOrder.set('shipping_fee', 0)
unifiedOrder.set('total_amount', servicePrice)
unifiedOrder.set('paid_amount', servicePrice)
unifiedOrder.set('pricing_data_source', pricingObj != null ? (pricingObj.getString('data_source') ?? '') : '')
const dispatchStatus = orderObj.getString('dispatch_status') ?? ''
const dispatchErrorMessage = orderObj.getString('dispatch_error_message') ?? ''
unifiedOrder.set('merchant_id', orderObj.getString('current_staff_id') ?? '')
unifiedOrder.set('shop_name', providerName != '' ? providerName : '康养上门服务')
unifiedOrder.set('service_info', serviceInfo)
unifiedOrder.set('ml_order_items', [] as UTSJSONObject[])
unifiedOrder.set('dispatch_status', dispatchStatus)
unifiedOrder.set('dispatch_error_code', orderObj.getString('dispatch_error_code') ?? '')
unifiedOrder.set('dispatch_error_message', dispatchErrorMessage)
return unifiedOrder
}
@@ -4870,7 +4912,7 @@ class SupabaseService {
if (!matchedKeyword) {
continue
}
if (!this.matchesServiceStatusTab(rawObj.getString('status') ?? '', params.statusTab)) {
if (!this.matchesServiceStatusTab(rawObj.getString('status') ?? '', params.statusTab, rawObj.getString('dispatch_status') ?? '')) {
continue
}
filtered.push(this.buildUnifiedServiceOrder(rawItem))
@@ -5016,7 +5058,7 @@ class SupabaseService {
const response = await supa
.from('hss_service_orders')
.select('status')
.select('status,payment_status,dispatch_status')
.eq('user_id', userId)
.is('consumer_deleted_at', null)
.limit(500)
@@ -5031,9 +5073,11 @@ class SupabaseService {
for (let i = 0; i < rawList.length; i++) {
const rawObj = JSON.parse(JSON.stringify(rawList[i])) as UTSJSONObject
const normalizedStatus = this.normalizeServiceStatus(rawObj.getString('status') ?? '')
if (normalizedStatus == 'created') {
const dispatchStatus = rawObj.getString('dispatch_status') ?? ''
const paymentStatus = rawObj.getNumber('payment_status') ?? 0
if (normalizedStatus == 'created' && paymentStatus == 1) {
counts.set('pending', (counts.getNumber('pending') ?? 0) + 1)
} else if (normalizedStatus == 'paid' || normalizedStatus == 'assigned') {
} else if ((normalizedStatus == 'paid' || normalizedStatus == 'assigned') && dispatchStatus != 'failed') {
counts.set('accepted', (counts.getNumber('accepted') ?? 0) + 1)
} else if (normalizedStatus == 'accepted' || normalizedStatus == 'departed') {
counts.set('scheduled', (counts.getNumber('scheduled') ?? 0) + 1)
@@ -5041,6 +5085,8 @@ class SupabaseService {
counts.set('inservice', (counts.getNumber('inservice') ?? 0) + 1)
} else if (normalizedStatus == 'completed' || normalizedStatus == 'pending_acceptance' || normalizedStatus == 'accepted_by_user' || normalizedStatus == 'reviewed' || normalizedStatus == 'settled') {
counts.set('completed', (counts.getNumber('completed') ?? 0) + 1)
} else if (dispatchStatus == 'failed') {
counts.set('accepted', (counts.getNumber('accepted') ?? 0) + 1)
}
}
return counts
@@ -5608,6 +5654,10 @@ class SupabaseService {
console.error('[payUnifiedOrder] 订单状态已变更,拒绝支付:', latestStatus, latestPaymentStatus)
return false
}
if (source == 'service' && (latestOrder.getString('pricing_data_source') ?? '') == 'dev_seed') {
console.error('[payUnifiedOrder] 测试套餐订单禁止真实支付')
return false
}
const tableName = this.getUnifiedOrderTableName(orderId, source)
const isService = tableName == 'hss_service_orders'