From f57064d2fa563ee4cfeeccd715850ba8b8aa4f60 Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期一, 18 五月 2026 09:44:33 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_New_pro' into dev_宁夏_英泽防锈

---
 doc/20260515_设备巡检异常联动维修单_前端联调文档.md                                                                   |  170 ++
 src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java                          |   31 
 src/main/java/com/ruoyi/stock/service/StockInventoryService.java                                     |    2 
 src/main/resources/mapper/basic/CustomerMapper.xml                                                   |    3 
 src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java                                             |    3 
 src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java                                     |    4 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java                               |   12 
 src/main/java/com/ruoyi/project/system/controller/SysLoginController.java                            |   42 
 src/main/resources/logback.xml                                                                       |   16 
 src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java                     |   33 
 src/main/resources/manufacturing-agent-prompt.txt                                                    |    8 
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java                                      |    1 
 src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java                             |    2 
 src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java              |   40 
 src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java                    |    8 
 src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java                                  |    2 
 src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java                              |    9 
 src/main/resources/mapper/sales/SalesLedgerProductMapper.xml                                         |    4 
 src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java                             |   99 +
 src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java                                |    4 
 src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java                                  |    1 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java                      |  106 +
 src/main/resources/application-dev.yml                                                               |    2 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java                            |   42 
 src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java                                        |    7 
 src/main/java/com/ruoyi/device/controller/DeviceRepairController.java                                |   10 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java                       |   53 
 src/main/java/com/ruoyi/device/pojo/DeviceRepair.java                                                |   12 
 src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java                                        | 1035 +++++++++++++++
 src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java                              |   32 
 src/main/java/com/ruoyi/quality/pojo/QualityInspect.java                                             |   10 
 src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java                                             |    4 
 src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java                             |    2 
 src/main/resources/mapper/production/ProductionOperationTaskMapper.xml                               |    1 
 src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java                                    |    4 
 src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java                                |    2 
 src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java                                       |    2 
 src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java                                       |   12 
 src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java                                        |    2 
 src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java                            |    3 
 doc/前端联调文档-设备报修保养财务模块改造.md                                                                           |  233 +++
 src/main/java/com/ruoyi/framework/security/LoginUser.java                                            |    5 
 src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml              |   14 
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java                   |  236 +++
 .gitignore                                                                                           |    2 
 src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java |    9 
 src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java                                          |    8 
 src/main/resources/mapper/sales/SalesQuotationMapper.xml                                             |    2 
 src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml                  |   29 
 doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql                                   |   22 
 src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml                                    |    7 
 src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java                  |    8 
 src/main/java/com/ruoyi/basic/dto/ProductModelDto.java                                               |    2 
 src/main/java/com/ruoyi/device/service/IDeviceRepairService.java                                     |    4 
 src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java                            |    2 
 src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java                                   |   35 
 src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml                              |    7 
 src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java                          |    2 
 src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java    |   22 
 src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java                                      |   20 
 src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java                                 |  102 +
 src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java                                             |    3 
 src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java                                |  136 ++
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java                                      |   20 
 src/main/resources/mapper/stock/StockInventoryMapper.xml                                             |  345 ++++-
 doc/20260516_制造智能助手前端联调文档.md                                                                         |  258 +++
 src/main/java/com/ruoyi/stock/controller/StockInventoryController.java                               |   20 
 src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java                                    |    6 
 src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java               |  473 ++++++
 src/main/resources/mapper/device/DeviceRepairMapper.xml                                              |   14 
 src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java                                         |   21 
 src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java                                |    3 
 src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java                                              |    1 
 73 files changed, 3,612 insertions(+), 294 deletions(-)

diff --git a/.gitignore b/.gitignore
index d135432..ba9a2c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@
 .gradle
 /build/
 !gradle/wrapper/gradle-wrapper.jar
-
+claude.md
 target/
 /${project.build.directory}/
 !.mvn/wrapper/maven-wrapper.jar
diff --git a/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql b/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql
new file mode 100644
index 0000000..7f34fe0
--- /dev/null
+++ b/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql
@@ -0,0 +1,22 @@
+ALTER TABLE `inspection_task`
+    ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT '宸℃椤圭洰' AFTER `task_name`;
+
+ALTER TABLE `inspection_task`
+    ADD COLUMN `inspection_result` VARCHAR(1) NULL COMMENT '宸℃缁撴灉 0寮傚父 1姝e父' AFTER `remarks`,
+    ADD COLUMN `abnormal_description` VARCHAR(500) NULL COMMENT '寮傚父鎻忚堪' AFTER `inspection_result`,
+    ADD COLUMN `device_repair_id` BIGINT NULL COMMENT '鍏宠仈缁翠慨鍗旾D' AFTER `abnormal_description`,
+    ADD COLUMN `acceptance_user_id` BIGINT NULL COMMENT '楠屾敹浜篒D' AFTER `device_repair_id`,
+    ADD COLUMN `acceptance_name` VARCHAR(100) NULL COMMENT '楠屾敹浜�' AFTER `acceptance_user_id`;
+
+ALTER TABLE `timing_task`
+    ADD COLUMN `inspection_project` VARCHAR(100) NULL COMMENT '宸℃椤圭洰' AFTER `task_name`,
+    ADD COLUMN `is_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '鏄惁鍚敤 0鍚� 1鏄�' AFTER `is_active`;
+
+CREATE INDEX `idx_inspection_task_device_repair_id`
+    ON `inspection_task` (`device_repair_id`);
+
+CREATE INDEX `idx_inspection_task_inspection_result`
+    ON `inspection_task` (`inspection_result`);
+
+CREATE INDEX `idx_timing_task_is_enabled`
+    ON `timing_task` (`is_enabled`);
diff --git "a/doc/20260515_\350\256\276\345\244\207\345\267\241\346\243\200\345\274\202\345\270\270\350\201\224\345\212\250\347\273\264\344\277\256\345\215\225_\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260515_\350\256\276\345\244\207\345\267\241\346\243\200\345\274\202\345\270\270\350\201\224\345\212\250\347\273\264\344\277\256\345\215\225_\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..0df5fcc
--- /dev/null
+++ "b/doc/20260515_\350\256\276\345\244\207\345\267\241\346\243\200\345\274\202\345\270\270\350\201\224\345\212\250\347\273\264\344\277\256\345\215\225_\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,170 @@
+# 璁惧宸℃涓庡畾鏃跺贰妫�鍓嶇鑱旇皟鏂囨。锛坕nspectiontask锛�
+
+> 鏇存柊鏃ユ湡锛�2026-05-15  
+> 閫傜敤妯″潡锛氳澶囧贰妫�浠诲姟 `inspectiontask`锛坄/inspectionTask`锛変笌瀹氭椂宸℃浠诲姟锛坄/timingTask`锛�
+
+## 1. 鏈鏀瑰姩
+
+1. 宸℃浠诲姟鏂板瀛楁锛�
+   - `inspectionProject`锛堝贰妫�椤圭洰锛�
+   - `inspectionResult`锛堝贰妫�缁撴灉锛宍0`寮傚父 / `1`姝e父锛屽繀濉級
+   - `abnormalDescription`锛堝紓甯告弿杩帮級
+   - `deviceRepairId`锛堝叧鑱旂淮淇崟ID锛屽紓甯告椂鍚庣鑷姩鍥炲~锛�
+   - `acceptanceUserId`锛堥獙鏀朵汉ID锛�
+   - `acceptanceName`锛堥獙鏀朵汉锛�
+2. 寮傚父鏍¢獙瑙勫垯锛�
+   - `inspectionResult=1`锛堟甯革級锛氱収鐗囬潪蹇呭~銆�
+   - `inspectionResult=0`锛堝紓甯革級锛氬繀椤绘湁鐓х墖锛屼笖蹇呴』濉啓 `abnormalDescription`銆�
+3. 寮傚父鑱斿姩瑙勫垯锛�
+   - 寮傚父淇濆瓨鍚庤嚜鍔ㄧ敓鎴� `device_repair` 骞跺洖濉� `deviceRepairId`銆�
+4. 瀹氭椂浠诲姟鏂板瀛楁锛�
+   - `inspectionProject`锛堝贰妫�椤圭洰锛�
+   - `isEnabled`锛堟槸鍚﹀惎鐢紝`0`鍚� / `1`鏄級
+5. 澶囨敞甯﹀叆瑙勫垯锛�
+   - 瀹氭椂浠诲姟鑷姩鐢熸垚宸℃璁板綍鏃讹紝鑻ュ畾鏃朵换鍔� `remarks` 鏈夊�硷紝浼氭嫾鎺ュ埌宸℃璁板綍澶囨敞涓��
+
+## 2. 鏁版嵁搴撳彉鏇�
+
+鑱旇皟鍓嶆墽琛� SQL锛�
+
+- [doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql](/D:/鐗涢┈/鍗楅��/鍚庣/product-inventory-management-after-jdk25/doc/20260515_device_maintenance_inspection_abnormal_acceptance.sql)
+
+> 璇存槑锛氳鑴氭湰褰撳墠浣滅敤浜� `inspection_task` 涓� `timing_task` 涓ゅ紶琛紝鏂囦欢鍚嶅巻鍙蹭繚鐣欐湭鏀广��
+
+## 3. 宸℃浠诲姟鎺ュ彛
+
+### 3.1 淇濆瓨鎺ュ彛
+
+`POST /inspectionTask/addOrEditInspectionTask`
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| id | long | 鍚� | 鏈夊��=淇敼锛屾棤鍊�=鏂板 |
+| taskId | int | 寤鸿蹇呭~ | 璁惧ID锛堢敤浜庡紓甯歌嚜鍔ㄥ缓缁翠慨鍗曪級 |
+| taskName | string | 寤鸿蹇呭~ | 璁惧鍚嶇О |
+| inspectionProject | string | 鍚� | 宸℃椤圭洰 |
+| inspectorId | string | 鍚� | 宸℃浜篒D锛屾敮鎸侀�楀彿鍒嗛殧 |
+| inspectionResult | string | 鏄� | `0`=寮傚父锛宍1`=姝e父 |
+| abnormalDescription | string | 鏉′欢蹇呭~ | 寮傚父鏃跺繀濉� |
+| acceptanceUserId | long | 鍚� | 楠屾敹浜篒D |
+| acceptanceName | string | 鍚� | 楠屾敹浜哄鍚� |
+| commonFileListDTO | array | 鏉′欢蹇呭~ | 闄勪欢缁�1锛堝紓甯告椂涓夌粍鑷冲皯涓�缁勬湁鍥撅級 |
+| commonFileListAfterDTO | array | 鏉′欢蹇呭~ | 闄勪欢缁�2锛堝紓甯告椂涓夌粍鑷冲皯涓�缁勬湁鍥撅級 |
+| commonFileListBeforeDTO | array | 鏉′欢蹇呭~ | 闄勪欢缁�3锛堝紓甯告椂涓夌粍鑷冲皯涓�缁勬湁鍥撅級 |
+
+寮傚父绀轰緥锛�
+
+```json
+{
+  "taskId": 1001,
+  "taskName": "绌哄帇鏈篈-01",
+  "inspectionProject": "娑︽粦绯荤粺",
+  "inspectorId": "12",
+  "inspectionResult": "0",
+  "abnormalDescription": "鐢垫満寮傚搷锛屾俯鍗囧亸楂�",
+  "acceptanceUserId": 20,
+  "commonFileListDTO": [
+    {
+      "id": 90001,
+      "application": "file"
+    }
+  ]
+}
+```
+
+姝e父绀轰緥锛�
+
+```json
+{
+  "taskId": 1001,
+  "taskName": "绌哄帇鏈篈-01",
+  "inspectionProject": "鐐规",
+  "inspectorId": "12",
+  "inspectionResult": "1",
+  "acceptanceUserId": 20
+}
+```
+
+### 3.2 鍒楄〃鎺ュ彛
+
+`GET /inspectionTask/list`
+
+杩斿洖鍖呭惈鏂板瀛楁锛�
+
+- `inspectionProject`
+- `inspectionResult`
+- `abnormalDescription`
+- `deviceRepairId`
+- `acceptanceUserId`
+- `acceptanceName`
+
+## 4. 瀹氭椂浠诲姟鎺ュ彛
+
+### 4.1 淇濆瓨鎺ュ彛
+
+`POST /timingTask/addOrEditTimingTask`
+
+鏂板/鏇存柊瀛楁锛�
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| inspectionProject | string | 鍚� | 宸℃椤圭洰 |
+| remarks | string | 鍚� | 澶囨敞 |
+| isEnabled | int | 鍚� | `0`=绂佺敤锛宍1`=鍚敤锛涗笉浼犻粯璁ゅ惎鐢� |
+
+绀轰緥锛�
+
+```json
+{
+  "taskName": "绌哄帇鏈篈-01瀹氭椂宸℃",
+  "inspectionProject": "鏈堝害宸℃",
+  "taskId": 1001,
+  "inspectorIds": "12,13",
+  "frequencyType": "DAILY",
+  "frequencyDetail": "09:00",
+  "remarks": "閲嶇偣妫�鏌ヨ酱鎵挎俯搴�",
+  "isEnabled": 1
+}
+```
+
+### 4.2 鍚敤鐘舵�佽涓�
+
+1. `isEnabled=1`锛氫换鍔¤繘鍏ヨ皟搴︼紝鎸夐娆¤嚜鍔ㄧ敓鎴愬贰妫�璁板綍銆�
+2. `isEnabled=0`锛氫换鍔′笉璋冨害锛涘凡瀛樺湪璋冨害浼氳绉婚櫎銆�
+
+### 4.3 澶囨敞甯﹀叆瑙勫垯
+
+瀹氭椂浠诲姟鑷姩鐢熸垚宸℃璁板綍鏃讹細
+
+1. 宸℃璁板綍澶囨敞鍥哄畾鍓嶇紑锛歚鑷姩鐢熸垚鑷畾鏃朵换鍔D: {id}`
+2. 褰撳畾鏃朵换鍔� `remarks` 闈炵┖鏃讹紝鎷兼帴涓猴細  
+   `鑷姩鐢熸垚鑷畾鏃朵换鍔D: {id}锛泏remarks}`
+
+## 5. 寮傚父鑷姩寤虹淮淇崟瑙勫垯
+
+褰撳贰妫�璁板綍 `inspectionResult=0` 鏃讹細
+
+1. 鑻� `deviceRepairId` 涓虹┖锛屽悗绔嚜鍔ㄥ垱寤� `device_repair`锛�
+   - `deviceLedgerId`锛氭潵鑷� `taskId`
+   - `deviceName`锛氫紭鍏� `taskName`锛屽惁鍒欏彇璁惧鍙拌处鍚嶇О
+   - `remark`锛氬紓甯告弿杩�
+   - `status`锛歚0`锛堝緟缁翠慨锛�
+2. 鑻ュ凡鏈夊叧鑱旂淮淇崟锛屼粎鍚屾鏇存柊缁翠慨鍗� `remark`銆�
+
+## 6. 鍓嶇鏀归�犲缓璁�
+
+1. 宸℃琛ㄥ崟鏂板 `inspectionProject` 杈撳叆妗嗐��
+2. 宸℃琛ㄥ崟淇濈暀鈥滄甯�/寮傚父鈥濊仈鍔ㄦ牎楠岋細
+   - 寮傚父鏃跺己鍒跺紓甯告弿杩� + 鑷冲皯涓�缁勫浘鐗囥��
+3. 瀹氭椂浠诲姟琛ㄥ崟鏂板鈥滄槸鍚﹀惎鐢ㄢ�濆紑鍏冲苟鏄犲皠 `isEnabled`銆�
+4. 瀹氭椂浠诲姟琛ㄥ崟鏂板 `inspectionProject` 涓� `remarks` 杈撳叆椤广��
+5. 宸℃鍒楄〃灞曠ず `inspectionProject` 鍜� `deviceRepairId`锛堟敮鎸佽烦杞淮淇崟璇︽儏锛夈��
+
+## 7. 鑱旇皟楠屾敹娓呭崟
+
+1. 宸℃鏂板/淇敼鍙纭彁浜� `inspectionProject` 骞跺湪鍒楄〃鍥炴樉銆�
+2. 寮傚父宸℃锛堟湁鎻忚堪+鏈夊浘锛変繚瀛樻垚鍔熷苟鍥炲~ `deviceRepairId`銆�
+3. 寮傚父宸℃缂烘弿杩版垨缂哄浘鐗囨椂琚嫤鎴��
+4. 瀹氭椂浠诲姟 `isEnabled=0` 鏃朵笉鍐嶈Е鍙戣嚜鍔ㄥ贰妫�璁板綍銆�
+5. 瀹氭椂浠诲姟 `isEnabled=1` 鏃舵寜棰戞鐢熸垚宸℃璁板綍銆�
+6. 瀹氭椂浠诲姟鏈� `remarks` 鏃讹紝鑷姩宸℃璁板綍澶囨敞甯︿笂璇ュ唴瀹广��
diff --git "a/doc/20260516_\345\210\266\351\200\240\346\231\272\350\203\275\345\212\251\346\211\213\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260516_\345\210\266\351\200\240\346\231\272\350\203\275\345\212\251\346\211\213\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..d4b9a1f
--- /dev/null
+++ "b/doc/20260516_\345\210\266\351\200\240\346\231\272\350\203\275\345\212\251\346\211\213\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,258 @@
+# 鍒堕�犳櫤鑳藉姪鎵嬪墠绔仈璋冩枃妗o紙`manufacturing-ai`锛�
+
+> 鏇存柊鏃ユ湡锛�2026-05-16  
+> 閫傜敤妯″潡锛氱敓浜х幇鍦恒�佽鍒掋�佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞�  
+> 鑳藉姏鑼冨洿锛氭煡銆侀棶銆佸姙銆侀璀︺�佸垎鏋�
+
+## 1. 鎺ュ彛鎬昏
+
+1. 娴佸紡瀵硅瘽锛歚POST /manufacturing-ai/chat`
+2. 浼氳瘽鍒楄〃锛歚GET /manufacturing-ai/history/sessions`
+3. 浼氳瘽娑堟伅锛歚GET /manufacturing-ai/history/messages/{memoryId}`
+4. 鍒犻櫎浼氳瘽锛歚DELETE /manufacturing-ai/history/{memoryId}`
+
+璇存槑锛�
+- `/chat` 涓� **SSE/娴佸紡鏂囨湰** 杩斿洖锛坄text/stream;charset=utf-8`锛夈��
+- 鍛戒腑鈥滄煡/棰勮/鍒嗘瀽/鍔炩�濆伐鍏锋椂锛屾祦寮忔渶缁堝唴瀹规槸 **JSON 瀛楃涓�**锛堜笉鏄� `AjaxResult`锛夈��
+- 鏈懡涓伐鍏锋椂锛岃繑鍥炴櫘閫氳嚜鐒惰瑷�鏂囨湰銆�
+
+## 2. 閴存潈涓庤姹傚ご
+
+- 缁熶竴浣跨敤绯荤粺鐧诲綍鎬侊紙`Authorization` 涓庣幇鏈夋帴鍙d竴鑷达級銆�
+- `POST /manufacturing-ai/chat` 璇锋眰澶达細`Content-Type: application/json`銆�
+
+## 3. 瀵硅瘽鎺ュ彛
+
+### 3.1 璇锋眰
+
+```http
+POST /manufacturing-ai/chat
+Content-Type: application/json
+```
+
+```json
+{
+  "memoryId": "mfg-ai-001",
+  "message": "鏌ヨ澶囪タ闂ㄥ瓙鍙橀鍣ㄧ殑缁翠慨鎯呭喌"
+}
+```
+
+瀛楁璇存槑锛�
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+| --- | --- | --- | --- |
+| memoryId | string | 鏄� | 浼氳瘽 ID锛屽墠绔敓鎴愬苟澶嶇敤 |
+| message | string | 鏄� | 鐢ㄦ埛杈撳叆 |
+
+### 3.2 杩斿洖锛堟祦寮忥級
+
+```http
+Content-Type: text/stream;charset=utf-8
+```
+
+鍓嶇澶勭悊寤鸿锛�
+1. 鎸夋祦鎷兼帴瀹屾暣鏂囨湰銆�
+2. 灏濊瘯 `JSON.parse(fullText)`锛�
+   - 鎴愬姛锛氭寜缁撴瀯鍖栫粨鏋滄覆鏌撱��
+   - 澶辫触锛氭寜鏅�氳亰澶╂枃鏈覆鏌撱��
+
+## 4. 缁撴瀯鍖栧搷搴斿崗璁�
+
+### 4.1 閫氱敤缁撴瀯
+
+```json
+{
+  "success": true,
+  "type": "manufacturing_device_repair_list",
+  "description": "宸茶繑鍥炶澶囩淮淇褰曘��",
+  "summary": {},
+  "data": {},
+  "charts": {}
+}
+```
+
+### 4.2 `type` 鏋氫妇
+
+| type | 鍦烘櫙 |
+| --- | --- |
+| manufacturing_site_snapshot | 鐢熶骇鐜板満姒傝 |
+| manufacturing_plan_list | 鐢熶骇璁″垝鏌ヨ |
+| manufacturing_workorder_list | 宸ュ崟鏌ヨ |
+| manufacturing_device_list | 璁惧鍙拌处鏌ヨ |
+| manufacturing_device_repair_list | 璁惧缁翠慨璁板綍鏌ヨ |
+| manufacturing_quality_list | 璐ㄩ噺鏌ヨ |
+| manufacturing_material_list | 鐗╂枡搴撳瓨鏌ヨ |
+| manufacturing_exception_list | 寮傚父澶勭悊鏌ヨ |
+| manufacturing_warning | 棰勮鐪嬫澘 |
+| manufacturing_analysis | 缁忚惀鍒嗘瀽 |
+| manufacturing_action_plan | 鍔炵悊寤鸿锛堝姩浣滃崱锛� |
+
+## 5. 鈥滄煡鈥濊兘鍔涜仈璋冭鐐�
+
+### 5.1 璁惧鐩稿叧璺敱瑙勫垯锛堝叧閿級
+
+- 褰撶敤鎴疯緭鍏ュ寘鍚� `缁翠慨/鎶ヤ慨/妫�淇�/缁存姢`锛岃澶囧煙浼氳繑鍥� `manufacturing_device_repair_list`锛堟煡 `device_repair`锛夈��
+- 鏈寘鍚互涓婅瘝鏃讹紝杩斿洖 `manufacturing_device_list`锛堟煡璁惧鍙拌处锛夈��
+
+绀轰緥锛�
+- `鏌ヨ澶嘇-01` -> `manufacturing_device_list`
+- `鏌ヨ澶嘇-01缁翠慨鎯呭喌` -> `manufacturing_device_repair_list`
+
+### 5.2 缁翠慨璁板綍鏃堕棿杩囨护瑙勫垯锛堝叧閿級
+
+- 鐢ㄦ埛鏄庣‘甯︽椂闂存潯浠讹紙濡傗�滄湰鏈�/涓婂懆/杩�7澶�/2026-05-01 鍒� 2026-05-16鈥濓級鎵嶆寜鏃堕棿杩囨护缁翠慨璁板綍銆�
+- 鏈甫鏃堕棿鏉′欢鏃讹紝涓嶉粯璁ゆ寜杩� 30 澶╂埅鏂紝閬垮厤鍘嗗彶缁翠慨璁板綍琚杩囨护銆�
+
+### 5.3 鍏抽敭璇嶅鐞嗚鍒欙紙璁惧/缁翠慨锛�
+
+- 绯荤粺浼氭竻娲楀櫔闊宠瘝锛歚鏌ヨ/鏌ョ湅/璇�/璁惧/缁翠慨鎯呭喌/璁板綍/淇℃伅` 绛夈��
+- 鍚屾椂浼氶�氳繃璁惧鍙拌处鍖归厤 `deviceLedgerId` 鍏滃簳锛屽啀鍥炴煡缁翠慨璁板綍锛岄檷浣庘�滄湁鏁版嵁浣嗘煡涓嶅埌鈥濈殑姒傜巼銆�
+
+### 5.4 鍒楄〃缁撴灉绾﹀畾
+
+- 鍒楄〃鏁版嵁缁熶竴鍦� `data.items`
+- 缁熻鎽樿鍦� `summary`
+
+甯哥敤瀛楁锛�
+
+| type | 甯哥敤瀛楁 |
+| --- | --- |
+| manufacturing_plan_list | `mpsNo`, `requiredDate`, `status` |
+| manufacturing_workorder_list | `workOrderNo`, `planStartTime`, `planEndTime`, `status` |
+| manufacturing_device_list | `deviceName`, `deviceModel`, `pendingRepairCount` |
+| manufacturing_device_repair_list | `deviceName`, `deviceModel`, `repairTime`, `repairName`, `maintenanceName`, `status`, `createTime` |
+
+## 6. 鈥滈璀︹�濊仈璋冭鐐�
+
+- `type = manufacturing_warning`
+- 棰勮鏄庣粏鍦� `data.items`锛屾瘡椤瑰寘鍚細
+  - `level`锛歚high` / `medium`
+  - `title`
+  - `count`
+  - `detail`
+
+鐘舵�佸彛寰勶細
+- 璁惧鈥滃緟缁翠慨鈥濈粺璁℃寜 `status = 0` 璁$畻锛堜笉鍐嶆妸鍏朵粬鐘舵�佽鍏ュ緟缁翠慨锛夈��
+
+## 7. 鈥滃垎鏋愨�濊仈璋冭鐐�
+
+- `type = manufacturing_analysis`
+- 鍏抽敭鎸囨爣鍦� `summary`
+- 鎸囨爣鍗″湪 `data.coreMetrics`
+- 鍥捐〃閰嶇疆鍦� `charts`锛�
+  - `charts.domainBarOption`
+  - `charts.qualityPieOption`
+
+鍥捐〃閰嶇疆鍙洿鎺ョ粰 ECharts 浣跨敤銆�
+
+## 8. 鈥滃姙鈥濊兘鍔涜仈璋冭鐐�
+
+褰撳墠鈥滃姙鈥濅负 **鍔炵悊寤鸿妯″紡**锛圓I 杈撳嚭鍔ㄤ綔鍗★紝鍓嶇纭鍚庤皟鐢ㄧ洰鏍囦笟鍔℃帴鍙o級銆�
+
+- `type = manufacturing_action_plan`
+- 鍔ㄤ綔鍗℃暟缁勶細`data.actionCards`
+
+鍔ㄤ綔鍗″瓧娈碉細
+
+| 瀛楁 | 璇存槑 |
+| --- | --- |
+| code | 鍔ㄤ綔缂栫爜 |
+| name | 鍔ㄤ綔鍚嶇О |
+| method | 璇锋眰鏂规硶 |
+| targetApi | 鐩爣涓氬姟鎺ュ彛 |
+| requiredFields | 蹇呭~瀛楁 |
+| examplePayload | 绀轰緥鍙傛暟 |
+| description | 璇存槑 |
+
+鍐呯疆鍔ㄤ綔绀轰緥锛�
+1. `POST /productionOperationTask/assign`
+2. `POST /device/repair`
+3. `POST /quality/qualityUnqualified/deal`
+4. `POST /stockInventory/addstockInventory`
+5. `POST /procurementExceptionRecord/add`
+
+## 9. 浼氳瘽绠$悊鎺ュ彛
+
+### 9.1 浼氳瘽鍒楄〃
+
+```http
+GET /manufacturing-ai/history/sessions
+```
+
+`AjaxResult.data` 瀛楁锛�
+- `memoryId`
+- `title`
+- `lastMessage`
+- `messageCount`
+- `lastChatTime`
+
+### 9.2 浼氳瘽娑堟伅
+
+```http
+GET /manufacturing-ai/history/messages/{memoryId}
+```
+
+`AjaxResult.data` 瀛楁锛�
+- `role`锛歚user` / `assistant` / `system` / `tool`
+- `content`
+- `filePaths`
+
+### 9.3 鍒犻櫎浼氳瘽
+
+```http
+DELETE /manufacturing-ai/history/{memoryId}
+```
+
+杩斿洖鏍囧噯 `AjaxResult`銆�
+
+## 10. 閿欒涓庤竟鐣�
+
+`/chat` 甯歌杩斿洖鏂囨湰锛�
+- `memoryId涓嶈兘涓虹┖`
+- `message涓嶈兘涓虹┖`
+
+寤鸿鍓嶇鍙戦�佸墠鍏堝仛蹇呭~鏍¢獙銆�
+
+## 11. 鍓嶇鑱旇皟娴佺▼寤鸿
+
+1. 鐧诲綍鍚庡垱寤哄苟澶嶇敤 `memoryId`銆�
+2. 璋冪敤 `/manufacturing-ai/chat`锛屾寜 SSE 鎷兼帴瀹屾暣鏂囨湰銆�
+3. 鍏堝皾璇� JSON 瑙f瀽锛�
+   - 鎴愬姛锛氭寜 `type` 璺敱鍒板搴� UI锛堝垪琛�/棰勮/鍒嗘瀽/鍔ㄤ綔鍗★級銆�
+   - 澶辫触锛氭寜鏅�氳亰澶╂秷鎭睍绀恒��
+4. 鈥滃姙鈥濆満鏅敱鐢ㄦ埛纭鍔ㄤ綔鍗″悗锛屽墠绔皟鐢� `targetApi` 瀹屾垚涓氬姟鎻愪氦銆�
+5. 閫氳繃鍘嗗彶鎺ュ彛鍋氫細璇濆洖鏄句笌鍒犻櫎銆�
+
+## 12. 鍓嶇闆嗘垚绾︽潫锛堟湰娆¤ˉ鍏咃級
+
+### 12.1 鏅鸿兘浣撴柊澧炰笌寮圭獥鍚屾瑙勫垯锛堝己鍒讹級
+
+1. 褰� `src/views/aiIndustrialBrain/index.vue` 鏂板鏅鸿兘浣擄紙`agents`锛夐�昏緫鏃讹紝蹇呴』鍚屾纭寮圭獥鍔╂墜鍙敤鎬с��
+2. 寮圭獥鍔╂墜缁熶竴鐢� `src/components/AIChatSidebar/assistants/index.js` 鐨� `assistantRegistry` 娉ㄥ唽銆�
+3. 鏂板鏅鸿兘浣撶殑 `key` 鑻ヨ鍦ㄥ脊绐椾腑鍙敤锛屽繀椤诲湪 `assistantRegistry` 涓彁渚涘悓鍚嶉厤缃��
+4. 鏈湪 `assistantRegistry` 娉ㄥ唽鐨勬櫤鑳戒綋锛屽脊绐楁樉绀轰负 `pending`锛堝紑鍙戜腑锛夋�併��
+
+### 12.2 鐢熶骇鍔╂墜鎺ュ叆绾﹀畾
+
+1. 鐢熶骇鍔╂墜閰嶇疆浣嶄簬 `src/components/AIChatSidebar/assistants/productionAssistant.js`锛宍apiBase = /manufacturing-ai`銆�
+2. AI 宸ヤ笟澶ц剳涓敓浜ф櫤鑳戒綋杩涘叆寮圭獥鍚庯紝榛樿浣跨敤 `production` 鍔╂墜銆�
+3. 鍏ㄥ眬鍙充晶瀵硅瘽妗嗗姪鎵嬪垏鎹㈠垪琛ㄥ凡鍖呭惈锛�
+   - `general`锛堝緟鍔炲姪鐞嗭級
+   - `purchase`锛堥噰璐姪鐞嗭級
+   - `production`锛堢敓浜у姪鐞嗭級
+
+### 12.3 瀛楁涓枃鍖栧睍绀鸿鍒�
+
+1. 闈㈠悜涓氬姟鐢ㄦ埛鐨勫瓧娈靛悕銆佹爣绛俱�佸繀濉彁绀轰笉鐩存帴灞曠ず鑻辨枃 key銆�
+2. `requiredFields`銆乣missingFields` 鎻愮ず闇�杞崲涓轰腑鏂囪矾寰勬爣绛撅紙绀轰緥锛歚缂哄皯蹇呭~瀛楁锛氬伐鍗曞彿銆佽鍒掔粨鏉熸椂闂碻锛夈��
+3. 缁撴瀯鍖栧垪琛ㄥ垪鍚嶃�佹憳瑕佹寚鏍囥�佸姩浣滃崱瀛楁浼樺厛鏄剧ず涓枃锛涜嫳鏂� key 浠呯敤浜庢帴鍙i�氫俊涓庤皟璇曘��
+
+## 13. 鏈鏇存柊璁板綍锛�2026-05-16锛�
+
+1. 鏂板璁惧缁翠慨璁板綍杩斿洖绫诲瀷锛歚manufacturing_device_repair_list`銆�
+2. 淇璁惧鍩熸剰鍥惧垎娴侊細`缁翠慨/鎶ヤ慨/妫�淇�/缁存姢` 璧扮淮淇褰曪紝涓嶅啀璇蛋璁惧鍒楄〃銆�
+3. 淇缁翠慨璁板綍鏃堕棿杩囨护锛氫粎鍦ㄧ敤鎴锋槑纭椂闂存潯浠舵椂鐢熸晥銆�
+4. 淇寰呯淮淇粺璁″彛寰勶細鎸� `status = 0` 缁熻銆�
+5. 鏂板 AI 宸ヤ笟澶ц剳鏅鸿兘浣撲笌寮圭獥鍚屾缁存姢瑙勫垯锛氭柊澧炴櫤鑳戒綋蹇呴』鍚屾娉ㄥ唽寮圭獥鍔╂墜銆�
+6. 鐢熶骇鍔╂墜宸叉帴鍏ュ伐涓氬ぇ鑴戝脊绐椾笌鍏ㄥ眬鍙充晶瀵硅瘽妗嗗姪鎵嬪垏鎹€��
+7. 澧炲姞瀛楁涓枃鍖栧睍绀虹害鏉燂細閬垮厤鑻辨枃瀛楁瀵逛笟鍔$敤鎴风洿鍑恒��
diff --git "a/doc/\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243-\350\256\276\345\244\207\346\212\245\344\277\256\344\277\235\345\205\273\350\264\242\345\212\241\346\250\241\345\235\227\346\224\271\351\200\240.md" "b/doc/\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243-\350\256\276\345\244\207\346\212\245\344\277\256\344\277\235\345\205\273\350\264\242\345\212\241\346\250\241\345\235\227\346\224\271\351\200\240.md"
new file mode 100644
index 0000000..43c468c
--- /dev/null
+++ "b/doc/\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243-\350\256\276\345\244\207\346\212\245\344\277\256\344\277\235\345\205\273\350\264\242\345\212\241\346\250\241\345\235\227\346\224\271\351\200\240.md"
@@ -0,0 +1,233 @@
+# 鍓嶇鑱旇皟鏂囨。锛堣澶囨姤淇� / 璁惧淇濆吇瀹氭椂浠诲姟 / 璐㈠姟绉戠洰鎬昏处锛�
+
+## 1. 鍙樻洿鑼冨洿
+
+鏈鑱旇皟娑夊強 3 涓ā鍧楋細
+
+1. 璐㈠姟妯″潡锛氱鐩�昏处鍘绘帀鍑瘉瀛楀彿銆佹憳瑕侊紝鍙繑鍥� 1 鏉″悎璁℃暟鎹��
+2. 璁惧淇濆吇瀹氭椂浠诲姟锛氭柊澧� `淇濆吇浜篳 瀛楁锛屽畾鏃朵换鍔$敓鎴愪繚鍏昏褰曟椂甯﹀叆銆�
+3. 璁惧鎶ヤ慨锛氱‘璁ゆ姤淇悗鏂板楠屾敹瀹℃壒锛岄獙鏀堕�氳繃鍚庢墠绠楀畬缁撱��
+
+---
+
+## 2. 鎺ュ彛娓呭崟
+
+### 2.1 璐㈠姟-绉戠洰鎬昏处
+
+- **GET** `/financial/ledger/general`
+- 璇存槑锛氳繑鍥炵鐩�昏处鍚堣锛屼粎 1 鏉¤褰曘��
+
+#### 璇锋眰鍙傛暟锛圦uery锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| `subjectCode` | string | 鏄� | 绉戠洰缂栫爜 |
+| `startMonth` | string | 鏄� | 寮�濮嬫湀浠斤紝鏍煎紡 `YYYY-MM` |
+| `endMonth` | string | 鏄� | 缁撴潫鏈堜唤锛屾牸寮� `YYYY-MM` |
+
+#### 杩斿洖缁撴瀯
+
+`R<List<FinLedgerRowVo>>`
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": [
+    {
+      "rowType": "yearly_total",
+      "date": "2026-05-31",
+      "debit": 12000.00,
+      "credit": 8000.00,
+      "direction": "鍊�",
+      "balance": 4000.00
+    }
+  ]
+}
+```
+
+#### 鑱旇皟娉ㄦ剰
+
+1. `data` 鍥哄畾鍙湁 1 鏉★紙鍚堣锛夈��
+2. `voucherNo`銆乣summary` 涓嶈繑鍥烇紙涓嶅啀灞曠ず鍑瘉瀛楀彿銆佹憳瑕侊級銆�
+
+---
+
+### 2.2 璁惧淇濆吇瀹氭椂浠诲姟锛堟柊澧炰繚鍏讳汉锛�
+
+- 鍩虹璺緞锛歚/deviceMaintenanceTask`
+- 鐩稿叧鎺ュ彛锛�
+  - **POST** `/add`
+  - **POST** `/update`
+  - **GET** `/listPage`
+
+#### 鏂板瀛楁
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|---|---|---|
+| `maintenancePerson` | string | 淇濆吇浜� |
+
+#### 鏂板/鏇存柊璇锋眰绀轰緥
+
+```json
+{
+  "id": 1,
+  "taskName": "绌哄帇鏈轰繚鍏讳换鍔�",
+  "taskId": 1001,
+  "maintenancePerson": "寮犱笁",
+  "frequencyType": "MONTHLY",
+  "frequencyDetail": "10,09:00",
+  "remarks": "姣忔湀渚嬭淇濆吇"
+}
+```
+
+#### 瀹氭椂浠诲姟涓嬪彂琛屼负
+
+瀹氭椂浠诲姟鎵ц鍚庯紝绯荤粺鑷姩鍒涘缓淇濆吇璁板綍锛坄device_maintenance`锛夋椂浼氬啓鍏ワ細
+
+- `maintenanceActuallyName = maintenancePerson`
+
+鍗冲墠绔湪瀹氭椂浠诲姟閲岀淮鎶ょ殑淇濆吇浜猴紝浼氳嚜鍔ㄥ甫鍏ュ埌淇濆吇璁板綍銆�
+
+---
+
+### 2.3 璁惧鎶ヤ慨锛堢‘璁ゅ悗楠屾敹瀹℃壒锛�
+
+- 鍩虹璺緞锛歚/device/repair`
+
+#### 鐘舵�佸畾涔�
+
+| 鐘舵�佸�� | 鍚箟 |
+|---|---|
+| `0` | 寰呯淮淇� |
+| `3` | 寰呴獙鏀� |
+| `1` | 瀹岀粨 |
+| `2` | 澶辫触 |
+
+#### 2.3.1 缁翠慨纭锛堝師纭鎶ヤ慨锛�
+
+- **POST** `/device/repair/repair`
+- 璇存槑锛氭彁浜ゅ悗鐘舵�佷粠 `寰呯淮淇�(0)` 杩涘叆 `寰呴獙鏀�(3)`锛屼笉鍐嶇洿鎺ュ畬缁撱��
+
+璇锋眰绀轰緥锛�
+
+```json
+{
+  "id": 10001,
+  "maintenanceName": "鏉庡洓",
+  "maintenanceTime": "2026-05-14 10:30:00",
+  "maintenanceResult": "鏇存崲杞存壙骞惰瘯杩愯姝e父",
+  "sparePartsUseList": [
+    {
+      "id": 501,
+      "quantity": 2
+    }
+  ]
+}
+```
+
+甯歌澶辫触鎻愮ず锛堢敤浜庡墠绔脊绐楋級锛�
+
+- `鎶ヤ慨璁板綍涓嶅瓨鍦╜
+- `璇ユ姤淇凡瀹岀粨锛屼笉鑳介噸澶嶇‘璁ょ淮淇甡
+- `璇ユ姤淇凡鎻愪氦楠屾敹瀹℃壒`
+- `澶囦欢 xxx 鏁伴噺涓嶈冻`
+
+#### 2.3.2 楠屾敹瀹℃壒锛堟柊澧烇級
+
+- **POST** `/device/repair/acceptance`
+- 璇存槑锛氫粎 `寰呴獙鏀�(3)` 鍙鎵癸紱瀹℃壒閫氳繃鍚庣姸鎬佹敼涓� `瀹岀粨(1)`銆�
+
+璇锋眰鍙傛暟锛圔ody锛夛細
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| `id` | long | 鏄� | 鎶ヤ慨璁板綍ID |
+| `acceptanceName` | string | 鏄� | 楠屾敹浜� |
+| `acceptanceTime` | string | 鏄� | 楠屾敹鏃堕棿锛屾牸寮� `yyyy-MM-dd HH:mm:ss` |
+| `acceptanceRemark` | string | 鏄� | 楠屾敹澶囨敞 |
+
+璇锋眰绀轰緥锛�
+
+```json
+{
+  "id": 10001,
+  "acceptanceName": "鐜嬩簲",
+  "acceptanceTime": "2026-05-14 11:00:00",
+  "acceptanceRemark": "缁翠慨椤规牳楠岄�氳繃锛岃澶囪繍琛屾甯�"
+}
+```
+
+甯歌澶辫触鎻愮ず锛�
+
+- `鎶ヤ慨璁板綍id涓嶈兘涓虹┖`
+- `鎶ヤ慨璁板綍涓嶅瓨鍦╜
+- `璇ユ姤淇湭杩涘叆寰呴獙鏀剁姸鎬侊紝涓嶈兘瀹℃壒`
+- `楠屾敹浜轰笉鑳戒负绌篳
+- `楠屾敹鏃堕棿涓嶈兘涓虹┖`
+- `楠屾敹澶囨敞涓嶈兘涓虹┖`
+
+#### 2.3.3 鏅�氭洿鏂版帴鍙i檺鍒�
+
+- **PUT** `/device/repair`
+- 闄愬埗锛氫笉鑳介�氳繃鏅�氭洿鏂扮洿鎺ユ妸鐘舵�佹敼鎴� `瀹岀粨(1)`锛堝繀椤昏蛋楠屾敹瀹℃壒鎺ュ彛锛夈��
+- 澶辫触鎻愮ず锛歚璇峰厛鎻愪氦楠屾敹瀹℃壒锛岄獙鏀堕�氳繃鍚庢墠鍙畬缁揱
+
+---
+
+## 3. 杩斿洖瀛楁鍙樻洿锛堟姤淇垪琛�/璇︽儏锛�
+
+浠ヤ笅鎺ュ彛杩斿洖宸叉柊澧為獙鏀跺瓧娈碉細
+
+- **GET** `/device/repair/page`
+- **GET** `/device/repair/{id}`
+
+鏂板杩斿洖瀛楁锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|---|---|---|
+| `acceptanceName` | string | 楠屾敹浜� |
+| `acceptanceTime` | string | 楠屾敹鏃堕棿 |
+| `acceptanceRemark` | string | 楠屾敹澶囨敞 |
+
+---
+
+## 4. 鍓嶇鏀归�犲缓璁�
+
+1. 鎶ヤ慨鍒楄〃澧炲姞鐘舵�佸�� `3=寰呴獙鏀禶 鐨勫睍绀烘枃妗堜笌绛涢�夐」銆�
+2. 鈥滅‘璁ょ淮淇�濇寜閽皟鐢� `/device/repair/repair`锛屾垚鍔熷悗鍒锋柊涓哄緟楠屾敹鐘舵�併��
+3. 鏂板鈥滈獙鏀跺鎵光�濆脊绐楋紝蹇呭~锛�
+   - 楠屾敹浜�
+   - 楠屾敹鏃堕棿
+   - 楠屾敹澶囨敞
+4. 绂佹鍦ㄦ櫘閫氱紪杈戦〉鐩存帴灏嗙姸鎬佺疆涓哄畬缁撱��
+5. 璁惧淇濆吇瀹氭椂浠诲姟鏂板鈥滀繚鍏讳汉鈥濊緭鍏ラ」锛屽苟鍦ㄥ垪琛�/璇︽儏灞曠ず銆�
+6. 绉戠洰鎬昏处椤甸潰鎸夊崟琛屽悎璁℃覆鏌擄紝涓嶅啀鏄剧ず鍑瘉瀛楀彿銆佹憳瑕佸垪銆�
+
+---
+
+## 5. 鑱旇皟妫�鏌ユ竻鍗�
+
+1. 绉戠洰鎬昏处鏌ヨ杩斿洖 `data.length === 1`锛屼笖鏃� `voucherNo/summary`銆�
+2. 鏂板淇濆吇瀹氭椂浠诲姟鏃朵紶 `maintenancePerson`锛屽垪琛ㄨ兘鍥炴樉銆�
+3. 瀹氭椂浠诲姟瑙﹀彂鍚庯紝鐢熸垚鐨勪繚鍏昏褰� `maintenanceActuallyName` 涓庡畾鏃朵换鍔′繚鍏讳汉涓�鑷淬��
+4. 鎶ヤ慨鍗曟祦绋嬶細`0寰呯淮淇� -> 3寰呴獙鏀� -> 1瀹岀粨`銆�
+5. 寰呴獙鏀跺崟鎹湭濉獙鏀朵汉/楠屾敹鏃堕棿/楠屾敹澶囨敞鏃讹紝鍚庣杩斿洖瀵瑰簲閿欒鎻愮ず銆�
+6. 灏濊瘯閫氳繃 `PUT /device/repair` 鐩存帴璁句负瀹岀粨鏃讹紝鍚庣杩斿洖鎷︽埅鎻愮ず銆�
+
+---
+
+## 6. 鏁版嵁搴撳彉鏇达紙鑱旇皟鍓嶇‘璁わ級
+
+```sql
+ALTER TABLE maintenance_task
+  ADD COLUMN maintenance_person VARCHAR(100) NULL COMMENT '淇濆吇浜�';
+
+ALTER TABLE device_repair
+  ADD COLUMN acceptance_name VARCHAR(100) NULL COMMENT '楠屾敹浜�',
+  ADD COLUMN acceptance_time DATETIME NULL COMMENT '楠屾敹鏃堕棿',
+  ADD COLUMN acceptance_remark VARCHAR(500) NULL COMMENT '楠屾敹澶囨敞';
+```
+
+> 鑻ユ湭鎵ц浠ヤ笂 SQL锛岀浉鍏虫帴鍙d細鍑虹幇瀛楁涓嶅瓨鍦ㄥ紓甯搞��
+
diff --git a/src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java b/src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java
index c7c6258..3f90ea2 100644
--- a/src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/financial/FinVoucherDto.java
@@ -1,6 +1,7 @@
 package com.ruoyi.account.bean.dto.financial;
 
 import com.ruoyi.account.pojo.financial.FinVoucher;
+import com.ruoyi.basic.dto.StorageBlobDTO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -17,4 +18,6 @@
      * 鍑瘉鏄庣粏鍒嗗綍銆�
      */
     private List<FinVoucherEntryDto> entries;
+
+    private List<StorageBlobDTO> storageBlobDTOs;
 }
diff --git a/src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java b/src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java
index d01baf5..da4131f 100644
--- a/src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java
+++ b/src/main/java/com/ruoyi/account/bean/vo/financial/FinLedgerRowVo.java
@@ -1,5 +1,6 @@
 package com.ruoyi.account.bean.vo.financial;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -9,6 +10,7 @@
  * 绉戠洰璐﹁鏁版嵁杩斿洖瀵硅薄銆�
  */
 @Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
 public class FinLedgerRowVo {
 
     /**
diff --git a/src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java b/src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java
index d1eab48..4b23545 100644
--- a/src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java
+++ b/src/main/java/com/ruoyi/account/bean/vo/financial/FinVoucherDetailVo.java
@@ -2,6 +2,8 @@
 
 import com.ruoyi.account.pojo.financial.FinVoucher;
 import com.ruoyi.account.pojo.financial.FinVoucherEntry;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.dto.StorageBlobVO;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
@@ -18,4 +20,5 @@
      * 鍑瘉鍒嗗綍鍒楄〃銆�
      */
     private List<FinVoucherEntry> entries;
+    private List<StorageBlobVO> storageBlobVOList;
 }
diff --git a/src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java
index a489c67..a36b241 100644
--- a/src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/financial/FinLedgerServiceImpl.java
@@ -41,7 +41,7 @@
         if (startMonth.isAfter(endMonth)) {
             throw new ServiceException("寮�濮嬫湀浠戒笉鑳藉ぇ浜庣粨鏉熸湀浠�");
         }
-        return buildLedgerRows(queryDto.getSubjectCode(), startMonth, endMonth, null, null);
+        return Collections.singletonList(buildGeneralLedgerTotalRow(queryDto.getSubjectCode(), startMonth, endMonth));
     }
 
     @Override
@@ -117,6 +117,37 @@
         return rows;
     }
 
+    private FinLedgerRowVo buildGeneralLedgerTotalRow(String subjectCode, YearMonth startMonth, YearMonth endMonth) {
+        LocalDate startDate = startMonth.atDay(1);
+        LocalDate endDate = endMonth.atEndOfMonth();
+
+        List<FinLedgerEntryRecordVo> openingEntries = finVoucherEntryMapper.listPostedEntriesBefore(
+                subjectCode, startDate, null, null
+        );
+        BigDecimal openingBalance = calculateBalance(openingEntries);
+
+        List<FinLedgerEntryRecordVo> currentPeriodEntries = finVoucherEntryMapper.listPostedEntries(
+                subjectCode, startDate, endDate, null, null
+        );
+
+        BigDecimal totalDebit = ZERO;
+        BigDecimal totalCredit = ZERO;
+        for (FinLedgerEntryRecordVo entry : currentPeriodEntries) {
+            totalDebit = totalDebit.add(money(entry.getDebit()));
+            totalCredit = totalCredit.add(money(entry.getCredit()));
+        }
+
+        BigDecimal endingBalance = openingBalance.add(totalDebit).subtract(totalCredit);
+        FinLedgerRowVo totalRow = new FinLedgerRowVo();
+        totalRow.setRowType("yearly_total");
+        totalRow.setDate(endDate);
+        totalRow.setDebit(money(totalDebit));
+        totalRow.setCredit(money(totalCredit));
+        totalRow.setBalance(money(endingBalance));
+        totalRow.setDirection(resolveDirection(endingBalance));
+        return totalRow;
+    }
+
     private Map<YearMonth, List<FinLedgerEntryRecordVo>> groupEntriesByMonth(List<FinLedgerEntryRecordVo> entries) {
         Map<YearMonth, List<FinLedgerEntryRecordVo>> map = new LinkedHashMap<>();
         for (FinLedgerEntryRecordVo entry : entries) {
diff --git a/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
index 9e09020..b7548ef 100644
--- a/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/financial/FinVoucherServiceImpl.java
@@ -15,6 +15,9 @@
 import com.ruoyi.account.pojo.financial.FinVoucher;
 import com.ruoyi.account.pojo.financial.FinVoucherEntry;
 import com.ruoyi.account.service.financial.FinVoucherService;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import lombok.RequiredArgsConstructor;
@@ -38,6 +41,7 @@
 
     private final FinVoucherEntryMapper finVoucherEntryMapper;
     private final AccountSubjectMapper accountSubjectMapper;
+    private final FileUtil fileUtil;
 
     @Override
     public IPage<FinVoucher> pageList(Page<FinVoucher> page, FinVoucherPageDto queryDto) {
@@ -80,6 +84,8 @@
         }
         save(voucher);
         saveEntries(voucher.getId(), validEntries);
+        // 5. 淇濆瓨閿�鍞彴璐﹂檮浠�
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs());
         return true;
     }
 
@@ -113,6 +119,7 @@
         deleteWrapper.eq(FinVoucherEntry::getVoucherId, voucher.getId());
         finVoucherEntryMapper.delete(deleteWrapper);
         saveEntries(voucher.getId(), validEntries);
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_VOUCHER, voucher.getId(), dto.getStorageBlobDTOs());
         return true;
     }
 
@@ -159,6 +166,7 @@
         FinVoucherDetailVo vo = new FinVoucherDetailVo();
         BeanUtils.copyProperties(voucher, vo);
         vo.setEntries(entries);
+        vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_VOUCHER, id));
         return vo;
     }
 
diff --git a/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java b/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java
new file mode 100644
index 0000000..f0e8cf7
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java
@@ -0,0 +1,21 @@
+package com.ruoyi.ai.assistant;
+
+import dev.langchain4j.service.MemoryId;
+import dev.langchain4j.service.SystemMessage;
+import dev.langchain4j.service.UserMessage;
+import dev.langchain4j.service.spring.AiService;
+import reactor.core.publisher.Flux;
+
+import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
+
+@AiService(
+        wiringMode = EXPLICIT,
+        streamingChatModel = "qwenStreamingChatModel",
+        chatMemoryProvider = "chatMemoryProviderManufacturing",
+        tools = "manufacturingAgentTools"
+)
+public interface ManufacturingAgent {
+
+    @SystemMessage(fromResource = "manufacturing-agent-prompt.txt")
+    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
+}
diff --git a/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java
new file mode 100644
index 0000000..e9d9396
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/assistant/ManufacturingIntentExecutor.java
@@ -0,0 +1,136 @@
+package com.ruoyi.ai.assistant;
+
+import com.ruoyi.ai.tools.ManufacturingAgentTools;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+public class ManufacturingIntentExecutor {
+
+    private static final Pattern LIMIT_PATTERN = Pattern.compile("(鍓峾鏈�杩�)?(\\d{1,2})鏉�");
+    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
+
+    private final ManufacturingAgentTools manufacturingAgentTools;
+
+    public ManufacturingIntentExecutor(ManufacturingAgentTools manufacturingAgentTools) {
+        this.manufacturingAgentTools = manufacturingAgentTools;
+    }
+
+    public String tryExecute(String memoryId, String message) {
+        if (!StringUtils.hasText(message)) {
+            return null;
+        }
+        String text = message.trim();
+        String keyword = extractKeyword(text);
+        Integer limit = extractLimit(text);
+        String startDate = extractStartDate(text);
+        String endDate = extractEndDate(text);
+
+        if (containsAny(text, "棰勮", "鍛婅", "椋庨櫓", "鎻愰啋")) {
+            return manufacturingAgentTools.getWarningBoard(memoryId, startDate, endDate, text);
+        }
+        if (containsAny(text, "鍒嗘瀽", "缁熻", "瓒嬪娍", "鐪嬫澘", "鎶ヨ〃", "鎬昏")) {
+            return manufacturingAgentTools.analyzeFactory(memoryId, startDate, endDate, text);
+        }
+        if (containsAny(text, "鍔�", "澶勭悊", "娲惧伐", "瀹夋帓", "闂幆", "璺熻繘", "澶勭疆")) {
+            return manufacturingAgentTools.planActions(memoryId, text);
+        }
+
+        if (containsAny(text, "鐢熶骇鐜板満", "鐜板満", "杞﹂棿")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "site", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "璁″垝", "鎺掍骇", "mps")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "plan", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "宸ュ崟", "浣滀笟鍗�", "浠诲姟鍗�", "浠诲姟")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "workorder", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "璁惧", "缁翠慨", "淇濆吇", "鏁呴殰")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "device", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "璐ㄩ噺", "璐ㄦ", "涓嶅悎鏍�", "妫�楠�")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "quality", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "鐗╂枡", "搴撳瓨", "搴撲綅", "鍏ュ簱", "鍑哄簱")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "material", keyword, limit, startDate, endDate, text);
+        }
+        if (containsAny(text, "寮傚父", "渚嬪", "鍋忓樊")) {
+            return manufacturingAgentTools.queryDomain(memoryId, "exception", keyword, limit, startDate, endDate, text);
+        }
+        return null;
+    }
+
+    private boolean containsAny(String text, String... keywords) {
+        for (String keyword : keywords) {
+            if (text.toLowerCase().contains(keyword.toLowerCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Integer extractLimit(String text) {
+        Matcher matcher = LIMIT_PATTERN.matcher(text);
+        return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
+    }
+
+    private String extractStartDate(String text) {
+        Matcher matcher = DATE_PATTERN.matcher(text);
+        return matcher.find() ? matcher.group(1) : null;
+    }
+
+    private String extractEndDate(String text) {
+        Matcher matcher = DATE_PATTERN.matcher(text);
+        if (!matcher.find()) {
+            return null;
+        }
+        return matcher.find() ? matcher.group(1) : null;
+    }
+
+    private String extractKeyword(String text) {
+        String cleaned = text
+                .replace("鏌ヨ", "")
+                .replace("鏌ョ湅", "")
+                .replace("甯垜", "")
+                .replace("璇�", "")
+                .replace("涓�涓�", "")
+                .replace("鎵�鏈�", "")
+                .replace("鍏ㄩ儴", "")
+                .replace("浠婂勾", "")
+                .replace("鏈勾", "")
+                .replace("鍘诲勾", "")
+                .replace("鏈湀", "")
+                .replace("涓婃湀", "")
+                .replace("鏈懆", "")
+                .replace("涓婂懆", "")
+                .replace("浠婂ぉ", "")
+                .replace("鏄ㄥぉ", "")
+                .replace("杩�30澶�", "")
+                .replace("杩�7澶�", "")
+                .replace("杩�15澶�", "")
+                .replace("杩�60澶�", "")
+                .replace("鏈�杩�30澶�", "")
+                .replace("鏈�杩�7澶�", "")
+                .replace("鏈�杩�15澶�", "")
+                .replace("鏈�杩�60澶�", "")
+                .replace("鐢熶骇鐜板満", "")
+                .replace("鐜板満", "")
+                .replace("鐢熶骇宸ュ崟", "")
+                .replace("鐢熶骇", "")
+                .replace("璁″垝", "")
+                .replace("鎺掍骇", "")
+                .replace("宸ュ崟", "")
+                .replace("璁惧", "")
+                .replace("璐ㄩ噺", "")
+                .replace("鐗╂枡", "")
+                .replace("搴撳瓨", "")
+                .replace("寮傚父", "")
+                .replace("鍓�10鏉�", "")
+                .replace("鏈�杩�10鏉�", "")
+                .trim();
+        return cleaned.length() >= 2 ? cleaned : null;
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java b/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java
new file mode 100644
index 0000000..79aa222
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/config/ManufacturingAgentConfig.java
@@ -0,0 +1,20 @@
+package com.ruoyi.ai.config;
+
+import com.ruoyi.ai.store.MongoChatMemoryStore;
+import dev.langchain4j.memory.chat.ChatMemoryProvider;
+import dev.langchain4j.memory.chat.MessageWindowChatMemory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ManufacturingAgentConfig {
+
+    @Bean
+    ChatMemoryProvider chatMemoryProviderManufacturing(MongoChatMemoryStore mongoChatMemoryStore) {
+        return memoryId -> MessageWindowChatMemory.builder()
+                .id(memoryId)
+                .maxMessages(30)
+                .chatMemoryStore(mongoChatMemoryStore)
+                .build();
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java b/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
new file mode 100644
index 0000000..cb7c0ba
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
@@ -0,0 +1,102 @@
+package com.ruoyi.ai.controller;
+
+import com.ruoyi.ai.assistant.ManufacturingAgent;
+import com.ruoyi.ai.assistant.ManufacturingIntentExecutor;
+import com.ruoyi.ai.bean.ChatForm;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.ai.service.AiChatSessionService;
+import com.ruoyi.ai.store.MongoChatMemoryStore;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.UserMessage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Flux;
+
+import java.util.List;
+
+@Tag(name = "鍒堕�犳櫤鑳藉姪鎵�")
+@RestController
+@RequestMapping("/manufacturing-ai")
+public class ManufacturingAiController extends BaseController {
+
+    private final ManufacturingAgent manufacturingAgent;
+    private final ManufacturingIntentExecutor manufacturingIntentExecutor;
+    private final AiSessionUserContext aiSessionUserContext;
+    private final MongoChatMemoryStore mongoChatMemoryStore;
+    private final AiChatSessionService aiChatSessionService;
+
+    public ManufacturingAiController(ManufacturingAgent manufacturingAgent,
+                                     ManufacturingIntentExecutor manufacturingIntentExecutor,
+                                     AiSessionUserContext aiSessionUserContext,
+                                     MongoChatMemoryStore mongoChatMemoryStore,
+                                     AiChatSessionService aiChatSessionService) {
+        this.manufacturingAgent = manufacturingAgent;
+        this.manufacturingIntentExecutor = manufacturingIntentExecutor;
+        this.aiSessionUserContext = aiSessionUserContext;
+        this.mongoChatMemoryStore = mongoChatMemoryStore;
+        this.aiChatSessionService = aiChatSessionService;
+    }
+
+    @Operation(summary = "鍒堕�犲璇�")
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    public Flux<String> chat(@RequestBody ChatForm chatForm) {
+        if (!StringUtils.hasText(chatForm.getMemoryId())) {
+            return Flux.just("memoryId涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(chatForm.getMessage())) {
+            return Flux.just("message涓嶈兘涓虹┖");
+        }
+
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        String memoryId = chatForm.getMemoryId();
+        String userMessage = chatForm.getMessage();
+
+        aiSessionUserContext.bind(memoryId, loginUser);
+        aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
+
+        String directResponse = manufacturingIntentExecutor.tryExecute(memoryId, userMessage);
+        if (StringUtils.isNotEmpty(directResponse)) {
+            mongoChatMemoryStore.appendMessages(
+                    memoryId,
+                    List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
+            );
+            aiChatSessionService.refreshSessionStats(memoryId, loginUser);
+            return Flux.just(directResponse);
+        }
+
+        return manufacturingAgent.chat(memoryId, userMessage)
+                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
+                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
+    }
+
+    @Operation(summary = "鍒堕�犱細璇濆垪琛�")
+    @GetMapping("/history/sessions")
+    public AjaxResult listSessions() {
+        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
+    }
+
+    @Operation(summary = "鍒堕�犱細璇濇秷鎭�")
+    @GetMapping("/history/messages/{memoryId}")
+    public AjaxResult listMessages(@PathVariable String memoryId) {
+        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
+    }
+
+    @Operation(summary = "鍒犻櫎鍒堕�犱細璇�")
+    @DeleteMapping("/history/{memoryId}")
+    public AjaxResult deleteSession(@PathVariable String memoryId) {
+        aiSessionUserContext.remove(memoryId);
+        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java b/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java
new file mode 100644
index 0000000..1ff96c1
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/tools/ManufacturingAgentTools.java
@@ -0,0 +1,1035 @@
+package com.ruoyi.ai.tools;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.device.mapper.DeviceDefectRecordMapper;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.mapper.DeviceRepairMapper;
+import com.ruoyi.device.pojo.DeviceDefectRecord;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.pojo.DeviceRepair;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper;
+import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord;
+import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
+import com.ruoyi.production.mapper.ProductionOrderMapper;
+import com.ruoyi.production.mapper.ProductionPlanMapper;
+import com.ruoyi.production.mapper.ProductionProductMainMapper;
+import com.ruoyi.production.pojo.ProductionOperationTask;
+import com.ruoyi.production.pojo.ProductionOrder;
+import com.ruoyi.production.pojo.ProductionPlan;
+import com.ruoyi.production.pojo.ProductionProductMain;
+import com.ruoyi.quality.mapper.QualityInspectMapper;
+import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
+import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.quality.pojo.QualityUnqualified;
+import com.ruoyi.stock.mapper.StockInventoryMapper;
+import com.ruoyi.stock.pojo.StockInventory;
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+import dev.langchain4j.agent.tool.ToolMemoryId;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Component
+public class ManufacturingAgentTools {
+
+    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final int DEFAULT_LIMIT = 10;
+    private static final int MAX_LIMIT = 30;
+    private static final int DEVICE_REPAIR_STATUS_PENDING = 0;
+
+    private final ProductionPlanMapper productionPlanMapper;
+    private final ProductionOrderMapper productionOrderMapper;
+    private final ProductionOperationTaskMapper productionOperationTaskMapper;
+    private final ProductionProductMainMapper productionProductMainMapper;
+    private final DeviceLedgerMapper deviceLedgerMapper;
+    private final DeviceRepairMapper deviceRepairMapper;
+    private final DeviceDefectRecordMapper deviceDefectRecordMapper;
+    private final QualityInspectMapper qualityInspectMapper;
+    private final QualityUnqualifiedMapper qualityUnqualifiedMapper;
+    private final StockInventoryMapper stockInventoryMapper;
+    private final ProcurementExceptionRecordMapper procurementExceptionRecordMapper;
+    private final AiSessionUserContext aiSessionUserContext;
+
+    public ManufacturingAgentTools(ProductionPlanMapper productionPlanMapper,
+                                   ProductionOrderMapper productionOrderMapper,
+                                   ProductionOperationTaskMapper productionOperationTaskMapper,
+                                   ProductionProductMainMapper productionProductMainMapper,
+                                   DeviceLedgerMapper deviceLedgerMapper,
+                                   DeviceRepairMapper deviceRepairMapper,
+                                   DeviceDefectRecordMapper deviceDefectRecordMapper,
+                                   QualityInspectMapper qualityInspectMapper,
+                                   QualityUnqualifiedMapper qualityUnqualifiedMapper,
+                                   StockInventoryMapper stockInventoryMapper,
+                                   ProcurementExceptionRecordMapper procurementExceptionRecordMapper,
+                                   AiSessionUserContext aiSessionUserContext) {
+        this.productionPlanMapper = productionPlanMapper;
+        this.productionOrderMapper = productionOrderMapper;
+        this.productionOperationTaskMapper = productionOperationTaskMapper;
+        this.productionProductMainMapper = productionProductMainMapper;
+        this.deviceLedgerMapper = deviceLedgerMapper;
+        this.deviceRepairMapper = deviceRepairMapper;
+        this.deviceDefectRecordMapper = deviceDefectRecordMapper;
+        this.qualityInspectMapper = qualityInspectMapper;
+        this.qualityUnqualifiedMapper = qualityUnqualifiedMapper;
+        this.stockInventoryMapper = stockInventoryMapper;
+        this.procurementExceptionRecordMapper = procurementExceptionRecordMapper;
+        this.aiSessionUserContext = aiSessionUserContext;
+    }
+
+    @Tool(name = "鏌ヨ鍒堕�犱笟鍔″煙鏁版嵁", value = "鎸変笟鍔″煙鏌ヨ鐢熶骇鐜板満銆佽鍒掋�佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗙浉鍏虫暟鎹��")
+    public String queryDomain(@ToolMemoryId String memoryId,
+                              @P(value = "涓氬姟鍩燂紝site/plan/workorder/device/quality/material/exception") String domain,
+                              @P(value = "鍏抽敭瀛楋紝鍙笉浼�", required = false) String keyword,
+                              @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�30", required = false) Integer limit,
+                              @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                              @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                              @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡備粖骞淬�佹湰鏈堛�佽繎30澶�", required = false) String timeRange) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        int finalLimit = normalizeLimit(limit);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange);
+        boolean hasTimeConstraint = hasTimeConstraint(startDate, endDate, timeRange);
+        String normalizedDomain = normalizeDomain(domain);
+
+        return switch (normalizedDomain) {
+            case "site" -> siteSnapshot(loginUser, range);
+            case "plan" -> listProductionPlans(loginUser, keyword, finalLimit, range);
+            case "workorder" -> listWorkOrders(loginUser, keyword, finalLimit, range);
+            case "device" -> isRepairIntent(keyword, timeRange)
+                    ? listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint)
+                    : listDevices(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit);
+            case "repair" -> listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint);
+            case "quality" -> listQualityIssues(loginUser, keyword, finalLimit, range);
+            case "material" -> listMaterialInventory(loginUser, keyword, finalLimit);
+            case "exception" -> listExceptions(loginUser, keyword, finalLimit, range);
+            default -> jsonResponse(false, "manufacturing_query", "涓嶆敮鎸佺殑涓氬姟鍩�: " + safe(domain), Map.of(), Map.of(), Map.of());
+        };
+    }
+
+    @Tool(name = "鍒堕�犻璀︾湅鏉�", value = "璁$畻璁″垝銆佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗙殑棰勮淇℃伅銆�")
+    public String getWarningBoard(@ToolMemoryId String memoryId,
+                                  @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                                  @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                                  @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡備粖澶┿�佹湰鍛ㄣ�佹湰鏈堛�佽繎30澶�", required = false) String timeRange) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange);
+        LocalDate today = LocalDate.now();
+
+        long overduePlanCount = countOverduePlans(loginUser, today);
+        long overdueWorkOrderCount = countOverdueWorkOrders(loginUser, today);
+        long pendingRepairCount = countPendingRepairs(loginUser);
+        long qualityOpenCount = countOpenQualityIssues(loginUser, range);
+        long lowStockCount = countLowStock(loginUser);
+        long exceptionCount = countExceptionRecords(loginUser, range);
+
+        List<Map<String, Object>> warningItems = new ArrayList<>();
+        if (overduePlanCount > 0) {
+            warningItems.add(warningItem("high", "璁″垝閫炬湡", overduePlanCount, "鏈夌敓浜ц鍒掕秴杩囬渶姹傛棩鏈熶粛鏈畬鎴�"));
+        }
+        if (overdueWorkOrderCount > 0) {
+            warningItems.add(warningItem("high", "宸ュ崟閫炬湡", overdueWorkOrderCount, "鏈夊伐鍗曡鍒掔粨鏉熸棩鏈熷凡杩囦粛鏈畬宸�"));
+        }
+        if (pendingRepairCount > 0) {
+            warningItems.add(warningItem("medium", "璁惧寰呯淮淇�", pendingRepairCount, "瀛樺湪寰呯淮淇�/缁翠慨涓殑璁惧"));
+        }
+        if (qualityOpenCount > 0) {
+            warningItems.add(warningItem("high", "璐ㄩ噺鏈棴鐜�", qualityOpenCount, "瀛樺湪鏈鐞嗗畬鎴愮殑涓嶅悎鏍艰褰�"));
+        }
+        if (lowStockCount > 0) {
+            warningItems.add(warningItem("medium", "鐗╂枡浣庡簱瀛�", lowStockCount, "搴撳瓨鏁伴噺浣庝簬鎴栫瓑浜庨璀﹂槇鍊�"));
+        }
+        if (exceptionCount > 0) {
+            warningItems.add(warningItem("medium", "寮傚父璁板綍", exceptionCount, "鏃堕棿鑼冨洿鍐呭瓨鍦ㄥ紓甯稿鐞嗚褰�"));
+        }
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", range.start().toString());
+        summary.put("endDate", range.end().toString());
+        summary.put("warningCount", warningItems.size());
+        summary.put("overduePlanCount", overduePlanCount);
+        summary.put("overdueWorkOrderCount", overdueWorkOrderCount);
+        summary.put("pendingRepairCount", pendingRepairCount);
+        summary.put("qualityOpenCount", qualityOpenCount);
+        summary.put("lowStockCount", lowStockCount);
+        summary.put("exceptionCount", exceptionCount);
+
+        return jsonResponse(true, "manufacturing_warning", "宸茶繑鍥炲埗閫犻璀︾湅鏉裤��", summary,
+                Map.of("items", warningItems), Map.of());
+    }
+
+    @Tool(name = "鍒堕�犵粡钀ュ垎鏋�", value = "鎸夋椂闂磋寖鍥磋緭鍑哄埗閫犲叧閿寚鏍囷紝鏀寔鏌ャ�侀棶銆佸垎鏋愬満鏅��")
+    public String analyzeFactory(@ToolMemoryId String memoryId,
+                                 @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                                 @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                                 @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡傛湰鏈堛�佽繎30澶�", required = false) String timeRange) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange);
+
+        long planTotal = countPlans(loginUser, range);
+        long planCompleted = countPlansByStatus(loginUser, range, 2);
+        long workOrderTotal = countWorkOrders(loginUser, range);
+        long workOrderCompleted = countWorkOrdersByStatus(loginUser, range, 2);
+        long workOrderInProgress = countWorkOrdersByStatus(loginUser, range, 1);
+
+        long outputCount = countOutputs(loginUser, range);
+        long deviceTotal = countDevices(loginUser);
+        long pendingRepairCount = countPendingRepairs(loginUser);
+        long qualityInspectTotal = countQualityInspect(loginUser, range);
+        long qualityNgCount = countOpenQualityIssues(loginUser, range);
+        long materialSkuCount = countInventorySku(loginUser);
+        long lowStockCount = countLowStock(loginUser);
+        long exceptionCount = countExceptionRecords(loginUser, range);
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", range.start().toString());
+        summary.put("endDate", range.end().toString());
+        summary.put("planTotal", planTotal);
+        summary.put("planCompleted", planCompleted);
+        summary.put("planCompletionRate", toRate(planCompleted, planTotal));
+        summary.put("workOrderTotal", workOrderTotal);
+        summary.put("workOrderCompleted", workOrderCompleted);
+        summary.put("workOrderInProgress", workOrderInProgress);
+        summary.put("workOrderCompletionRate", toRate(workOrderCompleted, workOrderTotal));
+        summary.put("outputCount", outputCount);
+        summary.put("deviceTotal", deviceTotal);
+        summary.put("pendingRepairCount", pendingRepairCount);
+        summary.put("qualityInspectTotal", qualityInspectTotal);
+        summary.put("qualityNgCount", qualityNgCount);
+        summary.put("qualityIssueRate", toRate(qualityNgCount, qualityInspectTotal));
+        summary.put("materialSkuCount", materialSkuCount);
+        summary.put("lowStockCount", lowStockCount);
+        summary.put("exceptionCount", exceptionCount);
+
+        List<Map<String, Object>> coreMetrics = List.of(
+                metric("璁″垝瀹屾垚鐜�", toRate(planCompleted, planTotal)),
+                metric("宸ュ崟瀹屾垚鐜�", toRate(workOrderCompleted, workOrderTotal)),
+                metric("璐ㄩ噺寮傚父鐜�", toRate(qualityNgCount, qualityInspectTotal)),
+                metric("浣庡簱瀛樺崰姣�", toRate(lowStockCount, materialSkuCount))
+        );
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("domainBarOption", buildDomainBarOption(summary));
+        charts.put("qualityPieOption", buildQualityPieOption(qualityInspectTotal, qualityNgCount));
+
+        return jsonResponse(true, "manufacturing_analysis", "宸茶繑鍥炲埗閫犲垎鏋愮粨鏋溿��", summary,
+                Map.of("coreMetrics", coreMetrics), charts);
+    }
+
+    @Tool(name = "鐢熸垚鍒堕�犲姙鐞嗗缓璁�", value = "鏍规嵁鐢ㄦ埛闂杈撳嚭鍙墽琛岀殑鍔炵悊鍔ㄤ綔寤鸿锛屽寘鎷洰鏍囦笟鍔℃帴鍙c�佸繀濉瓧娈靛拰绀轰緥銆�")
+    public String planActions(@ToolMemoryId String memoryId,
+                              @P("鐢ㄦ埛璇夋眰鍘熸枃") String userQuery) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        List<Map<String, Object>> actionCards = new ArrayList<>();
+
+        if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "宸ュ崟", "娲惧伐", "浣滀笟")) {
+            actionCards.add(actionCard(
+                    "workorder_assign",
+                    "宸ュ崟娲惧伐",
+                    "POST",
+                    "/productionOperationTask/assign",
+                    List.of("id", "userIds"),
+                    Map.of("id", 10001, "userIds", "12,13"),
+                    "灏嗗伐鍗曞垎閰嶇粰鎸囧畾浜哄憳锛岄�傜敤浜庣幇鍦鸿皟搴︺��"));
+        }
+        if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "璁惧", "缁翠慨", "鏁呴殰")) {
+            actionCards.add(actionCard(
+                    "device_repair_create",
+                    "鍒涘缓璁惧缁翠慨鍗�",
+                    "POST",
+                    "/device/repair",
+                    List.of("deviceLedgerId", "deviceName", "repairName", "remark"),
+                    Map.of("deviceLedgerId", 1001, "deviceName", "绌哄帇鏈篈-01", "repairName", "寮犱笁", "remark", "寮傚搷骞朵即闅忔俯鍗�"),
+                    "鏂板缓缁翠慨鍗曪紝杩涘叆璁惧寮傚父澶勭悊闂幆銆�"));
+        }
+        if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "璐ㄩ噺", "涓嶅悎鏍�", "闂幆")) {
+            actionCards.add(actionCard(
+                    "quality_unqualified_deal",
+                    "澶勭悊涓嶅悎鏍煎崟",
+                    "POST",
+                    "/quality/qualityUnqualified/deal",
+                    List.of("id", "dealResult", "dealName"),
+                    Map.of("id", 3001, "dealResult", "杩斿伐鍚庡妫�", "dealName", "鏉庡洓"),
+                    "瀵逛笉鍚堟牸璁板綍鎵ц澶勭疆骞堕棴鐜��"));
+        }
+        if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "鐗╂枡", "搴撳瓨", "琛ユ枡")) {
+            actionCards.add(actionCard(
+                    "material_inbound",
+                    "琛ュ厖搴撳瓨",
+                    "POST",
+                    "/stockInventory/addstockInventory",
+                    List.of("productModelId", "batchNo", "qualitity"),
+                    Map.of("productModelId", 5001, "batchNo", "B2026051601", "qualitity", 120),
+                    "褰撲綆搴撳瓨棰勮瑙﹀彂鏃讹紝澧炲姞搴撳瓨鏁伴噺銆�"));
+        }
+        if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "寮傚父", "閲囪喘寮傚父", "鏉ユ枡寮傚父")) {
+            actionCards.add(actionCard(
+                    "procurement_exception_add",
+                    "鐧昏寮傚父璁板綍",
+                    "POST",
+                    "/procurementExceptionRecord/add",
+                    List.of("purchaseLedgerId", "exceptionReason", "exceptionNum"),
+                    Map.of("purchaseLedgerId", 888, "exceptionReason", "鍒版枡鐭己", "exceptionNum", 24),
+                    "鐧昏閲囪喘/鏉ユ枡寮傚父锛屼究浜庡悗缁拷韪拰鍒嗘瀽銆�"));
+        }
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("actionCount", actionCards.size());
+        summary.put("userId", loginUser.getUserId());
+        summary.put("tenantId", loginUser.getTenantId());
+
+        return jsonResponse(true, "manufacturing_action_plan", "宸茬敓鎴愬姙鐞嗗缓璁紝璇峰墠绔紩瀵肩敤鎴风‘璁ゅ悗璋冪敤鐩爣涓氬姟鎺ュ彛銆�",
+                summary, Map.of("actionCards", actionCards), Map.of());
+    }
+
+    private String siteSnapshot(LoginUser loginUser, DateRange range) {
+        long planTotal = countPlans(loginUser, range);
+        long workOrderTotal = countWorkOrders(loginUser, range);
+        long outputCount = countOutputs(loginUser, range);
+        long deviceTotal = countDevices(loginUser);
+        long pendingRepairCount = countPendingRepairs(loginUser);
+        long qualityOpenCount = countOpenQualityIssues(loginUser, range);
+        long lowStockCount = countLowStock(loginUser);
+        long exceptionCount = countExceptionRecords(loginUser, range);
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", range.start().toString());
+        summary.put("endDate", range.end().toString());
+        summary.put("planTotal", planTotal);
+        summary.put("workOrderTotal", workOrderTotal);
+        summary.put("outputCount", outputCount);
+        summary.put("deviceTotal", deviceTotal);
+        summary.put("pendingRepairCount", pendingRepairCount);
+        summary.put("qualityOpenCount", qualityOpenCount);
+        summary.put("lowStockCount", lowStockCount);
+        summary.put("exceptionCount", exceptionCount);
+
+        return jsonResponse(true, "manufacturing_site_snapshot", "宸茶繑鍥炵敓浜х幇鍦烘瑙堛��", summary, Map.of(), Map.of());
+    }
+
+    private String listProductionPlans(LoginUser loginUser, String keyword, int limit, DateRange range) {
+        LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
+        wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(ProductionPlan::getMpsNo, keyword)
+                    .or().like(ProductionPlan::getRemark, keyword)
+                    .or().like(ProductionPlan::getSource, keyword));
+        }
+        wrapper.orderByDesc(ProductionPlan::getRequiredDate, ProductionPlan::getId).last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(productionPlanMapper.selectList(wrapper)).stream()
+                .map(this::toPlanItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_plan_list", "宸茶繑鍥炵敓浜ц鍒掑垪琛ㄣ��",
+                rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
+    }
+
+    private String listWorkOrders(LoginUser loginUser, String keyword, int limit, DateRange range) {
+        LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
+        wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
+                .le(ProductionOperationTask::getPlanEndTime, range.end());
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(ProductionOperationTask::getWorkOrderNo, keyword)
+                    .or().like(ProductionOperationTask::getUserIds, keyword));
+        }
+        wrapper.orderByDesc(ProductionOperationTask::getPlanEndTime, ProductionOperationTask::getId)
+                .last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(productionOperationTaskMapper.selectList(wrapper)).stream()
+                .map(this::toWorkOrderItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_workorder_list", "宸茶繑鍥炲伐鍗曞垪琛ㄣ��",
+                rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
+    }
+
+    private String listDevices(LoginUser loginUser, String keyword, int limit) {
+        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
+                    .or().like(DeviceLedger::getDeviceModel, keyword)
+                    .or().like(DeviceLedger::getDeviceBrand, keyword));
+        }
+        wrapper.orderByDesc(DeviceLedger::getId).last("limit " + limit);
+
+        Map<Long, Long> pendingRepairMap = pendingRepairCountByDevice(loginUser);
+        List<Map<String, Object>> items = defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
+                .map(item -> toDeviceItem(item, pendingRepairMap.getOrDefault(item.getId(), 0L)))
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_device_list", "宸茶繑鍥炶澶囧垪琛ㄣ��",
+                Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
+    }
+
+    private String listDeviceRepairs(LoginUser loginUser, String keyword, int limit, DateRange range, boolean hasTimeConstraint) {
+        LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
+        Long currentDeptId = loginUser.getCurrentDeptId();
+        if (currentDeptId != null) {
+            wrapper.and(w -> w.eq(DeviceRepair::getDeptId, currentDeptId).or().isNull(DeviceRepair::getDeptId));
+        }
+        if (hasTimeConstraint) {
+            wrapper.ge(DeviceRepair::getCreateTime, range.start().atStartOfDay())
+                    .lt(DeviceRepair::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
+        if (StringUtils.hasText(keyword)) {
+            List<Long> matchedDeviceIds = findDeviceLedgerIdsByKeyword(loginUser, keyword);
+            wrapper.and(w -> {
+                w.like(DeviceRepair::getDeviceName, keyword)
+                        .or().like(DeviceRepair::getDeviceModel, keyword)
+                        .or().like(DeviceRepair::getRemark, keyword)
+                        .or().like(DeviceRepair::getRepairName, keyword)
+                        .or().like(DeviceRepair::getMaintenanceName, keyword);
+                if (!matchedDeviceIds.isEmpty()) {
+                    w.or().in(DeviceRepair::getDeviceLedgerId, matchedDeviceIds);
+                }
+            });
+        }
+        wrapper.orderByDesc(DeviceRepair::getCreateTime, DeviceRepair::getId).last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(deviceRepairMapper.selectList(wrapper)).stream()
+                .map(this::toDeviceRepairItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_device_repair_list", "宸茶繑鍥炶澶囩淮淇褰曘��",
+                rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
+    }
+
+    private String listQualityIssues(LoginUser loginUser, String keyword, int limit, DateRange range) {
+        LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
+        wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
+                .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()));
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(QualityUnqualified::getProductName, keyword)
+                    .or().like(QualityUnqualified::getDefectivePhenomena, keyword)
+                    .or().like(QualityUnqualified::getDealResult, keyword));
+        }
+        wrapper.orderByDesc(QualityUnqualified::getCheckTime, QualityUnqualified::getId).last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(qualityUnqualifiedMapper.selectList(wrapper)).stream()
+                .map(this::toQualityItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_quality_list", "宸茶繑鍥炶川閲忓紓甯稿垪琛ㄣ��",
+                rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
+    }
+
+    private String listMaterialInventory(LoginUser loginUser, String keyword, int limit) {
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(StockInventory::getBatchNo, keyword)
+                    .or().like(StockInventory::getProductModelId, keyword));
+        }
+        wrapper.orderByDesc(StockInventory::getId).last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(stockInventoryMapper.selectList(wrapper)).stream()
+                .map(this::toMaterialItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_material_list", "宸茶繑鍥炵墿鏂欏簱瀛樺垪琛ㄣ��",
+                Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
+    }
+
+    private String listExceptions(LoginUser loginUser, String keyword, int limit, DateRange range) {
+        LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
+        wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
+                .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        if (StringUtils.hasText(keyword)) {
+            wrapper.like(ProcurementExceptionRecord::getExceptionReason, keyword);
+        }
+        wrapper.orderByDesc(ProcurementExceptionRecord::getCreateTime, ProcurementExceptionRecord::getId)
+                .last("limit " + limit);
+
+        List<Map<String, Object>> items = defaultList(procurementExceptionRecordMapper.selectList(wrapper)).stream()
+                .map(this::toExceptionItem)
+                .collect(Collectors.toList());
+        return jsonResponse(true, "manufacturing_exception_list", "宸茶繑鍥炲紓甯稿鐞嗗垪琛ㄣ��",
+                rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
+    }
+
+    private long countPlans(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
+        wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
+        return productionPlanMapper.selectCount(wrapper);
+    }
+
+    private long countPlansByStatus(LoginUser loginUser, DateRange range, int status) {
+        LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
+        wrapper.ge(ProductionPlan::getRequiredDate, range.start())
+                .le(ProductionPlan::getRequiredDate, range.end())
+                .eq(ProductionPlan::getStatus, status);
+        return productionPlanMapper.selectCount(wrapper);
+    }
+
+    private long countWorkOrders(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
+        wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
+                .le(ProductionOperationTask::getPlanEndTime, range.end());
+        return productionOperationTaskMapper.selectCount(wrapper);
+    }
+
+    private long countWorkOrdersByStatus(LoginUser loginUser, DateRange range, int status) {
+        LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
+        wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
+                .le(ProductionOperationTask::getPlanEndTime, range.end())
+                .eq(ProductionOperationTask::getStatus, status);
+        return productionOperationTaskMapper.selectCount(wrapper);
+    }
+
+    private long countOutputs(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<ProductionProductMain> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
+        wrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay())
+                .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        return productionProductMainMapper.selectCount(wrapper);
+    }
+
+    private long countDevices(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
+        return deviceLedgerMapper.selectCount(wrapper);
+    }
+
+    private long countPendingRepairs(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
+        wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
+        return deviceRepairMapper.selectCount(wrapper);
+    }
+
+    private long countQualityInspect(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<QualityInspect> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), QualityInspect::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityInspect::getDeptId);
+        wrapper.ge(QualityInspect::getCheckTime, toDate(range.start()))
+                .lt(QualityInspect::getCheckTime, toExclusiveEndDate(range.end()));
+        return qualityInspectMapper.selectCount(wrapper);
+    }
+
+    private long countOpenQualityIssues(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
+        wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
+                .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()))
+                .ne(QualityUnqualified::getInspectState, 2);
+        return qualityUnqualifiedMapper.selectCount(wrapper);
+    }
+
+    private long countInventorySku(LoginUser loginUser) {
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
+        return stockInventoryMapper.selectCount(wrapper);
+    }
+
+    private long countLowStock(LoginUser loginUser) {
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
+        wrapper.isNotNull(StockInventory::getWarnNum);
+        List<StockInventory> stocks = defaultList(stockInventoryMapper.selectList(wrapper));
+        return stocks.stream()
+                .filter(this::isLowStock)
+                .count();
+    }
+
+    private long countExceptionRecords(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
+        wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
+                .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        return procurementExceptionRecordMapper.selectCount(wrapper);
+    }
+
+    private long countOverduePlans(LoginUser loginUser, LocalDate today) {
+        LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
+        wrapper.lt(ProductionPlan::getRequiredDate, today).ne(ProductionPlan::getStatus, 2);
+        return productionPlanMapper.selectCount(wrapper);
+    }
+
+    private long countOverdueWorkOrders(LoginUser loginUser, LocalDate today) {
+        LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
+        wrapper.lt(ProductionOperationTask::getPlanEndTime, today).ne(ProductionOperationTask::getStatus, 2);
+        return productionOperationTaskMapper.selectCount(wrapper);
+    }
+
+    private Map<Long, Long> pendingRepairCountByDevice(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
+        wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
+        return defaultList(deviceRepairMapper.selectList(wrapper)).stream()
+                .filter(item -> item.getDeviceLedgerId() != null)
+                .collect(Collectors.groupingBy(DeviceRepair::getDeviceLedgerId, Collectors.counting()));
+    }
+
+    private Map<String, Object> toPlanItem(ProductionPlan item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("mpsNo", safe(item.getMpsNo()));
+        map.put("requiredDate", formatDate(item.getRequiredDate()));
+        map.put("promisedDeliveryDate", formatDate(item.getPromisedDeliveryDate()));
+        map.put("qtyRequired", item.getQtyRequired());
+        map.put("quantityIssued", item.getQuantityIssued());
+        map.put("status", item.getStatus());
+        map.put("source", safe(item.getSource()));
+        map.put("remark", safe(item.getRemark()));
+        return map;
+    }
+
+    private Map<String, Object> toWorkOrderItem(ProductionOperationTask item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("workOrderNo", safe(item.getWorkOrderNo()));
+        map.put("productionOrderId", item.getProductionOrderId());
+        map.put("planStartTime", formatDate(item.getPlanStartTime()));
+        map.put("planEndTime", formatDate(item.getPlanEndTime()));
+        map.put("actualStartTime", formatDate(item.getActualStartTime()));
+        map.put("actualEndTime", formatDate(item.getActualEndTime()));
+        map.put("planQuantity", item.getPlanQuantity());
+        map.put("completeQuantity", item.getCompleteQuantity());
+        map.put("status", item.getStatus());
+        map.put("userIds", safe(item.getUserIds()));
+        return map;
+    }
+
+    private Map<String, Object> toDeviceItem(DeviceLedger item, long pendingRepairCount) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("deviceName", safe(item.getDeviceName()));
+        map.put("deviceModel", safe(item.getDeviceModel()));
+        map.put("deviceBrand", safe(item.getDeviceBrand()));
+        map.put("status", safe(item.getStatus()));
+        map.put("storageLocation", safe(item.getStorageLocation()));
+        map.put("supplierName", safe(item.getSupplierName()));
+        map.put("pendingRepairCount", pendingRepairCount);
+        return map;
+    }
+
+    private Map<String, Object> toDeviceRepairItem(DeviceRepair item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("deviceLedgerId", item.getDeviceLedgerId());
+        map.put("deviceName", safe(item.getDeviceName()));
+        map.put("deviceModel", safe(item.getDeviceModel()));
+        map.put("repairTime", formatDate(item.getRepairTime()));
+        map.put("repairName", safe(item.getRepairName()));
+        map.put("maintenanceName", safe(item.getMaintenanceName()));
+        map.put("maintenanceTime", formatDateTime(item.getMaintenanceTime()));
+        map.put("maintenanceResult", safe(item.getMaintenanceResult()));
+        map.put("acceptanceName", safe(item.getAcceptanceName()));
+        map.put("acceptanceTime", formatDateTime(item.getAcceptanceTime()));
+        map.put("status", item.getStatus());
+        map.put("remark", safe(item.getRemark()));
+        map.put("createTime", formatDateTime(item.getCreateTime()));
+        return map;
+    }
+
+    private Map<String, Object> toQualityItem(QualityUnqualified item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("checkTime", formatDate(item.getCheckTime()));
+        map.put("inspectState", item.getInspectState());
+        map.put("productId", item.getProductId());
+        map.put("productName", safe(item.getProductName()));
+        map.put("model", safe(item.getModel()));
+        map.put("quantity", item.getQuantity());
+        map.put("defectivePhenomena", safe(item.getDefectivePhenomena()));
+        map.put("dealResult", safe(item.getDealResult()));
+        map.put("dealName", safe(item.getDealName()));
+        map.put("dealTime", formatDate(item.getDealTime()));
+        return map;
+    }
+
+    private Map<String, Object> toMaterialItem(StockInventory item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("productModelId", item.getProductModelId());
+        map.put("batchNo", safe(item.getBatchNo()));
+        map.put("qualitity", item.getQualitity());
+        map.put("lockedQuantity", item.getLockedQuantity());
+        map.put("warnNum", item.getWarnNum());
+        map.put("lowStock", isLowStock(item));
+        map.put("remark", safe(item.getRemark()));
+        return map;
+    }
+
+    private Map<String, Object> toExceptionItem(ProcurementExceptionRecord item) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("id", item.getId());
+        map.put("purchaseLedgerId", item.getPurchaseLedgerId());
+        map.put("exceptionReason", safe(item.getExceptionReason()));
+        map.put("exceptionNum", item.getExceptionNum());
+        map.put("createTime", formatDateTime(item.getCreateTime()));
+        return map;
+    }
+
+    private boolean isLowStock(StockInventory item) {
+        BigDecimal quantity = item.getQualitity();
+        BigDecimal warnNum = item.getWarnNum();
+        if (quantity == null || warnNum == null) {
+            return false;
+        }
+        return quantity.compareTo(warnNum) <= 0;
+    }
+
+    private Map<String, Object> warningItem(String level, String title, long count, String detail) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("level", level);
+        map.put("title", title);
+        map.put("count", count);
+        map.put("detail", detail);
+        return map;
+    }
+
+    private Map<String, Object> actionCard(String code,
+                                           String name,
+                                           String method,
+                                           String targetApi,
+                                           List<String> requiredFields,
+                                           Map<String, Object> examplePayload,
+                                           String description) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("code", code);
+        map.put("name", name);
+        map.put("method", method);
+        map.put("targetApi", targetApi);
+        map.put("requiredFields", requiredFields);
+        map.put("examplePayload", examplePayload);
+        map.put("description", description);
+        return map;
+    }
+
+    private Map<String, Object> metric(String label, String value) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("label", label);
+        map.put("value", value);
+        return map;
+    }
+
+    private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", range.start().toString());
+        summary.put("endDate", range.end().toString());
+        summary.put("count", count);
+        summary.put("keyword", safe(keyword));
+        return summary;
+    }
+
+    private Map<String, Object> buildDomainBarOption(Map<String, Object> summary) {
+        List<String> xData = List.of("璁″垝", "宸ュ崟", "璁惧", "璐ㄩ噺", "鐗╂枡", "寮傚父");
+        List<Number> yData = List.of(
+                numberValue(summary.get("planTotal")),
+                numberValue(summary.get("workOrderTotal")),
+                numberValue(summary.get("deviceTotal")),
+                numberValue(summary.get("qualityNgCount")),
+                numberValue(summary.get("lowStockCount")),
+                numberValue(summary.get("exceptionCount"))
+        );
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "鍒堕�犲煙鍏抽敭鏁伴噺", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", xData));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "鏁伴噺", "type", "bar", "data", yData)));
+        return option;
+    }
+
+    private Map<String, Object> buildQualityPieOption(long inspectTotal, long ngCount) {
+        long passCount = Math.max(inspectTotal - ngCount, 0);
+        List<Map<String, Object>> data = List.of(
+                Map.of("name", "涓嶅悎鏍�", "value", ngCount),
+                Map.of("name", "闈炰笉鍚堟牸", "value", passCount)
+        );
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "璐ㄩ噺缁撴灉鍒嗗竷", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "item"));
+        option.put("series", List.of(Map.of("name", "璐ㄩ噺", "type", "pie", "radius", "60%", "data", data)));
+        return option;
+    }
+
+    private int numberValue(Object value) {
+        if (value instanceof Number number) {
+            return number.intValue();
+        }
+        return 0;
+    }
+
+    private String toRate(long numerator, long denominator) {
+        if (denominator <= 0) {
+            return "0.00%";
+        }
+        BigDecimal rate = new BigDecimal(numerator)
+                .multiply(new BigDecimal("100"))
+                .divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP);
+        return rate.toPlainString() + "%";
+    }
+
+    private String normalizeDomain(String domain) {
+        if (!StringUtils.hasText(domain)) {
+            return "";
+        }
+        String value = domain.trim().toLowerCase();
+        return switch (value) {
+            case "鐢熶骇鐜板満", "site", "factory", "workshop" -> "site";
+            case "璁″垝", "plan", "schedule" -> "plan";
+            case "宸ュ崟", "workorder", "work_order", "task" -> "workorder";
+            case "璁惧", "device", "equipment" -> "device";
+            case "缁翠慨", "repair", "maintenance" -> "repair";
+            case "璐ㄩ噺", "quality", "qc" -> "quality";
+            case "鐗╂枡", "material", "inventory", "stock" -> "material";
+            case "寮傚父", "exception", "abnormal" -> "exception";
+            default -> value;
+        };
+    }
+
+    private boolean isRepairIntent(String keyword, String userQuery) {
+        String query = safe(userQuery);
+        return containsAny(safe(keyword), "缁翠慨", "鎶ヤ慨", "妫�淇�", "缁存姢")
+                || containsAny(query, "缁翠慨", "鎶ヤ慨", "妫�淇�", "缁存姢");
+    }
+
+    private String normalizeDeviceQueryKeyword(String keyword, String userQuery) {
+        String source = StringUtils.hasText(keyword) ? keyword : userQuery;
+        if (!StringUtils.hasText(source)) {
+            return null;
+        }
+        String cleaned = source
+                .replace("鏌ヨ", "")
+                .replace("鏌ョ湅", "")
+                .replace("甯垜", "")
+                .replace("璇�", "")
+                .replace("鏌�", "")
+                .replace("璁惧", "")
+                .replace("缁翠慨璁板綍", "")
+                .replace("缁翠慨鎯呭喌", "")
+                .replace("鎶ヤ慨璁板綍", "")
+                .replace("鎶ヤ慨鎯呭喌", "")
+                .replace("缁翠慨", "")
+                .replace("鎶ヤ慨", "")
+                .replace("鎯呭喌", "")
+                .replace("璁板綍", "")
+                .replace("淇℃伅", "")
+                .replace("鐨�", "")
+                .replace("涓�涓�", "")
+                .trim();
+        return cleaned.length() >= 2 ? cleaned : null;
+    }
+
+    private List<Long> findDeviceLedgerIdsByKeyword(LoginUser loginUser, String keyword) {
+        if (!StringUtils.hasText(keyword)) {
+            return List.of();
+        }
+        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
+        Long currentDeptId = loginUser.getCurrentDeptId();
+        if (currentDeptId != null) {
+            wrapper.and(w -> w.eq(DeviceLedger::getDeptId, currentDeptId).or().isNull(DeviceLedger::getDeptId));
+        }
+        wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
+                .or().like(DeviceLedger::getDeviceModel, keyword)
+                .or().like(DeviceLedger::getDeviceBrand, keyword));
+        wrapper.orderByDesc(DeviceLedger::getId).last("limit 200");
+        return defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
+                .map(DeviceLedger::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+
+    private boolean hasTimeConstraint(String startDate, String endDate, String userQuery) {
+        if (StringUtils.hasText(startDate) || StringUtils.hasText(endDate)) {
+            return true;
+        }
+        if (!StringUtils.hasText(userQuery)) {
+            return false;
+        }
+        String text = userQuery.trim();
+        return containsAny(text, "浠婂ぉ", "鏄ㄥぉ", "鏈懆", "涓婂懆", "鏈湀", "涓婃湀", "浠婂勾", "鍘诲勾", "杩�", "鏈�杩�");
+    }
+
+    private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
+        LocalDate today = LocalDate.now();
+        LocalDate start = parseLocalDate(startDate);
+        LocalDate end = parseLocalDate(endDate);
+        if (start != null || end != null) {
+            LocalDate s = start != null ? start : end;
+            LocalDate e = end != null ? end : start;
+            if (s.isAfter(e)) {
+                LocalDate temp = s;
+                s = e;
+                e = temp;
+            }
+            return new DateRange(s, e, s + "鑷�" + e);
+        }
+        if (!StringUtils.hasText(timeRange)) {
+            return new DateRange(today.minusDays(29), today, "杩�30澶�");
+        }
+        String text = timeRange.trim();
+        if (text.contains("浠婂ぉ")) {
+            return new DateRange(today, today, "浠婂ぉ");
+        }
+        if (text.contains("鏈懆")) {
+            LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+            return new DateRange(startOfWeek, today, "鏈懆");
+        }
+        if (text.contains("鏈湀")) {
+            return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
+        }
+        if (text.contains("鏈勾") || text.contains("浠婂勾")) {
+            return new DateRange(today.withDayOfYear(1), today, "浠婂勾");
+        }
+        if (text.contains("鍘诲勾")) {
+            LocalDate firstDay = today.minusYears(1).withDayOfYear(1);
+            LocalDate lastDay = today.minusYears(1).withMonth(12).withDayOfMonth(31);
+            return new DateRange(firstDay, lastDay, "鍘诲勾");
+        }
+        if (text.contains("涓婃湀")) {
+            LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
+            return new DateRange(startOfLastMonth, startOfLastMonth.withDayOfMonth(startOfLastMonth.lengthOfMonth()), "涓婃湀");
+        }
+        java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(杩憒鏈�杩�)(\\d+)(澶﹟鍛▅涓湀|鏈坾骞�)").matcher(text);
+        if (matcher.find()) {
+            int amount = Integer.parseInt(matcher.group(2));
+            String unit = matcher.group(3);
+            LocalDate relativeStart = switch (unit) {
+                case "澶�" -> today.minusDays(Math.max(amount - 1L, 0));
+                case "鍛�" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
+                case "涓湀", "鏈�" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
+                case "骞�" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
+                default -> today.minusDays(29);
+            };
+            return new DateRange(relativeStart, today, "杩�" + amount + unit);
+        }
+        return new DateRange(today.minusDays(29), today, "杩�30澶�");
+    }
+
+    private LocalDate parseLocalDate(String text) {
+        if (!StringUtils.hasText(text)) {
+            return null;
+        }
+        return LocalDate.parse(text.trim(), DATE_FMT);
+    }
+
+    private Date toDate(LocalDate date) {
+        return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
+    private Date toExclusiveEndDate(LocalDate date) {
+        return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
+    private String formatDate(LocalDate date) {
+        return date == null ? "" : DATE_FMT.format(date);
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return "";
+        }
+        return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
+    }
+
+    private String formatDateTime(LocalDateTime time) {
+        if (time == null) {
+            return "";
+        }
+        return time.truncatedTo(ChronoUnit.SECONDS).toString().replace('T', ' ');
+    }
+
+    private int normalizeLimit(Integer limit) {
+        if (limit == null || limit <= 0) {
+            return DEFAULT_LIMIT;
+        }
+        return Math.min(limit, MAX_LIMIT);
+    }
+
+    private boolean containsAny(String text, String... values) {
+        for (String value : values) {
+            if (text.contains(value)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
+        if (tenantId != null) {
+            wrapper.eq(field, tenantId);
+        }
+    }
+
+    private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
+        if (deptId != null) {
+            wrapper.eq(field, deptId);
+        }
+    }
+
+    private LoginUser currentLoginUser(String memoryId) {
+        LoginUser loginUser = aiSessionUserContext.get(memoryId);
+        if (loginUser != null) {
+            return loginUser;
+        }
+        return SecurityUtils.getLoginUser();
+    }
+
+    private String safe(Object value) {
+        return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
+    }
+
+    private <T> List<T> defaultList(List<T> list) {
+        return list == null ? List.of() : list;
+    }
+
+    private String jsonResponse(boolean success,
+                                String type,
+                                String description,
+                                Map<String, Object> summary,
+                                Map<String, Object> data,
+                                Map<String, Object> charts) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("success", success);
+        result.put("type", type);
+        result.put("description", description);
+        result.put("summary", summary == null ? Map.of() : summary);
+        result.put("data", data == null ? Map.of() : data);
+        result.put("charts", charts == null ? Map.of() : charts);
+        return JSON.toJSONString(result);
+    }
+
+    private record DateRange(LocalDate start, LocalDate end, String label) {
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java b/src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
index 56d420e..8c5db04 100644
--- a/src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
@@ -75,5 +75,5 @@
      */
     private BigDecimal maintenancePrice;
 
-    private List<StorageBlobDTO> storageBlobDTOList;
+    private List<StorageBlobDTO> storageBlobDTOS;
 }
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
index b0046d7..0b1a854 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -220,6 +220,8 @@
                     //鏇存敼鍑哄簱瀹℃牳鐘舵�侊紙寰呯‘璁ゆ敼鎴愬緟瀹℃牳锛�
                     stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
                 } else if (status.equals(3)) {
+                    //鍒犻櫎鍘熸湰锛堝緟纭锛夌殑鍑哄簱瀹℃牳鐘舵��
+                    stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
                     shippingInfo.setStatus("瀹℃牳鎷掔粷");
                 } else if (status.equals(1)) {
                     shippingInfo.setStatus("瀹℃牳涓�");
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
index bf2cdb2..c9d7aae 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -138,7 +138,7 @@
                 .collect(Collectors.joining(","));
         approveNodeService.initApproveNodes(nodeIdStr, no, approveProcessVO.getApproveDeptId());
         // 闄勪欢缁戝畾
-        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approveProcess.getId(), approveProcessVO.getStorageBlobDTOList());
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approveProcess.getId(), approveProcessVO.getStorageBlobDTOS());
         /*娑堟伅閫氱煡*/
         Long id = nodeIds.getFirst();
         if (approveProcess.getApproveType() == 8) {
diff --git a/src/main/java/com/ruoyi/basic/dto/ProductModelDto.java b/src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
index 4a88508..15e428d 100644
--- a/src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
+++ b/src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
@@ -3,9 +3,11 @@
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.production.bean.dto.ProductStructureDto;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 
 import java.util.List;
 
+@EqualsAndHashCode(callSuper = true)
 @Data
 public class ProductModelDto extends ProductModel {
     private List<ProductStructureDto> productStructureList;
diff --git a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
index 9c63efb..ef7e57c 100644
--- a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -204,6 +204,7 @@
     SALES_REFUND_AMOUNT_ORDER("sales_refund_amount_order"),
     SALES_RECEIPT_RETURN("sales_receipt_return"),
     ACCOUNT_EXPENSE("account_expense"),
+    FIN_VOUCHER("fin_voucher"),
     ACCOUNT_FILE("account_file");
 
     private final String type;
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
index 85b542a..be34fc0 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -18,8 +18,11 @@
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.sales.dto.LossProductModelDto;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.technology.mapper.TechnologyBomMapper;
+import com.ruoyi.technology.pojo.TechnologyBom;
 import lombok.AllArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -43,10 +46,16 @@
 
     private final ProductMapper productMapper;
     private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final TechnologyBomMapper technologyBomMapper;
     private ProductModelMapper productModelMapper;
 
     @Override
     public int addOrEditProductModel(ProductModelDto productModelDto) {
+        String model = StringUtils.trim(productModelDto.getModel());
+        String productCode = StringUtils.trim(productModelDto.getProductCode());
+        productModelDto.setModel(model);
+        productModelDto.setProductCode(productCode);
+        checkModelAndProductCodeUnique(model, productCode, productModelDto.getId());
 
         if (productModelDto.getId() == null) {
             ProductModel productModel = new ProductModel();
@@ -54,6 +63,21 @@
             return productModelMapper.insert(productModel);
         } else {
             return productModelMapper.updateById(productModelDto);
+        }
+    }
+
+    private void checkModelAndProductCodeUnique(String model, String productCode, Long currentId) {
+        if (StringUtils.isEmpty(model) || StringUtils.isEmpty(productCode)) {
+            return;
+        }
+        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ProductModel::getModel, model)
+                .eq(ProductModel::getProductCode, productCode)
+                .ne(currentId != null, ProductModel::getId, currentId)
+                .last("limit 1");
+        ProductModel duplicateProductModel = productModelMapper.selectOne(queryWrapper);
+        if (duplicateProductModel != null) {
+            throw new ServiceException("瀵瑰簲鐨勫瀷鍙�" + model + "鐨勪骇鍝佺紪鐮�" + productCode + "宸茬粡瀛樺湪");
         }
     }
 
@@ -66,6 +90,14 @@
 
             throw new RuntimeException("宸茬粡瀛樺湪璇ヤ骇鍝佺殑閿�鍞彴璐﹀拰閲囪喘鍙拌处");
         }
+
+        // 鏄惁瀛樺湪BOM
+        List<TechnologyBom> technologyBoms = technologyBomMapper.selectList(new QueryWrapper<TechnologyBom>()
+                .lambda().in(TechnologyBom::getProductModelId, ids));
+        if (CollectionUtils.isNotEmpty(technologyBoms)) {
+            throw new RuntimeException("宸茬粡瀛樺湪璇ヤ骇鍝佺殑BOM鏁版嵁");
+        }
+
         return productModelMapper.deleteBatchIds(Arrays.asList(ids));
     }
 
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
index 5a7f679..2b9eb40 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -13,12 +13,10 @@
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.service.IProductService;
 import com.ruoyi.basic.vo.ProductModelVo;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.framework.web.domain.AjaxResult;
 import lombok.AllArgsConstructor;
 import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -94,17 +92,18 @@
         if (ObjectUtils.isEmpty(productDto.getParentId())) {
             throw new IllegalArgumentException("璇烽�夋嫨鐖惰妭鐐�");
         }
+        String productName = StringUtils.trim(productDto.getProductName());
+        if (StringUtils.isEmpty(productName)) {
+            throw new IllegalArgumentException("浜у搧鍚嶇О涓嶈兘涓虹┖");
+        }
+        productDto.setProductName(productName);
+        checkProductNameUnique(productDto.getParentId(), productName, productDto.getId());
         if (productDto.getId() == null) {
             // 鏂板浜у搧閫昏緫
-            if (productDto.getParentId() == null) {
-                // 鑻ユ湭鎸囧畾鐖惰妭鐐癸紝榛樿涓烘牴鑺傜偣锛坧arentId 璁句负 null锛�
-                productDto.setParentId(null);
-            } else {
-                // 妫�鏌ョ埗鑺傜偣鏄惁瀛樺湪锛堝彲閫夛紝鏍规嵁涓氬姟闇�姹傦級
-                Product parent = productMapper.selectById(productDto.getParentId());
-                if (parent == null) {
-                    throw new IllegalArgumentException("鐖惰妭鐐逛笉瀛樺湪锛屾棤娉曟坊鍔犲瓙浜у搧");
-                }
+            // 妫�鏌ョ埗鑺傜偣鏄惁瀛樺湪锛堝彲閫夛紝鏍规嵁涓氬姟闇�姹傦級
+            Product parent = productMapper.selectById(productDto.getParentId());
+            if (parent == null) {
+                throw new IllegalArgumentException("鐖惰妭鐐逛笉瀛樺湪锛屾棤娉曟坊鍔犲瓙浜у搧");
             }
             return productMapper.insert(productDto);
         } else {
@@ -118,6 +117,18 @@
         }
     }
 
+    private void checkProductNameUnique(Long parentId, String productName, Long currentId) {
+        LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(Product::getParentId, parentId)
+                .eq(Product::getProductName, productName)
+                .ne(currentId != null, Product::getId, currentId)
+                .last("limit 1");
+        Product duplicateProduct = productMapper.selectOne(queryWrapper);
+        if (duplicateProduct != null) {
+            throw new IllegalArgumentException("瀵瑰簲鐨�" + productName + "宸茬粡瀛樺湪");
+        }
+    }
+
     @Override
     public int delProductByIds(Long[] ids) {
         // 1. 鍒犻櫎瀛愯〃 product_model 涓叧鑱旂殑鏁版嵁
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java b/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
index 6a3824d..dc6a64e 100644
--- a/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
+++ b/src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
@@ -2,6 +2,10 @@
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.approve.pojo.KnowledgeBase;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.collaborativeApproval.dto.SealApplicationManagementDTO;
 import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
 import com.ruoyi.collaborativeApproval.service.SealApplicationManagementService;
 import com.ruoyi.common.utils.SecurityUtils;
@@ -27,6 +31,7 @@
 public class SealApplicationManagementController {
     private SealApplicationManagementService sealApplicationManagementService;
     private ISysNoticeService sysNoticeService;
+    private FileUtil fileUtil;
 
     @GetMapping("/getList")
     @Operation(summary = "鍒嗛〉鏌ヨ")
@@ -36,8 +41,13 @@
 
     @PostMapping("/add")
     @Operation(summary = "鏂板")
-    public AjaxResult add(@RequestBody SealApplicationManagement sealApplicationManagement){
+    public AjaxResult add(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
         sealApplicationManagementService.save(sealApplicationManagement);
+        // 5. 淇濆瓨閿�鍞彴璐﹂檮浠�
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE,
+                RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT,
+                sealApplicationManagement.getId(),
+                sealApplicationManagement.getStorageBlobDTOs());
         //娑堟伅閫氱煡
         sysNoticeService.simpleNoticeByUser("鐢ㄥ嵃瀹℃壒",
                 "鐢宠缂栧彿锛�"+sealApplicationManagement.getApplicationNum()+"\t"
@@ -49,7 +59,12 @@
 
     @PostMapping("/update")
     @Operation(summary = "淇敼")
-    public AjaxResult update(@RequestBody SealApplicationManagement sealApplicationManagement){
+    public AjaxResult update(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
+        // 5. 淇濆瓨閿�鍞彴璐﹂檮浠�
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE,
+                RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT,
+                sealApplicationManagement.getId(),
+                sealApplicationManagement.getStorageBlobDTOs());
         return AjaxResult.success(sealApplicationManagementService.updateById(sealApplicationManagement));
     }
 
@@ -59,6 +74,9 @@
         if (CollectionUtils.isEmpty(ids)) {
             throw new RuntimeException("璇蜂紶鍏ヨ鍒犻櫎鐨処D");
         }
+        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE,
+                RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT,
+                ids);
         return AjaxResult.success(sealApplicationManagementService.removeBatchByIds(ids));
     }
 
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java b/src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java
index 8dc8454..7757490 100644
--- a/src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java
+++ b/src/main/java/com/ruoyi/collaborativeApproval/dto/SealApplicationManagementDTO.java
@@ -1,7 +1,11 @@
 package com.ruoyi.collaborativeApproval.dto;
 
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.dto.StorageBlobVO;
 import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
 import lombok.Data;
+
+import java.util.List;
 
 @Data
 public class SealApplicationManagementDTO extends SealApplicationManagement {
@@ -11,4 +15,8 @@
 
     //瀹℃壒浜�
     private String approveUserName;
+
+    private List<StorageBlobDTO> storageBlobDTOs;
+    private List<StorageBlobVO> storageBlobVOList;
+
 }
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java
index d63f2d2..ba20ae2 100644
--- a/src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java
@@ -3,6 +3,8 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.collaborativeApproval.dto.SealApplicationManagementDTO;
 import com.ruoyi.collaborativeApproval.mapper.SealApplicationManagementMapper;
 import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
@@ -14,9 +16,14 @@
 @RequiredArgsConstructor
 public class SealApplicationManagementServiceImpl extends ServiceImpl<SealApplicationManagementMapper, SealApplicationManagement> implements SealApplicationManagementService {
     private final SealApplicationManagementMapper sealApplicationManagementMapper;
+    private final FileUtil fileUtil;
 
     @Override
     public IPage<SealApplicationManagementDTO> listPage(Page page, SealApplicationManagement sealApplicationManagement) {
-        return sealApplicationManagementMapper.listPage(page, sealApplicationManagement);
+        IPage<SealApplicationManagementDTO> sealApplicationManagementDTOIPage = sealApplicationManagementMapper.listPage(page, sealApplicationManagement);
+        sealApplicationManagementDTOIPage.getRecords().forEach(item -> {
+            item.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT, item.getId()));
+        });
+        return sealApplicationManagementDTOIPage;
     }
 }
diff --git a/src/main/java/com/ruoyi/device/controller/DeviceRepairController.java b/src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
index d3c19bb..7df7c26 100644
--- a/src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
+++ b/src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
@@ -46,10 +46,16 @@
         return deviceRepairService.updateDeviceRepair(deviceRepairDto);
     }
 
-    @PostMapping ("repair")
+    @PostMapping ("/repair")
     @Operation(summary = "璁惧缁翠慨")
     public AjaxResult repair( @RequestBody DeviceRepairDto deviceRepairDto) {
-        return deviceRepairService.updateDeviceRepair(deviceRepairDto);
+        return deviceRepairService.confirmRepair(deviceRepairDto);
+    }
+
+    @PostMapping ("/acceptance")
+    @Operation(summary = "璁惧鎶ヤ慨楠屾敹瀹℃壒")
+    public AjaxResult acceptance(@RequestBody DeviceRepairDto deviceRepairDto) {
+        return deviceRepairService.approveRepairAcceptance(deviceRepairDto);
     }
 
     @DeleteMapping("/{ids}")
diff --git a/src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java b/src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java
index c782da2..caf5c1a 100644
--- a/src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java
+++ b/src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java
@@ -47,6 +47,18 @@
     @Excel(name = "缁翠慨缁撴灉")
     private String maintenanceResult;
 
+    @Schema(description = "楠屾敹浜�")
+    @Excel(name = "楠屾敹浜�")
+    private String acceptanceName;
+
+    @Schema(description = "楠屾敹鏃堕棿")
+    @Excel(name = "楠屾敹鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime acceptanceTime;
+
+    @Schema(description = "楠屾敹澶囨敞")
+    @Excel(name = "楠屾敹澶囨敞")
+    private String acceptanceRemark;
+
     @Schema(description = "鐘舵��")
     @Excel(name = "鐘舵��")
     private String statusStr;
diff --git a/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java b/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
index 9db134e..c9efa55 100644
--- a/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
+++ b/src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
@@ -50,7 +50,19 @@
     @Schema(description = "缁翠慨缁撴灉")
     private String maintenanceResult;
 
+    @Schema(description = "楠屾敹浜�")
+    private String acceptanceName;
+
+    @Schema(description = "楠屾敹鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime acceptanceTime;
+
+    @Schema(description = "楠屾敹澶囨敞")
+    private String acceptanceRemark;
+
     @Schema(description = "鐘舵�� 0 寰呯淮淇� 1瀹岀粨 2 澶辫触")
+    // 0:寰呯淮淇� 1:瀹岀粨 2:澶辫触 3:寰呴獙鏀�
     private Integer status;
 
     @Schema(description = "鍒涘缓鏃堕棿")
diff --git a/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java b/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
index 2e35584..350a09d 100644
--- a/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
+++ b/src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -44,6 +44,10 @@
     @Schema(description = "璁惧id")
     private Long taskId;
 
+    @Schema(description = "淇濆吇浜�")
+    @Excel(name = "淇濆吇浜�")
+    private String maintenancePerson;
+
     @Schema(description = "棰戞")
     @Excel(name = "棰戞")
     private String frequencyType;
diff --git a/src/main/java/com/ruoyi/device/service/IDeviceRepairService.java b/src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
index 8d20acd..ae0b913 100644
--- a/src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
+++ b/src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
@@ -19,6 +19,10 @@
 
     AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto);
 
+    AjaxResult confirmRepair(DeviceRepairDto deviceRepairDto);
+
+    AjaxResult approveRepairAcceptance(DeviceRepairDto deviceRepairDto);
+
     void export(HttpServletResponse response, Long[] ids);
 
     DeviceRepairVo detailById(Long id);
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
index e3ebb0e..e5a73ac 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -47,6 +47,11 @@
     private final SparePartsRequisitionRecordService sparePartsRequisitionRecordService;
     private final FileUtil fileUtil;
 
+    private static final int STATUS_PENDING_REPAIR = 0;
+    private static final int STATUS_COMPLETED = 1;
+    private static final int STATUS_FAILED = 2;
+    private static final int STATUS_PENDING_ACCEPTANCE = 3;
+
     @Override
     public IPage<DeviceRepairVo> queryPage(Page page, DeviceRepairDto deviceRepairDto) {
         IPage<DeviceRepairVo> pageDto = deviceRepairMapper.queryPage(page, deviceRepairDto);
@@ -62,6 +67,9 @@
         DeviceLedger byId = deviceLedgerService.getById(deviceRepairDto.getDeviceLedgerId());
         deviceRepairDto.setDeviceName(byId.getDeviceName());
         deviceRepairDto.setDeviceModel(byId.getDeviceModel());
+        if (deviceRepairDto.getStatus() == null) {
+            deviceRepairDto.setStatus(STATUS_PENDING_REPAIR);
+        }
         boolean save = this.save(deviceRepairDto);
         if (save) {
             // 澶勭悊鍥剧墖涓婁紶
@@ -75,6 +83,15 @@
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto) {
         DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
+        if (oldDeviceRepair == null) {
+            return AjaxResult.error("鎶ヤ慨璁板綍涓嶅瓨鍦�");
+        }
+        if (deviceRepairDto.getStatus() != null
+                && deviceRepairDto.getStatus() == STATUS_COMPLETED
+                && (oldDeviceRepair.getStatus() == null
+                || oldDeviceRepair.getStatus() != STATUS_COMPLETED)) {
+            return AjaxResult.error("璇峰厛鎻愪氦楠屾敹瀹℃壒锛岄獙鏀堕�氳繃鍚庢墠鍙畬缁�");
+        }
         // 澶勭悊澶囦欢浣跨敤鎯呭喌
         if (CollectionUtils.isNotEmpty(deviceRepairDto.getSparePartsUseList())) {
             List<Long> sparePartIds = new ArrayList<>();
@@ -131,6 +148,58 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult confirmRepair(DeviceRepairDto deviceRepairDto) {
+        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
+        if (oldDeviceRepair == null) {
+            return AjaxResult.error("鎶ヤ慨璁板綍涓嶅瓨鍦�");
+        }
+        if (oldDeviceRepair.getStatus() != null && oldDeviceRepair.getStatus() == STATUS_COMPLETED) {
+            return AjaxResult.error("璇ユ姤淇凡瀹岀粨锛屼笉鑳介噸澶嶇‘璁ょ淮淇�");
+        }
+        if (oldDeviceRepair.getStatus() != null && oldDeviceRepair.getStatus() == STATUS_PENDING_ACCEPTANCE) {
+            return AjaxResult.error("璇ユ姤淇凡鎻愪氦楠屾敹瀹℃壒");
+        }
+        deviceRepairDto.setStatus(STATUS_PENDING_ACCEPTANCE);
+        return updateDeviceRepair(deviceRepairDto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public AjaxResult approveRepairAcceptance(DeviceRepairDto deviceRepairDto) {
+        if (deviceRepairDto.getId() == null) {
+            return AjaxResult.error("鎶ヤ慨璁板綍id涓嶈兘涓虹┖");
+        }
+        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
+        if (oldDeviceRepair == null) {
+            return AjaxResult.error("鎶ヤ慨璁板綍涓嶅瓨鍦�");
+        }
+        if (oldDeviceRepair.getStatus() == null || oldDeviceRepair.getStatus() != STATUS_PENDING_ACCEPTANCE) {
+            return AjaxResult.error("璇ユ姤淇湭杩涘叆寰呴獙鏀剁姸鎬侊紝涓嶈兘瀹℃壒");
+        }
+        if (StringUtils.isBlank(deviceRepairDto.getAcceptanceName())) {
+            return AjaxResult.error("楠屾敹浜轰笉鑳戒负绌�");
+        }
+        if (deviceRepairDto.getAcceptanceTime() == null) {
+            return AjaxResult.error("楠屾敹鏃堕棿涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isBlank(deviceRepairDto.getAcceptanceRemark())) {
+            return AjaxResult.error("楠屾敹澶囨敞涓嶈兘涓虹┖");
+        }
+
+        DeviceRepair update = new DeviceRepair();
+        update.setId(deviceRepairDto.getId());
+        update.setAcceptanceName(deviceRepairDto.getAcceptanceName());
+        update.setAcceptanceTime(deviceRepairDto.getAcceptanceTime());
+        update.setAcceptanceRemark(deviceRepairDto.getAcceptanceRemark());
+        update.setStatus(STATUS_COMPLETED);
+        if (this.updateById(update)) {
+            return AjaxResult.success();
+        }
+        return AjaxResult.error("楠屾敹瀹℃壒澶辫触");
+    }
+
+    @Override
     public void export(HttpServletResponse response, Long[] ids) {
         if (ids == null || ids.length == 0) {
             List<DeviceRepair> supplierManageList = this.list();
@@ -138,24 +207,19 @@
             supplierManageList.stream().forEach(deviceRepair -> {
                 DeviceRepairExeclDto deviceRepairExeclDto = new DeviceRepairExeclDto();
                 BeanUtils.copyProperties(deviceRepair,deviceRepairExeclDto);
-                deviceRepairExeclDto.setStatusStr(deviceRepair.getStatus() == 0 ? "寰呯淮淇�" : deviceRepair.getStatus() == 1 ? "瀹岀粨" : "澶辫触");
-
+                deviceRepairExeclDto.setStatusStr(resolveStatusText(deviceRepair.getStatus()));
                 deviceLedgerExeclDtos.add(deviceRepairExeclDto);
             });
             ExcelUtil<DeviceRepairExeclDto> util = new ExcelUtil<DeviceRepairExeclDto>(DeviceRepairExeclDto.class);
             util.exportExcel(response, deviceLedgerExeclDtos, "璁惧鎶ヤ慨瀵煎嚭");
         }else {
-            ArrayList<Long> arrayList = new ArrayList<>();
-            Arrays.stream(ids).map(id -> {
-                return arrayList.add( id);
-            });
+            ArrayList<Long> arrayList = new ArrayList<>(Arrays.asList(ids));
             List<DeviceRepair> supplierManageList = deviceRepairMapper.selectBatchIds(arrayList);
             ArrayList<DeviceRepairExeclDto> deviceLedgerExeclDtos = new ArrayList<>();
             supplierManageList.stream().forEach(deviceRepair -> {
                 DeviceRepairExeclDto deviceRepairExeclDto = new DeviceRepairExeclDto();
                 BeanUtils.copyProperties(deviceRepair,deviceRepairExeclDto);
-                deviceRepairExeclDto.setStatusStr(deviceRepair.getStatus() == 0 ? "寰呯淮淇�" : deviceRepair.getStatus() == 1 ? "瀹岀粨" : "澶辫触");
-
+                deviceRepairExeclDto.setStatusStr(resolveStatusText(deviceRepair.getStatus()));
                 deviceLedgerExeclDtos.add(deviceRepairExeclDto);
             });
             ExcelUtil<DeviceRepairExeclDto> util = new ExcelUtil<DeviceRepairExeclDto>(DeviceRepairExeclDto.class);
@@ -164,6 +228,25 @@
 
     }
 
+    private String resolveStatusText(Integer status) {
+        if (status == null) {
+            return "";
+        }
+        if (status == STATUS_PENDING_REPAIR) {
+            return "寰呯淮淇�";
+        }
+        if (status == STATUS_COMPLETED) {
+            return "瀹岀粨";
+        }
+        if (status == STATUS_FAILED) {
+            return "澶辫触";
+        }
+        if (status == STATUS_PENDING_ACCEPTANCE) {
+            return "寰呴獙鏀�";
+        }
+        return "鏈煡";
+    }
+
     @Override
     public DeviceRepairVo detailById(Long id) {
         DeviceRepairVo vo = deviceRepairMapper.detailById(id);
diff --git a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
index 09d5d77..204667b 100644
--- a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
+++ b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
@@ -93,6 +93,7 @@
         inspectionTask.setMaintenanceTaskId(timingTask.getId());
         inspectionTask.setDeviceLedgerId(timingTask.getTaskId());
         inspectionTask.setMaintenancePlanTime(LocalDateTime.now());
+        inspectionTask.setMaintenanceActuallyName(timingTask.getMaintenancePerson());
         inspectionTask.setFrequencyType(timingTask.getFrequencyType());
         inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
         inspectionTask.setTenantId(timingTask.getTenantId());
diff --git a/src/main/java/com/ruoyi/framework/security/LoginUser.java b/src/main/java/com/ruoyi/framework/security/LoginUser.java
index 330ae43..aad84a9 100644
--- a/src/main/java/com/ruoyi/framework/security/LoginUser.java
+++ b/src/main/java/com/ruoyi/framework/security/LoginUser.java
@@ -301,7 +301,10 @@
     public void setUser(SysUser user)
     {
         this.user = user;
-        this.aiEnabled = user == null ? null : user.getAiEnabled();
+        if (user != null && user.getAiEnabled() != null)
+        {
+            this.aiEnabled = user.getAiEnabled();
+        }
     }
 
     @Override
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
index c41d626..4e7efad 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -30,6 +30,10 @@
     @Excel(name = "宸℃浠诲姟鍚嶇О")
     private String taskName;
 
+    @Schema(description = "宸℃椤圭洰")
+    @Excel(name = "宸℃椤圭洰")
+    private String inspectionProject;
+
     @Schema(description = "璁惧id")
     private Integer taskId;
 
@@ -44,6 +48,22 @@
     @Excel(name = "澶囨敞")
     private String remarks;
 
+    @Schema(description = "宸℃缁撴灉 0 寮傚父 1 姝e父")
+    private String inspectionResult;
+
+    @Schema(description = "寮傚父鎻忚堪")
+    private String abnormalDescription;
+
+    @Schema(description = "鍏宠仈缁翠慨鍗旾D")
+    private Long deviceRepairId;
+
+    @Schema(description = "楠屾敹浜篒D")
+    private Long acceptanceUserId;
+
+    @Schema(description = "楠屾敹浜�")
+    @Excel(name = "楠屾敹浜�")
+    private String acceptanceName;
+
     @Schema(description = "浠诲姟鐧昏浜篒D")
     private Long registrantId;
 
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
index 2d8722e..43a3edc 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -33,6 +33,10 @@
     @Excel(name = "宸℃浠诲姟鍚嶇О")
     private String taskName;
 
+    @Schema(description = "宸℃椤圭洰")
+    @Excel(name = "宸℃椤圭洰")
+    private String inspectionProject;
+
     @Schema(description = "璁惧id")
     private Integer taskId;
 
@@ -60,6 +64,10 @@
     @Schema(description = "鏄惁婵�娲�")
     private boolean isActive;
 
+    @Schema(description = "鏄惁鍚敤 0鍚� 1鏄�")
+    @Excel(name = "鏄惁鍚敤", readConverterExp = "0=鍚�,1=鏄�")
+    private Integer isEnabled;
+
     @Schema(description = "澶囨敞")
     @Excel(name = "澶囨敞")
     private String remarks;
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
index 410336e..5826fa7 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -5,12 +5,17 @@
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
 import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.mapper.DeviceRepairMapper;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.pojo.DeviceRepair;
 import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
 import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
 import com.ruoyi.inspectiontask.pojo.InspectionTask;
@@ -23,6 +28,7 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.format.DateTimeFormatter;
+import java.util.Date;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -43,6 +49,14 @@
 
     private final FileUtil fileUtil;
 
+    private final DeviceRepairMapper deviceRepairMapper;
+
+    private final DeviceLedgerMapper deviceLedgerMapper;
+
+    private static final String INSPECTION_RESULT_ABNORMAL = "0";
+    private static final String INSPECTION_RESULT_NORMAL = "1";
+    private static final int REPAIR_STATUS_PENDING = 0;
+
     @Override
     public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
         LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
@@ -50,14 +64,15 @@
         if (StringUtils.isNotBlank(inspectionTaskDto.getTaskName())) {
             queryWrapper.like(InspectionTask::getTaskName, inspectionTaskDto.getTaskName());
         }
+        if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
+            queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
+        }
         IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
 
         //  鏃犳暟鎹彁鍓嶈繑鍥�
         if (CollectionUtils.isEmpty(entityPage.getRecords())) {
             return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
         }
-        // 鑾峰彇id闆嗗悎
-        List<Long> ids = entityPage.getRecords().stream().map(InspectionTask::getId).collect(Collectors.toList());
         //鐧昏浜篿ds
         List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
         // 鎵归噺鏌ヨ鐧昏浜�
@@ -68,9 +83,6 @@
         } else {
             sysUserMap = new HashMap<>();
         }
-        //宸℃浜篿ds
-        List<String> inspectorIds = entityPage.getRecords().stream().map(InspectionTask::getInspectorId).collect(Collectors.toList());
-
         //鑾峰彇鎵�鏈変笉閲嶅鐨勭敤鎴稩D
         Set<Long> allUserIds = entityPage.getRecords().stream()
                 .map(InspectionTask::getInspectorId) // 鑾峰彇"2,3"杩欐牱鐨勫瓧绗︿覆
@@ -140,24 +152,230 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto) {
+        InspectionTask oldInspectionTask = null;
+        if (Objects.nonNull(inspectionTaskDto.getId())) {
+            oldInspectionTask = inspectionTaskMapper.selectById(inspectionTaskDto.getId());
+            if (oldInspectionTask == null) {
+                throw new IllegalArgumentException("宸℃浠诲姟涓嶅瓨鍦�");
+            }
+        }
+        validateInspectionInput(inspectionTaskDto, oldInspectionTask);
+
         InspectionTask inspectionTask = new InspectionTask();
         BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
         inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
         inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
+        fillAcceptanceInfo(inspectionTask, oldInspectionTask);
         int i;
         if (Objects.isNull(inspectionTaskDto.getId())) {
             i = inspectionTaskMapper.insert(inspectionTask);
         } else {
             i = inspectionTaskMapper.updateById(inspectionTask);
         }
-        // 淇濆瓨鏂囦欢
-        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListDTO());
-        fileUtil.saveStorageAttachment(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListAfterDTO());
-        fileUtil.saveStorageAttachment(ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId(), inspectionTaskDto.getCommonFileListBeforeDTO());
+        if (i <= 0) {
+            return i;
+        }
+        Long linkedRepairId = syncRepairOrderIfAbnormal(inspectionTask, oldInspectionTask);
+        if (linkedRepairId != null && !Objects.equals(linkedRepairId, inspectionTask.getDeviceRepairId())) {
+            InspectionTask relationUpdate = new InspectionTask();
+            relationUpdate.setId(inspectionTask.getId());
+            relationUpdate.setDeviceRepairId(linkedRepairId);
+            inspectionTaskMapper.updateById(relationUpdate);
+            inspectionTask.setDeviceRepairId(linkedRepairId);
+        }
+        // 淇濆瓨鏂囦欢锛堝瓧娈典笉浼犲垯淇濈暀鍘嗗彶锛�
+        saveInspectionAttachments(inspectionTask.getId(), inspectionTaskDto);
 
         return i;
     }
 
+    private void validateInspectionInput(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
+        String inspectionResult = inspectionTaskDto.getInspectionResult();
+        if (StringUtils.isBlank(inspectionResult) && oldInspectionTask != null) {
+            inspectionResult = oldInspectionTask.getInspectionResult();
+        }
+        if (StringUtils.isBlank(inspectionResult)) {
+            throw new IllegalArgumentException("璇烽�夋嫨宸℃缁撴灉");
+        }
+        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionResult) && !INSPECTION_RESULT_NORMAL.equals(inspectionResult)) {
+            throw new IllegalArgumentException("宸℃缁撴灉浠呮敮鎸侊細0-寮傚父锛�1-姝e父");
+        }
+        inspectionTaskDto.setInspectionResult(inspectionResult);
+
+        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionResult)) {
+            return;
+        }
+
+        String abnormalDescription = inspectionTaskDto.getAbnormalDescription();
+        if (StringUtils.isBlank(abnormalDescription) && oldInspectionTask != null) {
+            abnormalDescription = oldInspectionTask.getAbnormalDescription();
+        }
+        if (StringUtils.isBlank(abnormalDescription)) {
+            throw new IllegalArgumentException("宸℃缁撴灉涓哄紓甯告椂锛屽紓甯告弿杩颁笉鑳戒负绌�");
+        }
+        inspectionTaskDto.setAbnormalDescription(abnormalDescription);
+
+        if (!hasAnyInspectionPhotoAfterSave(inspectionTaskDto, oldInspectionTask)) {
+            throw new IllegalArgumentException("宸℃缁撴灉涓哄紓甯告椂锛屽繀椤讳笂浼犺嚦灏戜竴寮犵収鐗�");
+        }
+    }
+
+    private boolean hasAnyInspectionPhotoAfterSave(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
+        Long recordId = oldInspectionTask == null ? null : oldInspectionTask.getId();
+        return hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListDTO(), ApplicationTypeEnum.FILE, recordId)
+                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListAfterDTO(), ApplicationTypeEnum.AFTER_FILE, recordId)
+                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListBeforeDTO(), ApplicationTypeEnum.BEFORE_FILE, recordId);
+    }
+
+    private boolean hasApplicationPhotoAfterSave(List<StorageBlobDTO> requestPhotos, ApplicationTypeEnum applicationType, Long recordId) {
+        if (requestPhotos != null) {
+            return !requestPhotos.isEmpty();
+        }
+        if (recordId == null) {
+            return false;
+        }
+        return CollectionUtils.isNotEmpty(fileUtil.getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(applicationType, RecordTypeEnum.INSPECTION_TASK, recordId));
+    }
+
+    private void fillAcceptanceInfo(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
+        Long acceptanceUserId = inspectionTask.getAcceptanceUserId();
+        if (acceptanceUserId == null && oldInspectionTask != null) {
+            acceptanceUserId = oldInspectionTask.getAcceptanceUserId();
+        }
+        if (acceptanceUserId != null) {
+            inspectionTask.setAcceptanceUserId(acceptanceUserId);
+        }
+
+        String acceptanceName = inspectionTask.getAcceptanceName();
+        if (StringUtils.isBlank(acceptanceName) && acceptanceUserId != null) {
+            SysUser acceptanceUser = sysUserMapper.selectUserById(acceptanceUserId);
+            if (acceptanceUser != null) {
+                acceptanceName = acceptanceUser.getNickName();
+            }
+        }
+        if (StringUtils.isBlank(acceptanceName) && oldInspectionTask != null) {
+            acceptanceName = oldInspectionTask.getAcceptanceName();
+        }
+        inspectionTask.setAcceptanceName(acceptanceName);
+    }
+
+    private Long syncRepairOrderIfAbnormal(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
+        if (!INSPECTION_RESULT_ABNORMAL.equals(inspectionTask.getInspectionResult())) {
+            return inspectionTask.getDeviceRepairId();
+        }
+        Long linkedRepairId = inspectionTask.getDeviceRepairId();
+        if (linkedRepairId == null && oldInspectionTask != null) {
+            linkedRepairId = oldInspectionTask.getDeviceRepairId();
+        }
+
+        if (linkedRepairId != null) {
+            DeviceRepair updateRepair = new DeviceRepair();
+            updateRepair.setId(linkedRepairId);
+            updateRepair.setRemark(inspectionTask.getAbnormalDescription());
+            deviceRepairMapper.updateById(updateRepair);
+            return linkedRepairId;
+        }
+
+        DeviceRepair deviceRepair = buildDeviceRepair(inspectionTask, oldInspectionTask);
+        deviceRepairMapper.insert(deviceRepair);
+        return deviceRepair.getId();
+    }
+
+    private DeviceRepair buildDeviceRepair(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
+        DeviceRepair deviceRepair = new DeviceRepair();
+        Long deviceLedgerId = resolveDeviceLedgerId(inspectionTask, oldInspectionTask);
+        DeviceLedger deviceLedger = resolveDeviceLedger(deviceLedgerId);
+        deviceRepair.setDeviceLedgerId(deviceLedgerId);
+        deviceRepair.setDeviceName(resolveDeviceName(inspectionTask, oldInspectionTask, deviceLedger));
+        deviceRepair.setDeviceModel(deviceLedger == null ? null : deviceLedger.getDeviceModel());
+        deviceRepair.setRepairName(resolveRepairReporter(inspectionTask, oldInspectionTask));
+        deviceRepair.setRepairTime(new Date());
+        deviceRepair.setRemark(inspectionTask.getAbnormalDescription());
+        deviceRepair.setMachineryCategory(deviceLedger == null ? null : deviceLedger.getType());
+        deviceRepair.setStatus(REPAIR_STATUS_PENDING);
+        return deviceRepair;
+    }
+
+    private Long resolveDeviceLedgerId(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
+        Integer taskId = inspectionTask.getTaskId();
+        if (taskId == null && oldInspectionTask != null) {
+            taskId = oldInspectionTask.getTaskId();
+        }
+        if (taskId == null || taskId <= 0) {
+            return null;
+        }
+        return taskId.longValue();
+    }
+
+    private DeviceLedger resolveDeviceLedger(Long deviceLedgerId) {
+        if (deviceLedgerId == null) {
+            return null;
+        }
+        return deviceLedgerMapper.selectById(deviceLedgerId);
+    }
+
+    private String resolveDeviceName(InspectionTask inspectionTask, InspectionTask oldInspectionTask, DeviceLedger deviceLedger) {
+        String taskName = inspectionTask.getTaskName();
+        if (StringUtils.isBlank(taskName) && oldInspectionTask != null) {
+            taskName = oldInspectionTask.getTaskName();
+        }
+        if (StringUtils.isNotBlank(taskName)) {
+            return taskName;
+        }
+        return deviceLedger == null ? null : deviceLedger.getDeviceName();
+    }
+
+    private String resolveRepairReporter(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
+        String reporter = inspectionTask.getInspector();
+        if (StringUtils.isBlank(reporter) && oldInspectionTask != null) {
+            reporter = oldInspectionTask.getInspector();
+        }
+        if (StringUtils.isNotBlank(reporter)) {
+            return reporter;
+        }
+        String inspectorNameByUserId = resolveInspectorNameByUserId(inspectionTask.getInspectorId());
+        if (StringUtils.isBlank(inspectorNameByUserId) && oldInspectionTask != null) {
+            inspectorNameByUserId = resolveInspectorNameByUserId(oldInspectionTask.getInspectorId());
+        }
+        if (StringUtils.isNotBlank(inspectorNameByUserId)) {
+            return inspectorNameByUserId;
+        }
+        try {
+            return SecurityUtils.getUsername();
+        } catch (Exception ignored) {
+            return "system";
+        }
+    }
+
+    private String resolveInspectorNameByUserId(String inspectorIds) {
+        if (StringUtils.isBlank(inspectorIds)) {
+            return null;
+        }
+        String firstInspectorId = Arrays.stream(inspectorIds.split(","))
+                .map(String::trim)
+                .filter(StringUtils::isNotBlank)
+                .findFirst()
+                .orElse(null);
+        if (!StringUtils.isNumeric(firstInspectorId)) {
+            return null;
+        }
+        SysUser sysUser = sysUserMapper.selectUserById(Long.parseLong(firstInspectorId));
+        return sysUser == null ? null : sysUser.getNickName();
+    }
+
+    private void saveInspectionAttachments(Long inspectionTaskId, InspectionTaskDto inspectionTaskDto) {
+        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.FILE, inspectionTaskDto.getCommonFileListDTO());
+        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.AFTER_FILE, inspectionTaskDto.getCommonFileListAfterDTO());
+        saveAttachmentIfPresent(inspectionTaskId, ApplicationTypeEnum.BEFORE_FILE, inspectionTaskDto.getCommonFileListBeforeDTO());
+    }
+
+    private void saveAttachmentIfPresent(Long inspectionTaskId, ApplicationTypeEnum applicationTypeEnum, List<StorageBlobDTO> storageBlobDTOS) {
+        if (storageBlobDTOS == null) {
+            return;
+        }
+        fileUtil.saveStorageAttachment(applicationTypeEnum, RecordTypeEnum.INSPECTION_TASK, inspectionTaskId, storageBlobDTOS);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int delByIds(Long[] ids) {
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
index d9c5f69..adc2416 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -3,7 +3,7 @@
 import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
 import com.ruoyi.inspectiontask.pojo.InspectionTask;
 import com.ruoyi.inspectiontask.pojo.TimingTask;
-import lombok.RequiredArgsConstructor;
+import com.ruoyi.common.utils.StringUtils;
 import org.quartz.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
@@ -47,6 +47,9 @@
             TimingTask timingTask = tasks.isEmpty() ? null : tasks.get(0);
             if (timingTask == null) {
                 throw new JobExecutionException("鎵句笉鍒板畾鏃朵换鍔�: " + taskId);
+            }
+            if (timingTask.getIsEnabled() != null && timingTask.getIsEnabled() == 0) {
+                return;
             }
 
 //            if (!timingTask.isActive()) {
@@ -100,10 +103,15 @@
 
         // 澶嶅埗鍩烘湰灞炴��
         inspectionTask.setTaskName(timingTask.getTaskName());
+        inspectionTask.setInspectionProject(timingTask.getInspectionProject());
         inspectionTask.setTaskId(timingTask.getTaskId());
         inspectionTask.setInspectorId(timingTask.getInspectorIds());
         inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
-        inspectionTask.setRemarks("鑷姩鐢熸垚鑷畾鏃朵换鍔D: " + timingTask.getId());
+        String remarks = "鑷姩鐢熸垚鑷畾鏃朵换鍔D: " + timingTask.getId();
+        if (StringUtils.isNotBlank(timingTask.getRemarks())) {
+            remarks = remarks + "锛�" + timingTask.getRemarks();
+        }
+        inspectionTask.setRemarks(remarks);
         inspectionTask.setRegistrantId(timingTask.getRegistrantId());
         inspectionTask.setFrequencyType(timingTask.getFrequencyType());
         inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index 8772f48..779dc5f 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -35,6 +35,8 @@
     private final TimingTaskMapper timingTaskMapper;
     private final TimingTaskScheduler timingTaskScheduler;
     private final SysUserMapper sysUserMapper;
+    private static final int ENABLED = 1;
+    private static final int DISABLED = 0;
 
 
     @Override
@@ -44,6 +46,12 @@
         LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>();
         if (StringUtils.isNotBlank(timingTask.getTaskName())) {
             queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName());
+        }
+        if (StringUtils.isNotBlank(timingTask.getInspectionProject())) {
+            queryWrapper.like(TimingTask::getInspectionProject, timingTask.getInspectionProject());
+        }
+        if (timingTask.getIsEnabled() != null) {
+            queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
         }
         IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
 
@@ -115,8 +123,17 @@
     @Override
     @Transactional
     public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
+        TimingTask oldTimingTask = null;
+        if (Objects.nonNull(timingTaskDto.getId())) {
+            oldTimingTask = timingTaskMapper.selectById(timingTaskDto.getId());
+            if (oldTimingTask == null) {
+                throw new IllegalArgumentException("瀹氭椂浠诲姟涓嶅瓨鍦�");
+            }
+        }
         TimingTask timingTask = new TimingTask();
         BeanUtils.copyProperties(timingTaskDto, timingTask);
+        timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask));
+        timingTask.setActive(ENABLED == timingTask.getIsEnabled());
         // 1. 瑙f瀽瀛楃涓蹭负 LocalDate锛堝彧鍖呭惈骞存湀鏃ワ級
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         LocalDate localDate = LocalDate.now();
@@ -132,13 +149,12 @@
         // 璁剧疆鍒涘缓浜轰俊鎭拰榛樿鍊�
         if (Objects.isNull(timingTaskDto.getId())) {
             timingTask.setRegistrationDate(LocalDate.now());
-            timingTask.setActive(true);
 
             // 璁$畻棣栨鎵ц鏃堕棿
             LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
             timingTask.setNextExecutionTime(firstExecutionTime);
             int result = timingTaskMapper.insert(timingTask);
-            if (result > 0) {
+            if (result > 0 && isEnabled(timingTask.getIsEnabled(), timingTask.isActive())) {
                 // 鏂板鎴愬姛鍚庢坊鍔犲埌璋冨害鍣�
                 timingTaskScheduler.scheduleTimingTask(timingTask);
             }
@@ -148,8 +164,17 @@
 
             int result = timingTaskMapper.updateById(timingTask);
             if (result > 0) {
-                // 鏇存柊鎴愬姛鍚庨噸鏂拌皟搴︿换鍔�
-                timingTaskScheduler.rescheduleTimingTask(timingTask);
+                boolean oldEnabled = isEnabled(oldTimingTask == null ? null : oldTimingTask.getIsEnabled(), oldTimingTask != null && oldTimingTask.isActive());
+                boolean newEnabled = isEnabled(timingTask.getIsEnabled(), timingTask.isActive());
+                if (!newEnabled) {
+                    timingTaskScheduler.unscheduleTimingTask(timingTask.getId());
+                } else if (oldEnabled) {
+                    // 鏇存柊鎴愬姛鍚庨噸鏂拌皟搴︿换鍔�
+                    timingTaskScheduler.rescheduleTimingTask(timingTask);
+                } else {
+                    // 浠庣鐢ㄦ敼涓哄惎鐢ㄦ椂閲嶆柊鍒涘缓璋冨害浠诲姟
+                    timingTaskScheduler.scheduleTimingTask(timingTask);
+                }
             }
             return result;
         }
@@ -451,6 +476,26 @@
         return days;
     }
 
+    private Integer resolveEnabledValue(Integer requestEnabled, TimingTask oldTimingTask) {
+        if (requestEnabled != null) {
+            return requestEnabled;
+        }
+        if (oldTimingTask != null) {
+            if (oldTimingTask.getIsEnabled() != null) {
+                return oldTimingTask.getIsEnabled();
+            }
+            return oldTimingTask.isActive() ? ENABLED : DISABLED;
+        }
+        return ENABLED;
+    }
+
+    private boolean isEnabled(Integer enabledValue, boolean activeFallback) {
+        if (enabledValue != null) {
+            return ENABLED == enabledValue;
+        }
+        return activeFallback;
+    }
+
 
 
     @Override
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index 8e004e4..0beecd2 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -160,6 +160,7 @@
 
     }
 
+    //鍒犻櫎鍑哄簱璁板綍
     public void deleteStockOutRecord(Long recordId, String recordType) {
         StockOutRecord one = stockOutRecordService.getOne(new QueryWrapper<StockOutRecord>()
                 .lambda().eq(StockOutRecord::getRecordId, recordId)
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
index d0dab50..31cdc79 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
@@ -3,25 +3,40 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.production.bean.dto.ProductionBomStructureDto;
 import com.ruoyi.production.bean.vo.ProductionBomStructureVo;
 import com.ruoyi.production.mapper.ProductionBomStructureMapper;
 import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
 import com.ruoyi.production.mapper.ProductionOrderBomMapper;
 import com.ruoyi.production.mapper.ProductionOrderMapper;
+import com.ruoyi.production.mapper.ProductionOrderRoutingMapper;
 import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper;
+import com.ruoyi.production.mapper.ProductionOrderRoutingOperationParamMapper;
+import com.ruoyi.production.mapper.ProductionProductMainMapper;
 import com.ruoyi.production.pojo.ProductionBomStructure;
 import com.ruoyi.production.pojo.ProductionOperationTask;
 import com.ruoyi.production.pojo.ProductionOrder;
 import com.ruoyi.production.pojo.ProductionOrderBom;
+import com.ruoyi.production.pojo.ProductionOrderRouting;
 import com.ruoyi.production.pojo.ProductionOrderRoutingOperation;
+import com.ruoyi.production.pojo.ProductionOrderRoutingOperationParam;
+import com.ruoyi.production.pojo.ProductionProductMain;
 import com.ruoyi.production.service.ProductionBomStructureService;
+import com.ruoyi.technology.mapper.TechnologyOperationMapper;
+import com.ruoyi.technology.mapper.TechnologyOperationParamMapper;
+import com.ruoyi.technology.mapper.TechnologyParamMapper;
+import com.ruoyi.technology.pojo.TechnologyOperation;
+import com.ruoyi.technology.pojo.TechnologyOperationParam;
+import com.ruoyi.technology.pojo.TechnologyParam;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -40,8 +55,14 @@
     private final ProductionBomStructureMapper productionBomStructureMapper;
     private final ProductionOrderBomMapper productionOrderBomMapper;
     private final ProductionOrderMapper productionOrderMapper;
+    private final ProductionOrderRoutingMapper productionOrderRoutingMapper;
     private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
+    private final ProductionOrderRoutingOperationParamMapper productionOrderRoutingOperationParamMapper;
     private final ProductionOperationTaskMapper productionOperationTaskMapper;
+    private final ProductionProductMainMapper productionProductMainMapper;
+    private final TechnologyOperationMapper technologyOperationMapper;
+    private final TechnologyOperationParamMapper technologyOperationParamMapper;
+    private final TechnologyParamMapper technologyParamMapper;
 
     /**
      * 鏍规嵁BOM鏌ヨ骞剁粍瑁呯粨鏋勬爲銆�
@@ -177,12 +198,17 @@
                 Wrappers.<ProductionBomStructure>lambdaQuery()
                         .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId)
                         .orderByAsc(ProductionBomStructure::getId));
+        //鍚屾闇�姹傛暟閲�
         syncStructureDemandedQuantity(structureList, orderQuantity);
+        Long rootProductModelId = orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId();
+        //鍚屾鐢熶骇宸ヨ壓璺嚎
+        syncRoutingOperationsByBom(currentProductionOrderId, productionOrder, orderBom, structureList, rootProductModelId);
+        //鍚屾宸ュ崟
         syncTaskPlanQuantity(
                 currentProductionOrderId,
                 structureList,
                 orderQuantity,
-                orderBom.getProductModelId() != null ? orderBom.getProductModelId() : productionOrder.getProductModelId());
+                rootProductModelId);
     }
 
     private void syncStructureDemandedQuantity(List<ProductionBomStructure> structureList, BigDecimal orderQuantity) {
@@ -190,19 +216,22 @@
             return;
         }
         List<ProductionBomStructure> updateList = new ArrayList<>();
+        BigDecimal lastProcessDemandedQuantity = orderQuantity;
         for (ProductionBomStructure structure : structureList) {
             if (structure == null || structure.getId() == null) {
                 continue;
             }
-            BigDecimal demandedQuantity = defaultDecimal(structure.getUnitQuantity()).multiply(orderQuantity);
-            if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) {
-                continue;
-            }
+
+            BigDecimal demandedQuantity = lastProcessDemandedQuantity.multiply(defaultDecimal(structure.getUnitQuantity()));
+//            if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) {
+//                continue;
+//            }
             ProductionBomStructure update = new ProductionBomStructure();
             update.setId(structure.getId());
             update.setDemandedQuantity(demandedQuantity);
             updateList.add(update);
             structure.setDemandedQuantity(demandedQuantity);
+            lastProcessDemandedQuantity = demandedQuantity;
         }
         if (!updateList.isEmpty()) {
             this.updateBatchById(updateList);
@@ -220,7 +249,6 @@
         if (taskList == null || taskList.isEmpty()) {
             return;
         }
-
         Set<Long> routingOperationIds = taskList.stream()
                 .map(ProductionOperationTask::getProductionOrderRoutingOperationId)
                 .filter(Objects::nonNull)
@@ -228,23 +256,26 @@
         if (routingOperationIds.isEmpty()) {
             return;
         }
-
         Map<Long, ProductionOrderRoutingOperation> routingOperationMap = productionOrderRoutingOperationMapper
                 .selectBatchIds(routingOperationIds)
                 .stream()
                 .filter(item -> item != null && item.getId() != null)
                 .collect(Collectors.toMap(ProductionOrderRoutingOperation::getId, item -> item, (left, right) -> left));
-        Map<String, BigDecimal> demandedQuantityMap = buildOperationDemandedQuantityMap(structureList, rootProductModelId, orderQuantity);
-
+        // Keep task plan quantities aligned with the same order BOM snapshot demand used during snapshot creation.
+        Map<String, BigDecimal> demandedQuantityMap = buildOperationDemandedQuantityMap(structureList, rootProductModelId);
         for (ProductionOperationTask task : taskList) {
             if (task == null || task.getId() == null || task.getProductionOrderRoutingOperationId() == null) {
                 continue;
             }
             ProductionOrderRoutingOperation routingOperation = routingOperationMap.get(task.getProductionOrderRoutingOperationId());
-            if (routingOperation == null || routingOperation.getTechnologyRoutingOperationId() == null) {
+            if (routingOperation == null) {
                 continue;
             }
-            BigDecimal planQuantity = resolveTaskPlanQuantity(routingOperation, demandedQuantityMap, orderQuantity);
+            BigDecimal planQuantity = resolveTaskPlanQuantity(
+                    routingOperation,
+                    demandedQuantityMap,
+                    orderQuantity,
+                    rootProductModelId);
             if (compareDecimal(task.getPlanQuantity(), planQuantity) == 0) {
                 continue;
             }
@@ -255,9 +286,334 @@
         }
     }
 
+    private void syncRoutingOperationsByBom(Long productionOrderId,
+                                            ProductionOrder productionOrder,
+                                            ProductionOrderBom orderBom,
+                                            List<ProductionBomStructure> structureList,
+                                            Long rootProductModelId) {
+        ProductionOrderRouting orderRouting = getOrCreateOrderRoutingSnapshot(productionOrderId, productionOrder, orderBom, rootProductModelId);
+        List<ProductionOrderRoutingOperation> desiredOperationList = buildDesiredRoutingOperationList(structureList, rootProductModelId);
+        List<ProductionOrderRoutingOperation> existingOperationList = productionOrderRoutingOperationMapper.selectList(
+                Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
+                        .eq(ProductionOrderRoutingOperation::getOrderRoutingId, orderRouting.getId())
+                        .eq(ProductionOrderRoutingOperation::getProductionOrderId, productionOrderId)
+                        .orderByAsc(ProductionOrderRoutingOperation::getDragSort)
+                        .orderByAsc(ProductionOrderRoutingOperation::getId));
+        Map<String, Deque<ProductionOrderRoutingOperation>> existingBucketMap = buildExistingRoutingOperationBucketMap(existingOperationList);
+        List<ProductionOrderRoutingOperation> finalOperationList = new ArrayList<>();
+        for (ProductionOrderRoutingOperation desiredOperation : desiredOperationList) {
+            String bucketKey = buildRoutingOperationBucketKey(
+                    desiredOperation.getTechnologyOperationId(),
+                    desiredOperation.getProductModelId());
+            Deque<ProductionOrderRoutingOperation> matchedQueue = existingBucketMap.get(bucketKey);
+            ProductionOrderRoutingOperation matchedOperation = matchedQueue == null ? null : matchedQueue.pollFirst();
+            if (matchedOperation == null) {
+                matchedOperation = insertRoutingOperationSnapshot(orderRouting.getId(), productionOrderId, desiredOperation);
+            } else {
+                updateRoutingOperationSnapshotIfNecessary(desiredOperation, orderRouting.getId(), productionOrderId, matchedOperation);
+            }
+            finalOperationList.add(matchedOperation);
+        }
+        for (Deque<ProductionOrderRoutingOperation> queue : existingBucketMap.values()) {
+            while (queue != null && !queue.isEmpty()) {
+                removeRoutingOperationSnapshot(queue.pollFirst());
+            }
+        }
+        syncRoutingOperationTasks(productionOrderId, finalOperationList);
+    }
+
+    private ProductionOrderRouting getOrCreateOrderRoutingSnapshot(Long productionOrderId,
+                                                                   ProductionOrder productionOrder,
+                                                                   ProductionOrderBom orderBom,
+                                                                   Long rootProductModelId) {
+        ProductionOrderRouting orderRouting = productionOrderRoutingMapper.selectOne(
+                Wrappers.<ProductionOrderRouting>lambdaQuery()
+                        .eq(ProductionOrderRouting::getProductionOrderId, productionOrderId)
+                        .orderByDesc(ProductionOrderRouting::getId)
+                        .last("limit 1"));
+        if (orderRouting == null) {
+            orderRouting = new ProductionOrderRouting();
+            orderRouting.setProductionOrderId(productionOrderId);
+            orderRouting.setProductModelId(rootProductModelId);
+            orderRouting.setTechnologyRoutingId(productionOrder == null ? null : productionOrder.getTechnologyRoutingId());
+            orderRouting.setBomId(orderBom == null ? null : orderBom.getBomId());
+            orderRouting.setOrderBomId(orderBom == null ? null : orderBom.getId());
+            productionOrderRoutingMapper.insert(orderRouting);
+            return orderRouting;
+        }
+        ProductionOrderRouting update = new ProductionOrderRouting();
+        update.setId(orderRouting.getId());
+        boolean changed = false;
+        if (!Objects.equals(orderRouting.getProductModelId(), rootProductModelId)) {
+            update.setProductModelId(rootProductModelId);
+            orderRouting.setProductModelId(rootProductModelId);
+            changed = true;
+        }
+        Long technologyRoutingId = productionOrder == null ? null : productionOrder.getTechnologyRoutingId();
+        if (!Objects.equals(orderRouting.getTechnologyRoutingId(), technologyRoutingId)) {
+            update.setTechnologyRoutingId(technologyRoutingId);
+            orderRouting.setTechnologyRoutingId(technologyRoutingId);
+            changed = true;
+        }
+        Long bomId = orderBom == null ? null : orderBom.getBomId();
+        if (!Objects.equals(orderRouting.getBomId(), bomId)) {
+            update.setBomId(bomId);
+            orderRouting.setBomId(bomId);
+            changed = true;
+        }
+        Long orderBomId = orderBom == null ? null : orderBom.getId();
+        if (!Objects.equals(orderRouting.getOrderBomId(), orderBomId)) {
+            update.setOrderBomId(orderBomId);
+            orderRouting.setOrderBomId(orderBomId);
+            changed = true;
+        }
+        if (changed) {
+            productionOrderRoutingMapper.updateById(update);
+        }
+        return orderRouting;
+    }
+
+    private List<ProductionOrderRoutingOperation> buildDesiredRoutingOperationList(List<ProductionBomStructure> structureList,
+                                                                                   Long rootProductModelId) {
+        if (structureList == null || structureList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        Map<Long, ProductionBomStructure> structureById = structureList.stream()
+                .filter(item -> item != null && item.getId() != null)
+                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
+        Map<String, ProductionBomStructure> uniqueOperationMap = new LinkedHashMap<>();
+        for (ProductionBomStructure bomStructure : structureList) {
+            if (bomStructure == null || bomStructure.getTechnologyOperationId() == null) {
+                continue;
+            }
+            Long outputProductModelId = resolveOutputProductModelId(resolveOperationOutputNode(bomStructure, structureById), rootProductModelId);
+            uniqueOperationMap.putIfAbsent(buildBomOperationDedupKey(bomStructure, outputProductModelId), bomStructure);
+        }
+        List<ProductionOrderRoutingOperation> desiredOperationList = new ArrayList<>();
+        int dragSort = 1;
+        for (ProductionBomStructure bomStructure : uniqueOperationMap.values()) {
+            Long outputProductModelId = resolveOutputProductModelId(resolveOperationOutputNode(bomStructure, structureById), rootProductModelId);
+            TechnologyOperation technologyOperation = getTechnologyOperation(bomStructure.getTechnologyOperationId());
+            ProductionOrderRoutingOperation routingOperation = new ProductionOrderRoutingOperation();
+            routingOperation.setProductModelId(outputProductModelId);
+            routingOperation.setTechnologyOperationId(bomStructure.getTechnologyOperationId());
+            routingOperation.setOperationName(technologyOperation == null ? null : technologyOperation.getName());
+            routingOperation.setIsQuality(technologyOperation == null ? null : technologyOperation.getIsQuality());
+            routingOperation.setIsProduction(technologyOperation == null ? null : technologyOperation.getIsProduction());
+            routingOperation.setType(technologyOperation == null ? null : technologyOperation.getType());
+            routingOperation.setDragSort(dragSort++);
+            desiredOperationList.add(routingOperation);
+        }
+        return desiredOperationList;
+    }
+
+    private Map<String, Deque<ProductionOrderRoutingOperation>> buildExistingRoutingOperationBucketMap(List<ProductionOrderRoutingOperation> existingOperationList) {
+        Map<String, Deque<ProductionOrderRoutingOperation>> existingBucketMap = new LinkedHashMap<>();
+        if (existingOperationList == null || existingOperationList.isEmpty()) {
+            return existingBucketMap;
+        }
+        for (ProductionOrderRoutingOperation routingOperation : existingOperationList) {
+            String bucketKey = buildRoutingOperationBucketKey(
+                    routingOperation.getTechnologyOperationId(),
+                    routingOperation.getProductModelId());
+            existingBucketMap.computeIfAbsent(bucketKey, key -> new ArrayDeque<>()).addLast(routingOperation);
+        }
+        return existingBucketMap;
+    }
+
+    private ProductionOrderRoutingOperation insertRoutingOperationSnapshot(Long orderRoutingId,
+                                                                           Long productionOrderId,
+                                                                           ProductionOrderRoutingOperation desiredOperation) {
+        ProductionOrderRoutingOperation insert = new ProductionOrderRoutingOperation();
+        insert.setOrderRoutingId(orderRoutingId);
+        insert.setProductionOrderId(productionOrderId);
+        insert.setProductModelId(desiredOperation.getProductModelId());
+        insert.setTechnologyOperationId(desiredOperation.getTechnologyOperationId());
+        insert.setOperationName(desiredOperation.getOperationName());
+        insert.setIsQuality(desiredOperation.getIsQuality());
+        insert.setIsProduction(desiredOperation.getIsProduction());
+        insert.setType(desiredOperation.getType());
+        insert.setDragSort(desiredOperation.getDragSort());
+        productionOrderRoutingOperationMapper.insert(insert);
+        syncRoutingOperationParams(insert.getId(), productionOrderId, insert.getTechnologyOperationId());
+        return insert;
+    }
+
+    private void updateRoutingOperationSnapshotIfNecessary(ProductionOrderRoutingOperation currentOperation,
+                                                           Long orderRoutingId,
+                                                           Long productionOrderId,
+                                                           ProductionOrderRoutingOperation desiredOperation) {
+        if (currentOperation == null || currentOperation.getId() == null) {
+            return;
+        }
+        ProductionOrderRoutingOperation update = new ProductionOrderRoutingOperation();
+        update.setId(currentOperation.getId());
+        boolean changed = false;
+        if (!Objects.equals(currentOperation.getOrderRoutingId(), orderRoutingId)) {
+            update.setOrderRoutingId(orderRoutingId);
+            currentOperation.setOrderRoutingId(orderRoutingId);
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getProductionOrderId(), productionOrderId)) {
+            update.setProductionOrderId(productionOrderId);
+            currentOperation.setProductionOrderId(productionOrderId);
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getProductModelId(), desiredOperation.getProductModelId())) {
+            update.setProductModelId(desiredOperation.getProductModelId());
+            currentOperation.setProductModelId(desiredOperation.getProductModelId());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getTechnologyOperationId(), desiredOperation.getTechnologyOperationId())) {
+            update.setTechnologyOperationId(desiredOperation.getTechnologyOperationId());
+            currentOperation.setTechnologyOperationId(desiredOperation.getTechnologyOperationId());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getOperationName(), desiredOperation.getOperationName())) {
+            update.setOperationName(desiredOperation.getOperationName());
+            currentOperation.setOperationName(desiredOperation.getOperationName());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getIsQuality(), desiredOperation.getIsQuality())) {
+            update.setIsQuality(desiredOperation.getIsQuality());
+            currentOperation.setIsQuality(desiredOperation.getIsQuality());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getIsProduction(), desiredOperation.getIsProduction())) {
+            update.setIsProduction(desiredOperation.getIsProduction());
+            currentOperation.setIsProduction(desiredOperation.getIsProduction());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getType(), desiredOperation.getType())) {
+            update.setType(desiredOperation.getType());
+            currentOperation.setType(desiredOperation.getType());
+            changed = true;
+        }
+        if (!Objects.equals(currentOperation.getDragSort(), desiredOperation.getDragSort())) {
+            update.setDragSort(desiredOperation.getDragSort());
+            currentOperation.setDragSort(desiredOperation.getDragSort());
+            changed = true;
+        }
+        if (changed) {
+            productionOrderRoutingOperationMapper.updateById(update);
+        }
+    }
+
+    private void removeRoutingOperationSnapshot(ProductionOrderRoutingOperation routingOperation) {
+        if (routingOperation == null || routingOperation.getId() == null) {
+            return;
+        }
+        ProductionOperationTask task = productionOperationTaskMapper.selectOne(
+                Wrappers.<ProductionOperationTask>lambdaQuery()
+                        .eq(ProductionOperationTask::getProductionOrderRoutingOperationId, routingOperation.getId())
+                        .last("limit 1"));
+        if (task != null) {
+            validateTaskCanRemove(task);
+            productionOperationTaskMapper.deleteById(task.getId());
+        }
+        productionOrderRoutingOperationParamMapper.delete(
+                Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
+                        .eq(ProductionOrderRoutingOperationParam::getProductionOrderRoutingOperationId, routingOperation.getId()));
+        productionOrderRoutingOperationMapper.deleteById(routingOperation.getId());
+    }
+
+    private void syncRoutingOperationTasks(Long productionOrderId, List<ProductionOrderRoutingOperation> routingOperationList) {
+        if (routingOperationList == null || routingOperationList.isEmpty()) {
+            return;
+        }
+        List<Long> routingOperationIdList = routingOperationList.stream()
+                .map(ProductionOrderRoutingOperation::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (routingOperationIdList.isEmpty()) {
+            return;
+        }
+        Map<Long, ProductionOperationTask> taskByRoutingOperationId = productionOperationTaskMapper.selectList(
+                        Wrappers.<ProductionOperationTask>lambdaQuery()
+                                .in(ProductionOperationTask::getProductionOrderRoutingOperationId, routingOperationIdList)
+                                .orderByAsc(ProductionOperationTask::getId))
+                .stream()
+                .filter(item -> item != null && item.getProductionOrderRoutingOperationId() != null)
+                .collect(Collectors.toMap(
+                        ProductionOperationTask::getProductionOrderRoutingOperationId,
+                        item -> item,
+                        (left, right) -> left,
+                        LinkedHashMap::new));
+        for (int i = 0; i < routingOperationList.size(); i++) {
+            ProductionOrderRoutingOperation routingOperation = routingOperationList.get(i);
+            if (routingOperation == null || routingOperation.getId() == null) {
+                continue;
+            }
+            boolean shouldHaveTask = i == routingOperationList.size() - 1 || Boolean.TRUE.equals(routingOperation.getIsProduction());
+            ProductionOperationTask existingTask = taskByRoutingOperationId.get(routingOperation.getId());
+            if (shouldHaveTask) {
+                if (existingTask == null) {
+                    ProductionOperationTask task = new ProductionOperationTask();
+                    task.setProductionOrderId(productionOrderId);
+                    task.setProductionOrderRoutingOperationId(routingOperation.getId());
+                    task.setPlanQuantity(BigDecimal.ZERO);
+                    task.setCompleteQuantity(BigDecimal.ZERO);
+                    task.setWorkOrderNo(generateNextTaskNo());
+                    task.setStatus(2);
+                    productionOperationTaskMapper.insert(task);
+                }
+                continue;
+            }
+            if (existingTask != null) {
+                validateTaskCanRemove(existingTask);
+                productionOperationTaskMapper.deleteById(existingTask.getId());
+            }
+        }
+    }
+
+    private void validateTaskCanRemove(ProductionOperationTask task) {
+        if (task == null || task.getId() == null) {
+            return;
+        }
+        if (defaultDecimal(task.getCompleteQuantity()).compareTo(BigDecimal.ZERO) > 0) {
+            throw new ServiceException("宸ュ簭宸蹭骇鐢熸姤宸ヨ褰曪紝鏃犳硶鏍规嵁 BOM 鍙樻洿鍒犻櫎瀵瑰簲宸ュ簭蹇収");
+        }
+        long reportCount = productionProductMainMapper.selectCount(
+                Wrappers.<ProductionProductMain>lambdaQuery()
+                        .eq(ProductionProductMain::getProductionOperationTaskId, task.getId()));
+        if (reportCount > 0) {
+            throw new ServiceException("宸ュ簭宸蹭骇鐢熸姤宸ヨ褰曪紝鏃犳硶鏍规嵁 BOM 鍙樻洿鍒犻櫎瀵瑰簲宸ュ崟");
+        }
+    }
+
+    private void syncRoutingOperationParams(Long routingOperationId, Long productionOrderId, Long technologyOperationId) {
+        if (routingOperationId == null || technologyOperationId == null) {
+            return;
+        }
+        List<TechnologyOperationParam> operationParamList = technologyOperationParamMapper.selectList(
+                Wrappers.<TechnologyOperationParam>lambdaQuery()
+                        .eq(TechnologyOperationParam::getTechnologyOperationId, technologyOperationId)
+                        .orderByAsc(TechnologyOperationParam::getId));
+        for (TechnologyOperationParam operationParam : operationParamList) {
+            TechnologyParam technologyParam = technologyParamMapper.selectById(operationParam.getTechnologyParamId());
+            if (technologyParam == null) {
+                continue;
+            }
+            ProductionOrderRoutingOperationParam snapshot = new ProductionOrderRoutingOperationParam();
+            snapshot.setProductionOrderId(productionOrderId);
+            snapshot.setProductionOrderRoutingOperationId(routingOperationId);
+            snapshot.setTechnologyOperationId(operationParam.getTechnologyOperationId());
+            snapshot.setTechnologyOperationParamId(operationParam.getId());
+            snapshot.setParamId(technologyParam.getId());
+            snapshot.setParamCode(technologyParam.getParamCode());
+            snapshot.setParamName(technologyParam.getParamName());
+            snapshot.setParamType(technologyParam.getParamType());
+            snapshot.setParamFormat(technologyParam.getParamFormat());
+            snapshot.setUnit(technologyParam.getUnit());
+            snapshot.setIsRequired(technologyParam.getIsRequired());
+            snapshot.setRemark(technologyParam.getRemark());
+            snapshot.setStandardValue(operationParam.getStandardValue());
+            productionOrderRoutingOperationParamMapper.insert(snapshot);
+        }
+    }
+
     private Map<String, BigDecimal> buildOperationDemandedQuantityMap(List<ProductionBomStructure> structureList,
-                                                                      Long rootProductModelId,
-                                                                      BigDecimal orderQuantity) {
+                                                                      Long rootProductModelId) {
         if (structureList == null || structureList.isEmpty()) {
             return Collections.emptyMap();
         }
@@ -265,26 +621,44 @@
                 .filter(item -> item != null && item.getId() != null)
                 .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
         Map<String, BigDecimal> demandedQuantityMap = new HashMap<>();
+        Set<String> mergedOutputNodeKeySet = new HashSet<>();
         for (ProductionBomStructure bomStructure : structureList) {
-            if (bomStructure == null || bomStructure.getTechnologyOperationId() == null || bomStructure.getUnitQuantity() == null) {
+            if (bomStructure == null || bomStructure.getTechnologyOperationId() == null) {
                 continue;
             }
-            Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, rootProductModelId);
+            // Resolve the output node first, then read the output node demand for the task plan quantity.
+            ProductionBomStructure outputNode = resolveOperationOutputNode(bomStructure, structureById);
+            Long outputProductModelId = resolveOutputProductModelId(outputNode, rootProductModelId);
+            if (outputProductModelId == null) {
+                continue;
+            }
+            String mergedOutputNodeKey = buildOperationOutputNodeKey(
+                    bomStructure.getTechnologyOperationId(),
+                    outputNode == null ? null : outputNode.getId(),
+                    outputProductModelId);
+            if (!mergedOutputNodeKeySet.add(mergedOutputNodeKey)) {
+                continue;
+            }
+            // Multiple input rows can point to the same output node, so only count that output demand once.
             String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId);
-            demandedQuantityMap.merge(key, bomStructure.getUnitQuantity().multiply(orderQuantity), BigDecimal::add);
+            demandedQuantityMap.merge(key, defaultDecimal(outputNode == null ? null : outputNode.getDemandedQuantity()), BigDecimal::add);
         }
         return demandedQuantityMap;
     }
 
     private BigDecimal resolveTaskPlanQuantity(ProductionOrderRoutingOperation routingOperation,
                                                Map<String, BigDecimal> demandedQuantityMap,
-                                               BigDecimal orderQuantity) {
+                                               BigDecimal orderQuantity,
+                                               Long rootProductModelId) {
         if (routingOperation == null || demandedQuantityMap == null || demandedQuantityMap.isEmpty()) {
             return orderQuantity;
         }
+        Long outputProductModelId = routingOperation.getProductModelId() != null
+                ? routingOperation.getProductModelId()
+                : rootProductModelId;
         String key = buildOperationDemandedQuantityKey(
                 routingOperation.getTechnologyOperationId(),
-                routingOperation.getProductModelId());
+                outputProductModelId);
         BigDecimal planQuantity = demandedQuantityMap.get(key);
         return planQuantity != null ? planQuantity : orderQuantity;
     }
@@ -293,21 +667,64 @@
         return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId);
     }
 
-    private Long resolveOutputProductModelId(ProductionBomStructure bomStructure,
-                                             Map<Long, ProductionBomStructure> structureById,
-                                             Long rootProductModelId) {
+    private String buildRoutingOperationBucketKey(Long operationId, Long outputProductModelId) {
+        return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId);
+    }
+
+    private String buildBomOperationDedupKey(ProductionBomStructure bomStructure, Long outputProductModelId) {
+        Long operationId = bomStructure == null ? null : bomStructure.getTechnologyOperationId();
+        Long parentId = bomStructure == null ? null : bomStructure.getParentId();
+        return operationId + "#" + outputProductModelId + "#" + parentId;
+    }
+
+    private String buildOperationOutputNodeKey(Long operationId, Long outputNodeId, Long outputProductModelId) {
+        return String.valueOf(operationId) + "#" + String.valueOf(outputNodeId) + "#" + String.valueOf(outputProductModelId);
+    }
+
+    private ProductionBomStructure resolveOperationOutputNode(ProductionBomStructure bomStructure,
+                                                              Map<Long, ProductionBomStructure> structureById) {
         if (bomStructure == null) {
+            return null;
+        }
+        // The root node is the first output node; other rows use their direct parent as the current operation output.
+        if (bomStructure.getParentId() == null) {
+            return bomStructure;
+        }
+        ProductionBomStructure parent = structureById.get(bomStructure.getParentId());
+        return parent != null ? parent : bomStructure;
+    }
+
+    private Long resolveOutputProductModelId(ProductionBomStructure outputNode,
+                                             Long rootProductModelId) {
+        if (outputNode == null) {
             return rootProductModelId;
         }
-        Long parentId = bomStructure.getParentId();
-        if (parentId == null) {
-            return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId();
+        return outputNode.getProductModelId() != null ? outputNode.getProductModelId() : rootProductModelId;
+    }
+
+    private TechnologyOperation getTechnologyOperation(Long technologyOperationId) {
+        if (technologyOperationId == null) {
+            return null;
         }
-        ProductionBomStructure parent = structureById.get(parentId);
-        if (parent != null && parent.getProductModelId() != null) {
-            return parent.getProductModelId();
+        return technologyOperationMapper.selectById(technologyOperationId);
+    }
+
+    private String generateNextTaskNo() {
+        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        ProductionOperationTask latestTask = productionOperationTaskMapper.selectOne(
+                Wrappers.<ProductionOperationTask>lambdaQuery()
+                        .likeRight(ProductionOperationTask::getWorkOrderNo, "GD" + datePrefix)
+                        .orderByDesc(ProductionOperationTask::getWorkOrderNo)
+                        .last("limit 1"));
+        int sequenceNumber = 1;
+        if (latestTask != null && latestTask.getWorkOrderNo() != null && latestTask.getWorkOrderNo().startsWith("GD" + datePrefix)) {
+            try {
+                sequenceNumber = Integer.parseInt(latestTask.getWorkOrderNo().substring(("GD" + datePrefix).length())) + 1;
+            } catch (NumberFormatException ignored) {
+                sequenceNumber = 1;
+            }
         }
-        return rootProductModelId != null ? rootProductModelId : bomStructure.getProductModelId();
+        return "GD" + String.format("%s%03d", datePrefix, sequenceNumber);
     }
 
     private BigDecimal defaultDecimal(BigDecimal value) {
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
index 6e9457d..bd6f2df 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
@@ -23,9 +23,11 @@
 import com.ruoyi.production.bean.dto.ProductionOperationTaskDto;
 import com.ruoyi.production.bean.vo.ProductionOperationTaskVo;
 import com.ruoyi.production.mapper.ProductionOrderMapper;
+import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper;
 import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
 import com.ruoyi.production.pojo.ProductionOrder;
 import com.ruoyi.production.pojo.ProductionOperationTask;
+import com.ruoyi.production.pojo.ProductionOrderRoutingOperation;
 import com.ruoyi.production.service.ProductionOperationTaskService;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
@@ -48,6 +50,7 @@
 
     private final SysUserMapper sysUserMapper;
     private final ProductionOrderMapper productionOrderMapper;
+    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
 
     private final FileUtil fileUtil;
 
@@ -61,6 +64,7 @@
         // 鍒嗛〉鏌ヨ鐢熶骇宸ュ簭浠诲姟
         Page<ProductionOperationTaskVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
         IPage<ProductionOperationTaskVo> result = baseMapper.pageProductionOperationTask(voPage, dto);
+        fillOperationTypes(result.getRecords());
         fillUserNames(result.getRecords());
         return result;
     }
@@ -69,6 +73,7 @@
     public List<ProductionOperationTaskVo> listProductionOperationTask(ProductionOperationTaskDto dto) {
         // 鏌ヨ宸ュ簭浠诲姟鍒楄〃
         List<ProductionOperationTaskVo> result = BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class);
+        fillOperationTypes(result);
         fillUserNames(result);
         return result;
     }
@@ -81,6 +86,7 @@
             return null;
         }
         ProductionOperationTaskVo vo = BeanUtil.copyProperties(item, ProductionOperationTaskVo.class);
+        fillOperationTypes(Collections.singletonList(vo));
         if (item.getProductionOrderId() != null) {
             ProductionOrder productionOrder = productionOrderMapper.selectById(item.getProductionOrderId());
             if (productionOrder != null) {
@@ -370,6 +376,38 @@
     @Override
     public List<ProductionOperationTaskVo> getOperation(ProductionOperationTaskDto dto) {
         // 鏌ヨ宸ュ簭浠诲姟鍒楄〃
-        return baseMapper.getOperation(dto);
+        List<ProductionOperationTaskVo> result = baseMapper.getOperation(dto);
+        fillOperationTypes(result);
+        return result;
+    }
+
+    private void fillOperationTypes(List<ProductionOperationTaskVo> voList) {
+        // 鍥炲~宸ュ簭绫诲瀷锛�0 璁℃椂 / 1 璁′欢锛�
+        if (voList == null || voList.isEmpty()) {
+            return;
+        }
+        Set<Long> operationIds = voList.stream()
+                .filter(Objects::nonNull)
+                .map(ProductionOperationTaskVo::getProductionOrderRoutingOperationId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toCollection(LinkedHashSet::new));
+        if (operationIds.isEmpty()) {
+            return;
+        }
+        Map<Long, Integer> typeByOperationId = productionOrderRoutingOperationMapper
+                .selectBatchIds(new ArrayList<>(operationIds))
+                .stream()
+                .filter(Objects::nonNull)
+                .collect(Collectors.toMap(
+                        ProductionOrderRoutingOperation::getId,
+                        ProductionOrderRoutingOperation::getType,
+                        (left, right) -> left
+                ));
+        for (ProductionOperationTaskVo vo : voList) {
+            if (vo == null || vo.getType() != null || vo.getProductionOrderRoutingOperationId() == null) {
+                continue;
+            }
+            vo.setType(typeByOperationId.get(vo.getProductionOrderRoutingOperationId()));
+        }
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
index 5a8ea9c..ecdb37c 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -245,7 +245,18 @@
                         .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())
                         .orderByDesc(TechnologyRoutingOperation::getDragSort)
                         .orderByDesc(TechnologyRoutingOperation::getId));
-        Map<String, BigDecimal> operationDemandedQuantityMap = buildOperationDemandedQuantityMap(technologyRouting, productionOrder);
+        // Build task plan quantities from order BOM snapshot demand instead of recomputing from technology BOM units.
+        Long rootProductModelId = orderBom != null && orderBom.getProductModelId() != null
+                ? orderBom.getProductModelId()
+                : productionOrder.getProductModelId();
+        List<ProductionBomStructure> orderBomStructureList = orderBom == null || orderBom.getId() == null
+                ? Collections.emptyList()
+                : productionBomStructureMapper.selectList(
+                Wrappers.<ProductionBomStructure>lambdaQuery()
+                        .eq(ProductionBomStructure::getProductionOrderBomId, orderBom.getId())
+                        .orderByAsc(ProductionBomStructure::getId));
+        Map<String, BigDecimal> operationDemandedQuantityMap =
+                buildOperationDemandedQuantityMap(orderBomStructureList, rootProductModelId);
         Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds(
         // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
                         routingOperations.stream()
@@ -279,7 +290,11 @@
                 ProductionOperationTask task = new ProductionOperationTask();
                 task.setProductionOrderRoutingOperationId(targetOperation.getId());
                 task.setProductionOrderId(productionOrder.getId());
-                task.setPlanQuantity(resolveTaskPlanQuantity(sourceOperation, operationDemandedQuantityMap, productionOrder));
+                task.setPlanQuantity(resolveTaskPlanQuantity(
+                        sourceOperation,
+                        operationDemandedQuantityMap,
+                        productionOrder,
+                        rootProductModelId));
                 task.setCompleteQuantity(BigDecimal.ZERO);
                 task.setWorkOrderNo(generateNextTaskNo());
                 task.setStatus(2);
@@ -314,47 +329,52 @@
         return syncedParamCount;
     }
 
-    private Map<String, BigDecimal> buildOperationDemandedQuantityMap(TechnologyRouting technologyRouting,
-                                                                      ProductionOrder productionOrder) {
-        if (technologyRouting == null || technologyRouting.getBomId() == null) {
+    private Map<String, BigDecimal> buildOperationDemandedQuantityMap(List<ProductionBomStructure> bomStructures,
+                                                                      Long rootProductModelId) {
+        if (bomStructures == null || bomStructures.isEmpty()) {
             return Collections.emptyMap();
         }
-        BigDecimal orderQuantity = defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
-        List<TechnologyBomStructure> bomStructures = technologyBomStructureMapper.selectList(
-                Wrappers.<TechnologyBomStructure>lambdaQuery()
-                        .eq(TechnologyBomStructure::getBomId, technologyRouting.getBomId())
-                        .isNotNull(TechnologyBomStructure::getOperationId)
-                        .orderByAsc(TechnologyBomStructure::getId));
-        if (bomStructures.isEmpty()) {
-            return Collections.emptyMap();
-        }
-
-        Map<Long, TechnologyBomStructure> structureById = bomStructures.stream()
+        Map<Long, ProductionBomStructure> structureById = bomStructures.stream()
                 .filter(item -> item != null && item.getId() != null)
-                .collect(Collectors.toMap(TechnologyBomStructure::getId, item -> item, (left, right) -> left));
+                .collect(Collectors.toMap(ProductionBomStructure::getId, item -> item, (left, right) -> left));
         Map<String, BigDecimal> demandedQuantityMap = new HashMap<>();
-        for (TechnologyBomStructure bomStructure : bomStructures) {
-            if (bomStructure == null || bomStructure.getOperationId() == null) {
+        Set<String> mergedOutputNodeKeySet = new HashSet<>();
+        for (ProductionBomStructure bomStructure : bomStructures) {
+            if (bomStructure == null || bomStructure.getTechnologyOperationId() == null) {
                 continue;
             }
-            BigDecimal unitQuantity = bomStructure.getUnitQuantity();
-            if (unitQuantity == null) {
+            // The BOM row points to the producing operation; task quantity should come from that operation's output node.
+            ProductionBomStructure outputNode = resolveOperationOutputNode(bomStructure, structureById);
+            Long outputProductModelId = resolveOutputProductModelId(outputNode, rootProductModelId);
+            if (outputProductModelId == null) {
                 continue;
             }
-            Long outputProductModelId = resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId());
-            String key = buildOperationDemandedQuantityKey(bomStructure.getOperationId(), outputProductModelId);
-            demandedQuantityMap.merge(key, unitQuantity.multiply(orderQuantity), BigDecimal::add);
+            String mergedOutputNodeKey = buildOperationOutputNodeKey(
+                    bomStructure.getTechnologyOperationId(),
+                    outputNode == null ? null : outputNode.getId(),
+                    outputProductModelId);
+            if (!mergedOutputNodeKeySet.add(mergedOutputNodeKey)) {
+                continue;
+            }
+            // demandedQuantity is already the order-level required output quantity for the current output node.
+            BigDecimal demandedQuantity = defaultDecimal(outputNode == null ? null : outputNode.getDemandedQuantity());
+            String key = buildOperationDemandedQuantityKey(bomStructure.getTechnologyOperationId(), outputProductModelId);
+            demandedQuantityMap.merge(key, demandedQuantity, BigDecimal::add);
         }
         return demandedQuantityMap;
     }
 
     private BigDecimal resolveTaskPlanQuantity(TechnologyRoutingOperation sourceOperation,
                                                Map<String, BigDecimal> operationDemandedQuantityMap,
-                                               ProductionOrder productionOrder) {
+                                               ProductionOrder productionOrder,
+                                               Long rootProductModelId) {
         if (sourceOperation == null || operationDemandedQuantityMap == null || operationDemandedQuantityMap.isEmpty()) {
             return defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
         }
-        String key = buildOperationDemandedQuantityKey(sourceOperation.getTechnologyOperationId(), sourceOperation.getProductModelId());
+        Long outputProductModelId = sourceOperation.getProductModelId() != null
+                ? sourceOperation.getProductModelId()
+                : rootProductModelId;
+        String key = buildOperationDemandedQuantityKey(sourceOperation.getTechnologyOperationId(), outputProductModelId);
         BigDecimal planQuantity = operationDemandedQuantityMap.get(key);
         return planQuantity != null ? planQuantity : defaultDecimal(productionOrder == null ? null : productionOrder.getQuantity());
     }
@@ -363,21 +383,29 @@
         return String.valueOf(operationId) + "#" + String.valueOf(outputProductModelId);
     }
 
-    private Long resolveOutputProductModelId(TechnologyBomStructure bomStructure,
-                                             Map<Long, TechnologyBomStructure> structureById,
-                                             Long routingProductModelId) {
+    private String buildOperationOutputNodeKey(Long operationId, Long outputNodeId, Long outputProductModelId) {
+        return String.valueOf(operationId) + "#" + String.valueOf(outputNodeId) + "#" + String.valueOf(outputProductModelId);
+    }
+
+    private ProductionBomStructure resolveOperationOutputNode(ProductionBomStructure bomStructure,
+                                                              Map<Long, ProductionBomStructure> structureById) {
         if (bomStructure == null) {
-            return routingProductModelId;
+            return null;
         }
-        Long parentId = bomStructure.getParentId();
-        if (parentId == null) {
-            return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
+        // The root node is the first output node; child rows use their direct parent as the current operation output.
+        if (bomStructure.getParentId() == null) {
+            return bomStructure;
         }
-        TechnologyBomStructure parent = structureById.get(parentId);
-        if (parent != null && parent.getProductModelId() != null) {
-            return parent.getProductModelId();
+        ProductionBomStructure parent = structureById.get(bomStructure.getParentId());
+        return parent != null ? parent : bomStructure;
+    }
+
+    private Long resolveOutputProductModelId(ProductionBomStructure outputNode,
+                                             Long rootProductModelId) {
+        if (outputNode == null) {
+            return rootProductModelId;
         }
-        return routingProductModelId != null ? routingProductModelId : bomStructure.getProductModelId();
+        return outputNode.getProductModelId() != null ? outputNode.getProductModelId() : rootProductModelId;
     }
 
     private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) {
@@ -409,6 +437,7 @@
         productionOrderBomMapper.insert(orderBom);
 
         Map<Long, Long> idMap = new HashMap<>();
+        BigDecimal lastProcessDemandedQuantity = orderQuantity;
         for (TechnologyBomStructure source : structureList) {
             // 瀛愯妭鐐� parentId 闇�瑕佹槧灏勬垚鏂板揩鐓ц妭鐐� id锛屾墠鑳戒繚鐣欏師濮� BOM 灞傜骇銆�
             ProductionBomStructure target = new ProductionBomStructure();
@@ -418,10 +447,11 @@
             target.setProductModelId(source.getProductModelId());
             target.setTechnologyOperationId(source.getOperationId());
             target.setUnitQuantity(source.getUnitQuantity());
-            target.setDemandedQuantity(source.getUnitQuantity().multiply(orderQuantity));
+            target.setDemandedQuantity(lastProcessDemandedQuantity.multiply(source.getUnitQuantity()));
             target.setUnit(source.getUnit());
             productionBomStructureMapper.insert(target);
             idMap.put(source.getId(), target.getId());
+            lastProcessDemandedQuantity = target.getDemandedQuantity();
         }
         return orderBom;
     }
diff --git a/src/main/java/com/ruoyi/project/system/controller/SysLoginController.java b/src/main/java/com/ruoyi/project/system/controller/SysLoginController.java
index e08c368..e5c5bfa 100644
--- a/src/main/java/com/ruoyi/project/system/controller/SysLoginController.java
+++ b/src/main/java/com/ruoyi/project/system/controller/SysLoginController.java
@@ -12,10 +12,10 @@
 import com.ruoyi.project.system.domain.SysMenu;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.domain.vo.SysUserDeptVo;
-import com.ruoyi.project.system.mapper.SysDeptMapper;
-import com.ruoyi.project.system.service.ISysMenuService;
-import com.ruoyi.project.system.service.ISysUserDeptService;
-import com.ruoyi.project.system.service.ISysUserService;
+import com.ruoyi.project.system.mapper.SysDeptMapper;
+import com.ruoyi.project.system.service.ISysMenuService;
+import com.ruoyi.project.system.service.ISysUserDeptService;
+import com.ruoyi.project.system.service.ISysUserService;
 import lombok.AllArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.ObjectUtils;
@@ -41,11 +41,11 @@
 {
     private SysLoginService loginService;
     private ISysMenuService menuService;
-    private SysPermissionService permissionService;
-    private TokenService tokenService;
-    private ISysUserDeptService userDeptService;
-    private ISysUserService userService;
-    private SysDeptMapper sysDeptMapper;
+    private SysPermissionService permissionService;
+    private TokenService tokenService;
+    private ISysUserDeptService userDeptService;
+    private ISysUserService userService;
+    private SysDeptMapper sysDeptMapper;
 
     /**
      * 鐧诲綍鏂规硶
@@ -73,17 +73,7 @@
     public AjaxResult getInfo()
     {
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        SysUser user = userService.selectUserById(loginUser.getUserId());
-        if (user == null)
-        {
-            user = loginUser.getUser();
-        }
-        else
-        {
-            loginUser.setUser(user);
-            loginUser.setAiEnabled(user.getAiEnabled());
-            tokenService.setLoginUser(loginUser);
-        }
+        SysUser user = loginUser.getUser();
         // 鑾峰彇褰撳墠鐧诲綍鍏徃
         Long tenantId = loginUser.getTenantId();
         if(null != tenantId){
@@ -102,12 +92,12 @@
             loginUser.setPermissions(permissions);
             tokenService.refreshToken(loginUser);
         }
-        AjaxResult ajax = AjaxResult.success();
-        ajax.put("user", user);
-        ajax.put("aiEnabled", loginUser.getAiEnabled());
-        ajax.put("roles", roles);
-        ajax.put("permissions", permissions);
-        return ajax;
+        AjaxResult ajax = AjaxResult.success();
+        ajax.put("user", user);
+        ajax.put("aiEnabled", loginUser.getAiEnabled());
+        ajax.put("roles", roles);
+        ajax.put("permissions", permissions);
+        return ajax;
     }
 
     /**
diff --git a/src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java b/src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java
index fdd7503..5003107 100644
--- a/src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java
+++ b/src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java
@@ -5,6 +5,7 @@
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
+import java.io.Serializable;
 import java.math.BigDecimal;
 import java.util.Date;
 
@@ -16,7 +17,7 @@
  */
 @Data
 @TableName("product_record")
-public class ProductRecord {
+public class ProductRecord implements Serializable {
     private static final long serialVersionUID = 1L;
 
     /**
diff --git a/src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java b/src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java
index 3f1690c..793fba6 100644
--- a/src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java
+++ b/src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java
@@ -69,10 +69,10 @@
     private Integer type;
 
     @Schema(description = "浜у搧id")
-    private Integer productId;
+    private Long productId;
 
     @Schema(description = "鍨嬪彿id")
-    private Integer productModelId;
+    private Long productModelId;
 
     private String register;
 
diff --git a/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java b/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
index 9d8a07d..62b047a 100644
--- a/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
+++ b/src/main/java/com/ruoyi/quality/pojo/QualityInspect.java
@@ -96,9 +96,17 @@
     /**
      * 鏁伴噺
      */
-    @Excel(name = "鏁伴噺")
+    @Excel(name = "鎬绘暟閲�")
     private BigDecimal quantity;
 
+    @Excel(name = "鍚堟牸鏁伴噺")
+    @TableField("qualified_quantity")
+    private BigDecimal qualifiedQuantity;
+
+    @Excel(name = "涓嶅悎鏍兼暟閲�")
+    @TableField("unqualified_quantity")
+    private BigDecimal unqualifiedQuantity;
+
     /**
      * 妫�娴嬪崟浣�
      */
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
index b23bb67..625ff8b 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -1,6 +1,7 @@
 package com.ruoyi.quality.service.impl;
 
 
+import cn.hutool.core.lang.Assert;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -35,6 +36,7 @@
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigDecimal;
 import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -92,17 +94,10 @@
         if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
             throw new RuntimeException("璇峰厛鍒ゆ柇鏄惁鍚堟牸");
         }
-        /*鍒ゆ柇涓嶅悎鏍�*/
-        if (qualityInspect.getCheckResult().equals("涓嶅悎鏍�")) {
-            QualityUnqualified qualityUnqualified = new QualityUnqualified();
-            BeanUtils.copyProperties(qualityInspect, qualityUnqualified);
-            qualityUnqualified.setInspectState(0);//寰呭鐞�
-            List<QualityInspectParam> inspectParams = qualityInspectParamService.list(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId()));
-            String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(","));
-            qualityUnqualified.setDefectivePhenomena(text + "杩欎簺鎸囨爣涓瓨鍦ㄤ笉鍚堟牸");//涓嶅悎鏍肩幇璞�
-            qualityUnqualified.setInspectId(qualityInspect.getId());
-            qualityUnqualifiedMapper.insert(qualityUnqualified);
-        } else {
+
+        // 鍖哄垎鍚堟牸鏁伴噺浠ュ強涓嶅悎鏍煎鐞嗚繘琛屽搴旂殑澶勭悊
+        Assert.isTrue(qualityInspect.getQuantity().compareTo(qualityInspect.getQualifiedQuantity().add(qualityInspect.getUnqualifiedQuantity())) == 0,"璇锋鏌ュ悎鏍兼暟閲忓拰涓嶅悎鏍兼暟閲忥紝闇�瑕佸悎鏍兼暟閲�+涓嶅悎鏍兼暟閲忎笌鎬绘暟淇濇寔涓�鑷�");
+        if(qualityInspect.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0){
             //鍚堟牸鐩存帴鍏ュ簱
             // stockUtils.addStock(qualityInspect.getProductModelId(), qualityInspect.getQuantity(), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode(), qualityInspect.getId());
             //浠呮坊鍔犲叆搴撹褰�
@@ -114,13 +109,25 @@
             }
             stockInventoryDto.setRecordId(qualityInspect.getId());
             stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
-            stockInventoryDto.setQualitity(qualityInspect.getQuantity());
+            stockInventoryDto.setQualitity(qualityInspect.getQualifiedQuantity());
             stockInventoryDto.setBatchNo(resolveProductionBatchNo(
                     qualityInspect.getProductMainId(),
                     qualityInspect.getId(),
                     qualityInspect.getProductModelId()));
             stockInventoryService.addStockInRecordOnly(stockInventoryDto);
         }
+        if(qualityInspect.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) > 0){
+            QualityUnqualified qualityUnqualified = new QualityUnqualified();
+            BeanUtils.copyProperties(qualityInspect, qualityUnqualified);
+            qualityUnqualified.setInspectState(0);//寰呭鐞�
+            qualityUnqualified.setQuantity(qualityInspect.getUnqualifiedQuantity());
+            List<QualityInspectParam> inspectParams = qualityInspectParamService.list(Wrappers.<QualityInspectParam>lambdaQuery().eq(QualityInspectParam::getInspectId, inspect.getId()));
+            String text = inspectParams.stream().map(QualityInspectParam::getParameterItem).collect(Collectors.joining(","));
+            qualityUnqualified.setDefectivePhenomena(text + "杩欎簺鎸囨爣涓瓨鍦ㄤ笉鍚堟牸");//涓嶅悎鏍肩幇璞�
+            qualityUnqualified.setInspectId(qualityInspect.getId());
+            qualityUnqualifiedMapper.insert(qualityUnqualified);
+        }
+
         qualityInspect.setInspectState(1);//宸叉彁浜�
         return qualityInspectMapper.updateById(qualityInspect);
     }
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java b/src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
index 3e64d15..2e55e79 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java
@@ -14,7 +14,12 @@
     private Long id;
     @Schema(description = "閿�鍞姤浠峰崟id")
     private Long salesQuotationId;
-
+    @Schema(description = "浜у搧Id")
+    @TableField(value = "product_id")
+    private Long productId;
+    @Schema(description = "浜у搧瑙勬牸Id")
+    @TableField(value = "product_model_id")
+    private Long productModelId;
     @Schema(description = "鍟嗗搧鍚嶇О")
     private String product;
     @Schema(description = "鍟嗗搧瑙勬牸")
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
index f06cad0..de86ad0 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -13,6 +13,7 @@
 import com.ruoyi.approve.bean.vo.ApproveProcessVO;
 import com.ruoyi.basic.mapper.CustomerMapper;
 import com.ruoyi.basic.pojo.Customer;
+import com.ruoyi.common.enums.IsDeleteEnum;
 import com.ruoyi.common.utils.OrderUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
@@ -138,6 +139,7 @@
         // 鍒犻櫎鎶ヤ环瀹℃壒
         ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
                 .eq(ApproveProcess::getApproveType, 6)
+                .eq(ApproveProcess::getApproveDelete, IsDeleteEnum.NOT_DELETED)
                 .eq(ApproveProcess::getApproveReason, salesQuotation.getQuotationNo()));
         if(one != null){
             approveProcessService.delByIds(Collections.singletonList(one.getId()));
diff --git a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
index b112549..8d16029 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -13,7 +13,6 @@
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
 import com.ruoyi.procurementrecord.utils.StockUtils;
-import com.ruoyi.sales.dto.SalesLedgerProductDto;
 import com.ruoyi.sales.dto.ShippingApproveDto;
 import com.ruoyi.sales.dto.ShippingInfoDto;
 import com.ruoyi.sales.dto.ShippingProductDetailDto;
@@ -96,12 +95,6 @@
         if (CollectionUtils.isEmpty(shippingInfos)) return false;
         // 鍒犻櫎闄勪欢
         commonFileService.deleteByBusinessIds(ids, FileNameType.SHIP.getValue());
-        // 鎵e凡鍙戣揣搴撳瓨
-        for (ShippingInfo shippingInfo : shippingInfos) {
-            if ("宸插彂璐�".equals(shippingInfo.getStatus())) {
-                stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
-            }
-        }
         // 鍒犻櫎鍙戣揣瀹℃壒
         if (CollectionUtils.isNotEmpty(shippingInfos)) {
             for (ShippingInfo shippingInfo : shippingInfos) {
@@ -111,6 +104,8 @@
                     List<Long> list = one.stream().map(ApproveProcess::getId).toList();
                     approveProcessService.delByIds(list);
                 }
+                // 鎵e凡鍙戣揣搴撳瓨
+                stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
             }
         }
         //鍒犻櫎鍙戣揣鏄庣粏
diff --git a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
index 44fe885..a0a9fa5 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -51,6 +51,19 @@
         return R.ok(stockInventoryDtoIPage);
     }
 
+    /**
+     * 鏌ヨ瀵瑰簲鎵瑰彿鍜屾暟閲�
+     * @param page
+     * @param stockInventoryDto
+     * @return
+     */
+    @GetMapping("/getBatchNoQty")
+    @Operation(summary = "鏌ヨ瀵瑰簲鎵瑰彿鍜屾暟閲�")
+    public R getBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
+        IPage<StockInventoryDto> stockInventoryDtoIPage = stockInventoryService.getBatchNoQty(page, stockInventoryDto);
+        return R.ok(stockInventoryDtoIPage);
+    }
+
     @PostMapping("/addstockInventory")
     @Operation(summary = "鏂板搴撳瓨")
     public R addstockInventory(@RequestBody StockInventoryDto stockInventoryDto) {
@@ -85,7 +98,7 @@
     }
 
 
-    @PostMapping("importStockInventory")
+    @PostMapping("/importStockInventory")
     @Operation(summary = "瀵煎叆搴撳瓨")
     public R importStockInventory(MultipartFile file) {
         return stockInventoryService.importStockInventory(file);
@@ -105,13 +118,13 @@
         stockInventoryService.exportStockInventory(response, stockInventoryDto);
     }
 
-    @GetMapping("stockInventoryPage")
+    @GetMapping("/stockInventoryPage")
     @Operation(summary = "搴撳瓨鎶ヨ〃鏌ヨ")
     public R stockInventoryPage(Page page, StockInventoryDto stockInventoryDto) {
         return R.ok(stockInventoryService.stockInventoryPage(stockInventoryDto,page));
     }
 
-    @GetMapping("stockInAndOutRecord")
+    @GetMapping("/stockInAndOutRecord")
     @Operation(summary = "缁熻鍚勪釜浜у搧鐨勫叆搴撳拰鍑哄簱璁板綍")
     public R stockInAndOutRecord(StockInventoryDto stockInventoryDto,Page page) {
         return R.ok(stockInventoryService.stockInAndOutRecord(stockInventoryDto,page));
@@ -128,7 +141,6 @@
     public R thawStock(@RequestBody StockInventoryDto stockInventoryDto) {
         return R.ok(stockInventoryService.thawStock(stockInventoryDto));
     }
-
 
     @GetMapping("/getByModelId")
     @Operation(summary = "鏍规嵁浜у搧瑙勬牸ID鑾峰彇鍏ュ簱璁板綍")
diff --git a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
index 2be6512..bc08eee 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
@@ -76,4 +76,7 @@
 
     @Schema(description = "涓嶅悎鏍煎簱瀛業D")
     private Long unQualifiedId;
+
+    @Schema(description = "浜у搧id")
+    private Long productId;
 }
diff --git a/src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java b/src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java
index d705110..a5a9642 100644
--- a/src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java
+++ b/src/main/java/com/ruoyi/stock/execl/StockInRecordExportData.java
@@ -18,11 +18,13 @@
     private String model;
     @Excel(name = "鍗曚綅")
     private String unit;
+    @Excel(name = "鎵瑰彿")
+    private String batchNo;
     @Excel(name = "鍏ュ簱鏉ユ簮")
     private String recordType;
     @Excel(name = "鍏ュ簱鏁伴噺")
     private String stockInNum;
-    @Excel(name = "鍏ュ簱鏃堕棿")
+    @Excel(name = "鍏ュ簱鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java b/src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java
index a5cec35..a608401 100644
--- a/src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java
+++ b/src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java
@@ -19,6 +19,10 @@
     @Excel(name = "鍗曚綅")
     private String unit;
 
+    @Excel(name = "鎵瑰彿")
+    private String batchNo;
+
+
     @Excel(name = "鍚堟牸搴撳瓨鏁伴噺")
     private BigDecimal qualifiedQuantity;
 
diff --git a/src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java b/src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java
index f120817..c0f6b25 100644
--- a/src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java
+++ b/src/main/java/com/ruoyi/stock/execl/StockOutRecordExportData.java
@@ -17,11 +17,13 @@
     private String model;
     @Excel(name = "鍗曚綅")
     private String unit;
+    @Excel(name = "鎵瑰彿")
+    private String batchNo;
     @Excel(name = "鍑哄簱鏉ユ簮")
     private String recordType;
     @Excel(name = "鍑哄簱鏁伴噺")
-    private String stockInNum;
-    @Excel(name = "鍑哄簱鏃堕棿")
+    private String stockOutNum;
+    @Excel(name = "鍑哄簱鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java b/src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java
index 7fd6e36..6f90ec4 100644
--- a/src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java
+++ b/src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java
@@ -19,6 +19,8 @@
     @Excel(name = "鍗曚綅")
     private String unit;
 
+    @Excel(name = "鎵瑰彿")
+    private String batchNo;
     @Excel(name = "搴撳瓨鏁伴噺")
     private BigDecimal qualitity;
 
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
index a31cf5b..dbc271b 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -56,4 +56,6 @@
     List<StockInventory> listSelectableBatchNoByProductModelIds(@Param("productModelIds") List<Long> productModelIds);
 
     List<StockInventory> getByModelId(@Param("productModelId") Long productModelId);
+
+    IPage<StockInventoryDto> getBatchNoQty(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
 }
diff --git a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
index d47cc4b..5d534d5 100644
--- a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -47,4 +47,6 @@
     Boolean thawStock(StockInventoryDto stockInventoryDto);
 
     List<StockInventory> getByModelId(Long modelId);
+
+    IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto);
 }
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
index ab34aa8..7051075 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -120,7 +120,7 @@
     public void exportStockInRecord(HttpServletResponse response, StockInRecordDto stockInRecordDto) {
         List<StockInRecordExportData> list = stockInRecordMapper.listStockInRecordExportData(stockInRecordDto);
         for (StockInRecordExportData stockInRecordExportData : list) {
-            if (stockInRecordExportData.getType().equals("0")) {
+            if (!stockInRecordExportData.getType().equals("0")) {
                 stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
             }else {
                 stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
index 3808916..24605ba 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -29,7 +29,7 @@
 import com.ruoyi.stock.service.StockOutRecordService;
 import com.ruoyi.stock.service.StockUninventoryService;
 import jakarta.servlet.http.HttpServletResponse;
-import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -51,15 +51,16 @@
  * @since 2026-01-21 04:16:36
  */
 @Service
-@AllArgsConstructor
+@RequiredArgsConstructor
 public class StockInventoryServiceImpl extends ServiceImpl<StockInventoryMapper, StockInventory> implements StockInventoryService {
 
-    private  StockInventoryMapper stockInventoryMapper;
-    private StockInRecordService stockInRecordService;
-    private StockOutRecordService stockOutRecordService;
-    private StockUninventoryService stockUninventoryService;
-    private SalesLedgerProductMapper salesLedgerProductMapper;
-    private ProductModelMapper productModelMapper;
+    private final StockInventoryMapper stockInventoryMapper;
+    private final StockInRecordService stockInRecordService;
+    private final StockOutRecordService stockOutRecordService;
+    private final StockUninventoryService stockUninventoryService;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final ProductModelMapper productModelMapper;
+
     @Override
     public IPage<StockInventoryDto> pagestockInventory(Page page, StockInventoryDto stockInventoryDto) {
         return stockInventoryMapper.pagestockInventory(page, stockInventoryDto);
@@ -105,8 +106,8 @@
             newStockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
             newStockInventory.setWarnNum(stockInventoryDto.getWarnNum());
             stockInventoryMapper.insert(newStockInventory);
-        }else {
-             stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
+        } else {
+            stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
         }
         return true;
     }
@@ -116,7 +117,7 @@
     @Transactional(rollbackFor = Exception.class)
     public Boolean subtractStockInventory(StockInventoryDto stockInventoryDto) {
         LambdaQueryWrapper<StockInventory> eq = new QueryWrapper<StockInventory>().lambda()
-            .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
+                .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
         if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) {
             eq.isNull(StockInventory::getBatchNo);
             stockInventoryDto.setBatchNo(null);
@@ -336,7 +337,7 @@
                         }
 
                         stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
-                        this.addstockInventory(stockInventoryDto);
+                        this.addStockInRecordOnly(stockInventoryDto);
                         successCount++;
                     }
 
@@ -392,28 +393,28 @@
 
         List<StockInventoryExportData> list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto);
         ExcelUtil<StockInventoryExportData> util = new ExcelUtil<>(StockInventoryExportData.class);
-        util.exportExcel(response,list, "搴撳瓨淇℃伅");
+        util.exportExcel(response, list, "搴撳瓨淇℃伅");
     }
 
     @Override
     public IPage<StockInRecordDto> stockInventoryPage(StockInventoryDto stockInventoryDto, Page page) {
-        return stockInventoryMapper.stockInventoryPage(stockInventoryDto,page);
+        return stockInventoryMapper.stockInventoryPage(stockInventoryDto, page);
     }
 
     @Override
     public IPage<StockInventoryDto> stockInAndOutRecord(StockInventoryDto stockInventoryDto, Page page) {
-        return stockInventoryMapper.stockInAndOutRecord(stockInventoryDto,page);
+        return stockInventoryMapper.stockInAndOutRecord(stockInventoryDto, page);
     }
 
     @Override
     public Boolean frozenStock(StockInventoryDto stockInventoryDto) {
         StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
-        if (stockInventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
+        if (stockInventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
             throw new RuntimeException("鍐荤粨鏁伴噺涓嶈兘瓒呰繃搴撳瓨鏁伴噺");
         }
         if (ObjectUtils.isEmpty(stockInventory.getLockedQuantity())) {
             stockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
-        }else {
+        } else {
             stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().add(stockInventoryDto.getLockedQuantity()));
         }
         return this.updateById(stockInventory);
@@ -422,7 +423,7 @@
     @Override
     public Boolean thawStock(StockInventoryDto stockInventoryDto) {
         StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
-        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
+        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
             throw new RuntimeException("瑙e喕鏁伴噺涓嶈兘瓒呰繃鍐荤粨鏁伴噺");
         }
         stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
@@ -433,4 +434,9 @@
     public List<StockInventory> getByModelId(Long modelId) {
         return stockInventoryMapper.getByModelId(modelId);
     }
+
+    @Override
+    public IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
+        return stockInventoryMapper.getBatchNoQty(page, stockInventoryDto);
+    }
 }
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index b1869c3..3e16326 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -260,7 +260,7 @@
   upload-dir: D:/ruoyi/prod/uploads # 姝e紡鐩綍 鍚庢湡鍒犻櫎
   path: D:/ruoyi/prod/uploads # 涓婁紶鐩綍
   urlPrefix: /common # 閾炬帴鍓嶇紑
-  domain: http://127.0.0.1:7005 # 鍩熷悕鍓嶇紑
+  domain: http://127.0.0.1:7006 # 鍩熷悕鍓嶇紑
   expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
   useLimit: 10 # 浣跨敤娆℃暟
   compress: true # 鏄惁鍘嬬缉
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index d69a572..9bffe41 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration>
     <!-- 鏃ュ織瀛樻斁璺緞 -->
-	<property name="log.path" value="/home/ruoyi/logs" />
+	<property name="log.path" value="./logs" />
     <!-- 鏃ュ織杈撳嚭鏍煎紡 -->
 	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
 
@@ -11,7 +11,7 @@
 			<pattern>${log.pattern}</pattern>
 		</encoder>
 	</appender>
-	
+
 	<!-- 绯荤粺鏃ュ織杈撳嚭 -->
 	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
 	    <file>${log.path}/sys-info.log</file>
@@ -34,7 +34,7 @@
             <onMismatch>DENY</onMismatch>
         </filter>
 	</appender>
-	
+
 	<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
 	    <file>${log.path}/sys-error.log</file>
         <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
@@ -56,7 +56,7 @@
             <onMismatch>DENY</onMismatch>
         </filter>
     </appender>
-	
+
 	<!-- 鐢ㄦ埛璁块棶鏃ュ織杈撳嚭  -->
     <appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
 		<file>${log.path}/sys-user.log</file>
@@ -70,7 +70,7 @@
             <pattern>${log.pattern}</pattern>
         </encoder>
     </appender>
-	
+
 	<!-- 绯荤粺妯″潡鏃ュ織绾у埆鎺у埗  -->
 	<logger name="com.ruoyi" level="info" />
 	<!-- Spring鏃ュ織绾у埆鎺у埗  -->
@@ -79,15 +79,15 @@
 	<root level="info">
 		<appender-ref ref="console" />
 	</root>
-	
+
 	<!--绯荤粺鎿嶄綔鏃ュ織-->
     <root level="info">
         <appender-ref ref="file_info" />
         <appender-ref ref="file_error" />
     </root>
-	
+
 	<!--绯荤粺鐢ㄦ埛鎿嶄綔鏃ュ織-->
     <logger name="sys-user" level="info">
         <appender-ref ref="sys-user"/>
     </logger>
-</configuration> 
\ No newline at end of file
+</configuration>
diff --git a/src/main/resources/manufacturing-agent-prompt.txt b/src/main/resources/manufacturing-agent-prompt.txt
new file mode 100644
index 0000000..c1a30c8
--- /dev/null
+++ b/src/main/resources/manufacturing-agent-prompt.txt
@@ -0,0 +1,8 @@
+浣犳槸浼佷笟鍒堕�犳櫤鑳藉姪鎵嬶紝瑕嗙洊鐢熶骇鐜板満銆佽鍒掋�佸伐鍗曘�佽澶囥�佽川閲忋�佺墿鏂欍�佸紓甯稿鐞嗕竷涓煙銆�
+
+宸ヤ綔瑙勫垯锛�
+1. 鐢ㄦ埛鎻愬嚭鈥滄煡銆侀棶銆侀璀︺�佸垎鏋愨�濋渶姹傛椂锛屼紭鍏堣皟鐢ㄥ伐鍏锋嬁缁撴瀯鍖栫粨鏋滐紝涓嶈鑷嗛�犱笟鍔℃暟鎹��
+2. 鐢ㄦ埛鎻愬嚭鈥滃姙鈥濋渶姹傛椂锛屼紭鍏堣緭鍑哄姙鐞嗗缓璁姩浣滃崱锛堟帴鍙c�佸繀濉瓧娈点�佺ず渚嬶級锛屾槑纭渶瑕佸墠绔簩娆$‘璁ゃ��
+3. 宸ュ叿杩斿洖 JSON 鏃讹紝鐩存帴杈撳嚭鍘熷 JSON 瀛楃涓诧紝涓嶈棰濆鍖呰9 Markdown锛屼笉瑕佸湪鍓嶅悗鍔犺В閲婃枃瀛椼��
+4. 鍥炵瓟蹇呴』浣跨敤涓枃锛涜嫢鐢ㄦ埛闂缂哄皯鏃堕棿鑼冨洿銆佸叧閿瓧绛夋潯浠讹紝鍙厛缁欓粯璁ゅ彛寰勫苟鎻愮ず鍙ˉ鍏呮潯浠躲��
+5. 鑻ユ棤娉曚粠宸ュ叿缁撴灉寰楀埌缁撹锛屾槑纭鏄庣己灏戠殑绛涢�夋潯浠舵垨涓氬姟瀛楁銆�
diff --git a/src/main/resources/mapper/basic/CustomerMapper.xml b/src/main/resources/mapper/basic/CustomerMapper.xml
index 57dea6c..20aaefc 100644
--- a/src/main/resources/mapper/basic/CustomerMapper.xml
+++ b/src/main/resources/mapper/basic/CustomerMapper.xml
@@ -26,6 +26,7 @@
         from customer c
         left join sys_user u on c.usage_user = u.user_id
         <where>
+            and c.usage_status = 1
             <if test="c.customerName != null and c.customerName != ''">
                 and customer_name like concat('%', #{c.customerName}, '%')
             </if>
@@ -107,4 +108,4 @@
             </if>
         </where>
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml b/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
index 5da6dff..d8a5956 100644
--- a/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
+++ b/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
@@ -4,13 +4,27 @@
 
 
     <select id="listPage" resultType="com.ruoyi.collaborativeApproval.dto.SealApplicationManagementDTO">
-        select sam.*, su.user_name as create_user_name, d.dept_name as department,
-        su1.nick_name as approveUserName
-        from seal_application_management sam
-        left join sys_user su on sam.create_user = su.user_id
-        left join sys_user su1 on sam.approve_user_id = su1.user_id
-        left join sys_user_dept sud on su.user_id = sud.user_id
-        left join sys_dept d on sud.dept_id = d.dept_id
+        SELECT
+        sam.*,
+        su.user_name AS create_user_name,
+
+        GROUP_CONCAT(DISTINCT d.dept_name ORDER BY d.dept_id SEPARATOR ',') AS department,
+
+        su1.nick_name AS approveUserName
+
+        FROM seal_application_management sam
+
+        LEFT JOIN sys_user su
+        ON sam.create_user = su.user_id
+
+        LEFT JOIN sys_user su1
+        ON sam.approve_user_id = su1.user_id
+
+        LEFT JOIN sys_user_dept sud
+        ON su.user_id = sud.user_id
+
+        LEFT JOIN sys_dept d
+        ON sud.dept_id = d.dept_id
         <where>
             <if test="ew.applicationNum != null and ew.applicationNum != ''">
                 and sam.application_num like concat('%',#{ew.applicationNum},'%')
@@ -22,5 +36,6 @@
                 and sam.status = #{ew.status}
             </if>
         </where>
+        GROUP BY sam.id
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/device/DeviceRepairMapper.xml b/src/main/resources/mapper/device/DeviceRepairMapper.xml
index 2eb1d29..713e346 100644
--- a/src/main/resources/mapper/device/DeviceRepairMapper.xml
+++ b/src/main/resources/mapper/device/DeviceRepairMapper.xml
@@ -11,10 +11,13 @@
                 dr.repair_time,
                 dr.repair_name,
                 dr.remark,
-                dr.maintenance_name,
-                dr.maintenance_time,
-                dr.maintenance_result,
-                dr.status,
+                 dr.maintenance_name,
+                 dr.maintenance_time,
+                 dr.maintenance_result,
+                 dr.acceptance_name,
+                 dr.acceptance_time,
+                 dr.acceptance_remark,
+                 dr.status,
                 dr.create_time,
                 dr.update_time,
                 dr.create_user,
@@ -60,6 +63,9 @@
                dr.maintenance_name,
                dr.maintenance_time,
                dr.maintenance_result,
+               dr.acceptance_name,
+               dr.acceptance_time,
+               dr.acceptance_remark,
                dr.status,
                dr.create_time,
                dr.update_time,
diff --git a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
index e4200af..65741f5 100644
--- a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
+++ b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
@@ -15,7 +15,9 @@
         next_date,
         record_date,
         CASE
-        WHEN most_date &gt;=  DATE_FORMAT(now(),'%Y-%m-%d') THEN 1
+        WHEN most_date IS NOT NULL
+        AND valid IS NOT NULL
+        AND DATE_ADD(most_date, INTERVAL valid DAY) &gt;= CURDATE() THEN 1
         ELSE 2
         END AS status,
         create_user,
@@ -40,10 +42,16 @@
             <if test="req.status != null">
                 <choose>
                     <when test="req.status == 1">
-                        AND most_date &gt;=  DATE_FORMAT(now(),'%Y-%m-%d')
+                        AND most_date IS NOT NULL
+                        AND valid IS NOT NULL
+                        AND DATE_ADD(most_date, INTERVAL valid DAY) &gt;= CURDATE()
                     </when>
                     <when test="req.status == 2">
-                        AND most_date &lt;  DATE_FORMAT(now(),'%Y-%m-%d')
+                        AND (
+                        most_date IS NULL
+                        OR valid IS NULL
+                        OR DATE_ADD(most_date, INTERVAL valid DAY) &lt; CURDATE()
+                        )
                     </when>
                 </choose>
             </if>
diff --git a/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml b/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
index 93bd879..6b15d56 100644
--- a/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
+++ b/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
@@ -19,7 +19,7 @@
             sor.outbound_batches,
             sor.stock_out_num,
             sor.batch_no,
-               GREATEST(sor.stock_out_num - COALESCE(rsp.num, 0), 0) AS un_quantity,
+               GREATEST(sor.stock_out_num - COALESCE(rs1.total_return_num1, 0), 0) AS un_quantity,
                COALESCE(rs.total_return_num, 0)                             AS total_return_num
         FROM return_sale_product rsp
                  LEFT JOIN return_management rm ON rm.id = rsp.return_management_id
@@ -34,6 +34,11 @@
                             FROM return_sale_product
                             WHERE 1 = 1 and return_management_id = #{returnManagementId}
                             GROUP BY stock_out_record_id) rs ON rs.stock_out_record_id = sor.id
+                 LEFT JOIN (SELECT stock_out_record_id,
+                                   SUM(num) AS total_return_num1
+                            FROM return_sale_product
+                            WHERE 1 = 1
+                            GROUP BY stock_out_record_id) rs1 ON rs1.stock_out_record_id = sor.id
         where rm.id =#{returnManagementId}
     </select>
     <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
diff --git a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
index 6db5455..f029ef7 100644
--- a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
+++ b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
@@ -159,6 +159,7 @@
 
     <select id="getOperation" resultType="com.ruoyi.production.bean.vo.ProductionOperationTaskVo">
         select poro.operation_name as operationName,
+               max(poro.type) as type,
                count(pot.id) as productionTaskCount,
                sum(ifnull(pot.plan_quantity, 0)) as planQuantity,
                sum(ifnull(pot.complete_quantity, 0)) as completeQuantity,
diff --git a/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml b/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
index a001e9a..6732a66 100644
--- a/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
+++ b/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
@@ -132,7 +132,7 @@
            slp.tax_inclusive_unit_price,
            prop.return_quantity,
            prop.purchase_return_order_id,
-           GREATEST(sir.stock_in_num - COALESCE(prop.return_quantity, 0), 0) AS un_quantity,
+           GREATEST(sir.stock_in_num - COALESCE(rs1.total_return_num1, 0), 0) AS un_quantity,
            COALESCE(rs.total_return_num, 0)                             AS total_return_num
     from purchase_return_order_products prop
     left join purchase_return_orders pro on prop.purchase_return_order_id = pro.id
@@ -143,6 +143,11 @@
                FROM purchase_return_order_products
                WHERE 1 = 1 and purchase_return_order_id = #{id}
                GROUP BY stock_in_record_id) rs ON rs.stock_in_record_id = sir.id
+    LEFT JOIN (SELECT stock_in_record_id,
+                      SUM(return_quantity) AS total_return_num1
+               FROM purchase_return_order_products
+               WHERE 1 = 1 and purchase_return_order_id = #{id}
+               GROUP BY stock_in_record_id) rs1 ON rs1.stock_in_record_id = sir.id
     where pro.id = #{id}
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
index f3fa32d..7e352d8 100644
--- a/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -48,7 +48,8 @@
         END as has_sufficient_stock,
         (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) as no_quantity,
         CASE
-        WHEN (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN '寰呭彂璐�'
+         WHEN IFNULL(t3.shipped_quantity, 0) = 0 THEN '寰呭彂璐�'
+         WHEN (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) > 0 THEN '閮ㄥ垎鍙戣揣'
         ELSE '宸插彂璐�'
         END as shippingStatus
         FROM
@@ -62,6 +63,7 @@
         SELECT sales_ledger_product_id, IFNULL(SUM(spd.quantity), 0) as shipped_quantity
         FROM shipping_info si
         LEFT JOIN shipping_product_detail spd ON si.id = spd.shipping_info_id
+        where si.status != '瀹℃牳鎷掔粷'
         GROUP BY sales_ledger_product_id
         ) t3 ON t3.sales_ledger_product_id = T1.id
         left join product_model pm ON T1.product_model_id = pm.id
diff --git a/src/main/resources/mapper/sales/SalesQuotationMapper.xml b/src/main/resources/mapper/sales/SalesQuotationMapper.xml
index cf15b63..3c93850 100644
--- a/src/main/resources/mapper/sales/SalesQuotationMapper.xml
+++ b/src/main/resources/mapper/sales/SalesQuotationMapper.xml
@@ -20,4 +20,4 @@
             AND t1.status = #{salesQuotationDto.status}
         </if>
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index 9612933..c71ce31 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -99,6 +99,138 @@
         INNER JOIN product_tree pt ON p.parent_id = pt.id
         )
         select
+        GROUP_CONCAT(DISTINCT batch_no ORDER BY batch_no SEPARATOR ',') as batch_no,
+        MAX(qualifiedId) as qualifiedId,
+        MAX(unQualifiedId) as unQualifiedId,
+        SUM(qualifiedQuantity) as qualifiedQuantity,
+        SUM(unQualifiedQuantity) as unQualifiedQuantity,
+        SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
+        SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
+        SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
+        SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
+        SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
+        SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
+        product_model_id,
+        MAX(create_time) as create_time,
+        MAX(update_time) as update_time,
+        MAX(warn_num) as warn_num,
+        MAX(version) as version,
+        model,
+        MAX(remark) as remark,
+        unit,
+        product_name,
+        product_id,
+        'combined' as stockType
+        from (
+        select
+        si.batch_no,
+        si.id as qualifiedId,
+        null as unQualifiedId,
+        si.qualitity as qualifiedQuantity,
+        0 as unQualifiedQuantity,
+        COALESCE(si.locked_quantity, 0) as locked_quantity,
+        COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
+        0 as unQualifiedLockedQuantity,
+        si.product_model_id,
+        si.create_time,
+        si.update_time,
+        COALESCE(si.warn_num, 0) as warn_num,
+        si.version,
+        (si.qualitity - COALESCE(si.locked_quantity, 0)) as un_locked_quantity,
+        pm.model,
+        si.remark,
+        pm.unit,
+        p.product_name,
+        p.id as product_id,
+        (
+        select IFNULL(SUM(sor.stock_out_num), 0)
+        from stock_out_record sor
+        where sor.product_model_id = si.product_model_id
+        and (
+        (si.batch_no is null and sor.batch_no is null)
+        or si.batch_no = sor.batch_no
+        )
+        and sor.type = '0'
+        and sor.approval_status = 0
+        ) as qualifiedPendingOut,
+        0 as unQualifiedPendingOut
+        from stock_inventory si
+        left join product_model pm on si.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+
+        union all
+
+        select
+        su.batch_no,
+        null as qualifiedId,
+        su.id as unQualifiedId,
+        0 as qualifiedQuantity,
+        su.qualitity as unQualifiedQuantity,
+        COALESCE(su.locked_quantity, 0) as locked_quantity,
+        0 as qualifiedLockedQuantity,
+        COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
+        su.product_model_id,
+        su.create_time,
+        su.update_time,
+        0 as warn_num,
+        su.version,
+        (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity,
+        pm.model,
+        su.remark,
+        pm.unit,
+        p.product_name,
+        p.id as product_id,
+        0 as qualifiedPendingOut,
+        (
+        select IFNULL(SUM(sor.stock_out_num), 0)
+        from stock_out_record sor
+        where sor.product_model_id = su.product_model_id
+        and (
+        (su.batch_no is null and sor.batch_no is null)
+        or su.batch_no = sor.batch_no
+        )
+        and sor.type = '1'
+        and sor.approval_status = 0
+        ) as unQualifiedPendingOut
+        from stock_uninventory su
+        left join product_model pm on su.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+        ) as combined
+        <where>
+            <if test="ew.productName != null and ew.productName !=''">
+                and combined.product_name in (
+                select distinct p.product_name
+                from product p
+                left join product_model pm on p.id = pm.product_id
+                where p.product_name like concat('%',#{ew.productName},'%')
+                or pm.model like concat('%',#{ew.productName},'%')
+                )
+            </if>
+            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
+                and combined.product_id in (select id from product_tree)
+            </if>
+        </where>
+        group by
+        product_model_id,
+        model,
+        unit,
+        product_name,
+        product_id
+    </select>
+
+    <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
+        WITH RECURSIVE product_tree AS (
+        SELECT id
+        FROM product
+        WHERE id = #{ew.topParentProductId}
+
+        UNION ALL
+
+        SELECT p.id
+        FROM product p
+        INNER JOIN product_tree pt ON p.parent_id = pt.id
+        )
+        select
             batch_no,
             MAX(qualifiedId) as qualifiedId,
             MAX(unQualifiedId) as unQualifiedId,
@@ -204,84 +336,6 @@
             </if>
         </where>
         group by batch_no, product_model_id, model, unit, product_name, product_id
-    </select>
-
-    <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
-        WITH RECURSIVE product_tree AS (
-        SELECT id
-        FROM product
-        WHERE id = #{ew.topParentProductId}
-
-        UNION ALL
-
-        SELECT p.id
-        FROM product p
-        INNER JOIN product_tree pt ON p.parent_id = pt.id
-        )
-        select
-            SUM(qualifiedQuantity) as qualifiedQuantity,
-            SUM(unQualifiedQuantity) as unQualifiedQuantity,
-            SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
-            SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
-            model,
-            unit,
-            product_name,
-            MAX(warn_num) as warn_num,
-            MAX(remark) as remark,
-            MAX(update_time) as update_time
-        from (
-            select
-            si.qualitity as qualifiedQuantity,
-            0 as unQualifiedQuantity,
-            COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
-            0 as unQualifiedLockedQuantity,
-            si.product_model_id,
-            si.create_time,
-            si.update_time,
-            COALESCE(si.warn_num, 0) as warn_num,
-            si.remark,
-            pm.model,
-            pm.unit,
-            p.product_name,
-            p.id as product_id
-            from stock_inventory si
-            left join product_model pm on si.product_model_id = pm.id
-            left join product p on pm.product_id = p.id
-
-            union all
-
-            select
-            0 as qualifiedQuantity,
-            su.qualitity as unQualifiedQuantity,
-            0 as qualifiedLockedQuantity,
-            COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
-            su.product_model_id,
-            su.create_time,
-            su.update_time,
-            0 as warn_num,
-            su.remark,
-            pm.model,
-            pm.unit,
-            p.product_name,
-            p.id as product_id
-            from stock_uninventory su
-            left join product_model pm on su.product_model_id = pm.id
-            left join product p on pm.product_id = p.id
-        ) as combined
-        <where>
-            <if test="ew.productName != null and ew.productName !=''">
-                and combined.product_name in (
-                select distinct p.product_name
-                from product p
-                left join product_model pm on p.id = pm.product_id
-                where p.product_name like concat('%',#{ew.productName},'%') or pm.model like concat('%',#{ew.productName},'%')
-                )
-            </if>
-            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
-                and combined.product_id in (select id from product_tree)
-            </if>
-        </where>
-        group by product_model_id, model, unit, product_name
     </select>
     <select id="stockInventoryPage" resultType="com.ruoyi.stock.dto.StockInRecordDto">
         select sir.*,si.qualitity as current_stock,
@@ -471,6 +525,141 @@
                     group by spd.stock_inventory_id
                  ) as sd on sd.stock_inventory_id = si.id
         where si.product_model_id = #{productModelId}
+        and si.qualitity > IFNULL(sd.qualitity, 0)
+    </select>
+
+    <select id="getBatchNoQty" resultType="com.ruoyi.stock.dto.StockInventoryDto">
+        select
+        batch_no,
+        MAX(qualifiedId) as qualifiedId,
+        MAX(unQualifiedId) as unQualifiedId,
+
+        SUM(qualifiedQuantity) as qualifiedQuantity,
+        SUM(unQualifiedQuantity) as unQualifiedQuantity,
+
+        SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
+        SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
+
+        SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
+        SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
+
+        SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
+        SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
+
+        product_model_id,
+        model,
+        unit,
+        product_name,
+        product_id,
+
+        MAX(create_time) as create_time,
+        MAX(update_time) as update_time,
+        MAX(warn_num) as warn_num,
+        MAX(version) as version,
+        MAX(remark) as remark,
+
+        'combined' as stockType
+        from (
+        select
+        si.batch_no,
+        si.id as qualifiedId,
+        null as unQualifiedId,
+
+        si.qualitity as qualifiedQuantity,
+        0 as unQualifiedQuantity,
+
+        COALESCE(si.locked_quantity, 0) as qualifiedLockedQuantity,
+        0 as unQualifiedLockedQuantity,
+
+        si.product_model_id,
+        pm.model,
+        pm.unit,
+        p.product_name,
+        p.id as product_id,
+
+        si.create_time,
+        si.update_time,
+        COALESCE(si.warn_num, 0) as warn_num,
+        si.version,
+        si.remark,
+
+        (
+        select IFNULL(SUM(sor.stock_out_num), 0)
+        from stock_out_record sor
+        where sor.product_model_id = si.product_model_id
+        and (
+        (si.batch_no is null and sor.batch_no is null)
+        or si.batch_no = sor.batch_no
+        )
+        and sor.type = '0'
+        and sor.approval_status = 0
+        ) as qualifiedPendingOut,
+
+        0 as unQualifiedPendingOut
+        from stock_inventory si
+        left join product_model pm on si.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+
+        union all
+
+        select
+        su.batch_no,
+        null as qualifiedId,
+        su.id as unQualifiedId,
+
+        0 as qualifiedQuantity,
+        su.qualitity as unQualifiedQuantity,
+
+        0 as qualifiedLockedQuantity,
+        COALESCE(su.locked_quantity, 0) as unQualifiedLockedQuantity,
+
+        su.product_model_id,
+        pm.model,
+        pm.unit,
+        p.product_name,
+        p.id as product_id,
+
+        su.create_time,
+        su.update_time,
+        0 as warn_num,
+        su.version,
+        su.remark,
+
+        0 as qualifiedPendingOut,
+
+        (
+        select IFNULL(SUM(sor.stock_out_num), 0)
+        from stock_out_record sor
+        where sor.product_model_id = su.product_model_id
+        and (
+        (su.batch_no is null and sor.batch_no is null)
+        or su.batch_no = sor.batch_no
+        )
+        and sor.type = '1'
+        and sor.approval_status = 0
+        ) as unQualifiedPendingOut
+        from stock_uninventory su
+        left join product_model pm on su.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+        ) as combined
+        <where>
+            <if test="ew.productModelId != null and ew.productModelId > 0">
+                and combined.product_model_id = #{ew.productModelId}
+            </if>
+
+            <if test="ew.productId != null and ew.productId > 0">
+                and combined.product_id = #{ew.productId}
+            </if>
+        </where>
+        group by
+        batch_no,
+        product_model_id,
+        model,
+        unit,
+        product_name,
+        product_id
+        order by
+        batch_no
     </select>
 
 </mapper>

--
Gitblit v1.9.3