Commit 6a2a05b32ef9d8a86c052b9664ec579952fd7f60

Authored by YunaiV
1 parent 660a8515

✨ feat(mes): 添加生产批号字段及相关逻辑

在多个 VO 和服务中添加生产批号字段,更新相关逻辑以支持批号的生成和管理。此变更旨在增强物料消耗记录的追踪能力,确保生产过程中的批次管理更加清晰和有效。
Showing 22 changed files with 313 additions and 51 deletions
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java
@@ -110,6 +110,9 @@ public class MesProFeedbackRespVO { @@ -110,6 +110,9 @@ public class MesProFeedbackRespVO {
110 @Schema(description = "过期日期") 110 @Schema(description = "过期日期")
111 private LocalDateTime expireDate; 111 private LocalDateTime expireDate;
112 112
  113 + @Schema(description = "生产批号", example = "LOT20260321")
  114 + private String lotNumber;
  115 +
113 @Schema(description = "排产数量", example = "5000.00") 116 @Schema(description = "排产数量", example = "5000.00")
114 @ExcelProperty("排产数量") 117 @ExcelProperty("排产数量")
115 private BigDecimal scheduledQuantity; 118 private BigDecimal scheduledQuantity;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java
@@ -49,6 +49,9 @@ public class MesProFeedbackSaveReqVO { @@ -49,6 +49,9 @@ public class MesProFeedbackSaveReqVO {
49 @Schema(description = "过期日期") 49 @Schema(description = "过期日期")
50 private LocalDateTime expireDate; 50 private LocalDateTime expireDate;
51 51
  52 + @Schema(description = "生产批号", example = "LOT20260321")
  53 + private String lotNumber;
  54 +
52 @Schema(description = "排产数量", example = "5000.00") 55 @Schema(description = "排产数量", example = "5000.00")
53 private BigDecimal scheduledQuantity; 56 private BigDecimal scheduledQuantity;
54 57
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
@@ -134,7 +134,7 @@ public class MesProTaskController { @@ -134,7 +134,7 @@ public class MesProTaskController {
134 } 134 }
135 135
136 @GetMapping("/gantt-list") 136 @GetMapping("/gantt-list")
137 - @Operation(summary = "获得甘特图任务列表", description = "对齐 KTG:后端组装工单=project + 任务=task 列表") 137 + @Operation(summary = "获得甘特图任务列表", description = "后端组装工单=project + 任务=task 列表")
138 @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") 138 @PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
139 public CommonResult<List<GanttDataRespVO>> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) { 139 public CommonResult<List<GanttDataRespVO>> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) {
140 // 1.1 查询匹配的工单(不分页) 140 // 1.1 查询匹配的工单(不分页)
@@ -251,7 +251,7 @@ public class MesProTaskController { @@ -251,7 +251,7 @@ public class MesProTaskController {
251 } 251 }
252 252
253 /** 253 /**
254 - * 拼接甘特图显示文本,对齐 KTG 格式:"[产品名][数量][单位]" 254 + * 拼接甘特图显示文本,格式:"[产品名][数量][单位]"
255 */ 255 */
256 private String buildGanttText(MesMdItemDO item, BigDecimal quantity, 256 private String buildGanttText(MesMdItemDO item, BigDecimal quantity,
257 Map<Long, MesMdUnitMeasureDO> unitMap) { 257 Map<Long, MesMdUnitMeasureDO> unitMap) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/GanttDataRespVO.java
@@ -8,7 +8,7 @@ import java.math.BigDecimal; @@ -8,7 +8,7 @@ import java.math.BigDecimal;
8 import java.time.LocalDateTime; 8 import java.time.LocalDateTime;
9 9
10 /** 10 /**
11 - * 甘特图数据项 VO(对齐 KTG GanttData) 11 + * 甘特图数据项 VO
12 */ 12 */
13 @Schema(description = "管理后台 - 甘特图数据项 Response VO") 13 @Schema(description = "管理后台 - 甘特图数据项 Response VO")
14 @Data 14 @Data
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java
@@ -97,6 +97,12 @@ public class MesProFeedbackDO extends BaseDO { @@ -97,6 +97,12 @@ public class MesProFeedbackDO extends BaseDO {
97 */ 97 */
98 private LocalDateTime expireDate; 98 private LocalDateTime expireDate;
99 /** 99 /**
  100 + * 生产批号
  101 + *
  102 + * TODO @芋艿:预留字段,当前未启用
  103 + */
  104 + private String lotNumber;
  105 + /**
100 * 排产数量 106 * 排产数量
101 */ 107 */
102 private BigDecimal scheduledQuantity; 108 private BigDecimal scheduledQuantity;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/task/MesProTaskMapper.java
@@ -5,8 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -5,8 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO; 6 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
  11 +import java.math.BigDecimal;
10 import java.util.List; 12 import java.util.List;
11 13
12 /** 14 /**
@@ -42,4 +44,15 @@ public interface MesProTaskMapper extends BaseMapperX&lt;MesProTaskDO&gt; { @@ -42,4 +44,15 @@ public interface MesProTaskMapper extends BaseMapperX&lt;MesProTaskDO&gt; {
42 .orderByDesc(MesProTaskDO::getId)); 44 .orderByDesc(MesProTaskDO::getId));
43 } 45 }
44 46
  47 + default void updateProducedQuantity(Long id,
  48 + BigDecimal incrProducedQuantity,
  49 + BigDecimal incrQualifyQuantity,
  50 + BigDecimal incrUnqualifyQuantity) {
  51 + update(null, new LambdaUpdateWrapper<MesProTaskDO>()
  52 + .eq(MesProTaskDO::getId, id)
  53 + .setSql("produced_quantity = IFNULL(produced_quantity, 0) + " + incrProducedQuantity)
  54 + .setSql("qualify_quantity = IFNULL(qualify_quantity, 0) + " + incrQualifyQuantity)
  55 + .setSql("unqualify_quantity = IFNULL(unqualify_quantity, 0) + " + incrUnqualifyQuantity));
  56 + }
  57 +
45 } 58 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkorderMapper.java
@@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO; 6 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO;
7 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
  11 +import java.math.BigDecimal;
  12 +
10 /** 13 /**
11 * MES 生产工单 Mapper 14 * MES 生产工单 Mapper
12 * 15 *
@@ -32,4 +35,10 @@ public interface MesProWorkOrderMapper extends BaseMapperX&lt;MesProWorkOrderDO&gt; { @@ -32,4 +35,10 @@ public interface MesProWorkOrderMapper extends BaseMapperX&lt;MesProWorkOrderDO&gt; {
32 return selectOne(MesProWorkOrderDO::getCode, code); 35 return selectOne(MesProWorkOrderDO::getCode, code);
33 } 36 }
34 37
  38 + default void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) {
  39 + update(null, new LambdaUpdateWrapper<MesProWorkOrderDO>()
  40 + .eq(MesProWorkOrderDO::getId, id)
  41 + .setSql("quantity_produced = IFNULL(quantity_produced, 0) + " + incrQuantityProduced));
  42 + }
  43 +
35 } 44 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/packages/MesWmPackageLineMapper.java
@@ -17,7 +17,7 @@ public interface MesWmPackageLineMapper extends BaseMapperX&lt;MesWmPackageLineDO&gt; @@ -17,7 +17,7 @@ public interface MesWmPackageLineMapper extends BaseMapperX&lt;MesWmPackageLineDO&gt;
17 17
18 default PageResult<MesWmPackageLineDO> selectPage(MesWmPackageLinePageReqVO reqVO) { 18 default PageResult<MesWmPackageLineDO> selectPage(MesWmPackageLinePageReqVO reqVO) {
19 // TODO @芋艿:需确认“父箱详情 -> 装箱清单”是否需要汇总当前箱及其所有子孙箱的明细; 19 // TODO @芋艿:需确认“父箱详情 -> 装箱清单”是否需要汇总当前箱及其所有子孙箱的明细;
20 - // ktg-mes 当前通过 SQL + ancestors 实现聚合查询,这里现在仅按 packageId 精确查询。 20 + // TODO @芋艿:对齐,当前通过 SQL + ancestors 实现聚合查询,这里现在仅按 packageId 精确查询。
21 return selectPage(reqVO, new LambdaQueryWrapperX<MesWmPackageLineDO>() 21 return selectPage(reqVO, new LambdaQueryWrapperX<MesWmPackageLineDO>()
22 .eqIfPresent(MesWmPackageLineDO::getPackageId, reqVO.getPackageId()) 22 .eqIfPresent(MesWmPackageLineDO::getPackageId, reqVO.getPackageId())
23 .orderByDesc(MesWmPackageLineDO::getId)); 23 .orderByDesc(MesWmPackageLineDO::getId));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productproduce/MesWmProductProduceLineMapper.java
@@ -31,4 +31,8 @@ public interface MesWmProductProduceLineMapper extends BaseMapperX&lt;MesWmProductP @@ -31,4 +31,8 @@ public interface MesWmProductProduceLineMapper extends BaseMapperX&lt;MesWmProductP
31 delete(MesWmProductProduceLineDO::getProduceId, produceId); 31 delete(MesWmProductProduceLineDO::getProduceId, produceId);
32 } 32 }
33 33
  34 + default List<MesWmProductProduceLineDO> selectListByFeedbackId(Long feedbackId) {
  35 + return selectList(MesWmProductProduceLineDO::getFeedbackId, feedbackId);
  36 + }
  37 +
34 } 38 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -47,7 +47,6 @@ public interface DictTypeConstants { @@ -47,7 +47,6 @@ public interface DictTypeConstants {
47 String MES_PRO_FEEDBACK_CHANNEL = "mes_pro_feedback_channel"; // MES 生产报工途径 47 String MES_PRO_FEEDBACK_CHANNEL = "mes_pro_feedback_channel"; // MES 生产报工途径
48 String MES_WM_ARRIVAL_NOTICE_STATUS = "mes_wm_arrival_notice_status"; // MES 到货通知单状态 48 String MES_WM_ARRIVAL_NOTICE_STATUS = "mes_wm_arrival_notice_status"; // MES 到货通知单状态
49 String MES_WM_ITEM_RECEIPT_STATUS = "mes_wm_item_receipt_status"; // MES 采购入库单状态 49 String MES_WM_ITEM_RECEIPT_STATUS = "mes_wm_item_receipt_status"; // MES 采购入库单状态
50 - String MES_WM_PRODUCT_PRODUCE_STATUS = "mes_product_produce_status"; // MES 生产入库单状态  
51 String MES_WM_QUALITY_STATUS = "mes_wm_quality_status"; // MES 质量状态(待检/合格/不合格) 50 String MES_WM_QUALITY_STATUS = "mes_wm_quality_status"; // MES 质量状态(待检/合格/不合格)
52 String MES_WM_RETURN_ISSUE_STATUS = "mes_return_issue_status"; // MES 生产退料单状态 51 String MES_WM_RETURN_ISSUE_STATUS = "mes_return_issue_status"; // MES 生产退料单状态
53 String MES_WM_RETURN_ISSUE_TYPE = "mes_return_issue_type"; // MES 生产退料类型 52 String MES_WM_RETURN_ISSUE_TYPE = "mes_return_issue_type"; // MES 生产退料类型
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
@@ -66,11 +66,24 @@ public interface MesProFeedbackService { @@ -66,11 +66,24 @@ public interface MesProFeedbackService {
66 void rejectFeedback(Long id); 66 void rejectFeedback(Long id);
67 67
68 /** 68 /**
69 - * 审批报工(审批中 -> 已完成 或 待检验) 69 + * 审批通过报工单,执行生产入库流程
70 * 70 *
71 - * @param id 编号  
72 - * @param userId 当前操作用户编号  
73 - * @return true=已完成, false=待检验 71 + * <p>整体流程如下:
  72 + * <ol>
  73 + * <li>校验报工单状态和数量</li>
  74 + * <li>生成物料消耗单,扣减线边库物料</li>
  75 + * <li>根据工序配置(关键工序 & 是否需要检验)分情况处理:
  76 + * <ul>
  77 + * <li><b>关键工序 + 需要检验</b>:生成产出单(质量状态=待检验),报工单状态 → 待检验,等待质检完成回调</li>
  78 + * <li><b>关键工序 + 无需检验</b>:生成产出单(按合格/不合格拆行),执行产品入库,更新任务/工单已生产数量,报工单状态 → 已完成</li>
  79 + * <li><b>非关键工序</b>:不生成产出单,不更新任务/工单数量,报工单状态 → 已完成</li>
  80 + * </ul>
  81 + * </li>
  82 + * </ol>
  83 + *
  84 + * @param id 报工单编号
  85 + * @param userId 当前操作用户编号(审核人)
  86 + * @return true=已完成, false=待检验(需等质检回调)
74 */ 87 */
75 boolean approveFeedback(Long id, Long userId); 88 boolean approveFeedback(Long id, Long userId);
76 89
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -8,15 +8,20 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedba @@ -8,15 +8,20 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedba
8 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO; 8 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
9 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; 9 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
10 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; 10 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
11 -import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;  
12 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; 11 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
  12 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemconsume.MesWmItemConsumeDO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
13 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; 15 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
14 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; 16 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
  17 +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
15 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; 18 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
16 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; 19 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
17 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; 20 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
18 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; 21 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
19 import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService; 22 import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
  23 +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
  24 +import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
20 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
21 import org.springframework.context.annotation.Lazy; 26 import org.springframework.context.annotation.Lazy;
22 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
@@ -25,6 +30,7 @@ import org.springframework.validation.annotation.Validated; @@ -25,6 +30,7 @@ import org.springframework.validation.annotation.Validated;
25 30
26 import java.math.BigDecimal; 31 import java.math.BigDecimal;
27 import java.time.LocalDateTime; 32 import java.time.LocalDateTime;
  33 +import java.util.List;
28 34
29 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
30 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 36 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -44,19 +50,19 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService { @@ -44,19 +50,19 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService {
44 50
45 @Resource 51 @Resource
46 private MesProWorkOrderService workOrderService; 52 private MesProWorkOrderService workOrderService;
47 -  
48 @Resource 53 @Resource
49 private MesProRouteProcessService routeProcessService; 54 private MesProRouteProcessService routeProcessService;
50 -  
51 @Resource 55 @Resource
52 private MesMdWorkstationService workstationService; 56 private MesMdWorkstationService workstationService;
53 -  
54 @Resource 57 @Resource
55 @Lazy // 避免循环依赖 58 @Lazy // 避免循环依赖
56 private MesProTaskService taskService; 59 private MesProTaskService taskService;
57 -  
58 @Resource 60 @Resource
59 private MesWmItemConsumeService itemConsumeService; 61 private MesWmItemConsumeService itemConsumeService;
  62 + @Resource
  63 + private MesWmProductProduceService productProduceService;
  64 + @Resource
  65 + private MesWmProductProduceLineService produceLineService;
60 66
61 @Override 67 @Override
62 public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) { 68 public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
@@ -143,39 +149,75 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService { @@ -143,39 +149,75 @@ public class MesProFeedbackServiceImpl implements MesProFeedbackService {
143 throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity()); 149 throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity());
144 } 150 }
145 151
146 - // 2. 物料消耗:根据工序 BOM 生成消耗记录 152 + // 2. 物料消耗:根据工序 BOM 生成消耗记录并执行扣减
147 MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback); 153 MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
148 if (itemConsume != null) { 154 if (itemConsume != null) {
149 itemConsumeService.finishItemConsume(itemConsume.getId()); 155 itemConsumeService.finishItemConsume(itemConsume.getId());
150 } 156 }
151 157
152 - // 3. 查询工序的 keyFlag + checkFlag 158 + // 3. 查询工序的关键工序标识 + 检验标识
153 MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId( 159 MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId(
154 feedback.getRouteId(), feedback.getProcessId()); 160 feedback.getRouteId(), feedback.getProcessId());
155 boolean keyFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getKeyFlag()); 161 boolean keyFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getKeyFlag());
156 boolean checkFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getCheckFlag()); 162 boolean checkFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getCheckFlag());
157 163
158 - // 4. 情况一:关键工序 + 需要检验 → 待检验状态(等质检完成后入库)  
159 - if (keyFlag && checkFlag) {  
160 - feedbackMapper.updateById(new MesProFeedbackDO().setId(id)  
161 - .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus())  
162 - .setApproveUserId(userId));  
163 - // TODO @芋艿:WM 产品产出(WmProductProduceService)待补充 generateProductProduce(质量状态=待检验)  
164 - return false;  
165 - }  
166 -  
167 - // 4.1 情况二:关键工序 + 无需检验 → 直接完成 164 + // 4. 关键工序:生成产出单,并根据是否需要检验决定入库方式
168 if (keyFlag) { 165 if (keyFlag) {
169 - // TODO @芋艿:WM 产品产出(WmProductProduceService)待补充 generateProductProduce + executeProductProduce  
170 - // TODO @芋艿:updateProTaskAndWorkorder 待补充级联更新任务/工单的已生产数量 166 + // 4.1 需要检验:生成产出单(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库
  167 + if (checkFlag) {
  168 + // TODO @AI(from codex):当前这里只生成待检产出并把报工置为待检验;后续还需要在 IPQC 完成时继续拆分产出行、完成产出入库、
  169 + // 回写任务/工单进度,并把报工状态改为已完成。
  170 + productProduceService.generateProductProduce(feedback, true);
  171 + feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
  172 + .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()));
  173 + return false;
  174 + }
  175 + // 4.2 无需检验:生成产出单(按合格/不合格拆行),直接完成入库,并更新任务/工单的已生产数量
  176 + MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false);
  177 + productProduceService.finishProductProduce(produce.getId());
  178 + updateTaskAndWorkOrderByFeedback(feedback);
171 } 179 }
172 - // 4.2 更新状态为已完成 + 审核人 180 +
  181 + // 5. 非关键工序:不生成产出单,不更新任务/工单数量,直接完成
173 feedbackMapper.updateById(new MesProFeedbackDO().setId(id) 182 feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
174 .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus()) 183 .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus())
175 .setApproveUserId(userId)); 184 .setApproveUserId(userId));
176 return true; // 已完成 185 return true; // 已完成
177 } 186 }
178 187
  188 + /**
  189 + * 根据当前报工单的最终结果,更新生产任务和生产工单的进度
  190 + *
  191 + * <ul>
  192 + * <li>使用报工数量(feedbackQuantity),累加任务和工单的已生产数量</li>
  193 + * <li>使用产出单行按 qualityStatus 聚合的合格品/不合格品数量,累加任务的合格品/不合格品数量
  194 + * (不直接用 feedback 上的数量,确保质检回调场景下数量来源正确)</li>
  195 + * </ul>
  196 + *
  197 + * @param feedback 报工单
  198 + */
  199 + private void updateTaskAndWorkOrderByFeedback(MesProFeedbackDO feedback) {
  200 + // 1. 查询该报工单关联的所有产出单行,按质量状态聚合数量
  201 + BigDecimal qualifiedQty = BigDecimal.ZERO;
  202 + BigDecimal unqualifiedQty = BigDecimal.ZERO;
  203 + List<MesWmProductProduceLineDO> lines = produceLineService.getProductProduceLineListByFeedbackId(feedback.getId());
  204 + for (MesWmProductProduceLineDO line : lines) {
  205 + if (ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())) {
  206 + unqualifiedQty = unqualifiedQty.add(line.getQuantity());
  207 + }
  208 + if (ObjUtil.equal(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) {
  209 + qualifiedQty = qualifiedQty.add(line.getQuantity());
  210 + }
  211 + }
  212 +
  213 + // 2. 更新任务的已生产/合格/不合格数量
  214 + taskService.updateProducedQuantity(feedback.getTaskId(),
  215 + feedback.getFeedbackQuantity(), qualifiedQty, unqualifiedQty);
  216 + // 3. 更新工单的已生产数量
  217 + workOrderService.updateProducedQuantity(feedback.getWorkOrderId(),
  218 + feedback.getFeedbackQuantity());
  219 + }
  220 +
179 // ==================== 校验方法 ==================== 221 // ==================== 校验方法 ====================
180 222
181 private MesProFeedbackDO validateFeedbackExists(Long id) { 223 private MesProFeedbackDO validateFeedbackExists(Long id) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveRe @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveRe
6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.math.BigDecimal;
9 import java.util.Collection; 10 import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 import java.util.Map; 12 import java.util.Map;
@@ -121,4 +122,17 @@ public interface MesProTaskService { @@ -121,4 +122,17 @@ public interface MesProTaskService {
121 */ 122 */
122 void cancelTaskByOrderId(Long workOrderId); 123 void cancelTaskByOrderId(Long workOrderId);
123 124
  125 + /**
  126 + * 累加任务的已生产、合格、不合格数量
  127 + *
  128 + * @param id 任务编号
  129 + * @param incrProducedQuantity 本次已生产数量增量(不良品也计入)
  130 + * @param incrQualifyQuantity 本次合格品数量增量
  131 + * @param incrUnqualifyQuantity 本次不合格品数量增量
  132 + */
  133 + void updateProducedQuantity(Long id,
  134 + BigDecimal incrProducedQuantity,
  135 + BigDecimal incrQualifyQuantity,
  136 + BigDecimal incrUnqualifyQuantity);
  137 +
124 } 138 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -196,4 +196,15 @@ public class MesProTaskServiceImpl implements MesProTaskService { @@ -196,4 +196,15 @@ public class MesProTaskServiceImpl implements MesProTaskService {
196 } 196 }
197 } 197 }
198 198
  199 + @Override
  200 + public void updateProducedQuantity(Long id,
  201 + BigDecimal incrProducedQuantity,
  202 + BigDecimal incrQualifyQuantity,
  203 + BigDecimal incrUnqualifyQuantity) {
  204 + // 校验任务存在
  205 + validateTaskExists(id);
  206 + // 更新数量
  207 + taskMapper.updateProducedQuantity(id, incrProducedQuantity, incrQualifyQuantity, incrUnqualifyQuantity);
  208 + }
  209 +
199 } 210 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkorderService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkO @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkO
6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; 6 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.math.BigDecimal;
9 import java.util.Collection; 10 import java.util.Collection;
10 import java.util.List; 11 import java.util.List;
11 import java.util.Map; 12 import java.util.Map;
@@ -119,4 +120,12 @@ public interface MesProWorkOrderService { @@ -119,4 +120,12 @@ public interface MesProWorkOrderService {
119 */ 120 */
120 List<MesProWorkOrderDO> getWorkOrderSimpleList(); 121 List<MesProWorkOrderDO> getWorkOrderSimpleList();
121 122
  123 + /**
  124 + * 累加工单的已生产数量
  125 + *
  126 + * @param id 工单编号
  127 + * @param incrQuantityProduced 本次已生产数量增量
  128 + */
  129 + void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced);
  130 +
122 } 131 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkorderServiceImpl.java
@@ -20,6 +20,7 @@ import org.springframework.stereotype.Service; @@ -20,6 +20,7 @@ import org.springframework.stereotype.Service;
20 import org.springframework.transaction.annotation.Transactional; 20 import org.springframework.transaction.annotation.Transactional;
21 import org.springframework.validation.annotation.Validated; 21 import org.springframework.validation.annotation.Validated;
22 22
  23 +import java.math.BigDecimal;
23 import java.time.LocalDateTime; 24 import java.time.LocalDateTime;
24 import java.util.Collection; 25 import java.util.Collection;
25 import java.util.Collections; 26 import java.util.Collections;
@@ -233,4 +234,12 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService { @@ -233,4 +234,12 @@ public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
233 return workOrderMapper.selectList(); 234 return workOrderMapper.selectList();
234 } 235 }
235 236
  237 + @Override
  238 + public void updateProducedQuantity(Long id, BigDecimal incrQuantityProduced) {
  239 + // 校验工单存在
  240 + validateWorkOrderExists(id);
  241 + // 更新数量
  242 + workOrderMapper.updateProducedQuantity(id, incrQuantityProduced);
  243 + }
  244 +
236 } 245 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/ipqc/MesQcIpqcServiceImpl.java
@@ -124,7 +124,8 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService { @@ -124,7 +124,8 @@ public class MesQcIpqcServiceImpl implements MesQcIpqcService {
124 updateObj.setStatus(MesQcStatusEnum.FINISHED.getStatus()); 124 updateObj.setStatus(MesQcStatusEnum.FINISHED.getStatus());
125 ipqcMapper.updateById(updateObj); 125 ipqcMapper.updateById(updateObj);
126 126
127 - // TODO @芋艿:IPQC 完成时的 PRO/WM 联动(报工反馈更新、产品产出单拆分),待 WM 模块迁移后对接 127 + // TODO @AI(from codex):如果检验来源是报工,需要对待检产出按检验结果拆分合格/不合格行、生成产出明细、完成产品产出入库、
  128 + // 回写任务/工单数量,并将报工状态从待检验改为已完成。
128 } 129 }
129 130
130 @Override 131 @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
@@ -20,8 +20,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @@ -20,8 +20,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
20 /** 20 /**
21 * MES 物料消耗记录 Service 实现类 21 * MES 物料消耗记录 Service 实现类
22 * 22 *
23 - * 参考 KTG {@code WmItemConsumeServiceImpl#generateItemConsume}  
24 - *  
25 * @author 芋道源码 23 * @author 芋道源码
26 */ 24 */
27 @Service 25 @Service
@@ -60,12 +58,13 @@ public class MesWmItemConsumeServiceImpl implements MesWmItemConsumeService { @@ -60,12 +58,13 @@ public class MesWmItemConsumeServiceImpl implements MesWmItemConsumeService {
60 .consumeId(consume.getId()).itemId(bom.getItemId()) 58 .consumeId(consume.getId()).itemId(bom.getItemId())
61 .quantity(bom.getQuantity().multiply(feedback.getFeedbackQuantity())).build()); 59 .quantity(bom.getQuantity().multiply(feedback.getFeedbackQuantity())).build());
62 itemConsumeLineService.createItemConsumeLineBatch(lines); 60 itemConsumeLineService.createItemConsumeLineBatch(lines);
  61 + // TODO @AI(from codex):对齐,这里还需要按线边库/FIFO 生成消耗明细,并在库存不足时回退为无批次消耗明细。
63 return consume; 62 return consume;
64 } 63 }
65 64
66 @Override 65 @Override
67 public void finishItemConsume(Long consumeId) { 66 public void finishItemConsume(Long consumeId) {
68 - // TODO @芋艿:待 MaterialStockService 补充 decreaseStock 后实现库存扣减 67 + // TODO @AI(from codex):对齐,这里还需要按消耗明细扣减线边库库存,不能只把消耗单状态改为已完成。
69 // List<MesWmItemConsumeLineDO> lines = itemConsumeLineService.getItemConsumeLineListByConsumeId(consumeId); 68 // List<MesWmItemConsumeLineDO> lines = itemConsumeLineService.getItemConsumeLineListByConsumeId(consumeId);
70 // for (MesWmItemConsumeLineDO line : lines) { 69 // for (MesWmItemConsumeLineDO line : lines) {
71 // materialStockService.decreaseStock(line.getItemId(), ..., line.getQuantity()); 70 // materialStockService.decreaseStock(line.getItemId(), ..., line.getQuantity());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineService.java
@@ -74,4 +74,12 @@ public interface MesWmProductProduceLineService { @@ -74,4 +74,12 @@ public interface MesWmProductProduceLineService {
74 */ 74 */
75 MesWmProductProduceLineDO validateProductProduceLineExists(Long id); 75 MesWmProductProduceLineDO validateProductProduceLineExists(Long id);
76 76
  77 + /**
  78 + * 根据报工记录ID获取行列表
  79 + *
  80 + * @param feedbackId 报工记录ID
  81 + * @return 行列表
  82 + */
  83 + List<MesWmProductProduceLineDO> getProductProduceLineListByFeedbackId(Long feedbackId);
  84 +
77 } 85 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceLineServiceImpl.java
@@ -97,4 +97,9 @@ public class MesWmProductProduceLineServiceImpl implements MesWmProductProduceLi @@ -97,4 +97,9 @@ public class MesWmProductProduceLineServiceImpl implements MesWmProductProduceLi
97 return line; 97 return line;
98 } 98 }
99 99
  100 + @Override
  101 + public List<MesWmProductProduceLineDO> getProductProduceLineListByFeedbackId(Long feedbackId) {
  102 + return produceLineMapper.selectListByFeedbackId(feedbackId);
  103 + }
  104 +
100 } 105 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mes.service.wm.productproduce; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.mes.service.wm.productproduce;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProducePageReqVO; 4 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProducePageReqVO;
5 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProduceSaveReqVO; 5 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProduceSaveReqVO;
  6 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
6 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; 7 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
7 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
8 9
@@ -79,4 +80,13 @@ public interface MesWmProductProduceService { @@ -79,4 +80,13 @@ public interface MesWmProductProduceService {
79 */ 80 */
80 Boolean checkProductProduceQuantity(Long id); 81 Boolean checkProductProduceQuantity(Long id);
81 82
  83 + /**
  84 + * 根据报工记录,自动生成产品产出单(头 + 行 + 明细)
  85 + *
  86 + * @param feedback 报工记录
  87 + * @param checkFlag 是否需要检验(true=待检验,false=按合格/不合格拆分行)
  88 + * @return 生成的产品产出单
  89 + */
  90 + MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag);
  91 +
82 } 92 }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
@@ -2,25 +2,36 @@ package cn.iocoder.yudao.module.mes.service.wm.productproduce; @@ -2,25 +2,36 @@ package cn.iocoder.yudao.module.mes.service.wm.productproduce;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 9 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  10 +import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
9 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProducePageReqVO; 11 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProducePageReqVO;
10 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProduceSaveReqVO; 12 import cn.iocoder.yudao.module.mes.controller.admin.wm.productproduce.vo.MesWmProductProduceSaveReqVO;
  13 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
  14 +import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
  15 +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
11 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO; 16 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDetailDO;
12 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; 17 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
13 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; 18 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
  19 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceDetailMapper;
  20 +import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper;
14 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; 21 import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper;
15 import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductProduceStatusEnum; 22 import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductProduceStatusEnum;
  23 +import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
16 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService; 24 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
17 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; 25 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
  26 +import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
18 import jakarta.annotation.Resource; 27 import jakarta.annotation.Resource;
19 import org.springframework.stereotype.Service; 28 import org.springframework.stereotype.Service;
20 import org.springframework.transaction.annotation.Transactional; 29 import org.springframework.transaction.annotation.Transactional;
21 import org.springframework.validation.annotation.Validated; 30 import org.springframework.validation.annotation.Validated;
22 31
23 import java.math.BigDecimal; 32 import java.math.BigDecimal;
  33 +import java.time.LocalDate;
  34 +import java.time.LocalDateTime;
24 import java.util.List; 35 import java.util.List;
25 36
26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 37 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -34,7 +45,13 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @@ -34,7 +45,13 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
34 public class MesWmProductProduceServiceImpl implements MesWmProductProduceService { 45 public class MesWmProductProduceServiceImpl implements MesWmProductProduceService {
35 46
36 @Resource 47 @Resource
37 - private MesWmProductProduceMapper produceMapper; 48 + private MesWmProductProduceMapper productProduceMapper;
  49 + // TODO @芋艿:需要优化,不要直接调用对方的 mapper
  50 + @Resource
  51 + private MesWmProductProduceLineMapper produceLineMapper;
  52 + // TODO @芋艿:需要优化,不要直接调用对方的 mapper
  53 + @Resource
  54 + private MesWmProductProduceDetailMapper produceDetailMapper;
38 55
39 @Resource 56 @Resource
40 private MesWmProductProduceLineService produceLineService; 57 private MesWmProductProduceLineService produceLineService;
@@ -44,6 +61,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -44,6 +61,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
44 private MesMdWorkstationService workstationService; 61 private MesMdWorkstationService workstationService;
45 @Resource 62 @Resource
46 private MesProWorkOrderService workOrderService; 63 private MesProWorkOrderService workOrderService;
  64 + @Resource
  65 + private MesWmBatchService batchService;
47 66
48 @Override 67 @Override
49 @Transactional(rollbackFor = Exception.class) 68 @Transactional(rollbackFor = Exception.class)
@@ -59,7 +78,7 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -59,7 +78,7 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
59 // 2. 插入主表 78 // 2. 插入主表
60 MesWmProductProduceDO produce = BeanUtils.toBean(createReqVO, MesWmProductProduceDO.class); 79 MesWmProductProduceDO produce = BeanUtils.toBean(createReqVO, MesWmProductProduceDO.class);
61 produce.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus()); 80 produce.setStatus(MesWmProductProduceStatusEnum.PREPARE.getStatus());
62 - produceMapper.insert(produce); 81 + productProduceMapper.insert(produce);
63 return produce.getId(); 82 return produce.getId();
64 } 83 }
65 84
@@ -78,7 +97,7 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -78,7 +97,7 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
78 97
79 // 2. 更新主表 98 // 2. 更新主表
80 MesWmProductProduceDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductProduceDO.class); 99 MesWmProductProduceDO updateObj = BeanUtils.toBean(updateReqVO, MesWmProductProduceDO.class);
81 - produceMapper.updateById(updateObj); 100 + productProduceMapper.updateById(updateObj);
82 } 101 }
83 102
84 @Override 103 @Override
@@ -92,32 +111,31 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -92,32 +111,31 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
92 // 2.2 级联删除行 111 // 2.2 级联删除行
93 produceLineService.deleteProductProduceLineByProduceId(id); 112 produceLineService.deleteProductProduceLineByProduceId(id);
94 // 2.3 删除主表 113 // 2.3 删除主表
95 - produceMapper.deleteById(id); 114 + productProduceMapper.deleteById(id);
96 } 115 }
97 116
98 @Override 117 @Override
99 public MesWmProductProduceDO getProductProduce(Long id) { 118 public MesWmProductProduceDO getProductProduce(Long id) {
100 - return produceMapper.selectById(id); 119 + return productProduceMapper.selectById(id);
101 } 120 }
102 121
103 @Override 122 @Override
104 public PageResult<MesWmProductProduceDO> getProductProducePage(MesWmProductProducePageReqVO pageReqVO) { 123 public PageResult<MesWmProductProduceDO> getProductProducePage(MesWmProductProducePageReqVO pageReqVO) {
105 - return produceMapper.selectPage(pageReqVO); 124 + return productProduceMapper.selectPage(pageReqVO);
106 } 125 }
107 126
108 @Override 127 @Override
109 @Transactional(rollbackFor = Exception.class) 128 @Transactional(rollbackFor = Exception.class)
110 public void finishProductProduce(Long id) { 129 public void finishProductProduce(Long id) {
111 - // 1. 校验存在 + 草稿状态 130 + // 1.1 校验存在 + 草稿状态
112 validateProductProduceExistsAndPrepare(id); 131 validateProductProduceExistsAndPrepare(id);
113 -  
114 - // 2. 校验至少有一条行 132 + // 1.2 校验至少有一条行
115 List<MesWmProductProduceLineDO> lines = produceLineService.getProductProduceLineListByProduceId(id); 133 List<MesWmProductProduceLineDO> lines = produceLineService.getProductProduceLineListByProduceId(id);
116 if (CollUtil.isEmpty(lines)) { 134 if (CollUtil.isEmpty(lines)) {
117 throw exception(WM_PRODUCT_PRODUCE_NO_LINE); 135 throw exception(WM_PRODUCT_PRODUCE_NO_LINE);
118 } 136 }
119 137
120 - // 3. 校验每行明细数量之和等于行数量 138 + // 2. 校验每行明细数量之和等于行数量
121 for (MesWmProductProduceLineDO line : lines) { 139 for (MesWmProductProduceLineDO line : lines) {
122 List<MesWmProductProduceDetailDO> details = produceDetailService.getProductProduceDetailListByLineId(line.getId()); 140 List<MesWmProductProduceDetailDO> details = produceDetailService.getProductProduceDetailListByLineId(line.getId());
123 BigDecimal totalDetailQty = CollectionUtils.getSumValue(details, 141 BigDecimal totalDetailQty = CollectionUtils.getSumValue(details,
@@ -127,17 +145,17 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -127,17 +145,17 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
127 } 145 }
128 } 146 }
129 147
130 - // TODO @芋艿:完成入库时更新库存(对齐 ktg-mes 的 storageCoreService.processProductProduce),待库存事务模块就绪后实现 148 + // TODO @AI(from codex):对齐,这里还需要按产出明细执行库存入账,而不是只校验明细并更新单据状态。
131 149
132 - // 4. 更新入库单状态  
133 - produceMapper.updateById(new MesWmProductProduceDO() 150 + // 3. 更新入库单状态
  151 + productProduceMapper.updateById(new MesWmProductProduceDO()
134 .setId(id).setStatus(MesWmProductProduceStatusEnum.FINISHED.getStatus())); 152 .setId(id).setStatus(MesWmProductProduceStatusEnum.FINISHED.getStatus()));
135 } 153 }
136 154
137 @Override 155 @Override
138 @Transactional(rollbackFor = Exception.class) 156 @Transactional(rollbackFor = Exception.class)
139 public void cancelProductProduce(Long id) { 157 public void cancelProductProduce(Long id) {
140 - // 校验存在 158 + // 1. 校验存在
141 MesWmProductProduceDO produce = validateProductProduceExists(id); 159 MesWmProductProduceDO produce = validateProductProduceExists(id);
142 // 已完成和已取消不允许取消 160 // 已完成和已取消不允许取消
143 if (ObjectUtils.equalsAny(produce.getStatus(), 161 if (ObjectUtils.equalsAny(produce.getStatus(),
@@ -146,8 +164,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -146,8 +164,8 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
146 throw exception(WM_PRODUCT_PRODUCE_CANCEL_NOT_ALLOWED); 164 throw exception(WM_PRODUCT_PRODUCE_CANCEL_NOT_ALLOWED);
147 } 165 }
148 166
149 - // 取消  
150 - produceMapper.updateById(new MesWmProductProduceDO() 167 + // 2. 取消
  168 + productProduceMapper.updateById(new MesWmProductProduceDO()
151 .setId(id).setStatus(MesWmProductProduceStatusEnum.CANCELED.getStatus())); 169 .setId(id).setStatus(MesWmProductProduceStatusEnum.CANCELED.getStatus()));
152 } 170 }
153 171
@@ -166,8 +184,93 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -166,8 +184,93 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
166 } 184 }
167 185
168 @Override 186 @Override
  187 + @Transactional(rollbackFor = Exception.class)
  188 + public MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag) {
  189 + // 0. 查询关联的工单(用于获取 clientId 等信息)
  190 + MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());
  191 +
  192 + // 1. 创建产出单头
  193 + MesWmProductProduceDO produce = MesWmProductProduceDO.builder()
  194 + .workOrderId(feedback.getWorkOrderId()).feedbackId(feedback.getId()).taskId(feedback.getTaskId())
  195 + .workstationId(feedback.getWorkstationId()).processId(feedback.getProcessId())
  196 + .produceDate(LocalDateTime.now()).status(MesWmProductProduceStatusEnum.PREPARE.getStatus())
  197 + .build();
  198 + productProduceMapper.insert(produce);
  199 +
  200 + // 2. 获取或生成批次
  201 + MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO()
  202 + .setItemId(feedback.getItemId())
  203 + .setProduceDate(LocalDate.now().atStartOfDay()) // 截断到当天零点,确保同一天的报工生成相同批次号
  204 + .setExpireDate(feedback.getExpireDate())
  205 + .setWorkOrderId(feedback.getWorkOrderId())
  206 + .setClientId(workOrder != null ? workOrder.getClientId() : null)
  207 + .setWorkstationId(feedback.getWorkstationId())
  208 + .setLotNumber(feedback.getLotNumber());
  209 + // TODO @AI(from codex):对齐 批次生成还需要带上工单来源单号(salesOrderCode / coCode 对应 orderSourceCode);
  210 + // 否则启用该维度的批次规则会生成错误批次或直接缺参失败。
  211 + MesWmBatchDO batch = batchService.getOrGenerateBatchCode(batchReqVO);
  212 + Long batchId = batch != null ? batch.getId() : null;
  213 + String batchCode = batch != null ? batch.getCode() : null;
  214 +
  215 + // 3. 根据是否需要检验分支处理
  216 + if (checkFlag) {
  217 + // 3.1 需要检验:创建一条行(质量状态=待检验),不生成明细
  218 + MesWmProductProduceLineDO line = buildProduceLine(produce, feedback, batchId, batchCode,
  219 + feedback.getFeedbackQuantity(), MesWmQualityStatusEnum.PENDING.getStatus());
  220 + produceLineMapper.insert(line);
  221 + // TODO @芋艿:先不生成明细行,等待检验完成时,再根据行的质量状态生成明细行
  222 + } else {
  223 + // 3.2 无需检验:按合格品/不合格品各生成一行 + 明细
  224 + BigDecimal qualifiedQty = ObjectUtil.defaultIfNull(feedback.getQualifiedQuantity(), BigDecimal.ZERO);
  225 + BigDecimal unqualifiedQty = ObjectUtil.defaultIfNull(feedback.getUnqualifiedQuantity(), BigDecimal.ZERO);
  226 + // 3.2.1 不合格品行 + 明细
  227 + if (unqualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
  228 + MesWmProductProduceLineDO unqualifiedLine = buildProduceLine(produce, feedback, batchId, batchCode,
  229 + unqualifiedQty, MesWmQualityStatusEnum.FAIL.getStatus());
  230 + produceLineMapper.insert(unqualifiedLine);
  231 + MesWmProductProduceDetailDO unqualifiedDetail = buildProduceDetail(produce, feedback, batchId, batchCode,
  232 + unqualifiedLine.getId(), unqualifiedQty);
  233 + produceDetailMapper.insert(unqualifiedDetail);
  234 + }
  235 + // 3.2.2 合格品行 + 明细
  236 + if (qualifiedQty.compareTo(BigDecimal.ZERO) > 0) {
  237 + MesWmProductProduceLineDO qualifiedLine = buildProduceLine(produce, feedback, batchId, batchCode,
  238 + qualifiedQty, MesWmQualityStatusEnum.PASS.getStatus());
  239 + produceLineMapper.insert(qualifiedLine);
  240 + MesWmProductProduceDetailDO qualifiedDetail = buildProduceDetail(produce, feedback, batchId, batchCode,
  241 + qualifiedLine.getId(), qualifiedQty);
  242 + produceDetailMapper.insert(qualifiedDetail);
  243 + }
  244 + }
  245 + return produce;
  246 + }
  247 +
  248 + private MesWmProductProduceLineDO buildProduceLine(MesWmProductProduceDO produce, MesProFeedbackDO feedback,
  249 + Long batchId, String batchCode,
  250 + BigDecimal quantity, Integer qualityStatus) {
  251 + return MesWmProductProduceLineDO.builder()
  252 + .produceId(produce.getId()).feedbackId(feedback.getId())
  253 + .itemId(feedback.getItemId()).quantity(quantity)
  254 + .batchId(batchId).batchCode(batchCode)
  255 + .expireDate(feedback.getExpireDate()).lotNumber(feedback.getLotNumber())
  256 + .qualityStatus(qualityStatus)
  257 + .build();
  258 + }
  259 +
  260 + private MesWmProductProduceDetailDO buildProduceDetail(MesWmProductProduceDO produce, MesProFeedbackDO feedback,
  261 + Long batchId, String batchCode,
  262 + Long lineId, BigDecimal quantity) {
  263 + return MesWmProductProduceDetailDO.builder()
  264 + .produceId(produce.getId()).lineId(lineId)
  265 + .itemId(feedback.getItemId()).quantity(quantity)
  266 + .batchId(batchId).batchCode(batchCode)
  267 + // TODO @AI(from codex):对齐 产出明细需要补齐虚拟仓/库区/库位(VIRTUAL_WH/VIRTUAL_WS/VIRTUAL_WA),否则后续库存入账定位不完整。
  268 + .build();
  269 + }
  270 +
  271 + @Override
169 public MesWmProductProduceDO validateProductProduceExists(Long id) { 272 public MesWmProductProduceDO validateProductProduceExists(Long id) {
170 - MesWmProductProduceDO produce = produceMapper.selectById(id); 273 + MesWmProductProduceDO produce = productProduceMapper.selectById(id);
171 if (produce == null) { 274 if (produce == null) {
172 throw exception(WM_PRODUCT_PRODUCE_NOT_EXISTS); 275 throw exception(WM_PRODUCT_PRODUCE_NOT_EXISTS);
173 } 276 }
@@ -179,10 +282,11 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic @@ -179,10 +282,11 @@ public class MesWmProductProduceServiceImpl implements MesWmProductProduceServic
179 */ 282 */
180 private MesWmProductProduceDO validateProductProduceExistsAndPrepare(Long id) { 283 private MesWmProductProduceDO validateProductProduceExistsAndPrepare(Long id) {
181 MesWmProductProduceDO produce = validateProductProduceExists(id); 284 MesWmProductProduceDO produce = validateProductProduceExists(id);
182 - if (ObjUtil.notEqual(MesWmProductProduceStatusEnum.PREPARE.getStatus(), produce.getStatus())) { 285 + if (ObjUtil.notEqual(produce.getStatus(), MesWmProductProduceStatusEnum.PREPARE.getStatus())) {
183 throw exception(WM_PRODUCT_PRODUCE_STATUS_INVALID); 286 throw exception(WM_PRODUCT_PRODUCE_STATUS_INVALID);
184 } 287 }
185 return produce; 288 return produce;
186 } 289 }
187 290
  291 +
188 } 292 }