Эх сурвалжийг харах

新增订单维度强制结算功能,修复设备维度结算按钮无反应

后端:新增 POST /washOrder/forceSettle/{orderId} 接口,按订单号查运行中
订单并强制结束;WashOrderService 新增 forceSettleOrder 方法。
前端:admin-web 操作栏结算按钮 onClick 填空补全;admin-web-new 操作栏
新增结算按钮;两端订单列表均新增强制结算按钮(仅对进行中订单显示)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 3 өдөр өмнө
parent
commit
38988e07dd

+ 28 - 1
admin-web-new/src/views/admin/ordering/index.vue

@@ -2,6 +2,7 @@
 import { reactive, onMounted, ref, nextTick } from "vue";
 import { http } from "@/utils/http";
 import { message } from "@/utils/message";
+import { ElMessage, ElMessageBox } from "element-plus";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { ExtDLabel, ExtDSelect } from "@/components/ExtForm";
 import { formatDict } from "@/utils/dict";
@@ -108,6 +109,23 @@ const handleView = (row: any) => {
   dialogRef.value?.open(row);
 };
 
+const handleForceSettle = (row: any) => {
+  ElMessageBox.confirm(
+    `确定要对订单「${row.orderId}」进行强制结算吗?此操作将强制结束该订单并释放设备。`,
+    "提示",
+    {
+      confirmButtonText: "确定结算",
+      cancelButtonText: "取消",
+      type: "warning"
+    }
+  ).then(() => {
+    http.request("post", `/washOrder/forceSettle/${row.orderId}`).then(() => {
+      ElMessage.success("强制结算成功");
+      loadData(true);
+    });
+  }).catch(() => {});
+};
+
 const formatMoney = (value: number) => {
   if (value === null || value === undefined) return "0.00";
   return (value / 100).toFixed(2);
@@ -258,11 +276,20 @@ const processDetailData = (detail: any[]) => {
             </template>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="100" fixed="right">
+        <el-table-column label="操作" width="160" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" link size="small" @click="handleView(row)">
               详情
             </el-button>
+            <el-button
+              v-if="row.orderStatus === 0"
+              type="danger"
+              link
+              size="small"
+              @click="handleForceSettle(row)"
+            >
+              强制结算
+            </el-button>
           </template>
         </el-table-column>
       </el-table>

+ 22 - 1
admin-web-new/src/views/admin/station/device.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 import { reactive, onMounted, ref, nextTick, watch } from "vue";
 import { getStationDeviceList, removeDevice, getStationList } from "@/api/station";
+import { stopDevice } from "@/api/device";
 import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import { useRoute, useRouter } from "vue-router";
 import { ElMessage, ElMessageBox } from "element-plus";
@@ -218,6 +219,23 @@ const handleRemoteControl = (row: any) => {
   }
   remoteDialogRef.value?.open(row);
 };
+
+const handleSettle = (row: any) => {
+  ElMessageBox.confirm(
+    `确定要对设备「${row.shortId}」进行强制结算吗?此操作将强制结束该设备当前未完结的订单。`,
+    "提示",
+    {
+      confirmButtonText: "确定结算",
+      cancelButtonText: "取消",
+      type: "warning"
+    }
+  ).then(() => {
+    stopDevice(row.shortId).then(() => {
+      ElMessage.success("结算成功");
+      loadData(true);
+    });
+  }).catch(() => {});
+};
 </script>
 
 <template>
@@ -384,11 +402,14 @@ const handleRemoteControl = (row: any) => {
             </template>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="240" fixed="right">
+        <el-table-column label="操作" width="320" fixed="right">
           <template #default="{ row }">
             <el-button type="primary" link size="small" @click="handleRemoteControl(row)">
               远程控制
             </el-button>
+            <el-button type="success" link size="small" @click="handleSettle(row)">
+              结算
+            </el-button>
             <el-button type="warning" link size="small" @click="handleEdit(row)">
               编辑
             </el-button>

+ 25 - 0
admin-web/src/views/admin/ordering/index.vue

@@ -159,6 +159,19 @@
 
           </template>
         </el-table-column>
+        <el-table-column label="操作" width="100" fixed="right">
+          <template #default="{row}">
+            <el-button
+              v-if="row.orderStatus === 0"
+              type="danger"
+              text
+              size="small"
+              @click="handleForceSettle(row)"
+            >
+              强制结算
+            </el-button>
+          </template>
+        </el-table-column>
       </el-table>
 
       <ext-page class="page-pager" v-model:value="state.pageQuery" @change="loadData(false)"/>
@@ -178,6 +191,7 @@ import u from '/@/utils/u'
 import {Msg} from "/@/utils/message";
 import {Session} from "/@/utils/storage";
 import {useRoute} from "vue-router";
+import {ElButton} from 'element-plus';
 
 const route = useRoute();
 
@@ -340,6 +354,17 @@ const handleRowDelete = (row: any) => {
     })
   });
 };
+// 强制结算
+const handleForceSettle = (row: any) => {
+  Msg.confirm(`确定要对订单「${row.orderId}」进行强制结算吗?此操作将强制结束该订单并释放设备。`).then(() => {
+    $body(`/washOrder/forceSettle/${row.orderId}`).then(() => {
+      Msg.message("强制结算成功", 'success')
+      loadData(true)
+    }).catch(() => {
+      Msg.message("强制结算失败", 'error')
+    })
+  });
+};
 
 //endregion
 

+ 11 - 2
admin-web/src/views/admin/station/device/index.vue

@@ -87,7 +87,7 @@ import ExtTable from "/@/components/form/ExtTable.vue";
 
 import mittBus from '/@/utils/mitt';
 
-import {ElButton} from 'element-plus'
+import {ElButton, ElMessageBox} from 'element-plus'
 
 import DeviceDialog from '/@/views/admin/station/device/dialog.vue'
 import DeviceConfigDialog from '/@/views/admin/station/device/config.vue'
@@ -196,7 +196,16 @@ const state = reactive({
                     text: true,
                     size: 'small',
                     onClick: () => {
-                      // handleRowClick('edit', row)
+                      ElMessageBox.confirm(`确定要对设备「${rowData.shortId}」进行强制结算吗?此操作将强制结束该设备当前未完结的订单。`, '提示', {
+                        confirmButtonText: '确定结算',
+                        cancelButtonText: '取消',
+                        type: 'warning'
+                      }).then(() => {
+                        $body(`/washDevice/stopDevice/${rowData.shortId}`).then(() => {
+                          Msg.message('结算成功', 'success')
+                          loadData(true)
+                        })
+                      }).catch(() => {})
                     }
                   }, () => '结算') : '',
               proxy.$auth('washDevice.remove') ?

+ 13 - 0
car-wash-admin/src/main/java/com/kym/admin/controller/WashOrderController.java

@@ -60,5 +60,18 @@ public class WashOrderController extends IController {
         return R.success(washOrderService.detail(orderId));
     }
 
+    /**
+     * 强制结算订单(订单维度)
+     *
+     * @param orderId 订单号
+     */
+    @SaCheckPermission("washOrder.modify")
+    @SysLog("强制结算")
+    @PostMapping("/forceSettle/{orderId}")
+    R<?> forceSettle(@PathVariable String orderId) {
+        washOrderService.forceSettleOrder(orderId);
+        return R.success();
+    }
+
 
 }

+ 2 - 0
car-wash-service/src/main/java/com/kym/service/WashOrderService.java

@@ -44,6 +44,8 @@ public interface WashOrderService extends MyBaseService<WashOrder> {
      */
     WashOrderVo detail(String orderId);
 
+    void forceSettleOrder(String orderId);
+
     Map<String, Integer> sumAmountByDate(LocalDate statDay);
 
     Map<String, Integer> sumMonthAmount(LocalDate statDay);

+ 42 - 0
car-wash-service/src/main/java/com/kym/service/impl/WashOrderServiceImpl.java

@@ -189,6 +189,48 @@ public class WashOrderServiceImpl extends MyBaseServiceImpl<WashOrderMapper, Was
         throw new BusinessException("关闭订单失败,请稍后重试或联系管理员");
     }
 
+    @Override
+    public void forceSettleOrder(String orderId) {
+        var order = lambdaQuery()
+                .eq(WashOrder::getOrderId, orderId)
+                .eq(WashOrder::getOrderStatus, WashOrder.ORDER_STATUS_开机)
+                .eq(WashOrder::getPayStatus, WashOrder.PAY_STATUS_未支付)
+                .one();
+        if (order == null) {
+            throw new BusinessException("未找到该订单或订单已完结");
+        }
+
+        // 尝试通过 RRpc 发送关闭命令
+        boolean sent = sendCloseOrderWithRetry(order.getProductKey(), order.getDeviceName(), order.getOrderId(), 1);
+        if (sent) {
+            return;
+        }
+
+        // RRpc 失败,主动查询设备订单状态
+        log.warn("订单 {} 关闭命令发送失败,尝试主动查询设备状态", order.getOrderId());
+        try {
+            OrderInfo orderInfo = awoaraService.queryOrder(order.getProductKey(), order.getDeviceName(), order.getOrderId());
+            if (orderInfo != null && orderInfo.getClose_type() != null && !orderInfo.getClose_type().isEmpty()) {
+                log.info("订单 {} 设备已关闭,执行结算", order.getOrderId());
+                orderSettlementService.settleOrder(order, orderInfo);
+                return;
+            }
+            if (orderInfo != null && (orderInfo.getClose_type() == null || orderInfo.getClose_type().isEmpty())) {
+                // 设备还在运行,尝试强制关闭
+                log.info("订单 {} 设备仍在运行,尝试强制关闭", order.getOrderId());
+                try {
+                    awoaraService.forceCloseOrder(order.getProductKey(), order.getDeviceName());
+                } catch (Exception fe) {
+                    log.error("订单 {} 强制关闭也失败", order.getOrderId(), fe);
+                }
+            }
+        } catch (Exception e) {
+            log.error("订单 {} 主动查询设备状态也失败", order.getOrderId(), e);
+        }
+
+        throw new BusinessException("强制结算失败,请稍后重试或联系管理员");
+    }
+
     private boolean sendCloseOrderWithRetry(String productKey, String deviceName, String orderId, int maxRetries) {
         for (int i = 0; i <= maxRetries; i++) {
             try {