完善服务模块缺少付款页的bug
This commit is contained in:
268
utils/homeServiceBookingTime.uts
Normal file
268
utils/homeServiceBookingTime.uts
Normal 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()
|
||||
}
|
||||
@@ -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
161
utils/homecareStatus.uts
Normal 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
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user