Browse Source

fix(botp): 转换结果补编辑界面参数并修复分录行被覆盖丢失

- 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

修复的分组/合并场景下"主表正确、子表只剩一条"问题即由后者引起
dev-lml
lml 4 days ago
parent
commit
b278f5d100
  1. 37
      common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java
  2. 22
      common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java
  3. 92
      common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java
  4. 2
      common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java
  5. 4
      common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java

37
common/common-botp/src/main/java/apelet/common/botp/dto/ConvertEntryTable.java

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
package apelet.common.botp.dto;
import lombok.Data;
/**
* 目标单据的分录表信息。
*
* <p>转换结果里只带出「这张单据有哪些分录表、各自的 tableId 和表名」,
* 不重复展开分录行——分录行本身在 {@link ConvertedBill#getBillData()} 里。</p>
*
* <p>前端拼装 /onlineOperation/executePluginBatch 的 slaveData 时,
* key 必须是用 {@link #tableId} 而不是 {@link #tableName},这一点与单条 executePlugin 一致。</p>
*
* @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 里对应的字段名。
*
* <p>转换结果的分录是以「关系名」挂到 BillData 上的,前端按这个 key 取分录行,
* 再以 {@link #tableId} 为 key 放进 slaveData。</p>
*/
private String dataKey;
}

22
common/common-botp/src/main/java/apelet/common/botp/dto/ConvertResult.java

@ -51,4 +51,26 @@ public class ConvertResult { @@ -51,4 +51,26 @@ public class ConvertResult {
* 表单类型:1 路由表单 / 2 在线表单。
*/
private String formType;
/**
* 目标单据编辑界面的页面参数,口径与旧 TransformResultDto#formVal 一致:
* 在线表单为编辑表单 id,路由表单为路由名。
*
* <p>{@code formStatus == "2"}(保存状态,目标单未落库)时,前端据此打开编辑页并触发保存。</p>
*/
private String formVal;
/**
* 目标单据编辑界面对应的在线表单 id(仅在线表单有值,路由表单为 null)。
*
* <p>formVal 是字符串形态的兼容字段,本字段为强类型形态,可直接作为
* /onlineOperation/executePluginBatch 的 formId 使用。</p>
*/
private Long editFormId;
/**
* 目标单据的分录表信息(在线表单才有),前端据此把 BillData 里的分录组装成
* executePluginBatch 入参 slaveData(key 为 tableId)。
*/
private List<ConvertEntryTable> entryTables = new ArrayList<>();
}

92
common/common-botp/src/main/java/apelet/common/botp/service/execute/ConvertOrchestrator.java

@ -2,6 +2,7 @@ package apelet.common.botp.service.execute; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -484,6 +498,84 @@ public class ConvertOrchestrator {
}
/**
* 解析目标单据编辑界面对应的在线表单 id。
*
* <p>优先取规则上配置的「下游在线编辑界面」(downEditInterface);
* 配置缺失时按目标表反查编辑表单(form_type = 5)兜底。</p>
*
* @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<OnlineForm> 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。
*
* <p>取目标表 ORM 元数据里 relationType=1(一对多)的链接列。dataKey 用 relationName,
* 与 {@code BillDataConverter} 把分录挂到 BillData 上的 key 保持同一口径。</p>
*
* @param destFormHead 目标单元数据。
* @return 分录表信息列表;无分录时返回空列表。
*/
private List<ConvertEntryTable> resolveEntryTables(OnlFormHead destFormHead) {
List<ConvertEntryTable> 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<OnlineTable>()
.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。

2
common/common-botp/src/main/java/apelet/common/botp/service/impl/ConvertRuleServiceImpl.java

@ -543,6 +543,8 @@ public class ConvertRuleServiceImpl extends BaseService<ConvertRule,Long> implem @@ -543,6 +543,8 @@ public class ConvertRuleServiceImpl extends BaseService<ConvertRule,Long> implem
TransformResultDto dto = new TransformResultDto();
dto.setFormStatus(result.getFormStatus());
dto.setFormType(result.getFormType());
// 新旧接口保持一致:formVal 原样透出,老前端拿它打开目标编辑页。
dto.setFormVal(result.getFormVal());
if (result.getBills().isEmpty()) {
return dto;
}

4
common/common-botp/src/main/java/apelet/common/botp/util/BillDataConverter.java

@ -77,8 +77,10 @@ public class BillDataConverter { @@ -77,8 +77,10 @@ public class BillDataConverter {
*/
private List<Map<String, Object>> convertEntries(ObjectCollection objectCollection) {
List<Map<String, Object>> entryList = new ArrayList<>();
Map<String, Object> entryMap = new HashMap<>();
for (int i = 0; i < objectCollection.size(); i++) {
// 每条分录行必须独占一个 Map:放在循环外会让所有行共用一个实例,
// 结果就是后一行覆盖前一行,前端永远只看到一条明细
Map<String, Object> entryMap = new HashMap<>();
ObjectValue entryValue = objectCollection.getObject(i);
Iterator<?> entryIt = entryValue.getValues().keySet().iterator();
while (entryIt.hasNext()) {

Loading…
Cancel
Save