src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
@@ -16,30 +16,10 @@ @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 +39,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/dto/ProductionPlanImportDto.java
@@ -1,12 +1,11 @@ package com.ruoyi.production.bean.dto; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.util.Date; import java.time.LocalDate; /** * <br> @@ -20,126 +19,43 @@ @Data @Schema(name = "销售生产需求 Excel导入导出DTO") public class ProductionPlanImportDto { /** * 申请单编号 */ @Schema(description = "申请单编号") @Excel(name = "申请单编号") private String applyNo; /** * 客户名称 */ @Schema(description = "客户名称") @Excel(name = "客户名称") private String customerName; @Schema(description = "主生产计划号") @Excel(name = "主生产计划号") private String mpsNo; /** * 物料编码 */ @Schema(description = "物料编码") @Excel(name = "物料编码") private String materialCode; @Schema(description = "需求日期") @Excel(name = "需求日期") private LocalDate requiredDate; /** * 产品名称 */ @Schema(description = "备注") @Excel(name = "备注") private String remark; @Schema(description = "需求数量") @Excel(name = "需求数量") private BigDecimal qtyRequired; @Schema(description = "来源 销售/内部") @Excel(name = "来源 销售/内部") private String source; @Schema(description = "承诺日期") @Excel(name = "承诺日期") private LocalDate promisedDeliveryDate; @Schema(description = "产品名称") @Excel(name = "产品名称") private String productName; /** * 产品规格 */ @Schema(description = "产品规格") @Excel(name = "产品规格") private String productSpec; @Schema(description = "规格型号") @Excel(name = "规格型号") private String model; /** * 长 */ @Schema(description = "长") @Excel(name = "长(mm)") private Integer length; @Schema(description = "单位") @Excel(name = "单位") private String unit; /** * 宽 */ @Schema(description = "宽") @Excel(name = "宽(mm)") private Integer width; /** * 高 */ @Schema(description = "高") @Excel(name = "高(mm)") private Integer height; /** * 块数 */ @Schema(description = "块数") @Excel(name = "块数") private Integer quantity; /** * 方数 */ @Schema(description = "方数") @Excel(name = "方数") private BigDecimal volume; /** * 强度 */ @Schema(description = "强度") @Excel(name = "强度") private String strength; /** * 开始日期 */ @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @Schema(description = "开始日期") @Excel(name = "开始日期", width = 20, dateFormat = "yyyy-MM-dd") private Date startDate; /** * 结束日期 */ @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @Schema(description = "结束日期") @Excel(name = "结束日期", width = 20, dateFormat = "yyyy-MM-dd") private Date endDate; /** * 提交人 */ @Schema(description = "提交人") @Excel(name = "提交人") private String submitter; /** * 提交人组织 */ @Schema(description = "提交人组织") @Excel(name = "提交人组织") private String submitOrg; /** * 备注1 */ @Schema(description = "备注1") @Excel(name = "备注1") private String remarkOne; /** * 备注2 */ @Schema(description = "备注2") @Excel(name = "备注2") private String remarkTwo; /** * 创建人 @@ -155,22 +71,11 @@ @Excel(name = "修改人", type = Excel.Type.EXPORT) private String modifierName; /** * 数据同步类型:1=手动 2=定时任务 */ @Schema(description = "数据同步类型:1=手动 2=定时任务") private Integer dataSyncType; /** * 数据来源类型:1=同步 2=新增 * 已下发数量 */ @Schema(description = "数据来源类型:1=同步 2=新增") private Integer dataSourceType; /** * 下发数量 */ @Schema(description = "下发数量") @Excel(name = "下发数量", type = Excel.Type.EXPORT) @Schema(description = "已下发数量") @Excel(name = "已下发数量", type = Excel.Type.EXPORT) private BigDecimal assignedQuantity; } src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java
@@ -9,8 +9,6 @@ @Data @Schema(name = "ProductionPlanVo", description = "生产计划返回对象") public class ProductionPlanVo extends ProductionPlan { @Schema(description = "物料编码") private String materialCode; @Schema(description = "产品名称") private String productName; @@ -21,6 +19,6 @@ @Schema(description = "单位") private String unit; @Schema(description = "关联产品物料ID") private Long productMaterialId; @Schema(description = "产品ID") private Long productId; } src/main/java/com/ruoyi/production/mapper/ProductionPlanMapper.java
@@ -27,5 +27,4 @@ List<ProductionPlanDto> selectWithMaterialByIds(@Param("ids") List<Long> ids); ProductionPlanDto selectProductionPlanDtoById(@Param("productionPlanId") Long productionPlanId); } src/main/java/com/ruoyi/production/pojo/ProductionPlan.java
@@ -30,6 +30,12 @@ @TableId(value = "id", type = IdType.AUTO) private Long id; @Schema(description = "销售台账id") private Long salesLedgerId; @Schema(description = "销售产品规格id") private Long salesLedgerProductId; @Schema(description = "主生产计划号") private String mpsNo; @@ -55,6 +61,10 @@ @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; @Schema(description = "部门ID") @TableField(fill = FieldFill.INSERT) private Long deptId; @Schema(description = "产品型号id") private Long productModelId; @@ -64,7 +74,7 @@ @Schema(description = "是否下发制造订单") private Boolean issued; @Schema(description = "来源") @Schema(description = "来源 销售/内部") private String source; @Schema(description = "审核状态") @@ -72,9 +82,6 @@ @Schema(description = "承诺日期") private LocalDate promisedDeliveryDate; @Schema(description = "申请单编号") private String applyNo; @Schema(description = "状态 0未下发 1部分下发 2已下发") private Integer status; 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
@@ -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; @@ -17,6 +18,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 +26,24 @@ 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.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; 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 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,133 +51,98 @@ } /** * 合并生产计划 * 合并生产计划并下发生产订单。 * 业务约束: * 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; } @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); 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; } @@ -179,51 +150,49 @@ @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())) { 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 + " 已存在"); } } @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; @@ -240,80 +209,81 @@ 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<>(); 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 (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 (!mpsNos.add(mpsNo)) { throw new ServiceException("导入失败:Excel 中存在重复的申请单编号 " + mpsNo); } } 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)) .stream().map(ProductionPlan::getApplyNo).collect(Collectors.toList()); throw new ServiceException("导入失败,申请单编号已存在: " + String.join(", ", existApplyNos)); 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(0); 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 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); } } src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -232,6 +232,7 @@ private BigDecimal ticketsTotal = BigDecimal.ZERO; @Schema(description = "是否质检") //针对采购台账,是否质检 private Boolean isChecked; @TableField(exist = false) @@ -251,4 +252,8 @@ @TableField(fill = FieldFill.INSERT) private Long deptId; @Schema(description = "是否生产") //针对销售台账,是否生产 private Boolean isProduction; } src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -6,20 +6,14 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; 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.ProductionPlan; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.quality.mapper.QualityInspectMapper; import com.ruoyi.quality.pojo.QualityInspect; import com.ruoyi.sales.dto.InvoiceRegistrationProductDto; import com.ruoyi.sales.dto.SalesLedgerProductDto; import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper; @@ -68,7 +62,7 @@ private final ProductionAccountMapper productionAccountMapper; private final SalesLedgerMapper salesLedgerMapper; private final PurchaseLedgerMapper purchaseLedgerMapper; private final ProductionOrderMapper productionOrderMapper; private final ProductionPlanMapper productionPlanMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionOrderService productionOrderService; private final TechnologyRoutingMapper technologyRoutingMapper; @@ -256,70 +250,36 @@ * 新增生产数据 */ public void addProductionData(SalesLedgerProduct salesLedgerProduct) { ProductionOrder productionOrder = new ProductionOrder(); productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); productionOrder.setSaleLedgerProductId(salesLedgerProduct.getId().intValue()); productionOrder.setNpsNo(generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); productionOrder.setQuantity(salesLedgerProduct.getQuantity()); productionOrder.setCompleteQuantity(BigDecimal.ZERO); //先判断该产品是否需要生产 if (!salesLedgerProduct.getIsProduction()) { return; } ProductionPlan productionPlan = new ProductionPlan(); productionPlan.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); productionPlan.setProductModelId(salesLedgerProduct.getProductModelId()); productionPlan.setSalesLedgerProductId(salesLedgerProduct.getId()); productionPlan.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))); productionPlan.setQtyRequired(salesLedgerProduct.getQuantity()); productionPlanMapper.insert(productionPlan); TechnologyRouting routing = technologyRoutingMapper.selectOne( new QueryWrapper<TechnologyRouting>().lambda() .eq(TechnologyRouting::getProductModelId, salesLedgerProduct.getProductModelId()) .orderByDesc(TechnologyRouting::getCreateTime) .last("LIMIT 1")); if (routing != null) { productionOrder.setTechnologyRoutingId(routing.getId()); } productionOrderMapper.insert(productionOrder); if (productionOrder.getTechnologyRoutingId() != null) { productionOrderService.syncProductionOrderSnapshot(productionOrder.getId()); } } /** * 删除生产数据 * 删除生产计划 */ public void deleteProductionData(List<Long> productIds) { List<ProductionOrder> productionOrders = productionOrderMapper.selectList( new LambdaQueryWrapper<ProductionOrder>() .in(ProductionOrder::getSaleLedgerProductId, productIds.stream().map(Long::intValue).collect(Collectors.toList()))); if (org.springframework.util.CollectionUtils.isEmpty(productionOrders)) { List<ProductionPlan> productionPlans = productionPlanMapper.selectList( new LambdaQueryWrapper<ProductionPlan>() .in(ProductionPlan::getSalesLedgerProductId, productIds.stream().map(Long::intValue).collect(Collectors.toList()))); if (org.springframework.util.CollectionUtils.isEmpty(productionPlans)) { return; } List<Long> orderIds = productionOrders.stream().map(ProductionOrder::getId).collect(Collectors.toList()); List<Long> taskIds = productionOperationTaskMapper.selectList( new LambdaQueryWrapper<ProductionOperationTask>() .in(ProductionOperationTask::getProductionOrderId, orderIds)) .stream().map(ProductionOperationTask::getId).collect(Collectors.toList()); if (!taskIds.isEmpty()) { List<ProductionProductMain> productMains = productionProductMainMapper.selectList( new LambdaQueryWrapper<ProductionProductMain>() .in(ProductionProductMain::getProductionOperationTaskId, taskIds)); List<Long> productMainIds = productMains.stream().map(ProductionProductMain::getId).collect(Collectors.toList()); if (!productMainIds.isEmpty()) { List<QualityInspect> qualityInspects = qualityInspectMapper.selectList( new LambdaQueryWrapper<QualityInspect>().in(QualityInspect::getProductMainId, productMainIds)); qualityInspects.forEach(qualityInspect -> { if (qualityInspect.getInspectState() == 1) { throw new RuntimeException("已提交的检验单不能删除"); //如果生产计划已下发则不能删除 if (productionPlans.stream().anyMatch(productionPlan -> productionPlan.getStatus() != 0)) { throw new RuntimeException("生产计划已下发,不能删除该销售产品"); } }); productionProductOutputMapper.deleteByProductMainIds(productMainIds); productionProductInputMapper.deleteByProductMainIds(productMainIds); qualityInspectMapper.deleteByProductMainIds(productMainIds); productionAccountMapper.delete(new LambdaQueryWrapper<ProductionAccount>() .in(ProductionAccount::getProductionProductMainId, productMainIds)); for (Long productMainId : productMainIds) { stockUtils.deleteStockOutRecord(productMainId, StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode()); stockUtils.deleteStockInRecord(productMainId, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode()); } } productionProductMainMapper.delete(new LambdaQueryWrapper<ProductionProductMain>() .in(ProductionProductMain::getProductionOperationTaskId, taskIds)); } productionOrderService.removeProductionOrder(orderIds); List<Long> ids = productionPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList()); productionPlanMapper.deleteByIds(ids); } @Override @@ -432,21 +392,21 @@ return R.ok(); } private String generateNextOrderNo(String datePrefix) { QueryWrapper<ProductionOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.likeRight("nps_no", "SC" + datePrefix); queryWrapper.orderByDesc("nps_no"); private String generateNextPlanNo(String datePrefix) { QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>(); queryWrapper.likeRight("mps_no", "JH" + datePrefix); queryWrapper.orderByDesc("mps_no"); queryWrapper.last("LIMIT 1"); ProductionOrder latestOrder = productionOrderMapper.selectOne(queryWrapper); ProductionPlan latestPlan = productionPlanMapper.selectOne(queryWrapper); int sequence = 1; if (latestOrder != null && latestOrder.getNpsNo() != null && !latestOrder.getNpsNo().isEmpty()) { String sequenceStr = latestOrder.getNpsNo().substring(("SC" + datePrefix).length()); 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 "SC" + datePrefix + String.format("%04d", sequence); return "JH" + datePrefix + String.format("%04d", sequence); } } src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -520,7 +520,7 @@ List<Long> productIds = products.stream() .map(SalesLedgerProduct::getId) .collect(Collectors.toList()); //删除生产数据 //删除生产计划 salesLedgerProductServiceImpl.deleteProductionData(productIds); // 批量删除产品子表 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>