本文本描述了在单据转换场景下,通过特定插件实现两个单据体(单据A的体1和体2)同时携带数据到目标单据(单据B的体1)的详细过程。单据转换目前仅支持单据头、一个单据体及一个子单据体的直接携带,但可通过插件扩展实现更复杂的数据携带与关联。示例中,A单据体1和体2的数据被携带至B单据体1,并建立了相应的关联关系。通过注册插件干预转换过程,解决了系统默认无法将单据体数据直接携带至子单据体的限制。同时,还通过反写规则,实现了B单据体1数据对A单据体1和体2的反馈更新。插件源码部分展示了如何在转换插件中实现这些功能,包括检查关联配置、获取源单据体数据、为目标关联父实体赋值以及处理基础资料数据包的逻辑。
背景说明:
目前单据转换只支持单据头,一个单据体,一个子单据体的携带(可以配置多个子单据体携带,但只能携带一行)。
单据关联关系,源单可以是单据头,单据体,子单据体, 目标单据是关联配置中的单据实体(只能是单据头或单据体)
默认关联关系是:单据头--》单据头, 单据体--》单据体;可以使用插件干预使子单据体--》单据体,但无法干预为单据体--》子单据体,因为目标单只会用关联主实体去关联。
实现方式:
方式1、 通过转换插件,取原单另外一个实体数据包,自行解析赋值到目标单据实体
方式2、配置转换规则,调用下推转换,得到另外一个目标数据包,然后合并数据包
示例概况:
两个单据:单据A简称A,有单据体1,单据体2 ,子单据体1; 单据B简称B,有单据体1,单据体2 ,子单据体1,关联配置实体为单据体1
单据转换:A单据体1携带数据到B单据体1,并注册了单据A到单据B的转换插件
3. 两个反写规则:B单据体1中的基本单位数量反写A单据体1的基本单位数量, B单据体1中的基本单位数量反写A单据体2的基本单位数量
4. 目标:实现A单据体1和A单据体2同时携带数据到B单据体1,并分别创建关联关系。
示例过程说明:
A单据体1包含1条数据,A单据体2包含条数据
2. 单据A下推单据B,合并成3条分录。
3. 单据B保存,分别反写A单据体1基本单位数量,A单据体2基本单位单位数量。
4. 单据A下查也能查询到3条数据,故三条数据都创建了关联关系。
插件源码:
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); } } }
推荐阅读