13 小时以前 83d6b764019d3eef0f9c98cf11b98f98e1ef9274
feat(mes): 增加工艺参数模板功能支持

- 在工艺路线工序、生产任务、工单工序实体中增加工艺参数模板编号字段
- 实现生产任务根据工艺参数模板生成参数记录快照的功能
- 增加报工时提交工艺参数值的功能支持
- 添加任务首次和最近报工时间的自动记录
- 移除AI模块中多余向量存储依赖,统一使用OpenAI兼容接口
- 更新相关数据库表结构和索引配置
- 提供前端联调文档说明工艺参数功能集成方案
已添加2个文件
已修改16个文件
368 ■■■■■ 文件已修改
docs/mes_pro_task_process_param_integration.md 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/mes_pro_task_process_param.sql 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ai/pom.xml 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackQuickCreateReqVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderProcessDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderParamRecordMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderParamRecordService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderParamRecordServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_pro_task_process_param_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
# ç”Ÿäº§ä»»åŠ¡å·¥è‰ºå‚æ•° - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- å·¥è‰ºè·¯çº¿å·¥åºç»´æŠ¤
- ç”Ÿäº§æŽ’产任务新增、编辑和详情
- ç”Ÿäº§æŠ¥å·¥æ–°å¢žã€ç¼–辑和详情
- å·¥å•参数记录查询
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. å·¥è‰ºè·¯çº¿å·¥åºå¯é€‰æ‹©å·¥è‰ºå‚数模板,模板编号随路线工序保存。
2. è‡ªåŠ¨æŽ’äº§æ ¹æ®å·¥å•å·¥åºæˆ–è·¯çº¿å·¥åºç”Ÿæˆç”Ÿäº§ä»»åŠ¡ï¼Œå¹¶å°†å‚æ•°æ¨¡æ¿ç¼–å·å¸¦å…¥ä»»åŠ¡ã€‚
3. ä»»åŠ¡ç”Ÿæˆæ—¶ï¼Œç³»ç»Ÿå¤åˆ¶æ¨¡æ¿åŠæ˜Žç»†å¿«ç…§åˆ°ä»»åŠ¡å‚æ•°è®°å½•ï¼›åŽç»­æ¨¡æ¿ä¿®æ”¹ä¸å½±å“è¯¥ä»»åŠ¡å¿«ç…§ã€‚
4. æŠ¥å·¥ä»¥ä»»åŠ¡ä¸ºæ ¸å¿ƒï¼Œå‚æ•°å€¼æŒ‰ä»»åŠ¡å‚æ•°è®°å½•æ˜Žç»†ç¼–å·æäº¤å¹¶ä¿å­˜ï¼›æ— æ¨¡æ¿ä»»åŠ¡ä»å¯æ­£å¸¸æŠ¥å·¥ã€‚
5. æŠ¥å·¥å®¡æ‰¹ã€è´¨æ£€å›žè°ƒå’Œæ•°é‡å›žå†™ä¸é‡æ–°è¯»å–模板,历史参数值保持不变。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| POST | `/mes/pro/route-process/create` | æ–°å¢žè·¯çº¿å·¥åºï¼Œå¯ä¿å­˜ `paramTemplateId` |
| PUT | `/mes/pro/route-process/update` | ä¿®æ”¹è·¯çº¿å·¥åºåŠå‚数模板 |
| POST | `/mes/pro/task/create` | æ‰‹å·¥åˆ›å»ºä»»åŠ¡ï¼Œå¯ä¼  `paramTemplateId` |
| POST | `/mes/pro/task/auto-generate` | è‡ªåŠ¨æŽ’äº§ï¼Œä»»åŠ¡ç»§æ‰¿å·¥å•å·¥åºæˆ–è·¯çº¿å·¥åºçš„æ¨¡æ¿ |
| POST | `/mes/pro/feedback/create` | åˆ›å»ºæŠ¥å·¥ï¼Œå¯æäº¤ `paramValues` |
| PUT | `/mes/pro/feedback/update` | ä¿®æ”¹è‰ç¨¿æŠ¥å·¥åŠå‚数值 |
| POST | `/mes/pro/feedback/submit` | æäº¤æŠ¥å·¥ï¼Œè¿›å…¥å®¡æ‰¹æˆ–直接完成 |
### å‚数字段
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `paramTemplateId` | Long | å¦ | ä»»åŠ¡æˆ–è·¯çº¿å·¥åºä½¿ç”¨çš„å·¥è‰ºå‚æ•°æ¨¡æ¿ç¼–å· |
| `paramValues` | Array | å¦ | æŠ¥å·¥å¡«å†™çš„参数值;无模板时不传 |
| `paramValues[].detailId` | Long | æ˜¯ï¼ˆä¼ å…¥æ—¶ï¼‰ | ä»»åŠ¡å‚æ•°è®°å½•æ˜Žç»†ç¼–å·ï¼Œä¸æ˜¯æ¨¡æ¿æ˜Žç»†ç¼–å· |
| `paramValues[].value` | String | å¦ | æœ¬æ¬¡æŠ¥å·¥å‚数值 |
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|---|---|---|
| `paramTemplateId` | è·¯çº¿å·¥åºã€ä»»åŠ¡è¯¦æƒ… | å±•示当前任务采用的参数模板 |
| å‚数编码、名称、类型、单位 | ä»»åŠ¡å‚æ•°è®°å½•ã€æŠ¥å·¥è¡¨å• | ä»Žä»»åŠ¡å¿«ç…§è¯»å–ï¼Œä¸ç›´æŽ¥å›žæŸ¥æ¨¡æ¿ |
| å‚数值 | æŠ¥å·¥è¡¨å•、报工详情、历史记录 | ä¿å­˜åˆ°å‚数记录明细,展示已保存值 |
| `firstFeedbackTime`、`lastFeedbackTime` | ä»»åŠ¡è¯¦æƒ… | é¦–次和最近一次正式报工时间 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|---|---|
| æ— å‚数模板 | ä»»åŠ¡æ­£å¸¸ç”Ÿæˆï¼ŒæŠ¥å·¥ä¸è¦æ±‚å‚æ•°å€¼ |
| æœ‰å‚数模板 | ä»»åŠ¡ç”Ÿæˆæ—¶å›ºåŒ–æ¨¡æ¿æ˜Žç»†å¿«ç…§ï¼ŒæŠ¥å·¥æŒ‰ä»»åŠ¡æ˜Žç»†å¡«å†™å‚æ•° |
| æ¨¡æ¿åŽç»­ä¿®æ”¹ | ä¸å½±å“å·²æœ‰ä»»åŠ¡å‚æ•°å¿«ç…§å’Œå·²å¡«å†™æŠ¥å·¥å€¼ |
| å‚数明细编号不属于当前任务 | åŽç«¯æ‹’绝保存 |
| å®¡æ‰¹æˆ–质检回调 | åªå¤„理报工和生产数量,不重新生成参数记录 |
| æ­£å¼æŠ¥å·¥åŽ | ä»»åŠ¡å‚æ•°ä½œä¸ºåŽ†å²è®°å½•ä¿ç•™ï¼Œä¸è¦†ç›–åŽŸå¿«ç…§ |
## æ³¨æ„äº‹é¡¹
- æŠ¥å·¥é¡µé¢åº”先根据任务参数记录获取参数定义,再以明细 `detailId` æäº¤å€¼ã€‚
- ä¸è¦å°†æ¨¡æ¿æ˜Žç»†ç¼–号当作报工参数明细编号使用。
- æ•°å€¼ã€æ—¥æœŸã€å¸ƒå°”等参数的输入格式应与参数类型保持一致。
- `paramValues` ä¸ºç©ºä¸ä»£è¡¨åˆ é™¤å‚数记录;没有模板的任务才不生成参数记录。
docs/sql/mes_pro_task_process_param.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
-- MES ç”Ÿäº§ä»»åŠ¡ä¸Žå·¥è‰ºå‚æ•°æ¨¡æ¿å…³è”
-- æ‰§è¡Œå‰è¯·ç¡®è®¤å­—段不存在;MySQL ä¸æ”¯æŒ ALTER TABLE ADD COLUMN IF NOT EXISTS。
ALTER TABLE mes_pro_task ADD COLUMN param_template_id bigint DEFAULT NULL COMMENT '工艺参数模板编号' AFTER process_id;
ALTER TABLE mes_pro_route_process ADD COLUMN param_template_id bigint DEFAULT NULL COMMENT '工艺参数模板编号' AFTER process_id;
ALTER TABLE mes_pro_work_order_process ADD COLUMN param_template_id bigint DEFAULT NULL COMMENT '工艺参数模板编号' AFTER process_id;
CREATE INDEX idx_mes_pro_work_order_param_record_task_id
    ON mes_pro_work_order_param_record (task_id);
CREATE INDEX idx_mes_pro_work_order_param_record_detail_record_id
    ON mes_pro_work_order_param_record_detail (record_id);
yudao-module-ai/pom.xml
@@ -20,9 +20,6 @@
    </description>
    <properties>
        <spring-ai.version>2.0.0</spring-ai.version>
        <!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
        <alibaba-ai.version>2.0.0-M1.1</alibaba-ai.version>
        <tinyflow.version>1.2.6</tinyflow.version>
    </properties>
    <dependencies>
@@ -36,12 +33,6 @@
            <artifactId>yudao-module-system</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-infra</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <dependency>
@@ -79,7 +70,7 @@
            <artifactId>yudao-spring-boot-starter-excel</artifactId>
        </dependency>
        <!-- Spring AI Model æ¨¡åž‹æŽ¥å…¥ -->
        <!-- Spring AI æ¨¡åž‹æŽ¥å…¥ï¼šç»Ÿä¸€èµ° OpenAI å…¼å®¹æŽ¥å£ï¼ˆDeepSeek/通义千问等) -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
@@ -91,50 +82,7 @@
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-anthropic</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-deepseek</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-google-genai</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-ollama</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-stability-ai</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <!-- å‘量存储:https://db-engines.com/en/ranking/vector+dbms -->
        <dependency>
            <!-- Qdrant:https://qdrant.tech/ -->
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-vector-store-redis</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-spring-boot-starter-redis</artifactId>
        </dependency>
        <!-- Milvus å‘量存储 -->
        <dependency>
            <!-- Milvus:https://milvus.io/ -->
            <groupId>org.springframework.ai</groupId>
@@ -174,80 +122,6 @@
            <version>5.4.0</version>
        </dependency>
        <!-- MCP ç›¸å…³ -->
        <!--
            ç‰¹æ®Šè¯´æ˜Žï¼šä¸èƒ½ä½¿ç”¨ spring-ai-starter-mcp-server-webflux æˆ– spring-ai-starter-mcp-client-webflux ï¼ï¼ï¼
            åŽŸå› ï¼šé¡¹ç›®ä½¿ç”¨äº† SpringMVC,而不是 WebFlux。引入上述 2 ä¸ªï¼Œä¼šå¯¼è‡´ SSE Server å¤±æ•ˆã€‚
        -->
        <dependency>
            <!-- æœåŠ¡ç«¯ -->
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
            <version>${spring-ai.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger.core.v3</groupId>
                    <artifactId>swagger-annotations-jakarta</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-autoconfigure-mcp-server-common</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <!-- å®¢æˆ·ç«¯ -->
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-autoconfigure-mcp-client-common</artifactId>
            <version>${spring-ai.version}</version>
        </dependency>
        <!-- TinyFlow:AI å·¥ä½œæµ -->
        <dependency>
            <groupId>dev.tinyflow</groupId>
            <artifactId>tinyflow-java-core</artifactId>
            <version>${tinyflow.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.jfinal</groupId>
                    <artifactId>enjoy</artifactId>
                </exclusion>
                <exclusion>
                    <!-- è§£å†³ https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1318/ é—®é¢˜ -->
                    <groupId>com.agentsflex</groupId>
                    <artifactId>agents-flex-store-elasticsearch</artifactId>
                </exclusion>
                <exclusion>
                    <!-- è§£å†³ https://t.zsxq.com/pCBZC é—®é¢˜ -->
                    <groupId>com.agentsflex</groupId>
                    <artifactId>agents-flex-search-engine-es</artifactId>
                </exclusion>
                <exclusion>
                    <!-- TODO @芋艿:暂时移除 groovy,和 iot å†²çª -->
                    <groupId>org.codehaus.groovy</groupId>
                    <artifactId>groovy-all</artifactId>
                </exclusion>
                <!-- è§£å†³å’Œ logback çš„æ—¥å¿—冲突 -->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-simple</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-slf4j-impl</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-reload4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
@@ -13,11 +13,8 @@
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.OpenAiEmbeddingModel;
import org.springframework.ai.openai.OpenAiEmbeddingOptions;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
import org.springframework.ai.tokenizer.TokenCountEstimator;
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -30,11 +27,7 @@
 * å‘量库配置见 yudao.ai.vector-store èŠ‚ç‚¹ï¼Œä¸å†ä¾èµ– Spring AI çš„ Milvus è‡ªåŠ¨é…ç½®å±žæ€§
 */
@Configuration
@EnableConfigurationProperties({
        YudaoAiProperties.class,
        QdrantVectorStoreProperties.class,
        RedisVectorStoreProperties.class
})
@EnableConfigurationProperties(YudaoAiProperties.class)
@Slf4j
public class AiAutoConfiguration {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackQuickCreateReqVO.java
@@ -5,6 +5,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Schema(description = "管理后台 - MES å¿«é€ŸæŠ¥å·¥ Request VO")
@Data
@@ -24,6 +25,9 @@
    @Schema(description = "不良品数量", example = "2")
    private BigDecimal unqualifiedQuantity;
    @Schema(description = "工艺参数值")
    private List<MesProFeedbackSaveReqVO.ParamValue> paramValues;
    @Schema(description = "备注", example = "快速报工")
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java
@@ -6,6 +6,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - MES ç”Ÿäº§æŠ¥å·¥æ–°å¢ž/修改 Request VO")
@Data
@@ -84,6 +85,19 @@
    @NotNull(message = "审核人不能为空")
    private Long approveUserId;
    @Schema(description = "工艺参数值")
    private List<ParamValue> paramValues;
    @Data
    public static class ParamValue {
        @Schema(description = "任务参数记录明细编号", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "参数明细不能为空")
        private Long detailId;
        @Schema(description = "参数值")
        private String value;
    }
    @Schema(description = "备注", example = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessRespVO.java
@@ -19,6 +19,9 @@
    @Schema(description = "工序编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long processId;
    @Schema(description = "工艺参数模板编号", example = "1")
    private Long paramTemplateId;
    @Schema(description = "工序编码")
    private String processCode;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/route/vo/process/MesProRouteProcessSaveReqVO.java
@@ -21,6 +21,9 @@
    @NotNull(message = "工序编号不能为空")
    private Long processId;
    @Schema(description = "工艺参数模板编号", example = "1")
    private Long paramTemplateId;
    @Schema(description = "序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "序号不能为空")
    private Integer sort;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskSaveReqVO.java
@@ -28,6 +28,9 @@
    @Schema(description = "工序编号", example = "1")
    private Long processId; // ç¼–辑时,可不传递
    @Schema(description = "工艺参数模板编号", example = "1")
    private Long paramTemplateId;
    @Schema(description = "产品物料编号", example = "100")
    private Long itemId; // ç¼–辑时,可不传递
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/route/MesProRouteProcessDO.java
@@ -41,6 +41,10 @@
     */
    private Long processId;
    /**
     * å·¥è‰ºå‚数模板编号
     */
    private Long paramTemplateId;
    /**
     * åºå·
     */
    private Integer sort;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/task/MesProTaskDO.java
@@ -69,6 +69,10 @@
     */
    private Long processId;
    /**
     * å·¥è‰ºå‚数模板编号
     */
    private Long paramTemplateId;
    /**
     * äº§å“ç‰©æ–™ç¼–号
     *
     * å…³è” {@link MesMdItemDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderProcessDO.java
@@ -40,6 +40,10 @@
     */
    private Long processId;
    /**
     * å·¥è‰ºå‚数模板编号
     */
    private Long paramTemplateId;
    /**
     * åºå·
     */
    private Integer sort;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderParamRecordMapper.java
@@ -27,6 +27,13 @@
                .orderByDesc(MesProWorkOrderParamRecordDO::getId));
    }
    default MesProWorkOrderParamRecordDO selectFirstByTaskId(Long taskId) {
        return selectOne(new LambdaQueryWrapperX<MesProWorkOrderParamRecordDO>()
                .eq(MesProWorkOrderParamRecordDO::getTaskId, taskId)
                .orderByAsc(MesProWorkOrderParamRecordDO::getId)
                .last("LIMIT 1"));
    }
    default List<MesProWorkOrderParamRecordDO> selectListByWorkOrderId(Long workOrderId) {
        return selectList(new LambdaQueryWrapperX<MesProWorkOrderParamRecordDO>()
                .eq(MesProWorkOrderParamRecordDO::getWorkOrderId, workOrderId)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -29,6 +29,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderParamRecordService;
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
@@ -72,6 +73,9 @@
    @Resource
    private MesProWorkOrderService workOrderService;
    @Resource
    @Lazy
    private MesProWorkOrderParamRecordService paramRecordService;
    @Resource
    private MesProRouteProcessService routeProcessService;
    @Resource
    private MesProWorkOrderBomService workOrderBomService;
@@ -109,6 +113,7 @@
                .setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus())
                .setItemId(task.getItemId());
        feedbackMapper.insert(feedback);
        saveParamValues(task.getId(), createReqVO.getParamValues());
        return feedback.getId();
    }
@@ -123,6 +128,16 @@
        MesProFeedbackDO updateObj = BeanUtils.toBean(updateReqVO, MesProFeedbackDO.class)
                .setItemId(task.getItemId());
        feedbackMapper.updateById(updateObj);
        saveParamValues(task.getId(), updateReqVO.getParamValues());
    }
    private void saveParamValues(Long taskId, List<MesProFeedbackSaveReqVO.ParamValue> paramValues) {
        if (CollUtil.isEmpty(paramValues)) {
            return;
        }
        paramRecordService.updateTaskParamValues(taskId,
                paramValues.stream().map(MesProFeedbackSaveReqVO.ParamValue::getDetailId).toList(),
                paramValues.stream().map(MesProFeedbackSaveReqVO.ParamValue::getValue).toList());
    }
    @Override
@@ -509,6 +524,7 @@
                .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
                .setRemark(reqVO.getRemark());
        feedbackMapper.insert(feedback);
        saveParamValues(task.getId(), reqVO.getParamValues());
        // 5. è¿”回结果
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(task.getWorkOrderId());
        return new MesProFeedbackQuickCreateRespVO()
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -33,6 +33,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderParamRecordService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -107,6 +108,9 @@
    @Lazy
    private MesProWorkOrderProcessService workOrderProcessService;
    @Resource
    @Lazy
    private MesProWorkOrderParamRecordService workOrderParamRecordService;
    @Resource
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private MesMdWorkstationMachineService workstationMachineService;
@@ -146,7 +150,10 @@
        }
        // 2.2 æ’å…¥
        taskMapper.insert(task);
        if (task.getParamTemplateId() != null) {
            workOrderParamRecordService.createTaskParamRecord(task.getId(), task.getParamTemplateId());
        }
        // 2.3 ä¿å­˜æŒ‡å®šæŠ¥å·¥äººå‘˜
        updateTaskAssignees(task.getId(), createReqVO.getAssigneeUserIds());
        
@@ -257,6 +264,7 @@
                result.add(new MesProRouteProcessDO()
                        .setRouteId(routeId)
                        .setProcessId(wp.getProcessId())
                        .setParamTemplateId(wp.getParamTemplateId())
                        .setSort(wp.getSort())
                        .setKeyFlag(wp.getKeyFlag())
                        .setCheckFlag(wp.getCheckFlag())
@@ -384,6 +392,15 @@
        validateTaskExists(id);
        // æ›´æ–°æ•°é‡
        taskMapper.updateProducedQuantity(id, incrProducedQuantity, incrQualifyQuantity, incrUnqualifyQuantity);
        MesProTaskDO task = taskMapper.selectById(id);
        if (task != null) {
            LocalDateTime now = LocalDateTime.now();
            MesProTaskDO timeUpdate = new MesProTaskDO().setId(id).setLastFeedbackTime(now);
            if (task.getFirstFeedbackTime() == null) {
                timeUpdate.setFirstFeedbackTime(now);
            }
            taskMapper.updateById(timeUpdate);
        }
    }
    @Override
@@ -477,9 +494,13 @@
                    .setName(buildTaskName(item, workOrder.getQuantity()))
                    .setCode(autoCodeRecordService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_TASK_CODE.getCode()))
                    .setStatus(MesProTaskStatusEnum.PREPARE.getStatus())
                    .setParamTemplateId(rp.getParamTemplateId())
                    .setClientId(workOrder.getClientId())
                    .setColorCode(rp.getColorCode());
            taskMapper.insert(task);
            if (task.getParamTemplateId() != null) {
                workOrderParamRecordService.createTaskParamRecord(task.getId(), task.getParamTemplateId());
            }
            taskIds.add(task.getId());
        }
        // 7. æ›´æ–°å·¥å•已排产数量
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderParamRecordService.java
@@ -25,6 +25,11 @@
    Long createParamRecord(Long workOrderId, @Valid MesProWorkOrderParamRecordSaveReqVO createReqVO);
    /**
     * æ ¹æ®æ¨¡æ¿ä¸ºç”Ÿäº§ä»»åŠ¡å›ºåŒ–å‚æ•°å¿«ç…§
     */
    Long createTaskParamRecord(Long taskId, Long templateId);
    /**
     * æ›´æ–°å·¥å•参数记录(仅更新填写值,不换模板)
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
@@ -64,4 +69,9 @@
     */
    List<MesProWorkOrderParamRecordDO> getParamRecordListByWorkOrderId(Long workOrderId);
    /**
     * æ›´æ–°ä»»åŠ¡å‚æ•°è®°å½•ä¸­çš„æŠ¥å·¥å€¼
     */
    void updateTaskParamValues(Long taskId, List<Long> detailIds, List<String> values);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderParamRecordServiceImpl.java
@@ -53,6 +53,42 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createTaskParamRecord(Long taskId, Long templateId) {
        MesProTaskDO task = mesProTaskService.getTask(taskId);
        if (task == null || templateId == null) {
            return null;
        }
        MesProWorkOrderDO workOrder = mesProWorkOrderService.getWorkOrder(task.getWorkOrderId());
        MesPdProcessParamTemplateDO template = templateService.validateProcessParamTemplateExists(templateId);
        List<MesPdProcessParamTemplateDetailDO> details = templateService.getProcessParamTemplateDetailListByTemplateId(templateId);
        if (CollUtil.isEmpty(details)) {
            throw exception(PRO_WORK_ORDER_PARAM_RECORD_TEMPLATE_NO_DETAIL);
        }
        MesProWorkOrderParamRecordDO record = MesProWorkOrderParamRecordDO.builder()
                .workOrderId(workOrder != null ? workOrder.getId() : task.getWorkOrderId())
                .taskId(taskId)
                .templateId(template.getId())
                .templateCode(template.getTemplateCode())
                .templateName(template.getTemplateName())
                .recordNo("TASK-" + taskId)
                .remark("生产任务排产时生成的参数快照")
                .build();
        paramRecordMapper.insert(record);
        paramRecordDetailMapper.insertBatch(details.stream().map(detail -> MesProWorkOrderParamRecordDetailDO.builder()
                .recordId(record.getId())
                .templateDetailId(detail.getId())
                .paramCode(detail.getParamCode())
                .paramName(detail.getParamName())
                .paramType(detail.getParamType())
                .unitMeasureId(detail.getUnitMeasureId())
                .paramValue(null)
                .remark(detail.getRemark())
                .build()).toList());
        return record.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createParamRecord(Long taskId, MesProWorkOrderParamRecordSaveReqVO createReqVO) {
        // 1. æ ¡éªŒå·¥å•存在
@@ -191,6 +227,29 @@
        return paramRecordMapper.selectListByWorkOrderId(workOrderId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateTaskParamValues(Long taskId, List<Long> detailIds, List<String> values) {
        if (CollUtil.isEmpty(detailIds)) {
            return;
        }
        MesProWorkOrderParamRecordDO record = paramRecordMapper.selectFirstByTaskId(taskId);
        if (record == null) {
            return;
        }
        List<MesProWorkOrderParamRecordDetailDO> details = paramRecordDetailMapper.selectListByRecordId(record.getId());
        Map<Long, MesProWorkOrderParamRecordDetailDO> detailMap = details.stream()
                .collect(Collectors.toMap(MesProWorkOrderParamRecordDetailDO::getId, d -> d));
        for (int i = 0; i < detailIds.size(); i++) {
            MesProWorkOrderParamRecordDetailDO detail = detailMap.get(detailIds.get(i));
            if (detail == null) {
                throw exception(PRO_WORK_ORDER_PARAM_RECORD_DETAIL_NOT_IN_TEMPLATE);
            }
            detail.setParamValue(values != null && values.size() > i ? values.get(i) : null);
            paramRecordDetailMapper.updateById(detail);
        }
    }
    // ==================== æ ¡éªŒæ–¹æ³• ====================
    private MesProWorkOrderParamRecordDO validateParamRecordExists(Long id) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java
@@ -151,6 +151,7 @@
        for (MesProRouteProcessDO routeProcess : routeProcessList) {
            processList.add(MesProWorkOrderProcessDO.builder()
                    .processId(routeProcess.getProcessId())
                    .paramTemplateId(routeProcess.getParamTemplateId())
                    .sort(routeProcess.getSort())
                    .nextProcessId(routeProcess.getNextProcessId())
                    .linkType(routeProcess.getLinkType())