فهرست منبع

feat(order): 新增小程序订单控制器并优化联系人编号生成逻辑
- 重构客户联系人编号生成逻辑,使用独立计数方式并添加角色ID
- 优化供应商联系人编号生成机制,采用序列化工具统一管理
- 实现小程序下单流程,包括购物车处理和订单创建逻辑

hurx 18 ساعت پیش
والد
کامیت
49637286e9

+ 7 - 2
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -1124,10 +1124,15 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
         CustomerContact contact = new CustomerContact();
         contact.setCustomerId(customerId);
         //  生成联系人编号
-        String seqId = SequenceUtils.nextPaddedIdStr(CONTACT_NO_KEY, Duration.ofDays(3650), 3);
-        String contactNo = customerNo + seqId;
+        // 1. 构造带客户编号的 Key,实现“每个客户独立计数”
+        String seqKey = "customer_contact:contact_no:" + customerNo;
+        // 2. 调用工具类
+        String seqId = SequenceUtils.nextPaddedIdStr(seqKey, Duration.ofDays(3650), 4);
+        // 3. 拼接最终结果
+        String contactNo = "1" + customerNo + seqId;
         contact.setContactNo(contactNo);
         contact.setUserId(userId);
+        contact.setRoleId(1L);
         contact.setContactName(bo.getPurchaseName());
         contact.setPhone(bo.getPurchasePhone());
         contact.setCustomLoginName(bo.getPurchasePhone());

+ 8 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SupplierInfoServiceImpl.java

@@ -24,6 +24,7 @@ import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.redis.utils.SequenceUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.customer.domain.*;
 import org.dromara.customer.domain.bo.*;
@@ -46,6 +47,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import java.time.Duration;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -2228,7 +2230,12 @@ public class SupplierInfoServiceImpl extends ServiceImpl<SupplierInfoMapper, Sup
         contact.setSupplierNo(supplierNo);
         contact.setUserName(bo.getPurchaseName());
         contact.setPhone(bo.getPurchasePhone());
-        contact.setPhone(bo.getPurchasePhone());
+        String seqKey = "supplier_contact:supplier_contact_no:" + supplierNo;
+        // 2. 调用工具类
+        String seqId = SequenceUtils.nextPaddedIdStr(seqKey, Duration.ofDays(3650), 4);
+        // 3. 拼接最终结果
+        String contactNo = "1" + supplierNo + seqId;
+        contact.setUserNo(contactNo);
         contact.setUserId(remoteUserId);
         contact.setIsPrimaryContact("1"); // 设为主联系人
         contact.setIsRegister("1");

+ 370 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/controller/mini/MiniOrderController.java

@@ -0,0 +1,370 @@
+package org.dromara.order.controller.mini;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.enums.OrderSourceEnum;
+import org.dromara.common.core.enums.OrderStatus;
+import org.dromara.common.core.enums.SysPlatformYesNo;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.api.RemoteCustomerContactService;
+import org.dromara.customer.api.RemoteCustomerService;
+import org.dromara.customer.api.domain.CustomerApiVo;
+import org.dromara.order.domain.bo.OrderMainBo;
+import org.dromara.order.domain.bo.OrderProductBo;
+import org.dromara.order.domain.bo.PcSubmitOrderBo;
+import org.dromara.order.domain.dto.OrderPayDto;
+import org.dromara.order.domain.vo.OrderMainVo;
+import org.dromara.order.domain.vo.OrderProductVo;
+import org.dromara.order.service.IOrderCustomerFlowLinkService;
+import org.dromara.order.service.IOrderCustomerFlowNodeLinkService;
+import org.dromara.order.service.IOrderCustomerFlowService;
+import org.dromara.order.service.IOrderMainService;
+import org.dromara.product.api.RemoteProductService;
+import org.dromara.product.api.RemoteProductShoppingCartService;
+import org.dromara.product.api.domain.ProductVo;
+import org.dromara.product.api.domain.RemoteProductShoppingCartVo;
+import org.dromara.system.api.RemoteUserService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.dromara.common.mybatis.core.mapper.BaseMapperPlus.log;
+
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/miniOrder")
+public class MiniOrderController extends BaseController {
+
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @DubboReference
+    private RemoteCustomerService remoteCustomerService;
+
+    @DubboReference
+    private RemoteProductService remoteProductService;
+
+    @DubboReference
+    private RemoteCustomerContactService remoteCustomerContactService;
+
+    @DubboReference
+    private RemoteProductShoppingCartService remoteProductShoppingCartService;
+
+    //客户订单流程
+    private final IOrderCustomerFlowService orderCustomerFlowService;
+
+    private final IOrderCustomerFlowLinkService orderCustomerFlowLinkService;
+
+    private final IOrderMainService orderMainService;
+
+
+    private final IOrderCustomerFlowNodeLinkService orderCustomerFlowNodeLinkService;
+
+    /**
+     * 查询订单列表
+     * 小程序用户只能查询自己的订单
+     */
+    @GetMapping("/list")
+    public TableDataInfo<OrderMainVo> list(OrderMainBo bo, PageQuery pageQuery) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+
+        Long userId = LoginHelper.getLoginUser().getUserId();
+        // 强制设置企业ID,防止越权访问
+        bo.setCustomerId(customerId);
+        bo.setUserId(userId);
+        bo.setIsSplitChild(SysPlatformYesNo.NO.getCode());
+        return orderMainService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取订单详细信息
+     * 小程序用户只能查询自己的订单
+     *
+     * @param id 订单主键
+     */
+    @GetMapping("/{id}")
+    public R<OrderMainVo> getInfo(@NotNull(message = "主键不能为空")
+                                  @PathVariable("id") Long id) {
+        // 查询订单信息
+        OrderMainVo vo = orderMainService.queryById(id);
+
+        // 验证订单是否属于当前用户的小程序用户
+        if (vo != null) {
+            Long customerId = LoginHelper.getLoginUser().getCustomerId();
+            Long userId = LoginHelper.getLoginUser().getUserId();
+            if (!userId.equals(vo.getUserId())) {
+                return R.fail("无权访问该订单");
+            }
+        }
+
+        return R.ok(vo);
+    }
+
+    /**
+     * 根据订单ID查询订单商品明细
+     * MINI端用户只能查询自己的订单商品
+     *
+     * @param orderIds 订单ID列表
+     */
+    @GetMapping("/products")
+    public TableDataInfo<OrderProductVo> getOrderProducts(@RequestParam("orderIds") List<Long> orderIds) {
+        if (orderIds == null || orderIds.isEmpty()) {
+            throw new IllegalArgumentException("订单ID列表不能为空");
+        }
+        if (orderIds.size() > 1000) {
+            throw new IllegalArgumentException("订单ID数量不能超过1000个");
+        }
+
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+
+        // 强制设置企业ID,防止越权访问
+        Long userId = LoginHelper.getLoginUser().getUserId();
+
+        // 验证所有订单是否都属于当前用户的企业
+        for (Long orderId : orderIds) {
+            OrderMainVo order = orderMainService.queryById(orderId);
+            if (order == null) {
+                throw new IllegalArgumentException("订单ID " + orderId + " 不存在");
+            }
+            if (!userId.equals(order.getUserId())) {
+                throw new IllegalArgumentException("无权访问订单ID " + orderId);
+            }
+        }
+
+        Set<Long> uniqueOrderIds = new HashSet<>(orderIds);
+        return orderMainService.getCustomerOrderProductList(uniqueOrderIds);
+    }
+
+    /**
+     * 取消订单
+     * MINI端用户只能取消自己的订单
+     *
+     * @param bo 订单业务对象
+     */
+    @Log(title = "MINI端-订单管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/cancel")
+    public R<Void> cancelOrder(@RequestBody OrderMainBo bo) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        // 获取当前登录用户ID
+        Long userId = LoginHelper.getLoginUser().getUserId();
+
+        // 验证订单是否属于当前用户的企业
+        OrderMainVo existingOrder = orderMainService.queryById(bo.getId());
+        if (existingOrder == null) {
+            return R.fail("订单不存在");
+        }
+        if (!userId.equals(existingOrder.getUserId())) {
+            return R.fail("无权取消该订单");
+        }
+
+        // 强制设置企业ID
+        bo.setCustomerId(customerId);
+        bo.setOrderStatuses(OrderStatus.CANCEL.getCode());
+
+        return toAjax(orderMainService.updateStatus(bo));
+    }
+
+    /**
+     * 提交订单(MINI端下单)
+     * 调用服务层 insertByBo 方法实现
+     */
+    @Log(title = "MINI端-提交订单", businessType = BusinessType.INSERT)
+    @PostMapping("/submit")
+    public R<Long> submitOrder(@RequestBody @Validated PcSubmitOrderBo bo) {
+        try {
+            // 获取当前登录用户的企业ID
+            Long userId = LoginHelper.getLoginUser().getUserId();
+
+            Long customerId = LoginHelper.getLoginUser().getCustomerId();
+            Map<Long, CustomerApiVo> longCustomerApiVoMap = remoteCustomerService.selectCustomerByIds(Set.of(customerId));
+            CustomerApiVo customerApiVo = longCustomerApiVoMap.get(customerId);
+
+            // 构造 OrderMainBo 对象
+            OrderMainBo mainBo = new OrderMainBo();
+            mainBo.setCustomerId(customerId);
+            mainBo.setUserId(userId);
+            mainBo.setShippingAddressId(bo.getShippingAddressId());
+            mainBo.setPurchaseReason(bo.getPurchaseReason());
+            mainBo.setRemark(bo.getRemark());
+            mainBo.setExpenseType(bo.getExpenseType());
+            mainBo.setShippingFee(bo.getShippingFee());
+
+            if (null != customerApiVo) {
+                mainBo.setCompanyId(customerApiVo.getCompanyId());
+                mainBo.setCustomerCode(customerApiVo.getCustomerNo());
+            }
+
+            // 解析配送时间
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+            LocalDate deliveryLocalDate = LocalDate.parse(bo.getDeliveryDate(), formatter);
+            Date expectedDeliveryTime = Date.from(deliveryLocalDate.atStartOfDay().toInstant(java.time.ZoneOffset.UTC));
+            mainBo.setExpectedDeliveryTime(expectedDeliveryTime);
+
+            // 设置仓库(假设默认仓库)
+            mainBo.setWarehouseId(1L); // TODO: 后续可配置或根据客户动态获取
+
+
+            // 设置订单状态为待支付
+            mainBo.setOrderStatus(OrderStatus.PENDING_PAYMENT.getCode());
+
+            // 设置审核状态为待审核
+            mainBo.setCheckStatus("0");
+
+            //商城下单默认需要审核--如果设置审批流则不需要
+            mainBo.setIsNeedCheck(SysPlatformYesNo.YES.getCode());
+
+            mainBo.setOrderSource(OrderSourceEnum.BEFORE_ADD.getCode());
+
+            // 设置订单商品列表
+            // mainBo.setOrderProductBos(bo.getOrderProductBos());
+            Map<Long, Long> productNumMap;
+            // 1. 获取购物车项
+            if (bo.getPlaceOrderType() == 1) {
+                Set<Long> productShoppingCartIds = bo.getProductShoppingCartId();
+                if (productShoppingCartIds == null || productShoppingCartIds.isEmpty()) {
+                    throw new IllegalArgumentException("购物车项ID不能为空");
+                }
+
+                List<RemoteProductShoppingCartVo> shoppingCartList = remoteProductShoppingCartService.getShoppingCartList(productShoppingCartIds);
+                if (shoppingCartList.isEmpty()) {
+                    throw new IllegalStateException("购物车数据不存在");
+                }
+
+                // 2. 构建 productId -> productNum 映射(防御性:过滤 null key)
+                productNumMap = shoppingCartList.stream()
+                    .filter(item -> item.getProductId() != null && item.getProductNum() != null)
+                    .collect(Collectors.toMap(
+                        RemoteProductShoppingCartVo::getProductId,
+                        RemoteProductShoppingCartVo::getProductNum,
+                        (existing, replacement) -> existing // 防止重复 key 冲突(理论上不应发生)
+                    ));
+            } else {
+                productNumMap = bo.getProductInfo().stream()
+                    .filter(item -> item.getProductId() != null && item.getProductNum() != null)
+                    .collect(Collectors.toMap(
+                        PcSubmitOrderBo.PcOrderProduct::getProductId,
+                        PcSubmitOrderBo.PcOrderProduct::getProductNum,
+                        (existing, replacement) -> existing // 防止重复 key 冲突(理论上不应发生)
+                    ));
+            }
+
+
+            // 3. 提取所有商品 ID(使用 List/Collection,而非拼接字符串!)
+            List<Long> productIds = new ArrayList<>(productNumMap.keySet());
+
+
+            // 如果远程服务只接受字符串
+            List<ProductVo> productDetails = remoteProductService.getProductDetails(productIds);
+
+            // 4. 构建订单商品列表
+            List<OrderProductBo> orderProductBos = productDetails.stream()
+                .map(productVo -> {
+                    Long productId = productVo.getId();
+                    Long quantity = productNumMap.get(productId);
+                    if (quantity == null) {
+                        // 理论上不会发生,但防御性处理
+                        log.warn("商品 {} 在购物车中无数量信息,跳过");
+                        return null;
+                    }
+
+                    OrderProductBo orderProductBo = new OrderProductBo();
+                    orderProductBo.setProductId(productId);
+                    orderProductBo.setProductNo(productVo.getProductNo());
+                    orderProductBo.setProductName(productVo.getItemName());
+                    orderProductBo.setProductUnitId(Long.valueOf(productVo.getUnitId()));
+                    orderProductBo.setProductUnit(productVo.getUnitName());
+                    orderProductBo.setProductImage(productVo.getProductImage());
+                    orderProductBo.setOrderPrice(productVo.getMemberPrice());
+                    orderProductBo.setMarketPrice(productVo.getMarketPrice());
+                    orderProductBo.setTaxRate(productVo.getTaxRate());
+                    orderProductBo.setCategoryName(productVo.getCategoryName());
+                    orderProductBo.setPurchasingPrice(productVo.getPurchasingPrice());
+                    orderProductBo.setMinOrderQuantity(productVo.getMinOrderQuantity());
+                    orderProductBo.setMinSellingPrice(productVo.getMinSellingPrice());
+                    orderProductBo.setOrderQuantity(quantity);
+                    orderProductBo.setSubtotal(productVo.getMemberPrice().multiply(BigDecimal.valueOf(quantity)));
+                    return orderProductBo;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+            if (orderProductBos.isEmpty()) {
+                throw new IllegalStateException("无可下单的商品");
+            }
+
+            mainBo.setOrderProductBos(orderProductBos);
+
+            // 5. 保存订单(关键:成功后再删除购物车)
+            Long orderId = orderMainService.insertOrder(bo, mainBo);
+
+            return R.ok(orderId); // 正确返回 ID
+        } catch (Exception e) {
+            log.error("小程序端提交订单失败", e);
+            return R.fail("下单失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 订单支付(MINI端下单)
+     */
+    @Log(title = "MINI端-订单支付", businessType = BusinessType.UPDATE)
+    @PostMapping("/orderPay")
+    public R<?> orderPay(@RequestBody @Validated OrderPayDto payDto) {
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+
+        // 调用 Service,返回支付结果
+        Boolean orderPayResult = orderMainService.orderPay(
+            customerId,
+            payDto.getOrderId(),
+            payDto.getPayType()
+        );
+
+        return R.ok(orderPayResult);
+    }
+
+    @GetMapping("/batchConfirmation")
+    public R<Void> batchConfirmation(@RequestParam("orderIds") List<Long> orderIds) {
+        if (orderIds == null || orderIds.isEmpty()) {
+            throw new IllegalArgumentException("订单ID列表不能为空");
+        }
+        if (orderIds.size() > 1000) {
+            throw new IllegalArgumentException("订单ID数量不能超过1000个");
+        }
+
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getLoginUser().getCustomerId();
+        // 获取当前登录用户的用户ID
+        Long userId = LoginHelper.getLoginUser().getUserId();
+
+        // 验证所有订单是否都属于当前用户的企业
+        for (Long orderId : orderIds) {
+            OrderMainVo order = orderMainService.queryById(orderId);
+            if (order == null) {
+                throw new IllegalArgumentException("订单ID " + orderId + " 不存在");
+            }
+            if (!userId.equals(order.getUserId())) {
+                throw new IllegalArgumentException("无权访问订单ID " + orderId);
+            }
+        }
+
+        Set<Long> uniqueOrderIds = new HashSet<>(orderIds);
+        return toAjax(orderMainService.batchConfirmation(uniqueOrderIds));
+    }
+}