手工修复与源单关联关系
需求背景
下游单据是手工录入、或者引入的,与源单没有关联关系,不支持联查、反写。
对于已经保存的下游单据,现在用户需要手工修复与源单的关联关系,以支持联查、反写。
本章介绍如何开发表单插件,根据下游单据上填写好的的源单编号、源单行序号,自动建立起与源单的关联关系。
案例设计
假设下游的单据头,有如下几个字段,在手工录单(或引入)时,由调用者填写好源单信息:
srcbilltype:单据头字段,基础资料字段类型,记录源单类型,如demo_bill1;
srcbillno:单据头字段,文本字段,记录源单单据编号,如”JD-001”;
srcentrykey:单据头字段,文本字段,记录源单单据体名称,如”entryentity”;
srcrowseq:单据体字段,整数类型,记录源单行序号,如1,2,3…
相关知识点
关联子实体:系统会在目标单的单据体下级,创建一个关联子表,记录源单类型、源单内码、分录行内码,保存时,会基于此建立起与源单的关联关系并反写。关联子实体,通常以单据体标识加上”_lk”后缀命名,又可称为lk子实体、lk表等。如果是单据头的下级关联子实体,标识固定为”billhead_lk”。
实体表格编码:也称为tableId,为了明确的追查到源单行,关联子实体中,需要记录源单类型、源单单据体名称。这两个值是文本类型,直接记录会占用比较多的空间。因此,平台特别为每一种单据、单据体,都分配一个唯一的长整型数字,作为表格编码,即tableId。并改造关联子实体,只记录一个长整数的tableid,不再记录源单类型、单据体名称,节省不少存储空间。联查时,会自动根据表格编码,反查到明确的单据类型及单据体。
TableDefine对象:平台封装的公共对象,包含了主实体编码、单据体名称和表格TableId之间的对应关系。可以通过公共服务接口(EntityMetadataCache.loadTableDefine方法),传入单据主实体编码+单据体名称获取,也可以传入tableId获取;通过这个对象,可以根据单据和单据体追查tableId,也可以根据tableId反查单据和单据体。
示例代码
案例实现思路::
开发单据的表单插件,派生自AbstractBillPlugIn;
重写itemClick方法,响应用户点击菜单事件,调用一个帮助类,修复关联关系;
封装一个独立的帮助类,根据传入的参数,修复下游单据与源单的关联关系,修复逻辑:
读取目标上引入的源单类型、源单编号字段值
根据源单编号,加载源单数据包,只需要加载源单内码、分录行内码、分录行序号;
对源单分录行进行逐行遍历,记录源单行号和源单内码、源单行内码之间的对应关系;
对目标单分录行进行逐行遍历,根据引入的源单行号,找到对应的源单内码、分录行内码;
把对应的源单内码、分录内码,填写在关联子实体中;
调用反写引擎,提交已经修复好的目标单数据包,执行关联关系创建与反写源单;
示例代码:
界面插件 – 响应菜单点击
如下示例代码,是一个界面的插件,响应菜单点击事件,调用修复关联关系的帮助类,传入修复参数:
package kd.bos.plugin.sample.bill.billconvert.bizcase;
import kd.bos.bill.AbstractBillPlugIn;
import kd.bos.dataentity.utils.StringUtils;
import kd.bos.form.control.events.ItemClickEvent;
public class AutoFixLinkFormPlugin extends AbstractBillPlugIn {
@Override
public void itemClick(ItemClickEvent evt) {
super.itemClick(evt);
if (StringUtils.equals(evt.getItemKey(), "btnfixlink")) {
AutoFixLinkSample fixLink = new AutoFixLinkSample();
fixLink.fixRowLink(
this.getView().getModel().getDataEntityType().getName(),
"entryentity",
"srcbilltype",
"srcbillno",
"srcentrykey",
"srcrowseq",
this.getModel().getDataEntity().getPkValue());
}
}
}
点击复制错误复制成功
帮助类 – 修复关联关系
如下示例代码,是一个专用类,根据传入的参数,修改下游单据的关联关系。
前提要求:下游单据上,必须要有源单类型、源单编号、源单单据体、源单单据体行号等四个字段,本示例代码需要根据这四个字段的值,定位源单,填写关联子实体行:
package kd.bos.plugin.sample.bill.billconvert.bizcase;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import kd.bos.dataentity.entity.DynamicObject;
import kd.bos.dataentity.entity.DynamicObjectCollection;
import kd.bos.dataentity.utils.StringUtils;
import kd.bos.entity.BillEntityType;
import kd.bos.entity.EntityMetadataCache;
import kd.bos.entity.botp.runtime.TableDefine;
import kd.bos.orm.query.QCP;
import kd.bos.orm.query.QFilter;
import kd.bos.servicehelper.BusinessDataServiceHelper;
import kd.bos.servicehelper.DispatchServiceHelper;
/**
* 示例:提供方法,自动创建(修复)与源单的关联关系,支持联查反写
*
* @author rd_johnnyding
*/
public class AutoFixLinkSample {
/**
* 手动修复下游单据与源单的关联关系
*
* @param targetEntityNumber 下游单据标识,如 demo_bill1
* @param targetEntryKey 下游单据的关联主单据体:下游单据可能有多个单据体,本函数只能修复其中一个单据体的关联关系
* @param srcBillTypeFld 下游单据头上,记录源单类型的字段名:据此确定每张单据的源单类型;实际应用中可以改为直接传入源单类型
* @param srcBillNoFld 下游单据头上,记录源单单据编号的字段名:据此确定每张单据的源单
* @param srcEntryFld 下游单据头上,记录源单单据体标识的字段名:据此确定源单关联主单据体;实际应用中可以改为直接传入源单单据体名
* @param srcRowSeqFld 下游单据体上,记录源单单据体行号的字段名:据此定位源单行
* @param targetBillId 下游单据内码集合
*
* @remark
* 1. 为了实现本函数的演示效果,需要在目标单上,建立有源单类型、源单编号、源单单据体名、源单行号等四个字段,以定位源单行;
* 实际应用中,可能源单类型、源单单据体名是固定的,不需要增加字段记录
* 2. 为了避免演示代码过于复杂,本函数只处理单张目标单;
* 实际应用中需要调整为支持批量处理
*/
public void fixRowLink(String targetEntityNumber,
String targetEntryKey,
String srcBillTypeFld, String srcBillNoFld,
String srcEntryFld, String srcRowSeqFld,
Object targetBillId) {
// TODO 参数检查:检查各参数是否传入了合法的值,本演示代码,各参数必填,不允许空
// 读取需修复的目标单
DynamicObject targetBillObj = BusinessDataServiceHelper.loadSingle(targetBillId, targetEntityNumber);
// 获取目标单单据体的实体表格定义:主实体 + 单据体 -> 对应一个唯一的实体表格定义
TableDefine targetTableDefine = EntityMetadataCache.loadTableDefine(targetEntityNumber, targetEntryKey);
// 获取源单单据体的表格定义:记录关联关系时,需要用到此对象中的tableId值,用一个长整数值唯一标识源单及单据体
TableDefine srcTableDefine = this.loadSrcTableDefine(targetBillObj, srcBillTypeFld, srcEntryFld);
String srcBillNo = targetBillObj.getString(srcBillNoFld);
// 根据源单编号,读取源单数据
DynamicObject[] sourceBillObjs = this.loadSourceBill(srcTableDefine, srcBillNo);
this.createLinkEntity(targetTableDefine, targetBillObj, srcTableDefine, sourceBillObjs, srcRowSeqFld);
// 调用目标单的保存操作,保存维护好的关联子实体行数据,并自动调用反写引擎,创建关联关系及反写:
SaveServiceHelper.saveOperate(
targetEntityNumber, // 目标单主实体编码
new DynamicObject[] {targetBillObj}, // 目标单数据包
OperateOption.create()); // 操作参数,可通过option传入各种自定义参数
}
/**
* 从目标单单据头字段中,获取源单类型、源单单据体字段值,据此访问数据库,获取源单单据体表格定义
*
* @param targetBillObj 目标单数据包
* @param srcBillTypeFld 目标单上,记录源单单据类型的字段名
* @param srcEntryFld 目标单上,记录源单单据体标识的字段名
*
* @return 关联的源单及单据体表格定义
*/
private TableDefine loadSrcTableDefine( DynamicObject targetBillObj, String srcBillTypeFld, String srcEntryFld) {
// 提取源单类型、源单单据编号字段值
Object srcBillTypeFldValue = targetBillObj.get(srcBillTypeFld);
String srcEntityNumber = "";
if (srcBillTypeFldValue instanceof DynamicObject) {
srcEntityNumber = (String)((DynamicObject)srcBillTypeFldValue).getPkValue();
}
else {
srcEntityNumber = (String)srcBillTypeFldValue;
}
String srcEntryKey = targetBillObj.getString(srcEntryFld);
if (StringUtils.isBlank(srcEntityNumber)
|| StringUtils.isBlank(srcEntryKey)) {
return null;
}
// 获取源单单据体的表格编码:记录关联关系时,需要用此编码,标识源单及单据体
TableDefine srcTableDefine = EntityMetadataCache.loadTableDefine(srcEntityNumber, srcEntryKey);
return srcTableDefine;
}
/**
* 从目标单中提取源单编号,读取出源单数据包
*
* @param srcTableDefine 源单单据体实体表格定义:对象中包含了单据主实体标识、单据体标识、单据体tableId等内容
* @param srcBillNo 源单单据编号
*
* @return 源单数据包,仅包含源单内码、源单编号、源单行内码、行号
*/
private DynamicObject[] loadSourceBill(TableDefine srcTableDefine, String srcBillNo) {
BillEntityType sourceMainType = (BillEntityType)EntityMetadataCache.getDataEntityType(srcTableDefine.getEntityNumber());
// 读取源单:仅读取需要用到的字段:源单内码、源单单据编号、源单行内码、源单行号
Set<String> selectFields = new HashSet<>();
selectFields.add("id");
selectFields.add(sourceMainType.getBillNo());
selectFields.add(srcTableDefine.getEntityKey() + ".id");
selectFields.add(srcTableDefine.getEntityKey() + ".seq");
QFilter filter = new QFilter(sourceMainType.getBillNo(), QCP.equals, srcBillNo);
DynamicObject[] sourceBillObjs = BusinessDataServiceHelper.load(
srcTableDefine.getEntityNumber(),
StringUtils.join(selectFields.toArray(), ","),
new QFilter[] {filter});
return sourceBillObjs;
}
/**
* 修改目标单,为每行单据体,添加关联子实体数据行,记录关联的源单行内码
*
* @param targetTableDefine 目标单单据体表格定义:包含了目标单标识、单据体标识
* @param targetBillObj 目标单
* @param srcTableDefine 源单单据体表格定义:包含了源单标识、单据体标识、tableid
* @param sourceBillObjs 源单
* @param srcRowSeqFld 目标单单据体上,记录源单行号的字段名:需要基于此字段值定位源单行
*
* @remark
* 根据目标单分录行上,记录的源单行号字段值,匹配找到源单行,把源单行内码,记录在目标单分录行上
*/
private void createLinkEntity(
TableDefine targetTableDefine, DynamicObject targetBillObj,
TableDefine srcTableDefine, DynamicObject[] sourceBillObjs,
String srcRowSeqFld) {
// 循环分析源单行,提取源单行号、源单内码、分录行内码的关系,放在字典中备用: key = 源单行号; value = [源单内码、分录行内码]
Map<Integer, Object[]> srcRowIds = new HashMap<>();
for(DynamicObject srcObj : sourceBillObjs) {
DynamicObjectCollection srcRows = srcObj.getDynamicObjectCollection(srcTableDefine.getEntityKey());
for (DynamicObject srcRow : srcRows) {
Integer srcRowSeq = srcRow.getInt("seq");
Object[] srcRowId = new Object[] {srcObj.getPkValue(), srcRow.getPkValue()};
srcRowIds.put(srcRowSeq, srcRowId);
}
}
Long srcTableId = srcTableDefine.getTableId();
// 拼接处关联子实体标识:如果是单据头下的lk子表,固定使用billhead_lk;如果是单据体下的lk子表,用单据体标识+lk
String lkEntryKey = targetTableDefine.getEntityKey() + "_lk";
// 循环分析下游单的单据体行,逐一建立起与源单行的关联关系
DynamicObjectCollection targetRows = targetBillObj.getDynamicObjectCollection(targetTableDefine.getEntityKey());
for(DynamicObject targetRow : targetRows) {
// 获取下级_lk子实体行
DynamicObjectCollection linkRows = targetRow.getDynamicObjectCollection(lkEntryKey);
if (!linkRows.isEmpty()) {
// 已经有关联源单,无需再重建
continue;
}
// 寻找匹配的源单行:提取本行的匹配字段值,和源单行进行比对
Integer srcRowSeq = targetRow.getInt(srcRowSeqFld);
if (srcRowIds.containsKey(srcRowSeq)) {
// 找到了匹配的行,创建一条_lk子实体上数据,记录源单内码
DynamicObject linkRow = new DynamicObject(linkRows.getDynamicObjectType());
linkRows.add(linkRow);
// 在lk行中,记录源单分录表格编码、源单内码、源单分录内码
Object[] srcRowId = srcRowIds.get(srcRowSeq);
linkRow.set(lkEntryKey + "_stableid", srcTableId); // 源单分录表格编码:以此标识源单类型及单据体
linkRow.set(lkEntryKey + "_sbillid", srcRowId[0]); // 源单内码
linkRow.set(lkEntryKey + "_sid", srcRowId[1]); // 源单分录行内码
}
}
}
}
手工修复与源单关联关系
本文2024-09-23 00:25:57发表“云苍穹知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-cangqiong-139427.html