From a274b897e58c958903c3e00da6c1ccb16646a979 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 12 三月 2026 11:54:13 +0800
Subject: [PATCH] feat: 物料规格和物料的新增、修改和删除

---
 src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java |   16 
 src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java    |  235 +++++++++++++---
 src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java                    |   66 ++++
 src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java            |   16 +
 src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java                  |   38 ++
 src/main/java/com/ruoyi/production/pojo/ProductMaterial.java                       |   34 --
 src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java    |   69 ++++
 src/main/java/com/ruoyi/production/service/ProductMaterialService.java             |   11 
 doc/宁夏-中盛建材.sql                                                                    |   69 ++--
 src/main/resources/mapper/production/ProductMaterialSkuMapper.xml                  |   22 +
 src/main/java/com/ruoyi/production/controller/ProductMaterialController.java       |   42 +++
 src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java |  166 +++++++++++
 src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java          |   26 +
 src/main/resources/mapper/production/ProductMaterialMapper.xml                     |    8 
 14 files changed, 685 insertions(+), 133 deletions(-)

diff --git "a/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql" "b/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
index 9c16abe..4b84c86 100644
--- "a/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
+++ "b/doc/\345\256\201\345\244\217-\344\270\255\347\233\233\345\273\272\346\235\220.sql"
@@ -111,42 +111,41 @@
 
 CREATE TABLE `product_material`
 (
-    `id`                    INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '涓婚敭ID',
-    `tenant_id`             BIGINT       DEFAULT NULL COMMENT '绉熸埛ID',
-    `material_type_id`      INT          DEFAULT NULL COMMENT '鍏宠仈鐗╂枡绫诲瀷ID',
-    `inventory_category_id` INT          DEFAULT NULL COMMENT '鍏宠仈瀛樿揣绫诲埆ID',
-    `identifier_code`       VARCHAR(100) DEFAULT NULL COMMENT '鏍囪瘑缂栫爜',
-    `material_code`         VARCHAR(100) DEFAULT NULL COMMENT '鐗╂枡浠g爜',
-    `product_name`          VARCHAR(255) DEFAULT NULL COMMENT '浜у搧鍚嶇О',
-    `material_name`         VARCHAR(255) DEFAULT NULL COMMENT '鐗╂枡鍝佸悕',
-    `specification`         VARCHAR(255) DEFAULT NULL COMMENT '瑙勬牸鍨嬪彿',
-    `base_unit`             VARCHAR(50)  DEFAULT NULL COMMENT '鍩烘湰鍗曚綅',
-    `material_attribute`    VARCHAR(100) DEFAULT NULL COMMENT '鐗╂枡灞炴��',
-    `finished_product_name` VARCHAR(100) DEFAULT NULL COMMENT '鎴愬搧鍝佸悕',
-    `originator_name`       VARCHAR(100) DEFAULT NULL COMMENT '鎻愪氦浜哄鍚�',
-    `originator_org`        VARCHAR(255) DEFAULT '瀹佸涓垱缁胯兘瀹炰笟闆嗗洟鏈夐檺鍏徃',
+    `id`                    BIGINT       NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+    `tenant_id`             BIGINT      DEFAULT NULL COMMENT '绉熸埛ID',
+    `material_type_id`      INT         DEFAULT NULL COMMENT '鐗╂枡绫诲瀷ID',
+    `inventory_category_id` INT         DEFAULT NULL COMMENT '瀛樿揣绫诲埆ID',
+    `material_name`         VARCHAR(255) NOT NULL COMMENT '鐗╂枡鍚嶇О',
+    `base_unit`             VARCHAR(50) DEFAULT NULL COMMENT '鍩烘湰鍗曚綅',
     `remark`                TEXT COMMENT '澶囨敞',
-    `create_time`           DATETIME     DEFAULT CURRENT_TIMESTAMP,
-    `update_time`           DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    INDEX `idx_type_id` (`material_type_id`),
-    INDEX `idx_cat_id` (`inventory_category_id`)
+    `create_time`           DATETIME    DEFAULT CURRENT_TIMESTAMP,
+    `update_time`           DATETIME    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_type_id` (`material_type_id`),
+    KEY `idx_cat_id` (`inventory_category_id`),
+    UNIQUE KEY `uk_material_name` (`material_name`)
 ) ENGINE = INNODB
-  DEFAULT CHARSET = utf8mb4 COMMENT = '鐗╂枡淇℃伅琛�';
-
-ALTER TABLE product_material
-    ADD COLUMN form_instance_id   VARCHAR(100) DEFAULT NULL COMMENT '瀹滄惌琛ㄥ崟瀹炰緥ID',
-    ADD COLUMN form_modified_time DATETIME     DEFAULT NULL COMMENT '瀹滄惌淇敼鏃堕棿';
-
-CREATE TABLE `product_material_config`
+  DEFAULT CHARSET = utf8mb4 COMMENT = '鐗╂枡涓昏〃';
+CREATE TABLE `product_material_sku`
 (
-    `id`          int          NOT NULL AUTO_INCREMENT PRIMARY KEY,
-    `config_type` varchar(50)  NOT NULL COMMENT '鍖哄垎绫诲瀷: MATERIAL_TYPE 鎴� INVENTORY_CAT',
-    `config_name` varchar(100) NOT NULL COMMENT '鏄剧ず鐨勫悕绉�'
-) ENGINE = InnoDB COMMENT ='鐗╂枡淇℃伅琛ㄩ厤缃〃';
+    `id`                 BIGINT NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+    `material_id`        BIGINT NOT NULL COMMENT '鐗╂枡ID',
+    `identifier_code`    VARCHAR(100) DEFAULT NULL COMMENT '鏍囪瘑缂栫爜',
+    `material_code`      VARCHAR(100) DEFAULT NULL COMMENT '鐗╂枡缂栫爜',
+    `specification`      VARCHAR(255) DEFAULT NULL COMMENT '瑙勬牸鍨嬪彿',
+    `supply_type`        VARCHAR(20)  DEFAULT NULL COMMENT '渚涘簲鏂瑰紡',
+    `originator_name`    VARCHAR(100) DEFAULT NULL COMMENT '鎻愪氦浜哄鍚�',
+    `originator_org`     VARCHAR(255) DEFAULT '瀹佸涓垱缁胯兘瀹炰笟闆嗗洟鏈夐檺鍏徃',
+    `form_instance_id`   VARCHAR(100) DEFAULT NULL COMMENT '瀹滄惌琛ㄥ崟瀹炰緥ID',
+    `form_modified_time` DATETIME     DEFAULT NULL COMMENT '瀹滄惌淇敼鏃堕棿',
+    `create_time`        DATETIME     DEFAULT CURRENT_TIMESTAMP,
+    `update_time`        DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_material_id` (`material_id`),
+    UNIQUE KEY `uk_material_spec` (`material_id`, `specification`),
+    CONSTRAINT `fk_material` FOREIGN KEY (`material_id`) REFERENCES `product_material` (`id`) ON DELETE CASCADE
+) ENGINE = INNODB
+  DEFAULT CHARSET = utf8mb4 COMMENT = '鐗╂枡瑙勬牸琛�';
 
-ALTER TABLE `production_plan`
-    ADD COLUMN `product_material_id` int DEFAULT NULL COMMENT '鍏宠仈鐗╂枡淇℃伅琛↖D' AFTER `material_code`;
-
--- 寤鸿椤轰究鍔犱笂绱㈠紩锛屾彁鍗囧叧鑱旀煡璇㈤�熷害
-ALTER TABLE `production_plan`
-    ADD INDEX `idx_product_material_id` (`product_material_id`);
\ No newline at end of file
+ALTER TABLE product_material_sku
+    DROP FOREIGN KEY fk_material;
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/production/controller/ProductMaterialController.java b/src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
index 144b3f2..e95379f 100644
--- a/src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
+++ b/src/main/java/com/ruoyi/production/controller/ProductMaterialController.java
@@ -3,12 +3,20 @@
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.production.pojo.ProductMaterial;
 import com.ruoyi.production.service.ProductMaterialService;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
 
 /**
  * <br>
@@ -34,4 +42,38 @@
         return AjaxResult.success();
     }
 
+    @GetMapping("/list")
+    @ApiOperation("鐗╂枡鏁版嵁")
+    @Log(title = "鐗╂枡鏁版嵁", businessType = BusinessType.OTHER)
+    public AjaxResult productMaterialList(String materialName) {
+        Map<String, List<ProductMaterial>> productMaterialMap = productMaterialService.ProductMaterialList(materialName);
+        return AjaxResult.success(productMaterialMap);
+    }
+
+    @PostMapping("/add")
+    @ApiOperation("鏂板鐗╂枡")
+    @Log(title = "鏂板鐗╂枡", businessType = BusinessType.INSERT)
+    public AjaxResult addProductMaterial(@RequestBody ProductMaterial productMaterial) {
+        productMaterialService.addProductMaterial(productMaterial);
+        return AjaxResult.success();
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("淇敼鐗╂枡")
+    @Log(title = "淇敼鐗╂枡", businessType = BusinessType.UPDATE)
+    public AjaxResult updateProductMaterial(@RequestBody ProductMaterial productMaterial) {
+        productMaterialService.updateProductMaterial(productMaterial);
+        return AjaxResult.success();
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("鍒犻櫎鐗╂枡")
+    @Log(title = "鍒犻櫎鐗╂枡", businessType = BusinessType.DELETE)
+    public AjaxResult deleteProductMaterial(@RequestBody List<Long> ids) {
+        productMaterialService.deleteProductMaterial(ids);
+        return AjaxResult.success();
+    }
+
+
+
 }
diff --git a/src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java b/src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java
new file mode 100644
index 0000000..f9145d1
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/controller/ProductMaterialSkuController.java
@@ -0,0 +1,69 @@
+package com.ruoyi.production.controller;
+
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.production.dto.ProductMaterialSkuDto;
+import com.ruoyi.production.pojo.ProductMaterialSku;
+import com.ruoyi.production.service.ProductMaterialSkuService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+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 java.util.List;
+
+/**
+ * <br>
+ * 鐗╂枡瑙勬牸鎺у埗灞�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:43
+ */
+@RestController
+@RequestMapping("/productMaterialSku")
+public class ProductMaterialSkuController {
+
+    @Autowired
+    private ProductMaterialSkuService productMaterialSkuService;
+
+    @GetMapping("/list")
+    @ApiOperation("鐗╂枡瑙勬牸鏁版嵁闆嗗悎")
+    @Log(title = "鐗╂枡瑙勬牸鏁版嵁闆嗗悎", businessType = BusinessType.OTHER)
+    public AjaxResult productMaterialSkuList(@RequestParam("materialId") Long materialId) {
+        List<ProductMaterialSkuDto> list = productMaterialSkuService.productMaterialSkuList(materialId);
+        return AjaxResult.success(list);
+    }
+
+    @PostMapping("/add")
+    @ApiOperation("鏂板鐗╂枡瑙勬牸")
+    @Log(title = "鏂板鐗╂枡瑙勬牸", businessType = BusinessType.INSERT)
+    public AjaxResult addProductMaterialSku(@RequestBody ProductMaterialSku productMaterialSku) {
+        productMaterialSkuService.addProductMaterialSku(productMaterialSku);
+        return AjaxResult.success();
+    }
+
+    @PutMapping("/update")
+    @ApiOperation("淇敼鐗╂枡瑙勬牸")
+    @Log(title = "淇敼鐗╂枡瑙勬牸", businessType = BusinessType.UPDATE)
+    public AjaxResult updateProductMaterialSku(@RequestBody ProductMaterialSku productMaterialSku) {
+        productMaterialSkuService.updateProductMaterialSku(productMaterialSku);
+        return AjaxResult.success();
+    }
+
+    @DeleteMapping("/delete")
+    @ApiOperation("鍒犻櫎鐗╂枡瑙勬牸")
+    @Log(title = "鍒犻櫎鐗╂枡瑙勬牸", businessType = BusinessType.DELETE)
+    public AjaxResult deleteProductMaterialSku(@RequestBody List<Long> ids) {
+        productMaterialSkuService.deleteProductMaterialSku(ids);
+        return AjaxResult.success();
+    }
+}
diff --git a/src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java b/src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java
new file mode 100644
index 0000000..749e53b
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/dto/ProductMaterialSkuDto.java
@@ -0,0 +1,38 @@
+package com.ruoyi.production.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * <br>
+ * 鐗╂枡 + 瑙勬牸 DTO
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:50
+ */
+@Data
+@ApiModel(value = "ProductMaterialSkuDto", description = "鐗╂枡SKU淇℃伅")
+public class ProductMaterialSkuDto {
+
+    @ApiModelProperty("鐗╂枡ID")
+    private Long materialId;
+
+    @ApiModelProperty("鐗╂枡鍚嶇О")
+    private String materialName;
+
+    @ApiModelProperty("鍗曚綅")
+    private String baseUnit;
+
+    @ApiModelProperty("瑙勬牸ID")
+    private Long skuId;
+
+    @ApiModelProperty("瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @ApiModelProperty("渚涘簲鏂瑰紡")
+    private String supplyType;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java b/src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java
new file mode 100644
index 0000000..0bdbcf2
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/mapper/ProductMaterialSkuMapper.java
@@ -0,0 +1,16 @@
+package com.ruoyi.production.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.production.pojo.ProductMaterialSku;
+
+/**
+ * <br>
+ * 鐗╂枡瑙勬牸Mapper
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:04
+ */
+public interface ProductMaterialSkuMapper extends BaseMapper<ProductMaterialSku> {
+}
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductMaterial.java b/src/main/java/com/ruoyi/production/pojo/ProductMaterial.java
index ee51416..55fb868 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductMaterial.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductMaterial.java
@@ -21,12 +21,12 @@
  */
 @Data
 @TableName("product_material")
-@ApiModel(value = "ProductMaterial", description = "浜у搧鐗╂枡淇℃伅琛�")
+@ApiModel(value = "ProductMaterial", description = "鐗╂枡涓昏〃")
 public class ProductMaterial {
 
     @TableId(type = IdType.AUTO)
     @ApiModelProperty("涓婚敭ID")
-    private Integer id;
+    private Long id;
 
     @ApiModelProperty("绉熸埛ID")
     private Long tenantId;
@@ -37,32 +37,11 @@
     @ApiModelProperty("瀛樿揣绫诲埆ID")
     private Integer inventoryCategoryId;
 
-    @ApiModelProperty("鏍囪瘑缂栫爜")
-    private String identifierCode;
-
-    @ApiModelProperty("鐗╂枡浠g爜")
-    private String materialCode;
-
-    @ApiModelProperty("鐗╂枡鍝佸悕")
+    @ApiModelProperty("鐗╂枡鍚嶇О")
     private String materialName;
-
-    @ApiModelProperty("瑙勬牸鍨嬪彿")
-    private String specification;
 
     @ApiModelProperty("鍩烘湰鍗曚綅")
     private String baseUnit;
-
-    @ApiModelProperty("鐗╂枡灞炴��")
-    private String materialAttribute;
-
-    @ApiModelProperty("鎴愬搧鍝佸悕")
-    private String finishedProductName;
-
-    @ApiModelProperty("鎻愪氦浜哄鍚�")
-    private String originatorName;
-
-    @ApiModelProperty("鎻愪氦浜虹粍缁�")
-    private String originatorOrg;
 
     @ApiModelProperty("澶囨敞")
     private String remark;
@@ -74,11 +53,4 @@
     @ApiModelProperty("淇敼鏃堕棿")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
-
-    @ApiModelProperty("瀹滄惌琛ㄥ崟瀹炰緥ID")
-    private String formInstanceId;
-
-    @ApiModelProperty("瀹滄惌淇敼鏃堕棿")
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime formModifiedTime;
 }
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java b/src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java
new file mode 100644
index 0000000..e5c1c35
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/pojo/ProductMaterialSku.java
@@ -0,0 +1,66 @@
+package com.ruoyi.production.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * <br>
+ * 鐗╂枡瑙勬牸琛�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:02
+ */
+@Data
+@TableName("product_material_sku")
+@ApiModel(value = "ProductMaterialSku", description = "鐗╂枡瑙勬牸琛�")
+public class ProductMaterialSku {
+
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty("涓婚敭ID")
+    private Long id;
+
+    @ApiModelProperty("鐗╂枡ID")
+    private Long materialId;
+
+    @ApiModelProperty("鏍囪瘑缂栫爜")
+    private String identifierCode;
+
+    @ApiModelProperty("鐗╂枡缂栫爜")
+    private String materialCode;
+
+    @ApiModelProperty("瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @ApiModelProperty("渚涘簲鏂瑰紡锛堣嚜鍒讹紝澶栬喘锛�")
+    private String supplyType;
+
+    @ApiModelProperty("鎻愪氦浜哄鍚�")
+    private String originatorName;
+
+    @ApiModelProperty("鎻愪氦浜虹粍缁�")
+    private String originatorOrg;
+
+    @ApiModelProperty("瀹滄惌琛ㄥ崟瀹炰緥ID")
+    private String formInstanceId;
+
+    @ApiModelProperty("瀹滄惌淇敼鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime formModifiedTime;
+
+    @ApiModelProperty("鍒涘缓鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("淇敼鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}
diff --git a/src/main/java/com/ruoyi/production/service/ProductMaterialService.java b/src/main/java/com/ruoyi/production/service/ProductMaterialService.java
index 34fd9a7..4d24475 100644
--- a/src/main/java/com/ruoyi/production/service/ProductMaterialService.java
+++ b/src/main/java/com/ruoyi/production/service/ProductMaterialService.java
@@ -3,6 +3,9 @@
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.production.pojo.ProductMaterial;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * <br>
  * 浜у搧鐗╂枡淇℃伅鎺ュ彛
@@ -17,4 +20,12 @@
     void loadProductMaterialData();
 
     void syncProductMaterialJob();
+
+    Map<String, List<ProductMaterial>> ProductMaterialList(String materialName);
+
+    void addProductMaterial(ProductMaterial productMaterial);
+
+    void updateProductMaterial(ProductMaterial productMaterial);
+
+    void deleteProductMaterial(List<Long> ids);
 }
diff --git a/src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java b/src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java
new file mode 100644
index 0000000..b24a444
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/ProductMaterialSkuService.java
@@ -0,0 +1,26 @@
+package com.ruoyi.production.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.production.dto.ProductMaterialSkuDto;
+import com.ruoyi.production.pojo.ProductMaterialSku;
+
+import java.util.List;
+
+/**
+ * <br>
+ * 鐗╂枡瑙勬牸鎺ュ彛
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:04
+ */
+public interface ProductMaterialSkuService extends IService<ProductMaterialSku> {
+    List<ProductMaterialSkuDto> productMaterialSkuList(Long materialId);
+
+    void addProductMaterialSku(ProductMaterialSku productMaterialSku);
+
+    void updateProductMaterialSku(ProductMaterialSku productMaterialSku);
+
+    void deleteProductMaterialSku(List<Long> ids);
+}
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
index 7367dc7..b1dd5b4 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductMaterialServiceImpl.java
@@ -5,6 +5,7 @@
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.ruoyi.framework.config.AliDingConfig;
@@ -12,8 +13,10 @@
 import com.ruoyi.production.mapper.ProductMaterialMapper;
 import com.ruoyi.production.pojo.ProductMaterial;
 import com.ruoyi.production.pojo.ProductMaterialConfig;
+import com.ruoyi.production.pojo.ProductMaterialSku;
 import com.ruoyi.production.service.ProductMaterialConfigService;
 import com.ruoyi.production.service.ProductMaterialService;
+import com.ruoyi.production.service.ProductMaterialSkuService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -25,8 +28,7 @@
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
@@ -48,17 +50,22 @@
     @Autowired
     private ProductMaterialConfigService productMaterialConfigService;
 
+    @Autowired
+    private ProductMaterialSkuService productMaterialSkuService;
+
     /**
      * 鍚屾閿侊紝闃叉鎵嬪姩鍜屽畾鏃朵换鍔″悓鏃舵墽琛�
      */
     private final ReentrantLock syncLock = new ReentrantLock();
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void loadProductMaterialData() {
         syncProductMaterialData(1);
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void syncProductMaterialJob() {
         syncProductMaterialData(2);
     }
@@ -94,13 +101,7 @@
                 JSONObject searchParam = buildSearchParam(lastSyncTime, pageNumber, pageSize);
 
                 // 璋冪敤瀹滄惌鎺ュ彛鎷夊彇鏁版嵁
-                String dataRes = HttpUtils.sendPostJson(
-                        aliDingConfig.getSearchFormDataUrl(),
-                        searchParam.toJSONString(),
-                        StandardCharsets.UTF_8.name(),
-                        null,
-                        accessToken
-                );
+                String dataRes = HttpUtils.sendPostJson(aliDingConfig.getSearchFormDataUrl(), searchParam.toJSONString(), StandardCharsets.UTF_8.name(), null, accessToken);
 
                 if (StringUtils.isEmpty(dataRes)) {
                     log.warn("绗� {} 椤垫媺鍙栨暟鎹负绌�", pageNumber);
@@ -117,7 +118,7 @@
                 }
 
                 // 瑙f瀽骞朵繚瀛樻暟鎹�
-                List<ProductMaterial> list = parseProductMaterials(dataArr, totalCount);
+                List<ProductMaterialSku> list = parseProductMaterials(dataArr, totalCount);
                 if (!list.isEmpty()) {
                     // 澶勭悊鏇存柊鎴栨柊澧�
                     int affected = processSaveOrUpdate(list);
@@ -141,8 +142,7 @@
     }
 
     private String getAccessToken() {
-        String params = "appkey=" + aliDingConfig.getAppKey()
-                + "&appsecret=" + aliDingConfig.getAppSecret();
+        String params = "appkey=" + aliDingConfig.getAppKey() + "&appsecret=" + aliDingConfig.getAppSecret();
         String tokenRes = HttpUtils.sendGet(aliDingConfig.getAccessTokenUrl(), params);
         JSONObject tokenObj = JSON.parseObject(tokenRes);
         String accessToken = tokenObj.getString("access_token");
@@ -153,9 +153,9 @@
     }
 
     private LocalDateTime getLastSyncTime() {
-        LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.orderByDesc(ProductMaterial::getFormModifiedTime).last("LIMIT 1");
-        ProductMaterial lastRecord = this.getOne(queryWrapper);
+        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(ProductMaterialSku::getFormModifiedTime).last("LIMIT 1");
+        ProductMaterialSku lastRecord = productMaterialSkuService.getOne(queryWrapper);
         return lastRecord != null ? lastRecord.getFormModifiedTime() : null;
     }
 
@@ -193,20 +193,18 @@
         searchParam.put("orderConfigJson", "{\"gmt_modified\":\"+\"}");
 
         if (lastSyncTime != null) {
-            String startTime = lastSyncTime.plusSeconds(1).atZone(ZoneId.systemDefault())
-                    .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+            String startTime = lastSyncTime.plusSeconds(1).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
             searchParam.put("modifiedFromTimeGMT", startTime);
         }
 
-        String endTime = LocalDateTime.now().atZone(ZoneId.systemDefault())
-                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        String endTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
         searchParam.put("modifiedToTimeGMT", endTime);
 
         return searchParam;
     }
 
-    private List<ProductMaterial> parseProductMaterials(JSONArray dataArr, Integer totalCount) {
-        List<ProductMaterial> list = new ArrayList<>();
+    private List<ProductMaterialSku> parseProductMaterials(JSONArray dataArr, Integer totalCount) {
+        List<ProductMaterialSku> list = new ArrayList<>();
         LocalDateTime now = LocalDateTime.now();
 
         for (int i = 0; i < dataArr.size(); i++) {
@@ -214,47 +212,78 @@
             String formInstanceId = item.getString("formInstanceId");
 
             JSONObject originator = item.getJSONObject("originator");
-            String originatorName = originator != null && originator.containsKey("userName")
-                    ? originator.getJSONObject("userName").getString("nameInChinese") : "鏈煡";
-
+            String originatorName = originator != null && originator.containsKey("userName") ? originator.getJSONObject("userName").getString("nameInChinese") : "鏈煡";
             JSONObject formData = item.getJSONObject("formData");
+            //  澶勭悊鐗╂枡涓昏〃鏁版嵁
             ProductMaterial material = new ProductMaterial();
-
-            material.setFormInstanceId(formInstanceId);
-            material.setIdentifierCode(formData.getString("textField_l92h77ju"));
-            material.setMaterialCode(formData.getString("textField_l92f36f2"));
             material.setMaterialName(formData.getString("textField_l92f36f5"));
-            material.setSpecification(formData.getString("textField_l92f36f6"));
             material.setBaseUnit(formData.getString("textField_la147lnw"));
-            material.setMaterialAttribute(formData.getString("selectField_la14k51j"));
-            material.setFinishedProductName(formData.getString("radioField_lbkk2nn2"));
             material.setRemark(formData.getString("textareaField_l92f36f9"));
 
-            // 澶勭悊鐗╂枡绫诲瀷鍜屽瓨璐х被鍒�
             String materialType = formData.getString("selectField_l92f36fb");
             String inventoryCat = formData.getString("selectField_la154noy");
             material.setMaterialTypeId(getOrCreateConfigId(materialType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
             material.setInventoryCategoryId(getOrCreateConfigId(inventoryCat, MaterialConfigTypeEnum.INVENTORY_CAT.name()));
 
-            material.setOriginatorName(originatorName);
-            material.setOriginatorOrg("瀹佸涓垱缁胯兘瀹炰笟闆嗗洟鏈夐檺鍏徃");
+            Long materialId = getOrCreateMaterial(material);
 
-            material.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
-            material.setCreateTime(now);
-            material.setUpdateTime(now);
+            //  澶勭悊鐗╂枡瑙勬牸鏁版嵁
+            ProductMaterialSku sku = new ProductMaterialSku();
+            sku.setMaterialId(materialId);
+            sku.setFormInstanceId(formInstanceId);
+            sku.setIdentifierCode(formData.getString("textField_l92h77ju"));
+            sku.setMaterialCode(formData.getString("textField_l92f36f2"));
+            sku.setSpecification(formData.getString("textField_l92f36f6"));
+            sku.setSupplyType(formData.getString("selectField_la14k51j"));
+            sku.setOriginatorName(originatorName);
+            sku.setOriginatorOrg("瀹佸涓垱缁胯兘瀹炰笟闆嗗洟鏈夐檺鍏徃");
+            sku.setFormModifiedTime(parseUtcTime(item.getString("modifiedTimeGMT")));
+            sku.setCreateTime(now);
+            sku.setUpdateTime(now);
 
-            list.add(material);
+            list.add(sku);
         }
         return list;
+    }
+
+    private Long getOrCreateMaterial(ProductMaterial material) {
+        LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ProductMaterial::getMaterialName, material.getMaterialName());
+
+        ProductMaterial exist = this.getOne(queryWrapper);
+        if (exist == null) {
+            material.setCreateTime(LocalDateTime.now());
+            material.setUpdateTime(LocalDateTime.now());
+            this.save(material);
+            return material.getId();
+        } else {
+            // 濡傛灉宸插瓨鍦紝浣嗗叧閿睘鎬у彂鐢熷彉鍖栵紝鍒欒繘琛屾洿鏂帮紙浠ュ疁鎼暟鎹负鍑嗭級
+            boolean needUpdate = false;
+            if (material.getMaterialTypeId() != null && !material.getMaterialTypeId().equals(exist.getMaterialTypeId())) {
+                exist.setMaterialTypeId(material.getMaterialTypeId());
+                needUpdate = true;
+            }
+            if (material.getInventoryCategoryId() != null && !material.getInventoryCategoryId().equals(exist.getInventoryCategoryId())) {
+                exist.setInventoryCategoryId(material.getInventoryCategoryId());
+                needUpdate = true;
+            }
+            if (StringUtils.isNotEmpty(material.getBaseUnit()) && !material.getBaseUnit().equals(exist.getBaseUnit())) {
+                exist.setBaseUnit(material.getBaseUnit());
+                needUpdate = true;
+            }
+            if (needUpdate) {
+                exist.setUpdateTime(LocalDateTime.now());
+                this.updateById(exist);
+            }
+            return exist.getId();
+        }
     }
 
     private Integer getOrCreateConfigId(String name, String type) {
         if (StringUtils.isEmpty(name)) {
             return null;
         }
-        ProductMaterialConfig config = productMaterialConfigService.getOne(new LambdaQueryWrapper<ProductMaterialConfig>()
-                .eq(ProductMaterialConfig::getConfigName, name)
-                .eq(ProductMaterialConfig::getConfigType, type));
+        ProductMaterialConfig config = productMaterialConfigService.getOne(new LambdaQueryWrapper<ProductMaterialConfig>().eq(ProductMaterialConfig::getConfigName, name).eq(ProductMaterialConfig::getConfigType, type));
         if (config == null) {
             config = new ProductMaterialConfig();
             config.setConfigName(name);
@@ -264,27 +293,30 @@
         return config.getId();
     }
 
-    private int processSaveOrUpdate(List<ProductMaterial> list) {
+    private int processSaveOrUpdate(List<ProductMaterialSku> list) {
         if (list == null || list.isEmpty()) {
             return 0;
         }
         int affected = 0;
 
-        for (ProductMaterial material : list) {
-            ProductMaterial exist = this.getOne(new LambdaQueryWrapper<ProductMaterial>()
-                    .eq(ProductMaterial::getFormInstanceId, material.getFormInstanceId()));
+        for (ProductMaterialSku sku : list) {
 
+            ProductMaterialSku exist =
+                    productMaterialSkuService.getOne(new LambdaQueryWrapper<ProductMaterialSku>()
+                            .eq(ProductMaterialSku::getMaterialId, sku.getMaterialId())
+                            .eq(ProductMaterialSku::getSpecification, sku.getSpecification()));
             if (exist == null) {
-                this.save(material);
+                productMaterialSkuService.save(sku);
                 affected++;
-                log.info("鏂板鐗╂枡鏁版嵁 formInstanceId={}", material.getFormInstanceId());
+                log.info("鏂板鐗╂枡瑙勬牸 {}", sku.getSpecification());
             } else {
-                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(material.getFormModifiedTime())) {
-                    material.setId(exist.getId());
-                    material.setCreateTime(exist.getCreateTime());
-                    this.updateById(material);
+                if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(sku.getFormModifiedTime())) {
+                    sku.setId(exist.getId());
+                    sku.setCreateTime(exist.getCreateTime());
+                    productMaterialSkuService.updateById(sku);
+
                     affected++;
-                    log.info("鏇存柊鐗╂枡鏁版嵁 formInstanceId={}", material.getFormInstanceId());
+                    log.info("鏇存柊鐗╂枡瑙勬牸 {}", sku.getSpecification());
                 }
             }
         }
@@ -303,4 +335,103 @@
             return null;
         }
     }
+
+    @Override
+    public Map<String, List<ProductMaterial>> ProductMaterialList(String materialName) {
+
+        List<ProductMaterialConfig> materialConfigList =
+                productMaterialConfigService.list(new LambdaQueryWrapper<ProductMaterialConfig>()
+                        .eq(ProductMaterialConfig::getConfigType, MaterialConfigTypeEnum.MATERIAL_TYPE.name()));
+
+        Map<String, List<ProductMaterial>> productMaterialMap = new HashMap<>();
+        if (materialConfigList == null || materialConfigList.isEmpty()) {
+            return productMaterialMap;
+        }
+        for (ProductMaterialConfig materialConfig : materialConfigList) {
+            LambdaQueryWrapper<ProductMaterial> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(ProductMaterial::getMaterialTypeId, materialConfig.getId())
+                    .select(ProductMaterial::getId, ProductMaterial::getMaterialName)
+                    .like(materialName != null && !materialName.isEmpty(), ProductMaterial::getMaterialName, materialName);
+
+            List<ProductMaterial> productMaterialList = list(wrapper);
+
+            if (productMaterialList != null && !productMaterialList.isEmpty()) {
+                productMaterialMap.put(materialConfig.getConfigName(), productMaterialList);
+            }
+        }
+
+        return productMaterialMap;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void addProductMaterial(ProductMaterial productMaterial) {
+        validateProductMaterial(productMaterial, false);
+        if (existsMaterialName(productMaterial.getMaterialName(), null)) {
+            throw new ServiceException("鐗╂枡鍚嶇О宸插瓨鍦�");
+        }
+        LocalDateTime now = LocalDateTime.now();
+        if (productMaterial.getCreateTime() == null) {
+            productMaterial.setCreateTime(now);
+        }
+        productMaterial.setUpdateTime(now);
+        if (!this.save(productMaterial)) {
+            throw new ServiceException("鏂板鐗╂枡澶辫触");
+        }
+        log.info("鏂板鐗╂枡鎴愬姛 materialName={}", productMaterial.getMaterialName());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateProductMaterial(ProductMaterial productMaterial) {
+        validateProductMaterial(productMaterial, true);
+        ProductMaterial exist = this.getById(productMaterial.getId());
+        if (exist == null) {
+            throw new ServiceException("鐗╂枡涓嶅瓨鍦�");
+        }
+        if (existsMaterialName(productMaterial.getMaterialName(), productMaterial.getId())) {
+            throw new ServiceException("鐗╂枡鍚嶇О宸插瓨鍦�");
+        }
+        productMaterial.setUpdateTime(LocalDateTime.now());
+        if (!this.updateById(productMaterial)) {
+            throw new ServiceException("淇敼鐗╂枡澶辫触");
+        }
+        log.info("淇敼鐗╂枡鎴愬姛 id={}", productMaterial.getId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteProductMaterial(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            throw new ServiceException("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        if (!this.removeByIds(ids)) {
+            throw new ServiceException("鍒犻櫎鐗╂枡澶辫触");
+        }
+        log.info("鍒犻櫎鐗╂枡鎴愬姛 ids={}", ids);
+    }
+
+    private void validateProductMaterial(ProductMaterial productMaterial, boolean requireId) {
+        if (productMaterial == null) {
+            throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
+        }
+        if (requireId && productMaterial.getId() == null) {
+            throw new ServiceException("涓婚敭ID涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(productMaterial.getMaterialName())) {
+            throw new ServiceException("鐗╂枡鍚嶇О涓嶈兘涓虹┖");
+        }
+    }
+
+    private boolean existsMaterialName(String materialName, Long excludeId) {
+        if (StringUtils.isEmpty(materialName)) {
+            return false;
+        }
+        LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ProductMaterial::getMaterialName, materialName);
+        if (excludeId != null) {
+            queryWrapper.ne(ProductMaterial::getId, excludeId);
+        }
+        return this.count(queryWrapper) > 0;
+    }
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java
new file mode 100644
index 0000000..289b3ad
--- /dev/null
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductMaterialSkuServiceImpl.java
@@ -0,0 +1,166 @@
+package com.ruoyi.production.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.production.dto.ProductMaterialSkuDto;
+import com.ruoyi.production.mapper.ProductMaterialMapper;
+import com.ruoyi.production.mapper.ProductMaterialSkuMapper;
+import com.ruoyi.production.pojo.ProductMaterial;
+import com.ruoyi.production.pojo.ProductMaterialSku;
+import com.ruoyi.production.service.ProductMaterialSkuService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <br>
+ * 鐗╂枡瑙勬牸鎺ュ彛瀹炵幇绫�
+ * </br>
+ *
+ * @author deslrey
+ * @version 1.0
+ * @since 2026/03/12 10:05
+ */
+@Slf4j
+@Service
+public class ProductMaterialSkuServiceImpl
+        extends ServiceImpl<ProductMaterialSkuMapper, ProductMaterialSku>
+        implements ProductMaterialSkuService {
+
+    @Autowired
+    private ProductMaterialMapper productMaterialMapper;
+
+    /**
+     * 鏌ヨ鐗╂枡瑙勬牸鍒楄〃
+     */
+    @Override
+    public List<ProductMaterialSkuDto> productMaterialSkuList(Long materialId) {
+
+        if (materialId == null) {
+            return Collections.emptyList();
+        }
+
+        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ProductMaterialSku::getMaterialId, materialId)
+                .orderByAsc(ProductMaterialSku::getId);
+        List<ProductMaterialSku> skuList = this.list(queryWrapper);
+        if (skuList == null || skuList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        // 鏌ヨ鐗╂枡淇℃伅
+        ProductMaterial material = productMaterialMapper.selectById(materialId);
+
+        String materialName = material != null ? material.getMaterialName() : null;
+        String baseUnit = material != null ? material.getBaseUnit() : null;
+        List<ProductMaterialSkuDto> result = new ArrayList<>(skuList.size());
+        for (ProductMaterialSku sku : skuList) {
+            ProductMaterialSkuDto dto = new ProductMaterialSkuDto();
+            dto.setMaterialId(materialId);
+            dto.setMaterialName(materialName);
+            dto.setBaseUnit(baseUnit);
+            dto.setSkuId(sku.getId());
+            dto.setSpecification(sku.getSpecification());
+            dto.setSupplyType(sku.getSupplyType());
+            result.add(dto);
+        }
+
+        return result;
+    }
+
+    /**
+     * 鏂板鐗╂枡瑙勬牸
+     */
+    @Override
+    public void addProductMaterialSku(ProductMaterialSku sku) {
+        validateProductMaterialSku(sku, false);
+        // 鏍¢獙鐗╂枡鏄惁瀛樺湪
+        ProductMaterial material = productMaterialMapper.selectById(sku.getMaterialId());
+        if (material == null) {
+            throw new ServiceException("鐗╂枡涓嶅瓨鍦�");
+        }
+        // 鏍¢獙瑙勬牸鏄惁閲嶅
+        if (existsSameSpecification(sku.getMaterialId(), sku.getSpecification(), null)) {
+            throw new ServiceException("璇ョ墿鏂欏凡瀛樺湪鐩稿悓瑙勬牸");
+        }
+        LocalDateTime now = LocalDateTime.now();
+        if (sku.getCreateTime() == null) {
+            sku.setCreateTime(now);
+        }
+        sku.setUpdateTime(now);
+
+        if (!this.save(sku)) {
+            throw new ServiceException("鏂板鐗╂枡瑙勬牸澶辫触");
+        }
+        log.info("鏂板鐗╂枡瑙勬牸鎴愬姛 materialId={}, specification={}", sku.getMaterialId(), sku.getSpecification());
+    }
+
+    /**
+     * 淇敼鐗╂枡瑙勬牸
+     */
+    @Override
+    public void updateProductMaterialSku(ProductMaterialSku sku) {
+        validateProductMaterialSku(sku, true);
+        // 鏍¢獙瑙勬牸鏄惁閲嶅
+        if (existsSameSpecification(sku.getMaterialId(), sku.getSpecification(), sku.getId())) {
+            throw new ServiceException("璇ョ墿鏂欏凡瀛樺湪鐩稿悓瑙勬牸");
+        }
+        sku.setUpdateTime(LocalDateTime.now());
+        if (!this.updateById(sku)) {
+            throw new ServiceException("淇敼鐗╂枡瑙勬牸澶辫触");
+        }
+        log.info("淇敼鐗╂枡瑙勬牸鎴愬姛 id={}", sku.getId());
+    }
+
+    /**
+     * 鍒犻櫎鐗╂枡瑙勬牸
+     */
+    @Override
+    public void deleteProductMaterialSku(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            throw new ServiceException("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        if (!this.removeByIds(ids)) {
+            throw new ServiceException("鍒犻櫎鐗╂枡瑙勬牸澶辫触");
+        }
+        log.info("鍒犻櫎鐗╂枡瑙勬牸鎴愬姛 ids={}", ids);
+    }
+
+    /**
+     * 鍙傛暟鏍¢獙
+     */
+    private void validateProductMaterialSku(ProductMaterialSku sku, boolean requireId) {
+        if (sku == null) {
+            throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
+        }
+        if (requireId && sku.getId() == null) {
+            throw new ServiceException("涓婚敭ID涓嶈兘涓虹┖");
+        }
+        if (sku.getMaterialId() == null) {
+            throw new ServiceException("鐗╂枡ID涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(sku.getSpecification())) {
+            throw new ServiceException("瑙勬牸涓嶈兘涓虹┖");
+        }
+    }
+
+    /**
+     * 鏍¢獙鏄惁瀛樺湪鐩稿悓瑙勬牸
+     */
+    private boolean existsSameSpecification(Long materialId, String specification, Long excludeId) {
+        LambdaQueryWrapper<ProductMaterialSku> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ProductMaterialSku::getMaterialId, materialId)
+                .eq(ProductMaterialSku::getSpecification, specification);
+        if (excludeId != null) {
+            queryWrapper.ne(ProductMaterialSku::getId, excludeId);
+        }
+
+        return this.count(queryWrapper) > 0;
+    }
+}
diff --git a/src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java
index 1fa88f9..de8cfe4 100644
--- a/src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java
+++ b/src/main/java/com/ruoyi/productionPlan/service/impl/ProductionPlanServiceImpl.java
@@ -15,9 +15,9 @@
 import com.ruoyi.common.utils.http.HttpUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.config.AliDingConfig;
-import com.ruoyi.production.pojo.ProductMaterial;
+import com.ruoyi.production.pojo.ProductMaterialSku;
 import com.ruoyi.production.pojo.ProductOrder;
-import com.ruoyi.production.service.ProductMaterialService;
+import com.ruoyi.production.service.ProductMaterialSkuService;
 import com.ruoyi.production.service.ProductOrderService;
 import com.ruoyi.productionPlan.dto.ProductionPlanDto;
 import com.ruoyi.productionPlan.dto.ProductionPlanImportDto;
@@ -72,7 +72,7 @@
     private ProductOrderPlanMapper productOrderPlanMapper;
 
     @Autowired
-    private ProductMaterialService productMaterialService;
+    private ProductMaterialSkuService productMaterialSkuService;
 
     /**
      * 鍚屾閿侊紝纭繚鎵嬪姩鍜屽畾鏃朵换鍔′笉鍚屾椂鎵ц
@@ -386,11 +386,11 @@
                 String materialCode = row.getString("textField_l9xo62q5");
                 // 鏍规嵁鐗╂枡缂栫爜鏌ヨ鐗╂枡淇℃伅琛紝鍏宠仈鐗╂枡ID
                 if (StringUtils.isNotEmpty(materialCode)) {
-                    LambdaQueryWrapper<ProductMaterial> queryWrapper = new LambdaQueryWrapper<>();
-                    queryWrapper.eq(ProductMaterial::getMaterialCode, materialCode);
-                    ProductMaterial productMaterial = productMaterialService.getOne(queryWrapper);
-                    if (productMaterial != null) {
-                        plan.setProductMaterialId(productMaterial.getId());
+                    LambdaQueryWrapper<ProductMaterialSku> skuQueryWrapper = new LambdaQueryWrapper<>();
+                    skuQueryWrapper.eq(ProductMaterialSku::getMaterialCode, materialCode);
+                    ProductMaterialSku sku = productMaterialSkuService.getOne(skuQueryWrapper);
+                    if (sku != null && sku.getMaterialId() != null) {
+                        plan.setProductMaterialId(sku.getMaterialId().intValue());
                     }
                 }
 
diff --git a/src/main/resources/mapper/production/ProductMaterialMapper.xml b/src/main/resources/mapper/production/ProductMaterialMapper.xml
index 84d8ca4..0f55a3b 100644
--- a/src/main/resources/mapper/production/ProductMaterialMapper.xml
+++ b/src/main/resources/mapper/production/ProductMaterialMapper.xml
@@ -3,20 +3,14 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
 <mapper namespace="com.ruoyi.production.mapper.ProductMaterialMapper">
+
     <resultMap id="ProductMaterialResultMap" type="com.ruoyi.production.pojo.ProductMaterial">
         <id property="id" column="id"/>
         <result property="tenantId" column="tenant_id"/>
         <result property="materialTypeId" column="material_type_id"/>
         <result property="inventoryCategoryId" column="inventory_category_id"/>
-        <result property="identifierCode" column="identifier_code"/>
-        <result property="materialCode" column="material_code"/>
         <result property="materialName" column="material_name"/>
-        <result property="specification" column="specification"/>
         <result property="baseUnit" column="base_unit"/>
-        <result property="materialAttribute" column="material_attribute"/>
-        <result property="finishedProductName" column="finished_product_name"/>
-        <result property="originatorName" column="originator_name"/>
-        <result property="originatorOrg" column="originator_org"/>
         <result property="remark" column="remark"/>
         <result property="createTime" column="create_time"/>
         <result property="updateTime" column="update_time"/>
diff --git a/src/main/resources/mapper/production/ProductMaterialSkuMapper.xml b/src/main/resources/mapper/production/ProductMaterialSkuMapper.xml
new file mode 100644
index 0000000..ccd26ed
--- /dev/null
+++ b/src/main/resources/mapper/production/ProductMaterialSkuMapper.xml
@@ -0,0 +1,22 @@
+<?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.production.mapper.ProductMaterialSkuMapper">
+
+    <resultMap id="ProductMaterialSkuResultMap" type="com.ruoyi.production.pojo.ProductMaterialSku">
+        <id property="id" column="id"/>
+        <result property="materialId" column="material_id"/>
+        <result property="identifierCode" column="identifier_code"/>
+        <result property="materialCode" column="material_code"/>
+        <result property="specification" column="specification"/>
+        <result property="supplyType" column="supply_type"/>
+        <result property="originatorName" column="originator_name"/>
+        <result property="originatorOrg" column="originator_org"/>
+        <result property="formInstanceId" column="form_instance_id"/>
+        <result property="formModifiedTime" column="form_modified_time"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+</mapper>
\ No newline at end of file

--
Gitblit v1.9.3