| | |
| | | //package com.ruoyi.production.service.impl; |
| | | // |
| | | //import com.alibaba.fastjson2.JSONArray; |
| | | //import com.alibaba.fastjson2.JSONObject; |
| | | //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.mapper.ProductionPlanMapper; |
| | | //import com.ruoyi.production.pojo.ProductionPlan; |
| | | //import com.ruoyi.production.service.ProductionPlanService; |
| | | //import lombok.RequiredArgsConstructor; |
| | | //import org.springframework.stereotype.Service; |
| | | //import org.springframework.transaction.annotation.Transactional; |
| | | //import org.springframework.web.multipart.MultipartFile; |
| | | // |
| | | //import jakarta.servlet.http.HttpServletResponse; |
| | | //import java.math.BigDecimal; |
| | | //import java.time.Instant; |
| | | //import java.time.LocalDateTime; |
| | | //import java.time.ZoneId; |
| | | //import java.util.*; |
| | | //import java.util.concurrent.locks.ReentrantLock; |
| | | //import java.util.stream.Collectors; |
| | | // |
| | | ///** |
| | | // * <p> |
| | | // * 生产计划表 服务实现类 |
| | | // * </p> |
| | | // * |
| | | // * @author 芯导软件(江苏)有限公司 |
| | | // * @since 2026-04-21 02:11:10 |
| | | // */ |
| | | //@Service |
| | | //@RequiredArgsConstructor |
| | | //public class ProductionPlanServiceImpl extends ServiceImpl<ProductionPlanMapper, ProductionPlan> implements ProductionPlanService { |
| | | // |
| | | // private ProductionPlanMapper productionPlanMapper; |
| | | // |
| | | // |
| | | // /** |
| | | // * 同步锁,确保手动和定时任务不同时执行 |
| | | // */ |
| | | // private final ReentrantLock syncLock = new ReentrantLock(); |
| | | // |
| | | // @Override |
| | | // public IPage<ProductionPlanVo> listPage(Page<ProductionPlanDto> page, ProductionPlanDto productionPlanDto) { |
| | | // |
| | | // return productionPlanMapper.selectPage(page, null); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 页面手动同步 |
| | | // */ |
| | | // @Override |
| | | // public void loadProdData() { |
| | | // syncProdData(1); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 定时任务同步 |
| | | // */ |
| | | // @Override |
| | | // public void syncProdDataJob() { |
| | | // syncProdData(2); |
| | | // } |
| | | // |
| | | // /** |
| | | // * 合并生产计划 |
| | | // */ |
| | | // @Override |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public boolean combine(ProductionPlanDto productionPlanDto) { |
| | | // if (productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) { |
| | | // return false; |
| | | // } |
| | | // |
| | | // // 查询主生产计划 |
| | | // List<ProductionPlanDto> 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("合并失败,存在不同的产品规格"); |
| | | // } |
| | | // |
| | | // // 叠加剩余方数 |
| | | // BigDecimal totalRemainingVolume = plans.stream() |
| | | // .map(ProductionPlan::getRemainingVolume) |
| | | // .filter(Objects::nonNull) |
| | | // .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | // // 判断下发数量是否大于等于剩余方数 |
| | | // if (productionPlanDto.getTotalAssignedQuantity().compareTo(totalRemainingVolume) > 0) { |
| | | // throw new BaseException("操作失败,下发数量不能大于剩余方数"); |
| | | // } |
| | | // |
| | | // // 创建生产订单 |
| | | // ProductOrder productOrder = new ProductOrder(); |
| | | // productOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity()); |
| | | // productOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime()); |
| | | // productOrder.setStatus(ProductOrderStatusEnum.WAIT.getCode()); |
| | | // productOrder.setStrength(productionPlanDto.getStrength()); |
| | | // productOrder.setProductMaterialSkuId(plans.get(0).getProductMaterialSkuId()); |
| | | // |
| | | // Long orderId = productOrderService.insertProductOrder(productOrder); |
| | | // |
| | | // // 当下发的产品为砌块或板材,就拉取BOM子集与工艺路线子集数据存入到附表中 |
| | | // if ("砌块".equals(productionPlanDto.getProductName())) { |
| | | // productOrder.setRouteId(productionOrderAppendixService.populateBlocks(orderId, productionPlanDto)); |
| | | // } |
| | | // if ("板材".equals(productionPlanDto.getProductName())) { |
| | | // productOrder.setRouteId(productionOrderAppendixService.populatePlates(orderId, productionPlanDto)); |
| | | // } |
| | | // // 更新绑定的工艺路线 |
| | | // productOrderService.updateById(productOrder); |
| | | // |
| | | // // 根据下发数量,从第一个生产计划开始分配方数 |
| | | // BigDecimal assignedVolume = BigDecimal.ZERO; |
| | | // for (ProductionPlan plan : plans) { |
| | | // BigDecimal volume = plan.getVolume(); |
| | | // if (volume == null) { |
| | | // continue; |
| | | // } |
| | | // // 计算剩余方数 |
| | | // BigDecimal remainingVolume = plan.getRemainingVolume(); |
| | | // if (remainingVolume.compareTo(BigDecimal.ZERO) <= 0) { |
| | | // continue; |
| | | // } |
| | | // |
| | | // ProductOrderPlan productOrderPlan = new ProductOrderPlan(); |
| | | // productOrderPlan.setProductOrderId(productOrder.getId()); |
| | | // productOrderPlan.setProductionPlanId(plan.getId()); |
| | | // |
| | | // if (assignedVolume.add(remainingVolume).compareTo(productionPlanDto.getTotalAssignedQuantity()) >= 0) { |
| | | // // 最后一个计划,分配剩余方数 |
| | | // BigDecimal lastRemainingVolume = productionPlanDto.getTotalAssignedQuantity().subtract(assignedVolume); |
| | | // BigDecimal assignedQuantity = Optional.ofNullable(plan.getAssignedQuantity()).orElse(BigDecimal.ZERO).add(lastRemainingVolume); |
| | | // plan.setAssignedQuantity(assignedQuantity); |
| | | // plan.setStatus(assignedQuantity.compareTo(plan.getVolume()) >= 0 ? 2 : 1); |
| | | // productOrderPlan.setAssignedQuantity(lastRemainingVolume); |
| | | // productionPlanMapper.updateById(plan); |
| | | // productOrderPlanMapper.insert(productOrderPlan); |
| | | // break; |
| | | // } |
| | | // |
| | | // // 分配当前计划方数 |
| | | // BigDecimal assignedQuantity = Optional.ofNullable(plan.getAssignedQuantity()).orElse(BigDecimal.ZERO).add(remainingVolume); |
| | | // plan.setAssignedQuantity(assignedQuantity); |
| | | // plan.setStatus(assignedQuantity.compareTo(plan.getVolume()) >= 0 ? 2 : 1); |
| | | // productOrderPlan.setAssignedQuantity(remainingVolume); |
| | | // // 更新生产计划 |
| | | // productionPlanMapper.updateById(plan); |
| | | // // 创建关联关系 |
| | | // productOrderPlanMapper.insert(productOrderPlan); |
| | | // assignedVolume = assignedVolume.add(remainingVolume); |
| | | // } |
| | | // |
| | | // for (ProductionPlan plan : plans) { |
| | | // BigDecimal assignedQuantity = Optional.ofNullable(plan.getAssignedQuantity()).orElse(BigDecimal.ZERO); |
| | | // BigDecimal volume = Optional.ofNullable(plan.getVolume()).orElse(BigDecimal.ZERO); |
| | | // if (assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | // plan.setStatus(0); |
| | | // } else if (assignedQuantity.compareTo(volume) >= 0) { |
| | | // plan.setStatus(2); |
| | | // } else { |
| | | // plan.setStatus(1); |
| | | // } |
| | | // productionPlanMapper.updateById(plan); |
| | | // } |
| | | // return true; |
| | | // } |
| | | // |
| | | // @Override |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public boolean add(ProductionPlanDto productionPlanDto) { |
| | | // if (StringUtils.isEmpty(productionPlanDto.getApplyNo())) { |
| | | // throw new ServiceException("新增失败,申请单编号不能为空"); |
| | | // } |
| | | // Long count = productionPlanMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery() |
| | | // .eq(ProductionPlan::getApplyNo, productionPlanDto.getApplyNo())); |
| | | // if (count > 0) { |
| | | // throw new ServiceException("新增失败,申请单编号 " + productionPlanDto.getApplyNo() + " 已存在"); |
| | | // } |
| | | // productionPlanDto.setDataSourceType(DataSourceTypeEnum.MANUAL.getCode()); |
| | | // productionPlanDto.setStatus(0); |
| | | // productionPlanMapper.insert(productionPlanDto); |
| | | // return true; |
| | | // } |
| | | // |
| | | // @Override |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public boolean update(ProductionPlanDto productionPlanDto) { |
| | | // if (productionPlanDto == null || productionPlanDto.getId() == null) { |
| | | // throw new ServiceException("编辑失败,数据不能为空"); |
| | | // } |
| | | // ProductionPlan productionPlan = getById(productionPlanDto.getId()); |
| | | // if (productionPlan == null) { |
| | | // throw new ServiceException("编辑失败,主生产计划不存在"); |
| | | // } |
| | | // |
| | | // if (StringUtils.isNotEmpty(productionPlanDto.getApplyNo()) |
| | | // && !productionPlanDto.getApplyNo().equals(productionPlan.getApplyNo())) { |
| | | // |
| | | // Long count = productionPlanMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery() |
| | | // .eq(ProductionPlan::getApplyNo, productionPlanDto.getApplyNo()) |
| | | // .ne(ProductionPlan::getId, productionPlanDto.getId())); // 排除自身 |
| | | // |
| | | // if (count > 0) { |
| | | // throw new ServiceException("编辑失败,申请单编号 " + productionPlanDto.getApplyNo() + " 已被占用"); |
| | | // } |
| | | // } |
| | | // // 已下发状态,不能编辑 |
| | | // if (productionPlan.getStatus() != 0) { |
| | | // throw new BaseException("编辑失败,该生产计划已下发或部分下发状态,禁止编辑"); |
| | | // } |
| | | // |
| | | // // 查询是否有关联订单 |
| | | // boolean hasProductOrderPlan = productOrderPlanMapper.selectList(Wrappers.<ProductOrderPlan>lambdaQuery() |
| | | // .eq(ProductOrderPlan::getProductionPlanId, productionPlanDto.getId())) |
| | | // .stream().anyMatch(p -> p.getProductOrderId() != null); |
| | | // |
| | | // if (hasProductOrderPlan) { |
| | | // if (productionPlanDto.getVolume().compareTo(productionPlan.getVolume()) < 0) { |
| | | // throw new BaseException("方数不能递减"); |
| | | // } |
| | | // } |
| | | // |
| | | // return productionPlanMapper.updateById(productionPlanDto) > 0; |
| | | // } |
| | | // |
| | | // @Override |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public boolean delete(List<Long> ids) { |
| | | // // 如果存在已下发的计划,则不能删除 |
| | | // if (productionPlanMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getId, ids)).stream().anyMatch(p -> p.getStatus() == 1 || p.getStatus() == 2)) { |
| | | // throw new BaseException("删除失败,存在已下发或部分下发的计划"); |
| | | // } |
| | | // // 如果有关联订单,则不能删除 |
| | | // if (productOrderPlanMapper.selectList(Wrappers.<ProductOrderPlan>lambdaQuery().in(ProductOrderPlan::getProductionPlanId, ids)).stream().anyMatch(p -> p.getProductOrderId() != null)) { |
| | | // throw new BaseException("删除失败,存在关联订单"); |
| | | // } |
| | | // |
| | | // return productionPlanMapper.deleteBatchIds(ids) > 0; |
| | | // } |
| | | // |
| | | // /** |
| | | // * 同步数据 |
| | | // */ |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public void syncProdData(Integer dataSyncType) { |
| | | // if (!syncLock.tryLock()) { |
| | | // log.warn("同步正在进行中,本次 {} 同步请求被跳过", dataSyncType == 1 ? "手动同步" : "定时任务同步"); |
| | | // return; |
| | | // } |
| | | // |
| | | // try { |
| | | // JSONArray searchConditions = new JSONArray(); |
| | | // JSONObject condition = new JSONObject(); |
| | | // condition.put("key", "processApprovedResult"); |
| | | // JSONArray valueArray = new JSONArray(); |
| | | // valueArray.add("agree"); |
| | | // |
| | | // condition.put("value", valueArray); |
| | | // condition.put("type", "ARRAY"); |
| | | // condition.put("operator", "in"); |
| | | // condition.put("componentName", "SelectField"); |
| | | // searchConditions.add(condition); |
| | | // |
| | | // String searchFieldJson = searchConditions.toJSONString(); |
| | | // |
| | | // JSONArray dataArr = AliDingUtils.getFormDataList(aliDingConfig, aliDingConfig.getProducePlanFormUuid(), searchFieldJson, this, ProductionPlan::getFormModifiedTime); |
| | | // |
| | | // if (dataArr.isEmpty()) { |
| | | // log.info("没有更多新数据需要同步"); |
| | | // return; |
| | | // } |
| | | // |
| | | // // 解析并保存数据 |
| | | // List<ProductionPlan> list = parseProductionPlans(dataArr, dataSyncType, dataArr.size()); |
| | | // if (!list.isEmpty()) { |
| | | // // 处理更新或新增 |
| | | // int affected = processSaveOrUpdate(list); |
| | | // log.info("数据同步完成,共同步 {} 条数据", affected); |
| | | // } |
| | | // |
| | | // } catch (Exception e) { |
| | | // log.error("同步生产计划异常", e); |
| | | // } finally { |
| | | // // 释放锁 |
| | | // syncLock.unlock(); |
| | | // } |
| | | // } |
| | | // |
| | | // private List<ProductionPlan> parseProductionPlans(JSONArray dataArr, Integer dataSyncType, Integer totalCount) { |
| | | // List<ProductionPlan> list = new ArrayList<>(); |
| | | // LocalDateTime now = LocalDateTime.now(); |
| | | // |
| | | // for (int i = 0; i < dataArr.size(); i++) { |
| | | // JSONObject item = dataArr.getJSONObject(i); |
| | | // String formInstanceId = item.getString("formInstanceId"); |
| | | // String serialNo = item.getString("serialNo"); |
| | | // |
| | | // JSONObject originator = item.getJSONObject("originator"); |
| | | // String originatorName = originator != null && originator.containsKey("userName") |
| | | // ? originator.getJSONObject("userName").getString("nameInChinese") : "未知"; |
| | | // |
| | | // JSONObject formData = item.getJSONObject("formData"); |
| | | // JSONArray tableArr = formData.getJSONArray("tableField_l7fytfcn"); |
| | | // if (tableArr == null || tableArr.isEmpty()) { |
| | | // continue; |
| | | // } |
| | | // |
| | | // for (int j = 0; j < tableArr.size(); j++) { |
| | | // JSONObject row = tableArr.getJSONObject(j); |
| | | // ProductionPlan plan = new ProductionPlan(); |
| | | // |
| | | // plan.setFormInstanceId(formInstanceId); |
| | | // plan.setSerialNo(serialNo); |
| | | // plan.setApplyNo(formData.getString("textField_l7fytfco")); |
| | | // plan.setCustomerName(formData.getString("textField_lbkozohg")); |
| | | // |
| | | // String materialCode = row.getString("textField_l9xo62q5"); |
| | | // // 根据物料编码查询物料信息表,关联物料ID |
| | | // if (StringUtils.isNotEmpty(materialCode)) { |
| | | // LambdaQueryWrapper<ProductMaterialSku> skuQueryWrapper = new LambdaQueryWrapper<>(); |
| | | // skuQueryWrapper.eq(ProductMaterialSku::getMaterialCode, materialCode); |
| | | // ProductMaterialSku sku = productMaterialSkuService.getOne(skuQueryWrapper); |
| | | // if (sku != null) { |
| | | // plan.setProductMaterialSkuId(sku.getId()); |
| | | // } |
| | | // } |
| | | // |
| | | // plan.setLength(row.getInteger("numberField_lb7lgatg_value")); |
| | | // plan.setWidth(row.getInteger("numberField_lb7lgath_value")); |
| | | // plan.setHeight(row.getInteger("numberField_lb7lgati_value")); |
| | | // plan.setQuantity(row.getInteger("numberField_lb7lgatj_value")); |
| | | // plan.setVolume(row.getBigDecimal("numberField_l7fytfd3_value")); |
| | | // plan.setStrength(row.getString("radioField_m9urarr2_id")); |
| | | // |
| | | // JSONArray dateArr = row.getJSONArray("cascadeDateField_lfxqqluw"); |
| | | // if (dateArr != null && dateArr.size() == 2) { |
| | | // try { |
| | | // long start = Long.parseLong(dateArr.getString(0)); |
| | | // long end = Long.parseLong(dateArr.getString(1)); |
| | | // |
| | | // Date startDate = Date.from(Instant.ofEpochMilli(start) |
| | | // .atZone(ZoneId.systemDefault()) |
| | | // .toLocalDate() |
| | | // .atStartOfDay(ZoneId.systemDefault()) |
| | | // .toInstant()); |
| | | // Date endDate = Date.from(Instant.ofEpochMilli(end) |
| | | // .atZone(ZoneId.systemDefault()) |
| | | // .toLocalDate() |
| | | // .atStartOfDay(ZoneId.systemDefault()) |
| | | // .toInstant()); |
| | | // |
| | | // plan.setStartDate(startDate); |
| | | // plan.setEndDate(endDate); |
| | | // } catch (Exception e) { |
| | | // log.warn("解析日期失败: {}", dateArr); |
| | | // } |
| | | // } |
| | | // |
| | | // plan.setSubmitter(originatorName); |
| | | // plan.setSubmitOrg("宁夏中创绿能实业集团有限公司"); |
| | | // plan.setRemarkOne(formData.getString("textareaField_l7fytfcy")); |
| | | // plan.setRemarkTwo(formData.getString("textField_l7fytfcx")); |
| | | // plan.setCreatorName(originatorName); |
| | | // |
| | | // JSONObject modifyUser = item.getJSONObject("modifyUser"); |
| | | // if (modifyUser != null && modifyUser.containsKey("userName")) { |
| | | // plan.setModifierName(modifyUser.getJSONObject("userName").getString("nameInChinese")); |
| | | // } |
| | | // |
| | | // plan.setFormCreatedTime(AliDingUtils.parseUtcTime(item.getString("createdTimeGMT"))); |
| | | // plan.setFormModifiedTime(AliDingUtils.parseUtcTime(item.getString("modifiedTimeGMT"))); |
| | | // plan.setDataSourceType(DataSourceTypeEnum.DING_TALK.getCode()); |
| | | // plan.setCreateTime(now); |
| | | // plan.setUpdateTime(now); |
| | | // plan.setTotalCount(totalCount); |
| | | // |
| | | // list.add(plan); |
| | | // } |
| | | // } |
| | | // return list; |
| | | // } |
| | | // |
| | | // private int processSaveOrUpdate(List<ProductionPlan> list) { |
| | | // if (list == null || list.isEmpty()) { |
| | | // return 0; |
| | | // } |
| | | // int affected = 0; |
| | | // |
| | | // // 去重 formInstanceId |
| | | // Set<String> formIds = list.stream() |
| | | // .map(ProductionPlan::getFormInstanceId) |
| | | // .collect(Collectors.toSet()); |
| | | // |
| | | // // 查询数据库已有数据 |
| | | // List<ProductionPlan> existList = this.list(new LambdaQueryWrapper<ProductionPlan>().in(ProductionPlan::getFormInstanceId, formIds)); |
| | | // |
| | | // // Map (formInstanceId + materialCode) |
| | | // Map<String, ProductionPlan> existMap = new HashMap<>(); |
| | | // for (ProductionPlan p : existList) { |
| | | // String key = p.getFormInstanceId() + "_" + p.getProductMaterialSkuId(); |
| | | // existMap.put(key, p); |
| | | // } |
| | | // |
| | | // // 遍历同步数据 |
| | | // for (ProductionPlan plan : list) { |
| | | // String key = plan.getFormInstanceId() + "_" + plan.getProductMaterialSkuId(); |
| | | // ProductionPlan exist = existMap.get(key); |
| | | // if (exist == null) { |
| | | // // 新增 |
| | | // this.save(plan); |
| | | // affected++; |
| | | // log.info("新增数据 formInstanceId={}, materialCode={}", plan.getFormInstanceId(), plan.getProductMaterialSkuId()); |
| | | // } else { |
| | | // // 判断是否需要更新 |
| | | // if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(plan.getFormModifiedTime())) { |
| | | // plan.setId(exist.getId()); |
| | | // plan.setCreateTime(exist.getCreateTime()); |
| | | // this.updateById(plan); |
| | | // affected++; |
| | | // log.info("更新数据 formInstanceId={}, materialCode={}", plan.getFormInstanceId(), plan.getProductMaterialSkuId()); |
| | | // } |
| | | // } |
| | | // } |
| | | // return affected; |
| | | // } |
| | | // |
| | | // @Override |
| | | // public List<ProductionPlanSummaryDto> summaryByProductType(ProductionPlanSummaryDto query) { |
| | | // return baseMapper.selectSummaryByProductType(query); |
| | | // } |
| | | // |
| | | // @Override |
| | | // @Transactional(rollbackFor = Exception.class) |
| | | // public void importProdData(MultipartFile file) { |
| | | // if (file == null || file.isEmpty()) { |
| | | // throw new ServiceException("导入数据不能为空"); |
| | | // } |
| | | // ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | // List<ProductionPlanImportDto> 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<String> applyNos = new HashSet<>(); |
| | | // Set<String> 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.<ProductionPlan>lambdaQuery() |
| | | // .in(ProductionPlan::getApplyNo, applyNos)); |
| | | // if (existApplyNoCount > 0) { |
| | | // List<String> existApplyNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery() |
| | | // .in(ProductionPlan::getApplyNo, applyNos)) |
| | | // .stream().map(ProductionPlan::getApplyNo).collect(Collectors.toList()); |
| | | // throw new ServiceException("导入失败,申请单编号已存在: " + String.join(", ", existApplyNos)); |
| | | // } |
| | | // |
| | | // Map<String, Long> skuMap = productMaterialSkuService.list(Wrappers.<ProductMaterialSku>lambdaQuery() |
| | | // .in(ProductMaterialSku::getMaterialCode, materialCodes)) |
| | | // .stream().collect(Collectors.toMap(ProductMaterialSku::getMaterialCode, ProductMaterialSku::getId, (k1, k2) -> k1)); |
| | | // |
| | | // List<String> missingCodes = materialCodes.stream() |
| | | // .filter(code -> !skuMap.containsKey(code)) |
| | | // .collect(Collectors.toList()); |
| | | // if (!missingCodes.isEmpty()) { |
| | | // throw new ServiceException("导入失败,以下物料编码不存在: " + String.join(", ", missingCodes)); |
| | | // } |
| | | // |
| | | // LocalDateTime now = LocalDateTime.now(); |
| | | // List<ProductionPlan> entityList = list.stream().map(dto -> { |
| | | // ProductionPlan entity = new ProductionPlan(); |
| | | // BeanUtils.copyProperties(dto, entity); |
| | | // entity.setProductMaterialSkuId(skuMap.get(dto.getMaterialCode())); |
| | | // entity.setAssignedQuantity(BigDecimal.ZERO); |
| | | // entity.setDataSourceType(DataSourceTypeEnum.MANUAL.getCode()); |
| | | // entity.setStatus(0); |
| | | // entity.setCreateTime(now); |
| | | // entity.setUpdateTime(now); |
| | | // return entity; |
| | | // }).collect(Collectors.toList()); |
| | | // |
| | | // this.saveBatch(entityList); |
| | | // } |
| | | // |
| | | // @Override |
| | | // public void exportProdData(HttpServletResponse response, List<Long> ids) { |
| | | // List<ProductionPlan> list; |
| | | // if (ids != null && !ids.isEmpty()) { |
| | | // list = baseMapper.selectBatchIds(ids); |
| | | // } else { |
| | | // list = baseMapper.selectList(null); |
| | | // } |
| | | // |
| | | // List<ProductionPlanImportDto> exportList = new ArrayList<>(); |
| | | // for (ProductionPlan entity : list) { |
| | | // ProductionPlanImportDto dto = new ProductionPlanImportDto(); |
| | | // BeanUtils.copyProperties(entity, dto); |
| | | // exportList.add(dto); |
| | | // } |
| | | // ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | // util.exportExcel(response, exportList, "销售生产需求数据"); |
| | | // } |
| | | // |
| | | //} |
| | | package com.ruoyi.production.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | | 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.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.ProductionOrderService; |
| | | 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.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class ProductionPlanServiceImpl extends ServiceImpl<ProductionPlanMapper, ProductionPlan> implements ProductionPlanService { |
| | | |
| | | private static final int PLAN_STATUS_WAIT = 0; |
| | | private static final int PLAN_STATUS_PARTIAL = 1; |
| | | private static final int PLAN_STATUS_ISSUED = 2; |
| | | |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOrderService productionOrderService; |
| | | |
| | | @Override |
| | | public IPage<ProductionPlanVo> listPage(Page<ProductionPlanDto> page, ProductionPlanDto productionPlanDto) { |
| | | return productionPlanMapper.listPage(page, productionPlanDto); |
| | | } |
| | | |
| | | /** |
| | | * 合并生产计划并下发生产订单。 |
| | | * 业务约束: |
| | | * 1. 仅允许同一产品型号的计划合并; |
| | | * 2. 已下发或部分下发的计划禁止再次合并; |
| | | * 3. 下发数量不能大于所选计划需求总量; |
| | | * 4. 订单创建统一复用 ProductionOrderService.saveProductionOrder,确保工艺/BOM/领料主单等后续逻辑一致。 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean combine(ProductionPlanDto productionPlanDto) { |
| | | if (productionPlanDto == null || productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | List<Long> planIds = productionPlanDto.getIds().stream() |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | if (planIds.isEmpty()) { |
| | | throw new ServiceException("下发失败,未选择生产计划"); |
| | | } |
| | | |
| | | List<ProductionPlanDto> planLists = productionPlanMapper.selectWithMaterialByIds(planIds); |
| | | if (planLists == null || planLists.isEmpty() || planLists.size() != planIds.size()) { |
| | | throw new ServiceException("下发失败,生产计划不存在或已被删除"); |
| | | } |
| | | |
| | | ProductionPlanDto firstPlan = planLists.getFirst(); |
| | | if (firstPlan.getProductModelId() == null) { |
| | | throw new ServiceException("下发失败,生产计划缺少产品型号"); |
| | | } |
| | | |
| | | boolean hasDifferentModel = planLists.stream() |
| | | .anyMatch(item -> !Objects.equals(item.getProductModelId(), firstPlan.getProductModelId())); |
| | | if (hasDifferentModel) { |
| | | throw new BaseException("合并失败,所选生产计划的产品型号不一致"); |
| | | } |
| | | |
| | | boolean hasIssuedPlan = planLists.stream() |
| | | .anyMatch(item -> item.getStatus() != null && item.getStatus() == PLAN_STATUS_ISSUED); |
| | | if (hasIssuedPlan) { |
| | | throw new BaseException("合并失败,所选生产计划存在已下发或部分下发数据"); |
| | | } |
| | | |
| | | BigDecimal totalRequiredQuantity = planLists.stream() |
| | | .map(this::resolveRemainingQuantity) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (totalRequiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("下发失败,所选生产计划需求总量必须大于0"); |
| | | } |
| | | |
| | | BigDecimal assignedQuantity = productionPlanDto.getTotalAssignedQuantity(); |
| | | if (assignedQuantity == null || assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | throw new ServiceException("下发失败,下发数量必须大于0"); |
| | | } |
| | | if (assignedQuantity.compareTo(totalRequiredQuantity) > 0) { |
| | | throw new ServiceException("下发失败,下发数量不能大于计划需求总量"); |
| | | } |
| | | |
| | | BigDecimal remainingForOrderBind = assignedQuantity; |
| | | List<Long> issuedPlanIds = new ArrayList<>(); |
| | | for (ProductionPlanDto plan : planLists) { |
| | | BigDecimal remainingQuantity = resolveRemainingQuantity(plan); |
| | | if (remainingForOrderBind.compareTo(BigDecimal.ZERO) <= 0 || remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | continue; |
| | | } |
| | | BigDecimal issueForThisPlan = remainingForOrderBind.min(remainingQuantity); |
| | | remainingForOrderBind = remainingForOrderBind.subtract(issueForThisPlan); |
| | | if (issueForThisPlan.compareTo(BigDecimal.ZERO) > 0) { |
| | | issuedPlanIds.add(plan.getId()); |
| | | } |
| | | } |
| | | if (issuedPlanIds.isEmpty()) { |
| | | throw new ServiceException("Issue failed, no quantity available for dispatch"); |
| | | } |
| | | |
| | | ProductionOrder productionOrder = new ProductionOrder(); |
| | | productionOrder.setProductionPlanIds(formatPlanIds(issuedPlanIds)); |
| | | productionOrder.setProductModelId(firstPlan.getProductModelId()); |
| | | productionOrder.setQuantity(assignedQuantity); |
| | | productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime()); |
| | | |
| | | boolean saved = productionOrderService.saveProductionOrder(productionOrder); |
| | | if (!saved) { |
| | | throw new ServiceException("下发失败,生产订单保存失败"); |
| | | } |
| | | |
| | | //已下发数量 |
| | | BigDecimal remainingAssignedQuantity = assignedQuantity; |
| | | List<ProductionPlan> updates = new ArrayList<>(); |
| | | for (ProductionPlanDto plan : planLists) { |
| | | 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 = BigDecimal.ZERO; |
| | | if (remainingAssignedQuantity.compareTo(BigDecimal.ZERO) > 0 && remainingQuantity.compareTo(BigDecimal.ZERO) > 0) { |
| | | issuedQuantity = remainingAssignedQuantity.min(remainingQuantity); |
| | | remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity); |
| | | } |
| | | |
| | | BigDecimal totalIssuedQuantity = historicalIssuedQuantity.add(issuedQuantity); |
| | | int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity); |
| | | ProductionPlan update = new ProductionPlan(); |
| | | update.setId(plan.getId()); |
| | | update.setStatus(planStatus); |
| | | update.setQuantityIssued(totalIssuedQuantity); |
| | | update.setIssued(planStatus == PLAN_STATUS_ISSUED); |
| | | updates.add(update); |
| | | } |
| | | if (!updates.isEmpty()) { |
| | | this.updateBatchById(updates); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean add(ProductionPlanDto dto) { |
| | | if (StringUtils.isBlank(dto.getMpsNo())) { |
| | | dto.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); |
| | | }else checkMpsNoUnique(dto.getMpsNo(), null); |
| | | dto.setStatus(PLAN_STATUS_WAIT); |
| | | dto.setSource("内部"); |
| | | 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() != PLAN_STATUS_WAIT) { |
| | | throw new BaseException("编辑失败,该生产计划已下发或部分下发,禁止编辑"); |
| | | } |
| | | |
| | | if (StringUtils.isNotBlank(dto.getMpsNo()) && !dto.getMpsNo().equals(old.getMpsNo())) { |
| | | checkMpsNoUnique(dto.getMpsNo(), dto.getId()); |
| | | } |
| | | |
| | | return productionPlanMapper.updateById(dto) > 0; |
| | | } |
| | | |
| | | private void checkMpsNoUnique(String mpsNo, Long excludeId) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = Wrappers.lambdaQuery(); |
| | | wrapper.eq(ProductionPlan::getMpsNo, mpsNo); |
| | | if (excludeId != null) { |
| | | wrapper.ne(ProductionPlan::getId, excludeId); |
| | | } |
| | | if (productionPlanMapper.selectCount(wrapper) > 0) { |
| | | throw new ServiceException("生产计划号 " + mpsNo + " 已存在"); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean delete(List<Long> ids) { |
| | | if (productionPlanMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getId, ids)) |
| | | .stream() |
| | | .anyMatch(p -> p.getStatus() == PLAN_STATUS_PARTIAL || p.getStatus() == PLAN_STATUS_ISSUED)) { |
| | | throw new BaseException("删除失败,存在已下发或部分下发的计划"); |
| | | } |
| | | |
| | | if (productionOrderMapper.selectList(Wrappers.<ProductionOrder>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<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | List<ProductionPlanImportDto> list; |
| | | try { |
| | | list = excelUtil.importExcel(file.getInputStream()); |
| | | } catch (Exception e) { |
| | | throw new ServiceException("Excel解析失败"); |
| | | } |
| | | if (list == null || list.isEmpty()) { |
| | | throw new ServiceException("Excel没有数据"); |
| | | } |
| | | Set<String> mpsNos = new HashSet<>(); |
| | | for (int i = 0; i < list.size(); i++) { |
| | | ProductionPlanImportDto dto = list.get(i); |
| | | String mpsNo = dto.getMpsNo(); |
| | | if (StringUtils.isEmpty(mpsNo)) { |
| | | generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))); |
| | | } |
| | | if (!mpsNos.add(mpsNo)) { |
| | | throw new ServiceException("导入失败:Excel 中存在重复的申请单编号 " + mpsNo); |
| | | } |
| | | } |
| | | Long existApplyNoCount = baseMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery() |
| | | .in(ProductionPlan::getMpsNo, mpsNos)); |
| | | if (existApplyNoCount > 0) { |
| | | List<String> existMpsNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery() |
| | | .in(ProductionPlan::getMpsNo, mpsNos)) |
| | | .stream() |
| | | .map(ProductionPlan::getMpsNo) |
| | | .collect(Collectors.toList()); |
| | | throw new ServiceException("导入失败,生产计划号已存在: " + String.join(", ", existMpsNos)); |
| | | } |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | List<ProductionPlan> entityList = list.stream().map(dto -> { |
| | | ProductionPlan entity = new ProductionPlan(); |
| | | BeanUtils.copyProperties(dto, entity); |
| | | entity.setStatus(PLAN_STATUS_WAIT); |
| | | entity.setSource("内部"); |
| | | entity.setCreateTime(now); |
| | | entity.setUpdateTime(now); |
| | | return entity; |
| | | }).collect(Collectors.toList()); |
| | | this.saveBatch(entityList); |
| | | } |
| | | |
| | | @Override |
| | | public void exportProdData(HttpServletResponse response, List<Long> ids) { |
| | | List<ProductionPlanDto> list = productionPlanMapper.selectWithMaterialByIds(ids); |
| | | List<ProductionPlanImportDto> exportList = new ArrayList<>(); |
| | | for (ProductionPlanDto entity : list) { |
| | | ProductionPlanImportDto dto = new ProductionPlanImportDto(); |
| | | BeanUtils.copyProperties(entity, dto); |
| | | exportList.add(dto); |
| | | } |
| | | ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class); |
| | | util.exportExcel(response, exportList, "主生产计划"); |
| | | } |
| | | |
| | | 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 PLAN_STATUS_WAIT; |
| | | } |
| | | if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return PLAN_STATUS_WAIT; |
| | | } |
| | | return issuedQuantity.compareTo(requiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED; |
| | | } |
| | | |
| | | private String formatPlanIds(List<Long> planIds) { |
| | | return planIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .map(String::valueOf) |
| | | .collect(Collectors.joining(",", "[", "]")); |
| | | } |
| | | |
| | | private String generateNextPlanNo(String datePrefix) { |
| | | QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>(); |
| | | queryWrapper.likeRight("mps_no", "JH" + datePrefix); |
| | | queryWrapper.orderByDesc("mps_no"); |
| | | queryWrapper.last("LIMIT 1"); |
| | | ProductionPlan latestPlan = productionPlanMapper.selectOne(queryWrapper); |
| | | int sequence = 1; |
| | | if (latestPlan != null && latestPlan.getMpsNo() != null && !latestPlan.getMpsNo().isEmpty()) { |
| | | String sequenceStr = latestPlan.getMpsNo().substring(("JH" + datePrefix).length()); |
| | | try { |
| | | sequence = Integer.parseInt(sequenceStr) + 1; |
| | | } catch (NumberFormatException e) { |
| | | sequence = 1; |
| | | } |
| | | } |
| | | return "JH" + datePrefix + String.format("%04d", sequence); |
| | | } |
| | | } |