From b278f5d100a2651abd34da6a492e60846941e698 Mon Sep 17 00:00:00 2001 From: lml <1297555865@qq.com> Date: Mon, 26 Oct 2026 17:59:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(botp):=20=E8=BD=AC=E6=8D=A2=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E8=A1=A5=E7=BC=96=E8=BE=91=E7=95=8C=E9=9D=A2=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=88=86=E5=BD=95=E8=A1=8C?= =?UTF-8?q?=E8=A2=AB=E8=A6=86=E7=9B=96=E4=B8=A2=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConvertResult 新增 formVal / editFormId / entryTables:formStatus=2(未落库)时 前端据此打开目标编辑页并触发保存,并把 BillData 里的分录组装成 executePluginBatch 的 slaveData(key 为 tableId) - ConvertOrchestrator 新增 resolveEditFormId:优先取规则下行编辑界面, 缺失时按目标表反查 form_type=5 编辑表单兜底,多命中取第一个并告警 - ConvertOrchestrator 新增 resolveEntryTables:从目标表 ORM 元数据取 relationType=1 的链接列,dataKey 取 relationName 与 BillData 口径一致 - 新增 ConvertEntryTable - 修复 BillDataConverter.convertEntries:entryMap 提到循环内, 原先所有分录行共用一个 Map 实例,后行覆盖前行,前端永远只看到一条明细 - ConvertRuleServiceImpl 兼容入口透传 formVal 修复的分组/合并场景下"主表正确、子表只剩一条"问题即由后者引起 --- .../apelet/common/botp/dto/ConvertEntryTable.java | 37 +++++++++ .../java/apelet/common/botp/dto/ConvertResult.java | 22 ++++++ .../botp/service/execute/ConvertOrchestrator.java | 92 ++++++++++++++++++++++ .../botp/service/impl/ConvertRuleServiceImpl.java | 2 + .../apelet/common/botp/util/BillDataConverter.java | 4 +- 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java diff --git a/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java b/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java new file mode 100644 index 0000000..e0e6b1f --- /dev/null +++ b/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java @@ -0,0 +1,37 @@ +package apelet.common.botp.dto; + +import lombok.Data; + +/** + * 目标单据的分录表信息。 + * + *

转换结果里只带出「这张单据有哪些分录表、各自的 tableId 和表名」, + * 不重复展开分录行——分录行本身在 {@link ConvertedBill#getBillData()} 里。

+ * + *

前端拼装 /onlineOperation/executePluginBatch 的 slaveData 时, + * key 必须是用 {@link #tableId} 而不是 {@link #tableName},这一点与单条 executePlugin 一致。

+ * + * @author chenchuchuan + * @since 2026-10-26 + */ +@Data +public class ConvertEntryTable { + + /** + * 分录表 id(zz_online_table.table_id),executePluginBatch 的 slaveData key。 + */ + private Long tableId; + + /** + * 分录表物理名(如 t_purchasebill_entry)。 + */ + private String tableName; + + /** + * 在 BillData.entryData 里对应的字段名。 + * + *

转换结果的分录是以「关系名」挂到 BillData 上的,前端按这个 key 取分录行, + * 再以 {@link #tableId} 为 key 放进 slaveData。

+ */ + private String dataKey; +} diff --git a/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java b/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java index 26849b1..d867e33 100644 --- a/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java +++ b/common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java @@ -51,4 +51,26 @@ public class ConvertResult { * 表单类型:1 路由表单 / 2 在线表单。 */ private String formType; + + /** + * 目标单据编辑界面的页面参数,口径与旧 TransformResultDto#formVal 一致: + * 在线表单为编辑表单 id,路由表单为路由名。 + * + *

{@code formStatus == "2"}(保存状态,目标单未落库)时,前端据此打开编辑页并触发保存。

+ */ + private String formVal; + + /** + * 目标单据编辑界面对应的在线表单 id(仅在线表单有值,路由表单为 null)。 + * + *

formVal 是字符串形态的兼容字段,本字段为强类型形态,可直接作为 + * /onlineOperation/executePluginBatch 的 formId 使用。

+ */ + private Long editFormId; + + /** + * 目标单据的分录表信息(在线表单才有),前端据此把 BillData 里的分录组装成 + * executePluginBatch 入参 slaveData(key 为 tableId)。 + */ + private List entryTables = new ArrayList<>(); } 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 086deab..233bbab 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 @@ -2,6 +2,7 @@ package apelet.common.botp.service.execute; import apelet.common.botp.dao.ConvertFieldmappingEntryMapper; import apelet.common.botp.dao.ConvertRuleMapper; +import apelet.common.botp.dto.ConvertEntryTable; import apelet.common.botp.dto.ConvertRequest; import apelet.common.botp.dto.ConvertResult; import apelet.common.botp.dto.ConvertedBill; @@ -25,15 +26,22 @@ import apelet.common.generator.dao.OnlFormHeadMapper; import apelet.common.generator.model.OnlFormHead; import apelet.common.generator.utils.OrmGenDataSourceUtil; import apelet.common.online.model.OnlineColumn; +import apelet.common.online.model.OnlineForm; import apelet.common.online.model.OnlineTable; import apelet.common.online.model.constant.FieldKind; +import apelet.common.online.model.constant.FormType; import apelet.common.online.rule.condition.ConditionEvaluator; import apelet.common.online.rule.model.RuleCondition; import apelet.common.online.rule.model.RuleEvalContext; import apelet.common.online.service.OnlineColumnService; +import apelet.common.online.service.OnlineFormService; import apelet.common.online.service.OnlineTableService; +import apelet.common.orm.impl.EntityColumn; +import apelet.common.orm.impl.EntityLinkRelation; +import apelet.common.orm.impl.EntityTable; import apelet.common.orm.impl.Filter; import apelet.common.orm.impl.FilterItem; +import apelet.common.orm.impl.LinkEntityColumn; import apelet.common.orm.impl.Selector; import apelet.common.orm.impl.SelectorItem; import com.alibaba.fastjson.JSON; @@ -98,6 +106,8 @@ public class ConvertOrchestrator { private OnlineTableService onlineTableService; @Autowired private OnlineColumnService onlineColumnService; + @Autowired + private OnlineFormService onlineFormService; /** * 执行转换。 @@ -126,6 +136,10 @@ public class ConvertOrchestrator { boolean autoSaveOn = isAutoSaveOn(rule); result.setFormStatus(autoSaveOn ? Constant.FormStatus_AddNew : Constant.FormStatus_Save); result.setFormType(rule.getDownInterfaceType()); + // formStatus == 2(保存状态)时前端要自己打开编辑页保存,必须知道目标编辑界面。 + result.setFormVal(rule.getDownEditFormVal()); + result.setEditFormId(this.resolveEditFormId(rule, destFormHead)); + result.getEntryTables().addAll(this.resolveEntryTables(destFormHead)); if (passedIds.isEmpty()) { log.info("选单过滤后无可转换的源单,规则id={},传入={}条", rule.getId(), request.getSrcBillIds().size()); return result; @@ -484,6 +498,84 @@ public class ConvertOrchestrator { } /** + * 解析目标单据编辑界面对应的在线表单 id。 + * + *

优先取规则上配置的「下游在线编辑界面」(downEditInterface); + * 配置缺失时按目标表反查编辑表单(form_type = 5)兜底。

+ * + * @param rule 转换规则。 + * @param destFormHead 目标单元数据。 + * @return 在线编辑表单 id;路由表单或确实查不到时返回 null。 + */ + private Long resolveEditFormId(ConvertRule rule, OnlFormHead destFormHead) { + // 路由表单的编辑界面是菜单路由,不是在线表单,这里不返回 id。 + if (!Constant.InterfaceType_ONLINE.equals(rule.getDownInterfaceType())) { + return null; + } + if (rule.getDownEditInterface() != null) { + return rule.getDownEditInterface(); + } + // 兜底:按目标表反查在线编辑表单。 + List editFormList = + onlineFormService.queryByTableName(destFormHead.getTableName(), FormType.EDIT_FORM); + if (editFormList == null || editFormList.isEmpty()) { + log.warn("转换规则未配置下游在线编辑界面,且按目标表 [{}] 反查编辑表单为空,规则id={}", + destFormHead.getTableName(), rule.getId()); + return null; + } + if (editFormList.size() > 1) { + log.warn("目标表 [" + destFormHead.getTableName() + "] 存在 " + editFormList.size() + + " 个编辑表单,取第一个;建议在规则上显式配置下游在线编辑界面,规则id=" + rule.getId()); + } + return editFormList.get(0).getFormId(); + } + + /** + * 解析目标单据的分录表信息,供前端组装 executePluginBatch 的 slaveData。 + * + *

取目标表 ORM 元数据里 relationType=1(一对多)的链接列。dataKey 用 relationName, + * 与 {@code BillDataConverter} 把分录挂到 BillData 上的 key 保持同一口径。

+ * + * @param destFormHead 目标单元数据。 + * @return 分录表信息列表;无分录时返回空列表。 + */ + private List resolveEntryTables(OnlFormHead destFormHead) { + List entryTableList = new ArrayList<>(); + EntityTable entityTable = ormGenDataSourceUtil.getEntityTable(destFormHead.getTableName()); + if (entityTable == null || entityTable.getEntityColumnList() == null) { + return entryTableList; + } + for (EntityColumn entityColumn : entityTable.getEntityColumnList()) { + // 分录是「一对多」链接列,与 collectEntryBusinessIdForRemove 的判断口径保持一致。 + if (!(entityColumn instanceof LinkEntityColumn)) { + continue; + } + EntityLinkRelation relation = ((LinkEntityColumn) entityColumn).getEntityLinkRelation(); + if (relation == null || relation.getRelationType() == null || relation.getRelationType() != 1) { + continue; + } + if (relation.getSuperTable() == null || StringUtils.isBlank(relation.getSuperTable().getTableName())) { + continue; + } + String entryTableName = relation.getSuperTable().getTableName(); + // tableId 前端要用作 slaveData 的 key,取自在线表元数据。 + OnlineTable entryTable = onlineTableService.getOne(new LambdaQueryWrapper() + .eq(OnlineTable::getTableName, entryTableName).last("LIMIT 1")); + if (entryTable == null) { + log.warn("目标表 [{}] 的分录表 [{}] 未在在线表中登记,跳过;前端无法据此拼装 slaveData", + destFormHead.getTableName(), entryTableName); + continue; + } + ConvertEntryTable entryTableInfo = new ConvertEntryTable(); + entryTableInfo.setTableId(entryTable.getTableId()); + entryTableInfo.setTableName(entryTableName); + entryTableInfo.setDataKey(relation.getRelationName()); + entryTableList.add(entryTableInfo); + } + return entryTableList; + } + + /** * 加载字段映射分录。 * * @param ruleId 规则 id。 diff --git a/common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java b/common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java index 3a01520..34ecdf2 100644 --- a/common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java +++ b/common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java @@ -543,6 +543,8 @@ public class ConvertRuleServiceImpl extends BaseService implem TransformResultDto dto = new TransformResultDto(); dto.setFormStatus(result.getFormStatus()); dto.setFormType(result.getFormType()); + // 新旧接口保持一致:formVal 原样透出,老前端拿它打开目标编辑页。 + dto.setFormVal(result.getFormVal()); if (result.getBills().isEmpty()) { return dto; } diff --git a/common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java b/common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java index d058074..79414ec 100644 --- a/common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java +++ b/common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java @@ -77,8 +77,10 @@ public class BillDataConverter { */ private List> convertEntries(ObjectCollection objectCollection) { List> entryList = new ArrayList<>(); - Map entryMap = new HashMap<>(); for (int i = 0; i < objectCollection.size(); i++) { + // 每条分录行必须独占一个 Map:放在循环外会让所有行共用一个实例, + // 结果就是后一行覆盖前一行,前端永远只看到一条明细 + Map entryMap = new HashMap<>(); ObjectValue entryValue = objectCollection.getObject(i); Iterator entryIt = entryValue.getValues().keySet().iterator(); while (entryIt.hasNext()) {