| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.Comparator; |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | |
| | | @Service |
| | | @Validated |
| | | public class MesProRouteProcessServiceImpl implements MesProRouteProcessService { |
| | | |
| | | /** |
| | | * 产出排序:主产出优先,其次按 sort,最后按 id |
| | | */ |
| | | private static final Comparator<MesProRouteProcessOutputDO> OUTPUT_ORDER = Comparator |
| | | .comparing((MesProRouteProcessOutputDO o) -> Boolean.TRUE.equals(o.getMainFlag()), |
| | | Comparator.reverseOrder()) |
| | | .thenComparing(MesProRouteProcessOutputDO::getSort, Comparator.nullsLast(Comparator.naturalOrder())) |
| | | .thenComparing(MesProRouteProcessOutputDO::getId, Comparator.nullsLast(Comparator.naturalOrder())); |
| | | |
| | | @Resource |
| | | private MesProRouteProcessMapper routeProcessMapper; |
| | |
| | | } |
| | | String filtered = outputs.stream() |
| | | .filter(output -> output.getProductId() == null || output.getProductId().equals(productId)) |
| | | .sorted(OUTPUT_ORDER) |
| | | .map(MesProRouteProcessOutputDO::getItemId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | |
| | | } |
| | | return outputs.stream() |
| | | .filter(output -> output.getProductId() == null || output.getProductId().equals(productId)) |
| | | .sorted(OUTPUT_ORDER) |
| | | .map(MesProRouteProcessOutputDO::getItemId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesProRouteProcessOutputDO> getProcessOutputs(Long routeProcessId, Long productId) { |
| | | if (routeProcessId == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | List<MesProRouteProcessOutputDO> outputs = routeProcessOutputMapper.selectListByRouteProcessId(routeProcessId); |
| | | if (CollUtil.isEmpty(outputs)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | // 主产出优先排序,并按 itemId 去重 |
| | | Map<Long, MesProRouteProcessOutputDO> distinct = new LinkedHashMap<>(); |
| | | outputs.stream() |
| | | .filter(output -> output.getProductId() == null || output.getProductId().equals(productId)) |
| | | .filter(output -> output.getItemId() != null) |
| | | .sorted(OUTPUT_ORDER) |
| | | .forEach(output -> distinct.putIfAbsent(output.getItemId(), output)); |
| | | return new ArrayList<>(distinct.values()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | if (CollUtil.isNotEmpty(reqVO.getOutputs())) { |
| | | itemIds = reqVO.getOutputs().stream() |
| | | .filter(Objects::nonNull) |
| | | .sorted(Comparator.comparing(MesProRouteProcessSaveReqVO.OutputItem::getSort, |
| | | .sorted(Comparator |
| | | .comparing((MesProRouteProcessSaveReqVO.OutputItem o) -> Boolean.TRUE.equals(o.getMainFlag()), |
| | | Comparator.reverseOrder()) |
| | | .thenComparing(MesProRouteProcessSaveReqVO.OutputItem::getSort, |
| | | Comparator.nullsLast(Comparator.naturalOrder())) |
| | | .thenComparing(MesProRouteProcessSaveReqVO.OutputItem::getItemId, |
| | | Comparator.nullsLast(Comparator.naturalOrder()))) |
| | | .map(MesProRouteProcessSaveReqVO.OutputItem::getItemId) |
| | | .filter(Objects::nonNull) |
| | |
| | | Set<String> keys = new HashSet<>(); |
| | | int index = 0; |
| | | if (CollUtil.isNotEmpty(reqVO.getOutputs())) { |
| | | for (MesProRouteProcessSaveReqVO.OutputItem output : reqVO.getOutputs()) { |
| | | if (output == null || output.getItemId() == null) { |
| | | continue; |
| | | } |
| | | List<MesProRouteProcessSaveReqVO.OutputItem> items = reqVO.getOutputs().stream() |
| | | .filter(Objects::nonNull) |
| | | .filter(output -> output.getItemId() != null) |
| | | .collect(Collectors.toList()); |
| | | // 校验:同一归属产品分组内主产出唯一、配比合法 |
| | | validateOutputs(items); |
| | | for (MesProRouteProcessSaveReqVO.OutputItem output : items) { |
| | | index++; |
| | | if (!keys.add(output.getProductId() + "_" + output.getItemId())) { |
| | | continue; |
| | | } |
| | | boolean main = Boolean.TRUE.equals(output.getMainFlag()); |
| | | BigDecimal quantity = main ? BigDecimal.ONE : output.getQuantity(); |
| | | outputs.add(buildOutput(routeProcess, output.getProductId(), output.getItemId(), |
| | | output.getSort() != null ? output.getSort() : index, output.getRemark())); |
| | | output.getSort() != null ? output.getSort() : index, quantity, main, output.getRemark())); |
| | | } |
| | | } else { |
| | | // 兼容只传 outputItemIds 的调用方:产出是路线产品时归属到自身,否则视为通用 |
| | | // 兼容只传 outputItemIds 的调用方:产出是路线产品时归属到自身,否则视为通用; |
| | | // 每个归属产品分组的第一条默认作为主产出 |
| | | Set<Long> mainAssignedProducts = new HashSet<>(); |
| | | for (Long itemId : reqVO.getOutputItemIds()) { |
| | | if (itemId == null) { |
| | | continue; |
| | | } |
| | | index++; |
| | | Long productId = routeProductIds.contains(itemId) ? itemId : null; |
| | | if (!keys.add(productId + "_" + itemId)) { |
| | | continue; |
| | | } |
| | | outputs.add(buildOutput(routeProcess, productId, itemId, index, null)); |
| | | index++; |
| | | boolean main = mainAssignedProducts.add(productId); |
| | | outputs.add(buildOutput(routeProcess, productId, itemId, index, BigDecimal.ONE, main, null)); |
| | | } |
| | | } |
| | | if (CollUtil.isNotEmpty(outputs)) { |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 校验工序产出配置:同一归属产品分组内最多一个主产出、多个产出时必须指定主产出,非主产出配比必须大于 0 |
| | | */ |
| | | private void validateOutputs(List<MesProRouteProcessSaveReqVO.OutputItem> items) { |
| | | if (CollUtil.isEmpty(items)) { |
| | | return; |
| | | } |
| | | Map<Long, List<MesProRouteProcessSaveReqVO.OutputItem>> groupByProduct = new HashMap<>(); |
| | | for (MesProRouteProcessSaveReqVO.OutputItem item : items) { |
| | | groupByProduct.computeIfAbsent(item.getProductId(), key -> new ArrayList<>()).add(item); |
| | | } |
| | | for (List<MesProRouteProcessSaveReqVO.OutputItem> group : groupByProduct.values()) { |
| | | long mainCount = group.stream().filter(item -> Boolean.TRUE.equals(item.getMainFlag())).count(); |
| | | if (mainCount > 1) { |
| | | throw exception(PRO_ROUTE_PROCESS_OUTPUT_MAIN_DUPLICATE); |
| | | } |
| | | if (mainCount == 0 && group.size() > 1) { |
| | | throw exception(PRO_ROUTE_PROCESS_OUTPUT_MAIN_REQUIRED); |
| | | } |
| | | for (MesProRouteProcessSaveReqVO.OutputItem item : group) { |
| | | if (Boolean.TRUE.equals(item.getMainFlag())) { |
| | | continue; |
| | | } |
| | | // 未填配比默认按 1 处理,填了则必须大于 0 |
| | | if (item.getQuantity() != null && item.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw exception(PRO_ROUTE_PROCESS_OUTPUT_RATIO_INVALID); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private MesProRouteProcessOutputDO buildOutput(MesProRouteProcessDO routeProcess, Long productId, |
| | | Long itemId, Integer sort, String remark) { |
| | | Long itemId, Integer sort, BigDecimal quantity, |
| | | Boolean mainFlag, String remark) { |
| | | return MesProRouteProcessOutputDO.builder() |
| | | .routeProcessId(routeProcess.getId()) |
| | | .routeId(routeProcess.getRouteId()) |
| | | .processId(routeProcess.getProcessId()) |
| | | .productId(productId) |
| | | .itemId(itemId) |
| | | .quantity(mainFlag != null && mainFlag ? BigDecimal.ONE : quantity) |
| | | .mainFlag(Boolean.TRUE.equals(mainFlag)) |
| | | .sort(sort) |
| | | .remark(remark) |
| | | .build(); |