From 975a710a46d03c9b28056b7ca847997ba2b3fb62 Mon Sep 17 00:00:00 2001
From: chenchuchuan <13554600537@163.com>
Date: Thu, 22 Oct 2026 11:06:47 +0800
Subject: [PATCH] =?UTF-8?q?refactor(botp):=20=E5=B0=86=E6=BA=90=E5=8D=95?=
=?UTF-8?q?=E5=85=B3=E9=97=AD=E6=A0=87=E8=AE=B0=E4=BB=8E=E8=A1=8C=E7=BA=A7?=
=?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=95=B4=E5=8D=95=E7=BA=A7=E5=88=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将 closed_flag 字段定义从分录表移至主表,语义改为整单关闭
- 反写执行时将 entries 按源单分组,对每张源单主表分别求值关闭条件
- 新增 resolveTableName 方法,按单元数据 id 解析源单表名
- 简化插件排序逻辑,移除自定义 parseOrder 方法改用自然排序加 nullsLast
- 更新 BotpRuleConstant 注释,明确主表字段定位与整单关闭语义
---
.../botp/model/constant/BotpRuleConstant.java | 13 ++--
.../common/botp/service/execute/BotpService.java | 30 +--------
.../service/execute/WriteBackOrchestrator.java | 77 +++++++++++-----------
.../service/impl/OnlFormHeadServiceImpl.java | 23 +++----
4 files changed, 63 insertions(+), 80 deletions(-)
diff --git a/common/common-botp/src/main/java/apelet/common/botp/model/constant/BotpRuleConstant.java b/common/common-botp/src/main/java/apelet/common/botp/model/constant/BotpRuleConstant.java
index 2310007..58d9c43 100644
--- a/common/common-botp/src/main/java/apelet/common/botp/model/constant/BotpRuleConstant.java
+++ b/common/common-botp/src/main/java/apelet/common/botp/model/constant/BotpRuleConstant.java
@@ -22,13 +22,16 @@ public final class BotpRuleConstant {
// ==================== 单据上的系统标准字段(列名) ====================
/**
- * 源单行关闭标记列名。
+ * 源单关闭标记列名。
*
- *
分录表的系统标准字段:新建表单由 {@code OnlFormHeadServiceImpl} 与 parent_id / seq
- * 同批自动生成;存量表由 {@code zz-resource/db-scripts/botp-source-close-flag.sql} 补列。
+ * 主表的系统标准字段:新建表单由 {@code OnlFormHeadServiceImpl} 与
+ * billstatus / srcbillid 等同批自动生成;存量表由
+ * {@code zz-resource/db-scripts/botp-source-close-flag.sql} 补列。
*
- * 反写规则的「源单行关闭条件」成立时写 1、不成立时写 0——
- * 撤销/弃审把数量退回后条件不再成立,会写回 0 自动解除关闭。
+ * 语义是整单关闭:反写规则的「源单行关闭条件」成立(下推量与回写量相等)时写 1、
+ * 不成立时写 0——撤销/弃审把数量退回后条件不再成立,会写回 0 自动解除关闭。
+ *
+ * 挂在主表而非分录表:条件比较的是单据级的数量,且单表单据没有分录表可挂。
*/
public static final String COLUMN_CLOSED_FLAG = "closed_flag";
diff --git a/common/common-botp/src/main/java/apelet/common/botp/service/execute/BotpService.java b/common/common-botp/src/main/java/apelet/common/botp/service/execute/BotpService.java
index 367b437..199cf0d 100644
--- a/common/common-botp/src/main/java/apelet/common/botp/service/execute/BotpService.java
+++ b/common/common-botp/src/main/java/apelet/common/botp/service/execute/BotpService.java
@@ -3,11 +3,7 @@ package apelet.common.botp.service.execute;
import apelet.common.botp.dao.ConvertRelationEntryMapper;
import apelet.common.botp.dao.ConvertRelationMapper;
import apelet.common.botp.exception.BotpMappingException;
-import apelet.common.botp.model.ConvertFieldmappingEntry;
-import apelet.common.botp.model.ConvertPluginEntry;
-import apelet.common.botp.model.ConvertRelation;
-import apelet.common.botp.model.ConvertRelationEntry;
-import apelet.common.botp.model.ConvertRule;
+import apelet.common.botp.model.*;
import apelet.common.botp.model.constant.BotpRuleConstant;
import apelet.common.botp.model.constant.MergeMethodEnum;
import apelet.common.botp.model.constant.ValueMethodEnum;
@@ -62,13 +58,10 @@ import java.util.*;
public class BotpService {
/**
- * 插件执行顺序:按 serialNumber 数值升序。
- *
- * serialNumber 在库里是 varchar,直接按字符串比较会得到 {@code "10" < "2"} 的错误顺序;
- * 这里解析成数值再排。解析不了的排在最后。彻底修法是把它改成整型列(需存量数据迁移)。
+ * 插件执行顺序:按 serialNumber 升序,未配置的排在最后。
*/
private static final Comparator PLUGIN_ORDER =
- Comparator.comparingInt(entry -> parseOrder(entry.getSerialNumber()));
+ Comparator.comparing(ConvertPluginEntry::getSerialNumber, Comparator.nullsLast(Comparator.naturalOrder()));
@Autowired
private ConvertRelationMapper convertRelationMapper;
@@ -703,21 +696,4 @@ public class BotpService {
String autoSave = convertRule.getAutoSave();
return BotpRuleConstant.SWITCH_ON_NUMERIC_VALUE.equals(autoSave) || BotpRuleConstant.SWITCH_ON_VALUE.equals(autoSave);
}
-
- /**
- * 解析插件执行顺序,非数字排在最后。
- *
- * @param serialNumber 配置里的顺序值。
- * @return 可比较的数值。
- */
- private static int parseOrder(String serialNumber) {
- if (StringUtils.isBlank(serialNumber)) {
- return Integer.MAX_VALUE;
- }
- try {
- return Integer.parseInt(serialNumber.trim());
- } catch (NumberFormatException e) {
- return Integer.MAX_VALUE;
- }
- }
}
diff --git a/common/common-botp/src/main/java/apelet/common/botp/service/execute/WriteBackOrchestrator.java b/common/common-botp/src/main/java/apelet/common/botp/service/execute/WriteBackOrchestrator.java
index b4d4560..106c888 100644
--- a/common/common-botp/src/main/java/apelet/common/botp/service/execute/WriteBackOrchestrator.java
+++ b/common/common-botp/src/main/java/apelet/common/botp/service/execute/WriteBackOrchestrator.java
@@ -358,17 +358,14 @@ public class WriteBackOrchestrator implements BotpReverseWriteFacade {
}
/**
- * 源单行关闭:关闭标记始终反映「当前是否满足关闭条件」。
+ * 源单关闭:关闭标记始终反映「当前是否满足关闭条件」。
*
- * 按源分录行逐行求值,不是拿表头求值再一刀切——「已下推数量 >= 数量」这类条件
- * 引用的是分录行上的字段,用表头求值永远取不到值。
+ * 单据级语义:条件比较的是整张源单的数量(下推量与回写量相等即关闭),
+ * 所以写的是源单主表那一行,不是分录行。多对一时一张目标单可能对应多张源单,
+ * 按源单分别求值、分别关闭。
*
* 条件成立写 1,不成立写 0:撤销 / 弃审把数量退回来之后条件不再成立,
- * 这里会把标记清掉、解除关闭(PRD §6.3 ④)。只写 1 不写 0 的话,被关掉的行永远关着。
- *
- * 条件里的字段引用建议写成 {@code 分录表.分录表.字段} 形式(如
- * {@code pur_apply_entry.pur_apply_entry.orderqty >= pur_apply_entry.pur_apply_entry.qty}),
- * 这样在行上下文里能直接解析。
+ * 这里会把标记清掉、解除关闭(PRD §6.3 ④)。只写 1 不写 0 的话,被关掉的单永远关着。
*
* @param rule 反写规则。
* @param entries 关联关系。
@@ -379,21 +376,25 @@ public class WriteBackOrchestrator implements BotpReverseWriteFacade {
if (StringUtils.isBlank(expression)) {
return;
}
+ String sourceTable = resolveTableName(String.valueOf(rule.getSourceDocuments()));
+ if (StringUtils.isBlank(sourceTable)) {
+ log.warn("反写规则未配置源单或源单元数据不存在,跳过源单关闭。规则id={}", rule.getId());
+ return;
+ }
+ Map> bySourceBill = new LinkedHashMap<>();
for (ConvertRelationEntry entry : entries) {
- String table = entry.getSourceEntryNumber();
- if (StringUtils.isBlank(table)) {
- // 走了 srcbillid 回退分支时拿不到源分录表名,无法安全写回,跳过
- log.warn("源分录表名缺失(可能走了 srcbillid 回退分支),跳过源单行关闭。规则id={}", rule.getId());
- return;
+ if (StringUtils.isBlank(entry.getSourceId())) {
+ continue;
}
- ObjectValue sourceRow = queryRow(table, entry.getSourceEntryId());
- if (sourceRow == null) {
+ bySourceBill.computeIfAbsent(entry.getSourceId(), k -> new ArrayList<>()).add(entry);
+ }
+ for (String sourceBillId : bySourceBill.keySet()) {
+ ObjectValue sourceBill = queryRow(sourceTable, sourceBillId);
+ if (sourceBill == null) {
continue;
}
- // 关闭标记始终反映「当前是否满足关闭条件」:正向推满时置 1,
- // 撤销/弃审把数量退回来之后条件不再成立,这里会写回 0 解除关闭(PRD §6.3 ④)。
- boolean shouldClose = conditionHit(expression, null, sourceRow);
- writeSourceRow(table, entry.getSourceEntryId(), BotpRuleConstant.COLUMN_CLOSED_FLAG, shouldClose ? 1 : 0);
+ boolean shouldClose = conditionHit(expression, null, sourceBill);
+ writeSourceRow(sourceTable, sourceBillId, BotpRuleConstant.COLUMN_CLOSED_FLAG, shouldClose ? 1 : 0);
}
}
@@ -425,6 +426,25 @@ public class WriteBackOrchestrator implements BotpReverseWriteFacade {
}
/**
+ * 按单元数据 id 解析表名。
+ *
+ * @param headId 单元数据 id。
+ * @return 表名;解析不到时返回 null。
+ */
+ private String resolveTableName(String headId) {
+ if (StringUtils.isBlank(headId)) {
+ return null;
+ }
+ try {
+ OnlFormHead head = onlFormHeadService.getById(Long.valueOf(headId));
+ return head == null ? null : head.getTableName();
+ } catch (Exception e) {
+ log.warn("解析单元数据表名失败,headId={},原因={}", headId, e.getMessage());
+ return null;
+ }
+ }
+
+ /**
* 覆盖方式记原值:把反写前的源单字段值写进关联关系,撤销时读出来恢复。
*
* @param entry 关联关系。
@@ -563,7 +583,7 @@ public class WriteBackOrchestrator implements BotpReverseWriteFacade {
if (entries.isEmpty()) {
return Collections.emptyList();
}
- entries.sort((a, b) -> Integer.compare(parseOrder(a.getSerialNumber()), parseOrder(b.getSerialNumber())));
+ entries.sort(Comparator.comparing(ConvertPluginEntry::getSerialNumber, Comparator.nullsLast(Comparator.naturalOrder())));
List plugins = new ArrayList<>(entries.size());
for (ConvertPluginEntry entry : entries) {
plugins.add(plugInLoader.load(entry.getClassName(), WriteBackPlugIn.class));
@@ -782,23 +802,6 @@ public class WriteBackOrchestrator implements BotpReverseWriteFacade {
}
/**
- * 解析插件执行顺序,非数字排在最后。
- *
- * @param serialNumber 配置里的顺序值。
- * @return 可比较的数值。
- */
- private static int parseOrder(String serialNumber) {
- if (StringUtils.isBlank(serialNumber)) {
- return Integer.MAX_VALUE;
- }
- try {
- return Integer.parseInt(serialNumber.trim());
- } catch (NumberFormatException e) {
- return Integer.MAX_VALUE;
- }
- }
-
- /**
* 校验请求入参。
*
* @param request 反写触发请求。
diff --git a/common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java b/common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
index 8c7cd3b..6967ee0 100644
--- a/common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
+++ b/common/common-generator/src/main/java/apelet/common/generator/service/impl/OnlFormHeadServiceImpl.java
@@ -833,6 +833,18 @@ public class OnlFormHeadServiceImpl extends ServiceImpl