src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
@@ -20,10 +20,6 @@ @Excel(name = "产品名称") private String productName; @Schema(description = "客户名称") @Excel(name = "客户名称") private String customerName; @Schema(description = "产品规格") @Excel(name = "产品规格") private String model; 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; 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/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; @@ -135,12 +138,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 +162,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 +204,6 @@ if (file == null || file.isEmpty()) { throw new ServiceException("导入数据不能为空"); } ExcelUtil<ProductionPlanImportDto> excelUtil = new ExcelUtil<>(ProductionPlanImportDto.class); List<ProductionPlanImportDto> list; try { @@ -210,77 +211,54 @@ } 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 String formatPlanIds(List<Long> planIds) { @@ -290,4 +268,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); } } 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,17 +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.*; 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; @@ -65,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; @@ -253,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("已提交的检验单不能删除"); } }); 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)); //如果生产计划已下发则不能删除 if (productionPlans.stream().anyMatch(productionPlan -> productionPlan.getStatus() != 0)) { throw new RuntimeException("生产计划已下发,不能删除该销售产品"); } productionOrderService.removeProductionOrder(orderIds); List<Long> ids = productionPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList()); productionPlanMapper.deleteByIds(ids); } @Override @@ -429,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); // 批量删除产品子表