package com.ruoyi.production.service.impl;
|
|
import cn.hutool.core.bean.BeanUtil;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.production.bean.dto.ProductionOrderDto;
|
import com.ruoyi.production.bean.vo.ProductionOrderVo;
|
import com.ruoyi.production.mapper.ProductionBomStructureMapper;
|
import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
|
import com.ruoyi.production.mapper.ProductionOrderBomMapper;
|
import com.ruoyi.production.mapper.ProductionOrderMapper;
|
import com.ruoyi.production.mapper.ProductionOrderPickMapper;
|
import com.ruoyi.production.mapper.ProductionOrderPickRecordMapper;
|
import com.ruoyi.production.mapper.ProductionOrderRoutingMapper;
|
import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper;
|
import com.ruoyi.production.mapper.ProductionOrderRoutingOperationParamMapper;
|
import com.ruoyi.production.mapper.ProductionPlanMapper;
|
import com.ruoyi.production.mapper.ProductionProductMainMapper;
|
import com.ruoyi.production.pojo.ProductionOrderPick;
|
import com.ruoyi.production.pojo.ProductionOrderPickRecord;
|
import com.ruoyi.production.pojo.ProductionBomStructure;
|
import com.ruoyi.production.pojo.ProductionOperationTask;
|
import com.ruoyi.production.pojo.ProductionOrder;
|
import com.ruoyi.production.pojo.ProductionOrderBom;
|
import com.ruoyi.production.pojo.ProductionOrderRouting;
|
import com.ruoyi.production.pojo.ProductionOrderRoutingOperation;
|
import com.ruoyi.production.pojo.ProductionOrderRoutingOperationParam;
|
import com.ruoyi.production.pojo.ProductionPlan;
|
import com.ruoyi.production.pojo.ProductionProductMain;
|
import com.ruoyi.production.enums.ProductOrderStatusEnum;
|
import com.ruoyi.production.service.ProductionOrderService;
|
import com.ruoyi.technology.mapper.TechnologyBomMapper;
|
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
|
import com.ruoyi.technology.mapper.TechnologyRoutingMapper;
|
import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper;
|
import com.ruoyi.technology.mapper.TechnologyRoutingOperationParamMapper;
|
import com.ruoyi.technology.pojo.TechnologyBom;
|
import com.ruoyi.technology.pojo.TechnologyBomStructure;
|
import com.ruoyi.technology.pojo.TechnologyRouting;
|
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
|
import com.ruoyi.technology.pojo.TechnologyRoutingOperationParam;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.ArrayList;
|
import java.util.Comparator;
|
import java.util.HashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
@Service
|
@Transactional(rollbackFor = Exception.class)
|
@RequiredArgsConstructor
|
public class ProductionOrderServiceImpl extends ServiceImpl<ProductionOrderMapper, ProductionOrder> implements ProductionOrderService {
|
|
private final ProductionOrderRoutingMapper productionOrderRoutingMapper;
|
private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
|
private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper;
|
private final ProductionOperationTaskMapper productionOperationTaskMapper;
|
private final ProductionOrderBomMapper productionOrderBomMapper;
|
private final ProductionBomStructureMapper productionBomStructureMapper;
|
private final ProductionProductMainMapper productionProductMainMapper;
|
private final ProductionOrderPickMapper productionOrderPickMapper;
|
private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
|
private final ProductionPlanMapper productionPlanMapper;
|
private final TechnologyRoutingMapper technologyRoutingMapper;
|
private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
|
private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper;
|
private final TechnologyBomMapper technologyBomMapper;
|
private final TechnologyBomStructureMapper technologyBomStructureMapper;
|
|
@Override
|
public com.baomidou.mybatisplus.core.metadata.IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) {
|
Page<ProductionOrder> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
return this.page(entityPage, buildQueryWrapper(dto)).convert(item -> BeanUtil.copyProperties(item, ProductionOrderVo.class));
|
}
|
|
@Override
|
public List<ProductionOrderVo> listProductionOrder(ProductionOrderDto dto) {
|
return BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOrderVo.class);
|
}
|
|
@Override
|
public ProductionOrderVo getProductionOrderInfo(Long id) {
|
ProductionOrder item = this.getById(id);
|
return item == null ? null : BeanUtil.copyProperties(item, ProductionOrderVo.class);
|
}
|
|
@Override
|
public boolean saveProductionOrder(ProductionOrder productionOrder) {
|
ProductionOrder oldOrder = productionOrder.getId() == null ? null : this.getById(productionOrder.getId());
|
// 下单入口统一补齐来源单据、计划和工艺信息,避免前端分别传多套字段。
|
validateAndFillOrder(productionOrder, oldOrder);
|
if (productionOrder.getNpsNo() == null || productionOrder.getNpsNo().trim().isEmpty()) {
|
productionOrder.setNpsNo(generateNextOrderNo());
|
}
|
if (productionOrder.getCompleteQuantity() == null) {
|
productionOrder.setCompleteQuantity(BigDecimal.ZERO);
|
}
|
if (productionOrder.getStatus() == null) {
|
productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode());
|
}
|
boolean saved = this.saveOrUpdate(productionOrder);
|
if (!saved) {
|
return false;
|
}
|
syncProductionPlanIssueStatus(oldOrder, productionOrder);
|
boolean needSync = productionOrder.getTechnologyRoutingId() != null
|
&& (oldOrder == null
|
|| !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId())
|
|| !Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId())
|
|| compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0
|
|| productionOrderRoutingMapper.selectCount(Wrappers.<ProductionOrderRouting>lambdaQuery()
|
.eq(ProductionOrderRouting::getProductionOrderId, productionOrder.getId())) == 0);
|
if (needSync) {
|
// 工艺、产品或数量变化后,订单快照必须和当前下单数据重新对齐。
|
syncProductionOrderSnapshot(productionOrder.getId());
|
} else {
|
// 未重建快照时,也要确保备料主单和订单数量保持同步。
|
upsertOrderPick(productionOrder);
|
}
|
return true;
|
}
|
|
@Override
|
public boolean removeProductionOrder(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return false;
|
}
|
for (Long id : ids) {
|
ProductionOrder productionOrder = this.getById(id);
|
clearProductionSnapshot(id);
|
releaseProductionPlanIssueStatus(productionOrder);
|
}
|
return this.removeByIds(ids);
|
}
|
|
@Override
|
public int syncProductionOrderSnapshot(Long productionOrderId) {
|
ProductionOrder productionOrder = this.getById(productionOrderId);
|
if (productionOrder == null) {
|
throw new ServiceException("Production order not found");
|
}
|
if (productionOrder.getTechnologyRoutingId() == null) {
|
throw new ServiceException("technologyRoutingId is required");
|
}
|
TechnologyRouting technologyRouting = technologyRoutingMapper.selectById(productionOrder.getTechnologyRoutingId());
|
if (technologyRouting == null) {
|
throw new ServiceException("Technology routing not found");
|
}
|
// 订单快照按“先清后建”处理,保证工艺路线、工序、参数、BOM 全部来自同一版本。
|
clearProductionSnapshot(productionOrderId);
|
|
ProductionOrderRouting orderRouting = new ProductionOrderRouting();
|
orderRouting.setProductionOrderId(productionOrder.getId());
|
orderRouting.setTechnologyRoutingId(technologyRouting.getId());
|
orderRouting.setProductModelId(technologyRouting.getProductModelId());
|
orderRouting.setProcessRouteCode(technologyRouting.getProcessRouteCode());
|
orderRouting.setDescription(technologyRouting.getDescription());
|
orderRouting.setBomId(technologyRouting.getBomId());
|
productionOrderRoutingMapper.insert(orderRouting);
|
|
int syncedParamCount = 0;
|
List<TechnologyRoutingOperation> routingOperations = technologyRoutingOperationMapper.selectList(
|
Wrappers.<TechnologyRoutingOperation>lambdaQuery()
|
.eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())
|
.orderByAsc(TechnologyRoutingOperation::getDragSort)
|
.orderByAsc(TechnologyRoutingOperation::getId));
|
for (TechnologyRoutingOperation sourceOperation : routingOperations) {
|
// 订单工序保存的是工艺工序快照,后续报工只依赖快照,不再直接引用工艺主数据。
|
ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation();
|
targetOperation.setProductionOrderId(productionOrder.getId());
|
targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId());
|
targetOperation.setTechnologyRoutingId(orderRouting.getId());
|
targetOperation.setProductModelId(sourceOperation.getProductModelId());
|
targetOperation.setDragSort(sourceOperation.getDragSort());
|
targetOperation.setIsQuality(sourceOperation.getIsQuality());
|
productionOrderRoutingOperationMapper.insert(targetOperation);
|
|
ProductionOperationTask task = new ProductionOperationTask();
|
task.setTechnologyRoutingOperationId(targetOperation.getId());
|
task.setProductionOrderId(productionOrder.getId());
|
task.setPlanQuantity(defaultDecimal(productionOrder.getQuantity()));
|
task.setCompleteQuantity(BigDecimal.ZERO);
|
task.setWorkOrderNo(generateNextTaskNo());
|
task.setStatus(2);
|
productionOperationTaskMapper.insert(task);
|
|
List<TechnologyRoutingOperationParam> sourceParams = technologyRoutingOperationParamMapper.selectList(
|
Wrappers.<TechnologyRoutingOperationParam>lambdaQuery()
|
.eq(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, sourceOperation.getId())
|
.orderByAsc(TechnologyRoutingOperationParam::getId));
|
for (TechnologyRoutingOperationParam sourceParam : sourceParams) {
|
// 工序执行参数同样做快照,避免工艺参数调整影响已下达订单。
|
ProductionOrderRoutingOperationParam targetParam = new ProductionOrderRoutingOperationParam();
|
targetParam.setProductionOrderId(productionOrder.getId());
|
targetParam.setTechnologyRoutingOperationId(targetOperation.getId());
|
targetParam.setTechnologyRoutingOperationParamId(sourceParam.getId());
|
targetParam.setParamId(sourceParam.getParamId());
|
targetParam.setTechnologyOperationId(sourceParam.getTechnologyOperationId());
|
targetParam.setTechnologyOperationParamId(sourceParam.getTechnologyOperationParamId());
|
targetParam.setParamCode(sourceParam.getParamCode());
|
targetParam.setParamName(sourceParam.getParamName());
|
targetParam.setParamType(sourceParam.getParamType());
|
targetParam.setParamFormat(sourceParam.getParamFormat());
|
targetParam.setUnit(sourceParam.getUnit());
|
targetParam.setIsRequired(sourceParam.getIsRequired());
|
targetParam.setRemark(sourceParam.getRemark());
|
targetParam.setStandardValue(sourceParam.getStandardValue());
|
productionOrderRoutingOperationParamMapper.insert(targetParam);
|
syncedParamCount++;
|
}
|
}
|
|
syncProductionOrderBomSnapshot(productionOrder, technologyRouting);
|
upsertOrderPick(productionOrder);
|
return syncedParamCount;
|
}
|
|
private void syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) {
|
if (technologyRouting.getBomId() == null) {
|
return;
|
}
|
TechnologyBom technologyBom = technologyBomMapper.selectById(technologyRouting.getBomId());
|
if (technologyBom == null) {
|
throw new ServiceException("Technology BOM not found");
|
}
|
List<TechnologyBomStructure> structureList = technologyBomStructureMapper.selectList(
|
Wrappers.<TechnologyBomStructure>lambdaQuery()
|
.eq(TechnologyBomStructure::getBomId, technologyBom.getId())
|
.orderByAsc(TechnologyBomStructure::getId));
|
TechnologyBomStructure root = structureList.stream().filter(item -> item.getParentId() == null).findFirst().orElse(null);
|
BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity());
|
|
ProductionOrderBom orderBom = new ProductionOrderBom();
|
orderBom.setProductionOrderId(productionOrder.getId());
|
orderBom.setBomId(Long.valueOf(technologyBom.getId()));
|
orderBom.setProductModelId(root != null ? root.getProductModelId() : productionOrder.getProductModelId());
|
orderBom.setTechnologyOperationId(root == null ? null : root.getOperationId());
|
orderBom.setUnitQuantity(root != null && root.getUnitQuantity() != null ? root.getUnitQuantity() : BigDecimal.ONE);
|
orderBom.setDemandedQuantity(orderQuantity);
|
orderBom.setUnit(root == null ? null : root.getUnit());
|
productionOrderBomMapper.insert(orderBom);
|
|
Map<Long, Long> idMap = new HashMap<>();
|
for (TechnologyBomStructure source : structureList) {
|
// 子节点 parentId 需要映射成新快照节点 id,才能保留原始 BOM 层级。
|
ProductionBomStructure target = new ProductionBomStructure();
|
target.setProductionOrderId(productionOrder.getId());
|
target.setProductionOrderBomId(orderBom.getId());
|
target.setParentId(source.getParentId() == null ? null : idMap.get(source.getParentId()));
|
target.setProductModelId(source.getProductModelId());
|
target.setTechnologyOperationId(source.getOperationId());
|
target.setUnitQuantity(source.getUnitQuantity());
|
target.setDemandedQuantity(resolveBomDemandQuantity(source, orderQuantity));
|
target.setUnit(source.getUnit());
|
productionBomStructureMapper.insert(target);
|
idMap.put(source.getId(), target.getId());
|
}
|
}
|
|
private void clearProductionSnapshot(Long productionOrderId) {
|
// 已产生领料记录后禁止重建,避免备料/投料依据与订单快照脱节。
|
boolean hasPickRecord = productionOrderPickRecordMapper.selectCount(
|
Wrappers.<ProductionOrderPickRecord>lambdaQuery()
|
.eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0;
|
if (hasPickRecord) {
|
throw new ServiceException("Production order pick records already exist, snapshot cannot be regenerated");
|
}
|
List<Long> taskIds = productionOperationTaskMapper.selectList(
|
Wrappers.<ProductionOperationTask>lambdaQuery()
|
.eq(ProductionOperationTask::getProductionOrderId, productionOrderId))
|
.stream().map(ProductionOperationTask::getId).collect(Collectors.toList());
|
if (!taskIds.isEmpty()) {
|
// 已有报工记录说明订单已开工,此时不允许再重建快照。
|
boolean started = productionProductMainMapper.selectCount(
|
Wrappers.<ProductionProductMain>lambdaQuery()
|
.in(ProductionProductMain::getProductionOperationTaskId, taskIds)) > 0;
|
if (started) {
|
throw new ServiceException("Production order already started, snapshot cannot be regenerated");
|
}
|
productionOperationTaskMapper.delete(Wrappers.<ProductionOperationTask>lambdaQuery()
|
.eq(ProductionOperationTask::getProductionOrderId, productionOrderId));
|
}
|
productionOrderRoutingOperationParamMapper.delete(Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
|
.eq(ProductionOrderRoutingOperationParam::getProductionOrderId, productionOrderId));
|
productionOrderRoutingOperationMapper.delete(Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
|
.eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId));
|
productionOrderRoutingMapper.delete(Wrappers.<ProductionOrderRouting>lambdaQuery()
|
.eq(ProductionOrderRouting::getProductionOrderId, productionOrderId));
|
productionBomStructureMapper.delete(Wrappers.<ProductionBomStructure>lambdaQuery()
|
.eq(ProductionBomStructure::getProductionOrderId, productionOrderId));
|
productionOrderBomMapper.delete(Wrappers.<ProductionOrderBom>lambdaQuery()
|
.eq(ProductionOrderBom::getProductionOrderId, productionOrderId));
|
productionOrderPickMapper.delete(Wrappers.<ProductionOrderPick>lambdaQuery()
|
.eq(ProductionOrderPick::getProductionOrderId, productionOrderId));
|
}
|
|
private LambdaQueryWrapper<ProductionOrder> buildQueryWrapper(ProductionOrderDto dto) {
|
ProductionOrder query = dto == null ? new ProductionOrder() : dto;
|
return Wrappers.<ProductionOrder>lambdaQuery()
|
.eq(query.getId() != null, ProductionOrder::getId, query.getId())
|
.eq(query.getProductModelId() != null, ProductionOrder::getProductModelId, query.getProductModelId())
|
.eq(query.getTechnologyRoutingId() != null, ProductionOrder::getTechnologyRoutingId, query.getTechnologyRoutingId())
|
.like(query.getNpsNo() != null && !query.getNpsNo().trim().isEmpty(), ProductionOrder::getNpsNo, query.getNpsNo())
|
.orderByDesc(ProductionOrder::getId);
|
}
|
|
private String generateNextOrderNo() {
|
String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
String prefix = "SC" + datePrefix;
|
ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery()
|
.likeRight(ProductionOrder::getNpsNo, prefix)
|
.orderByDesc(ProductionOrder::getNpsNo)
|
.last("limit 1"));
|
int sequence = 1;
|
if (latestOrder != null && latestOrder.getNpsNo() != null && latestOrder.getNpsNo().startsWith(prefix)) {
|
try {
|
sequence = Integer.parseInt(latestOrder.getNpsNo().substring(prefix.length())) + 1;
|
} catch (NumberFormatException ignored) {
|
sequence = 1;
|
}
|
}
|
return prefix + String.format("%04d", sequence);
|
}
|
|
private String generateNextTaskNo() {
|
String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
String prefix = "GD" + datePrefix;
|
ProductionOperationTask lastTask = productionOperationTaskMapper.selectOne(
|
Wrappers.<ProductionOperationTask>lambdaQuery()
|
.likeRight(ProductionOperationTask::getWorkOrderNo, prefix)
|
.orderByDesc(ProductionOperationTask::getWorkOrderNo)
|
.last("limit 1"));
|
int sequence = 1;
|
if (lastTask != null && lastTask.getWorkOrderNo() != null && lastTask.getWorkOrderNo().startsWith(prefix)) {
|
try {
|
sequence = Integer.parseInt(lastTask.getWorkOrderNo().substring(prefix.length())) + 1;
|
} catch (NumberFormatException ignored) {
|
sequence = 1;
|
}
|
}
|
return prefix + String.format("%03d", sequence);
|
}
|
|
private BigDecimal defaultDecimal(BigDecimal value) {
|
return value == null ? BigDecimal.ZERO : value;
|
}
|
|
private void validateAndFillOrder(ProductionOrder productionOrder, ProductionOrder oldOrder) {
|
if (productionOrder == null) {
|
throw new ServiceException("Production order is required");
|
}
|
fillFromProductionPlans(productionOrder);
|
if (productionOrder.getProductModelId() == null) {
|
throw new ServiceException("productModelId is required when manually creating a production order");
|
}
|
if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
|
throw new ServiceException("quantity must be greater than 0");
|
}
|
if (productionOrder.getTechnologyRoutingId() == null) {
|
// 未显式指定工艺路线时,按产品规格选最新一条工艺作为默认路线。
|
TechnologyRouting technologyRouting = technologyRoutingMapper.selectOne(
|
Wrappers.<TechnologyRouting>lambdaQuery()
|
.eq(TechnologyRouting::getProductModelId, productionOrder.getProductModelId())
|
.orderByDesc(TechnologyRouting::getId)
|
.last("limit 1"));
|
if (technologyRouting == null) {
|
throw new ServiceException("No technology routing found for the product model");
|
}
|
productionOrder.setTechnologyRoutingId(technologyRouting.getId());
|
}
|
if (oldOrder != null && ProductOrderStatusEnum.isStarted(oldOrder.getStatus())) {
|
// 开工后只允许修正非核心字段,核心生产依据锁定。
|
if (!Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId())
|
|| !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId())
|
|| compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0) {
|
throw new ServiceException("Started production orders cannot modify product, routing or quantity");
|
}
|
}
|
}
|
|
private void fillFromProductionPlans(ProductionOrder productionOrder) {
|
List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
|
if (planIds.isEmpty()) {
|
return;
|
}
|
// 多计划合并转单时,所有计划必须属于同一规格,且只能下发一次。
|
List<ProductionPlan> productionPlans = productionPlanMapper.selectBatchIds(planIds);
|
if (productionPlans.size() != planIds.size()) {
|
throw new ServiceException("Some production plans do not exist");
|
}
|
Set<Long> productModelIds = productionPlans.stream()
|
.map(ProductionPlan::getProductModelId)
|
.collect(Collectors.toSet());
|
if (productModelIds.size() > 1) {
|
throw new ServiceException("Selected production plans must belong to the same product model");
|
}
|
if (Boolean.TRUE.equals(productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued())))) {
|
throw new ServiceException("Selected production plans already issued");
|
}
|
ProductionPlan firstPlan = productionPlans.get(0);
|
if (productionOrder.getProductModelId() == null) {
|
productionOrder.setProductModelId(firstPlan.getProductModelId());
|
} else if (!Objects.equals(productionOrder.getProductModelId(), firstPlan.getProductModelId())) {
|
throw new ServiceException("productModelId does not match the production plans");
|
}
|
if (productionOrder.getQuantity() == null || productionOrder.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
|
productionOrder.setQuantity(productionPlans.stream()
|
.map(ProductionPlan::getQtyRequired)
|
.reduce(BigDecimal.ZERO, BigDecimal::add));
|
}
|
if (productionOrder.getPlanCompleteTime() == null) {
|
LocalDate planCompleteTime = productionPlans.stream()
|
.map(this::resolvePlanCompleteDate)
|
.filter(Objects::nonNull)
|
.min(Comparator.naturalOrder())
|
.orElse(null);
|
productionOrder.setPlanCompleteTime(planCompleteTime);
|
}
|
productionOrder.setProductionPlanIds(formatPlanIds(planIds));
|
}
|
|
private void syncProductionPlanIssueStatus(ProductionOrder oldOrder, ProductionOrder newOrder) {
|
// 只处理本次增量变化,避免无关计划被重复写状态。
|
Set<Long> oldIds = new LinkedHashSet<>(parsePlanIds(oldOrder == null ? null : oldOrder.getProductionPlanIds()));
|
Set<Long> newIds = new LinkedHashSet<>(parsePlanIds(newOrder == null ? null : newOrder.getProductionPlanIds()));
|
Set<Long> toRelease = new LinkedHashSet<>(oldIds);
|
toRelease.removeAll(newIds);
|
Set<Long> toIssue = new LinkedHashSet<>(newIds);
|
toIssue.removeAll(oldIds);
|
if (!toRelease.isEmpty()) {
|
updatePlanIssuedFlag(new ArrayList<>(toRelease), false);
|
}
|
if (!toIssue.isEmpty()) {
|
updatePlanIssuedFlag(new ArrayList<>(toIssue), true);
|
}
|
}
|
|
private void releaseProductionPlanIssueStatus(ProductionOrder productionOrder) {
|
if (productionOrder == null) {
|
return;
|
}
|
List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
|
if (!planIds.isEmpty()) {
|
updatePlanIssuedFlag(planIds, false);
|
}
|
}
|
|
private void updatePlanIssuedFlag(List<Long> planIds, boolean issued) {
|
if (planIds == null || planIds.isEmpty()) {
|
return;
|
}
|
List<ProductionPlan> plans = productionPlanMapper.selectBatchIds(planIds);
|
for (ProductionPlan plan : plans) {
|
ProductionPlan update = new ProductionPlan();
|
update.setId(plan.getId());
|
update.setIssued(issued);
|
productionPlanMapper.updateById(update);
|
}
|
}
|
|
private void upsertOrderPick(ProductionOrder productionOrder) {
|
if (productionOrder == null || productionOrder.getId() == null) {
|
return;
|
}
|
// 订单下达后自动生成一张备料主单,后续领料记录都挂在这张单上。
|
ProductionOrderPick orderPick = productionOrderPickMapper.selectOne(
|
Wrappers.<ProductionOrderPick>lambdaQuery()
|
.eq(ProductionOrderPick::getProductionOrderId, productionOrder.getId())
|
.last("limit 1"));
|
if (orderPick == null) {
|
orderPick = new ProductionOrderPick();
|
orderPick.setProductionOrderId(productionOrder.getId());
|
}
|
orderPick.setProductModelId(productionOrder.getProductModelId() == null ? null : Math.toIntExact(productionOrder.getProductModelId()));
|
orderPick.setQuantity(defaultDecimal(productionOrder.getQuantity()));
|
orderPick.setRemark("下单自动生成");
|
if (orderPick.getId() == null) {
|
productionOrderPickMapper.insert(orderPick);
|
} else {
|
productionOrderPickMapper.updateById(orderPick);
|
}
|
}
|
|
private BigDecimal resolveBomDemandQuantity(TechnologyBomStructure source, BigDecimal orderQuantity) {
|
// 工艺 BOM 中的需求量按“单件需求 * 订单数量”展开成订单级需求。
|
BigDecimal baseQuantity = source.getDemandedQuantity() != null ? source.getDemandedQuantity() : source.getUnitQuantity();
|
baseQuantity = baseQuantity == null ? BigDecimal.ZERO : baseQuantity;
|
if (baseQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
return baseQuantity.multiply(orderQuantity);
|
}
|
|
private List<Long> parsePlanIds(String productionPlanIds) {
|
if (productionPlanIds == null || productionPlanIds.trim().isEmpty()) {
|
return new ArrayList<>();
|
}
|
String normalized = productionPlanIds.replace("[", "").replace("]", "").trim();
|
if (normalized.isEmpty()) {
|
return new ArrayList<>();
|
}
|
return java.util.Arrays.stream(normalized.split(","))
|
.map(String::trim)
|
.filter(item -> !item.isEmpty())
|
.map(Long::valueOf)
|
.distinct()
|
.collect(Collectors.toList());
|
}
|
|
private String formatPlanIds(List<Long> planIds) {
|
if (planIds == null || planIds.isEmpty()) {
|
return null;
|
}
|
return planIds.stream()
|
.distinct()
|
.map(String::valueOf)
|
.collect(Collectors.joining(",", "[", "]"));
|
}
|
|
private LocalDate resolvePlanCompleteDate(ProductionPlan productionPlan) {
|
if (productionPlan == null) {
|
return null;
|
}
|
if (productionPlan.getPromisedDeliveryDate() != null) {
|
return productionPlan.getPromisedDeliveryDate().toLocalDate();
|
}
|
if (productionPlan.getRequiredDate() != null) {
|
return productionPlan.getRequiredDate().toLocalDate();
|
}
|
return null;
|
}
|
|
private int compareDecimal(BigDecimal left, BigDecimal right) {
|
return defaultDecimal(left).compareTo(defaultDecimal(right));
|
}
|
}
|