单据转换实现多单据体到目标单的携带和关联原创
金蝶云社区-eris
eris
50人赞赏了该文章 6,634次浏览 未经作者许可,禁止转载编辑于2022年04月27日 10:58:37
summary-icon摘要由AI智能服务提供

本文本描述了在单据转换场景下,通过特定插件实现两个单据体(单据A的体1和体2)同时携带数据到目标单据(单据B的体1)的详细过程。单据转换目前仅支持单据头、一个单据体及一个子单据体的直接携带,但可通过插件扩展实现更复杂的数据携带与关联。示例中,A单据体1和体2的数据被携带至B单据体1,并建立了相应的关联关系。通过注册插件干预转换过程,解决了系统默认无法将单据体数据直接携带至子单据体的限制。同时,还通过反写规则,实现了B单据体1数据对A单据体1和体2的反馈更新。插件源码部分展示了如何在转换插件中实现这些功能,包括检查关联配置、获取源单据体数据、为目标关联父实体赋值以及处理基础资料数据包的逻辑。

背景说明:

  1. 目前单据转换只支持单据头,一个单据体,一个子单据体的携带(可以配置多个子单据体携带,但只能携带一行)。

  2. 单据关联关系,源单可以是单据头,单据体,子单据体, 目标单据是关联配置中的单据实体(只能是单据头或单据体)

  3. 默认关联关系是:单据头--》单据头, 单据体--》单据体;可以使用插件干预使子单据体--》单据体,但无法干预为单据体--》子单据体,因为目标单只会用关联主实体去关联。

实现方式:

     方式1、 通过转换插件,取原单另外一个实体数据包,自行解析赋值到目标单据实体

     方式2、配置转换规则,调用下推转换,得到另外一个目标数据包,然后合并数据包


示例概况:

  1. 两个单据:单据A简称A,有单据体1,单据体2 ,子单据体1;  单据B简称B,有单据体1,单据体2 ,子单据体1,关联配置实体为单据体1

  2. 单据转换:A单据体1携带数据到B单据体1,并注册了单据A到单据B的转换插件

   3. 两个反写规则:B单据体1中的基本单位数量反写A单据体1的基本单位数量, B单据体1中的基本单位数量反写A单据体2的基本单位数量

   4. 目标:实现A单据体1和A单据体2同时携带数据到B单据体1,并分别创建关联关系。


示例过程说明:

  1.  A单据体1包含1条数据,A单据体2包含条数据

image.png

2. 单据A下推单据B,合并成3条分录。

image.png

3. 单据B保存,分别反写A单据体1基本单位数量,A单据体2基本单位单位数量。

image.png

4. 单据A下查也能查询到3条数据,故三条数据都创建了关联关系。

image.png


插件源码:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Kingdee.BOS;
using Kingdee.BOS.Core;
using Kingdee.BOS.DataEntity;
using Kingdee.BOS.Core.Bill;
using Kingdee.BOS.Contracts;
using Kingdee.BOS.ServiceHelper;
using Kingdee.BOS.Util;
using Kingdee.BOS.JSON;
using Kingdee.BOS.Orm.DataEntity;
using Kingdee.BOS.Core.DynamicForm;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.Metadata.FieldElement;
using Kingdee.BOS.Core.Metadata.FormElement;
using Kingdee.BOS.Core.Metadata.EntityElement;
using Kingdee.BOS.Core.Metadata.ConvertElement.PlugIn;
using Kingdee.BOS.Core.Metadata.ConvertElement.PlugIn.Args;
using Kingdee.BOS.Core.Const;
using Kingdee.BOS.Core.SqlBuilder;
using Kingdee.BOS.Orm.Metadata.DataEntity;
using Kingdee.BOS.BusinessEntity.BusinessFlow;
using Kingdee.BOS.Core.Metadata.ConvertElement;
 namespace Kingdee.BOS.TestPlugIn
{
    /// <summary>
    /// 实现两个单据体携带,并跟目标关联父实体创建关联数据包
    /// </summary>
    [HotUpdate]
    [Description("单据A到单据B转换插件")]
    public class BillAToBillBConvertPlugIn : AbstractConvertPlugIn
    {
        //关联数据包相关字段的说明和示例可以参考:https://vip.kingdee.com/article/158703
        // 关联实体,流程图
        private DynamicProperty _linkFlowIdProperty = null;
        // 关联实体,流程路线
        private DynamicProperty _linkFlowLineIdProperty = null;
        // 关联实体,转换规则
        private DynamicProperty _linkRuleIdProperty = null;
        // 关联实体,源单表格编码
        private DynamicProperty _linkSTableNameProperty = null;
        // 关联实体,源单单据内码
        private DynamicProperty _linkSBillIdProperty = null;
        // 关联实体,源单被关联实体内码,一般为分录内码
        private DynamicProperty _linkSIdProperty = null;
        //控制字段Key和属性集合
        private Dictionary<string, Tuple<DynamicProperty, DynamicProperty>> _dicProperys = null;
        public override void OnAfterCreateLink(CreateLinkEventArgs e)
        {
            base.OnAfterCreateLink(e);
            ///检查下游单据是否存在关联配置
            var targetLinkSet =e.TargetBusinessInfo.GetForm().LinkSet ;
            if (targetLinkSet == null || targetLinkSet.LinkEntitys == null || targetLinkSet.LinkEntitys.Count == 0)
            {
                return;
            }
            ///得到需要携带的第二个源单单据体数据
            Entity srcEntity2 = e.SourceBusinessInfo.GetEntity("FEntity2"); //第二个来源单单据实体
            var srcPkFieldName = e.SourceBusinessInfo.GetForm().PkFieldName; //来源单据主键字段名
            List<long> lstSrcPkValues = new List<long>();  //源单主键集合(单据内码)
            List<DynamicObject> lstSrcObjs = new List<DynamicObject>();//源单数据包集合
            var targetExDatas = e.TargetExtendedDataEntities.FindByEntityKey("FBillHead");  //目标单数据包集合
            targetExDatas.ToList().ForEach(x => lstSrcObjs.AddRange(x[BOSConst.ConvSourceExtKey] as List<DynamicObject>));
            lstSrcObjs.ForEach(x => lstSrcPkValues.Add(ObjectUtils.Object2Int64(x[srcPkFieldName])));
            //来源单单据体2数据包
            var srcEntity2DynObjs = this.GetEntity2DynamicObjs(lstSrcPkValues, e.SourceBusinessInfo, srcEntity2);
            if (srcEntity2DynObjs.Count == 0) return;
            ///把源单第二个单据体数据付给关联父实体
            var targetLinConfig = targetLinkSet.LinkEntitys[0]; //平台只支持一个关联实体,故取第一个关联设置就可以
            var targetLinkEntity = e.TargetBusinessInfo.GetEntity(targetLinConfig.Key); //关联实体
            var targetParentEntityKey = targetLinConfig.ParentEntityKey;  //目标单据关联父实体Key
            var targeLinktParentEntity = e.TargetBusinessInfo.GetEntity(targetParentEntityKey);  //目标单据关联父实体
            var srcEntity2TableDefine = this.GetTableDefine(e.SourceBusinessInfo.GetForm().Id, srcEntity2.Key); //第二个源单单据体表定义
            this.InitLinkFieldProperty(targetLinkEntity, targetLinConfig, e.TargetBusinessInfo); //初始化关联字段属性
            var convertRule = this.Option.GetVariableValue<ConvertRuleElement>("Rule"); //得到转换规则
            var tMaterialField = e.TargetBusinessInfo.GetField("FMaterial") as BaseDataField;
            var materialObjs = this.GetMaterialObjs(e.TargetBusinessInfo, tMaterialField, srcEntity2DynObjs);//基础资料数据包需要单独赋值,这里先记录相关信息
            //对目标单数据包进行循环,把源单第二个单据体数据付给目标关联父实体并创建关联数据包
            foreach (var exTargetData in targetExDatas)
            {
                List<long> currSrcPks = new List<long>(); //目标数据包对应的来源单据内码集合
                var targetBillObj = exTargetData.DataEntity;  //目标单整单数据包
                var currSrcObjs = exTargetData[BOSConst.ConvSourceExtKey] as List<DynamicObject>; //目标单对应的源单数据包集合
                currSrcObjs.ForEach(x => currSrcPks.Add(ObjectUtils.Object2Int64(x[srcPkFieldName])));
                //当前对应的第二个单据体数据包
                var currSrcEntity2Objs = srcEntity2DynObjs.Where(x => currSrcPks.Contains(ObjectUtils.Object2Int64(x[srcPkFieldName]))).ToList();
                if (currSrcEntity2Objs.Count == 0) continue;
                //这里目标单据的关联父实体数据报集合
                var targetLinkParentObjs = targeLinktParentEntity.DynamicProperty.GetValue(targetBillObj) as DynamicObjectCollection;
                //对来源单第二个单据体数据包进行循环
                foreach (var currSrcObj in currSrcEntity2Objs)
                {
                    var tObj = new DynamicObject(targeLinktParentEntity.DynamicObjectType);
                    var materialId = ObjectUtils.Object2Int64(currSrcObj["FEntry2Material"]);
                    tObj["Material_Id"] = materialId;//基础资料的赋值不仅仅要赋值Id 还要给引用数据报赋值
                    tObj[tMaterialField.PropertyName] = materialObjs.First(x => ObjectUtils.Object2Int64(x["Id"]) == materialId);
                    tObj["BaseQty"] = currSrcObj["FEntry2BaseQty"];
                    tObj["Entry1Note"] = currSrcObj["FEntry2Note"];
                    targetLinkParentObjs.Add(tObj);
                    //处理关联数据包
                    var linkObjs = targetLinkEntity.DynamicProperty.GetValueFast(tObj) as DynamicObjectCollection;
                    var targetLinkObj = new DynamicObject(targetLinkEntity.DynamicObjectType);
                    this._linkFlowIdProperty.SetValueFast(targetLinkObj, ""); // 业务流程图内码
                    this._linkFlowLineIdProperty.SetValueFast(targetLinkObj, 0);//流程路线
                    this._linkRuleIdProperty.SetValueFast(targetLinkObj, convertRule.Id); //转换规则
                    this._linkSTableNameProperty.SetValueFast(targetLinkObj, srcEntity2TableDefine.TableNumber);  // 来源单据体表编码
                    this._linkSBillIdProperty.SetValueFast(targetLinkObj, currSrcObj[srcPkFieldName]);                    //源单单据ID
                    this._linkSIdProperty.SetValueFast(targetLinkObj, currSrcObj[srcEntity2.Key + "_" + srcEntity2.EntryPkFieldName]); //源单第二个单据体内码
                    // 控制字段处理
                    foreach (var item in this._dicProperys)
                    {
                        item.Value.Item1.SetValue(targetLinkObj, currSrcObj[item.Key]);
                        item.Value.Item2.SetValue(targetLinkObj, currSrcObj[item.Key]);
                    }
                    linkObjs.Add(targetLinkObj);
                }
            }
        }
        /// <summary>
        /// 得到物料数据包
       /// </summary>
       /// <param name="tBInfo"></param>
       /// <param name="tMaterialField"></param>
       /// <param name="srcEntity2Objs"></param>
       /// <returns></returns>
        private DynamicObject[] GetMaterialObjs(BusinessInfo tBInfo,BaseDataField tMaterialField,  DynamicObjectCollection srcEntity2Objs)
        {
            var lstMaterialIds = srcEntity2Objs.Select(x => ObjectUtils.Object2Int64(x["FEntry2Material"])).ToList();
            QueryBuilderParemeter queryParam = new QueryBuilderParemeter();
            var tupleFilterInfo = this.GetFilter(tMaterialField.LookUpObject.PkFieldName, lstMaterialIds);
            queryParam.FormId = tMaterialField.LookUpObject.FormId;
            queryParam.FilterClauseWihtKey = tupleFilterInfo.Item1;
            queryParam.SqlParams.AddRange(tupleFilterInfo.Item2);
            DynamicObject[] dynamicObjs = BusinessDataServiceHelper.LoadFromCache(this.Context, tMaterialField.RefFormDynamicObjectType, queryParam);
            return dynamicObjs;
        }
       /// <summary>
        /// 初始化关联字段属性
        /// </summary>
        /// <param name="targetLinkEntity"></param>
        private void InitLinkFieldProperty(Entity targetLinkEntity, LinkEntity targetLinkEntitySet, BusinessInfo targetBInfo)
        {
            // 关联实体的字段属性
            DynamicObjectType linkEntityDT = targetLinkEntity.DynamicObjectType;
            this._linkFlowIdProperty = linkEntityDT.Properties["FlowId"];
            this._linkFlowLineIdProperty = linkEntityDT.Properties["FlowLineId"];
            this._linkRuleIdProperty = linkEntityDT.Properties["RuleId"];
            this._linkSTableNameProperty = linkEntityDT.Properties["STableName"];
            this._linkSBillIdProperty = linkEntityDT.Properties["SBillId"];
            this._linkSIdProperty = linkEntityDT.Properties["SId"];
            this._dicProperys = new Dictionary<string, Tuple<DynamicProperty, DynamicProperty>>();
            foreach (var key in targetLinkEntitySet.WriteBackFieldKeys)
            {
                if (!this._dicProperys.ContainsKey(key))
                {
                    Field wbField = targetBInfo.GetField(key);
                    var wbFieldProperty = linkEntityDT.Properties[wbField.PropertyName];
                    var wbFieldOldProperty = linkEntityDT.Properties[wbField.PropertyName + "Old"];
                    this._dicProperys[key] = Tuple.Create<DynamicProperty, DynamicProperty>(wbFieldProperty, wbFieldOldProperty);
                }
            }
        }
        /// <summary>
        /// 得到来源单单据体2数据包
        /// </summary>
        /// <param name="lstSrcPkValues"></param>
        /// <param name="srcBInfo"></param>
        /// <param name="srcEntity2"></param>
        /// <returns></returns>
        private DynamicObjectCollection GetEntity2DynamicObjs(List<long> lstSrcPkValues,BusinessInfo srcBInfo,Entity srcEntity2)
        {
            QueryBuilderParemeter queryParam = new QueryBuilderParemeter();
            queryParam.FormId = srcBInfo.GetForm().Id;
            List<SelectorItemInfo> lstItems = new List<SelectorItemInfo>();
            lstItems.Add(new SelectorItemInfo(srcBInfo.GetForm().PkFieldName)); //单据主键
            lstItems.Add(new SelectorItemInfo(srcEntity2.Key + "_" + srcEntity2.EntryPkFieldName)); //单据体2主键
            lstItems.Add(new SelectorItemInfo("FEntry2Material")); //单据体2中的物料2
            lstItems.Add(new SelectorItemInfo("FEntry2BaseQty")); //基本单位数量
            lstItems.Add(new SelectorItemInfo("FEntry2Note"));//备注
            queryParam.SelectItems = lstItems; //需要选择的字段列表集合
            var tupleFilterInfo = this.GetFilter(srcBInfo.GetForm().PkFieldName, lstSrcPkValues);
            queryParam.FilterClauseWihtKey = string.Format(" {0} and {1} != 0", tupleFilterInfo.Item1, srcEntity2.EntryPkFieldName);
            queryParam.SqlParams.AddRange(tupleFilterInfo.Item2);
            var dynObjs = QueryServiceHelper.GetDynamicObjectCollection(this.Context, queryParam);
            return dynObjs;
        }
        /// <summary>
        /// 得到过滤条件和参数
        /// </summary>
        /// <param name="pkFieldName"></param>
        /// <param name="entryPkName"></param>
        /// <param name="lstSrcPkValues"></param>
        /// <returns></returns>
        private Tuple<string, List<SqlParam>> GetFilter(string pkFieldName, List<long> lstSrcPkValues)
        {
            string filter = string.Empty;
            List<SqlParam> lstParams = new List<SqlParam>();
            var arrPkValues = lstSrcPkValues.Distinct().ToArray();
            if (arrPkValues.Length == 1)
            {
                filter = string.Format("{0} = {1} ", pkFieldName, arrPkValues[0]);
            }
            else if (arrPkValues.Length <= 50)
            {
                filter = string.Format("{0} in ({1}) ", pkFieldName, string.Join(",", arrPkValues));
            }
            else
            {
                var cardSql = StringUtils.GetSqlWithCardinality(arrPkValues.Length, "@PKValue", 1, false);
                filter = string.Format("  EXISTS ( {0} where b.FID = {1})  ", cardSql, pkFieldName);
                lstParams.Add(new SqlParam("@PKValue", KDDbType.udt_inttable, arrPkValues));
            }
            return Tuple.Create<string, List<SqlParam>>(filter, lstParams);
        }
      /// <summary>
        /// 得到表定义,关联节点中的表编码都是来自表定义,此中的表编码跟原数据中实体对应的表名正常情况下是一样的
        /// 但是如果实体表名修改过,并且表定义中已有过定义,那么表定义中的表编码是不会改变的。
        /// </summary>
        /// <param name="formId"></param>
        /// <param name="entityKey"></param>
        /// <returns></returns>
        private TableDefine GetTableDefine(string formId, string entityKey)
        {
            return BusinessFlowServiceHelper.LoadTableDefine(this.Context, formId, entityKey);
        }
    }
}



图标赞 50
50人点赞
还没有人点赞,快来当第一个点赞的人吧!
图标打赏
0人打赏
还没有人打赏,快来当第一个打赏的人吧!