package com.ruoyi.production.service.impl; 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.common.exception.ServiceException; import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.production.bean.dto.ProductionPlanDto; import com.ruoyi.production.bean.dto.ProductionPlanImportDto; import com.ruoyi.production.bean.vo.ProductionPlanVo; import com.ruoyi.production.enums.ProductOrderStatusEnum; import com.ruoyi.production.mapper.ProductionOrderMapper; import com.ruoyi.production.mapper.ProductionPlanMapper; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.pojo.ProductionPlan; import com.ruoyi.production.service.ProductionPlanService; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ProductionPlanServiceImpl extends ServiceImpl implements ProductionPlanService { private final ProductionPlanMapper productionPlanMapper; private final ProductionOrderMapper productionOrderMapper; @Override public IPage listPage(Page page, ProductionPlanDto productionPlanDto) { IPage planVoIPage = productionPlanMapper.listPage(page, productionPlanDto) .convert(dto -> { ProductionPlanVo vo = new ProductionPlanVo(); BeanUtils.copyProperties(dto, vo); return vo; }); return planVoIPage; } /** * 合并生产计划 */ @Override @Transactional(rollbackFor = Exception.class) public boolean combine(ProductionPlanDto productionPlanDto) { if (productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) { return false; } // 查询主生产计划 List plans = productionPlanMapper.selectWithMaterialByIds(productionPlanDto.getIds()); if (plans == null || plans.isEmpty()) { throw new ServiceException("下发失败,生产计划不存在"); } // 校验是否存在不同的产品名称 String firstProductName = plans.get(0).getProductName(); if (plans.stream().anyMatch(p -> p.getProductName() == null || !p.getProductName().equals(firstProductName))) { throw new BaseException("合并失败,存在不同的产品名称"); } // 校验是否存在不同的产品规格 String firstProductSpec = plans.get(0).getModel(); if (plans.stream().anyMatch(p -> p.getModel() == null || !p.getModel().equals(firstProductSpec))) { throw new BaseException("合并失败,存在不同的产品规格"); } // 创建生产订单 ProductionOrder productionOrder = new ProductionOrder(); productionOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity()); productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime()); productionOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode()); productionOrder.setStrength(productionPlanDto.getStrength()); return true; } @Override @Transactional(rollbackFor = Exception.class) public boolean add(ProductionPlanDto dto) { if (StringUtils.isBlank(dto.getApplyNo())) { throw new ServiceException("新增失败,申请单编号不能为空"); } checkApplyNoUnique(dto.getApplyNo(), null); dto.setStatus(0); return productionPlanMapper.insert(dto) > 0; } @Override @Transactional(rollbackFor = Exception.class) public boolean update(ProductionPlanDto dto) { if (dto == null || dto.getId() == null) { throw new ServiceException("编辑失败,数据不能为空"); } ProductionPlan old = getById(dto.getId()); if (old == null) { throw new ServiceException("编辑失败,主生产计划不存在"); } // 状态校验 if (old.getStatus() != 0) { throw new BaseException("编辑失败,该生产计划已下发或部分下发状态,禁止编辑"); } // applyNo变更才校验 if (StringUtils.isNotBlank(dto.getApplyNo()) && !dto.getApplyNo().equals(old.getApplyNo())) { checkApplyNoUnique(dto.getApplyNo(), dto.getId()); } return productionPlanMapper.updateById(dto) > 0; } private void checkApplyNoUnique(String applyNo, Long excludeId) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(ProductionPlan::getApplyNo, applyNo); if (excludeId != null) { wrapper.ne(ProductionPlan::getId, excludeId); } if (productionPlanMapper.selectCount(wrapper) > 0) { throw new ServiceException("申请单编号 " + applyNo + " 已存在"); } } @Override @Transactional(rollbackFor = Exception.class) public boolean delete(List ids) { // 如果存在已下发的计划,则不能删除 if (productionPlanMapper.selectList(Wrappers.lambdaQuery().in(ProductionPlan::getId, ids)).stream().anyMatch(p -> p.getStatus() == 1 || p.getStatus() == 2)) { throw new BaseException("删除失败,存在已下发或部分下发的计划"); } // 如果有关联订单,则不能删除 if (productionOrderMapper.selectList(Wrappers.lambdaQuery().in(ProductionOrder::getProductionPlanIds, ids)).stream().anyMatch(p -> p.getId() != null)) { throw new BaseException("删除失败,存在关联订单"); } return productionPlanMapper.deleteBatchIds(ids) > 0; } @Override @Transactional(rollbackFor = Exception.class) public void importProdData(MultipartFile file) { if (file == null || file.isEmpty()) { throw new ServiceException("导入数据不能为空"); } ExcelUtil excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class); List list; try { list = excelUtil.importExcel(file.getInputStream()); } catch (Exception e) { log.error("生产需求Excel导入失败", e); throw new ServiceException("Excel解析失败"); } if (list == null || list.isEmpty()) { throw new ServiceException("Excel没有数据"); } Set applyNos = new HashSet<>(); Set materialCodes = new HashSet<>(); for (int i = 0; i < list.size(); i++) { ProductionPlanImportDto dto = list.get(i); String applyNo = dto.getApplyNo(); String materialCode = dto.getMaterialCode(); if (StringUtils.isEmpty(applyNo)) { throw new ServiceException("导入失败:第 " + (i + 2) + " 行申请单编号不能为空"); } if (!applyNos.add(applyNo)) { throw new ServiceException("导入失败:Excel 中存在重复的申请单编号: " + applyNo); } if (StringUtils.isEmpty(materialCode)) { throw new ServiceException("导入失败:第 " + (i + 2) + " 行物料编码不能为空"); } String strength = dto.getStrength(); if (StringUtils.isNotEmpty(strength)) { if (!"A3.5".equals(strength) && !"A5.0".equals(strength)) { throw new ServiceException("导入失败:第 " + (i + 2) + " 行强度只能是 A3.5 或 A5.0"); } } materialCodes.add(materialCode); } // 申请单编号是否已存在 Long existApplyNoCount = baseMapper.selectCount(Wrappers.lambdaQuery() .in(ProductionPlan::getApplyNo, applyNos)); if (existApplyNoCount > 0) { List existApplyNos = baseMapper.selectList(Wrappers.lambdaQuery() .in(ProductionPlan::getApplyNo, applyNos)) .stream().map(ProductionPlan::getApplyNo).collect(Collectors.toList()); throw new ServiceException("导入失败,申请单编号已存在: " + String.join(", ", existApplyNos)); } LocalDateTime now = LocalDateTime.now(); List entityList = list.stream().map(dto -> { ProductionPlan entity = new ProductionPlan(); BeanUtils.copyProperties(dto, entity); entity.setStatus(0); entity.setCreateTime(now); entity.setUpdateTime(now); return entity; }).collect(Collectors.toList()); this.saveBatch(entityList); } @Override public void exportProdData(HttpServletResponse response, List ids) { List list; if (ids != null && !ids.isEmpty()) { list = baseMapper.selectBatchIds(ids); } else { list = baseMapper.selectList(null); } List exportList = new ArrayList<>(); for (ProductionPlan entity : list) { ProductionPlanImportDto dto = new ProductionPlanImportDto(); BeanUtils.copyProperties(entity, dto); exportList.add(dto); } ExcelUtil util = new ExcelUtil<>(ProductionPlanImportDto.class); util.exportExcel(response, exportList, "销售生产需求数据"); } }