手工修复与源单关联关系

栏目:云苍穹知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:1

手工修复与源单关联关系

需求背景

下游单据是手工录入、或者引入的,与源单没有关联关系,不支持联查、反写。


对于已经保存的下游单据,现在用户需要手工修复与源单的关联关系,以支持联查、反写。


本章介绍如何开发表单插件,根据下游单据上填写好的的源单编号、源单行序号,自动建立起与源单的关联关系。


案例设计

假设下游的单据头,有如下几个字段,在手工录单(或引入)时,由调用者填写好源单信息:


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]);            // 源单分录行内码    

            }

        }

    }

}


手工修复与源单关联关系

需求背景下游单据是手工录入、或者引入的,与源单没有关联关系,不支持联查、反写。对于已经保存的下游单据,现在用户需要手工修复与源单的...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息