From ac2e2b5af3726ee64dc37c3ebf26989a0346f22a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 07 十一月 2026 19:16:05 +0800
Subject: [PATCH] feat(feedback): 优化报工审批流程并增加报工人校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java |  324 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 312 insertions(+), 12 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
index d19426b..c2bba44 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -2,23 +2,35 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.mes.service.pd.processparamtemplate.MesPdProcessParamTemplateService;
+import cn.iocoder.yudao.module.mes.service.pro.feedback.dto.MesTaskParamSnapshot;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGeneratePreviewRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationWorkerDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
+import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationMachineService;
 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
+import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationWorkerService;
 import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteDO;
@@ -39,28 +51,52 @@
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_SCHEDULE_INVALID;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_CANNOT_PAUSE;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_HAS_FEEDBACK;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_PAUSED;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskPauseRecordDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskPauseRecordMapper;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPauseReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskResumeReqVO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 
 /**
  * MES 鐢熶骇浠诲姟 Service 瀹炵幇绫�
  *
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
  */
 @Service
 @Validated
 public class MesProTaskServiceImpl implements MesProTaskService {
+    @Resource
+    private cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProScheduleGuardService scheduleGuard;
+    @Resource
+    private cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper scheduleWorkOrderMapper;
 
     @Resource
     private MesProTaskMapper taskMapper;
+    @Resource
+    private MesPdProcessParamTemplateService paramTemplateService;
 
     @Resource
     private MesProFeedbackMapper feedbackMapper;
+
+    @Resource
+    private MesProTaskPauseRecordMapper taskPauseRecordMapper;
 
     @Resource
     @Lazy
@@ -86,46 +122,71 @@
     private MesProWorkOrderProcessService workOrderProcessService;
     @Resource
     private MesMdAutoCodeRecordService autoCodeRecordService;
+    @Resource
+    private MesMdWorkstationMachineService workstationMachineService;
+    @Resource
+    @Lazy
+    private MesDvMachineryService machineryService;
+    @Resource
+    private MesCalTeamService teamService;
+    @Resource
+    private MesMdWorkstationWorkerService workstationWorkerService;
+    @Resource
+    private AdminUserApi adminUserApi;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createTask(MesProTaskSaveReqVO createReqVO) {
+        scheduleGuard.lock();
         // 1. 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
-        workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
+        var station = workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
+        if (java.util.Objects.equals(workOrder.getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) && !java.util.Objects.equals(station.getProcessId(), createReqVO.getProcessId())) {
+            throw exception(PRO_TASK_SCHEDULE_INVALID, "宸ヤ綔绔欎笌宸ュ簭涓嶅尮閰�");
+        }
         routeService.validateRouteExists(createReqVO.getRouteId());
         processService.validateProcessExistsAndEnable(createReqVO.getProcessId());
+        fillTeamInfo(createReqVO);
 
         // 2. 鑷姩纭畾 itemId锛氫紭鍏堝墠绔紶鍏� 鈫� 宸ヨ壓璺嚎宸ュ簭鐨勪骇鍑轰骇鍝� 鈫� 宸ュ崟鎴愬搧
         Long itemId = createReqVO.getItemId();
         if (itemId == null) {
             MesProRouteProcessDO routeProcess = routeProcessService
                     .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
-            if (routeProcess != null && routeProcess.getOutputItemId() != null) {
-                itemId = routeProcess.getOutputItemId();
-            } else {
-                itemId = workOrder.getProductId();
-            }
+            itemId = resolveTaskItemId(routeProcess, workOrder.getProductId());
         }
         MesMdItemDO item = itemService.validateItemExists(itemId);
 
         // 3.1 鏋勫缓浠诲姟 DO锛氳嚜鍔ㄥ~鍏呭鎴蜂俊鎭�
+        List<Long> assigneeUserIds = createReqVO.getAssigneeUserIds();
+        if (CollUtil.isEmpty(assigneeUserIds)) {
+            // 鏈寚瀹氭姤宸ヤ汉鏃堕粯璁ゅ彇宸ヤ綔绔欓厤缃殑浜哄憳锛岄伩鍏嶄竴閿帓浜х敓鎴愮殑浠诲姟鏃犱汉鍙姤
+            assigneeUserIds = getWorkstationWorkerUserIds(createReqVO.getWorkstationId());
+        }
         MesProTaskDO task = BeanUtils.toBean(createReqVO, MesProTaskDO.class)
                 .setItemId(itemId) // 纭繚浣跨敤璁$畻鍚庣殑 itemId
                 .setName(buildTaskName(item, createReqVO.getQuantity()))
                 .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_TASK_CODE.getCode()))
+                .setAssigneeUserIds(joinUserIds(assigneeUserIds))
                 .setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
         if (task.getClientId() == null && workOrder.getClientId() != null) {
             task.setClientId(workOrder.getClientId());
         }
         // 2.2 鎻掑叆
+        scheduleGuard.validate(workOrder, task, null);
+        freezeTaskParamConfig(task);
         taskMapper.insert(task);
+        synchronizeScheduledQuantity(workOrder, task.getRouteId());
         return task.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateTask(MesProTaskSaveReqVO updateReqVO) {
-        // 1.1 鏍¢獙瀛樺湪
-        MesProTaskDO task = validateTaskExists(updateReqVO.getId());
+        scheduleGuard.lock();
+        MesProTaskDO task = taskMapper.selectOne(new LambdaQueryWrapperX<MesProTaskDO>()
+                .eq(MesProTaskDO::getId, updateReqVO.getId()).last("FOR UPDATE"));
+        if (task == null) throw exception(PRO_TASK_NOT_EXISTS);
         // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
         if (updateReqVO.getWorkOrderId() != null) {
             workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId());
@@ -139,6 +200,7 @@
         if (updateReqVO.getProcessId() != null) {
             processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
         }
+        fillTeamInfo(updateReqVO);
         MesMdItemDO item;
         if (updateReqVO.getItemId() != null) {
             item = itemService.validateItemExists(updateReqVO.getItemId());
@@ -147,10 +209,121 @@
         }
         BigDecimal quantity = updateReqVO.getQuantity() != null ? updateReqVO.getQuantity() : task.getQuantity();
 
+        MesProTaskDO candidate = BeanUtils.toBean(task, MesProTaskDO.class)
+                .setWorkOrderId(updateReqVO.getWorkOrderId() != null ? updateReqVO.getWorkOrderId() : task.getWorkOrderId())
+                .setRouteId(updateReqVO.getRouteId() != null ? updateReqVO.getRouteId() : task.getRouteId())
+                .setProcessId(updateReqVO.getProcessId() != null ? updateReqVO.getProcessId() : task.getProcessId())
+                .setWorkstationId(updateReqVO.getWorkstationId() != null ? updateReqVO.getWorkstationId() : task.getWorkstationId())
+                .setQuantity(quantity)
+                .setStartTime(updateReqVO.getStartTime() != null ? updateReqVO.getStartTime() : task.getStartTime())
+                .setEndTime(updateReqVO.getEndTime() != null ? updateReqVO.getEndTime() : task.getEndTime());
+        var station = workstationService.validateWorkstationExists(candidate.getWorkstationId());
+        if (java.util.Objects.equals(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()).getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) && !java.util.Objects.equals(station.getProcessId(), candidate.getProcessId())) {
+            throw exception(PRO_TASK_SCHEDULE_INVALID, "宸ヤ綔绔欎笌宸ュ簭涓嶅尮閰�");
+        }
+        scheduleGuard.validate(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()), candidate, task.getId());
         // 2. 鏇存柊
         MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class)
                 .setName(buildTaskName(item, quantity));
+        // 鎶ュ伐浜轰负 null 琛ㄧず涓嶄慨鏀癸紱绌洪泦鍚堣〃绀烘竻绌洪檺鍒讹紙鎵�鏈変汉鍙姤宸ワ級
+        if (updateReqVO.getAssigneeUserIds() != null) {
+            updateObj.setAssigneeUserIds(joinUserIds(updateReqVO.getAssigneeUserIds()));
+        }
+        if (updateReqVO.getProcessId() != null && !updateReqVO.getProcessId().equals(task.getProcessId())) {
+            if (feedbackMapper.selectCountByTaskId(task.getId()) > 0) throw exception(PRO_TASK_HAS_FEEDBACK);
+            freezeTaskParamConfig(updateObj);
+        }
         taskMapper.updateById(updateObj);
+        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()), candidate.getRouteId());
+        if (!java.util.Objects.equals(task.getWorkOrderId(), candidate.getWorkOrderId())) synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
+    }
+
+    private void synchronizeScheduledQuantity(MesProWorkOrderDO order, Long routeId) {
+        if (!java.util.Objects.equals(order.getType(), cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum.SELF.getType()) || order.getQuantity() == null || routeId == null) return;
+        List<MesProRouteProcessDO> required = buildRouteProcessList(routeId, order.getId());
+        List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(order.getId());
+        BigDecimal scheduled = required.isEmpty() ? BigDecimal.ZERO : order.getQuantity();
+        for (var process : required) {
+            BigDecimal amount = tasks.stream().filter(task -> java.util.Objects.equals(task.getProcessId(), process.getProcessId())
+                            && !java.util.Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()))
+                    .map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity()).reduce(BigDecimal.ZERO, BigDecimal::add);
+            scheduled = scheduled.min(amount);
+        }
+        scheduleWorkOrderMapper.updateById(new MesProWorkOrderDO().setId(order.getId()).setQuantityScheduled(scheduled));
+    }
+
+    /** 浠庡伐搴忓拰妯℃澘澶嶅埗瀹氫箟锛屽悗缁ā鏉跨淮鎶や笉鏀瑰彉宸叉帓浜т换鍔°�� */
+    private void freezeTaskParamConfig(MesProTaskDO task) {
+        var process = processService.getProcess(task.getProcessId());
+        if (process == null) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_NOT_EXISTS);
+        int mode = process.getParamCollectMode() == null ? 0 : process.getParamCollectMode();
+        task.setParamCollectMode(mode);
+        task.setParamTemplateSnapshot("");
+        task.setParamSharedSnapshot("");
+        if (mode == 0) return;
+        if ((mode != 1 && mode != 2) || process.getParamTemplateId() == null)
+            throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_PARAM_CONFIG_INVALID);
+        var template = paramTemplateService.validateProcessParamTemplateExists(process.getParamTemplateId());
+        var details = paramTemplateService.getProcessParamTemplateDetailListByTemplateId(template.getId());
+        if (CollUtil.isEmpty(details)) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_PARAM_RECORD_TEMPLATE_NO_DETAIL);
+        MesTaskParamSnapshot snapshot = new MesTaskParamSnapshot();
+        snapshot.setTemplateId(template.getId());
+        snapshot.setTemplateCode(template.getTemplateCode());
+        snapshot.setTemplateName(template.getTemplateName());
+        List<MesTaskParamSnapshot.Detail> frozenDetails = BeanUtils.toBean(details, MesTaskParamSnapshot.Detail.class);
+        for (MesTaskParamSnapshot.Detail detail : frozenDetails) {
+            if (detail.getUnitMeasureId() != null) {
+                var unit = unitMeasureService.getUnitMeasure(detail.getUnitMeasureId());
+                if (unit != null) detail.setUnitMeasureName(unit.getName());
+            }
+        }
+        snapshot.setDetails(frozenDetails);
+        task.setParamTemplateSnapshot(JsonUtils.toJsonString(snapshot));
+    }
+
+    /**
+     * 鏍¢獙鐝粍瀛樺湪骞跺洖濉彮缁勫悕绉�
+     */
+    private void fillTeamInfo(MesProTaskSaveReqVO reqVO) {
+        if (reqVO.getTeamId() == null) {
+            return;
+        }
+        MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId());
+        if (team != null) {
+            reqVO.setTeamName(team.getName());
+        }
+    }
+
+    /**
+     * 鑾峰緱宸ヤ綔绔欓厤缃矖浣嶅搴旂殑鍛樺伐缂栧彿
+     */
+    private List<Long> getWorkstationWorkerUserIds(Long workstationId) {
+        if (workstationId == null) {
+            return Collections.emptyList();
+        }
+        List<MesMdWorkstationWorkerDO> workers = workstationWorkerService
+                .getWorkstationWorkerListByWorkstationId(workstationId);
+        if (CollUtil.isEmpty(workers)) {
+            return Collections.emptyList();
+        }
+        Set<Long> postIds = convertSet(workers, MesMdWorkstationWorkerDO::getPostId);
+        if (CollUtil.isEmpty(postIds)) {
+            return Collections.emptyList();
+        }
+        return convertList(adminUserApi.getUserListByPostIds(postIds), AdminUserRespDTO::getId);
+    }
+
+    /**
+     * 灏嗘姤宸ヤ汉鐢ㄦ埛缂栧彿鍒楄〃鎷兼帴涓洪�楀彿鍒嗛殧瀛楃涓�
+     *
+     * @return null 琛ㄧず涓嶄慨鏀�/涓嶉檺鍒讹紱绌哄瓧绗︿覆琛ㄧず娓呯┖闄愬埗
+     */
+    private String joinUserIds(List<Long> userIds) {
+        if (userIds == null) {
+            return null;
+        }
+        return userIds.stream().filter(java.util.Objects::nonNull).distinct()
+                .map(String::valueOf).collect(java.util.stream.Collectors.joining(","));
     }
 
     /**
@@ -215,21 +388,52 @@
         return routeProcesses;
     }
 
+    /**
+     * 浠诲姟浜у嚭鐗╂枡锛氫紭鍏堝彇宸ュ簭鍦ㄥ綋鍓嶄骇鍝佷笅鐨勪骇鍑猴紙鎸夊綊灞炶繃婊わ級锛屾病鏈変骇鍑烘椂鍙栧伐鍗曟垚鍝�
+     */
+    private Long resolveTaskItemId(MesProRouteProcessDO routeProcess, Long productId) {
+        if (routeProcess == null) {
+            return productId;
+        }
+        if (routeProcess.getId() != null) {
+            // 宸ヨ壓璺嚎妯℃澘锛氭寜浜у嚭褰掑睘杩囨护鍑哄綋鍓嶄骇鍝佺殑浜у嚭
+            List<Long> outputIds = routeProcessService
+                    .getOutputItemIdsByRouteProcessId(routeProcess.getId(), productId);
+            if (outputIds != null) {
+                // 鏈夊綊灞為厤缃細璇ヤ骇鍝佹棤浜у嚭鏃跺彇宸ュ崟鎴愬搧锛沶ull 琛ㄧず瀛橀噺鏃犲綊灞炴暟鎹紝缁х画璧拌仛鍚堝厹搴�
+                return CollUtil.isNotEmpty(outputIds) ? outputIds.get(0) : productId;
+            }
+        }
+        // 宸ュ崟宸ュ簭蹇収 / 瀛橀噺鏃犲綊灞炴暟鎹細浜у嚭宸叉寜浜у搧杩囨护锛屽彇绗竴涓�
+        String outputItemId = routeProcess.getOutputItemId();
+        if (outputItemId != null && !outputItemId.isBlank()) {
+            return Long.valueOf(outputItemId.split(",")[0].trim());
+        }
+        return productId;
+    }
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteTask(Long id) {
-        // 1. 鏍¢獙瀛樺湪涓旈潪缁堟��
-        validateTaskNotFinished(id);
+        scheduleGuard.lock();
+        MesProTaskDO task = validateTaskNotFinished(id);
         // 2. 鏍¢獙浠诲姟涓嬫棤鎶ュ伐璁板綍锛岄伩鍏嶅垹闄ゅ悗鎶ュ伐杩芥函鏂摼
         if (feedbackMapper.selectCountByTaskId(id) > 0) {
             throw exception(PRO_TASK_HAS_FEEDBACK);
         }
         // 3. 鍒犻櫎
         taskMapper.deleteById(id);
+        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
     }
 
     @Override
     public MesProTaskDO getTask(Long id) {
         return taskMapper.selectById(id);
+    }
+
+    @Override
+    public MesProTaskDO getTaskByCode(String code) {
+        return taskMapper.selectByCode(code);
     }
 
     @Override
@@ -240,6 +444,20 @@
     @Override
     public PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO) {
         return taskMapper.selectPendingFeedbackPage(pageReqVO);
+    }
+
+    @Override
+    public Map<Long, Long> getPendingFeedbackCountByWorkstation(Long assigneeUserId) {
+        List<Map<String, Object>> rows = taskMapper.selectPendingFeedbackCountByWorkstation(assigneeUserId);
+        Map<Long, Long> result = new HashMap<>(rows.size());
+        for (Map<String, Object> row : rows) {
+            Object workstationId = row.get("workstation_id");
+            Object count = row.get("pending_count");
+            if (workstationId instanceof Number && count instanceof Number) {
+                result.put(((Number) workstationId).longValue(), ((Number) count).longValue());
+            }
+        }
+        return result;
     }
 
     @Override
@@ -318,6 +536,33 @@
     }
 
     @Override
+    public Integer calculateDuration(Long workstationId, BigDecimal quantity) {
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return 0;
+        }
+        // 1. 鏌ヨ宸ヤ綔绔欑粦瀹氱殑璁惧
+        List<MesMdWorkstationMachineDO> machines = workstationMachineService
+                .getWorkstationMachineListByWorkstationId(workstationId);
+        if (CollUtil.isEmpty(machines)) {
+            return 0;
+        }
+        // 2. 鑾峰彇璁惧璇︽儏锛岃绠楁�绘晥鐜�
+        Set<Long> machineryIds = convertSet(machines, MesMdWorkstationMachineDO::getMachineryId);
+        List<MesDvMachineryDO> machineryList = machineryService.getMachineryList(machineryIds);
+        BigDecimal totalEfficiency = BigDecimal.ZERO;
+        for (MesDvMachineryDO m : machineryList) {
+            BigDecimal efficiency = m.getProductionEfficiency() != null ? m.getProductionEfficiency() : BigDecimal.ZERO;
+            Integer belt = m.getConveyorBelt() != null && m.getConveyorBelt() > 0 ? m.getConveyorBelt() : 1;
+            totalEfficiency = totalEfficiency.add(efficiency.multiply(BigDecimal.valueOf(belt)));
+        }
+        if (totalEfficiency.compareTo(BigDecimal.ZERO) <= 0) {
+            return 0;
+        }
+        // 3. 鐢熶骇鏃堕暱锛堝皬鏃讹級 = ceil(鎺掍骇鏁伴噺 / 鎬绘晥鐜�)锛屾晥鐜囧崟浣嶄负姣忓皬鏃朵骇閲�
+        return quantity.divide(totalEfficiency, 0, java.math.RoundingMode.CEILING).intValue();
+    }
+
+    @Override
     public MesProTaskAutoGeneratePreviewRespVO previewAutoGenerate(MesProTaskAutoGenerateReqVO reqVO) {
         // 1. 鏍¢獙宸ュ崟宸茬‘璁�
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
@@ -353,6 +598,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public List<Long> autoGenerateTasks(MesProTaskAutoGenerateReqVO reqVO) {
+        scheduleGuard.lock();
         // 1. 鏍¢獙宸ュ崟宸茬‘璁�
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
         // 2. 纭畾宸ヨ壓璺嚎锛氫紭鍏堣姹傛寚瀹� 鈫� 宸ュ崟鍏宠仈 鈫� 浜у搧鍏宠仈
@@ -370,7 +616,7 @@
         List<Long> taskIds = new java.util.ArrayList<>();
         for (MesProRouteProcessDO rp : routeProcesses) {
             cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId());
-            Long itemId = rp.getOutputItemId() != null ? rp.getOutputItemId() : workOrder.getProductId();
+            Long itemId = resolveTaskItemId(rp, workOrder.getProductId());
             cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO item = itemService.validateItemExists(itemId);
             MesProTaskDO task = new MesProTaskDO()
                     .setWorkOrderId(workOrder.getId())
@@ -383,6 +629,8 @@
                     .setStatus(MesProTaskStatusEnum.PREPARE.getStatus())
                     .setClientId(workOrder.getClientId())
                     .setColorCode(rp.getColorCode());
+            scheduleGuard.validate(workOrder, task, null);
+            freezeTaskParamConfig(task);
             taskMapper.insert(task);
             taskIds.add(task.getId());
         }
@@ -391,4 +639,56 @@
         return taskIds;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void pauseTask(MesProTaskPauseReqVO pauseReqVO) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪
+        MesProTaskDO task = validateTaskExists(pauseReqVO.getId());
+        // 2. 鏍¢獙鐘舵�佹槸鍚﹀厑璁稿仠宸ワ紙濡傚繀椤绘槸 鐢熶骇涓� 鎴� 閮ㄥ垎瀹屾垚锛�
+        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PRODUCING.getStatus(),
+                MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) {
+            throw exception(PRO_TASK_CANNOT_PAUSE);
+        }
+        
+        // 3. 淇敼浠诲姟鐘舵�佷负宸插仠宸�
+        taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(MesProTaskStatusEnum.PAUSED.getStatus()));
+
+        // 4. 鍒涘缓鍋滃伐璁板綍
+        MesProTaskPauseRecordDO pauseRecord = new MesProTaskPauseRecordDO()
+                .setTaskId(task.getId())
+                .setWorkOrderId(task.getWorkOrderId())
+                .setWorkstationId(task.getWorkstationId())
+                .setReason(pauseReqVO.getReason())
+                .setRemark(pauseReqVO.getRemark())
+                .setPauseTime(LocalDateTime.now())
+                .setPauseUserId(SecurityFrameworkUtils.getLoginUserId());
+        taskPauseRecordMapper.insert(pauseRecord);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void resumeTask(MesProTaskResumeReqVO resumeReqVO) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪
+        MesProTaskDO task = validateTaskExists(resumeReqVO.getId());
+        // 2. 鏍¢獙鐘舵�佸繀椤绘槸宸插仠宸�
+        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PAUSED.getStatus())) {
+            throw exception(PRO_TASK_NOT_PAUSED);
+        }
+
+        // 3. 鎭㈠浠诲姟鐘舵��
+        Integer resumeStatus = task.getProducedQuantity() != null
+                && task.getProducedQuantity().signum() > 0
+                ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus()
+                : MesProTaskStatusEnum.PRODUCING.getStatus();
+        taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus));
+
+        // 4. 鏇存柊鏈�鏂扮殑涓�鏉″仠宸ヨ褰�
+        MesProTaskPauseRecordDO pauseRecord = taskPauseRecordMapper.selectLastUnresumedByTaskId(task.getId());
+        if (pauseRecord != null) {
+            pauseRecord.setResumeTime(LocalDateTime.now());
+            pauseRecord.setResumeUserId(SecurityFrameworkUtils.getLoginUserId());
+            taskPauseRecordMapper.updateById(pauseRecord);
+        }
+    }
+
 }

--
Gitblit v1.9.3