需求背景
业务代码在对下游单据进行处理时,常常需要找到源头某单,并明确下游单据行与源单行之间的对应关系,根据行对应关系进行相关逻辑处理,如检查、或更新源单分录行字段值。
正常情况下,反写源单可以通过配置反写规则实现,不需要自行开发插件。
本章介绍如何使用平台提供的服务接口,搜索源单,并明确下游单据分录行和源单分录行之间的对应关系。
案例设计
假设源头核心单据是demo_botp1,当前处理的下游单据是demo_botp3:
demo_botp1 下推生成 demo_botp2;
demo_botp2 接着下推生成 demo_botp3;
当前正在审核demo_botp3,需要找到源头的demo_botp1,并确定好行之间的对应关系;
相关知识点
TableDefine:单据实体表格定义对象,记录单据、单据体分配的表格编码(即tableId),也可以使用tableId反查到是什么单据的什么单据体;
RowId对象:BOTP引擎中,使用RowId对象,包装单据主表编码、单据内码、单据体表格编码、单据体内码,可以通过RowId的属性值,明确的知道其对应什么单据,那个单据体的那一行数据。
BFRowLinkUpNode:行关系追溯树,以下游单据单据体行作为根节点,逐层往上记录其直接源单行。通过逐层往上的方式,找到一条分录行全链条的所有源单行,包括直接源单、跨级源单;
示例代码
案例实现思路:
开发单据demo_botp3的审核操作插件,派生自AbstractOperationServicePlugIn;
重写beforeExecuteOperationTransaction方法,此方法在操作校验通过,开启事务保护前触发,可以在此事件中,检查、整理数据;
调用BFTrackerServiceHelper服务方法,传入本单主实体编码和单据内码,搜索本单分录行的源单行追溯树;
从源单行追溯树上,寻找指定的源单行,与当前行关联起来,存储在字典中;
package kd.bos.plugin.sample.bill.billconvert.bizcase; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import kd.bos.entity.EntityMetadataCache; import kd.bos.entity.ExtendedDataEntity; import kd.bos.entity.botp.runtime.BFRowLinkUpNode; import kd.bos.entity.botp.runtime.TableDefine; import kd.bos.entity.plugin.AbstractOperationServicePlugIn; import kd.bos.entity.plugin.args.BeforeOperationArgs; import kd.bos.servicehelper.botp.BFTrackerServiceHelper; /** * 示例:在单据审核时,搜索源头单据,并明确行之间的对应关系 * @author rd_johnnyding * */ public class FindSourceRowOpPlugin extends AbstractOperationServicePlugIn { /** * 开启事务保护前,触发本事件:对数据进行检查、整理 */ @Override public void beforeExecuteOperationTransaction(BeforeOperationArgs e) { if (e.getValidExtDataEntities().isEmpty()) { return; } // 取出源单主表的表格定义、源单单据体的表格定义 String botpbill1_EntityNumber = "demo_botp1"; String botpbill1_EntryKey = "entryentity"; TableDefine srcMainTable = EntityMetadataCache.loadTableDefine(botpbill1_EntityNumber, botpbill1_EntityNumber); TableDefine srcEntryTable = EntityMetadataCache.loadTableDefine(botpbill1_EntityNumber, botpbill1_EntryKey); // 获取当前单据(下游单据)的主实体编码、单据内码 String targetEntityNumber = this.billEntityType.getName(); String targetEntryKey = "entryentity"; TableDefine targetEntryTable = EntityMetadataCache.loadTableDefine(targetEntityNumber, targetEntryKey); Set<Object> billIds = new HashSet<>(); for(ExtendedDataEntity dataEntity : e.getValidExtDataEntities()) { billIds.add(dataEntity.getBillPkId()); } // 调用平台的服务,获取源单行追溯树:可以从追溯树上,追查每条分录行的来历 List<BFRowLinkUpNode> linkUpNodes = BFTrackerServiceHelper.loadLinkUpNodes( targetEntityNumber, // 下游单据主实体编码 "", // 可选参数,如果需要按分录行追溯,此参数传入单据体名称,否则按整单追溯 billIds.toArray(new Long[0])); // 如果按分录行追溯,此参数传入分录行内码;按整单追溯,此参数传入单据内码 // 定义一个字典,记录下游单据行,和源单行之间的对应关系:每行可能对应多行源单,多对一合并生成 Map<Long, Set<Long>> rowId_SrcRowIds = new HashMap<Long, Set<Long>>(); // 对读取出行追溯树,逐条分析,找出其对应的源单行 for(BFRowLinkUpNode linkUpNode : linkUpNodes) { if (Long.compare(targetEntryTable.getTableId(), linkUpNode.getRowId().getTableId()) != 0) { // 其他单据体行,略过不处理 continue; } // 获取下游单据体行内码 Long targetRowId = linkUpNode.getRowId().getEntryId(); // 把下游单据体行,添加进字典 if (!rowId_SrcRowIds.containsKey(targetRowId)) { rowId_SrcRowIds.put(targetRowId, new HashSet<>()); } // 传入需要查找的源单主表的tableId,搜索出源头上所有主表tableId符合的节点 List<BFRowLinkUpNode> srcRowNodes = linkUpNode.findSourceNodes(srcMainTable.getTableId()); // 把各源单行添加到字典中,使用目标单行内码作为key检索 for(BFRowLinkUpNode srcRowNode : srcRowNodes) { if (Long.compare(srcEntryTable.getTableId(), srcRowNode.getRowId().getTableId()) != 0) { // 是源单其他单据体行,略过不处理 continue; } Long srcRowId = srcRowNode.getRowId().getEntryId(); rowId_SrcRowIds.get(targetRowId).add(srcRowId); } } if (!rowId_SrcRowIds.isEmpty()) { // TODO 至此,rowId_SrcRowIds中,已经记录了每行对应的源单行内码,可以进行后续处理 } } }
推荐阅读