package com.ruoyi.production.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.production.dto.ProductionProductRouteItemDto;
import com.ruoyi.production.dto.ProductionProductRouteItemParamDto;
import com.ruoyi.production.dto.ProductionRecordDto;
import com.ruoyi.production.enums.ProductOrderStatusEnum;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.*;
import com.ruoyi.production.vo.ProductionOrderRouteItemParamVo;
import com.ruoyi.production.vo.ProductionOrderRouteItemVo;
import com.ruoyi.production.vo.ProductionOrderStructureVo;
import com.ruoyi.production.vo.ProductionRecordVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
*
* 生产报工记录Service实现类
*
*
* @author deslrey
* @version 1.0
* @since 2026/03/23 10:23
*/
@Slf4j
@Service
public class ProductionRecordServiceImpl implements ProductionRecordService {
@Value("${file.upload-dir}")
private String uploadDir;
@Autowired
private ProductionProductMainService productionProductMainService;
@Autowired
private ProductionProductInputService productionProductInputService;
@Autowired
private ProductionProductOutputService productionProductOutputService;
@Autowired
private IProductionProductRouteItemService productionProductRouteItemService;
@Autowired
private IProductionProductRouteItemParamService productionProductRouteItemParamService;
@Autowired
private IProductionProductRouteItemFileService productionProductRouteItemFileService;
@Autowired
private ProductOrderService productOrderService;
@Autowired
private IProductionOrderRouteItemService productionOrderRouteItemService;
@Autowired
private IProductionOrderRouteItemParamService productionOrderRouteItemParamService;
@Autowired
private IProductionOrderStructureService productionOrderStructureService;
@Autowired
private ProductProcessService productProcessService;
@Autowired
private ProductMaterialSkuService productMaterialSkuService;
@Autowired
private ProductMaterialService productMaterialService;
@Autowired
private TempFileMapper tempFileMapper;
@Override
public ProductionRecordVo productRouteItem(Long productOrderId) {
if (productOrderId == null) {
throw new ServiceException("请选择生产订单再新增报工");
}
// 查询出生产订单绑定的工艺路线
ProductOrder productOrder = productOrderService.getById(productOrderId);
if (productOrder == null) {
throw new ServiceException("未查询到该生产订单的信息");
}
// 通过工艺路线查询出绑定的工序及参数
List productionOrderRouteItemList = productionOrderRouteItemService.list(new LambdaQueryWrapper()
.eq(ProductionOrderRouteItem::getOrderId, productOrderId)
.eq(ProductionOrderRouteItem::getRouteId, productOrder.getRouteId()));
if (productionOrderRouteItemList == null || productionOrderRouteItemList.isEmpty()) {
throw new ServiceException("该生产订单绑定的工艺路线无工序");
}
ProductionRecordVo productionRecordVo = new ProductionRecordVo();
productionRecordVo.setProductOrderId(productOrderId);
productionRecordVo.setProductRouteId(productOrder.getRouteId());
List productionOrderRouteItemVoList = new ArrayList<>();
// 根据绑定的工序查询出工序参数及BOM需要消耗的产品
for (ProductionOrderRouteItem orderRouteItem : productionOrderRouteItemList) {
ProductionOrderRouteItemVo productionOrderRouteItemVo = new ProductionOrderRouteItemVo();
productionOrderRouteItemVo.setProcessId(orderRouteItem.getProcessId());
if (orderRouteItem.getProcessId() != null) {
ProductProcess process = productProcessService.getById(orderRouteItem.getProcessId());
if (process != null) {
productionOrderRouteItemVo.setProcessName(process.getName());
}
}
List orderRouteItemParamList = productionOrderRouteItemParamService.list(new LambdaQueryWrapper()
.eq(ProductionOrderRouteItemParam::getOrderId, productOrderId)
.eq(ProductionOrderRouteItemParam::getRouteItemId, orderRouteItem.getId()));
if (orderRouteItemParamList != null && !orderRouteItemParamList.isEmpty()) {
List paraVoList = orderRouteItemParamList.stream().map(param -> {
ProductionOrderRouteItemParamVo paraVo = new ProductionOrderRouteItemParamVo();
BeanUtils.copyProperties(param, paraVo);
paraVo.setParamFormat(DateUtils.toUpperCasePattern(paraVo.getParamFormat()));
return paraVo;
}).collect(Collectors.toList());
productionOrderRouteItemVo.setOrderRouteItemParaVos(paraVoList);
}
List orderStructureList = productionOrderStructureService.list(new LambdaQueryWrapper()
.eq(ProductionOrderStructure::getOrderId, productOrderId)
.eq(ProductionOrderStructure::getProcessId, orderRouteItem.getProcessId()));
if (orderStructureList != null && !orderStructureList.isEmpty()) {
List structureVoList = orderStructureList.stream().map(struct -> {
ProductionOrderStructureVo structureVo = new ProductionOrderStructureVo();
BeanUtils.copyProperties(struct, structureVo);
// 查询出产品的名称/模型
ProductMaterialSku productMaterialSku = productMaterialSkuService.getById(structureVo.getProductModelId());
ProductMaterial productMaterial = productMaterialService.getById(productMaterialSku.getProductId());
structureVo.setProductName(productMaterial.getProductName());
structureVo.setModel(productMaterialSku.getModel());
return structureVo;
}).collect(Collectors.toList());
productionOrderRouteItemVo.setOrderStructureVos(structureVoList);
}
// 每一个工序的参数、BOM消耗产品
productionOrderRouteItemVoList.add(productionOrderRouteItemVo);
}
productionRecordVo.setProductionOrderRouteItemVos(productionOrderRouteItemVoList);
return productionRecordVo;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addProductionRecordService(ProductionRecordDto dto) {
if (dto == null) {
throw new ServiceException("报工失败,数据不能为空");
}
if (dto.getProductOrderId() == null) {
throw new ServiceException("报工失败,生产订单ID不能为空");
}
if (dto.getProductId() == null) {
throw new ServiceException("报工失败,产品信息不能为空");
}
if (StringUtils.isEmpty(dto.getPostName()) || StringUtils.isEmpty(dto.getSchedule())) {
throw new ServiceException("报工失败,岗位人员/班次信息不能为空");
}
// 更新生产订单数据
ProductOrder productOrder = productOrderService.getById(dto.getProductOrderId());
if (productOrder == null) {
throw new ServiceException("报工失败,生产订单不存在");
}
// 当前报工合格数量
BigDecimal qualifiedQty = dto.getQualifiedQuantity() == null ? BigDecimal.ZERO : dto.getQualifiedQuantity();
// 已完成数量
BigDecimal completeQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
// 累加(只算合格)
BigDecimal newCompleteQty = completeQty.add(qualifiedQty);
productOrder.setCompleteQuantity(newCompleteQty);
// 需求数量
BigDecimal totalQty = productOrder.getQuantity() == null ? BigDecimal.ZERO : productOrder.getQuantity();
// 剩余可报工数量
BigDecimal remainQty = totalQty.subtract(completeQty);
// 本次报工不能超过剩余数量
if (qualifiedQty.compareTo(remainQty) > 0) {
throw new ServiceException("报工失败,本次报工数量不能大于剩余可报工数量");
}
// 设置开始时间(第一次报工)
if (productOrder.getStartTime() == null) {
productOrder.setStartTime(LocalDateTime.now());
}
// 状态判断
if (newCompleteQty.compareTo(totalQty) >= 0 && totalQty.compareTo(BigDecimal.ZERO) > 0) {
productOrder.setStatus(ProductOrderStatusEnum.FINISHED.getCode());
productOrder.setEndTime(LocalDateTime.now());
} else {
productOrder.setStatus(ProductOrderStatusEnum.RUNNING.getCode());
}
boolean update = productOrderService.updateById(productOrder);
if (!update) {
throw new ServiceException("报工失败,生产订单更新失败");
}
// 完成报工主表-投入表-产出表数据
ProductionProductMain productionProductMain = new ProductionProductMain();
productionProductMain.setProductNo("BG" + UUID.randomUUID());
productionProductMain.setProductOrderId(dto.getProductOrderId());
productionProductMain.setSchedule(dto.getSchedule());
boolean result = productionProductMainService.save(productionProductMain);
if (!result) {
throw new ServiceException("报工失败,数据存储失败");
}
ProductionProductInput productionProductInput = new ProductionProductInput();
productionProductInput.setProductMainId(productionProductMain.getId());
productionProductInput.setProductModelId(dto.getProductId());
productionProductInput.setQuantity(dto.getQuantity());
result = productionProductInputService.save(productionProductInput);
if (!result) {
throw new ServiceException("报工失败,生产投入存储失败");
}
ProductionProductOutput productionProductOutput = new ProductionProductOutput();
productionProductOutput.setProductMainId(productionProductMain.getId());
productionProductOutput.setProductModelId(dto.getProductId());
productionProductOutput.setQuantity(dto.getQualifiedQuantity());
productionProductOutput.setScrapQty(dto.getUnqualifiedQuantity());
result = productionProductOutputService.save(productionProductOutput);
if (!result) {
throw new ServiceException("报工失败,生产产出存储失败");
}
// 处理工序
List productionProductRouteItemDtoList = dto.getProductionProductRouteItemDtoList();
if (productionProductRouteItemDtoList == null || productionProductRouteItemDtoList.isEmpty()) {
throw new ServiceException("报工失败,工序参数不能为空");
}
for (ProductionProductRouteItemDto productRouteItemDto : productionProductRouteItemDtoList) {
// 处理工序主表
ProductionProductRouteItem productRouteItemEntity = new ProductionProductRouteItem();
BeanUtils.copyProperties(productRouteItemDto, productRouteItemEntity, "id");
productRouteItemEntity.setProductMainId(productionProductMain.getId());
productionProductRouteItemService.save(productRouteItemEntity);
// 处理参数及消耗产品
List productionProductRouteItemParamDtoList = productRouteItemDto.getProductionProductRouteItemParamDtoList();
if (productionProductRouteItemParamDtoList != null && !productionProductRouteItemParamDtoList.isEmpty()) {
for (ProductionProductRouteItemParamDto productRouteItemParamDto : productionProductRouteItemParamDtoList) {
ProductionProductRouteItemParam paramEntity = new ProductionProductRouteItemParam();
BeanUtils.copyProperties(productRouteItemParamDto, paramEntity, "id");
paramEntity.setProductionProductRouteItemId(productRouteItemEntity.getId());
if (paramEntity.getProductId() != null) {
} else {
ProductionOrderRouteItemParam productionOrderRouteItemParam = productionOrderRouteItemParamService.getById(productRouteItemParamDto.getId());
paramEntity.setParamName(productionOrderRouteItemParam.getParamName());
paramEntity.setParamType(productionOrderRouteItemParam.getParamType());
paramEntity.setParamFormat(productionOrderRouteItemParam.getParamFormat());
paramEntity.setValueMode(productionOrderRouteItemParam.getValueMode());
}
productionProductRouteItemParamService.save(paramEntity);
}
}
// 处理工序上传的附件
List files = productRouteItemDto.getFiles();
if (files != null && !files.isEmpty()) {
for (String tempId : files) {
TempFile tempFile = tempFileMapper.selectById(tempId);
if (tempFile == null) {
log.warn("未找到临时文件记录: {}", tempId);
continue;
}
try {
// 正式目录路径(按日期分组)
String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
Path formalDirPath = Paths.get(formalDir);
if (!Files.exists(formalDirPath)) {
Files.createDirectories(formalDirPath);
}
// 正式文件名
String originalFilename = tempFile.getOriginalName();
String fileExtension = FilenameUtils.getExtension(originalFilename);
String formalFilename = productRouteItemEntity.getId() + "_" +
System.currentTimeMillis() + "_" +
UUID.randomUUID().toString().substring(0, 8) +
(StringUtils.hasText(fileExtension) ? "." + fileExtension : "");
Path formalFilePath = formalDirPath.resolve(formalFilename);
// 文件迁移 (复制+删除)
Files.copy(Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(Paths.get(tempFile.getTempPath()));
// 保存报工附件记录
ProductionProductRouteItemFile fileEntity = new ProductionProductRouteItemFile();
fileEntity.setProductionProductRouteItemId(productRouteItemEntity.getId());
fileEntity.setFileName(originalFilename);
fileEntity.setFileUrl(formalFilePath.toString());
fileEntity.setFileSuffix(fileExtension);
fileEntity.setFileSize(Files.size(formalFilePath));
fileEntity.setCreateTime(LocalDateTime.now());
fileEntity.setTenantId(SecurityUtils.getLoginUser().getTenantId());
productionProductRouteItemFileService.save(fileEntity);
// 删除临时文件记录
tempFileMapper.deleteById(tempId);
log.info("工序附件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
} catch (IOException e) {
log.error("工序附件迁移失败: {}", tempFile.getTempPath(), e);
throw new ServiceException("工序附件处理异常: " + e.getMessage());
}
}
}
}
}
}