Ver Fonte

订单评审列表变更流程

Pengxy há 3 meses atrás
pai
commit
70903dcbc4
18 ficheiros alterados com 1092 adições e 27 exclusões
  1. 7 0
      yudao-order-server/pom.xml
  2. 45 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/OrderChangeController.java
  3. 32 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderChangeCreateReqVO.java
  4. 93 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderChangeRespVO.java
  5. 104 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/OrderChangeDO.java
  6. 3 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/OrderDO.java
  7. 20 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/OrderChangeMapper.java
  8. 9 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/OrderEntryMapper.java
  9. 36 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/OrderChangeService.java
  10. 191 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderChangeServiceImpl.java
  11. 31 0
      yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/listener/OrderChangeStatusListener.java
  12. 13 6
      yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/order.ts
  13. 11 0
      yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/orderChange.ts
  14. 24 0
      yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts
  15. 26 16
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/OrderReview.vue
  16. 55 5
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/OrderChangeForm.vue
  17. 289 0
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/orderChange/create.vue
  18. 103 0
      yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/orderChange/detail.vue

+ 7 - 0
yudao-order-server/pom.xml

@@ -23,6 +23,13 @@
             <version>${revision}</version>
         </dependency>
 
+        <!-- BPM 工作流能力 -->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-bpm</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
         <!-- 业务组件 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 45 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/OrderChangeController.java

@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.order.controller.admin.order;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeCreateReqVO;
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeRespVO;
+import cn.iocoder.yudao.module.order.service.OrderChangeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * 订单变更流程 Controller
+ */
+@Tag(name = "管理后台 - 订单变更流程")
+@RestController
+@RequestMapping("/order/change")
+@Validated
+public class OrderChangeController {
+
+    @Resource
+    private OrderChangeService orderChangeService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建订单变更申请")
+    @PreAuthorize("@ss.hasPermission('order:change:create')")
+    public CommonResult<Long> createOrderChange(@Valid @RequestBody OrderChangeCreateReqVO createReqVO) {
+        return success(orderChangeService.createOrderChange(getLoginUserId(), createReqVO));
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获取订单变更详情")
+    @Parameter(name = "id", description = "订单ID", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('order:change:query')")
+    public CommonResult<OrderChangeRespVO> getOrderChange(@RequestParam("id") Long id) {
+        return success(orderChangeService.getOrderChange(id));
+    }
+}

+ 32 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderChangeCreateReqVO.java

@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.order.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 订单变更申请创建 Request VO")
+@Data
+public class OrderChangeCreateReqVO {
+
+    @Schema(description = "订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "订单ID不能为空")
+    private Long orderId;
+
+    @Schema(description = "变更原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户原因")
+    @NotNull(message = "变更原因不能为空")
+    private String changeReason;
+
+    @Schema(description = "变更要求", requiredMode = Schema.RequiredMode.REQUIRED, example = "变更订单")
+    @NotNull(message = "变更要求不能为空")
+    private String changeType;
+
+    @Schema(description = "变更内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "修改交期")
+    @NotNull(message = "变更内容不能为空")
+    private String changeContent;
+
+    @Schema(description = "发起人自选审批人 Map", example = "{taskKey1: [1, 2]}")
+    private Map<String, List<Long>> startUserSelectAssignees;
+}

+ 93 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/controller/admin/order/vo/OrderChangeRespVO.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.order.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 订单变更申请 Response VO")
+@Data
+public class OrderChangeRespVO {
+
+    @Schema(description = "订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "订单编号")
+    private String billNo;
+
+    @Schema(description = "订单类别")
+    private String orderType;
+
+    @Schema(description = "客户名称")
+    private String customName;
+
+    @Schema(description = "客户编码")
+    private String customNo;
+
+    @Schema(description = "客户级别")
+    private String customLevel;
+
+    @Schema(description = "签订日期")
+    private LocalDateTime date;
+
+    @Schema(description = "客户下单日期")
+    private LocalDateTime rdate;
+
+    @Schema(description = "是否加急")
+    private String urgent;
+
+    @Schema(description = "客户订单号")
+    private String billFrom;
+
+    @Schema(description = "国家")
+    private String country;
+
+    @Schema(description = "变更原因")
+    private String changeReason;
+
+    @Schema(description = "变更要求")
+    private String changeType;
+
+    @Schema(description = "变更内容")
+    private String changeContent;
+
+    @Schema(description = "流程状态")
+    private String flowstate;
+
+    @Schema(description = "流程实例ID")
+    private String processInstanceId;
+
+    @Schema(description = "申请时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "订单明细")
+    private List<OrderEntryVO> items;
+
+    @Data
+    public static class OrderEntryVO {
+        @Schema(description = "明细ID")
+        private Long id;
+
+        @Schema(description = "物料编码")
+        private String itemNumber;
+
+        @Schema(description = "物料名称")
+        private String itemName;
+
+        @Schema(description = "规格型号")
+        private String specification;
+
+        @Schema(description = "单位")
+        private String unit;
+
+        @Schema(description = "订单数量")
+        private java.math.BigDecimal qty;
+
+        @Schema(description = "已发货数量")
+        private java.math.BigDecimal deliverCount;
+
+        @Schema(description = "订单进度")
+        private Integer progress;
+    }
+}

+ 104 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/OrderChangeDO.java

@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.order.dal.dataobject;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 订单变更记录 DO
+ */
+@TableName("crm_seorder_change")
+@Data
+public class OrderChangeDO {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 关联的订单ID */
+    @TableField("seorder_id")
+    private String seorderId;
+
+    /** 订单号 */
+    @TableField("bill_no")
+    private String billNo;
+
+    /** 订单类型 */
+    @TableField("order_type")
+    private String orderType;
+
+    /** 客户ID */
+    @TableField("custom_id")
+    private Long customId;
+
+    /** 客户名称 */
+    @TableField("custom_name")
+    private String customName;
+
+    /** 客户编码 */
+    @TableField("custom_no")
+    private String customNo;
+
+    /** 客户级别 */
+    @TableField("custom_level")
+    private String customLevel;
+
+    /** 签订日期 */
+    private LocalDateTime date;
+
+    /** 下单日期 */
+    private LocalDateTime rdate;
+
+    /** 是否加急 */
+    private String urgent;
+
+    /** 变更原因 */
+    @TableField("change_Reason")
+    private String changeReason;
+
+    /** 变更要求 */
+    @TableField("change_Type")
+    private String changeType;
+
+    /** 变更内容 */
+    @TableField("change_content")
+    private String changeContent;
+
+    /** 流程状态 */
+    private String flowstate;
+
+    /** 是否关闭 */
+    private Boolean closed;
+
+    /** 是否删除 */
+    @TableField("IsDeleted")
+    private Boolean isDeleted;
+
+    /** 工厂ID */
+    @TableField("factory_id")
+    private Long factoryId;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+    
+    @TableField("create_by")
+    private Long createBy;
+    
+    @TableField("create_by_name")
+    private String createByName;
+    
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+    
+    @TableField("update_by")
+    private Long updateBy;
+    
+    @TableField("update_by_name")
+    private String updateByName;
+    
+    @TableField("tenant_id")
+    private Long tenantId;
+}

+ 3 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/dataobject/OrderDO.java

@@ -161,6 +161,9 @@ public class OrderDO {
     private String reason;
     /** 流程状态 flowstate */
     private String flowstate;
+    /** 流程实例ID process_instance_id */
+    @TableField("process_instance_id")
+    private String processInstanceId;
 
     /** 逻辑删除标识,表字段 IsDeleted */
     @TableLogic(value = "0", delval = "1")

+ 20 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/OrderChangeMapper.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.order.dal.mysql;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.order.dal.dataobject.OrderChangeDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 订单变更记录 Mapper
+ */
+@Mapper
+public interface OrderChangeMapper extends BaseMapperX<OrderChangeDO> {
+
+    default OrderChangeDO selectByOrderId(Long orderId) {
+        return selectOne(new LambdaQueryWrapper<OrderChangeDO>()
+                .eq(OrderChangeDO::getSeorderId, String.valueOf(orderId))
+                .orderByDesc(OrderChangeDO::getId)
+                .last("LIMIT 1"));
+    }
+}

+ 9 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/dal/mysql/OrderEntryMapper.java

@@ -3,7 +3,10 @@ package cn.iocoder.yudao.module.order.dal.mysql;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.module.order.dal.dataobject.OrderEntryDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
 
@@ -22,5 +25,11 @@ public interface OrderEntryMapper extends BaseMapperX<OrderEntryDO> {
     default void deleteByOrderId(Long orderId) {
         delete(new LambdaQueryWrapper<OrderEntryDO>().eq(OrderEntryDO::getSeorderId, orderId));
     }
+
+    /**
+     * 根据订单ID更新明细的进度
+     */
+    @Update("UPDATE crm_seorderentry SET progress = #{progress} WHERE seorder_id = #{orderId}")
+    void updateProgressByOrderId(@Param("orderId") Long orderId, @Param("progress") Integer progress);
 }
 

+ 36 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/OrderChangeService.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.order.service;
+
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeCreateReqVO;
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeRespVO;
+import jakarta.validation.Valid;
+
+/**
+ * 订单变更流程 Service 接口
+ */
+public interface OrderChangeService {
+
+    /**
+     * 创建订单变更申请,并发起工作流
+     *
+     * @param userId 用户ID
+     * @param createReqVO 创建请求
+     * @return 订单ID
+     */
+    Long createOrderChange(Long userId, @Valid OrderChangeCreateReqVO createReqVO);
+
+    /**
+     * 更新订单变更流程状态
+     *
+     * @param id 订单ID
+     * @param status 流程状态
+     */
+    void updateOrderChangeStatus(Long id, Integer status);
+
+    /**
+     * 获取订单变更详情
+     *
+     * @param id 订单ID
+     * @return 订单变更详情
+     */
+    OrderChangeRespVO getOrderChange(Long id);
+}

+ 191 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/impl/OrderChangeServiceImpl.java

@@ -0,0 +1,191 @@
+package cn.iocoder.yudao.module.order.service.impl;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeCreateReqVO;
+import cn.iocoder.yudao.module.order.controller.admin.order.vo.OrderChangeRespVO;
+import cn.iocoder.yudao.module.order.dal.dataobject.OrderChangeDO;
+import cn.iocoder.yudao.module.order.dal.dataobject.OrderDO;
+import cn.iocoder.yudao.module.order.dal.dataobject.OrderEntryDO;
+import cn.iocoder.yudao.module.order.dal.mysql.OrderChangeMapper;
+import cn.iocoder.yudao.module.order.dal.mysql.OrderEntryMapper;
+import cn.iocoder.yudao.module.order.dal.mysql.OrderMapper;
+import cn.iocoder.yudao.module.order.service.OrderChangeService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.order.enums.ErrorCodeConstants.ORDER_NOT_EXISTS;
+
+/**
+ * 订单变更流程 Service 实现类
+ */
+@Service
+@Validated
+public class OrderChangeServiceImpl implements OrderChangeService {
+
+    /**
+     * 订单变更对应的流程定义 KEY
+     */
+    public static final String PROCESS_KEY = "Order_Change";
+
+    @Resource
+    private OrderMapper orderMapper;
+
+    @Resource
+    private OrderEntryMapper orderEntryMapper;
+
+    @Resource
+    private OrderChangeMapper orderChangeMapper;
+
+    @Resource
+    private BpmProcessInstanceApi processInstanceApi;
+
+    @Resource
+    private ConfigApi configApi;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @TenantIgnore
+    public Long createOrderChange(Long userId, OrderChangeCreateReqVO createReqVO) {
+        // 1. 校验订单是否存在
+        OrderDO order = orderMapper.selectById(createReqVO.getOrderId());
+        if (order == null) {
+            throw exception(ORDER_NOT_EXISTS);
+        }
+
+        // 2. 创建变更记录
+        OrderChangeDO changeDO = new OrderChangeDO();
+        changeDO.setSeorderId(String.valueOf(order.getId()));
+        changeDO.setBillNo(order.getBillNo());
+        changeDO.setOrderType(order.getOrderType());
+        changeDO.setCustomId(order.getCustomId());
+        changeDO.setCustomName(order.getCustomName());
+        changeDO.setCustomNo(order.getCustomNo());
+        changeDO.setCustomLevel(order.getCustomLevel());
+        changeDO.setDate(order.getDate());
+        changeDO.setRdate(order.getRdate());
+        changeDO.setUrgent(order.getUrgent());
+        changeDO.setChangeReason(createReqVO.getChangeReason());
+        changeDO.setChangeType(createReqVO.getChangeType());
+        changeDO.setChangeContent(createReqVO.getChangeContent());
+        changeDO.setFlowstate("审批中");
+        changeDO.setClosed(false);
+        changeDO.setIsDeleted(false);
+        changeDO.setCreateTime(LocalDateTime.now());
+        changeDO.setCreateBy(userId);
+        // 从配置管理获取默认租户编码设置到factory_id
+        String defaultDomain = configApi.getConfigValueByKey("order.default.domain");
+        if (defaultDomain != null) {
+            changeDO.setFactoryId(Long.parseLong(defaultDomain));
+        }
+        orderChangeMapper.insert(changeDO);
+
+        // 3. 更新订单的流程状态
+        OrderDO updateOrder = new OrderDO();
+        updateOrder.setId(order.getId());
+        updateOrder.setFlowstate("审批中");
+        orderMapper.updateById(updateOrder);
+
+        // 4. 发起 BPM 流程
+        Map<String, Object> processInstanceVariables = new HashMap<>();
+        processInstanceVariables.put("orderId", order.getId());
+        processInstanceVariables.put("changeId", changeDO.getId());
+        processInstanceVariables.put("billNo", order.getBillNo());
+        processInstanceVariables.put("changeReason", createReqVO.getChangeReason());
+        processInstanceVariables.put("changeType", createReqVO.getChangeType());
+        processInstanceVariables.put("changeContent", createReqVO.getChangeContent());
+
+        String processInstanceId = processInstanceApi.createProcessInstance(userId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(PROCESS_KEY)
+                        .setVariables(processInstanceVariables)
+                        .setBusinessKey(String.valueOf(changeDO.getId()))
+                        .setStartUserSelectAssignees(createReqVO.getStartUserSelectAssignees()));
+
+        // 5. 将工作流的编号,更新到订单中
+        orderMapper.updateById(new OrderDO().setId(order.getId()).setProcessInstanceId(processInstanceId));
+
+        return changeDO.getId();
+    }
+
+    @Override
+    @TenantIgnore
+    public void updateOrderChangeStatus(Long id, Integer status) {
+        // 校验变更记录是否存在
+        OrderChangeDO changeDO = orderChangeMapper.selectById(id);
+        if (changeDO == null) {
+            return;
+        }
+
+        // 根据流程状态更新
+        String flowstate;
+        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(status)) {
+            flowstate = "审批完成";
+            // 审批通过后,更新订单明细的进度为0(再评审)
+            Long orderId = Long.parseLong(changeDO.getSeorderId());
+            orderEntryMapper.updateProgressByOrderId(orderId, 0);
+            // 更新订单的流程状态
+            orderMapper.updateById(new OrderDO().setId(orderId).setFlowstate(flowstate));
+        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(status)) {
+            flowstate = "审批拒绝";
+            Long orderId = Long.parseLong(changeDO.getSeorderId());
+            orderMapper.updateById(new OrderDO().setId(orderId).setFlowstate(flowstate));
+        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(status)) {
+            flowstate = "已取消";
+            Long orderId = Long.parseLong(changeDO.getSeorderId());
+            orderMapper.updateById(new OrderDO().setId(orderId).setFlowstate(flowstate));
+        } else {
+            flowstate = "审批中";
+        }
+
+        // 更新变更记录的流程状态
+        OrderChangeDO updateChange = new OrderChangeDO();
+        updateChange.setId(id);
+        updateChange.setFlowstate(flowstate);
+        orderChangeMapper.updateById(updateChange);
+    }
+
+    @Override
+    @TenantIgnore
+    public OrderChangeRespVO getOrderChange(Long id) {
+        // 查询变更记录
+        OrderChangeDO changeDO = orderChangeMapper.selectById(id);
+        if (changeDO == null) {
+            return null;
+        }
+
+        // 转换为响应VO
+        OrderChangeRespVO respVO = new OrderChangeRespVO();
+        respVO.setId(changeDO.getId());
+        respVO.setBillNo(changeDO.getBillNo());
+        respVO.setOrderType(changeDO.getOrderType() != null ? String.valueOf(changeDO.getOrderType()) : null);
+        respVO.setCustomName(changeDO.getCustomName());
+        respVO.setCustomNo(changeDO.getCustomNo());
+        respVO.setCustomLevel(changeDO.getCustomLevel());
+        respVO.setDate(changeDO.getDate());
+        respVO.setUrgent(changeDO.getUrgent());
+        respVO.setFlowstate(changeDO.getFlowstate());
+        respVO.setChangeReason(changeDO.getChangeReason());
+        respVO.setChangeType(changeDO.getChangeType());
+        respVO.setChangeContent(changeDO.getChangeContent());
+
+        // 查询订单明细
+        Long orderId = Long.parseLong(changeDO.getSeorderId());
+        List<OrderEntryDO> entries = orderEntryMapper.selectListByOrderId(orderId);
+        respVO.setItems(BeanUtils.toBean(entries, OrderChangeRespVO.OrderEntryVO.class));
+
+        return respVO;
+    }
+}

+ 31 - 0
yudao-order-server/src/main/java/cn/iocoder/yudao/module/order/service/listener/OrderChangeStatusListener.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.order.service.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.order.service.OrderChangeService;
+import cn.iocoder.yudao.module.order.service.impl.OrderChangeServiceImpl;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+/**
+ * 订单变更流程结果的监听器实现类
+ *
+ * 监听流程实例的最终结果,更新订单的流程状态
+ */
+@Component
+public class OrderChangeStatusListener extends BpmProcessInstanceStatusEventListener {
+
+    @Resource
+    private OrderChangeService orderChangeService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return OrderChangeServiceImpl.PROCESS_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        // 根据流程实例的最终状态,更新订单的流程状态
+        orderChangeService.updateOrderChangeStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
+    }
+}

+ 13 - 6
yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/order.ts

@@ -1,18 +1,25 @@
 import request from '@/config/axios'
+import axios from 'axios'
 
 // 订单评审列表
 export const getOrderReviewList = (params: any) => {
   return request.get({ url: '/order/review/list', params })
 }
 
-// 订单评审
-export const reviewOrder = (data: any) => {
-  return request.post({ url: '/order/review', data })
+// 订单评审 - 调用外部接口
+export const reviewOrder = (data: { ids: number[] }) => {
+  const ids = data.ids.join(',')
+  return axios.post(
+    `http://123.60.180.165:8087/api/business/resource-examine/receiveresult?companyid=1000&type=0&ids=${ids}`
+  )
 }
 
-// 交期确认
-export const confirmDeliveryDate = (data: any) => {
-  return request.post({ url: '/order/confirm-delivery', data })
+// 交期确认 - 调用外部接口
+export const confirmDeliveryDate = (data: { ids: number[] }) => {
+  const ids = data.ids.join(',')
+  return axios.get(
+    `http://123.60.180.165:8087/api/business/resource-examine/reviewExamineResult?type=0&ids=${ids}`
+  )
 }
 
 // 资源解锁

+ 11 - 0
yudao-ui/yudao-ui-admin-vue3/src/api/jiaohuo/orderChange.ts

@@ -0,0 +1,11 @@
+import request from '@/config/axios'
+
+// 创建订单变更申请
+export const createOrderChange = (data: any) => {
+  return request.post({ url: '/order/change/create', data })
+}
+
+// 获取订单变更详情
+export const getOrderChange = (id: number) => {
+  return request.get({ url: '/order/change/get', params: { id } })
+}

+ 24 - 0
yudao-ui/yudao-ui-admin-vue3/src/router/modules/remaining.ts

@@ -320,6 +320,30 @@ const remainingRouter: AppRouteRecordRaw[] = [
         }
       },
       {
+        path: 'orderChange/create',
+        component: () => import('@/views/jiaohuo/orderChange/create.vue'),
+        name: 'OrderChangeCreate',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '发起订单变更',
+          activeMenu: '/jiaohuo/order-review'
+        }
+      },
+      {
+        path: 'orderChange/detail',
+        component: () => import('@/views/jiaohuo/orderChange/detail.vue'),
+        name: 'OrderChangeDetail',
+        meta: {
+          noCache: true,
+          hidden: true,
+          canTo: true,
+          title: '查看订单变更',
+          activeMenu: '/jiaohuo/order-review'
+        }
+      },
+      {
         path: 'manager/model/create',
         component: () => import('@/views/bpm/model/form/index.vue'),
         name: 'BpmModelCreate',

+ 26 - 16
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/OrderReview.vue

@@ -127,21 +127,19 @@
 
     <!-- 销售订单表单 -->
     <SalesOrderForm ref="orderFormRef" @success="handleSearch" />
-
-    <!-- 订单变更表单 -->
-    <OrderChangeForm ref="changeFormRef" @success="handleSearch" />
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref, reactive, computed } from 'vue'
+import { useRouter } from 'vue-router'
 import { Search, Refresh, Plus, Check, CircleCheck, Unlock, Edit, View, EditPen } from '@element-plus/icons-vue'
 import { getOrderReviewList, reviewOrder, confirmDeliveryDate, unlockResource } from '@/api/jiaohuo/order'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import SalesOrderForm from './components/SalesOrderForm.vue'
-import OrderChangeForm from './components/OrderChangeForm.vue'
 import { formatDate } from '@/utils/format'
 
+const { push } = useRouter()
 const loading = ref(false)
 const tableData = ref([])
 const selectedRows = ref([])
@@ -162,7 +160,6 @@ const pagination = reactive({
 const hasSelection = computed(() => selectedRows.value.length > 0)
 
 const orderFormRef = ref(null)
-const changeFormRef = ref(null)
 
 // 查询
 const handleSearch = async () => {
@@ -223,9 +220,12 @@ const handleView = (row) => {
   orderFormRef.value.open('view', row.id)
 }
 
-// 变更
-const handleChange = (row) => {
-  changeFormRef.value.open(row.id)
+// 变更 - 跳转到订单变更发起页面
+const handleChange = (row: any) => {
+  push({
+    name: 'OrderChangeCreate',
+    query: { id: row.id }
+  })
 }
 
 // 订单评审
@@ -241,10 +241,15 @@ const handleReview = async () => {
     })
 
     loading.value = true
-    const ids = selectedRows.value.map(row => row.id)
-    await reviewOrder({ ids })
-    ElMessage.success('订单评审成功')
-    handleSearch()
+    const ids = selectedRows.value.map((row) => row.id)
+    const res = await reviewOrder({ ids })
+    // 外部接口返回文本格式处理
+    if (res.data === 'ok') {
+      ElMessage.success('资源检查完成')
+      handleSearch()
+    } else {
+      ElMessage.error('资源检查异常:' + res.data)
+    }
   } catch (error) {
     if (error !== 'cancel') {
       ElMessage.error('订单评审失败:' + (error.message || '未知错误'))
@@ -267,10 +272,15 @@ const handleConfirm = async () => {
     })
 
     loading.value = true
-    const ids = selectedRows.value.map(row => row.id)
-    await confirmDeliveryDate({ ids })
-    ElMessage.success('交期确认成功')
-    handleSearch()
+    const ids = selectedRows.value.map((row) => row.id)
+    const res = await confirmDeliveryDate({ ids })
+    // 外部接口返回文本格式处理
+    if (res.data === 'ok') {
+      ElMessage.success('当前评审数据完成')
+      handleSearch()
+    } else {
+      ElMessage.error('当前评审数据失败:' + res.data)
+    }
   } catch (error) {
     if (error !== 'cancel') {
       ElMessage.error('交期确认失败:' + (error.message || '未知错误'))

+ 55 - 5
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/components/OrderChangeForm.vue

@@ -64,8 +64,8 @@
         <el-col :span="8">
           <el-form-item label="是否加急" prop="urgent">
             <el-radio-group v-model="formData.urgent" disabled>
-              <el-radio :label="1">是</el-radio>
-              <el-radio :label="0">否</el-radio>
+              <el-radio :value="1">是</el-radio>
+              <el-radio :value="0">否</el-radio>
             </el-radio-group>
           </el-form-item>
         </el-col>
@@ -138,7 +138,7 @@
         <el-table-column prop="deliver_count" label="已发货数量" width="120" align="right" />
         <el-table-column prop="progress" label="订单进度" width="100">
           <template #default="{ row }">
-            <el-tag v-if="row.progress">{{ row.progress }}</el-tag>
+            <el-tag v-if="row.progress !== ''">{{ getProgressText(row.progress) }}</el-tag>
           </template>
         </el-table-column>
       </el-table>
@@ -218,6 +218,23 @@ const formRules = {
 
 const flowList = ref([])
 
+// 进度映射
+const progressMap = {
+  '0': '新建',
+  '1': '评审',
+  '2': '排产',
+  '3': '齐套',
+  '4': '采购',
+  '5': '备料',
+  '6': '生产',
+  '7': '发运'
+}
+
+// 获取进度文本
+const getProgressText = (progress) => {
+  return progressMap[String(progress)] || progress || '-'
+}
+
 const emit = defineEmits(['success'])
 
 // 打开对话框
@@ -230,8 +247,41 @@ const open = async (id) => {
 // 加载数据
 const loadData = async (id) => {
   try {
-    const { data } = await getOrderDetail(id)
-    Object.assign(formData, data)
+    const data = await getOrderDetail(id)
+    if (data) {
+      // 处理日期格式
+      const formatDate = (val) => {
+        if (!val) return ''
+        if (typeof val === 'number') {
+          return new Date(val).toISOString().substring(0, 10)
+        }
+        return String(val).substring(0, 10)
+      }
+      
+      Object.assign(formData, {
+        bill_no: data.billNo || '',
+        order_type: data.orderType || '',
+        custom_name: data.customName || '',
+        date: formatDate(data.date),
+        custom_level: data.customLevel || 0,
+        urgent: data.urgent ? Number(data.urgent) : 0,
+        bill_from: data.billFrom || '',
+        country: data.country || '',
+        rdate: formatDate(data.rdate),
+        change_reason: data.changeReason || '',
+        change_type: data.changeType || '',
+        change_content: data.changeContent || '',
+        items: (data.items || []).map((item) => ({
+          item_number: item.itemNumber || '',
+          item_name: item.itemName || '',
+          specification: item.specification || '',
+          unit: item.unit || '',
+          qty: item.qty || 0,
+          deliver_count: item.deliverCount || 0,
+          progress: item.progress || ''
+        }))
+      })
+    }
   } catch (error) {
     ElMessage.error('加载数据失败:' + (error.message || '未知错误'))
   }

+ 289 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/orderChange/create.vue

@@ -0,0 +1,289 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="16">
+      <ContentWrap title="订单变更申请">
+        <el-form
+          ref="formRef"
+          v-loading="formLoading"
+          :model="formData"
+          :rules="formRules"
+          label-width="120px"
+        >
+          <!-- 订单基本信息(只读) -->
+          <el-divider content-position="left">订单信息</el-divider>
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="订单编号">
+                <el-input v-model="orderInfo.billNo" disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="订单类别">
+                <el-input :value="orderInfo.orderType === '1' ? '销售' : '计划'" disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="客户名称">
+                <el-input v-model="orderInfo.customName" disabled />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="签订日期">
+                <el-input :value="formatDate(orderInfo.date)" disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="客户级别">
+                <el-input v-model="orderInfo.customLevel" disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="是否加急">
+                <el-input :value="orderInfo.urgent == 1 ? '是' : '否'" disabled />
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <!-- 变更信息 -->
+          <el-divider content-position="left">变更信息</el-divider>
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="变更原因" prop="changeReason">
+                <el-select v-model="formData.changeReason" placeholder="请选择变更原因">
+                  <el-option label="客户原因" value="客户原因" />
+                  <el-option label="公司原因" value="公司原因" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="8">
+              <el-form-item label="变更要求" prop="changeType">
+                <el-select v-model="formData.changeType" placeholder="请选择变更要求">
+                  <el-option label="变更订单" value="变更订单" />
+                  <el-option label="取消订单" value="取消订单" />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="24">
+              <el-form-item label="变更内容" prop="changeContent">
+                <el-input
+                  v-model="formData.changeContent"
+                  type="textarea"
+                  :rows="4"
+                  placeholder="请输入变更内容"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <!-- 订单明细 -->
+          <el-divider content-position="left">订单明细</el-divider>
+          <el-table :data="orderInfo.items" border stripe max-height="300">
+            <el-table-column prop="itemNumber" label="产品代码" width="150" />
+            <el-table-column prop="itemName" label="产品名称" width="200" />
+            <el-table-column prop="specification" label="规格型号" width="150" />
+            <el-table-column prop="unit" label="单位" width="80" />
+            <el-table-column prop="qty" label="订单数量" width="100" align="right" />
+            <el-table-column prop="deliverCount" label="已发货" width="100" align="right" />
+            <el-table-column prop="progress" label="进度" width="80">
+              <template #default="{ row }">
+                {{ getProgressText(row.progress) }}
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <el-form-item style="margin-top: 20px">
+            <el-button :disabled="formLoading" type="primary" @click="submitForm">
+              提交申请
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+    </el-col>
+
+    <!-- 审批流程 -->
+    <el-col :span="8">
+      <ContentWrap title="审批流程" :bodyStyle="{ padding: '0 20px 0' }">
+        <ProcessInstanceTimeline
+          ref="timelineRef"
+          :activity-nodes="activityNodes"
+          :show-status-icon="false"
+          @select-user-confirm="selectUserConfirm"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+</template>
+
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import * as OrderChangeApi from '@/api/jiaohuo/orderChange'
+import { getOrderDetail } from '@/api/jiaohuo/order'
+import { useTagsViewStore } from '@/store/modules/tagsView'
+import * as DefinitionApi from '@/api/bpm/definition'
+import ProcessInstanceTimeline from '@/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue'
+import * as ProcessInstanceApi from '@/api/bpm/processInstance'
+import { CandidateStrategy, NodeId } from '@/components/SimpleProcessDesignerV2/src/consts'
+import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
+
+defineOptions({ name: 'OrderChangeCreate' })
+
+const message = useMessage()
+const { delView } = useTagsViewStore()
+const { push, currentRoute } = useRouter()
+const { query } = useRoute()
+
+const formLoading = ref(false)
+const orderId = ref<number>(query.id as unknown as number)
+
+const orderInfo = ref<any>({
+  billNo: '',
+  orderType: '',
+  customName: '',
+  customNo: '',
+  customLevel: '',
+  date: '',
+  rdate: '',
+  urgent: '',
+  billFrom: '',
+  country: '',
+  items: []
+})
+
+const formData = ref({
+  orderId: orderId.value,
+  changeReason: '',
+  changeType: '',
+  changeContent: ''
+})
+
+const formRules = reactive({
+  changeReason: [{ required: true, message: '请选择变更原因', trigger: 'change' }],
+  changeType: [{ required: true, message: '请选择变更要求', trigger: 'change' }],
+  changeContent: [{ required: true, message: '请输入变更内容', trigger: 'blur' }]
+})
+
+const formRef = ref()
+
+// 审批相关
+const processDefineKey = 'Order_Change'
+const startUserSelectTasks = ref([])
+const startUserSelectAssignees = ref({})
+const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
+const processDefinitionId = ref('')
+
+// 进度映射
+const progressMap = {
+  '0': '再评审',
+  '1': '新建',
+  '2': '评审',
+  '3': '确认'
+}
+
+const getProgressText = (progress: any) => {
+  return progressMap[String(progress)] || progress || '-'
+}
+
+/** 加载订单信息 */
+const loadOrderInfo = async () => {
+  if (!orderId.value) {
+    message.error('订单ID不能为空')
+    return
+  }
+  try {
+    const data = await getOrderDetail(orderId.value)
+    if (data) {
+      orderInfo.value = data
+    }
+  } catch (error) {
+    message.error('加载订单信息失败')
+  }
+}
+
+/** 提交表单 */
+const submitForm = async () => {
+  if (!formRef) return
+  const valid = await formRef.value.validate()
+  if (!valid) return
+
+  // 校验指定审批人
+  if (startUserSelectTasks.value?.length > 0) {
+    for (const userTask of startUserSelectTasks.value) {
+      if (
+        Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
+        startUserSelectAssignees.value[userTask.id].length === 0
+      ) {
+        return message.warning(`请选择${userTask.name}的审批人`)
+      }
+    }
+  }
+
+  formLoading.value = true
+  try {
+    const data = { ...formData.value } as any
+    data.orderId = orderId.value
+    if (startUserSelectTasks.value?.length > 0) {
+      data.startUserSelectAssignees = startUserSelectAssignees.value
+    }
+    await OrderChangeApi.createOrderChange(data)
+    message.success('提交成功')
+    delView(unref(currentRoute))
+    push({ path: '/s1/order/order-review' })
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 获取审批详情 */
+const getApprovalDetail = async () => {
+  try {
+    const data = await ProcessInstanceApi.getApprovalDetail({
+      processDefinitionId: processDefinitionId.value,
+      activityId: NodeId.START_USER_NODE_ID,
+      processVariablesStr: JSON.stringify({ orderId: orderId.value })
+    })
+
+    if (!data) {
+      return
+    }
+    activityNodes.value = data.activityNodes
+    startUserSelectTasks.value = data.activityNodes?.filter(
+      (node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
+    )
+    if (startUserSelectTasks.value?.length > 0) {
+      for (const node of startUserSelectTasks.value) {
+        startUserSelectAssignees.value[node.id] = []
+      }
+    }
+  } catch (error) {
+    console.error('获取审批详情失败', error)
+  }
+}
+
+/** 选择发起人 */
+const selectUserConfirm = (id: string, userList: any[]) => {
+  startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
+}
+
+/** 初始化 */
+onMounted(async () => {
+  await loadOrderInfo()
+
+  const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
+    undefined,
+    processDefineKey
+  )
+
+  if (!processDefinitionDetail) {
+    message.warning('订单变更的流程模型未配置,请检查!')
+    return
+  }
+  processDefinitionId.value = processDefinitionDetail.id
+  startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
+
+  await getApprovalDetail()
+})
+</script>

+ 103 - 0
yudao-ui/yudao-ui-admin-vue3/src/views/jiaohuo/orderChange/detail.vue

@@ -0,0 +1,103 @@
+<template>
+  <ContentWrap>
+    <el-descriptions :column="2" border>
+      <el-descriptions-item label="订单编号">{{ detailData.billNo }}</el-descriptions-item>
+      <el-descriptions-item label="订单类别">
+        {{ detailData.orderType === '1' ? '销售' : '计划' }}
+      </el-descriptions-item>
+      <el-descriptions-item label="客户名称">{{ detailData.customName }}</el-descriptions-item>
+      <el-descriptions-item label="客户编码">{{ detailData.customNo }}</el-descriptions-item>
+      <el-descriptions-item label="签订日期">
+        {{ formatDate(detailData.date, 'YYYY-MM-DD') }}
+      </el-descriptions-item>
+      <el-descriptions-item label="客户级别">{{ detailData.customLevel }}</el-descriptions-item>
+      <el-descriptions-item label="是否加急">
+        {{ detailData.urgent == 1 ? '是' : '否' }}
+      </el-descriptions-item>
+      <el-descriptions-item label="流程状态">
+        <el-tag :type="getFlowStateType(detailData.flowstate)">
+          {{ detailData.flowstate || '-' }}
+        </el-tag>
+      </el-descriptions-item>
+      <el-descriptions-item label="变更原因">{{ detailData.changeReason }}</el-descriptions-item>
+      <el-descriptions-item label="变更要求">{{ detailData.changeType }}</el-descriptions-item>
+      <el-descriptions-item label="变更内容" :span="2">
+        {{ detailData.changeContent }}
+      </el-descriptions-item>
+    </el-descriptions>
+
+    <!-- 订单明细 -->
+    <el-divider content-position="left">订单明细</el-divider>
+    <el-table :data="detailData.items" border stripe>
+      <el-table-column prop="itemNumber" label="产品代码" width="150" />
+      <el-table-column prop="itemName" label="产品名称" width="200" />
+      <el-table-column prop="specification" label="规格型号" width="150" />
+      <el-table-column prop="unit" label="单位" width="80" />
+      <el-table-column prop="qty" label="订单数量" width="100" align="right" />
+      <el-table-column prop="deliverCount" label="已发货" width="100" align="right" />
+      <el-table-column prop="progress" label="进度" width="80">
+        <template #default="{ row }">
+          {{ getProgressText(row.progress) }}
+        </template>
+      </el-table-column>
+    </el-table>
+  </ContentWrap>
+</template>
+
+<script lang="ts" setup>
+import { formatDate } from '@/utils/formatTime'
+import { propTypes } from '@/utils/propTypes'
+import * as OrderChangeApi from '@/api/jiaohuo/orderChange'
+
+defineOptions({ name: 'OrderChangeDetail' })
+
+const { query } = useRoute()
+
+const props = defineProps({
+  id: propTypes.number.def(undefined)
+})
+
+const detailLoading = ref(false)
+const detailData = ref<any>({
+  items: []
+})
+const queryId = query.id as unknown as number
+
+// 进度映射
+const progressMap = {
+  '0': '再评审',
+  '1': '新建',
+  '2': '评审',
+  '3': '确认'
+}
+
+const getProgressText = (progress: any) => {
+  return progressMap[String(progress)] || progress || '-'
+}
+
+const getFlowStateType = (state: string) => {
+  const typeMap = {
+    '审批中': 'warning',
+    '审批完成': 'success',
+    '审批拒绝': 'danger',
+    '已取消': 'info'
+  }
+  return typeMap[state] || 'info'
+}
+
+/** 获取数据 */
+const getInfo = async () => {
+  detailLoading.value = true
+  try {
+    detailData.value = await OrderChangeApi.getOrderChange(props.id || queryId)
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+defineExpose({ open: getInfo })
+
+onMounted(() => {
+  getInfo()
+})
+</script>