feat(production): 新增生产工单管理功能
- 创建 ProductionOperationTask 实体类定义工单数据结构
- 实现 ProductionOperationTaskController 提供工单的增删改查接口
- 开发 ProductionOperationTaskService 和实现类处理业务逻辑
- 配置 ProductionOperationTaskMapper 及 XML 文件实现数据库操作
- 添加 ProductionOperationTaskVo 视图对象用于数据展示
- 扩展 ProductionOrder 实体类增加生产订单相关属性
- 更新 ProductionOrderMapper.xml 完善订单查询映射配置
- 优化 ProductionOrderPickRecordMapper.xml 记录物料领取明细
- 新增 ProductionOrderRoutingOperationParam 参数配置实体
- 完善 工序参数处理逻辑
已修改28个文件
565 ■■■■ 文件已修改
src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductInputController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOperationTask.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperationParam.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOperationTaskService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 137 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderMapper.xml 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderPickRecordMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductInputMapper.xml 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductOutputMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityInspectMapper.xml 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java
@@ -1,24 +1,15 @@
package com.ruoyi.ai.config;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
import java.util.List;
/**
 * @author :yys
@@ -35,8 +26,8 @@
    @Autowired
    private EmbeddingModel embeddingModel;
    @Value("${knowledge.one}")
    private String one;
//    @Value("${knowledge.one}")
//    private String one;
//
//    @Value("${knowledge.two}")
//    private String two;
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
@@ -1,67 +1,68 @@
package com.ruoyi.production.bean.dto;
import com.ruoyi.production.pojo.ProductionOrderRoutingOperationParam;
import com.ruoyi.production.pojo.ProductionProductMain;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductionProductMainDto {
    @Schema(description = "主键ID")
    private Long id;
    @Schema(description = "产品编号")
    private String productNo;
    @Schema(description = "用户ID")
    private Long userId;
    @Schema(description = "用户名称")
    private String userName;
@Schema(name = "ProductionProductMainDto", description = "生产报工查询对象")
public class ProductionProductMainDto extends ProductionProductMain {
    @Schema(description = "产品工艺路线明细ID")
    private Long productProcessRouteItemId;
    @Schema(description = "工单ID")
    private Long workOrderId;
    @Schema(description = "生产工序任务ID")
    private Long productionOperationTaskId;
    @Schema(description = "状态")
    private Integer status;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @Schema(description = "生产报工表id")
    private Long productMainId;
    @Schema(description = "租户ID")
    private Long tenantId;
    @Schema(description = "创建人")
    private Integer createUser;
    @Schema(description = "更新人")
    private Integer updateUser;
    @Schema(description = "部门ID")
    private Long deptId;
    @Schema(description = "工单编号")
    private String workOrderNo;
    @Schema(description = "工单状态")
    private String workOrderStatus;
    @Schema(description = "昵称")
    private String nickName;
    @Schema(description = "数量")
    private BigDecimal quantity;
    @Schema(description = "报废数量")
    private BigDecimal scrapQty;
    @Schema(description = "产品名称")
    private String productName;
    @Schema(description = "产品型号名称")
    private String productModelName;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "销售合同编号")
    private String salesContractNo;
    @Schema(description = "排产日期")
    private LocalDate schedulingDate;
    @Schema(description = "排产人员名称")
    private String schedulingUserName;
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "工序")
    private String process;
    @Schema(description = "工序参数列表")
    private List<ProductionOrderRoutingOperationParam> productionOperationParamList;
}
src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java
@@ -1,8 +1,34 @@
package com.ruoyi.production.bean.vo;
import com.ruoyi.production.pojo.ProductionOperationTask;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductionOperationTaskVo extends ProductionOperationTask {
    @Schema(description = "订单号")
    private String npsNo;
    @Schema(description = "产品名称")
    private String productName;
    @Schema(description = "规格型号")
    private String model;
    @Schema(description = "单位")
    private String unit;
    @Schema(description = "工序名称")
    private String operationName;
    @Schema(description = "工单类型 正常 /返工返修")
    private String workOrderType;
    @Schema(description = "完成进度")
    private BigDecimal completionStatus;
}
src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java
@@ -6,6 +6,7 @@
import com.ruoyi.production.bean.vo.ProductionOperationTaskVo;
import com.ruoyi.production.pojo.ProductionOperationTask;
import com.ruoyi.production.service.ProductionOperationTaskService;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
@@ -63,4 +64,11 @@
    public R<Boolean> remove(@RequestBody List<Long> ids) {
        return R.ok(productionOperationTaskService.removeProductionOperationTask(ids));
    }
    @Operation(summary = "产品工单更新")
    @PostMapping("/updateProductWorkOrder")
    public R updateProductWorkOrder(@RequestBody ProductionOperationTaskDto dto) {
        return R.ok(productionOperationTaskService.updateProductWorkOrder(dto));
    }
}
src/main/java/com/ruoyi/production/controller/ProductionProductInputController.java
@@ -6,12 +6,11 @@
import com.ruoyi.production.service.ProductionProductInputService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("productionProductInput")
@RequestMapping("/productionProductInput")
@RestController
@Tag(name = "生产投入")
@AllArgsConstructor
@@ -19,7 +18,7 @@
    private ProductionProductInputService productionProductInputService;
    @GetMapping("listPage")
    @GetMapping("/listPage")
    public R page(Page<ProductionProductInputDto> page, ProductionProductInputDto productionProductInputDto) {
        return R.ok(productionProductInputService.listPageProductionProductInputDto(page, productionProductInputDto));
    }
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java
@@ -5,16 +5,15 @@
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.production.bean.dto.ProductionProductMainDto;
import com.ruoyi.production.service.ProductionProductMainService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequestMapping("productionProductMain")
@RequestMapping("/productionProductMain")
@RestController
@Tag(name = "生产报工")
@AllArgsConstructor
@@ -28,7 +27,7 @@
     * @param productionProductMainDto
     * @return
     */
    @GetMapping("listPage")
    @GetMapping("/listPage")
    public R page(Page<ProductionProductMainDto> page, ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.listPageProductionProductMainDto(page, productionProductMainDto));
    }
@@ -50,7 +49,7 @@
     * @param productionProductMainDto
     * @return
     */
    @PostMapping("addProductMain")
    @PostMapping("/addProductMain")
    public R addProductMain(@RequestBody ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.addProductMain(productionProductMainDto));
    }
src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java
@@ -1,8 +1,12 @@
package com.ruoyi.production.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.home.dto.ProductionTaskStatisticsDto;
import com.ruoyi.home.dto.processDataProductionStatisticsDto;
import com.ruoyi.production.bean.dto.ProductionOperationTaskDto;
import com.ruoyi.production.bean.vo.ProductionOperationTaskVo;
import com.ruoyi.production.pojo.ProductionOperationTask;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -22,6 +26,9 @@
@Mapper
public interface ProductionOperationTaskMapper extends BaseMapper<ProductionOperationTask> {
    IPage<ProductionOperationTaskVo> pageProductionOperationTask(Page<ProductionOperationTaskVo> page,
                                                                 @Param("c") ProductionOperationTaskDto dto);
    List<ProductionTaskStatisticsDto> selectTaskStatisticsByDate(@Param("startDate") LocalDate startDate,
                                                                 @Param("endDate") LocalDate endDate);
src/main/java/com/ruoyi/production/pojo/ProductionOperationTask.java
@@ -28,8 +28,8 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "工艺路线工序表id")
    private Long technologyRoutingOperationId;
    @Schema(description = "生产工艺路线工序表id")
    private Long productionOrderRoutingOperationId;
    @Schema(description = "录入时间")
    @TableField(fill = FieldFill.INSERT)
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -74,6 +74,6 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planCompleteTime;
    @Schema(description = "状态(1.待开始、2.进行中、3.已完成、4.已取消)")
    @Schema(description = "状态(1.待开始 2.进行中 3.已完成 4.已取消)")
    private Integer status;
}
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperationParam.java
@@ -85,6 +85,9 @@
    @Schema(description = "标准值")
    private String standardValue;
    @Schema(description = "输入值")
    private String inputValue;
    @Schema(description = "生产订单工艺路线工序ID")
    private Long productionOrderRoutingOperationId;
}
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java
@@ -37,9 +37,6 @@
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java
@@ -34,10 +34,6 @@
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "报废数量")
    private BigDecimal scrapQty;
    @Schema(description = "创建用户")
src/main/java/com/ruoyi/production/service/ProductionOperationTaskService.java
@@ -21,4 +21,6 @@
    boolean saveProductionOperationTask(ProductionOperationTask productionOperationTask);
    boolean removeProductionOperationTask(List<Long> ids);
    int updateProductWorkOrder(ProductionOperationTaskDto dto);
}
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
@@ -18,14 +18,14 @@
@Service
@RequiredArgsConstructor
public class ProductionOperationTaskServiceImpl extends ServiceImpl<ProductionOperationTaskMapper, ProductionOperationTask>
        implements ProductionOperationTaskService {
public class ProductionOperationTaskServiceImpl extends ServiceImpl<ProductionOperationTaskMapper, ProductionOperationTask> implements ProductionOperationTaskService {
    @Override
    public IPage<ProductionOperationTaskVo> pageProductionOperationTask(Page<ProductionOperationTaskDto> page,
                                                                         ProductionOperationTaskDto dto) {
        Page<ProductionOperationTask> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        return this.page(entityPage, buildQueryWrapper(dto)).convert(item -> BeanUtil.copyProperties(item, ProductionOperationTaskVo.class));
    public IPage<ProductionOperationTaskVo> pageProductionOperationTask(Page<ProductionOperationTaskDto> page, ProductionOperationTaskDto dto) {
        Page<ProductionOperationTaskVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        return baseMapper.pageProductionOperationTask(voPage, dto);
    }
    @Override
@@ -54,11 +54,16 @@
        return Wrappers.<ProductionOperationTask>lambdaQuery()
                .eq(query.getId() != null, ProductionOperationTask::getId, query.getId())
                .eq(query.getProductionOrderId() != null, ProductionOperationTask::getProductionOrderId, query.getProductionOrderId())
                .eq(query.getTechnologyRoutingOperationId() != null,
                        ProductionOperationTask::getTechnologyRoutingOperationId, query.getTechnologyRoutingOperationId())
                .eq(query.getProductionOrderRoutingOperationId() != null,
                        ProductionOperationTask::getProductionOrderRoutingOperationId, query.getProductionOrderRoutingOperationId())
                .eq(query.getStatus() != null, ProductionOperationTask::getStatus, query.getStatus())
                .like(query.getWorkOrderNo() != null && !query.getWorkOrderNo().trim().isEmpty(),
                        ProductionOperationTask::getWorkOrderNo, query.getWorkOrderNo())
                .orderByDesc(ProductionOperationTask::getId);
    }
    @Override
    public int updateProductWorkOrder(ProductionOperationTaskDto dto) {
        return baseMapper.updateById(dto);
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
@@ -90,7 +90,7 @@
            }
            String workOrderNoStr = "GD" + String.format("%s%03d", datePrefix, sequenceNumber);
            ProductionOperationTask productionOperationTask = new ProductionOperationTask();
            productionOperationTask.setTechnologyRoutingOperationId(productionOrderRoutingOperation.getId());
            productionOperationTask.setProductionOrderRoutingOperationId(productionOrderRoutingOperation.getId());
            productionOperationTask.setProductionOrderId(productionOrderRoutingOperation.getProductionOrderId());
            productionOperationTask.setPlanQuantity(BigDecimal.ZERO);
            productionOperationTask.setCompleteQuantity(BigDecimal.ZERO);
@@ -106,7 +106,7 @@
        try {
            ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectOne(
                    new LambdaQueryWrapper<ProductionOperationTask>()
                            .eq(ProductionOperationTask::getTechnologyRoutingOperationId, id)
                            .eq(ProductionOperationTask::getProductionOrderRoutingOperationId, id)
                            .last("limit 1"));
            if (productionOperationTask == null) {
                throw new RuntimeException("删除失败:未找到关联的生产工单");
@@ -128,7 +128,7 @@
                routingId = deleteItem.getOrderRoutingId();
            }
            productionOperationTaskMapper.delete(new LambdaQueryWrapper<ProductionOperationTask>()
                    .eq(ProductionOperationTask::getTechnologyRoutingOperationId, id));
                    .eq(ProductionOperationTask::getProductionOrderRoutingOperationId, id));
            productionOrderRoutingOperationMapper.deleteById(id);
            if (routingId != null) {
                List<ProductionOrderRoutingOperation> operationList = productionOrderRoutingOperationMapper.selectList(
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -242,7 +242,7 @@
            productionOrderRoutingOperationMapper.insert(targetOperation);
            ProductionOperationTask task = new ProductionOperationTask();
            task.setTechnologyRoutingOperationId(targetOperation.getId());
            task.setProductionOrderRoutingOperationId(targetOperation.getId());
            task.setProductionOrderId(productionOrder.getId());
            task.setPlanQuantity(defaultDecimal(productionOrder.getQuantity()));
            task.setCompleteQuantity(BigDecimal.ZERO);
@@ -450,6 +450,12 @@
        if (productionPlans.size() != planIds.size()) {
            throw new ServiceException("部分生产计划不存在");
        }
        Map<Long, ProductionPlan> planMap = productionPlans.stream()
                .collect(Collectors.toMap(ProductionPlan::getId, item -> item, (left, right) -> left));
        ProductionPlan mainPlan = planMap.get(planIds.get(0));
        if (mainPlan == null) {
            throw new ServiceException("主生产计划不存在");
        }
        Set<Long> productModelIds = productionPlans.stream()
                .map(ProductionPlan::getProductModelId)
                .collect(Collectors.toSet());
@@ -459,7 +465,7 @@
        if (productionPlans.stream().anyMatch(item -> item.getStatus() != null && item.getStatus() == 2)) {
            throw new ServiceException("所选生产计划已下发");
        }
        ProductionPlan firstPlan = productionPlans.get(0);
        ProductionPlan firstPlan = mainPlan;
        if (productionOrder.getProductModelId() == null) {
            productionOrder.setProductModelId(firstPlan.getProductModelId());
        } else if (!Objects.equals(productionOrder.getProductModelId(), firstPlan.getProductModelId())) {
@@ -698,19 +704,22 @@
            }
        }
        List<ProductionOrderPickVo> result = new ArrayList<>(bomStructureList.size());
        Map<String, ProductionOrderPickVo> mergedPickMap = new LinkedHashMap<>();
        for (ProductionBomStructureVo structure : bomStructureList) {
            if (structure == null || structure.getProductModelId() == null) {
                continue;
            }
            Long productModelId = structure.getProductModelId();
            ProductionOrderPickVo vo = new ProductionOrderPickVo();
            String mergeKey = String.valueOf(structure.getTechnologyOperationId()) + "#" + productModelId;
            ProductionOrderPickVo vo = mergedPickMap.get(mergeKey);
            if (vo == null) {
                vo = new ProductionOrderPickVo();
            vo.setProductModelId(productModelId);
            vo.setOperationName(structure.getOperationName());
            vo.setTechnologyOperationId(structure.getTechnologyOperationId());
            vo.setProductName(structure.getProductName());
            vo.setModel(structure.getModel());
            vo.setDemandedQuantity(defaultDecimal(structure.getDemandedQuantity()));
                vo.setDemandedQuantity(BigDecimal.ZERO);
            vo.setUnit(structure.getUnit());
            List<String> batchNoList = stockBatchNoMap.get(productModelId) == null
                    ? Collections.emptyList()
@@ -718,8 +727,10 @@
            vo.setBatchNoList(batchNoList);
            vo.setStockQuantity(stockQuantityMap.getOrDefault(productModelId, BigDecimal.ZERO));
            vo.setBom(true);
            result.add(vo);
                mergedPickMap.put(mergeKey, vo);
        }
        return result;
            vo.setDemandedQuantity(defaultDecimal(vo.getDemandedQuantity()).add(defaultDecimal(structure.getDemandedQuantity())));
        }
        return new ArrayList<>(mergedPickMap.values());
    }
}
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -40,8 +40,10 @@
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@@ -63,7 +65,10 @@
    private final ProductionAccountMapper productionAccountMapper;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final ProductionOrderMapper productionOrderMapper;
    private final ProductionOrderBomMapper productionOrderBomMapper;
    private final ProductionBomStructureMapper productionBomStructureMapper;
    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
    private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper;
    private final TechnologyRoutingOperationMapper technologyRoutingOperationMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    private final StockUtils stockUtils;
@@ -87,7 +92,8 @@
    @Override
    public Boolean addProductMain(ProductionProductMainDto dto) {
        if (dto.getProductionOperationTaskId() == null) {
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("请传入生产工单ID");
        }
        return addProductMainByProductionTask(dto);
@@ -109,12 +115,16 @@
    private Boolean addProductMainByProductionTask(ProductionProductMainDto dto) {
        // 报工以订单工序快照为准,避免工艺主数据变更后影响历史工单执行。
        Long taskId = resolveTaskId(dto);
        if (taskId == null) {
            throw new ServiceException("productionOperationTaskId can not be null");
        }
        SysUser user = userMapper.selectUserById(dto.getUserId());
        ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(dto.getProductionOperationTaskId());
        ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(taskId);
        if (productionOperationTask == null) {
            throw new ServiceException("生产工单不存在");
        }
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getTechnologyRoutingOperationId());
        ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId());
        if (routingOperation == null) {
            throw new ServiceException("订单工艺路线工序不存在");
        }
@@ -122,6 +132,7 @@
        if (productionOrder == null) {
            throw new ServiceException("生产订单不存在");
        }
        syncOperationParamInputValue(dto, routingOperation.getId());
        TechnologyRoutingOperation technologyRoutingOperation = technologyRoutingOperationMapper.selectById(routingOperation.getTechnologyRoutingOperationId());
        TechnologyOperation technologyOperation = technologyRoutingOperation == null ? null
                : technologyOperationMapper.selectById(technologyRoutingOperation.getTechnologyOperationId());
@@ -133,17 +144,17 @@
        ProductionProductMain productionProductMain = new ProductionProductMain();
        productionProductMain.setProductNo(generateProductNo());
        productionProductMain.setUserId(dto.getUserId());
        productionProductMain.setUserName(dto.getUserName());
        productionProductMain.setProductionOperationTaskId(productionOperationTask.getId());
        productionProductMain.setUserId(user == null ? dto.getUserId() : user.getUserId());
        productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName());
        productionProductMain.setProductionOperationTaskId(taskId);
        productionProductMain.setStatus(0);
        productionProductMainMapper.insert(productionProductMain);
        List<ProductStructureDto> productStructureDtos = new ArrayList<>();
        ProductStructureDto productStructureDto = new ProductStructureDto();
        productStructureDto.setProductModelId(productModel.getId());
        productStructureDto.setUnitQuantity(BigDecimal.ONE);
        productStructureDtos.add(productStructureDto);
        List<ProductStructureDto> productStructureDtos = resolveInputStructures(
                productionOrder.getId(), routingOperation, productModel.getId());
        if (productStructureDtos.isEmpty()) {
            throw new ServiceException("未找到当前工序对应的BOM投入节点");
        }
        for (ProductStructureDto item : productStructureDtos) {
            // 当前实现按工序成品直接作为投入,后续若接入领料记录可在这里替换来源。
            ProductionProductInput productionProductInput = new ProductionProductInput();
@@ -153,8 +164,6 @@
            productionProductInput.setInputQuantity(item.getUnitQuantity().multiply(defaultDecimal(dto.getQuantity())));
            productionProductInput.setQuantity(productionProductInput.getInputQuantity());
            productionProductInputMapper.insert(productionProductInput);
            stockUtils.substractStock(item.getProductModelId(), productionProductInput.getInputQuantity(),
                    StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode(), productionProductMain.getId());
        }
        ProductionProductOutput productionProductOutput = new ProductionProductOutput();
@@ -261,6 +270,99 @@
        return true;
    }
    private void syncOperationParamInputValue(ProductionProductMainDto dto, Long productionOrderRoutingOperationId) {
        if (dto == null || productionOrderRoutingOperationId == null) {
            return;
        }
        List<ProductionOrderRoutingOperationParam> paramList = dto.getProductionOperationParamList();
        if (paramList == null || paramList.isEmpty()) {
            return;
        }
        List<ProductionOrderRoutingOperationParam> dbParamList = productionOrderRoutingOperationParamMapper.selectList(
                Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                        .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, productionOrderRoutingOperationId));
        if (dbParamList == null || dbParamList.isEmpty()) {
            return;
        }
        Map<Long, ProductionOrderRoutingOperationParam> dbParamMap = dbParamList.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionOrderRoutingOperationParam::getId, item -> item, (left, right) -> left));
        for (ProductionOrderRoutingOperationParam param : paramList) {
            if (param == null || param.getId() == null) {
                continue;
            }
            ProductionOrderRoutingOperationParam dbParam = dbParamMap.get(param.getId());
            if (dbParam == null) {
                throw new ServiceException("工序参数不存在或不属于当前工单工序,ID=" + param.getId());
            }
            if (Objects.equals(dbParam.getInputValue(), param.getInputValue())) {
                continue;
            }
            ProductionOrderRoutingOperationParam updateParam = new ProductionOrderRoutingOperationParam();
            updateParam.setId(dbParam.getId());
            updateParam.setInputValue(param.getInputValue());
            productionOrderRoutingOperationParamMapper.updateById(updateParam);
        }
    }
    private List<ProductStructureDto> resolveInputStructures(Long productionOrderId,
                                                             ProductionOrderRoutingOperation routingOperation,
                                                             Long outputProductModelId) {
        if (productionOrderId == null || routingOperation == null || routingOperation.getTechnologyOperationId() == null) {
            return new ArrayList<>();
        }
        ProductionOrderBom orderBom = productionOrderBomMapper.selectOne(
                Wrappers.<ProductionOrderBom>lambdaQuery()
                        .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)
                        .orderByDesc(ProductionOrderBom::getId)
                        .last("limit 1"));
        if (orderBom == null || orderBom.getId() == null) {
            return new ArrayList<>();
        }
        List<ProductionBomStructure> bomNodeList = productionBomStructureMapper.selectList(
                Wrappers.<ProductionBomStructure>lambdaQuery()
                        .eq(ProductionBomStructure::getProductionOrderBomId, orderBom.getId())
                        .orderByAsc(ProductionBomStructure::getId));
        if (bomNodeList.isEmpty()) {
            return new ArrayList<>();
        }
        Map<Long, ProductionBomStructure> nodeMap = bomNodeList.stream()
                .filter(item -> item != null && item.getId() != null)
                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
        Long currentOutputModelId = routingOperation.getProductModelId() != null
                ? routingOperation.getProductModelId()
                : outputProductModelId;
        Map<Long, BigDecimal> unitQtyByProductModel = new LinkedHashMap<>();
        for (ProductionBomStructure node : bomNodeList) {
            if (node == null || node.getParentId() == null || node.getProductModelId() == null) {
                continue;
            }
            if (!Objects.equals(node.getTechnologyOperationId(), routingOperation.getTechnologyOperationId())) {
                continue;
            }
            ProductionBomStructure parent = nodeMap.get(node.getParentId());
            if (parent == null || !Objects.equals(parent.getProductModelId(), currentOutputModelId)) {
                continue;
            }
            unitQtyByProductModel.merge(node.getProductModelId(), defaultDecimal(node.getUnitQuantity()), BigDecimal::add);
        }
        List<ProductStructureDto> result = new ArrayList<>();
        for (Map.Entry<Long, BigDecimal> entry : unitQtyByProductModel.entrySet()) {
            if (entry.getValue().compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            ProductStructureDto item = new ProductStructureDto();
            item.setProductModelId(entry.getKey());
            item.setUnitQuantity(entry.getValue());
            result.add(item);
        }
        return result;
    }
    private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) {
        // 删除报工需要同步回滚质检、库存、工时核算和订单/工单进度。
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(
@@ -295,7 +397,7 @@
            productionOperationTaskMapper.updateById(productionOperationTask);
            ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId());
            ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getTechnologyRoutingOperationId());
            ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOperationTask.getProductionOrderRoutingOperationId());
            if (productionOrder != null && routingOperation != null) {
                // 只有最后一道工序的报工才会影响生产订单完工数量。
                List<ProductionOrderRoutingOperation> routingOperationList = productionOrderRoutingOperationMapper.selectList(
@@ -362,6 +464,13 @@
        return value == null ? BigDecimal.ZERO : value;
    }
    private Long resolveTaskId(ProductionProductMainDto dto) {
        if (dto == null) {
            return null;
        }
        return dto.getProductionOperationTaskId();
    }
    @Override
    public ArrayList<Long> listMain(List<Long> idList) {
        return productionProductMainMapper.listMain(idList);
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -169,7 +169,7 @@
            productionOrderRoutingOperationMapper.insert(newOperation);
            ProductionOperationTask newTask = new ProductionOperationTask();
            newTask.setTechnologyRoutingOperationId(newOperation.getId());
            newTask.setProductionOrderRoutingOperationId(newOperation.getId());
            newTask.setProductionOrderId(newOrder.getId());
            newTask.setPlanQuantity(newOrder.getQuantity());
            newTask.setCompleteQuantity(BigDecimal.ZERO);
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
@@ -1,7 +1,6 @@
package com.ruoyi.technology.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -10,20 +9,8 @@
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.technology.bean.dto.TechnologyRoutingDto;
import com.ruoyi.technology.bean.vo.TechnologyRoutingVo;
import com.ruoyi.technology.mapper.TechnologyBomStructureMapper;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.mapper.TechnologyOperationParamMapper;
import com.ruoyi.technology.mapper.TechnologyParamMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingOperationMapper;
import com.ruoyi.technology.mapper.TechnologyRoutingOperationParamMapper;
import com.ruoyi.technology.pojo.TechnologyBomStructure;
import com.ruoyi.technology.pojo.TechnologyOperation;
import com.ruoyi.technology.pojo.TechnologyOperationParam;
import com.ruoyi.technology.pojo.TechnologyParam;
import com.ruoyi.technology.pojo.TechnologyRouting;
import com.ruoyi.technology.pojo.TechnologyRoutingOperation;
import com.ruoyi.technology.pojo.TechnologyRoutingOperationParam;
import com.ruoyi.technology.mapper.*;
import com.ruoyi.technology.pojo.*;
import com.ruoyi.technology.service.TechnologyRoutingService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -31,6 +18,7 @@
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -126,7 +114,7 @@
        }
        List<TechnologyBomStructure> bomStructures = technologyBomStructureMapper.selectList(
                Wrappers.<TechnologyBomStructure>lambdaQuery()
                        .eq(TechnologyBomStructure::getBomId, Long.valueOf(technologyRouting.getBomId()))
                        .eq(TechnologyBomStructure::getBomId, technologyRouting.getBomId())
                        .isNotNull(TechnologyBomStructure::getOperationId)
                        .orderByAsc(TechnologyBomStructure::getId)
        );
@@ -135,16 +123,24 @@
        }
        // 同一个 BOM 中可能重复引用相同工序,这里按首次出现顺序去重。
        Map<Long, TechnologyBomStructure> uniqueOperationMap = new LinkedHashMap<>();
        Map<Long, TechnologyBomStructure> structureById = new HashMap<>();
        for (TechnologyBomStructure bomStructure : bomStructures) {
            uniqueOperationMap.putIfAbsent(bomStructure.getOperationId(), bomStructure);
            if (bomStructure != null && bomStructure.getId() != null) {
                structureById.put(bomStructure.getId(), bomStructure);
            }
        }
        Map<String, TechnologyBomStructure> uniqueOperationMap = new LinkedHashMap<>();
        for (TechnologyBomStructure bomStructure : bomStructures) {
            Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId());
            uniqueOperationMap.putIfAbsent(buildBomOperationDedupKey(bomStructure, outputProductModelId), bomStructure);
        }
        int dragSort = 1;
        for (TechnologyBomStructure bomStructure : uniqueOperationMap.values()) {
            TechnologyRoutingOperation routingOperation = new TechnologyRoutingOperation();
            routingOperation.setTechnologyRoutingId(technologyRouting.getId());
            routingOperation.setProductModelId(technologyRouting.getProductModelId());
            routingOperation.setProductModelId(resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId()));
            routingOperation.setTechnologyOperationId(bomStructure.getOperationId());
            routingOperation.setDragSort(dragSort++);
            routingOperation.setIsQuality(getOperationQuality(bomStructure.getOperationId()));
@@ -153,6 +149,31 @@
        }
    }
    private String buildBomOperationDedupKey(TechnologyBomStructure bomStructure, Long outputProductModelId) {
        Long operationId = bomStructure == null ? null : bomStructure.getOperationId();
        Long parentId = bomStructure == null ? null : bomStructure.getParentId();
        return operationId + "#"
                + outputProductModelId + "#"
                + parentId;
    }
    private Long resolveOutputProductModelId(TechnologyBomStructure bomStructure,
                                             Map<Long, TechnologyBomStructure> structureById,
                                             Long routingProductModelId) {
        if (bomStructure == null) {
            return routingProductModelId;
        }
        Long parentId = bomStructure.getParentId();
        if (parentId == null) {
            return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
        }
        TechnologyBomStructure parent = structureById.get(parentId);
        if (parent != null && parent.getProductModelId() != null) {
            return parent.getProductModelId();
        }
        return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
    }
    private void syncRoutingOperationParams(Long technologyRoutingOperationId, Long technologyOperationId) {
        List<TechnologyOperationParam> operationParamList = technologyOperationParamMapper.selectList(
                Wrappers.<TechnologyOperationParam>lambdaQuery()
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
@@ -5,7 +5,7 @@
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.production.pojo.ProductionOperationTask">
        <id column="id" property="id" />
        <result column="technology_routing_operation_id" property="technologyRoutingOperationId" />
        <result column="production_order_routing_operation_id" property="productionOrderRoutingOperationId" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="work_order_no" property="workOrderNo" />
@@ -21,6 +21,43 @@
        <result column="dept_id" property="deptId" />
    </resultMap>
    <select id="pageProductionOperationTask" resultType="com.ruoyi.production.bean.vo.ProductionOperationTaskVo">
        select pot.*,
               po.nps_no as npsNo,
               p.product_name as productName,
               pm.model as model,
               pm.unit as unit,
               poro.operation_name as operationName,
        ROUND(pot.complete_quantity / pot.plan_quantity * 100, 2) AS completionStatus,
        CASE
            WHEN pot.work_order_no LIKE 'FG%' THEN '返工返修'
            ELSE '正常'
            END AS work_order_type
        from production_operation_task pot
                 left join production_order po on pot.production_order_id = po.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
        <where>
            <if test="c != null and c.id != null">
                and pot.id = #{c.id}
            </if>
            <if test="c != null and c.productionOrderId != null">
                and pot.production_order_id = #{c.productionOrderId}
            </if>
            <if test="c != null and c.productionOrderRoutingOperationId != null">
                and pot.production_order_routing_operation_id = #{c.productionOrderRoutingOperationId}
            </if>
            <if test="c != null and c.status != null">
                and pot.status = #{c.status}
            </if>
            <if test="c != null and c.workOrderNo != null and c.workOrderNo != ''">
                and pot.work_order_no like concat('%', #{c.workOrderNo}, '%')
            </if>
        </where>
        order by pot.id desc
    </select>
    <select id="selectTaskStatisticsByDate" resultType="com.ruoyi.home.dto.ProductionTaskStatisticsDto">
        select pot.id,
               pot.work_order_no,
@@ -30,17 +67,16 @@
               pot.actual_end_time as actualEndTime,
               pot.plan_quantity as planQuantity,
               ifnull(pot.complete_quantity, 0) as completeQuantity,
               top2.name as processName,
               poro.operation_name as processName,
               p.product_name as productName,
               pm.model,
               pm.unit,
               po.nps_no as productOrderNpsNo
        from production_operation_task pot
                 left join production_order po on pot.production_order_id = po.id
                 left join product_model pm on po.product_model_id = pm.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
                 left join technology_routing_operation tro on pot.technology_routing_operation_id = tro.id
                 left join technology_operation top2 on tro.technology_operation_id = top2.id
        where date(pot.create_time) between #{startDate} and #{endDate}
        order by pot.create_time desc
    </select>
@@ -55,15 +91,14 @@
    </select>
    <select id="calculateProductionStatistics" resultType="com.ruoyi.home.dto.processDataProductionStatisticsDto">
        select top2.name as processName,
        select poro.operation_name as processName,
               sum(ifnull(ppi.input_quantity, 0)) as totalInput,
               sum(ifnull(ppo.scrap_qty, 0)) as totalScrap,
               sum(ifnull(ppo.quantity, 0) - ifnull(ppo.scrap_qty, 0)) as totalOutput
        from production_product_output ppo
                 inner join production_product_main ppm on ppo.production_product_main_id = ppm.id
                 inner join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 inner join technology_routing_operation tro on pot.technology_routing_operation_id = tro.id
                 inner join technology_operation top2 on tro.technology_operation_id = top2.id
        left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join production_product_input ppi on ppi.production_product_main_id = ppm.id
        <where>
            <if test="startDateTime != null">
@@ -76,13 +111,13 @@
                and ppm.create_user = #{userId}
            </if>
            <if test="processIds != null and processIds.size() > 0">
                and top2.id in
                and poro.technology_operation_id in
                <foreach collection="processIds" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
        </where>
        group by top2.id, top2.name
        group by poro.technology_operation_id, poro.operation_name
    </select>
</mapper>
src/main/resources/mapper/production/ProductionOrderMapper.xml
@@ -45,6 +45,8 @@
        po.dept_id,
        po.plan_complete_time,
        po.status,
        po_sales.salesContractNo,
        po_sales.customerName,
        p.product_name as productName,
        pm.model as model,
        tr.process_route_code as processRouteCode,
@@ -53,6 +55,18 @@
    <sql id="ProductionOrderVoFrom">
        from production_order po
                 left join (
            select po2.id as orderId,
                   group_concat(distinct sl2.sales_contract_no order by sl2.sales_contract_no separator ',') as salesContractNo,
                   group_concat(distinct sl2.customer_name order by sl2.customer_name separator ',') as customerName,
                   group_concat(distinct sl2.project_name order by sl2.project_name separator ',') as projectName,
                   min(sl2.delivery_date) as deliveryDate
            from production_order po2
                     left join production_plan pp2
                               on find_in_set(pp2.id, replace(replace(replace(po2.production_plan_ids, '[', ''), ']', ''), ' ', '')) > 0
                     left join sales_ledger sl2 on sl2.id = pp2.sales_ledger_id
            group by po2.id
        ) po_sales on po_sales.orderId = po.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
@@ -131,9 +145,9 @@
    <select id="selectProgressOrders" resultType="com.ruoyi.home.dto.ProductionProgressOrderDto">
        select po.nps_no,
               sl.sales_contract_no,
               sl.project_name,
               sl.customer_name,
               po_sales.salesContractNo,
               po_sales.projectName,
               po_sales.customerName,
               p.product_name as productCategory,
               pm.model as specificationModel,
               tr.process_route_code as processRouteCode,
@@ -141,11 +155,22 @@
               ifnull(po.complete_quantity, 0) as completeQuantity,
               round(ifnull(po.complete_quantity, 0) / nullif(po.quantity, 0) * 100, 2) as completionStatus,
               tb.bom_no,
               datediff(sl.delivery_date, curdate()) as deliveryDaysDiff,
               sl.delivery_date,
               datediff(po_sales.deliveryDate, curdate()) as deliveryDaysDiff,
               po_sales.deliveryDate as deliveryDate,
               false as isFh
        from production_order po
                 left join sales_ledger sl on po.sales_ledger_id = sl.id
                 left join (
            select po2.id as orderId,
                   group_concat(distinct sl2.sales_contract_no order by sl2.sales_contract_no separator ',') as salesContractNo,
                   group_concat(distinct sl2.customer_name order by sl2.customer_name separator ',') as customerName,
                   group_concat(distinct sl2.project_name order by sl2.project_name separator ',') as projectName,
                   min(sl2.delivery_date) as deliveryDate
            from production_order po2
                     left join production_plan pp2
                               on find_in_set(pp2.id, replace(replace(replace(po2.production_plan_ids, '[', ''), ']', ''), ' ', '')) > 0
                     left join sales_ledger sl2 on sl2.id = pp2.sales_ledger_id
            group by po2.id
        ) po_sales on po_sales.orderId = po.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
src/main/resources/mapper/production/ProductionOrderPickRecordMapper.xml
@@ -30,7 +30,7 @@
               pm.unit as unit
        from production_order_pick_record popr
                 left join production_operation_task pot on popr.production_operation_task_id = pot.id
                 left join production_order_routing_operation poro on pot.technology_routing_operation_id = poro.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on popr.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
        where popr.production_order_id = #{productionOrderId}
@@ -47,7 +47,7 @@
               popr.create_time as supplementTime
        from production_order_pick_record popr
                 left join production_operation_task pot on popr.production_operation_task_id = pot.id
                 left join production_order_routing_operation poro on pot.technology_routing_operation_id = poro.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on popr.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
                 left join sys_user su on popr.create_user = su.user_id
src/main/resources/mapper/production/ProductionProductInputMapper.xml
@@ -24,7 +24,7 @@
                 left join production_product_main ppm on ppi.production_product_main_id = ppm.id
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.id
                 left join product_model pm on po.product_model_id = pm.id
                 left join product_model pm on ppi.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
        <where>
            <if test="c.productNo != null and c.productNo != ''">
@@ -36,6 +36,9 @@
            <if test="c.productName != null and c.productName != ''">
                and p.product_name like concat('%', #{c.productName}, '%')
            </if>
            <if test="c.productMainId != null and c.productMainId != ''">
                and ppm.id = #{c.productMainId}
            </if>
        </where>
        order by ppi.create_time desc
    </select>
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -30,19 +30,27 @@
               p.product_name as productName,
               pm.model as productModelName,
               pm.unit,
               sl.sales_contract_no as salesContractNo,
               po_sales.salesContractNo,
               date(ppm.create_time) as schedulingDate,
               su.nick_name as schedulingUserName,
               sl.customer_name as customerName,
               top2.name as process
               po_sales.customerName,
               poro.operation_name as process
        from production_product_main ppm
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.id
                 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 production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join (
            select po2.id as orderId,
                   group_concat(distinct sl2.sales_contract_no order by sl2.sales_contract_no separator ',') as salesContractNo,
                   group_concat(distinct sl2.customer_name order by sl2.customer_name separator ',') as customerName
            from production_order po2
                     left join production_plan pp2
                               on find_in_set(pp2.id, replace(replace(replace(po2.production_plan_ids, '[', ''), ']', ''), ' ', '')) > 0
                     left join sales_ledger sl2 on sl2.id = pp2.sales_ledger_id
            group by po2.id
        ) po_sales on po_sales.orderId = po.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
                 left join technology_routing_operation tro on pot.technology_routing_operation_id = tro.id
                 left join technology_operation top2 on tro.technology_operation_id = top2.id
                 left join sys_user su on ppm.create_user = su.user_id
                 left join production_product_output ppo on ppo.production_product_main_id = ppm.id
        <where>
@@ -53,10 +61,10 @@
                and pot.work_order_no like concat('%', #{c.workOrderNo}, '%')
            </if>
            <if test="c.salesContractNo != null and c.salesContractNo != ''">
                and sl.sales_contract_no like concat('%', #{c.salesContractNo}, '%')
                and po_sales.salesContractNo like concat('%', #{c.salesContractNo}, '%')
            </if>
            <if test="c.customerName != null and c.customerName != ''">
                and sl.customer_name like concat('%', #{c.customerName}, '%')
                and po_sales.customerName like concat('%', #{c.customerName}, '%')
            </if>
            <if test="c.productName != null and c.productName != ''">
                and p.product_name like concat('%', #{c.productName}, '%')
@@ -65,10 +73,13 @@
                and pm.model like concat('%', #{c.productModelName}, '%')
            </if>
            <if test="c.process != null and c.process != ''">
                and top2.name like concat('%', #{c.process}, '%')
                and poro.operation_name like concat('%', #{c.process}, '%')
            </if>
            <if test="c.schedulingDate != null">
                and date(ppm.create_time) = #{c.schedulingDate}
            </if>
            <if test="c.productMainId != null">
                and ppm.id = #{c.productMainId}
            </if>
        </where>
        order by ppm.create_time desc
@@ -84,16 +95,15 @@
               p.product_name as productName,
               pm.model as productModelName,
               pm.unit,
               top2.name as process,
               poro.operation_name as process,
               ifnull(ppo.quantity, 0) as quantity,
               ifnull(ppo.scrap_qty, 0) as scrapQty
        from production_product_main ppm
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.id
                 left join product_model pm on po.product_model_id = pm.id
                 left join production_order_routing_operation poro on pot.production_order_routing_operation_id = poro.id
                 left join product_model pm on pm.id = ifnull(poro.product_model_id, po.product_model_id)
                 left join product p on pm.product_id = p.id
                 left join technology_routing_operation tro on pot.technology_routing_operation_id = tro.id
                 left join technology_operation top2 on tro.technology_operation_id = top2.id
                 left join production_product_output ppo on ppo.production_product_main_id = ppm.id
        order by ppm.create_time desc
    </select>
src/main/resources/mapper/production/ProductionProductOutputMapper.xml
@@ -23,7 +23,7 @@
                 left join production_product_main ppm on ppo.production_product_main_id = ppm.id
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.id
                 left join product_model pm on po.product_model_id = pm.id
                 left join product_model pm on ppo.product_model_id = pm.id
        <where>
            <if test="c.productNo != null and c.productNo != ''">
                and ppm.product_no like concat('%', #{c.productNo}, '%')
src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -9,8 +9,8 @@
                pl.purchase_contract_number as purchase_contract_no
            </when>
            <otherwise>
                pwo.work_order_no,
                sl.sales_contract_no
                pot.work_order_no,
                po_sales.sales_contract_no
            </otherwise>
        </choose>
        FROM
@@ -21,9 +21,17 @@
            </when>
            <otherwise>
                LEFT JOIN production_product_main ppm ON qi.product_main_id = ppm.id
                LEFT JOIN product_work_order pwo ON ppm.work_order_id = pwo.id
                left join product_order po ON po.id = pwo.product_order_id
                left join sales_ledger sl ON sl.id = po.sales_ledger_id
                LEFT JOIN production_operation_task pot ON ppm.production_operation_task_id = pot.id
                left join production_order po ON po.id = pot.production_order_id
                left join (
                    select po2.id as order_id,
                           group_concat(distinct sl2.sales_contract_no order by sl2.sales_contract_no separator ',') as sales_contract_no
                    from production_order po2
                             left join production_plan pp2
                                       on find_in_set(pp2.id, replace(replace(replace(po2.production_plan_ids, '[', ''), ']', ''), ' ', '')) > 0
                             left join sales_ledger sl2 on sl2.id = pp2.sales_ledger_id
                    group by po2.id
                ) po_sales ON po_sales.order_id = po.id
            </otherwise>
        </choose>
        WHERE
src/main/resources/mapper/quality/QualityTestStandardMapper.xml
@@ -24,11 +24,11 @@
        SELECT qts.*
        FROM quality_test_standard qts
                 left join quality_test_standard_binding qtsb on qtsb.test_standard_id = qts.id
                 left join product_process pp on qts.process_id = pp.id
                 left join technology_operation toper on qts.process_id = toper.id
        WHERE qtsb.product_id = #{productId}
          AND qts.inspect_type = #{inspectType}
        <if test="process!='' and process!=null">
            and pp.name = #{process}
            and toper.name = #{process}
        </if>
        order by qts.id desc
    </select>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -29,10 +29,17 @@
        select slp.*
        from quality_inspect qi
                 left join production_product_main ppm on qi.product_main_id = ppm.id
                 left join product_work_order pwo on ppm.work_order_id = pwo.id
                 left join product_order po on pwo.product_order_id = po.id
                 left join sales_ledger_product slp on po.sales_ledger_product_id = slp.id and slp.type = 1
                 left join production_operation_task pot on ppm.production_operation_task_id = pot.id
                 left join production_order po on pot.production_order_id = po.id
                 left join production_plan pp_main
                           on find_in_set(pp_main.id, replace(replace(replace(po.production_plan_ids, '[', ''), ']', ''), ' ', '')) > 0
                 left join sales_ledger_product slp on slp.sales_ledger_id = pp_main.sales_ledger_id
            and slp.product_model_id = po.product_model_id
            and slp.type = 1
        where qi.product_main_id = #{productMainId}
          and slp.id is not null
        order by slp.id desc
        limit 1
    </select>