| | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.collection.ListUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.GanttDataRespVO; |
| | | 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.MesProTaskPauseReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskResumeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.client.MesMdClientDO; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | 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.MesMdWorkstationDO; |
| | |
| | | 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.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.service.md.client.MesMdClientService; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | 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.MesMdWorkstationService; |
| | |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.Parameters; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; |
| | | import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService; |
| | | 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.MesProTaskPauseRecordRespVO; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| | | |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管理后台 - MES 生产任务") |
| | | @RestController |
| | | @RequestMapping("/mes/pro/task") |
| | | @Validated |
| | | public class MesProTaskController { |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProBatchScheduleService batchScheduleService; |
| | | |
| | | @PostMapping("/auto-schedule/preview") |
| | | @Operation(summary = "批量一键排产预览") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") |
| | | public CommonResult<cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProBatchSchedulePreviewRespVO> previewBatchSchedule( |
| | | @Valid @RequestBody cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProBatchScheduleReqVO request) { |
| | | return success(batchScheduleService.preview(request)); |
| | | } |
| | | |
| | | @PostMapping("/auto-schedule") |
| | | @Operation(summary = "确认批量一键排产") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") |
| | | public CommonResult<java.util.Map<String, Object>> executeBatchSchedule( |
| | | @Valid @RequestBody cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProBatchScheduleReqVO request) { |
| | | return success(java.util.Map.of("taskIds", batchScheduleService.execute(request))); |
| | | } |
| | | |
| | | @Resource |
| | | private MesProTaskService taskService; |
| | |
| | | @Resource |
| | | private MesMdItemService itemService; |
| | | @Resource |
| | | private MesMdClientService clientService; |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private MesMdUnitMeasureService unitMeasureService; |
| | | @Resource |
| | | private MesProRouteProcessService routeProcessService; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | private MesProFeedbackService feedbackService; |
| | | @Resource |
| | | private MesProTaskPauseRecordMapper pauseRecordMapper; |
| | | @Resource |
| | | private AdminUserApi adminUserApi; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "创建生产任务") |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/calculate-duration") |
| | | @Operation(summary = "计算生产时长", description = "根据工作站绑定设备的生产效率和传送履带,自动计算生产时长(小时)") |
| | | @Parameters({ |
| | | @Parameter(name = "workstationId", description = "工作站编号", required = true), |
| | | @Parameter(name = "quantity", description = "排产数量", required = true) |
| | | }) |
| | | public CommonResult<java.util.Map<String, Object>> calculateDuration( |
| | | @RequestParam("workstationId") Long workstationId, |
| | | @RequestParam("quantity") BigDecimal quantity) { |
| | | Integer duration = taskService.calculateDuration(workstationId, quantity); |
| | | return success(java.util.Map.of("duration", duration)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "删除生产任务") |
| | | @Parameter(name = "id", description = "编号", required = true) |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/pause") |
| | | @Operation(summary = "生产任务停工") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:update')") |
| | | public CommonResult<Boolean> pauseTask(@Valid @RequestBody MesProTaskPauseReqVO pauseReqVO) { |
| | | taskService.pauseTask(pauseReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/resume") |
| | | @Operation(summary = "生产任务复工") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:update')") |
| | | public CommonResult<Boolean> resumeTask(@Valid @RequestBody MesProTaskResumeReqVO resumeReqVO) { |
| | | taskService.resumeTask(resumeReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/auto-generate/preview") |
| | | @Operation(summary = "一键排产预览") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") |
| | | public CommonResult<MesProTaskAutoGeneratePreviewRespVO> previewAutoGenerate( |
| | | @Valid @RequestBody MesProTaskAutoGenerateReqVO reqVO) { |
| | | return success(taskService.previewAutoGenerate(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/auto-generate") |
| | | @Operation(summary = "一键排产执行") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:create')") |
| | | public CommonResult<java.util.Map<String, Object>> autoGenerate( |
| | | @Valid @RequestBody MesProTaskAutoGenerateReqVO reqVO) { |
| | | List<Long> taskIds = taskService.autoGenerateTasks(reqVO); |
| | | return success(java.util.Map.of("taskIds", taskIds)); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获得生产任务") |
| | | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") |
| | | public CommonResult<MesProTaskRespVO> getTask(@RequestParam("id") Long id) { |
| | | MesProTaskDO task = taskService.getTask(id); |
| | | if (task == null) { |
| | |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "获得生产任务分页") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") |
| | | public CommonResult<PageResult<MesProTaskRespVO>> getTaskPage(@Valid MesProTaskPageReqVO pageReqVO) { |
| | | PageResult<MesProTaskDO> pageResult = taskService.getTaskPage(pageReqVO); |
| | | return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal())); |
| | |
| | | |
| | | @GetMapping("/pending-feedback-page") |
| | | @Operation(summary = "获得待报工台账分页", description = "已排产且仍有待报工数量的任务(状态=进行中)") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") |
| | | public CommonResult<PageResult<MesProTaskRespVO>> getPendingFeedbackPage(@Valid MesProTaskPageReqVO pageReqVO) { |
| | | if (Boolean.TRUE.equals(pageReqVO.getOnlyMine())) { |
| | | pageReqVO.setAssigneeUserId(getLoginUserId()); |
| | | } |
| | | PageResult<MesProTaskDO> pageResult = taskService.getPendingFeedbackPage(pageReqVO); |
| | | return success(new PageResult<>(buildTaskRespVOList(pageResult.getList()), pageResult.getTotal())); |
| | | List<MesProTaskRespVO> respVOList = buildTaskRespVOList(pageResult.getList()); |
| | | |
| | | if (CollUtil.isNotEmpty(respVOList)) { |
| | | Set<Long> taskIds = convertSet(respVOList, MesProTaskRespVO::getId); |
| | | List<MesProTaskPauseRecordDO> records = pauseRecordMapper.selectListByTaskIds(taskIds); |
| | | if (CollUtil.isNotEmpty(records)) { |
| | | Set<Long> userIds = new HashSet<>(); |
| | | records.forEach(r -> { |
| | | if (r.getPauseUserId() != null) userIds.add(r.getPauseUserId()); |
| | | if (r.getResumeUserId() != null) userIds.add(r.getResumeUserId()); |
| | | }); |
| | | Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); |
| | | |
| | | Map<Long, List<MesProTaskPauseRecordRespVO>> pauseRecordMap = new HashMap<>(); |
| | | for (MesProTaskPauseRecordDO record : records) { |
| | | MesProTaskPauseRecordRespVO vo = BeanUtils.toBean(record, MesProTaskPauseRecordRespVO.class); |
| | | if (record.getPauseUserId() != null && userMap.containsKey(record.getPauseUserId())) { |
| | | vo.setPauseUserName(userMap.get(record.getPauseUserId()).getNickname()); |
| | | } |
| | | if (record.getResumeUserId() != null && userMap.containsKey(record.getResumeUserId())) { |
| | | vo.setResumeUserName(userMap.get(record.getResumeUserId()).getNickname()); |
| | | } |
| | | pauseRecordMap.computeIfAbsent(record.getTaskId(), k -> new ArrayList<>()).add(vo); |
| | | } |
| | | respVOList.forEach(vo -> vo.setPauseRecords(pauseRecordMap.get(vo.getId()))); |
| | | } |
| | | } |
| | | |
| | | return success(new PageResult<>(respVOList, pageResult.getTotal())); |
| | | } |
| | | |
| | | @GetMapping("/pending-count-by-workstation") |
| | | @Operation(summary = "按工作站获得待报工任务数", description = "工作台选择工作站卡片展示,口径同待报工台账") |
| | | public CommonResult<Map<Long, Long>> getPendingFeedbackCountByWorkstation( |
| | | @RequestParam(value = "onlyMine", required = false) Boolean onlyMine) { |
| | | Long assigneeUserId = Boolean.TRUE.equals(onlyMine) ? getLoginUserId() : null; |
| | | return success(taskService.getPendingFeedbackCountByWorkstation(assigneeUserId)); |
| | | } |
| | | |
| | | @GetMapping("/gantt-list") |
| | | @Operation(summary = "获得甘特图任务列表", description = "后端组装工单=project + 任务=task 列表") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-task:query')") |
| | | public CommonResult<List<GanttDataRespVO>> listGanttTaskList(@Valid MesProWorkOrderPageReqVO reqVO) { |
| | | // 1.1 查询匹配的工单(不分页) |
| | | reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); |
| | |
| | | convertSet(list, MesProTaskDO::getItemId)); |
| | | Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap( |
| | | convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId)); |
| | | Map<Long, MesMdClientDO> clientMap = clientService.getClientMap( |
| | | Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap( |
| | | convertSet(list, MesProTaskDO::getClientId)); |
| | | // 工序的 checkFlag:批量查询后构建 routeId -> processId -> checkFlag 的双层 Map |
| | | Map<Long, AdminUserRespDTO> assigneeUserMap = adminUserApi.getUserMap( |
| | | list.stream().flatMap(task -> parseUserIds(task.getAssigneeUserIds()).stream()) |
| | | .collect(java.util.stream.Collectors.toSet())); |
| | | // 工序的 checkFlag + backflushFlag:工单工序快照优先,工艺路线兜底 |
| | | Set<Long> workOrderIds = convertSet(list, MesProTaskDO::getWorkOrderId); |
| | | Map<Long, Map<Long, Boolean>> workOrderProcessCheckFlagMap = new HashMap<>(); |
| | | Map<Long, Map<Long, Boolean>> workOrderProcessBackflushFlagMap = new HashMap<>(); |
| | | if (CollUtil.isNotEmpty(workOrderIds)) { |
| | | List<MesProWorkOrderProcessDO> allWorkOrderProcesses = |
| | | workOrderProcessService.getWorkOrderProcessListByWorkOrderIds(workOrderIds); |
| | | for (MesProWorkOrderProcessDO wp : allWorkOrderProcesses) { |
| | | workOrderProcessCheckFlagMap |
| | | .computeIfAbsent(wp.getWorkOrderId(), k -> new HashMap<>()) |
| | | .put(wp.getProcessId(), Boolean.TRUE.equals(wp.getCheckFlag())); |
| | | workOrderProcessBackflushFlagMap |
| | | .computeIfAbsent(wp.getWorkOrderId(), k -> new HashMap<>()) |
| | | .put(wp.getProcessId(), Boolean.TRUE.equals(wp.getBackflushFlag())); |
| | | } |
| | | } |
| | | Set<Long> routeIds = convertSet(list, MesProTaskDO::getRouteId); |
| | | Map<Long, Map<Long, Boolean>> routeProcessCheckFlagMap = new HashMap<>(); |
| | | Map<Long, Map<Long, Boolean>> routeProcessBackflushFlagMap = new HashMap<>(); |
| | | if (CollUtil.isNotEmpty(routeIds)) { |
| | | List<MesProRouteProcessDO> allRouteProcesses = routeProcessService.getRouteProcessListByRouteIds(routeIds); |
| | | for (MesProRouteProcessDO rp : allRouteProcesses) { |
| | | routeProcessCheckFlagMap |
| | | .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>()) |
| | | .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getCheckFlag())); |
| | | routeProcessBackflushFlagMap |
| | | .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>()) |
| | | .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getBackflushFlag())); |
| | | } |
| | | } |
| | | // 报工在途量:按任务聚合 feedbackQuantity(草稿/审批中/待检验 = 在途;已完成 = 已审批) |
| | |
| | | BigDecimal inTransit = inTransitMap.getOrDefault(task.getId(), BigDecimal.ZERO); |
| | | vo.setInTransitQuantity(inTransit); |
| | | vo.setReportableQuantity(quantity.subtract(produced).subtract(inTransit).max(BigDecimal.ZERO)); |
| | | List<Long> assigneeUserIds = parseUserIds(task.getAssigneeUserIds()); |
| | | vo.setAssigneeUserIds(assigneeUserIds); |
| | | if (CollUtil.isNotEmpty(assigneeUserIds)) { |
| | | vo.setAssigneeUserNames(assigneeUserIds.stream() |
| | | .map(assigneeUserMap::get).filter(Objects::nonNull) |
| | | .map(AdminUserRespDTO::getNickname) |
| | | .collect(java.util.stream.Collectors.joining("、"))); |
| | | } |
| | | findAndThen(workOrderMap, task.getWorkOrderId(), wo -> |
| | | vo.setWorkOrderCode(wo.getCode()).setWorkOrderName(wo.getName()).setRequestDate(wo.getRequestDate())); |
| | | findAndThen(workstationMap, task.getWorkstationId(), ws -> |
| | |
| | | findAndThen(unitMeasureMap, item.getUnitMeasureId(), unit -> |
| | | vo.setUnitMeasureName(unit.getName())); |
| | | }); |
| | | findAndThen(clientMap, task.getClientId(), c -> |
| | | vo.setClientName(c.getName())); |
| | | findAndThen(routeProcessCheckFlagMap, task.getRouteId(), processCheckMap -> |
| | | findAndThen(processCheckMap, task.getProcessId(), vo::setCheckFlag)); |
| | | findAndThen(customerMap, task.getClientId(), customer -> |
| | | vo.setClientName(customer.getName())); |
| | | Boolean checkFlag = resolveProcessFlag(task, workOrderProcessCheckFlagMap, routeProcessCheckFlagMap); |
| | | if (checkFlag != null) vo.setCheckFlag(checkFlag); |
| | | Boolean backflushFlag = resolveProcessFlag(task, workOrderProcessBackflushFlagMap, routeProcessBackflushFlagMap); |
| | | if (backflushFlag != null) vo.setBackflushFlag(backflushFlag); |
| | | return vo; |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 解析任务工序的标识:工单工序快照优先,工艺路线兜底 |
| | | */ |
| | | private Boolean resolveProcessFlag(MesProTaskDO task, |
| | | Map<Long, Map<Long, Boolean>> workOrderProcessMap, |
| | | Map<Long, Map<Long, Boolean>> routeProcessMap) { |
| | | Map<Long, Boolean> workOrderFlags = workOrderProcessMap.get(task.getWorkOrderId()); |
| | | Boolean flag = workOrderFlags != null ? workOrderFlags.get(task.getProcessId()) : null; |
| | | if (flag != null) { |
| | | return flag; |
| | | } |
| | | Map<Long, Boolean> routeFlags = routeProcessMap.get(task.getRouteId()); |
| | | return routeFlags != null ? routeFlags.get(task.getProcessId()) : null; |
| | | } |
| | | |
| | | /** |
| | | * 解析逗号分隔的用户编号串 |
| | | */ |
| | | private static List<Long> parseUserIds(String csv) { |
| | | if (StrUtil.isBlank(csv)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<Long> userIds = new ArrayList<>(); |
| | | for (String part : csv.split(",")) { |
| | | String trimmed = part.trim(); |
| | | if (trimmed.isEmpty()) { |
| | | continue; |
| | | } |
| | | try { |
| | | userIds.add(Long.valueOf(trimmed)); |
| | | } catch (NumberFormatException ignored) { |
| | | } |
| | | } |
| | | return userIds; |
| | | } |
| | | |
| | | /** |
| | | * 拼接甘特图显示文本,格式:"[产品名][数量][单位]" |
| | | */ |
| | | private String buildGanttText(MesMdItemDO item, BigDecimal quantity, |