package cn.iocoder.yudao.module.mes.service.pro.route; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteCompleteImportWorkbook; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteImportRespVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.MesProRouteSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProRouteProcessSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.product.MesProRouteProductSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.productbom.MesProRouteProductBomSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.process.MesProProcessMapper; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.support.TransactionTemplate; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_IMPORT_LIST_IS_EMPTY; /** 按路线分事务批量导入完整配置;失败路线不会留下部分产品或工序。 */ @Service public class MesProRouteCompleteImportService { @Resource private TransactionTemplate transactionTemplate; @Resource private MesProRouteService routeService; @Resource private MesProRouteProductService productService; @Resource private MesProRouteProcessService processService; @Resource private MesProRouteProductBomService bomService; @Resource private MesMdItemService itemService; @Resource private MesProProcessMapper processMapper; @Resource private MesWmWarehouseService warehouseService; @Resource private MesWmWarehouseLocationService locationService; @Resource private MesWmWarehouseAreaService areaService; public MesProRouteImportRespVO importRoutes(MesProRouteCompleteImportWorkbook book) { if (book.getRoutes() == null || book.getRoutes().isEmpty()) { throw exception(PRO_ROUTE_IMPORT_LIST_IS_EMPTY); } MesProRouteImportRespVO result = MesProRouteImportRespVO.builder() .createCodes(new ArrayList<>()).updateCodes(new ArrayList<>()) .failureCodes(new LinkedHashMap<>()).build(); Set seen = new HashSet<>(); int rowNumber = 1; for (MesProRouteCompleteImportWorkbook.RouteRow row : book.getRoutes()) { rowNumber++; String code = trim(row.getRouteCode()); String key = StrUtil.isBlank(code) ? "路线表第 " + rowNumber + " 行" : code; if (StrUtil.isBlank(code) || StrUtil.isBlank(row.getName())) { result.getFailureCodes().put(key, "路线编码和名称不能为空"); continue; } if (!seen.add(code)) { result.getFailureCodes().put("路线表第 " + rowNumber + " 行(" + code + ")", "导入文件中路线编码重复"); continue; } try { transactionTemplate.execute(status -> { importOne(book, row, code); return null; }); result.getCreateCodes().add(code); } catch (RuntimeException ex) { result.getFailureCodes().put(key, ex.getMessage()); } } Set known = book.getRoutes().stream().map(r -> trim(r.getRouteCode())) .collect(Collectors.toSet()); collectUnknownRoutes(result.getFailureCodes(), known, "产品", book.getProducts() == null ? List.of() : book.getProducts().stream().map(MesProRouteCompleteImportWorkbook.ProductRow::getRouteCode).toList()); collectUnknownRoutes(result.getFailureCodes(), known, "工序", book.getProcesses() == null ? List.of() : book.getProcesses().stream().map(MesProRouteCompleteImportWorkbook.ProcessRow::getRouteCode).toList()); collectUnknownRoutes(result.getFailureCodes(), known, "工序产出", book.getOutputs() == null ? List.of() : book.getOutputs().stream().map(MesProRouteCompleteImportWorkbook.OutputRow::getRouteCode).toList()); collectUnknownRoutes(result.getFailureCodes(), known, "工序投料", book.getInputs() == null ? List.of() : book.getInputs().stream().map(MesProRouteCompleteImportWorkbook.InputRow::getRouteCode).toList()); return result; } private void importOne(MesProRouteCompleteImportWorkbook book, MesProRouteCompleteImportWorkbook.RouteRow row, String code) { List products = rows(book.getProducts()).stream() .filter(r -> code.equals(trim(r.getRouteCode()))).toList(); List processes = rows(book.getProcesses()).stream() .filter(r -> code.equals(trim(r.getRouteCode()))) .sorted(Comparator.comparing(MesProRouteCompleteImportWorkbook.ProcessRow::getSort, Comparator.nullsLast(Comparator.naturalOrder()))).toList(); if (products.isEmpty() || processes.isEmpty()) { throw new IllegalArgumentException("至少需要一条产品和一道工序"); } MesProRouteSaveReqVO route = new MesProRouteSaveReqVO(); route.setCode(code); route.setName(trim(row.getName())); route.setDescription(row.getDescription()); route.setRemark(row.getRemark()); Long routeId = routeService.createRoute(route); Set productCodes = new HashSet<>(); for (MesProRouteCompleteImportWorkbook.ProductRow product : products) { String itemCode = trim(product.getItemCode()); if (!productCodes.add(itemCode)) { throw new IllegalArgumentException("产品编码重复:" + itemCode); } MesProRouteProductSaveReqVO request = new MesProRouteProductSaveReqVO(); request.setRouteId(routeId); request.setItemId(itemId(itemCode)); MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(trim(product.getWarehouseCode())); MesWmWarehouseLocationDO location = locationService.getWarehouseLocationByCode(trim(product.getLocationCode())); MesWmWarehouseAreaDO area = areaService.getWarehouseAreaByCode(trim(product.getAreaCode())); if (warehouse == null || location == null || area == null) { throw new IllegalArgumentException("产品 " + itemCode + " 的仓库、库区或库位编码不存在"); } request.setWarehouseId(warehouse.getId()); request.setLocationId(location.getId()); request.setAreaId(area.getId()); request.setQuantity(product.getQuantity()); request.setProductionTime(product.getProductionTime()); request.setTimeUnitType(product.getTimeUnitType()); request.setRemark(product.getRemark()); productService.createRouteProduct(request); } Set processCodes = new HashSet<>(); Set sorts = new HashSet<>(); for (MesProRouteCompleteImportWorkbook.ProcessRow process : processes) { String processCode = trim(process.getProcessCode()); if (!processCodes.add(processCode) || process.getSort() == null || !sorts.add(process.getSort())) { throw new IllegalArgumentException("工序编码或排序重复/为空:" + processCode); } MesProProcessDO processDO = processMapper.selectByCode(processCode); if (processDO == null) { throw new IllegalArgumentException("工序编码不存在:" + processCode); } MesProRouteProcessSaveReqVO request = new MesProRouteProcessSaveReqVO(); request.setRouteId(routeId); request.setProcessId(processDO.getId()); request.setSort(process.getSort()); request.setParamTemplateId(process.getParamTemplateId()); request.setYieldRate(process.getYieldRate()); request.setLinkType(process.getLinkType()); request.setPrepareTime(process.getPrepareTime()); request.setWaitTime(process.getWaitTime()); request.setColorCode(process.getColorCode()); request.setKeyFlag(flag(process.getKeyFlag(), processCode, "关键工序")); request.setCheckFlag(flag(process.getCheckFlag(), processCode, "质检工序")); request.setBackflushFlag(flag(process.getBackflushFlag(), processCode, "倒冲")); request.setRemark(process.getRemark()); List outputs = new ArrayList<>(); for (MesProRouteCompleteImportWorkbook.OutputRow output : rows(book.getOutputs())) { if (!code.equals(trim(output.getRouteCode())) || !processCode.equals(trim(output.getProcessCode()))) { continue; } MesProRouteProcessSaveReqVO.OutputItem value = new MesProRouteProcessSaveReqVO.OutputItem(); value.setItemId(itemId(output.getItemCode())); if (StrUtil.isNotBlank(output.getProductCode())) { String ownerCode = trim(output.getProductCode()); if (!productCodes.contains(ownerCode)) { throw new IllegalArgumentException("产出归属产品未关联路线:" + ownerCode); } value.setProductId(itemId(ownerCode)); } value.setSort(output.getSort()); value.setRemark(output.getRemark()); outputs.add(value); } request.setOutputs(outputs); processService.createRouteProcess(request); } for (MesProRouteCompleteImportWorkbook.OutputRow output : rows(book.getOutputs())) { if (code.equals(trim(output.getRouteCode())) && !processCodes.contains(trim(output.getProcessCode()))) { throw new IllegalArgumentException("产出引用了未配置的工序:" + output.getProcessCode()); } } for (MesProRouteCompleteImportWorkbook.InputRow input : rows(book.getInputs())) { if (!code.equals(trim(input.getRouteCode()))) { continue; } String processCode = trim(input.getProcessCode()); String productCode = trim(input.getProductCode()); if (!processCodes.contains(processCode) || !productCodes.contains(productCode)) { throw new IllegalArgumentException("投料引用了未配置的工序或产品:" + processCode + "/" + productCode); } MesProRouteProductBomSaveReqVO request = new MesProRouteProductBomSaveReqVO(); request.setRouteId(routeId); request.setProcessId(processMapper.selectByCode(processCode).getId()); request.setProductId(itemId(productCode)); request.setItemId(itemId(input.getItemCode())); request.setQuantity(input.getQuantity()); request.setRemark(input.getRemark()); bomService.createRouteProductBom(request); } } private Long itemId(String code) { String normalized = trim(code); MesMdItemDO item = StrUtil.isBlank(normalized) ? null : itemService.getItemByCode(normalized); if (item == null) { throw new IllegalArgumentException("物料编码不存在:" + normalized); } return item.getId(); } private static boolean flag(String value, String processCode, String field) { String normalized = trim(value); if (StrUtil.isBlank(normalized) || "否".equals(normalized) || "false".equalsIgnoreCase(normalized) || "0".equals(normalized)) { return false; } if ("是".equals(normalized) || "true".equalsIgnoreCase(normalized) || "1".equals(normalized)) { return true; } throw new IllegalArgumentException("工序 " + processCode + " 的" + field + "只能填写是或否"); } private static String trim(String value) { return StrUtil.trim(value); } private static List rows(List values) { return values == null ? List.of() : values; } private static void collectUnknownRoutes(Map failures, Set known, String sheet, List codes) { for (String code : codes) { if (!known.contains(trim(code))) { failures.put(sheet + "表路线 " + code, "路线表中不存在此路线编码"); } } } }