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.MesProRouteFullExcelVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.*; import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.process.MesProProcessMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.*; import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.*; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService; import java.util.*; 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_FULL_EXCEL_INVALID; /** 导出指定路线选中的模板产品及所需中间产出。 */ @Service public class MesProRouteFullExcelService { public static final String REFERENCE = "@模板产品"; @Resource private MesProRouteMapper routeMapper; @Resource private MesProRouteProductMapper productMapper; @Resource private MesProRouteProcessMapper processMapper; @Resource private MesProRouteProcessOutputMapper outputMapper; @Resource private MesProRouteProductBomMapper bomMapper; @Resource private MesMdItemMapper itemMapper; @Resource private MesMdItemTypeService itemTypeService; @Resource private MesProProcessMapper operationMapper; @Resource private MesWmWarehouseMapper warehouseMapper; @Resource private MesWmWarehouseLocationMapper locationMapper; @Resource private MesWmWarehouseAreaMapper areaMapper; @Transactional(readOnly = true) public List exportRouteReference(Long routeId) { MesProRouteDO selectedRoute = routeMapper.selectById(routeId); if (selectedRoute == null) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "工艺路线不存在"); List rows = new ArrayList<>(); for (MesProRouteDO route : List.of(selectedRoute)) { List allProducts = productMapper.selectListByRouteId(route.getId()); List refs = allProducts.stream().filter(p -> Boolean.TRUE.equals(p.getTemplateReference())).toList(); if (refs.size() > 1) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "路线 " + route.getCode() + " 的模板引用不唯一"); if (refs.isEmpty()) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "请先选择该路线的模板引用产品"); MesProRouteProductDO ref = refs.get(0); if (!isFinished(ref.getItemId())) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "路线 " + route.getCode() + " 的模板引用须选择成品"); List processes = processMapper.selectListByRouteId(route.getId()); List outputs = outputMapper.selectListByRouteId(route.getId()); Map> byProcess = outputs.stream().collect(Collectors.groupingBy(MesProRouteProcessOutputDO::getRouteProcessId)); Set needed = new LinkedHashSet<>(); needed.add(ref.getItemId()); for (MesProRouteProcessDO process : processes) { List part = byProcess.get(process.getId()); if (part != null && !part.isEmpty()) { part.stream().filter(o -> o.getProductId() == null || o.getProductId().equals(ref.getItemId())) .map(MesProRouteProcessOutputDO::getItemId).filter(Objects::nonNull).forEach(needed::add); } else if (StrUtil.isNotBlank(process.getOutputItemId())) { Set other = allProducts.stream().map(MesProRouteProductDO::getItemId) .filter(id -> !id.equals(ref.getItemId()) && isFinished(id)).collect(Collectors.toSet()); for (String id : process.getOutputItemId().split(",")) if (StrUtil.isNotBlank(id)) { Long itemId = Long.valueOf(id.trim()); if (!other.contains(itemId)) needed.add(itemId); } } } MesProRouteFullExcelVO header = row(route, "路线"); header.setRouteName(route.getName()); header.setRouteDescription(route.getDescription()); header.setRouteRemark(route.getRemark()); rows.add(header); for (MesProRouteProductDO product : allProducts) { if (!needed.contains(product.getItemId())) continue; var warehouse = product.getWarehouseId() == null ? null : warehouseMapper.selectById(product.getWarehouseId()); var location = product.getLocationId() == null ? null : locationMapper.selectById(product.getLocationId()); var area = product.getAreaId() == null ? null : areaMapper.selectById(product.getAreaId()); MesProRouteFullExcelVO r = row(route, "产品"); r.setProductCode(itemCode(product.getItemId())); r.setTemplateReference(product.getId().equals(ref.getId()) ? "是" : "否"); r.setWarehouseCode(warehouse == null ? null : warehouse.getCode()); r.setLocationCode(location == null ? null : location.getCode()); r.setAreaCode(area == null ? null : area.getCode()); r.setQuantity(product.getQuantity()); r.setProductionTime(product.getProductionTime()); r.setTimeUnitType(timeUnitName(product.getTimeUnitType())); r.setProductRemark(product.getRemark()); rows.add(r); } for (MesProRouteProcessDO process : processes) { var operation = operationMapper.selectById(process.getProcessId()); if (operation == null) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "路线 " + route.getCode() + " 的工序不存在"); MesProRouteFullExcelVO r = row(route, "工序"); r.setProcessCode(operation.getCode()); r.setProcessSort(process.getSort()); r.setLinkType(process.getLinkType()); r.setPrepareTime(process.getPrepareTime()); r.setWaitTime(process.getWaitTime()); r.setColorCode(process.getColorCode()); r.setKeyFlag(yn(process.getKeyFlag())); r.setCheckFlag(yn(process.getCheckFlag())); r.setBackflushFlag(yn(process.getBackflushFlag())); r.setProcessRemark(process.getRemark()); rows.add(r); List part = byProcess.get(process.getId()); if (part != null && !part.isEmpty()) { for (var out : part) { if (out.getProductId() != null && !out.getProductId().equals(ref.getItemId())) continue; MesProRouteFullExcelVO o = row(route, "产出"); o.setProcessCode(operation.getCode()); o.setProductCode(out.getProductId() == null ? "" : REFERENCE); o.setItemCode(Objects.equals(out.getItemId(), ref.getItemId()) ? REFERENCE : itemCode(out.getItemId())); o.setOutputSort(out.getSort()); o.setDetailRemark(out.getRemark()); rows.add(o); } } else if (StrUtil.isNotBlank(process.getOutputItemId())) { for (String id : process.getOutputItemId().split(",")) if (StrUtil.isNotBlank(id)) { Long itemId = Long.valueOf(id.trim()); if (!needed.contains(itemId)) continue; MesProRouteFullExcelVO o = row(route, "产出"); o.setProcessCode(operation.getCode()); o.setProductCode(itemId.equals(ref.getItemId()) ? REFERENCE : ""); o.setItemCode(itemId.equals(ref.getItemId()) ? REFERENCE : itemCode(itemId)); rows.add(o); } } } var boms = bomMapper.selectListByProductIds(route.getId(), needed); for (MesProRouteProductBomDO bom : boms) { var operation = operationMapper.selectById(bom.getProcessId()); if (operation == null) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "投料的工序不存在"); MesProRouteFullExcelVO r = row(route, "投料"); r.setProcessCode(operation.getCode()); r.setProductCode(bom.getProductId().equals(ref.getItemId()) ? REFERENCE : itemCode(bom.getProductId())); r.setItemCode(itemCode(bom.getItemId())); r.setBomQuantity(bom.getQuantity()); r.setDetailRemark(bom.getRemark()); rows.add(r); } } return rows; } private MesProRouteFullExcelVO row(MesProRouteDO route, String type) { MesProRouteFullExcelVO row = new MesProRouteFullExcelVO(); row.setRecordType(type); row.setRouteCode(route.getCode()); return row; } private String itemCode(Long id) { MesMdItemDO item = itemMapper.selectById(id); if (item == null) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "引用的物料不存在:" + id); return item.getCode(); } private boolean isFinished(Long itemId) { MesMdItemDO item = itemMapper.selectById(itemId); if (item == null) throw exception(PRO_ROUTE_FULL_EXCEL_INVALID, "引用的物料不存在:" + itemId); var category = itemTypeService.getItemType(item.getItemTypeId()); Set seen = new HashSet<>(); while (category != null && seen.add(category.getId())) { if ("FG".equals(category.getCode())) return true; if (category.getParentId() == null || category.getParentId() == 0L) break; category = itemTypeService.getItemType(category.getParentId()); } return false; } private String timeUnitName(String value) { if (value == null) return null; return switch (value) { case "MINUTE" -> "分钟"; case "HOUR" -> "小时"; case "DAY" -> "天"; default -> value; }; } private String yn(Boolean value) { return Boolean.TRUE.equals(value) ? "是" : "否"; } }