diff --git a/common/common-botp/src/main/java/apelet/common/botp/config/MybatisPlusConfig.java b/common/common-botp/src/main/java/apelet/common/botp/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..17b6ecc
--- /dev/null
+++ b/common/common-botp/src/main/java/apelet/common/botp/config/MybatisPlusConfig.java
@@ -0,0 +1,27 @@
+package apelet.common.botp.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * BOTP 模块的 MyBatis-Plus 插件配置。
+ *
+ *
注册乐观锁插件:实体上标注了 {@code @Version} 的字段(转换规则 / 反写规则的 ruleVersion)
+ * 在执行 {@code updateById} 时会自动带上 {@code WHERE version = 旧值} 并写入 {@code version + 1},
+ * 更新影响行数为 0 即代表版本已被别人改过。
+ *
+ * 该插件只对带 {@code @Version} 字段的实体生效,因此不会改变其它模块 / 其它实体的更新行为。
+ *
+ */
+@Configuration
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+ return interceptor;
+ }
+}
diff --git a/common/common-botp/src/main/java/apelet/common/botp/controller/ConvertRuleController.java b/common/common-botp/src/main/java/apelet/common/botp/controller/ConvertRuleController.java
index 33db53c..2138c30 100644
--- a/common/common-botp/src/main/java/apelet/common/botp/controller/ConvertRuleController.java
+++ b/common/common-botp/src/main/java/apelet/common/botp/controller/ConvertRuleController.java
@@ -149,6 +149,27 @@ public class ConvertRuleController {
return ResponseResult.success(convertRuleService.getConvertFieldmappingEntry(id));
}
+ @GetMapping(value = "/getVersionList")
+ @Schema(description = "转换规则版本快照列表")
+ public ResponseResult> getVersionList(@RequestParam Long id) {
+ return ResponseResult.success(convertRuleService.getVersionList(id));
+ }
+
+ @GetMapping(value = "/getVersionSnapshot")
+ @Schema(description = "转换规则指定版本的全量快照")
+ public ResponseResult> getVersionSnapshot(@RequestParam Long id,
+ @RequestParam(required = false) Integer ruleVersion) {
+ return ResponseResult.success(convertRuleService.getVersionSnapshot(id, ruleVersion));
+ }
+
+ @GetMapping(value = "/compareVersion")
+ @Schema(description = "转换规则版本对比(新增/删除/修改,不做逐字 diff)")
+ public ResponseResult> compareVersion(@RequestParam Long id,
+ @RequestParam Integer fromVersion,
+ @RequestParam(required = false) Integer toVersion) {
+ return ResponseResult.success(convertRuleService.compareVersion(id, fromVersion, toVersion));
+ }
+
/**
* 根据源单据查询关联关系
*
diff --git a/common/common-botp/src/main/java/apelet/common/botp/controller/ReverseWritingRulesController.java b/common/common-botp/src/main/java/apelet/common/botp/controller/ReverseWritingRulesController.java
index 103c8aa..902ecbd 100644
--- a/common/common-botp/src/main/java/apelet/common/botp/controller/ReverseWritingRulesController.java
+++ b/common/common-botp/src/main/java/apelet/common/botp/controller/ReverseWritingRulesController.java
@@ -8,7 +8,6 @@ import apelet.common.botp.service.IReverseWritingFormulaService;
import apelet.common.botp.service.IReverseWritingRulesService;
import apelet.common.core.annotation.MyRequestBody;
import apelet.common.core.constant.ErrorCodeEnum;
-import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.MyPageData;
import apelet.common.core.object.MyPageParam;
import apelet.common.core.object.ResponseResult;
@@ -88,46 +87,12 @@ public class ReverseWritingRulesController {
@PostMapping(value = "/saveOrUpdate")
@Schema(description = "添加主数据和分录数据")
- @Transactional(rollbackFor = Exception.class)
public ResponseResult> addAll(@RequestBody ReverseWritingRulesDto dto) {
if (dto == null) {
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED);
}
- ReverseWritingRules reverseWritingRules = new ReverseWritingRules();
- BeanUtils.copyProperties(dto, reverseWritingRules);
- if (reverseWritingRules.getId() != null) {
- // 更新走按主键就地更新,避免删除重建导致创建人等审计字段丢失
- ReverseWritingRules original = rulesService.getById(reverseWritingRules.getId());
- if (original != null) {
- reverseWritingRules.setCreateUserId(original.getCreateUserId());
- reverseWritingRules.setCreateTime(original.getCreateTime());
- }
- reverseWritingRules.setUpdateTime(new Date());
- if (!rulesService.updateById(reverseWritingRules)) {
- throw new MyRuntimeException("反写规则配置数据保存失败!");
- }
- // 分录按规则id全量覆盖
- formulaService.remove(new LambdaQueryWrapper().eq(ReverseWritingFormula::getRulesId, reverseWritingRules.getId()));
- businessService.remove(new LambdaQueryWrapper().eq(ReverseWritingBusiness::getRulesId, reverseWritingRules.getId()));
- } else {
- reverseWritingRules.setCreateTime(new Date());
- boolean save = rulesService.save(reverseWritingRules);
- if (!save) {
- throw new MyRuntimeException("反写规则配置数据保存失败!");
- }
- }
- //反写公式
- List formulaList = dto.getFormulaList();
- if (ObjectUtil.isNotEmpty(formulaList)) {
- formulaList.forEach(f -> f.setRulesId(reverseWritingRules.getId()));
- formulaService.saveBatch(formulaList);
- }
- // 业务规则
- List businessList = dto.getBusinessList();
- if (ObjectUtil.isNotEmpty(businessList)) {
- businessList.forEach(f -> f.setRulesId(reverseWritingRules.getId()));
- businessService.saveBatch(businessList);
- }
+ // 版本号 +1 与全量快照均在 Service 内完成,与主表/子表写入处于同一事务
+ rulesService.saveAll(dto);
return ResponseResult.success("保存成功");
}
@@ -156,4 +121,25 @@ public class ReverseWritingRulesController {
return ResponseResult.success(businessList);
}
+ @GetMapping(value = "/getVersionList")
+ @Schema(description = "反写规则版本快照列表")
+ public ResponseResult> getVersionList(@RequestParam Long id) {
+ return ResponseResult.success(rulesService.getVersionList(id));
+ }
+
+ @GetMapping(value = "/getVersionSnapshot")
+ @Schema(description = "反写规则指定版本的全量快照")
+ public ResponseResult> getVersionSnapshot(@RequestParam Long id,
+ @RequestParam(required = false) Integer ruleVersion) {
+ return ResponseResult.success(rulesService.getVersionSnapshot(id, ruleVersion));
+ }
+
+ @GetMapping(value = "/compareVersion")
+ @Schema(description = "反写规则版本对比(新增/删除/修改,不做逐字 diff)")
+ public ResponseResult> compareVersion(@RequestParam Long id,
+ @RequestParam Integer fromVersion,
+ @RequestParam(required = false) Integer toVersion) {
+ return ResponseResult.success(rulesService.compareVersion(id, fromVersion, toVersion));
+ }
+
}
diff --git a/common/common-botp/src/main/java/apelet/common/botp/dao/ConvertRuleVerMapper.java b/common/common-botp/src/main/java/apelet/common/botp/dao/ConvertRuleVerMapper.java
new file mode 100644
index 0000000..b4be72b
--- /dev/null
+++ b/common/common-botp/src/main/java/apelet/common/botp/dao/ConvertRuleVerMapper.java
@@ -0,0 +1,11 @@
+package apelet.common.botp.dao;
+
+import apelet.common.botp.model.ConvertRuleVer;
+import apelet.common.core.base.dao.BaseDaoMapper;
+
+/**
+ * 转换规则版本快照 Mapper。
+ *
+ */
+public interface ConvertRuleVerMapper extends BaseDaoMapper {
+}
diff --git a/common/common-botp/src/main/java/apelet/common/botp/dao/ReverseWritingRulesVerMapper.java b/common/common-botp/src/main/java/apelet/common/botp/dao/ReverseWritingRulesVerMapper.java
new file mode 100644
index 0000000..876579c
--- /dev/null
+++ b/common/common-botp/src/main/java/apelet/common/botp/dao/ReverseWritingRulesVerMapper.java
@@ -0,0 +1,11 @@
+package apelet.common.botp.dao;
+
+import apelet.common.botp.model.ReverseWritingRulesVer;
+import apelet.common.core.base.dao.BaseDaoMapper;
+
+/**
+ * 反写规则版本快照 Mapper。
+ *
+ */
+public interface ReverseWritingRulesVerMapper extends BaseDaoMapper {
+}
diff --git a/common/common-botp/src/main/java/apelet/common/botp/dto/RuleVersionDiffDto.java b/common/common-botp/src/main/java/apelet/common/botp/dto/RuleVersionDiffDto.java
new file mode 100644
index 0000000..5b598ed
--- /dev/null
+++ b/common/common-botp/src/main/java/apelet/common/botp/dto/RuleVersionDiffDto.java
@@ -0,0 +1,147 @@
+package apelet.common.botp.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 规则版本对比结果。
+ *
+ * 对比粒度是「字段 / 分录行」,只给出新增、删除、修改三类结论,不做逐字 diff:
+ * 规则主表按字段比对;子表分录按业务主键配对后按字段比对,配对不上的分别算新增或删除。
+ *
+ * @author admin
+ * @since 2024-05-17
+ */
+@Data
+@Schema(description = "规则版本对比结果")
+public class RuleVersionDiffDto implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 变化类型:新增。
+ */
+ public static final String CHANGE_ADDED = "ADDED";
+
+ /**
+ * 变化类型:删除。
+ */
+ public static final String CHANGE_REMOVED = "REMOVED";
+
+ /**
+ * 变化类型:修改。
+ */
+ public static final String CHANGE_MODIFIED = "MODIFIED";
+
+ @Schema(description = "规则Id")
+ private Long ruleId;
+
+ @Schema(description = "对比基准版本号")
+ private Integer fromVersion;
+
+ @Schema(description = "对比目标版本号")
+ private Integer toVersion;
+
+ @Schema(description = "基准版本的快照时间")
+ private Date fromCreateTime;
+
+ @Schema(description = "目标版本的快照时间")
+ private Date toCreateTime;
+
+ @Schema(description = "规则主表字段差异")
+ private List ruleDiff = new ArrayList<>();
+
+ /**
+ * 子表分录差异,key 为快照分区名(fieldMappings / pluginEntries / formulas / businesses)。
+ */
+ @Schema(description = "子表分录差异")
+ private Map entries = new LinkedHashMap<>();
+
+ @Schema(description = "新增条目数(主表字段 + 各子表分录)")
+ private int addedCount;
+
+ @Schema(description = "删除条目数")
+ private int removedCount;
+
+ @Schema(description = "修改条目数")
+ private int modifiedCount;
+
+ /**
+ * 单个字段的差异。
+ */
+ @Data
+ @Schema(description = "字段差异")
+ public static class FieldDiff implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "字段名")
+ private String field;
+
+ @Schema(description = "变化类型:ADDED / REMOVED / MODIFIED")
+ private String changeType;
+
+ @Schema(description = "基准版本的值")
+ private Object oldValue;
+
+ @Schema(description = "目标版本的值")
+ private Object newValue;
+
+ public static FieldDiff of(String field, String changeType, Object oldValue, Object newValue) {
+ FieldDiff diff = new FieldDiff();
+ diff.setField(field);
+ diff.setChangeType(changeType);
+ diff.setOldValue(oldValue);
+ diff.setNewValue(newValue);
+ return diff;
+ }
+ }
+
+ /**
+ * 一类子表分录的差异。
+ */
+ @Data
+ @Schema(description = "子表分录差异")
+ public static class EntryDiff implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "新增的分录行")
+ private List