liding
2026-04-24 270c132a66a26b29a540cf696e9078015fb58de4
fix:1.生产计划部分
已修改6个文件
337 ■■■■■ 文件已修改
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionPlanMapper.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
@@ -16,30 +16,14 @@
@Data
public class ProductionPlanDto extends ProductionPlan {
    /**
     * 物料编码
     */
    @Schema(description = "物料编码")
    @Excel(name = "物料编码")
    private String materialCode;
    /**
     * 产品名称
     */
    @Schema(description = "产品名称")
    @Excel(name = "产品名称")
    private String productName;
    /**
     * 客户名称
     */
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    /**
     * 产品规格
     */
    @Schema(description = "产品规格")
    @Excel(name = "产品规格")
    private String model;
@@ -59,17 +43,7 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
    /**
     * 关联物料信息表
     */
    @Schema(description = "关联物料信息表ID")
    private Long productMaterialId;
    /**
     * 强度
     */
    @Schema(description = "强度")
    @Excel(name = "强度")
    private String strength;
    @Schema(description = "产品ID")
    private Long productId;
}
src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java
@@ -21,6 +21,6 @@
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "关联产品物料ID")
    private Long productMaterialId;
    @Schema(description = "产品ID")
    private Long productId;
}
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -2,6 +2,7 @@
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;
@@ -62,7 +63,7 @@
    private final FileUtil fileUtil;
    @Override
    public com.baomidou.mybatisplus.core.metadata.IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) {
    public IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) {
        Page<ProductionOrderVo> result = (Page<ProductionOrderVo>) baseMapper.pageProductionOrder(page, dto);
        fillProductImages(result.getRecords());
        return result;
@@ -359,18 +360,18 @@
        if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("quantity must be greater than 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) {
                throw new ServiceException("No technology routing found for the product model");
            }
            productionOrder.setTechnologyRoutingId(technologyRouting.getId());
        }
//        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) {
//                throw new ServiceException("No technology routing found for the product model");
//            }
//            productionOrder.setTechnologyRoutingId(technologyRouting.getId());
//        }
        if (oldOrder != null && ProductOrderStatusEnum.isStarted(oldOrder.getStatus())) {
            // 开工后只允许修正非核心字段,核心生产依据锁定。
            if (!Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId())
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -17,6 +17,7 @@
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;
@@ -24,20 +25,22 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ProductionPlanServiceImpl extends ServiceImpl<ProductionPlanMapper, ProductionPlan> implements ProductionPlanService {
    private final ProductionPlanMapper productionPlanMapper;
    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) {
@@ -45,121 +48,87 @@
    }
    /**
     * 合并生产计划
     * 合并生产计划并下发生产订单。
     * 业务约束:
     * 1. 仅允许同一产品型号的计划合并;
     * 2. 已下发或部分下发的计划禁止再次合并;
     * 3. 下发数量不能大于所选计划需求总量;
     * 4. 订单创建统一复用 ProductionOrderService.saveProductionOrder,确保工艺/BOM/领料主单等后续逻辑一致。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean combine(ProductionPlanDto productionPlanDto) {
        if (productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) {
        if (productionPlanDto == null || productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) {
            return false;
        }
        //  查询主生产计划
        List<ProductionPlanDto> plans = productionPlanMapper.selectWithMaterialByIds(productionPlanDto.getIds());
        if (plans == null || plans.isEmpty()) {
            throw new ServiceException("下发失败,生产计划不存在");
        List<Long> planIds = productionPlanDto.getIds().stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (planIds.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("合并失败,存在不同的产品名称");
        List<ProductionPlanDto> plans = productionPlanMapper.selectWithMaterialByIds(planIds);
        if (plans == null || plans.isEmpty() || plans.size() != planIds.size()) {
            throw new ServiceException("下发失败,生产计划不存在或已被删除");
        }
        // 校验是否存在不同的产品规格
        String firstProductSpec = plans.get(0).getModel();
        if (plans.stream().anyMatch(p -> p.getModel() == null || !p.getModel().equals(firstProductSpec))) {
            throw new BaseException("合并失败,存在不同的产品规格");
        ProductionPlanDto firstPlan = plans.getFirst();
        if (firstPlan.getProductModelId() == null) {
            throw new ServiceException("下发失败,生产计划缺少产品型号");
        }
        // 创建生产订单
        boolean hasDifferentModel = plans.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);
        if (hasIssuedPlan) {
            throw new BaseException("合并失败,所选生产计划存在已下发或部分下发数据");
        }
        BigDecimal totalRequiredQuantity = plans.stream()
                .map(ProductionPlan::getQtyRequired)
                .filter(Objects::nonNull)
                .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("下发失败,下发数量不能大于计划需求总量");
        }
        ProductionOrder productionOrder = new ProductionOrder();
        productionOrder.setQuantity(productionPlanDto.getTotalAssignedQuantity());
        productionOrder.setProductionPlanIds(formatPlanIds(planIds));
        productionOrder.setProductModelId(firstPlan.getProductModelId());
        productionOrder.setQuantity(assignedQuantity);
        productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
//        // 叠加剩余方数
//        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);
//        }
        boolean saved = productionOrderService.saveProductionOrder(productionOrder);
        if (!saved) {
            throw new ServiceException("下发失败,生产订单保存失败");
        }
        int targetStatus = assignedQuantity.compareTo(totalRequiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED;
        List<ProductionPlan> updates = planIds.stream().map(id -> {
            ProductionPlan update = new ProductionPlan();
            update.setId(id);
            update.setStatus(targetStatus);
            return update;
        }).collect(Collectors.toList());
        if (!updates.isEmpty()) {
            this.updateBatchById(updates);
        }
        return true;
    }
@@ -171,7 +140,7 @@
        }
        checkApplyNoUnique(dto.getApplyNo(), null);
        dto.setStatus(0);
        dto.setStatus(PLAN_STATUS_WAIT);
        return productionPlanMapper.insert(dto) > 0;
    }
@@ -179,22 +148,19 @@
    @Transactional(rollbackFor = Exception.class)
    public boolean update(ProductionPlanDto dto) {
        if (dto == null || dto.getId() == null) {
            throw new ServiceException("编辑失败,数据不能为空");
            throw new ServiceException("编辑失败,数据不能为空");
        }
        ProductionPlan old = getById(dto.getId());
        if (old == null) {
            throw new ServiceException("编辑失败,主生产计划不存在");
            throw new ServiceException("编辑失败,生产计划不存在");
        }
        // 状态校验
        if (old.getStatus() != 0) {
            throw new BaseException("编辑失败,该生产计划已下发或部分下发状态,禁止编辑");
        if (old.getStatus() != PLAN_STATUS_WAIT) {
            throw new BaseException("编辑失败,该生产计划已下发或部分下发,禁止编辑");
        }
        // applyNo变更才校验
        if (StringUtils.isNotBlank(dto.getApplyNo())
                && !dto.getApplyNo().equals(old.getApplyNo())) {
        if (StringUtils.isNotBlank(dto.getApplyNo()) && !dto.getApplyNo().equals(old.getApplyNo())) {
            checkApplyNoUnique(dto.getApplyNo(), dto.getId());
        }
@@ -204,11 +170,9 @@
    private void checkApplyNoUnique(String applyNo, Long excludeId) {
        LambdaQueryWrapper<ProductionPlan> 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 + " 已存在");
        }
@@ -217,13 +181,16 @@
    @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)) {
        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("删除失败,存在关联订单");
        if (productionOrderMapper.selectList(Wrappers.<ProductionOrder>lambdaQuery().in(ProductionOrder::getProductionPlanIds, ids))
                .stream()
                .anyMatch(p -> p.getId() != null)) {
            throw new BaseException("删除失败,存在关联生产订单");
        }
        return productionPlanMapper.deleteBatchIds(ids) > 0;
@@ -235,12 +202,12 @@
        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解析失败");
        }
@@ -259,29 +226,28 @@
                throw new ServiceException("导入失败:第 " + (i + 2) + " 行申请单编号不能为空");
            }
            if (!applyNos.add(applyNo)) {
                throw new ServiceException("导入失败:Excel 中存在重复的申请单编号: " + 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");
                }
            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));
        if (existApplyNoCount > 0) {
            List<String> existApplyNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery()
                            .in(ProductionPlan::getApplyNo, applyNos))
                    .stream().map(ProductionPlan::getApplyNo).collect(Collectors.toList());
                    .stream()
                    .map(ProductionPlan::getApplyNo)
                    .collect(Collectors.toList());
            throw new ServiceException("导入失败,申请单编号已存在: " + String.join(", ", existApplyNos));
        }
@@ -289,7 +255,7 @@
        List<ProductionPlan> entityList = list.stream().map(dto -> {
            ProductionPlan entity = new ProductionPlan();
            BeanUtils.copyProperties(dto, entity);
            entity.setStatus(0);
            entity.setStatus(PLAN_STATUS_WAIT);
            entity.setCreateTime(now);
            entity.setUpdateTime(now);
            return entity;
@@ -316,4 +282,12 @@
        ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class);
        util.exportExcel(response, exportList, "销售生产需求数据");
    }
    private String formatPlanIds(List<Long> planIds) {
        return planIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .map(String::valueOf)
                .collect(Collectors.joining(",", "[", "]"));
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -11,10 +11,7 @@
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.pojo.ProductionAccount;
import com.ruoyi.production.pojo.ProductionOperationTask;
import com.ruoyi.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionProductMain;
import com.ruoyi.production.pojo.*;
import com.ruoyi.production.service.ProductionOrderService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
src/main/resources/mapper/production/ProductionPlanMapper.xml
@@ -23,31 +23,25 @@
    <select id="listPage" resultType="com.ruoyi.production.bean.vo.ProductionPlanVo">
        SELECT
        pp.*,
        pms.material_code AS materialCode,
        pmdl.model,
        pms.product_id AS productMaterialId,
        pm.model,
        p.id as productId,
        p.product_name AS productName,
        pmdl.unit
        pm.unit
        FROM production_plan pp
        left join product_material_sku pms on pp.product_material_sku_id = pms.id
        left join product_model pmdl on pp.product_model_id = pmdl.id
        left join product p on pmdl.product_id = p.id
        ORDER BY COALESCE(pp.form_modified_time, pp.id) DESC
        left join product_model pm on pp.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        ORDER BY COALESCE(pp.id) DESC
    </select>
    <select id="selectWithMaterialByIds" resultType="com.ruoyi.production.bean.dto.ProductionPlanDto">
        SELECT
        pp.*,
        pms.material_code AS materialCode,
        pmdl.model,
        pm.model,
        p.product_name AS productName,
        pmdl.unit,
        pms.product_id AS productMaterialId
        pm.unit
        FROM production_plan pp
        LEFT JOIN product_material_sku pms ON pp.product_material_sku_id = pms.id
        LEFT JOIN product_model pmdl ON pp.product_model_id = pmdl.id
        LEFT JOIN product p ON pmdl.product_id = p.id
        LEFT JOIN product_model pm ON pp.product_model_id = pm.id
        LEFT JOIN product p ON pm.product_id = p.id
        WHERE pp.id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
@@ -57,15 +51,12 @@
    <select id="selectProductionPlanDtoById" resultType="com.ruoyi.production.bean.dto.ProductionPlanDto">
        SELECT
        pp.*,
        pms.material_code AS materialCode,
        pmdl.model,
        pms.product_id AS productMaterialId,
        pm.model,
        p.product_name AS productName,
        pmdl.unit
        pm.unit
        FROM production_plan pp
        left join product_material_sku pms on pp.product_material_sku_id = pms.id
        left join product_model pmdl on pp.product_model_id = pmdl.id
        left join product p on pmdl.product_id = p.id
        left join product_model pm on pp.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        WHERE pp.id = #{productionPlanId}
    </select>
</mapper>