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 | 470 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 462 insertions(+), 8 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 f45fb1b..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,55 +2,101 @@
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;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
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
@@ -66,34 +112,81 @@
@Resource
private MesProRouteService routeService;
@Resource
+ @Lazy
+ private MesProRouteProcessService routeProcessService;
+ @Resource
+ @Lazy
+ private MesProRouteProductService routeProductService;
+ @Resource
+ @Lazy
+ 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());
- MesMdItemDO item = itemService.validateItemExists(createReqVO.getItemId());
+ fillTeamInfo(createReqVO);
- // 2.1 鏋勫缓浠诲姟 DO锛氳嚜鍔ㄥ~鍏呭鎴蜂俊鎭�
+ // 2. 鑷姩纭畾 itemId锛氫紭鍏堝墠绔紶鍏� 鈫� 宸ヨ壓璺嚎宸ュ簭鐨勪骇鍑轰骇鍝� 鈫� 宸ュ崟鎴愬搧
+ Long itemId = createReqVO.getItemId();
+ if (itemId == null) {
+ MesProRouteProcessDO routeProcess = routeProcessService
+ .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
+ 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());
@@ -107,6 +200,7 @@
if (updateReqVO.getProcessId() != null) {
processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
}
+ fillTeamInfo(updateReqVO);
MesMdItemDO item;
if (updateReqVO.getItemId() != null) {
item = itemService.validateItemExists(updateReqVO.getItemId());
@@ -115,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(","));
}
/**
@@ -135,21 +340,100 @@
return item.getName() + "銆�" + quantity + "銆�" + unitName;
}
+ /**
+ * 纭畾鎺掍骇浣跨敤鐨勫伐鑹鸿矾绾� ID锛氫紭鍏堣姹傛寚瀹� 鈫� 宸ュ崟鍏宠仈 鈫� 浜у搧鍏宠仈
+ */
+ private Long resolveRouteId(Long reqRouteId, MesProWorkOrderDO workOrder) {
+ if (reqRouteId != null) {
+ return reqRouteId;
+ }
+ if (workOrder.getRouteId() != null) {
+ return workOrder.getRouteId();
+ }
+ // 浠庝骇鍝佸叧鑱旂殑宸ヨ壓璺嚎鑾峰彇
+ MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(workOrder.getProductId());
+ if (routeProduct == null) {
+ throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_NOT_EXISTS);
+ }
+ return routeProduct.getRouteId();
+ }
+
+ /**
+ * 鏋勫缓宸ュ簭鍒楄〃锛氫紭鍏堜娇鐢ㄥ伐鍗曞凡淇濆瓨鐨勫伐搴� 鈫� 鍥為��鍒板伐鑹鸿矾绾挎ā鏉垮伐搴�
+ */
+ private List<MesProRouteProcessDO> buildRouteProcessList(Long routeId, Long workOrderId) {
+ // 1. 浼樺厛浣跨敤宸ュ崟宸蹭繚瀛樼殑宸ュ簭
+ List<MesProWorkOrderProcessDO> woProcesses = workOrderProcessService
+ .getWorkOrderProcessListByWorkOrderId(workOrderId);
+ if (CollUtil.isNotEmpty(woProcesses)) {
+ List<MesProRouteProcessDO> result = new java.util.ArrayList<>();
+ for (MesProWorkOrderProcessDO wp : woProcesses) {
+ result.add(new MesProRouteProcessDO()
+ .setRouteId(routeId)
+ .setProcessId(wp.getProcessId())
+ .setSort(wp.getSort())
+ .setKeyFlag(wp.getKeyFlag())
+ .setCheckFlag(wp.getCheckFlag())
+ .setBackflushFlag(wp.getBackflushFlag())
+ .setOutputItemId(wp.getOutputItemId())
+ .setColorCode(wp.getColorCode()));
+ }
+ return result;
+ }
+ // 2. 鍥為��鍒板伐鑹鸿矾绾挎ā鏉垮伐搴�
+ List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
+ if (CollUtil.isEmpty(routeProcesses)) {
+ throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_ENABLE_NO_PROCESS);
+ }
+ 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
@@ -160,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
@@ -237,4 +535,160 @@
taskMapper.updateProducedQuantity(id, incrProducedQuantity, incrQualifyQuantity, incrUnqualifyQuantity);
}
+ @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());
+ // 2. 纭畾宸ヨ壓璺嚎锛氫紭鍏堣姹傛寚瀹� 鈫� 宸ュ崟鍏宠仈 鈫� 浜у搧鍏宠仈
+ Long routeId = resolveRouteId(reqVO.getRouteId(), workOrder);
+ MesProRouteDO route = routeService.validateRouteExists(routeId);
+ // 3. 鑾峰彇宸ュ簭鍒楄〃锛氫紭鍏堝伐鍗曞伐搴� 鈫� 宸ヨ壓璺嚎宸ュ簭
+ List<MesProRouteProcessDO> routeProcesses = buildRouteProcessList(routeId, reqVO.getWorkOrderId());
+ // 鎸夊簭鍙锋帓搴�
+ routeProcesses.sort(java.util.Comparator.comparing(MesProRouteProcessDO::getSort, java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder())));
+ // 4. 鏌ヨ宸ュ簭鍚嶇О
+ java.util.Set<Long> processIds = routeProcesses.stream().map(MesProRouteProcessDO::getProcessId).collect(java.util.stream.Collectors.toSet());
+ java.util.Map<Long, cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO> procMap = processService.getProcessMap(processIds);
+ // 5. 缁勮棰勮
+ MesProTaskAutoGeneratePreviewRespVO vo = new MesProTaskAutoGeneratePreviewRespVO()
+ .setWorkOrderId(workOrder.getId())
+ .setRouteId(routeId)
+ .setRouteName(route.getName());
+ java.util.List<MesProTaskAutoGeneratePreviewRespVO.TaskPreview> taskList = new java.util.ArrayList<>();
+ for (MesProRouteProcessDO rp : routeProcesses) {
+ cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId());
+ taskList.add(new MesProTaskAutoGeneratePreviewRespVO.TaskPreview()
+ .setProcessId(rp.getProcessId())
+ .setProcessName(proc != null ? proc.getName() : null)
+ .setSort(rp.getSort())
+ .setQuantity(workOrder.getQuantity())
+ .setKeyFlag(rp.getKeyFlag()));
+ }
+ vo.setTaskList(taskList);
+ return vo;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public List<Long> autoGenerateTasks(MesProTaskAutoGenerateReqVO reqVO) {
+ scheduleGuard.lock();
+ // 1. 鏍¢獙宸ュ崟宸茬‘璁�
+ MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
+ // 2. 纭畾宸ヨ壓璺嚎锛氫紭鍏堣姹傛寚瀹� 鈫� 宸ュ崟鍏宠仈 鈫� 浜у搧鍏宠仈
+ Long routeId = resolveRouteId(reqVO.getRouteId(), workOrder);
+ routeService.validateRouteExists(routeId);
+ // 3. 鏍¢獙鎺掍骇鏁伴噺
+ workOrderService.validateScheduleQuantity(reqVO.getWorkOrderId(), workOrder.getQuantity());
+ // 4. 鑾峰彇宸ュ簭鍒楄〃锛氫紭鍏堝伐鍗曞伐搴� 鈫� 宸ヨ壓璺嚎宸ュ簭
+ List<MesProRouteProcessDO> routeProcesses = buildRouteProcessList(routeId, reqVO.getWorkOrderId());
+ routeProcesses.sort(java.util.Comparator.comparing(MesProRouteProcessDO::getSort, java.util.Comparator.nullsLast(java.util.Comparator.naturalOrder())));
+ // 5. 鏌ヨ宸ュ簭淇℃伅
+ java.util.Set<Long> processIds = routeProcesses.stream().map(MesProRouteProcessDO::getProcessId).collect(java.util.stream.Collectors.toSet());
+ java.util.Map<Long, cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO> procMap = processService.getProcessMap(processIds);
+ // 6. 涓烘瘡涓伐搴忓垱寤轰换鍔�
+ 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 = 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())
+ .setRouteId(routeId)
+ .setProcessId(rp.getProcessId())
+ .setItemId(itemId)
+ .setQuantity(workOrder.getQuantity())
+ .setName(buildTaskName(item, workOrder.getQuantity()))
+ .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_TASK_CODE.getCode()))
+ .setStatus(MesProTaskStatusEnum.PREPARE.getStatus())
+ .setClientId(workOrder.getClientId())
+ .setColorCode(rp.getColorCode());
+ scheduleGuard.validate(workOrder, task, null);
+ freezeTaskParamConfig(task);
+ taskMapper.insert(task);
+ taskIds.add(task.getId());
+ }
+ // 7. 鏇存柊宸ュ崟宸叉帓浜ф暟閲�
+ workOrderService.updateScheduledQuantity(workOrder.getId(), workOrder.getQuantity());
+ 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