package com.ruoyi.system.controller; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaMode; import cn.hutool.json.JSONUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.metadata.WriteSheet; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.config.DrawingFtpProperties; import com.ruoyi.common.config.DingTalkProperties; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.PageQuery; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.validate.AddGroup; import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.dingding.util.DingUtil; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.excel.DefaultExcelListener; import com.ruoyi.common.excel.ExcelResult; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.helper.LoginHelper; import com.ruoyi.common.utils.FtpUtil; import com.ruoyi.common.utils.KingdeeRouteDepartmentHelper; import com.ruoyi.common.utils.JsyyOrderNo; import com.ruoyi.common.utils.ProjectStatusLock; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.SmbUtil; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.constant.DingTodoSceneCodes; import com.ruoyi.system.domain.*; import com.ruoyi.system.domain.bo.ProcessRouteBo; import com.ruoyi.system.domain.dto.*; import com.ruoyi.system.domain.vo.PcRigidChainVo; import com.ruoyi.system.domain.vo.PlannedProcessVo; import com.ruoyi.system.domain.vo.ProcessRouteVo; import com.ruoyi.system.domain.vo.ProductionOrderVo; import com.ruoyi.system.jdmain.rouplan.Model; import com.ruoyi.system.mapper.BomDetailsMapper; import com.ruoyi.system.mapper.MaterialBomMapper; import com.ruoyi.system.mapper.ProcessOrderProMapper; import com.ruoyi.system.mapper.ProcessRouteMapper; import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.service.*; import com.ruoyi.system.service.support.OperationPlanningWorkshopUpdateService; import com.ruoyi.system.service.support.OperationReportWorkshopUpdateService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import java.io.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; /** * 工艺路线 * * @date 2024-10-09 */ @Validated @RequiredArgsConstructor @RestController @RequestMapping("/system/route") @Slf4j public class ProcessRouteController extends BaseController { private final ProcessRouteMapper baseMapper; private final IProcessRouteService iProcessRouteService; private final OperationPlanningWorkshopUpdateService operationPlanningWorkshopUpdateService; private final OperationReportWorkshopUpdateService operationReportWorkshopUpdateService; private final IBomDetailsService iBomDetailsService; private final ProcessOrderProMapper proMapper; private final DrawingFtpProperties drawingFtpProperties; private final IImMaterialService materialService; @Autowired BomDetailsMapper bomDetailsMapper; @Autowired MaterialBomMapper materialBomMapper; @Autowired private DingTalkProperties dingTalkProperties; @Autowired private ISysOssService iSysOssService; @Autowired private IProcessOrderProService processOrderProService; /** * 计划表落盘 + 按 plan-user-id 推送(与 PLM 导出共用) */ private final ProcessPlanExcelPushService processPlanExcelPushService; private final IDrawingRouteOperLogService drawingRouteOperLogService; private final IDingTodoDispatchService dingTodoDispatchService; private final DrawingFileQueryService drawingFileQueryService; @Autowired private ISysUserService userService; @Autowired private com.ruoyi.common.dingding.robot.RobotGroupService robotGroupService; @Autowired private com.ruoyi.common.dingding.media.MediaService mediaService; private Long generateUniqueParentId(Long originalId) { return originalId + 1000; } @Log(title = "查看BOM上传状态") @SaCheckPermission("system:route:viewGetBomUploadStatus") @PostMapping("/viewGetBomUploadStatus") public R viewGetBomUploadStatus(@RequestParam String rooteProdet) { return iProcessRouteService.viewGetBomUploadStatus(rooteProdet); } /** * 主动更新可用库存 */ @SaCheckPermission("system:route:list") @GetMapping("/list2") public TableDataInfo list2(ProcessRouteBo bo, PageQuery pageQuery) { try { // 查询工艺路线数据 TableDataInfo processRouteVoTableDataInfo = iProcessRouteService.queryPageList2(bo, pageQuery); // 用于存储父子关系的Map,key为"物料编码_物料名称",值为父级ProcessRouteVo Map routeMap = new HashMap<>(); // 存储顶级列表 List topLevelList = new ArrayList<>(); // 获取查询返回的行数据 List rows = processRouteVoTableDataInfo.getRows(); // 遍历所有工艺路线记录 for (ProcessRouteVo processRouteVo : rows) { // 组合父级键值,物料编码 + 物料名称 String parentCodeAndName = String.format("%s_%s", processRouteVo.getMaterialCode(), processRouteVo.getMaterialName()); // 查找是否已存在父节点 ProcessRouteVo parent = routeMap.get(parentCodeAndName); if (parent == null) { // 如果父节点不存在,创建并初始化 parent = new ProcessRouteVo(); parent.setId(generateUniqueParentId(processRouteVo.getId())); parent.setRouteDescription(processRouteVo.getRouteDescription()); // parent.setParentId((Long.valueOf(generateUniqueParentId(processRouteVo.getId())))); parent.setMaterialCode(processRouteVo.getMaterialCode()); parent.setMaterialName(processRouteVo.getMaterialName()); parent.setMaterial(processRouteVo.getMaterial()); parent.setDiscWeight(processRouteVo.getDiscWeight()); parent.setFirstBatchQuantity(processRouteVo.getFirstBatchQuantity()); // 初始化子节点列表 parent.setChildren(new ArrayList<>()); // 将父节点放入Map中 routeMap.put(parentCodeAndName, parent); // 将父节点加入顶级列表 topLevelList.add(parent); } // 创建子节点,并将其添加到父节点的子集 ProcessRouteVo child = createChildVo(processRouteVo, parent.getId(), parent.getMaterialCode(), parent.getMaterialName()); parent.getChildren().add(child); } // 设置返回的rows为顶级节点列表 processRouteVoTableDataInfo.setRows(topLevelList); // 返回构建后的数据 return processRouteVoTableDataInfo; } catch (Exception e) { e.printStackTrace(); return new TableDataInfo<>(); } } /** * 查询工艺路线列表。项目令号「查看工艺」只分配 processlist 时也要能拉数。 */ @SaCheckPermission(value = {"system:route:list", "system:orderPro:processlist"}, mode = SaMode.OR) @GetMapping("/list") public TableDataInfo list(ProcessRouteBo bo, PageQuery pageQuery) { try { // 查询工艺路线数据 TableDataInfo processRouteVoTableDataInfo = iProcessRouteService.queryPageList(bo, pageQuery); // 用于存储父子关系的Map,key为"物料编码_物料名称",值为父级ProcessRouteVo Map routeMap = new HashMap<>(); // 存储顶级列表 List topLevelList = new ArrayList<>(); // 获取查询返回的行数据 List rows = processRouteVoTableDataInfo.getRows(); // 遍历所有工艺路线记录 for (ProcessRouteVo processRouteVo : rows) { // 组合父级键值,物料编码 + 物料名称 String parentCodeAndName = String.format("%s_%s", processRouteVo.getMaterialCode(), processRouteVo.getMaterialName()); // 查找是否已存在父节点 ProcessRouteVo parent = routeMap.get(parentCodeAndName); if (parent == null) { // 如果父节点不存在,创建并初始化 parent = new ProcessRouteVo(); parent.setId(generateUniqueParentId(processRouteVo.getId())); parent.setRouteDescription(processRouteVo.getRouteDescription()); // parent.setParentId((Long.valueOf(generateUniqueParentId(processRouteVo.getId())))); parent.setMaterialCode(processRouteVo.getMaterialCode()); parent.setMaterialName(processRouteVo.getMaterialName()); parent.setMaterial(processRouteVo.getMaterial()); parent.setDiscWeight(processRouteVo.getDiscWeight()); parent.setBatchQuantity(processRouteVo.getBatchQuantity()); parent.setFirstBatchQuantity(processRouteVo.getFirstBatchQuantity()); // 初始化子节点列表 parent.setChildren(new ArrayList<>()); // 将父节点放入Map中 routeMap.put(parentCodeAndName, parent); // 将父节点加入顶级列表 topLevelList.add(parent); } // 创建子节点,并将其添加到父节点的子集 ProcessRouteVo child = createChildVo(processRouteVo, parent.getId(), parent.getMaterialCode(), parent.getMaterialName()); parent.getChildren().add(child); } // 设置返回的rows为顶级节点列表 processRouteVoTableDataInfo.setRows(topLevelList); // 返回构建后的数据 return processRouteVoTableDataInfo; } catch (Exception e) { e.printStackTrace(); return new TableDataInfo<>(); } } /** * 创建子节点的辅助方法 * * @param processRouteVo 原始数据 * @return ProcessRouteVo 子节点 */ private ProcessRouteVo createChildVo(ProcessRouteVo processRouteVo, Long parentId, String materialCode, String materialName) { ProcessRouteVo child = new ProcessRouteVo(); child.setId(processRouteVo.getId()); child.setParentId(parentId); child.setProcessNo(processRouteVo.getProcessNo()); child.setWorkCenter(processRouteVo.getWorkCenter()); child.setProcessName(processRouteVo.getProcessName()); child.setProcessDescription(processRouteVo.getProcessDescription()); child.setProcessControl(processRouteVo.getProcessControl()); child.setActivityDuration(processRouteVo.getActivityDuration()); child.setActivityUnit(processRouteVo.getActivityUnit()); child.setUnitQuantity(processRouteVo.getUnitQuantity()); child.setPlanStartTime(processRouteVo.getPlanStartTime()); child.setPlanEndTime(processRouteVo.getPlanEndTime()); child.setXuStartTime(processRouteVo.getXuStartTime()); child.setXuEndTime(processRouteVo.getXuEndTime()); return child; } /** * 导出工艺路线列表 */ @SaCheckPermission("system:route:export2") @Log(title = "工艺路线", businessType = BusinessType.EXPORT) @PostMapping("/export2") public void export2(ProcessRouteBo bo, HttpServletResponse response) { log.info("获取项目生产数据表:{}", bo); List list = iProcessRouteService.queryList(bo); List bomlist = JdUtil.getPRD_PPBOM(bo.getRouteDescription()); //过滤bomlist 只要009开头的标准件 bomlist = bomlist.stream().filter(bom -> bom.getMaterialCode().startsWith("009")).collect(Collectors.toList()); if (list.isEmpty() && bomlist.isEmpty()) { throw new ServiceException("没有数据"); } try { // 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("工艺路线", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); ServletOutputStream os = response.getOutputStream(); // 创建ExcelWriter try (com.alibaba.excel.ExcelWriter excelWriter = EasyExcel.write(os).autoCloseStream(false) // 防止自动关闭流 .build()) { // 写入工艺路线sheet if (!list.isEmpty()) { WriteSheet writeSheet1 = EasyExcel.writerSheet(0, "工艺路线").head(ProcessRoute.class).build(); excelWriter.write(list, writeSheet1); } // 写入标准件清单sheet if (!bomlist.isEmpty()) { WriteSheet writeSheet2 = EasyExcel.writerSheet(1, "标准件清单").head(MaterialUsageDTO2.class).build(); excelWriter.write(bomlist, writeSheet2); } } } catch (Exception e) { throw new RuntimeException("导出Excel异常: " + e.getMessage(), e); } } @SaCheckPermission("system:route:export") @Log(title = "工艺路线", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(ProcessRouteBo bo, HttpServletResponse response) { log.info("获取项目生产数据表:{}", bo); // 下载Excel模板 SmbUtil.downloadExcelFiles(bo.getRouteDescription()); // 构建Excel文件路径 String ExcelName = "D:\\file\\" + bo.getRouteDescription() + "汇总表.xlsx"; File file = new File(ExcelName); if (!file.exists()) { throw new ServiceException("文件不存在,请确认路径是否正确"); } //读取file的所有的物料编码 try { if (file.exists()) { // 读取Excel的sheet6 从第三行开始 读取到第4列 无数据的跳过 DefaultExcelListener listener = new DefaultExcelListener<>(true); EasyExcel.read(ExcelName, ProcessRoute.class, listener).sheet(6).headRowNumber(3).doRead(); } } catch (Exception e) { throw new RuntimeException(e); } // 查询要写入的数据是否 List list = iProcessRouteService.queryList(bo); try { // 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("工艺路线", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); ServletOutputStream os = response.getOutputStream(); // 使用 EasyExcel 写入已有Excel的第七个sheet(index=6) try (ExcelWriter excelWriter = EasyExcel.write(os).withTemplate(file) // 指定模板文件 .autoCloseStream(true).build()) { // 写入到第七个 sheet(index=6),并命名为“工艺路线” WriteSheet writeSheet = EasyExcel.writerSheet("已有工艺路线").head(ProcessRoute.class).needHead(true).build(); excelWriter.write(list, writeSheet); } os.flush(); } catch (Exception e) { log.error("导出Excel异常", e); throw new RuntimeException("导出Excel异常: " + e.getMessage(), e); } } /** * 获取工艺路线详细信息 * * @param id 主键 */ @SaCheckPermission("system:route:query") @GetMapping("/{id}") public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) { return R.ok(iProcessRouteService.queryById(id)); } /** * 新增工艺路线 */ @SaCheckPermission("system:route:add") @Log(title = "工艺路线", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping() public R add(@Validated(AddGroup.class) @RequestBody ProcessRouteBo bo) { return toAjax(iProcessRouteService.insertByBo(bo)); } /** * 导出 * 修改工艺路线 */ @SaCheckPermission("system:route:edit") @Log(title = "工艺路线", businessType = BusinessType.UPDATE) @RepeatSubmit() @PutMapping() public R edit(@Validated(EditGroup.class) @RequestBody ProcessRouteBo bo) { return toAjax(iProcessRouteService.updateByBo(bo)); } /** * 删除工艺路线 * * @param ids 主键串 */ @SaCheckPermission("system:route:remove") @Log(title = "工艺路线", businessType = BusinessType.DELETE) @DeleteMapping("/{ids}") public R remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) { return toAjax(iProcessRouteService.deleteWithValidByIds(Arrays.asList(ids), true)); } @Log(title = "生产工艺计划表导入", businessType = BusinessType.IMPORT) @SaCheckPermission("system:route:importRoute") @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importData(@RequestParam("file") MultipartFile file, @RequestParam(value = "productionOrderNo", required = false) String expectedOrderNo) throws Exception { String originalFilename = file.getOriginalFilename(); log.info("读取文件名: " + originalFilename); // 读取工艺部分sheet ExcelResult result = ExcelUtil.importExcelSheet6(file.getInputStream(), ProcessRouteVo.class, true); List list1 = result.getList(); // 读取总装部分sheet ExcelResult result1 = ExcelUtil.importExcelSheet1(file.getInputStream(), ProductionOrderVo.class, true); List list = result1.getList(); long started = System.currentTimeMillis(); if (list1 == null || list1.isEmpty()) { recordRouteUpload(null, expectedOrderNo, originalFilename, 0, IDrawingRouteOperLogService.STATUS_FAIL, "工艺生产计划表为空", started); return R.fail("工艺生产计划表为空,请检查此表或将sheet移动至第七个!"); } // 防呆:当前项目令号必须与文件内「生产令号」一致 String orderNoMismatch = validateImportOrderNo(expectedOrderNo, list1, list); if (orderNoMismatch != null) { ProcessOrderPro orderForMismatch = StringUtils.isNotBlank(expectedOrderNo) ? processOrderProService.selectByProjectNumber(expectedOrderNo.trim()) : null; recordRouteUpload(orderForMismatch, expectedOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, orderNoMismatch, started); return R.fail(orderNoMismatch); } String resolvedOrderNo = firstNonBlankRouteDescription(list1); if (StringUtils.isBlank(resolvedOrderNo) && StringUtils.isNotBlank(expectedOrderNo)) { resolvedOrderNo = expectedOrderNo.trim(); } // 后面异步推送会捕获令号,必须是 effectively final final String productionOrderNo = resolvedOrderNo; ProcessOrderPro orderForLock = processOrderProService.selectByProjectNumber(productionOrderNo); if (orderForLock != null) { String statusDeny = ProjectStatusLock.denyIfLocked(orderForLock.getBomStatus(), ProjectStatusLock.Action.UPLOAD_ROUTE); if (statusDeny != null) { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, statusDeny, started); return R.fail(statusDeny); } } List bomDetails = iBomDetailsService.selectByProjectNumber(productionOrderNo); List route = iProcessRouteService.selectByProjectNumber(productionOrderNo); if (!bomDetails.isEmpty()) { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, "已导入过BOM,请先清空再上传", started); return R.fail("项目 " + productionOrderNo + "已导入过BOM,请先清空再上传"); } if (!route.isEmpty()) { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, "已导入过工艺,请先清空再上传", started); return R.fail("项目 " + productionOrderNo + "已导入过工艺 ,请先清空再上传"); } String workCenterError = validateImportWorkCenters(list1); if (workCenterError != null) { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, "工段校验失败", started); return R.fail("生产工艺计划表工段校验失败:\n" + workCenterError); } if (iProcessRouteService.saveData(list1, list)) { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_SUCCESS, null, started); // 计划表落到服务器固定目录(app.plan-excel.base-dir/令号/),保留不删 File savedPlanFile = null; try { File dir = new File(processPlanExcelPushService.getPlanExcelBaseDir(), productionOrderNo); if (!dir.exists() && !dir.mkdirs()) { log.warn("创建计划表目录失败: {}", dir.getAbsolutePath()); } savedPlanFile = new File(dir, originalFilename); file.transferTo(savedPlanFile); log.info("上传的工艺计划表已落盘: {}", savedPlanFile.getAbsolutePath()); } catch (Exception e) { log.error("保存上传的工艺计划表失败", e); } final File uploadFile = savedPlanFile; // 获取当前登录用户昵称(需要在异步线程前获取) String currentUserNickName = "未知用户"; try { Long userId = LoginHelper.getUserId(); if (userId != null) { SysUser user = userService.selectUserById(userId); if (user != null && StringUtils.isNotEmpty(user.getNickName())) { currentUserNickName = user.getNickName(); } } } catch (Exception e) { log.warn("获取当前登录用户昵称失败", e); } String todoProjectName = orderForLock == null ? null : orderForLock.getProductionName(); Date todoProjectEndTime = orderForLock == null ? null : orderForLock.getProjectEndTime(); String planUserIds = dingTalkProperties.getPlanUserId(); // 异步:按 plan-user-id 发文件 + 发工艺审核卡片(落盘文件保留) String finalNickName = currentUserNickName; String operator = drawingRouteOperLogService.currentOperator(); String clientIp = drawingRouteOperLogService.currentClientIp(); Long projectId = orderForLock == null ? null : orderForLock.getId(); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); CompletableFuture.runAsync(() -> { Thread.currentThread().setContextClassLoader(contextClassLoader); long notifyStarted = System.currentTimeMillis(); ProcessPlanExcelPushService.FilePushResult filePush = processPlanExcelPushService.sendToPlanUsers(uploadFile, originalFilename); String todoError = dingTodoDispatchService.sendIfEnabled( DingTodoSceneCodes.ROUTE_UPLOAD_PLAN, productionOrderNo, todoProjectName, todoProjectEndTime, uploadFile, planUserIds); if (StringUtils.isNotBlank(todoError) && filePush != null) { filePush.setErrorMsg(StringUtils.isBlank(filePush.getErrorMsg()) ? "待办: " + todoError : filePush.getErrorMsg() + "; 待办: " + todoError); if (filePush.getSuccessCount() > 0) { filePush.setFailCount(filePush.getFailCount() + 1); } } boolean cardOk = false; String cardError = null; String outTrackId = null; try { ProcessOrderPro order = processOrderProService.selectByProjectNumber(productionOrderNo); if (order != null) { // 获取 AccessToken String accessToken = DingUtil.getAccessToken(dingTalkProperties.getAppKey(), dingTalkProperties.getAppSecret()); // 构造卡片数据 Map cardDataMap = new HashMap<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); cardDataMap.put("pro_number", StringUtils.defaultIfEmpty(order.getProductionOrderNo(), "")); cardDataMap.put("createTime", order.getRouteUptime() != null ? sdf.format(new Date()) : ""); cardDataMap.put("title", finalNickName + "提交工艺审核"); cardDataMap.put("lastMessage", "请尽快处理"); cardDataMap.put("status", StringUtils.defaultIfEmpty("待审批", "待审批")); outTrackId = "audit-card-" + System.currentTimeMillis(); // 调用发送给工艺负责人的方法 createAndDeliverCardToUser DingUtil.createAndDeliverCardToUser( accessToken, dingTalkProperties.getRouteCardTemplateId(), Collections.singletonList(dingTalkProperties.getRouteApprovalId()), outTrackId, cardDataMap, dingTalkProperties.getCallbackRouteKey() ); // 统一存入带 userId 后缀的完整 outTrackId,以便和钉钉回调保持一致 String fullOutTrackId = outTrackId + "_" + dingTalkProperties.getRouteApprovalId(); // 更新数据库中的AuditCardId ProcessOrderPro update = new ProcessOrderPro(); update.setId(order.getId()); update.setUpdateBy("Dingding"); update.setAuditCardId(fullOutTrackId); update.setBomStatus(2L); // 更新审核状态为1(已审核/提交审核) update.setRouteStatus(0L); proMapper.updateById(update); cardOk = true; } else { cardError = "项目不存在,未发送审核卡片"; } } catch (Exception e) { cardError = e.getMessage(); log.error("发送钉钉卡片异常", e); } recordRouteNotifyAfterUpload(productionOrderNo, projectId, originalFilename, finalNickName, filePush, cardOk, cardError, outTrackId, operator, clientIp, notifyStarted); }); return R.ok("上传物料成功"); } else { recordRouteUpload(orderForLock, productionOrderNo, originalFilename, list1.size(), IDrawingRouteOperLogService.STATUS_FAIL, "导入失败", started); return R.fail("导入失败"); } } /** * 防呆:页面所选项目令号须与 Excel「生产令号」一致。 * 工艺 sheet(routeDescription)与总装 sheet(productionOrderNo)都会检查。 * * @return 错误提示;通过则 null */ private String validateImportOrderNo(String expectedOrderNo, List routes, List products) { LinkedHashSet fileNos = new LinkedHashSet<>(); if (routes != null) { for (ProcessRouteVo row : routes) { if (row != null && StringUtils.isNotBlank(row.getRouteDescription())) { fileNos.add(row.getRouteDescription().trim()); } } } if (products != null) { for (ProductionOrderVo row : products) { if (row != null && StringUtils.isNotBlank(row.getProductionOrderNo())) { fileNos.add(row.getProductionOrderNo().trim()); } } } if (fileNos.isEmpty()) { return "导入文件未读到生产令号,请检查工艺计划表「生产令号」列"; } // 大小写不同仍视为同一令号,避免 Excel 大小写差异误报多个令号 String fileNo = null; for (String no : fileNos) { if (fileNo == null) { fileNo = no; } else if (!fileNo.equalsIgnoreCase(no)) { return "导入文件中存在多个生产令号:" + String.join("、", fileNos) + ",请确认是否导错文件"; } } if (StringUtils.isBlank(expectedOrderNo)) { return "请先选择要导入的项目令号"; } String expected = expectedOrderNo.trim(); if (!expected.equalsIgnoreCase(fileNo)) { return "导入令号不一致:当前项目为「" + expected + "」,文件中为「" + fileNo + "」,请确认后重新导入"; } return null; } /** * 取工艺表中第一条非空生产令号。 */ private static String firstNonBlankRouteDescription(List routes) { if (routes == null) { return null; } for (ProcessRouteVo row : routes) { if (row != null && StringUtils.isNotBlank(row.getRouteDescription())) { return row.getRouteDescription().trim(); } } return null; } private void recordRouteUpload(ProcessOrderPro order, String productionOrderNo, String fileName, int lineCount, String status, String errorMsg, long started) { Map summary = new LinkedHashMap<>(); summary.put("lineCount", lineCount); summary.put("fileName", fileName); drawingRouteOperLogService.record(IDrawingRouteOperLogService.BIZ_ROUTE_UPLOAD, row -> { row.setChannel(IDrawingRouteOperLogService.CHANNEL_MANUAL); row.setApiPath("/system/route/importData"); row.setProjectId(order == null ? null : order.getId()); row.setProductionOrderNo(productionOrderNo); row.setFileName(fileName); row.setStatus(status); row.setErrorMsg(errorMsg); row.setSummaryJson(JSONUtil.toJsonStr(summary)); row.setDurationMs(System.currentTimeMillis() - started); }); } private void recordRouteNotifyAfterUpload(String productionOrderNo, Long projectId, String fileName, String nickName, ProcessPlanExcelPushService.FilePushResult filePush, boolean cardOk, String cardError, String outTrackId, String operator, String clientIp, long started) { String fileStatus = filePush == null ? IDrawingRouteOperLogService.STATUS_FAIL : filePush.resolveStatus(); boolean fileOk = IDrawingRouteOperLogService.STATUS_SUCCESS.equals(fileStatus); String status; if (fileOk && cardOk) { status = IDrawingRouteOperLogService.STATUS_SUCCESS; } else if (!fileOk && !cardOk) { status = IDrawingRouteOperLogService.STATUS_FAIL; } else { status = IDrawingRouteOperLogService.STATUS_PARTIAL; } StringBuilder error = new StringBuilder(); if (filePush != null && StringUtils.isNotBlank(filePush.getErrorMsg())) { error.append("文件: ").append(filePush.getErrorMsg()); } if (StringUtils.isNotBlank(cardError)) { if (error.length() > 0) { error.append("; "); } error.append("卡片: ").append(cardError); } Map summary = new LinkedHashMap<>(); summary.put("fileStatus", fileStatus); summary.put("fileSuccessCount", filePush == null ? 0 : filePush.getSuccessCount()); summary.put("fileFailCount", filePush == null ? 0 : filePush.getFailCount()); summary.put("cardOk", cardOk); summary.put("outTrackId", outTrackId); summary.put("submitter", nickName); String notifyUsers = joinNotifyUsers( filePush == null ? null : filePush.getUserIds(), dingTalkProperties.getRouteApprovalId()); drawingRouteOperLogService.record(IDrawingRouteOperLogService.BIZ_ROUTE_NOTIFY, row -> { row.setChannel(IDrawingRouteOperLogService.CHANNEL_MANUAL); row.setApiPath("/system/route/importData"); row.setProjectId(projectId); row.setProductionOrderNo(productionOrderNo); row.setNotifyType(IDrawingRouteOperLogService.NOTIFY_FILE_AND_CARD); row.setNotifyUserIds(notifyUsers); row.setMessageTitle(nickName + "提交工艺审核"); row.setFileName(fileName); row.setStatus(status); row.setErrorMsg(error.length() == 0 ? null : error.toString()); row.setSummaryJson(JSONUtil.toJsonStr(summary)); row.setOperator(operator); row.setClientIp(clientIp); row.setDurationMs(System.currentTimeMillis() - started); }); } private static String joinNotifyUsers(String... parts) { StringBuilder sb = new StringBuilder(); if (parts == null) { return null; } for (String part : parts) { if (StringUtils.isBlank(part)) { continue; } if (sb.length() > 0) { sb.append(","); } sb.append(part.trim()); } return sb.length() == 0 ? null : sb.toString(); } /** * 查询组焊件集合 */ @Log(title = "查询组焊件集合") @SaCheckPermission("system:route:pushGroupWeldments") @PostMapping("/pushGroupWeldments") public List pushGroupWeldments(@RequestBody ProcessRoute processRoute) { return iProcessRouteService.pushGroupWeldments(processRoute); } /** * 查询需要单独生成BOm 的原材料集合 */ @Log(title = "查询需要单独生成BOM的原材料集合") @SaCheckPermission("system:route:getRawBom") @PostMapping("/getRawBom") public List getRawBom(@RequestParam String rooteProdet) { return iProcessRouteService.getRawBom(rooteProdet); } /** * 查询工艺表中所有的需要做工艺的物料 * * @return RequestBody List routeVoList */ public List getProcessRouteList(@RequestParam String rooteProdet) { List list = iProcessRouteService.pushRawMater(rooteProdet); return list; } @Log(title = "查询工艺工序集合") @SaCheckPermission("system:route:getProcessRoute") @PostMapping("/getProcessRoute") public List getProcessRoute(@RequestParam String rooteProdet) { return iProcessRouteService.getProcessRoute(rooteProdet); } @Log(title = "推送工艺工序") @SaCheckPermission("system:route:pushRouteBom") @PostMapping("/pushRouteBom") public R pushRouteBom(@RequestParam String rooteProdet, @RequestParam String groupName) { String jsyyDeny = JsyyOrderNo.denyIfMatches(rooteProdet); if (jsyyDeny != null) { return R.fail(jsyyDeny); } ProcessOrderPro orderForLock = processOrderProService.selectByProjectNumber(rooteProdet); if (orderForLock != null) { String statusDeny = ProjectStatusLock.denyIfLocked(orderForLock.getBomStatus(), ProjectStatusLock.Action.PUSH_ROUTE); if (statusDeny != null) { return R.fail(statusDeny); } } return iProcessRouteService.pushRouteBom(rooteProdet, groupName); } /** * 按物料推送工艺:只推所选物料,不改项目整单状态。 */ @Log(title = "按物料推送工艺工序") @SaCheckPermission("system:route:pushRouteBom") @PostMapping("/pushRouteBomByMaterial") public R pushRouteBomByMaterial(@RequestParam String rooteProdet, @RequestParam String groupName, @RequestParam String materialCodes) { String jsyyDeny = JsyyOrderNo.denyIfMatches(rooteProdet); if (jsyyDeny != null) { return R.fail(jsyyDeny); } ProcessOrderPro orderForLock = processOrderProService.selectByProjectNumber(rooteProdet); if (orderForLock != null) { String statusDeny = ProjectStatusLock.denyIfLocked(orderForLock.getBomStatus(), ProjectStatusLock.Action.PUSH_ROUTE); if (statusDeny != null) { return R.fail(statusDeny); } } if (StringUtils.isBlank(materialCodes)) { return R.fail("请选择要推送的物料"); } List codes = Arrays.stream(materialCodes.split("[,,;;\\s]+")) .map(String::trim) .filter(StringUtils::isNotBlank) .distinct() .collect(Collectors.toList()); return iProcessRouteService.pushRouteBomByMaterial(rooteProdet, groupName, codes); } @Log(title = "审核通过(发送钉钉通知,不推送金蝶)") @SaCheckPermission("system:route:approveRoute") @PostMapping("/approveRoute") public R approveRoute(@RequestParam String rooteProdet) { return processOrderProService.approveRoute(rooteProdet); } /** * 查询项目列表 */ @Log(title = "获取所有的项目令号") @SaCheckPermission("system:route:getDistinctProjectCodes") @GetMapping("/getDistinctProjectCodes") public ResponseEntity> getDistinctProjectCodes(String query) { return ResponseEntity.ok(iProcessRouteService.getDistinctProjectCodes(query)); } @Log(title = "获取工序列表") @SaCheckPermission("system:route:getProcessInfoList") @GetMapping("/getProcessInfoList") public ResponseEntity> getProcessInfoList(String query) { return ResponseEntity.ok(iProcessRouteService.getProcessInfoList(query)); } @Log(title = "根据生产令号获取生产编号==》获取计划编号") @SaCheckPermission("system:route:getSelecPlanRouteList") @PostMapping("/getSelecPlanRouteList") public List getSelecPlanRouteList(@RequestParam String rooteProdet) { return iProcessRouteService.getSelecPlanRouteList(rooteProdet); } @Log(title = "生成工序计划单PDF") @SaCheckPermission("system:route:generatePDFs") @GetMapping("/generatePDFs") public void generatePDFs(String rooteProdet, @RequestParam(value = "materialCode", required = false) String materialCode, HttpServletResponse response) throws IOException { if (StringUtils.isBlank(rooteProdet)) { throw new ServiceException("生产令号不能为空"); } // 有物料编码:单份 PDF;无物料编码:令号下全部 PDF 压缩包 String filePath = iProcessRouteService.generatePDFs(rooteProdet, materialCode); File outFile = new File(filePath); if (!outFile.exists()) { throw new FileNotFoundException("文件未找到: " + filePath); } if (outFile.length() <= 0) { throw new IOException("文件为空: " + filePath); } boolean isPdf = filePath.toLowerCase().endsWith(".pdf"); boolean isZip = filePath.toLowerCase().endsWith(".zip"); if (!isPdf && !isZip) { throw new IOException("未生成PDF或压缩包: " + filePath); } response.setContentType(isPdf ? "application/pdf" : "application/zip"); String encoded = URLEncoder.encode(outFile.getName(), "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded); try (FileInputStream fis = new FileInputStream(outFile); OutputStream outputStreams = response.getOutputStream()) { byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { outputStreams.write(buffer, 0, len); } outputStreams.flush(); } } /** * 下载 PLM 图纸/模型。后端校验项目来源后转发文件服务。 */ @Log(title = "下载图纸模型", businessType = BusinessType.EXPORT) @SaCheckPermission("system:route:downloadDrawingModel") @GetMapping("/downloadDrawingModel") public void downloadDrawingModel(@RequestParam String productionOrderNo, @RequestParam String materialCode, @RequestParam String fileType, HttpServletResponse response) { drawingFileQueryService.download(productionOrderNo, materialCode, fileType, response); } @Log(title = "更新计划时间") @SaCheckPermission("system:route:updateProcessPlan") @PostMapping("/updateProcessPlan") public List updateProcessPlan(@RequestParam String rooteProdet) throws Exception { return iProcessRouteService.updateProcessPlan(rooteProdet); } /** * 按车间+物料默认条件拉取工序计划,回填工艺时间,保存时部门统一为 018(与 {@link OperationPlanningWorkshopUpdateService} 一致)。 */ @Log(title = "按车间物料更新工序计划(默认条件)") @SaCheckPermission("system:route:updateProcessPlan") @PostMapping("/updateProcessPlanByWorkshop") public R> updateProcessPlanByWorkshop() throws Exception { return R.ok(operationPlanningWorkshopUpdateService.updateProcessPlanByWorkshopFilter()); } /** * 同上,BillQuery 过滤条件可自定义(部门 Compare/Value、物料 Compare/Value)。 */ @Log(title = "按车间物料更新工序计划(自定义条件)") @SaCheckPermission("system:route:updateProcessPlan") @PostMapping("/updateProcessPlanByWorkshopCustom") public R> updateProcessPlanByWorkshopCustom( @RequestParam String departmentCompare, @RequestParam String departmentNumber, @RequestParam String productCompare, @RequestParam String productFilterValue) throws Exception { return R.ok(operationPlanningWorkshopUpdateService.updateProcessPlanByWorkshopFilter( departmentCompare, departmentNumber, productCompare, productFilterValue)); } @Log(title = "按车间物料更新工序汇报单(默认条件)") @SaCheckPermission("system:route:updateProcessPlan") @PostMapping("/updateOperationReportByWorkshop") public R> updateOperationReportByWorkshop() throws Exception { return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter()); } @Log(title = "按车间物料更新工序汇报单(固定条件67/007/17/-MP)") @SaCheckPermission("system:route:updateProcessPlan") @PostMapping("/updateOperationReportByWorkshopCustom") public R> updateOperationReportByWorkshopCustom() throws Exception { return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter( "67", "007", "17", "-MP")); } /** * 更新生产订单的时间 */ @Log(title = "更新生产订单时间") @SaCheckPermission("system:route:updateProcesTime") @PostMapping("/updateProcesTime") public List updateProcesTime(@RequestParam String rooteProdet) throws Exception { return iProcessRouteService.updateProcesTime(rooteProdet); } /** * 保存工艺路线 */ @SaCheckPermission("system:route:addRoute") @Log(title = "保存工艺路线", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/addRoute") public R addRoute(@RequestBody List list) { return iProcessRouteService.addRoute(list); } @SaCheckPermission("system:route:deleteRoute") @Log(title = "删除工艺路线", businessType = BusinessType.INSERT) @RepeatSubmit() @PostMapping("/deleteRoute") public List deleteRoute(@RequestBody ProcessRoute processRoute) { return iProcessRouteService.deleteRoute(processRoute); } @Log(title = "获取材料bom列表") @SaCheckPermission("system:route:getBomInfo") @PostMapping("/getBomInfo") public ResponseEntity> getProcessMaterialList(@RequestParam(value = "materialCode") String materialCode, @RequestParam(value = "materialName") String materialName, @RequestParam(value = "productionOrderNo") String productionOrderNo) { return ResponseEntity.ok(iProcessRouteService.getProcessMaterialList(materialCode, materialName, productionOrderNo)); } @Log(title = "获取金蝶列表") @SaCheckPermission("system:route:getProcessRouteList") @GetMapping("/getProcessRouteList") public ResponseEntity> getProcessRouteList(@RequestParam(value = "materialCode") String materialCode, @RequestParam(value = "materialName") String materialName, @RequestParam(value = "productionOrderNo") String productionOrderNo) { return ResponseEntity.ok(iProcessRouteService.getProcessRouteList(materialCode, materialName, productionOrderNo)); } @Log(title = "导入时间", businessType = BusinessType.IMPORT) @SaCheckPermission("system:route:importDataTime") @PostMapping(value = "/importDataTime", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importDataTime(@RequestParam("file") MultipartFile file) throws Exception { String originalFilename = file.getOriginalFilename(); log.info("读取文件名: " + originalFilename); ExcelResult result = ExcelUtil.importExcelSheet6(file.getInputStream(), ProcessRouteVo.class, true); List list = result.getList(); iProcessRouteService.importDataTime(list); return R.ok("更新成功"); } @Log(title = "导入时间", businessType = BusinessType.IMPORT) @SaCheckPermission("system:route:importDataTime123") @PostMapping(value = "/importDataTime123", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importDataTime123(@RequestParam("file") MultipartFile file) throws Exception { DefaultExcelListener listener = new DefaultExcelListener<>(true); EasyExcel.read(file.getInputStream(), PcRigidChainVo.class, listener).excelType(ExcelTypeEnum.XLS).sheet(1).headRowNumber(4).doRead(); List list1 = listener.getExcelResult().getList(); for (PcRigidChainVo pcRigidChainVO : list1) { log.info("数据 ===============>: " + JSONUtil.toJsonStr(pcRigidChainVO)); } return R.ok("更新成功"); } @Log(title = "获取金蝶工艺路线") @SaCheckPermission("system:route:getSelectProcessRout1e") @PostMapping("/getSelectProcessRout1e") public List getSelectProcessRoute1(@RequestParam String materilCode) { return iProcessRouteService.getSelectProcessRoute(materilCode); } private void processCombinedDTO(CombinedDTO combinedDTO, List bomDetailsList, List routeList, List materialBoms, String productionOrderNo) { // 处理工序列表 combinedDTO.getProcesses().forEach(process -> { ProcessRoute processRoute = createProcessRoute(process, combinedDTO, productionOrderNo); routeList.add(processRoute); }); // 处理物料清单 combinedDTO.getMaterialUsageDTOList().forEach(materialUsageDTO -> { BomDetails bomDetails = createBomDetails(materialUsageDTO, combinedDTO, productionOrderNo); bomDetailsList.add(bomDetails); }); // 处理材料BOM combinedDTO.getMaterialUsageDTOList().forEach(materialUsageDTO -> { MaterialBom materialBom = creatBomMaterial(materialUsageDTO, combinedDTO, productionOrderNo); materialBoms.add(materialBom); }); } private void processMaterAndRoute(JDMaterialAndRoute materialAndRoute, List bomDetailsList, List routeList, List materialBoms, String productionOrderNo) { // 处理工序列表 materialAndRoute.getPlannedProcessVos().forEach(processRouteDTO -> { ProcessRoute processRoute = createProcessRoute1(processRouteDTO, materialAndRoute, productionOrderNo); routeList.add(processRoute); }); // 处理物料清单 materialAndRoute.getMaterialUseDTOS().forEach(materialUsageDTO -> { BomDetails bomDetails = createBomDetails1(materialUsageDTO, materialAndRoute, productionOrderNo); bomDetailsList.add(bomDetails); }); // 处理材料BOM materialAndRoute.getMaterialUseDTOS().forEach(materialUsageDTO -> { MaterialBom materialBom = creatBomMaterial1(materialUsageDTO, materialAndRoute, productionOrderNo); materialBoms.add(materialBom); }); } private MaterialBom creatBomMaterial1(MaterialUseDTO materialUsageDTO, JDMaterialAndRoute materialAndRoute, String productionOrderNo) { MaterialBom materialBom = new MaterialBom(); materialBom.setProjectNumber(productionOrderNo); materialBom.setParentMaterialCode(materialAndRoute.getMaterialCode()); materialBom.setParentMaterialName(materialAndRoute.getMaterialName()); materialBom.setMaterialCode(materialUsageDTO.getMaterialCode()); materialBom.setMaterialName(materialUsageDTO.getMaterialName()); materialBom.setUnit(materialUsageDTO.getChildUnit()); materialBom.setMaterialType(materialUsageDTO.getCaizhi()); //保留四位小数 materialBom.setQuantity(String.valueOf(BigDecimal.valueOf(materialUsageDTO.getFenzi()).divide(new BigDecimal(materialUsageDTO.getFenmu()), 4, RoundingMode.HALF_UP))); return materialBom; } private BomDetails createBomDetails1(MaterialUseDTO materialUsageDTO, JDMaterialAndRoute materialAndRoute, String productionOrderNo) { BomDetails bomDetails = new BomDetails(); bomDetails.setTotalWeight(productionOrderNo); bomDetails.setFNumber(materialAndRoute.getMaterialCode()); bomDetails.setFName(materialAndRoute.getMaterialName()); bomDetails.setPartNumber(materialUsageDTO.getMaterialCode()); bomDetails.setUnitWeight("是"); bomDetails.setWareHouse(materialUsageDTO.getChildUnit()); //子项分子 bomDetails.setQuantity(String.valueOf(materialUsageDTO.getFenzi())); //子项分母 bomDetails.setDenominator(Double.valueOf(materialUsageDTO.getFenmu())); bomDetails.setName(materialUsageDTO.getMaterialName()); bomDetails.setMaterial(materialUsageDTO.getCaizhi()); return bomDetails; } private ProcessRoute createProcessRoute1(ProcessRouteDTO processRouteDTO, JDMaterialAndRoute materialAndRoute, String productionOrderNo) { ProcessRoute processRoute = new ProcessRoute(); // 设置路线描述为生产订单号 processRoute.setRouteDescription(productionOrderNo); // 设置工序号 processRoute.setProcessNo(processRouteDTO != null ? processRouteDTO.getProcessNo() : null); // 设置工序名称 processRoute.setProcessName(processRouteDTO != null ? processRouteDTO.getProcessName() : null); if (materialAndRoute != null && StringUtils.isNotEmpty(materialAndRoute.getMaterialCode())) { ImMaterial materialByCode = materialService.getMaterialByCode(materialAndRoute.getMaterialCode()); if (materialByCode != null && materialByCode.getSingleWeight() != null) { processRoute.setDiscWeight(Double.valueOf(String.valueOf(materialByCode.getSingleWeight()))); } else { processRoute.setDiscWeight(null); } } else { processRoute.setDiscWeight(null); } processRoute.setMaterial(materialAndRoute != null ? materialAndRoute.getCaizhi() : null); // 设置物料代码 processRoute.setMaterialCode(materialAndRoute != null ? materialAndRoute.getMaterialCode() : null); processRoute.setMaterialName(materialAndRoute != null ? materialAndRoute.getMaterialName() : null); processRoute.setWorkCenter(processRouteDTO != null ? processRouteDTO.getWorkCenter() : null); // 基本时长 processRoute.setActivityDuration(processRouteDTO != null ? processRouteDTO.getActivityDuration() : null); processRoute.setProcessDescription(processRouteDTO != null ? processRouteDTO.getProcessDescription() : null); processRoute.setProcessControl(processRouteDTO != null ? processRouteDTO.getProcessControl() : null); processRoute.setXuStartTime(null); processRoute.setXuEndTime(null); processRoute.setActivityUnit("分"); // 本批数量对应 processRoute.setBatchQuantity(materialAndRoute != null ? materialAndRoute.getBenpi() : null); if (materialAndRoute != null && materialAndRoute.getDantai() != null) { processRoute.setFirstBatchQuantity(Double.valueOf(materialAndRoute.getDantai())); } else { processRoute.setFirstBatchQuantity(null); } List materialUsageDTOList = materialAndRoute != null ? materialAndRoute.getMaterialUseDTOS() : null; if (materialUsageDTOList != null) { for (MaterialUseDTO materialUsageDTO : materialUsageDTOList) { if (materialUsageDTO != null) { processRoute.setUnitQuantity(materialUsageDTO.getFenzi()); processRoute.setBomMaterial(materialUsageDTO.getCaizhi()); processRoute.setRawMaterialCode(materialUsageDTO.getMaterialCode()); processRoute.setRawMaterialName(materialUsageDTO.getMaterialName()); processRoute.setBomUnit(materialUsageDTO.getChildUnit()); if ("根".equals(materialUsageDTO.getChildUnit())) { if (materialUsageDTO.getFenmu() != null) { processRoute.setDiscUsage(String.valueOf(Double.valueOf(materialUsageDTO.getFenmu()))); } else { processRoute.setDiscUsage(null); } } else { if (materialUsageDTO.getFenzi() != null && materialUsageDTO.getFenmu() != null && materialUsageDTO.getFenmu() != 0) { processRoute.setDiscUsage(String.valueOf(materialUsageDTO.getFenzi() / materialUsageDTO.getFenmu())); } else { processRoute.setDiscUsage(null); } } processRoute.setMaterial(materialUsageDTO.getCaizhi()); } else { processRoute.setUnitQuantity(null); processRoute.setBomMaterial(null); processRoute.setRawMaterialCode(null); processRoute.setRawMaterialName(null); processRoute.setBomUnit(null); processRoute.setDiscUsage(null); processRoute.setMaterial(null); } } } return processRoute; } @Log(title = "生成金蝶标准工艺") @SaCheckPermission("system:route:getKindeeExcel") @GetMapping("/getKindeeExcel") public ResponseEntity getKindeeExcel(String rooteProdet) throws IOException { List planRouteList = iProcessRouteService.getSelecPlanRouteList(rooteProdet); List bomDetailsList = new ArrayList<>(); List routeList = new ArrayList<>(); List materialBoms = new ArrayList<>(); // 将数据写入excel 中 for (CombinedDTO combinedDTO : planRouteList) { processCombinedDTO(combinedDTO, bomDetailsList, routeList, materialBoms, rooteProdet); } // 批量插入数据库 try { bomDetailsMapper.insertBatch(bomDetailsList); baseMapper.insertBatch(routeList); materialBomMapper.insertBatch(materialBoms); return ResponseEntity.ok("保存成功"); } catch (Exception e) { // 处理异常并返回失败信息 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("保存失败: " + e.getMessage()); } } private ProcessRoute createProcessRoute(PlannedProcessVo process, CombinedDTO combinedDTO, String productionOrderNo) { ProcessRoute processRoute = new ProcessRoute(); // 设置路线描述为生产订单号 processRoute.setRouteDescription(productionOrderNo); // 设置工序号 processRoute.setProcessNo(process.getFOperNumber()); // 设置工序名称 processRoute.setProcessName(process.getFProcessName()); String materialCode = combinedDTO.getMaterialCode(); try { List singleWeightList = iProcessRouteService.getSingleWeght(materialCode); if (singleWeightList != null && !singleWeightList.isEmpty()) { processRoute.setDiscWeight(singleWeightList.get(0).getDiscWeight()); } } catch (Exception e) { // 记录日志或采取其他措施 log.error("Error fetching single weight for material code: " + materialCode, e); } // 设置物料代码 processRoute.setMaterialCode(combinedDTO.getMaterialCode()); processRoute.setMaterialName(combinedDTO.getMaterialName()); Double fOperQty = process.getFOperQty(); long l = fOperQty.longValue(); processRoute.setWorkCenter(process.getFWorkCenterName()); // 基本时长 processRoute.setActivityDuration(process.getFActivity1BaseQty()); processRoute.setProcessDescription(process.getFOperDescription()); processRoute.setProcessControl(process.getFOptCtrlCodeIFName()); processRoute.setXuStartTime(process.getFSeqPlanStartTime()); processRoute.setXuEndTime(process.getFSeqPlanFinishTime()); processRoute.setActivityUnit("分"); // 本批数量对应 processRoute.setBatchQuantity(process.getFMOQty()); List materialUsageDTOList = combinedDTO.getMaterialUsageDTOList(); for (MaterialUsageDTO materialUsageDTO : materialUsageDTOList) { processRoute.setUnitQuantity(materialUsageDTO.getFNumerator()); processRoute.setBomMaterial(materialUsageDTO.getChildMaterial()); processRoute.setRawMaterialCode(materialUsageDTO.getMaterialCode()); processRoute.setRawMaterialName(materialUsageDTO.getMaterialName()); processRoute.setBomUnit(materialUsageDTO.getUnit()); if (materialUsageDTO.getUnit().equals("根")) { processRoute.setDiscUsage(String.valueOf(materialUsageDTO.getFDenominator())); } else { processRoute.setDiscUsage(String.valueOf(materialUsageDTO.getFNumerator() / materialUsageDTO.getFDenominator())); } processRoute.setMaterial(materialUsageDTO.getChildMaterial()); } return processRoute; } private BomDetails createBomDetails(MaterialUsageDTO materialUsageDTO, CombinedDTO combinedDTO, String productionOrderNo) { BomDetails bomDetails = new BomDetails(); bomDetails.setTotalWeight(productionOrderNo); bomDetails.setFNumber(combinedDTO.getMaterialCode()); bomDetails.setFName(combinedDTO.getMaterialName()); bomDetails.setPartNumber(materialUsageDTO.getMaterialCode()); bomDetails.setUnitWeight("是"); bomDetails.setWareHouse(materialUsageDTO.getUnit()); //子项分子 bomDetails.setQuantity(String.valueOf(materialUsageDTO.getFNumerator())); //子项分母 bomDetails.setDenominator(materialUsageDTO.getFDenominator()); bomDetails.setName(materialUsageDTO.getMaterialName()); if (materialUsageDTO.getItemType().equals("1")) { bomDetails.setStats("外购"); } else { bomDetails.setStats("自制"); } bomDetails.setMaterial(materialUsageDTO.getChildMaterial()); return bomDetails; } private MaterialBom creatBomMaterial(MaterialUsageDTO materialUsageDTO, CombinedDTO combinedDTO, String productionOrderNo) { MaterialBom materialBom = new MaterialBom(); materialBom.setProjectNumber(productionOrderNo); materialBom.setParentMaterialCode(combinedDTO.getMaterialCode()); materialBom.setParentMaterialName(combinedDTO.getMaterialName()); materialBom.setMaterialCode(materialUsageDTO.getMaterialCode()); materialBom.setMaterialName(materialUsageDTO.getMaterialName()); materialBom.setUnit(materialUsageDTO.getUnit()); materialBom.setMaterialType(materialUsageDTO.getItemType()); materialBom.setQuantity(String.valueOf(BigDecimal.valueOf(materialUsageDTO.getFNumerator()))); return materialBom; } /** * 导出工艺路线列表 */ @SaCheckPermission("system:route:getAllRouteAndUse") @Log(title = "获取全部工艺路线和物料清单", businessType = BusinessType.OTHER) @PostMapping("/getAllRouteAndUse") public void getAllRouteAndUse(@RequestParam("rooteProdet") String rooteProdet) { log.info("获取项目生产数据表:{}", rooteProdet); // 下载Excel模板 (切换为FTP方式) // SmbUtil.downloadExcelFiles(rooteProdet); String localDir = "D:\\file"; String remoteDir = "/2026/" + rooteProdet; R ftpResult = FtpUtil.downloadFtpDirectoryFiles(drawingFtpProperties, remoteDir, localDir); if (R.isError(ftpResult)) { log.error("FTP下载失败: {}", ftpResult.getMsg()); } else { log.info("FTP下载结果: {}", ftpResult.getMsg()); } // 构建Excel文件路径 String ExcelName = "D:\\file\\" + rooteProdet + "汇总表.xlsx"; File file = new File(ExcelName); if (!file.exists()) { throw new ServiceException("文件不存在,请确认路径是否正确"); } //读取file的所有的物料编码 try { if (file.exists()) { // 读取Excel的sheet6 从第三行开始 读取到第4列 无数据的跳过 DefaultExcelListener listener = new DefaultExcelListener<>(true); EasyExcel.read(ExcelName, ProcessRouteGetDTO.class, listener).sheet(6).headRowNumber(3).doRead(); List list = listener.getExcelResult().getList(); List list1 = iProcessRouteService.getProcessRouteGD(list, rooteProdet); List bomDetailsList = new ArrayList<>(); List routeList = new ArrayList<>(); List materialBoms = new ArrayList<>(); for (JDMaterialAndRoute materialAndRoute : list1) { processMaterAndRoute(materialAndRoute, bomDetailsList, routeList, materialBoms, rooteProdet); } bomDetailsMapper.insertBatch(bomDetailsList); baseMapper.insertBatch(routeList); materialBomMapper.insertBatch(materialBoms); } } catch (Exception e) { throw new RuntimeException(e); } } /** * 更新生产订单仓库字段 */ @SaCheckPermission("system:route:updateProductionOrders") @Log(title = "更新生产订单仓库", businessType = BusinessType.OTHER) @PostMapping("/updateProductionOrders") public R updateProductionOrders(@RequestParam("rooteProdet") String rooteProdet, @RequestParam("cangKuNum") String cangKuNum) throws Exception { //判断此项目是否有 List list = iProcessRouteService.updateProductionOrders(rooteProdet, cangKuNum); return R.ok(list); } /** * 更新采购申请入库仓库仓库 */ @SaCheckPermission("system:route:updateProductionOrders") @Log(title = "更新采购申请单仓库", businessType = BusinessType.OTHER) @PostMapping("/updateCgOrders") public R updateCgOrders(@RequestParam("rooteProdet") String rooteProdet, @RequestParam("cangKuNum") String cangKuNum) throws Exception { //判断此项目是否有 List list = iProcessRouteService.updateCgOrders(rooteProdet, cangKuNum); return R.ok(list); } /** * 导入前校验「工作中心(加工车间)」:工段合法; * 同一物料下机加(006)与装配(007)可混用,铆焊(018)不得与其他车间混用。 */ private static String validateImportWorkCenters(List list) { if (list == null || list.isEmpty()) { return null; } StringBuilder sb = new StringBuilder(); validateImportWorkCenterPerRow(list, sb); validateImportMaterialSingleWorkshop(list, sb); return sb.length() == 0 ? null : sb.toString(); } /** * 逐行:工段非空且在允许列表内 */ private static void validateImportWorkCenterPerRow(List list, StringBuilder sb) { int seq = 0; for (ProcessRouteVo vo : list) { if (vo == null) { continue; } Long processNo = vo.getProcessNo(); String workCenter = vo.getWorkCenter(); boolean hasProcess = processNo != null; boolean hasWorkCenter = StringUtils.isNotBlank(workCenter); if (!hasProcess && !hasWorkCenter) { continue; } seq++; if (!hasWorkCenter) { appendWorkCenterError(sb, seq, processNo, vo.getMaterialCode(), "工作中心不能为空"); continue; } try { String dept = KingdeeRouteDepartmentHelper.resolveDepartmentFNumber(workCenter); log.debug("工段校验通过 第{}条工艺 processNo={} workCenter={} dept={}", seq, processNo, KingdeeRouteDepartmentHelper.normalize(workCenter), dept); } catch (ServiceException e) { appendWorkCenterError(sb, seq, processNo, vo.getMaterialCode(), e.getMessage()); } } } /** * 同一物料编码下:机加(006)与装配(007)允许混用;铆焊(018)不得与机加/装配混用。 */ private static void validateImportMaterialSingleWorkshop(List list, StringBuilder sb) { Map> materialToDepts = new LinkedHashMap<>(); Map> materialToWorkCenters = new LinkedHashMap<>(); for (ProcessRouteVo vo : list) { if (vo == null || vo.getProcessNo() == null || StringUtils.isBlank(vo.getWorkCenter())) { continue; } String materialCode = StringUtils.trim(vo.getMaterialCode()); if (StringUtils.isBlank(materialCode)) { continue; } String workCenter = KingdeeRouteDepartmentHelper.normalize(vo.getWorkCenter()); String dept; try { dept = KingdeeRouteDepartmentHelper.resolveDepartmentFNumber(workCenter); } catch (ServiceException e) { continue; } materialToDepts.computeIfAbsent(materialCode, k -> new LinkedHashSet<>()).add(dept); materialToWorkCenters.computeIfAbsent(materialCode, k -> new LinkedHashSet<>()).add(workCenter); } for (Map.Entry> entry : materialToDepts.entrySet()) { if (!KingdeeRouteDepartmentHelper.hasIncompatibleWorkshops(entry.getValue())) { continue; } String materialCode = entry.getKey(); String deptDesc = entry.getValue().stream() .map(d -> KingdeeRouteDepartmentHelper.departmentLabel(d) + "(" + d + ")") .collect(Collectors.joining("、")); String wcDesc = String.join("、", materialToWorkCenters.getOrDefault(materialCode, new LinkedHashSet<>())); if (sb.length() > 0) { sb.append('\n'); } sb.append("物料 ").append(materialCode) .append(" 的工艺路线存在不兼容的加工车间(").append(deptDesc) .append("),涉及工段:").append(wcDesc) .append(";机加与装配可同物料混用,铆焊车间不得与其他车间混用"); } } private static void appendWorkCenterError(StringBuilder sb, int seq, Long processNo, String materialCode, String msg) { if (sb.length() > 0) { sb.append('\n'); } sb.append("第").append(seq).append("条工艺"); if (processNo != null) { sb.append(" 工序号=").append(processNo); } if (StringUtils.isNotBlank(materialCode)) { sb.append(" 物料=").append(materialCode); } sb.append(":").append(msg); } }