src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
@@ -5,6 +5,7 @@ import com.ruoyi.framework.web.domain.R; import com.ruoyi.production.bean.dto.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; import com.ruoyi.production.bean.vo.ProductionPlanVo; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.service.ProductionOrderService; import io.swagger.v3.oas.annotations.Operation; @@ -43,18 +44,11 @@ } @PostMapping("/addOrder") @Operation(summary = "新增生产订单", description = "新增下单只支持两种方式:1. 生产计划生成,传 productionPlanIds,系统自动汇总计划得到产品规格和数量;" + "2. 手动新增,必须传 productModelId 和 quantity。" @Operation(summary = "新增生产订单", description = "新增下单只支持1种方式:生产计划生成,传 productionPlanIds,系统自动汇总计划得到产品规格和数量;" + "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))) , content = @Content(schema = @Schema(implementation = ProductionOrder.class))) public R<Boolean> add(@RequestBody ProductionOrder productionOrder) { return R.ok(productionOrderService.saveProductionOrder(productionOrder)); } @PutMapping("/editOrder") @Operation(summary = "修改生产订单") public R<Boolean> edit(@RequestBody ProductionOrder productionOrder) { return R.ok(productionOrderService.saveProductionOrder(productionOrder)); } @@ -75,4 +69,10 @@ public R<Boolean> remove(@RequestBody List<Long> ids) { return R.ok(productionOrderService.removeProductionOrder(ids)); } @GetMapping("/source/{id}") @Operation(summary = "生产订单查询来源") public R<List<ProductionPlanVo>> getSource(@PathVariable Long id) { return R.ok(productionOrderService.getSource(id)); } } src/main/java/com/ruoyi/production/mapper/ProductionPlanMapper.java
@@ -27,4 +27,6 @@ List<ProductionPlanDto> selectWithMaterialByIds(@Param("ids") List<Long> ids); ProductionPlanDto selectProductionPlanDtoById(@Param("productionPlanId") Long productionPlanId); List<ProductionPlanVo> getSource(@Param("ids") List<Long> planIds); } src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -29,9 +29,6 @@ @TableId(value = "id", type = IdType.AUTO) private Long id; @Schema(description = "销售台账id。生产下单接口通常不需要前端手工填写,存在销售来源时由系统内部回填。") private Long salesLedgerId; @Schema(description = "生产计划ID列表,格式:[1,2,3]。如果按生产计划生成订单,新增时传这个字段即可,系统会自动汇总产品规格和数量。") private String productionPlanIds; @@ -63,9 +60,6 @@ @Schema(description = "结束日期") private LocalDateTime endTime; @Schema(description = "销售产品规格id。生产下单接口一般不作为前端必填项,存在销售来源时由系统内部关联。") private Integer salesLedgerProductId; @Schema(description = "创建人ID") @TableField(fill = FieldFill.INSERT) src/main/java/com/ruoyi/production/pojo/ProductionOrderPick.java
@@ -3,8 +3,6 @@ import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.math.BigDecimal; src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java
@@ -31,8 +31,8 @@ @Schema(description = "工艺路线工序表id") private Long technologyRoutingOperationId; @Schema(description = "工艺路线id") private Long technologyRoutingId; @Schema(description = "生产订单工艺路线id") private Long orderRoutingId; @Schema(description = "产品规格id") private Long productModelId; src/main/java/com/ruoyi/production/pojo/ProductionPlan.java
@@ -74,9 +74,6 @@ @Schema(description = "已下发数量") private BigDecimal quantityIssued; @Schema(description = "是否下发制造订单") private Boolean issued; @Schema(description = "来源 销售/内部") private String source; src/main/java/com/ruoyi/production/service/ProductionOrderService.java
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.production.bean.dto.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; import com.ruoyi.production.bean.vo.ProductionPlanVo; import com.ruoyi.production.pojo.ProductionOrder; import java.util.List; @@ -24,4 +25,6 @@ int syncProductionOrderSnapshot(Long productionOrderId); Object bindingRoute(ProductionOrderDto productionOrderDto); List<ProductionPlanVo> getSource(Long id); } src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
@@ -91,7 +91,7 @@ Long routingId = null; ProductionOrderRoutingOperation deleteItem = productionOrderRoutingOperationMapper.selectById(id); if (deleteItem != null) { routingId = deleteItem.getTechnologyRoutingId(); routingId = deleteItem.getOrderRoutingId(); } productionOperationTaskMapper.delete(new LambdaQueryWrapper<ProductionOperationTask>() .eq(ProductionOperationTask::getTechnologyRoutingOperationId, id)); @@ -99,7 +99,7 @@ if (routingId != null) { List<ProductionOrderRoutingOperation> operationList = productionOrderRoutingOperationMapper.selectList( Wrappers.<ProductionOrderRoutingOperation>lambdaQuery() .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, routingId) .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingId) .eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId) .orderByAsc(ProductionOrderRoutingOperation::getDragSort)); for (int i = 0; i < operationList.size(); i++) { @@ -121,7 +121,7 @@ ProductionOrderRoutingOperation oldItem = productionOrderRoutingOperationMapper.selectById(productionOrderRoutingOperation.getId()); List<ProductionOrderRoutingOperation> operationList = productionOrderRoutingOperationMapper.selectList( Wrappers.<ProductionOrderRoutingOperation>lambdaQuery() .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, oldItem.getTechnologyRoutingId()) .eq(ProductionOrderRoutingOperation::getOrderRoutingId, oldItem.getOrderRoutingId()) .orderByAsc(ProductionOrderRoutingOperation::getDragSort)); Integer targetPosition = productionOrderRoutingOperation.getDragSort(); if (targetPosition != null && targetPosition >= 1) { src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -16,14 +16,13 @@ import com.ruoyi.common.exception.ServiceException; import com.ruoyi.production.bean.dto.ProductionOrderDto; import com.ruoyi.production.bean.vo.ProductionOrderVo; import com.ruoyi.production.bean.vo.ProductionPlanVo; import com.ruoyi.production.enums.ProductOrderStatusEnum; import com.ruoyi.production.mapper.*; import com.ruoyi.production.pojo.*; import com.ruoyi.production.service.ProductionOrderService; 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.technology.mapper.*; import com.ruoyi.technology.pojo.*; import lombok.RequiredArgsConstructor; @@ -105,7 +104,6 @@ if (!saved) { return false; } syncProductionPlanIssueStatus(oldOrder, productionOrder); boolean needSync = productionOrder.getTechnologyRoutingId() != null && (oldOrder == null || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) @@ -180,6 +178,16 @@ } @Override public List<ProductionPlanVo> getSource(Long id) { ProductionOrder productionOrder = baseMapper.selectById(id); if (productionOrder != null && productionOrder.getProductionPlanIds() != null) { List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds()); return productionPlanMapper.getSource(planIds); } return null; } @Override public int syncProductionOrderSnapshot(Long productionOrderId) { ProductionOrder productionOrder = this.getById(productionOrderId); if (productionOrder == null) { @@ -224,7 +232,7 @@ ProductionOrderRoutingOperation targetOperation = new ProductionOrderRoutingOperation(); targetOperation.setProductionOrderId(productionOrder.getId()); targetOperation.setTechnologyRoutingOperationId(sourceOperation.getId()); targetOperation.setTechnologyRoutingId(orderRouting.getId()); targetOperation.setOrderRoutingId(orderRouting.getId()); targetOperation.setProductModelId(sourceOperation.getProductModelId()); targetOperation.setDragSort(sourceOperation.getDragSort()); targetOperation.setIsQuality(sourceOperation.getIsQuality()); @@ -354,7 +362,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()) @@ -406,7 +413,6 @@ if (productionOrder == null) { throw new ServiceException("生产订单不能为空"); } fillFromSalesLedgerProduct(productionOrder); fillFromProductionPlans(productionOrder); if (productionOrder.getProductModelId() == null) { throw new ServiceException("产品规格ID不能为空"); @@ -414,54 +420,23 @@ if (defaultDecimal(productionOrder.getQuantity()).compareTo(BigDecimal.ZERO) <= 0) { throw new ServiceException("下单数量必须大于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("未找到该产品规格对应的工艺路线"); // } // 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) { productionOrder.setTechnologyRoutingId(technologyRouting.getId()); } } if (oldOrder != null && ProductOrderStatusEnum.isStarted(oldOrder.getStatus())) { // 开工后只允许修正非核心字段,核心生产依据锁定。 if (!Objects.equals(oldOrder.getProductModelId(), productionOrder.getProductModelId()) || !Objects.equals(oldOrder.getTechnologyRoutingId(), productionOrder.getTechnologyRoutingId()) || compareDecimal(oldOrder.getQuantity(), productionOrder.getQuantity()) != 0) { throw new ServiceException("生产订单已开工,不能修改产品、工艺路线或数量"); } } } private void fillFromSalesLedgerProduct(ProductionOrder productionOrder) { if (productionOrder.getSalesLedgerProductId() == null) { return; } // 销售明细是订单来源时,以销售明细为准回填销售台账、产品规格和默认数量。 SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productionOrder.getSalesLedgerProductId().longValue()); if (salesLedgerProduct == null) { throw new ServiceException("销售台账产品不存在"); } if (productionOrder.getSalesLedgerId() == null) { productionOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId()); } else if (!Objects.equals(productionOrder.getSalesLedgerId(), salesLedgerProduct.getSalesLedgerId())) { throw new ServiceException("销售台账ID与销售台账产品不一致"); } if (productionOrder.getProductModelId() == null) { productionOrder.setProductModelId(salesLedgerProduct.getProductModelId()); } else if (!Objects.equals(productionOrder.getProductModelId(), salesLedgerProduct.getProductModelId())) { throw new ServiceException("产品规格ID与销售台账产品不一致"); } 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()); } } } @@ -507,43 +482,73 @@ productionOrder.setProductionPlanIds(formatPlanIds(planIds)); } private void syncProductionPlanIssueStatus(ProductionOrder oldOrder, ProductionOrder newOrder) { // 只处理本次增量变化,避免无关计划被重复写状态。 Set<Long> oldIds = new LinkedHashSet<>(parsePlanIds(oldOrder == null ? null : oldOrder.getProductionPlanIds())); Set<Long> newIds = new LinkedHashSet<>(parsePlanIds(newOrder == null ? null : newOrder.getProductionPlanIds())); Set<Long> toRelease = new LinkedHashSet<>(oldIds); toRelease.removeAll(newIds); Set<Long> toIssue = new LinkedHashSet<>(newIds); toIssue.removeAll(oldIds); if (!toRelease.isEmpty()) { updatePlanIssuedFlag(new ArrayList<>(toRelease), false); } if (!toIssue.isEmpty()) { updatePlanIssuedFlag(new ArrayList<>(toIssue), true); } } private void releaseProductionPlanIssueStatus(ProductionOrder productionOrder) { if (productionOrder == null) { return; } List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds()); if (!planIds.isEmpty()) { updatePlanIssuedFlag(planIds, false); // 生产订单删除--对应的生产计划的已下发数量要减去 updatePlanIssuedFlag(planIds, productionOrder.getQuantity()); } } private void updatePlanIssuedFlag(List<Long> planIds, boolean issued) { //生产订单删除,生产计划的已下发数量对应变更 private void updatePlanIssuedFlag(List<Long> planIds, BigDecimal remainingAssignedQuantity) { if (planIds == null || planIds.isEmpty()) { return; } List<ProductionPlan> plans = productionPlanMapper.selectBatchIds(planIds); //下发数量减去 List<ProductionPlan> updates = new ArrayList<>(); for (ProductionPlan plan : plans) { BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); if (requiredQuantity.compareTo(BigDecimal.ZERO) < 0) { requiredQuantity = BigDecimal.ZERO; } BigDecimal remainingQuantity = resolveRemainingQuantity(plan); BigDecimal historicalIssuedQuantity = requiredQuantity.subtract(remainingQuantity); BigDecimal issuedQuantity = remainingAssignedQuantity.min(historicalIssuedQuantity); remainingAssignedQuantity = remainingAssignedQuantity.subtract(issuedQuantity); BigDecimal totalIssuedQuantity = historicalIssuedQuantity.subtract(issuedQuantity); int planStatus = resolvePlanStatus(requiredQuantity, totalIssuedQuantity); ProductionPlan update = new ProductionPlan(); update.setId(plan.getId()); update.setIssued(issued); productionPlanMapper.updateById(update); update.setStatus(planStatus); update.setQuantityIssued(totalIssuedQuantity); updates.add(update); } if (!updates.isEmpty()) { productionPlanMapper.updateById(updates); } } private BigDecimal resolveRemainingQuantity(ProductionPlan plan) { if (plan == null) { return BigDecimal.ZERO; } BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO); if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { return BigDecimal.ZERO; } BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO); if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { return requiredQuantity; } if (issuedQuantity.compareTo(requiredQuantity) >= 0) { return BigDecimal.ZERO; } return requiredQuantity.subtract(issuedQuantity); } private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) { if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) { return 0; } if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) { return 0; } return issuedQuantity.compareTo(requiredQuantity) < 0 ? 1 : 2; } private void upsertOrderPick(ProductionOrder productionOrder) { src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -160,7 +160,6 @@ update.setId(plan.getId()); update.setStatus(planStatus); update.setQuantityIssued(totalIssuedQuantity); update.setIssued(planStatus == PLAN_STATUS_ISSUED); updates.add(update); } if (!updates.isEmpty()) { src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -169,7 +169,7 @@ List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList( Wrappers.<ProductionOrderRoutingOperation>lambdaQuery() .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, routingOperation.getTechnologyRoutingId()) .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId()) .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId())); boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size()); if (productQty.compareTo(BigDecimal.ZERO) > 0) { @@ -301,7 +301,7 @@ // 只有最后一道工序的报工才会影响生产订单完工数量。 List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList( Wrappers.<ProductionOrderRoutingOperation>lambdaQuery() .eq(ProductionOrderRoutingOperation::getTechnologyRoutingId, routingOperation.getTechnologyRoutingId()) .eq(ProductionOrderRoutingOperation::getOrderRoutingId, routingOperation.getOrderRoutingId()) .eq(ProductionOrderRoutingOperation::getProductionOrderId, routingOperation.getProductionOrderId())); boolean isLastOperation = routingOperation.getDragSort() != null && routingOperation.getDragSort().equals(routingOperationList.size()); if (isLastOperation) { src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -163,7 +163,7 @@ BeanUtils.copyProperties(sourceOperation, newOperation); newOperation.setId(null); newOperation.setProductionOrderId(newOrder.getId()); newOperation.setTechnologyRoutingId(routingIdMap.get(sourceOperation.getTechnologyRoutingId())); newOperation.setOrderRoutingId(routingIdMap.get(sourceOperation.getOrderRoutingId())); newOperation.setCreateTime(null); newOperation.setUpdateTime(null); productionOrderRoutingOperationMapper.insert(newOperation); 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="sales_ledger_product_id" property="salesLedgerProductId" /> <result column="create_user" property="createUser" /> <result column="dept_id" property="deptId" /> <result column="plan_complete_time" property="planCompleteTime" /> @@ -33,7 +31,6 @@ <sql id="ProductionOrderVoColumns"> po.id, po.sales_ledger_id, po.production_plan_ids, po.product_model_id, po.nps_no, @@ -44,13 +41,10 @@ po.complete_quantity, po.start_time, po.end_time, po.sales_ledger_product_id, po.create_user, po.dept_id, po.plan_complete_time, po.status, sl.sales_contract_no as salesContractNo, sl.customer_name as customerName, p.product_name as productName, pm.model as model, tr.process_route_code as processRouteCode, @@ -59,7 +53,6 @@ <sql id="ProductionOrderVoFrom"> from production_order po left join sales_ledger sl on po.sales_ledger_id = sl.id left join product_model pm on po.product_model_id = pm.id left join product p on pm.product_id = p.id left join technology_routing tr on po.technology_routing_id = tr.id @@ -72,17 +65,11 @@ <if test="c.id != null"> and po.id = #{c.id} </if> <if test="c.salesLedgerId != null"> and po.sales_ledger_id = #{c.salesLedgerId} </if> <if test="c.productModelId != null"> and po.product_model_id = #{c.productModelId} </if> <if test="c.technologyRoutingId != null"> and po.technology_routing_id = #{c.technologyRoutingId} </if> <if test="c.salesLedgerProductId != null"> and po.sales_ledger_product_id = #{c.salesLedgerProductId} </if> <if test="c.status != null"> and po.status = #{c.status} src/main/resources/mapper/production/ProductionPlanMapper.xml
@@ -84,4 +84,23 @@ left join product p on pm.product_id = p.id WHERE pp.id = #{productionPlanId} </select> <select id="getSource" resultType="com.ruoyi.production.bean.vo.ProductionPlanVo"> SELECT pp.*, pm.model, p.product_name AS productName, pm.unit, sl.sales_contract_no, sl.customer_name, sl.project_name FROM production_plan pp LEFT JOIN product_model pm ON pp.product_model_id = pm.id LEFT JOIN product p ON pm.product_id = p.id left join sales_ledger sl on pp.sales_ledger_id = sl.id WHERE pp.id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> ORDER BY pp.id ASC </select> </mapper>