From 76f9639e3c0e14cc6a4e05d3a3212799ea1c48fb Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期六, 29 八月 2026 09:31:40 +0800
Subject: [PATCH] 实现 Ticket05 共享字段与电池组持久化
---
ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java | 5 +
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImplTest.java | 13 ++++
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImplTest.java | 32 ++++++++++
ruoyi-admin/src/main/resources/db/migration/V12__custom_field_type_constraints.sql | 3 +
ruoyi-system/src/main/resources/mapper/system/TBatteryGroupMapper.xml | 2
ruoyi-system/src/main/resources/mapper/system/TBatteryGroupFieldMapper.xml | 8 ++
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBatteryGroupFieldMapper.java | 3 +
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImpl.java | 4 +
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImpl.java | 63 ++++++++++++++++++++
9 files changed, 129 insertions(+), 4 deletions(-)
diff --git a/ruoyi-admin/src/main/resources/db/migration/V12__custom_field_type_constraints.sql b/ruoyi-admin/src/main/resources/db/migration/V12__custom_field_type_constraints.sql
new file mode 100644
index 0000000..128e68e
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration/V12__custom_field_type_constraints.sql
@@ -0,0 +1,3 @@
+ALTER TABLE t_battery_group_field
+ ADD CONSTRAINT chk_battery_group_field_type
+ CHECK (field_type IN ('text', 'number', 'time', 'remark'));
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java b/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
index 0bab1a8..c7d2b28 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
@@ -94,6 +94,11 @@
assertTrue(v11.contains("ALTER TABLE t_battery_group_relation_field"));
assertTrue(v11.contains("field_value VARCHAR(500)"));
assertFalse(v11.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
+
+ String v12 = resource("db/migration/V12__custom_field_type_constraints.sql");
+ assertTrue(v12.contains("chk_battery_group_field_type"));
+ assertTrue(v12.contains("'text', 'number', 'time', 'remark'"));
+ assertFalse(v12.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
}
private static String sha256(String path)
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImplTest.java b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImplTest.java
index 58e5394..167d687 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImplTest.java
@@ -187,6 +187,19 @@
verify(batteryGroupFieldMapper).deleteBatteryGroupFieldById(2L, "admin");
}
+ @Test
+ void deleteByIdsShouldRejectReferencedField()
+ {
+ when(batteryGroupFieldMapper.selectBatteryGroupFieldById(1L)).thenReturn(field);
+ when(batteryGroupFieldMapper.countFieldReferences(1L)).thenReturn(1);
+
+ ServiceException exception = assertThrows(ServiceException.class,
+ () -> batteryGroupFieldService.deleteBatteryGroupFieldByIds(new Long[] { 1L }, "admin"));
+
+ assertEquals("字段已被模板或电池组引用,不能删除", exception.getMessage());
+ verify(batteryGroupFieldMapper, never()).deleteBatteryGroupFieldById(anyLong(), any(String.class));
+ }
+
private TBatteryGroupField statusCommand(String status)
{
TBatteryGroupField command = new TBatteryGroupField();
diff --git a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImplTest.java b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImplTest.java
index 88a70bc..d47148e 100644
--- a/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImplTest.java
+++ b/ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImplTest.java
@@ -71,7 +71,8 @@
group.setTemplateId(5L);
TBatteryGroupRelationField relation = new TBatteryGroupRelationField();
relation.setBatteryGroupFieldId(7L);
- relation.setFieldValue("72Ah");
+ relation.setFieldValue("72");
+ relation.setFieldType("number");
group.setTemplateFieldRelations(Collections.singletonList(relation));
when(batteryGroupMapper.selectBatteryGroupById(12L)).thenReturn(current);
@@ -88,7 +89,34 @@
verify(batteryGroupMapper).updateBatteryGroup(group);
ArgumentCaptor<List<TBatteryGroupRelationField>> captor = ArgumentCaptor.forClass(List.class);
verify(batteryGroupMapper).batchInsertRelationFields(captor.capture());
- assertEquals("72Ah", captor.getValue().get(0).getFieldValue());
+ assertEquals("72", captor.getValue().get(0).getFieldValue());
assertEquals("1", group.getStatus());
}
+
+ @Test
+ void insertShouldRejectInvalidTypedFieldValue()
+ {
+ TBatteryGroup group = new TBatteryGroup();
+ group.setGroupName("样品组");
+ group.setModel("M-01");
+ group.setBatteryNos(Collections.singletonList("BAT-01"));
+ group.setTemplateId(5L);
+ TBatteryGroupRelationField relation = new TBatteryGroupRelationField();
+ relation.setBatteryGroupFieldId(7L);
+ relation.setFieldValue("72Ah");
+ group.setTemplateFieldRelations(Collections.singletonList(relation));
+ TBatteryGroupRelationField validField = new TBatteryGroupRelationField();
+ validField.setBatteryGroupFieldId(7L);
+ validField.setFieldType("number");
+ when(batteryGroupMapper.selectBatteryGroupByName("样品组")).thenReturn(null);
+ when(batteryGroupMapper.selectBatteryByNo("BAT-01", null)).thenReturn(null);
+ when(batteryGroupMapper.selectTemplate(5L)).thenReturn(new TBatteryGroup());
+ when(batteryGroupMapper.selectTemplateFieldIds(5L)).thenReturn(Collections.singletonList(7L));
+ when(batteryGroupMapper.selectValidTemplateFields(5L, Collections.singletonList(7L)))
+ .thenReturn(Collections.singletonList(validField));
+
+ org.junit.jupiter.api.Assertions.assertThrows(com.ruoyi.common.exception.ServiceException.class,
+ () -> batteryGroupService.insertBatteryGroup(group));
+ verify(batteryGroupMapper, never()).insertBatteryGroup(any(TBatteryGroup.class));
+ }
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBatteryGroupFieldMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBatteryGroupFieldMapper.java
index b04dadd..5ef1fec 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBatteryGroupFieldMapper.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/TBatteryGroupFieldMapper.java
@@ -31,4 +31,7 @@
/** 逻辑删除字段。 */
int deleteBatteryGroupFieldById(@Param("fieldId") Long fieldId, @Param("updateBy") String updateBy);
+
+ /** 查询字段是否已被模板或电池组使用。 */
+ int countFieldReferences(Long fieldId);
}
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImpl.java
index c88e51a..6783d95 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupFieldServiceImpl.java
@@ -113,6 +113,10 @@
for (Long fieldId : fieldIds)
{
selectBatteryGroupFieldById(fieldId);
+ if (batteryGroupFieldMapper.countFieldReferences(fieldId) > 0)
+ {
+ throw new ServiceException("字段已被模板或电池组引用,不能删除");
+ }
}
int rows = 0;
for (Long fieldId : fieldIds)
diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImpl.java
index 87023d2..1b62df7 100644
--- a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImpl.java
+++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TBatteryGroupServiceImpl.java
@@ -1,7 +1,13 @@
package com.ruoyi.system.service.impl;
import java.util.ArrayList;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -207,6 +213,7 @@
throw new ServiceException("模板不存在");
}
List<Long> fieldIds = new ArrayList<>();
+ Map<Long, String> fieldTypes = new HashMap<>();
if (group.getTemplateFieldRelations() != null)
{
for (TBatteryGroupRelationField relation : group.getTemplateFieldRelations())
@@ -234,9 +241,22 @@
{
throw new ServiceException("电池组字段必须与模板字段一致");
}
- if (batteryGroupMapper.selectValidTemplateFields(group.getTemplateId(), fieldIds).size() != fieldIds.size())
+ List<TBatteryGroupRelationField> validFields = batteryGroupMapper.selectValidTemplateFields(group.getTemplateId(), fieldIds);
+ if (validFields == null || validFields.size() != fieldIds.size())
{
throw new ServiceException("电池组字段不属于指定模板");
+ }
+ for (TBatteryGroupRelationField field : validFields)
+ {
+ fieldTypes.put(field.getBatteryGroupFieldId(), field.getFieldType());
+ }
+ if (group.getTemplateFieldRelations() != null)
+ {
+ for (TBatteryGroupRelationField relation : group.getTemplateFieldRelations())
+ {
+ relation.setFieldValue(trimToNull(relation.getFieldValue()));
+ validateFieldValue(fieldTypes.get(relation.getBatteryGroupFieldId()), relation.getFieldValue());
+ }
}
}
@@ -279,6 +299,41 @@
batteryGroupMapper.batchInsertRelationFields(relations);
}
+ /** 按字段定义校验业务值,空值表示该字段暂未填写。 */
+ private void validateFieldValue(String fieldType, String value)
+ {
+ if (value == null || fieldType == null) return;
+ try
+ {
+ if ("number".equals(fieldType))
+ {
+ new BigDecimal(value);
+ }
+ else if ("time".equals(fieldType))
+ {
+ try
+ {
+ LocalDate.parse(value);
+ }
+ catch (DateTimeParseException exception)
+ {
+ try
+ {
+ LocalDateTime.parse(value, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ }
+ catch (DateTimeParseException nested)
+ {
+ LocalDateTime.parse(value);
+ }
+ }
+ }
+ }
+ catch (NumberFormatException | DateTimeParseException exception)
+ {
+ throw new ServiceException("模板字段值格式不正确");
+ }
+ }
+
private void validateStatusChange(TBatteryGroup group, String currentStatus)
{
if ("1".equals(group.getStatus()) && !"1".equals(currentStatus)
@@ -287,4 +342,10 @@
throw new ServiceException("测试中的电池组不能禁用");
}
}
+
+ private String trimToNull(String value)
+ {
+ String trimmed = StringUtils.trim(value);
+ return StringUtils.isEmpty(trimmed) ? null : trimmed;
+ }
}
diff --git a/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupFieldMapper.xml b/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupFieldMapper.xml
index b7e600c..ee534ef 100644
--- a/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupFieldMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupFieldMapper.xml
@@ -95,4 +95,12 @@
where field_id = #{fieldId}
and del_flag = 0
</update>
+
+ <select id="countFieldReferences" resultType="int">
+ select
+ (select count(1) from t_battery_group_template_field
+ where field_id = #{fieldId} and del_flag = 0)
+ + (select count(1) from t_battery_group_relation_field
+ where battery_group_field_id = #{fieldId} and del_flag = '0')
+ </select>
</mapper>
diff --git a/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupMapper.xml b/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupMapper.xml
index 38ce8c6..aa4aaad 100644
--- a/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/TBatteryGroupMapper.xml
@@ -55,7 +55,7 @@
<select id="selectTemplate" resultType="TBatteryGroup">select template_id, template_name from t_battery_group_template where template_id = #{templateId}</select>
<select id="selectTemplateFieldIds" resultType="Long">select field_id from t_battery_group_template_field where template_id = #{templateId} and del_flag = '0' order by sort_order, relation_id</select>
<select id="selectValidTemplateFields" resultMap="TBatteryGroupRelationFieldResult">select r.relation_id, r.template_id, r.field_id as battery_group_field_id, f.field_name, f.field_type, '0' as del_flag from t_battery_group_template_field r inner join t_battery_group_field f on f.field_id = r.field_id and f.del_flag = '0' where r.template_id = #{templateId} and r.del_flag = '0' and r.field_id in <foreach collection="fieldIds" item="fieldId" open="(" separator="," close=")">#{fieldId}</foreach></select>
- <select id="selectBatteryByNo" resultMap="TBatteryGroupBatteryResult">select relation_id, group_id, battery_no, sort_order, del_flag from t_battery_group_battery where battery_no = #{batteryNo} and del_flag = '0' limit 1</select>
+ <select id="selectBatteryByNo" resultMap="TBatteryGroupBatteryResult">select relation_id, group_id, battery_no, sort_order, del_flag from t_battery_group_battery where battery_no = #{batteryNo} and del_flag = '0' <if test="groupId != null">and group_id != #{groupId}</if> limit 1</select>
<insert id="insertBatteryGroup" parameterType="TBatteryGroup" useGeneratedKeys="true" keyProperty="groupId">insert into t_battery_group (group_name, model, purpose, z, h, f, a, b, status, create_by, create_time, remark, del_flag) values (#{groupName}, #{model}, #{purpose}, #{z}, #{h}, #{f}, #{a}, #{b}, #{status}, #{createBy}, sysdate(), #{remark}, '0')</insert>
<update id="updateBatteryGroup" parameterType="TBatteryGroup">update t_battery_group set group_name=#{groupName}, model=#{model}, purpose=#{purpose}, z=#{z}, h=#{h}, f=#{f}, a=#{a}, b=#{b}, status=#{status}, update_by=#{updateBy}, update_time=sysdate(), remark=#{remark} where group_id=#{groupId} and del_flag='0'</update>
<update id="updateBatteryGroupStatus" parameterType="TBatteryGroup">update t_battery_group set status=#{status}, update_by=#{updateBy}, update_time=sysdate() where group_id=#{groupId} and del_flag='0'</update>
--
Gitblit v1.7.1