evoToK3Cloud/ruoyi-system/src/main/java/com/ruoyi/system/controller/ProcessRouteController.java
2026-10-27 17:37:34 +08:00

1476 lines
73 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<String> viewGetBomUploadStatus(@RequestParam String rooteProdet) {
return iProcessRouteService.viewGetBomUploadStatus(rooteProdet);
}
/**
* 主动更新可用库存
*/
@SaCheckPermission("system:route:list")
@GetMapping("/list2")
public TableDataInfo<ProcessRouteVo> list2(ProcessRouteBo bo, PageQuery pageQuery) {
try {
// 查询工艺路线数据
TableDataInfo<ProcessRouteVo> processRouteVoTableDataInfo = iProcessRouteService.queryPageList2(bo, pageQuery);
// 用于存储父子关系的Map,key为"物料编码_物料名称",值为父级ProcessRouteVo
Map<String, ProcessRouteVo> routeMap = new HashMap<>();
// 存储顶级列表
List<ProcessRouteVo> topLevelList = new ArrayList<>();
// 获取查询返回的行数据
List<ProcessRouteVo> 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<ProcessRouteVo> list(ProcessRouteBo bo, PageQuery pageQuery) {
try {
// 查询工艺路线数据
TableDataInfo<ProcessRouteVo> processRouteVoTableDataInfo = iProcessRouteService.queryPageList(bo, pageQuery);
// 用于存储父子关系的Map,key为"物料编码_物料名称",值为父级ProcessRouteVo
Map<String, ProcessRouteVo> routeMap = new HashMap<>();
// 存储顶级列表
List<ProcessRouteVo> topLevelList = new ArrayList<>();
// 获取查询返回的行数据
List<ProcessRouteVo> 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<ProcessRoute> list = iProcessRouteService.queryList(bo);
List<MaterialUsageDTO2> 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<ProcessRoute> listener = new DefaultExcelListener<>(true);
EasyExcel.read(ExcelName, ProcessRoute.class, listener).sheet(6).headRowNumber(3).doRead();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
// 查询要写入的数据是否
List<ProcessRoute> 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<ProcessRouteVo> 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<Void> 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<Void> 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<Void> 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<ProcessRouteVo> result = ExcelUtil.importExcelSheet6(file.getInputStream(), ProcessRouteVo.class, true);
List<ProcessRouteVo> list1 = result.getList();
// 读取总装部分sheet
ExcelResult<ProductionOrderVo> result1 = ExcelUtil.importExcelSheet1(file.getInputStream(), ProductionOrderVo.class, true);
List<ProductionOrderVo> 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> bomDetails = iBomDetailsService.selectByProjectNumber(productionOrderNo);
List<ProcessRoute> 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<String, String> 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<ProcessRouteVo> routes,
List<ProductionOrderVo> products) {
LinkedHashSet<String> 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<ProcessRouteVo> 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<String, Object> 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<String, Object> 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<ProcessRoute> pushGroupWeldments(@RequestBody ProcessRoute processRoute) {
return iProcessRouteService.pushGroupWeldments(processRoute);
}
/**
* 查询需要单独生成BOm 的原材料集合
*/
@Log(title = "查询需要单独生成BOM的原材料集合")
@SaCheckPermission("system:route:getRawBom")
@PostMapping("/getRawBom")
public List<ProcessRoute> getRawBom(@RequestParam String rooteProdet) {
return iProcessRouteService.getRawBom(rooteProdet);
}
/**
* 查询工艺表中所有的需要做工艺的物料
*
* @return RequestBody List<ProcessRouteVo> routeVoList
*/
public List<ProcessRoute> getProcessRouteList(@RequestParam String rooteProdet) {
List<ProcessRoute> list = iProcessRouteService.pushRawMater(rooteProdet);
return list;
}
@Log(title = "查询工艺工序集合")
@SaCheckPermission("system:route:getProcessRoute")
@PostMapping("/getProcessRoute")
public List<ProcessRouteXuDTO> getProcessRoute(@RequestParam String rooteProdet) {
return iProcessRouteService.getProcessRoute(rooteProdet);
}
@Log(title = "推送工艺工序")
@SaCheckPermission("system:route:pushRouteBom")
@PostMapping("/pushRouteBom")
public R<ProcessRoutePushResultDTO> 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<ProcessRoutePushResultDTO> 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<String> 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<Void> approveRoute(@RequestParam String rooteProdet) {
return processOrderProService.approveRoute(rooteProdet);
}
/**
* 查询项目列表
*/
@Log(title = "获取所有的项目令号")
@SaCheckPermission("system:route:getDistinctProjectCodes")
@GetMapping("/getDistinctProjectCodes")
public ResponseEntity<List<String>> getDistinctProjectCodes(String query) {
return ResponseEntity.ok(iProcessRouteService.getDistinctProjectCodes(query));
}
@Log(title = "获取工序列表")
@SaCheckPermission("system:route:getProcessInfoList")
@GetMapping("/getProcessInfoList")
public ResponseEntity<List<String>> getProcessInfoList(String query) {
return ResponseEntity.ok(iProcessRouteService.getProcessInfoList(query));
}
@Log(title = "根据生产令号获取生产编号==》获取计划编号")
@SaCheckPermission("system:route:getSelecPlanRouteList")
@PostMapping("/getSelecPlanRouteList")
public List<CombinedDTO> 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<Model> updateProcessPlan(@RequestParam String rooteProdet) throws Exception {
return iProcessRouteService.updateProcessPlan(rooteProdet);
}
/**
* 按车间+物料默认条件拉取工序计划,回填工艺时间,保存时部门统一为 018(与 {@link OperationPlanningWorkshopUpdateService} 一致)。
*/
@Log(title = "按车间物料更新工序计划(默认条件)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateProcessPlanByWorkshop")
public R<List<Model>> updateProcessPlanByWorkshop() throws Exception {
return R.ok(operationPlanningWorkshopUpdateService.updateProcessPlanByWorkshopFilter());
}
/**
* 同上,BillQuery 过滤条件可自定义(部门 Compare/Value、物料 Compare/Value)。
*/
@Log(title = "按车间物料更新工序计划(自定义条件)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateProcessPlanByWorkshopCustom")
public R<List<Model>> 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<List<Model>> updateOperationReportByWorkshop() throws Exception {
return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter());
}
@Log(title = "按车间物料更新工序汇报单(固定条件67/007/17/-MP)")
@SaCheckPermission("system:route:updateProcessPlan")
@PostMapping("/updateOperationReportByWorkshopCustom")
public R<List<Model>> updateOperationReportByWorkshopCustom() throws Exception {
return R.ok(operationReportWorkshopUpdateService.updateOperationReportByWorkshopFilter(
"67", "007", "17", "-MP"));
}
/**
* 更新生产订单的时间
*/
@Log(title = "更新生产订单时间")
@SaCheckPermission("system:route:updateProcesTime")
@PostMapping("/updateProcesTime")
public List<Model> updateProcesTime(@RequestParam String rooteProdet) throws Exception {
return iProcessRouteService.updateProcesTime(rooteProdet);
}
/**
* 保存工艺路线
*/
@SaCheckPermission("system:route:addRoute")
@Log(title = "保存工艺路线", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/addRoute")
public R<Void> addRoute(@RequestBody List<ProcessRouteVo> list) {
return iProcessRouteService.addRoute(list);
}
@SaCheckPermission("system:route:deleteRoute")
@Log(title = "删除工艺路线", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/deleteRoute")
public List<ProcessRoute> deleteRoute(@RequestBody ProcessRoute processRoute) {
return iProcessRouteService.deleteRoute(processRoute);
}
@Log(title = "获取材料bom列表")
@SaCheckPermission("system:route:getBomInfo")
@PostMapping("/getBomInfo")
public ResponseEntity<List<MaterialBom>> 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<List<ProcessRouteJdDTO>> 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<Void> importDataTime(@RequestParam("file") MultipartFile file) throws Exception {
String originalFilename = file.getOriginalFilename();
log.info("读取文件名: " + originalFilename);
ExcelResult<ProcessRouteVo> result = ExcelUtil.importExcelSheet6(file.getInputStream(), ProcessRouteVo.class, true);
List<ProcessRouteVo> 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<Void> importDataTime123(@RequestParam("file") MultipartFile file) throws Exception {
DefaultExcelListener<PcRigidChainVo> listener = new DefaultExcelListener<>(true);
EasyExcel.read(file.getInputStream(), PcRigidChainVo.class, listener).excelType(ExcelTypeEnum.XLS).sheet(1).headRowNumber(4).doRead();
List<PcRigidChainVo> 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<ProcessRouteSelectDTO> getSelectProcessRoute1(@RequestParam String materilCode) {
return iProcessRouteService.getSelectProcessRoute(materilCode);
}
private void processCombinedDTO(CombinedDTO combinedDTO, List<BomDetails> bomDetailsList, List<ProcessRoute> routeList, List<MaterialBom> 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<BomDetails> bomDetailsList, List<ProcessRoute> routeList, List<MaterialBom> 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<MaterialUseDTO> 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<String> getKindeeExcel(String rooteProdet) throws IOException {
List<CombinedDTO> planRouteList = iProcessRouteService.getSelecPlanRouteList(rooteProdet);
List<BomDetails> bomDetailsList = new ArrayList<>();
List<ProcessRoute> routeList = new ArrayList<>();
List<MaterialBom> 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<ProcessRoute> 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<MaterialUsageDTO> 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<String> 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<ProcessRouteGetDTO> listener = new DefaultExcelListener<>(true);
EasyExcel.read(ExcelName, ProcessRouteGetDTO.class, listener).sheet(6).headRowNumber(3).doRead();
List<ProcessRouteGetDTO> list = listener.getExcelResult().getList();
List<JDMaterialAndRoute> list1 = iProcessRouteService.getProcessRouteGD(list, rooteProdet);
List<BomDetails> bomDetailsList = new ArrayList<>();
List<ProcessRoute> routeList = new ArrayList<>();
List<MaterialBom> 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<String> 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<String> list = iProcessRouteService.updateCgOrders(rooteProdet, cangKuNum);
return R.ok(list);
}
/**
* 导入前校验「工作中心(加工车间)」:工段合法;
* 同一物料下机加(006)与装配(007)可混用,铆焊(018)不得与其他车间混用。
*/
private static String validateImportWorkCenters(List<ProcessRouteVo> 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<ProcessRouteVo> 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<ProcessRouteVo> list, StringBuilder sb) {
Map<String, LinkedHashSet<String>> materialToDepts = new LinkedHashMap<>();
Map<String, LinkedHashSet<String>> 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<String, LinkedHashSet<String>> 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);
}
}