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.metadata.IPage;
|
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.basic.dto.StorageBlobVO;
|
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
|
import com.ruoyi.basic.mapper.StorageBlobMapper;
|
import com.ruoyi.basic.pojo.StorageAttachment;
|
import com.ruoyi.basic.pojo.StorageBlob;
|
import com.ruoyi.basic.utils.FileUtil;
|
import com.ruoyi.common.constant.StorageAttachmentConstants;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.production.bean.dto.ProductionOrderDto;
|
import com.ruoyi.production.bean.vo.ProductionOrderVo;
|
import com.ruoyi.production.bean.vo.ProductionPlanVo;
|
import com.ruoyi.production.enums.ProductOrderStatusEnum;
|
import com.ruoyi.production.mapper.*;
|
import com.ruoyi.production.pojo.*;
|
import com.ruoyi.production.service.ProductionOrderService;
|
import com.ruoyi.sales.mapper.SalesLedgerMapper;
|
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
|
import com.ruoyi.technology.mapper.*;
|
import com.ruoyi.technology.pojo.*;
|
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.format.DateTimeFormatter;
|
import java.util.*;
|
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 StorageAttachmentMapper storageAttachmentMapper;
|
private final StorageBlobMapper storageBlobMapper;
|
private final SalesLedgerMapper salesLedgerMapper;
|
private final SalesLedgerProductMapper salesLedgerProductMapper;
|
private final TechnologyRoutingMapper technologyRoutingMapper;
|
private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
|
private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper;
|
private final TechnologyOperationMapper technologyOperationMapper;
|
private final TechnologyBomMapper technologyBomMapper;
|
private final TechnologyBomStructureMapper technologyBomStructureMapper;
|
private final FileUtil fileUtil;
|
|
@Override
|
public IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) {
|
Page<ProductionOrderVo> result = (Page<ProductionOrderVo>) baseMapper.pageProductionOrder(page, dto);
|
fillProductImages(result.getRecords());
|
return result;
|
}
|
|
@Override
|
public List<ProductionOrderVo> listProductionOrder(ProductionOrderDto dto) {
|
List<ProductionOrderVo> records = baseMapper.listProductionOrder(dto);
|
fillProductImages(records);
|
return records;
|
}
|
|
@Override
|
public ProductionOrderVo getProductionOrderInfo(Long id) {
|
ProductionOrderVo item = baseMapper.getProductionOrderInfo(id);
|
if (item == null) {
|
return null;
|
}
|
fillProductImages(java.util.Collections.singletonList(item));
|
return item;
|
}
|
|
@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;
|
}
|
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 Integer bindingRoute(ProductionOrderDto productionOrderDto) {
|
if (productionOrderDto == null || productionOrderDto.getId() == null) {
|
throw new ServiceException("生产订单ID不能为空");
|
}
|
ProductionOrder productionOrder = this.getById(productionOrderDto.getId());
|
if (productionOrder == null) {
|
throw new ServiceException("生产订单不存在");
|
}
|
|
Long targetRoutingId = productionOrderDto.getTechnologyRoutingId() == null
|
? productionOrder.getTechnologyRoutingId()
|
: productionOrderDto.getTechnologyRoutingId();
|
if (targetRoutingId == null) {
|
throw new ServiceException("工艺路线ID不能为空");
|
}
|
TechnologyRouting targetRouting = technologyRoutingMapper.selectById(targetRoutingId);
|
if (targetRouting == null) {
|
throw new ServiceException("工艺路线不存在");
|
}
|
if (productionOrder.getProductModelId() != null
|
&& !Objects.equals(productionOrder.getProductModelId(), targetRouting.getProductModelId())) {
|
throw new ServiceException("工艺路线与生产订单产品规格不匹配");
|
}
|
|
if (ProductOrderStatusEnum.isStarted(productionOrder.getStatus())
|
&& !Objects.equals(productionOrder.getTechnologyRoutingId(), targetRoutingId)) {
|
throw new ServiceException("生产订单已开工,不能修改工艺路线");
|
}
|
|
if (!Objects.equals(productionOrder.getTechnologyRoutingId(), targetRoutingId)) {
|
ProductionOrder update = new ProductionOrder();
|
update.setId(productionOrder.getId());
|
update.setTechnologyRoutingId(targetRoutingId);
|
if (!this.updateById(update)) {
|
throw new ServiceException("绑定工艺路线失败");
|
}
|
}
|
|
// 绑定路线仅重建订单侧快照数据
|
return syncProductionOrderSnapshot(productionOrder.getId());
|
}
|
|
@Override
|
public List<ProductionPlanVo> getSource(Long id) {
|
ProductionOrder productionOrder = baseMapper.selectById(id);
|
if (productionOrder != null && productionOrder.getProductionPlanIds() != null) {
|
List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
|
return productionPlanMapper.getSource(planIds);
|
}
|
return null;
|
}
|
|
@Override
|
public int syncProductionOrderSnapshot(Long productionOrderId) {
|
ProductionOrder productionOrder = this.getById(productionOrderId);
|
if (productionOrder == null) {
|
throw new ServiceException("生产订单不存在");
|
}
|
if (productionOrder.getTechnologyRoutingId() == null) {
|
throw new ServiceException("工艺路线ID不能为空");
|
}
|
TechnologyRouting technologyRouting = technologyRoutingMapper.selectById(productionOrder.getTechnologyRoutingId());
|
if (technologyRouting == null) {
|
throw new ServiceException("工艺路线不存在");
|
}
|
// 订单快照按“先清后建”处理,保证工艺路线、工序、参数、BOM 全部来自同一版本。
|
clearProductionSnapshot(productionOrderId);
|
ProductionOrderBom orderBom = syncProductionOrderBomSnapshot(productionOrder, technologyRouting);
|
|
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());
|
orderRouting.setOrderBomId(orderBom == null ? null : orderBom.getId());
|
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));
|
Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds(
|
routingOperations.stream()
|
.map(TechnologyRoutingOperation::getTechnologyOperationId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet()))
|
.stream()
|
.collect(Collectors.toMap(TechnologyOperation::getId, TechnologyOperation::getName, (a, b) -> a));
|
for (TechnologyRoutingOperation sourceOperation : routingOperations) {
|
// 订单工序保存的是工艺工序快照,后续报工只依赖快照,不再直接引用工艺主数据。
|
ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation();
|
targetOperation.setProductionOrderId(productionOrder.getId());
|
targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId());
|
targetOperation.setOrderRoutingId(orderRouting.getId());
|
targetOperation.setProductModelId(sourceOperation.getProductModelId());
|
targetOperation.setDragSort(sourceOperation.getDragSort());
|
targetOperation.setIsQuality(sourceOperation.getIsQuality());
|
targetOperation.setOperationName(operationNameMap.get(sourceOperation.getTechnologyOperationId()));
|
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++;
|
}
|
}
|
|
upsertOrderPick(productionOrder);
|
return syncedParamCount;
|
}
|
|
private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) {
|
if (technologyRouting.getBomId() == null) {
|
return null;
|
}
|
TechnologyBom technologyBom = technologyBomMapper.selectById(technologyRouting.getBomId());
|
if (technologyBom == null) {
|
throw new ServiceException("工艺BOM不存在");
|
}
|
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());
|
}
|
return orderBom;
|
}
|
|
private void clearProductionSnapshot(Long productionOrderId) {
|
// 已产生领料记录后禁止重建,避免备料/投料依据与订单快照脱节。
|
boolean hasPickRecord = productionOrderPickRecordMapper.selectCount(
|
Wrappers.<ProductionOrderPickRecord>lambdaQuery()
|
.eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0;
|
if (hasPickRecord) {
|
throw new ServiceException("生产订单已存在领料记录,不能重新生成快照");
|
}
|
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("生产订单已开工,不能重新生成快照");
|
}
|
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("生产订单不能为空");
|
}
|
fillFromProductionPlans(productionOrder);
|
if (productionOrder.getProductModelId() == null) {
|
throw new ServiceException("产品规格ID不能为空");
|
}
|
if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
|
throw new ServiceException("下单数量必须大于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) {
|
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("生产订单已开工,不能修改产品、工艺路线或数量");
|
}
|
}
|
}
|
|
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("部分生产计划不存在");
|
}
|
Set<Long> productModelIds = productionPlans.stream()
|
.map(ProductionPlan::getProductModelId)
|
.collect(Collectors.toSet());
|
if (productModelIds.size() > 1) {
|
throw new ServiceException("所选生产计划必须属于同一产品规格");
|
}
|
if (productionPlans.stream().anyMatch(item -> item.getStatus() != null && item.getStatus() == 2)) {
|
throw new ServiceException("所选生产计划已下发");
|
}
|
ProductionPlan firstPlan = productionPlans.get(0);
|
if (productionOrder.getProductModelId() == null) {
|
productionOrder.setProductModelId(firstPlan.getProductModelId());
|
} else if (!Objects.equals(productionOrder.getProductModelId(), firstPlan.getProductModelId())) {
|
throw new ServiceException("产品规格ID与生产计划不一致");
|
}
|
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 releaseProductionPlanIssueStatus(ProductionOrder productionOrder) {
|
if (productionOrder == null) {
|
return;
|
}
|
List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
|
if (!planIds.isEmpty()) {
|
// 生产订单删除--对应的生产计划的已下发数量要减去
|
updatePlanIssuedFlag(planIds, productionOrder.getQuantity());
|
}
|
}
|
|
//生产订单删除,生产计划的已下发数量对应变更
|
private void updatePlanIssuedFlag(List<Long> planIds, BigDecimal remainingAssignedQuantity) {
|
if (planIds == null || planIds.isEmpty()) {
|
return;
|
}
|
List<ProductionPlan> plans = productionPlanMapper.selectBatchIds(planIds);
|
//下发数量减去
|
List<ProductionPlan> updates = new ArrayList<>();
|
for (ProductionPlan plan : plans) {
|
BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO);
|
if (requiredQuantity.compareTo(BigDecimal.ZERO) < 0) {
|
requiredQuantity = BigDecimal.ZERO;
|
}
|
BigDecimal remainingQuantity = resolveRemainingQuantity(plan);
|
BigDecimal historicalIssuedQuantity = requiredQuantity.subtract(remainingQuantity);
|
BigDecimal issuedQuantity = remainingAssignedQuantity.min(historicalIssuedQuantity);
|
remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity);
|
BigDecimal totalIssuedQuantity = historicalIssuedQuantity.subtract(issuedQuantity);
|
int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity);
|
ProductionPlan update = new ProductionPlan();
|
update.setId(plan.getId());
|
update.setStatus(planStatus);
|
update.setQuantityIssued(totalIssuedQuantity);
|
updates.add(update);
|
}
|
if (!updates.isEmpty()) {
|
productionPlanMapper.updateById(updates);
|
}
|
}
|
|
private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
|
if (plan == null) {
|
return BigDecimal.ZERO;
|
}
|
BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO);
|
if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
return BigDecimal.ZERO;
|
}
|
BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO);
|
if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
return requiredQuantity;
|
}
|
if (issuedQuantity.compareTo(requiredQuantity) >= 0) {
|
return BigDecimal.ZERO;
|
}
|
return requiredQuantity.subtract(issuedQuantity);
|
}
|
|
private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) {
|
if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
return 0;
|
}
|
if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
|
return 0;
|
}
|
return issuedQuantity.compareTo(requiredQuantity) < 0 ? 1 : 2;
|
}
|
|
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();
|
}
|
if (productionPlan.getRequiredDate() != null) {
|
return productionPlan.getRequiredDate();
|
}
|
return null;
|
}
|
|
private int compareDecimal(BigDecimal left, BigDecimal right) {
|
return defaultDecimal(left).compareTo(defaultDecimal(right));
|
}
|
|
private void fillProductImages(List<ProductionOrderVo> records) {
|
if (records == null || records.isEmpty()) {
|
return;
|
}
|
List<Long> productModelIds = records.stream()
|
.map(ProductionOrderVo::getProductModelId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
if (productModelIds.isEmpty()) {
|
return;
|
}
|
|
List<StorageAttachment> attachments = storageAttachmentMapper.selectList(
|
Wrappers.<StorageAttachment>lambdaQuery()
|
.in(StorageAttachment::getRecordId, productModelIds)
|
.eq(StorageAttachment::getApplication, StorageAttachmentConstants.StorageAttachmentImage)
|
.eq(StorageAttachment::getDeleted, 0L)
|
.orderByAsc(StorageAttachment::getId));
|
if (attachments == null || attachments.isEmpty()) {
|
return;
|
}
|
|
Map<Long, List<StorageAttachment>> attachmentMap = attachments.stream()
|
.collect(Collectors.groupingBy(StorageAttachment::getRecordId, java.util.LinkedHashMap::new, Collectors.toList()));
|
List<Long> blobIds = attachments.stream()
|
.map(StorageAttachment::getStorageBlobId)
|
.filter(Objects::nonNull)
|
.distinct()
|
.collect(Collectors.toList());
|
if (blobIds.isEmpty()) {
|
return;
|
}
|
|
Map<Long, StorageBlob> blobMap = storageBlobMapper.selectBatchIds(blobIds).stream()
|
.filter(Objects::nonNull)
|
.collect(Collectors.toMap(StorageBlob::getId, item -> item));
|
for (ProductionOrderVo record : records) {
|
List<StorageAttachment> modelAttachments = attachmentMap.get(record.getProductModelId());
|
if (modelAttachments == null || modelAttachments.isEmpty()) {
|
continue;
|
}
|
List<StorageBlobVO> images = modelAttachments.stream()
|
.map(StorageAttachment::getStorageBlobId)
|
.map(blobMap::get)
|
.filter(Objects::nonNull)
|
.map(this::toStorageBlobVO)
|
.collect(Collectors.toList());
|
if (!images.isEmpty()) {
|
record.setProductImages(images);
|
}
|
}
|
}
|
|
private StorageBlobVO toStorageBlobVO(StorageBlob blob) {
|
StorageBlobVO vo = BeanUtil.copyProperties(blob, StorageBlobVO.class);
|
vo.setPreviewURL(fileUtil.buildSignedPreviewUrl(vo));
|
vo.setDownloadURL(fileUtil.buildSignedDownloadUrl(vo));
|
return vo;
|
}
|
}
|