qmj 1 месяц назад
Родитель
Сommit
3e45a2d773

+ 0 - 143
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.java

@@ -78,149 +78,6 @@ public class PosStoreController extends BaseController {
     private IPosOrderService posOrderService;
 
 
-    /**
-     * 商家端下单
-     */
-    @PostMapping("/createOrder")
-    @Auth
-    @Anonymous
-    @Transactional(rollbackFor = Exception.class)
-    public AjaxResult createOrder(@RequestHeader String token, @RequestBody OrderCreateInput input)
-    {
-        JwtUtil jwtUtil = new JwtUtil();
-        String id = jwtUtil.getusid(token);
-        String shdz=null;
-        createOrderParent(input,Long.valueOf(id),shdz);
-        createOrderChild(input,Long.valueOf(id),Long.valueOf(id),shdz);
-
-        OrderParent result = orderParentService.getOne(new LambdaQueryWrapper<OrderParent>().eq(OrderParent::getDdId, input.getDdId().toString()));
-        System.out.println("创建返回信息:"+result);
-        return super.success(result);
-    }
-
-    //创建父订单
-    private void createOrderParent(OrderCreateInput input,Long UserId,String shdz){
-        OrderParent orderParent = new OrderParent();
-        orderParent.setUserId(UserId);
-        orderParent.setOrderStatus(0L);
-        orderParent.setTotalAmount(input.getTotalAmount());
-        orderParent.setActualPayAmount(input.getActualPayAmount());
-        orderParent.setPaymentMethod(input.getPaymentMethod());
-//        orderParent.setPaymentTime(new Date());
-        orderParent.setCreateTime(new Date());
-//        orderParent.setUpdateTime(new Date());
-        orderParent.setDdId(input.getDdId().toString());
-        orderParent.setTableId(input.getTableId());
-        orderParent.setShdzId(input.getShdzId());
-        orderParent.setShAddress(shdz);
-        orderParent.setType(input.getType());
-        orderParent.setRemarks(input.getRemarks());
-        orderParentService.save(orderParent);
-    }
-    //创建子订单
-    private void createOrderChild(OrderCreateInput input,Long orderParentId,Long userId,String shdz){
-        // 检查items是否为空
-        if (input.getItems() == null || input.getItems().isEmpty()) {
-            throw new ServiceException(MessageUtils.message("订单商品不能为空"));
-        }
-        String ddId = input.getDdId().toString();
-        List<Long> mdIdList = input.getItems().stream().map(OrderCreatItem::getMdId).collect(Collectors.toList());
-        // 将Long类型的门店ID转换为Integer类型用于查询(因为PosStore.getId()返回Integer)
-        List<Integer> mdIdIntegerList = mdIdList.stream().map(Long::intValue).collect(Collectors.toList());
-        List<PosStore> storeList = posStoreService.list(new LambdaQueryWrapper<PosStore>().in(PosStore::getId, mdIdIntegerList));
-        LambdaQueryWrapper<OperatingHours> wrapper = new LambdaQueryWrapper<>();
-        wrapper.in(OperatingHours::getMdId, mdIdList);
-        List<OperatingHours> hourslist = operatingHoursService.list(wrapper);
-        boolean isMultiStore = input.getItems().size() > 1;
-        // 循环items,为每个item创建一条PosOrder
-        int index = 0;
-        for (OrderCreatItem item : input.getItems()) {
-            index++;
-            // 单店铺:子订单号与父订单一致;多店铺:子订单号追加序号
-            String subddId = isMultiStore ? ddId + String.format("%03d", index) : ddId;
-            PosOrder posOrder = new PosOrder();
-            posOrder.setIsDisplay(true);
-           posOrder.setDisplayTime(new Date());
-            posOrder.setShdzId(input.getShdzId());
-            // 设置子订单的基本信息
-            posOrder.setDdId(subddId);
-            posOrder.setParentDdId(input.getDdId().toString()); // 设置父订单ID
-            posOrder.setCretim(new Date());
-            posOrder.setState(2L); // 初始状态
-            // 根据item设置订单信息
-            posOrder.setShId(item.getShId());
-            posOrder.setMdId(item.getMdId());
-            posOrder.setShdzId(input.getShdzId());
-            posOrder.setUserId(userId);
-            posOrder.setAmount(item.getAmount());
-            posOrder.setRemarks(item.getRemarks());
-            posOrder.setType(item.getType());
-            posOrder.setDelryTime(item.getDelryTime());
-            posOrder.setFood(JSON.toJSONString(item.getFood()));
-            // 设置支付相关信息(从父订单继承)
-            posOrder.setPayType(input.getPaymentMethod());
-            //设置桌号(从父订单继承)
-            posOrder.setTableId(input.getTableId());
-            posOrder.setTableNo(input.getTableNo());
-            posOrder.setLogo(item.getLogo());
-            posOrder.setPosName(item.getPosName());
-            posOrder.setJvli(item.getJvli());
-            posOrder.setFreight(item.getFreight());
-            posOrder.setShdzId(input.getShdzId());
-            posOrder.setShAddress(shdz);
-            posOrder.setFoodAmount(item.getFoodAmount());
-            Optional<PosStore> storeOptional = storeList.stream()
-                    .filter(x -> item.getMdId().equals(Long.valueOf(x.getId())))
-                    .findFirst();
-            if (storeOptional.isEmpty()) {
-                throw new ServiceException(MessageUtils.message("no.mendian.not.exist"));
-            }
-            PosStore store = storeOptional.get();
-            posOrder.setLongitude(store.getLongitude());
-            posOrder.setLatitude(store.getLatitude());
-            //设置订单分类
-            storeList.stream().filter(s -> Long.valueOf(s.getId()).equals(item.getMdId())).findFirst().ifPresent(st -> {
-                posOrder.setOrderCategory(String.valueOf(st.getType()));
-            });
-            setPickUpNum(posOrder);
-            posOrderService.save(posOrder);
-        }
-    }
-
-    //生成取餐码,根据门店id,当天取餐码从1开始累加
-    private void setPickUpNum(PosOrder order) {
-        if (order == null || order.getMdId() == null) {
-            return;
-        }
-        try {
-            // 获取当前日期,格式为 yyyy-MM-dd
-            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-            String today = dateFormat.format(new Date());
-
-            // 查询当天该门店的最大取餐码
-            LambdaQueryWrapper<PosOrder> queryWrapper = new LambdaQueryWrapper<>();
-            queryWrapper.eq(PosOrder::getMdId, order.getMdId());
-            queryWrapper.like(PosOrder::getCretim, today);
-            queryWrapper.orderByDesc(PosOrder::getPickUpNum);
-            queryWrapper.last("LIMIT 1");
-
-            PosOrder lastOrder = posOrderService.getOne(queryWrapper);
-            long nextNum = 1L;
-            if (lastOrder != null && lastOrder.getPickUpNum() != null) {
-                // 直接使用Long类型的取餐码
-                nextNum = lastOrder.getPickUpNum() + 1;
-            }
-            // 生成新的取餐码:直接使用Long数字
-            order.setPickUpNum(nextNum);
-            logger.info("生成取餐码成功:门店ID={}, 取餐码={}", order.getMdId(), nextNum);
-
-        } catch (Exception e) {
-            logger.error("生成取餐码失败:门店ID={}, 错误信息={}", order.getMdId(), e.getMessage(), e);
-            // 生成失败时使用时间戳作为备用方案
-            Long fallbackPickUpNum = System.currentTimeMillis() % 10000;
-            order.setPickUpNum(fallbackPickUpNum);
-        }
-    }
 
 
     /**

+ 36 - 55
ruoyi-admin/src/main/java/com/ruoyi/app/order/OrderAppealController.java

@@ -89,63 +89,44 @@ public class OrderAppealController extends BaseController {
 
 
     /**
-     * 用户取消订单
+     * 用户取消订单 — 已废弃,由 UserOrderController.cancelOrder 替代(新状态体系)
      */
-    @Anonymous
-    @Auth
-    @GetMapping("/userCancelpOrder")
-    @Transactional(timeout = 30, rollbackFor = Exception.class)
-    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
-    public AjaxResult userCancelpOrder(@RequestHeader String token, @RequestParam Long ddId, @RequestParam String language) {
-        System.out.println("用户取消订单语言:" + language);
-        // 正确解析语言参数,支持 "zh_CN", "zh-CN", "zh", "en-US" 等格式
-        Locale locale = parseLocale(language);
-        System.out.println("解析后的Locale: language=" + locale.getLanguage() + ", country=" + locale.getCountry());
-        LocaleContextHolder.setLocale(locale);
-        JwtUtil jwtUtil = new JwtUtil();
-        String userid = jwtUtil.getusid(token);
-        LambdaQueryWrapper<PosOrder> orderQuery = new LambdaQueryWrapper<>();
-        orderQuery.eq(PosOrder::getDdId, ddId);
-        PosOrder order = posOrderService.getOne(orderQuery);
-        if (!order.getUserId().equals(Long.valueOf(userid))) {
-//            String message = MessageUtils.message("no.order.nocanceluser.message");
-            throw new ServiceException("不是订单用户,无权限操作");
-        }
-        if (order.getQsId() != null) {
-            //MessageUtils.message("no.order.hasqs.message")
-            throw new ServiceException("骑手已经接单,无法取消");
-        }
-        PosOrder update = new PosOrder();
-        update.setId(order.getId());
-        // 取消订单:state=4(已取消)
-        update.setState(4L);
-        if ("1".equals(order.getCollectPayment())) {
-            UserBilling bill = billingService.getOne(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getDdId, String.valueOf(order.getDdId())).eq(UserBilling::getUserId, Long.valueOf(userid)));
-            //账单作废
-            if (bill != null) {
-                UserBilling user = new UserBilling();
-                user.setId(bill.getId());
-                user.setState("2");  //作废
-                billingService.saveOrUpdate(user);
-            }
-        }
-        posOrderService.saveOrUpdate(update);
-//        PosAppeal appeal = new PosAppeal();
-//        appeal.setUserId(order.getUserId());
-//        appeal.setUserType("0");
-//        appeal.setDdId(order.getDdId());
-//        appeal.setContent(MessageUtils.message("no.order.hasnoqs.message"));
-//        posAppealService.insertPosAppeal(appeal);
-//        InfoUser shUer = infoUserService.getById(order.getShId());
-//        if (!StringUtils.isEmpty(shUer.getCid())) {
-//            String title =
-//                    "no.message.push.message";
-//            String body = OrderPushBodyDto.getJson(String.valueOf(order.getDdId()), String.valueOf(update.getState()));
-//            PayPush.shPushHandleLocal(new PayPush(), pushEventService, shUer.getUserId(), shUer.getCid(), title, "no.order.hasnoqs.message", body, shUer.getCidType(), String.valueOf(order.getDdId()));
+//    @Anonymous
+//    @Auth
+//    @GetMapping("/userCancelpOrder")
+//    @Transactional(timeout = 30, rollbackFor = Exception.class)
+//    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+//    public AjaxResult userCancelpOrder(@RequestHeader String token, @RequestParam Long ddId, @RequestParam String language) {
+//        System.out.println("用户取消订单语言:" + language);
+//        Locale locale = parseLocale(language);
+//        System.out.println("解析后的Locale: language=" + locale.getLanguage() + ", country=" + locale.getCountry());
+//        LocaleContextHolder.setLocale(locale);
+//        JwtUtil jwtUtil = new JwtUtil();
+//        String userid = jwtUtil.getusid(token);
+//        LambdaQueryWrapper<PosOrder> orderQuery = new LambdaQueryWrapper<>();
+//        orderQuery.eq(PosOrder::getDdId, ddId);
+//        PosOrder order = posOrderService.getOne(orderQuery);
+//        if (!order.getUserId().equals(Long.valueOf(userid))) {
+//            throw new ServiceException("不是订单用户,无权限操作");
 //        }
-
-        return success();
-    }
+//        if (order.getQsId() != null) {
+//            throw new ServiceException("骑手已经接单,无法取消");
+//        }
+//        PosOrder update = new PosOrder();
+//        update.setId(order.getId());
+//        update.setState(4L);
+//        if ("1".equals(order.getCollectPayment())) {
+//            UserBilling bill = billingService.getOne(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getDdId, String.valueOf(order.getDdId())).eq(UserBilling::getUserId, Long.valueOf(userid)));
+//            if (bill != null) {
+//                UserBilling user = new UserBilling();
+//                user.setId(bill.getId());
+//                user.setState("2");
+//                billingService.saveOrUpdate(user);
+//            }
+//        }
+//        posOrderService.saveOrUpdate(update);
+//        return success();
+//    }
 
 //    /**
 //     * 商家取消订单

+ 65 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java

@@ -47,6 +47,9 @@ import com.ruoyi.system.service.*;
 import com.ruoyi.system.utils.Auth;
 import com.ruoyi.system.utils.GetArea;
 import com.ruoyi.system.utils.JwtUtil;
+import com.ruoyi.system.utils.OrderLogHelper;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.core.domain.entity.SysUser;
 import lombok.SneakyThrows;
 import org.jetbrains.annotations.NotNull;
 import org.redisson.api.RLock;
@@ -138,6 +141,8 @@ public class PosOrderController extends BaseController {
     private UserService userService;
     @Autowired
     private PosOrderShOprateController shOprateController;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
 
 
     //查询用户足迹
@@ -238,6 +243,14 @@ public class PosOrderController extends BaseController {
                     returnPoints(order.getUserId(), Long.valueOf(order.getDdId()), Long.valueOf(order.getPoints()));
                 }
                 if (org) {
+                    // 平台管理员修改订单状态,添加操作日志
+                    SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+                    String adminName = adminUser != null ? adminUser.getNickName() : "";
+                    PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+                    String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+                    orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                        "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
                     return success(MessageUtils.message("no.modify.success"), posOrder.getId());
                 } else {
                     return error();
@@ -283,6 +296,14 @@ public class PosOrderController extends BaseController {
 
                     boolean org = posOrderService.saveOrUpdate(posOrder);
                     if (org) {
+                        // 平台管理员修改订单状态(骑手操作),添加操作日志
+                        SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+                        String adminName = adminUser != null ? adminUser.getNickName() : "";
+                        PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+                        String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+                        orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                            "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
                         //到付订单,设置用户账单为完成
                         if (posOrder.getState() == 3 && "1".equals(orst.getCollectPayment())) {
                             updateUserBill(orst.getDdId(), orst.getUserId(), orst.getQsId());
@@ -390,6 +411,15 @@ public class PosOrderController extends BaseController {
             boolean org = posOrderService.saveOrUpdate(posOrder);
             if (org) {
                 PosOrder order = posOrderService.getById(posOrder.getId());
+
+                // 平台管理员修改订单状态(商家接单),添加操作日志
+                SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+                String adminName = adminUser != null ? adminUser.getNickName() : "";
+                PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+                String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+                orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                    "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
                 return success(MessageUtils.message("no.modify.success"), posOrder.getId());
             } else {
                 return error();
@@ -401,6 +431,15 @@ public class PosOrderController extends BaseController {
                 boolean org = posOrderService.saveOrUpdate(posOrder);
                 if (org) {
                     PosOrder order = posOrderService.getById(posOrder.getId());
+
+                    // 平台管理员修改订单状态(商家接单),添加操作日志
+                    SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+                    String adminName = adminUser != null ? adminUser.getNickName() : "";
+                    PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+                    String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+                    orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                        "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
                     return success(MessageUtils.message("no.modify.success"), posOrder.getId());
                 } else {
                     return error();
@@ -414,6 +453,15 @@ public class PosOrderController extends BaseController {
         boolean org = posOrderService.saveOrUpdate(posOrder);
         if (org) {
             PosOrder order = posOrderService.getById(posOrder.getId());
+
+            // 平台管理员修改订单状态(订单完成),添加操作日志
+            SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+            String adminName = adminUser != null ? adminUser.getNickName() : "";
+            PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+            String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+            orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
             QueryWrapper<UserBilling> wrapper = new QueryWrapper<>();
             wrapper.eq("dd_id", order.getDdId());
 
@@ -432,6 +480,15 @@ public class PosOrderController extends BaseController {
     private AjaxResult setOrderState7(PosOrder posOrder) {
         boolean org = posOrderService.saveOrUpdate(posOrder);
         PosOrder order = posOrderService.getById(posOrder.getId());
+
+        // 平台管理员修改订单状态(同意退款),添加操作日志
+        SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+        String adminName = adminUser != null ? adminUser.getNickName() : "";
+        PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+        String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+        orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+            "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
         if (order.getPoints() != null && order.getPoints() > 0) {
             returnPoints(order.getUserId(), Long.valueOf(order.getDdId()), Long.valueOf(order.getPoints()));
         }
@@ -477,6 +534,14 @@ public class PosOrderController extends BaseController {
             shOprateController.chuCan(ordera, posOrder);
             PosOrder order = posOrderService.getById(posOrder.getId());
 
+            // 平台管理员修改订单状态(无状态变更),添加操作日志
+            SysUser adminUser = SecurityUtils.getLoginUser().getUser();
+            String adminName = adminUser != null ? adminUser.getNickName() : "";
+            PosOrder beforeOrder = posOrderService.getById(posOrder.getId());
+            String logDdId = beforeOrder != null ? String.valueOf(beforeOrder.getDdId()) : "";
+            orderLogHelper.log(logDdId, 1, adminUser != null ? adminUser.getUserId() : null, adminName,
+                "平台管理员" + adminName + "修改订单状态: " + (posOrder.getState() != null ? posOrder.getState() : ""));
+
             return success(MessageUtils.message("no.modify.success"), posOrder.getId());
         } else {
             return error();

+ 18 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java

@@ -26,6 +26,7 @@ import com.ruoyi.system.service.IPosStoreService;
 import com.ruoyi.system.service.IUserBillingService;
 import com.ruoyi.system.utils.Auth;
 import com.ruoyi.system.utils.JwtUtil;
+import com.ruoyi.system.utils.OrderLogHelper;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -57,6 +58,8 @@ public class PosOrderQsOprateController extends BaseController {
     private IUserBillingService userBillingService;
     @Autowired
     private IPosStoreService posStoreService;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
 
     /**
      * 骑手接单:校验 type=0 且 afterSaleStatus=0,设置 deliveryStatus=1
@@ -83,6 +86,11 @@ public class PosOrderQsOprateController extends BaseController {
         posOrder.setId(order.getId());
         posOrder.setDeliveryStatus(1L);
         posOrder.setQsId(Long.valueOf(qsId));
+
+        InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+        String qsName = qsUser != null ? qsUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "接单成功");
+
         return setOrderQsState(posOrder, qsId, push);
     }
 
@@ -108,6 +116,11 @@ public class PosOrderQsOprateController extends BaseController {
         posOrder.setId(order.getId());
         posOrder.setDeliveryStatus(2L);
         posOrder.setQsImg(input.getQsImg());
+
+        InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+        String qsName = qsUser != null ? qsUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已取餐");
+
         return setOrderQsState(posOrder, qsId, push);
     }
 
@@ -132,6 +145,11 @@ public class PosOrderQsOprateController extends BaseController {
         posOrder.setDeliveryStatus(3L);
         posOrder.setState(3L);
         posOrder.setSdTime(new Date());
+
+        InfoUser qsUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(qsId)));
+        String qsName = qsUser != null ? qsUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 3, Long.valueOf(qsId), qsName, "骑手" + qsName + "已送达");
+
         return setOrderQsState(posOrder, qsId, push);
     }
 

+ 176 - 8
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java

@@ -4,7 +4,10 @@ package com.ruoyi.app.order;
  * 订单商家操作
  */
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.app.order.dto.OrderCreatItem;
+import com.ruoyi.app.order.dto.OrderCreateInput;
 import com.ruoyi.app.order.dto.OrderPushBodyDto;
 import com.ruoyi.app.utils.PayPush;
 import com.ruoyi.app.utils.event.PushEventService;
@@ -12,21 +15,21 @@ import com.ruoyi.common.annotation.Anonymous;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.MessageUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.framework.manager.AsyncManager;
-import com.ruoyi.system.domain.InfoUser;
-import com.ruoyi.system.domain.PosOrder;
-import com.ruoyi.system.service.IInfoUserService;
-import com.ruoyi.system.service.IPosOrderService;
+import com.ruoyi.system.domain.*;
+import com.ruoyi.system.service.*;
 import com.ruoyi.system.utils.Auth;
 import com.ruoyi.system.utils.JwtUtil;
+import com.ruoyi.system.utils.OrderLogHelper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.TimerTask;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * posorderController
@@ -43,6 +46,159 @@ public class PosOrderShOprateController extends BaseController {
     private IInfoUserService infoUserService;
     @Autowired
     private PushEventService pushEventService;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
+    @Autowired
+    private IPosStoreService posStoreService;
+    @Autowired  //经营时间
+    private IOperatingHoursService operatingHoursService;
+    @Autowired
+    private IOrderParentService orderParentService;
+
+    /**
+     * 商家端下单
+     */
+    @PostMapping("/createOrder")
+    @Auth
+    @Anonymous
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult createOrder(@RequestHeader String token, @RequestBody OrderCreateInput input)
+    {
+        JwtUtil jwtUtil = new JwtUtil();
+        String id = jwtUtil.getusid(token);
+        String shdz=null;
+        createOrderParent(input,Long.valueOf(id),shdz);
+        createOrderChild(input,Long.valueOf(id),Long.valueOf(id),shdz);
+
+        OrderParent result = orderParentService.getOne(new LambdaQueryWrapper<OrderParent>().eq(OrderParent::getDdId, input.getDdId().toString()));
+        System.out.println("创建返回信息:"+result);
+        return super.success(result);
+    }
+
+    //创建父订单
+    private void createOrderParent(OrderCreateInput input,Long UserId,String shdz){
+        OrderParent orderParent = new OrderParent();
+        orderParent.setUserId(UserId);
+        orderParent.setOrderStatus(0L);
+        orderParent.setTotalAmount(input.getTotalAmount());
+        orderParent.setActualPayAmount(input.getActualPayAmount());
+        orderParent.setPaymentMethod(input.getPaymentMethod());
+//        orderParent.setPaymentTime(new Date());
+        orderParent.setCreateTime(new Date());
+//        orderParent.setUpdateTime(new Date());
+        orderParent.setDdId(input.getDdId().toString());
+        orderParent.setTableId(input.getTableId());
+        orderParent.setShdzId(input.getShdzId());
+        orderParent.setShAddress(shdz);
+        orderParent.setType(input.getType());
+        orderParent.setRemarks(input.getRemarks());
+        orderParentService.save(orderParent);
+    }
+    //创建子订单
+    private void createOrderChild(OrderCreateInput input,Long orderParentId,Long userId,String shdz){
+        // 检查items是否为空
+        if (input.getItems() == null || input.getItems().isEmpty()) {
+            throw new ServiceException(MessageUtils.message("订单商品不能为空"));
+        }
+        String ddId = input.getDdId().toString();
+        List<Long> mdIdList = input.getItems().stream().map(OrderCreatItem::getMdId).collect(Collectors.toList());
+        // 将Long类型的门店ID转换为Integer类型用于查询(因为PosStore.getId()返回Integer)
+        List<Integer> mdIdIntegerList = mdIdList.stream().map(Long::intValue).collect(Collectors.toList());
+        List<PosStore> storeList = posStoreService.list(new LambdaQueryWrapper<PosStore>().in(PosStore::getId, mdIdIntegerList));
+        LambdaQueryWrapper<OperatingHours> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(OperatingHours::getMdId, mdIdList);
+        List<OperatingHours> hourslist = operatingHoursService.list(wrapper);
+        boolean isMultiStore = input.getItems().size() > 1;
+        // 循环items,为每个item创建一条PosOrder
+        int index = 0;
+        for (OrderCreatItem item : input.getItems()) {
+            index++;
+            // 单店铺:子订单号与父订单一致;多店铺:子订单号追加序号
+            String subddId = isMultiStore ? ddId + String.format("%03d", index) : ddId;
+            PosOrder posOrder = new PosOrder();
+            posOrder.setIsDisplay(true);
+            posOrder.setDisplayTime(new Date());
+            posOrder.setShdzId(input.getShdzId());
+            // 设置子订单的基本信息
+            posOrder.setDdId(subddId);
+            posOrder.setParentDdId(input.getDdId().toString()); // 设置父订单ID
+            posOrder.setCretim(new Date());
+            posOrder.setState(1L); // 初始状态
+            // 根据item设置订单信息
+            posOrder.setShId(item.getShId());
+            posOrder.setMdId(item.getMdId());
+            posOrder.setShdzId(input.getShdzId());
+            posOrder.setUserId(userId);
+            posOrder.setAmount(item.getAmount());
+            posOrder.setRemarks(item.getRemarks());
+            posOrder.setType(item.getType());
+            posOrder.setDelryTime(item.getDelryTime());
+            posOrder.setFood(JSON.toJSONString(item.getFood()));
+            // 设置支付相关信息(从父订单继承)
+            posOrder.setPayType(input.getPaymentMethod());
+            //设置桌号(从父订单继承)
+            posOrder.setTableId(input.getTableId());
+            posOrder.setTableNo(input.getTableNo());
+            posOrder.setLogo(item.getLogo());
+            posOrder.setPosName(item.getPosName());
+            posOrder.setJvli(item.getJvli());
+            posOrder.setFreight(item.getFreight());
+            posOrder.setShdzId(input.getShdzId());
+            posOrder.setShAddress(shdz);
+            posOrder.setFoodAmount(item.getFoodAmount());
+            Optional<PosStore> storeOptional = storeList.stream()
+                    .filter(x -> item.getMdId().equals(Long.valueOf(x.getId())))
+                    .findFirst();
+            if (storeOptional.isEmpty()) {
+                throw new ServiceException(MessageUtils.message("no.mendian.not.exist"));
+            }
+            PosStore store = storeOptional.get();
+            posOrder.setLongitude(store.getLongitude());
+            posOrder.setLatitude(store.getLatitude());
+            //设置订单分类
+            storeList.stream().filter(s -> Long.valueOf(s.getId()).equals(item.getMdId())).findFirst().ifPresent(st -> {
+                posOrder.setOrderCategory(String.valueOf(st.getType()));
+            });
+            setPickUpNum(posOrder);
+            posOrderService.save(posOrder);
+        }
+    }
+
+    //生成取餐码,根据门店id,当天取餐码从1开始累加
+    private void setPickUpNum(PosOrder order) {
+        if (order == null || order.getMdId() == null) {
+            return;
+        }
+        try {
+            // 获取当前日期,格式为 yyyy-MM-dd
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+            String today = dateFormat.format(new Date());
+
+            // 查询当天该门店的最大取餐码
+            LambdaQueryWrapper<PosOrder> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(PosOrder::getMdId, order.getMdId());
+            queryWrapper.like(PosOrder::getCretim, today);
+            queryWrapper.orderByDesc(PosOrder::getPickUpNum);
+            queryWrapper.last("LIMIT 1");
+
+            PosOrder lastOrder = posOrderService.getOne(queryWrapper);
+            long nextNum = 1L;
+            if (lastOrder != null && lastOrder.getPickUpNum() != null) {
+                // 直接使用Long类型的取餐码
+                nextNum = lastOrder.getPickUpNum() + 1;
+            }
+            // 生成新的取餐码:直接使用Long数字
+            order.setPickUpNum(nextNum);
+            logger.info("生成取餐码成功:门店ID={}, 取餐码={}", order.getMdId(), nextNum);
+
+        } catch (Exception e) {
+            logger.error("生成取餐码失败:门店ID={}, 错误信息={}", order.getMdId(), e.getMessage(), e);
+            // 生成失败时使用时间戳作为备用方案
+            Long fallbackPickUpNum = System.currentTimeMillis() % 10000;
+            order.setPickUpNum(fallbackPickUpNum);
+        }
+    }
+
 
     /**
      * 商家接单:state 从 0 改为 1
@@ -59,6 +215,9 @@ public class PosOrderShOprateController extends BaseController {
         update.setId(order.getId());
         update.setState(1L);
         posOrderService.saveOrUpdate(update);
+        InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+        String shName = shUser != null ? shUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已接单");
         return AjaxResult.success();
     }
 
@@ -82,6 +241,9 @@ public class PosOrderShOprateController extends BaseController {
             update.setDeliveryStatus(0L);
         }
         posOrderService.saveOrUpdate(update);
+        InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+        String shName = shUser != null ? shUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已出餐");
         // 出餐推送
         chuCan(order, update);
         return AjaxResult.success();
@@ -107,6 +269,9 @@ public class PosOrderShOprateController extends BaseController {
         update.setState(3L);
         update.setPayStatus(1L);
         posOrderService.saveOrUpdate(update);
+        InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+        String shName = shUser != null ? shUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "已完成订单");
         return AjaxResult.success();
     }
 
@@ -128,6 +293,9 @@ public class PosOrderShOprateController extends BaseController {
         update.setId(order.getId());
         update.setState(4L);
         posOrderService.saveOrUpdate(update);
+        InfoUser shUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(new JwtUtil().getusid(token))));
+        String shName = shUser != null ? shUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 2, Long.valueOf(new JwtUtil().getusid(token)), shName, "商家" + shName + "取消订单");
         return AjaxResult.success();
     }
 

+ 6 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/TestTask.java

@@ -16,6 +16,7 @@ import com.ruoyi.system.mapper.UserBillingMapper;
 import com.ruoyi.system.service.IPosOrderService;
 import com.ruoyi.system.service.IPoseOrderZalopayService;
 import com.ruoyi.system.service.IUserBillingService;
+import com.ruoyi.system.utils.OrderLogHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -47,6 +48,8 @@ public class TestTask {
     private ZaloPayConfig zaloPayConfig;
     @Autowired
     private ZaloPay zaloPay;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
 
 
     private static Logger logger = LoggerFactory.getLogger(TestTask.class);
@@ -64,6 +67,7 @@ public class TestTask {
             posOrder.setId(ordlist.get(i).getId());
             posOrder.setState(4L); // state=4 已取消(替换旧 state=10)
             posOrderService.saveOrUpdate(posOrder);
+            orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动取消超时未接单订单");
             try {
                 if(ordlist.get(i).getPoints()!=null && ordlist.get(i).getPoints()>0){
                     posorder.returnPoints(ordlist.get(i).getUserId(), Long.valueOf(ordlist.get(i).getDdId()),Long.valueOf(ordlist.get(i).getPoints()));
@@ -109,6 +113,7 @@ public class TestTask {
                 posOrder.setId(list.get(i).getId());
                 posOrder.setAfterSaleStatus(6L); // afterSaleStatus=6 售后完成(替换旧 state=11)
                 posOrderService.saveOrUpdate(posOrder);
+                orderLogHelper.logSync(String.valueOf(list.get(i).getDdId()), 0, null, "系统", "系统处理退款完成");
             }
         }
 
@@ -138,6 +143,7 @@ public class TestTask {
                 pos.setPayStatus(1L);
             }
             posOrderService.saveOrUpdate(pos);
+            orderLogHelper.logSync(String.valueOf(ordlist.get(i).getDdId()), 0, null, "系统", "系统自动完成超时订单");
             PosOrder order = posOrderService.getById(ordlist.get(i).getId());
             posorder.setSanghuBilling(order);
             posorder.setQishouBilling(order);

+ 20 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java

@@ -23,6 +23,7 @@ import com.ruoyi.system.service.*;
 import com.ruoyi.system.utils.Auth;
 import com.ruoyi.system.utils.GetArea;
 import com.ruoyi.system.utils.JwtUtil;
+import com.ruoyi.system.utils.OrderLogHelper;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -59,6 +60,8 @@ public class UserOrderController extends BaseController {
     private IInfoUserService infoUserService;
     @Autowired
     private PushEventService pushEventService;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
 
     /**
      * 创建订单
@@ -197,6 +200,11 @@ public class UserOrderController extends BaseController {
             setPickUpNum(posOrder);
             posOrderService.save(posOrder);
 
+            // 创建订单成功,添加操作日志
+            InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, userId));
+            String uName = uUser != null ? uUser.getNickName() : "";
+            orderLogHelper.log(subddId, 4, userId, uName, "用户" + uName + "创建订单");
+
             // 创建订单成功,给商家推送"您有新订单了"
             InfoUser shUser = infoUserService.getById(item.getShId());
             if (shUser != null && !StringUtils.isEmpty(shUser.getCid())) {
@@ -436,6 +444,12 @@ public class UserOrderController extends BaseController {
         }
         order.setState(3L);
         posOrderService.updateById(order);
+
+        // 确认取餐成功,添加操作日志
+        InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
+        String uName = uUser != null ? uUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "确认取餐");
+
         return success("确认取餐成功");
     }
 
@@ -462,6 +476,12 @@ public class UserOrderController extends BaseController {
         }
         order.setState(4L);
         posOrderService.updateById(order);
+
+        // 取消订单成功,添加操作日志
+        InfoUser uUser = infoUserService.getOne(new LambdaQueryWrapper<InfoUser>().eq(InfoUser::getUserId, Long.valueOf(userId)));
+        String uName = uUser != null ? uUser.getNickName() : "";
+        orderLogHelper.log(String.valueOf(order.getDdId()), 4, Long.valueOf(userId), uName, "用户" + uName + "取消订单");
+
         return success("取消订单成功");
     }
 

+ 4 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/pay/PayController.java

@@ -21,6 +21,7 @@ import com.ruoyi.system.mapper.RiderPositionMapper;
 import com.ruoyi.system.service.*;
 import com.ruoyi.system.utils.Auth;
 import com.ruoyi.system.utils.JwtUtil;
+import com.ruoyi.system.utils.OrderLogHelper;
 import lombok.SneakyThrows;
 import org.apache.poi.util.StringUtil;
 import org.slf4j.Logger;
@@ -62,6 +63,8 @@ public class PayController extends BaseController {
     private RiderPositionMapper riderPositionMapper;
     @Autowired
     private PushEventService pushEventService;
+    @Autowired
+    private OrderLogHelper orderLogHelper;
 
 
     //VNPay
@@ -264,6 +267,7 @@ public class PayController extends BaseController {
                                     order.setState(0L); // 保持 state=0(待处理),支付成功不改订单状态
                                     order.setPayStatus(1L); // 支付状态设为已支付
                                     posOrderService.saveOrUpdate(order);
+                                    orderLogHelper.logSync(String.valueOf(order.getDdId()), 0, null, "系统", "系统收到支付成功回调");
                                     QueryWrapper<UserBilling> wrapper = new QueryWrapper<>();
                                     wrapper.eq("dd_id", posOrder.getDdId());
                                     wrapper.eq("type", "3");

+ 5 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/PosOrder.java

@@ -86,6 +86,11 @@ public class PosOrder {
 
     /**
      * 订单状态
+     * | 0 | 待处理 |
+     * | 1 | 已接单 |
+     * | 2 | 已出餐 |
+     * | 3 | 已完成 |
+     * | 4 | 已取消 |
      */
     @Excel(name = "订单状态")
     private Long state;

+ 163 - 0
specs/007-orderlog/spec.md

@@ -0,0 +1,163 @@
+# 007 - 订单操作日志
+
+**Feature Branch**: `007-order-log`
+**Created**: 2026-05-19
+**Status**: Done
+**Input**: 平台管理端新增订单操作日志页面,记录所有订单状态变更的完整审计轨迹。
+
+## 背景
+
+订单状态已拆分为四字段(state/deliveryStatus/payStatus/afterSaleStatus),状态变更涉及多方角色(平台管理员、商家、骑手、用户、系统)。需要独立的日志表记录每次变更,用于问题排查和运营审计。
+
+## 需求概览
+
+新增 `pos_order_log` 表,在每次订单状态变更时自动写入一条日志。平台管理端提供日志查询页面,支持按订单号、操作人筛选和导出。
+
+## 数据库设计
+
+### 新表:pos_order_log
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT (AUTO_INCREMENT) | 主键 |
+| dd_id | VARCHAR(64) | 订单号(关联 pos_order.dd_id) |
+| operator_type | TINYINT | 操作人类型:0 系统, 1 平台管理员, 2 商家, 3 骑手, 4 用户 |
+| operator_id | BIGINT | 操作人ID(对应各角色表的主键) |
+| operator_name | VARCHAR(128) | 操作人名称(冗余存储,避免查询时 JOIN) |
+| content | VARCHAR(512) | 操作内容描述(如"平台管理员tongdai修改订单状态: 取餐中 → 配送中") |
+| create_time | DATETIME | 操作时间 |
+
+```sql
+-- 2026-05-19 新增订单操作日志表
+CREATE TABLE pos_order_log (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  dd_id VARCHAR(64) NOT NULL COMMENT '订单号',
+  operator_type TINYINT NOT NULL COMMENT '操作人类型:0系统,1平台管理员,2商家,3骑手,4用户',
+  operator_id BIGINT COMMENT '操作人ID',
+  operator_name VARCHAR(128) COMMENT '操作人名称',
+  content VARCHAR(512) NOT NULL COMMENT '操作内容',
+  create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+  INDEX idx_dd_id (dd_id),
+  INDEX idx_operator_id (operator_id),
+  INDEX idx_create_time (create_time)
+) COMMENT='订单操作日志';
+```
+
+### operator_type 枚举
+
+| 值 | 含义 | 说明 |
+|----|------|------|
+| 0 | 系统 | 定时任务、自动取消等系统触发 |
+| 1 | 平台管理员 | 后台管理端操作 |
+| 2 | 商家 | 商家端操作(接单、出餐、完成等) |
+| 3 | 骑手 | 骑手端操作(接单、取餐、送达等) |
+| 4 | 用户 | 用户端操作(下单、取消、确认取餐等) |
+
+## 日志写入时机
+
+在以下操作执行成功后写入日志(由 Service 层统一处理):
+
+### 商家操作(operator_type=2)
+| 操作 | content 模板 |
+|------|-------------|
+| 接单 | `商家{名称}已接单` |
+| 出餐 | `商家{名称}已出餐` |
+| 完成(自取/堂食) | `商家{名称}已完成订单` |
+| 取消订单 | `商家{名称}取消订单` |
+
+### 骑手操作(operator_type=3)
+| 操作 | content 模板 |
+|------|-------------|
+| 接单 | `骑手{名称}接单成功` |
+| 取餐 | `骑手{名称}已取餐` |
+| 送达 | `骑手{名称}已送达` |
+
+### 用户操作(operator_type=4)
+| 操作 | content 模板 |
+|------|-------------|
+| 下单 | `用户{名称}创建订单` |
+| 取消订单 | `用户{名称}取消订单` |
+| 确认取餐 | `用户{名称}确认取餐` |
+| 申请退款 | `用户{名称}申请退款` |
+
+### 平台管理员操作(operator_type=1)
+| 操作 | content 模板 |
+|------|-------------|
+| 修改订单状态 | `平台管理员{名称}修改订单状态: {旧状态} → {新状态}` |
+| 修改配送状态 | `平台管理员{名称}修改配送状态: {旧状态} → {新状态}` |
+
+### 系统操作(operator_type=0)
+| 操作 | content 模板 |
+|------|-------------|
+| 超时自动取消 | `系统自动取消超时未接单订单` |
+| 超时自动完成 | `系统自动完成超时订单` |
+| 支付回调 | `系统收到支付成功回调` |
+
+## 平台管理端页面
+
+### 页面位置
+`E:\QtwCode\foodie\foodie-admin-vue\src\views\system\order\log.vue`(新增页面)
+
+### 搜索条件
+
+| 搜索字段 | 类型 | 说明 |
+|---------|------|------|
+| 订单号 | 输入框 | 模糊搜索 dd_id |
+| 操作人ID | 输入框 | 精确匹配 operator_id |
+| 操作人名称 | 输入框 | 模糊搜索 operator_name |
+
+### 表格列
+
+| 列名 | 字段 | 说明 |
+|------|------|------|
+| 主键ID | id | |
+| 订单号 | ddId | |
+| 操作人类型 | operatorType | 显示中文名称(系统/平台/商家/骑手/用户) |
+| 操作人ID | operatorId | |
+| 操作人名称 | operatorName | |
+| 操作内容 | content | |
+| 时间 | createTime | |
+| 操作 | - | 查看按钮(跳转订单详情或弹窗显示详情) |
+
+### 操作按钮
+- **搜索**:按筛选条件查询
+- **重置**:清空筛选条件
+- **导出**:导出当前筛选结果为 Excel
+
+### 菜单配置
+在平台管理端的"订单管理"菜单下新增"订单日志"子菜单。
+
+## 后端接口
+
+### 日志列表接口
+**接口**:`GET /system/orderLog/list`
+**权限**:`system:orderLog:list`
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| ddId | String | 否 | 订单号(模糊搜索) |
+| operatorId | Long | 否 | 操作人ID |
+| operatorName | String | 否 | 操作人名称(模糊搜索) |
+| pageNum | int | 是 | 页码,默认1 |
+| pageSize | int | 是 | 每页条数,默认10 |
+
+### 日志详情接口
+**接口**:`GET /system/orderLog/{id}`
+**权限**:`system:orderLog:query`
+
+### 导出接口
+**接口**:`POST /system/orderLog/export`
+**权限**:`system:orderLog:export`
+
+## 实现范围
+
+### 本次实现(P1)
+1. 新建 `pos_order_log` 表
+2. 新建 PosOrderLog 实体类、Mapper、Service、Controller
+3. 在所有订单状态变更处插入日志记录
+4. 平台管理端新增日志查询页面
+
+### 后续扩展(P2)
+- 按时间范围筛选
+- 按操作人类型筛选
+- 订单详情弹窗中展示该订单的操作日志时间线

+ 33 - 0
updatesql/sql.md

@@ -15,3 +15,36 @@ ALTER TABLE pos_order ADD COLUMN after_sale_status BIGINT DEFAULT 0 COMMENT '售
 -- 废弃 dining_status 字段(不删除,新逻辑不再使用)
 -- ALTER TABLE pos_order DROP COLUMN dining_status;
 ```
+
+## 2026-05-19 新增订单操作日志表(007-orderlog)
+
+```sql
+CREATE TABLE pos_order_log (
+  id BIGINT AUTO_INCREMENT PRIMARY KEY,
+  dd_id VARCHAR(64) NOT NULL COMMENT '订单号',
+  operator_type TINYINT NOT NULL COMMENT '操作人类型:0系统,1平台管理员,2商家,3骑手,4用户',
+  operator_id BIGINT COMMENT '操作人ID',
+  operator_name VARCHAR(128) COMMENT '操作人名称',
+  content VARCHAR(512) NOT NULL COMMENT '操作内容',
+  create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
+  INDEX idx_dd_id (dd_id),
+  INDEX idx_operator_id (operator_id),
+  INDEX idx_create_time (create_time)
+) COMMENT='订单操作日志';
+```
+
+## 2026-05-19 新增订单操作日志菜单
+
+```sql
+-- 新增订单操作日志菜单
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+SELECT '订单日志', menu_id, 6, 'orderLog', 'system/order/log', 'C', '0', '0', 'system:orderLog:list', 'log', 'admin', NOW(), '订单操作日志菜单'
+FROM sys_menu WHERE menu_name = '订单管理' AND parent_id = 0 LIMIT 1;
+
+-- 日志查询按钮权限
+SET @logMenuId = (SELECT menu_id FROM sys_menu WHERE perms = 'system:orderLog:list' LIMIT 1);
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志查询', @logMenuId, 1, '#', '', 'F', '0', '0', 'system:orderLog:query', '#', 'admin', NOW());
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, menu_type, visible, status, perms, icon, create_by, create_time)
+VALUES ('订单日志导出', @logMenuId, 2, '#', '', 'F', '0', '0', 'system:orderLog:export', '#', 'admin', NOW());
+```