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