From 4fdde57a837b47b0a04aa17a7627c21b7425eda2 Mon Sep 17 00:00:00 2001
From: wlzboy <66905212@qq.com>
Date: 星期五, 26 十二月 2025 23:25:11 +0800
Subject: [PATCH] feat: 优化取消时,调度单中显示原因

---
 ruoyi-system/src/main/java/com/ruoyi/system/service/IDispatchOrdService.java            |    2 
 取消原因同步到DispatchOrd功能说明.md                                                               |  273 +++++++++++++++++
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java          |   43 ++
 app/pagesTask/create-emergency.vue                                                      |    8 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskSyncUtilService.java       |    4 
 ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java               |   12 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DispatchOrdServiceImpl.java    |    7 
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java        |   61 +++
 ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml                     |    8 
 ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java    |   62 +++
 ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java                |    8 
 app/pagesTask/detail.vue                                                                |  429 +++++++++++++++++++++++---
 ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskStatusPushServiceImpl.java |   12 
 13 files changed, 870 insertions(+), 59 deletions(-)

diff --git a/app/pagesTask/create-emergency.vue b/app/pagesTask/create-emergency.vue
index 166e11c..d3db251 100644
--- a/app/pagesTask/create-emergency.vue
+++ b/app/pagesTask/create-emergency.vue
@@ -916,8 +916,11 @@
       if (!this.validateForm()) {
         return
       }
-      
-      // 鑾峰彇褰撳墠鏃ユ湡锛堟牸寮廦YYY-MM-DD锛�
+      this.doSubmitTask();
+   
+    },
+    checkTaskDp(){
+   // 鑾峰彇褰撳墠鏃ユ湡锛堟牸寮廦YYY-MM-DD锛�
       const today = new Date()
       const createDate = today.getFullYear() + '-' + 
                         String(today.getMonth() + 1).padStart(2, '0') + '-' + 
@@ -946,7 +949,6 @@
         this.$modal.showToast('妫�鏌ュけ璐ワ紝璇烽噸璇�')
       })
     },
-    
     // 鎵ц瀹為檯鐨勬彁浜ゆ搷浣�
     doSubmitTask() {
       this.$modal.confirm('纭畾瑕佷繚瀛樹换鍔″悧锛�').then(() => {
diff --git a/app/pagesTask/detail.vue b/app/pagesTask/detail.vue
index 0382fc3..375ace9 100644
--- a/app/pagesTask/detail.vue
+++ b/app/pagesTask/detail.vue
@@ -5,6 +5,9 @@
         <uni-icons type="arrowleft" size="20"></uni-icons>
       </view>
       <view class="title">浠诲姟璇︽儏</view>
+      <view class="edit-btn" @click="handleEdit" v-if="taskDetail && !isTaskFinished">
+        <uni-icons type="compose" size="20" color="#007AFF"></uni-icons>
+      </view>
     </view>
     
     <scroll-view class="detail-content" scroll-y="true" v-if="taskDetail">
@@ -64,6 +67,17 @@
                   {{ isAssigneeReady(assignee) ? '宸插氨缁�' : '鏈氨缁�' }}
                 </view>
               </view>
+            </view>
+            <!-- 褰撳墠鐧诲綍浜烘槸璇ユ墽琛屼汉涓旀湭灏辩华鏃舵樉绀哄氨缁寜閽� -->
+            <view 
+              v-if="showAssigneeReadyFeature() && isAssigneeSelf(assignee) && !isAssigneeReady(assignee) && taskDetail.taskStatus === 'PENDING'"
+              class="assignee-ready-btn"
+              :data-user-id="assignee.userId || assignee.oaUserId"
+              :data-user-name="assignee.userName"
+              :data-index="index"
+              @click="handleReadyClick"
+            >
+              鐐瑰嚮灏辩华
             </view>
           </view>
         </view>
@@ -373,6 +387,51 @@
       <text>鍔犺浇涓�...</text>
     </view>
     
+    <!-- 寮哄埗瀹屾垚瀵硅瘽妗� -->
+    <uni-popup ref="forceCompletePopup" type="center" :is-mask-click="false">
+      <view class="force-complete-dialog">
+        <view class="dialog-title">璇疯緭鍏ユ椂闂�</view>
+        <view class="time-picker-item">
+          <view class="time-label">杞繍寮�濮嬫椂闂�</view>
+          <picker 
+            mode="date" 
+            :value="getDateFromDateTime(forceCompleteForm.actualStartTime)" 
+            @change="selectStartDate"
+          >
+            <view class="picker-value">{{ getDateFromDateTime(forceCompleteForm.actualStartTime) || '璇烽�夋嫨鏃ユ湡' }}</view>
+          </picker>
+          <picker 
+            mode="time" 
+            :value="getTimeFromDateTime(forceCompleteForm.actualStartTime)" 
+            @change="selectStartTime"
+          >
+            <view class="picker-value">{{ getTimeFromDateTime(forceCompleteForm.actualStartTime) || '璇烽�夋嫨鏃堕棿' }}</view>
+          </picker>
+        </view>
+        <view class="time-picker-item">
+          <view class="time-label">杞繍缁撴潫鏃堕棿</view>
+          <picker 
+            mode="date" 
+            :value="getDateFromDateTime(forceCompleteForm.actualEndTime)" 
+            @change="selectEndDate"
+          >
+            <view class="picker-value">{{ getDateFromDateTime(forceCompleteForm.actualEndTime) || '璇烽�夋嫨鏃ユ湡' }}</view>
+          </picker>
+          <picker 
+            mode="time" 
+            :value="getTimeFromDateTime(forceCompleteForm.actualEndTime)" 
+            @change="selectEndTime"
+          >
+            <view class="picker-value">{{ getTimeFromDateTime(forceCompleteForm.actualEndTime) || '璇烽�夋嫨鏃堕棿' }}</view>
+          </picker>
+        </view>
+        <view class="dialog-buttons">
+          <button class="cancel-btn" @click="closeForceCompleteDialog">鍙栨秷</button>
+          <button class="confirm-btn" @click="confirmForceComplete">纭畾</button>
+        </view>
+      </view>
+    </uni-popup>
+    
     <!-- 鍙栨秷鍘熷洜閫夋嫨瀵硅瘽妗� -->
     <uni-popup ref="cancelPopup" type="center" :is-mask-click="false">
       <view class="cancel-dialog">
@@ -395,22 +454,9 @@
     
     <!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
     <view class="action-buttons" v-if="taskDetail">
-      <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず缂栬緫銆佸嚭鍙戙�佸彇娑� -->
+      <!-- 寰呭鐞嗙姸鎬�: 鏄剧ず鍑哄彂銆佸彇娑堛�佸己鍒跺畬鎴� -->
       <template v-if="taskDetail.taskStatus === 'PENDING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
-          <button 
-            v-if="showAssigneeReadyFeature() && isMultipleAssignees() && !isCurrentUserReady()"
-            class="action-btn primary" 
-            @click="handleReadyAction()"
-          >
-            灏辩华
-          </button>
           <button 
             class="action-btn primary" 
             @click="handleDepartAction()"
@@ -423,17 +469,17 @@
           >
             鍙栨秷
           </button>
+          <button 
+            class="action-btn force-complete" 
+            @click="showForceCompleteTimeDialog()"
+          >
+            寮哄埗瀹屾垚
+          </button>
         </template>
       </template>
       
-      <!-- 鍑哄彂涓姸鎬�: 鏄剧ず缂栬緫銆佸凡鍒拌揪銆佸己鍒剁粨鏉� -->
+      <!-- 鍑哄彂涓姸鎬�: 鏄剧ず宸插埌杈俱�佸己鍒剁粨鏉� -->
       <template v-else-if="taskDetail.taskStatus === 'DEPARTING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -450,14 +496,8 @@
         </template>
       </template>
       
-      <!-- 宸插埌杈剧姸鎬�: 鏄剧ず缂栬緫銆佸凡杩旂▼ -->
+      <!-- 宸插埌杈剧姸鎬�: 鏄剧ず宸茶繑绋� -->
       <template v-else-if="taskDetail.taskStatus === 'ARRIVED'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -468,14 +508,8 @@
         </template>
       </template>
       
-      <!-- 杩旂▼涓姸鎬�: 鏄剧ず缂栬緫銆佸凡瀹屾垚 -->
+      <!-- 杩旂▼涓姸鎬�: 鏄剧ず宸插畬鎴� -->
       <template v-else-if="taskDetail.taskStatus === 'RETURNING'">
-        <button 
-          class="action-btn edit" 
-          @click="handleEdit"
-        >
-          淇敼
-        </button>
         <template v-if="isCurrentUserAssignee()">
           <button 
             class="action-btn primary" 
@@ -521,7 +555,12 @@
         paymentInfo: null, // 鏀粯淇℃伅
         cancelReasonList: [], // 鍙栨秷鍘熷洜鍒楄〃
         showCancelDialog: false, // 鏄剧ず鍙栨秷鍘熷洜瀵硅瘽妗�
-        selectedCancelReason: '' // 閫変腑鐨勫彇娑堝師鍥�
+        selectedCancelReason: '', // 閫変腑鐨勫彇娑堝師鍥�
+        showForceCompleteDialog: false, // 鏄剧ず寮哄埗瀹屾垚瀵硅瘽妗�
+        forceCompleteForm: {
+          actualStartTime: '',
+          actualEndTime: ''
+        }
       }
     },
     computed: {
@@ -1392,18 +1431,53 @@
         const userId = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.userId
         return assignee && (assignee.userId === userId || assignee.oaUserId === userId)
       },
-
-      // 鎵ц浜虹偣鍑烩�滃氨缁��
-      markAssigneeReady(assignee) {
-        if (!assignee || !this.taskDetail) {
-          this.$modal.showToast('鎵ц浜烘垨浠诲姟淇℃伅涓嶅瓨鍦�')
+      
+      // 澶勭悊灏辩华鎸夐挳鐐瑰嚮锛堥�氳繃data灞炴�ц幏鍙栨墽琛屼汉淇℃伅锛�
+      handleReadyClick(e) {
+        const dataset = e.currentTarget.dataset
+        const index = dataset.index
+        console.log('handleReadyClick - dataset:', dataset)
+        console.log('handleReadyClick - index:', index)
+        
+        if (index === undefined || index === null) {
+          this.$modal.showToast('鏃犳硶鑾峰彇鎵ц浜虹储寮�')
           return
         }
+        
+        const assignee = this.taskDetail.assignees[index]
+        console.log('handleReadyClick - assignee:', assignee)
+        
+        if (!assignee) {
+          this.$modal.showToast('鎵ц浜轰俊鎭笉瀛樺湪')
+          return
+        }
+        
+        this.markAssigneeReady(assignee)
+      },
+
+      // 鎵ц浜虹偣鍑�"灏辩华"
+      markAssigneeReady(assignee) {
+        console.log('markAssigneeReady 琚皟鐢紝鍙傛暟:', assignee)
+        console.log('taskDetail:', this.taskDetail)
+              
+        if (!this.taskDetail) {
+          this.$modal.showToast('浠诲姟淇℃伅涓嶅瓨鍦�')
+          return
+        }
+              
+        if (!assignee) {
+          this.$modal.showToast('鎵ц浜轰俊鎭笉瀛樺湪')
+          return
+        }
+              
         const userId = assignee.userId || assignee.oaUserId
+        console.log('鎵ц浜篒D:', userId)
+              
         if (!userId) {
           this.$modal.showToast('鏃犳硶璇嗗埆鎵ц浜篒D')
           return
         }
+              
         this.$modal.showLoading && this.$modal.showLoading('鎻愪氦涓�...')
         setAssigneeReady(this.taskId).then(() => {
           this.$modal.hideLoading && this.$modal.hideLoading()
@@ -1581,7 +1655,10 @@
       
       // 閫夋嫨鍙栨秷鍘熷洜
       selectCancelReason(e) {
-        this.selectedCancelReason = e.detail.value
+        const index = parseInt(e.detail.value)
+        if (this.cancelReasonList && this.cancelReasonList[index]) {
+          this.selectedCancelReason = this.cancelReasonList[index].value
+        }
       },
       
       // 甯﹀彇娑堝師鍥犵殑鐘舵�佹洿鏂�
@@ -1596,6 +1673,167 @@
         }
         const reason = this.cancelReasonList.find(r => r.value === value)
         return reason ? reason.label : value
+      },
+      
+      // 鏄剧ず寮哄埗瀹屾垚鏃堕棿瀵硅瘽妗�
+      showForceCompleteTimeDialog() {
+        // 鏍¢獙浠诲姟鏄惁婊¤冻寮哄埗瀹屾垚鏉′欢
+        const validation = this.validateForceComplete()
+        if (!validation.valid) {
+          this.$modal.showToast(validation.message)
+          return
+        }
+        
+        // 閲嶇疆琛ㄥ崟
+        const now = new Date()
+        const nowDateStr = this.formatDateForPicker(now)
+        const nowTimeStr = this.formatTimeForPicker(now)
+        
+        // 寮�濮嬫椂闂达細浼樺厛浣跨敤棰勭害鏃堕棿锛屽鏋滄病鏈夊垯浣跨敤褰撳墠鏃堕棿
+        let startTimeStr = nowDateStr + ' ' + nowTimeStr
+        if (this.taskDetail && this.taskDetail.plannedStartTime) {
+          const plannedTime = new Date(this.taskDetail.plannedStartTime)
+          const year = plannedTime.getFullYear()
+          // 妫�鏌ユ槸鍚︽槸鏈夋晥鏃堕棿锛堟帓闄�1900銆�1970绛夋棤鏁堝勾浠斤級
+          if (year > 2000) {
+            const plannedDateStr = this.formatDateForPicker(plannedTime)
+            const plannedTimeStr = this.formatTimeForPicker(plannedTime)
+            startTimeStr = plannedDateStr + ' ' + plannedTimeStr
+          }
+        }
+        
+        this.forceCompleteForm = {
+          actualStartTime: startTimeStr,
+          actualEndTime: nowDateStr + ' ' + nowTimeStr
+        }
+        
+        this.$refs.forceCompletePopup.open()
+      },
+      
+      // 鏍¢獙鏄惁鍙互寮哄埗瀹屾垚
+      validateForceComplete() {
+        if (!this.taskDetail) {
+          return { valid: false, message: '浠诲姟淇℃伅涓嶅瓨鍦�' }
+        }
+        
+        // 妫�鏌ユ槸鍚︽湁鎵ц浜�
+        if (!this.taskDetail.assignees || this.taskDetail.assignees.length === 0) {
+          return { valid: false, message: '璇峰厛鍒嗛厤鎵ц浜�' }
+        }
+        
+        // 妫�鏌ユ槸鍚︽湁杞﹁締
+        if (!this.taskDetail.assignedVehicles || this.taskDetail.assignedVehicles.length === 0) {
+          return { valid: false, message: '璇峰厛鍒嗛厤鎵ц杞﹁締' }
+        }
+        
+        return { valid: true }
+      },
+      
+      // 鍏抽棴寮哄埗瀹屾垚瀵硅瘽妗�
+      closeForceCompleteDialog() {
+        this.$refs.forceCompletePopup.close()
+      },
+      
+      // 纭寮哄埗瀹屾垚
+      confirmForceComplete() {
+        // 鏍¢獙鏃堕棿
+        if (!this.forceCompleteForm.actualStartTime || !this.forceCompleteForm.actualEndTime) {
+          this.$modal.showToast('璇烽�夋嫨寮�濮嬪拰缁撴潫鏃堕棿')
+          return
+        }
+        
+        const startTime = new Date(this.forceCompleteForm.actualStartTime)
+        const endTime = new Date(this.forceCompleteForm.actualEndTime)
+        
+        if (startTime >= endTime) {
+          this.$modal.showToast('缁撴潫鏃堕棿蹇呴』澶т簬寮�濮嬫椂闂�')
+          return
+        }
+        
+        this.$refs.forceCompletePopup.close()
+        
+        // 璋冪敤API鏇存柊浠诲姟
+        this.forceCompleteTask()
+      },
+      
+      // 寮哄埗瀹屾垚浠诲姟
+      forceCompleteTask() {
+        uni.showLoading({
+          title: '澶勭悊涓�...'
+        })
+        
+        const statusData = {
+          taskStatus: 'COMPLETED',
+          actualStartTime: this.forceCompleteForm.actualStartTime,
+          actualEndTime: this.forceCompleteForm.actualEndTime,
+          remark: '寮哄埗瀹屾垚浠诲姟'
+        }
+        
+        changeTaskStatus(this.taskId, statusData).then(response => {
+          uni.hideLoading()
+          this.$modal.showToast('浠诲姟宸插畬鎴�')
+          // 閲嶆柊鍔犺浇浠诲姟璇︽儏
+          this.loadTaskDetail()
+        }).catch(error => {
+          uni.hideLoading()
+          console.error('寮哄埗瀹屾垚浠诲姟澶辫触:', error)
+          this.$modal.showToast('鎿嶄綔澶辫触锛岃閲嶈瘯')
+        })
+      },
+      
+      // 閫夋嫨寮�濮嬫棩鏈�
+      selectStartDate(e) {
+        const date = e.detail.value
+        const time = this.getTimeFromDateTime(this.forceCompleteForm.actualStartTime) || '00:00'
+        this.forceCompleteForm.actualStartTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨寮�濮嬫椂闂�
+      selectStartTime(e) {
+        const time = e.detail.value
+        const date = this.getDateFromDateTime(this.forceCompleteForm.actualStartTime) || this.formatDateForPicker(new Date())
+        this.forceCompleteForm.actualStartTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨缁撴潫鏃ユ湡
+      selectEndDate(e) {
+        const date = e.detail.value
+        const time = this.getTimeFromDateTime(this.forceCompleteForm.actualEndTime) || '00:00'
+        this.forceCompleteForm.actualEndTime = date + ' ' + time
+      },
+      
+      // 閫夋嫨缁撴潫鏃堕棿
+      selectEndTime(e) {
+        const time = e.detail.value
+        const date = this.getDateFromDateTime(this.forceCompleteForm.actualEndTime) || this.formatDateForPicker(new Date())
+        this.forceCompleteForm.actualEndTime = date + ' ' + time
+      },
+      
+      // 浠庢棩鏈熸椂闂村瓧绗︿覆涓彁鍙栨棩鏈�
+      getDateFromDateTime(dateTimeStr) {
+        if (!dateTimeStr) return ''
+        return dateTimeStr.split(' ')[0] || ''
+      },
+      
+      // 浠庢棩鏈熸椂闂村瓧绗︿覆涓彁鍙栨椂闂�
+      getTimeFromDateTime(dateTimeStr) {
+        if (!dateTimeStr) return ''
+        return dateTimeStr.split(' ')[1] || ''
+      },
+      
+      // 鏍煎紡鍖栨棩鏈熶负 picker 闇�瑕佺殑鏍煎紡 (YYYY-MM-DD)
+      formatDateForPicker(date) {
+        const year = date.getFullYear()
+        const month = String(date.getMonth() + 1).padStart(2, '0')
+        const day = String(date.getDate()).padStart(2, '0')
+        return `${year}-${month}-${day}`
+      },
+      
+      // 鏍煎紡鍖栨椂闂翠负 picker 闇�瑕佺殑鏍煎紡 (HH:mm)
+      formatTimeForPicker(date) {
+        const hour = String(date.getHours()).padStart(2, '0')
+        const minute = String(date.getMinutes()).padStart(2, '0')
+        return `${hour}:${minute}`
       },
       
     }
@@ -1626,9 +1864,19 @@
       }
       
       .title {
+        flex: 1;
         font-size: 36rpx;
         font-weight: bold;
         color: #333;
+      }
+      
+      .edit-btn {
+        width: 60rpx;
+        height: 60rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
       }
     }
     
@@ -1726,6 +1974,9 @@
             }
             
             .assignee-role {
+              display: flex;
+              align-items: center;
+              
               .role-tag {
                 display: inline-block;
                 padding: 4rpx 12rpx;
@@ -1746,15 +1997,6 @@
                 }
               }
               
-              .assignee-ready-btn {
-                margin-left: 12rpx;
-                padding: 8rpx 16rpx;
-                font-size: 24rpx;
-                border-radius: 6rpx;
-                background-color: #34C759;
-                color: #fff;
-                border: none;
-              }
               .ready-badge {
                 display: inline-block;
                 margin-left: 12rpx;
@@ -1771,6 +2013,17 @@
                 }
               }
             }
+          }
+          
+          .assignee-ready-btn {
+            margin-left: 12rpx;
+            padding: 8rpx 16rpx;
+            font-size: 24rpx;
+            border-radius: 6rpx;
+            background-color: #34C759;
+            color: #fff;
+            border: none;
+            flex-shrink: 0;
           }
         }
       }
@@ -1964,6 +2217,11 @@
           color: white;
         }
         
+        &.force-complete {
+          background-color: #5856d6;
+          color: white;
+        }
+        
         &:first-child {
           margin-left: 0;
         }
@@ -2057,5 +2315,74 @@
         }
       }
     }
+    
+    // 寮哄埗瀹屾垚瀵硅瘽妗嗘牱寮�
+    .force-complete-dialog {
+      width: 600rpx;
+      background-color: white;
+      border-radius: 20rpx;
+      padding: 40rpx;
+      
+      .dialog-title {
+        font-size: 32rpx;
+        font-weight: bold;
+        text-align: center;
+        margin-bottom: 30rpx;
+        color: #333;
+      }
+      
+      .time-picker-item {
+        margin-bottom: 30rpx;
+        
+        .time-label {
+          font-size: 28rpx;
+          color: #333;
+          margin-bottom: 15rpx;
+          font-weight: 500;
+        }
+        
+        picker {
+          display: inline-block;
+          width: 48%;
+          
+          &:first-of-type {
+            margin-right: 4%;
+          }
+          
+          .picker-value {
+            padding: 20rpx;
+            background-color: #f5f5f5;
+            border-radius: 10rpx;
+            font-size: 26rpx;
+            color: #333;
+            text-align: center;
+          }
+        }
+      }
+      
+      .dialog-buttons {
+        display: flex;
+        gap: 20rpx;
+        margin-top: 40rpx;
+        
+        button {
+          flex: 1;
+          height: 80rpx;
+          border-radius: 10rpx;
+          font-size: 30rpx;
+          border: none;
+          
+          &.cancel-btn {
+            background-color: #f0f0f0;
+            color: #666;
+          }
+          
+          &.confirm-btn {
+            background-color: #5856d6;
+            color: white;
+          }
+        }
+      }
+    }
   }
 </style>
\ No newline at end of file
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
index e41be20..09db629 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskController.java
@@ -1,10 +1,14 @@
 package com.ruoyi.web.controller.task;
 
+import java.util.Date;
 import java.util.List;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.system.domain.SysTaskEmergency;
 import com.ruoyi.system.service.*;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -306,6 +310,23 @@
             sysTaskService.saveCancelInfo(taskId, request.getCancelReason());
         }
         
+        // 濡傛灉鏄己鍒跺畬鎴愶紝鏇存柊瀹為檯寮�濮嬫椂闂村拰缁撴潫鏃堕棿
+        if (newStatus == TaskStatus.COMPLETED && request.getActualStartTime() != null && request.getActualEndTime() != null) {
+            SysTask task = new SysTask();
+            task.setTaskId(taskId);
+            task.setTaskStatus(newStatus.getCode());
+            //灏哠tring杞垚Date
+
+            task.setActualStartTime(DateUtils.parseDate(request.getActualStartTime()));
+            task.setActualEndTime(DateUtils.parseDate(request.getActualEndTime()));
+            task.setRemark(request.getRemark());
+            task.setUpdateBy(SecurityUtils.getUsername());
+            task.setUpdateTime(DateUtils.getNowDate());
+            
+            int result = sysTaskService.forceCompleteTask(task);
+            return toAjax(result);
+        }
+        
         // 濡傛灉鍖呭惈GPS浣嶇疆淇℃伅锛屼娇鐢ㄥ甫浣嶇疆鐨勬柟娉�
         if (request.getLatitude() != null && request.getLongitude() != null) {
            String address= mapService.reverseGeocoding(request.getLongitude(), request.getLatitude());
@@ -462,6 +483,12 @@
         
         // 鍙栨秷鐩稿叧瀛楁
         private String cancelReason;  // 鍙栨秷鍘熷洜锛堝叧鑱旀暟鎹瓧鍏竧ask_cancel_reason锛�
+        
+        // 寮哄埗瀹屾垚鐩稿叧瀛楁
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
+        private String actualStartTime;  // 瀹為檯寮�濮嬫椂闂�
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
+        private String actualEndTime;    // 瀹為檯缁撴潫鏃堕棿
 
         public String getTaskStatus() {
             return taskStatus;
@@ -566,5 +593,21 @@
         public void setCancelReason(String cancelReason) {
             this.cancelReason = cancelReason;
         }
+        
+        public String getActualStartTime() {
+            return actualStartTime;
+        }
+        
+        public void setActualStartTime(String actualStartTime) {
+            this.actualStartTime = actualStartTime;
+        }
+        
+        public String getActualEndTime() {
+            return actualEndTime;
+        }
+        
+        public void setActualEndTime(String actualEndTime) {
+            this.actualEndTime = actualEndTime;
+        }
     }
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java b/ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java
index 861d5a9..edebfb9 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java
@@ -6,7 +6,9 @@
 import com.ruoyi.system.domain.SysTaskEmergency;
 import com.ruoyi.system.domain.enums.TaskStatus;
 import com.ruoyi.system.event.TaskStatusChangedEvent;
+import com.ruoyi.system.mapper.DispatchOrdMapper;
 import com.ruoyi.system.mapper.LegacyTransferSyncMapper;
+import com.ruoyi.system.mapper.SysDictDataMapper;
 import com.ruoyi.system.mapper.SysTaskEmergencyMapper;
 import com.ruoyi.system.mapper.SysTaskMapper;
 import com.ruoyi.system.mapper.SysUserMapper;
@@ -46,6 +48,12 @@
 
     @Autowired
     private SysUserMapper sysUserMapper;
+    
+    @Autowired
+    private DispatchOrdMapper dispatchOrdMapper;
+    
+    @Autowired
+    private SysDictDataMapper sysDictDataMapper;
     
     /**
      * 鐩戝惉浠诲姟鐘舵�佸彉鏇翠簨浠�
@@ -111,6 +119,11 @@
                 log.debug("浠诲姟鐘舵�佷笉闇�瑕佸悓姝ュ埌鏃х郴缁燂紝浠诲姟ID: {}, 鐘舵��: {}",
                     event.getTaskId(), newTaskStatus.getInfo());
                 return;
+            }
+            
+            // 濡傛灉鏄彇娑堢姸鎬侊紝鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd琛�
+            if (TaskStatus.CANCELLED.equals(newTaskStatus) && emergency.getCancelReason() != null) {
+                syncCancelReasonToDispatchOrd(emergency.getLegacyDispatchOrdId(), emergency.getCancelReason());
             }
             
             // 鎻掑叆鐘舵�佸彉鏇磋褰曞埌DispatchOrd_Running琛�
@@ -192,4 +205,53 @@
             // 涓嶆姏鍑哄紓甯革紝閬垮厤褰卞搷涓绘祦绋�
         }
     }
+    
+    /**
+     * 鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd琛�
+     * 
+     * @param dispatchOrdId 璋冨害鍗旾D
+     * @param cancelReason 鍙栨秷鍘熷洜锛堝瓧鍏竩alue锛�
+     */
+    private void syncCancelReasonToDispatchOrd(Long dispatchOrdId, String cancelReason) {
+        try {
+            if (cancelReason == null || cancelReason.isEmpty()) {
+                log.debug("鍙栨秷鍘熷洜涓虹┖锛岃烦杩囧悓姝ワ紝DispatchOrdID: {}", dispatchOrdId);
+                return;
+            }
+            
+            // 灏哻ancelReason锛堝瓧鍏竩alue锛夎浆鎹负Integer
+            Integer cancelReasonId = null;
+            try {
+                cancelReasonId = Integer.valueOf(cancelReason);
+            } catch (NumberFormatException e) {
+                log.error("鍙栨秷鍘熷洜鏍煎紡閿欒锛屾棤娉曡浆鎹负鏁板瓧锛宑ancelReason: {}, DispatchOrdID: {}", cancelReason, dispatchOrdId);
+                return;
+            }
+            
+            // 浠庢暟鎹瓧鍏镐腑鏌ヨ鍙栨秷鍘熷洜鏂囨湰
+            String cancelReasonText = sysDictDataMapper.selectDictLabel("task_cancel_reason", cancelReason);
+            if (cancelReasonText == null || cancelReasonText.isEmpty()) {
+                log.warn("鏈壘鍒板彇娑堝師鍥犲搴旂殑鏂囨湰锛宑ancelReason: {}, DispatchOrdID: {}", cancelReason, dispatchOrdId);
+                cancelReasonText = cancelReason; // 浣跨敤鍘熷�间綔涓洪澶�
+            }
+            
+            log.info("寮�濮嬪悓姝ュ彇娑堝師鍥犲埌DispatchOrd锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜ID: {}, 鍙栨秷鍘熷洜鏂囨湰: {}", 
+                dispatchOrdId, cancelReasonId, cancelReasonText);
+            
+            // 璋冪敤Mapper鏇存柊DispatchOrd琛�
+            int rows = dispatchOrdMapper.updateDispatchOrdCancelReason(dispatchOrdId, cancelReasonId, cancelReasonText);
+            
+            if (rows > 0) {
+                log.info("鎴愬姛鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜: {} ({})", 
+                    dispatchOrdId, cancelReasonText, cancelReasonId);
+            } else {
+                log.warn("鍚屾鍙栨秷鍘熷洜澶辫触锛屾湭鎵惧埌瀵瑰簲鐨勮皟搴﹀崟锛孌ispatchOrdID: {}", dispatchOrdId);
+            }
+            
+        } catch (Exception e) {
+            log.error("鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd寮傚父锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜: {}", 
+                dispatchOrdId, cancelReason, e);
+            // 涓嶆姏鍑哄紓甯革紝閬垮厤褰卞搷涓绘祦绋�
+        }
+    }
 }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java
index 42c13f4..17b116f 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java
@@ -85,4 +85,16 @@
      * @return 褰卞搷琛屾暟
      */
     public int updateDispatchOrdState(@Param("dispatchOrdID") Long dispatchOrdID, @Param("dispatchOrdState") Integer dispatchOrdState);
+    
+    /**
+     * 鏇存柊璋冨害鍗曞彇娑堝師鍥�
+     * 
+     * @param dispatchOrdID 璋冨害鍗旾D
+     * @param cancelReasonId 鍙栨秷鍘熷洜ID
+     * @param cancelReasonText 鍙栨秷鍘熷洜鏂囨湰
+     * @return 褰卞搷琛屾暟
+     */
+    public int updateDispatchOrdCancelReason(@Param("dispatchOrdID") Long dispatchOrdID, 
+                                             @Param("cancelReasonId") Integer cancelReasonId,
+                                             @Param("cancelReasonText") String cancelReasonText);
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDispatchOrdService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDispatchOrdService.java
index 7c37db6..3e3efa0 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/IDispatchOrdService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IDispatchOrdService.java
@@ -86,4 +86,6 @@
      * @return 褰卞搷琛屾暟
      */
     public int updateDispatchOrdState(Long dispatchOrdID, Integer dispatchOrdState);
+
+    public void cancelDispatchOrd(Long dispatchOrdID,Integer cancelReason,String cancelCReasonTxt);
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
index 7988e3e..ce9ae98 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -125,6 +125,14 @@
     public int changeTaskStatus(Long taskId, TaskStatus newStatus, String remark);
 
     /**
+     * 寮哄埗瀹屾垚浠诲姟锛堟寚瀹氬疄闄呭紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�
+     * 
+     * @param task 浠诲姟淇℃伅锛堝寘鍚玹askId銆乼askStatus銆乤ctualStartTime銆乤ctualEndTime銆乺emark锛�
+     * @return 缁撴灉
+     */
+    public int forceCompleteTask(SysTask task);
+
+    /**
      * 鍙樻洿浠诲姟鐘舵�侊紙鍚獹PS浣嶇疆淇℃伅锛�
      * 
      * @param taskId 浠诲姟ID
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DispatchOrdServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DispatchOrdServiceImpl.java
index 6e59894..9a61dbb 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DispatchOrdServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/DispatchOrdServiceImpl.java
@@ -121,6 +121,11 @@
     public int updateDispatchOrdState(Long dispatchOrdID, Integer dispatchOrdState) {
         return dispatchOrdMapper.updateDispatchOrdState(dispatchOrdID, dispatchOrdState);
     }
-        
+
+    @Override
+    public void cancelDispatchOrd(Long dispatchOrdID, Integer cancelReason, String cancelCReasonTxt) {
+        dispatchOrdMapper.updateDispatchOrdCancelReason(dispatchOrdID, cancelReason, cancelCReasonTxt);
+    }
+
 
 } 
\ No newline at end of file
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
index 51f3800..f79c520 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -931,6 +931,67 @@
     }
 
     /**
+     * 寮哄埗瀹屾垚浠诲姟锛堟寚瀹氬疄闄呭紑濮嬫椂闂村拰缁撴潫鏃堕棿锛�
+     * 
+     * @param task 浠诲姟淇℃伅
+     * @return 缁撴灉
+     */
+    @Override
+    public int forceCompleteTask(SysTask task) {
+        if (task == null || task.getTaskId() == null) {
+            throw new RuntimeException("浠诲姟淇℃伅涓嶈兘涓虹┖");
+        }
+        
+        SysTask oldTask = sysTaskMapper.selectSysTaskByTaskId(task.getTaskId());
+        if (oldTask == null) {
+            throw new RuntimeException("浠诲姟涓嶅瓨鍦�");
+        }
+        
+        // 鏍¢獙寮�濮嬫椂闂村拰缁撴潫鏃堕棿
+        if (task.getActualStartTime() == null || task.getActualEndTime() == null) {
+            throw new RuntimeException("瀹為檯寮�濮嬫椂闂村拰缁撴潫鏃堕棿涓嶈兘涓虹┖");
+        }
+        
+        if (task.getActualStartTime().after(task.getActualEndTime())) {
+            throw new RuntimeException("缁撴潫鏃堕棿蹇呴』澶т簬寮�濮嬫椂闂�");
+        }
+        
+        // 璁板綍鏃х姸鎬�
+        String oldStatus = oldTask.getTaskStatus();
+        TaskStatus oldTaskStatus = TaskStatus.getByCode(oldStatus);
+        
+        // 鏇存柊浠诲姟
+        int result = sysTaskMapper.updateTaskStatus(task);
+        
+        // 璁板綍鎿嶄綔鏃ュ織
+        if (result > 0) {
+            recordTaskLog(task.getTaskId(), "FORCE_COMPLETE", "寮哄埗瀹屾垚浠诲姟", 
+                         oldStatus, task.getTaskStatus(), 
+                         SecurityUtils.getUserId(), SecurityUtils.getUsername());
+            
+            // 鍙戝竷浠诲姟鐘舵�佸彉鏇翠簨浠�
+            TaskStatus newTaskStatus = TaskStatus.getByCode(task.getTaskStatus());
+            eventPublisher.publishEvent(new TaskStatusChangedEvent(
+                this,
+                task.getTaskId(),
+                oldTask.getTaskCode(),
+                oldStatus,
+                task.getTaskStatus(),
+                oldTaskStatus != null ? oldTaskStatus.getInfo() : "鏈煡",
+                newTaskStatus != null ? newTaskStatus.getInfo() : "鏈煡",
+                null, // assigneeIds
+                SecurityUtils.getUserId(),
+                SecurityUtils.getUserId(),
+                null, // longitude
+                null, // latitude
+                null  // address
+            ));
+        }
+        
+        return result;
+    }
+
+    /**
      * 鍙樻洿浠诲姟鐘舵�侊紙鍚獹PS浣嶇疆淇℃伅锛�
      * 
      * @param taskId 浠诲姟ID
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskStatusPushServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskStatusPushServiceImpl.java
index a9b79e8..72e4127 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskStatusPushServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskStatusPushServiceImpl.java
@@ -129,6 +129,10 @@
                     log.info("鍙栨秷杞繍浠诲姟: {}", emergency.getLegacyServiceOrdId());
                     cancelTask(emergency.getLegacyServiceOrdId(), emergency.getCancelReason(), emergency.getCancelBy());
                 }
+                if(LongUtil.isNotEmpty(emergency.getLegacyDispatchOrdId())){
+                    log.info("鍙栨秷璋冨害鍗�: {}", emergency.getLegacyDispatchOrdId());
+                    cancelDispatch(emergency.getLegacyDispatchOrdId(), emergency.getCancelReason(), emergency.getCancelBy());
+                }
             }
             // 鎺ㄩ�佺姸鎬佸埌鏃х郴缁�
             boolean result = updateLegacyTaskStatus(emergency.getLegacyDispatchOrdId(), targetStatusCode);
@@ -150,7 +154,11 @@
             return false;
         }
     }
-    
+
+    private void cancelDispatch(Long legacyDispatchOrdId, String cancelReason, String cancelReasonText) {
+        dispatchOrdService.cancelDispatchOrd(legacyDispatchOrdId, Integer.parseInt(cancelReason), cancelReasonText);
+    }
+
     /**
      * 鎵归噺鎺ㄩ�佷换鍔$姸鎬佸埌鏃х郴缁�
      * 浣跨敤鍒嗛〉鏌ヨ锛岀‘淇濇墍鏈夌鍚堟潯浠剁殑浠诲姟閮借兘琚帹閫�
@@ -223,7 +231,7 @@
     private void cancelTask(Long serviceOrderId, String cancelReason, String cancelBy){
         // 鍙栨秷浠诲姟锛屾洿鏂癝QL Server涓殑ServiceOrder琛�
         try {
-            if (serviceOrderId == null || serviceOrderId <= 0) {
+            if (LongUtil.isEmpty(serviceOrderId)) {
                 log.warn("ServiceOrderID涓虹┖锛屾棤娉曞彇娑堜换鍔�");
                 return;
             }
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskSyncUtilService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskSyncUtilService.java
index de82106..35aa96a 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskSyncUtilService.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TaskSyncUtilService.java
@@ -228,12 +228,12 @@
         params.put("CommissionScenarioID", "0"); // 浼佸井缁╂晥鏂规
         params.put("ServiceOrdOperationRemarks", "鏂扮郴缁熷悓姝ュ垱寤�"); // 鎿嶄綔澶囨敞
         params.put("ServiceOrdEstimatedOrderDate", ""); // 棰勮娲惧崟鏃堕棿
-        params.put("ServiceOrdSource", "10"); // 璁㈠崟鏉ユ簮锛�10=鏂扮郴缁燂級
+        params.put("ServiceOrdSource", "0"); // 璁㈠崟鏉ユ簮锛�10=鏂扮郴缁燂級
         params.put("OrderLevel", "0"); // 鏌ョ湅绛夌骇
         params.put("ServiceOrdDepartureType", "1"); // 棰勭害绫诲瀷
         params.put("ConditionLevel", "0"); // 鐥呴噸绾у埆
         params.put("DirectionType", "0"); // 杞繍鍘诲悜
-        params.put("ServiceOrd_m", ""); // 鏉ユ簮鍏ュ彛
+        params.put("ServiceOrd_m", "1"); // 鏉ユ簮鍏ュ彛
         params.put("FromHQ2_is", "0"); // 骞垮窞鎬婚儴鎺ㄩ�佷换鍔℃爣璁�
         params.put("OrderPrice_Auto", "0"); // 璁㈠崟鑷姩鎶ヤ环鍙傝�冨��
 
diff --git a/ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml b/ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml
index e6379d2..565843e 100644
--- a/ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml
@@ -107,5 +107,13 @@
         set DispatchOrdState = #{dispatchOrdState}
         where DispatchOrdID = #{dispatchOrdID}
     </update>
+    
+    <!-- 鏇存柊璋冨害鍗曞彇娑堝師鍥� -->
+    <update id="updateDispatchOrdCancelReason">
+        update DispatchOrd 
+        set DispatchOrdCancelReason = #{cancelReasonId},
+            DispatchOrdCancelReasonTXT = #{cancelReasonText}
+        where DispatchOrdID = #{dispatchOrdID}
+    </update>
 
 </mapper> 
\ No newline at end of file
diff --git "a/\345\217\226\346\266\210\345\216\237\345\233\240\345\220\214\346\255\245\345\210\260DispatchOrd\345\212\237\350\203\275\350\257\264\346\230\216.md" "b/\345\217\226\346\266\210\345\216\237\345\233\240\345\220\214\346\255\245\345\210\260DispatchOrd\345\212\237\350\203\275\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..86b4c94
--- /dev/null
+++ "b/\345\217\226\346\266\210\345\216\237\345\233\240\345\220\214\346\255\245\345\210\260DispatchOrd\345\212\237\350\203\275\350\257\264\346\230\216.md"
@@ -0,0 +1,273 @@
+# 鍙栨秷鍘熷洜鍚屾鍒癉ispatchOrd鍔熻兘璇存槑
+
+## 鍔熻兘姒傝堪
+
+鍦ㄨ浆杩愪换鍔″彇娑堟椂锛岃嚜鍔ㄥ皢鍙栨秷鍘熷洜鍚屾鍒版棫绯荤粺鐨凞ispatchOrd琛ㄤ腑锛岃褰曞埌`DispatchOrdCancelReason`锛堝彇娑堝師鍥營D锛夊拰`DispatchOrdCancelReasonTXT`锛堝彇娑堝師鍥犳枃鏈級瀛楁銆�
+
+## 瀹炵幇鏂瑰紡
+
+### 1. 鏍稿績娴佺▼
+
+褰撲换鍔$姸鎬佸彉鏇翠负"宸插彇娑�"鏃讹細
+1. 鐩戝惉鍣�(`DispatchOrdRunningListener`)妫�娴嬪埌鍙栨秷鐘舵��
+2. 浠巂sys_task_emergency`琛ㄨ幏鍙栧彇娑堝師鍥狅紙cancelReason瀛楁锛屽瓨鍌ㄦ暟鎹瓧鍏竩alue锛�
+3. 灏嗗彇娑堝師鍥爒alue杞崲涓篒nteger浣滀负DispatchOrdCancelReason
+4. 浠庢暟鎹瓧鍏竊task_cancel_reason`鏌ヨ瀵瑰簲鐨勪腑鏂囨爣绛句綔涓篋ispatchOrdCancelReasonTXT
+5. 鎵цUPDATE璇彞鏇存柊DispatchOrd琛�
+
+### 2. 鏁版嵁鏄犲皠鍏崇郴
+
+| 鏂扮郴缁熷瓧娈� | 鏂扮郴缁熻〃 | 鏃х郴缁熷瓧娈� | 鏃х郴缁熻〃 | 璇存槑 |
+|-----------|---------|-----------|---------|------|
+| cancel_reason | sys_task_emergency | DispatchOrdCancelReason | DispatchOrd | 鍙栨秷鍘熷洜ID锛堟暟瀛楋級 |
+| 瀛楀吀label | sys_dict_data | DispatchOrdCancelReasonTXT | DispatchOrd | 鍙栨秷鍘熷洜鏂囨湰锛堜腑鏂囷級 |
+
+### 3. 鍙栨秷鍘熷洜瀛楀吀
+
+鏁版嵁瀛楀吀绫诲瀷锛歚task_cancel_reason`
+
+鍖呭惈26涓彇娑堝師鍥犻�夐」锛�
+1. 浠锋牸涓嶆帴鍙�
+2. 鏃堕棿绱ф��
+3. 鍑鸿溅閫熷害鎱�
+4. 閫夋嫨鍏朵粬杞�
+5. 鐥呬汉娌℃湁鐢熷懡浣撳緛
+6. 鍖荤敓鎶ゅ+鍧囦笉瓒�
+7. 鐥呬汉鎯呭喌鏈夊彉
+8. 鍏朵粬
+9. 绗笁鏂瑰彇娑�
+10. 娴嬭瘯璁㈠崟
+11. 浼犳煋鎬х柧鐥�
+12. 瀹跺睘鎸傛満/鎷掓帴/涓嶆帴鐢佃瘽
+13. 鎶ゅ+涓嶈冻
+14. 鍖荤敓涓嶈冻
+15. 璁惧涓嶈冻(鍛煎惛鏈�)
+16. 瀹跺睘娌¤仈绯诲ソ搴婁綅
+17. 绉讳氦鍒嗘敮鏈烘瀯鎵ц
+18. 绉讳氦鍔炰簨澶�(婀涙睙)
+19. 绉讳氦鍔炰簨澶�(鑼傚悕)
+20. 瀹跺睘涓嶈偗閫忛湶淇℃伅
+21. 鎵�鍦ㄥ尰闄�/鐩殑鍦板尰闄㈡淳杞�
+22. 鑷┚杞︽帴閫佹偅鑰�
+23. 閫夋嫨鍏朵粬鏈烘瀯杞﹁締
+24. 澶栬仈閫氱煡鍙栨秷
+25. 澶栬仈鏃犲弽棣�
+26. 瀹跺睘鏃犲師鍥犵洿鎺ュ憡鐭ュ彇娑�
+
+## 浠g爜淇敼娓呭崟
+
+### 1. DispatchOrdMapper.java
+
+**鏂囦欢璺緞**: `ruoyi-system/src/main/java/com/ruoyi/system/mapper/DispatchOrdMapper.java`
+
+**鏂板鏂规硶**:
+```java
+/**
+ * 鏇存柊璋冨害鍗曞彇娑堝師鍥�
+ * 
+ * @param dispatchOrdID 璋冨害鍗旾D
+ * @param cancelReasonId 鍙栨秷鍘熷洜ID
+ * @param cancelReasonText 鍙栨秷鍘熷洜鏂囨湰
+ * @return 褰卞搷琛屾暟
+ */
+public int updateDispatchOrdCancelReason(@Param("dispatchOrdID") Long dispatchOrdID, 
+                                         @Param("cancelReasonId") Integer cancelReasonId,
+                                         @Param("cancelReasonText") String cancelReasonText);
+```
+
+### 2. DispatchOrdMapper.xml
+
+**鏂囦欢璺緞**: `ruoyi-system/src/main/resources/mapper/system/DispatchOrdMapper.xml`
+
+**鏂板SQL**:
+```xml
+<!-- 鏇存柊璋冨害鍗曞彇娑堝師鍥� -->
+<update id="updateDispatchOrdCancelReason">
+    update DispatchOrd 
+    set DispatchOrdCancelReason = #{cancelReasonId},
+        DispatchOrdCancelReasonTXT = #{cancelReasonText}
+    where DispatchOrdID = #{dispatchOrdID}
+</update>
+```
+
+### 3. DispatchOrdRunningListener.java
+
+**鏂囦欢璺緞**: `ruoyi-system/src/main/java/com/ruoyi/system/listener/DispatchOrdRunningListener.java`
+
+**鏂板渚濊禆娉ㄥ叆**:
+```java
+@Autowired
+private DispatchOrdMapper dispatchOrdMapper;
+
+@Autowired
+private SysDictDataMapper sysDictDataMapper;
+```
+
+**鏂板import**:
+```java
+import com.ruoyi.system.mapper.DispatchOrdMapper;
+import com.ruoyi.system.mapper.SysDictDataMapper;
+```
+
+**淇敼handleTaskStatusChangedEvent鏂规硶**:
+鍦ㄧ姸鎬佽浆鎹㈠悗澧炲姞鍙栨秷鍘熷洜鍚屾閫昏緫锛堢122-126琛岋級锛�
+```java
+// 濡傛灉鏄彇娑堢姸鎬侊紝鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd琛�
+if (TaskStatus.CANCELLED.equals(newTaskStatus) && emergency.getCancelReason() != null) {
+    syncCancelReasonToDispatchOrd(emergency.getLegacyDispatchOrdId(), emergency.getCancelReason());
+}
+```
+
+**鏂板鏂规硶**:
+```java
+/**
+ * 鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd琛�
+ * 
+ * @param dispatchOrdId 璋冨害鍗旾D
+ * @param cancelReason 鍙栨秷鍘熷洜锛堝瓧鍏竩alue锛�
+ */
+private void syncCancelReasonToDispatchOrd(Long dispatchOrdId, String cancelReason) {
+    try {
+        if (cancelReason == null || cancelReason.isEmpty()) {
+            log.debug("鍙栨秷鍘熷洜涓虹┖锛岃烦杩囧悓姝ワ紝DispatchOrdID: {}", dispatchOrdId);
+            return;
+        }
+        
+        // 灏哻ancelReason锛堝瓧鍏竩alue锛夎浆鎹负Integer
+        Integer cancelReasonId = null;
+        try {
+            cancelReasonId = Integer.valueOf(cancelReason);
+        } catch (NumberFormatException e) {
+            log.error("鍙栨秷鍘熷洜鏍煎紡閿欒锛屾棤娉曡浆鎹负鏁板瓧锛宑ancelReason: {}, DispatchOrdID: {}", cancelReason, dispatchOrdId);
+            return;
+        }
+        
+        // 浠庢暟鎹瓧鍏镐腑鏌ヨ鍙栨秷鍘熷洜鏂囨湰
+        String cancelReasonText = sysDictDataMapper.selectDictLabel("task_cancel_reason", cancelReason);
+        if (cancelReasonText == null || cancelReasonText.isEmpty()) {
+            log.warn("鏈壘鍒板彇娑堝師鍥犲搴旂殑鏂囨湰锛宑ancelReason: {}, DispatchOrdID: {}", cancelReason, dispatchOrdId);
+            cancelReasonText = cancelReason; // 浣跨敤鍘熷�间綔涓洪澶�
+        }
+        
+        log.info("寮�濮嬪悓姝ュ彇娑堝師鍥犲埌DispatchOrd锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜ID: {}, 鍙栨秷鍘熷洜鏂囨湰: {}", 
+            dispatchOrdId, cancelReasonId, cancelReasonText);
+        
+        // 璋冪敤Mapper鏇存柊DispatchOrd琛�
+        int rows = dispatchOrdMapper.updateDispatchOrdCancelReason(dispatchOrdId, cancelReasonId, cancelReasonText);
+        
+        if (rows > 0) {
+            log.info("鎴愬姛鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜: {} ({})", 
+                dispatchOrdId, cancelReasonText, cancelReasonId);
+        } else {
+            log.warn("鍚屾鍙栨秷鍘熷洜澶辫触锛屾湭鎵惧埌瀵瑰簲鐨勮皟搴﹀崟锛孌ispatchOrdID: {}", dispatchOrdId);
+        }
+        
+    } catch (Exception e) {
+        log.error("鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd寮傚父锛孌ispatchOrdID: {}, 鍙栨秷鍘熷洜: {}", 
+            dispatchOrdId, cancelReason, e);
+        // 涓嶆姏鍑哄紓甯革紝閬垮厤褰卞搷涓绘祦绋�
+    }
+}
+```
+
+## 鎵цSQL绀轰緥
+
+褰撳彇娑堝師鍥犱负"浠锋牸涓嶆帴鍙�"锛坴alue=1锛夋椂锛屽疄闄呮墽琛岀殑SQL锛�
+
+```sql
+UPDATE DispatchOrd 
+SET DispatchOrdCancelReason = 1,
+    DispatchOrdCancelReasonTXT = '浠锋牸涓嶆帴鍙�'
+WHERE DispatchOrdID = 12345
+```
+
+## 鍚屾鏉′欢
+
+鍙栨秷鍘熷洜鍚屾闇�瑕佹弧瓒充互涓嬫潯浠讹細
+1. 鉁� 鏃х郴缁熷悓姝ュ凡鍚敤
+2. 鉁� 浠诲姟绫诲瀷涓烘�ユ晳杞繍浠诲姟锛圗MERGENCY_TRANSFER锛�
+3. 鉁� 浠诲姟宸插悓姝ュ埌鏃х郴缁燂紙鏈塴egacyDispatchOrdId锛�
+4. 鉁� 浠诲姟鐘舵�佸彉鏇翠负"宸插彇娑�"锛圕ANCELLED锛�
+5. 鉁� 浠诲姟璁板綍浜嗗彇娑堝師鍥狅紙cancelReason涓嶄负绌猴級
+
+## 鎵ц鏃舵満
+
+**寮傛鎵ц**锛氫换鍔$姸鎬佸彉鏇翠负"宸插彇娑�"鏃讹紝閫氳繃浜嬩欢鐩戝惉鍣ㄥ紓姝ュ鐞嗭紝涓嶅奖鍝嶄富涓氬姟娴佺▼銆�
+
+## 鏃ュ織璇存槑
+
+### 姝e父鏃ュ織
+```
+INFO - 鏀跺埌浠诲姟鐘舵�佸彉鏇翠簨浠讹紝鍑嗗鍚屾鍒癉ispatchOrd_Running锛屼换鍔D锛�1001锛屾棫鐘舵�侊細PENDING锛屾柊鐘舵�侊細CANCELLED
+INFO - 寮�濮嬪悓姝ュ彇娑堝師鍥犲埌DispatchOrd锛孌ispatchOrdID: 12345, 鍙栨秷鍘熷洜ID: 1, 鍙栨秷鍘熷洜鏂囨湰: 浠锋牸涓嶆帴鍙�
+INFO - 鎴愬姛鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd锛孌ispatchOrdID: 12345, 鍙栨秷鍘熷洜: 浠锋牸涓嶆帴鍙� (1)
+```
+
+### 璺宠繃鏃ュ織
+```
+DEBUG - 鍙栨秷鍘熷洜涓虹┖锛岃烦杩囧悓姝ワ紝DispatchOrdID: 12345
+```
+
+### 寮傚父鏃ュ織
+```
+WARN - 鏈壘鍒板彇娑堝師鍥犲搴旂殑鏂囨湰锛宑ancelReason: 99, DispatchOrdID: 12345
+ERROR - 鍙栨秷鍘熷洜鏍煎紡閿欒锛屾棤娉曡浆鎹负鏁板瓧锛宑ancelReason: abc, DispatchOrdID: 12345
+WARN - 鍚屾鍙栨秷鍘熷洜澶辫触锛屾湭鎵惧埌瀵瑰簲鐨勮皟搴﹀崟锛孌ispatchOrdID: 12345
+ERROR - 鍚屾鍙栨秷鍘熷洜鍒癉ispatchOrd寮傚父锛孌ispatchOrdID: 12345, 鍙栨秷鍘熷洜: 1
+```
+
+## 楠岃瘉鏂规硶
+
+### 1. 鏁版嵁搴撻獙璇�
+
+鏌ヨDispatchOrd琛ㄧ殑鍙栨秷鍘熷洜瀛楁锛�
+```sql
+SELECT 
+    DispatchOrdID,
+    DispatchOrdState,
+    DispatchOrdCancelReason,
+    DispatchOrdCancelReasonTXT
+FROM DispatchOrd
+WHERE DispatchOrdID = 12345
+```
+
+### 2. 娴嬭瘯姝ラ
+
+1. 鍦ㄦ柊绯荤粺鍒涘缓杞繍浠诲姟骞跺悓姝ュ埌鏃х郴缁�
+2. 鍦ㄤ换鍔¤鎯呴〉鐐瑰嚮"鍙栨秷"鎸夐挳
+3. 閫夋嫨鍙栨秷鍘熷洜锛堝"浠锋牸涓嶆帴鍙�"锛�
+4. 纭鍙栨秷
+5. 妫�鏌ュ簲鐢ㄦ棩蹇楋紝纭鍚屾鎴愬姛
+6. 鏌ヨDispatchOrd琛紝楠岃瘉鍙栨秷鍘熷洜瀛楁宸叉洿鏂�
+
+### 3. 棰勬湡缁撴灉
+
+- DispatchOrdCancelReason = 1
+- DispatchOrdCancelReasonTXT = "浠锋牸涓嶆帴鍙�"
+
+## 鐗规�ц鏄�
+
+1. **寮傛澶勭悊**锛氫笉闃诲涓讳笟鍔℃祦绋嬶紝鎻愰珮绯荤粺鍝嶅簲閫熷害
+2. **瀹归敊鏈哄埗**锛氬悓姝ュけ璐ヤ笉褰卞搷浠诲姟鍙栨秷鎿嶄綔
+3. **瀹屾暣鏃ュ織**锛氳褰曡缁嗙殑鍚屾杩囩▼鍜岀粨鏋�
+4. **鏁版嵁杞崲**锛氳嚜鍔ㄥ皢瀛楀吀value杞崲涓篒D鍜屾枃鏈�
+5. **浜嬩欢椹卞姩**锛氬埄鐢⊿pring浜嬩欢鏈哄埗瀹炵幇瑙h��
+6. **鑷姩鍚屾**锛氭棤闇�鎵嬪姩瑙﹀彂锛岀姸鎬佸彉鏇存椂鑷姩鎵ц
+
+## 娉ㄦ剰浜嬮」
+
+1. **浠呴檺鍙栨秷鐘舵��**锛氬彧鏈変换鍔$姸鎬佸彉鏇翠负CANCELLED鏃舵墠浼氬悓姝ュ彇娑堝師鍥�
+2. **渚濊禆璋冨害鍗曞悓姝�**锛氬繀椤诲厛鍚屾鍒版棫绯荤粺鎵嶈兘鏇存柊鍙栨秷鍘熷洜
+3. **寮傚父涓嶄腑鏂�**锛氬悓姝ュ紓甯稿彧璁板綍鏃ュ織锛屼笉鎶涘嚭寮傚父锛屼笉褰卞搷涓绘祦绋�
+4. **鏁版嵁搴撳瓧娈�**锛氱‘淇滵ispatchOrd琛ㄦ湁DispatchOrdCancelReason鍜孌ispatchOrdCancelReasonTXT瀛楁
+5. **瀛楀吀渚濊禆**锛氫緷璧栨暟鎹瓧鍏竧ask_cancel_reason姝g‘閰嶇疆
+
+## 鐩稿叧鏂囨。
+
+- [鍙栨秷鍘熷洜鍔熻兘瀹炵幇璇存槑.md](./鍙栨秷鍘熷洜鍔熻兘瀹炵幇璇存槑.md)
+- [杞繍浠诲姟鐘舵�佸彉鏇磋褰曞悓姝ュ姛鑳借鏄�.md](./prd/杞繍浠诲姟鐘舵�佸彉鏇磋褰曞悓姝ュ姛鑳借鏄�.md)
+- [鏃х郴缁熷悓姝�-蹇�熷紑濮�.md](./prd/鏃х郴缁熷悓姝�-蹇�熷紑濮�.md)
+
+## 鏇存柊鏃ュ織
+
+- 2025-12-26: 瀹炵幇鍙栨秷鍘熷洜鍚屾鍒癉ispatchOrd鍔熻兘

--
Gitblit v1.9.1