From 846569f7f2d087133848793170d791f046e300d0 Mon Sep 17 00:00:00 2001 From: lml <1297555865@qq.com> Date: Fri, 23 Oct 2026 17:35:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(botp):=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E7=BB=84=E5=90=88=E5=B9=B6=E6=97=B6=E6=BA=90=E5=8D=95=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=8A=A5=E9=94=99=E4=B8=8E=E5=AD=90=E8=A1=A8=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConvertOrchestrator#loadGroupKeys:orm 的 FilterItem.in 不支持集合, 传 List 会生成 id in ('[1,2,3]') 的非法 SQL。改用 id = ? OR id = ? 组合,仍是单次查询;异常消息补上底层原因。 - BotpService#groupSourceRows:分录归并误用表头级 group_by_field 作行键, 分录表无该列导致全部源行塌进同一组、目标单只剩一条明细。改为每条源 分录行对应一条目标分录行,分单字段仅用于决定生成几张目标单。 --- .../common/botp/service/execute/BotpService.java | 43 +++++++++------------- .../botp/service/execute/ConvertOrchestrator.java | 32 +++++++++++----- 2 files changed, 40 insertions(+), 35 deletions(-) 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 199cf0d..22b264b 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 @@ -103,7 +103,7 @@ public class BotpService { } ConvertMappingPlan plan = buildMappingPlan(convertFieldmappingEntryList, srcFormHead, destFormHead); List sourceBills = loadSourceBills(plan, srcFormHead, srcBillIds); - Map> entryGroups = buildEntryGroups(plan, sourceBills, convertRule); + Map> entryGroups = buildEntryGroups(plan, sourceBills); BtpTransformResult result = initDestBillData(plan, sourceBills, convertRule, entryGroups); ObjectValue destBillData = result.getBillDataList().getObject(0); applyMappings(plan, destBillData, sourceBills, convertRule, entryGroups); @@ -358,59 +358,52 @@ public class BotpService { } /** - * 为每个目标分录表预计算归并结果,供建骨架与逐列映射共用(避免算两遍)。 + * 为每个目标分录表预计算源行分组,供建骨架与逐列映射共用(避免算两遍)。 * * @param plan 映射计划。 * @param sourceBills 源单数据。 - * @param convertRule 转换规则(提供行键字段)。 - * @return 目标分录表名 → 归并后的源行组。 + * @return 目标分录表名 → 源行组。 */ - private Map> buildEntryGroups(ConvertMappingPlan plan, List sourceBills, ConvertRule convertRule) { + private Map> buildEntryGroups(ConvertMappingPlan plan, List sourceBills) { Map> entryGroups = new LinkedHashMap<>(); for (String destEntryTable : plan.getDestEntryColumns().keySet()) { - entryGroups.put(destEntryTable, groupSourceRows(plan, sourceBills, destEntryTable, convertRule.getGroupByField())); + entryGroups.put(destEntryTable, groupSourceRows(plan, sourceBills, destEntryTable)); } return entryGroups; } /** - * 把批次内所有源单的某张源分录表摊平后按行键归并。 + * 把批次内所有源单的某张源分录表摊平成「一行源行一组」。 + * + *

为什么分录不再按 {@code group_by_field} 归并:分单字段是源单表头级的, + * 只用来决定「生成几张目标单」(见 {@code ConvertOrchestrator#splitByGroup})。 + * 分录表上没有这一列,拿它当行键会让每一行都取到 null、全部塌进同一个分组, + * 最终一张目标单只剩一条明细——即使它合并了多张源单的全部源行。

+ * + *

保持「每条源分录行 → 一条目标分录行」:按分组合并时,每张目标单保留该组内 + * 全部源单的全部明细行,不丢行、不互相覆盖。同一目标行的多值折算仍由 + * {@code merge_method} 在映射阶段决定(当前每条目标行只对应一条源行,故按「直接取」处理)。

* * @param plan 映射计划。 * @param sourceBills 源单数据。 * @param destEntryTable 目标分录表名。 - * @param mergeKeyField 行键字段,空表示不合并。 * @return 分组结果,每组对应一条目标分录行。 */ private List groupSourceRows(ConvertMappingPlan plan, List sourceBills, - String destEntryTable, String mergeKeyField) { + String destEntryTable) { String srcEntryTable = resolveSourceEntryTable(plan, destEntryTable); - List flatRows = new ArrayList<>(); + List groups = new ArrayList<>(); for (ObjectValue bill : sourceBills) { ObjectCollection rows = bill.getObjectCollection(srcEntryTable); if (rows == null) { continue; } for (int i = 0; i < rows.size(); i++) { - flatRows.add(new ConvertSourceRow(bill, rows.getObject(i))); - } - } - List groups = new ArrayList<>(); - if (StringUtils.isBlank(mergeKeyField)) { - for (ConvertSourceRow flat : flatRows) { ConvertSourceRowGroup group = new ConvertSourceRowGroup(); - group.add(flat.getBill(), flat.getRow()); + group.add(bill, rows.getObject(i)); groups.add(group); } - return groups; - } - Map grouped = new LinkedHashMap<>(); - for (ConvertSourceRow flat : flatRows) { - Object key = flat.getRow().get(mergeKeyField); - grouped.computeIfAbsent(key == null ? "" : String.valueOf(key), k -> new ConvertSourceRowGroup()) - .add(flat.getBill(), flat.getRow()); } - groups.addAll(grouped.values()); return groups; } diff --git a/common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java b/common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java index 20b227a..086deab 100644 --- a/common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java +++ b/common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java @@ -309,9 +309,14 @@ public class ConvertOrchestrator { } /** - * 批量取源单的分组字段值。 + * 批量取源单的分组字段值:一次查询取回本批次全部源单的分组值。 * - *

一次 {@code id in (...)} 查询取回全部源单的分组值,避免逐条 queryOne 的 N 次往返。

+ *

为什么用 {@code id = ? OR id = ?} 而不是 {@code id in (...)}: + * orm 的 {@code FilterItem.in} 在这个版本里不支持集合参数—— + * SQL 生成器只对 Integer/Long/String/日期等标量做处理,对其它对象直接 + * {@code toString()} 加引号拼进 SQL。因此传入 {@code List} 会生成 + * {@code id in ('[1, 2, 3]')},方括号是非法 SQL,查询必然失败(线上即此报错)。 + * 这里改用同等条件的 OR 组合,仍是单次查询;源单在同一张表内,无需逐条往返。

* * @param srcTable 源单表名。 * @param billIds 源单 id 集合。 @@ -323,12 +328,20 @@ public class ConvertOrchestrator { if (billIds == null || billIds.isEmpty()) { return groupKeys; } - List ids = new ArrayList<>(billIds.size()); + Filter filter = new Filter(); + StringBuilder format = new StringBuilder(); + int index = 0; for (String billId : billIds) { - ids.add(Long.valueOf(billId)); + filter.add(new FilterItem("id", FilterItem.equals, Long.valueOf(billId))); + if (index > 0) { + format.append(" or "); + } + format.append("#").append(index++); + } + // 只有一个条件时不必设置组合格式,保持默认即可 + if (index > 1) { + filter.setFormat(format.toString()); } - Filter filter = new Filter(); - filter.add(new FilterItem("id", FilterItem.in, ids)); Selector selector = new Selector(); selector.getList().add(new SelectorItem("id")); selector.getList().add(new SelectorItem(groupByField)); @@ -336,10 +349,9 @@ public class ConvertOrchestrator { try { rows = ormGenDataSourceUtil.query(srcTable, filter, selector); } catch (Exception e) { - throw new BotpMappingException("按分单字段查询源单失败,表名=" + srcTable + ",字段=" + groupByField, e); - } - if (rows == null) { - return groupKeys; + // 带上底层原因:否则日志里只剩"查询源单失败",要再翻一层堆栈才看得到真实 SQL 错误 + throw new BotpMappingException("按分单字段查询源单失败,表名=" + srcTable + + ",字段=" + groupByField + ",id=" + billIds + ",原因=" + e.getMessage(), e); } for (int i = 0; i < rows.size(); i++) { ObjectValue row = rows.getObject(i);