From a3d3ba426c2a5a6015888d5e32e0f9704b4d125c Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 24 九月 2026 14:50:34 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_天津_建诚恒商贸' into dev_天津_建诚恒商贸

---
 src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java              |   10 
 src/main/java/com/ruoyi/basic/dto/ProductImportRowDto.java                               |   29 +
 src/main/resources/mapper/account/AccountStatementMapper.xml                             |   10 
 src/main/java/com/ruoyi/basic/controller/ProductController.java                          |    9 
 src/main/resources/mapper/basic/SupplierManageMapper.xml                                 |  105 ++++-
 src/main/java/com/ruoyi/basic/service/IProductService.java                               |    5 
 src/main/resources/mapper/stock/StockInventoryMapper.xml                                 |    3 
 src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java                  |  223 ++++++++++---
 src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java            |   18 
 sql/product-inventory-management-new-pro.sql                                             |   22 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java            |   54 ++-
 src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java |   10 
 src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java   |   18 
 src/main/resources/static/产品导入模板.xlsx                                                    |    0 
 doc/20260913_alter_quantity_scale_8.sql                                                  |   42 ++
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java                   |    2 
 src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java                       |   44 ++
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java          |   15 
 src/main/resources/application-qhddzxc.yml                                               |  268 +++++++++++++++
 src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java                              |   76 ++++
 src/main/java/com/ruoyi/basic/service/IProductModelService.java                          |    2 
 21 files changed, 810 insertions(+), 155 deletions(-)

diff --git a/doc/20260913_alter_quantity_scale_8.sql b/doc/20260913_alter_quantity_scale_8.sql
new file mode 100644
index 0000000..07bc1f7
--- /dev/null
+++ b/doc/20260913_alter_quantity_scale_8.sql
@@ -0,0 +1,42 @@
+-- 鏁伴噺瀛楁缁熶竴淇濈暀 8 浣嶅皬鏁帮紙decimal(20, 8)锛氭暣鏁颁綅淇濈暀 12 浣嶏級
+-- 娑夊強鍔熻兘锛�
+--   1. 浜у搧缁撴瀯锛圔OM锛夊崟浣嶄骇鍑烘墍闇�鏁伴噺锛堜骇鍝佺粨鏋勮鎯呫�佺紪杈戝脊绐楋級
+--   2. 鐢熶骇璁㈠崟-宸ヨ壓璺嚎 BOM 缁撴瀯鍗曚綅浜у嚭鎵�闇�鏁伴噺
+--   3. 鐢熶骇璁㈠崟-棰嗘枡鏁伴噺锛堥鏂欏彴璐﹀脊绐楋級
+--   4. 搴撳瓨閾捐矾锛堥鏂欐墸鍑忓簱瀛樸�佸嚭搴�/鍏ュ簱璁板綍銆佸悎鏍�/涓嶅悎鏍煎簱瀛樸�佽鍗曢攣瀹氭暟閲忥級
+
+-- ----------------------------
+-- BOM 缁撴瀯琛�
+-- ----------------------------
+ALTER TABLE `technology_bom_structure`
+    MODIFY COLUMN `unit_quantity` decimal(20, 8) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�';
+
+ALTER TABLE `production_bom_structure`
+    MODIFY COLUMN `unit_quantity` decimal(20, 8) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�';
+
+-- ----------------------------
+-- 棰嗘枡琛紙production_order_pick.quantity 瀛橀鐢ㄦ暟閲忥級
+-- ----------------------------
+ALTER TABLE `production_order_pick`
+    MODIFY COLUMN `quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺';
+
+ALTER TABLE `production_order_pick_record`
+    MODIFY COLUMN `pick_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鏁伴噺',
+    MODIFY COLUMN `before_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鍓嶆暟閲�',
+    MODIFY COLUMN `after_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鍚庡墿浣欐暟閲�';
+
+-- ----------------------------
+-- 搴撳瓨閾捐矾琛�
+-- ----------------------------
+ALTER TABLE `stock_inventory`
+    MODIFY COLUMN `qualitity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺',
+    MODIFY COLUMN `locked_quantity` decimal(20, 8) NULL DEFAULT 0.00000000 COMMENT '琚鍗曢攣瀹氭暟閲�';
+
+ALTER TABLE `stock_uninventory`
+    MODIFY COLUMN `qualitity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺';
+
+ALTER TABLE `stock_in_record`
+    MODIFY COLUMN `stock_in_num` decimal(20, 8) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺';
+
+ALTER TABLE `stock_out_record`
+    MODIFY COLUMN `stock_out_num` decimal(20, 8) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺';
diff --git a/sql/product-inventory-management-new-pro.sql b/sql/product-inventory-management-new-pro.sql
index 35c0008..71d483b 100644
--- a/sql/product-inventory-management-new-pro.sql
+++ b/sql/product-inventory-management-new-pro.sql
@@ -2705,7 +2705,7 @@
   `product_model_id` bigint NOT NULL COMMENT '浜у搧瑙勬牸id',
   `technology_operation_id` bigint NULL DEFAULT NULL COMMENT '宸ュ簭id',
   `production_order_bom_id` bigint NULL DEFAULT NULL COMMENT '鐢熶骇璁㈠崟BOMid',
-  `unit_quantity` decimal(16, 4) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�',
+  `unit_quantity` decimal(20, 8) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�',
   `demanded_quantity` decimal(16, 4) NULL DEFAULT NULL COMMENT '闇�姹傛暟閲�',
   `unit` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍗曚綅',
   `create_user` bigint NULL DEFAULT NULL COMMENT '鍒涘缓浜篒D',
@@ -2815,7 +2815,7 @@
 CREATE TABLE `production_order_pick`  (
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭id',
   `product_model_id` int NULL DEFAULT NULL COMMENT '浜у搧瑙勬牸id',
-  `quantity` decimal(16, 2) NULL DEFAULT NULL COMMENT '鏁伴噺',
+  `quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
   `create_time` timestamp NULL DEFAULT NULL,
   `update_time` timestamp NULL DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
@@ -2845,9 +2845,9 @@
   `production_operation_task_id` bigint NULL DEFAULT 0 COMMENT '鐢熶骇宸ュ崟id',
   `product_model_id` int NULL DEFAULT NULL COMMENT '浜у搧瑙勬牸id',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
-  `pick_quantity` decimal(16, 4) NULL DEFAULT NULL COMMENT '棰嗘枡鏁伴噺',
-  `before_quantity` decimal(16, 4) NULL DEFAULT NULL COMMENT '棰嗘枡鍓嶆暟閲�',
-  `after_quantity` decimal(16, 4) NULL DEFAULT NULL COMMENT '棰嗘枡鍚庡墿浣欐暟閲�',
+  `pick_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鏁伴噺',
+  `before_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鍓嶆暟閲�',
+  `after_quantity` decimal(20, 8) NULL DEFAULT NULL COMMENT '棰嗘枡鍚庡墿浣欐暟閲�',
   `pick_type` tinyint NULL DEFAULT 1 COMMENT '棰嗘枡绫诲瀷锛�1姝e父棰嗘枡  2琛ユ枡',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '澶囨敞',
   `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
@@ -4763,7 +4763,7 @@
   `id` int NOT NULL AUTO_INCREMENT,
   `inbound_batches` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍏ュ簱鎵规',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
-  `stock_in_num` decimal(16, 4) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺',
+  `stock_in_num` decimal(20, 8) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺',
   `record_id` int NULL DEFAULT NULL COMMENT '鍏ュ簱鏉ユ簮id',
   `record_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍏ュ簱绫诲瀷',
   `product_model_id` int NULL DEFAULT NULL COMMENT '浜у搧瑙勬牸id',
@@ -4788,11 +4788,11 @@
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭id',
   `product_model_id` int NULL DEFAULT NULL COMMENT '瑙勬牸id',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
-  `qualitity` decimal(16, 4) NULL DEFAULT NULL COMMENT '鏁伴噺',
+  `qualitity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺',
   `create_time` timestamp NULL DEFAULT NULL,
   `update_time` timestamp NULL DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
   `version` int NULL DEFAULT NULL COMMENT '鐗堟湰鍙�',
-  `locked_quantity` decimal(16, 4) NULL DEFAULT 0.0000 COMMENT '琚鍗曢攣瀹氭暟閲�',
+  `locked_quantity` decimal(20, 8) NULL DEFAULT 0.00000000 COMMENT '琚鍗曢攣瀹氭暟閲�',
   `warn_num` decimal(16, 4) NULL DEFAULT NULL COMMENT '棰勮鏁伴噺',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '澶囨敞',
   `create_user` bigint NULL DEFAULT NULL COMMENT '鍒涘缓浜篒D',
@@ -4808,7 +4808,7 @@
   `id` int NOT NULL AUTO_INCREMENT,
   `outbound_batches` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍏ュ簱鎵规',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
-  `stock_out_num` decimal(16, 4) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺',
+  `stock_out_num` decimal(20, 8) NULL DEFAULT NULL COMMENT '鍏ュ簱鏁伴噺',
   `record_id` int NULL DEFAULT NULL COMMENT '鍏ュ簱鏉ユ簮id',
   `record_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍏ュ簱绫诲瀷',
   `product_model_id` int NULL DEFAULT NULL COMMENT '浜у搧瑙勬牸id',
@@ -4831,7 +4831,7 @@
   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭id',
   `product_model_id` int NULL DEFAULT NULL COMMENT '瑙勬牸id',
   `batch_no` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鎵瑰彿',
-  `qualitity` decimal(16, 4) NULL DEFAULT NULL COMMENT '鏁伴噺',
+  `qualitity` decimal(20, 8) NULL DEFAULT NULL COMMENT '鏁伴噺',
   `create_time` timestamp NULL DEFAULT NULL,
   `update_time` timestamp NULL DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
   `version` int NULL DEFAULT NULL COMMENT '鐗堟湰鍙�',
@@ -5302,7 +5302,7 @@
   `parent_id` bigint NULL DEFAULT NULL COMMENT '鐖惰妭鐐笽D',
   `product_model_id` bigint NOT NULL COMMENT '浜у搧id',
   `operation_id` bigint NULL DEFAULT NULL COMMENT '宸ュ簭id',
-  `unit_quantity` decimal(16, 4) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�',
+  `unit_quantity` decimal(20, 8) NOT NULL COMMENT '鍗曚綅浜у嚭闇�瑕佹暟閲�',
   `demanded_quantity` decimal(16, 4) NULL DEFAULT NULL COMMENT '闇�姹傛暟閲�',
   `unit` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '鍗曚綅',
   `bom_id` bigint NOT NULL COMMENT 'bom鐨刬d',
diff --git a/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java b/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
index 49e7ae3..a88085b 100644
--- a/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
+++ b/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
@@ -49,12 +49,12 @@
 
     @Override
     public IPage<AfterSalesServiceNewDto> listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
-        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
-        SysDept sysDept = sysDeptMapper.selectDeptById(tenantId);
+        //Long tenantId = SecurityUtils.getLoginUser().getTenantId();
+        //SysDept sysDept = sysDeptMapper.selectDeptById(tenantId);
         IPage<AfterSalesServiceNewDto> afterSalesServiceIPage = afterSalesServiceMapper.listPage(page, afterSalesService);
-        afterSalesServiceIPage.getRecords().forEach(item -> {
-            item.setDeptName(sysDept.getDeptName());
-        });
+        //afterSalesServiceIPage.getRecords().forEach(item -> {
+        //    item.setDeptName(sysDept.getDeptName());
+        //});
         return afterSalesServiceIPage;
     }
 
diff --git a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java b/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
index eaa487c..a31c540 100644
--- a/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
+++ b/src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
@@ -7,15 +7,26 @@
 import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig;
 import io.pinecone.clients.Index;
 import io.pinecone.clients.Pinecone;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
+import java.io.IOException;
+import java.util.function.Supplier;
+
 /**
  * 鍚戦噺瀛樺偍閰嶇疆
  */
+@Slf4j
 @Configuration
 public class EmbeddingStoreConfig {
+
+    /** Pinecone 杩炴帴閲嶈瘯娆℃暟锛氭瀯閫犳湡浼氬悓姝ュ彂璧� HTTP/2 鎻℃墜锛屽伓鍙� "Required SETTINGS preface not received" */
+    private static final int CONNECT_ATTEMPTS = 3;
+
+    /** 閲嶈瘯闂撮殧鍩烘暟锛堟绉掞級锛屾寜娆℃暟绾挎�ч��閬� */
+    private static final long RETRY_BACKOFF_MILLIS = 1500L;
 
     @Value("${pinecone.api-key:pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9}")
     private String pineconeApiKey;
@@ -33,12 +44,12 @@
 
     @Bean
     public Index pineconeIndex(Pinecone pinecone) {
-        return pinecone.getIndexConnection(indexName);
+        return retry("鑾峰彇 Pinecone 绱㈠紩杩炴帴", () -> pinecone.getIndexConnection(indexName));
     }
 
     @Bean
     public EmbeddingStore<TextSegment> embeddingStore(EmbeddingModel embeddingModel) {
-        return PineconeEmbeddingStore.builder()
+        return retry("鍒濆鍖� Pinecone 鍚戦噺瀛樺偍", () -> PineconeEmbeddingStore.builder()
                 .apiKey(pineconeApiKey)
                 .index(indexName)
                 .nameSpace(namespace)
@@ -47,6 +58,63 @@
                         .region("us-east-1")
                         .dimension(embeddingModel.dimension())
                         .build())
-                .build();
+                .build());
     }
-}
\ No newline at end of file
+
+    /**
+     * 瀵� Pinecone 鍚姩鏈熻繛鎺ュ仛鏈夐檺娆¢噸璇曘��
+     * <p>
+     * Pinecone 瀹㈡埛绔浐瀹氳蛋 HTTP/2锛屾瀯閫� EmbeddingStore / 鑾峰彇绱㈠紩杩炴帴鏃朵細鍚屾璋冪敤鎺у埗闈㈡帴鍙o紝
+     * 鍋跺彂鎻℃墜澶辫触锛坖ava.io.IOException: Required SETTINGS preface not received锛変細浠ヨ繍琛屾椂寮傚父鎶涘嚭锛�
+     * 鐩存帴瀵艰嚧鏁翠釜 ApplicationContext 鍔犺浇澶辫触銆傝繖閲屼粎瀵硅繖绫荤灛鏃剁綉缁滃紓甯搁噸璇曪紝鍏跺畠寮傚父绔嬪嵆鎶涘嚭銆�
+     */
+    private <T> T retry(String action, Supplier<T> supplier) {
+        RuntimeException last = null;
+        for (int attempt = 1; attempt <= CONNECT_ATTEMPTS; attempt++) {
+            try {
+                return supplier.get();
+            } catch (RuntimeException e) {
+                if (!isTransientConnectFailure(e) || attempt == CONNECT_ATTEMPTS) {
+                    throw e;
+                }
+                last = e;
+                log.warn("{}澶辫触锛堢 {}/{} 娆★級锛寋}ms 鍚庨噸璇曪細{}",
+                        action, attempt, CONNECT_ATTEMPTS, RETRY_BACKOFF_MILLIS * attempt, rootMessage(e));
+                sleep(RETRY_BACKOFF_MILLIS * attempt);
+            }
+        }
+        throw last;
+    }
+
+    /** 娌� cause 閾惧垽鏂槸鍚︿负 HTTP/2 鎻℃墜绫荤殑鐬椂杩炴帴澶辫触 */
+    private boolean isTransientConnectFailure(Throwable e) {
+        for (Throwable t = e; t != null; t = t.getCause()) {
+            if (t instanceof IOException) {
+                return true;
+            }
+            String message = t.getMessage();
+            if (message != null && (message.contains("SETTINGS preface")
+                    || message.contains("Connection reset")
+                    || message.contains("timed out"))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String rootMessage(Throwable e) {
+        Throwable root = e;
+        while (root.getCause() != null && root.getCause() != root) {
+            root = root.getCause();
+        }
+        return root.getMessage();
+    }
+
+    private void sleep(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/controller/ProductController.java b/src/main/java/com/ruoyi/basic/controller/ProductController.java
index 1c5224b..d6c8eda 100644
--- a/src/main/java/com/ruoyi/basic/controller/ProductController.java
+++ b/src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -5,13 +5,11 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.basic.dto.ProductDto;
 import com.ruoyi.basic.dto.ProductModelDto;
-import com.ruoyi.basic.dto.ProductModelExportDto;
 import com.ruoyi.basic.dto.ProductTreeDto;
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.service.IProductModelService;
 import com.ruoyi.basic.service.IProductService;
 import com.ruoyi.basic.vo.ProductModelVo;
-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.controller.BaseController;
@@ -125,8 +123,8 @@
      */
     @PostMapping("/import")
     @Log(title = "瀵煎叆浜у搧", businessType = BusinessType.IMPORT)
-    public AjaxResult importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
-        return productModelService.importProductModel(file, productId);
+    public AjaxResult importProductModel(@RequestParam("file") MultipartFile file) {
+        return productModelService.importProductModel(file);
     }
 
     /**
@@ -136,7 +134,6 @@
     @Operation(summary = "浜у搧瀵煎叆妯℃澘")
     @Log(title = "浜у搧瀵煎叆妯℃澘", businessType = BusinessType.EXPORT)
     public void importProduct(HttpServletResponse response) {
-        ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
-        excelUtil.importTemplateExcel(response, "浜у搧瑙勬牸瀵煎叆妯℃澘");
+        productService.exportImportTemplate(response);
     }
 }
diff --git a/src/main/java/com/ruoyi/basic/dto/ProductImportRowDto.java b/src/main/java/com/ruoyi/basic/dto/ProductImportRowDto.java
new file mode 100644
index 0000000..4695d93
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/dto/ProductImportRowDto.java
@@ -0,0 +1,29 @@
+package com.ruoyi.basic.dto;
+
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 浜у搧鎵归噺瀵煎叆琛岋紙涓変釜sheet锛氬師鏂�/鍗婃垚鍝�/鎴愬搧锛�
+ */
+@Data
+public class ProductImportRowDto {
+
+    @Excel(name = "浜у搧鐖剁被")
+    private String productParentName;
+
+    @Excel(name = "浜у搧澶х被")
+    private String productCategoryName;
+
+    @Excel(name = "浜у搧鍚嶇О")
+    private String productName;
+
+    @Excel(name = "浜у搧缂栫爜")
+    private String productCode;
+
+    @Excel(name = "瑙勬牸鍨嬪彿")
+    private String model;
+
+    @Excel(name = "鍗曚綅")
+    private String unit;
+}
diff --git a/src/main/java/com/ruoyi/basic/service/IProductModelService.java b/src/main/java/com/ruoyi/basic/service/IProductModelService.java
index dd268d6..9361fee 100644
--- a/src/main/java/com/ruoyi/basic/service/IProductModelService.java
+++ b/src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -34,5 +34,5 @@
      */
     IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
 
-    AjaxResult importProductModel(MultipartFile file, Integer productId);
+    AjaxResult importProductModel(MultipartFile file);
 }
diff --git a/src/main/java/com/ruoyi/basic/service/IProductService.java b/src/main/java/com/ruoyi/basic/service/IProductService.java
index 1abaad4..353cd28 100644
--- a/src/main/java/com/ruoyi/basic/service/IProductService.java
+++ b/src/main/java/com/ruoyi/basic/service/IProductService.java
@@ -8,11 +8,12 @@
 import com.ruoyi.basic.pojo.Product;
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.vo.ProductModelVo;
+import jakarta.servlet.http.HttpServletResponse;
 
 import java.util.List;
 
 public interface IProductService extends IService<Product> {
-    
+
     int addOrEditProduct(ProductDto productDto);
 
     int delProductByIds(Long[] ids);
@@ -20,4 +21,6 @@
     List<ProductTreeDto> selectProductList(ProductDto productDto);
 
     IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel);
+
+    void exportImportTemplate(HttpServletResponse response);
 }
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 be34fc0..cb0c373 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -7,6 +7,7 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.basic.dto.ProductDto;
+import com.ruoyi.basic.dto.ProductImportRowDto;
 import com.ruoyi.basic.dto.ProductModelDto;
 import com.ruoyi.basic.mapper.ProductMapper;
 import com.ruoyi.basic.mapper.ProductModelMapper;
@@ -18,7 +19,6 @@
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.web.domain.AjaxResult;
-import com.ruoyi.sales.dto.LossProductModelDto;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.technology.mapper.TechnologyBomMapper;
@@ -28,11 +28,13 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * 銆愯濉啓鍔熻兘鍚嶇О銆慡ervice涓氬姟灞傚鐞�
@@ -48,6 +50,8 @@
     private final SalesLedgerProductMapper salesLedgerProductMapper;
     private final TechnologyBomMapper technologyBomMapper;
     private ProductModelMapper productModelMapper;
+
+    private static final List<String> IMPORT_SHEET_NAMES = Arrays.asList("鍘熸潗鏂�", "鍘熸枡", "鍗婃垚鍝�", "鎴愬搧");
 
     @Override
     public int addOrEditProductModel(ProductModelDto productModelDto) {
@@ -124,69 +128,184 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public AjaxResult importProductModel(MultipartFile file, Integer productId) {
-        if (productId == null) {
-            return AjaxResult.error("璇峰厛閫夋嫨浜у搧鍐嶅鍏ヨ鏍煎瀷鍙�");
+    public AjaxResult importProductModel(MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            return AjaxResult.error("璇烽�夋嫨瑕佸鍏ョ殑鏂囦欢");
         }
 
-        Product product = productMapper.selectById(productId);
-        if (product == null) {
-            return AjaxResult.error("閫夋嫨鐨勪骇鍝佷笉瀛樺湪");
-        }
-
+        Map<String, List<ProductImportRowDto>> sheetMap;
         try {
-            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
-            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
+            ExcelUtil<ProductImportRowDto> excelUtil = new ExcelUtil<>(ProductImportRowDto.class);
+            sheetMap = excelUtil.importExcelMultiSheet(IMPORT_SHEET_NAMES, file.getInputStream(), 0);
+        } catch (Exception e) {
+            log.error("瑙f瀽浜у搧瀵煎叆鏂囦欢寮傚父", e);
+            throw new ServiceException("浜у搧瀵煎叆鏂囦欢瑙f瀽澶辫触");
+        }
+        if (sheetMap.isEmpty()) {
+            return AjaxResult.error("鏈壘鍒� [鍘熸枡/鍗婃垚鍝�/鎴愬搧] 宸ヤ綔琛紝鎴栨枃浠朵腑娌℃湁鏁版嵁");
+        }
 
-            if (CollectionUtils.isEmpty(productModelList)) {
-                return AjaxResult.error("瀵煎叆鏁版嵁涓嶈兘涓虹┖");
+        int successCount = 0;
+        int skipCount = 0;
+        Set<String> generatedCodes = new HashSet<>();
+        for (String sheetName : IMPORT_SHEET_NAMES) {
+            List<ProductImportRowDto> rows = sheetMap.get(sheetName);
+            if (CollectionUtils.isEmpty(rows)) {
+                continue;
             }
-
-            //  鑾峰彇褰撳墠浜у搧涓嬫墍鏈夌殑瑙勬牸鍨嬪彿鍚�
-            List<ProductModel> existingModels = list(new LambdaQueryWrapper<ProductModel>().eq(ProductModel::getProductId, productId));
-            Set<String> existingModelNames = existingModels.stream().map(ProductModel::getModel).collect(Collectors.toSet());
-
-            List<ProductModel> waitToSaveList = new ArrayList<>();
-            int skipCount = 0;
-
-            for (int i = 0; i < productModelList.size(); i++) {
-                ProductModel item = productModelList.get(i);
+            Long topId = findOrCreateTopProduct(sheetName);
+            Map<String, Long> nodeCache = new HashMap<>();
+            Map<Long, Set<String>> codeCache = new HashMap<>();
+            Map<Long, Set<String>> modelCache = new HashMap<>();
+            for (int i = 0; i < rows.size(); i++) {
+                ProductImportRowDto row = rows.get(i);
                 int rowNum = i + 2;
-
-                if (StringUtils.isEmpty(item.getProductCode())) {
-                    return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [浜у搧缂栫爜] 涓嶈兘涓虹┖");
-                }
-                if (StringUtils.isEmpty(item.getModel())) {
-                    return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [瑙勬牸鍨嬪彿] 涓嶈兘涓虹┖");
-                }
-                if (StringUtils.isEmpty(item.getUnit())) {
-                    return AjaxResult.error("绗� " + rowNum + " 琛屽鍏ュけ璐�: [鍗曚綅] 涓嶈兘涓虹┖");
+                String parentName = trimToNull(row.getProductParentName());
+                String categoryName = trimToNull(row.getProductCategoryName());
+                String productName = trimToNull(row.getProductName());
+                if (productName == null) {
+                    throw new ServiceException("绗� " + rowNum + " 琛� [浜у搧鍚嶇О] 涓嶈兘涓虹┖");
                 }
 
-                //  鍘婚噸,濡傛灉宸插寘鍚鍨嬪彿,鍒欒烦杩�
-                if (existingModelNames.contains(item.getModel())) {
+                String path = String.valueOf(topId);
+                Long currentParentId = topId;
+                if (parentName != null) {
+                    path = path + ">" + parentName;
+                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, parentName);
+                }
+                if (categoryName != null) {
+                    path = path + ">" + categoryName;
+                    currentParentId = ensureChildNode(nodeCache, path, currentParentId, categoryName);
+                }
+                path = path + ">" + productName;
+                Long leafId = ensureChildNode(nodeCache, path, currentParentId, productName);
+
+                String model = StringUtils.isEmpty(row.getModel()) ? "/" : row.getModel().trim();
+
+                // 鍚屼竴浜у搧涓嬪悕绉帮紙浜у搧鑺傜偣锛�+ 鍨嬪彿鐩稿悓鍒欒烦杩�
+                Set<String> models = modelCache.computeIfAbsent(leafId, this::loadProductModels);
+                if (!models.add(model)) {
                     skipCount++;
                     continue;
                 }
 
-                item.setProductId(product.getId());
-                waitToSaveList.add(item);
+                String code = trimToNull(row.getProductCode());
+                if (code == null) {
+                    code = generateUniqueCode(generatedCodes);
+                }
+                Set<String> codes = codeCache.computeIfAbsent(leafId, this::loadProductCodes);
+                if (!codes.add(code)) {
+                    skipCount++;
+                    continue;
+                }
 
-                existingModelNames.add(item.getModel());
+                ProductModel productModel = new ProductModel();
+                productModel.setProductId(leafId);
+                productModel.setProductCode(code);
+                productModel.setModel(model);
+                productModel.setUnit(trimToNull(row.getUnit()));
+                productModelMapper.insert(productModel);
+                successCount++;
             }
-
-            if (!waitToSaveList.isEmpty()) {
-                saveBatch(waitToSaveList);
-            }
-
-            if (skipCount == 0) {
-                return AjaxResult.success(String.format("鎴愬姛瀵煎叆 %d 鏉℃暟鎹�", waitToSaveList.size()));
-            } else {
-                return AjaxResult.success(String.format("鎴愬姛瀵煎叆 %d 鏉★紝璺宠繃宸插瓨鍦ㄦ暟鎹� %d 鏉�", waitToSaveList.size(), skipCount));
-            }
-        } catch (Exception e) {
-            log.error("瀵煎叆浜у搧瑙勬牸寮傚父", e);
-            throw new ServiceException("瀵煎叆澶辫触");
         }
+
+        String message = skipCount > 0
+                ? String.format("鎴愬姛瀵煎叆 %d 鏉★紝璺宠繃宸插瓨鍦� %d 鏉�", successCount, skipCount)
+                : String.format("鎴愬姛瀵煎叆 %d 鏉�", successCount);
+        return AjaxResult.success(message);
+    }
+
+    private Long findOrCreateTopProduct(String name) {
+        Product root = productMapper.selectOne(new LambdaQueryWrapper<Product>()
+                .isNull(Product::getParentId)
+                .eq(Product::getProductName, name)
+                .last("limit 1"));
+        if (root != null) {
+            return root.getId();
+        }
+        Product product = new Product();
+        product.setProductName(name);
+        productMapper.insert(product);
+        return product.getId();
+    }
+
+    private Long ensureChildNode(Map<String, Long> nodeCache, String path, Long parentId, String name) {
+        Long id = nodeCache.get(path);
+        if (id != null) {
+            return id;
+        }
+        Product existing = productMapper.selectOne(new LambdaQueryWrapper<Product>()
+                .eq(Product::getParentId, parentId)
+                .eq(Product::getProductName, name)
+                .last("limit 1"));
+        if (existing != null) {
+            nodeCache.put(path, existing.getId());
+            return existing.getId();
+        }
+        Product product = new Product();
+        product.setParentId(parentId);
+        product.setProductName(name);
+        productMapper.insert(product);
+        nodeCache.put(path, product.getId());
+        return product.getId();
+    }
+
+    private Set<String> loadProductCodes(Long productId) {
+        List<ProductModel> list = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
+                .eq(ProductModel::getProductId, productId));
+        Set<String> codes = new HashSet<>();
+        for (ProductModel productModel : list) {
+            if (productModel.getProductCode() != null) {
+                codes.add(productModel.getProductCode().trim());
+            }
+        }
+        return codes;
+    }
+
+    private Set<String> loadProductModels(Long productId) {
+        List<ProductModel> list = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>()
+                .eq(ProductModel::getProductId, productId));
+        Set<String> models = new HashSet<>();
+        for (ProductModel productModel : list) {
+            if (StringUtils.isEmpty(productModel.getModel())) {
+                models.add("/");
+            } else {
+                models.add(productModel.getModel().trim());
+            }
+        }
+        return models;
+    }
+
+    private String generateUniqueCode(Set<String> generatedCodes) {
+        for (int i = 0; i < 200; i++) {
+            String code = randomCode(5);
+            if (generatedCodes.contains(code)) {
+                continue;
+            }
+            Long count = productModelMapper.selectCount(new LambdaQueryWrapper<ProductModel>()
+                    .eq(ProductModel::getProductCode, code));
+            if (count == null || count == 0) {
+                generatedCodes.add(code);
+                return code;
+            }
+        }
+        throw new ServiceException("鐢熸垚鍞竴浜у搧缂栫爜澶辫触锛岃閲嶈瘯");
+    }
+
+    private static String randomCode(int length) {
+        String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            sb.append(chars.charAt(ThreadLocalRandom.current().nextInt(chars.length())));
+        }
+        return sb.toString();
+    }
+
+    private static String trimToNull(String value) {
+        if (value == null) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
     }
 }
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
index 2b9eb40..b8f2f44 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -15,15 +15,23 @@
 import com.ruoyi.basic.vo.ProductModelVo;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 @Service
 @AllArgsConstructor
+@Slf4j
 public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
 
     private ProductMapper productMapper;
@@ -130,16 +138,40 @@
     }
 
     @Override
+    public void exportImportTemplate(HttpServletResponse response) {
+        String templatePath = "static/浜у搧瀵煎叆妯℃澘.xlsx";
+        try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(templatePath)) {
+            if (inputStream == null) {
+                throw new FileNotFoundException("妯℃澘鏂囦欢涓嶅瓨鍦細" + templatePath);
+            }
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("浜у搧瀵煎叆妯℃澘.xlsx", "utf-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
+            try (OutputStream outputStream = response.getOutputStream()) {
+                byte[] buffer = new byte[1024];
+                int len;
+                while ((len = inputStream.read(buffer)) > 0) {
+                    outputStream.write(buffer, 0, len);
+                }
+                outputStream.flush();
+            }
+        } catch (IOException e) {
+            log.error("瀵煎嚭浜у搧瀵煎叆妯℃澘澶辫触", e);
+            try {
+                response.getWriter().write("妯℃澘瀵煎嚭澶辫触锛�" + e.getMessage());
+            } catch (IOException ex) {
+                log.error("鍝嶅簲杈撳嚭閿欒", ex);
+            }
+        }
+    }
+
+    @Override
     public int delProductByIds(Long[] ids) {
-        // 1. 鍒犻櫎瀛愯〃 product_model 涓叧鑱旂殑鏁版嵁
         LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.in(ProductModel::getProductId, ids);
         productModelMapper.delete(queryWrapper);
 
-        // 2. 鍒犻櫎涓昏〃 product 鏁版嵁
-        int deleteCount = productMapper.deleteBatchIds(Arrays.asList(ids));
-
-        return deleteCount;
+        return productMapper.deleteBatchIds(Arrays.asList(ids));
     }
-
 }
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 0513bdc..6d275e1 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java
@@ -217,22 +217,28 @@
             return;
         }
         List<ProductionBomStructure> updateList = new ArrayList<>();
-        BigDecimal lastProcessDemandedQuantity = orderQuantity;
+        // 闇�姹傞噺鎸夆�滅埗鑺傜偣闇�姹傞噺 脳 鏈鍗曡�椻�濋�愬眰璁$畻锛屽厔寮熻妭鐐逛簰涓嶅奖鍝嶏紝缁撴灉涓庡綍鍏ラ『搴忔棤鍏炽��
+        Map<Long, BigDecimal> demandedQuantityById = new HashMap<>();
         for (ProductionBomStructure structure : structureList) {
             if (structure == null || structure.getId() == null) {
                 continue;
             }
 
-            BigDecimal demandedQuantity = lastProcessDemandedQuantity.multiply(defaultDecimal(structure.getUnitQuantity()));
-//            if (compareDecimal(structure.getDemandedQuantity(), demandedQuantity) == 0) {
-//                continue;
-//            }
+            // 鏍硅妭鐐逛互璁㈠崟鏁伴噺涓哄熀鏁帮紝瀛愯妭鐐逛互鐖惰妭鐐归渶姹傞噺涓哄熀鏁般��
+            boolean rootNode = structure.getParentId() == null || structure.getParentId() == 0L;
+            BigDecimal baseQuantity = rootNode
+                    ? orderQuantity
+                    : demandedQuantityById.get(structure.getParentId());
+            if (baseQuantity == null) {
+                throw new ServiceException("BOM缁撴瀯鏁版嵁寮傚父锛屽瓨鍦ㄦ壘涓嶅埌鐖惰妭鐐圭殑瀛愯妭鐐�");
+            }
+            BigDecimal demandedQuantity = baseQuantity.multiply(defaultDecimal(structure.getUnitQuantity()));
             ProductionBomStructure update = new ProductionBomStructure();
             update.setId(structure.getId());
             update.setDemandedQuantity(demandedQuantity);
             updateList.add(update);
             structure.setDemandedQuantity(demandedQuantity);
-            lastProcessDemandedQuantity = demandedQuantity;
+            demandedQuantityById.put(structure.getId(), demandedQuantity);
         }
         if (!updateList.isEmpty()) {
             this.updateBatchById(updateList);
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 5c103b8..e31fc86 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -439,7 +439,8 @@
         productionOrderBomMapper.insert(orderBom);
 
         Map<Long, Long> idMap = new HashMap<>();
-        BigDecimal lastProcessDemandedQuantity = orderQuantity;
+        // 闇�姹傞噺鎸夆�滅埗鑺傜偣闇�姹傞噺 脳 鏈鍗曡�椻�濋�愬眰璁$畻锛屽厔寮熻妭鐐逛簰涓嶅奖鍝嶏紝缁撴灉涓庡綍鍏ラ『搴忔棤鍏炽��
+        Map<Long, BigDecimal> demandedQuantityBySourceId = new HashMap<>();
         for (TechnologyBomStructure source : structureList) {
             // 瀛愯妭鐐� parentId 闇�瑕佹槧灏勬垚鏂板揩鐓ц妭鐐� id锛屾墠鑳戒繚鐣欏師濮� BOM 灞傜骇銆�
             ProductionBomStructure target = new ProductionBomStructure();
@@ -449,11 +450,19 @@
             target.setProductModelId(source.getProductModelId());
             target.setTechnologyOperationId(source.getOperationId());
             target.setUnitQuantity(source.getUnitQuantity());
-            target.setDemandedQuantity(lastProcessDemandedQuantity.multiply(source.getUnitQuantity()));
+            // 鏍硅妭鐐逛互璁㈠崟鏁伴噺涓哄熀鏁帮紝瀛愯妭鐐逛互鐖惰妭鐐归渶姹傞噺涓哄熀鏁般��
+            boolean rootNode = source.getParentId() == null || source.getParentId() == 0L;
+            BigDecimal baseQuantity = rootNode
+                    ? orderQuantity
+                    : demandedQuantityBySourceId.get(source.getParentId());
+            if (baseQuantity == null) {
+                throw new ServiceException("BOM缁撴瀯鏁版嵁寮傚父锛屽瓨鍦ㄦ壘涓嶅埌鐖惰妭鐐圭殑瀛愯妭鐐�");
+            }
+            target.setDemandedQuantity(baseQuantity.multiply(defaultDecimal(source.getUnitQuantity())));
             target.setUnit(source.getUnit());
             productionBomStructureMapper.insert(target);
             idMap.put(source.getId(), target.getId());
-            lastProcessDemandedQuantity = target.getDemandedQuantity();
+            demandedQuantityBySourceId.put(source.getId(), target.getDemandedQuantity());
         }
         return orderBom;
     }
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
index a140608..d69b3e7 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -8,6 +8,9 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.deepoove.poi.XWPFTemplate;
 import com.deepoove.poi.config.Configure;
+import com.deepoove.poi.data.TextRenderData;
+import com.deepoove.poi.policy.TextRenderPolicy;
+import com.deepoove.poi.template.run.RunTemplate;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.DateUtils;
@@ -32,6 +35,7 @@
 import com.ruoyi.stock.service.StockInventoryService;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
+import org.apache.poi.xwpf.usermodel.XWPFRun;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -43,6 +47,7 @@
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -247,6 +252,11 @@
         InputStream inputStream = this.getClass().getResourceAsStream("/static/report-template.docx");
         Configure configure = Configure.builder()
                 .bind("paramList", new HackLoopTableRenderPolicy())
+                .bind("inspect.checkTime", (eleTemplate, data, xwpfTemplate) -> {
+                    XWPFRun run = ((RunTemplate) eleTemplate).getRun();
+                    String text = data == null ? "" : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, (Date) data);
+                    TextRenderPolicy.Helper.renderTextRun(run, new TextRenderData(text));
+                })
                 .build();
         String finalInspectType = inspectType;
         XWPFTemplate template = XWPFTemplate.compile(inputStream, configure).render(
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 e697a21..d05b0b0 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -38,7 +38,10 @@
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
@@ -133,11 +136,13 @@
             shippingInfoService.delete(shippingInfos.stream().map(ShippingInfo::getId).collect(Collectors.toList()));
         }
 
-        // 鍙兘灞炰簬澶氫釜涓昏〃
-        Set<Long> mainIds = deletedProducts.stream()
-                .map(SalesLedgerProduct::getSalesLedgerId)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toSet());
+        // 鍙兘灞炰簬澶氫釜涓昏〃锛屾寜 type 鍒嗙粍锛堥攢鍞�/閲囪喘浜у搧瀛愯〃鍏辩敤涓斾富琛� id 鍙兘鐩稿悓锛�
+        Map<Integer, Set<Long>> mainIdsByType = new HashMap<>();
+        deletedProducts.stream()
+                .filter(product -> product.getSalesLedgerId() != null)
+                .forEach(product -> mainIdsByType
+                        .computeIfAbsent(product.getType() == null ? 1 : product.getType(), key -> new HashSet<>())
+                        .add(product.getSalesLedgerId()));
 
         // 2. 鎵ц鍒犻櫎鎿嶄綔
         int result = salesLedgerProductMapper.deleteBatchIds(Arrays.asList(ids));
@@ -145,20 +150,33 @@
         deleteProductionData(Arrays.asList(ids));
 
         // 3. 瀵规瘡涓富琛↖D杩涜閲戦鏇存柊
-        for (Long salesLedgerId : mainIds) {
-            LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
-            wrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId);
-            List<SalesLedgerProduct> remainingProducts = salesLedgerProductMapper.selectList(wrapper);
+        mainIdsByType.forEach((type, mainIds) -> {
+            for (Long salesLedgerId : mainIds) {
+                LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId)
+                        .eq(SalesLedgerProduct::getType, type);
+                List<SalesLedgerProduct> remainingProducts = salesLedgerProductMapper.selectList(wrapper);
 
-            // 璋冪敤閫氱敤鏂规硶鏇存柊涓昏〃閲戦
-            updateMainContractAmount(
-                    salesLedgerId,
-                    remainingProducts,
-                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
-                    salesLedgerMapper,
-                    SalesLedger.class
-            );
-        }
+                // 璋冪敤閫氱敤鏂规硶鏇存柊涓昏〃閲戦
+                if (type == 1) {
+                    updateMainContractAmount(
+                            salesLedgerId,
+                            remainingProducts,
+                            SalesLedgerProduct::getTaxInclusiveTotalPrice,
+                            salesLedgerMapper,
+                            SalesLedger.class
+                    );
+                } else {
+                    updateMainContractAmount(
+                            salesLedgerId,
+                            remainingProducts,
+                            SalesLedgerProduct::getTaxInclusiveTotalPrice,
+                            purchaseLedgerMapper,
+                            PurchaseLedger.class
+                    );
+                }
+            }
+        });
         return result;
     }
 
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index 7f2caf2..7bcfafa 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -687,8 +687,10 @@
             return 0;
         }
         // 鍒犻櫎閿�鍞鐞嗘暟鎹�
+        // 浜у搧瀛愯〃閿�鍞�/閲囪喘鍏辩敤锛屼笖涓昏〃 id 鍙兘鐩稿悓锛屽繀椤绘寜 type 鍖哄垎锛屽惁鍒欎細璇垹閲囪喘鍙拌处鐨勪骇鍝佹暟鎹�
         LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList)
+                .eq(SalesLedgerProduct::getType, 1)
                 .select(SalesLedgerProduct::getId);
 
         List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(queryWrapper);
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 8894288..8d7d77c 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java
@@ -116,9 +116,21 @@
         }
         if (oldBom.getProductModelId() != null && !oldBom.getProductModelId().equals(technologyBom.getProductModelId())) {
             technologyRoutingMapper.updateProductModelByBomId(technologyBom.getProductModelId(), technologyBom.getId().longValue());
-            technologyBomStructureService.remove(Wrappers.<TechnologyBomStructure>lambdaQuery()
-                    .eq(TechnologyBomStructure::getBomId, technologyBom.getId().longValue()));
-            initRootStructure(technologyBom.getId().longValue(), technologyBom.getProductModelId());
+            // 瑙勬牸鍙樺寲鏃朵粎鏇存柊鏍硅妭鐐规寚鍚戠殑瑙勬牸锛屼繚鐣欎笅绾OM缁撴瀯
+            List<TechnologyBomStructure> roots = technologyBomStructureService.list(Wrappers.<TechnologyBomStructure>lambdaQuery()
+                    .eq(TechnologyBomStructure::getBomId, technologyBom.getId().longValue())
+                    //.isNull(TechnologyBomStructure::getParentId)
+            );
+            if (roots.isEmpty()) {
+                initRootStructure(technologyBom.getId().longValue(), technologyBom.getProductModelId());
+            } else {
+                ProductModel productModel = productModelService.getById(technologyBom.getProductModelId());
+                for (TechnologyBomStructure root : roots) {
+                    root.setProductModelId(technologyBom.getProductModelId());
+                    root.setUnit(productModel.getUnit());
+                    technologyBomStructureService.updateById(root);
+                }
+            }
         }
         if (technologyBom.getBomNo() == null) {
             technologyBom.setBomNo(oldBom.getBomNo());
diff --git a/src/main/resources/application-qhddzxc.yml b/src/main/resources/application-qhddzxc.yml
new file mode 100644
index 0000000..5bf32da
--- /dev/null
+++ b/src/main/resources/application-qhddzxc.yml
@@ -0,0 +1,268 @@
+# 椤圭洰鐩稿叧閰嶇疆
+ruoyi:
+  # 鍚嶇О
+  name: RuoYi
+  # 鐗堟湰
+  version: 3.8.9
+  # 鐗堟潈骞翠唤
+  copyrightYear: 2025
+  # 鏂囦欢璺緞 绀轰緥锛� Windows閰嶇疆D:/ruoyi/uploadPath锛孡inux閰嶇疆 /home/ruoyi/uploadPath锛�
+  profile: /javaWork/product-inventory-management/file
+
+  # 鑾峰彇ip鍦板潃寮�鍏�
+  addressEnabled: false
+  # 楠岃瘉鐮佺被鍨� math 鏁板瓧璁$畻 char 瀛楃楠岃瘉
+  captchaType: math
+  # 鍗忓悓瀹℃壒缂栧彿鍓嶇紑(閰嶇疆鏂囦欢鍚庣紑鍛藉悕)
+  approvalNumberPrefix: NEW
+
+  # 涓帹 Unipush 閰嶇疆
+  getui:
+    appId: PfjyAAE0FK64FaO1w2CMb1
+    appKey: zTMb831OEL6J4GK1uE3Ob4
+    masterSecret: K1GFtsv42v61tXGnF7SGE5
+    domain: https://restapi.getui.cn/v2/
+    # 绂荤嚎鎺ㄩ�佷娇鐢ㄧ殑鍖呭悕/缁勪欢鍚�
+    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
+
+# 寮�鍙戠幆澧冮厤缃�
+server:
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+  port: 9003
+  servlet:
+    # 搴旂敤鐨勮闂矾寰�
+    context-path: /
+  tomcat:
+    # tomcat鐨刄RI缂栫爜
+    uri-encoding: UTF-8
+    # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
+    accept-count: 1000
+    threads:
+      # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
+      max: 800
+      # Tomcat鍚姩鍒濆鍖栫殑绾跨▼鏁帮紝榛樿鍊�10
+      min-spare: 100
+
+# 鏃ュ織閰嶇疆
+logging:
+  level:
+    com.ruoyi: warn
+    org.springframework: warn
+
+minio:
+  endpoint: http://114.132.189.42/
+  port: 7019
+  secure: false
+  accessKey: admin
+  secretKey: 12345678
+  preview-expiry: 24 # 棰勮鍦板潃榛樿24灏忔椂
+  default-bucket: jxc
+# 鐢ㄦ埛閰嶇疆
+user:
+  password:
+    # 瀵嗙爜鏈�澶ч敊璇鏁�
+    maxRetryCount: 5
+    # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+    lockTime: 10
+
+# Spring閰嶇疆
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 涓诲簱鏁版嵁婧�
+      master:
+        url: jdbc:mysql://172.17.0.1:3306/product-inventory-management-qhddzxc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: xd@123456..
+      # 浠庡簱鏁版嵁婧�
+      slave:
+        # 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
+        enabled: false
+        url:
+        username:
+        password:
+      # 鍒濆杩炴帴鏁�
+      initialSize: 5
+      # 鏈�灏忚繛鎺ユ睜鏁伴噺
+      minIdle: 10
+      # 鏈�澶ц繛鎺ユ睜鏁伴噺
+      maxActive: 20
+      # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+      maxWait: 60000
+      # 閰嶇疆杩炴帴瓒呮椂鏃堕棿
+      connectTimeout: 30000
+      # 閰嶇疆缃戠粶瓒呮椂鏃堕棿
+      socketTimeout: 60000
+      # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶆槸姣
+      timeBetweenEvictionRunsMillis: 60000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      minEvictableIdleTimeMillis: 300000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      maxEvictableIdleTimeMillis: 900000
+      # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        allow:
+        url-pattern: /druid/*
+        # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 鎱QL璁板綍
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 璧勬簮淇℃伅
+  messages:
+    # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+    basename: i18n/messages
+  # 鏂囦欢涓婁紶
+  servlet:
+    multipart:
+      # 鍗曚釜鏂囦欢澶у皬
+      max-file-size: 1GB
+      # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+      max-request-size: 2GB
+  # 鏈嶅姟妯″潡
+  devtools:
+    restart:
+      # 鐑儴缃插紑鍏�
+      enabled: false
+  # redis 閰嶇疆
+  data:
+    mongodb:
+      uri: mongodb://114.132.189.42:9028/chat_memory_db_qhddzxc
+    # redis 閰嶇疆
+    redis:
+      # 鍦板潃
+#      host: 127.0.0.1
+      host: 172.17.0.1
+      # 绔彛锛岄粯璁や负6379
+      port: 6379
+      # 鏁版嵁搴撶储寮�
+      database: 0
+      # 瀵嗙爜
+      #    password: root2022!
+      password:
+
+      # 杩炴帴瓒呮椂鏃堕棿
+      timeout: 10s
+      lettuce:
+        pool:
+          # 杩炴帴姹犱腑鐨勬渶灏忕┖闂茶繛鎺�
+          min-idle: 0
+          # 杩炴帴姹犱腑鐨勬渶澶х┖闂茶繛鎺�
+          max-idle: 8
+          # 杩炴帴姹犵殑鏈�澶ф暟鎹簱杩炴帴鏁�
+          max-active: 8
+          # #杩炴帴姹犳渶澶ч樆濉炵瓑寰呮椂闂达紙浣跨敤璐熷�艰〃绀烘病鏈夐檺鍒讹級
+          max-wait: -1ms
+
+  # Quartz瀹氭椂浠诲姟閰嶇疆锛堟柊澧為儴鍒嗭級
+  quartz:
+    job-store-type: jdbc  # 浣跨敤鏁版嵁搴撳瓨鍌�
+    jdbc:
+      initialize-schema: never  # 棣栨杩愯鏃惰嚜鍔ㄥ垱寤鸿〃缁撴瀯锛屾垚鍔熷悗鏀逛负never
+      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL琛ㄧ粨鏋勮剼鏈�
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceName: RuoYiScheduler
+            instanceId: AUTO
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL閫傞厤
+            tablePrefix: qrtz_  # 琛ㄥ悕鍓嶇紑锛屼笌鑴氭湰涓�鑷�
+            isClustered: false  # 鍗曡妭鐐规ā寮忥紙闆嗙兢闇�鏀逛负true锛�
+            clusterCheckinInterval: 10000
+            txIsolationLevelSerializable: true
+          threadPool:
+            class: org.quartz.simpl.SimpleThreadPool
+            threadCount: 10  # 绾跨▼姹犲ぇ灏�
+            threadPriority: 5
+            makeThreadsDaemons: true
+          updateCheck: false  # 鍏抽棴鐗堟湰妫�鏌�
+# token閰嶇疆
+token:
+  # 浠ょ墝鑷畾涔夋爣璇�
+  header: Authorization
+  # 浠ょ墝瀵嗛挜
+  secret: xpAVjhCjQDaDB7mjPAzMDSbQWXNu2zYkTdDNUsPMS5Xx8QMmQVYN7n74eZrYJxDJ
+  # 浠ょ墝鏈夋晥鏈燂紙榛樿30鍒嗛挓锛�
+  expireTime: 450
+
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+  # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
+  typeAliasesPackage: com.ruoyi.**.pojo
+  # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
+  configLocation: classpath:mybatis/mybatis-config.xml
+  global-config:
+    enable-sql-runner: true
+    db-config:
+      id-type: auto
+
+# PageHelper鍒嗛〉鎻掍欢
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger閰嶇疆
+swagger:
+  # 鏄惁寮�鍚痵wagger
+  enabled: true
+  # 璇锋眰鍓嶇紑
+  pathMapping: /dev-api
+
+# 闃叉XSS鏀诲嚮
+xss:
+  # 杩囨护寮�鍏�
+  enabled: true
+  # 鎺掗櫎閾炬帴锛堝涓敤閫楀彿鍒嗛殧锛�
+  excludes: /system/notice
+  # 鍖归厤閾炬帴
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 浠g爜鐢熸垚
+gen:
+  # 浣滆��
+  author: ruoyi
+  # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+  packageName: com.ruoyi.project.system
+  # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸true
+  autoRemovePre: false
+  # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+  tablePrefix: sys_
+  # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
+  allowOverwrite: false
+
+# 鏂囦欢涓婁紶閰嶇疆
+file:
+  temp-dir: /javaWork/product-inventory-management/file/temp/uploads   # 涓存椂鐩綍
+  upload-dir: /javaWork/product-inventory-management/file/prod/uploads # 姝e紡鐩綍
+  path: /javaWork/product-inventory-management/file # 涓婁紶鐩綍
+  urlPrefix: /prod-api/common # 閾炬帴鍓嶇紑
+  domain: http://1.15.17.182:9111 # 鍩熷悕鍓嶇紑
+  expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+  useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
diff --git a/src/main/resources/mapper/account/AccountStatementMapper.xml b/src/main/resources/mapper/account/AccountStatementMapper.xml
index 324d243..9f99288 100644
--- a/src/main/resources/mapper/account/AccountStatementMapper.xml
+++ b/src/main/resources/mapper/account/AccountStatementMapper.xml
@@ -122,12 +122,12 @@
             -- 杩涢」绋庢槑缁嗭紙閲囪喘璁㈠崟锛�
             SELECT
                 pl.purchase_contract_number AS invoiceNo,
-                pl.sales_contract_no AS salesContractNo,
-                pl.supplier_name AS supplierName,
-                NULL AS customerName,
+                pl.purchase_contract_number AS salesContractNo,
+                NULL AS supplierName,
+                pl.supplier_name AS customerName,
                 '杩涢」' AS orderType,
                 pl.entry_date AS invoiceDate,
-                slp.tax_rate AS taxRate,
+                MAX(slp.tax_rate) AS taxRate,
                 ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS jTaxAmount,
                 0 AS xTaxAmount,
                 ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS taxAmount
@@ -148,7 +148,7 @@
                 sl.customer_name AS customerName,
                 '閿�椤�' AS orderType,
                 sl.entry_date AS invoiceDate,
-                slp.tax_rate AS taxRate,
+                MAX(slp.tax_rate)  AS taxRate,
                 0 AS jTaxAmount,
                 ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS xTaxAmount,
                 ROUND(SUM(slp.tax_inclusive_total_price - slp.tax_exclusive_total_price), 2) AS taxAmount
diff --git a/src/main/resources/mapper/basic/SupplierManageMapper.xml b/src/main/resources/mapper/basic/SupplierManageMapper.xml
index 4ac8ef6..8496792 100644
--- a/src/main/resources/mapper/basic/SupplierManageMapper.xml
+++ b/src/main/resources/mapper/basic/SupplierManageMapper.xml
@@ -70,44 +70,74 @@
         </where>
     </select>
     <select id="supplierTransactions" resultType="com.ruoyi.purchase.vo.SupplierTransactionsVo">
-        select T1.supplier_id,
+        select c.supplier_id,
                sm.supplier_name,
-               T1.contractAmounts,
-               IFNULL(T2.paymentAmount, 0) AS paymentAmount,
-               IFNULL(T3.InboundAmount, 0) - IFNULL(T4.returnAmount, 0) AS payableAmount
-        from (select supplier_id, sum(contract_amount) as contractAmounts from purchase_ledger group by supplier_id) T1
-        left join (select supplier_id, sum(payment_amount) as paymentAmount from account_purchase_payment group by supplier_id) T2 on T1.supplier_id = T2.supplier_id
-        left join (
-            SELECT
-                pl.supplier_id,
-                sum(sir.stock_in_num * slp.tax_inclusive_unit_price) AS InboundAmount
-            FROM stock_in_record sir
-                     -- 10 绫诲瀷鎵嶅叧鑱旇川妫�琛�
-                     LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
-                -- 鍔ㄦ�佸叧鑱旈噰璐紙鑷姩閫傞厤 7 鍜� 10锛�
-                     LEFT JOIN purchase_ledger pl
-                               ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
-                -- 浜у搧鍏宠仈涓嶅姩
-                     LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
-            -- 鏉′欢
-            WHERE sir.approval_status = 1 AND slp.type = 2
-              AND sir.record_type IN ('7','10')
+               c.contractAmounts,
+               -- 浠樻閲戦锛氶�愬悎鍚屾寜鍚堝悓閲戦灏侀《鍚庡啀姹囨�伙紝閬垮厤閲嶅浠樻瀵艰嚧浠樻閲戦澶т簬鍚堝悓閲戦
+               c.paymentAmount,
+               -- 搴斾粯閲戦锛氶�愬悎鍚屻�屽彇(鍏ュ簱閲戦, 鍚堝悓閲戦)杈冨皬鑰� - 灏侀《鍚庝粯娆鹃噾棰� - 閫�璐ч噾棰濄�嶅悗鍐嶆眹鎬伙紝涓嬮檺涓� 0
+               c.payableAmount
+        from (
+            select pl.supplier_id,
+                   sum(COALESCE(pl.contract_amount, 0)) as contractAmounts,
+                   sum(LEAST(IFNULL(pay.paymentAmount, 0), COALESCE(pl.contract_amount, 0))) as paymentAmount,
+                   sum(GREATEST(
+                       IFNULL(ib.InboundAmount, 0)
+                           - IF(ib.InboundAmount <![CDATA[ > ]]> COALESCE(pl.contract_amount, 0), ib.InboundAmount - COALESCE(pl.contract_amount, 0), 0)
+                       - LEAST(IFNULL(pay.paymentAmount, 0), COALESCE(pl.contract_amount, 0))
+                       - IFNULL(rt.returnAmount, 0)
+                   , 0)) as payableAmount
+            from purchase_ledger pl
+            left join (
+                select
+                    t.purchase_ledger_id as id,
+                    sum(t.payment_amount) as paymentAmount
+                from (
+                    select
+                        app.id,
+                        app.payment_amount,
+                        pl.id as purchase_ledger_id
+                    from account_purchase_payment app
+                    left join account_payment_application apa on app.account_payment_application_id = apa.id
+                    left join stock_in_record sir on FIND_IN_SET(sir.id, apa.stock_in_record_ids) > 0
+                    LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
+                    LEFT JOIN purchase_ledger pl
+                              ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
+                    WHERE sir.approval_status = 1
+                      AND sir.record_type IN ('7','10')
+                    group by app.id, pl.id
+                ) t
+                group by t.purchase_ledger_id
+            ) pay on pay.id = pl.id
+            left join (
+                SELECT
+                    pl.id,
+                    sum(sir.stock_in_num * slp.tax_inclusive_unit_price) AS InboundAmount
+                FROM stock_in_record sir
+                         LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
+                         LEFT JOIN purchase_ledger pl
+                                   ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
+                         LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
+                WHERE sir.approval_status = 1 AND slp.type = 2
+                  AND sir.record_type IN ('7','10')
+                group by pl.id
+            ) ib on ib.id = pl.id
+            left join (
+                select pl.id,
+                       sum(pro.total_amount) as returnAmount
+                from purchase_return_orders pro
+                         left join purchase_ledger pl on pro.purchase_ledger_id = pl.id
+                group by pl.id
+            ) rt on rt.id = pl.id
             group by pl.supplier_id
-        ) T3 on T3.supplier_id=T1.supplier_id
-        left join (
-            select
-                supplier_id,
-                sum(total_amount) as returnAmount
-            from purchase_return_orders pro
-            group by supplier_id
-        ) T4 on T4.supplier_id=T1.supplier_id
-        left join supplier_manage sm on T1.supplier_id = sm.id
+        ) c
+        left join supplier_manage sm on c.supplier_id = sm.id
         <where>
             <if test="supplierName!=null and supplierName!=''">
                 AND sm.supplier_name LIKE CONCAT('%',#{supplierName},'%')
             </if>
         </where>
-        order by T1.supplier_id desc
+        order by c.supplier_id desc
     </select>
     <select id="supplierTransactionsDetails"
             resultType="com.ruoyi.purchase.vo.SupplierTransactionsDetailsVo">
@@ -115,8 +145,17 @@
               pl.purchase_contract_number,
               pl.execution_date,
               pl.contract_amount,
-              IFNULL(T1.paymentAmount, 0) AS paymentAmount,
-              IFNULL(T2.InboundAmount, 0) - IFNULL(T3.returnAmount, 0) AS payableAmount
+              -- 浠樻閲戦鎸夊悎鍚岄噾棰濆皝椤讹紝閬垮厤閲嶅浠樻瀵艰嚧浠樻閲戦澶т簬鍚堝悓閲戦
+              LEAST(IFNULL(T1.paymentAmount, 0), COALESCE(pl.contract_amount, 0)) AS paymentAmount,
+              -- 搴斾粯閲戦 = 鍙�(鍏ュ簱閲戦, 鍚堝悓閲戦)鐨勮緝灏忚�� - 灏侀《鍚庝粯娆鹃噾棰� - 閫�璐ч噾棰�
+              -- 鍏ュ簱閲戦鎸夊悎鍚岄噾棰濆皝椤讹紝閬垮厤鍏ュ簱娴佺▼閲嶅鎻愪氦瀵艰嚧搴斾粯閲戦澶т簬鍚堝悓閲戦锛�
+              -- 宸蹭粯娆鹃噾棰濓紙鍥炴/浠樻鍗曪級涓庨��璐ч噾棰濆潎闇�浠庡簲浠樹腑鎵e噺銆�
+              GREATEST(
+                  IFNULL(T2.InboundAmount, 0)
+                      - IF(T2.InboundAmount <![CDATA[ > ]]> COALESCE(pl.contract_amount, 0), T2.InboundAmount - COALESCE(pl.contract_amount, 0), 0)
+                  - LEAST(IFNULL(T1.paymentAmount, 0), COALESCE(pl.contract_amount, 0))
+                  - IFNULL(T3.returnAmount, 0)
+              , 0) AS payableAmount
        from purchase_ledger pl
        left join (
            select
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index ed780a7..2681163 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -222,7 +222,7 @@
         unit,
         product_name,
         product_id
-        order by combined.create_time desc
+        order by MAX(combined.create_time) desc
     </select>
 
     <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
@@ -537,6 +537,7 @@
                  left join product p on pm.product_id = p.id
         where si.product_model_id = #{productModelId}
         and si.qualitity > IFNULL(sd.qualitity, 0)
+        order by si.create_time asc
     </select>
 
     <select id="getBatchNoQty" resultType="com.ruoyi.stock.dto.StockInventoryDto">
diff --git "a/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx" "b/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
new file mode 100644
index 0000000..64d7b69
--- /dev/null
+++ "b/src/main/resources/static/\344\272\247\345\223\201\345\257\274\345\205\245\346\250\241\346\235\277.xlsx"
Binary files differ

--
Gitblit v1.9.3