diff --git a/zt-module-base/zt-module-base-server/pom.xml b/zt-module-base/zt-module-base-server/pom.xml
index c921c70f..3547b084 100644
--- a/zt-module-base/zt-module-base-server/pom.xml
+++ b/zt-module-base/zt-module-base-server/pom.xml
@@ -73,6 +73,20 @@
com.zt.plat
zt-spring-boot-starter-mybatis
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.49
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/MaterialHasPropertiesController.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/MaterialHasPropertiesController.java
index 74cd3366..94308579 100644
--- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/MaterialHasPropertiesController.java
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/MaterialHasPropertiesController.java
@@ -91,6 +91,14 @@ public class MaterialHasPropertiesController {
return success(BeanUtils.toBean(pageResult, MaterialHasPropertiesRespVO.class));
}
+ @PostMapping("/batch-save")
+ @Operation(summary = "批量保存物料持有属性(全量替换)")
+ @PreAuthorize("@ss.hasPermission('base:material-has-properties:update')")
+ public CommonResult batchSave(@Valid @RequestBody MaterialHasPropertiesBatchSaveReqVO reqVO) {
+ MaterialHasPropertiesBatchSaveRespVO resp = materialHasPropertiesService.batchSave(reqVO);
+ return success(resp);
+ }
+
@GetMapping("/export-excel")
@Operation(summary = "导出物料持有属性 Excel")
@PreAuthorize("@ss.hasPermission('base:material-has-properties:export')")
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchItemReqVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchItemReqVO.java
new file mode 100644
index 00000000..7f9cb6de
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchItemReqVO.java
@@ -0,0 +1,32 @@
+package com.zt.plat.module.base.controller.admin.materialhasproperties.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "物料持有属性批量保存单项 Request VO")
+@Data
+public class MaterialHasPropertiesBatchItemReqVO {
+
+ @Schema(description = "主键ID", example = "6800")
+ private Long id;
+
+ @Schema(description = "属性ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "8607")
+ @NotNull(message = "属性ID不能为空")
+ private Long propertiesId;
+
+ @Schema(description = "计量单位ID-默认计量单位", example = "23731")
+ private Long unitId;
+
+ @Schema(description = "属性值", requiredMode = Schema.RequiredMode.REQUIRED)
+ private String value;
+
+ @Schema(description = "是否关键属性-关键属性表示物料唯一性")
+ private Integer isKey;
+
+ @Schema(description = "是否计量定价")
+ private Integer isMetering;
+
+ @Schema(description = "排序号")
+ private Long sort;
+}
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveReqVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveReqVO.java
new file mode 100644
index 00000000..dac8badb
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveReqVO.java
@@ -0,0 +1,22 @@
+package com.zt.plat.module.base.controller.admin.materialhasproperties.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Schema(description = "管理后台 - 物料持有属性批量保存 Request VO")
+@Data
+public class MaterialHasPropertiesBatchSaveReqVO {
+
+ @Schema(description = "物料信息 ID", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "物料信息 ID 不能为空")
+ private Long infomationId;
+
+ @Schema(description = "属性列表", requiredMode = Schema.RequiredMode.REQUIRED)
+ @Valid
+ private List properties = new ArrayList<>();
+}
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveRespVO.java
new file mode 100644
index 00000000..be50488e
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/MaterialHasPropertiesBatchSaveRespVO.java
@@ -0,0 +1,15 @@
+package com.zt.plat.module.base.controller.admin.materialhasproperties.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Schema(description = "管理后台 - 物料持有属性批量保存 Response VO")
+@Data
+public class MaterialHasPropertiesBatchSaveRespVO {
+
+ @Schema(description = "行级错误列表(为空表示全部成功)")
+ private List rowErrors = new ArrayList<>();
+}
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/RowValidationErrorVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/RowValidationErrorVO.java
new file mode 100644
index 00000000..884818d7
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/materialhasproperties/vo/RowValidationErrorVO.java
@@ -0,0 +1,19 @@
+package com.zt.plat.module.base.controller.admin.materialhasproperties.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "行级校验错误信息")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RowValidationErrorVO {
+
+ @Schema(description = "错误所在行(1 开始)")
+ private Integer rowIndex;
+
+ @Schema(description = "错误提示")
+ private String message;
+}
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java
index e1e87804..fcc2f08f 100644
--- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/base/MaterialInfomationServiceImpl.java
@@ -65,14 +65,7 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService
validateMaterialClassForBinding(createReqVO.getClassesId());
MaterialInfomationDO materialInfomation = BeanUtils.toBean(createReqVO, MaterialInfomationDO.class);
materialInfomationMapper.insert(materialInfomation);
- if (createReqVO.getClassesId() != null) {
- MaterialHasClassesDO relation = MaterialHasClassesDO.builder()
- .classesId(createReqVO.getClassesId())
- .infomationId(materialInfomation.getId())
- .build();
- materialHasClassesMapper.insert(relation);
- materialInfomation.setClassesId(createReqVO.getClassesId());
- }
+ bindMaterialClassIfAbsent(materialInfomation.getId(), createReqVO.getClassesId());
return CollUtil.getFirst(buildRespList(Collections.singletonList(materialInfomation)));
}
@@ -85,13 +78,27 @@ public class MaterialInfomationServiceImpl implements MaterialInfomationService
materialInfomationMapper.updateById(updateObj);
materialHasClassesMapper.delete(new LambdaUpdateWrapper()
.eq(MaterialHasClassesDO::getInfomationId, updateReqVO.getId()));
- if (updateReqVO.getClassesId() != null) {
- MaterialHasClassesDO relation = MaterialHasClassesDO.builder()
- .classesId(updateReqVO.getClassesId())
- .infomationId(updateReqVO.getId())
- .build();
- materialHasClassesMapper.insert(relation);
+ bindMaterialClassIfAbsent(updateReqVO.getId(), updateReqVO.getClassesId());
+ }
+
+ /**
+ * 绑定物料与分类关系(若已存在则跳过),避免唯一索引冲突
+ */
+ private void bindMaterialClassIfAbsent(Long materialId, Long classesId) {
+ if (materialId == null || classesId == null) {
+ return;
}
+ Long exists = materialHasClassesMapper.selectCount(new LambdaQueryWrapperX()
+ .eq(MaterialHasClassesDO::getInfomationId, materialId)
+ .eq(MaterialHasClassesDO::getClassesId, classesId));
+ if (exists != null && exists > 0) {
+ return;
+ }
+ MaterialHasClassesDO relation = MaterialHasClassesDO.builder()
+ .classesId(classesId)
+ .infomationId(materialId)
+ .build();
+ materialHasClassesMapper.insert(relation);
}
@Override
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesService.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesService.java
index 3e3d8485..aafbba27 100644
--- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesService.java
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesService.java
@@ -59,4 +59,11 @@ public interface MaterialHasPropertiesService {
*/
PageResult getMaterialHasPropertiesPage(MaterialHasPropertiesPageReqVO pageReqVO);
+ /**
+ * 批量保存物料属性(全量替换指定物料的属性关系)
+ * @param batchReqVO 请求参数
+ * @return 行级校验错误(为空表示成功)
+ */
+ MaterialHasPropertiesBatchSaveRespVO batchSave(MaterialHasPropertiesBatchSaveReqVO batchReqVO);
+
}
\ No newline at end of file
diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java
index 2559c263..1618e5e2 100644
--- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java
+++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImpl.java
@@ -1,6 +1,7 @@
package com.zt.plat.module.base.service.materialhasproperties;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
@@ -14,6 +15,7 @@ import com.zt.plat.framework.common.pojo.PageParam;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.base.dal.dao.materialhasproperties.MaterialHasPropertiesMapper;
+import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.common.util.collection.CollectionUtils.convertList;
@@ -89,4 +91,55 @@ public class MaterialHasPropertiesServiceImpl implements MaterialHasPropertiesSe
return materialHasPropertiesMapper.selectPage(pageReqVO);
}
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MaterialHasPropertiesBatchSaveRespVO batchSave(MaterialHasPropertiesBatchSaveReqVO batchReqVO) {
+ MaterialHasPropertiesBatchSaveRespVO resp = new MaterialHasPropertiesBatchSaveRespVO();
+ Long infoId = batchReqVO.getInfomationId();
+ if (infoId == null) {
+ resp.getRowErrors().add(new RowValidationErrorVO(0, "物料信息 ID 不能为空"));
+ return resp;
+ }
+ // 全量替换:先删除该物料的已有属性
+ materialHasPropertiesMapper.delete(new LambdaQueryWrapperX()
+ .eq(MaterialHasPropertiesDO::getInfomationId, infoId));
+
+ List properties = batchReqVO.getProperties();
+ if (CollUtil.isEmpty(properties)) {
+ return resp;
+ }
+
+ // 去重并按提交顺序插入
+ Set dedupKeys = new LinkedHashSet<>();
+ for (int i = 0; i < properties.size(); i++) {
+ MaterialHasPropertiesBatchItemReqVO item = properties.get(i);
+ String propIdStr = item.getPropertiesId() == null ? null : String.valueOf(item.getPropertiesId());
+ if (StrUtil.isBlank(propIdStr)) {
+ resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "属性 ID 不能为空"));
+ continue;
+ }
+ if (StrUtil.isBlank(item.getValue())) {
+ resp.getRowErrors().add(new RowValidationErrorVO(i + 1, "属性值不能为空"));
+ continue;
+ }
+ String key = propIdStr;
+ if (!dedupKeys.add(key)) {
+ // 重复的属性直接跳过后续插入,避免唯一冲突
+ continue;
+ }
+ MaterialHasPropertiesDO entity = MaterialHasPropertiesDO.builder()
+ .infomationId(infoId)
+ .propertiesId(item.getPropertiesId())
+ .unitId(item.getUnitId())
+ .value(item.getValue())
+ .isKey(item.getIsKey())
+ .isMetering(item.getIsMetering())
+ .sort(item.getSort() == null ? (long) (i + 1) : item.getSort())
+ .build();
+ materialHasPropertiesMapper.insert(entity);
+ }
+
+ return resp;
+ }
+
}
\ No newline at end of file
diff --git a/zt-module-base/zt-module-base-server/src/test/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImplTest.java b/zt-module-base/zt-module-base-server/src/test/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImplTest.java
new file mode 100644
index 00000000..e1fe4b74
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/test/java/com/zt/plat/module/base/service/materialhasproperties/MaterialHasPropertiesServiceImplTest.java
@@ -0,0 +1,146 @@
+package com.zt.plat.module.base.service.materialhasproperties;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zt.plat.framework.test.core.ut.BaseDbUnitTest;
+import com.zt.plat.module.base.controller.admin.materialhasproperties.vo.MaterialHasPropertiesBatchItemReqVO;
+import com.zt.plat.module.base.controller.admin.materialhasproperties.vo.MaterialHasPropertiesBatchSaveReqVO;
+import com.zt.plat.module.base.controller.admin.materialhasproperties.vo.MaterialHasPropertiesBatchSaveRespVO;
+import com.zt.plat.module.base.controller.admin.materialhasproperties.vo.RowValidationErrorVO;
+import com.zt.plat.module.base.dal.dao.materialhasproperties.MaterialHasPropertiesMapper;
+import com.zt.plat.module.base.dal.dataobject.materialhasproperties.MaterialHasPropertiesDO;
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.zt.plat.framework.test.core.util.RandomUtils.randomLongId;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * {@link MaterialHasPropertiesServiceImpl} 单元测试
+ */
+@Import(MaterialHasPropertiesServiceImpl.class)
+class MaterialHasPropertiesServiceImplTest extends BaseDbUnitTest {
+
+ @Resource
+ private MaterialHasPropertiesServiceImpl materialHasPropertiesService;
+
+ @Resource
+ private MaterialHasPropertiesMapper materialHasPropertiesMapper;
+
+ private MaterialHasPropertiesDO insertRelation(Long infoId, Long propId, String value, Long sort) {
+ MaterialHasPropertiesDO entity = MaterialHasPropertiesDO.builder()
+ .id(randomLongId())
+ .infomationId(infoId)
+ .propertiesId(propId)
+ .value(value)
+ .sort(sort)
+ .build();
+ materialHasPropertiesMapper.insert(entity);
+ return entity;
+ }
+
+ @Test
+ void batchSave_shouldReplaceAndDedup() {
+ Long infoId = 1001L;
+ // 旧数据:同一个物料下的旧记录 + 其他物料记录
+ insertRelation(infoId, 9L, "old", 1L);
+ insertRelation(2002L, 99L, "other", 1L);
+
+ MaterialHasPropertiesBatchItemReqVO item1 = new MaterialHasPropertiesBatchItemReqVO();
+ item1.setPropertiesId(9L);
+ item1.setValue("new-val");
+ item1.setIsKey(1);
+
+ MaterialHasPropertiesBatchItemReqVO item2 = new MaterialHasPropertiesBatchItemReqVO();
+ item2.setPropertiesId(10L);
+ item2.setValue("v10");
+ item2.setSort(5L);
+
+ // 重复属性,应该被去重跳过
+ MaterialHasPropertiesBatchItemReqVO itemDuplicate = new MaterialHasPropertiesBatchItemReqVO();
+ itemDuplicate.setPropertiesId(9L);
+ itemDuplicate.setValue("dup");
+
+ MaterialHasPropertiesBatchSaveReqVO req = new MaterialHasPropertiesBatchSaveReqVO();
+ req.setInfomationId(infoId);
+ req.setProperties(CollUtil.newArrayList(item1, item2, itemDuplicate));
+
+ MaterialHasPropertiesBatchSaveRespVO resp = materialHasPropertiesService.batchSave(req);
+
+ assertThat(resp.getRowErrors()).isEmpty();
+
+ List result = materialHasPropertiesMapper.selectList();
+ // 只应保留当前物料的新两条 + 另一物料的 1 条
+ assertThat(result).hasSize(3);
+
+ List current = result.stream()
+ .filter(item -> infoId.equals(item.getInfomationId()))
+ .toList();
+ assertThat(current).hasSize(2);
+
+ Set propIds = current.stream().map(MaterialHasPropertiesDO::getPropertiesId).collect(Collectors.toSet());
+ assertThat(propIds).containsExactlyInAnyOrder(9L, 10L);
+
+ MaterialHasPropertiesDO updated = current.stream()
+ .filter(item -> item.getPropertiesId().equals(9L)).findFirst().orElseThrow();
+ assertThat(updated.getValue()).isEqualTo("new-val");
+ }
+
+ @Test
+ void batchSave_missingInfoId_shouldReturnErrorAndSkipInsert() {
+ MaterialHasPropertiesBatchSaveReqVO req = new MaterialHasPropertiesBatchSaveReqVO();
+ MaterialHasPropertiesBatchSaveRespVO resp = materialHasPropertiesService.batchSave(req);
+
+ assertThat(resp.getRowErrors()).hasSize(1);
+ RowValidationErrorVO error = resp.getRowErrors().get(0);
+ assertThat(error.getMessage()).contains("物料信息 ID 不能为空");
+
+ assertThat(materialHasPropertiesMapper.selectList()).isEmpty();
+ }
+
+ @Test
+ void batchSave_emptyList_shouldDeleteExisting() {
+ Long infoId = 3003L;
+ insertRelation(infoId, 77L, "keep", 1L);
+
+ MaterialHasPropertiesBatchSaveReqVO req = new MaterialHasPropertiesBatchSaveReqVO();
+ req.setInfomationId(infoId);
+ req.setProperties(List.of());
+
+ MaterialHasPropertiesBatchSaveRespVO resp = materialHasPropertiesService.batchSave(req);
+ assertThat(resp.getRowErrors()).isEmpty();
+
+ List current = materialHasPropertiesMapper.selectList(new QueryWrapper()
+ .eq("INF_ID", infoId));
+ assertThat(current).isEmpty();
+ }
+
+ @Test
+ void batchSave_blankValue_shouldCollectRowError() {
+ Long infoId = 4004L;
+
+ MaterialHasPropertiesBatchItemReqVO item = new MaterialHasPropertiesBatchItemReqVO();
+ item.setPropertiesId(55L);
+ item.setValue(" ");
+
+ MaterialHasPropertiesBatchSaveReqVO req = new MaterialHasPropertiesBatchSaveReqVO();
+ req.setInfomationId(infoId);
+ req.setProperties(List.of(item));
+
+ MaterialHasPropertiesBatchSaveRespVO resp = materialHasPropertiesService.batchSave(req);
+
+ assertThat(resp.getRowErrors()).hasSize(1);
+ RowValidationErrorVO error = resp.getRowErrors().get(0);
+ assertThat(error.getRowIndex()).isEqualTo(1);
+ assertThat(error.getMessage()).contains("属性值不能为空");
+
+ List saved = materialHasPropertiesMapper.selectList(new QueryWrapper()
+ .eq("INF_ID", infoId));
+ assertThat(saved).isEmpty();
+ }
+}
diff --git a/zt-module-base/zt-module-base-server/src/test/resources/application-unit-test.yaml b/zt-module-base/zt-module-base-server/src/test/resources/application-unit-test.yaml
new file mode 100644
index 00000000..3b2416ee
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/test/resources/application-unit-test.yaml
@@ -0,0 +1,39 @@
+spring:
+ main:
+ lazy-initialization: true
+ banner-mode: off
+
+--- #################### 数据库相关配置 ####################
+
+spring:
+ datasource:
+ name: zt-base
+ url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value
+ driver-class-name: org.h2.Driver
+ username: sa
+ password:
+ druid:
+ async-init: true
+ initial-size: 1
+ sql:
+ init:
+ schema-locations: classpath:/sql/create_tables.sql
+
+ data:
+ redis:
+ host: 127.0.0.1
+ port: 16379
+ database: 0
+
+mybatis-plus:
+ lazy-initialization: true
+ type-aliases-package: ${zt.info.base-package}.dal.dataobject
+ global-config:
+ db-config:
+ id-type: AUTO
+
+zt:
+ info:
+ base-package: com.zt.plat.module.base
+ AES:
+ key: XDV71a+xqStEA3WH
diff --git a/zt-module-base/zt-module-base-server/src/test/resources/sql/clean.sql b/zt-module-base/zt-module-base-server/src/test/resources/sql/clean.sql
new file mode 100644
index 00000000..5bcffbe4
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/test/resources/sql/clean.sql
@@ -0,0 +1 @@
+TRUNCATE TABLE bse_mtrl_hs_prps;
diff --git a/zt-module-base/zt-module-base-server/src/test/resources/sql/create_tables.sql b/zt-module-base/zt-module-base-server/src/test/resources/sql/create_tables.sql
new file mode 100644
index 00000000..144d366a
--- /dev/null
+++ b/zt-module-base/zt-module-base-server/src/test/resources/sql/create_tables.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS bse_mtrl_hs_prps (
+ id BIGINT PRIMARY KEY,
+ INF_ID BIGINT NOT NULL,
+ PRPS_ID BIGINT NOT NULL,
+ UNT_ID BIGINT,
+ VAL VARCHAR(255),
+ IS_KY INT,
+ IS_MTNG INT,
+ SRT BIGINT,
+ creator VARCHAR(64) DEFAULT '',
+ create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ updater VARCHAR(64) DEFAULT '',
+ update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ deleted BIT DEFAULT FALSE NOT NULL,
+ tenant_id BIGINT DEFAULT 1 NOT NULL
+);