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