From 9d42f647f5589e4a560d745d6b359ae6c273bd8d Mon Sep 17 00:00:00 2001
From: zss <zss@example.com>
Date: 星期一, 11 五月 2026 13:08:52 +0800
Subject: [PATCH] Merge branch 'dev_New_pro' into dev_宁夏_英泽防锈

---
 src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml                                 |    4 
 src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java              |    1 
 src/main/resources/mapper/account/AccountExpenseMapper.xml                                           |    2 
 src/main/java/com/ruoyi/sales/service/ShippingInfoService.java                                       |    3 
 src/main/java/com/ruoyi/account/bean/dto/SalesRefundAmountOrderDto.java                              |    3 
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java                                      |   17 
 src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java               |    1 
 src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java                             |    5 
 doc/20260508_采购多文件分析附件存储与历史回显联调说明.md                                                                 |  149 +
 src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java                                      |    2 
 src/main/resources/application-dev.yml                                                               |   14 
 src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java                                                 |   17 
 src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java                            |    4 
 src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java                                              |   69 
 src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java                                |   30 
 src/main/java/com/ruoyi/production/controller/ProductionOrderController.java                         |   18 
 src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java                    |    3 
 src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml                               |   28 
 src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java                                         |   52 
 src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java                                             |   16 
 src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml                       |    1 
 src/main/resources/mapper/account/AccountIncomeMapper.xml                                            |    2 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java |   23 
 src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java              |    9 
 src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java                     |    7 
 src/main/java/com/ruoyi/production/controller/ProductionPlanController.java                          |    9 
 src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java                                   |    4 
 src/main/java/com/ruoyi/production/service/ProductionPlanService.java                                |    2 
 src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java                              |    4 
 src/main/java/com/ruoyi/account/controller/AccountExpenseController.java                             |    2 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java                  |  424 ++++-
 src/main/java/com/ruoyi/account/controller/AccountSubjectController.java                             |   70 
 src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java                            |   58 
 src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java                                   |    3 
 src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDistributionVO.java                               |    2 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java                   |   27 
 src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java                                           |    1 
 src/main/java/com/ruoyi/account/bean/vo/SalesReturnVo.java                                           |   48 
 src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java                                    |    5 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java                        |    3 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java            |    1 
 src/main/java/com/ruoyi/account/bean/dto/AccountSubjectDto.java                                      |   10 
 src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java                            |    2 
 src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java                           |    8 
 src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java                                       |   26 
 src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java                                 |    9 
 src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java                             |    5 
 src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java                         |    3 
 src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java                                     |    2 
 src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java                                     |    4 
 src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java                          |   28 
 src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java                |   23 
 src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java                     |    2 
 src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java                                |   23 
 src/main/java/com/ruoyi/account/bean/dto/AccountDto.java                                             |   14 
 src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java                          |    8 
 src/main/resources/mapper/sales/ShippingInfoMapper.xml                                               |   29 
 src/main/java/com/ruoyi/account/mapper/SalesRefundAmountOrderMapper.java                             |    2 
 src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java                        |   12 
 src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java                                     |   18 
 src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java               |   14 
 src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java             |    8 
 src/main/resources/mapper/production/ProductionProductMainMapper.xml                                 |   12 
 src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDetail.java                                       |    2 
 src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java                              |    7 
 src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java                                |    3 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java                         |   18 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java               |    2 
 src/main/java/com/ruoyi/sales/dto/ShippingApproveDto.java                                            |   15 
 src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java              |   20 
 src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java      |    1 
 src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java                              |   13 
 src/main/resources/mapper/staff/StaffOnJobMapper.xml                                                 |   12 
 src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java                                  |   35 
 src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java                       |  319 +++
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java      |    9 
 src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java                    |   16 
 src/main/java/com/ruoyi/account/bean/dto/SalesReceiptReturnDto.java                                  |    2 
 src/main/resources/mapper/production/ProductionAccountMapper.xml                                     |   12 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java                      |   57 
 src/main/resources/mapper/device/DeviceLedgerMapper.xml                                              |    2 
 src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java                              |    3 
 src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java                         |    2 
 src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java                                        |    4 
 src/main/java/com/ruoyi/account/controller/AccountSalesController.java                               |   68 
 src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java                            |    4 
 src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java                         |    4 
 src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml                                   |    2 
 src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java                          |   77 
 src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java                              |    3 
 src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java                                   |   11 
 src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java                           |    4 
 src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java                                            |    5 
 src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java                             |    8 
 src/main/resources/mapper/production/ProductionOperationTaskMapper.xml                               |    1 
 src/main/java/com/ruoyi/account/pojo/AccountSubject.java                                             |  111 +
 src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java                                         |    5 
 src/main/java/com/ruoyi/account/service/AccountIncomeService.java                                    |    6 
 src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java                           |    4 
 src/main/resources/mapper/production/ProductionPlanMapper.xml                                        |    3 
 src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java                            |    3 
 src/main/java/com/ruoyi/account/service/AccountExpenseService.java                                   |    8 
 src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java                                      |  855 ----------
 src/main/java/com/ruoyi/account/service/AccountSalesService.java                                     |   28 
 src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java                            |   40 
 src/main/resources/mapper/account/AccountSubjectMapper.xml                                           |   21 
 src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml                            |    1 
 src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java                                           |   73 
 src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml                                    |   24 
 src/main/java/com/ruoyi/account/service/AccountSubjectService.java                                   |   24 
 src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java                      |   10 
 src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java                  |    2 
 src/main/java/com/ruoyi/account/bean/dto/ReportDateDto.java                                          |    3 
 src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java                                         |    3 
 src/main/resources/mapper/sales/ShippingProductDetailMapper.xml                                      |    9 
 src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java                                        |    8 
 src/main/java/com/ruoyi/ai/service/PurchaseAiService.java                                            | 1031 +++++++++++++
 doc/20260507_采购多文件分析确认接口传参类型约束.md                                                                    |  166 ++
 src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java                                            |    2 
 src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java                                          |   10 
 src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java                                     |    4 
 src/main/java/com/ruoyi/account/service/SalesRefundAmountOrderService.java                           |    2 
 /dev/null                                                                                            |   24 
 src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java                                         |   26 
 src/main/java/com/ruoyi/account/bean/dto/AccountSubjectImportDto.java                                |   36 
 src/main/java/com/ruoyi/account/service/SalesReceiptReturnService.java                               |    2 
 src/main/java/com/ruoyi/production/pojo/ProductionOrder.java                                         |    8 
 src/main/java/com/ruoyi/account/service/impl/SalesReceiptReturnServiceImpl.java                      |    2 
 128 files changed, 3,404 insertions(+), 1,296 deletions(-)

diff --git "a/doc/20260507_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\347\241\256\350\256\244\346\216\245\345\217\243\344\274\240\345\217\202\347\261\273\345\236\213\347\272\246\346\235\237.md" "b/doc/20260507_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\347\241\256\350\256\244\346\216\245\345\217\243\344\274\240\345\217\202\347\261\273\345\236\213\347\272\246\346\235\237.md"
new file mode 100644
index 0000000..69535e7
--- /dev/null
+++ "b/doc/20260507_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\347\241\256\350\256\244\346\216\245\345\217\243\344\274\240\345\217\202\347\261\273\345\236\213\347\272\246\346\235\237.md"
@@ -0,0 +1,166 @@
+# 閲囪喘澶氭枃浠跺垎鏋愮‘璁ゆ帴鍙d紶鍙傜被鍨嬬害鏉燂紙`purchase_ledger`锛�
+
+## 1. 閫傜敤鎺ュ彛
+
+- `POST /purchase-ai/analyze-files/confirm`
+- `businessType = purchase_ledger`
+
+> 鏈枃鐢ㄤ簬绾︽潫鍓嶇鎻愪氦鍒扮‘璁ゆ帴鍙g殑 `payload` 绫诲瀷锛岄伩鍏� `Cannot deserialize ...` 杩欑被鍙嶅簭鍒楀寲寮傚父銆�
+
+## 2. 椤跺眰璇锋眰浣�
+
+```json
+{
+  "businessType": "purchase_ledger",
+  "payload": {
+    "purchaseLedgers": []
+  }
+}
+```
+
+## 3. payload 缁撴瀯
+
+鎺ㄨ崘缁熶竴浣跨敤鎵归噺缁撴瀯锛堝嵆浣垮彧鏈� 1 鏉★級锛�
+
+```json
+{
+  "purchaseLedgers": [
+    {
+      "purchaseContractNumber": "CG-2026-001",
+      "supplierId": 10001,
+      "entryDate": "2026-05-07",
+      "type": 2,
+      "approvalStatus": 1,
+      "productData": [
+        {
+          "productCategory": "閽㈡潗",
+          "specificationModel": "Q235-A",
+          "unit": "鍚�",
+          "quantity": 10,
+          "taxInclusiveUnitPrice": 1200,
+          "taxInclusiveTotalPrice": 12000,
+          "taxRate": 13,
+          "type": 2
+        }
+      ]
+    }
+  ]
+}
+```
+
+鍚庣涔熷吋瀹光�滃崟鏉$洿浼犫�濓紙`payload` 鐩存帴鏄竴鏉� `PurchaseLedgerDto`锛夛紝浣嗕笉寤鸿鏂板墠绔户缁娇鐢ㄣ��
+
+## 4. Java 绫诲瀷鍒� JSON 绫诲瀷鏄犲皠瑙勫垯
+
+- `Long` / `Integer`锛氫紶 **number**锛堟暣鏁帮級锛屼笉瑕佷紶 `"pending"`銆乣"1绾�"` 杩欑被瀛楃涓层��
+- `BigDecimal`锛氫紶 **number**锛堝彲灏忔暟锛夛紝涓嶈浼犲甫閫楀彿銆佸崟浣嶇殑瀛楃涓诧紙濡� `"12,000鍏�"`锛夈��
+- `Date`锛氫紶 **string**锛屾牸寮忓浐瀹� `yyyy-MM-dd`銆�
+- `Boolean`锛氫紶 `true/false`锛屼笉瑕佷紶 `"true"`銆乣"鏄�"`銆�
+- `List<T>`锛氫紶鏁扮粍 `[]`銆�
+
+## 5. `PurchaseLedgerDto` 瀛楁绫诲瀷绾︽潫锛堢‘璁ゆ帴鍙e彲璇嗗埆瀛楁锛�
+
+| 瀛楁 | Java 绫诲瀷 | JSON 绫诲瀷 | 璇存槑 |
+| --- | --- | --- | --- |
+| entryDateStart | String | string | 鏌ヨ鍖洪棿寮�濮嬫棩鏈燂紙`yyyy-MM-dd`锛� |
+| entryDateEnd | String | string | 鏌ヨ鍖洪棿缁撴潫鏃ユ湡锛坄yyyy-MM-dd`锛� |
+| id | Long | number(integer) | 鍙拌处ID |
+| purchaseContractNumber | String | string | **蹇呭~**锛堜富涓氬姟鍗曞彿锛� |
+| supplierId | Long | number(integer) | `supplierId` 涓� `supplierName` 浜岄�変竴蹇呭~ |
+| supplierName | String | string | `supplierId` 涓� `supplierName` 浜岄�変竴蹇呭~ |
+| recorderId | Long | number(integer) | 褰曞叆浜篒D |
+| recorderName | String | string | 褰曞叆浜哄悕绉� |
+| salesContractNo | String | string | 閿�鍞悎鍚屽彿 |
+| salesContractNoId | Long | number(integer) | 閿�鍞悎鍚孖D |
+| projectName | String | string | 椤圭洰鍚嶇О |
+| entryDate | Date | string(`yyyy-MM-dd`) | 褰曞叆鏃ユ湡锛涚己鐪佹椂鍚庣浼氳ˉ褰撳ぉ |
+| executionDate | Date | string(`yyyy-MM-dd`) | 绛捐鏃ユ湡 |
+| remarks | String | string | 澶囨敞 |
+| attachmentMaterials | String | string | 闄勪欢璇存槑/璺緞 |
+| createdAt | Date | string(`yyyy-MM-dd`) | 鍒涘缓鏃ユ湡 |
+| updatedAt | Date | string(`yyyy-MM-dd`) | 鏇存柊鏃ユ湡 |
+| salesLedgerId | Long | number(integer) | 閿�鍞彴璐D |
+| hasChildren | Boolean | boolean | 鏄惁鏈夊瓙椤� |
+| Type | Integer | number(integer) | 鍘嗗彶瀛楁锛堜笉鎺ㄨ崘鏂板墠绔娇鐢級 |
+| productData | List<SalesLedgerProduct> | array | 浜у搧鏄庣粏锛岃涓嬭妭 |
+| tempFileIds | List<String> | array[string] | 涓存椂鏂囦欢ID鍒楄〃 |
+| SalesLedgerFiles | List<CommonFile> | array[object] | 鍘嗗彶鍏煎瀛楁 |
+| phoneNumber | String | string | 涓氬姟鍛樻墜鏈哄彿 |
+| businessPersonId | Long | number(integer) | 涓氬姟鍛業D |
+| productId | Long | number(integer) | 浜у搧ID |
+| productModelId | Long | number(integer) | 浜у搧瑙勬牸ID |
+| invoiceNumber | String | string | 鍙戠エ鍙� |
+| invoiceAmount | BigDecimal | number | 鍙戠エ閲戦 |
+| ticketRegistrationId | Long | number(integer) | 鏉ョエ鐧昏ID |
+| contractAmount | BigDecimal | number | 鍚堝悓閲戦 |
+| receiptPaymentAmount | BigDecimal | number | 鏉ョエ閲戦 |
+| unReceiptPaymentAmount | BigDecimal | number | 鏈潵绁ㄩ噾棰� |
+| type | Integer | number(integer) | 鍙拌处绫诲瀷锛岄噰璐浐瀹� `2`锛堢己鐪佹椂鍚庣琛� `2`锛� |
+| paymentMethod | String | string | 浠樻鏂瑰紡 |
+| approvalStatus | Integer | number(integer) | 瀹℃壒鐘舵�侊紝**涓ョ浼犲瓧绗︿覆**锛堝 `"pending"`锛� |
+| templateName | String | string | 妯℃澘鍚嶇О |
+
+### 瀹℃壒鐘舵�佸缓璁�硷紙`approvalStatus`锛�
+
+寤鸿鎸夋暟瀛椾紶鍊硷細
+
+- `1`锛氬緟瀹℃牳
+- `2`锛氬鏍镐腑
+- `3`锛氬鏍搁�氳繃
+- `4`锛氬鏍告嫆缁�/澶辫触
+- `5`锛氭ā鏉挎暟鎹紙鍘嗗彶瀹氫箟锛�
+
+## 6. `productData`锛坄SalesLedgerProduct`锛夊缓璁瓧娈靛強绫诲瀷
+
+| 瀛楁 | Java 绫诲瀷 | JSON 绫诲瀷 | 璇存槑 |
+| --- | --- | --- | --- |
+| productCategory | String | string | **蹇呭~**锛屼骇鍝佸ぇ绫�/鍚嶇О |
+| specificationModel | String | string | **蹇呭~**锛岃鏍煎瀷鍙� |
+| unit | String | string | **蹇呭~**锛屽崟浣� |
+| quantity | BigDecimal | number | **蹇呭~**锛屾暟閲� |
+| taxRate | BigDecimal | number | 绋庣巼锛堝 `13`锛� |
+| taxInclusiveUnitPrice | BigDecimal | number | **蹇呭~**锛屽惈绋庡崟浠� |
+| taxInclusiveTotalPrice | BigDecimal | number | **蹇呭~**锛屽惈绋庢�讳环 |
+| taxExclusiveTotalPrice | BigDecimal | number | 涓嶅惈绋庢�讳环锛堝彲涓嶄紶锛屽悗绔彲鎺ㄥ锛� |
+| invoiceType | String | string | 鍙戠エ绫诲瀷 |
+| productId | Long | number(integer) | 浜у搧ID |
+| productModelId | Long | number(integer) | 浜у搧鍨嬪彿ID |
+| isChecked | Boolean | boolean | 鏄惁璐ㄦ |
+| type | Integer | number(integer) | 閲囪喘浜у搧鍥哄畾 `2`锛堝缓璁紶 `2`锛� |
+
+## 7. 蹇呭~涓庡悗绔粯璁よ涓�
+
+- 鍙拌处涓昏〃蹇呭~锛歚purchaseContractNumber`锛屼互鍙� `supplierId` / `supplierName` 浜岄�変竴銆�
+- 浜у搧鏄庣粏鑻ヤ紶浜� `productData`锛屽垯姣忔潯浜у搧蹇呭~锛歚productCategory`銆乣specificationModel`銆乣unit`銆乣quantity`銆乣taxInclusiveUnitPrice`銆乣taxInclusiveTotalPrice`銆�
+- `entryDate` 涓虹┖鏃讹紝鍚庣琛ュ綋澶╂棩鏈熴��
+- `type` 涓虹┖鏃讹紝鍚庣琛� `2`銆�
+
+## 8. 鍓嶇楂橀閿欒绀轰緥
+
+閿欒锛堜細瑙﹀彂鍙嶅簭鍒楀寲寮傚父锛夛細
+
+```json
+{
+  "approvalStatus": "pending",
+  "type": "閲囪喘",
+  "supplierId": "渚涘簲鍟咥"
+}
+```
+
+姝g‘锛�
+
+```json
+{
+  "approvalStatus": 1,
+  "type": 2,
+  "supplierId": 10001
+}
+```
+
+## 9. 鎻愪氦鍓嶈嚜妫�娓呭崟
+
+1. 鎵�鏈� `Long/Integer/BigDecimal` 瀛楁閮戒负鏁板瓧锛屼笉鏄笟鍔¤瘝瀛楃涓层��
+2. 鎵�鏈夋棩鏈熷瓧娈甸兘鏄� `yyyy-MM-dd`銆�
+3. `approvalStatus` 浠呬紶鏁板瓧鐘舵�佺爜銆�
+4. `supplierId` 涓� `supplierName` 鑷冲皯鏈変竴涓湁鏁堝�笺��
+5. `productData` 涓繀濉垪榻愬叏涓斾负姝g‘绫诲瀷銆�
diff --git "a/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md" "b/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..c883a22
--- /dev/null
+++ "b/doc/20260508_\351\207\207\350\264\255\345\244\232\346\226\207\344\273\266\345\210\206\346\236\220\351\231\204\344\273\266\345\255\230\345\202\250\344\270\216\345\216\206\345\217\262\345\233\236\346\230\276\350\201\224\350\260\203\350\257\264\346\230\216.md"
@@ -0,0 +1,149 @@
+# 閲囪喘澶氭枃浠跺垎鏋愰檮浠跺瓨鍌ㄤ笌鍘嗗彶鍥炴樉鑱旇皟璇存槑
+
+> 鏇存柊鏃堕棿锛�2026-05-08  
+> 閫傜敤鑼冨洿锛氶噰璐櫤鑳戒綋澶氭枃浠跺垎鏋� + 鍘嗗彶浼氳瘽闄勪欢鍥炴樉
+
+## 1. 鍙樻洿鑳屾櫙
+
+鍚庣宸茶ˉ榻愪互涓嬭兘鍔涳細
+
+1. `POST /purchase-ai/analyze-files` 涓婁紶鏃跺厛瀛橀檮浠跺埌鏈嶅姟鍣紙鍏叡璁块棶锛夈��
+2. 鎸夋枃浠剁被鍨嬭繑鍥炲彲璁块棶璺緞锛�
+   - 鍥剧墖 / PDF锛氫紭鍏堥瑙堣矾寰�
+   - 鍏跺畠鏂囦欢锛氫紭鍏堜笅杞借矾寰�
+3. 鐢ㄦ埛鎻愰棶涓庨檮浠惰矾寰勫垎寮�瀛樺叆 Mongo銆�
+4. 鍘嗗彶娑堟伅鎺ュ彛鍙寜鈥滄秷鎭淮搴︹�濆洖浼犻檮浠惰矾寰勶紝鍓嶇鍙洿鎺ュ洖鏄鹃檮浠跺崱鐗囥��
+
+---
+
+## 2. 鎺ュ彛琛屼负
+
+### 2.1 澶氭枃浠跺垎鏋愭帴鍙�
+
+```http
+POST /purchase-ai/analyze-files
+Content-Type: multipart/form-data
+```
+
+璇锋眰鍙傛暟锛�
+
+- `files`: `MultipartFile[]`锛堝繀濉級
+- `message`: `string`锛堝彲閫夛級
+- `memoryId`: `string`锛堝彲閫夛級
+
+鍚庣澶勭悊娴佺▼锛�
+
+1. 璋冪敤 `StorageBlobService.upload(files, true)` 涓婁紶骞朵繚瀛橀檮浠躲��
+2. 鐢熸垚闄勪欢璁块棶璺緞锛堥瑙� / 涓嬭浇锛夈��
+3. 灏嗏�滄湰娆℃彁闂� + 鏈闄勪欢璺緞鍒楄〃鈥濆啓鍏� Mongo銆�
+4. 缁х画鎵ц鍘熸湁鏂囦欢瑙f瀽鍜� AI 鍒嗘瀽娴佺▼銆�
+
+鍙兘閿欒锛堟祦寮忔枃鏈級锛�
+
+- `鏂囦欢涓婁紶澶辫触`
+- `浼氳瘽鏂囦欢淇℃伅淇濆瓨澶辫触`
+
+### 2.2 鍘嗗彶娑堟伅鎺ュ彛锛堝墠绔噸鐐癸級
+
+```http
+GET /purchase-ai/history/messages/{memoryId}
+```
+
+娑堟伅瀵硅薄鏂板鍙�夊瓧娈� `filePaths`锛堜粎鐢ㄦ埛娑堟伅鍙兘鏈夊�硷級锛�
+
+```json
+{
+  "role": "user | assistant | system | tool | unknown",
+  "content": "娑堟伅鏂囨湰",
+  "filePaths": [
+    "/common/preview/xxx?publicKey=...",
+    "/common/download/yyy?publicKey=..."
+  ]
+}
+```
+
+璇存槑锛�
+
+1. `filePaths` 鍙兘缂哄け鎴栦负绌猴紙鑰佷細璇� / 鏅�氬璇濓級銆�
+2. 鍗曟潯鐢ㄦ埛娑堟伅鍙兘鍖呭惈澶氫釜闄勪欢璺緞銆�
+3. 璇ュ瓧娈靛凡鎸夋秷鎭淮搴﹀榻愶紝鍙洿鎺ョ敤浜庡巻鍙插洖鏄俱��
+
+---
+
+## 3. 鍓嶇鏀归�犲缓璁紙鎸夊綋鍓嶅疄鐜拌惤鍦帮級
+
+### 3.1 鍘嗗彶鍝嶅簲妯″瀷
+
+寤鸿鍦ㄥ巻鍙叉秷鎭師濮嬬粨鏋勪腑淇濈暀 `filePaths`锛�
+
+```ts
+type AiHistoryMessage = {
+  role: string;
+  content: string;
+  filePaths?: string[];
+};
+```
+
+### 3.2 UI 娑堟伅妯″瀷鏄犲皠
+
+鑻ュ墠绔〉闈㈢敤鐨勬槸 `localUploadFiles` 娓叉煋闄勪欢鍗$墖锛堝 `AIChatSidebar` 褰撳墠瀹炵幇锛夛紝鍘嗗彶娑堟伅闇�鍋氫竴娆℃槧灏勶細
+
+```ts
+type LocalUploadFileItem = {
+  previewId: string;
+  name: string;
+  size: number;
+  type: string;
+  isImage: boolean;
+  previewUrl: string;
+  rawFile: null;
+};
+```
+
+鏄犲皠瑙勫垯寤鸿锛�
+
+1. 浠� `role === 'user'` 涓� `filePaths?.length > 0` 鏃剁敓鎴� `localUploadFiles`銆�
+2. `previewId` 鐢� `${memoryId}-${messageIndex}-${fileIndex}` 鐢熸垚绋冲畾鍊笺��
+3. `name` 鍙粠 URL 璺緞瑙f瀽锛涜В鏋愬け璐ョ敤 `file-{n}`銆�
+4. `isImage` 鍙寜鎵╁睍鍚嶅垽鏂紙`png/jpg/jpeg/gif/webp/bmp/svg`锛夈��
+5. 鍥剧墖鐨� `previewUrl` 鐩存帴浣跨敤璺緞锛涢潪鍥剧墖鍙疆绌哄苟璧板浘鏍囧睍绀恒��
+
+### 3.3 娑堟伅娓叉煋
+
+瀵逛簬鐢ㄦ埛娑堟伅锛�
+
+1. 姝e父娓叉煋 `content`銆�
+2. 鑻ュ瓨鍦� `localUploadFiles`锛堟垨鐩存帴浣跨敤 `filePaths`锛夛紝鍦ㄦ秷鎭笅鏂规覆鏌撻檮浠跺垪琛�/鍗$墖銆�
+3. 閾炬帴鐩存帴浣跨敤鍚庣杩斿洖璺緞锛屼笉鍐嶆嫾鎺ユ垨浜屾鏀瑰啓銆�
+
+> 鍚庣宸插畬鎴愨�滈瑙�/涓嬭浇璺緞鈥濋�夋嫨锛屽墠绔彧璐熻矗灞曠ず涓庢墦寮�銆�
+
+### 3.4 鍏煎瑕佹眰
+
+- `filePaths` 缂哄け锛氫笉鎶ラ敊锛屼笉娓叉煋闄勪欢鍖哄煙銆�
+- 鑰佷細璇濓細缁х画鎸� `role/content` 灞曠ず锛屼笉褰卞搷鍘嗗彶璁板綍鏌ョ湅銆�
+- 澶氶檮浠讹細淇濇寔椤哄簭灞曠ず锛岄伩鍏嶆墦涔辩敤鎴蜂笂浼犻『搴忋��
+
+---
+
+## 4. Mongo 瀛楁璇存槑锛堝悗绔凡瀹炵幇锛�
+
+`chat_messages` 鏂囨。鏂板/浣跨敤瀛楁锛�
+
+- `analyzeUserQuestions: string[]`
+- `analyzeFilePaths: string[]`锛堝吋瀹规棫瀛楁锛�
+- `analyzeFilePathGroups: string[][]`锛堟帹鑽愶紝鎸夋彁闂垎缁勶級
+
+鍘嗗彶娑堟伅鍥炰紶鏃讹紝鍚庣浼樺厛璇诲彇 `analyzeFilePathGroups`锛屽苟鍏煎 `analyzeFilePaths`銆�
+
+---
+
+## 5. 鑱旇皟楠屾敹娓呭崟
+
+1. 鏂板缓浼氳瘽锛屼笂浼� 1 寮犲浘鐗� + 1 涓� Excel锛屽彂閫佸垎鏋愯姹傘��
+2. 璋冪敤 `GET /purchase-ai/history/messages/{memoryId}`锛岀‘璁ょ敤鎴锋秷鎭惈 `filePaths` 涓旀暟閲忔纭��
+3. 鍒锋柊椤甸潰鍚庨噸鏂拌繘鍏ュ悓涓�浼氳瘽锛岀‘璁ら檮浠跺崱鐗囧彲鍥炴樉銆�
+4. 鍒嗗埆楠岃瘉锛�
+   - 鍥剧墖/PDF 鍙瑙堣闂�
+   - 鍏跺畠鏂囦欢鍙笅杞借闂�
+5. 楠岃瘉鑰佷細璇濓紙鏃� `filePaths`锛夊彲姝e父灞曠ず锛岄〉闈笉鎶ラ敊銆�
diff --git a/src/main/java/com/ruoyi/account/dto/AccountDto.java b/src/main/java/com/ruoyi/account/bean/dto/AccountDto.java
similarity index 60%
rename from src/main/java/com/ruoyi/account/dto/AccountDto.java
rename to src/main/java/com/ruoyi/account/bean/dto/AccountDto.java
index 93ed680..5af0ca6 100644
--- a/src/main/java/com/ruoyi/account/dto/AccountDto.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/AccountDto.java
@@ -1,22 +1,10 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
-import com.baomidou.mybatisplus.annotation.*;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.ruoyi.account.pojo.AccountExpense;
-import com.ruoyi.account.pojo.AccountIncome;
-import com.ruoyi.dto.DateQueryDto;
-import com.ruoyi.framework.aspectj.lang.annotation.Excel;
-import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
 
-import jakarta.validation.constraints.NotBlank;
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.Date;
 import java.util.List;
-import java.util.Map;
 
 /**
  * 璐㈠姟绠$悊--璐㈠姟鎶ヨ〃
diff --git a/src/main/java/com/ruoyi/account/dto/AccountDto2.java b/src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java
similarity index 72%
rename from src/main/java/com/ruoyi/account/dto/AccountDto2.java
rename to src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java
index 235278b..7c4e9e5 100644
--- a/src/main/java/com/ruoyi/account/dto/AccountDto2.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java
@@ -1,12 +1,9 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
-import com.ruoyi.account.pojo.AccountExpense;
-import com.ruoyi.account.pojo.AccountIncome;
 import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.util.List;
 
 /**
  * 璐㈠姟绠$悊--璐㈠姟鎶ヨ〃(绫诲瀷)
diff --git a/src/main/java/com/ruoyi/account/dto/AccountDto3.java b/src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java
similarity index 91%
rename from src/main/java/com/ruoyi/account/dto/AccountDto3.java
rename to src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java
index 33b4b8e..117fa77 100644
--- a/src/main/java/com/ruoyi/account/dto/AccountDto3.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java
@@ -1,4 +1,4 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import lombok.Data;
 
diff --git a/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectDto.java b/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectDto.java
new file mode 100644
index 0000000..e26844d
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectDto.java
@@ -0,0 +1,10 @@
+package com.ruoyi.account.bean.dto;
+
+import com.ruoyi.account.pojo.AccountSubject;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AccountSubjectDto extends AccountSubject {
+}
diff --git a/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectImportDto.java b/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectImportDto.java
new file mode 100644
index 0000000..28c8ab9
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/dto/AccountSubjectImportDto.java
@@ -0,0 +1,36 @@
+package com.ruoyi.account.bean.dto;
+
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class AccountSubjectImportDto {
+
+    @Schema(description = "绉戠洰缂栫爜")
+    @Excel(name = "绉戠洰缂栫爜")
+    private String subjectCode;
+
+    @Schema(description = "绉戠洰鍚嶇О")
+    @Excel(name = "绉戠洰鍚嶇О")
+    private String subjectName;
+
+    @Schema(description = "绉戠洰绫诲瀷")
+    @Excel(name = "绉戠洰绫诲瀷")
+    private String subjectType;
+
+    @Schema(description = "浣欓鏂瑰悜")
+    @Excel(name = "浣欓鏂瑰悜")
+    private String balanceDirection;
+
+    /**
+     * 鐘舵�� 0鍚敤 1绂佺敤
+     */
+    @Schema(description = "鐘舵��")
+    @Excel(name = "鐘舵��",readConverterExp = "0=鍚敤,1=绂佺敤")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    @Excel(name = "澶囨敞")
+    private String remark;
+}
diff --git a/src/main/java/com/ruoyi/account/dto/DeviceTypeDetail.java b/src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDetail.java
similarity index 88%
rename from src/main/java/com/ruoyi/account/dto/DeviceTypeDetail.java
rename to src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDetail.java
index ccbc897..1ea4837 100644
--- a/src/main/java/com/ruoyi/account/dto/DeviceTypeDetail.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDetail.java
@@ -1,4 +1,4 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import lombok.Data;
 
diff --git a/src/main/java/com/ruoyi/account/dto/DeviceTypeDistributionVO.java b/src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDistributionVO.java
similarity index 94%
rename from src/main/java/com/ruoyi/account/dto/DeviceTypeDistributionVO.java
rename to src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDistributionVO.java
index 1c294bd..f540b42 100644
--- a/src/main/java/com/ruoyi/account/dto/DeviceTypeDistributionVO.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/DeviceTypeDistributionVO.java
@@ -1,4 +1,4 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import lombok.Data;
 
diff --git a/src/main/java/com/ruoyi/account/dto/ReportDateDto.java b/src/main/java/com/ruoyi/account/bean/dto/ReportDateDto.java
similarity index 92%
rename from src/main/java/com/ruoyi/account/dto/ReportDateDto.java
rename to src/main/java/com/ruoyi/account/bean/dto/ReportDateDto.java
index ab3799a..5e89162 100644
--- a/src/main/java/com/ruoyi/account/dto/ReportDateDto.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/ReportDateDto.java
@@ -1,11 +1,10 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDate;
-import java.util.Date;
 
 /**
  * @author :yys
diff --git a/src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java b/src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java
new file mode 100644
index 0000000..a30e035
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/dto/SalesOutboundDto.java
@@ -0,0 +1,26 @@
+package com.ruoyi.account.bean.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@Schema(name = "SalesOutboundDto", description = "璐㈠姟绠$悊--閿�鍞嚭搴撳彴璐�(浼犲弬)")
+public class SalesOutboundDto {
+
+    @Schema(description = "鍑哄簱鍗曞彿")
+    private String outboundBatches;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date startDate;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date endDate;
+}
diff --git a/src/main/java/com/ruoyi/account/dto/SalesReceiptReturnDto.java b/src/main/java/com/ruoyi/account/bean/dto/SalesReceiptReturnDto.java
similarity index 79%
rename from src/main/java/com/ruoyi/account/dto/SalesReceiptReturnDto.java
rename to src/main/java/com/ruoyi/account/bean/dto/SalesReceiptReturnDto.java
index 728d9a7..e0d9e81 100644
--- a/src/main/java/com/ruoyi/account/dto/SalesReceiptReturnDto.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/SalesReceiptReturnDto.java
@@ -1,4 +1,4 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import com.ruoyi.account.pojo.SalesReceiptReturn;
 import lombok.Data;
diff --git a/src/main/java/com/ruoyi/account/dto/SalesRefundAmountOrderDto.java b/src/main/java/com/ruoyi/account/bean/dto/SalesRefundAmountOrderDto.java
similarity index 86%
rename from src/main/java/com/ruoyi/account/dto/SalesRefundAmountOrderDto.java
rename to src/main/java/com/ruoyi/account/bean/dto/SalesRefundAmountOrderDto.java
index a948957..7ea68b9 100644
--- a/src/main/java/com/ruoyi/account/dto/SalesRefundAmountOrderDto.java
+++ b/src/main/java/com/ruoyi/account/bean/dto/SalesRefundAmountOrderDto.java
@@ -1,7 +1,6 @@
-package com.ruoyi.account.dto;
+package com.ruoyi.account.bean.dto;
 
 import com.ruoyi.account.pojo.SalesRefundAmountOrder;
-import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
diff --git a/src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java b/src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java
new file mode 100644
index 0000000..d635408
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/dto/SalesReturnDto.java
@@ -0,0 +1,26 @@
+package com.ruoyi.account.bean.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@Schema(name = "SalesReturnDto", description = "璐㈠姟绠$悊--閿�鍞��璐у彴璐�(浼犲弬)")
+public class SalesReturnDto {
+
+    @Schema(description = "閫�璐у崟鍙�")
+    private String returnNo;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "寮�濮嬫棩鏈�")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date startDate;
+
+    @Schema(description = "缁撴潫鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date endDate;
+}
diff --git a/src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java b/src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java
new file mode 100644
index 0000000..3154d1c
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/vo/AccountSubjectVo.java
@@ -0,0 +1,8 @@
+package com.ruoyi.account.bean.vo;
+
+import com.ruoyi.account.pojo.AccountSubject;
+import lombok.Data;
+
+@Data
+public class AccountSubjectVo extends AccountSubject {
+}
diff --git a/src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java b/src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java
new file mode 100644
index 0000000..cfc0a68
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/vo/SalesOutboundVo.java
@@ -0,0 +1,52 @@
+package com.ruoyi.account.bean.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@Schema(name = "SalesOutboundVo", description = "璐㈠姟绠$悊--閿�鍞嚭搴撳彴璐�(杩斿洖)")
+@ExcelIgnoreUnannotated
+public class SalesOutboundVo {
+
+    @Schema(description = "鍑哄簱鍗昳d")
+    private Long id;
+
+    @Schema(description = "鍑哄簱鍗曞彿")
+    @Excel(name = "鍑哄簱鍗曞彿")
+    private String outboundBatches;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    @Excel(name = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍑哄簱鏃ユ湡")
+    @Excel(name = "鍑哄簱鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date shippingDate;
+
+    @Schema(description = "浜у搧鍚嶇О")
+    @Excel(name = "浜у搧鍚嶇О")
+    private String productName;
+
+    @Schema(description = "浜у搧瑙勬牸")
+    @Excel(name = "浜у搧瑙勬牸")
+    private String  specificationModel;
+
+    @Schema(description = "鍑哄簱鏁伴噺")
+    @Excel(name = "鍑哄簱鏁伴噺")
+    private BigDecimal stockOutNum;
+
+    @Schema(description = "鍙戣揣缂栧彿")
+    @Excel(name = "鍙戣揣缂栧彿")
+    private String shippingNo;
+
+    @Schema(description = "閿�鍞鍗曞彿")
+    @Excel(name = "閿�鍞鍗曞彿")
+    private String salesContractNo;
+}
diff --git a/src/main/java/com/ruoyi/account/bean/vo/SalesReturnVo.java b/src/main/java/com/ruoyi/account/bean/vo/SalesReturnVo.java
new file mode 100644
index 0000000..c425737
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/bean/vo/SalesReturnVo.java
@@ -0,0 +1,48 @@
+package com.ruoyi.account.bean.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(name = "SalesReturnVo", description = "璐㈠姟绠$悊--閿�鍞��璐у彴璐�(杩斿洖)")
+@ExcelIgnoreUnannotated
+public class SalesReturnVo {
+
+    @Schema(description = "閫�璐у崟id")
+    private Long id;
+
+    @Excel(name = "閫�璐у崟鍙�")
+    @Schema(description = "閫�璐у崟鍙�")
+    private String returnNo;
+
+    @Schema(description = "瀹㈡埛鍚嶇О")
+    @Excel(name = "瀹㈡埛鍚嶇О")
+    private String customerName;
+
+    @Schema(description = "鍏宠仈鍙戣揣鍗曞彿")
+    @Excel(name = "鍏宠仈鍙戣揣鍗曞彿")
+    private String shippingNo;
+
+    @Schema(description = "閫�璐ф棩鏈�")
+    @Excel(name = "閫�璐ф棩鏈�")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime makeTime;
+
+    @Schema(description = "閫�娆炬�婚")
+    @Excel(name = "閫�娆炬�婚")
+    private BigDecimal refundAmount;
+
+    @Schema(description = "閫�璐у師鍥�")
+    @Excel(name = "閫�璐у師鍥�")
+    private String returnReason;
+
+    @Schema(description = "閿�鍞鍗曞彿")
+    @Excel(name = "閿�鍞鍗曞彿")
+    private String salesContractNo;
+}
diff --git a/src/main/java/com/ruoyi/account/controller/AccountExpenseController.java b/src/main/java/com/ruoyi/account/controller/AccountExpenseController.java
index 4796d33..9d49919 100644
--- a/src/main/java/com/ruoyi/account/controller/AccountExpenseController.java
+++ b/src/main/java/com/ruoyi/account/controller/AccountExpenseController.java
@@ -1,7 +1,7 @@
 package com.ruoyi.account.controller;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.ReportDateDto;
+import com.ruoyi.account.bean.dto.ReportDateDto;
 import com.ruoyi.account.pojo.AccountExpense;
 import com.ruoyi.account.service.AccountExpenseService;
 import com.ruoyi.account.service.AccountIncomeService;
diff --git a/src/main/java/com/ruoyi/account/controller/AccountSalesController.java b/src/main/java/com/ruoyi/account/controller/AccountSalesController.java
new file mode 100644
index 0000000..5fb5e4f
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/controller/AccountSalesController.java
@@ -0,0 +1,68 @@
+package com.ruoyi.account.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.SalesOutboundDto;
+import com.ruoyi.account.bean.dto.SalesReturnDto;
+import com.ruoyi.account.bean.vo.SalesOutboundVo;
+import com.ruoyi.account.bean.vo.SalesReturnVo;
+import com.ruoyi.account.service.AccountSalesService;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 璐㈠姟绠$悊鐨勯攢鍞儴鍒� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@RestController
+@RequestMapping("/accountSales")
+@RequiredArgsConstructor
+@Tag(name = "璐㈠姟绠$悊鐨勯攢鍞儴鍒�")
+public class AccountSalesController {
+
+    private final AccountSalesService accountSalesService;
+
+    @GetMapping("/listPageByOutbound")
+    @Log(title = "閿�鍞嚭搴撳彴璐�", businessType = BusinessType.OTHER)
+    @Operation(summary = "璐㈠姟绠$悊--閿�鍞嚭搴撳彴璐�")
+    public R<IPage<SalesOutboundVo>> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) {
+        IPage<SalesOutboundVo> listPage = accountSalesService.listPageByOutbound(page,salesOutboundDto);
+        return R.ok(listPage);
+    }
+
+    @PostMapping("/exportAccountSalesOutbound")
+    @Operation(summary = "瀵煎嚭閿�鍞嚭搴撴枃浠�")
+    @Log(title = "瀵煎嚭閿�鍞嚭搴撴枃浠�", businessType = BusinessType.EXPORT)
+    public void exportAccountSalesOutbound(HttpServletResponse response,SalesOutboundDto salesOutboundDto) {
+        accountSalesService.exportAccountSalesOutbound(response,salesOutboundDto);
+    }
+
+    @GetMapping("/listPageByReturn")
+    @Log(title = "閿�鍞��璐у彴璐�", businessType = BusinessType.OTHER)
+    @Operation(summary = "璐㈠姟绠$悊--閿�鍞��璐у彴璐�")
+    public R<IPage<SalesReturnVo>> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) {
+        IPage<SalesReturnVo> listPage = accountSalesService.listPageBySalesReturn(page,salesReturnDto);
+        return R.ok(listPage);
+    }
+
+    @PostMapping("/exportAccountSalesReturn")
+    @Operation(summary = "瀵煎嚭閿�鍞��璐ф枃浠�")
+    @Log(title = "瀵煎嚭閿�鍞��璐ф枃浠�", businessType = BusinessType.EXPORT)
+    public void exportAccountSalesReturn(HttpServletResponse response,SalesReturnDto salesReturnDto) {
+        accountSalesService.exportAccountSalesReturn(response,salesReturnDto);
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/account/controller/AccountSubjectController.java b/src/main/java/com/ruoyi/account/controller/AccountSubjectController.java
new file mode 100644
index 0000000..8282883
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/controller/AccountSubjectController.java
@@ -0,0 +1,70 @@
+package com.ruoyi.account.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.AccountSubjectDto;
+import com.ruoyi.account.bean.vo.AccountSubjectVo;
+import com.ruoyi.account.service.AccountSubjectService;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * <p>
+ * 鎬昏处绉戠洰琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@RestController
+@RequestMapping("/accountSubject")
+@RequiredArgsConstructor
+@Tag(name = "鎬昏处绉戠洰")
+public class AccountSubjectController {
+    private final AccountSubjectService accountSubjectService;
+
+    @GetMapping("/list")
+    @Log(title = "鎬昏处绉戠洰鏁版嵁闆嗗悎", businessType = BusinessType.OTHER)
+    @Operation(summary = "鎬昏处绉戠洰鍒嗛〉鏌ヨ")
+    public R<IPage<AccountSubjectVo>> AccountSubjectDtoList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) {
+        IPage<AccountSubjectVo> paramList = accountSubjectService.baseList(page, accountSubjectDto);
+        return R.ok(paramList);
+    }
+
+    @PostMapping("/add")
+    @Log(title = "鏂板鎬昏处绉戠洰", businessType = BusinessType.INSERT)
+    @Operation(summary = "鏂板鎬昏处绉戠洰")
+    public R AccountSubjectDtoAdd(@RequestBody AccountSubjectDto accountSubjectDto) {
+        return R.ok(accountSubjectService.save(accountSubjectDto));
+    }
+
+    @PutMapping("/edit")
+    @Log(title = "淇敼鎬昏处绉戠洰", businessType = BusinessType.UPDATE)
+    @Operation(summary = "淇敼鎬昏处绉戠洰")
+    public R AccountSubjectDtoEdit(@RequestBody AccountSubjectDto accountSubjectDto) {
+        return R.ok(accountSubjectService.updateById(accountSubjectDto));
+    }
+
+    @DeleteMapping("/remove/{ids}")
+    @Log(title = "鍒犻櫎鎬昏处绉戠洰", businessType = BusinessType.DELETE)
+    @Operation(summary = "鍒犻櫎鎬昏处绉戠洰")
+    public R AccountSubjectDtooRemove(@PathVariable Long[] ids) {
+        return R.ok(accountSubjectService.removeBatchByIds(Arrays.asList(ids)));
+    }
+
+    @PostMapping("/export")
+    @Operation(summary = "瀵煎嚭鎬昏处绉戠洰鏂囦欢")
+    @Log(title = "瀵煎嚭鎬昏处绉戠洰鏂囦欢", businessType = BusinessType.EXPORT)
+    public void exportAccountSubject(HttpServletResponse response) {
+        accountSubjectService.exportAccountSubject(response);
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java b/src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java
index 786e7c3..5968d10 100644
--- a/src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java
+++ b/src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java
@@ -1,7 +1,7 @@
 package com.ruoyi.account.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.ruoyi.account.dto.SalesReceiptReturnDto;
+import com.ruoyi.account.bean.dto.SalesReceiptReturnDto;
 import com.ruoyi.account.pojo.SalesReceiptReturn;
 import com.ruoyi.account.service.SalesReceiptReturnService;
 import com.ruoyi.account.service.impl.SalesReceiptReturnServiceImpl;
diff --git a/src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java b/src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java
index 7f8bf25..f547377 100644
--- a/src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java
+++ b/src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java
@@ -1,7 +1,7 @@
 package com.ruoyi.account.controller;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.SalesRefundAmountOrderDto;
+import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto;
 import com.ruoyi.account.pojo.SalesRefundAmountOrder;
 import com.ruoyi.account.service.SalesRefundAmountOrderService;
 import com.ruoyi.framework.web.domain.R;
diff --git a/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java b/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
index 3cd3fc8..4876e7b 100644
--- a/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
+++ b/src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
@@ -3,8 +3,8 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.AccountDto;
-import com.ruoyi.account.dto.AccountDto2;
+import com.ruoyi.account.bean.dto.AccountDto;
+import com.ruoyi.account.bean.dto.AccountDto2;
 import com.ruoyi.account.pojo.AccountExpense;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.dto.DateQueryDto;
diff --git a/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java b/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
index 9a9f951..9bb3f3a 100644
--- a/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
+++ b/src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
@@ -3,7 +3,7 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.AccountDto2;
+import com.ruoyi.account.bean.dto.AccountDto2;
 import com.ruoyi.account.pojo.AccountFile;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.dto.DateQueryDto;
diff --git a/src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java b/src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java
new file mode 100644
index 0000000..1fface5
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/mapper/AccountSubjectMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.account.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.account.pojo.AccountSubject;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 鎬昏处绉戠洰琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@Mapper
+public interface AccountSubjectMapper extends BaseMapper<AccountSubject> {
+
+}
diff --git a/src/main/java/com/ruoyi/account/mapper/SalesRefundAmountOrderMapper.java b/src/main/java/com/ruoyi/account/mapper/SalesRefundAmountOrderMapper.java
index adfae86..e2b0761 100644
--- a/src/main/java/com/ruoyi/account/mapper/SalesRefundAmountOrderMapper.java
+++ b/src/main/java/com/ruoyi/account/mapper/SalesRefundAmountOrderMapper.java
@@ -2,7 +2,7 @@
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.SalesRefundAmountOrderDto;
+import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto;
 import com.ruoyi.account.pojo.SalesRefundAmountOrder;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
diff --git a/src/main/java/com/ruoyi/account/pojo/AccountSubject.java b/src/main/java/com/ruoyi/account/pojo/AccountSubject.java
new file mode 100644
index 0000000..49ba3da
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/pojo/AccountSubject.java
@@ -0,0 +1,111 @@
+package com.ruoyi.account.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 鎬昏处绉戠洰琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@Getter
+@Setter
+@ToString
+@TableName("account_subject")
+@ApiModel(value = "AccountSubject瀵硅薄", description = "鎬昏处绉戠洰琛�")
+public class AccountSubject implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @ApiModelProperty("涓婚敭ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 绉戠洰缂栫爜(鍞竴鏍囪瘑)
+     */
+    @ApiModelProperty("绉戠洰缂栫爜(鍞竴鏍囪瘑)")
+    private String subjectCode;
+
+    /**
+     * 绉戠洰鍚嶇О
+     */
+    @ApiModelProperty("绉戠洰鍚嶇О")
+    private String subjectName;
+
+    /**
+     * 绉戠洰绫诲瀷
+     */
+    @ApiModelProperty("绉戠洰绫诲瀷")
+    private String subjectType;
+
+    /**
+     * 浣欓鏂瑰悜
+     */
+    @ApiModelProperty("浣欓鏂瑰悜")
+    private String balanceDirection;
+
+    /**
+     * 鐘舵�� 0鍚敤 1绂佺敤
+     */
+    @ApiModelProperty("鐘舵�� 0鍚敤 1绂佺敤")
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    @ApiModelProperty("澶囨敞")
+    private String remark;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @ApiModelProperty("鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private String createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ApiModelProperty("鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 淇敼浜�
+     */
+    @ApiModelProperty("淇敼浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateUser;
+
+    /**
+     * 淇敼鏃堕棿
+     */
+    @ApiModelProperty("淇敼鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 閮ㄩ棬ID
+     */
+    @ApiModelProperty("閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/account/service/AccountExpenseService.java b/src/main/java/com/ruoyi/account/service/AccountExpenseService.java
index 4997c23..b070485 100644
--- a/src/main/java/com/ruoyi/account/service/AccountExpenseService.java
+++ b/src/main/java/com/ruoyi/account/service/AccountExpenseService.java
@@ -3,10 +3,10 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.account.dto.AccountDto;
-import com.ruoyi.account.dto.AccountDto2;
-import com.ruoyi.account.dto.AccountDto3;
-import com.ruoyi.account.dto.ReportDateDto;
+import com.ruoyi.account.bean.dto.AccountDto;
+import com.ruoyi.account.bean.dto.AccountDto2;
+import com.ruoyi.account.bean.dto.AccountDto3;
+import com.ruoyi.account.bean.dto.ReportDateDto;
 import com.ruoyi.account.pojo.AccountExpense;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.dto.DateQueryDto;
diff --git a/src/main/java/com/ruoyi/account/service/AccountIncomeService.java b/src/main/java/com/ruoyi/account/service/AccountIncomeService.java
index 33bb02f..1f5d684 100644
--- a/src/main/java/com/ruoyi/account/service/AccountIncomeService.java
+++ b/src/main/java/com/ruoyi/account/service/AccountIncomeService.java
@@ -3,9 +3,9 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.account.dto.AccountDto2;
-import com.ruoyi.account.dto.AccountDto3;
-import com.ruoyi.account.dto.ReportDateDto;
+import com.ruoyi.account.bean.dto.AccountDto2;
+import com.ruoyi.account.bean.dto.AccountDto3;
+import com.ruoyi.account.bean.dto.ReportDateDto;
 import com.ruoyi.account.pojo.AccountIncome;
 
 import jakarta.servlet.http.HttpServletResponse;
diff --git a/src/main/java/com/ruoyi/account/service/AccountSalesService.java b/src/main/java/com/ruoyi/account/service/AccountSalesService.java
new file mode 100644
index 0000000..930c5dd
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/AccountSalesService.java
@@ -0,0 +1,28 @@
+package com.ruoyi.account.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.SalesOutboundDto;
+import com.ruoyi.account.bean.dto.SalesReturnDto;
+import com.ruoyi.account.bean.vo.SalesOutboundVo;
+import com.ruoyi.account.bean.vo.SalesReturnVo;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * <p>
+ * 璐㈠姟绠$悊鐨勯攢鍞儴鍒� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+public interface AccountSalesService  {
+
+    IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto);
+
+    void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto);
+
+    IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto);
+
+    void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto);
+}
diff --git a/src/main/java/com/ruoyi/account/service/AccountSubjectService.java b/src/main/java/com/ruoyi/account/service/AccountSubjectService.java
new file mode 100644
index 0000000..a7dd670
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/AccountSubjectService.java
@@ -0,0 +1,24 @@
+package com.ruoyi.account.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.AccountSubjectDto;
+import com.ruoyi.account.bean.vo.AccountSubjectVo;
+import com.ruoyi.account.pojo.AccountSubject;
+import com.baomidou.mybatisplus.extension.service.IService;
+import jakarta.servlet.http.HttpServletResponse;
+
+/**
+ * <p>
+ * 鎬昏处绉戠洰琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+public interface AccountSubjectService extends IService<AccountSubject> {
+
+    IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto);
+
+    void exportAccountSubject(HttpServletResponse response);
+}
diff --git a/src/main/java/com/ruoyi/account/service/SalesReceiptReturnService.java b/src/main/java/com/ruoyi/account/service/SalesReceiptReturnService.java
index 77c6aa9..456415d 100644
--- a/src/main/java/com/ruoyi/account/service/SalesReceiptReturnService.java
+++ b/src/main/java/com/ruoyi/account/service/SalesReceiptReturnService.java
@@ -1,7 +1,7 @@
 package com.ruoyi.account.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.ruoyi.account.dto.SalesReceiptReturnDto;
+import com.ruoyi.account.bean.dto.SalesReceiptReturnDto;
 import com.ruoyi.account.pojo.SalesReceiptReturn;
 import com.baomidou.mybatisplus.extension.service.IService;
 
diff --git a/src/main/java/com/ruoyi/account/service/SalesRefundAmountOrderService.java b/src/main/java/com/ruoyi/account/service/SalesRefundAmountOrderService.java
index bf45755..4465913 100644
--- a/src/main/java/com/ruoyi/account/service/SalesRefundAmountOrderService.java
+++ b/src/main/java/com/ruoyi/account/service/SalesRefundAmountOrderService.java
@@ -2,7 +2,7 @@
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.SalesRefundAmountOrderDto;
+import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto;
 import com.ruoyi.account.pojo.SalesRefundAmountOrder;
 import com.baomidou.mybatisplus.extension.service.IService;
 
diff --git a/src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java
index 40b1f31..9d392a2 100644
--- a/src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java
@@ -5,10 +5,10 @@
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.account.dto.AccountDto;
-import com.ruoyi.account.dto.AccountDto2;
-import com.ruoyi.account.dto.AccountDto3;
-import com.ruoyi.account.dto.ReportDateDto;
+import com.ruoyi.account.bean.dto.AccountDto;
+import com.ruoyi.account.bean.dto.AccountDto2;
+import com.ruoyi.account.bean.dto.AccountDto3;
+import com.ruoyi.account.bean.dto.ReportDateDto;
 import com.ruoyi.account.mapper.AccountExpenseMapper;
 import com.ruoyi.account.mapper.AccountIncomeMapper;
 import com.ruoyi.account.pojo.AccountExpense;
diff --git a/src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java
index 4a754e0..e760a2d 100644
--- a/src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java
@@ -5,8 +5,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.account.dto.AccountDto3;
-import com.ruoyi.account.dto.ReportDateDto;
+import com.ruoyi.account.bean.dto.AccountDto3;
+import com.ruoyi.account.bean.dto.ReportDateDto;
 import com.ruoyi.account.mapper.AccountIncomeMapper;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.account.service.AccountIncomeService;
diff --git a/src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java
new file mode 100644
index 0000000..8ad55fe
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/impl/AccountSalesServiceImpl.java
@@ -0,0 +1,58 @@
+package com.ruoyi.account.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.SalesOutboundDto;
+import com.ruoyi.account.bean.dto.SalesReturnDto;
+import com.ruoyi.account.bean.vo.SalesOutboundVo;
+import com.ruoyi.account.bean.vo.SalesReturnVo;
+import com.ruoyi.account.service.AccountSalesService;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
+import com.ruoyi.sales.mapper.ShippingInfoMapper;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 璐㈠姟绠$悊鐨勯攢鍞儴鍒� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@Service
+@RequiredArgsConstructor
+public class AccountSalesServiceImpl  implements AccountSalesService {
+
+    private final ShippingInfoMapper shippingInfoMapper;
+
+    private final ReturnManagementMapper returnManagementMapper;
+
+    @Override
+    public IPage<SalesOutboundVo> listPageByOutbound(Page page, SalesOutboundDto salesOutboundDto) {
+        return shippingInfoMapper.listPageByOutbound(page,salesOutboundDto);
+    }
+
+    @Override
+    public void exportAccountSalesOutbound(HttpServletResponse response, SalesOutboundDto salesOutboundDto) {
+        List<SalesOutboundVo> list = shippingInfoMapper.listPageByOutbound(new Page(1,-1),salesOutboundDto).getRecords();
+        ExcelUtil<SalesOutboundVo> util = new ExcelUtil<>(SalesOutboundVo.class);
+        util.exportExcel(response, list , "閿�鍞嚭搴�");
+    }
+
+    @Override
+    public IPage<SalesReturnVo> listPageBySalesReturn(Page page, SalesReturnDto salesReturnDto) {
+        return returnManagementMapper.listPageBySalesReturn(page,salesReturnDto);
+    }
+
+    @Override
+    public void exportAccountSalesReturn(HttpServletResponse response, SalesReturnDto salesReturnDto) {
+        List<SalesReturnVo> list = returnManagementMapper.listPageBySalesReturn(new Page(1,-1),salesReturnDto).getRecords();
+        ExcelUtil<SalesReturnVo> util = new ExcelUtil<>(SalesReturnVo.class);
+        util.exportExcel(response, list , "閿�鍞��璐�");
+    }
+}
diff --git a/src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java
new file mode 100644
index 0000000..152966a
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/service/impl/AccountSubjectServiceImpl.java
@@ -0,0 +1,77 @@
+package com.ruoyi.account.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.account.bean.dto.AccountSubjectDto;
+import com.ruoyi.account.bean.dto.AccountSubjectImportDto;
+import com.ruoyi.account.bean.vo.AccountSubjectVo;
+import com.ruoyi.account.mapper.AccountSubjectMapper;
+import com.ruoyi.account.pojo.AccountSubject;
+import com.ruoyi.account.service.AccountSubjectService;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 鎬昏处绉戠洰琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-07 04:45:30
+ */
+@Service
+@RequiredArgsConstructor
+public class AccountSubjectServiceImpl extends ServiceImpl<AccountSubjectMapper, AccountSubject> implements AccountSubjectService {
+
+    private final AccountSubjectMapper accountSubjectMapper;
+
+    @Override
+    public IPage<AccountSubjectVo> baseList(Page<AccountSubjectDto> page, AccountSubjectDto accountSubjectDto) {
+        LambdaQueryWrapper<AccountSubject> queryWrapper = new LambdaQueryWrapper<>();
+        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectCode())) {
+            queryWrapper.like(AccountSubject::getSubjectCode, accountSubjectDto.getSubjectCode());
+        }
+        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectName())) {
+            queryWrapper.like(AccountSubject::getSubjectName, accountSubjectDto.getSubjectName());
+        }
+        if (accountSubjectDto != null && StringUtils.isNotEmpty(accountSubjectDto.getSubjectType())) {
+            queryWrapper.eq(AccountSubject::getSubjectType, accountSubjectDto.getSubjectType());
+        }
+        queryWrapper.orderByDesc(AccountSubject::getId);
+
+        Page<AccountSubject> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        Page<AccountSubject> paramPage = page(entityPage, queryWrapper);
+
+        Page<AccountSubjectVo> resultPage = new Page<>(paramPage.getCurrent(), paramPage.getSize(), paramPage.getTotal());
+        List<AccountSubjectVo> records = new ArrayList<>(paramPage.getRecords().size());
+        for (AccountSubject item : paramPage.getRecords()) {
+            AccountSubjectVo vo = new AccountSubjectVo();
+            BeanUtils.copyProperties(item, vo);
+            records.add(vo);
+        }
+        resultPage.setRecords(records);
+        return resultPage;
+    }
+
+    @Override
+    public void exportAccountSubject(HttpServletResponse response) {
+        List<AccountSubject> list = accountSubjectMapper.selectList(null);
+        List<AccountSubjectImportDto> importDtos = list.stream().map(accountSubject -> {
+            AccountSubjectImportDto accountSubjectImportDto = new AccountSubjectImportDto();
+            BeanUtils.copyProperties(accountSubject, accountSubjectImportDto);
+            return accountSubjectImportDto;
+        }).collect(Collectors.toList());
+        ExcelUtil<AccountSubjectImportDto> util = new ExcelUtil<>(AccountSubjectImportDto.class);
+        util.exportExcel(response, importDtos , "鎬昏处绉戠洰");
+    }
+}
diff --git a/src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java
index 0ed77af..ab38285 100644
--- a/src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java
@@ -4,8 +4,8 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.DeviceTypeDetail;
-import com.ruoyi.account.dto.DeviceTypeDistributionVO;
+import com.ruoyi.account.bean.dto.DeviceTypeDetail;
+import com.ruoyi.account.bean.dto.DeviceTypeDistributionVO;
 import com.ruoyi.account.mapper.BorrowInfoMapper;
 import com.ruoyi.account.pojo.BorrowInfo;
 import com.ruoyi.device.mapper.DeviceLedgerMapper;
diff --git a/src/main/java/com/ruoyi/account/service/impl/SalesReceiptReturnServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/SalesReceiptReturnServiceImpl.java
index 3aa201e..c39697f 100644
--- a/src/main/java/com/ruoyi/account/service/impl/SalesReceiptReturnServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/SalesReceiptReturnServiceImpl.java
@@ -1,7 +1,7 @@
 package com.ruoyi.account.service.impl;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.ruoyi.account.dto.SalesReceiptReturnDto;
+import com.ruoyi.account.bean.dto.SalesReceiptReturnDto;
 import com.ruoyi.account.pojo.SalesReceiptReturn;
 import com.ruoyi.account.mapper.SalesReceiptReturnMapper;
 import com.ruoyi.account.service.SalesReceiptReturnService;
diff --git a/src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java
index 3dcc1cd..3bd5de9 100644
--- a/src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java
@@ -3,7 +3,7 @@
 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.account.dto.SalesRefundAmountOrderDto;
+import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto;
 import com.ruoyi.account.mapper.SalesRefundAmountOrderMapper;
 import com.ruoyi.account.pojo.SalesRefundAmountOrder;
 import com.ruoyi.account.service.SalesRefundAmountOrderService;
diff --git a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
index 2bb0625..158ea61 100644
--- a/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
+++ b/src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
@@ -1,151 +1,41 @@
 package com.ruoyi.ai.controller;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.ruoyi.ai.assistant.PurchaseAgent;
-import com.ruoyi.ai.assistant.PurchaseIntentExecutor;
 import com.ruoyi.ai.bean.ChatForm;
 import com.ruoyi.ai.bean.PurchaseAiConfirmRequest;
-import com.ruoyi.ai.context.AiSessionUserContext;
-import com.ruoyi.ai.service.AiChatSessionService;
-import com.ruoyi.ai.service.AiFileTextExtractor;
-import com.ruoyi.ai.store.MongoChatMemoryStore;
-import com.ruoyi.basic.mapper.SupplierManageMapper;
-import com.ruoyi.basic.pojo.SupplierManage;
+import com.ruoyi.ai.service.PurchaseAiService;
 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 com.ruoyi.purchase.dto.PurchaseLedgerDto;
-import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
-import com.ruoyi.purchase.pojo.PaymentRegistration;
-import com.ruoyi.purchase.service.IPaymentRegistrationService;
-import com.ruoyi.purchase.service.IPurchaseLedgerService;
-import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
-import com.ruoyi.sales.pojo.SalesLedgerProduct;
-import dev.langchain4j.data.image.Image;
-import dev.langchain4j.data.message.AiMessage;
-import dev.langchain4j.data.message.ChatMessage;
-import dev.langchain4j.data.message.Content;
-import dev.langchain4j.data.message.ImageContent;
-import dev.langchain4j.data.message.SystemMessage;
-import dev.langchain4j.data.message.TextContent;
-import dev.langchain4j.data.message.UserMessage;
-import dev.langchain4j.model.chat.StreamingChatLanguageModel;
-import dev.langchain4j.model.chat.response.ChatResponse;
-import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.web.bind.annotation.RequestParam;
 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.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 import reactor.core.publisher.Flux;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.Base64;
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.UUID;
 
 @Tag(name = "閲囪喘鏅鸿兘浣�")
 @RestController
 @RequestMapping("/purchase-ai")
 public class PurchaseAiController extends BaseController {
 
-    private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::";
-    private static final int MAX_FILE_COUNT = 10;
-    private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000;
-    private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000;
+    private final PurchaseAiService purchaseAiService;
 
-    private final PurchaseAgent purchaseAgent;
-    private final PurchaseIntentExecutor purchaseIntentExecutor;
-    private final AiSessionUserContext aiSessionUserContext;
-    private final MongoChatMemoryStore mongoChatMemoryStore;
-    private final AiChatSessionService aiChatSessionService;
-    private final AiFileTextExtractor aiFileTextExtractor;
-    private final ObjectMapper objectMapper;
-    private final IPurchaseLedgerService purchaseLedgerService;
-    private final IPaymentRegistrationService paymentRegistrationService;
-    private final PurchaseReturnOrdersService purchaseReturnOrdersService;
-    private final SupplierManageMapper supplierManageMapper;
-    private final StreamingChatLanguageModel purchaseVisionStreamingChatModel;
-
-    public PurchaseAiController(PurchaseAgent purchaseAgent,
-                                PurchaseIntentExecutor purchaseIntentExecutor,
-                                AiSessionUserContext aiSessionUserContext,
-                                MongoChatMemoryStore mongoChatMemoryStore,
-                                AiChatSessionService aiChatSessionService,
-                                AiFileTextExtractor aiFileTextExtractor,
-                                ObjectMapper objectMapper,
-                                IPurchaseLedgerService purchaseLedgerService,
-                                IPaymentRegistrationService paymentRegistrationService,
-                                PurchaseReturnOrdersService purchaseReturnOrdersService,
-                                SupplierManageMapper supplierManageMapper,
-                                @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
-        this.purchaseAgent = purchaseAgent;
-        this.purchaseIntentExecutor = purchaseIntentExecutor;
-        this.aiSessionUserContext = aiSessionUserContext;
-        this.mongoChatMemoryStore = mongoChatMemoryStore;
-        this.aiChatSessionService = aiChatSessionService;
-        this.aiFileTextExtractor = aiFileTextExtractor;
-        this.objectMapper = objectMapper;
-        this.purchaseLedgerService = purchaseLedgerService;
-        this.paymentRegistrationService = paymentRegistrationService;
-        this.purchaseReturnOrdersService = purchaseReturnOrdersService;
-        this.supplierManageMapper = supplierManageMapper;
-        this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel;
+    public PurchaseAiController(PurchaseAiService purchaseAiService) {
+        this.purchaseAiService = purchaseAiService;
     }
 
     @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 = purchaseIntentExecutor.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 purchaseAgent.chat(memoryId, userMessage)
-                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
-                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
+        return purchaseAiService.chat(chatForm, loginUser);
     }
 
     @Operation(summary = "閲囪喘澶氭枃浠跺垎鏋�")
@@ -153,751 +43,34 @@
     public Flux<String> analyzeFiles(@RequestParam("files") MultipartFile[] files,
                                      @RequestParam(value = "message", required = false) String message,
                                      @RequestParam(value = "memoryId", required = false) String memoryId) {
-        if (files == null || files.length == 0) {
-            return Flux.just("files涓嶈兘涓虹┖");
-        }
-        if (files.length > MAX_FILE_COUNT) {
-            return Flux.just("涓�娆℃渶澶氬垎鏋�" + MAX_FILE_COUNT + "涓枃浠�");
-        }
-
-        String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
-        String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX)
-                ? rawMemoryId
-                : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
-
         LoginUser loginUser = SecurityUtils.getLoginUser();
-        aiSessionUserContext.bind(finalMemoryId, loginUser);
-
-        String finalMessage = StringUtils.hasText(message)
-                ? message
-                : "璇峰垎鏋愯繖浜涢噰璐枃浠讹紝鎻愬彇鍙敤浜庝笟鍔″鐞嗙殑鏁版嵁锛屽苟鏁寸悊鎴愬緟瀹㈡埛纭鐨勬牸寮�";
-
-        String fileContent;
-        try {
-            fileContent = buildMultiFileContent(files);
-        } catch (IllegalArgumentException ex) {
-            return Flux.just(ex.getMessage());
-        } catch (IOException ex) {
-            return Flux.just("鏂囦欢璇诲彇澶辫触");
-        }
-
-        if (!StringUtils.hasText(fileContent)) {
-            return Flux.just("鏈彁鍙栧埌鏈夋晥鏂囦欢鍐呭");
-        }
-
-        String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent);
-        aiChatSessionService.touchSession(finalMemoryId, loginUser, "閲囪喘澶氭枃浠跺垎鏋�: " + finalMessage);
-
-        if (containsImageFile(files)) {
-            return chatWithPurchaseVisionModel(finalMemoryId, userPrompt, files)
-                    .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
-                    .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
-        }
-
-        return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt))
-                .onErrorResume(NoSuchElementException.class, ex -> {
-                    mongoChatMemoryStore.deleteMessages(finalMemoryId);
-                    return purchaseAgent.chat(finalMemoryId, userPrompt);
-                })
-                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
-                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
+        return purchaseAiService.analyzeFiles(files, message, memoryId, loginUser);
     }
 
     @Operation(summary = "閲囪喘澶氭枃浠跺垎鏋愮‘璁ゅ鐞�")
     @PostMapping("/analyze-files/confirm")
     public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) {
-        if (request == null || !StringUtils.hasText(request.getBusinessType())) {
-            return AjaxResult.error("businessType涓嶈兘涓虹┖");
-        }
-        if (request.getPayload() == null || request.getPayload().isEmpty()) {
-            return AjaxResult.error("payload涓嶈兘涓虹┖");
-        }
-
-        try {
-            String businessType = request.getBusinessType().trim();
-            return switch (businessType) {
-                case "purchase_ledger" -> processPurchaseLedger(request.getPayload());
-                case "payment_registration" -> processPaymentRegistration(request.getPayload());
-                case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload());
-                default -> AjaxResult.error("鏆備笉鏀寔璇ヤ笟鍔$被鍨�: " + businessType);
-            };
-        } catch (Exception ex) {
-            return AjaxResult.error(toCustomerMessage(ex));
-        }
+        return purchaseAiService.confirmAnalyzeResult(request);
     }
 
     @Operation(summary = "閲囪喘浼氳瘽鍒楄〃")
     @GetMapping("/history/sessions")
     public AjaxResult listSessions() {
-        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        return success(purchaseAiService.listSessions(loginUser));
     }
 
     @Operation(summary = "閲囪喘浼氳瘽娑堟伅")
     @GetMapping("/history/messages/{memoryId}")
     public AjaxResult listMessages(@PathVariable String memoryId) {
-        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        return success(purchaseAiService.listMessages(memoryId, loginUser));
     }
 
     @Operation(summary = "鍒犻櫎閲囪喘浼氳瘽")
     @DeleteMapping("/history/{memoryId}")
     public AjaxResult deleteSession(@PathVariable String memoryId) {
-        aiSessionUserContext.remove(memoryId);
-        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
-    }
-
-    private String buildMultiFileContent(MultipartFile[] files) throws IOException {
-        StringBuilder builder = new StringBuilder();
-        int totalLength = 0;
-        for (MultipartFile file : files) {
-            String text = aiFileTextExtractor.extractText(file);
-            if (!StringUtils.hasText(text)) {
-                continue;
-            }
-            String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH
-                    ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH)
-                    : text;
-            if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) {
-                int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength;
-                if (remain <= 0) {
-                    break;
-                }
-                limitedText = limitedText.substring(0, remain);
-            }
-            builder.append("\n--- 鏂囦欢: ")
-                    .append(file.getOriginalFilename())
-                    .append(" ---\n")
-                    .append(limitedText)
-                    .append('\n');
-            totalLength += limitedText.length();
-        }
-        return builder.toString();
-    }
-
-    private boolean containsImageFile(MultipartFile[] files) {
-        for (MultipartFile file : files) {
-            if (aiFileTextExtractor.isImageFile(file)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) {
-        return Flux.create(sink -> {
-            try {
-                List<Content> contents = new ArrayList<>();
-                contents.add(TextContent.from(userPrompt));
-                for (MultipartFile file : files) {
-                    if (!aiFileTextExtractor.isImageFile(file)) {
-                        continue;
-                    }
-                    contents.add(TextContent.from("涓嬮潰杩欏紶鍥剧墖鏂囦欢鍚嶏細" + file.getOriginalFilename()));
-                    contents.add(ImageContent.from(Image.builder()
-                            .base64Data(Base64.getEncoder().encodeToString(file.getBytes()))
-                            .mimeType(resolveImageMimeType(file))
-                            .build()));
-                }
-
-                List<ChatMessage> messages = List.of(
-                        SystemMessage.from("浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝浠庢枃鏈拰鍥剧墖涓瘑鍒噰璐彴璐︺�侀噰璐骇鍝佹槑缁嗐�佷粯娆炬垨閫�璐т俊鎭紝鍙緭鍑哄悎娉� JSON銆�"),
-                        UserMessage.from(contents)
-                );
-                purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() {
-                    @Override
-                    public void onPartialResponse(String partialResponse) {
-                        sink.next(partialResponse);
-                    }
-
-                    @Override
-                    public void onCompleteResponse(ChatResponse completeResponse) {
-                        sink.complete();
-                    }
-
-                    @Override
-                    public void onError(Throwable error) {
-                        sink.error(error);
-                    }
-                });
-            } catch (Exception ex) {
-                sink.next("鍥剧墖鏂囦欢璇诲彇澶辫触锛岃纭鍥剧墖鏍煎紡涓� png銆乯pg銆乯peg銆亀ebp 鎴� bmp锛屼笖澶у皬涓嶈秴杩�10MB");
-                sink.complete();
-            }
-        });
-    }
-
-    private String resolveImageMimeType(MultipartFile file) {
-        String contentType = file.getContentType();
-        if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) {
-            return contentType;
-        }
-        String filename = file.getOriginalFilename();
-        String ext = "";
-        if (StringUtils.hasText(filename) && filename.contains(".")) {
-            ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
-        }
-        return switch (ext) {
-            case "jpg", "jpeg" -> "image/jpeg";
-            case "webp" -> "image/webp";
-            case "bmp" -> "image/bmp";
-            default -> "image/png";
-        };
-    }
-
-    private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) {
-        return """
-                浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝涓ユ牸鏍规嵁鐢ㄦ埛涓婁紶鐨勫涓枃浠跺拰鐢ㄦ埛瑕佹眰鎻愬彇閲囪喘涓氬姟鏁版嵁銆�
-
-                鐢ㄦ埛瑕佹眰:
-                %s
-
-                杈撳嚭瑕佹眰:
-                1. 鍙緭鍑哄悎娉� JSON锛屼笉瑕� Markdown锛屼笉瑕侀澶栬В閲娿��
-                2. JSON 椤跺眰瀛楁鍥哄畾涓�:
-                   - success: boolean
-                   - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown
-                   - action: confirm_required
-                   - description: 涓枃璇存槑
-                   - confidence: 0鍒�1鐨勫皬鏁�
-                   - missingFields: 缂哄け瀛楁涓枃鍚嶇О鏁扮粍锛岄潰鍚戝鎴峰睍绀猴紝涓嶈杈撳嚭鑻辨枃瀛楁鍚�
-                   - warnings: 椋庨櫓鎻愮ず鏁扮粍
-                   - payload: 寰呭鎴风‘璁ょ殑鏁版嵁锛屽瓧娈靛悕蹇呴』浣跨敤鍚庣 DTO 瀛楁鍚�
-                   - preview: 缁欏鎴风‘璁ょ敤鐨勪腑鏂囨憳瑕佹暟缁�
-                3. 濡傛灉鍙垽鏂负閲囪喘鍙拌处锛宐usinessType 浣跨敤 purchase_ledger锛宲ayload.purchaseLedgers 涓洪噰璐鍗�/閲囪喘鍙拌处鏁扮粍:
-                   - purchaseLedgers: 閲囪喘璁㈠崟/閲囪喘鍙拌处鏁扮粍锛屾瘡鏉¤褰曞瓧娈靛悕蹇呴』涓� PurchaseLedgerDto 淇濇寔涓�鑷�
-                   - 浜у搧鏄庣粏蹇呴』鏀惧湪姣忔潯閲囪喘鍙拌处璁板綍鐨� productData 瀛楁涓紝productData 绫诲瀷涓� List<SalesLedgerProduct>
-                   - 涓嶈浼樺厛浣跨敤 payload 椤跺眰 productData锛涢《灞� productData 浠呬綔涓烘棫鏍煎紡鍏煎
-                   - 鏂囦欢閲岀殑鈥滈噰璐崟鍙封�濆氨鏄�滈噰璐悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� purchaseContractNumber
-                   - 鏂囦欢閲岀殑鈥滈攢鍞崟鍙封�濆氨鏄�滈攢鍞悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� salesContractNo
-                   - 鎵�鏈夋棩鏈熷瓧娈靛繀椤讳娇鐢� yyyy-MM-dd锛屼緥濡� 2026-04-30锛涗笉瑕佽緭鍑� 4/30/26銆�2026/4/30銆�2026骞�4鏈�30鏃� 鎴栧甫鏃跺垎绉掔殑鏍煎紡
-                   - 閲囪喘鍙拌处涓嶉渶瑕佸湪 payload 涓紶瀹℃壒浜猴紝涓嶈杈撳嚭 approveUserIds銆乤pproverId
-                   - missingFields 鍙~鍐欎笟鍔″繀濉絾鏃犳硶璇嗗埆鐨勫瓧娈碉紝涓嶈鎶� PurchaseLedgerDto 鐨勬墍鏈夌┖瀛楁閮藉垪涓虹己澶憋紱缂哄け椤瑰繀椤诲啓涓枃锛屼緥濡傗�滀緵搴斿晢鍚嶇О鈥濃�滃惈绋庡崟浠封�濓紝涓嶈鍐� supplierId銆乼axInclusiveUnitPrice
-                   - 閲囪喘鍙拌处涓昏〃蹇呭~瀛楁浠呮寜杩欎簺鍒ゆ柇: purchaseContractNumber銆乻upplierName 鎴� supplierId
-                   - productData 姣忔潯浜у搧蹇呭~瀛楁: productCategory銆乻pecificationModel銆乽nit銆乹uantity銆乼axInclusiveUnitPrice 鎴� taxInclusiveTotalPrice锛涘鏋滃彧鏈夊惈绋庢�讳环鍜屾暟閲忥紝蹇呴』璁$畻 taxInclusiveUnitPrice锛涘鏋滃彧鏈夊惈绋庡崟浠峰拰鏁伴噺锛屽繀椤昏绠� taxInclusiveTotalPrice
-                   - 浜у搧瀛楁鎸夐噰璐鍏ユ帴鍙� PurchaseLedgerProductImportDto 瀵归綈: 閲囪喘鍗曞彿銆佷骇鍝佸ぇ绫汇�佽鏍煎瀷鍙枫�佸崟浣嶃�佹暟閲忋�佺◣鐜囥�佸惈绋庡崟浠枫�佸惈绋庢�讳环銆佸彂绁ㄧ被鍨嬨�佹槸鍚﹁川妫�
-                   - 閲囪喘浜у搧 type 鍥哄畾涓� 2
-                   - purchaseLedgers 姣忔潯璁板綍鍙娇鐢ㄨ繖浜� PurchaseLedgerDto 瀛楁鍚�:
-                     entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName
-                   - productData 姣忔潯浜у搧鍙娇鐢ㄨ繖浜� SalesLedgerProduct 瀛楁鍚�:
-                     productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type
-                4. 濡傛灉鍙垽鏂负浠樻鐧昏锛宐usinessType 浣跨敤 payment_registration锛宲ayload.records 涓轰粯娆剧櫥璁版暟缁勶紝瀛楁灏介噺鍖呭惈 purchaseLedgerId銆乻alesLedgerProductId銆乧urrentPaymentAmount銆乸aymentMethod銆乸aymentDate銆�
-                5. 濡傛灉鍙垽鏂负閲囪喘閫�璐э紝businessType 浣跨敤 purchase_return_order锛宲ayload 鎸� PurchaseReturnOrderDto 缁勭粐锛屾槑缁嗘斁 purchaseReturnOrderProductsDtos銆�
-                6. 缂哄皯涓氬姟澶勭悊蹇呴』瀛楁鏃讹紝涓嶈缂栭�� ID锛屾妸瀛楁鏀惧叆 missingFields锛屽苟浠嶈繑鍥炲彲纭鐨勮崏绋挎暟鎹��
-                7. 鎵�鏈変腑鏂囧唴瀹圭洿鎺ヤ繚鐣欙紝涓嶈杞箟鎴� Unicode銆�
-
-                鏂囦欢鍐呭:
-                %s
-                """.formatted(message, fileContent);
-    }
-
-    private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception {
-        if (payload.containsKey("purchaseLedgers")) {
-            return processPurchaseLedgerBatch(payload);
-        }
-
-        Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload);
-        PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class);
-        AjaxResult ledgerResult = validatePurchaseLedger(dto, 0);
-        if (ledgerResult != null) {
-            return ledgerResult;
-        }
-        AjaxResult supplierResult = fillSupplierIdByName(dto);
-        if (supplierResult != null) {
-            return supplierResult;
-        }
-        AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0);
-        if (productResult != null) {
-            return productResult;
-        }
-        int result = purchaseLedgerService.addOrEditPurchase(dto);
-        return AjaxResult.success("閲囪喘鍙拌处宸插鐞�", result);
-    }
-
-    private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
-        List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers"));
-        if (purchaseLedgers.isEmpty()) {
-            return AjaxResult.error("purchaseLedgers涓嶈兘涓虹┖");
-        }
-
-        List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData"));
-        List<Map<String, Object>> results = new ArrayList<>();
-        for (int i = 0; i < purchaseLedgers.size(); i++) {
-            Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i));
-            PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class);
-            AjaxResult ledgerResult = validatePurchaseLedger(dto, i);
-            if (ledgerResult != null) {
-                return ledgerResult;
-            }
-            AjaxResult supplierResult = fillSupplierIdByName(dto);
-            if (supplierResult != null) {
-                return supplierResult;
-            }
-
-            List<SalesLedgerProduct> products = dto.getProductData();
-            if (products == null || products.isEmpty()) {
-                products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1);
-                dto.setProductData(products);
-            }
-            AjaxResult productResult = validatePurchaseProducts(products, i);
-            if (productResult != null) {
-                return productResult;
-            }
-            int result = purchaseLedgerService.addOrEditPurchase(dto);
-
-            Map<String, Object> item = new LinkedHashMap<>();
-            item.put("index", i);
-            item.put("purchaseContractNumber", dto.getPurchaseContractNumber());
-            item.put("supplierId", dto.getSupplierId());
-            item.put("supplierName", dto.getSupplierName());
-            item.put("productCount", products.size());
-            item.put("result", result);
-            results.add(item);
-        }
-        return AjaxResult.success("閲囪喘鍙拌处宸叉壒閲忓鐞�", results);
-    }
-
-    private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap,
-                                                            PurchaseLedgerDto dto,
-                                                            List<Map<String, Object>> productData,
-                                                            boolean onlyOneLedger) {
-        List<SalesLedgerProduct> products = new ArrayList<>();
-        for (Map<String, Object> productMap : productData) {
-            if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) {
-                products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class));
-            }
-        }
-        return products;
-    }
-
-    private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) {
-        Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "閲囪喘璁㈠崟id", "閲囪喘鍙拌处id");
-        if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) {
-            return true;
-        }
-
-        Long productSalesLedgerId = longValue(productMap, "salesLedgerId");
-        if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) {
-            return true;
-        }
-
-        String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
-        if (StringUtils.hasText(productContractNo)
-                && StringUtils.hasText(dto.getPurchaseContractNumber())
-                && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) {
-            return true;
-        }
-
-        String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
-        if (StringUtils.hasText(productContractNo)
-                && StringUtils.hasText(ledgerContractNo)
-                && productContractNo.trim().equals(ledgerContractNo.trim())) {
-            return true;
-        }
-
-        String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
-        if (StringUtils.hasText(productSalesContractNo)
-                && StringUtils.hasText(dto.getSalesContractNo())
-                && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) {
-            return true;
-        }
-
-        String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
-        if (StringUtils.hasText(productSalesContractNo)
-                && StringUtils.hasText(ledgerSalesContractNo)
-                && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) {
-            return true;
-        }
-
-        String productSupplierName = stringValue(productMap, "supplierName", "渚涘簲鍟嗗悕绉�");
-        return StringUtils.hasText(productSupplierName)
-                && StringUtils.hasText(dto.getSupplierName())
-                && productSupplierName.trim().equals(dto.getSupplierName().trim());
-    }
-
-    private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) {
-        Map<String, Object> target = new LinkedHashMap<>();
-        copyPurchaseLedgerDtoFields(source, target);
-        putDtoFieldIfPresent(source, target, "entryDateStart", "褰曞叆寮�濮嬫棩鏈�", "褰曞叆鏃ユ湡寮�濮�");
-        putDtoFieldIfPresent(source, target, "entryDateEnd", "褰曞叆缁撴潫鏃ユ湡", "褰曞叆鏃ユ湡缁撴潫");
-        putDtoFieldIfPresent(source, target, "id", "閲囪喘鍙拌处id", "閲囪喘璁㈠崟id", "涓婚敭");
-        putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
-        putDtoFieldIfPresent(source, target, "supplierId", "渚涘簲鍟唅d", "渚涘簲鍟咺D", "渚涘簲鍟嗗悕绉癷d", "渚涘簲鍟嗗悕绉癐D");
-        putDtoFieldIfPresent(source, target, "supplierName", "渚涘簲鍟�", "渚涘簲鍟嗗悕绉�");
-        putDtoFieldIfPresent(source, target, "isWhite", "鏄惁鐧藉悕鍗�");
-        putDtoFieldIfPresent(source, target, "recorderId", "褰曞叆浜篿d", "褰曞叆浜篒D", "褰曞叆浜哄鍚峣d", "褰曞叆浜哄鍚岻D");
-        putDtoFieldIfPresent(source, target, "recorderName", "褰曞叆浜�", "褰曞叆浜哄鍚�");
-        putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
-        putDtoFieldIfPresent(source, target, "salesContractNoId", "閿�鍞悎鍚屽彿id", "閿�鍞悎鍚屽彿ID", "閿�鍞崟鍙穒d", "閿�鍞崟鍙稩D");
-        putDtoFieldIfPresent(source, target, "projectName", "椤圭洰", "椤圭洰鍚嶇О");
-        putDtoFieldIfPresent(source, target, "entryDate", "褰曞叆鏃ユ湡");
-        putDtoFieldIfPresent(source, target, "executionDate", "绛捐鏃ユ湡", "鍚堝悓绛捐鏃ユ湡");
-        putDtoFieldIfPresent(source, target, "remarks", "澶囨敞", "璇存槑");
-        putDtoFieldIfPresent(source, target, "attachmentMaterials", "闄勪欢鏉愭枡", "闄勪欢鏉愭枡璺緞鎴栧悕绉�");
-        putDtoFieldIfPresent(source, target, "createdAt", "鍒涘缓鏃堕棿", "璁板綍鍒涘缓鏃堕棿");
-        putDtoFieldIfPresent(source, target, "updatedAt", "鏇存柊鏃堕棿", "璁板綍鏈�鍚庢洿鏂版椂闂�");
-        putDtoFieldIfPresent(source, target, "salesLedgerId", "閿�鍞彴璐d", "閿�鍞彴璐D", "鍏宠仈閿�鍞彴璐︿富琛ㄤ富閿�");
-        putDtoFieldIfPresent(source, target, "hasChildren", "鏄惁鏈夊瓙绾�", "鏄惁鏈夋槑缁�");
-        putDtoFieldIfPresent(source, target, "Type", "鍙拌处绫诲瀷", "涓氬姟绫诲瀷");
-        putDtoFieldIfPresent(source, target, "productData", "products", "浜у搧鏄庣粏", "閲囪喘浜у搧鏄庣粏");
-        putDtoFieldIfPresent(source, target, "tempFileIds", "涓存椂鏂囦欢id", "涓存椂鏂囦欢ID", "涓存椂鏂囦欢ids");
-        putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "闄勪欢鍒楄〃", "閿�鍞彴璐﹂檮浠�");
-        putDtoFieldIfPresent(source, target, "phoneNumber", "涓氬姟鍛樻墜鏈哄彿", "鎵嬫満鍙�");
-        putDtoFieldIfPresent(source, target, "businessPersonId", "涓氬姟鍛榠d", "涓氬姟鍛業D");
-        putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
-        putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID");
-        putDtoFieldIfPresent(source, target, "invoiceNumber", "鍙戠エ鍙�", "鍙戠エ鍙风爜");
-        putDtoFieldIfPresent(source, target, "invoiceAmount", "鍙戠エ閲戦", "鍙戠エ閲戦锛堝厓锛�");
-        putDtoFieldIfPresent(source, target, "ticketRegistrationId", "鏉ョエ鐧昏id", "鏉ョエ鐧昏ID");
-        putDtoFieldIfPresent(source, target, "contractAmount", "鍚堝悓閲戦", "鍚堝悓閲戦锛堜骇鍝佸惈绋庢�讳环锛�");
-        putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "鏉ョエ閲戦", "宸叉潵绁ㄩ噾棰�", "宸叉潵绁ㄩ噾棰�(鍏�)");
-        putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "鏈潵绁ㄩ噾棰�", "鏈潵绁ㄩ噾棰�(鍏�)");
-        putDtoFieldIfPresent(source, target, "type", "鏂囦欢绫诲瀷");
-        putDtoFieldIfPresent(source, target, "paymentMethod", "浠樻鏂瑰紡");
-        putDtoFieldIfPresent(source, target, "approvalStatus", "瀹℃壒鐘舵��");
-        putDtoFieldIfPresent(source, target, "templateName", "妯℃澘鍚嶇О");
-        target.remove("approveUserIds");
-        target.remove("approverId");
-        normalizeNestedProductData(target);
-        attachImportStyleProductData(source, target);
-        if (target.get("type") == null) {
-            target.put("type", 2);
-        }
-        target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
-        normalizePurchaseLedgerDateFields(target);
-        return target;
-    }
-
-    private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) {
-        if (target.get("productData") != null) {
-            return;
-        }
-        Map<String, Object> productMap = normalizeSalesLedgerProductMap(source);
-        if (hasImportStyleProductData(productMap)) {
-            target.put("productData", List.of(productMap));
-        }
-    }
-
-    private boolean hasImportStyleProductData(Map<String, Object> productMap) {
-        return hasMapText(productMap, "productCategory")
-                || hasMapText(productMap, "specificationModel")
-                || productMap.get("quantity") != null
-                || productMap.get("taxInclusiveUnitPrice") != null
-                || productMap.get("taxInclusiveTotalPrice") != null;
-    }
-
-    private boolean hasMapText(Map<String, Object> map, String key) {
-        Object value = map.get(key);
-        return value != null && StringUtils.hasText(String.valueOf(value));
-    }
-
-    private void normalizeNestedProductData(Map<String, Object> target) {
-        Object productDataValue = target.get("productData");
-        if (productDataValue == null) {
-            return;
-        }
-        List<Map<String, Object>> productMaps = toMapList(productDataValue);
-        List<Map<String, Object>> normalizedProducts = new ArrayList<>();
-        for (Map<String, Object> productMap : productMaps) {
-            normalizedProducts.add(normalizeSalesLedgerProductMap(productMap));
-        }
-        target.put("productData", normalizedProducts);
-    }
-
-    private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) {
-        Map<String, Object> target = new LinkedHashMap<>();
-        copySalesLedgerProductFields(source, target);
-        putDtoFieldIfPresent(source, target, "productCategory", "浜у搧澶х被", "浜у搧鍚嶇О", "浜у搧", "鍝佸悕", "鐗╂枡鍚嶇О");
-        putDtoFieldIfPresent(source, target, "specificationModel", "瑙勬牸鍨嬪彿", "鍨嬪彿", "瑙勬牸", "浜у搧瑙勬牸");
-        putDtoFieldIfPresent(source, target, "unit", "鍗曚綅");
-        putDtoFieldIfPresent(source, target, "quantity", "鏁伴噺", "閲囪喘鏁伴噺");
-        putDtoFieldIfPresent(source, target, "taxRate", "绋庣巼");
-        putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "鍚◣鍗曚环", "鍗曚环", "閲囪喘鍗曚环", "鍚◣浠锋牸");
-        putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "鍚◣鎬讳环", "鎬讳环", "閲囪喘閲戦", "閲戦", "鍚堝悓閲戦");
-        putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "涓嶅惈绋庢�讳环");
-        putDtoFieldIfPresent(source, target, "invoiceType", "鍙戠エ绫诲瀷", "鍙戠エ绫诲埆");
-        putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
-        putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID", "鍨嬪彿id", "鍨嬪彿ID");
-        putDtoFieldIfPresent(source, target, "isChecked", "鏄惁璐ㄦ", "鏄惁璐ㄦ楠�", "璐ㄦ");
-        putDtoFieldIfPresent(source, target, "type", "鍙拌处绫诲瀷");
-        normalizeProductAmounts(target);
-        target.putIfAbsent("type", 2);
-        return target;
-    }
-
-    private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) {
-        String[] productFields = {
-                "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit",
-                "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice",
-                "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum",
-                "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum",
-                "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate",
-                "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal",
-                "isChecked", "isProduction"
-        };
-        for (String field : productFields) {
-            if (source.containsKey(field)) {
-                target.put(field, source.get(field));
-            }
-        }
-    }
-
-    private void normalizeProductAmounts(Map<String, Object> target) {
-        BigDecimal quantity = decimalValue(target.get("quantity"));
-        BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice"));
-        BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
-        if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) {
-            target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP));
-        }
-        if (totalPrice == null && unitPrice != null && quantity != null) {
-            target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity));
-        }
-        BigDecimal taxRate = decimalValue(target.get("taxRate"));
-        totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
-        if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) {
-            BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP));
-            target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP));
-        }
-    }
-
-    private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
-        if (products == null || products.isEmpty()) {
-            return null;
-        }
-        for (int i = 0; i < products.size(); i++) {
-            SalesLedgerProduct product = products.get(i);
-            String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐︾殑绗�" + (i + 1) + "鏉′骇鍝�";
-            if (!StringUtils.hasText(product.getProductCategory())) {
-                return AjaxResult.error(prefix + "缂哄皯浜у搧鍚嶇О锛岃琛ュ厖鍚庡啀纭");
-            }
-            if (!StringUtils.hasText(product.getSpecificationModel())) {
-                return AjaxResult.error(prefix + "缂哄皯瑙勬牸鍨嬪彿锛岃琛ュ厖鍚庡啀纭");
-            }
-            if (!StringUtils.hasText(product.getUnit())) {
-                return AjaxResult.error(prefix + "缂哄皯鍗曚綅锛岃琛ュ厖鍚庡啀纭");
-            }
-            if (product.getQuantity() == null) {
-                return AjaxResult.error(prefix + "缂哄皯鏁伴噺");
-            }
-            if (product.getTaxInclusiveUnitPrice() == null) {
-                return AjaxResult.error(prefix + "缂哄皯鍚◣鍗曚环锛岃琛ュ厖鍚庡啀纭");
-            }
-            if (product.getTaxInclusiveTotalPrice() == null) {
-                return AjaxResult.error(prefix + "缂哄皯鍚◣鎬讳环锛岃琛ュ厖鍚庡啀纭");
-            }
-        }
-        return null;
-    }
-
-    private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
-        String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐�";
-        if (!StringUtils.hasText(dto.getPurchaseContractNumber())) {
-            return AjaxResult.error(prefix + "缂哄皯閲囪喘鍚堝悓鍙凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
-        }
-        if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) {
-            return AjaxResult.error(prefix + "缂哄皯渚涘簲鍟嗗悕绉帮紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
-        }
-        return null;
-    }
-
-    private void normalizePurchaseLedgerDateFields(Map<String, Object> target) {
-        normalizeDateField(target, "entryDate");
-        normalizeDateField(target, "executionDate");
-        normalizeDateField(target, "createdAt");
-        normalizeDateField(target, "updatedAt");
-    }
-
-    private void normalizeDateField(Map<String, Object> target, String fieldName) {
-        Object value = target.get(fieldName);
-        if (value == null) {
-            return;
-        }
-        String normalizedDate = normalizeDateValue(value);
-        if (StringUtils.hasText(normalizedDate)) {
-            target.put(fieldName, normalizedDate);
-        }
-    }
-
-    private String normalizeDateValue(Object value) {
-        if (value instanceof Date date) {
-            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
-        }
-        if (value instanceof Number number) {
-            return LocalDate.of(1899, 12, 30)
-                    .plusDays(number.longValue())
-                    .format(DateTimeFormatter.ISO_LOCAL_DATE);
-        }
-
-        String text = String.valueOf(value).trim();
-        if (!StringUtils.hasText(text)) {
-            return null;
-        }
-        if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') {
-            return text.substring(0, 10);
-        }
-
-        String normalizedText = text.replace("骞�", "-")
-                .replace("鏈�", "-")
-                .replace("鏃�", "")
-                .replace(".", "-")
-                .replace("/", "-")
-                .trim();
-        DateTimeFormatter[] formatters = {
-                DateTimeFormatter.ofPattern("yyyy-M-d"),
-                DateTimeFormatter.ofPattern("M-d-yyyy"),
-                DateTimeFormatter.ofPattern("M-d-yy")
-        };
-        for (DateTimeFormatter formatter : formatters) {
-            try {
-                return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE);
-            } catch (DateTimeParseException ignored) {
-                // Try the next supported input pattern.
-            }
-        }
-        return text;
-    }
-
-    private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) {
-        String[] dtoFields = {
-                "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber",
-                "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo",
-                "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials",
-                "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds",
-                "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber",
-                "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount",
-                "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName"
-        };
-        for (String field : dtoFields) {
-            if (source.containsKey(field)) {
-                target.put(field, source.get(field));
-            }
-        }
-    }
-
-    private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) {
-        if (target.containsKey(dtoField) && target.get(dtoField) != null) {
-            return;
-        }
-        for (String alias : aliases) {
-            Object value = source.get(alias);
-            if (value != null && StringUtils.hasText(String.valueOf(value))) {
-                target.put(dtoField, value);
-                return;
-            }
-        }
-    }
-
-    private List<Map<String, Object>> toMapList(Object value) {
-        if (value == null) {
-            return List.of();
-        }
-        return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() {
-        });
-    }
-
-    private String stringValue(Map<String, Object> map, String... keys) {
-        for (String key : keys) {
-            Object value = map.get(key);
-            if (value != null && StringUtils.hasText(String.valueOf(value))) {
-                return String.valueOf(value);
-            }
-        }
-        return null;
-    }
-
-    private Long longValue(Map<String, Object> map, String... keys) {
-        String value = stringValue(map, keys);
-        if (!StringUtils.hasText(value)) {
-            return null;
-        }
-        try {
-            return Long.parseLong(value.trim());
-        } catch (NumberFormatException ignored) {
-            return null;
-        }
-    }
-
-    private BigDecimal decimalValue(Object value) {
-        if (value == null) {
-            return null;
-        }
-        if (value instanceof BigDecimal decimal) {
-            return decimal;
-        }
-        if (value instanceof Number number) {
-            return new BigDecimal(String.valueOf(number));
-        }
-        String text = String.valueOf(value)
-                .replace(",", "")
-                .replace("锛�", "")
-                .replace("鍏�", "")
-                .replace("锟�", "")
-                .trim();
-        if (!StringUtils.hasText(text)) {
-            return null;
-        }
-        try {
-            return new BigDecimal(text);
-        } catch (NumberFormatException ignored) {
-            return null;
-        }
-    }
-
-    private String toCustomerMessage(Exception ex) {
-        String message = ex.getMessage();
-        if (!StringUtils.hasText(message)) {
-            return "澶勭悊澶辫触锛岃妫�鏌ョ‘璁ゆ暟鎹悗閲嶈瘯";
-        }
-        if (message.contains("tax_inclusive_unit_price")) {
-            return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庡崟浠凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�";
-        }
-        if (message.contains("tax_inclusive_total_price")) {
-            return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庢�讳环锛岃琛ュ厖鍚庡啀纭";
-        }
-        if (message.contains("entryDate")) {
-            return "澶勭悊澶辫触锛氬綍鍏ユ棩鏈熸牸寮忎笉姝g‘锛岃浣跨敤 yyyy-MM-dd锛屼緥濡� 2026-04-30";
-        }
-        if (message.contains("supplier")) {
-            return "澶勭悊澶辫触锛氫緵搴斿晢淇℃伅涓嶅畬鏁达紝璇风‘璁や緵搴斿晢鍚嶇О鎴栦緵搴斿晢ID";
-        }
-        if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) {
-            return "澶勭悊澶辫触锛氱‘璁ゆ暟鎹笉瀹屾暣鎴栨牸寮忎笉姝g‘锛岃妫�鏌ュ繀濉瓧娈靛悗閲嶈瘯";
-        }
-        return "澶勭悊澶辫触锛�" + message;
-    }
-
-    private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
-        if (dto.getSupplierId() != null) {
-            return null;
-        }
-        if (!StringUtils.hasText(dto.getSupplierName())) {
-            return AjaxResult.error("渚涘簲鍟咺D涓嶈兘涓虹┖锛涙湭璇嗗埆鍒颁緵搴斿晢鍚嶇О锛屾棤娉曡嚜鍔ㄥ尮閰嶄緵搴斿晢ID");
-        }
-
-        SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>()
-                .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim())
-                .last("limit 1"));
-        if (supplier == null) {
-            return AjaxResult.error("鏈壘鍒颁緵搴斿晢锛�" + dto.getSupplierName() + "锛岃鍏堢淮鎶や緵搴斿晢鎴栨墜鍔ㄩ�夋嫨渚涘簲鍟咺D");
-        }
-        dto.setSupplierId(supplier.getId());
-        return null;
-    }
-
-    private AjaxResult processPaymentRegistration(Map<String, Object> payload) {
-        Object recordsValue = payload.get("records");
-        List<PaymentRegistration> records;
-        if (recordsValue == null) {
-            records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class));
-        } else {
-            records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() {
-            });
-        }
-        int result = paymentRegistrationService.insertPaymentRegistration(records);
-        return AjaxResult.success("浠樻鐧昏宸插鐞�", result);
-    }
-
-    private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) {
-        PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class);
-        Boolean result = purchaseReturnOrdersService.add(dto);
-        return AjaxResult.success("閲囪喘閫�璐у崟宸插鐞�", result);
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        return toAjax(purchaseAiService.deleteSession(memoryId, loginUser));
     }
 }
diff --git a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java b/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
index 9242b3e..081bf12 100644
--- a/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
+++ b/src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
@@ -1,15 +1,28 @@
 package com.ruoyi.ai.dto;
 
-import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.List;
+
 @Data
 @NoArgsConstructor
-@AllArgsConstructor
 public class AiChatMessageDto {
 
     private String role;
 
     private String content;
+
+    private List<String> filePaths;
+
+    public AiChatMessageDto(String role, String content) {
+        this.role = role;
+        this.content = content;
+    }
+
+    public AiChatMessageDto(String role, String content, List<String> filePaths) {
+        this.role = role;
+        this.content = content;
+        this.filePaths = filePaths;
+    }
 }
diff --git a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java b/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
index 0a239d8..e2944cb 100644
--- a/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
+++ b/src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
@@ -9,6 +9,7 @@
 import org.springframework.data.mongodb.core.mapping.Document;
 
 import java.util.Date;
+import java.util.List;
 
 @Data
 @AllArgsConstructor
@@ -24,6 +25,21 @@
 
     private String content;
 
+    /**
+     * 澶氭枃浠跺垎鏋愮敤鎴锋彁闂俊鎭紙涓庢枃浠惰矾寰勫垎寮�瀛樺偍锛�
+     */
+    private List<String> analyzeUserQuestions;
+
+    /**
+     * 澶氭枃浠跺垎鏋愪笂浼犳枃浠惰矾寰勶紙鍥剧墖鍜� pdf 浣跨敤棰勮鍦板潃锛屽叾浠栦娇鐢ㄤ笅杞藉湴鍧�锛�
+     */
+    private List<String> analyzeFilePaths;
+
+    /**
+     * 澶氭枃浠跺垎鏋愭瘡娆℃彁闂搴旂殑鏂囦欢璺緞鍒嗙粍
+     */
+    private List<List<String>> analyzeFilePathGroups;
+
     private Date createTime;
 
     private Date updateTime;
diff --git a/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java b/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
new file mode 100644
index 0000000..0f24d64
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
@@ -0,0 +1,1031 @@
+package com.ruoyi.ai.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ruoyi.ai.assistant.PurchaseAgent;
+import com.ruoyi.ai.assistant.PurchaseIntentExecutor;
+import com.ruoyi.ai.bean.ChatForm;
+import com.ruoyi.ai.bean.PurchaseAiConfirmRequest;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.ai.dto.AiChatMessageDto;
+import com.ruoyi.ai.dto.AiChatSessionDto;
+import com.ruoyi.ai.store.MongoChatMemoryStore;
+import com.ruoyi.basic.dto.StorageBlobVO;
+import com.ruoyi.basic.mapper.SupplierManageMapper;
+import com.ruoyi.basic.pojo.SupplierManage;
+import com.ruoyi.basic.service.StorageBlobService;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.purchase.dto.PurchaseLedgerDto;
+import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
+import com.ruoyi.purchase.pojo.PaymentRegistration;
+import com.ruoyi.purchase.service.IPaymentRegistrationService;
+import com.ruoyi.purchase.service.IPurchaseLedgerService;
+import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import dev.langchain4j.data.image.Image;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.data.message.Content;
+import dev.langchain4j.data.message.ImageContent;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.TextContent;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.chat.StreamingChatLanguageModel;
+import dev.langchain4j.model.chat.response.ChatResponse;
+import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import reactor.core.publisher.Flux;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Base64;
+import java.util.Arrays;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.UUID;
+import java.nio.file.Files;
+
+@Service
+public class PurchaseAiService {
+
+    private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::";
+    private static final int MAX_FILE_COUNT = 10;
+    private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000;
+    private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000;
+
+    private final PurchaseAgent purchaseAgent;
+    private final PurchaseIntentExecutor purchaseIntentExecutor;
+    private final AiSessionUserContext aiSessionUserContext;
+    private final MongoChatMemoryStore mongoChatMemoryStore;
+    private final AiChatSessionService aiChatSessionService;
+    private final AiFileTextExtractor aiFileTextExtractor;
+    private final ObjectMapper objectMapper;
+    private final IPurchaseLedgerService purchaseLedgerService;
+    private final IPaymentRegistrationService paymentRegistrationService;
+    private final PurchaseReturnOrdersService purchaseReturnOrdersService;
+    private final StorageBlobService storageBlobService;
+    private final SupplierManageMapper supplierManageMapper;
+    private final StreamingChatLanguageModel purchaseVisionStreamingChatModel;
+
+    public PurchaseAiService(PurchaseAgent purchaseAgent,
+                                PurchaseIntentExecutor purchaseIntentExecutor,
+                                AiSessionUserContext aiSessionUserContext,
+                                MongoChatMemoryStore mongoChatMemoryStore,
+                                AiChatSessionService aiChatSessionService,
+                                AiFileTextExtractor aiFileTextExtractor,
+                                 ObjectMapper objectMapper,
+                                 IPurchaseLedgerService purchaseLedgerService,
+                                 IPaymentRegistrationService paymentRegistrationService,
+                                 PurchaseReturnOrdersService purchaseReturnOrdersService,
+                                 StorageBlobService storageBlobService,
+                                 SupplierManageMapper supplierManageMapper,
+                                 @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
+        this.purchaseAgent = purchaseAgent;
+        this.purchaseIntentExecutor = purchaseIntentExecutor;
+        this.aiSessionUserContext = aiSessionUserContext;
+        this.mongoChatMemoryStore = mongoChatMemoryStore;
+        this.aiChatSessionService = aiChatSessionService;
+        this.aiFileTextExtractor = aiFileTextExtractor;
+        this.objectMapper = objectMapper;
+        this.purchaseLedgerService = purchaseLedgerService;
+        this.paymentRegistrationService = paymentRegistrationService;
+        this.purchaseReturnOrdersService = purchaseReturnOrdersService;
+        this.storageBlobService = storageBlobService;
+        this.supplierManageMapper = supplierManageMapper;
+        this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel;
+    }
+
+    public Flux<String> chat(ChatForm chatForm, LoginUser loginUser) {
+        if (!StringUtils.hasText(chatForm.getMemoryId())) {
+            return Flux.just("memoryId涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(chatForm.getMessage())) {
+            return Flux.just("message涓嶈兘涓虹┖");
+        }
+
+        String memoryId = chatForm.getMemoryId();
+        String userMessage = chatForm.getMessage();
+
+        aiSessionUserContext.bind(memoryId, loginUser);
+        aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
+
+        String directResponse = purchaseIntentExecutor.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 purchaseAgent.chat(memoryId, userMessage)
+                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
+                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
+    }
+
+    public Flux<String> analyzeFiles(MultipartFile[] files,
+                                     String message,
+                                     String memoryId,
+                                     LoginUser loginUser) {
+        if (files == null || files.length == 0) {
+            return Flux.just("files涓嶈兘涓虹┖");
+        }
+        if (files.length > MAX_FILE_COUNT) {
+            return Flux.just("涓�娆℃渶澶氬垎鏋�" + MAX_FILE_COUNT + "涓枃浠�");
+        }
+
+        String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
+        String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX)
+                ? rawMemoryId
+                : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
+
+        aiSessionUserContext.bind(finalMemoryId, loginUser);
+
+        String finalMessage = StringUtils.hasText(message)
+                ? message
+                : "璇峰垎鏋愯繖浜涢噰璐枃浠讹紝鎻愬彇鍙敤浜庝笟鍔″鐞嗙殑鏁版嵁锛屽苟鏁寸悊鎴愬緟瀹㈡埛纭鐨勬牸寮�";
+
+        List<String> filePaths;
+        try {
+            List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true);
+            filePaths = resolveFileAccessPaths(uploadedFiles);
+        } catch (Exception ex) {
+            return Flux.just("鏂囦欢涓婁紶澶辫触");
+        }
+        try {
+            mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths);
+        } catch (Exception ex) {
+            return Flux.just("浼氳瘽鏂囦欢淇℃伅淇濆瓨澶辫触");
+        }
+
+        String fileContent;
+        try {
+            fileContent = buildMultiFileContent(files);
+        } catch (IllegalArgumentException ex) {
+            return Flux.just(ex.getMessage());
+        } catch (IOException ex) {
+            return Flux.just("鏂囦欢璇诲彇澶辫触");
+        }
+
+        if (!StringUtils.hasText(fileContent)) {
+            return Flux.just("鏈彁鍙栧埌鏈夋晥鏂囦欢鍐呭");
+        }
+
+        String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent);
+        aiChatSessionService.touchSession(finalMemoryId, loginUser, "閲囪喘澶氭枃浠跺垎鏋�: " + finalMessage);
+
+        if (containsImageFile(files)) {
+            return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files)
+                    .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
+                    .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
+        }
+
+        return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt))
+                .onErrorResume(NoSuchElementException.class, ex -> {
+                    mongoChatMemoryStore.deleteMessages(finalMemoryId);
+                    return purchaseAgent.chat(finalMemoryId, userPrompt);
+                })
+                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
+                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
+    }
+
+    public AjaxResult confirmAnalyzeResult(PurchaseAiConfirmRequest request) {
+        if (request == null || !StringUtils.hasText(request.getBusinessType())) {
+            return AjaxResult.error("businessType涓嶈兘涓虹┖");
+        }
+        if (request.getPayload() == null || request.getPayload().isEmpty()) {
+            return AjaxResult.error("payload涓嶈兘涓虹┖");
+        }
+
+        try {
+            String businessType = request.getBusinessType().trim();
+            return switch (businessType) {
+                case "purchase_ledger" -> processPurchaseLedger(request.getPayload());
+                case "payment_registration" -> processPaymentRegistration(request.getPayload());
+                case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload());
+                default -> AjaxResult.error("鏆備笉鏀寔璇ヤ笟鍔$被鍨�: " + businessType);
+            };
+        } catch (Exception ex) {
+            return AjaxResult.error(toCustomerMessage(ex));
+        }
+    }
+
+    public List<AiChatSessionDto> listSessions(LoginUser loginUser) {
+        return aiChatSessionService.listCurrentUserSessions(loginUser);
+    }
+
+    public List<AiChatMessageDto> listMessages(String memoryId, LoginUser loginUser) {
+        return aiChatSessionService.listCurrentUserMessages(memoryId, loginUser);
+    }
+
+    public boolean deleteSession(String memoryId, LoginUser loginUser) {
+        aiSessionUserContext.remove(memoryId);
+        return aiChatSessionService.deleteCurrentUserSession(memoryId, loginUser);
+    }
+
+    private String buildMultiFileContent(MultipartFile[] files) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        int totalLength = 0;
+        for (MultipartFile file : files) {
+            String text = aiFileTextExtractor.extractText(file);
+            if (!StringUtils.hasText(text)) {
+                continue;
+            }
+            String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH
+                    ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH)
+                    : text;
+            if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) {
+                int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength;
+                if (remain <= 0) {
+                    break;
+                }
+                limitedText = limitedText.substring(0, remain);
+            }
+            builder.append("\n--- 鏂囦欢: ")
+                    .append(file.getOriginalFilename())
+                    .append(" ---\n")
+                    .append(limitedText)
+                    .append('\n');
+            totalLength += limitedText.length();
+        }
+        return builder.toString();
+    }
+
+    private boolean containsImageFile(MultipartFile[] files) {
+        for (MultipartFile file : files) {
+            if (aiFileTextExtractor.isImageFile(file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) {
+        if (StringUtils.isEmpty(uploadedFiles)) {
+            return Collections.emptyList();
+        }
+        List<String> filePaths = new ArrayList<>();
+        for (StorageBlobVO uploadedFile : uploadedFiles) {
+            if (uploadedFile == null) {
+                continue;
+            }
+            String selectedPath;
+            if (shouldUsePreviewPath(uploadedFile)) {
+                selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL())
+                        ? uploadedFile.getPreviewURL()
+                        : uploadedFile.getDownloadURL();
+            } else {
+                selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL())
+                        ? uploadedFile.getDownloadURL()
+                        : uploadedFile.getPreviewURL();
+            }
+            if (StringUtils.hasText(selectedPath)) {
+                filePaths.add(selectedPath);
+            }
+        }
+        return filePaths;
+    }
+
+    private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) {
+        String contentType = uploadedFile.getContentType();
+        if (StringUtils.hasText(contentType)) {
+            String normalized = contentType.toLowerCase(Locale.ROOT);
+            if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) {
+                return true;
+            }
+        }
+        String filename = uploadedFile.getOriginalFilename();
+        if (!StringUtils.hasText(filename) || !filename.contains(".")) {
+            return false;
+        }
+        String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
+        return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf");
+    }
+
+    private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException {
+        List<MultipartFile> copies = new ArrayList<>();
+        for (MultipartFile file : files) {
+            copies.add(new InMemoryMultipartFile(
+                    file.getName(),
+                    file.getOriginalFilename(),
+                    file.getContentType(),
+                    file.getBytes()
+            ));
+        }
+        return copies;
+    }
+
+    private static final class InMemoryMultipartFile implements MultipartFile {
+        private final String name;
+        private final String originalFilename;
+        private final String contentType;
+        private final byte[] bytes;
+
+        private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) {
+            this.name = name;
+            this.originalFilename = originalFilename;
+            this.contentType = contentType;
+            this.bytes = bytes == null ? new byte[0] : bytes;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getOriginalFilename() {
+            return originalFilename;
+        }
+
+        @Override
+        public String getContentType() {
+            return contentType;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return bytes.length == 0;
+        }
+
+        @Override
+        public long getSize() {
+            return bytes.length;
+        }
+
+        @Override
+        public byte[] getBytes() {
+            return bytes.clone();
+        }
+
+        @Override
+        public InputStream getInputStream() {
+            return new ByteArrayInputStream(bytes);
+        }
+
+        @Override
+        public void transferTo(File dest) throws IOException, IllegalStateException {
+            Files.write(dest.toPath(), bytes);
+        }
+    }
+
+    private Flux<String> chatWithPurchaseVisionModel(String memoryId,
+                                                     String userMessage,
+                                                     String userPrompt,
+                                                     MultipartFile[] files) {
+        return Flux.create(sink -> {
+            StringBuilder assistantReply = new StringBuilder();
+            try {
+                List<Content> contents = new ArrayList<>();
+                contents.add(TextContent.from(userPrompt));
+                for (MultipartFile file : files) {
+                    if (!aiFileTextExtractor.isImageFile(file)) {
+                        continue;
+                    }
+                    contents.add(TextContent.from("涓嬮潰杩欏紶鍥剧墖鏂囦欢鍚嶏細" + file.getOriginalFilename()));
+                    contents.add(ImageContent.from(Image.builder()
+                            .base64Data(Base64.getEncoder().encodeToString(file.getBytes()))
+                            .mimeType(resolveImageMimeType(file))
+                            .build()));
+                }
+
+                List<ChatMessage> messages = List.of(
+                        SystemMessage.from("浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝浠庢枃鏈拰鍥剧墖涓瘑鍒噰璐彴璐︺�侀噰璐骇鍝佹槑缁嗐�佷粯娆炬垨閫�璐т俊鎭紝鍙緭鍑哄悎娉� JSON銆�"),
+                        UserMessage.from(contents)
+                );
+                safeAppendMessages(memoryId, List.of(UserMessage.from("閲囪喘澶氭枃浠跺垎鏋�: " + userMessage)));
+                purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() {
+                    @Override
+                    public void onPartialResponse(String partialResponse) {
+                        if (partialResponse != null) {
+                            assistantReply.append(partialResponse);
+                            sink.next(partialResponse);
+                        }
+                    }
+
+                    @Override
+                    public void onCompleteResponse(ChatResponse completeResponse) {
+                        if (StringUtils.hasText(assistantReply.toString())) {
+                            safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString())));
+                        }
+                        sink.complete();
+                    }
+
+                    @Override
+                    public void onError(Throwable error) {
+                        sink.error(error);
+                    }
+                });
+            } catch (Exception ex) {
+                sink.next("鍥剧墖鏂囦欢璇诲彇澶辫触锛岃纭鍥剧墖鏍煎紡涓� png銆乯pg銆乯peg銆亀ebp 鎴� bmp锛屼笖澶у皬涓嶈秴杩�10MB");
+                sink.complete();
+            }
+        });
+    }
+
+    private void safeAppendMessages(String memoryId, List<ChatMessage> messages) {
+        if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) {
+            return;
+        }
+        try {
+            mongoChatMemoryStore.appendMessages(memoryId, messages);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private String resolveImageMimeType(MultipartFile file) {
+        String contentType = file.getContentType();
+        if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) {
+            return contentType;
+        }
+        String filename = file.getOriginalFilename();
+        String ext = "";
+        if (StringUtils.hasText(filename) && filename.contains(".")) {
+            ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
+        }
+        return switch (ext) {
+            case "jpg", "jpeg" -> "image/jpeg";
+            case "webp" -> "image/webp";
+            case "bmp" -> "image/bmp";
+            default -> "image/png";
+        };
+    }
+
+    private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) {
+        return """
+                浣犳槸閲囪喘涓氬姟鏂囦欢鍒嗘瀽鍔╂墜銆傝涓ユ牸鏍规嵁鐢ㄦ埛涓婁紶鐨勫涓枃浠跺拰鐢ㄦ埛瑕佹眰鎻愬彇閲囪喘涓氬姟鏁版嵁銆�
+
+                鐢ㄦ埛瑕佹眰:
+                %s
+
+                杈撳嚭瑕佹眰:
+                1. 鍙緭鍑哄悎娉� JSON锛屼笉瑕� Markdown锛屼笉瑕侀澶栬В閲娿��
+                2. JSON 椤跺眰瀛楁鍥哄畾涓�:
+                   - success: boolean
+                   - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown
+                   - action: confirm_required
+                   - description: 涓枃璇存槑
+                   - confidence: 0鍒�1鐨勫皬鏁�
+                   - missingFields: 缂哄け瀛楁涓枃鍚嶇О鏁扮粍锛岄潰鍚戝鎴峰睍绀猴紝涓嶈杈撳嚭鑻辨枃瀛楁鍚�
+                   - warnings: 椋庨櫓鎻愮ず鏁扮粍
+                   - payload: 寰呭鎴风‘璁ょ殑鏁版嵁锛屽瓧娈靛悕蹇呴』浣跨敤鍚庣 DTO 瀛楁鍚�
+                   - preview: 缁欏鎴风‘璁ょ敤鐨勪腑鏂囨憳瑕佹暟缁�
+                3. 濡傛灉鍙垽鏂负閲囪喘鍙拌处锛宐usinessType 浣跨敤 purchase_ledger锛宲ayload.purchaseLedgers 涓洪噰璐鍗�/閲囪喘鍙拌处鏁扮粍:
+                   - purchaseLedgers: 閲囪喘璁㈠崟/閲囪喘鍙拌处鏁扮粍锛屾瘡鏉¤褰曞瓧娈靛悕蹇呴』涓� PurchaseLedgerDto 淇濇寔涓�鑷�
+                   - 浜у搧鏄庣粏蹇呴』鏀惧湪姣忔潯閲囪喘鍙拌处璁板綍鐨� productData 瀛楁涓紝productData 绫诲瀷涓� List<SalesLedgerProduct>
+                   - 涓嶈浼樺厛浣跨敤 payload 椤跺眰 productData锛涢《灞� productData 浠呬綔涓烘棫鏍煎紡鍏煎
+                   - 鏂囦欢閲岀殑鈥滈噰璐崟鍙封�濆氨鏄�滈噰璐悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� purchaseContractNumber
+                   - 鏂囦欢閲岀殑鈥滈攢鍞崟鍙封�濆氨鏄�滈攢鍞悎鍚屽彿鈥濓紝缁熶竴鏄犲皠涓� salesContractNo
+                   - 鎵�鏈夋棩鏈熷瓧娈靛繀椤讳娇鐢� yyyy-MM-dd锛屼緥濡� 2026-04-30锛涗笉瑕佽緭鍑� 4/30/26銆�2026/4/30銆�2026骞�4鏈�30鏃� 鎴栧甫鏃跺垎绉掔殑鏍煎紡
+                   - 閲囪喘鍙拌处涓嶉渶瑕佸湪 payload 涓紶瀹℃壒浜猴紝涓嶈杈撳嚭 approveUserIds銆乤pproverId
+                   - missingFields 鍙~鍐欎笟鍔″繀濉絾鏃犳硶璇嗗埆鐨勫瓧娈碉紝涓嶈鎶� PurchaseLedgerDto 鐨勬墍鏈夌┖瀛楁閮藉垪涓虹己澶憋紱缂哄け椤瑰繀椤诲啓涓枃锛屼緥濡傗�滀緵搴斿晢鍚嶇О鈥濃�滃惈绋庡崟浠封�濓紝涓嶈鍐� supplierId銆乼axInclusiveUnitPrice
+                   - 閲囪喘鍙拌处涓昏〃蹇呭~瀛楁浠呮寜杩欎簺鍒ゆ柇: purchaseContractNumber銆乻upplierName 鎴� supplierId
+                   - productData 姣忔潯浜у搧蹇呭~瀛楁: productCategory銆乻pecificationModel銆乽nit銆乹uantity銆乼axInclusiveUnitPrice 鎴� taxInclusiveTotalPrice锛涘鏋滃彧鏈夊惈绋庢�讳环鍜屾暟閲忥紝蹇呴』璁$畻 taxInclusiveUnitPrice锛涘鏋滃彧鏈夊惈绋庡崟浠峰拰鏁伴噺锛屽繀椤昏绠� taxInclusiveTotalPrice
+                   - 浜у搧瀛楁鎸夐噰璐鍏ユ帴鍙� PurchaseLedgerProductImportDto 瀵归綈: 閲囪喘鍗曞彿銆佷骇鍝佸ぇ绫汇�佽鏍煎瀷鍙枫�佸崟浣嶃�佹暟閲忋�佺◣鐜囥�佸惈绋庡崟浠枫�佸惈绋庢�讳环銆佸彂绁ㄧ被鍨嬨�佹槸鍚﹁川妫�
+                   - 閲囪喘浜у搧 type 鍥哄畾涓� 2
+                   - purchaseLedgers 姣忔潯璁板綍鍙娇鐢ㄨ繖浜� PurchaseLedgerDto 瀛楁鍚�:
+                     entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName
+                   - productData 姣忔潯浜у搧鍙娇鐢ㄨ繖浜� SalesLedgerProduct 瀛楁鍚�:
+                     productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type
+                4. 濡傛灉鍙垽鏂负浠樻鐧昏锛宐usinessType 浣跨敤 payment_registration锛宲ayload.records 涓轰粯娆剧櫥璁版暟缁勶紝瀛楁灏介噺鍖呭惈 purchaseLedgerId銆乻alesLedgerProductId銆乧urrentPaymentAmount銆乸aymentMethod銆乸aymentDate銆�
+                5. 濡傛灉鍙垽鏂负閲囪喘閫�璐э紝businessType 浣跨敤 purchase_return_order锛宲ayload 鎸� PurchaseReturnOrderDto 缁勭粐锛屾槑缁嗘斁 purchaseReturnOrderProductsDtos銆�
+                6. 缂哄皯涓氬姟澶勭悊蹇呴』瀛楁鏃讹紝涓嶈缂栭�� ID锛屾妸瀛楁鏀惧叆 missingFields锛屽苟浠嶈繑鍥炲彲纭鐨勮崏绋挎暟鎹��
+                7. 鎵�鏈変腑鏂囧唴瀹圭洿鎺ヤ繚鐣欙紝涓嶈杞箟鎴� Unicode銆�
+
+                鏂囦欢鍐呭:
+                %s
+                """.formatted(message, fileContent);
+    }
+
+    private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception {
+        if (payload.containsKey("purchaseLedgers")) {
+            return processPurchaseLedgerBatch(payload);
+        }
+
+        Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload);
+        PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class);
+        AjaxResult ledgerResult = validatePurchaseLedger(dto, 0);
+        if (ledgerResult != null) {
+            return ledgerResult;
+        }
+        AjaxResult supplierResult = fillSupplierIdByName(dto);
+        if (supplierResult != null) {
+            return supplierResult;
+        }
+        AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0);
+        if (productResult != null) {
+            return productResult;
+        }
+        int result = purchaseLedgerService.addOrEditPurchase(dto);
+        return AjaxResult.success("閲囪喘鍙拌处宸插鐞�", result);
+    }
+
+    private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
+        List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers"));
+        if (purchaseLedgers.isEmpty()) {
+            return AjaxResult.error("purchaseLedgers涓嶈兘涓虹┖");
+        }
+
+        List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData"));
+        List<Map<String, Object>> results = new ArrayList<>();
+        for (int i = 0; i < purchaseLedgers.size(); i++) {
+            Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i));
+            PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class);
+            AjaxResult ledgerResult = validatePurchaseLedger(dto, i);
+            if (ledgerResult != null) {
+                return ledgerResult;
+            }
+            AjaxResult supplierResult = fillSupplierIdByName(dto);
+            if (supplierResult != null) {
+                return supplierResult;
+            }
+
+            List<SalesLedgerProduct> products = dto.getProductData();
+            if (products == null || products.isEmpty()) {
+                products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1);
+                dto.setProductData(products);
+            }
+            AjaxResult productResult = validatePurchaseProducts(products, i);
+            if (productResult != null) {
+                return productResult;
+            }
+            int result = purchaseLedgerService.addOrEditPurchase(dto);
+
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("index", i);
+            item.put("purchaseContractNumber", dto.getPurchaseContractNumber());
+            item.put("supplierId", dto.getSupplierId());
+            item.put("supplierName", dto.getSupplierName());
+            item.put("productCount", products.size());
+            item.put("result", result);
+            results.add(item);
+        }
+        return AjaxResult.success("閲囪喘鍙拌处宸叉壒閲忓鐞�", results);
+    }
+
+    private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap,
+                                                            PurchaseLedgerDto dto,
+                                                            List<Map<String, Object>> productData,
+                                                            boolean onlyOneLedger) {
+        List<SalesLedgerProduct> products = new ArrayList<>();
+        for (Map<String, Object> productMap : productData) {
+            if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) {
+                products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class));
+            }
+        }
+        return products;
+    }
+
+    private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) {
+        Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "閲囪喘璁㈠崟id", "閲囪喘鍙拌处id");
+        if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) {
+            return true;
+        }
+
+        Long productSalesLedgerId = longValue(productMap, "salesLedgerId");
+        if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) {
+            return true;
+        }
+
+        String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
+        if (StringUtils.hasText(productContractNo)
+                && StringUtils.hasText(dto.getPurchaseContractNumber())
+                && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) {
+            return true;
+        }
+
+        String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
+        if (StringUtils.hasText(productContractNo)
+                && StringUtils.hasText(ledgerContractNo)
+                && productContractNo.trim().equals(ledgerContractNo.trim())) {
+            return true;
+        }
+
+        String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
+        if (StringUtils.hasText(productSalesContractNo)
+                && StringUtils.hasText(dto.getSalesContractNo())
+                && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) {
+            return true;
+        }
+
+        String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
+        if (StringUtils.hasText(productSalesContractNo)
+                && StringUtils.hasText(ledgerSalesContractNo)
+                && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) {
+            return true;
+        }
+
+        String productSupplierName = stringValue(productMap, "supplierName", "渚涘簲鍟嗗悕绉�");
+        return StringUtils.hasText(productSupplierName)
+                && StringUtils.hasText(dto.getSupplierName())
+                && productSupplierName.trim().equals(dto.getSupplierName().trim());
+    }
+
+    private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) {
+        Map<String, Object> target = new LinkedHashMap<>();
+        copyPurchaseLedgerDtoFields(source, target);
+        putDtoFieldIfPresent(source, target, "entryDateStart", "褰曞叆寮�濮嬫棩鏈�", "褰曞叆鏃ユ湡寮�濮�");
+        putDtoFieldIfPresent(source, target, "entryDateEnd", "褰曞叆缁撴潫鏃ユ湡", "褰曞叆鏃ユ湡缁撴潫");
+        putDtoFieldIfPresent(source, target, "id", "閲囪喘鍙拌处id", "閲囪喘璁㈠崟id", "涓婚敭");
+        putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "閲囪喘鍚堝悓鍙�", "閲囪喘鍗曞彿", "閲囪喘璁㈠崟鍙�");
+        putDtoFieldIfPresent(source, target, "supplierId", "渚涘簲鍟唅d", "渚涘簲鍟咺D", "渚涘簲鍟嗗悕绉癷d", "渚涘簲鍟嗗悕绉癐D");
+        putDtoFieldIfPresent(source, target, "supplierName", "渚涘簲鍟�", "渚涘簲鍟嗗悕绉�");
+        putDtoFieldIfPresent(source, target, "isWhite", "鏄惁鐧藉悕鍗�");
+        putDtoFieldIfPresent(source, target, "recorderId", "褰曞叆浜篿d", "褰曞叆浜篒D", "褰曞叆浜哄鍚峣d", "褰曞叆浜哄鍚岻D");
+        putDtoFieldIfPresent(source, target, "recorderName", "褰曞叆浜�", "褰曞叆浜哄鍚�");
+        putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "閿�鍞悎鍚屽彿", "閿�鍞崟鍙�", "閿�鍞鍗曞彿");
+        putDtoFieldIfPresent(source, target, "salesContractNoId", "閿�鍞悎鍚屽彿id", "閿�鍞悎鍚屽彿ID", "閿�鍞崟鍙穒d", "閿�鍞崟鍙稩D");
+        putDtoFieldIfPresent(source, target, "projectName", "椤圭洰", "椤圭洰鍚嶇О");
+        putDtoFieldIfPresent(source, target, "entryDate", "褰曞叆鏃ユ湡");
+        putDtoFieldIfPresent(source, target, "executionDate", "绛捐鏃ユ湡", "鍚堝悓绛捐鏃ユ湡");
+        putDtoFieldIfPresent(source, target, "remarks", "澶囨敞", "璇存槑");
+        putDtoFieldIfPresent(source, target, "attachmentMaterials", "闄勪欢鏉愭枡", "闄勪欢鏉愭枡璺緞鎴栧悕绉�");
+        putDtoFieldIfPresent(source, target, "createdAt", "鍒涘缓鏃堕棿", "璁板綍鍒涘缓鏃堕棿");
+        putDtoFieldIfPresent(source, target, "updatedAt", "鏇存柊鏃堕棿", "璁板綍鏈�鍚庢洿鏂版椂闂�");
+        putDtoFieldIfPresent(source, target, "salesLedgerId", "閿�鍞彴璐d", "閿�鍞彴璐D", "鍏宠仈閿�鍞彴璐︿富琛ㄤ富閿�");
+        putDtoFieldIfPresent(source, target, "hasChildren", "鏄惁鏈夊瓙绾�", "鏄惁鏈夋槑缁�");
+        putDtoFieldIfPresent(source, target, "Type", "鍙拌处绫诲瀷", "涓氬姟绫诲瀷");
+        putDtoFieldIfPresent(source, target, "productData", "products", "浜у搧鏄庣粏", "閲囪喘浜у搧鏄庣粏");
+        putDtoFieldIfPresent(source, target, "tempFileIds", "涓存椂鏂囦欢id", "涓存椂鏂囦欢ID", "涓存椂鏂囦欢ids");
+        putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "闄勪欢鍒楄〃", "閿�鍞彴璐﹂檮浠�");
+        putDtoFieldIfPresent(source, target, "phoneNumber", "涓氬姟鍛樻墜鏈哄彿", "鎵嬫満鍙�");
+        putDtoFieldIfPresent(source, target, "businessPersonId", "涓氬姟鍛榠d", "涓氬姟鍛業D");
+        putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
+        putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID");
+        putDtoFieldIfPresent(source, target, "invoiceNumber", "鍙戠エ鍙�", "鍙戠エ鍙风爜");
+        putDtoFieldIfPresent(source, target, "invoiceAmount", "鍙戠エ閲戦", "鍙戠エ閲戦锛堝厓锛�");
+        putDtoFieldIfPresent(source, target, "ticketRegistrationId", "鏉ョエ鐧昏id", "鏉ョエ鐧昏ID");
+        putDtoFieldIfPresent(source, target, "contractAmount", "鍚堝悓閲戦", "鍚堝悓閲戦锛堜骇鍝佸惈绋庢�讳环锛�");
+        putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "鏉ョエ閲戦", "宸叉潵绁ㄩ噾棰�", "宸叉潵绁ㄩ噾棰�(鍏�)");
+        putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "鏈潵绁ㄩ噾棰�", "鏈潵绁ㄩ噾棰�(鍏�)");
+        putDtoFieldIfPresent(source, target, "type", "鏂囦欢绫诲瀷");
+        putDtoFieldIfPresent(source, target, "paymentMethod", "浠樻鏂瑰紡");
+        putDtoFieldIfPresent(source, target, "approvalStatus", "瀹℃壒鐘舵��");
+        putDtoFieldIfPresent(source, target, "templateName", "妯℃澘鍚嶇О");
+        target.remove("approveUserIds");
+        target.remove("approverId");
+        normalizeNestedProductData(target);
+        attachImportStyleProductData(source, target);
+        if (target.get("type") == null) {
+            target.put("type", 2);
+        }
+        target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
+        normalizePurchaseLedgerDateFields(target);
+        return target;
+    }
+
+    private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) {
+        if (target.get("productData") != null) {
+            return;
+        }
+        Map<String, Object> productMap = normalizeSalesLedgerProductMap(source);
+        if (hasImportStyleProductData(productMap)) {
+            target.put("productData", List.of(productMap));
+        }
+    }
+
+    private boolean hasImportStyleProductData(Map<String, Object> productMap) {
+        return hasMapText(productMap, "productCategory")
+                || hasMapText(productMap, "specificationModel")
+                || productMap.get("quantity") != null
+                || productMap.get("taxInclusiveUnitPrice") != null
+                || productMap.get("taxInclusiveTotalPrice") != null;
+    }
+
+    private boolean hasMapText(Map<String, Object> map, String key) {
+        Object value = map.get(key);
+        return value != null && StringUtils.hasText(String.valueOf(value));
+    }
+
+    private void normalizeNestedProductData(Map<String, Object> target) {
+        Object productDataValue = target.get("productData");
+        if (productDataValue == null) {
+            return;
+        }
+        List<Map<String, Object>> productMaps = toMapList(productDataValue);
+        List<Map<String, Object>> normalizedProducts = new ArrayList<>();
+        for (Map<String, Object> productMap : productMaps) {
+            normalizedProducts.add(normalizeSalesLedgerProductMap(productMap));
+        }
+        target.put("productData", normalizedProducts);
+    }
+
+    private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) {
+        Map<String, Object> target = new LinkedHashMap<>();
+        copySalesLedgerProductFields(source, target);
+        putDtoFieldIfPresent(source, target, "productCategory", "浜у搧澶х被", "浜у搧鍚嶇О", "浜у搧", "鍝佸悕", "鐗╂枡鍚嶇О");
+        putDtoFieldIfPresent(source, target, "specificationModel", "瑙勬牸鍨嬪彿", "鍨嬪彿", "瑙勬牸", "浜у搧瑙勬牸");
+        putDtoFieldIfPresent(source, target, "unit", "鍗曚綅");
+        putDtoFieldIfPresent(source, target, "quantity", "鏁伴噺", "閲囪喘鏁伴噺");
+        putDtoFieldIfPresent(source, target, "taxRate", "绋庣巼");
+        putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "鍚◣鍗曚环", "鍗曚环", "閲囪喘鍗曚环", "鍚◣浠锋牸");
+        putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "鍚◣鎬讳环", "鎬讳环", "閲囪喘閲戦", "閲戦", "鍚堝悓閲戦");
+        putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "涓嶅惈绋庢�讳环");
+        putDtoFieldIfPresent(source, target, "invoiceType", "鍙戠エ绫诲瀷", "鍙戠エ绫诲埆");
+        putDtoFieldIfPresent(source, target, "productId", "浜у搧id", "浜у搧ID");
+        putDtoFieldIfPresent(source, target, "productModelId", "浜у搧瑙勬牸id", "浜у搧瑙勬牸ID", "鍨嬪彿id", "鍨嬪彿ID");
+        putDtoFieldIfPresent(source, target, "isChecked", "鏄惁璐ㄦ", "鏄惁璐ㄦ楠�", "璐ㄦ");
+        putDtoFieldIfPresent(source, target, "type", "鍙拌处绫诲瀷");
+        normalizeProductAmounts(target);
+        target.putIfAbsent("type", 2);
+        return target;
+    }
+
+    private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) {
+        String[] productFields = {
+                "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit",
+                "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice",
+                "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum",
+                "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum",
+                "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate",
+                "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal",
+                "isChecked", "isProduction"
+        };
+        for (String field : productFields) {
+            if (source.containsKey(field)) {
+                target.put(field, source.get(field));
+            }
+        }
+    }
+
+    private void normalizeProductAmounts(Map<String, Object> target) {
+        BigDecimal quantity = decimalValue(target.get("quantity"));
+        BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice"));
+        BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
+        if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) {
+            target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP));
+        }
+        if (totalPrice == null && unitPrice != null && quantity != null) {
+            target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity));
+        }
+        BigDecimal taxRate = decimalValue(target.get("taxRate"));
+        totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
+        if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) {
+            BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP));
+            target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP));
+        }
+    }
+
+    private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
+        if (products == null || products.isEmpty()) {
+            return null;
+        }
+        for (int i = 0; i < products.size(); i++) {
+            SalesLedgerProduct product = products.get(i);
+            String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐︾殑绗�" + (i + 1) + "鏉′骇鍝�";
+            if (!StringUtils.hasText(product.getProductCategory())) {
+                return AjaxResult.error(prefix + "缂哄皯浜у搧鍚嶇О锛岃琛ュ厖鍚庡啀纭");
+            }
+            if (!StringUtils.hasText(product.getSpecificationModel())) {
+                return AjaxResult.error(prefix + "缂哄皯瑙勬牸鍨嬪彿锛岃琛ュ厖鍚庡啀纭");
+            }
+            if (!StringUtils.hasText(product.getUnit())) {
+                return AjaxResult.error(prefix + "缂哄皯鍗曚綅锛岃琛ュ厖鍚庡啀纭");
+            }
+            if (product.getQuantity() == null) {
+                return AjaxResult.error(prefix + "缂哄皯鏁伴噺");
+            }
+            if (product.getTaxInclusiveUnitPrice() == null) {
+                return AjaxResult.error(prefix + "缂哄皯鍚◣鍗曚环锛岃琛ュ厖鍚庡啀纭");
+            }
+            if (product.getTaxInclusiveTotalPrice() == null) {
+                return AjaxResult.error(prefix + "缂哄皯鍚◣鎬讳环锛岃琛ュ厖鍚庡啀纭");
+            }
+        }
+        return null;
+    }
+
+    private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
+        String prefix = "绗�" + (ledgerIndex + 1) + "涓噰璐彴璐�";
+        if (!StringUtils.hasText(dto.getPurchaseContractNumber())) {
+            return AjaxResult.error(prefix + "缂哄皯閲囪喘鍚堝悓鍙凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
+        }
+        if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) {
+            return AjaxResult.error(prefix + "缂哄皯渚涘簲鍟嗗悕绉帮紝璇疯ˉ鍏呭悗鍐嶇‘璁�");
+        }
+        return null;
+    }
+
+    private void normalizePurchaseLedgerDateFields(Map<String, Object> target) {
+        normalizeDateField(target, "entryDate");
+        normalizeDateField(target, "executionDate");
+        normalizeDateField(target, "createdAt");
+        normalizeDateField(target, "updatedAt");
+    }
+
+    private void normalizeDateField(Map<String, Object> target, String fieldName) {
+        Object value = target.get(fieldName);
+        if (value == null) {
+            return;
+        }
+        String normalizedDate = normalizeDateValue(value);
+        if (StringUtils.hasText(normalizedDate)) {
+            target.put(fieldName, normalizedDate);
+        }
+    }
+
+    private String normalizeDateValue(Object value) {
+        if (value instanceof Date date) {
+            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
+        }
+        if (value instanceof Number number) {
+            return LocalDate.of(1899, 12, 30)
+                    .plusDays(number.longValue())
+                    .format(DateTimeFormatter.ISO_LOCAL_DATE);
+        }
+
+        String text = String.valueOf(value).trim();
+        if (!StringUtils.hasText(text)) {
+            return null;
+        }
+        if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') {
+            return text.substring(0, 10);
+        }
+
+        String normalizedText = text.replace("骞�", "-")
+                .replace("鏈�", "-")
+                .replace("鏃�", "")
+                .replace(".", "-")
+                .replace("/", "-")
+                .trim();
+        DateTimeFormatter[] formatters = {
+                DateTimeFormatter.ofPattern("yyyy-M-d"),
+                DateTimeFormatter.ofPattern("M-d-yyyy"),
+                DateTimeFormatter.ofPattern("M-d-yy")
+        };
+        for (DateTimeFormatter formatter : formatters) {
+            try {
+                return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE);
+            } catch (DateTimeParseException ignored) {
+                // Try the next supported input pattern.
+            }
+        }
+        return text;
+    }
+
+    private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) {
+        String[] dtoFields = {
+                "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber",
+                "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo",
+                "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials",
+                "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds",
+                "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber",
+                "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount",
+                "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName"
+        };
+        for (String field : dtoFields) {
+            if (source.containsKey(field)) {
+                target.put(field, source.get(field));
+            }
+        }
+    }
+
+    private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) {
+        if (target.containsKey(dtoField) && target.get(dtoField) != null) {
+            return;
+        }
+        for (String alias : aliases) {
+            Object value = source.get(alias);
+            if (value != null && StringUtils.hasText(String.valueOf(value))) {
+                target.put(dtoField, value);
+                return;
+            }
+        }
+    }
+
+    private List<Map<String, Object>> toMapList(Object value) {
+        if (value == null) {
+            return List.of();
+        }
+        return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() {
+        });
+    }
+
+    private String stringValue(Map<String, Object> map, String... keys) {
+        for (String key : keys) {
+            Object value = map.get(key);
+            if (value != null && StringUtils.hasText(String.valueOf(value))) {
+                return String.valueOf(value);
+            }
+        }
+        return null;
+    }
+
+    private Long longValue(Map<String, Object> map, String... keys) {
+        String value = stringValue(map, keys);
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        try {
+            return Long.parseLong(value.trim());
+        } catch (NumberFormatException ignored) {
+            return null;
+        }
+    }
+
+    private BigDecimal decimalValue(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof BigDecimal decimal) {
+            return decimal;
+        }
+        if (value instanceof Number number) {
+            return new BigDecimal(String.valueOf(number));
+        }
+        String text = String.valueOf(value)
+                .replace(",", "")
+                .replace("锛�", "")
+                .replace("鍏�", "")
+                .replace("锟�", "")
+                .trim();
+        if (!StringUtils.hasText(text)) {
+            return null;
+        }
+        try {
+            return new BigDecimal(text);
+        } catch (NumberFormatException ignored) {
+            return null;
+        }
+    }
+
+    private String toCustomerMessage(Exception ex) {
+        String message = ex.getMessage();
+        if (!StringUtils.hasText(message)) {
+            return "澶勭悊澶辫触锛岃妫�鏌ョ‘璁ゆ暟鎹悗閲嶈瘯";
+        }
+        if (message.contains("tax_inclusive_unit_price")) {
+            return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庡崟浠凤紝璇疯ˉ鍏呭悗鍐嶇‘璁�";
+        }
+        if (message.contains("tax_inclusive_total_price")) {
+            return "澶勭悊澶辫触锛氫骇鍝佹槑缁嗙己灏戝惈绋庢�讳环锛岃琛ュ厖鍚庡啀纭";
+        }
+        if (message.contains("entryDate")) {
+            return "澶勭悊澶辫触锛氬綍鍏ユ棩鏈熸牸寮忎笉姝g‘锛岃浣跨敤 yyyy-MM-dd锛屼緥濡� 2026-04-30";
+        }
+        if (message.contains("supplier")) {
+            return "澶勭悊澶辫触锛氫緵搴斿晢淇℃伅涓嶅畬鏁达紝璇风‘璁や緵搴斿晢鍚嶇О鎴栦緵搴斿晢ID";
+        }
+        if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) {
+            return "澶勭悊澶辫触锛氱‘璁ゆ暟鎹笉瀹屾暣鎴栨牸寮忎笉姝g‘锛岃妫�鏌ュ繀濉瓧娈靛悗閲嶈瘯";
+        }
+        return "澶勭悊澶辫触锛�" + message;
+    }
+
+    private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
+        if (dto.getSupplierId() != null) {
+            return null;
+        }
+        if (!StringUtils.hasText(dto.getSupplierName())) {
+            return AjaxResult.error("渚涘簲鍟咺D涓嶈兘涓虹┖锛涙湭璇嗗埆鍒颁緵搴斿晢鍚嶇О锛屾棤娉曡嚜鍔ㄥ尮閰嶄緵搴斿晢ID");
+        }
+
+        SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>()
+                .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim())
+                .last("limit 1"));
+        if (supplier == null) {
+            return AjaxResult.error("鏈壘鍒颁緵搴斿晢锛�" + dto.getSupplierName() + "锛岃鍏堢淮鎶や緵搴斿晢鎴栨墜鍔ㄩ�夋嫨渚涘簲鍟咺D");
+        }
+        dto.setSupplierId(supplier.getId());
+        return null;
+    }
+
+    private AjaxResult processPaymentRegistration(Map<String, Object> payload) {
+        Object recordsValue = payload.get("records");
+        List<PaymentRegistration> records;
+        if (recordsValue == null) {
+            records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class));
+        } else {
+            records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() {
+            });
+        }
+        int result = paymentRegistrationService.insertPaymentRegistration(records);
+        return AjaxResult.success("浠樻鐧昏宸插鐞�", result);
+    }
+
+    private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) {
+        PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class);
+        Boolean result = purchaseReturnOrdersService.add(dto);
+        return AjaxResult.success("閲囪喘閫�璐у崟宸插鐞�", result);
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java b/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
index 6d8c945..0ea436d 100644
--- a/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
+++ b/src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
@@ -114,7 +114,10 @@
             return new LinkedList<>();
         }
         List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
-        return messages.stream().map(this::convertMessage).collect(Collectors.toList());
+        List<AiChatMessageDto> messageDtos = messages.stream().map(this::convertMessage).collect(Collectors.toList());
+        List<List<String>> analyzeFilePathGroups = mongoChatMemoryStore.getAnalyzeFilePathGroups(memoryId);
+        attachAnalyzeFilePaths(messageDtos, analyzeFilePathGroups);
+        return messageDtos;
     }
 
     @Override
@@ -188,4 +191,22 @@
         }
         return new AiChatMessageDto("unknown", String.valueOf(message));
     }
+
+    private void attachAnalyzeFilePaths(List<AiChatMessageDto> messages,
+                                        List<List<String>> analyzeFilePathGroups) {
+        if (StringUtils.isEmpty(messages) || StringUtils.isEmpty(analyzeFilePathGroups)) {
+            return;
+        }
+        int analyzeIndex = 0;
+        for (AiChatMessageDto message : messages) {
+            if (!"user".equals(message.getRole()) || analyzeIndex >= analyzeFilePathGroups.size()) {
+                continue;
+            }
+            List<String> filePaths = analyzeFilePathGroups.get(analyzeIndex);
+            if (!StringUtils.isEmpty(filePaths)) {
+                message.setFilePaths(filePaths);
+            }
+            analyzeIndex++;
+        }
+    }
 }
diff --git a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java b/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
index e88f0e9..e6bcadb 100644
--- a/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
+++ b/src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
@@ -11,6 +11,8 @@
 import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.data.mongodb.core.query.Update;
 import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
 
 import java.util.Date;
 import java.util.LinkedList;
@@ -24,8 +26,7 @@
 
     @Override
     public List<ChatMessage> getMessages(Object memoryId) {
-        Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
-        ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);
+        ChatMessages chatMessages = findChatMessages(memoryId);
         if (chatMessages == null || chatMessages.getContent() == null) {
             return new LinkedList<>();
         }
@@ -56,7 +57,75 @@
         updateMessages(memoryId, messages);
     }
 
+    public void appendAnalyzeFileContext(Object memoryId, String userQuestion, List<String> filePaths) {
+        String memoryIdValue = memoryIdString(memoryId);
+        if (!StringUtils.hasText(memoryIdValue)) {
+            return;
+        }
+        List<String> validFilePaths = new LinkedList<>();
+        if (!CollectionUtils.isEmpty(filePaths)) {
+            for (String filePath : filePaths) {
+                if (StringUtils.hasText(filePath)) {
+                    validFilePaths.add(filePath);
+                }
+            }
+        }
+        if (!StringUtils.hasText(userQuestion) && validFilePaths.isEmpty()) {
+            return;
+        }
+        Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue));
+        Update update = new Update();
+        update.set("memoryId", memoryIdValue);
+        update.set("updateTime", new Date());
+        update.setOnInsert("createTime", new Date());
+        if (StringUtils.hasText(userQuestion)) {
+            update.push("analyzeUserQuestions", userQuestion);
+        }
+        if (!validFilePaths.isEmpty()) {
+            update.push("analyzeFilePaths").each(validFilePaths.toArray());
+            update.push("analyzeFilePathGroups", validFilePaths);
+        }
+        mongoTemplate.upsert(query, update, ChatMessages.class);
+    }
+
+    public List<String> getAnalyzeUserQuestions(Object memoryId) {
+        ChatMessages chatMessages = findChatMessages(memoryId);
+        if (chatMessages == null || CollectionUtils.isEmpty(chatMessages.getAnalyzeUserQuestions())) {
+            return new LinkedList<>();
+        }
+        return new LinkedList<>(chatMessages.getAnalyzeUserQuestions());
+    }
+
+    public List<List<String>> getAnalyzeFilePathGroups(Object memoryId) {
+        ChatMessages chatMessages = findChatMessages(memoryId);
+        if (chatMessages == null) {
+            return new LinkedList<>();
+        }
+        if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePathGroups())) {
+            if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePaths())) {
+                return new LinkedList<>();
+            }
+            List<List<String>> fallback = new LinkedList<>();
+            fallback.add(new LinkedList<>(chatMessages.getAnalyzeFilePaths()));
+            return fallback;
+        }
+        List<List<String>> groups = new LinkedList<>();
+        for (List<String> group : chatMessages.getAnalyzeFilePathGroups()) {
+            if (CollectionUtils.isEmpty(group)) {
+                groups.add(new LinkedList<>());
+            } else {
+                groups.add(new LinkedList<>(group));
+            }
+        }
+        return groups;
+    }
+
     private String memoryIdString(Object memoryId) {
         return memoryId == null ? "" : memoryId.toString();
     }
+
+    private ChatMessages findChatMessages(Object memoryId) {
+        Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
+        return mongoTemplate.findOne(query, ChatMessages.class);
+    }
 }
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 3caf7cf..2c5fda3 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -183,7 +183,7 @@
                             addQualityInspect(purchaseLedger, salesLedgerProduct);
                         } else {
                             //鐩存帴鍏ュ簱
-                            stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId());
+                            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
                         }
                     }
                 } else if (status.equals(3)) {
@@ -211,11 +211,10 @@
             }
             salesQuotationMapper.updateById(salesQuote);
         }
-        // 鍑哄簱瀹℃壒淇敼
+        // 鍑哄簱瀹℃壒淇敼=鍙戣揣瀹℃壒
         if (approveProcess.getApproveType().equals(7)) {
-            String[] split = approveProcess.getApproveReason().split(":");
             ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
-                    .eq(ShippingInfo::getShippingNo, split[1])
+                    .eq(ShippingInfo::getShippingNo, approveProcess.getApproveReason())
                     .orderByDesc(ShippingInfo::getCreateTime)
                     .last("limit 1"));
             if (shippingInfo != null) {
@@ -228,6 +227,7 @@
                 }
                 shippingInfoMapper.updateById(shippingInfo);
             }
+            //搴撳瓨鎵e噺
 
         }
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS());
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 9f505aa..bf2cdb2 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -1,6 +1,7 @@
 package com.ruoyi.approve.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -24,8 +25,10 @@
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.enums.FileNameType;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.utils.OrderUtils;
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.project.system.domain.SysDept;
 import com.ruoyi.project.system.domain.SysNotice;
 import com.ruoyi.project.system.domain.SysUser;
@@ -35,8 +38,10 @@
 import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.sales.mapper.CommonFileMapper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.mapper.ShippingInfoMapper;
 import com.ruoyi.sales.pojo.CommonFile;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.pojo.ShippingInfo;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
 import lombok.RequiredArgsConstructor;
@@ -65,6 +70,8 @@
     private final CommonFileServiceImpl commonFileService;
     private final ISysNoticeService sysNoticeService;
     private final PurchaseLedgerMapper purchaseLedgerMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final StockUtils stockUtils;
     private final ShippingInfoMapper shippingInfoMapper;
     private final ApproveNodeMapper approveNodeMapper;
     private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
@@ -80,22 +87,15 @@
                 .map(ApproveProcessConfigNodeVo::getApproverId)
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
-        if (list.isEmpty()) {
-            throw new RuntimeException("娴佺▼涓嶅瓨鍦�");
-        }
+        // 鏃犲鏍镐汉閫昏緫娣诲姞
         if (CollectionUtils.isEmpty(nodeIds)) {
-            autoPassPurchaseApproveIfNoApprover(approveProcessVO);
+            autoPassPurchaseApproveIfNoApprover(approveProcessVO); // 閲囪喘鍗曟棤瀹℃牳浜洪�昏緫
             return;
         }
         List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
         if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("瀹℃牳鐢ㄦ埛涓嶅瓨鍦�");
         if (sysDept == null) throw new RuntimeException("閮ㄩ棬涓嶅瓨鍦�");
         if (sysUser == null) throw new RuntimeException("鐢宠浜轰笉瀛樺湪");
-//        String today = LocalDate.now().format(DATE_FORMAT);
-//        Long approveId = dailyRedisCounter.incrementAndGetByDb();
-//        String formattedCount = String.format("%03d", approveId);
-//        //娴佺▼ ID
-//        String approveID = today + formattedCount;
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
         ApproveProcess approveProcess = new ApproveProcess();
         String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id");
@@ -159,9 +159,19 @@
                 || !StringUtils.hasText(approveProcessVO.getApproveReason())) {
             throw new RuntimeException("瀹℃牳鐢ㄦ埛涓嶅瓨鍦�");
         }
+
         purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>()
                 .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
                 .set(PurchaseLedger::getApprovalStatus, 3));
+        //閲囪喘鍏ュ簱
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
+                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
+                .last("limit 1"));
+        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
+                .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
+        for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
+            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
+        }
     }
 
     @Override
diff --git a/src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java b/src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
index c8c838a..4e5688f 100644
--- a/src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
+++ b/src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
@@ -15,6 +15,9 @@
 @Data
 public class ProductModelExportDto {
 
+    @Excel(name = "浜у搧缂栫爜")
+    private String productCode;
+
     @Excel(name = "瑙勬牸鍨嬪彿")
     private String model;
 
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 ce0ba64..85b542a 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -121,6 +121,9 @@
                 ProductModel item = productModelList.get(i);
                 int rowNum = i + 2;
 
+                if (StringUtils.isEmpty(item.getProductCode())) {
+                    return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [浜у搧缂栫爜] 涓嶈兘涓虹┖");
+                }
                 if (StringUtils.isEmpty(item.getModel())) {
                     return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [瑙勬牸鍨嬪彿] 涓嶈兘涓虹┖");
                 }
diff --git a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
index 367710b..01c4d7e 100644
--- a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -23,8 +23,9 @@
     SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱"),
     RETURN_HE_IN("14", "閿�鍞��璐�-鍚堟牸鍏ュ簱"),
     RETURN_UNSTOCK_IN("15", "閿�鍞��璐�-涓嶅悎鏍煎叆搴�"),
-    PICK_RETURN_IN("20", "閿�鍞��璐�-鍚堟牸鍏ュ簱"),
-    PURCHASE_RETURN_STOCK_OUT("21", "閲囪喘閫�璐�");
+    PICK_RETURN_IN("20", "棰嗘枡閫�鏂�-鍚堟牸鍏ュ簱"),
+    PURCHASE_RETURN_STOCK_OUT("21", "閲囪喘閫�璐�"),
+    FEED_RETURN_IN("22", "鐢熶骇閫�鏂�-鍚堟牸鍏ュ簱");
 
 
 
diff --git a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
index 5e8fe53..4fe1f6e 100644
--- a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -9,7 +9,9 @@
     PRODUCTION_REPORT_STOCK_OUT("3", "鐢熶骇鎶ュ伐-鍑哄簱"),
     SALE_STOCK_OUT("8", "閿�鍞�-鍑哄簱"),
     PURCHASE_RETURN_STOCK_OUT("9", "閲囪喘閫�璐�"),
-    SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱");
+    SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱"),
+    PICK_STOCK_OUT("14", "鐢熶骇棰嗘枡鍑哄簱"),
+    FEED_STOCK_OUT("15", "鐢熶骇琛ユ枡鍑哄簱");
 
     private final String code;
     private final String value;
diff --git a/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java b/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
index bb2ebb5..d103265 100644
--- a/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
+++ b/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -465,21 +465,37 @@
                     {
                         val = Convert.toBigDecimal(val);
                     }
-                    else if (Date.class == fieldType)
-                    {
-                        if (val instanceof String)
-                        {
-                            val = DateUtils.parseDate(val);
+                    else if (Date.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            val = DateUtils.parseDate(val);
                         }
                         else if (val instanceof Double)
-                        {
-                            val = DateUtil.getJavaDate((Double) val);
-                        }
-                    }
-                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
-                    {
-                        val = Convert.toBool(val, false);
-                    }
+                        {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    }
+                    else if (LocalDate.class == fieldType)
+                    {
+                        if (val instanceof String)
+                        {
+                            Date date = DateUtils.parseDate(val);
+                            val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
+                        }
+                        else if (val instanceof Date)
+                        {
+                            val = DateUtils.toLocalDate((Date) val);
+                        }
+                        else if (val instanceof Double)
+                        {
+                            val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
+                        }
+                    }
+                    else if (Boolean.TYPE == fieldType || Boolean.class == fieldType)
+                    {
+                        val = Convert.toBool(val, false);
+                    }
                     if (StringUtils.isNotNull(fieldType))
                     {
                         String propertyName = field.getName();
@@ -651,15 +667,24 @@
                         val = Convert.toFloat(val);
                     } else if (BigDecimal.class == fieldType) {
                         val = Convert.toBigDecimal(val);
-                    } else if (Date.class == fieldType) {
-                        if (val instanceof String) {
-                            val = DateUtils.parseDate(val);
-                        } else if (val instanceof Double) {
-                            val = DateUtil.getJavaDate((Double) val);
-                        }
-                    } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
-                        val = Convert.toBool(val, false);
-                    }
+                    } else if (Date.class == fieldType) {
+                        if (val instanceof String) {
+                            val = DateUtils.parseDate(val);
+                        } else if (val instanceof Double) {
+                            val = DateUtil.getJavaDate((Double) val);
+                        }
+                    } else if (LocalDate.class == fieldType) {
+                        if (val instanceof String) {
+                            Date date = DateUtils.parseDate(val);
+                            val = StringUtils.isNull(date) ? null : DateUtils.toLocalDate(date);
+                        } else if (val instanceof Date) {
+                            val = DateUtils.toLocalDate((Date) val);
+                        } else if (val instanceof Double) {
+                            val = DateUtils.toLocalDate(DateUtil.getJavaDate((Double) val));
+                        }
+                    } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
+                        val = Convert.toBool(val, false);
+                    }
 
                     if (StringUtils.isNotNull(fieldType)) {
                         String propertyName = field.getName();
diff --git a/src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java b/src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java
index a7d1a37..dc43e78 100644
--- a/src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java
+++ b/src/main/java/com/ruoyi/device/mapper/DeviceLedgerMapper.java
@@ -4,8 +4,8 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.account.dto.DeviceTypeDetail;
-import com.ruoyi.account.dto.DeviceTypeDistributionVO;
+import com.ruoyi.account.bean.dto.DeviceTypeDetail;
+import com.ruoyi.account.bean.dto.DeviceTypeDistributionVO;
 import com.ruoyi.device.dto.DeviceLedgerDto;
 import com.ruoyi.device.execl.DeviceLedgerExeclDto;
 import com.ruoyi.device.pojo.DeviceLedger;
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 897e569..e3ebb0e 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -4,7 +4,6 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.github.xiaoymin.knife4j.core.util.CollectionUtils;
-import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.utils.bean.BeanUtils;
@@ -123,7 +122,9 @@
                 });
             }
             // 澶勭悊鍥剧墖涓婁紶
-            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs());
+            if (deviceRepairDto.getStorageBlobDTOs() != null) {
+                fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs());
+            }
             return AjaxResult.success();
         }
         return AjaxResult.error();
diff --git a/src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java b/src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
index 4392109..ec1693e 100644
--- a/src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
+++ b/src/main/java/com/ruoyi/procurementrecord/mapper/ReturnManagementMapper.java
@@ -3,6 +3,8 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.SalesReturnDto;
+import com.ruoyi.account.bean.vo.SalesReturnVo;
 import com.ruoyi.procurementrecord.dto.ReturnManagementDto;
 import com.ruoyi.procurementrecord.pojo.ReturnManagement;
 import org.apache.ibatis.annotations.Param;
@@ -22,4 +24,6 @@
     IPage<ReturnManagementDto> listPage(Page page, @Param("req") ReturnManagementDto returnManagement);
 
     ReturnManagementDto getReturnManagementDtoById(Long id);
+
+    IPage<SalesReturnVo> listPageBySalesReturn(Page page, @Param("req") SalesReturnDto salesReturnDto);
 }
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
index e9b2615..b22d63d 100644
--- a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -3,7 +3,7 @@
 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.account.dto.SalesRefundAmountOrderDto;
+import com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto;
 import com.ruoyi.account.mapper.AccountExpenseMapper;
 import com.ruoyi.account.pojo.AccountExpense;
 import com.ruoyi.account.service.SalesRefundAmountOrderService;
@@ -30,6 +30,7 @@
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
+import org.springframework.util.ObjectUtils;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
@@ -61,8 +62,10 @@
 
     @Override
     public boolean addReturnManagementDto(ReturnManagementDto returnManagementDto) {
-        String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no");
-        returnManagementDto.setReturnNo(rt);
+        if (ObjectUtils.isEmpty(returnManagementDto.getReturnNo())){
+            String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no");
+            returnManagementDto.setReturnNo(rt);
+        }
         save(returnManagementDto);
         for (ReturnSaleProduct returnSaleProduct : returnManagementDto.getReturnSaleProducts()) {
             returnSaleProduct.setReturnManagementId(returnManagementDto.getId());
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index f561b49..5356500 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -81,6 +81,23 @@
     }
 
     /**
+     * 鍚堟牸鍏ュ簱甯︽壒娆″彿
+     * @param productModelId
+     * @param quantity
+     * @param recordType
+     * @param recordId
+     */
+    public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
+        StockInventoryDto stockInventoryDto = new StockInventoryDto();
+        stockInventoryDto.setRecordId(recordId);
+        stockInventoryDto.setRecordType(String.valueOf(recordType));
+        stockInventoryDto.setQualitity(quantity);
+        stockInventoryDto.setProductModelId(productModelId);
+        stockInventoryDto.setBatchNo(batchNo);
+        stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+    }
+
+    /**
      * 鍚堟牸鍑哄簱
      *
      * @param productModelId
diff --git a/src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java b/src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java
index 91b97ef..5bd2fd1 100644
--- a/src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java
+++ b/src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java
@@ -9,54 +9,54 @@
 import java.time.LocalDate;
 
 @Data
-@Schema(name = "ProductionAccountDto", description = "production account query dto")
+@Schema(name = "ProductionAccountDto", description = "鐢熶骇鏍哥畻鏌ヨ鍙傛暟")
 public class ProductionAccountDto extends ProductionAccount {
 
-    @Schema(description = "sales contract no")
+    @Schema(description = "閿�鍞悎鍚屽彿")
     private String salesContractNo;
 
-    @Schema(description = "customer contract no")
+    @Schema(description = "瀹㈡埛鍚堝悓鍙�")
     private String customerContractNo;
 
-    @Schema(description = "project name")
+    @Schema(description = "椤圭洰鍚嶇О")
     private String projectName;
 
-    @Schema(description = "customer name")
+    @Schema(description = "瀹㈡埛鍚嶇О")
     private String customerName;
 
-    @Schema(description = "product category")
+    @Schema(description = "浜у搧绫诲埆")
     private String productCategory;
 
-    @Schema(description = "specification model")
+    @Schema(description = "瑙勬牸鍨嬪彿")
     private String specificationModel;
 
-    @Schema(description = "scheduling user id")
+    @Schema(description = "鎺掍骇浜哄憳ID")
     private Long schedulingUserId;
 
-    @Schema(description = "scheduling user name")
+    @Schema(description = "鎺掍骇浜哄憳鍚嶇О")
     private String schedulingUserName;
 
-    @Schema(description = "process")
+    @Schema(description = "宸ュ簭")
     private String process;
 
-    @Schema(description = "date type(day/month)")
+    @Schema(description = "鏃ユ湡绫诲瀷锛堟寜澶�/鎸夋湀锛�")
     private String dateType;
 
-    @Schema(description = "day query date")
+    @Schema(description = "鎸夊ぉ鏌ヨ鏃ユ湡")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDate entryDate;
 
-    @Schema(description = "date range")
+    @Schema(description = "鏃ユ湡鑼冨洿")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDate[] dateRange;
 
-    @Schema(description = "start date")
+    @Schema(description = "寮�濮嬫棩鏈�")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDate entryDateStart;
 
-    @Schema(description = "end date")
+    @Schema(description = "缁撴潫鏃ユ湡")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDate entryDateEnd;
diff --git a/src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java b/src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
index 60ab542..c4363a8 100644
--- a/src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
+++ b/src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java
@@ -52,4 +52,7 @@
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDate requiredDateEnd;
 
+    @Schema(description = "閿�鍞悎鍚屽彿")
+    private String salesContractNo;
+
 }
diff --git a/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java b/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
index 9fc8d86..cbac4b5 100644
--- a/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
+++ b/src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java
@@ -12,63 +12,63 @@
 
 @EqualsAndHashCode(callSuper = true)
 @Data
-@Schema(name = "ProductionProductMainDto", description = "production report query dto")
+@Schema(name = "ProductionProductMainDto", description = "鐢熶骇鎶ュ伐鏌ヨ鍙傛暟")
 public class ProductionProductMainDto extends ProductionProductMain {
 
-    @Schema(description = "product process route item id")
+    @Schema(description = "浜у搧宸ヨ壓璺嚎宸ュ簭ID")
     private Long productProcessRouteItemId;
 
-    @Schema(description = "production report id")
+    @Schema(description = "鎶ュ伐ID")
     private Long productMainId;
 
-    @Schema(description = "tenant id")
+    @Schema(description = "绉熸埛ID")
     private Long tenantId;
 
-    @Schema(description = "work order no")
+    @Schema(description = "宸ュ崟缂栧彿")
     private String workOrderNo;
 
-    @Schema(description = "work order status")
+    @Schema(description = "宸ュ崟鐘舵��")
     private String workOrderStatus;
 
-    @Schema(description = "nick name")
+    @Schema(description = "鏄电О")
     private String nickName;
 
-    @Schema(description = "quantity")
+    @Schema(description = "鏁伴噺")
     private BigDecimal quantity;
 
-    @Schema(description = "scrap quantity")
+    @Schema(description = "鎶ュ簾鏁伴噺")
     private BigDecimal scrapQty;
 
-    @Schema(description = "product name")
+    @Schema(description = "浜у搧鍚嶇О")
     private String productName;
 
-    @Schema(description = "product model name")
+    @Schema(description = "浜у搧瑙勬牸鍨嬪彿")
     private String productModelName;
 
-    @Schema(description = "unit")
+    @Schema(description = "鍗曚綅")
     private String unit;
 
-    @Schema(description = "sales contract no")
+    @Schema(description = "閿�鍞悎鍚屽彿")
     private String salesContractNo;
 
-    @Schema(description = "scheduling date")
+    @Schema(description = "鎺掍骇鏃ユ湡")
     private LocalDate schedulingDate;
 
-    @Schema(description = "scheduling user name")
+    @Schema(description = "鎺掍骇浜哄憳鍚嶇О")
     private String schedulingUserName;
 
-    @Schema(description = "customer name")
+    @Schema(description = "瀹㈡埛鍚嶇О")
     private String customerName;
 
-    @Schema(description = "process")
+    @Schema(description = "宸ュ簭")
     private String process;
 
-    @Schema(description = "salary quota")
+    @Schema(description = "宸ヨ祫瀹氶")
     private BigDecimal workHours;
 
-    @Schema(description = "wages")
+    @Schema(description = "宸ヨ祫")
     private BigDecimal wages;
 
-    @Schema(description = "operation param list")
+    @Schema(description = "宸ュ簭鍙傛暟鍒楄〃")
     private List<ProductionOrderRoutingOperationParam> productionOperationParamList;
 }
diff --git a/src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java b/src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java
index 5d86e7c..68bcb23 100644
--- a/src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java
+++ b/src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java
@@ -8,52 +8,55 @@
 import java.time.LocalDate;
 
 @Data
-@Schema(name = "ProductionAccountVo", description = "production account page result")
+@Schema(name = "ProductionAccountVo", description = "鐢熶骇鏍哥畻鍒嗛〉缁撴灉")
 public class ProductionAccountVo {
 
-    @Schema(description = "customer contract no")
+    @Schema(description = "瀹㈡埛鍚堝悓鍙�")
     private String customerContractNo;
 
-    @Schema(description = "project name")
+    @Schema(description = "椤圭洰鍚嶇О")
     private String projectName;
 
-    @Schema(description = "customer name")
+    @Schema(description = "瀹㈡埛鍚嶇О")
     private String customerName;
 
-    @Schema(description = "product category")
+    @Schema(description = "浜у搧绫诲埆")
     private String productCategory;
 
-    @Schema(description = "specification model")
+    @Schema(description = "瑙勬牸鍨嬪彿")
     private String specificationModel;
 
-    @Schema(description = "unit")
+    @Schema(description = "鍗曚綅")
     private String unit;
 
-    @Schema(description = "scheduling user id")
+    @Schema(description = "鎺掍骇浜哄憳ID")
     private Long schedulingUserId;
 
-    @Schema(description = "scheduling user name")
+    @Schema(description = "鎺掍骇浜哄憳鍚嶇О")
     private String schedulingUserName;
 
-    @Schema(description = "wages")
+    @Schema(description = "宸ヨ祫")
     private BigDecimal wages;
 
-    @Schema(description = "finished quantity")
+    @Schema(description = "瀹屾垚鏁伴噺")
     private BigDecimal finishedNum;
 
-    @Schema(description = "salary quota")
+    @Schema(description = "宸ヨ祫瀹氶")
     private BigDecimal workHours;
 
-    @Schema(description = "output rate")
+    @Schema(description = "宸ユ椂")
+    private BigDecimal workHour;
+
+    @Schema(description = "浜у嚭鐜�")
     private String outputRate;
 
-    @Schema(description = "process")
+    @Schema(description = "宸ュ簭")
     private String process;
 
-    @Schema(description = "scheduling date")
+    @Schema(description = "鎺掍骇鏃ユ湡")
     @JsonFormat(pattern = "yyyy-MM-dd")
     private LocalDate schedulingDate;
 
-    @Schema(description = "scheduling month(yyyy-MM)")
+    @Schema(description = "鎺掍骇鏈堜唤(yyyy-MM)")
     private String schedulingMonth;
 }
diff --git a/src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java b/src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java
index 6c7c5a7..9929f19 100644
--- a/src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java
+++ b/src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java
@@ -46,4 +46,7 @@
 
     @Schema(description = "鏄惁缁撴潫锛�")
     private Boolean endOrder;
+
+    @Schema(description = "绫诲瀷 鍖哄垎璁℃椂鍜岃浠�(0璁℃椂1璁′欢)")
+    private Integer type;
 }
diff --git a/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java b/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java
index 1bb26a4..01ef33a 100644
--- a/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java
+++ b/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java
@@ -1,6 +1,7 @@
 package com.ruoyi.production.bean.vo;
 
 import com.ruoyi.basic.dto.StorageBlobVO;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
 import com.ruoyi.production.pojo.ProductionOrder;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -20,12 +21,15 @@
     private String customerName;
 
     @Schema(description = "浜у搧鍚嶇О")
+    @Excel(name = "浜у搧鍚嶇О",sort = 2)
     private String productName;
 
     @Schema(description = "瑙勬牸鍨嬪彿")
+    @Excel(name = "瑙勬牸",sort = 3)
     private String model;
 
     @Schema(description = "宸ヨ壓璺嚎缂栫爜")
+    @Excel(name = "宸ヨ壓璺嚎缂栧彿",sort = 4)
     private String processRouteCode;
 
     @Schema(description = "浜у搧鍥剧墖")
@@ -35,6 +39,7 @@
     private String bomNo;
 
     @Schema(description = "瀹屾垚杩涘害")
+    @Excel(name = "瀹屾垚杩涘害",sort = 7)
     private BigDecimal completionStatus;
 
     @Schema(description = "鏄惁宸查��鏂�")
diff --git a/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java b/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java
index 2a0571d..646d3cb 100644
--- a/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java
+++ b/src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java
@@ -9,6 +9,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 @Data
@@ -42,6 +43,9 @@
         @Schema(description = "鎶ュ伐涓讳俊鎭�")
         private ProductionProductMain reportMain;
 
+        @Schema(description = "宸ユ椂")
+        private BigDecimal workHour;
+
         @Schema(description = "鎶ュ伐浜у嚭鏄庣粏")
         private List<ProductionProductOutput> reportOutputList;
 
@@ -62,6 +66,9 @@
         @Schema(description = "鎶ュ伐涓讳俊鎭�")
         private ProductionProductMain reportMain;
 
+        @Schema(description = "宸ユ椂")
+        private BigDecimal workHour;
+
         @Schema(description = "璐ㄦ涓讳俊鎭�")
         private QualityInspect inspect;
 
diff --git a/src/main/java/com/ruoyi/production/controller/ProductionOrderController.java b/src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
index b5f7b97..0a68875 100644
--- a/src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
+++ b/src/main/java/com/ruoyi/production/controller/ProductionOrderController.java
@@ -1,7 +1,11 @@
 package com.ruoyi.production.controller;
 
+import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.production.bean.dto.ProductionOrderDto;
 import com.ruoyi.production.bean.vo.ProductionOrderPickVo;
@@ -10,13 +14,17 @@
 import com.ruoyi.production.bean.vo.ProductionOrderWorkOrderDetailVo;
 import com.ruoyi.production.pojo.ProductionOrder;
 import com.ruoyi.production.service.ProductionOrderService;
+import com.ruoyi.sales.dto.SalesLedgerDto;
+import com.ruoyi.sales.vo.SalesLedgerVo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Content;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @RestController
@@ -95,4 +103,14 @@
     public R updateOrder(@RequestBody ProductionOrderDto productionOrderDto) {
         return R.ok(productionOrderService.updateOrder(productionOrderDto));
     }
+
+
+    @Log(title = "鐢熶骇璁㈠崟瀵煎嚭", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ProductionOrderDto dto) {
+        IPage<ProductionOrderVo> productionOrderVoIPage = productionOrderService.pageProductionOrder(new Page<>(-1, -1), dto);
+        List<ProductionOrderVo> records = productionOrderVoIPage.getRecords();
+        ExcelUtil<ProductionOrderVo> util = new ExcelUtil<>(ProductionOrderVo.class);
+        util.exportExcel(response, records, "鐢熶骇璁㈠崟鏁版嵁");
+    }
 }
diff --git a/src/main/java/com/ruoyi/production/controller/ProductionPlanController.java b/src/main/java/com/ruoyi/production/controller/ProductionPlanController.java
index 6190a47..923f95c 100644
--- a/src/main/java/com/ruoyi/production/controller/ProductionPlanController.java
+++ b/src/main/java/com/ruoyi/production/controller/ProductionPlanController.java
@@ -14,6 +14,7 @@
 import io.swagger.v3.oas.annotations.Operation;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -85,10 +86,10 @@
         excelUtil.importTemplateExcel(response, "涓荤敓浜ц鍒掑鍏ユā鏉�");
     }
 
-    @PostMapping("/import")
+    @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
     @Operation(summary = "涓荤敓浜ц鍒掓暟鎹鍏�")
     @Log(title = "涓荤敓浜ц鍒掓暟鎹鍏�", businessType = BusinessType.IMPORT)
-    public R importProdData(@RequestParam("file") MultipartFile file) {
+    public R importProdData(@RequestPart("file") MultipartFile file) {
         productionPlanService.importProdData(file);
         return R.ok("瀵煎叆鎴愬姛");
     }
@@ -96,8 +97,8 @@
     @PostMapping("/export")
     @Operation(summary = "涓荤敓浜ц鍒掓暟鎹鍑�")
     @Log(title = "涓荤敓浜ц鍒掓暟鎹鍑�", businessType = BusinessType.EXPORT)
-    public void exportProdData(HttpServletResponse response, @RequestBody(required = false) List<Long> ids) {
-        productionPlanService.exportProdData(response, ids);
+    public void exportProdData(HttpServletResponse response, @RequestBody(required = false) ProductionPlanDto requestDto) {
+        productionPlanService.exportProdData(response, requestDto);
     }
 
 }
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java b/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
index 1f882b1..e52ee9d 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.annotation.*;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -36,6 +37,7 @@
     private Long productModelId;
 
     @Schema(description = "鐢熶骇璁㈠崟鍙�")
+    @Excel(name = "鐢熶骇璁㈠崟",sort = 0)
     private String npsNo;
 
     @Schema(description = "褰曞叆鏃堕棿")
@@ -50,15 +52,19 @@
     private Long technologyRoutingId;
 
     @Schema(description = "闇�姹傛暟閲忋�傛墜鍔ㄦ柊澧炴椂蹇呭~涓斿繀椤诲ぇ浜� 0锛涘鏋滀紶浜� productionPlanIds锛屽垯鍙敱绯荤粺鑷姩甯﹀嚭銆�")
+    @Excel(name = "闇�姹傛暟閲�",sort = 5)
     private BigDecimal quantity;
 
     @Schema(description = "瀹屾垚鏁伴噺")
+    @Excel(name = "瀹屾垚鏁伴噺",sort = 6)
     private BigDecimal completeQuantity;
 
     @Schema(description = "寮�濮嬫棩鏈�")
+    @Excel(name = "寮�濮嬫棩鏈�",sort = 8,dateFormat = "yyyy-MM-dd")
     private LocalDateTime startTime;
 
     @Schema(description = "缁撴潫鏃ユ湡")
+    @Excel(name = "缁撴潫鏃ユ湡",sort = 9,dateFormat = "yyyy-MM-dd")
     private LocalDateTime endTime;
 
     @Schema(description = "鍒涘缓浜篒D")
@@ -72,9 +78,11 @@
     @Schema(description = "璁″垝瀹屾垚鏃堕棿")
     @JsonFormat(pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "璁″垝瀹屾垚鏃堕棿",sort = 10,dateFormat = "yyyy-MM-dd")
     private LocalDate planCompleteTime;
 
     @Schema(description = "鐘舵�侊紙1.寰呭紑濮� 2.杩涜涓� 3.宸插畬鎴� 4.宸插彇娑� 5.宸茬粨鏉燂級")
+    @Excel(name = "鐘舵��",sort = 1,readConverterExp = "1=寰呭紑濮�,2=杩涜涓�,3=宸插畬鎴�,4=宸插彇娑�,5=宸茬粨鏉�")
     private Integer status;
 
     @Schema(description = "鏄惁缁撴潫锛�")
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java b/src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java
index fc36725..d784109 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java
@@ -67,4 +67,7 @@
 
     @Schema(description = "宸ュ簭琛╥d")
     private Long technologyOperationId;
+
+    @Schema(description = "绫诲瀷 鍖哄垎璁℃椂鍜岃浠讹紝0璁℃椂锛�1璁′欢")
+    private Integer type;
 }
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java b/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
index 0fa00eb..9de89fb 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java
@@ -7,6 +7,7 @@
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Data
@@ -56,4 +57,7 @@
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
 
+    @Schema(description = "宸ユ椂")
+    private BigDecimal workHour;
+
 }
diff --git a/src/main/java/com/ruoyi/production/service/ProductionPlanService.java b/src/main/java/com/ruoyi/production/service/ProductionPlanService.java
index 052676b..715e221 100644
--- a/src/main/java/com/ruoyi/production/service/ProductionPlanService.java
+++ b/src/main/java/com/ruoyi/production/service/ProductionPlanService.java
@@ -51,6 +51,6 @@
     /**
      * 瀵煎嚭鏁版嵁
      */
-    void exportProdData(HttpServletResponse response, List<Long> ids);
+    void exportProdData(HttpServletResponse response, ProductionPlanDto requestDto);
 
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java
index 9c1843c..c35c757 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java
@@ -24,16 +24,19 @@
 
     @Override
     public IPage<ProductionAccountVo> listPage(Page<ProductionAccountDto> page, ProductionAccountDto dto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇鏍哥畻鏁版嵁
         ProductionAccountDto queryDto = normalizeDateQuery(dto);
         return baseMapper.listPage(page, queryDto);
     }
 
     @Override
     public IPage<ProductionProductMainDto> listProductionDetails(ProductionAccountDto dto, Page page) {
+        // 鏌ヨ鐢熶骇鏍哥畻鏄庣粏
         return productionProductMainMapper.listProductionDetails(normalizeDateQuery(dto), page);
     }
 
     private ProductionAccountDto normalizeDateQuery(ProductionAccountDto dto) {
+        // 瑙勮寖鏃ユ湡鏌ヨ鑼冨洿锛岃ˉ榻愮己澶辩殑寮�濮嬫垨缁撴潫鏃堕棿
         if (dto == null) {
             return new ProductionAccountDto();
         }
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 cbae0dc..35cdb27 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
@@ -33,6 +33,7 @@
      */
     @Override
     public List<ProductionBomStructureVo> listByBomId(Long bomId) {
+        // 鎸塀OMID鏌ヨ鐢熶骇缁撴瀯鏁版嵁
         List<ProductionBomStructureVo> list = productionBomStructureMapper.listByBomId(bomId);
         Map<Long, ProductionBomStructureVo> map = new HashMap<>();
         for (ProductionBomStructureVo node : list) {
@@ -58,13 +59,17 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean addProductionBomStructure(ProductionBomStructureDto dto) {
+        // 鏂板鐢熶骇BOM缁撴瀯
+        // 璇诲彇褰撳墠璁㈠崟BOM涓婚敭锛屽苟鎶婂墠绔爲缁撴瀯鎷嶅钩鎴愬垪琛�
         Long orderBomId = dto.getProductionOrderBomId();
         List<ProductionBomStructureDto> flatDtoList = new ArrayList<>();
         flattenTree(dto.getChildren(), flatDtoList);
 
+        // 鏌ヨ鏁版嵁搴撳凡鏈夌粨鏋勶紝鐢ㄤ簬鍚庣画鍋氬鍒犳敼瀵规瘮
         List<ProductionBomStructure> dbList = this.list(new LambdaQueryWrapper<ProductionBomStructure>()
                 .eq(ProductionBomStructure::getProductionOrderBomId, orderBomId));
 
+        // 鏀堕泦鍓嶇浠嶇劧瀛樺湪鐨勮妭鐐笽D
         Set<Long> frontendIds = new HashSet<>();
         for (ProductionBomStructureDto item : flatDtoList) {
             if (item.getId() != null) {
@@ -72,16 +77,19 @@
             }
         }
 
+        // 璁$畻闇�瑕佸垹闄ょ殑鑺傜偣锛堟暟鎹簱鏈夈�佸墠绔凡鍒犻櫎锛�
         Set<Long> deleteIds = new HashSet<>();
         for (ProductionBomStructure dbItem : dbList) {
             if (!frontendIds.contains(dbItem.getId())) {
                 deleteIds.add(dbItem.getId());
             }
         }
+        // 鍏堝垹鎺夊墠绔凡缁忕Щ闄ょ殑鑺傜偣
         if (!deleteIds.isEmpty()) {
             this.removeByIds(deleteIds);
         }
 
+        // 鎸夋槸鍚︽湁ID鎷嗗垎涓烘柊澧炲拰鏇存柊锛屽悓鏃剁紦瀛樻柊澧炶妭鐐圭殑涓存椂ID鏄犲皠
         List<ProductionBomStructure> insertList = new ArrayList<>();
         List<ProductionBomStructure> updateList = new ArrayList<>();
         Map<String, ProductionBomStructure> tempEntityMap = new HashMap<>();
@@ -99,10 +107,12 @@
             }
         }
 
+        // 鎵归噺鏂板锛屾嬁鍒版暟鎹簱鐢熸垚鐨勭湡瀹濱D
         if (!insertList.isEmpty()) {
             this.saveBatch(insertList);
         }
 
+        // 鏂板鑺傜偣浜屾鍥炲啓鐖禝D锛堝墠绔紶鐨勬槸涓存椂鐖禝D锛�
         List<ProductionBomStructure> parentFixList = new ArrayList<>();
         for (ProductionBomStructureDto item : flatDtoList) {
             if (item.getId() == null && item.getParentTempId() != null) {
@@ -111,15 +121,18 @@
                     continue;
                 }
                 ProductionBomStructure parent = tempEntityMap.get(item.getParentTempId());
+                // 鐖惰妭鐐规槸鏈鏂板鏃讹紝鐩存帴鐢ㄦ柊澧炲悗鐨勭湡瀹濱D锛涘惁鍒欏洖閫�涓哄墠绔紶鍏ョ埗ID
                 Long realParentId = parent != null ? parent.getId() : Long.valueOf(item.getParentTempId());
                 child.setParentId(realParentId);
                 parentFixList.add(child);
             }
         }
 
+        // 鍥炲啓鏂板鑺傜偣鐨勭埗瀛愬叧绯�
         if (!parentFixList.isEmpty()) {
             this.updateBatchById(parentFixList);
         }
+        // 鎵归噺鏇存柊宸叉湁鑺傜偣
         if (!updateList.isEmpty()) {
             this.updateBatchById(updateList);
         }
@@ -130,6 +143,7 @@
      * 灏嗘爲褰㈢粨鏋勬媿骞虫垚鍒楄〃锛屼究浜庣粺涓�淇濆瓨銆�
      */
     private void flattenTree(List<ProductionBomStructureDto> source, List<ProductionBomStructureDto> result) {
+        // 鎵佸钩鍖栧鐞嗘爲
         if (source == null) {
             return;
         }
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 383193b..6e9457d 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java
@@ -58,6 +58,7 @@
 
     @Override
     public IPage<ProductionOperationTaskVo> pageProductionOperationTask(Page<ProductionOperationTaskDto> page, ProductionOperationTaskDto dto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇宸ュ簭浠诲姟
         Page<ProductionOperationTaskVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
         IPage<ProductionOperationTaskVo> result = baseMapper.pageProductionOperationTask(voPage, dto);
         fillUserNames(result.getRecords());
@@ -66,6 +67,7 @@
 
     @Override
     public List<ProductionOperationTaskVo> listProductionOperationTask(ProductionOperationTaskDto dto) {
+        // 鏌ヨ宸ュ簭浠诲姟鍒楄〃
         List<ProductionOperationTaskVo> result = BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOperationTaskVo.class);
         fillUserNames(result);
         return result;
@@ -73,6 +75,7 @@
 
     @Override
     public ProductionOperationTaskVo getProductionOperationTaskInfo(Long id) {
+        // 鑾峰彇鐢熶骇宸ュ簭浠诲姟璇︽儏
         ProductionOperationTask item = this.getById(id);
         if (item == null) {
             return null;
@@ -90,21 +93,25 @@
 
     @Override
     public boolean saveProductionOperationTask(ProductionOperationTask productionOperationTask) {
+        // 淇濆瓨鐢熶骇宸ュ簭浠诲姟
         return this.saveOrUpdate(productionOperationTask);
     }
 
     @Override
     public boolean removeProductionOperationTask(List<Long> ids) {
+        // 鍒犻櫎鐢熶骇宸ュ簭浠诲姟
         return ids != null && !ids.isEmpty() && this.removeByIds(ids);
     }
 
     @Override
     public int updateProductWorkOrder(ProductionOperationTaskDto dto) {
+        // 鏇存柊宸ュ簭浠诲姟瀵瑰簲鐨勫伐鍗曚俊鎭�
         return baseMapper.updateById(dto);
     }
 
     @Override
     public boolean assign(ProductionOperationTaskDto dto) {
+        // 鍒嗛厤宸ュ簭浠诲姟鎵ц浜�
         if (dto == null || dto.getId() == null) {
             throw new ServiceException("宸ュ崟ID涓嶈兘涓虹┖");
         }
@@ -120,6 +127,7 @@
     }
 
     private LambdaQueryWrapper<ProductionOperationTask> buildQueryWrapper(ProductionOperationTaskDto dto) {
+        // 鎸夋潯浠跺姩鎬佹瀯寤烘暟鎹簱鏌ヨ鏉′欢
         ProductionOperationTask query = dto == null ? new ProductionOperationTask() : dto;
         return Wrappers.<ProductionOperationTask>lambdaQuery()
                 .eq(query.getId() != null, ProductionOperationTask::getId, query.getId())
@@ -133,10 +141,12 @@
     }
 
     private void fillUserNames(List<ProductionOperationTaskVo> voList) {
+        // 濉厖鐢ㄦ埛鍚嶇О
         if (voList == null || voList.isEmpty()) {
             return;
         }
         Set<Long> userIdSet = new LinkedHashSet<>();
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         for (ProductionOperationTaskVo vo : voList) {
             if (vo == null) {
                 continue;
@@ -172,6 +182,7 @@
     }
 
     private List<Long> parseUserIdList(String userIds, boolean strict) {
+        // 瑙f瀽骞舵牎楠岀敤鎴稩D鏁扮粍瀛楃涓�
         if (StringUtils.isBlank(userIds)) {
             if (strict) {
                 throw new ServiceException("userIds鏍煎紡涓嶆纭紝蹇呴』涓篔SON鏁板瓧鏁扮粍");
@@ -199,6 +210,7 @@
 
     @Override
     public void down(HttpServletResponse response, ProductionOperationTaskDto dto) {
+        // 瀵煎嚭宸ュ簭浠诲姟鏁版嵁
         if (dto == null || dto.getId() == null) {
             throw new ServiceException("宸ュ崟ID涓嶈兘涓虹┖");
         }
@@ -250,15 +262,18 @@
     }
 
     private List<Map<String, Object>> buildTaskAttachmentImages(Long taskId) {
+        // 缁勮浠诲姟闄勪欢鍥剧墖鏁版嵁鐢ㄤ簬瀵煎嚭
         List<Map<String, Object>> images = new ArrayList<>();
         StorageAttachmentDTO storageAttachmentDTO = new StorageAttachmentDTO();
         storageAttachmentDTO.setRecordType(RecordTypeEnum.PRODUCTION_OPERATION_TASK.getType());
         storageAttachmentDTO.setRecordId(taskId);
         List<StorageBlobVO> taskWorkOrderFiles =
                 fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(storageAttachmentDTO);
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (CollectionUtils.isEmpty(taskWorkOrderFiles)) {
             return images;
         }
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         for (StorageBlobVO blobVO : taskWorkOrderFiles) {
             if (blobVO == null) {
                 continue;
@@ -286,6 +301,7 @@
     }
 
     private File resolveImageFile(StorageBlobVO blobVO) {
+        // 灏嗛檮浠朵俊鎭В鏋愪负鏈湴鍥剧墖鏂囦欢瀵硅薄
         if (blobVO == null || StringUtils.isBlank(blobVO.getUidFilename())) {
             return null;
         }
@@ -296,6 +312,7 @@
     }
 
     private PictureType resolvePictureType(StorageBlobVO blobVO) {
+        // 鎸夋枃浠跺悕鎴栧唴瀹圭被鍨嬭瘑鍒浘鐗囨牸寮�
         if (blobVO == null) {
             return null;
         }
@@ -311,6 +328,7 @@
     }
 
     private PictureType parsePictureTypeByFileName(String fileName) {
+        // 鏍规嵁鏂囦欢鍚庣紑瑙f瀽鍥剧墖鏍煎紡
         if (StringUtils.isBlank(fileName) || !fileName.contains(".")) {
             return null;
         }
@@ -322,6 +340,7 @@
     }
 
     private PictureType parsePictureTypeByContentType(String contentType) {
+        // 鏍规嵁Content-Type瑙f瀽鍥剧墖鏍煎紡
         if (StringUtils.isBlank(contentType)) {
             return null;
         }
@@ -350,6 +369,7 @@
 
     @Override
     public List<ProductionOperationTaskVo> getOperation(ProductionOperationTaskDto dto) {
+        // 鏌ヨ宸ュ簭浠诲姟鍒楄〃
         return baseMapper.getOperation(dto);
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java
index 1cf7c1e..4b71eb3 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java
@@ -24,6 +24,7 @@
 
     @Override
     public List<ProductionOrderPickRecordVo> listFeedingRecord(ProductionOrderPickRecordDto dto) {
+        // 鏌ヨ鎶曟枡璁板綍
         if (dto == null || dto.getProductionOrderId() == null || dto.getPickId() == null) {
             return Collections.emptyList();
         }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
index f45c117..554b95e 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -3,7 +3,9 @@
 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.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.production.bean.dto.ProductionOrderPickDto;
@@ -18,8 +20,12 @@
 import com.ruoyi.production.service.ProductionOrderPickService;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.mapper.StockInventoryMapper;
+import com.ruoyi.stock.pojo.StockInRecord;
 import com.ruoyi.stock.pojo.StockInventory;
+import com.ruoyi.stock.pojo.StockOutRecord;
 import com.ruoyi.stock.service.StockInventoryService;
+import com.ruoyi.stock.service.StockInRecordService;
+import com.ruoyi.stock.service.StockOutRecordService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -30,12 +36,8 @@
 import java.util.stream.Collectors;
 
 /**
- * <p>
- * 鐠併垹宕熸0鍡樻灐缁捐儻绔熸禒?閺堝秴濮熺�圭偟骞囩猾?
- * </p>
- *
- * @author 閼侯垰顕辨潪顖欐閿涘牊鐫欓懟蹇ョ礆閺堝妾洪崗顒�寰�
- * @since 2026-04-21 03:55:52
+ * 鐢熶骇璁㈠崟棰嗘枡鏈嶅姟瀹炵幇銆�
+ * 璐熻矗棰嗘枡鏂板銆佹洿鏂般�佽ˉ鏂欍�侀��鏂欏強搴撳瓨鑱斿姩銆�
  */
 @Service
 @RequiredArgsConstructor
@@ -43,27 +45,42 @@
 
     private static final byte PICK_TYPE_NORMAL = 1;
     private static final byte PICK_TYPE_FEEDING = 2;
+    private static final String PICK_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.PICK_STOCK_OUT.getCode();
+    private static final String FEED_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.FEED_STOCK_OUT.getCode();
+    private static final String PICK_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.PICK_RETURN_IN.getCode();
+    private static final String FEED_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.FEED_RETURN_IN.getCode();
 
     private final ProductionOrderMapper productionOrderMapper;
     private final ProductionOperationTaskMapper productionOperationTaskMapper;
     private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
     private final StockInventoryMapper stockInventoryMapper;
     private final StockInventoryService stockInventoryService;
+    private final StockInRecordService stockInRecordService;
+    private final StockOutRecordService stockOutRecordService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean savePick(ProductionOrderPickDto dto) {
+        // 棰嗘枡鏂板鎬绘祦绋嬶細
+        // 1) 瑙f瀽鍓嶇琛屾暟鎹苟閫愯鍚堝苟鍙傛暟锛�
+        // 2) 鏍¢獙鍙傛暟涓庢壒娆★紱
+        // 3) 鍏堜繚瀛橀鏂欎富璁板綍锛�
+        // 4) 鍐嶈蛋鈥滃嚭搴撶敵璇� + 瀹℃壒閫氳繃鈥濆畬鎴愬簱瀛樻墸鍑忥紱
+        // 5) 鍐欏叆棰嗘枡娴佹按锛岃褰曟暟閲忓彉鍖栬建杩广��
         List<ProductionOrderPickDto> pickItems = resolvePickItems(dto);
+        // 閫愯澶勭悊棰嗘枡鏁版嵁锛岃鍙风敤浜庢嫾瑁呯簿纭殑鎶ラ敊淇℃伅銆�
         for (int i = 0; i < pickItems.size(); i++) {
             int rowNo = i + 1;
             ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i));
+            // 姣忚閮藉仛瀹屾暣鏍¢獙锛屽紓甯镐俊鎭甫琛屽彿銆�
             validatePickParam(resolvedDto, rowNo);
 
+            // 缁熶竴澶勭悊鎵规锛堟敮鎸佸崟鎵规/澶氭壒娆★級銆�
             List<String> batchNoList = resolveBatchNoList(resolvedDto);
             String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
             String storedBatchNo = formatBatchNoStorage(batchNoList);
-            subtractInventory(resolvedDto.getProductModelId(), storedBatchNo, resolvedDto.getPickQuantity(), rowNo);
 
+            // 淇濆瓨棰嗘枡涓昏褰曞揩鐓с��
             ProductionOrderPick orderPick = new ProductionOrderPick();
             orderPick.setProductionOrderId(resolvedDto.getProductionOrderId());
             orderPick.setProductModelId(resolvedDto.getProductModelId());
@@ -75,8 +92,13 @@
             orderPick.setDemandedQuantity(resolvedDto.getDemandedQuantity());
             orderPick.setBom(resolvedDto.getBom());
             orderPick.setReturned(false);
+            // 鏂板涓昏褰曘��
             baseMapper.insert(orderPick);
 
+            // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+            subtractInventory(orderPick.getId(), resolvedDto.getProductModelId(), storedBatchNo, resolvedDto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+
+            // 璁板綍鏈棰嗘枡娴佹按锛坆efore=0锛宎fter=鏈棰嗘枡閲忥級銆�
             insertPickRecord(orderPick.getId(),
                     resolvedDto.getProductionOrderId(),
                     resolvedDto.getProductionOperationTaskId(),
@@ -95,8 +117,12 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean updatePick(ProductionOrderPickDto dto) {
+        // 棰嗘枡鏇存柊鍏ュ彛锛堝悓鎺ュ彛鍏煎涓夌被涓氬姟锛夛細
+        // 1) 鏅�氶鏂欐敼閲�/澧炲垹锛�
+        // 2) 琛ユ枡锛坧ickType=2锛夛紱
+        // 3) 閫�鏂欙紙returned=true锛夈��
         if (dto == null) {
-            throw new ServiceException("鍙樻洿鍙傛暟涓嶈兘涓虹┖");
+            throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
         }
         Long productionOrderId = resolveProductionOrderId(dto);
         if (productionOrderId == null) {
@@ -107,26 +133,32 @@
             throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
         }
 
+        // 鏌ヨ璁㈠崟涓嬬幇鏈夐鏂欒褰曞苟鏋勫缓ID绱㈠紩銆�
         List<ProductionOrderPick> existingPickList = baseMapper.selectList(
                 Wrappers.<ProductionOrderPick>lambdaQuery()
                         .eq(ProductionOrderPick::getProductionOrderId, productionOrderId));
+        // 杞垚Map渚夸簬鍚庣画鎸塈D蹇�熸牎楠屼笌鏇存柊銆�
         Map<Long, ProductionOrderPick> existingPickMap = existingPickList.stream()
                 .filter(item -> item.getId() != null)
                 .collect(Collectors.toMap(ProductionOrderPick::getId, Function.identity(), (a, b) -> a));
 
+        // 琛ユ枡璇锋眰鍗曠嫭璧拌ˉ鏂欏垎鏀��
         if (isFeedingRequest(dto)) {
             processFeedingPickItems(dto, existingPickMap, productionOrderId);
             return true;
         }
+        // 閫�鏂欒姹傚崟鐙蛋閫�鏂欏垎鏀��
         if (isReturnRequest(dto)) {
             processReturnPickItems(dto, existingPickMap, productionOrderId);
             return true;
         }
 
+        // 鏅�氭洿鏂板満鏅厛澶勭悊鏄惧紡鍒犻櫎銆�
         processDeletePickIds(dto, existingPickMap, productionOrderId);
 
         List<ProductionOrderPickDto> pickItems = resolveUpdateItems(dto);
         Set<Long> keepPickIdSet = new HashSet<>();
+        // keepPickIdSet 鐢ㄤ簬鏍囪鏈鍓嶇浠嶇劧淇濈暀鐨勬棫璁板綍锛屽悗缁敤浜庤瘑鍒�滄湭鍥炰紶鍗冲垹闄も�濈殑琛屻��
         for (int i = 0; i < pickItems.size(); i++) {
             int rowNo = i + 1;
             ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i));
@@ -145,12 +177,14 @@
             keepPickIdSet.add(resolvedDto.getId());
             updateExistingPick(resolvedDto, rowNo, existingPickMap);
         }
+        // 娓呯悊鍓嶇鏈洖浼犳棫琛屽苟鍥炶ˉ搴撳瓨銆�
         processMissingPickItems(dto, existingPickMap, productionOrderId, keepPickIdSet);
         return true;
     }
 
     @Override
     public List<ProductionOrderPickVo> listPickedDetail(Long productionOrderId) {
+        // 鏌ヨ璁㈠崟棰嗘枡鏄庣粏锛屽苟琛ラ綈鎵规灞曠ず瀛楁銆�
         if (productionOrderId == null) {
             return Collections.emptyList();
         }
@@ -163,6 +197,11 @@
     private void processDeletePickIds(ProductionOrderPickDto rootDto,
                                       Map<Long, ProductionOrderPick> existingPickMap,
                                       Long productionOrderId) {
+        // 澶勭悊鍓嶇鏄惧紡鍒犻櫎ID锛�
+        // 1) 鏍¢獙鍒犻櫎鐩爣鏄惁灞炰簬褰撳墠璁㈠崟锛�
+        // 2) 鍥炶ˉ搴撳瓨锛�
+        // 3) 鍒犻櫎涓昏褰曪紱
+        // 4) 璁板綍鍒犻櫎娴佹按銆�
         if (rootDto.getDeletePickIds() == null || rootDto.getDeletePickIds().isEmpty()) {
             return;
         }
@@ -173,14 +212,19 @@
             }
             ProductionOrderPick existingPick = existingPickMap.get(deleteId);
             if (existingPick == null || !Objects.equals(existingPick.getProductionOrderId(), productionOrderId)) {
-                throw new ServiceException("瑕佸垹闄ょ殑棰嗘枡璁板綍涓嶅瓨鍦ㄦ垨涓嶅睘浜庡綋鍓嶈鍗曪紝ID=" + deleteId);
+                throw new ServiceException("鍒犻櫎澶辫触锛氶鏂欒褰曚笉瀛樺湪鎴栦笉灞炰簬褰撳墠璁㈠崟锛孖D=" + deleteId);
             }
             String oldBatchNo = resolveInventoryBatchNoFromStored(existingPick.getBatchNo());
             BigDecimal oldQuantity = defaultDecimal(existingPick.getQuantity());
-            addInventory(existingPick.getProductModelId(), oldBatchNo, oldQuantity);
+            addInventory(existingPick.getId(), existingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+            productionOrderPickRecordMapper.delete(
+                    Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                            .eq(ProductionOrderPickRecord::getPickId, existingPick.getId())
+            );
             int affected = baseMapper.deleteById(deleteId);
             if (affected <= 0) {
-                throw new ServiceException("鍒犻櫎棰嗘枡澶辫触锛孖D=" + deleteId);
+                throw new ServiceException("鍒犻櫎棰嗘枡璁板綍澶辫触锛孖D=" + deleteId);
             }
             insertPickRecord(existingPick.getId(),
                     existingPick.getProductionOrderId(),
@@ -201,6 +245,9 @@
                                          Map<Long, ProductionOrderPick> existingPickMap,
                                          Long productionOrderId,
                                          Set<Long> keepPickIdSet) {
+        // 澶勭悊鈥滃墠绔湭鍥炰紶鈥濈殑鏃ц锛�
+        // 瀵瑰簲鍦烘櫙鏄敤鎴峰湪鍓嶇鍒犻櫎琛屼絾鏈斁鍏� deletePickIds銆�
+        // 杩欓噷鍏滃簳璇嗗埆骞舵墽琛屽洖琛ュ簱瀛� + 鍒犻櫎涓昏褰� + 鍐欐祦姘淬��
         if (rootDto.getPickList() == null) {
             return;
         }
@@ -213,10 +260,15 @@
         for (ProductionOrderPick missingPick : missingPickList) {
             String oldBatchNo = resolveInventoryBatchNoFromStored(missingPick.getBatchNo());
             BigDecimal oldQuantity = defaultDecimal(missingPick.getQuantity());
-            addInventory(missingPick.getProductModelId(), oldBatchNo, oldQuantity);
+            addInventory(missingPick.getId(), missingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+            productionOrderPickRecordMapper.delete(
+                    Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                            .eq(ProductionOrderPickRecord::getPickId, missingPick.getId())
+            );
             int affected = baseMapper.deleteById(missingPick.getId());
             if (affected <= 0) {
-                throw new ServiceException("鍒犻櫎棰嗘枡澶辫触锛孖D=" + missingPick.getId());
+                throw new ServiceException("鍒犻櫎鏈洖浼犻鏂欒褰曞け璐ワ紝ID=" + missingPick.getId());
             }
             insertPickRecord(missingPick.getId(),
                     missingPick.getProductionOrderId(),
@@ -234,10 +286,11 @@
     }
 
     private void addNewPickInUpdate(ProductionOrderPickDto dto, int rowNo) {
+        // 鏇存柊鍦烘櫙涓嬫柊澧炰竴鏉¢鏂欙細
+        // 鏂板涓昏褰� -> 鍑哄簱鐢宠骞跺鎵� -> 鍐欐祦姘淬��
         List<String> batchNoList = resolveBatchNoList(dto);
         String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
         String storedBatchNo = formatBatchNoStorage(batchNoList);
-        subtractInventory(dto.getProductModelId(), storedBatchNo, dto.getPickQuantity(), rowNo);
 
         ProductionOrderPick orderPick = new ProductionOrderPick();
         orderPick.setProductionOrderId(dto.getProductionOrderId());
@@ -251,6 +304,9 @@
         orderPick.setBom(dto.getBom());
         orderPick.setReturned(false);
         baseMapper.insert(orderPick);
+
+        // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+        subtractInventory(orderPick.getId(), dto.getProductModelId(), storedBatchNo, dto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
 
         insertPickRecord(orderPick.getId(),
                 dto.getProductionOrderId(),
@@ -268,6 +324,8 @@
     private void processFeedingPickItems(ProductionOrderPickDto rootDto,
                                          Map<Long, ProductionOrderPick> existingPickMap,
                                          Long productionOrderId) {
+        // 琛ユ枡娴佺▼鍏ュ彛锛�
+        // 閫愯鏍¢獙琛ユ枡鍙傛暟锛屾牎楠屽師棰嗘枡褰掑睘锛屽啀鎵ц琛ユ枡搴撳瓨鎵e噺鍜屼富璁板綍鍥炲啓銆�
         List<ProductionOrderPickDto> pickItems = resolveUpdateItems(rootDto);
         for (int i = 0; i < pickItems.size(); i++) {
             int rowNo = i + 1;
@@ -276,7 +334,7 @@
                 continue;
             }
             if (!isFeedingPick(resolvedDto)) {
-                throw new ServiceException("琛ユ枡璇锋眰涓殑棰嗘枡绫诲瀷蹇呴』鍏ㄩ儴涓�2");
+                throw new ServiceException("琛ユ枡璇锋眰涓瓨鍦ㄩ潪琛ユ枡绫诲瀷鏁版嵁");
             }
             if (resolvedDto.getProductionOrderId() == null) {
                 resolvedDto.setProductionOrderId(productionOrderId);
@@ -285,15 +343,20 @@
 
             ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId());
             if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) {
-                throw new ServiceException("绗�" + rowNo + "鏉¢鏂欒褰曚笉瀛樺湪鎴栦笉灞炰簬褰撳墠璁㈠崟");
+                throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
             }
             addFeedingPick(resolvedDto, oldPick, rowNo);
         }
     }
 
     private void addFeedingPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) {
+        // 琛ユ枡鏍稿績锛�
+        // 1) 鏍¢獙瑙勬牸涓�鑷达紱
+        // 2) 鎵e噺琛ユ枡搴撳瓨锛�
+        // 3) 鍐欒ˉ鏂欐祦姘达紱
+        // 4) 鍥炲啓涓诲崟绱琛ユ枡閲忓拰瀹為檯閲忋��
         if (dto.getProductModelId() != null && !Objects.equals(dto.getProductModelId(), oldPick.getProductModelId())) {
-            throw new ServiceException("绗�" + rowNo + "鏉¤ˉ鏂欎骇鍝佽鏍间笌棰嗘枡璁板綍涓嶄竴鑷�");
+            throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細浜у搧瑙勬牸涓庡師棰嗘枡璁板綍涓嶄竴鑷�");
         }
         Long productModelId = oldPick.getProductModelId();
         List<String> batchNoList = resolveBatchNoList(dto);
@@ -302,8 +365,9 @@
                 : formatBatchNoStorage(batchNoList);
         BigDecimal feedingQuantity = dto.getFeedingQuantity();
 
-        subtractInventory(productModelId, inventoryBatchNo, feedingQuantity, rowNo);
+        subtractInventory(oldPick.getId(), productModelId, inventoryBatchNo, feedingQuantity, rowNo, FEED_STOCK_OUT_RECORD_TYPE);
 
+        // 璁$畻琛ユ枡鍓嶅悗鏁伴噺骞跺啓琛ユ枡娴佹按銆�
         BigDecimal beforeFeedingQty = sumFeedingQuantity(dto.getProductionOrderId(), oldPick.getId());
         BigDecimal afterFeedingQty = beforeFeedingQty.add(feedingQuantity);
         insertPickRecord(oldPick.getId(),
@@ -322,9 +386,10 @@
         updatePick.setId(oldPick.getId());
         updatePick.setFeedingQty(afterFeedingQty);
         updatePick.setActualQty(calculateActualQty(oldPick, afterFeedingQty));
+        // 鍥炲啓涓昏褰曠殑琛ユ枡绱鍊间笌瀹為檯鐢ㄩ噺銆�
         int affected = baseMapper.updateById(updatePick);
         if (affected <= 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¤ˉ鏂欐�婚噺鏇存柊澶辫触");
+            throw new ServiceException("绗�" + rowNo + "琛岃ˉ鏂欏け璐ワ細鏇存柊棰嗘枡涓昏褰曞け璐�");
         }
         oldPick.setFeedingQty(afterFeedingQty);
         oldPick.setActualQty(updatePick.getActualQty());
@@ -333,6 +398,8 @@
     private void processReturnPickItems(ProductionOrderPickDto rootDto,
                                         Map<Long, ProductionOrderPick> existingPickMap,
                                         Long productionOrderId) {
+        // 閫�鏂欐祦绋嬪叆鍙o細
+        // 閫愯鏍¢獙閫�鏂欏弬鏁颁笌棰嗘枡褰掑睘锛屽啀鏇存柊閫�鏂欓噺涓庡疄闄呴噺瀛楁銆�
         List<ProductionOrderPickDto> pickItems = resolveUpdateItems(rootDto);
         for (int i = 0; i < pickItems.size(); i++) {
             int rowNo = i + 1;
@@ -347,33 +414,56 @@
 
             ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId());
             if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) {
-                throw new ServiceException("绗�" + rowNo + "鏉¢鏂欒褰曚笉瀛樺湪鎴栦笉灞炰簬褰撳墠璁㈠崟");
+                throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
             }
             updateReturnPick(resolvedDto, oldPick, rowNo);
         }
     }
 
     private void updateReturnPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) {
+        // 閫�鏂欐洿鏂帮細
+        // 1) returnQty 鎸夆�滄湰娆¢��鏂欓噺鈥濆鐞嗭紱
+        // 2) 鏈閫�鏂欓噺鍥炶ˉ鍒扳�滅敓浜ч��鏂欏叆搴撯�濓紱
+        // 3) 绱姞涓昏褰曢��鏂欐�婚噺骞堕噸绠楀疄闄呴噺銆�
+        BigDecimal oldReturnQty = defaultDecimal(oldPick.getReturnQty());
+        BigDecimal currentReturnQty = defaultDecimal(dto.getReturnQty());
+        BigDecimal totalReturnQty = oldReturnQty.add(currentReturnQty);
+        if (currentReturnQty.compareTo(BigDecimal.ZERO) > 0) {
+            String returnBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo());
+            addInventoryRecordOnly(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE);
+        }
+
+        BigDecimal actualQty = defaultDecimal(oldPick.getQuantity())
+                .add(defaultDecimal(oldPick.getFeedingQty()))
+                .subtract(totalReturnQty);
+        if (actualQty.compareTo(BigDecimal.ZERO) < 0) {
+            throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細绱閫�鏂欐暟閲忎笉鑳藉ぇ浜庡彲鐢ㄦ暟閲�");
+        }
         ProductionOrderPick updatePick = new ProductionOrderPick();
         updatePick.setId(oldPick.getId());
-        updatePick.setReturnQty(dto.getReturnQty());
-        updatePick.setActualQty(dto.getActualQty());
-        updatePick.setReturned(true);
+        updatePick.setReturnQty(totalReturnQty);
+        updatePick.setActualQty(actualQty);
+        updatePick.setReturned(totalReturnQty.compareTo(BigDecimal.ZERO) > 0);
         int affected = baseMapper.updateById(updatePick);
         if (affected <= 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢��鏂欎俊鎭洿鏂板け璐�");
+            throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細鏇存柊棰嗘枡涓昏褰曞け璐�");
         }
         oldPick.setReturnQty(updatePick.getReturnQty());
         oldPick.setActualQty(updatePick.getActualQty());
-        oldPick.setReturned(true);
+        oldPick.setReturned(updatePick.getReturned());
     }
 
     private void updateExistingPick(ProductionOrderPickDto dto,
                                     int rowNo,
                                     Map<Long, ProductionOrderPick> existingPickMap) {
+        // 鏅�氭洿鏂板崟琛屾牳蹇冩祦绋嬶細
+        // 1) 鏍¢獙鏃ц褰曞瓨鍦ㄤ笖灞炰簬褰撳墠璁㈠崟锛�
+        // 2) 姣旇緝鏂版棫鈥滆鏍�+鎵规鈥濓紝鍐冲畾搴撳瓨澶勭悊绛栫暐锛�
+        // 3) 鏇存柊涓昏褰曪紱
+        // 4) 鍐欏彉鏇存祦姘达紙璁板綍鍓嶅悗鏁伴噺鍙樺寲锛夈��
         ProductionOrderPick oldPick = existingPickMap.get(dto.getId());
         if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), dto.getProductionOrderId())) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂欒褰曚笉瀛樺湪鎴栦笉灞炰簬褰撳墠璁㈠崟");
+            throw new ServiceException("绗�" + rowNo + "琛屾洿鏂板け璐ワ細鏈壘鍒板搴旂殑棰嗘枡璁板綍");
         }
 
         Long oldProductModelId = oldPick.getProductModelId();
@@ -386,18 +476,30 @@
         String newStoredBatchNo = formatBatchNoStorage(newBatchNoList);
         BigDecimal newQuantity = dto.getPickQuantity();
 
+        // 鍒ゆ柇瑙勬牸+鎵规鎴栨暟閲忔槸鍚﹀彉鍖栵紝骞舵寜鍦烘櫙澶勭悊搴撳瓨锛�
+        // 1) 鍚岃鏍煎悓鎵规锛氭寜宸�煎鐞嗭紙澧為噺鎵e噺 / 鍑忛噺鍥為��锛夛紱
+        // 2) 瑙勬牸鎴栨壒娆″彉鍖栵細鍥為��鏃ч鏂欏悗鍐嶉噸鎻愭柊棰嗘枡銆�
         boolean sameStockKey = Objects.equals(oldProductModelId, newProductModelId)
                 && Objects.equals(oldBatchNo, newBatchNo);
+        boolean quantityChanged = oldQuantity.compareTo(newQuantity) != 0;
+        boolean needReissuePickRecord = !sameStockKey || quantityChanged;
         if (sameStockKey) {
-            BigDecimal delta = newQuantity.subtract(oldQuantity);
-            if (delta.compareTo(BigDecimal.ZERO) > 0) {
-                subtractInventory(newProductModelId, newStoredBatchNo, delta, rowNo);
-            } else if (delta.compareTo(BigDecimal.ZERO) < 0) {
-                addInventory(oldProductModelId, oldBatchNo, delta.abs());
+            BigDecimal deltaQuantity = newQuantity.subtract(oldQuantity);
+            if (deltaQuantity.compareTo(BigDecimal.ZERO) > 0) {
+                // 鏁伴噺澧炲姞锛屽彧鎵e噺鏂板閮ㄥ垎銆�
+                subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, deltaQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+            } else if (deltaQuantity.compareTo(BigDecimal.ZERO) < 0) {
+                // 鏁伴噺鍑忓皯锛屽彧鍥為��宸�奸儴鍒嗐��
+                addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, deltaQuantity.abs(), PICK_RETURN_IN_RECORD_TYPE);
             }
         } else {
-            addInventory(oldProductModelId, oldBatchNo, oldQuantity);
-            subtractInventory(newProductModelId, newStoredBatchNo, newQuantity, rowNo);
+            // 瑙勬牸鎴栨壒娆″彉鍖栵細鍏堝叏閲忓洖閫�鏃ч鏂欙紝鍐嶅叏閲忔墸鍑忔柊棰嗘枡銆�
+            addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, newQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+        }
+        if (needReissuePickRecord) {
+            // 姝e父棰嗘枡娴佹按鎸夆�滄渶鏂伴鏂欓噺鈥濋噸寤猴紝閬垮厤淇濈暀鍘嗗彶鏃у�笺��
+            deleteNormalPickRecord(oldPick.getId());
         }
 
         oldPick.setProductModelId(newProductModelId);
@@ -406,6 +508,9 @@
         oldPick.setRemark(dto.getRemark());
         oldPick.setOperationName(dto.getOperationName());
         oldPick.setTechnologyOperationId(dto.getTechnologyOperationId());
+        // 鏅�氭洿鏂颁篃瑕佸悓姝ラ噸绠楀疄闄呯敤閲忥紝閬垮厤娌跨敤鏃у�笺��
+        // 瑙勫垯锛氬疄闄呯敤閲� = 棰嗘枡鏁伴噺 + 琛ユ枡鏁伴噺 - 閫�鏂欐暟閲忋��
+        oldPick.setActualQty(calculateActualQty(oldPick, oldPick.getFeedingQty()));
         if (dto.getDemandedQuantity() != null) {
             oldPick.setDemandedQuantity(dto.getDemandedQuantity());
         }
@@ -414,18 +519,18 @@
         }
         int affected = baseMapper.updateById(oldPick);
         if (affected <= 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂欐洿鏂板け璐�");
+            throw new ServiceException("绗�" + rowNo + "琛屾洿鏂板け璐ワ細鏇存柊棰嗘枡璁板綍澶辫触");
         }
 
-        BigDecimal recordQuantity = sameStockKey ? oldQuantity.subtract(newQuantity).abs() : newQuantity;
-        if (recordQuantity.compareTo(BigDecimal.ZERO) > 0 || oldQuantity.compareTo(newQuantity) != 0 || !sameStockKey) {
+        // 濡傛灉鍙戠敓棰嗘枡閲嶆彁锛岃ˉ鍐欎竴鏉℃柊鐨勬甯搁鏂欐祦姘淬��
+        if (needReissuePickRecord) {
             insertPickRecord(oldPick.getId(),
                     dto.getProductionOrderId(),
                     dto.getProductionOperationTaskId(),
                     newProductModelId,
                     newBatchNo,
-                    recordQuantity,
-                    oldQuantity,
+                    newQuantity,
+                    BigDecimal.ZERO,
                     newQuantity,
                     dto.getPickType(),
                     dto.getRemark(),
@@ -444,6 +549,7 @@
                                   Byte pickType,
                                   String remark,
                                   String feedingReason) {
+        // 鍐欓鏂欐祦姘磋褰曪細缁熶竴璁板綍棰嗘枡/琛ユ枡/閫�鏂欐暟閲忓彉鍖栬建杩广��
         ProductionOrderPickRecord pickRecord = new ProductionOrderPickRecord();
         pickRecord.setPickId(pickId);
         pickRecord.setProductionOrderId(productionOrderId);
@@ -459,8 +565,31 @@
         productionOrderPickRecordMapper.insert(pickRecord);
     }
 
-    private void subtractInventory(Long productModelId, String batchNo, BigDecimal quantity, int rowNo) {
+    private void deleteNormalPickRecord(Long pickId) {
+        // 鍒犻櫎璇ラ鏂欏崟鍘嗗彶涓婄殑鈥滄甯搁鏂欌�濇祦姘达紝淇濈暀琛ユ枡/閫�鏂欐祦姘淬��
+        if (pickId == null) {
+            return;
+        }
+        productionOrderPickRecordMapper.delete(
+                Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                        .eq(ProductionOrderPickRecord::getPickId, pickId)
+                        .eq(ProductionOrderPickRecord::getPickType, PICK_TYPE_NORMAL)
+        );
+    }
+
+    private void subtractInventory(Long recordId,
+                                   Long productModelId,
+                                   String batchNo,
+                                   BigDecimal quantity,
+                                   int rowNo,
+                                   String stockOutRecordType) {
+        // 鎵e噺搴撳瓨鎬绘祦绋嬶細
+        // 1) 瑙f瀽鎵规鍒楄〃锛�
+        // 2) 璁$畻姣忎釜鎵规鍙敤閲忎笌鎬诲彲鐢ㄩ噺锛�
+        // 3) 鎸夋壒娆¢『搴忛�愮瑪鈥滄柊澧炲嚭搴撹褰曞苟瀹℃壒閫氳繃鈥濓紝鐩村埌鎵e畬鐩爣鏁伴噺锛�
+        // 4) 浠讳竴姝ュけ璐ュ嵆鎶涢敊骞跺洖婊氫簨鍔°��
         BigDecimal deductQuantity = defaultDecimal(quantity);
+        // 棰嗘枡鏁伴噺灏忎簬绛変簬0鏃讹紝涓嶉渶瑕佹墽琛屽簱瀛樻墸鍑忋��
         if (deductQuantity.compareTo(BigDecimal.ZERO) <= 0) {
             return;
         }
@@ -470,9 +599,12 @@
             batchNoList = Collections.singletonList(null);
         }
 
+        // 鍏堣绠楀悇鎵规鍙敤閲忥紝閬垮厤杈规墸杈圭畻瀵艰嚧鍒ゆ柇涓嶄竴鑷淬��
         Map<String, BigDecimal> availableQuantityMap = new LinkedHashMap<>();
         BigDecimal totalAvailableQuantity = BigDecimal.ZERO;
+        // 閬嶅巻鎵规锛岃绠楁瘡涓壒娆″彲鐢ㄥ簱瀛樸��
         for (String currentBatchNo : batchNoList) {
+            // 鏌ヨ褰撳墠瑙勬牸+鎵规鐨勫簱瀛樿褰曘��
             StockInventory stockInventory = stockInventoryMapper.selectOne(buildStockWrapper(productModelId, currentBatchNo));
             BigDecimal availableQuantity = BigDecimal.ZERO;
             if (stockInventory != null) {
@@ -488,10 +620,11 @@
 
         if (deductQuantity.compareTo(totalAvailableQuantity) > 0) {
             BigDecimal shortQuantity = deductQuantity.subtract(totalAvailableQuantity);
-            throw new ServiceException("棰嗘枡鍙敤搴撳瓨涓嶈冻锛屽彲鐢ㄥ簱瀛樹负" + formatQuantity(totalAvailableQuantity)
-                    + "锛岃繕宸�" + formatQuantity(shortQuantity));
+            throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細鍙敤搴撳瓨涓嶈冻锛屽綋鍓嶅彲鐢�"
+                    + formatQuantity(totalAvailableQuantity) + "锛屼粛缂哄皯" + formatQuantity(shortQuantity));
         }
 
+        // 鎸夋壒娆¢『搴忛�愮瑪鎵e噺搴撳瓨銆�
         BigDecimal remainingQuantity = deductQuantity;
         for (Map.Entry<String, BigDecimal> entry : availableQuantityMap.entrySet()) {
             if (remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) {
@@ -502,39 +635,137 @@
                 continue;
             }
             BigDecimal currentDeductQuantity = remainingQuantity.min(availableQuantity);
-            StockInventoryDto stockInventoryDto = new StockInventoryDto();
-            stockInventoryDto.setProductModelId(productModelId);
-            stockInventoryDto.setBatchNo(entry.getKey());
-            stockInventoryDto.setQualitity(currentDeductQuantity);
-            int affected = stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
-            if (affected <= 0) {
-                throw new ServiceException("绗�" + rowNo + "鏉¢鏂欐墸鍑忓簱瀛樺け璐�");
-            }
+            createAndApproveStockOutRecord(recordId, productModelId, entry.getKey(), currentDeductQuantity, rowNo, stockOutRecordType);
             remainingQuantity = remainingQuantity.subtract(currentDeductQuantity);
         }
 
         if (remainingQuantity.compareTo(BigDecimal.ZERO) > 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂欐墸鍑忓簱瀛樺け璐ワ紝鍓╀綑寰呮墸鍑忔暟閲忎负" + formatQuantity(remainingQuantity));
+            throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細浠嶆湁鏈墸鍑忔暟閲�" + formatQuantity(remainingQuantity));
         }
     }
 
-    private void addInventory(Long productModelId, String batchNo, BigDecimal quantity) {
+    private void createAndApproveStockOutRecord(Long recordId,
+                                                Long productModelId,
+                                                String batchNo,
+                                                BigDecimal quantity,
+                                                int rowNo,
+                                                String stockOutRecordType) {
+        // 搴撳瓨鎵e噺鏀逛负涓ゆ锛�
+        // 1) 鍏堣皟鐢� addStockOutRecordOnly 鏂板寰呭鎵瑰嚭搴撹褰曪紱
+        // 2) 鍐嶈皟鐢ㄥ嚭搴撳鎵癸紝瀹℃壒鐘舵�佸浐瀹氫紶 1锛堥�氳繃锛夈��
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryDto.setRecordType(stockOutRecordType);
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(quantity);
+            stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
+
+            LambdaQueryWrapper<StockOutRecord> recordWrapper = Wrappers.<StockOutRecord>lambdaQuery()
+                    .eq(StockOutRecord::getRecordId, stockInventoryDto.getRecordId())
+                    .eq(StockOutRecord::getRecordType, stockOutRecordType)
+                    .eq(StockOutRecord::getProductModelId, productModelId)
+                    .eq(StockOutRecord::getType, "0")
+                    .orderByDesc(StockOutRecord::getId)
+                    .last("limit 1");
+            if (StringUtils.isEmpty(batchNo)) {
+                recordWrapper.isNull(StockOutRecord::getBatchNo);
+            } else {
+                recordWrapper.eq(StockOutRecord::getBatchNo, batchNo);
+            }
+            StockOutRecord stockOutRecord = stockOutRecordService.getOne(recordWrapper, false);
+            if (stockOutRecord == null || stockOutRecord.getId() == null) {
+                throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細鏈壘鍒板搴斿嚭搴撶敵璇疯褰�");
+            }
+            stockOutRecordService.batchApprove(
+                    Collections.singletonList(stockOutRecord.getId()),
+                    ReviewStatusEnum.APPROVED.getCode()
+            );
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細" + ex.getMessage());
+        }
+    }
+
+    private void addInventory(Long recordId,
+                              Long productModelId,
+                              String batchNo,
+                              BigDecimal quantity,
+                              String stockInRecordType) {
+        // 鍥炶ˉ搴撳瓨鏀逛负涓ゆ锛�
+        // 1) 鍏堟柊澧炲叆搴撶敵璇凤紱
+        // 2) 鍐嶅鎵归�氳繃锛岀‘淇濆簱瀛樼珛鍒诲洖琛ョ敓鏁堛��
         BigDecimal addQuantity = defaultDecimal(quantity);
         if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
             return;
         }
-        StockInventoryDto stockInventoryDto = new StockInventoryDto();
-        stockInventoryDto.setProductModelId(productModelId);
-        stockInventoryDto.setBatchNo(batchNo);
-        stockInventoryDto.setQualitity(addQuantity);
-        stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.PICK_RETURN_IN.getCode()));
-        stockInventoryDto.setRecordId(0L);
-        stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(addQuantity);
+            stockInventoryDto.setRecordType(stockInRecordType);
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+
+            LambdaQueryWrapper<StockInRecord> recordWrapper = Wrappers.<StockInRecord>lambdaQuery()
+                    .eq(StockInRecord::getRecordId, stockInventoryDto.getRecordId())
+                    .eq(StockInRecord::getRecordType, stockInventoryDto.getRecordType())
+                    .eq(StockInRecord::getProductModelId, productModelId)
+                    .eq(StockInRecord::getType, "0")
+                    .orderByDesc(StockInRecord::getId)
+                    .last("limit 1");
+            if (StringUtils.isEmpty(batchNo)) {
+                recordWrapper.isNull(StockInRecord::getBatchNo);
+            } else {
+                recordWrapper.eq(StockInRecord::getBatchNo, batchNo);
+            }
+            StockInRecord stockInRecord = stockInRecordService.getOne(recordWrapper, false);
+            if (stockInRecord == null || stockInRecord.getId() == null) {
+                throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛氭湭鎵惧埌瀵瑰簲鍏ュ簱鐢宠璁板綍");
+            }
+            stockInRecordService.batchApprove(
+                    Collections.singletonList(stockInRecord.getId()),
+                    ReviewStatusEnum.APPROVED.getCode()
+            );
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛�" + ex.getMessage());
+        }
+    }
+
+    private void addInventoryRecordOnly(Long recordId,
+                                        Long productModelId,
+                                        String batchNo,
+                                        BigDecimal quantity,
+                                        String stockInRecordType) {
+        // 浠呰褰曞叆搴撶敵璇凤紝涓嶅仛瀹℃牳閫氳繃銆�
+        BigDecimal addQuantity = defaultDecimal(quantity);
+        if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(addQuantity);
+            stockInventoryDto.setRecordType(stockInRecordType);
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("閫�鏂欏叆搴撹褰曚繚瀛樺け璐ワ細" + ex.getMessage());
+        }
     }
 
     private List<ProductionOrderPickDto> resolvePickItems(ProductionOrderPickDto dto) {
+        // 瑙f瀽鏂板鍦烘櫙鐨勯鏂欐槑缁嗛泦鍚堛��
         if (dto == null) {
-            throw new ServiceException("棰嗘枡鍙傛暟涓嶈兘涓虹┖");
+            throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
         }
         if (dto.getPickList() != null && !dto.getPickList().isEmpty()) {
             return dto.getPickList();
@@ -543,6 +774,7 @@
     }
 
     private List<ProductionOrderPickDto> resolveUpdateItems(ProductionOrderPickDto dto) {
+        // 瑙f瀽鏇存柊鍦烘櫙鐨勯鏂欐槑缁嗛泦鍚堛��
         if (dto.getPickList() != null) {
             return dto.getPickList();
         }
@@ -553,6 +785,7 @@
     }
 
     private boolean isEmptyUpdateItem(ProductionOrderPickDto dto) {
+        // 鍒ゆ柇鏇存柊琛屾槸鍚︿负绌虹櫧鍗犱綅琛屻��
         return dto.getId() == null
                 && dto.getProductModelId() == null
                 && dto.getPickQuantity() == null
@@ -573,6 +806,7 @@
     }
 
     private Long resolveProductionOrderId(ProductionOrderPickDto dto) {
+        // 浼樺厛浠庝富DTO瑙f瀽璁㈠崟ID锛屼笉瀛樺湪鏃跺啀浠庡瓙椤逛腑鍥為��鏌ユ壘銆�
         if (dto.getProductionOrderId() != null) {
             return dto.getProductionOrderId();
         }
@@ -588,7 +822,12 @@
     }
 
     private ProductionOrderPickDto mergeDto(ProductionOrderPickDto rootDto, ProductionOrderPickDto itemDto) {
+        // 鍚堝苟瑙勫垯锛�
+        // - itemDto 浼樺厛鎵胯浇琛岀骇杈撳叆锛�
+        // - itemDto 缂哄け瀛楁浠� rootDto 鍏滃簳缁ф壙锛�
+        // - 杈撳嚭 merged 浣滀负缁熶竴涓氬姟鍏ュ弬銆�
         ProductionOrderPickDto merged = new ProductionOrderPickDto();
+        // 鍏堟嫹璐濊绾у瓧娈点��
         if (itemDto != null) {
             merged.setId(itemDto.getId());
             merged.setProductionOrderId(itemDto.getProductionOrderId());
@@ -667,51 +906,52 @@
     }
 
     private void validatePickParam(ProductionOrderPickDto dto, int rowNo) {
+        // 鏍¢獙鏅�氶鏂欏弬鏁帮紙璁㈠崟銆佽鏍笺�佹暟閲忋�佺被鍨嬶級銆�
         if (dto.getProductionOrderId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉$敓浜ц鍗旾D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
         }
         if (dto.getProductModelId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉′骇鍝佽鏍糏D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細浜у搧瑙勬牸涓嶈兘涓虹┖");
         }
         if (dto.getPickQuantity() == null || dto.getPickQuantity().compareTo(BigDecimal.ZERO) < 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂欐暟閲忎笉鑳藉皬浜�0");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡鏁伴噺涓嶈兘灏忎簬0");
         }
         if (dto.getPickType() != null && dto.getPickType() != PICK_TYPE_NORMAL && dto.getPickType() != PICK_TYPE_FEEDING) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂欑被鍨嬪彧鑳芥槸1鎴�2");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡绫诲瀷浠呮敮鎸�1(棰嗘枡)鎴�2(琛ユ枡)");
         }
     }
 
     private void validateFeedingParam(ProductionOrderPickDto dto, int rowNo) {
+        // 鏍¢獙琛ユ枡鍙傛暟锛堣鍗曘�侀鏂橧D銆佽ˉ鏂欐暟閲忋�佺被鍨嬶級銆�
         if (dto.getProductionOrderId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉$敓浜ц鍗旾D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
         }
         if (dto.getId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂橧D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡璁板綍ID涓嶈兘涓虹┖");
         }
         if (dto.getFeedingQuantity() == null || dto.getFeedingQuantity().compareTo(BigDecimal.ZERO) < 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉℃湰娆¤ˉ鏂欐暟閲忎笉鑳藉皬浜�0");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細琛ユ枡鏁伴噺涓嶈兘灏忎簬0");
         }
         if (!isFeedingPick(dto)) {
-            throw new ServiceException("绗�" + rowNo + "鏉¤ˉ鏂欑被鍨嬪繀椤讳负2");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細琛ユ枡鍦烘櫙涓嬮鏂欑被鍨嬪繀椤讳负2");
         }
     }
 
     private void validateReturnParam(ProductionOrderPickDto dto, int rowNo) {
+        // 鏍¢獙閫�鏂欏弬鏁帮紙璁㈠崟銆侀鏂橧D銆侀��鏂欓噺锛夈��
         if (dto.getProductionOrderId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉$敓浜ц鍗旾D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
         }
         if (dto.getId() == null) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢鏂橧D涓嶈兘涓虹┖");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡璁板綍ID涓嶈兘涓虹┖");
         }
         if (dto.getReturnQty() == null || dto.getReturnQty().compareTo(BigDecimal.ZERO) < 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉¢��鏂欐暟閲忎笉鑳戒负绌轰笖涓嶈兘灏忎簬0");
-        }
-        if (dto.getActualQty() == null || dto.getActualQty().compareTo(BigDecimal.ZERO) < 0) {
-            throw new ServiceException("绗�" + rowNo + "鏉″疄闄呮暟閲忎笉鑳戒负绌轰笖涓嶈兘灏忎簬0");
+            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細閫�鏂欐暟閲忎笉鑳藉皬浜�0");
         }
     }
 
     private boolean isFeedingRequest(ProductionOrderPickDto dto) {
+        // 鍒ゆ柇褰撳墠璇锋眰鏄惁灞炰簬琛ユ枡娴佺▼銆�
         if (isFeedingPick(dto)) {
             return true;
         }
@@ -724,10 +964,12 @@
     }
 
     private boolean isFeedingPick(ProductionOrderPickDto dto) {
+        // 鍒ゆ柇褰撳墠琛屾槸鍚︿负琛ユ枡绫诲瀷銆�
         return dto != null && Objects.equals(dto.getPickType(), PICK_TYPE_FEEDING);
     }
 
     private boolean isReturnRequest(ProductionOrderPickDto dto) {
+        // 鍒ゆ柇褰撳墠璇锋眰鏄惁灞炰簬閫�鏂欐祦绋嬨��
         if (isReturnPick(dto)) {
             return true;
         }
@@ -740,10 +982,12 @@
     }
 
     private boolean isReturnPick(ProductionOrderPickDto dto) {
+        // 鍒ゆ柇褰撳墠琛屾槸鍚︿负閫�鏂欑被鍨嬨��
         return dto != null && Boolean.TRUE.equals(dto.getReturned());
     }
 
     private BigDecimal sumFeedingQuantity(Long productionOrderId, Long pickId) {
+        // 姹囨�绘寚瀹氶鏂欏崟鐨勫巻鍙茶ˉ鏂欐�婚噺銆�
         List<ProductionOrderPickRecord> feedingRecords = productionOrderPickRecordMapper.selectList(
                 Wrappers.<ProductionOrderPickRecord>lambdaQuery()
                         .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)
@@ -756,12 +1000,14 @@
     }
 
     private BigDecimal calculateActualQty(ProductionOrderPick pick, BigDecimal feedingQty) {
+        // 鎸夆�滈鏂�+琛ユ枡-閫�鏂欌�濊绠楀疄闄呯敤閲忋��
         return defaultDecimal(pick.getQuantity())
                 .add(defaultDecimal(feedingQty))
                 .subtract(defaultDecimal(pick.getReturnQty()));
     }
 
     private String normalizeBatchNo(String batchNo) {
+        // 鏍囧噯鍖栨壒娆″彿锛堝幓绌虹櫧銆佺┖涓茶浆null锛夈��
         if (StringUtils.isEmpty(batchNo)) {
             return null;
         }
@@ -769,6 +1015,7 @@
         return trimBatchNo.isEmpty() ? null : trimBatchNo;
     }
     private List<String> resolveBatchNoList(ProductionOrderPickDto dto) {
+        // 浼樺厛瑙f瀽 batchNoList锛岀┖鍒欏洖閫�瑙f瀽 batchNo 瀛楃涓层��
         List<String> normalizedBatchNoList = normalizeBatchNoList(dto.getBatchNoList());
         if (!normalizedBatchNoList.isEmpty()) {
             return normalizedBatchNoList;
@@ -777,6 +1024,7 @@
     }
 
     private String pickInventoryBatchNo(List<String> batchNoList) {
+        // 浠庢壒娆¢泦鍚堜腑鍙栧簱瀛樻墸鍑忎娇鐢ㄧ殑鎵规銆�
         if (batchNoList == null || batchNoList.isEmpty()) {
             return null;
         }
@@ -784,10 +1032,12 @@
     }
 
     private String resolveInventoryBatchNoFromStored(String storedBatchNo) {
+        // 浠庢暟鎹簱瀛樺偍鎵规瀛楁涓弽瑙e彲鐢ㄦ壒娆°��
         return pickInventoryBatchNo(parseBatchNoValue(storedBatchNo));
     }
 
     private String formatBatchNoStorage(List<String> batchNoList) {
+        // 灏嗘壒娆¢泦鍚堟牸寮忓寲涓烘暟鎹簱瀛樺偍鍊笺��
         if (batchNoList == null || batchNoList.isEmpty()) {
             return null;
         }
@@ -798,6 +1048,7 @@
     }
 
     private List<String> normalizeBatchNoList(List<String> batchNoList) {
+        // 鎵归噺鏍囧噯鍖栨壒娆″彿骞跺幓閲嶃��
         if (batchNoList == null || batchNoList.isEmpty()) {
             return Collections.emptyList();
         }
@@ -812,6 +1063,7 @@
     }
 
     private void fillBatchNoList(List<ProductionOrderPickVo> detailList) {
+        // 灏嗗悓璁㈠崟+鍚岃鏍�+鍚屽伐搴忕殑鏁版嵁鎸夌粍鑱氬悎鎵规锛屼究浜庡墠绔粺涓�灞曠ず銆�
         if (detailList == null || detailList.isEmpty()) {
             return;
         }
@@ -832,9 +1084,11 @@
     }
 
     private void fillSelectableBatchNoList(List<ProductionOrderPickVo> detailList) {
+        // 鍚堝苟鈥滃凡閫夋壒娆♀�濆拰鈥滃簱瀛樺彲閫夋壒娆♀�濓紝鐢ㄤ簬鍓嶇涓嬫媺銆�
         if (detailList == null || detailList.isEmpty()) {
             return;
         }
+        // 鍏堟敹闆嗘槑缁嗕腑娑夊強鐨勮鏍糏D锛屾壒閲忔煡璇㈠簱瀛樻壒娆°��
         Set<Long> productModelIdSet = detailList.stream()
                 .map(ProductionOrderPickVo::getProductModelId)
                 .filter(Objects::nonNull)
@@ -868,30 +1122,35 @@
     }
 
     private String buildBatchNoGroupKey(ProductionOrderPickVo detail) {
-        return String.valueOf(detail.getProductionOrderId()) + "|"
-                + String.valueOf(detail.getProductModelId()) + "|"
-                + String.valueOf(detail.getTechnologyOperationId()) + "|"
-                + String.valueOf(detail.getOperationName());
+        // 鏋勫缓鎵规鑱氬悎鍒嗙粍閿��
+        return detail.getProductionOrderId() + "|"
+                + detail.getProductModelId() + "|"
+                + detail.getTechnologyOperationId() + "|"
+                + detail.getOperationName();
     }
 
     private List<String> parseBatchNoValue(String rawBatchNoValue) {
+        // 鎵规瑙f瀽鍏煎涓夌鏍煎紡锛�
+        // 1) 鍗曞�硷細A001
+        // 2) 閫楀彿鍒嗛殧锛欰001,A002
+        // 3) 绫籎SON鏁扮粍瀛楃涓诧細["A001","A002"]
         String normalizedValue = normalizeBatchNo(rawBatchNoValue);
         if (StringUtils.isEmpty(normalizedValue)) {
             return Collections.emptyList();
         }
-        if (normalizedValue.startsWith("[") && normalizedValue.endsWith("]")) {
+        if (normalizedValue != null && normalizedValue.startsWith("[") && normalizedValue.endsWith("]")) {
             String value = normalizedValue.substring(1, normalizedValue.length() - 1);
             if (StringUtils.isEmpty(value)) {
                 return Collections.emptyList();
             }
             List<String> parsed = Arrays.stream(value.split(","))
-                    .map(item -> item == null ? null : item.trim().replace("\"", "").replace("'", ""))
+                    .map(item -> item.trim().replace("\"", "").replace("'", ""))
                     .collect(Collectors.toList());
             return normalizeBatchNoList(parsed);
         }
-        if (normalizedValue.contains(",")) {
+        if (normalizedValue != null && normalizedValue.contains(",")) {
             List<String> parsed = Arrays.stream(normalizedValue.split(","))
-                    .map(item -> item == null ? null : item.trim())
+                    .map(item -> item.trim())
                     .collect(Collectors.toList());
             return normalizeBatchNoList(parsed);
         }
@@ -899,6 +1158,7 @@
     }
 
     private LambdaQueryWrapper<StockInventory> buildStockWrapper(Long productModelId, String batchNo) {
+        // 鏋勫缓搴撳瓨鏌ヨ鏉′欢锛堣鏍� + 鎵规锛夈��
         LambdaQueryWrapper<StockInventory> wrapper = Wrappers.<StockInventory>lambdaQuery()
                 .eq(StockInventory::getProductModelId, productModelId);
         if (StringUtils.isEmpty(batchNo)) {
@@ -910,10 +1170,12 @@
     }
 
     private BigDecimal defaultDecimal(BigDecimal value) {
+        // BigDecimal 绌哄�煎厹搴曪紝缁熶竴鎸�0澶勭悊銆�
         return value == null ? BigDecimal.ZERO : value;
     }
 
     private String formatQuantity(BigDecimal value) {
+        // 鏁伴噺鏍煎紡鍖栬緭鍑猴紙鍘婚櫎鏈熬鏃犳晥0锛夈��
         return defaultDecimal(value).stripTrailingZeros().toPlainString();
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java
index a99b194..d66ebc7 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java
@@ -48,17 +48,20 @@
 
     @Override
     public List<ProductionOrderRoutingOperationParamVo> listProductionOrderRoutingOperationParam(ProductionOrderRoutingOperationParamDto dto) {
+        // 鏌ヨ鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭鍙傛暟
         return BeanUtil.copyToList(this.list(buildQueryWrapper(dto)), ProductionOrderRoutingOperationParamVo.class);
     }
 
     @Override
     public ProductionOrderRoutingOperationParamVo getProductionOrderRoutingOperationParamInfo(Long id) {
+        // 鑾峰彇鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭鍙傛暟璇︽儏
         ProductionOrderRoutingOperationParam item = this.getById(id);
         return item == null ? null : BeanUtil.copyProperties(item, ProductionOrderRoutingOperationParamVo.class);
     }
 
     @Override
     public boolean saveProductionOrderRoutingOperationParam(ProductionOrderRoutingOperationParam item) {
+        // 淇濆瓨鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭鍙傛暟
         ProductionOrderRoutingOperation routingOperation = getRoutingOperation(item.getProductionOrderRoutingOperationId());
         fillFromSourceParam(item, routingOperation);
         validateManualFields(item);
@@ -68,10 +71,12 @@
 
     @Override
     public boolean removeProductionOrderRoutingOperationParam(Long id) {
+        // 鍒犻櫎鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭鍙傛暟
         return this.removeById(id);
     }
 
     private LambdaQueryWrapper<ProductionOrderRoutingOperationParam> buildQueryWrapper(ProductionOrderRoutingOperationParamDto dto) {
+        // 鎸夋潯浠跺姩鎬佹瀯寤烘暟鎹簱鏌ヨ鏉′欢
         ProductionOrderRoutingOperationParam query = dto == null ? new ProductionOrderRoutingOperationParam() : dto;
         return Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                 .eq(query.getId() != null, ProductionOrderRoutingOperationParam::getId, query.getId())
@@ -96,29 +101,31 @@
     }
 
     private ProductionOrderRoutingOperation getRoutingOperation(Long productionOrderRoutingOperationId) {
+        // 鑾峰彇宸ヨ壓璺嚎宸ュ簭
         if (productionOrderRoutingOperationId == null) {
-            throw new ServiceException("productionOrderRoutingOperationId is required");
+            throw new ServiceException("鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭ID涓嶈兘涓虹┖");
         }
         ProductionOrderRoutingOperation routingOperation = productionOrderRoutingOperationMapper.selectById(productionOrderRoutingOperationId);
         if (routingOperation == null) {
-            throw new ServiceException("Production order routing operation not found");
+            throw new ServiceException("鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭涓嶅瓨鍦�");
         }
         return routingOperation;
     }
 
     private void fillFromSourceParam(ProductionOrderRoutingOperationParam item, ProductionOrderRoutingOperation routingOperation) {
+        // 浠庢潵婧愬弬鏁板洖濉綋鍓嶅弬鏁伴粯璁ゅ��
         item.setProductionOrderId(routingOperation.getProductionOrderId());
         item.setProductionOrderRoutingOperationId(routingOperation.getId());
         ProductionOrder productionOrder = productionOrderMapper.selectById(routingOperation.getProductionOrderId());
         if (productionOrder == null) {
-            throw new ServiceException("Production order not found");
+            throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
         }
         if (item.getParamId() == null) {
             return;
         }
         TechnologyParam sourceParam = technologyParamMapper.selectById(item.getParamId());
         if (sourceParam == null) {
-            throw new ServiceException("Technology  param not found");
+            throw new ServiceException("宸ヨ壓鍙傛暟涓嶅瓨鍦�");
         }
         if (item.getTechnologyOperationParamId() != null) {
             TechnologyRoutingOperationParam sourceRoutingOperationParam = technologyRoutingOperationParamMapper.selectById(item.getTechnologyOperationParamId());
@@ -141,15 +148,17 @@
     }
 
     private void validateManualFields(ProductionOrderRoutingOperationParam item) {
+        // 鏍¢獙鎵嬪伐褰曞叆瀛楁鐨勫繀濉笌鏍煎紡
         if (item.getParamCode() == null || item.getParamCode().trim().isEmpty()) {
-            throw new ServiceException("paramCode is required");
+            throw new ServiceException("鍙傛暟缂栫爜涓嶈兘涓虹┖");
         }
         if (item.getParamName() == null || item.getParamName().trim().isEmpty()) {
-            throw new ServiceException("paramName is required");
+            throw new ServiceException("鍙傛暟鍚嶇О涓嶈兘涓虹┖");
         }
     }
 
     private void checkDuplicate(ProductionOrderRoutingOperationParam item) {
+        // 妫�鏌ユ暟鎹槸鍚﹂噸澶嶏紝閬垮厤閲嶅淇濆瓨
         boolean duplicate = productionOrderRoutingOperationParamMapper.selectCount(
                 Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                         .isNull(ProductionOrderRoutingOperationParam::getProductionProductMainId)
@@ -161,7 +170,7 @@
                         .ne(item.getId() != null, ProductionOrderRoutingOperationParam::getId, item.getId())
         ) > 0;
         if (duplicate) {
-            throw new ServiceException("Duplicate production order routing operation param");
+            throw new ServiceException("鐢熶骇璁㈠崟宸ヨ壓璺嚎宸ュ簭鍙傛暟閲嶅");
         }
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
index 886da11..3d16a07 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java
@@ -45,12 +45,15 @@
 
     @Override
     public R addRouteItem(ProductionOrderRoutingOperation productionOrderRoutingOperation) {
+        // 鏂板宸ヨ壓璺嚎
         int insert = productionOrderRoutingOperationMapper.insert(productionOrderRoutingOperation);
         //宸ュ簭鍏宠仈鐨勫弬鏁伴渶瑕佸悓姝ユ柊澧�
         List<TechnologyOperationParam> technologyOperationParams = technologyOperationParamMapper.selectList(Wrappers.<TechnologyOperationParam>lambdaQuery()
                 .eq(TechnologyOperationParam::getTechnologyOperationId, productionOrderRoutingOperation.getTechnologyOperationId()));
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (CollectionUtils.isNotEmpty(technologyOperationParams)){
             ArrayList<ProductionOrderRoutingOperationParam> productionOrderRoutingOperationParams = new ArrayList<>();
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
             for (TechnologyOperationParam technologyOperationParam : technologyOperationParams) {
                 TechnologyParam technologyParam = technologyParamMapper.selectById(technologyOperationParam.getTechnologyParamId());
                 ProductionOrderRoutingOperationParam productionOrderRoutingOperationParam = new ProductionOrderRoutingOperationParam();
@@ -103,11 +106,14 @@
 
     @Override
     public R deleteRouteItem(Long id) {
+        // 鍒犻櫎宸ヨ壓璺嚎
         try {
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
             ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectOne(
                     new LambdaQueryWrapper<ProductionOperationTask>()
                             .eq(ProductionOperationTask::getProductionOrderRoutingOperationId, id)
                             .last("limit 1"));
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
             if (productionOperationTask == null) {
                 throw new RuntimeException("鍒犻櫎澶辫触锛氭湭鎵惧埌鍏宠仈鐨勭敓浜у伐鍗�");
             }
@@ -118,6 +124,7 @@
             List<ProductionProductMain> productionProductMains = productionProductMainMapper.selectList(
                     new LambdaQueryWrapper<ProductionProductMain>()
                             .eq(ProductionProductMain::getProductionOperationTaskId, productionOperationTask.getId()));
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
             for (ProductionProductMain main : productionProductMains) {
                 productionProductMainService.removeProductMain(main.getId());
             }
@@ -140,6 +147,7 @@
                     ProductionOrderRoutingOperation item = operationList.get(i);
                     if (!Integer.valueOf(i + 1).equals(item.getDragSort())) {
                         item.setDragSort(i + 1);
+        // 鎸佷箙鍖栨垨杈撳嚭澶勭悊缁撴灉
                         productionOrderRoutingOperationMapper.updateById(item);
                     }
                 }
@@ -152,6 +160,7 @@
 
     @Override
     public int sortRouteItem(ProductionOrderRoutingOperation productionOrderRoutingOperation) {
+        // 鎺掑簭宸ヨ壓璺嚎
         ProductionOrderRoutingOperation oldItem = productionOrderRoutingOperationMapper.selectById(productionOrderRoutingOperation.getId());
         List<ProductionOrderRoutingOperation> operationList = productionOrderRoutingOperationMapper.selectList(
                 Wrappers.<ProductionOrderRoutingOperation>lambdaQuery()
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java
index 10443c1..0ffb34f 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java
@@ -21,6 +21,7 @@
 
     @Override
     public ProductionOrderRouting listMain(Long orderId) {
+        // 鏌ヨ涓昏〃ID闆嗗悎
         return productionOrderRoutingMapper.selectOne(
                 Wrappers.<ProductionOrderRouting>lambdaQuery()
                         .eq(ProductionOrderRouting::getProductionOrderId, orderId)
@@ -30,6 +31,7 @@
 
     @Override
     public List<ProductionOrderRoutingOperationVo> listItem(Long orderId) {
+        // 鏌ヨ宸ヨ壓璺嚎宸ュ簭鏄庣粏
         return productionOrderRoutingOperationMapper.selectVoListByOrderId(orderId);
     }
 }
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 9973d98..344800c 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -82,6 +82,7 @@
 
     @Override
     public IPage<ProductionOrderVo> pageProductionOrder(Page<ProductionOrderDto> page, ProductionOrderDto dto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇璁㈠崟
         Page<ProductionOrderVo> result = (Page<ProductionOrderVo>) baseMapper.pageProductionOrder(page, dto);
         fillProductImages(result.getRecords());
         return result;
@@ -89,6 +90,7 @@
 
     @Override
     public List<ProductionOrderVo> listProductionOrder(ProductionOrderDto dto) {
+        // 鏌ヨ鐢熶骇璁㈠崟鍒楄〃
         List<ProductionOrderVo> records = baseMapper.listProductionOrder(dto);
         fillProductImages(records);
         return records;
@@ -96,6 +98,7 @@
 
     @Override
     public ProductionOrderVo getProductionOrderInfo(Long id) {
+        // 鑾峰彇鐢熶骇璁㈠崟璇︽儏
         ProductionOrderVo item = baseMapper.getProductionOrderInfo(id);
         if (item == null) {
             return null;
@@ -106,6 +109,7 @@
 
     @Override
     public boolean saveProductionOrder(ProductionOrder productionOrder) {
+        // 淇濆瓨鐢熶骇璁㈠崟
         ProductionOrder oldOrder = productionOrder.getId() == null ? null : this.getById(productionOrder.getId());
         // 涓嬪崟鍏ュ彛缁熶竴琛ラ綈鏉ユ簮鍗曟嵁銆佽鍒掑拰宸ヨ壓淇℃伅锛岄伩鍏嶅墠绔垎鍒紶澶氬瀛楁銆�
         validateAndFillOrder(productionOrder, oldOrder);
@@ -137,6 +141,7 @@
 
     @Override
     public boolean removeProductionOrder(List<Long> ids) {
+        // 鍒犻櫎鐢熶骇璁㈠崟
         if (ids == null || ids.isEmpty()) {
             return false;
         }
@@ -150,6 +155,7 @@
 
     @Override
     public Integer bindingRoute(ProductionOrderDto productionOrderDto) {
+        // 涓鸿鍗曠粦瀹氬伐鑹鸿矾绾�
         if (productionOrderDto == null || productionOrderDto.getId() == null) {
             throw new ServiceException("鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
         }
@@ -193,6 +199,7 @@
 
     @Override
     public List<ProductionPlanVo> getSource(Long id) {
+        // 鏌ヨ璁㈠崟鍏宠仈鏉ユ簮璁″垝
         ProductionOrder productionOrder = baseMapper.selectById(id);
         if (productionOrder != null && productionOrder.getProductionPlanIds() != null) {
             List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
@@ -203,7 +210,9 @@
 
     @Override
     public int syncProductionOrderSnapshot(Long productionOrderId) {
+        // 鍚屾璁㈠崟宸ヨ壓銆佸伐搴忋�佸弬鏁板拰BOM蹇収
         ProductionOrder productionOrder = this.getById(productionOrderId);
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (productionOrder == null) {
             throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
         }
@@ -218,6 +227,7 @@
         clearProductionSnapshot(productionOrderId);
         ProductionOrderBom orderBom = syncProductionOrderBomSnapshot(productionOrder, technologyRouting);
 
+        //鐢熶骇璁㈠崟宸ヨ壓璺嚎琛�
         ProductionOrderRouting orderRouting = new ProductionOrderRouting();
         orderRouting.setProductionOrderId(productionOrder.getId());
         orderRouting.setTechnologyRoutingId(technologyRouting.getId());
@@ -229,12 +239,14 @@
         productionOrderRoutingMapper.insert(orderRouting);
 
         int syncedParamCount = 0;
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
         List<TechnologyRoutingOperation> routingOperations = technologyRoutingOperationMapper.selectList(
                 Wrappers.<TechnologyRoutingOperation>lambdaQuery()
                         .eq(TechnologyRoutingOperation::getTechnologyRoutingId, technologyRouting.getId())
                         .orderByDesc(TechnologyRoutingOperation::getDragSort)
                         .orderByDesc(TechnologyRoutingOperation::getId));
         Map<Long, String> operationNameMap = technologyOperationMapper.selectBatchIds(
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
                         routingOperations.stream()
                                 .map(TechnologyRoutingOperation::getTechnologyOperationId)
                                 .filter(Objects::nonNull)
@@ -258,6 +270,7 @@
             targetOperation.setIsQuality(sourceOperation.getIsQuality());
             targetOperation.setOperationName(operationNameMap.get(sourceOperation.getTechnologyOperationId()));
             targetOperation.setTechnologyOperationId(sourceOperation.getTechnologyOperationId());
+            targetOperation.setType(sourceOperation.getType());
             productionOrderRoutingOperationMapper.insert(targetOperation);
 
             boolean isLastOperation = lastDragSort != null && Objects.equals(sourceOperation.getDragSort(), lastDragSort);
@@ -301,6 +314,7 @@
     }
 
     private ProductionOrderBom syncProductionOrderBomSnapshot(ProductionOrder productionOrder, TechnologyRouting technologyRouting) {
+        // 鍚屾璁㈠崟BOM蹇収缁撴瀯
         if (technologyRouting.getBomId() == null) {
             return null;
         }
@@ -308,10 +322,12 @@
         if (technologyBom == null) {
             throw new ServiceException("宸ヨ壓BOM涓嶅瓨鍦�");
         }
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
         List<TechnologyBomStructure> structureList = technologyBomStructureMapper.selectList(
                 Wrappers.<TechnologyBomStructure>lambdaQuery()
                         .eq(TechnologyBomStructure::getBomId, technologyBom.getId())
                         .orderByAsc(TechnologyBomStructure::getId));
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         TechnologyBomStructure root = structureList.stream().filter(item -> item.getParentId() == null).findFirst().orElse(null);
         BigDecimal orderQuantity = defaultDecimal(productionOrder.getQuantity());
 
@@ -322,6 +338,7 @@
         orderBom.setRemark(technologyBom.getRemark());
         orderBom.setBomNo(technologyBom.getBomNo());
         orderBom.setVersion(technologyBom.getVersion());
+        // 鎸佷箙鍖栨垨杈撳嚭澶勭悊缁撴灉
         productionOrderBomMapper.insert(orderBom);
 
         Map<Long, Long> idMap = new HashMap<>();
@@ -343,16 +360,19 @@
     }
 
     private void clearProductionSnapshot(Long productionOrderId) {
-        // 宸蹭骇鐢熼鏂欒褰曞悗绂佹閲嶅缓锛岄伩鍏嶅鏂�/鎶曟枡渚濇嵁涓庤鍗曞揩鐓ц劚鑺傘��
+        // 娓呯悊璁㈠崟宸茬敓鎴愮殑宸ヨ壓涓嶣OM蹇収鏁版嵁
         boolean hasPickRecord = productionOrderPickRecordMapper.selectCount(
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
                 Wrappers.<ProductionOrderPickRecord>lambdaQuery()
                         .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId)) > 0;
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (hasPickRecord) {
             throw new ServiceException("鐢熶骇璁㈠崟宸插瓨鍦ㄩ鏂欒褰曪紝涓嶈兘閲嶆柊鐢熸垚蹇収");
         }
         List<Long> taskIds = productionOperationTaskMapper.selectList(
                         Wrappers.<ProductionOperationTask>lambdaQuery()
                                 .eq(ProductionOperationTask::getProductionOrderId, productionOrderId))
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
                 .stream().map(ProductionOperationTask::getId).collect(Collectors.toList());
         if (!taskIds.isEmpty()) {
             // 宸叉湁鎶ュ伐璁板綍璇存槑璁㈠崟宸插紑宸ワ紝姝ゆ椂涓嶅厑璁稿啀閲嶅缓蹇収銆�
@@ -380,6 +400,7 @@
     }
 
     private LambdaQueryWrapper<ProductionOrder> buildQueryWrapper(ProductionOrderDto dto) {
+        // 鎸夋潯浠跺姩鎬佹瀯寤烘暟鎹簱鏌ヨ鏉′欢
         ProductionOrder query = dto == null ? new ProductionOrder() : dto;
         return Wrappers.<ProductionOrder>lambdaQuery()
                 .eq(query.getId() != null, ProductionOrder::getId, query.getId())
@@ -390,6 +411,7 @@
     }
 
     private String generateNextOrderNo() {
+        // 鐢熸垚涓嬩竴涓敓浜ц鍗曞彿
         String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
         String prefix = "SC" + datePrefix;
         ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery()
@@ -408,6 +430,7 @@
     }
 
     private String generateNextTaskNo() {
+        // 鐢熸垚涓嬩竴涓敓浜у伐鍗曞彿
         String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
         String prefix = "GD" + datePrefix;
         ProductionOperationTask lastTask = productionOperationTaskMapper.selectOne(
@@ -427,10 +450,12 @@
     }
 
     private BigDecimal defaultDecimal(BigDecimal value) {
+        // 灏嗙┖鏁伴噺鍏滃簳涓�0锛岄伩鍏嶇┖鎸囬拡寮傚父
         return value == null ? BigDecimal.ZERO : value;
     }
 
     private void validateAndFillOrder(ProductionOrder productionOrder, ProductionOrder oldOrder) {
+        // 鏍¢獙璁㈠崟鍙傛暟骞惰ˉ榻愰粯璁ゅ��
         if (productionOrder == null) {
             throw new ServiceException("鐢熶骇璁㈠崟涓嶈兘涓虹┖");
         }
@@ -463,7 +488,9 @@
     }
 
     private void fillFromProductionPlans(ProductionOrder productionOrder) {
+        // 浠庡叧鑱旂敓浜ц鍒掑洖濉鍗曞叧閿瓧娈�
         List<Long> planIds = parsePlanIds(productionOrder.getProductionPlanIds());
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (planIds.isEmpty()) {
             return;
         }
@@ -472,6 +499,7 @@
         if (productionPlans.size() != planIds.size()) {
             throw new ServiceException("閮ㄥ垎鐢熶骇璁″垝涓嶅瓨鍦�");
         }
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         Map<Long, ProductionPlan> planMap = productionPlans.stream()
                 .collect(Collectors.toMap(ProductionPlan::getId, item -> item, (left, right) -> left));
         ProductionPlan mainPlan = planMap.get(planIds.get(0));
@@ -510,6 +538,7 @@
     }
 
     private void releaseProductionPlanIssueStatus(ProductionOrder productionOrder) {
+        // 鍥為��鐢熶骇璁″垝涓嬪彂鐘舵��
         if (productionOrder == null) {
             return;
         }
@@ -522,6 +551,7 @@
 
     //鐢熶骇璁㈠崟鍒犻櫎锛岀敓浜ц鍒掔殑宸蹭笅鍙戞暟閲忓搴斿彉鏇�
     private void updatePlanIssuedFlag(List<Long> planIds, BigDecimal remainingAssignedQuantity) {
+        // 鏇存柊璁″垝涓嬪彂鏍囪鍜屼笅鍙戞暟閲�
         if (planIds == null || planIds.isEmpty()) {
             return;
         }
@@ -551,6 +581,7 @@
     }
 
     private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
+        // 璁$畻褰撳墠璁″垝鎴栬褰曠殑鍓╀綑鏁伴噺
         if (plan == null) {
             return BigDecimal.ZERO;
         }
@@ -569,6 +600,7 @@
     }
 
     private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) {
+        // 鏍规嵁闇�姹傞噺鍜屼笅鍙戦噺鎺ㄥ璁″垝鐘舵��
         if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
             return 0;
         }
@@ -579,6 +611,7 @@
     }
 
     private List<Long> parsePlanIds(String productionPlanIds) {
+        // 灏嗚鍒扞D瀛楃涓茶В鏋愪负Long鍒楄〃
         if (productionPlanIds == null || productionPlanIds.trim().isEmpty()) {
             return new ArrayList<>();
         }
@@ -595,6 +628,7 @@
     }
 
     private String formatPlanIds(List<Long> planIds) {
+        // 灏嗚鍒扞D闆嗗悎鏍煎紡鍖栦负[1,2,3]瀛楃涓�
         if (planIds == null || planIds.isEmpty()) {
             return null;
         }
@@ -605,6 +639,7 @@
     }
 
     private LocalDate resolvePlanCompleteDate(ProductionPlan productionPlan) {
+        // 瑙f瀽璁″垝瀹屾垚鏃ユ湡
         if (productionPlan == null) {
             return null;
         }
@@ -618,13 +653,16 @@
     }
 
     private int compareDecimal(BigDecimal left, BigDecimal right) {
+        // 瀹夊叏姣旇緝涓や釜鏁伴噺鍊煎ぇ灏�
         return defaultDecimal(left).compareTo(defaultDecimal(right));
     }
 
     private void fillProductImages(List<ProductionOrderVo> records) {
+        // 濉厖浜у搧鍥剧墖
         if (records == null || records.isEmpty()) {
             return;
         }
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         List<Long> productModelIds = records.stream()
                 .map(ProductionOrderVo::getProductModelId)
                 .filter(Objects::nonNull)
@@ -634,6 +672,7 @@
             return;
         }
 
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
         List<StorageAttachment> attachments = storageAttachmentMapper.selectList(
                 Wrappers.<StorageAttachment>lambdaQuery()
                         .in(StorageAttachment::getRecordId, productModelIds)
@@ -676,6 +715,7 @@
     }
 
     private StorageBlobVO toStorageBlobVO(StorageBlob blob) {
+        // 灏嗗瓨鍌ㄦ枃浠跺璞¤浆鎹负VO
         StorageBlobVO vo = BeanUtil.copyProperties(blob, StorageBlobVO.class);
         vo.setPreviewURL(fileUtil.buildSignedPreviewUrl(vo));
         vo.setDownloadURL(fileUtil.buildSignedDownloadUrl(vo));
@@ -684,8 +724,10 @@
 
     @Override
     public ProductionOrderWorkOrderDetailVo getWorkOrderReportInspectDetail(ProductionOrderDto dto) {
+        // 鑾峰彇宸ュ崟璁㈠崟鎶ュ伐璐ㄦ鏄庣粏
         Long productionOrderId = resolveProductionOrderId(dto);
         ProductionOrderVo orderInfo = getProductionOrderInfo(productionOrderId);
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (orderInfo == null) {
             throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
         }
@@ -699,11 +741,12 @@
                 new Page<ProductionOperationTaskVo>(1, -1), taskQuery);
         List<ProductionOperationTaskVo> workOrderList = workOrderPage == null || workOrderPage.getRecords() == null
                 ? Collections.emptyList()
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
                 : workOrderPage.getRecords().stream()
                 .filter(Objects::nonNull)
                 .sorted(Comparator.comparing(ProductionOperationTaskVo::getId, Comparator.nullsLast(Comparator.naturalOrder())))
-                .collect(Collectors.toList());
-        if (workOrderList == null || workOrderList.isEmpty()) {
+                .toList();
+        if (workOrderList.isEmpty()) {
             detailVo.setWorkOrderList(Collections.emptyList());
             return detailVo;
         }
@@ -714,6 +757,7 @@
                 .collect(Collectors.toList());
         List<ProductionProductMain> reportMainList = workOrderIdList.isEmpty()
                 ? Collections.emptyList()
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
                 : productionProductMainMapper.selectList(
                 Wrappers.<ProductionProductMain>lambdaQuery()
                         .in(ProductionProductMain::getProductionOperationTaskId, workOrderIdList)
@@ -824,6 +868,7 @@
 
                 ProductionOrderWorkOrderDetailVo.ReportDetail reportDetail = new ProductionOrderWorkOrderDetailVo.ReportDetail();
                 reportDetail.setReportMain(reportMain);
+                reportDetail.setWorkHour(reportMain.getWorkHour());
                 reportDetail.setReportOutputList(reportOutputMap.getOrDefault(reportMainId, Collections.emptyList()));
                 reportDetail.setReportParamList(reportParamMap.getOrDefault(reportMainId, Collections.emptyList()));
                 reportDetailList.add(reportDetail);
@@ -834,6 +879,7 @@
                     inspectDetail.setReportId(reportMainId);
                     inspectDetail.setReportNo(reportMain.getProductNo());
                     inspectDetail.setReportMain(reportMain);
+                    inspectDetail.setWorkHour(reportMain.getWorkHour());
                     inspectDetail.setInspect(inspect);
                     inspectDetail.setInspectParamList(inspectParamMap.getOrDefault(inspect.getId(), Collections.emptyList()));
                     inspectDetail.setInspectFileList(inspectFileMap.getOrDefault(inspect.getId(), Collections.emptyList()));
@@ -851,6 +897,7 @@
     }
 
     private Long resolveProductionOrderId(ProductionOrderDto dto) {
+        // 浠庡叆鍙備腑瑙f瀽鐢熶骇璁㈠崟ID骞舵牎楠�
         if (dto == null) {
             throw new ServiceException("璇蜂紶鍏ョ敓浜ц鍗旾D鎴栫敓浜ц鍗曞彿");
         }
@@ -872,10 +919,12 @@
 
     @Override
     public List<ProductionOrderPickVo> pick(Long productionOrderId) {
+        // 鏌ヨ璁㈠崟棰嗘枡銆佹姇鏂欎笌閫�鏂欐槑缁�
         if (productionOrderId == null) {
             return Collections.emptyList();
         }
 
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
         ProductionOrderBom orderBom = productionOrderBomMapper.selectOne(
                 Wrappers.<ProductionOrderBom>lambdaQuery()
                         .eq(ProductionOrderBom::getProductionOrderId, productionOrderId)
@@ -890,6 +939,7 @@
             return Collections.emptyList();
         }
 
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         List<Long> productModelIds = bomStructureList.stream()
                 .map(ProductionBomStructureVo::getProductModelId)
                 .filter(Objects::nonNull)
@@ -946,6 +996,7 @@
 
     @Override
     public int updateOrder(ProductionOrderDto productionOrderDto) {
+        // 鏇存柊鐢熶骇璁㈠崟涓绘暟鎹�
         productionOrderDto.setStatus(5);
         return baseMapper.updateById(productionOrderDto);
     }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
index b3ad742..b00266b 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -6,6 +6,10 @@
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.mapper.ProductMapper;
+import com.ruoyi.basic.mapper.ProductModelMapper;
+import com.ruoyi.basic.pojo.Product;
+import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.StringUtils;
@@ -44,27 +48,32 @@
     private final ProductionPlanMapper productionPlanMapper;
     private final ProductionOrderMapper productionOrderMapper;
     private final ProductionOrderService productionOrderService;
+    private final ProductModelMapper productModelMapper;
+    private final ProductMapper productMapper;
 
     @Override
     public IPage<ProductionPlanVo> listPage(Page<ProductionPlanDto> page, ProductionPlanDto productionPlanDto) {
+        // 鍒嗛〉鏌ヨ涓荤敓浜ц鍒掑垪琛�
         return productionPlanMapper.listPage(page, productionPlanDto);
     }
 
     /**
      * 鍚堝苟鐢熶骇璁″垝骞朵笅鍙戠敓浜ц鍗曘��
-     * 涓氬姟绾︽潫锛�
+     * 绾︽潫锛�
      * 1. 浠呭厑璁稿悓涓�浜у搧鍨嬪彿鐨勮鍒掑悎骞讹紱
-     * 2. 宸蹭笅鍙戞垨閮ㄥ垎涓嬪彂鐨勮鍒掔姝㈠啀娆″悎骞讹紱
-     * 3. 涓嬪彂鏁伴噺涓嶈兘澶т簬鎵�閫夎鍒掗渶姹傛�婚噺锛�
-     * 4. 璁㈠崟鍒涘缓缁熶竴澶嶇敤 ProductionOrderService.saveProductionOrder锛岀‘淇濆伐鑹�/BOM/棰嗘枡涓诲崟绛夊悗缁�昏緫涓�鑷淬��
+     * 2. 宸蹭笅鍙戞垨閮ㄥ垎涓嬪彂鐨勮鍒掍笉鍏佽鍐嶆鍚堝苟锛�
+     * 3. 涓嬪彂鏁伴噺涓嶈兘澶т簬鎵�閫夎鍒掑墿浣欓渶姹傛�婚噺锛�
+     * 4. 涓嬪彂鏃剁粺涓�璋冪敤 ProductionOrderService.saveProductionOrder锛岀‘淇濆悗缁伐鑹�/BOM/棰嗘枡閫昏緫涓�鑷淬��
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean combine(ProductionPlanDto productionPlanDto) {
+        // 鍩虹鍏ュ弬鏍¢獙锛氭病鏈夊彲涓嬪彂璁″垝鍒欑洿鎺ヨ繑鍥� false
         if (productionPlanDto == null || productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) {
             return false;
         }
 
+        // 鍘荤┖銆佸幓閲嶏紝寰楀埌鏈鍙備笌鍚堝苟鐨勮鍒� ID
         List<Long> planIds = productionPlanDto.getIds().stream()
                 .filter(Objects::nonNull)
                 .distinct()
@@ -73,43 +82,52 @@
             throw new ServiceException("涓嬪彂澶辫触锛屾湭閫夋嫨鐢熶骇璁″垝");
         }
 
+        // 鏌ヨ骞舵牎楠岃鍒掓槸鍚﹂兘瀛樺湪
         List<ProductionPlanDto> planLists = productionPlanMapper.selectWithMaterialByIds(planIds);
         if (planLists == null || planLists.isEmpty() || planLists.size() != planIds.size()) {
             throw new ServiceException("涓嬪彂澶辫触锛岀敓浜ц鍒掍笉瀛樺湪鎴栧凡琚垹闄�");
         }
 
+        // 浠ョ涓�鏉¤鍒掍綔涓哄瀷鍙峰熀鍑�
         ProductionPlanDto firstPlan = planLists.getFirst();
         if (firstPlan.getProductModelId() == null) {
             throw new ServiceException("涓嬪彂澶辫触锛岀敓浜ц鍒掔己灏戜骇鍝佸瀷鍙�");
         }
 
+        // 浠呭厑璁稿悓鍨嬪彿璁″垝鍚堝苟涓嬪彂
         boolean hasDifferentModel = planLists.stream()
                 .anyMatch(item -> !Objects.equals(item.getProductModelId(), firstPlan.getProductModelId()));
         if (hasDifferentModel) {
             throw new BaseException("鍚堝苟澶辫触锛屾墍閫夌敓浜ц鍒掔殑浜у搧鍨嬪彿涓嶄竴鑷�");
         }
 
-        boolean hasIssuedPlan = planLists.stream()
-                .anyMatch(item -> item.getStatus() != null && item.getStatus() == PLAN_STATUS_ISSUED);
-        if (hasIssuedPlan) {
-            throw new BaseException("鍚堝苟澶辫触锛屾墍閫夌敓浜ц鍒掑瓨鍦ㄥ凡涓嬪彂鎴栭儴鍒嗕笅鍙戞暟鎹�");
+        // 浠呪�滃凡涓嬪彂鈥濊鍒掍笉鍏佽鍐嶆鍙備笌鍚堝苟涓嬪彂锛�
+        // 鈥滃緟涓嬪彂/閮ㄥ垎涓嬪彂鈥濆厑璁哥户缁笅鍙戝墿浣欐暟閲忋��
+        boolean hasFullyIssuedPlan = planLists.stream()
+                .anyMatch(item -> item.getStatus() != null
+                        && item.getStatus() == PLAN_STATUS_ISSUED);
+        if (hasFullyIssuedPlan) {
+            throw new BaseException("鍚堝苟澶辫触锛屾墍閫夌敓浜ц鍒掑瓨鍦ㄥ凡涓嬪彂鐨勬暟鎹�");
         }
 
+        // 璁$畻鏈鍙笅鍙戠殑鍓╀綑闇�姹傛�婚噺
         BigDecimal totalRequiredQuantity = planLists.stream()
                 .map(this::resolveRemainingQuantity)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         if (totalRequiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
-            throw new ServiceException("涓嬪彂澶辫触锛屾墍閫夌敓浜ц鍒掗渶姹傛�婚噺蹇呴』澶т簬0");
+            throw new ServiceException("涓嬪彂澶辫触锛屾墍閫夌敓浜ц鍒掑墿浣欓渶姹傛�婚噺蹇呴』澶т簬0");
         }
 
+        // 鏍¢獙涓嬪彂鏁伴噺
         BigDecimal assignedQuantity = productionPlanDto.getTotalAssignedQuantity();
         if (assignedQuantity == null || assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
             throw new ServiceException("涓嬪彂澶辫触锛屼笅鍙戞暟閲忓繀椤诲ぇ浜�0");
         }
         if (assignedQuantity.compareTo(totalRequiredQuantity) > 0) {
-            throw new ServiceException("涓嬪彂澶辫触锛屼笅鍙戞暟閲忎笉鑳藉ぇ浜庤鍒掗渶姹傛�婚噺");
+            throw new ServiceException("涓嬪彂澶辫触锛屼笅鍙戞暟閲忎笉鑳藉ぇ浜庤鍒掑墿浣欓渶姹傛�婚噺");
         }
 
+        // 鎸夎鍒掗『搴忓垎鎽婁笅鍙戞暟閲忥紝鏀堕泦瀹為檯鍙備笌涓嬪彂鐨勮鍒� ID
         BigDecimal remainingForOrderBind = assignedQuantity;
         List<Long> issuedPlanIds = new ArrayList<>();
         for (ProductionPlanDto plan : planLists) {
@@ -124,21 +142,20 @@
             }
         }
         if (issuedPlanIds.isEmpty()) {
-            throw new ServiceException("Issue failed, no quantity available for dispatch");
+            throw new ServiceException("涓嬪彂澶辫触锛屾棤鍙笅鍙戞暟閲�");
         }
 
+        // 鐢熸垚鐢熶骇璁㈠崟涓诲崟锛屽苟缁戝畾鏈涓嬪彂鍏宠仈鐨勮鍒�
         ProductionOrder productionOrder = new ProductionOrder();
         productionOrder.setProductionPlanIds(formatPlanIds(issuedPlanIds));
         productionOrder.setProductModelId(firstPlan.getProductModelId());
         productionOrder.setQuantity(assignedQuantity);
         productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime());
-
-        boolean saved = productionOrderService.saveProductionOrder(productionOrder);
-        if (!saved) {
+        if (!productionOrderService.saveProductionOrder(productionOrder)) {
             throw new ServiceException("涓嬪彂澶辫触锛岀敓浜ц鍗曚繚瀛樺け璐�");
         }
 
-        //宸蹭笅鍙戞暟閲�
+        // 鍥炲啓姣忔潯璁″垝鐨勭疮璁′笅鍙戦噺鍜岀姸鎬�
         BigDecimal remainingAssignedQuantity = assignedQuantity;
         List<ProductionPlan> updates = new ArrayList<>();
         for (ProductionPlanDto plan : planLists) {
@@ -163,6 +180,7 @@
             updates.add(update);
         }
         if (!updates.isEmpty()) {
+            // 鎵归噺鏇存柊璁″垝鐘舵�佷笌鏁伴噺
             this.updateBatchById(updates);
         }
         return true;
@@ -171,17 +189,24 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean add(ProductionPlanDto dto) {
+        // 鏂板涓荤敓浜ц鍒�
         if (StringUtils.isBlank(dto.getMpsNo())) {
-            dto.setMpsNo(generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))));
-        }else checkMpsNoUnique(dto.getMpsNo(), null);
+            String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+            dto.setMpsNo(buildPlanNo(datePrefix, resolveNextPlanSequence(datePrefix)));
+        } else {
+            checkMpsNoUnique(dto.getMpsNo(), null);
+        }
         dto.setStatus(PLAN_STATUS_WAIT);
-        dto.setSource("鍐呴儴");
+        if (StringUtils.isBlank(dto.getSource())) {
+            dto.setSource("鍐呴儴");
+        }
         return productionPlanMapper.insert(dto) > 0;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean update(ProductionPlanDto dto) {
+        // 鏇存柊涓荤敓浜ц鍒�
         if (dto == null || dto.getId() == null) {
             throw new ServiceException("缂栬緫澶辫触锛屾暟鎹笉鑳戒负绌�");
         }
@@ -202,20 +227,10 @@
         return productionPlanMapper.updateById(dto) > 0;
     }
 
-    private void checkMpsNoUnique(String mpsNo, Long excludeId) {
-        LambdaQueryWrapper<ProductionPlan> wrapper = Wrappers.lambdaQuery();
-        wrapper.eq(ProductionPlan::getMpsNo, mpsNo);
-        if (excludeId != null) {
-            wrapper.ne(ProductionPlan::getId, excludeId);
-        }
-        if (productionPlanMapper.selectCount(wrapper) > 0) {
-            throw new ServiceException("鐢熶骇璁″垝鍙� " + mpsNo + " 宸插瓨鍦�");
-        }
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean delete(List<Long> ids) {
+        // 鍒犻櫎涓荤敓浜ц鍒�
         if (productionPlanMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getId, ids))
                 .stream()
                 .anyMatch(p -> p.getStatus() == PLAN_STATUS_PARTIAL || p.getStatus() == PLAN_STATUS_ISSUED)) {
@@ -234,6 +249,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void importProdData(MultipartFile file) {
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (file == null || file.isEmpty()) {
             throw new ServiceException("瀵煎叆鏁版嵁涓嶈兘涓虹┖");
         }
@@ -247,104 +263,251 @@
         if (list == null || list.isEmpty()) {
             throw new ServiceException("Excel娌℃湁鏁版嵁");
         }
+
+        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        int nextSequence = resolveNextPlanSequence(datePrefix);
         Set<String> mpsNos = new HashSet<>();
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         for (int i = 0; i < list.size(); i++) {
             ProductionPlanImportDto dto = list.get(i);
-            String mpsNo = dto.getMpsNo();
+            String mpsNo = StringUtils.trim(dto.getMpsNo());
             if (StringUtils.isEmpty(mpsNo)) {
-                generateNextPlanNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
+                mpsNo = buildPlanNo(datePrefix, nextSequence++);
             }
+            dto.setMpsNo(mpsNo);
             if (!mpsNos.add(mpsNo)) {
-                throw new ServiceException("瀵煎叆澶辫触锛欵xcel 涓瓨鍦ㄩ噸澶嶇殑鐢宠鍗曠紪鍙� " + mpsNo);
+                throw new ServiceException("瀵煎叆澶辫触锛孍xcel涓瓨鍦ㄩ噸澶嶇殑涓荤敓浜ц鍒掑彿 " + mpsNo);
+            }
+            if (dto.getQtyRequired() == null || dto.getQtyRequired().compareTo(BigDecimal.ZERO) <= 0) {
+                throw new ServiceException("瀵煎叆澶辫触锛氱" + (i + 2) + "琛岄渶姹傛暟閲忓繀椤诲ぇ浜�0");
             }
         }
-        Long existApplyNoCount = baseMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery()
-                .in(ProductionPlan::getMpsNo, mpsNos));
+
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
+        Long existApplyNoCount = baseMapper.selectCount(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getMpsNo, mpsNos));
         if (existApplyNoCount > 0) {
-            List<String> existMpsNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery()
-                            .in(ProductionPlan::getMpsNo, mpsNos))
+            List<String> existMpsNos = baseMapper.selectList(Wrappers.<ProductionPlan>lambdaQuery().in(ProductionPlan::getMpsNo, mpsNos))
                     .stream()
                     .map(ProductionPlan::getMpsNo)
                     .collect(Collectors.toList());
-            throw new ServiceException("瀵煎叆澶辫触锛岀敓浜ц鍒掑彿宸插瓨鍦�: " + String.join(", ", existMpsNos));
+            throw new ServiceException("瀵煎叆澶辫触锛屼富鐢熶骇璁″垝鍙峰凡瀛樺湪: " + String.join(", ", existMpsNos));
         }
+
+        List<ProductModel> allModels = productModelMapper.selectList(Wrappers.<ProductModel>lambdaQuery());
+        Set<Long> productIds = allModels.stream()
+                .map(ProductModel::getProductId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, String> productNameById = productIds.isEmpty()
+                ? Collections.emptyMap()
+                : productMapper.selectBatchIds(productIds).stream()
+                .collect(Collectors.toMap(Product::getId, Product::getProductName, (a, b) -> a));
+
         LocalDateTime now = LocalDateTime.now();
-        List<ProductionPlan> entityList = list.stream().map(dto -> {
+        List<ProductionPlan> entityList = new ArrayList<>();
+        for (int i = 0; i < list.size(); i++) {
+            ProductionPlanImportDto dto = list.get(i);
             ProductionPlan entity = new ProductionPlan();
             BeanUtils.copyProperties(dto, entity);
+            entity.setProductModelId(resolveProductModelId(dto, i + 2, allModels, productNameById));
             entity.setStatus(PLAN_STATUS_WAIT);
-            entity.setSource("鍐呴儴");
+            entity.setSource(StringUtils.isNotEmpty(dto.getSource()) ? StringUtils.trim(dto.getSource()) : "鍐呴儴");
+            entity.setQuantityIssued(BigDecimal.ZERO);
             entity.setCreateTime(now);
             entity.setUpdateTime(now);
-            return entity;
-        }).collect(Collectors.toList());
-        this.saveBatch(entityList);
+            entityList.add(entity);
+        }
+        // 鎸佷箙鍖栨垨杈撳嚭澶勭悊缁撴灉
+        if (!this.saveBatch(entityList)) {
+            throw new ServiceException("瀵煎叆澶辫触锛屼繚瀛樼敓浜ц鍒掓暟鎹け璐�");
+        }
     }
 
     @Override
-    public void exportProdData(HttpServletResponse response, List<Long> ids) {
-        List<ProductionPlanDto> list = productionPlanMapper.selectWithMaterialByIds(ids);
+    public void exportProdData(HttpServletResponse response, ProductionPlanDto requestDto) {
+        // 瀵煎嚭涓荤敓浜ц鍒掓暟鎹�
+        List<Long> ids = requestDto == null || requestDto.getIds() == null
+                ? Collections.emptyList()
+                : requestDto.getIds().stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
+
         List<ProductionPlanImportDto> exportList = new ArrayList<>();
-        for (ProductionPlanDto entity : list) {
-            ProductionPlanImportDto dto = new ProductionPlanImportDto();
-            BeanUtils.copyProperties(entity, dto);
-            exportList.add(dto);
+        if (!ids.isEmpty()) {
+            List<ProductionPlanDto> list = productionPlanMapper.selectWithMaterialByIds(ids);
+            for (ProductionPlanDto item : list) {
+                ProductionPlanImportDto dto = new ProductionPlanImportDto();
+                BeanUtils.copyProperties(item, dto);
+                dto.setAssignedQuantity(item.getQuantityIssued());
+                exportList.add(dto);
+            }
+        } else {
+            ProductionPlanDto query = new ProductionPlanDto();
+            if (requestDto != null) {
+                BeanUtils.copyProperties(requestDto, query);
+            }
+            IPage<ProductionPlanVo> page = productionPlanMapper.listPage(new Page<>(1, -1), query);
+            if (page != null && page.getRecords() != null) {
+                for (ProductionPlanVo item : page.getRecords()) {
+                    ProductionPlanImportDto dto = new ProductionPlanImportDto();
+                    BeanUtils.copyProperties(item, dto);
+                    dto.setAssignedQuantity(item.getQuantityIssued());
+                    exportList.add(dto);
+                }
+            }
         }
+
         ExcelUtil<ProductionPlanImportDto> util = new ExcelUtil<>(ProductionPlanImportDto.class);
         util.exportExcel(response, exportList, "涓荤敓浜ц鍒�");
     }
 
-    private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
-        if (plan == null) {
-            return BigDecimal.ZERO;
+    /**
+     * 鏍¢獙涓荤敓浜ц鍒掑彿鍞竴鎬э紝鍙�氳繃 excludeId 鎺掗櫎褰撳墠璁板綍銆�
+     */
+    private void checkMpsNoUnique(String mpsNo, Long excludeId) {
+        // 鎸変富鐢熶骇璁″垝鍙锋煡璇㈤噸澶嶈褰�
+        LambdaQueryWrapper<ProductionPlan> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(ProductionPlan::getMpsNo, mpsNo);
+        if (excludeId != null) {
+            // 鏇存柊鏃舵帓闄ゅ綋鍓嶈褰曟湰韬�
+            wrapper.ne(ProductionPlan::getId, excludeId);
         }
-        BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO);
-        if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
-            return BigDecimal.ZERO;
+        if (productionPlanMapper.selectCount(wrapper) > 0) {
+            // 瀛樺湪閲嶅璁″垝鍙凤紝鐩存帴鎷︽埅
+            throw new ServiceException("鐢熶骇璁″垝鍙� " + mpsNo + " 宸插瓨鍦�");
         }
-        BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO);
-        if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
-            return requiredQuantity;
-        }
-        if (issuedQuantity.compareTo(requiredQuantity) >= 0) {
-            return BigDecimal.ZERO;
-        }
-        return requiredQuantity.subtract(issuedQuantity);
     }
 
-    private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) {
-        if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
-            return PLAN_STATUS_WAIT;
+    /**
+     * 鏍规嵁瀵煎叆琛岀殑鍨嬪彿銆佷骇鍝佸悕绉般�佸崟浣嶅畾浣嶅敮涓�鐨勪骇鍝佸瀷鍙� ID銆�
+     */
+    private Long resolveProductModelId(ProductionPlanImportDto dto, int rowNo, List<ProductModel> allModels,
+                                       Map<Long, String> productNameById) {
+        // 鍏堟寜瑙勬牸鍨嬪彿鍋氱涓�杞繃婊�
+        String model = StringUtils.trim(dto.getModel());
+        if (StringUtils.isEmpty(model)) {
+            throw new ServiceException("瀵煎叆澶辫触锛氱" + rowNo + "琛岃鏍煎瀷鍙蜂笉鑳戒负绌�");
         }
-        if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
-            return PLAN_STATUS_WAIT;
+
+        List<ProductModel> candidates = allModels.stream()
+                .filter(item -> model.equals(StringUtils.trim(item.getModel())))
+                .collect(Collectors.toList());
+        if (candidates.isEmpty()) {
+            throw new ServiceException("瀵煎叆澶辫触锛氱" + rowNo + "琛岃鏍煎瀷鍙蜂笉瀛樺湪锛屽瀷鍙凤細" + model);
         }
-        return issuedQuantity.compareTo(requiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED;
+
+        // 鑻ヤ紶浜嗕骇鍝佸悕绉帮紝鍐嶅仛绗簩杞繃婊�
+        String productName = StringUtils.trim(dto.getProductName());
+        if (StringUtils.isNotEmpty(productName)) {
+            candidates = candidates.stream()
+                    .filter(item -> productName.equals(StringUtils.trim(productNameById.get(item.getProductId()))))
+                    .collect(Collectors.toList());
+            if (candidates.isEmpty()) {
+                throw new ServiceException("瀵煎叆澶辫触锛氱" + rowNo + "琛屼骇鍝佸悕绉颁笌瑙勬牸鍨嬪彿涓嶅尮閰�");
+            }
+        }
+
+        // 鑻ヤ紶浜嗗崟浣嶏紝鍐嶅仛绗笁杞繃婊�
+        String unit = StringUtils.trim(dto.getUnit());
+        if (StringUtils.isNotEmpty(unit)) {
+            candidates = candidates.stream()
+                    .filter(item -> unit.equals(StringUtils.trim(item.getUnit())))
+                    .collect(Collectors.toList());
+            if (candidates.isEmpty()) {
+                throw new ServiceException("瀵煎叆澶辫触锛氱" + rowNo + "琛屽崟浣嶄笌瑙勬牸鍨嬪彿涓嶅尮閰�");
+            }
+        }
+
+        // 浠嶇劧澶氭潯璇存槑淇℃伅涓嶈冻浠ュ敮涓�瀹氫綅
+        if (candidates.size() > 1) {
+            throw new ServiceException("瀵煎叆澶辫触锛氱" + rowNo + "琛岃鏍煎瀷鍙峰尮閰嶅埌澶氫釜浜у搧锛岃琛ュ厖浜у搧鍚嶇О鎴栧崟浣�");
+        }
+        return candidates.get(0).getId();
     }
 
-    private String formatPlanIds(List<Long> planIds) {
-        return planIds.stream()
-                .filter(Objects::nonNull)
-                .distinct()
-                .map(String::valueOf)
-                .collect(Collectors.joining(",", "[", "]"));
+    /**
+     * 鐢熸垚涓荤敓浜ц鍒掑彿锛屾牸寮忥細JH + yyyyMMdd + 4浣嶆祦姘村彿銆�
+     */
+    private String buildPlanNo(String datePrefix, int sequence) {
+        // 缁熶竴璁″垝鍙锋牸寮忥細JH + 鏃ユ湡 + 4浣嶆祦姘村彿
+        return "JH" + datePrefix + String.format("%04d", sequence);
     }
 
-    private String generateNextPlanNo(String datePrefix) {
+    /**
+     * 鏌ヨ褰撴棩宸插瓨鍦ㄧ殑鏈�澶ф祦姘村彿锛屽苟杩斿洖涓嬩竴涓彲鐢ㄦ祦姘村彿銆�
+     */
+    private int resolveNextPlanSequence(String datePrefix) {
+        // 鏌ヨ褰撴棩鏈�鏂颁竴鏉¤鍒掑彿
         QueryWrapper<ProductionPlan> queryWrapper = new QueryWrapper<>();
         queryWrapper.likeRight("mps_no", "JH" + datePrefix);
         queryWrapper.orderByDesc("mps_no");
         queryWrapper.last("LIMIT 1");
         ProductionPlan latestPlan = productionPlanMapper.selectOne(queryWrapper);
+
+        // 榛樿浠� 0001 寮�濮�
         int sequence = 1;
-        if (latestPlan != null && latestPlan.getMpsNo() != null && !latestPlan.getMpsNo().isEmpty()) {
+        if (latestPlan != null && StringUtils.isNotEmpty(latestPlan.getMpsNo())) {
+            // 鎴彇鏈熬娴佹按鍙峰苟閫掑
             String sequenceStr = latestPlan.getMpsNo().substring(("JH" + datePrefix).length());
             try {
                 sequence = Integer.parseInt(sequenceStr) + 1;
-            } catch (NumberFormatException e) {
+            } catch (NumberFormatException ignored) {
+                // 鍘嗗彶鏁版嵁鏍煎紡寮傚父鏃跺洖閫�鍒� 0001
                 sequence = 1;
             }
         }
-        return "JH" + datePrefix + String.format("%04d", sequence);
+        return sequence;
+    }
+
+    /**
+     * 璁$畻鐢熶骇璁″垝鐨勫墿浣欐湭涓嬪彂鏁伴噺锛堥渶姹傞噺 - 宸蹭笅鍙戦噺锛屾渶灏忎负 0锛夈��
+     */
+    private BigDecimal resolveRemainingQuantity(ProductionPlan plan) {
+        // 绌哄璞℃寜 0 澶勭悊
+        if (plan == null) {
+            return BigDecimal.ZERO;
+        }
+        // 闇�姹傞噺涓虹┖鎴栧皬浜庣瓑浜� 0锛岃涓烘棤鍓╀綑
+        BigDecimal requiredQuantity = Optional.ofNullable(plan.getQtyRequired()).orElse(BigDecimal.ZERO);
+        if (requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        // 宸蹭笅鍙戦噺涓虹┖鎴栧皬浜庣瓑浜� 0锛屽墿浣欏嵆闇�姹傞噺
+        BigDecimal issuedQuantity = Optional.ofNullable(plan.getQuantityIssued()).orElse(BigDecimal.ZERO);
+        if (issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return requiredQuantity;
+        }
+        // 宸蹭笅鍙戦噺澶т簬绛変簬闇�姹傞噺锛屽墿浣欏綊闆�
+        if (issuedQuantity.compareTo(requiredQuantity) >= 0) {
+            return BigDecimal.ZERO;
+        }
+        // 姝e父鍦烘櫙杩斿洖宸��
+        return requiredQuantity.subtract(issuedQuantity);
+    }
+
+    /**
+     * 鎸夐渶姹傞噺涓庣疮璁′笅鍙戦噺鎺ㄥ璁″垝鐘舵�併��
+     */
+    private int resolvePlanStatus(BigDecimal requiredQuantity, BigDecimal issuedQuantity) {
+        // 鏃犳湁鏁堥渶姹傞噺鏃讹紝鐘舵�佷繚鎸佸緟涓嬪彂
+        if (requiredQuantity == null || requiredQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return PLAN_STATUS_WAIT;
+        }
+        // 鏈夐渶姹備絾鏈笅鍙戯紝鐘舵�佷粛涓哄緟涓嬪彂
+        if (issuedQuantity == null || issuedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return PLAN_STATUS_WAIT;
+        }
+        // 宸蹭笅鍙戦噺灏忎簬闇�姹傞噺涓洪儴鍒嗕笅鍙戯紝鍚﹀垯涓哄凡涓嬪彂
+        return issuedQuantity.compareTo(requiredQuantity) < 0 ? PLAN_STATUS_PARTIAL : PLAN_STATUS_ISSUED;
+    }
+
+    /**
+     * 灏嗚鍒� ID 闆嗗悎杞垚 [1,2,3] 褰㈠紡锛屽啓鍏ョ敓浜ц鍗曞叧鑱斿瓧娈点��
+     */
+    private String formatPlanIds(List<Long> planIds) {
+        // 鍘婚噸骞舵嫾鎺ヤ负 [1,2,3] 褰㈠紡鐨勫瓧绗︿覆
+        return planIds.stream()
+                .filter(Objects::nonNull)
+                .distinct()
+                .map(String::valueOf)
+                .collect(Collectors.joining(",", "[", "]"));
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java
index 38e0baf..64d78fb 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java
@@ -17,6 +17,7 @@
 
     @Override
     public IPage<ProductionProductInputDto> listPageProductionProductInputDto(Page page, ProductionProductInputDto productionProductInputDto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇浜у搧鍏ュ簱
         return productionProductInputMapper.listPageProductionProductInputDto(page, productionProductInputDto);
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
index 150fc89..808e75d 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -82,6 +82,7 @@
 
     @Override
     public IPage<ProductionProductMainDto> listPageProductionProductMainDto(Page page, ProductionProductMainDto productionProductMainDto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇鎶ュ伐涓昏〃
         IPage<ProductionProductMainDto> result = productionProductMainMapper.listPageProductionProductMainDto(page, productionProductMainDto);
         fillOperationParamList(result.getRecords());
         return result;
@@ -89,20 +90,24 @@
 
     @Override
     public IPage<ProductionProductMainDto> pageProductionProductMain(Page page, ProductionProductMainDto productionProductMainDto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇鎶ュ伐涓昏〃
         return listPageProductionProductMainDto(page, productionProductMainDto);
     }
 
     @Override
     public ProductionProductMainDto getProductionProductMainInfo(Long id) {
+        // 鑾峰彇鐢熶骇浜у搧涓昏〃璇︽儏
         return listPageProductionProductMainDto(new Page<>(1, 1), new ProductionProductMainDto() {{
             setId(id);
         }}).getRecords().stream().findFirst().orElse(null);
     }
 
     private void fillOperationParamList(List<ProductionProductMainDto> recordList) {
+        // 濉厖宸ュ簭鍙傛暟鍒楄〃
         if (recordList == null || recordList.isEmpty()) {
             return;
         }
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
         Set<Long> mainIdSet = recordList.stream()
                 .map(ProductionProductMainDto::getId)
                 .filter(Objects::nonNull)
@@ -112,6 +117,7 @@
             return;
         }
 
+        // 鏌ヨ骞跺噯澶囦笟鍔℃暟鎹�
         List<ProductionOrderRoutingOperationParam> paramList = productionOrderRoutingOperationParamMapper.selectList(
                 Wrappers.<ProductionOrderRoutingOperationParam>lambdaQuery()
                         .in(ProductionOrderRoutingOperationParam::getProductionProductMainId, mainIdSet)
@@ -211,6 +217,7 @@
 
     @Override
     public Boolean addProductMain(ProductionProductMainDto dto) {
+        // 鏂板鐢熶骇鎶ュ伐涓昏褰�
         Long taskId = resolveTaskId(dto);
         if (taskId == null) {
             throw new ServiceException("璇蜂紶鍏ョ敓浜у伐鍗旾D");
@@ -220,11 +227,13 @@
 
     @Override
     public Boolean saveProductionProductMain(ProductionProductMainDto productionProductMainDto) {
+        // 淇濆瓨鐢熶骇鎶ュ伐涓昏褰�
         return addProductMain(productionProductMainDto);
     }
 
     @Override
     public Boolean removeProductMain(Long id) {
+        // 鍒犻櫎鐢熶骇鎶ュ伐涓昏褰�
         ProductionProductMain currentMain = productionProductMainMapper.selectById(id);
         if (currentMain == null) {
             return true;
@@ -233,10 +242,10 @@
     }
 
     private Boolean addProductMainByProductionTask(ProductionProductMainDto dto) {
-        // 鎶ュ伐浠ヨ鍗曞伐搴忓揩鐓т负鍑嗭紝閬垮厤宸ヨ壓涓绘暟鎹彉鏇村悗褰卞搷鍘嗗彶宸ュ崟鎵ц銆�
+        // 鎸夌敓浜т换鍔℃柊澧炴姤宸ヤ富璁板綍
         Long taskId = resolveTaskId(dto);
         if (taskId == null) {
-            throw new ServiceException("productionOperationTaskId can not be null");
+            throw new ServiceException("鐢熶骇宸ュ崟ID涓嶈兘涓虹┖");
         }
         SysUser user = userMapper.selectUserById(dto.getUserId());
         ProductionOperationTask productionOperationTask = productionOperationTaskMapper.selectById(taskId);
@@ -266,6 +275,7 @@
         productionProductMain.setUserName(user == null ? dto.getUserName() : user.getNickName());
         productionProductMain.setProductionOperationTaskId(taskId);
         productionProductMain.setStatus(0);
+        productionProductMain.setWorkHour(dto.getWorkHour());
         productionProductMainMapper.insert(productionProductMain);
         syncOperationParamInputValue(dto, routingOperation.getId(), productionProductMain.getId());
 
@@ -522,12 +532,14 @@
     }
 
     private Boolean removeProductMainByProductionTask(ProductionProductMain productionProductMain) {
-        // 鍒犻櫎鎶ュ伐闇�瑕佸悓姝ュ洖婊氳川妫�銆佸簱瀛樸�佸伐鏃舵牳绠楀拰璁㈠崟/宸ュ崟杩涘害銆�
+        // 鎸夌敓浜т换鍔″洖婊氬苟鍒犻櫎鎶ュ伐涓昏褰�
         List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(
                 Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, productionProductMain.getId()));
+        // 鍙傛暟涓庡墠缃潯浠舵牎楠�
         if (qualityInspects.size() > 0) {
             List<QualityUnqualified> qualityUnqualifieds = qualityUnqualifiedMapper.selectList(
                     Wrappers.<QualityUnqualified>lambdaQuery()
+        // 閬嶅巻澶勭悊鏁版嵁骞剁粍瑁呯粨鏋�
                             .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())));
             if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState() == 1) {
                 throw new ServiceException("璇ユ潯鎶ュ伐宸茬粡涓嶅悎鏍煎鐞嗕簡锛屼笉鍏佽鍒犻櫎");
@@ -552,6 +564,7 @@
             } else {
                 productionOperationTask.setStatus(3);
             }
+        // 鎸佷箙鍖栨垨杈撳嚭澶勭悊缁撴灉
             productionOperationTaskMapper.updateById(productionOperationTask);
 
             ProductionOrder productionOrder = productionOrderMapper.selectById(productionOperationTask.getProductionOrderId());
@@ -600,6 +613,7 @@
     }
 
     private String generateProductNo() {
+        // 鐢熸垚涓嬩竴涓敓浜т骇鍝佺紪鍙�
         String datePrefix = "BG" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
         QueryWrapper<ProductionProductMain> queryWrapper = new QueryWrapper<>();
         queryWrapper.select("MAX(product_no) as maxNo").likeRight("product_no", datePrefix);
@@ -622,10 +636,12 @@
     }
 
     private BigDecimal defaultDecimal(BigDecimal value) {
+        // 灏嗙┖鏁伴噺鍏滃簳涓�0锛岄伩鍏嶇┖鎸囬拡寮傚父
         return value == null ? BigDecimal.ZERO : value;
     }
 
     private Long resolveTaskId(ProductionProductMainDto dto) {
+        // 浠庡叆鍙備腑瑙f瀽鐢熶骇宸ュ崟ID骞舵牎楠�
         if (dto == null) {
             return null;
         }
@@ -634,6 +650,7 @@
 
     @Override
     public ArrayList<Long> listMain(List<Long> idList) {
+        // 鏌ヨ涓昏〃ID闆嗗悎
         return productionProductMainMapper.listMain(idList);
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java
index fb2aa26..904c8cc 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java
@@ -17,6 +17,7 @@
 
     @Override
     public IPage<ProductionProductOutputDto> listPageProductionProductOutputDto(Page page, ProductionProductOutputDto productionProductOutputDto) {
+        // 鍒嗛〉鏌ヨ鐢熶骇浜у搧鍑哄簱
         return productionProductOutputMapper.listPageProductionProductOutputDto(page, productionProductOutputDto);
     }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java
index f06284d..e937ba4 100644
--- a/src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java
@@ -15,6 +15,7 @@
 
     @Override
     public UserAccountDto getByUserId(UserProductionAccountingDto dto) {
+        // 鎸夌敤鎴锋煡璇㈢敓浜ф牳绠椾俊鎭�
         if (dto == null || dto.getUserId() == null || dto.getDate() == null || dto.getDate().trim().isEmpty()) {
             return new UserAccountDto();
         }
diff --git a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
index c6b1406..455dc9b 100644
--- a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -48,11 +48,11 @@
      */
     @Excel(name = "渚涘簲鍟嗗悕绉�")
     private String supplierName;
-     /**
-     * 鏄惁鐧藉悕鍗�
-     */
-    @Excel(name = "鏄惁鐧藉悕鍗�")
-    private Integer isWhite;
+//     /**
+//     * 鏄惁鐧藉悕鍗�
+//     */
+//    @Excel(name = "鏄惁鐧藉悕鍗�")
+//    private Integer isWhite;
 
     /**
      * 褰曞叆浜哄鍚峣d
diff --git a/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java
similarity index 79%
rename from src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java
rename to src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java
index fb02943..68ef597 100644
--- a/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderHasAllInfoDto.java
@@ -1,4 +1,4 @@
-package com.ruoyi.purchase.vo;
+package com.ruoyi.purchase.dto;
 
 import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
 import lombok.AllArgsConstructor;
@@ -8,7 +8,7 @@
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
-public class PurchaseReturnOrderVo extends PurchaseReturnOrders {
+public class PurchaseReturnOrderHasAllInfoDto extends PurchaseReturnOrders {
     //渚涘簲鍟嗗悕绉�
     private String supplierName;
 
diff --git a/src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java b/src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java
index a680c12..c6f55b7 100644
--- a/src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java
+++ b/src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java
@@ -5,7 +5,8 @@
 import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
 import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.ruoyi.purchase.vo.PurchaseReturnOrderVo;
+import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
+import jakarta.validation.constraints.NotNull;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -19,5 +20,7 @@
  */
 @Mapper
 public interface PurchaseReturnOrdersMapper extends BaseMapper<PurchaseReturnOrders> {
-    IPage<PurchaseReturnOrderVo> listPage(Page page, @Param("params") PurchaseReturnOrderDto purchaseReturnOrder);
+    IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, @Param("params") PurchaseReturnOrderDto purchaseReturnOrder);
+
+    PurchaseReturnOrderHasAllInfoDto getPurchaseReturnOrderHasAllInfoById(@Param("id") @NotNull Long id);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java b/src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
index dc68108..2038e09 100644
--- a/src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
+++ b/src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java
@@ -6,7 +6,7 @@
 import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
-import com.ruoyi.purchase.vo.PurchaseReturnOrderVo;
+import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
 
 import jakarta.validation.constraints.NotNull;
 
@@ -19,7 +19,7 @@
  * @since 2026-03-06 11:44:38
  */
 public interface PurchaseReturnOrdersService extends IService<PurchaseReturnOrders> {
-    IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto);
+    IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto);
 
     Boolean add(PurchaseReturnOrderDto purchaseReturnOrderDto);
 
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
index c4a48cd..cdf80f9 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -132,6 +132,8 @@
     @Transactional(rollbackFor = Exception.class)
     public int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception {
         PurchaseLedger purchaseLedger = new PurchaseLedger();
+        // DTO杞珽ntity
+        BeanUtils.copyProperties(purchaseLedgerDto, purchaseLedger);
         SalesLedger salesLedger = salesLedgerMapper.selectById(purchaseLedgerDto.getSalesLedgerId());
         //褰曞叆浜�
         SysUser sysUser = userMapper.selectUserById(purchaseLedgerDto.getRecorderId());
@@ -146,9 +148,6 @@
 
         SupplierManage supplierManage = supplierManageMapper.selectById(purchaseLedgerDto.getSupplierId());
 
-        // DTO杞珽ntity
-
-        BeanUtils.copyProperties(purchaseLedgerDto, purchaseLedger);
         LoginUser loginUser = SecurityUtils.getLoginUser();
         if (ObjectUtils.isNotEmpty(loginUser) && null != loginUser.getTenantId()) {
             purchaseLedger.setTenantId(loginUser.getTenantId());
@@ -174,23 +173,14 @@
             }
             purchaseLedgerMapper.updateById(purchaseLedger);
         }
-        // 6.閲囪喘瀹℃牳鏂板锛涘鎵圭鐞嗘湭閰嶇疆閲囪喘瀹℃壒浜烘椂锛屽鎵规湇鍔′細鑷姩缃负瀹℃壒閫氳繃銆�
-        addApproveByPurchase(loginUser, purchaseLedger);
 
         // 4. 澶勭悊瀛愯〃鏁版嵁
         List<SalesLedgerProduct> productList = purchaseLedgerDto.getProductData();
         if (productList != null && !productList.isEmpty()) {
             handleSalesLedgerProducts(purchaseLedger.getId(), productList, purchaseLedgerDto.getType());
         }
-        //鏂板鍘熸潗鏂欐楠�  瀹℃壒涔嬪悗鎵嶇敓鎴愭楠�
-//        if (productList != null) {
-//            for (SalesLedgerProduct saleProduct : productList) {
-//                //鏄惁鎺ㄩ�佽川妫�锛屽鏋渢rue灏辨坊鍔�
-//                if (saleProduct.getIsChecked()) {
-//                    addQualityInspect(purchaseLedger, saleProduct);
-//                }
-//            }
-//        }
+        // 6.閲囪喘瀹℃牳鏂板锛涘鎵圭鐞嗘湭閰嶇疆閲囪喘瀹℃壒浜烘椂锛屽鎵规湇鍔′細鑷姩缃负瀹℃壒閫氳繃銆�
+        addApproveByPurchase(loginUser, purchaseLedger);
         // 5. 杩佺Щ涓存椂鏂囦欢鍒版寮忕洰褰�
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_LEDGER, purchaseLedger.getId(), purchaseLedgerDto.getStorageBlobDTOS());
         return 1;
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
index e75e986..b5003ae 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java
@@ -4,25 +4,33 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.account.service.AccountIncomeService;
 import com.ruoyi.common.enums.SaleEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
 import com.ruoyi.purchase.dto.PurchaseReturnOrderProductsDto;
+import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
 import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
 import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.purchase.pojo.PurchaseReturnOrderProducts;
 import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
 import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
 import com.ruoyi.purchase.vo.PurchaseReturnDetailsVo;
-import com.ruoyi.purchase.vo.PurchaseReturnOrderVo;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.service.ISalesLedgerService;
+import com.ruoyi.stock.mapper.StockOutRecordMapper;
+import com.ruoyi.stock.pojo.StockOutRecord;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -48,9 +56,13 @@
     private final PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
     private final ISalesLedgerService salesLedgerService;
     private final AccountIncomeService accountIncomeService;
+    private final StockUtils stockUtils;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final PurchaseLedgerMapper purchaseLedgerMapper;
+    private final StockOutRecordMapper stockOutRecordMapper;
 
     @Override
-    public IPage<PurchaseReturnOrderVo> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto) {
+    public IPage<PurchaseReturnOrderHasAllInfoDto> listPage(Page page, PurchaseReturnOrderDto purchaseReturnOrderDto) {
         return purchaseReturnOrdersMapper.listPage(page, purchaseReturnOrderDto);
     }
 
@@ -67,6 +79,10 @@
                 // 杩欓噷涓烘柊澧炲洜姝d涓簄ull
                 purchaseReturnOrderProductsDto.setId(null);
                 purchaseReturnOrderProductsMapper.insert(purchaseReturnOrderProductsDto);
+                //搴撳瓨闇�瑕佸嚭搴�(閲囪喘閫�璐�)
+                PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(purchaseReturnOrderDto.getPurchaseLedgerId());
+                SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(purchaseReturnOrderProductsDto.getSalesLedgerProductId());
+                stockUtils.substractStock(salesLedgerProduct.getProductModelId(), purchaseReturnOrderProductsDto.getReturnQuantity(), StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode(), purchaseReturnOrderDto.getId(), purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
             }
         }else {
             throw new RuntimeException("璇烽�夋嫨閫�璐у晢鍝�");
@@ -91,7 +107,7 @@
 
     @Override
     public PurchaseReturnDetailsVo getPurchaseReturnOrderDtoById(Long id) {
-        PurchaseReturnOrders purchaseReturnOrders = purchaseReturnOrdersMapper.selectById(id);
+        PurchaseReturnOrderHasAllInfoDto purchaseReturnOrders = purchaseReturnOrdersMapper.getPurchaseReturnOrderHasAllInfoById(id);
         PurchaseReturnDetailsVo purchaseReturnOrderDto = BeanUtil.copyProperties(purchaseReturnOrders, PurchaseReturnDetailsVo.class);
         // 鏌ヨ鍑轰粬鍏蜂綋瀵瑰簲鐨勯��璐�
         LambdaQueryWrapper<PurchaseReturnOrderProducts> queryWrapper = new LambdaQueryWrapper<>();
@@ -120,7 +136,10 @@
         LambdaUpdateWrapper<PurchaseReturnOrderProducts> updateWrapper = new LambdaUpdateWrapper<>();
         updateWrapper.eq(PurchaseReturnOrderProducts::getPurchaseReturnOrderId, id);
         purchaseReturnOrderProductsMapper.delete(updateWrapper);
-
+        //(閲囪喘閫�璐х殑鏁版嵁闇�瑕佸垹鎺�)
+        stockOutRecordMapper.delete(Wrappers.<StockOutRecord>lambdaQuery()
+                .eq(StockOutRecord::getRecordType,StockOutQualifiedRecordTypeEnum.PURCHASE_RETURN_STOCK_OUT.getCode())
+                .eq(StockOutRecord::getRecordId, id));
         // 璐㈠姟
         LambdaUpdateWrapper<AccountIncome> updateWrapperAccountIncome = new LambdaUpdateWrapper<>();
         updateWrapperAccountIncome.eq(AccountIncome::getBusinessId, id);
diff --git a/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java b/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
index 02317e6..975cd5c 100644
--- a/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
+++ b/src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java
@@ -1,6 +1,6 @@
 package com.ruoyi.purchase.vo;
 
-import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
+import com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -20,7 +20,7 @@
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
-public class PurchaseReturnDetailsVo extends PurchaseReturnOrders implements Serializable {
+public class PurchaseReturnDetailsVo extends PurchaseReturnOrderHasAllInfoDto implements Serializable {
 
     private List<PurchaseReturnOrderProductsDetailVo> purchaseReturnOrderProductsDetailVoList;
 
diff --git a/src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java b/src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
index 411291c..a4c1106 100644
--- a/src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
+++ b/src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -71,7 +71,6 @@
         if (CollUtil.isEmpty(list)) {
             return AjaxResult.success(list);
         }
-        //
         List<Long> productIds = list.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
         List<SimpleReturnOrderGroupDto> groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
         Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, item -> item.getSumReturnQuantity()));
@@ -83,13 +82,6 @@
             if (item.getFutureTicketsAmount().compareTo(BigDecimal.ZERO) == 0) {
                 item.setFutureTicketsAmount(BigDecimal.ZERO);
             }
-//            ProcurementPageDto procurementDto = new ProcurementPageDto();
-//            procurementDto.setSalesLedgerProductId(item.getId());
-//            procurementDto.setProductCategory(item.getProductCategory());
-//            IPage<ProcurementPageDtoCopy> result = procurementRecordService.listPageCopyByProduction(new Page<>(1,-1), procurementDto);
-//            BigDecimal stockQuantity = stockUtils.getStockQuantity(item.getProductModelId()).get("stockQuantity");
-
-//                ProcurementPageDtoCopy procurementDtoCopy = result.getRecords().get(0);
             if (item.getApproveStatus() != 2) {
                 if (item.getHasSufficientStock() == 0) {
                     item.setApproveStatus(0);
diff --git a/src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java b/src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java
deleted file mode 100644
index 8c95270..0000000
--- a/src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.ruoyi.sales.controller;
-
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
-import com.ruoyi.common.exception.ServiceException;
-import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.framework.web.controller.BaseController;
-import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.procurementrecord.utils.StockUtils;
-import com.ruoyi.sales.mapper.ShipmentApprovalMapper;
-import com.ruoyi.sales.pojo.SalesLedgerProduct;
-import com.ruoyi.sales.pojo.ShipmentApproval;
-import com.ruoyi.sales.service.ISalesLedgerProductService;
-import com.ruoyi.sales.service.ShipmentApprovalService;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Operation;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.AllArgsConstructor;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/shipmentApproval")
-@Tag(name = "鍙戣揣瀹℃壒绠$悊")
-@AllArgsConstructor
-public class ShipmentApprovalController extends BaseController {
-
-    private final ShipmentApprovalService shipmentApprovalService;
-    private final ShipmentApprovalMapper shipmentApprovalMapper;
-    private final ISalesLedgerProductService salesLedgerProductService;
-    private final StockUtils stockUtils;
-
-    @GetMapping("/listPage")
-    @Operation(summary = "鍙戣揣瀹℃壒鍒楄〃")
-    public AjaxResult listPage(Page page, ShipmentApproval req) {
-        IPage<ShipmentApproval> listPage = shipmentApprovalService.listPage(page,req);
-        return AjaxResult.success(listPage);
-    }
-
-    @PostMapping("/update")
-    @Operation(summary = "鍙戣揣瀹℃壒,鏇存柊鍙戣揣瀹℃壒鐘舵��")
-    @Transactional(rollbackFor = Exception.class)
-    public AjaxResult update(@RequestBody ShipmentApproval req) {
-
-        //  鏌ヨ鍙戣揣瀹℃壒
-        ShipmentApproval shipmentApproval = shipmentApprovalMapper.selectById(req.getId());
-        if (shipmentApproval == null) {
-            return AjaxResult.error("鍙戣揣瀹℃壒涓嶅瓨鍦�");
-        }
-
-        //  鏇存柊鍙戣揣瀹℃壒鐘舵��
-        shipmentApproval.setApproveStatus(req.getApproveStatus());
-        boolean update = shipmentApprovalService.updateById(shipmentApproval);
-        if (!update) {
-            //  浜嬪姟鍥炴粴
-            throw new ServiceException("鍙戣揣瀹℃壒鏇存柊澶辫触");
-        }
-        //  鏌ヨ鍏宠仈鐨勯攢鍞彴璐︿骇鍝�
-        SalesLedgerProduct salesLedgerProduct = salesLedgerProductService.getById(shipmentApproval.getSalesLedgerProductId());
-        if (salesLedgerProduct == null) {
-            //  鎶涘紓甯镐簨鍔″洖婊�
-            throw new ServiceException("閿�鍞彴璐︿笉瀛樺湪锛屽鎵瑰洖婊�");
-        }
-
-        //  鍚屾鏇存柊閿�鍞彴璐︿骇鍝佺殑瀹℃壒鐘舵��
-        salesLedgerProduct.setApproveStatus(req.getApproveStatus());
-        salesLedgerProductService.updateById(salesLedgerProduct);
-
-        //  瀹℃壒閫氳繃
-        if (req.getApproveStatus() == 3) {
-//            // 鏌ヨ閲囪喘鍏ュ簱璁板綍
-//            LambdaQueryWrapper<ProcurementRecordStorage> lambdaQueryWrapper = new LambdaQueryWrapper<ProcurementRecordStorage>()
-//                    .eq(ProcurementRecordStorage::getSalesLedgerProductId, req.getSalesLedgerProductId());
-//            ProcurementRecordStorage procurementRecordStorage = procurementRecordStorageService.getOne(lambdaQueryWrapper);
-//
-//            if (procurementRecordStorage == null) {
-//                // 淇濊瘉鍓嶉潰鐨勪慨鏀瑰叏閮ㄥ洖婊�
-//                throw new ServiceException("閲囪喘璁板綍涓嶅瓨鍦紝瀹℃壒鍥炴粴");
-//            }
-
-
-            //鍑哄簱
-            stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
-        }
-
-        return AjaxResult.success();
-    }
-
-
-
-    /**
-     * 瀵煎嚭鍙戣揣淇℃伅绠$悊
-     */
-    @PostMapping("/export")
-    @Operation(summary = "瀵煎嚭鍙戣揣瀹℃壒")
-    public void export(HttpServletResponse response) {
-        List<ShipmentApproval> list = shipmentApprovalService.list();
-        ExcelUtil<ShipmentApproval> util = new ExcelUtil<ShipmentApproval>(ShipmentApproval.class);
-        util.exportExcel(response, list, "鍙戣揣瀹℃壒");
-    }
-
-}
diff --git a/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java b/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
index 458be2d..0257f2a 100644
--- a/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
+++ b/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
@@ -61,7 +61,7 @@
         ApproveProcessVO approveProcessVO = new ApproveProcessVO();
         approveProcessVO.setApproveType(7);
         approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
-        approveProcessVO.setApproveReason(req.getType() + ":" +sh);
+        approveProcessVO.setApproveReason(sh);//鍙戣揣缂栧彿
         approveProcessVO.setApproveUserIds(req.getApproveUserIds());
         approveProcessVO.setApproveUser(loginUser.getUserId());
         approveProcessVO.setApproveTime(LocalDate.now().toString());
@@ -122,7 +122,14 @@
     }
 
     @GetMapping("/getDateil/{id}")
+    @Operation(summary = "閫氳繃id鏌ヨ璇︽儏")
     public R getDateil(@PathVariable("id") Long id) {
         return R.ok(shippingInfoService.getDetail(id));
     }
+
+    @GetMapping("/getDateilByShippingNo")
+    @Operation(summary = "閫氳繃鍙戣揣鍗曞彿鏌ヨ璇︽儏")
+    public R getDateilByShippingNo(String shippingNo) {
+        return R.ok(shippingInfoService.getDateilByShippingNo(shippingNo));
+    }
 }
diff --git a/src/main/java/com/ruoyi/sales/dto/ShippingApproveDto.java b/src/main/java/com/ruoyi/sales/dto/ShippingApproveDto.java
new file mode 100644
index 0000000..693016b
--- /dev/null
+++ b/src/main/java/com/ruoyi/sales/dto/ShippingApproveDto.java
@@ -0,0 +1,15 @@
+package com.ruoyi.sales.dto;
+
+import com.ruoyi.sales.pojo.ShippingInfo;
+import lombok.Data;
+
+import java.util.List;
+
+//鍙戣揣瀹℃壒鏌ョ湅璇︽儏
+@Data
+public class ShippingApproveDto {
+
+    private ShippingInfo shippingInfo;
+
+    private List<ShippingProductDetailDto> shippingProductDetailDtoList;
+}
diff --git a/src/main/java/com/ruoyi/sales/mapper/ShipmentApprovalMapper.java b/src/main/java/com/ruoyi/sales/mapper/ShipmentApprovalMapper.java
deleted file mode 100644
index c3bf397..0000000
--- a/src/main/java/com/ruoyi/sales/mapper/ShipmentApprovalMapper.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.ruoyi.sales.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.sales.pojo.ShipmentApproval;
-import com.ruoyi.sales.pojo.ShippingInfo;
-import org.apache.ibatis.annotations.Param;
-
-import java.util.List;
-
-
-public interface ShipmentApprovalMapper extends BaseMapper<ShipmentApproval> {
-    IPage<ShipmentApproval> listPage(Page page,@Param("req") ShipmentApproval req);
-
-}
diff --git a/src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java b/src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java
index 5c7a5bf..57b142d 100644
--- a/src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java
@@ -3,10 +3,11 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.bean.dto.SalesOutboundDto;
+import com.ruoyi.account.bean.vo.SalesOutboundVo;
 import com.ruoyi.sales.dto.SalesLedgerProductDto;
 import com.ruoyi.sales.dto.ShippingInfoDto;
 import com.ruoyi.sales.pojo.ShippingInfo;
-import com.ruoyi.sales.pojo.ShippingProductDetail;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -24,5 +25,5 @@
 
     List<ShippingInfo> getShippingInfoByCustomerName(String customerName);
 
-    List<ShippingProductDetail> getDateil(Long id);
+    IPage<SalesOutboundVo> listPageByOutbound(Page page, @Param("req") SalesOutboundDto salesOutboundDto);
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java b/src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java
index f564ba1..a679e0e 100644
--- a/src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java
@@ -4,6 +4,7 @@
 import com.ruoyi.sales.dto.ShippingProductDetailDto;
 import com.ruoyi.sales.pojo.ShippingProductDetail;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
 
@@ -19,4 +20,6 @@
 public interface ShippingProductDetailMapper extends BaseMapper<ShippingProductDetail> {
 
     List<ShippingProductDetailDto> getDetail(Long id);
+
+    List<ShippingProductDetailDto> getDateilByShippingNo(@Param("shippingNo") String shippingNo);
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java b/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
index c909871..fa528a9 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -257,5 +257,6 @@
     private Boolean isProduction;
 
     @TableField(exist = false)
+    @Schema(description = "寰呭彂璐ф暟閲�")
     private BigDecimal noQuantity;
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java b/src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java
deleted file mode 100644
index 6399728..0000000
--- a/src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java
+++ /dev/null
@@ -1,355 +0,0 @@
-package com.ruoyi.sales.pojo;
-
-import com.baomidou.mybatisplus.annotation.*;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.ruoyi.framework.aspectj.lang.annotation.Excel;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.Date;
-import java.util.List;
-
-@Data
-@TableName("shipment_approval")
-public class ShipmentApproval {
-    @TableId(type = IdType.AUTO)
-    private Long id;
-    @Schema(description = "鍙戣揣淇℃伅id")
-    private Long shippingInfoId;
-    @Schema(description = "閿�鍞彴璐d")
-    private Long salesLedgerId;
-    @Schema(description = "閿�鍞姤浠蜂骇鍝佽〃id")
-    private Long salesLedgerProductId;
-     @Schema(description = "鐢宠閮ㄩ棬id")
-    private Long approveDeptId;
-
-    @Schema(description = "鐢宠閮ㄩ棬鍚嶇О")
-    @Excel(name = "鐢宠閮ㄩ棬")
-    private String approveDeptName;
-     @Schema(description = "瀹℃壒鐢ㄦ埛id")
-    private Integer approveUserId;
-    @Schema(description = "瀹℃壒鐢ㄦ埛鍚嶇О")
-    @Excel(name = "瀹℃壒鐢ㄦ埛")
-    private String approveUserNames;
-
-    /**
-     * 瀹℃壒鐘舵��
-     */
-    @Schema(description = "瀹℃壒鐘舵�侊細0鏈嚭搴�,1宸插嚭搴�,2寰呭鏍�,3瀹℃牳瀹屾垚,4瀹℃牳澶辫触")
-    @Excel(name = "瀹℃壒鐘舵��", readConverterExp = "0=鏈嚭搴�,1=宸插嚭搴�,2=寰呭鏍�,3=瀹℃牳瀹屾垚,4=瀹℃牳澶辫触")
-    private Integer approveStatus;
-
-    @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
-    private LocalDateTime createTime;
-
-    @Schema(description = "淇敼鏃堕棿")
-    @TableField(fill = FieldFill.INSERT_UPDATE)
-    private LocalDateTime updateTime;
-
-    @Schema(description = "鍒涘缓鐢ㄦ埛")
-    @TableField(fill = FieldFill.INSERT)
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Integer createUser;
-
-    @Schema(description = "淇敼鐢ㄦ埛")
-    @TableField(fill = FieldFill.INSERT_UPDATE)
-    private Integer updateUser;
-
-    @Schema(description = "绉熸埛ID")
-    @TableField(fill = FieldFill.INSERT)
-    private Long tenantId;
-
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "鍙戣揣鏃ユ湡", width = 30, dateFormat = "yyyy-MM-dd")
-    @TableField(exist = false)
-    private Date shippingDate;
-
-    @Excel(name = "鍙戣揣杞︾墝鍙�")
-    @TableField(exist = false)
-    private String shippingCarNumber;
-
-    /**
-     * 棰勮鏁伴噺
-     */
-
-    @TableField(exist = false)
-    private BigDecimal warnNum;
-
-    /**
-     * 浜у搧澶х被
-     */
-    @Excel(name = "浜у搧澶х被")
-    @TableField(exist = false)
-    private String productCategory;
-
-    /**
-     * 瑙勬牸鍨嬪彿
-     */
-    @Excel(name = "瑙勬牸鍨嬪彿")
-    @TableField(exist = false)
-    private String specificationModel;
-
-    /**
-     * 鍗曚綅
-     */
-    @Excel(name = "鍗曚綅")
-    @TableField(exist = false)
-    private String unit;
-
-    /**
-     * 鏁伴噺
-     */
-    @Excel(name = "鏁伴噺")
-    @TableField(exist = false)
-    private BigDecimal quantity;
-    @Excel(name = "鏈�浣庡簱瀛樻暟閲�")
-    @TableField(exist = false)
-    private BigDecimal minStock;
-    /**
-     * 绋庣巼
-     */
-    @Excel(name = "绋庣巼")
-    @TableField(exist = false)
-    private BigDecimal taxRate;
-
-    /**
-     * 鍚◣鍗曚环
-     */
-    @Excel(name = "鍚◣鍗曚环")
-    @TableField(exist = false)
-    private BigDecimal taxInclusiveUnitPrice;
-
-    /**
-     * 鍚◣鎬讳环
-     */
-    @Excel(name = "鍚◣鎬讳环")
-    @TableField(exist = false)
-    private BigDecimal taxInclusiveTotalPrice;
-
-    /**
-     * 涓嶅惈绋庢�讳环
-     */
-    @Excel(name = "涓嶅惈绋庢�讳环")
-    @TableField(exist = false)
-    private BigDecimal taxExclusiveTotalPrice;
-
-    /**
-     * 鍙戠エ绫诲瀷
-     */
-
-    @TableField(exist = false)
-    private String invoiceType;
-
-    /**
-     * 鍙拌处绫诲瀷 1.閿�鍞� 2锛岄噰璐�
-     */
-    @TableField(exist = false)
-    private Integer type;
-
-    /**
-     * 鏈鏉ョエ鏁�
-     */
-    @TableField(exist = false)
-    private BigDecimal ticketsNum;
-
-    /**
-     * 鏈鏉ョエ閲戦(鍏�)
-     */
-    @TableField(exist = false)
-    private BigDecimal ticketsAmount;
-
-    /**
-     * 鏈潵绁ㄦ暟
-     */
-    @TableField(exist = false)
-    private BigDecimal futureTickets;
-
-    /**
-     * 鏈潵绁ㄩ噾棰�(鍏�)
-     */
-    @TableField(exist = false)
-    private BigDecimal futureTicketsAmount;
-
-    @Schema(description = "寮�绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal invoiceNum;
-
-    @Schema(description = "鏈紑绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal noInvoiceNum;
-
-    @Schema(description = "寮�绁ㄩ噾棰�")
-    @TableField(exist = false)
-    private BigDecimal invoiceAmount;
-
-    @Schema(description = "鏈紑绁ㄩ噾棰�")
-    @TableField(exist = false)
-    private BigDecimal noInvoiceAmount;
-
-    @Schema(description = "鏈寮�绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal currentInvoiceNum;
-
-    @TableField(exist = false)
-    @Schema(description = "鏈寮�绁ㄩ噾棰�")
-    private BigDecimal currentInvoiceAmount;
-
-    /**
-     *  浜у搧id
-     */
-    @TableField(exist = false)
-    private Long productId;
-
-    /**
-     * 浜у搧瑙勬牸id
-     */
-    @TableField(exist = false)
-    private Long productModelId;
-
-    @Schema(description = "鍒濆鏈紑绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal originalNoInvoiceNum;
-
-    @Schema(description = "涓存椂鏈紑绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal tempNoInvoiceNum;
-
-    @Schema(description = "涓存椂鏈紑绁ㄩ噾棰�")
-    @TableField(exist = false)
-    private BigDecimal tempnoInvoiceAmount;
-
-    @Schema(description = "涓存椂鏈潵绁ㄦ暟")
-    @TableField(exist = false)
-    private BigDecimal tempFutureTickets;
-
-    @Schema(description = "涓存椂鏈潵绁ㄩ噾棰�")
-    @TableField(exist = false)
-    private BigDecimal tempFutureTicketsAmount;
-
-    @Schema(description = "鐧昏浜�")
-    @TableField(exist = false)
-    private String register;
-
-    @Schema(description = "鐧昏鏃ユ湡")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    @Excel(name = "鐧昏鏃ユ湡", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
-    @TableField(exist = false)
-    private LocalDateTime registerDate;
-    /**
-     * 閿�鍞悎鍚屽彿
-     */
-    @Excel(name = "閿�鍞悎鍚屽彿")
-    @TableField(exist = false)
-    private String salesContractNo;
-
-    /**
-     * 瀹㈡埛鍚堝悓鍙�
-     */
-    @Excel(name = "瀹㈡埛鍚堝悓鍙�")
-    @TableField(exist = false)
-    private String customerContractNo;
-
-
-    /**
-     * 椤圭洰鍚嶇О
-     */
-    @Excel(name = "椤圭洰鍚嶇О")
-    @TableField(exist = false)
-    private String projectName;
-
-    /**
-     * 褰曞叆鏃ユ湡
-     */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @TableField(exist = false)
-    private Date entryDate;
-
-    /**
-     * 涓氬姟鍛�
-     */
-    @Excel(name = "涓氬姟鍛�")
-    @TableField(exist = false)
-    private String salesman;
-
-    @TableField(exist = false)
-    private Long customerId;
-
-    /**
-     * 瀹㈡埛鍚嶇О
-     */
-    @Excel(name = "瀹㈡埛鍚嶇О")
-    @TableField(exist = false)
-    private String customerName;
-
-    /**
-     * 褰曞叆浜�
-     */
-    @TableField(exist = false)
-    private String entryPerson;
-
-    @TableField(exist = false)
-    @Schema(description = "褰曞叆浜�")
-    @Excel(name = "褰曞叆浜�")
-    private String entryPersonName;
-
-    /**
-     * 澶囨敞
-     */
-    @Excel(name = "澶囨敞")
-    @TableField(exist = false)
-    private String remarks;
-
-    /**
-     * 闄勪欢鏉愭枡锛屽瓨鍌ㄦ枃浠跺悕绛夌浉鍏充俊鎭�
-     */
-    @TableField(exist = false)
-    private String attachmentMaterials;
-
-
-    /**
-     * 鍚堝悓閲戦锛堜骇鍝佸惈绋庢�讳环锛�
-     */
-    @Excel(name = "鍚堝悓閲戦")
-    @TableField(exist = false)
-    private BigDecimal contractAmount;
-
-    @TableField(exist = false)
-    @Schema(description = "鏈紑绁ㄩ噾棰�(鍏�)")
-    @Excel(name = "鏈紑绁ㄩ噾棰�")
-    private BigDecimal noInvoiceAmountTotal = BigDecimal.ZERO;
-
-    @Schema(description = "绛捐鏃ユ湡")
-    @TableField(exist = false)
-    private LocalDate executionDate;
-
-    @TableField(exist = false)
-    @Schema(description = "宸插紑绁ㄩ噾棰�(鍏�)")
-    @Excel(name = "宸插紑绁ㄩ噾棰�")
-    private BigDecimal invoiceTotal = BigDecimal.ZERO;
-
-    @TableField(exist = false)
-    @Schema(description = "鍥炴閲戦")
-    private BigDecimal receiptPaymentAmountTotal = BigDecimal.ZERO;
-
-    @TableField(exist = false)
-    @Schema(description = "寰呭洖娆鹃噾棰�")
-    private BigDecimal noReceiptAmount = BigDecimal.ZERO;
-
-    @Schema(description = "浠樻鏂瑰紡")
-    @TableField(exist = false)
-    private String paymentMethod;
-
-    @TableField(exist = false)
-    @Schema(description = "鐢熶骇鐘舵��")
-    private String productionStatus = "鏈紑濮�";
-
-    @TableField(fill = FieldFill.INSERT)
-    private Long deptId;
-}
diff --git a/src/main/java/com/ruoyi/sales/service/ShipmentApprovalService.java b/src/main/java/com/ruoyi/sales/service/ShipmentApprovalService.java
deleted file mode 100644
index 6af2c00..0000000
--- a/src/main/java/com/ruoyi/sales/service/ShipmentApprovalService.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.ruoyi.sales.service;
-
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.ruoyi.sales.pojo.ShipmentApproval;
-import com.ruoyi.sales.pojo.ShippingInfo;
-
-
-public interface ShipmentApprovalService extends IService<ShipmentApproval>{
-    IPage<ShipmentApproval> listPage(Page page, ShipmentApproval req);
-
-}
diff --git a/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java b/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
index c3e8a0c..69e8245 100644
--- a/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
+++ b/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.sales.dto.SalesLedgerProductDto;
+import com.ruoyi.sales.dto.ShippingApproveDto;
 import com.ruoyi.sales.dto.ShippingInfoDto;
 import com.ruoyi.sales.dto.ShippingProductDetailDto;
 import com.ruoyi.sales.pojo.ShippingInfo;
@@ -28,4 +29,6 @@
     boolean add(ShippingInfoDto req);
 
     List<ShippingProductDetailDto> getDetail(Long id);
+
+    ShippingApproveDto getDateilByShippingNo(String shippingNo);
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
index 37e06bb..8ce023b 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -101,9 +101,6 @@
 
     @Override
     public List<SalesLedgerProduct> selectSalesLedgerProductList(SalesLedgerProduct salesLedgerProduct) {
-//        LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
-//        queryWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerProduct.getSalesLedgerId())
-//                .eq(SalesLedgerProduct::getType, salesLedgerProduct.getType());
         List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectSalesLedgerProductList(salesLedgerProduct);
         if(!CollectionUtils.isEmpty(salesLedgerProducts)){
             salesLedgerProducts.forEach(item -> {
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 1ec6a8d..f06cad0 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -61,6 +61,8 @@
     public boolean add(SalesQuotationDto salesQuotationDto) {
         LoginUser loginUser = SecurityUtils.getLoginUser();
         SalesQuotation salesQuotation = new SalesQuotation();
+        BeanUtils.copyProperties(salesQuotationDto, salesQuotation);
+        salesQuotation.setId(null);
         Customer customer = customerMapper.selectById(Long.valueOf(salesQuotationDto.getCustomerId()));
         if (ObjectUtils.isNotEmpty(customer))  {
             salesQuotation.setCustomer(customer.getCustomerName());
diff --git a/src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java
deleted file mode 100644
index 9c358e7..0000000
--- a/src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.ruoyi.sales.service.impl;
-
-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.sales.mapper.ShipmentApprovalMapper;
-import com.ruoyi.sales.pojo.ShipmentApproval;
-import com.ruoyi.sales.service.ShipmentApprovalService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-/**
- * @author :yys
- * @date : 2025/10/22 9:33
- */
-@Service
-@Slf4j
-@RequiredArgsConstructor
-public class ShipmentApprovalServiceImpl extends ServiceImpl<ShipmentApprovalMapper, ShipmentApproval> implements ShipmentApprovalService {
-
-    private final ShipmentApprovalMapper shipmentApprovalMapper;
-
-    @Override
-    public IPage<ShipmentApproval> listPage(Page page, ShipmentApproval req) {
-        IPage<ShipmentApproval> listPage = shipmentApprovalMapper.listPage(page, req);
-        return listPage;
-    }
-}
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 545ac7d..ba17fe9 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -13,6 +13,7 @@
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 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;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
@@ -68,7 +69,6 @@
         }
         //鎵e噺搴撳瓨
         if(!"宸插彂璐�".equals(byId.getStatus())){
-//            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId());
             List<ShippingProductDetail> shippingProductDetails = shippingProductDetailMapper.selectList(new LambdaQueryWrapper<ShippingProductDetail>().eq(ShippingProductDetail::getShippingInfoId, req.getId()));
             if (CollectionUtils.isEmpty(shippingProductDetails)) {
                 throw new RuntimeException("鍙戣揣淇℃伅涓嶅瓨鍦�");
@@ -141,4 +141,15 @@
     public List<ShippingProductDetailDto> getDetail(Long id) {
         return shippingProductDetailMapper.getDetail(id);
     }
+
+    @Override
+    public ShippingApproveDto getDateilByShippingNo(String shippingNo) {
+        ShippingApproveDto shippingApproveDto = new ShippingApproveDto();
+        ShippingInfo shippingInfo = new ShippingInfo();
+        shippingInfo.setShippingNo(shippingNo);
+        shippingApproveDto.setShippingInfo(shippingInfoMapper.listPage(new Page(1, -1),shippingInfo).getRecords().get(0));
+        List<ShippingProductDetailDto> dateilByShippingNo = shippingProductDetailMapper.getDateilByShippingNo(shippingNo);
+        shippingApproveDto.setShippingProductDetailDtoList(dateilByShippingNo);
+        return shippingApproveDto;
+    }
 }
diff --git a/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java b/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
index 3378f80..ab27019 100644
--- a/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
+++ b/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
@@ -6,19 +6,18 @@
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.staff.dto.StaffOnJobDto;
+import com.ruoyi.staff.dto.StaffOnJobExcelDto;
 import com.ruoyi.staff.pojo.StaffContract;
 import com.ruoyi.staff.pojo.StaffOnJob;
 import com.ruoyi.staff.service.IStaffOnJobService;
-import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
-import com.ruoyi.staff.dto.StaffOnJobExcelDto;
 
-import jakarta.annotation.Resource;
-import jakarta.servlet.http.HttpServletResponse;
-import jakarta.validation.Valid;
 import java.util.List;
 
 /**
@@ -103,7 +102,7 @@
      * @return
      */
     @PostMapping("/renewContract/{id}")
-    public AjaxResult renewContract(@PathVariable("id") Long id, @RequestBody StaffContract staffContract) {
+    public AjaxResult renewContract(@PathVariable Long id, @RequestBody StaffContract staffContract) {
         return AjaxResult.success(staffOnJobService.renewContract(id, staffContract));
     }
 
diff --git a/src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java b/src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
index 061169b..1915946 100644
--- a/src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
+++ b/src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java
@@ -49,7 +49,7 @@
     @Schema(description = "鏄惁璐ㄦ")
     private Boolean isQuality;
 
-    @Schema(description = "绫诲瀷 鍖哄垎璁℃椂鍜岃浠�")
+    @Schema(description = "绫诲瀷 鍖哄垎璁℃椂鍜岃浠讹紝0璁℃椂锛�1璁′欢")
     private Integer type;
 
     @Schema(description = "璁惧id")
diff --git a/src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java b/src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java
index f91f1e1..93ae801 100644
--- a/src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java
+++ b/src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java
@@ -58,4 +58,7 @@
     @Schema(description = "閮ㄩ棬ID")
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    @Schema(description = "绫诲瀷 鍖哄垎璁℃椂鍜岃浠讹紝0璁℃椂锛�1璁′欢")
+    private Integer type;
 }
diff --git a/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java b/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
index 2a4cd1d..bac4f37 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
@@ -108,12 +108,12 @@
     @Transactional(rollbackFor = Exception.class)
     public R update(TechnologyBom technologyBom) {
         if (technologyBom.getId() == null) {
-            throw new ServiceException("BOM id is required");
+            throw new ServiceException("BOM ID涓嶈兘涓虹┖");
         }
         validateProductModel(technologyBom.getProductModelId());
         TechnologyBom oldBom = technologyBomMapper.selectById(technologyBom.getId());
         if (oldBom == null) {
-            throw new ServiceException("BOM not found");
+            throw new ServiceException("BOM涓嶅瓨鍦�");
         }
         if (oldBom.getProductModelId() != null && !oldBom.getProductModelId().equals(technologyBom.getProductModelId())) {
             technologyRoutingMapper.updateProductModelByBomId(technologyBom.getProductModelId(), technologyBom.getId().longValue());
@@ -135,12 +135,12 @@
     @Transactional(rollbackFor = Exception.class)
     public boolean batchDelete(List<Long> ids) {
         if (ids == null || ids.isEmpty()) {
-            throw new ServiceException("Select at least one BOM");
+            throw new ServiceException("璇疯嚦灏戦�夋嫨涓�涓狟OM");
         }
         List<TechnologyRouting> list = technologyRoutingMapper.selectList(Wrappers.<TechnologyRouting>lambdaQuery()
                 .in(TechnologyRouting::getBomId, ids));
         if (!list.isEmpty()) {
-            throw new ServiceException("BOM is referenced by routing");
+            throw new ServiceException("BOM宸茶宸ヨ壓璺嚎寮曠敤锛屼笉鑳藉垹闄�");
         }
         technologyBomStructureService.remove(Wrappers.<TechnologyBomStructure>lambdaQuery()
                 .in(TechnologyBomStructure::getBomId, ids));
@@ -152,11 +152,11 @@
      */
     private void validateProductModel(Long productModelId) {
         if (productModelId == null) {
-            throw new ServiceException("Product model is required");
+            throw new ServiceException("浜у搧瑙勬牸ID涓嶈兘涓虹┖");
         }
         ProductModel productModel = productModelService.getById(productModelId);
         if (productModel == null) {
-            throw new ServiceException("Product model not found");
+            throw new ServiceException("浜у搧瑙勬牸涓嶅瓨鍦�");
         }
     }
 
diff --git a/src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java b/src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java
index 107fa22..e8cd6c1 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java
@@ -41,21 +41,21 @@
     public boolean saveTechnologyOperationParam(TechnologyOperationParam technologyOperationParam) {
         if (technologyOperationParam.getTechnologyOperationId() == null
                 || technologyOperationMapper.selectById(technologyOperationParam.getTechnologyOperationId()) == null) {
-            throw new ServiceException("Operation not found");
+            throw new ServiceException("宸ュ簭涓嶅瓨鍦�");
         }
         if (technologyOperationParam.getTechnologyParamId() == null) {
-            throw new ServiceException("Param is required");
+            throw new ServiceException("鍙傛暟ID涓嶈兘涓虹┖");
         }
         TechnologyParam technologyParam = technologyParamMapper.selectById(technologyOperationParam.getTechnologyParamId());
         if (technologyParam == null) {
-            throw new ServiceException("Param not found");
+            throw new ServiceException("鍙傛暟涓嶅瓨鍦�");
         }
         boolean duplicate = technologyOperationParamMapper.selectCount(Wrappers.<TechnologyOperationParam>lambdaQuery()
                 .eq(TechnologyOperationParam::getTechnologyOperationId, technologyOperationParam.getTechnologyOperationId())
                 .eq(TechnologyOperationParam::getTechnologyParamId, technologyOperationParam.getTechnologyParamId())
                 .ne(technologyOperationParam.getId() != null, TechnologyOperationParam::getId, technologyOperationParam.getId())) > 0;
         if (duplicate) {
-            throw new ServiceException("Duplicate param in operation");
+            throw new ServiceException("宸ュ簭鍙傛暟閲嶅");
         }
         return this.saveOrUpdate(technologyOperationParam);
     }
diff --git a/src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java b/src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java
index a3e8f5e..c30cb06 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java
@@ -2,18 +2,22 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.technology.bean.dto.TechnologyParamDto;
 import com.ruoyi.technology.bean.vo.TechnologyParamVo;
+import com.ruoyi.technology.mapper.TechnologyOperationParamMapper;
 import com.ruoyi.technology.mapper.TechnologyParamMapper;
+import com.ruoyi.technology.pojo.TechnologyOperationParam;
 import com.ruoyi.technology.pojo.TechnologyParam;
 import com.ruoyi.technology.service.TechnologyParamService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -28,6 +32,7 @@
     private static final List<Integer> VALID_PARAM_TYPES = Arrays.asList(1, 2, 3, 4);
     private static final String PARAM_CODE_PREFIX = "PARAM_";
     private static final Byte DATE_PARAM_TYPE = (byte) 4;
+    private final TechnologyOperationParamMapper technologyOperationParamMapper;
 
     /**
      * 鍒嗛〉鏌ヨ鍩虹鍙傛暟骞舵牸寮忓寲鏃ユ湡绫诲瀷灞曠ず銆�
@@ -161,10 +166,13 @@
      * 鎵归噺鍒犻櫎鍩虹鍙傛暟銆�
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int deleteBaseParamByIds(Long[] ids) {
         if (ids == null || ids.length == 0) {
             throw new RuntimeException("鍒犻櫎ID涓嶈兘涓虹┖");
         }
-        return baseMapper.deleteBatchIds(Arrays.asList(ids));
+        technologyOperationParamMapper.delete(Wrappers.<TechnologyOperationParam>lambdaQuery()
+                .in(TechnologyOperationParam::getTechnologyParamId, Arrays.asList(ids)));
+        return baseMapper.deleteByIds(Arrays.asList(ids));
     }
 }
diff --git a/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java b/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
index 4079f84..0eff194 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
@@ -143,7 +143,10 @@
             routingOperation.setProductModelId(resolveOutputProductModelId(bomStructure, structureById, technologyRouting.getProductModelId()));
             routingOperation.setTechnologyOperationId(bomStructure.getOperationId());
             routingOperation.setDragSort(dragSort++);
-            routingOperation.setIsQuality(getOperationQuality(bomStructure.getOperationId()));
+            TechnologyOperation technologyOperation = getOperation(bomStructure.getOperationId());
+            routingOperation.setIsQuality(technologyOperation != null ? technologyOperation.getIsQuality() : null);
+            routingOperation.setIsProduction(technologyOperation != null ? technologyOperation.getIsProduction() : null);
+            routingOperation.setType(technologyOperation != null ? technologyOperation.getType() : null);
             technologyRoutingOperationMapper.insert(routingOperation);
             syncRoutingOperationParams(routingOperation.getId(), bomStructure.getOperationId());
         }
@@ -204,12 +207,11 @@
         }
     }
 
-    /**
-     * 璐ㄦ鏍囪瘑浠ュ伐搴忓熀纭�琛ㄥ畾涔変负鍑嗐��
-     */
-    private Boolean getOperationQuality(Long operationId) {
-        TechnologyOperation technologyOperation = technologyOperationMapper.selectById(operationId);
-        return technologyOperation != null ? technologyOperation.getIsQuality() : null;
+    private TechnologyOperation getOperation(Long operationId) {
+        if (operationId == null) {
+            return null;
+        }
+        return technologyOperationMapper.selectById(operationId);
     }
 
     private String buildProcessRouteCode(Long id) {
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index bd38c3b..9f2ffcc 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -254,8 +254,18 @@
   # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
   allowOverwrite: false
 
+# 鏂囦欢涓婁紶閰嶇疆
 file:
-  temp-dir: D:/ruoyi/temp/uploads   # 涓存椂鐩綍
-  upload-dir: D:/ruoyi/prod/uploads # 姝e紡鐩綍
+  temp-dir: D:/ruoyi/temp/uploads   # 涓存椂鐩綍 鍚庢湡鍒犻櫎
+  upload-dir: D:/ruoyi/prod/uploads # 姝e紡鐩綍 鍚庢湡鍒犻櫎
+  path: D:/ruoyi/prod/uploads # 涓婁紶鐩綍
+  urlPrefix: /common # 閾炬帴鍓嶇紑
+  domain: http://127.0.0.1:7005 # 鍩熷悕鍓嶇紑
+  expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+  useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
 knowledge:
   one: D:\鏂扮枂澶х綏绱犱紒涓氫骇鍝佷綋绯昏鏄庢枃妗�.md
+
diff --git a/src/main/resources/mapper/account/AccountExpenseMapper.xml b/src/main/resources/mapper/account/AccountExpenseMapper.xml
index f4039c8..77eed00 100644
--- a/src/main/resources/mapper/account/AccountExpenseMapper.xml
+++ b/src/main/resources/mapper/account/AccountExpenseMapper.xml
@@ -45,7 +45,7 @@
             AND expense_method = #{accountExpense.expenseMethod}
         </if>
     </select>
-    <select id="report" resultType="com.ruoyi.account.dto.AccountDto2">
+    <select id="report" resultType="com.ruoyi.account.bean.dto.AccountDto2">
         SELECT
         sdd.dict_label typeName,
         sum(expense_money) account
diff --git a/src/main/resources/mapper/account/AccountIncomeMapper.xml b/src/main/resources/mapper/account/AccountIncomeMapper.xml
index 9becd41..a1d4205 100644
--- a/src/main/resources/mapper/account/AccountIncomeMapper.xml
+++ b/src/main/resources/mapper/account/AccountIncomeMapper.xml
@@ -66,7 +66,7 @@
             AND income_method = #{accountIncome.incomeMethod}
         </if>
     </select>
-    <select id="report" resultType="com.ruoyi.account.dto.AccountDto2">
+    <select id="report" resultType="com.ruoyi.account.bean.dto.AccountDto2">
         SELECT
         sdd.dict_label typeName,
         ifnull(sum(income_money),0) account
diff --git a/src/main/resources/mapper/account/AccountSubjectMapper.xml b/src/main/resources/mapper/account/AccountSubjectMapper.xml
new file mode 100644
index 0000000..179d858
--- /dev/null
+++ b/src/main/resources/mapper/account/AccountSubjectMapper.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.account.mapper.AccountSubjectMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.account.pojo.AccountSubject">
+        <id column="id" property="id" />
+        <result column="subject_code" property="subjectCode" />
+        <result column="subject_name" property="subjectName" />
+        <result column="subject_type" property="subjectType" />
+        <result column="balance_direction" property="balanceDirection" />
+        <result column="status" property="status" />
+        <result column="remark" property="remark" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml b/src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml
index 355151a..05617b1 100644
--- a/src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml
+++ b/src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml
@@ -15,7 +15,7 @@
         <result column="create_user_id" property="createUserId" />
         <result column="update_user_id" property="updateUserId" />
     </resultMap>
-    <select id="pageSalesRefundAmountOrderDto" resultType="com.ruoyi.account.dto.SalesRefundAmountOrderDto">
+    <select id="pageSalesRefundAmountOrderDto" resultType="com.ruoyi.account.bean.dto.SalesRefundAmountOrderDto">
         select sl.sales_contract_no,
         sl.customer_contract_no,
         slp.specification_model,
diff --git a/src/main/resources/mapper/device/DeviceLedgerMapper.xml b/src/main/resources/mapper/device/DeviceLedgerMapper.xml
index c980120..f3b674e 100644
--- a/src/main/resources/mapper/device/DeviceLedgerMapper.xml
+++ b/src/main/resources/mapper/device/DeviceLedgerMapper.xml
@@ -88,7 +88,7 @@
         where id = #{id}
     </select>
     <select id="getDeviceTypeDistributionByYear"
-            resultType="com.ruoyi.account.dto.DeviceTypeDetail"
+            resultType="com.ruoyi.account.bean.dto.DeviceTypeDetail"
             parameterType="java.lang.Integer">
         SELECT
             `type`,
diff --git a/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml b/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
index 1b84282..a06b4f0 100644
--- a/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
+++ b/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
@@ -53,4 +53,30 @@
                  left join sales_ledger sl on si.sales_ledger_id = sl.id
         where rm.id = #{id}
     </select>
-</mapper>
\ No newline at end of file
+    <select id="listPageBySalesReturn" resultType="com.ruoyi.account.bean.vo.SalesReturnVo">
+         select rm.id,
+                rm.return_no,
+                c.customer_name,
+                si.shipping_no,
+                rm.make_time,
+                rm.refund_amount,
+                rm.return_reason,
+                rm.make_time,
+                sl.sales_contract_no
+        from return_management rm
+                 left join shipping_info si on rm.shipping_id = si.id
+                 left join customer c on rm.customer_id = c.id
+                 left join sales_ledger sl on si.sales_ledger_id = sl.id
+        where rm.status=1
+            <if test="req.returnNo != null and req.returnNo != ''">
+                and rm.return_no like concat('%',#{req.returnNo},'%')
+            </if>
+            <if test="req.customerName != null and req.customerName != ''">
+                and c.customer_name like concat('%',#{req.customerName},'%')
+            </if>
+            <if test="req.startDate != null and req.endDate != null">
+                AND DATE_FORMAT(rm.make_time, '%Y-%m-%d') BETWEEN #{startDate} AND #{endDate}
+            </if>
+         order by rm.id DESC
+    </select>
+</mapper>
diff --git a/src/main/resources/mapper/production/ProductionAccountMapper.xml b/src/main/resources/mapper/production/ProductionAccountMapper.xml
index 2841854..4be1c23 100644
--- a/src/main/resources/mapper/production/ProductionAccountMapper.xml
+++ b/src/main/resources/mapper/production/ProductionAccountMapper.xml
@@ -29,15 +29,19 @@
         pa.scheduling_user_id as schedulingUserId,
         pa.scheduling_user_name as schedulingUserName,
         cast(sum(
-            ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
             case
-                when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
-                then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
-                else 1
+                when poro.type = 0 then ifnull(pa.work_hours, 0) * ifnull(ppm.work_hour, 0)
+                else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
+                     case
+                         when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
+                         then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
+                         else 1
+                     end
             end
         ) as decimal(18,4)) as wages,
         cast(sum(ifnull(pa.finished_num, 0)) as decimal(18,4)) as finishedNum,
         cast(sum(ifnull(pa.work_hours, 0)) as decimal(18,4)) as workHours,
+        cast(sum(ifnull(ppm.work_hour, 0)) as decimal(18,4)) as workHour,
         case
             when sum(ifnull(ppo.quantity, 0) + ifnull(ppo.scrapQty, 0)) = 0 then '0%'
             else concat(
diff --git a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
index 577bcfd..6db5455 100644
--- a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
+++ b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
@@ -29,6 +29,7 @@
                pm.model as model,
                pm.unit as unit,
                poro.operation_name as operationName,
+               poro.type as type,
                IFNULL(scrapStat.scrapQty, 0) AS scrapQty,
         ROUND(IFNULL(pot.complete_quantity, 0) / NULLIF(pot.plan_quantity, 0) * 100, 2) AS completionStatus,
         CASE
diff --git a/src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml b/src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml
index 55739db..4d63797 100644
--- a/src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml
+++ b/src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml
@@ -13,6 +13,7 @@
         <result column="update_time" property="updateTime" />
         <result column="drag_sort" property="dragSort" />
         <result column="is_quality" property="isQuality" />
+        <result column="type" property="type" />
         <result column="create_user" property="createUser" />
         <result column="dept_id" property="deptId" />
     </resultMap>
diff --git a/src/main/resources/mapper/production/ProductionPlanMapper.xml b/src/main/resources/mapper/production/ProductionPlanMapper.xml
index a389d32..69ccd36 100644
--- a/src/main/resources/mapper/production/ProductionPlanMapper.xml
+++ b/src/main/resources/mapper/production/ProductionPlanMapper.xml
@@ -53,6 +53,9 @@
                 <if test="c.requiredDateStart != null and c.requiredDateEnd != null">
                     and pp.required_date between #{c.requiredDateStart} and #{c.requiredDateEnd}
                 </if>
+                <if test="c.salesContractNo != null and c.salesContractNo != ''">
+                    and sl.sales_contract_no like concat('%', #{c.salesContractNo}, '%')
+                </if>
             </if>
         </where>
         ORDER BY COALESCE(pp.id) DESC
diff --git a/src/main/resources/mapper/production/ProductionProductMainMapper.xml b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
index 5a78a5b..df585f9 100644
--- a/src/main/resources/mapper/production/ProductionProductMainMapper.xml
+++ b/src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -100,13 +100,17 @@
                ifnull(ppo.scrap_qty, 0) as scrapQty,
                date(pa.scheduling_date) as schedulingDate,
                pa.scheduling_user_name as schedulingUserName,
+               cast(ifnull(ppm.work_hour, 0) as decimal(18,4)) as workHour,
                cast(ifnull(pa.work_hours, 0) as decimal(18,4)) as workHours,
                cast(
-                   ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
                    case
-                       when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
-                       then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
-                       else 1
+                       when poro.type = 0 then ifnull(pa.work_hours, 0) * ifnull(ppm.work_hour, 0)
+                       else ifnull(pa.work_hours, 0) * ifnull(pa.finished_num, 0) *
+                            case
+                                when substring_index(pm.model, '*', -1) regexp '^[0-9]+(\\.[0-9]+)?$'
+                                then cast(substring_index(pm.model, '*', -1) as decimal(18,4))
+                                else 1
+                            end
                    end
                    as decimal(18,4)
                ) as wages
diff --git a/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml b/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
index 0232873..77435ad 100644
--- a/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
+++ b/src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml
@@ -18,16 +18,18 @@
         <result column="create_time" property="createTime" />
         <result column="update_time" property="updateTime" />
     </resultMap>
-
-    <select id="listPage" resultType="com.ruoyi.purchase.vo.PurchaseReturnOrderVo">
+    <sql id="getPurchaseReturnOrderHasAllInfoFormAndColumn">
         SELECT
-        pro.*,
-        sm.supplier_name as supplierName,
-        pl.purchase_contract_number as purchaseContractNumber
+            pro.*,
+            sm.supplier_name as supplier_name,
+            pl.purchase_contract_number as purchase_contract_number
         FROM purchase_return_orders pro
-        LEFT JOIN supplier_manage sm ON pro.supplier_id = sm.id
-        LEFT JOIN purchase_ledger pl ON pl.id = pro.purchase_ledger_id
-        where 1=1
+                 LEFT JOIN supplier_manage sm ON pro.supplier_id = sm.id
+                 LEFT JOIN purchase_ledger pl ON pl.id = pro.purchase_ledger_id
+    </sql>
+    <select id="listPage" resultType="com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto">
+        <include refid="getPurchaseReturnOrderHasAllInfoFormAndColumn"/>
+        <where>
         <if test="params.no != null and params.no != '' ">
             AND pro.no LIKE CONCAT('%',#{params.no},'%')
         </if>
@@ -43,6 +45,12 @@
         <if test="params.createUser != null">
             AND pro.create_user = #{params.createUser}
         </if>
+        </where>
         ORDER BY pro.create_time DESC
     </select>
+    <select id="getPurchaseReturnOrderHasAllInfoById"
+            resultType="com.ruoyi.purchase.dto.PurchaseReturnOrderHasAllInfoDto">
+        <include refid="getPurchaseReturnOrderHasAllInfoFormAndColumn"/>
+        where pro.id = #{id}
+    </select>
 </mapper>
diff --git a/src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml b/src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml
index fcaba49..ba790b1 100644
--- a/src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml
+++ b/src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml
@@ -56,7 +56,7 @@
                 AND T3.invoice_date = #{invoiceRegistrationProductDto.invoiceDate}
             </if>
         </where>
-        ORDER BY T1.create_time DESC
+        ORDER BY T1.create_time DESC, T1.id DESC
     </select>
 
     <select id="invoiceRegistrationProductPage" resultType="com.ruoyi.sales.dto.InvoiceRegistrationProductDto">
@@ -127,6 +127,6 @@
                 %H:%i:%s')+interval 1 day
             </if>
         </where>
-        ORDER BY T1.create_time DESC
+        ORDER BY T1.create_time DESC, T1.id DESC
     </select>
 </mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/sales/ShipmentApprovalMapper.xml b/src/main/resources/mapper/sales/ShipmentApprovalMapper.xml
deleted file mode 100644
index 68cd664..0000000
--- a/src/main/resources/mapper/sales/ShipmentApprovalMapper.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.sales.mapper.ShipmentApprovalMapper">
-
-    <select id="listPage" resultType="com.ruoyi.sales.pojo.ShipmentApproval">
-        SELECT *,
-        si.shipping_car_number,
-        T2.nick_name AS entry_person_name
-        FROM shipment_approval sa
-                 LEFT JOIN shipping_info si ON sa.shipping_info_id = si.id
-                 LEFT JOIN sales_ledger sl ON sa.sales_ledger_id = sl.id
-                 LEFT JOIN sales_ledger_product slp ON sa.sales_ledger_product_id = slp.id and slp.type = 1
-                 LEFT JOIN sys_user T2 ON sl.entry_person = T2.user_id
-        <where>
-            1=1
-            <if test="req.approveStatus != null and req.approveStatus != '' ">
-                AND  sa.approve_status = #{req.approveStatus}
-            </if>
-            <if test="req.salesContractNo != null and req.salesContractNo != '' ">
-                AND  sl.sales_contract_no LIKE CONCAT('%',#{req.salesContractNo},'%')
-            </if>
-        </where>
-    </select>
-</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/sales/ShippingInfoMapper.xml b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
index 048bb23..cd7dc3e 100644
--- a/src/main/resources/mapper/sales/ShippingInfoMapper.xml
+++ b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -87,4 +87,33 @@
         left join sales_ledger sl on si.sales_ledger_id = sl.id
         where si.status = '宸插彂璐�' and sl.customer_name = #{customerName}
     </select>
+    <select id="listPageByOutbound" resultType="com.ruoyi.account.bean.vo.SalesOutboundVo">
+         SELECT
+        sor.id,
+        sor.outbound_batches,
+        sl.customer_name,
+        s.shipping_date,
+        p.product_name,
+        slp.specification_model,
+        slp.stock_out_num,
+        s.shipping_no,
+        sl.sales_contract_no
+        FROM shipping_info s
+        LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
+        LEFT JOIN sales_ledger_product slp ON s.sales_ledger_product_id = slp.id and slp.type = 1
+        left join product_model pm on slp.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+        left join stock_out_record sor on sor.record_id = s.id and sor.record_type='13'
+        WHERE s.status='宸插彂璐�'
+        <if test="req.outboundBatches != null and req.outboundBatches != ''">
+            AND sor.outbound_batches LIKE CONCAT('%',#{req.outboundBatches},'%')
+        </if>
+        <if test="req.customerName != null and req.customerName != ''">
+            AND sl.customer_name LIKE CONCAT('%',#{req.customerName},'%')
+        </if>
+        <if test="req.startDate != null and req.endDate != null">
+            AND s.shipping_date BETWEEN #{startDate} AND #{endDate}
+        </if>
+        order by sor.id DESC
+    </select>
 </mapper>
diff --git a/src/main/resources/mapper/sales/ShippingProductDetailMapper.xml b/src/main/resources/mapper/sales/ShippingProductDetailMapper.xml
index 464af10..15a6168 100644
--- a/src/main/resources/mapper/sales/ShippingProductDetailMapper.xml
+++ b/src/main/resources/mapper/sales/ShippingProductDetailMapper.xml
@@ -18,5 +18,14 @@
                  left join product p on p.id = pm.product_id
         where spd.shipping_info_id = #{id}
     </select>
+    <select id="getDateilByShippingNo" resultType="com.ruoyi.sales.dto.ShippingProductDetailDto">
+         select si.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity
+         from shipping_product_detail spd
+                  left join shipping_info sp on sp.id = spd.shipping_info_id
+                  left join stock_inventory si on si.id = spd.stock_inventory_id
+                  left join product_model pm on pm.id = si.product_model_id
+                  left join product p on p.id = pm.product_id
+         where sp.shipping_no = #{shippingNo}
+    </select>
 
 </mapper>
diff --git a/src/main/resources/mapper/staff/StaffOnJobMapper.xml b/src/main/resources/mapper/staff/StaffOnJobMapper.xml
index b0c85bc..a256ff8 100644
--- a/src/main/resources/mapper/staff/StaffOnJobMapper.xml
+++ b/src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -6,14 +6,13 @@
         staff_on_job.*,
         sp.post_name as postName,
         sd.dept_name as deptName,
-        t1.contract_start_time
+        MIN(t1.contract_start_time) as contract_start_time,  -- 鍙栨渶鏃╁悎鍚屽紑濮嬫椂闂�
+        MAX(t1.contract_end_time) as contract_end_time
         FROM staff_on_job
-        LEFT JOIN
-        sys_post sp ON sp.post_id = staff_on_job.sys_post_id
-        LEFT JOIN
-        sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id
+        LEFT JOIN sys_post sp ON sp.post_id = staff_on_job.sys_post_id
+        LEFT JOIN sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id
         LEFT JOIN staff_contract as t1 ON t1.staff_on_job_id = staff_on_job.id
-        where 1=1
+        WHERE 1=1
         <if test="staffOnJob.staffState != null">
             AND staff_state = #{staffOnJob.staffState}
         </if>
@@ -26,6 +25,7 @@
         <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
             AND contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
         </if>
+        GROUP BY staff_on_job.id
     </select>
     <select id="staffOnJobList" resultType="com.ruoyi.staff.dto.StaffOnJobDto">
         SELECT
diff --git a/src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml b/src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml
index 1bbb25c..26053b8 100644
--- a/src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml
+++ b/src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml
@@ -12,6 +12,7 @@
         <result column="update_time" property="updateTime" />
         <result column="drag_sort" property="dragSort" />
         <result column="is_quality" property="isQuality" />
+        <result column="type" property="type" />
         <result column="create_user" property="createUser" />
         <result column="dept_id" property="deptId" />
     </resultMap>

--
Gitblit v1.9.3