chenhj
9 天以前 28cf22aaff7f092256db2ad6df699e17426f62ea
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -1,6 +1,7 @@
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;
@@ -26,7 +27,9 @@
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;
@@ -70,31 +73,30 @@
            throw new ServiceException("下发失败,未选择生产计划");
        }
        List<ProductionPlanDto> plans = productionPlanMapper.selectWithMaterialByIds(planIds);
        if (plans == null || plans.isEmpty() || plans.size() != planIds.size()) {
        List<ProductionPlanDto> planLists = productionPlanMapper.selectWithMaterialByIds(planIds);
        if (planLists == null || planLists.isEmpty() || planLists.size() != planIds.size()) {
            throw new ServiceException("下发失败,生产计划不存在或已被删除");
        }
        ProductionPlanDto firstPlan = plans.getFirst();
        ProductionPlanDto firstPlan = planLists.getFirst();
        if (firstPlan.getProductModelId() == null) {
            throw new ServiceException("下发失败,生产计划缺少产品型号");
        }
        boolean hasDifferentModel = plans.stream()
        boolean hasDifferentModel = planLists.stream()
                .anyMatch(item -> !Objects.equals(item.getProductModelId(), firstPlan.getProductModelId()));
        if (hasDifferentModel) {
            throw new BaseException("合并失败,所选生产计划的产品型号不一致");
        }
        boolean hasIssuedPlan = plans.stream()
                .anyMatch(item -> item.getStatus() != null && item.getStatus() != PLAN_STATUS_WAIT);
        boolean hasIssuedPlan = planLists.stream()
                .anyMatch(item -> item.getStatus() != null && item.getStatus() == PLAN_STATUS_ISSUED);
        if (hasIssuedPlan) {
            throw new BaseException("合并失败,所选生产计划存在已下发或部分下发数据");
        }
        BigDecimal totalRequiredQuantity = plans.stream()
                .map(ProductionPlan::getQtyRequired)
                .filter(Objects::nonNull)
        BigDecimal totalRequiredQuantity = planLists.stream()
                .map(this::resolveRemainingQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (totalRequiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("下发失败,所选生产计划需求总量必须大于0");
@@ -108,8 +110,25 @@
            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(planIds));
        productionOrder.setProductionPlanIds(formatPlanIds(issuedPlanIds));
        productionOrder.setProductModelId(firstPlan.getProductModelId());
        productionOrder.setQuantity(assignedQuantity);
        productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
@@ -119,13 +138,30 @@
            throw new ServiceException("下发失败,生产订单保存失败");
        }
        int targetStatus = assignedQuantity.compareTo(totalRequiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED;
        List<ProductionPlan> updates = planIds.stream().map(id -> {
        //已下发数量
        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(id);
            update.setStatus(targetStatus);
            return update;
        }).collect(Collectors.toList());
            update.setId(plan.getId());
            update.setStatus(planStatus);
            update.setQuantityIssued(totalIssuedQuantity);
            updates.add(update);
        }
        if (!updates.isEmpty()) {
            this.updateBatchById(updates);
        }
@@ -135,12 +171,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean add(ProductionPlanDto dto) {
        if (StringUtils.isBlank(dto.getApplyNo())) {
            throw new ServiceException("新增失败,申请单编号不能为空");
        }
        checkApplyNoUnique(dto.getApplyNo(), null);
        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;
    }
@@ -160,21 +195,21 @@
            throw new BaseException("编辑失败,该生产计划已下发或部分下发,禁止编辑");
        }
        if (StringUtils.isNotBlank(dto.getApplyNo()) && !dto.getApplyNo().equals(old.getApplyNo())) {
            checkApplyNoUnique(dto.getApplyNo(), dto.getId());
        if (StringUtils.isNotBlank(dto.getMpsNo()) && !dto.getMpsNo().equals(old.getMpsNo())) {
            checkMpsNoUnique(dto.getMpsNo(), dto.getId());
        }
        return productionPlanMapper.updateById(dto) > 0;
    }
    private void checkApplyNoUnique(String applyNo, Long excludeId) {
    private void checkMpsNoUnique(String mpsNo, Long excludeId) {
        LambdaQueryWrapper<ProductionPlan> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(ProductionPlan::getApplyNo, applyNo);
        wrapper.eq(ProductionPlan::getMpsNo, mpsNo);
        if (excludeId != null) {
            wrapper.ne(ProductionPlan::getId, excludeId);
        }
        if (productionPlanMapper.selectCount(wrapper) > 0) {
            throw new ServiceException("申请单编号 " + applyNo + " 已存在");
            throw new ServiceException("生产计划号 " + mpsNo + " 已存在");
        }
    }
@@ -202,7 +237,6 @@
        if (file == null || file.isEmpty()) {
            throw new ServiceException("导入数据不能为空");
        }
        ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class);
        List<ProductionPlanImportDto> list;
        try {
@@ -210,77 +244,82 @@
        } catch (Exception e) {
            throw new ServiceException("Excel解析失败");
        }
        if (list == null || list.isEmpty()) {
            throw new ServiceException("Excel没有数据");
        }
        Set<String> applyNos = new HashSet<>();
        Set<String> materialCodes = new HashSet<>();
        Set<String> mpsNos = 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) + " 行申请单编号不能为空");
            String mpsNo = dto.getMpsNo();
            if (StringUtils.isEmpty(mpsNo)) {
                generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
            }
            if (!applyNos.add(applyNo)) {
                throw new ServiceException("导入失败:Excel 中存在重复的申请单编号 " + applyNo);
            if (!mpsNos.add(mpsNo)) {
                throw new ServiceException("导入失败:Excel 中存在重复的申请单编号 " + mpsNo);
            }
            if (StringUtils.isEmpty(materialCode)) {
                throw new ServiceException("导入失败:第 " + (i + 2) + " 行物料编码不能为空");
            }
            String strength = dto.getStrength();
            if (StringUtils.isNotEmpty(strength) && !"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));
                .in(ProductionPlan::getMpsNo, mpsNos));
        if (existApplyNoCount > 0) {
            List<String> existApplyNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery()
                            .in(ProductionPlan::getApplyNo, applyNos))
            List<String> existMpsNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery()
                            .in(ProductionPlan::getMpsNo, mpsNos))
                    .stream()
                    .map(ProductionPlan::getApplyNo)
                    .map(ProductionPlan::getMpsNo)
                    .collect(Collectors.toList());
            throw new ServiceException("导入失败,申请单编号已存在: " + String.join(", ", existApplyNos));
            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<ProductionPlan> list;
        if (ids != null && !ids.isEmpty()) {
            list = baseMapper.selectBatchIds(ids);
        } else {
            list = baseMapper.selectList(null);
        }
        List<ProductionPlanDto> list = productionPlanMapper.selectWithMaterialByIds(ids);
        List<ProductionPlanImportDto> exportList = new ArrayList<>();
        for (ProductionPlan entity : list) {
        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, "销售生产需求数据");
        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) {
@@ -290,4 +329,22 @@
                .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);
    }
}