Răsfoiți Sursa

feat(order): 添加订单确认流程和客户交易数据分析功能

- 在CustomerInfoServiceImpl中添加订单审核方式验证和isNeedAffirmOrder方法
- 在OrderMainServiceImpl中实现基于客户设置的订单确认逻辑和交易数据统计
- 新增CustomerOrderTradeDataVo用于客户订单交易分析数据传输
- 添加customerOrderTradeData接口提供客户交易数据分析功能
- 实现远程调用isNeedAffirmOrder方法支持跨服务查询
- 重构RemoteCustomerServiceImpl移除冗余导入并实现新接口方法
hurx 19 ore în urmă
părinte
comite
472269cec9

+ 5 - 0
ruoyi-api/ruoyi-api-customer/src/main/java/org/dromara/customer/api/RemoteCustomerService.java

@@ -43,4 +43,9 @@ public interface RemoteCustomerService {
 
 
     CustomerAddressDTO addReceiverAddress(String provinceId, String cityId, String countyId, String address, String mobile);
     CustomerAddressDTO addReceiverAddress(String provinceId, String cityId, String countyId, String address, String mobile);
 
 
+    /**
+     * 是否需要确认订单
+     */
+    Boolean isNeedAffirmOrder(Long customerId);
+
 }
 }

+ 4 - 2
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/dubbo/RemoteCustomerServiceImpl.java

@@ -2,7 +2,6 @@ package org.dromara.customer.dubbo;
 
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollUtil;
-import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -146,5 +145,8 @@ public class RemoteCustomerServiceImpl implements RemoteCustomerService {
         return customerAddressDTO;
         return customerAddressDTO;
     }
     }
 
 
-
+    @Override
+    public Boolean isNeedAffirmOrder(Long customerId) {
+        return customerInfoService.isNeedAffirmOrder(customerId);
+    }
 }
 }

+ 1 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java

@@ -195,4 +195,5 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
 
 
     R<CustomerIndexDataVo> customerIndexData();
     R<CustomerIndexDataVo> customerIndexData();
 
 
+    Boolean isNeedAffirmOrder(Long customerId);
 }
 }

+ 19 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -1203,6 +1203,9 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
                 if (null == salesInfoVo.getCreditManagementId()) {
                 if (null == salesInfoVo.getCreditManagementId()) {
                     throw new ServiceException("ERP销售人员信用等级不能为空");
                     throw new ServiceException("ERP销售人员信用等级不能为空");
                 }
                 }
+                if (null == salesInfoVo.getOrderAudit()) {
+                    throw new ServiceException("ERP销售人员订单审核方式不能为空");
+                }
             }
             }
         }
         }
         return baseMapper.update(null,
         return baseMapper.update(null,
@@ -2013,4 +2016,20 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
 
 
         return R.ok(vo);
         return R.ok(vo);
     }
     }
+
+    //是否需要确认订单
+    @Override
+    public Boolean isNeedAffirmOrder(Long customerId) {
+        boolean affirmFlag = false;
+        CustomerInfoVo customerInfoVo = baseMapper.selectVoById(customerId);
+        if (ObjectUtils.isNotEmpty(customerInfoVo)) {
+            CustomerSalesInfoVo salesInfoVo = customerSalesInfoMapper.selectVoOne(new LambdaQueryWrapper<CustomerSalesInfo>().eq(CustomerSalesInfo::getCustomerId, customerInfoVo.getId()).last("LIMIT 1"));
+            if (ObjectUtils.isNotEmpty(salesInfoVo) && ObjectUtils.isNotEmpty(salesInfoVo.getOrderAudit())) {
+                if ("1".equals(salesInfoVo.getOrderAudit())) {//下单后需要客服确认
+                    affirmFlag = true;
+                }
+            }
+        }
+        return affirmFlag;
+    }
 }
 }

+ 13 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/controller/pc/PcOrderController.java

@@ -26,6 +26,7 @@ import org.dromara.order.domain.OrderCustomerFlowNodeLink;
 import org.dromara.order.domain.bo.*;
 import org.dromara.order.domain.bo.*;
 import org.dromara.order.domain.dto.OrderPayDto;
 import org.dromara.order.domain.dto.OrderPayDto;
 import org.dromara.order.domain.vo.OrderCountVo;
 import org.dromara.order.domain.vo.OrderCountVo;
+import org.dromara.order.domain.vo.CustomerOrderTradeDataVo;
 import org.dromara.order.domain.vo.OrderMainVo;
 import org.dromara.order.domain.vo.OrderMainVo;
 import org.dromara.order.domain.vo.OrderProductVo;
 import org.dromara.order.domain.vo.OrderProductVo;
 import org.dromara.order.domain.vo.OrderStatusStats;
 import org.dromara.order.domain.vo.OrderStatusStats;
@@ -87,6 +88,18 @@ public class PcOrderController extends BaseController {
 
 
     private final IOrderCustomerFlowNodeLinkService orderCustomerFlowNodeLinkService;
     private final IOrderCustomerFlowNodeLinkService orderCustomerFlowNodeLinkService;
 
 
+    /**
+     * 订单交易分析数据
+     */
+    @GetMapping("/customerOrderTradeData")
+    public R<CustomerOrderTradeDataVo> customerOrderTradeData() {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        if (ObjectUtil.isEmpty(customerId)) {
+            return R.fail("未获取到当前客户信息");
+        }
+        return orderMainService.customerOrderTradeData(customerId);
+    }
+
     /**
     /**
      * 查询当前企业的订单列表
      * 查询当前企业的订单列表
      * PC端用户只能查询自己企业的订单
      * PC端用户只能查询自己企业的订单

+ 81 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/domain/vo/CustomerOrderTradeDataVo.java

@@ -0,0 +1,81 @@
+package org.dromara.order.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 客户订单交易分析数据视图对象
+ *
+ * @author LionLi
+ * @date 2026-06-01
+ */
+@Data
+public class CustomerOrderTradeDataVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 购买金额(元) */
+    private BigDecimal totalAmount;
+
+    /** 购买数量(件) */
+    private Long totalQuantity;
+
+    /** 订单量(单) */
+    private Long orderCount;
+
+    /** 客单价(元/单) */
+    private BigDecimal avgOrderAmount;
+
+    /** 采购金额月度趋势 */
+    private List<MonthTrend> amountTrend;
+
+    /** 采购品类占比 */
+    private List<CategoryProportion> categoryProportion;
+
+    /** 采购商品日变化 */
+    private List<DailyChange> dailyChange;
+
+    @Data
+    public static class MonthTrend implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        /** 月份, 格式: "yyyy/MM" */
+        private String month;
+
+        /** 采购金额 */
+        private BigDecimal amount;
+    }
+
+    @Data
+    public static class CategoryProportion implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        /** 品类名称 */
+        private String name;
+
+        /** 采购数量 */
+        private Long value;
+    }
+
+    @Data
+    public static class DailyChange implements Serializable {
+        @Serial
+        private static final long serialVersionUID = 1L;
+
+        /** 日期序号, 如 "1", "2" */
+        private String day;
+
+        /** 购买金额 */
+        private BigDecimal amount;
+
+        /** 购买数量 */
+        private Long quantity;
+    }
+}

+ 2 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/IOrderMainService.java

@@ -177,4 +177,6 @@ public interface IOrderMainService extends IService<OrderMain> {
 
 
     R<OrderIndexDataVo> orderIndexData();
     R<OrderIndexDataVo> orderIndexData();
 
 
+    R<CustomerOrderTradeDataVo> customerOrderTradeData(Long customerId);
+
 }
 }

+ 130 - 1
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/impl/OrderMainServiceImpl.java

@@ -542,6 +542,8 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
 
 
             Long productQuantity = orderProductBos.stream().mapToLong(OrderProductBo::getOrderQuantity).sum();
             Long productQuantity = orderProductBos.stream().mapToLong(OrderProductBo::getOrderQuantity).sum();
 
 
+            Boolean needAffirmOrder = remoteCustomerService.isNeedAffirmOrder(bo.getCustomerId());
+
             // 应付总额 = 商品总价 + 运费
             // 应付总额 = 商品总价 + 运费
             BigDecimal payableAmount = totalAmount.add(bo.getShippingFee());
             BigDecimal payableAmount = totalAmount.add(bo.getShippingFee());
 
 
@@ -583,6 +585,16 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
                     bo.setOrderTime(orderTimeToUse);
                     bo.setOrderTime(orderTimeToUse);
                     bo.setOrderType("0");
                     bo.setOrderType("0");
                     bo.setAssigneeType(AssigneeTypeConstants.MKT.getCode());
                     bo.setAssigneeType(AssigneeTypeConstants.MKT.getCode());
+
+
+                    if ("0".equals(bo.getPayType())) {//信用支付
+                        String orderStatus = OrderStatus.PENDING_SHIPMENT.getCode();//待发货
+                        bo.setConfirmTime(new Date());
+                        if (needAffirmOrder) {
+                            orderStatus = OrderStatus.PENDING_CONFIRMATION.getCode();//待确认
+                        }
+                        bo.setOrderStatus(orderStatus);
+                    }
                     // --- 步骤 C: 转换对象 ---
                     // --- 步骤 C: 转换对象 ---
                     orderMain = MapstructUtils.convert(bo, OrderMain.class);
                     orderMain = MapstructUtils.convert(bo, OrderMain.class);
 
 
@@ -719,6 +731,8 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
     public Long insertOrder(PcSubmitOrderBo bo, OrderMainBo mainBo) {
     public Long insertOrder(PcSubmitOrderBo bo, OrderMainBo mainBo) {
         CustomerInfoDTO customerInfoDTO = remoteCustomerService.selectCustomerInfoById(mainBo.getCustomerId());
         CustomerInfoDTO customerInfoDTO = remoteCustomerService.selectCustomerInfoById(mainBo.getCustomerId());
 
 
+        Boolean affirmFlag = remoteCustomerService.isNeedAffirmOrder(mainBo.getCustomerId());
+
         RemoteCustomerContactVo remoteCustomerContactVo = remoteCustomerContactService.selectCustomerContactByCustomerIdAndUserId(mainBo.getCustomerId(), mainBo.getUserId());
         RemoteCustomerContactVo remoteCustomerContactVo = remoteCustomerContactService.selectCustomerContactByCustomerIdAndUserId(mainBo.getCustomerId(), mainBo.getUserId());
         if (ObjectUtils.isNotEmpty(remoteCustomerContactVo)) {
         if (ObjectUtils.isNotEmpty(remoteCustomerContactVo)) {
             mainBo.setContactId(remoteCustomerContactVo.getId());
             mainBo.setContactId(remoteCustomerContactVo.getId());
@@ -764,13 +778,17 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
         if (orderId != null && orderId > 0) {
         if (orderId != null && orderId > 0) {
             // 3. 初始化审批流程
             // 3. 初始化审批流程
             Boolean initOrderFlow = orderCustomerFlowService.initOrderFlow(orderId, mainBo.getContactId());
             Boolean initOrderFlow = orderCustomerFlowService.initOrderFlow(orderId, mainBo.getContactId());
+            String orderStatus = OrderStatus.PENDING_SHIPMENT.getCode();
+            if (affirmFlag) {
+                orderStatus = OrderStatus.PENDING_CONFIRMATION.getCode();
+            }
 
 
             // 4. 如果流程初始化成功,更新订单状态 (注意:这里再次触发了数据库更新)
             // 4. 如果流程初始化成功,更新订单状态 (注意:这里再次触发了数据库更新)
             if (initOrderFlow) {
             if (initOrderFlow) {
                 log.info("成功初始化审批流程,订单ID: {}", orderId);
                 log.info("成功初始化审批流程,订单ID: {}", orderId);
                 this.update(Wrappers.lambdaUpdate(OrderMain.class)
                 this.update(Wrappers.lambdaUpdate(OrderMain.class)
                     .eq(OrderMain::getId, orderId)
                     .eq(OrderMain::getId, orderId)
-                    .set(OrderMain::getOrderStatus, OrderStatus.PENDING_SHIPMENT.getCode())// 待发货
+                    .set(OrderMain::getOrderStatus, orderStatus)// 根据当前客户设置的是否需要确认来设置订单状态
                     .set(OrderMain::getIsNeedCheck, SysPlatformYesNo.YES.getCode())
                     .set(OrderMain::getIsNeedCheck, SysPlatformYesNo.YES.getCode())
                 );
                 );
 
 
@@ -2137,4 +2155,115 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
         proportion.setValue(value);
         proportion.setValue(value);
         return proportion;
         return proportion;
     }
     }
+
+    @Override
+    public R<CustomerOrderTradeDataVo> customerOrderTradeData(Long customerId) {
+        CustomerOrderTradeDataVo vo = new CustomerOrderTradeDataVo();
+        LocalDate today = LocalDate.now();
+
+        // ========== 1. 统计卡片(全部历史数据) ==========
+        List<OrderMain> allOrders = baseMapper.selectList(new LambdaQueryWrapper<OrderMain>()
+            .eq(OrderMain::getCustomerId, customerId)
+            .select(OrderMain::getTotalAmount, OrderMain::getProductQuantity));
+
+        BigDecimal totalAmount = BigDecimal.ZERO;
+        long totalQuantity = 0;
+        long orderCount = allOrders.size();
+        for (OrderMain order : allOrders) {
+            if (order.getTotalAmount() != null) {
+                totalAmount = totalAmount.add(order.getTotalAmount());
+            }
+            if (order.getProductQuantity() != null) {
+                totalQuantity += order.getProductQuantity();
+            }
+        }
+        vo.setTotalAmount(totalAmount);
+        vo.setTotalQuantity(totalQuantity);
+        vo.setOrderCount(orderCount);
+        vo.setAvgOrderAmount(orderCount > 0
+            ? totalAmount.divide(BigDecimal.valueOf(orderCount), 2, java.math.RoundingMode.HALF_UP)
+            : BigDecimal.ZERO);
+
+        // ========== 2. 采购金额月度趋势(今年12个月) ==========
+        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy/MM");
+        List<CustomerOrderTradeDataVo.MonthTrend> amountTrend = new ArrayList<>();
+        for (int i = 0; i < 12; i++) {
+            LocalDate monthStart = LocalDate.of(today.getYear(), i + 1, 1);
+            LocalDate monthEnd = monthStart.plusMonths(1);
+
+            List<OrderMain> monthOrders = baseMapper.selectList(new LambdaQueryWrapper<OrderMain>()
+                .eq(OrderMain::getCustomerId, customerId)
+                .ge(OrderMain::getCreateTime, java.sql.Date.valueOf(monthStart))
+                .lt(OrderMain::getCreateTime, java.sql.Date.valueOf(monthEnd))
+                .select(OrderMain::getTotalAmount));
+
+            BigDecimal monthAmount = monthOrders.stream()
+                .map(OrderMain::getTotalAmount).filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+            CustomerOrderTradeDataVo.MonthTrend mt = new CustomerOrderTradeDataVo.MonthTrend();
+            mt.setMonth(monthStart.format(monthFormatter));
+            mt.setAmount(monthAmount);
+            amountTrend.add(mt);
+        }
+        vo.setAmountTrend(amountTrend);
+
+        // ========== 3. 采购品类占比 ==========
+        List<OrderMain> customerOrders = baseMapper.selectList(new LambdaQueryWrapper<OrderMain>()
+            .eq(OrderMain::getCustomerId, customerId).eq(OrderMain::getCurrentLevel, "1")
+            .select(OrderMain::getId));
+        List<CustomerOrderTradeDataVo.CategoryProportion> categoryList = new ArrayList<>();
+        if (CollUtil.isNotEmpty(customerOrders)) {
+            Set<Long> orderIds = customerOrders.stream().map(OrderMain::getId).collect(Collectors.toSet());
+            List<OrderProduct> products = orderProductMapper.selectList(new LambdaQueryWrapper<OrderProduct>()
+                .in(OrderProduct::getOrderId, orderIds)
+                .select(OrderProduct::getCategoryName, OrderProduct::getOrderQuantity));
+
+            Map<String, Long> categoryMap = products.stream()
+                .filter(p -> p.getCategoryName() != null && p.getOrderQuantity() != null)
+                .collect(Collectors.groupingBy(OrderProduct::getCategoryName,
+                    Collectors.summingLong(OrderProduct::getOrderQuantity)));
+
+            categoryList = categoryMap.entrySet().stream()
+                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
+                .map(entry -> {
+                    CustomerOrderTradeDataVo.CategoryProportion cp = new CustomerOrderTradeDataVo.CategoryProportion();
+                    cp.setName(entry.getKey());
+                    cp.setValue(entry.getValue());
+                    return cp;
+                }).collect(Collectors.toList());
+        }
+        vo.setCategoryProportion(categoryList);
+
+        // ========== 4. 采购商品日变化(本月每天) ==========
+        LocalDate firstDayOfMonth = today.withDayOfMonth(1);
+        List<CustomerOrderTradeDataVo.DailyChange> dailyChange = new ArrayList<>();
+        for (int d = 1; d <= today.getDayOfMonth(); d++) {
+            LocalDate date = firstDayOfMonth.plusDays(d - 1);
+            Date dayStart = java.sql.Date.valueOf(date);
+            Date dayEnd = java.sql.Date.valueOf(date.plusDays(1));
+
+            List<OrderMain> dayOrders = baseMapper.selectList(new LambdaQueryWrapper<OrderMain>()
+                .eq(OrderMain::getCustomerId, customerId)
+                .ge(OrderMain::getCreateTime, dayStart)
+                .lt(OrderMain::getCreateTime, dayEnd)
+                .select(OrderMain::getTotalAmount, OrderMain::getProductQuantity));
+
+            BigDecimal dayAmount = dayOrders.stream()
+                .map(OrderMain::getTotalAmount).filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+            long dayQuantity = dayOrders.stream()
+                .map(OrderMain::getProductQuantity).filter(Objects::nonNull)
+                .mapToLong(Long::longValue).sum();
+
+            CustomerOrderTradeDataVo.DailyChange dc = new CustomerOrderTradeDataVo.DailyChange();
+            dc.setDay(String.valueOf(d));
+            dc.setAmount(dayAmount);
+            dc.setQuantity(dayQuantity);
+            dailyChange.add(dc);
+        }
+        vo.setDailyChange(dailyChange);
+
+        return R.ok(vo);
+    }
 }
 }