src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
@@ -8,6 +8,8 @@ import com.ruoyi.production.service.ProductionOrderService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -47,7 +49,18 @@ } @PostMapping @Operation(summary = "新增生产订单") @Operation( summary = "新增生产订单", description = "新增下单只支持两种方式:1. 生产计划生成,传 productionPlanIds,系统自动汇总计划得到产品规格和数量;" + "2. 手动新增,必须传 productModelId 和 quantity。" + "technologyRoutingId 为空时会自动匹配该产品规格最新工艺路线,quantity 最终必须大于 0。" ) @io.swagger.v3.oas.annotations.parameters.RequestBody( required = true, description = "前端友好提示:如果是生产计划生成,请传 productionPlanIds;" + "如果是手动新增,请至少填写 productModelId、quantity,且 quantity 必须大于 0。", content = @Content(schema = @Schema(implementation = ProductionOrder.class)) ) public R<Boolean> add(@RequestBody ProductionOrder productionOrder) { return R.ok(productionOrderService.saveProductionOrder(productionOrder)); } src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -29,13 +29,10 @@ @TableId(value = "id", type = IdType.AUTO) private Long id; @Schema(description = "销售台账id") private Long salesLedgerId; @Schema(description = "生产计划ID列表,格式:[1,2,3]") @Schema(description = "生产计划ID列表,格式:[1,2,3]。如果按生产计划生成订单,新增时传这个字段即可,系统会自动汇总产品规格和数量。") private String productionPlanIds; @Schema(description = "产品规格id") @Schema(description = "产品规格id。手动新增时必填;如果传了 productionPlanIds,则可由系统自动带出。") private Long productModelId; @Schema(description = "生产订单号") @@ -49,10 +46,10 @@ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @Schema(description = "工艺路线id") @Schema(description = "工艺路线id。新增时选填,不传则系统按产品规格自动匹配最新工艺路线。") private Long technologyRoutingId; @Schema(description = "需求数量") @Schema(description = "需求数量。手动新增时必填且必须大于 0;如果传了 productionPlanIds,则可由系统自动带出。") private BigDecimal quantity; @Schema(description = "完成数量") @@ -64,9 +61,6 @@ @Schema(description = "结束日期") private LocalDateTime endTime; @Schema(description = "销售产品规格id") private Integer saleLedgerProductId; @Schema(description = "创建人ID") @TableField(fill = FieldFill.INSERT) private Long createUser; @@ -75,7 +69,7 @@ @TableField(fill = FieldFill.INSERT) private Long deptId; @Schema(description = "计划完成时间") @Schema(description = "计划完成时间。选填;按生产计划生成订单时,系统会优先自动带出交期。") @JsonFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate planCompleteTime; src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -31,10 +31,6 @@ import com.ruoyi.production.pojo.ProductionPlan; import com.ruoyi.production.pojo.ProductionProductMain; import com.ruoyi.production.enums.ProductOrderStatusEnum; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.technology.mapper.TechnologyBomMapper; import com.ruoyi.technology.mapper.TechnologyBomStructureMapper; @@ -79,8 +75,6 @@ private final ProductionOrderPickMapper productionOrderPickMapper; private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper; private final ProductionPlanMapper productionPlanMapper; private final SalesLedgerMapper salesLedgerMapper; private final SalesLedgerProductMapper salesLedgerProductMapper; private final TechnologyRoutingMapper technologyRoutingMapper; private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper; private final TechnologyRoutingOperationParamMapper technologyRoutingOperationParamMapper; @@ -318,7 +312,6 @@ ProductionOrder query = dto == null ? new ProductionOrder() : dto; return Wrappers.<ProductionOrder>lambdaQuery() .eq(query.getId() != null, ProductionOrder::getId, query.getId()) .eq(query.getSalesLedgerId() != null, ProductionOrder::getSalesLedgerId, query.getSalesLedgerId()) .eq(query.getProductModelId() != null, ProductionOrder::getProductModelId, query.getProductModelId()) .eq(query.getTechnologyRoutingId() != null, ProductionOrder::getTechnologyRoutingId, query.getTechnologyRoutingId()) .like(query.getNpsNo() != null && !query.getNpsNo().trim().isEmpty(), ProductionOrder::getNpsNo, query.getNpsNo()) @@ -370,10 +363,9 @@ if (productionOrder == null) { throw new ServiceException("Production order is required"); } fillFromSalesLedgerProduct(productionOrder); fillFromProductionPlans(productionOrder); if (productionOrder.getProductModelId() == null) { throw new ServiceException("productModelId is required"); throw new ServiceException("productModelId is required when manually creating a production order"); } if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("quantity must be greater than 0"); @@ -396,36 +388,6 @@ || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0) { throw new ServiceException("Started production orders cannot modify product, routing or quantity"); } } } private void fillFromSalesLedgerProduct(ProductionOrder productionOrder) { if (productionOrder.getSaleLedgerProductId() == null) { return; } // 销售明细是订单来源时,以销售明细为准回填销售台账、产品规格和默认数量。 SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productionOrder.getSaleLedgerProductId().longValue()); if (salesLedgerProduct == null) { throw new ServiceException("Sales ledger product not found"); } if (productionOrder.getSalesLedgerId() == null) { productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); } else if (!Objects.equals(productionOrder.getSalesLedgerId(), salesLedgerProduct.getSalesLedgerId())) { throw new ServiceException("salesLedgerId does not match the sales ledger product"); } if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), salesLedgerProduct.getProductModelId())) { throw new ServiceException("productModelId does not match the sales ledger product"); } if (productionOrder.getQuantity() == null || productionOrder.getQuantity().compareTo(BigDecimal.ZERO) <= 0) { productionOrder.setQuantity(salesLedgerProduct.getQuantity()); } if (productionOrder.getPlanCompleteTime() == null && productionOrder.getSalesLedgerId() != null) { SalesLedger salesLedger = salesLedgerMapper.selectById(productionOrder.getSalesLedgerId()); if (salesLedger != null && salesLedger.getDeliveryDate() != null) { productionOrder.setPlanCompleteTime(salesLedger.getDeliveryDate()); } } } src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -245,8 +245,6 @@ } ProductionAccount productionAccount = new ProductionAccount(); productionAccount.setProductionProductMainId(productionProductMain.getId()); productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId()); productionAccount.setSalesLedgerProductId(productionOrder.getSaleLedgerProductId() == null ? null : productionOrder.getSaleLedgerProductId().longValue()); productionAccount.setSchedulingUserId(user == null ? null : user.getUserId()); productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName()); productionAccount.setFinishedNum(productQty); src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -6,6 +6,7 @@ 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.exception.ServiceException; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum; import com.ruoyi.framework.web.domain.R; @@ -13,9 +14,8 @@ 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.ProductionPlan; import com.ruoyi.production.pojo.ProductionProductMain; import com.ruoyi.production.service.ProductionOrderService; import com.ruoyi.purchase.mapper.PurchaseLedgerMapper; import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.quality.mapper.QualityInspectMapper; @@ -44,9 +44,7 @@ import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -68,9 +66,8 @@ 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; private final TechnologyBomStructureMapper technologyBomStructureMapper; private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper; @@ -256,70 +253,49 @@ * 新增生产数据 */ 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); 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()); if (!Integer.valueOf(1).equals(salesLedgerProduct.getType())) { return; } productionOrderMapper.insert(productionOrder); if (productionOrder.getTechnologyRoutingId() != null) { productionOrderService.syncProductionOrderSnapshot(productionOrder.getId()); SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId()); ProductionPlan productionPlan = new ProductionPlan(); productionPlan.setApplyNo(buildSalesPlanApplyNo(salesLedgerProduct.getId())); productionPlan.setSource("sales"); productionPlan.setProductModelId(salesLedgerProduct.getProductModelId()); productionPlan.setQtyRequired(salesLedgerProduct.getQuantity()); productionPlan.setRemark("销售台账自动生成"); productionPlan.setIssued(Boolean.FALSE); productionPlan.setStatus(0); if (salesLedger != null) { productionPlan.setMpsNo(salesLedger.getSalesContractNo()); if (salesLedger.getDeliveryDate() != null) { productionPlan.setRequiredDate(salesLedger.getDeliveryDate().atStartOfDay()); productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate().atStartOfDay()); } } productionPlanMapper.insert(productionPlan); } /** * 删除生产数据 */ 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<String> applyNos = productIds.stream() .filter(Objects::nonNull) .map(this::buildSalesPlanApplyNo) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(applyNos)) { 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)); List<ProductionPlan> productionPlans = productionPlanMapper.selectList( new LambdaQueryWrapper<ProductionPlan>().in(ProductionPlan::getApplyNo, applyNos)); if (CollectionUtils.isEmpty(productionPlans)) { return; } productionOrderService.removeProductionOrder(orderIds); boolean hasIssued = productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued()) || (item.getStatus() != null && item.getStatus() > 0)); if (hasIssued) { throw new ServiceException("对应生产计划已下发生成生产订单,请先处理生产计划/生产订单后再修改销售台账"); } productionPlanMapper.deleteByIds(productionPlans.stream().map(ProductionPlan::getId).collect(Collectors.toList())); } @Override @@ -432,21 +408,7 @@ return R.ok(); } private String generateNextOrderNo(String datePrefix) { QueryWrapper<ProductionOrder> queryWrapper = new QueryWrapper<>(); queryWrapper.likeRight("nps_no", "SC" + datePrefix); queryWrapper.orderByDesc("nps_no"); queryWrapper.last("LIMIT 1"); ProductionOrder latestOrder = productionOrderMapper.selectOne(queryWrapper); int sequence = 1; if (latestOrder != null && latestOrder.getNpsNo() != null && !latestOrder.getNpsNo().isEmpty()) { String sequenceStr = latestOrder.getNpsNo().substring(("SC" + datePrefix).length()); try { sequence = Integer.parseInt(sequenceStr) + 1; } catch (NumberFormatException e) { sequence = 1; } } return "SC" + datePrefix + String.format("%04d", sequence); private String buildSalesPlanApplyNo(Long salesLedgerProductId) { return "SALE_PRODUCT_" + salesLedgerProductId; } } src/main/resources/mapper/production/ProductionOrderMapper.xml
@@ -5,7 +5,6 @@ <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionOrder"> <id column="id" property="id" /> <result column="sales_ledger_id" property="salesLedgerId" /> <result column="production_plan_ids" property="productionPlanIds" /> <result column="product_model_id" property="productModelId" /> <result column="nps_no" property="npsNo" /> @@ -16,7 +15,6 @@ <result column="complete_quantity" property="completeQuantity" /> <result column="start_time" property="startTime" /> <result column="end_time" property="endTime" /> <result column="sale_ledger_product_id" property="saleLedgerProductId" /> <result column="create_user" property="createUser" /> <result column="dept_id" property="deptId" /> </resultMap>