Explorar o código

pc端退款界面开发

徐滕 hai 2 semanas
pai
achega
92c99a47ed

+ 4 - 16
forge-admin-ui/src/views/hotel/order.vue

@@ -197,6 +197,7 @@
 <script setup>
 <script setup>
 import { NDataTable, NInput, NModal, NSpin, useDialog, useMessage } from 'naive-ui'
 import { NDataTable, NInput, NModal, NSpin, useDialog, useMessage } from 'naive-ui'
 import { computed, h, onMounted, onUnmounted, ref, watch } from 'vue'
 import { computed, h, onMounted, onUnmounted, ref, watch } from 'vue'
+import { useRouter } from 'vue-router'
 import {
 import {
   acceptOrder,
   acceptOrder,
   approveRefund,
   approveRefund,
@@ -206,7 +207,6 @@ import {
   getOrderDashboard,
   getOrderDashboard,
   getOrderDetail,
   getOrderDetail,
   getOrderPage,
   getOrderPage,
-  hotelRefund,
   orderRefund,
   orderRefund,
   prepareOrder,
   prepareOrder,
   readyOrder,
   readyOrder,
@@ -226,6 +226,7 @@ const dialog = useDialog()
 const { dict } = useDict('hotel_order_status', 'hotel_pay_method', 'hotel_refund_status')
 const { dict } = useDict('hotel_order_status', 'hotel_pay_method', 'hotel_refund_status')
 
 
 const crudRef = ref(null)
 const crudRef = ref(null)
+const router = useRouter()
 
 
 // ==================== 暂停接单状态 ====================
 // ==================== 暂停接单状态 ====================
 const isPaused = ref(false)
 const isPaused = ref(false)
@@ -628,22 +629,9 @@ function handleAction(action, row) {
         },
         },
       })
       })
       break
       break
-    case 'hotelRefund': {
-      // 待支付订单走的是「取消」而不是「退单」:无资金流转,后端置 11已取消
-      const isCancel = row.status === 0
-      openReasonModal(isCancel ? '取消订单' : '前台退单', async (reason) => {
-        try {
-          await hotelRefund(row.id, reason)
-          message.success(isCancel ? '订单已取消' : '退单成功')
-        }
-        catch {
-          // 退款失败时订单不会变为已退单,错误详情已由请求拦截器统一弹窗展示
-        }
-        crudRef.value?.refresh()
-        loadDashboard()
-      })
+    case 'hotelRefund':
+      router.push({ path: '/hotel/orderRefund', query: { id: row.id } })
       break
       break
-    }
     case 'approveRefund':
     case 'approveRefund':
       dialog.warning({
       dialog.warning({
         title: '同意退单',
         title: '同意退单',

+ 510 - 0
forge-admin-ui/src/views/hotel/orderRefund.vue

@@ -0,0 +1,510 @@
+<template>
+  <div class="order-refund-page">
+    <div class="page-header">
+      <NButton text @click="handleBack">
+        ← 返回列表
+      </NButton>
+      <span class="page-title">订单详情 · {{ orderData?.orderNo || '' }}</span>
+    </div>
+    <div class="page-subtitle">
+      宾馆前台可查看完整信息,并可在此页面进行退单操作
+    </div>
+
+    <NSpin :show="loading">
+      <div v-if="orderData" class="refund-content">
+        <!-- 订单信息卡片 -->
+        <div class="info-card">
+          <div class="card-header">
+            <div>
+              <span class="room-name">{{ orderData.roomNo }} {{ orderData.roomName || '' }}</span>
+              <div class="order-meta">
+                {{ orderData.orderNo }} · {{ orderData.createTime }}
+              </div>
+            </div>
+            <DictTag dict-type="hotel_order_status" :value="String(orderData.status)" size="medium" />
+          </div>
+
+          <div class="card-info-row">
+            <div class="info-item">
+              <span class="info-label">联系人</span>
+              <span class="info-value">{{ orderData.contactName }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">联系电话</span>
+              <span class="info-value">{{ orderData.contactPhone }}</span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">支付方式</span>
+              <span class="info-value">
+                <DictTag v-if="orderData.payMethod" dict-type="hotel_pay_method" :value="orderData.payMethod" size="small" />
+                <span v-else>-</span>
+              </span>
+            </div>
+            <div class="info-item">
+              <span class="info-label">金额</span>
+              <span class="info-value amount">¥{{ orderData.totalAmount }}</span>
+            </div>
+          </div>
+
+          <!-- 菜品明细 -->
+          <div class="items-section">
+            <div class="items-title">
+              菜品明细
+            </div>
+            <div
+              v-for="(item, idx) in (orderData.items || [])"
+              :key="idx"
+              class="item-row"
+            >
+              <span>{{ item.dishName }} ×{{ item.quantity }} — ¥{{ item.subtotal }}</span>
+              <span v-if="item.specDesc" class="item-spec">({{ item.specDesc }})</span>
+            </div>
+            <div class="items-total">
+              合计 ¥{{ orderData.totalAmount }} · 免配送费
+            </div>
+          </div>
+        </div>
+
+        <!-- 退单操作区域 -->
+        <div class="refund-action-card">
+          <div class="refund-warning-title">
+            ⚠ 退单操作
+          </div>
+          <div class="refund-warning-text">
+            该订单当前状态为「<DictTag dict-type="hotel_order_status" :value="String(orderData.status)" size="small" />」,
+            宾馆前台可在任何阶段发起退单。退单后款项将原路返回至客人支付账户。
+          </div>
+          <div class="refund-form-row">
+            <NInput
+              v-model:value="refundReason"
+              type="textarea"
+              placeholder="请输入退单原因..."
+              :rows="2"
+              class="refund-reason-input"
+            />
+            <NButton
+              type="error"
+              size="large"
+              :loading="submitting"
+              :disabled="!refundReason || refundReason.trim() === ''"
+              @click="handleRefundConfirm"
+            >
+              确认退单 · ¥{{ orderData.totalAmount }}
+            </NButton>
+          </div>
+        </div>
+
+        <!-- 状态时间线 -->
+        <div v-if="hasTimeInfo" class="timeline-section">
+          <div class="timeline-title">
+             状态时间线
+          </div>
+          <div class="timeline-list">
+            <!-- 下单 -->
+            <div class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('create')]" />
+              <span class="timeline-text">{{ formatTime(orderData.createTime) }} · 顾客下单</span>
+            </div>
+            <!-- 支付 -->
+            <div v-if="orderData.payTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('pay')]" />
+              <span class="timeline-text">{{ formatTime(orderData.payTime) }} · 支付成功 · ¥{{ orderData.paidAmount || orderData.totalAmount }}</span>
+            </div>
+            <!-- 接单 -->
+            <div v-if="orderData.acceptTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('accept')]" />
+              <span class="timeline-text">{{ formatTime(orderData.acceptTime) }} · 餐厅前台接单</span>
+            </div>
+            <!-- 备餐 -->
+            <div v-if="orderData.prepareTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('prepare')]" />
+              <span class="timeline-text">{{ formatTime(orderData.prepareTime) }} · 开始备餐</span>
+            </div>
+            <!-- 出餐 -->
+            <div v-if="orderData.readyTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('ready')]" />
+              <span class="timeline-text">{{ formatTime(orderData.readyTime) }} · 出餐完成</span>
+            </div>
+            <!-- 配送 -->
+            <div v-if="orderData.deliverTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('deliver')]" />
+              <span class="timeline-text">{{ formatTime(orderData.deliverTime) }} · 开始配送</span>
+            </div>
+            <!-- 完成 -->
+            <div v-if="orderData.completeTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('complete')]" />
+              <span class="timeline-text">{{ formatTime(orderData.completeTime) }} · 配送完成</span>
+            </div>
+            <!-- 拒单 -->
+            <div v-if="orderData.rejectTime" class="timeline-item">
+              <span class="timeline-dot reject" />
+              <span class="timeline-text">{{ formatTime(orderData.rejectTime) }} · 已拒单{{ orderData.rejectReason ? ':' + orderData.rejectReason : '' }}</span>
+            </div>
+            <!-- 退单申请 -->
+            <div v-if="orderData.refundApplyTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('refundApply')]" />
+              <span class="timeline-text">{{ formatTime(orderData.refundApplyTime) }} · 发起退单申请{{ orderData.refundReason ? ':' + orderData.refundReason : '' }}</span>
+            </div>
+            <!-- 退单审核 -->
+            <div v-if="orderData.refundAuditTime" class="timeline-item">
+              <span :class="['timeline-dot', getDotClass('refundAudit')]" />
+              <span class="timeline-text">{{ formatTime(orderData.refundAuditTime) }} · 退单审核{{ orderData.status === 9 ? '通过' : '驳回' }}</span>
+            </div>
+            <!-- 退款完成 -->
+            <div v-if="orderData.refundTime" class="timeline-item">
+              <span class="timeline-dot refund" />
+              <span class="timeline-text">{{ formatTime(orderData.refundTime) }} · 退款完成 · ¥{{ orderData.refundAmount || orderData.totalAmount }}</span>
+            </div>
+            <!-- 取消 -->
+            <div v-if="orderData.cancelTime" class="timeline-item">
+              <span class="timeline-dot cancel" />
+              <span class="timeline-text">{{ formatTime(orderData.cancelTime) }} · 订单已取消</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </NSpin>
+  </div>
+</template>
+
+<script setup>
+import { NButton, NInput, NSpin, useMessage } from 'naive-ui'
+import { computed, onMounted, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { getOrderDetail, hotelRefund } from '@/api/hotel'
+import DictTag from '@/components/DictTag.vue'
+import { useDict } from '@/composables/useDict'
+
+defineOptions({ name: 'HotelOrderRefund' })
+
+useDict('hotel_order_status', 'hotel_pay_method')
+
+const router = useRouter()
+const route = useRoute()
+const message = useMessage()
+
+const loading = ref(false)
+const orderData = ref(null)
+const refundReason = ref('')
+const submitting = ref(false)
+
+const hasTimeInfo = computed(() => {
+  if (!orderData.value)
+    return false
+  const d = orderData.value
+  return d.payTime || d.acceptTime || d.prepareTime || d.readyTime || d.deliverTime
+    || d.completeTime || d.rejectTime || d.refundApplyTime || d.refundAuditTime
+    || d.refundTime || d.cancelTime
+})
+
+/**
+ * 根据步骤类型和订单状态返回圆点样式类
+ * - 终端态(拒单/退款完成/取消):固定颜色
+ * - 正常流程中:已完成=绿色,当前进行中=黄色
+ */
+function getDotClass(stepType) {
+  if (!orderData.value)
+    return ''
+  const status = orderData.value.status
+
+  // 终端态:始终用固定颜色
+  if (stepType === 'reject')
+    return 'reject'
+  if (stepType === 'cancel')
+    return 'cancel'
+
+  // 正常流程 + 退单流程:判断该步骤是否已完成
+  // 步骤对应的状态值
+  const stepStatusMap = {
+    create: 0,
+    pay: 1,
+    accept: 2,
+    prepare: 3,
+    ready: 4,
+    deliver: 5,
+    complete: 6,
+    refundApply: 8,
+    refundAudit: 9,
+  }
+  const stepStatus = stepStatusMap[stepType]
+  if (stepStatus === undefined)
+    return ''
+
+  // 如果订单状态已超过该步骤 → 已完成 → 绿色
+  if (status > stepStatus)
+    return 'completed'
+
+  // 否则是当前进行中的步骤 → 黄色
+  return 'active'
+}
+
+function formatTime(timeStr) {
+  if (!timeStr)
+    return ''
+  const match = String(timeStr).match(/(\d{2}:\d{2})/)
+  return match ? match[1] : timeStr
+}
+
+async function loadOrderDetail() {
+  const id = route.query.id
+  if (!id) {
+    message.error('缺少订单ID')
+    router.back()
+    return
+  }
+  loading.value = true
+  try {
+    const res = await getOrderDetail(id)
+    if (res.code === 200) {
+      orderData.value = res.data
+    }
+  }
+  catch {
+    message.error('加载订单详情失败')
+  }
+  finally {
+    loading.value = false
+  }
+}
+
+function handleBack() {
+  router.back()
+}
+
+async function handleRefundConfirm() {
+  if (!refundReason.value || refundReason.value.trim() === '') {
+    message.warning('请输入退单原因')
+    return
+  }
+  submitting.value = true
+  try {
+    await hotelRefund(orderData.value.id, refundReason.value.trim())
+    message.success('退单成功')
+    router.back()
+  }
+  catch {
+    // 退款失败时订单不会变为已退单,错误详情已由请求拦截器统一弹窗展示
+  }
+  finally {
+    submitting.value = false
+  }
+}
+
+onMounted(() => {
+  loadOrderDetail()
+})
+</script>
+
+<style scoped>
+.order-refund-page {
+  padding: 20px 24px;
+  max-width: 800px;
+  margin: 0 auto;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 4px;
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 700;
+  color: #333;
+}
+
+.page-subtitle {
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 20px;
+}
+
+.refund-content {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+/* ==================== 信息卡片 ==================== */
+.info-card {
+  background: #fff;
+  border: 1px solid #f0f0f0;
+  border-radius: 8px;
+  padding: 20px;
+}
+
+.card-header {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 16px;
+}
+
+.room-name {
+  font-size: 18px;
+  font-weight: 700;
+  color: #333;
+}
+
+.order-meta {
+  font-size: 13px;
+  color: #999;
+  margin-top: 4px;
+}
+
+.card-info-row {
+  display: flex;
+  gap: 32px;
+  flex-wrap: wrap;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 16px;
+}
+
+.info-item {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.info-label {
+  font-size: 12px;
+  color: #999;
+}
+
+.info-value {
+  font-size: 14px;
+  color: #333;
+}
+
+.info-value.amount {
+  font-weight: 700;
+  color: #f0a020;
+  font-size: 16px;
+}
+
+/* ==================== 菜品明细 ==================== */
+.items-section {
+  background: #f8f9fa;
+  border-radius: 6px;
+  padding: 12px 16px;
+}
+
+.items-title {
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 8px;
+}
+
+.item-row {
+  font-size: 14px;
+  color: #333;
+  padding: 4px 0;
+}
+
+.item-spec {
+  color: #999;
+  font-size: 12px;
+}
+
+.items-total {
+  border-top: 1px dashed #e0e0e0;
+  margin-top: 8px;
+  padding-top: 8px;
+  font-size: 15px;
+  font-weight: 700;
+  color: #333;
+}
+
+/* ==================== 退单操作 ==================== */
+.refund-action-card {
+  background: #fff5f5;
+  border: 1px solid #ffd0d0;
+  border-radius: 8px;
+  padding: 20px;
+}
+
+.refund-warning-title {
+  font-size: 15px;
+  font-weight: 700;
+  color: #d03050;
+  margin-bottom: 8px;
+}
+
+.refund-warning-text {
+  font-size: 13px;
+  color: #d03050;
+  margin-bottom: 12px;
+}
+
+.refund-form-row {
+  display: flex;
+  gap: 12px;
+  align-items: flex-start;
+}
+
+.refund-reason-input {
+  flex: 1;
+}
+
+/* ==================== 时间线 ==================== */
+.timeline-section {
+  padding: 4px 0;
+}
+
+.timeline-title {
+  font-size: 15px;
+  font-weight: 700;
+  margin-bottom: 12px;
+}
+
+.timeline-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+  padding-left: 8px;
+}
+
+.timeline-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.timeline-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #f0a020;
+  flex-shrink: 0;
+}
+
+.timeline-dot.completed {
+  background: #18a058;
+}
+
+.timeline-dot.active {
+  background: #f0a020;
+}
+
+.timeline-dot.reject {
+  background: #f0a020;
+}
+
+.timeline-dot.refund {
+  background: #d03050;
+}
+
+.timeline-dot.cancel {
+  background: #999;
+}
+
+.timeline-text {
+  font-size: 14px;
+  color: #666;
+}
+</style>

+ 13 - 0
forge-server/db/migration/V1.0.113__add_hotel_order_refund_hidden_menu.sql

@@ -0,0 +1,13 @@
+-- ============================================================
+-- 酒店订单退单详情页(隐藏菜单,不在侧边栏显示)
+-- 从订单列表页点击「退单」按钮跳转,需要注册路由以通过权限守卫
+-- ============================================================
+
+SET @hotel_parent_id = (SELECT id FROM sys_resource WHERE resource_name = '酒店管理' AND tenant_id = 1 AND parent_id = 0 LIMIT 1);
+SET @order_menu_id = (SELECT id FROM sys_resource WHERE resource_name = '订单管理' AND parent_id = @hotel_parent_id LIMIT 1);
+
+-- 退单详情页:挂在订单管理下,visible=0 不在菜单中显示,但路由可访问
+INSERT INTO sys_resource (tenant_id, resource_name, parent_id, resource_type, sort, path, component, is_external, is_public, menu_status, visible, icon, keep_alive, always_show, remark, create_time, update_time)
+SELECT 1, '订单退单', @order_menu_id, 2, 99, '/hotel/orderRefund', 'hotel/orderRefund', 0, 0, 1, 0, NULL, 0, 0, '订单退单操作详情页(隐藏菜单)', NOW(), NOW()
+WHERE @order_menu_id IS NOT NULL
+  AND NOT EXISTS (SELECT 1 FROM sys_resource WHERE path = '/hotel/orderRefund' AND tenant_id = 1);

+ 23 - 0
forge-server/db/migration/V1.0.114__add_hotel_order_timeline_fields.sql

@@ -0,0 +1,23 @@
+-- V1.0.114: hotel_order 补全订单全链路时间字段
+-- 新增:prepare_time(备餐)、reject_time(拒单)、refund_apply_time(退单申请)、
+--       refund_audit_time(退单审核)、cancel_time(取消:超时/退房联动)
+
+-- prepare_time:开始备餐时间
+ALTER TABLE hotel_order
+    ADD COLUMN IF NOT EXISTS prepare_time DATETIME DEFAULT NULL COMMENT '开始备餐时间';
+
+-- reject_time:拒单时间
+ALTER TABLE hotel_order
+    ADD COLUMN IF NOT EXISTS reject_time DATETIME DEFAULT NULL COMMENT '拒单时间';
+
+-- refund_apply_time:顾客/前台发起退单申请的时间
+ALTER TABLE hotel_order
+    ADD COLUMN IF NOT EXISTS refund_apply_time DATETIME DEFAULT NULL COMMENT '退单申请时间';
+
+-- refund_audit_time:前台审核退单(通过/驳回)的时间
+ALTER TABLE hotel_order
+    ADD COLUMN IF NOT EXISTS refund_audit_time DATETIME DEFAULT NULL COMMENT '退单审核时间';
+
+-- cancel_time:订单取消时间(超时取消 10 / 退房联动取消 11 / 待支付取消)
+ALTER TABLE hotel_order
+    ADD COLUMN IF NOT EXISTS cancel_time DATETIME DEFAULT NULL COMMENT '订单取消时间';

+ 16 - 1
forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/domain/HotelOrder.java

@@ -89,6 +89,9 @@ public class HotelOrder extends TenantEntity {
     /** 接单时间 */
     /** 接单时间 */
     private Date acceptTime;
     private Date acceptTime;
 
 
+    /** 开始备餐时间 */
+    private Date prepareTime;
+
     /** 出餐时间 */
     /** 出餐时间 */
     private Date readyTime;
     private Date readyTime;
 
 
@@ -98,6 +101,9 @@ public class HotelOrder extends TenantEntity {
     /** 完成时间 */
     /** 完成时间 */
     private Date completeTime;
     private Date completeTime;
 
 
+    /** 拒单时间 */
+    private Date rejectTime;
+
     /** 拒单原因 */
     /** 拒单原因 */
     private String rejectReason;
     private String rejectReason;
 
 
@@ -107,7 +113,13 @@ public class HotelOrder extends TenantEntity {
     /** 退单发起方(CUSTOMER=客户, HOTEL_DESK=前台) */
     /** 退单发起方(CUSTOMER=客户, HOTEL_DESK=前台) */
     private String refundInitiator;
     private String refundInitiator;
 
 
-    /** 退单时间 */
+    /** 退单申请时间(顾客/前台发起退单时写入) */
+    private Date refundApplyTime;
+
+    /** 退单审核时间(前台审核通过/驳回时写入) */
+    private Date refundAuditTime;
+
+    /** 退单时间(退款成功时写入) */
     private Date refundTime;
     private Date refundTime;
 
 
     /** 实际退款金额(元,退款成功后写入) */
     /** 实际退款金额(元,退款成功后写入) */
@@ -119,6 +131,9 @@ public class HotelOrder extends TenantEntity {
     /** 退款状态(0=未退款,1=退款成功,2=退款失败可重试,对应字典 hotel_refund_status) */
     /** 退款状态(0=未退款,1=退款成功,2=退款失败可重试,对应字典 hotel_refund_status) */
     private Integer refundStatus;
     private Integer refundStatus;
 
 
+    /** 取消时间(超时取消/退房联动取消/待支付取消时写入) */
+    private Date cancelTime;
+
     /** 删除标记(0=未删除,删除时写入主键ID) */
     /** 删除标记(0=未删除,删除时写入主键ID) */
     @TableLogic(value = "0", delval = "id")
     @TableLogic(value = "0", delval = "id")
     private Long delFlag;
     private Long delFlag;

+ 7 - 0
forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/service/impl/HotelOrderServiceImpl.java

@@ -295,6 +295,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
         updateWrapper.eq("id", order.getId())
         updateWrapper.eq("id", order.getId())
                 .set("status", HotelOrderConstants.STATUS_REJECTED)
                 .set("status", HotelOrderConstants.STATUS_REJECTED)
                 .set("reject_reason", reason)
                 .set("reject_reason", reason)
+                .set("reject_time", now)
                 .set("refund_time", now)
                 .set("refund_time", now)
                 .set("update_time", now);
                 .set("update_time", now);
         orderMapper.update(null, updateWrapper);
         orderMapper.update(null, updateWrapper);
@@ -312,6 +313,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
             throw new BusinessException("只有已接单的订单才能开始备餐");
             throw new BusinessException("只有已接单的订单才能开始备餐");
         }
         }
         order.setStatus(HotelOrderConstants.STATUS_PREPARING);
         order.setStatus(HotelOrderConstants.STATUS_PREPARING);
+        order.setPrepareTime(new Date());
         orderMapper.updateById(order);
         orderMapper.updateById(order);
         log.info("订单备餐: {}", order.getOrderNo());
         log.info("订单备餐: {}", order.getOrderNo());
     }
     }
@@ -375,6 +377,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
                     .set("status", HotelOrderConstants.STATUS_CANCELLED)
                     .set("status", HotelOrderConstants.STATUS_CANCELLED)
                     .set("refund_reason", reason)
                     .set("refund_reason", reason)
                     .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_CUSTOMER)
                     .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_CUSTOMER)
+                    .set("cancel_time", new Date())
                     .set("update_time", new Date());
                     .set("update_time", new Date());
             orderMapper.update(null, cancelWrapper);
             orderMapper.update(null, cancelWrapper);
             log.info("客户取消待支付订单: {}", order.getOrderNo());
             log.info("客户取消待支付订单: {}", order.getOrderNo());
@@ -397,6 +400,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
             order.setStatus(HotelOrderConstants.STATUS_REFUND_REQUESTED);
             order.setStatus(HotelOrderConstants.STATUS_REFUND_REQUESTED);
             order.setRefundReason(reason);
             order.setRefundReason(reason);
             order.setRefundInitiator(HotelOrderConstants.REFUND_INITIATOR_CUSTOMER);
             order.setRefundInitiator(HotelOrderConstants.REFUND_INITIATOR_CUSTOMER);
+            order.setRefundApplyTime(new Date());
             orderMapper.updateById(order);
             orderMapper.updateById(order);
             log.info("客户申请退单(待审核): {}", order.getOrderNo());
             log.info("客户申请退单(待审核): {}", order.getOrderNo());
 
 
@@ -444,6 +448,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
                     .set("status", HotelOrderConstants.STATUS_CANCELLED)
                     .set("status", HotelOrderConstants.STATUS_CANCELLED)
                     .set("refund_reason", reason)
                     .set("refund_reason", reason)
                     .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_HOTEL_DESK)
                     .set("refund_initiator", HotelOrderConstants.REFUND_INITIATOR_HOTEL_DESK)
+                    .set("cancel_time", now)
                     .set("update_time", now);
                     .set("update_time", now);
             orderMapper.update(null, cancelWrapper);
             orderMapper.update(null, cancelWrapper);
             log.info("前台取消待支付订单: {}", order.getOrderNo());
             log.info("前台取消待支付订单: {}", order.getOrderNo());
@@ -478,6 +483,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
         updateWrapper.eq("id", order.getId())
         updateWrapper.eq("id", order.getId())
                 .set("status", HotelOrderConstants.STATUS_REFUNDED)
                 .set("status", HotelOrderConstants.STATUS_REFUNDED)
                 .set("refund_time", now)
                 .set("refund_time", now)
+                .set("refund_audit_time", now)
                 .set("update_time", now);
                 .set("update_time", now);
         orderMapper.update(null, updateWrapper);
         orderMapper.update(null, updateWrapper);
         log.info("退单审核通过: {}", order.getOrderNo());
         log.info("退单审核通过: {}", order.getOrderNo());
@@ -496,6 +502,7 @@ public class HotelOrderServiceImpl implements HotelOrderService {
                 .set("status", HotelOrderConstants.STATUS_ACCEPTED)
                 .set("status", HotelOrderConstants.STATUS_ACCEPTED)
                 .set("refund_reason", null)
                 .set("refund_reason", null)
                 .set("refund_initiator", null)
                 .set("refund_initiator", null)
+                .set("refund_audit_time", new Date())
                 .set("update_time", new Date());
                 .set("update_time", new Date());
         orderMapper.update(null, updateWrapper);
         orderMapper.update(null, updateWrapper);
         log.info("退单审核驳回: {}", order.getOrderNo());
         log.info("退单审核驳回: {}", order.getOrderNo());

+ 15 - 0
forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/vo/HotelOrderVO.java

@@ -75,6 +75,9 @@ public class HotelOrderVO {
     /** 接单时间 */
     /** 接单时间 */
     private Date acceptTime;
     private Date acceptTime;
 
 
+    /** 开始备餐时间 */
+    private Date prepareTime;
+
     /** 出餐时间 */
     /** 出餐时间 */
     private Date readyTime;
     private Date readyTime;
 
 
@@ -84,6 +87,9 @@ public class HotelOrderVO {
     /** 完成时间 */
     /** 完成时间 */
     private Date completeTime;
     private Date completeTime;
 
 
+    /** 拒单时间 */
+    private Date rejectTime;
+
     /** 拒单原因 */
     /** 拒单原因 */
     private String rejectReason;
     private String rejectReason;
 
 
@@ -93,6 +99,12 @@ public class HotelOrderVO {
     /** 退单发起方 */
     /** 退单发起方 */
     private String refundInitiator;
     private String refundInitiator;
 
 
+    /** 退单申请时间 */
+    private Date refundApplyTime;
+
+    /** 退单审核时间 */
+    private Date refundAuditTime;
+
     /** 退单时间 */
     /** 退单时间 */
     private Date refundTime;
     private Date refundTime;
 
 
@@ -105,6 +117,9 @@ public class HotelOrderVO {
     /** 退款状态(0=未退款,1=退款成功,2=退款失败) */
     /** 退款状态(0=未退款,1=退款成功,2=退款失败) */
     private Integer refundStatus;
     private Integer refundStatus;
 
 
+    /** 取消时间 */
+    private Date cancelTime;
+
     /** 创建时间 */
     /** 创建时间 */
     private Date createTime;
     private Date createTime;
 
 

+ 1 - 0
forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/service/impl/HotelPayServiceImpl.java

@@ -519,6 +519,7 @@ public class HotelPayServiceImpl implements HotelPayService {
 
 
         order.setStatus(HotelOrderConstants.STATUS_PAY_TIMEOUT);
         order.setStatus(HotelOrderConstants.STATUS_PAY_TIMEOUT);
         order.setPayStatus(HotelOrderConstants.PAY_STATUS_TIMEOUT);
         order.setPayStatus(HotelOrderConstants.PAY_STATUS_TIMEOUT);
+        order.setCancelTime(new Date());
         orderMapper.updateById(order);
         orderMapper.updateById(order);
 
 
         log.info("订单超时取消: orderId={}, orderNo={}", order.getId(), order.getOrderNo());
         log.info("订单超时取消: orderId={}, orderNo={}", order.getId(), order.getOrderNo());

+ 10 - 3
forge-server/forge-business/forge-hotel/src/main/resources/mapper/business/hotel/order/HotelOrderMapper.xml

@@ -24,16 +24,21 @@
         <result column="custom_delivery_time" property="customDeliveryTime" />
         <result column="custom_delivery_time" property="customDeliveryTime" />
         <result column="note" property="note" />
         <result column="note" property="note" />
         <result column="accept_time" property="acceptTime" />
         <result column="accept_time" property="acceptTime" />
+        <result column="prepare_time" property="prepareTime" />
         <result column="ready_time" property="readyTime" />
         <result column="ready_time" property="readyTime" />
         <result column="deliver_time" property="deliverTime" />
         <result column="deliver_time" property="deliverTime" />
         <result column="complete_time" property="completeTime" />
         <result column="complete_time" property="completeTime" />
+        <result column="reject_time" property="rejectTime" />
         <result column="reject_reason" property="rejectReason" />
         <result column="reject_reason" property="rejectReason" />
         <result column="refund_reason" property="refundReason" />
         <result column="refund_reason" property="refundReason" />
         <result column="refund_initiator" property="refundInitiator" />
         <result column="refund_initiator" property="refundInitiator" />
+        <result column="refund_apply_time" property="refundApplyTime" />
+        <result column="refund_audit_time" property="refundAuditTime" />
         <result column="refund_time" property="refundTime" />
         <result column="refund_time" property="refundTime" />
         <result column="refund_amount" property="refundAmount" />
         <result column="refund_amount" property="refundAmount" />
         <result column="refund_trade_no" property="refundTradeNo" />
         <result column="refund_trade_no" property="refundTradeNo" />
         <result column="refund_status" property="refundStatus" />
         <result column="refund_status" property="refundStatus" />
+        <result column="cancel_time" property="cancelTime" />
         <result column="create_time" property="createTime" />
         <result column="create_time" property="createTime" />
         <result column="item_count" property="itemCount" />
         <result column="item_count" property="itemCount" />
     </resultMap>
     </resultMap>
@@ -44,9 +49,10 @@
         o.pay_status, o.pay_expire_time, o.pay_time, o.pay_trade_no, o.paid_amount, o.pay_source,
         o.pay_status, o.pay_expire_time, o.pay_time, o.pay_trade_no, o.paid_amount, o.pay_source,
         o.total_amount, o.delivery_fee,
         o.total_amount, o.delivery_fee,
         o.delivery_time_type, o.custom_delivery_time, o.note,
         o.delivery_time_type, o.custom_delivery_time, o.note,
-        o.accept_time, o.ready_time, o.deliver_time, o.complete_time,
-        o.reject_reason, o.refund_reason, o.refund_initiator, o.refund_time,
-        o.refund_amount, o.refund_trade_no, o.refund_status,
+        o.accept_time, o.prepare_time, o.ready_time, o.deliver_time, o.complete_time,
+        o.reject_time, o.reject_reason, o.refund_reason, o.refund_initiator,
+        o.refund_apply_time, o.refund_audit_time, o.refund_time,
+        o.refund_amount, o.refund_trade_no, o.refund_status, o.cancel_time,
         o.create_time,
         o.create_time,
         (SELECT IFNULL(SUM(oi.quantity), 0) FROM hotel_order_item oi WHERE oi.order_id = o.id AND oi.del_flag = 0) AS item_count
         (SELECT IFNULL(SUM(oi.quantity), 0) FROM hotel_order_item oi WHERE oi.order_id = o.id AND oi.del_flag = 0) AS item_count
     </sql>
     </sql>
@@ -159,6 +165,7 @@
     <update id="cancelPendingByStayId">
     <update id="cancelPendingByStayId">
         UPDATE hotel_order
         UPDATE hotel_order
         SET status = 11,
         SET status = 11,
+            cancel_time = NOW(),
             update_time = NOW()
             update_time = NOW()
         WHERE del_flag = 0
         WHERE del_flag = 0
           AND stay_id = #{stayId}
           AND stay_id = #{stayId}