huminmin
2026-04-25 a96948456e0a21ab33deeb2edfdd7ef343aec5bf
Merge branch 'dev_New_pro' of http://114.132.189.42:9002/r/product-inventory-management-after into dev_New_pro
已修改24个文件
394 ■■■■■ 文件已修改
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionPlanMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOperationTask.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderBom.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderPick.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 148 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationParamServiceImpl.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionPlanMapper.xml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyRoutingMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
@@ -42,4 +42,14 @@
    @Schema(description = "产品ID")
    private Long productId;
    @Schema(description = "筛选开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate requiredDateStart;
    @Schema(description = "筛选结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate requiredDateEnd;
}
src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java
@@ -21,4 +21,13 @@
    @Schema(description = "产品ID")
    private Long productId;
    @Schema(description = "销售合同号")
    private String salesContractNo;
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "项目名称")
    private String projectName;
}
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/ProductionOperationTask.java
@@ -73,4 +73,7 @@
    @Schema(description = "部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "权限用户ID列表,格式:[1,2,3]。指定多个用户ID时,用逗号分隔。")
    private String userIds;
}
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/ProductionOrderBom.java
@@ -3,11 +3,8 @@
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;
/**
 * <p>
@@ -28,26 +25,20 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "父节点ID")
    private Long parentId;
    @Schema(description = "生产订单id")
    private Long productionOrderId;
    @Schema(description = "产品规格id")
    private Long productModelId;
    @Schema(description = "工序id")
    private Long technologyOperationId;
    @Schema(description = "BOM编号")
    private String bomNo;
    @Schema(description = "单位产出需要数量")
    private BigDecimal unitQuantity;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "需求数量")
    private BigDecimal demandedQuantity;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "版本号")
    private String version;
    @Schema(description = "bom的id")
    private Long bomId;
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,14 +74,8 @@
    @Schema(description = "已下发数量")
    private BigDecimal quantityIssued;
    @Schema(description = "是否下发制造订单")
    private Boolean issued;
    @Schema(description = "来源 销售/内部")
    private String source;
    @Schema(description = "审核状态")
    private String isAudit;
    @Schema(description = "承诺日期")
    private LocalDate promisedDeliveryDate;
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());
@@ -289,10 +297,9 @@
        orderBom.setProductionOrderId(productionOrder.getId());
        orderBom.setBomId(Long.valueOf(technologyBom.getId()));
        orderBom.setProductModelId(root != null ? root.getProductModelId() : productionOrder.getProductModelId());
        orderBom.setTechnologyOperationId(root == null ? null : root.getOperationId());
        orderBom.setUnitQuantity(root != null && root.getUnitQuantity() != null ? root.getUnitQuantity() : BigDecimal.ONE);
        orderBom.setDemandedQuantity(orderQuantity);
        orderBom.setUnit(root == null ? null : root.getUnit());
        orderBom.setRemark(technologyBom.getRemark());
        orderBom.setBomNo(technologyBom.getBomNo());
        orderBom.setVersion(technologyBom.getVersion());
        productionOrderBomMapper.insert(orderBom);
        Map<Long, Long> idMap = new HashMap<>();
@@ -354,7 +361,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 +412,6 @@
        if (productionOrder == null) {
            throw new ServiceException("生产订单不能为空");
        }
        fillFromSalesLedgerProduct(productionOrder);
        fillFromProductionPlans(productionOrder);
        if (productionOrder.getProductModelId() == null) {
            throw new ServiceException("产品规格ID不能为空");
@@ -414,54 +419,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 +481,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) {
@@ -245,8 +245,8 @@
            }
            ProductionAccount productionAccount = new ProductionAccount();
            productionAccount.setProductionProductMainId(productionProductMain.getId());
            productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId());
            productionAccount.setSalesLedgerProductId(productionOrder.getSalesLedgerProductId() == null ? null : productionOrder.getSalesLedgerProductId().longValue());
//            productionAccount.setSalesLedgerId(productionOrder.getSalesLedgerId());
//            productionAccount.setSalesLedgerProductId(productionOrder.getSalesLedgerProductId() == null ? null : productionOrder.getSalesLedgerProductId().longValue());
            productionAccount.setSchedulingUserId(user == null ? null : user.getUserId());
            productionAccount.setSchedulingUserName(user == null ? dto.getUserName() : user.getNickName());
            productionAccount.setFinishedNum(productQty);
@@ -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/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -253,6 +253,7 @@
        if (!salesLedgerProduct.getIsProduction()) {
            return;
        }
        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId());
        ProductionPlan productionPlan = new ProductionPlan();
        productionPlan.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        productionPlan.setSalesLedgerProductId(salesLedgerProduct.getId());
@@ -261,6 +262,8 @@
        productionPlan.setQtyRequired(salesLedgerProduct.getQuantity());
        productionPlan.setSource("销售");
        productionPlan.setStatus(0);
        productionPlan.setRequiredDate(salesLedger.getDeliveryDate());//需求日期=交货日期
        productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate());//承诺日期=交货日期
        productionPlanMapper.insert(productionPlan);
    }
src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationController.java
@@ -7,6 +7,7 @@
import com.ruoyi.technology.bean.vo.TechnologyRoutingOperationVo;
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
import com.ruoyi.technology.service.TechnologyRoutingOperationService;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
@@ -57,4 +58,10 @@
    public R remove(@PathVariable("id") Long id) {
        return R.ok(technologyRoutingOperationService.removeTechnologyRoutingOperation(id));
    }
    @PostMapping ("/sort")
    @Operation(summary = "排序工艺路线工序")
    public R sort(@RequestBody TechnologyRoutingOperation technologyRoutingOperation) {
        return R.ok(technologyRoutingOperationService.sort(technologyRoutingOperation));
    }
}
src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationService.java
@@ -21,4 +21,6 @@
    boolean saveTechnologyRoutingOperation(TechnologyRoutingOperation technologyRoutingOperation);
    boolean removeTechnologyRoutingOperation(Long id);
    int sort(TechnologyRoutingOperation technologyRoutingOperation);
}
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationParamServiceImpl.java
@@ -3,6 +3,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.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -90,23 +91,25 @@
            );
        }
        int successCount = 0;
        for (TechnologyOperationParam operationParam : operationParamList) {
            boolean exists = technologyRoutingOperationParamMapper.selectCount(
                    Wrappers.<TechnologyRoutingOperationParam>lambdaQuery()
                            .eq(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, routingOperation.getId())
                            .eq(TechnologyRoutingOperationParam::getTechnologyOperationParamId, operationParam.getId())
            ) > 0;
            if (!replaceExisting && exists) {
                continue;
        if (CollectionUtils.isNotEmpty(operationParamList)) {
            for (TechnologyOperationParam operationParam : operationParamList) {
                boolean exists = technologyRoutingOperationParamMapper.selectCount(
                        Wrappers.<TechnologyRoutingOperationParam>lambdaQuery()
                                .eq(TechnologyRoutingOperationParam::getTechnologyRoutingOperationId, routingOperation.getId())
                                .eq(TechnologyRoutingOperationParam::getTechnologyOperationParamId, operationParam.getId())
                ) > 0;
                if (!replaceExisting && exists) {
                    continue;
                }
                TechnologyRoutingOperationParam snapshot = new TechnologyRoutingOperationParam();
                snapshot.setTechnologyRoutingOperationId(routingOperation.getId());
                snapshot.setTechnologyOperationParamId(operationParam.getId());
                snapshot.setTechnologyOperationId(operationParam.getTechnologyOperationId());
                snapshot.setStandardValue(operationParam.getStandardValue());
                fillFromOperationParam(snapshot, routingOperation);
                technologyRoutingOperationParamMapper.insert(snapshot);
                successCount++;
            }
            TechnologyRoutingOperationParam snapshot = new TechnologyRoutingOperationParam();
            snapshot.setTechnologyRoutingOperationId(routingOperation.getId());
            snapshot.setTechnologyOperationParamId(operationParam.getId());
            snapshot.setTechnologyOperationId(operationParam.getTechnologyOperationId());
            snapshot.setStandardValue(operationParam.getStandardValue());
            fillFromOperationParam(snapshot, routingOperation);
            technologyRoutingOperationParamMapper.insert(snapshot);
            successCount++;
        }
        return successCount;
    }
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationServiceImpl.java
@@ -76,6 +76,40 @@
        return removed;
    }
    @Override
    public int sort(TechnologyRoutingOperation technologyRoutingOperation) {
        //查询被改动的这条数据
        TechnologyRoutingOperation oldtechnologyRoutingOperation = technologyRoutingOperationMapper.selectById(technologyRoutingOperation.getId());
        //查询该工艺路线的所有工序并按照顺序排序
        List<TechnologyRoutingOperation> technologyRouteOperations = technologyRoutingOperationMapper.selectList(Wrappers.<TechnologyRoutingOperation>lambdaQuery()
                .eq(TechnologyRoutingOperation::getTechnologyRoutingId, oldtechnologyRoutingOperation.getTechnologyRoutingId())
                .orderByAsc(TechnologyRoutingOperation::getDragSort));
        // 获取目标位置(移动到第几个之后)
        Integer targetPosition = technologyRoutingOperation.getDragSort();
        if (targetPosition != null && targetPosition >= 0) {
            // 移动元素到新的位置
            technologyRouteOperations.remove(oldtechnologyRoutingOperation);
            technologyRouteOperations.add(targetPosition-1, oldtechnologyRoutingOperation);
            // 更新所有受影响的排序字段
            for (int i = 0; i < technologyRouteOperations.size(); i++) {
                TechnologyRoutingOperation item = technologyRouteOperations.get(i);
                if (!item.getId().equals(oldtechnologyRoutingOperation.getId())) {
                    // 检查是否需要更新排序值
                    if (item.getDragSort() != i+1) {
                        item.setDragSort(i+1);
                        technologyRoutingOperationMapper.updateById(item);
                    }
                } else {
                    // 更新原记录的新排序位置
                    oldtechnologyRoutingOperation.setDragSort(targetPosition);
                    technologyRoutingOperationMapper.updateById(oldtechnologyRoutingOperation);
                }
            }
            return 1;
        }
        return 0;
    }
    private Integer nextDragSort(Long technologyRoutingId) {
        TechnologyRoutingOperation lastOperation = technologyRoutingOperationMapper.selectOne(
                Wrappers.<TechnologyRoutingOperation>lambdaQuery()
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
@@ -16,7 +16,6 @@
        <result column="qty_required" property="qtyRequired"/>
        <result column="issued" property="issued"/>
        <result column="source" property="source"/>
        <result column="is_audit" property="isAudit"/>
        <result column="promised_delivery_date" property="promisedDeliveryDate"/>
    </resultMap>
@@ -26,10 +25,36 @@
        pm.model,
        p.id as productId,
        p.product_name AS productName,
        pm.unit
        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>
            <if test="c != null">
                <if test="c.id != null">
                    and pp.id = #{c.id}
                </if>
                <if test="c.productName != null and c.productName != ''">
                    and p.product_name like concat('%', #{c.productName}, '%')
                </if>
                <if test="c.model != null and c.model != ''">
                    and pm.model like concat('%', #{c.model}, '%')
                </if>
                <if test="c.status != null">
                    and pp.status = #{c.status}
                </if>
                <if test="c.mpsNo != null and c.mpsNo != ''">
                    and pp.mps_no like concat('%', #{c.mpsNo}, '%')
                </if>
                <if test="c.requiredDateStart != null and c.requiredDateEnd != null">
                    and pp.required_date between #{c.requiredDateStart} and #{c.requiredDateEnd}
                </if>
            </if>
        </where>
        ORDER BY COALESCE(pp.id) DESC
    </select>
@@ -59,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>
src/main/resources/mapper/technology/TechnologyRoutingMapper.xml
@@ -39,6 +39,9 @@
                <if test="c.productModelId != null">
                    and tr.product_model_id = #{c.productModelId}
                </if>
                <if test="c.model != null and c.model != ''">
                    and pm.model like concat('%', #{c.model}, '%')
                </if>
                <if test="c.bomId != null">
                    and tr.bom_id = #{c.bomId}
                </if>