
【场景】一般的业务单据指定了link实体,如果下推的时候跟link无关,则无法关联。
针对这种场景目前标准产品无法实现
【案例】采购申请单下推采购订单,而后采购订单通过选单建立自定义关系
<0>数据准备
已经跟采购申请单关联的采购订单

<1>第二个关系建立准备



<2>支持选单选择其他单据填入到另外的实体;但是不会关联(仅携带)

<3>通过操作服务插件保存时,保存自定义关联
```csharp
using Kingdee.BOS;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.ServiceHelper;
using System;
using System.Collections.Generic;
namespace DynamicFormPlugIn.BillConvert
{
[Kingdee.BOS.Util.HotUpdate]
[System.ComponentModel.Description("手动创建关联")]
public class CreateBusinessFlowNodePlugIn : AbstractOperationServicePlugIn
{
public class KeyConst
{
public static string SrcForm = "k85eec12fa5f24be7800779a5cfa22efd";
public static string TgtForm = "PUR_PurchaseOrder";
public static string Field_SBillId = "F_BHR_Integer";
public static string Entity = "F_BHR_Entity";
}
public override void OnPreparePropertys(PreparePropertysEventArgs e)
{
e.FieldKeys.Add(KeyConst.Field_SBillId);
}
/*
* 手动创建关联关系
* 操作逻辑:每次比对历史数据和本次修改后数据包的关联记录
* 历史的没有则为新增,数据包没有的则为删除
*
* 注意:由于业务流程有归档,因此本插件仅建议尽可能在归档业务前完成此逻辑,
* 获取历史数据比对支持归档查询:https://vip.kingdee.com/article/71661049120113152?productLineId=1
* 删除不支持归档删除,仅支持在当前表删除;一般的保存操作会将归档数据激活还原到当前表,但是部分场景不会
* 因此如果需要长时间后还需要调整关联关系的话,只能将业务流程的归档时间调大
*/
public override void EndOperationTransaction(EndOperationTransactionArgs e)
{
const string HeadKey = "FBillHead";
var srcTableDefine = BusinessFlowServiceHelper.LoadTableDefine(Context, KeyConst.SrcForm, HeadKey, true);
var tgtTableDefine = BusinessFlowServiceHelper.LoadTableDefine(Context, KeyConst.TgtForm, HeadKey, true);
Kingdee.BOS.Core.Metadata.EntityElement.Entity entity = this.BusinessInfo.GetEntity(KeyConst.Entity);
Kingdee.BOS.Core.Metadata.FieldElement.Field field = this.BusinessInfo.GetField(KeyConst.Field_SBillId);
foreach(var dataObj in e.DataEntitys)
{
int billId = Kingdee.BOS.Util.ObjectUtils.Object2Int(dataObj[0]);
var entityRows = dataObj[entity.EntryName] as Kingdee.BOS.Orm.DataEntity.DynamicObjectCollection;
if (entityRows.Count == 0)
continue;
Dictionary<int, List<string>> linkBillIds = SelectAllLinkNodes(srcTableDefine, tgtTableDefine, billId);
HashSet<int> curLinkIds = new HashSet<int>();
foreach (var entityRow in entityRows)
{
int srcBillId = Kingdee.BOS.Util.ObjectUtils.Object2Int(entityRow[field.PropertyName]);
curLinkIds.Add(srcBillId);
if(linkBillIds.ContainsKey(srcBillId))
{
continue;
}
CreateNode(srcTableDefine, srcBillId, tgtTableDefine, billId);
}
foreach(var hisBillId in linkBillIds)
{
if(curLinkIds.Contains(hisBillId.Key))
{
continue;
}
foreach(var instId in hisBillId.Value)
{
DeleteNode(instId);
}
}
}
}
/// <summary>
/// 创建关联节点
/// </summary>
/// <param name="srcForm"></param>
/// <param name="srcBillId"></param>
/// <param name="tgtForm"></param>
/// <param name="tgtBillId"></param>
private void CreateNode(Kingdee.BOS.BusinessEntity.BusinessFlow.TableDefine srcTd, int srcBillId,
Kingdee.BOS.BusinessEntity.BusinessFlow.TableDefine tgtTd, int tgtBillId)
{
/*
* 创建关联逻辑
* https://vip.kingdee.com/article/162633?productLineId=1
*/
string instId = Guid.NewGuid().ToString();
string rootId = Guid.NewGuid().ToString();
string nodeId = Guid.NewGuid().ToString();
Kingdee.BOS.App.Data.SqlObject sqlInst, sqlRoot, sqlNode;
List<Kingdee.BOS.App.Data.SqlObject> sqlObjList = new List<Kingdee.BOS.App.Data.SqlObject>();
{
string strCreateInst = string.Format(@"INSERT INTO T_BF_INSTANCE
(FINSTANCEID,FFLOWID,FSOURCEID,FMASTERID,FSTATUS,FFIRSTFORMID,FFIRSTBILLID,FFIRSTBILLNO,FSTARTTIME) VALUES
(@InstId, '','',@InstId,'A',@SrcForm, @SrcBillId,'',GETDATE())");
List<SqlParam> paramInst = new List<SqlParam>();
paramInst.Add(new SqlParam("@InstId", KDDbType.AnsiString, instId));
paramInst.Add(new SqlParam("@SrcForm", KDDbType.AnsiString, srcTd.TableNumber));
paramInst.Add(new SqlParam("@SrcBillId", KDDbType.Int32, srcBillId));
sqlInst = new Kingdee.BOS.App.Data.SqlObject(strCreateInst, paramInst);
sqlObjList.Add(sqlInst);
}
{
string strCreateRoot = string.Format(@"INSERT INTO T_BF_INSTANCEENTRY
(FROUTEID, FINSTANCEID, FLINEID, FSTABLENAME, FSID, FTTABLENAME, FTID, FFIRSTNODE, FCREATETIME) VALUES
(@RouteId, @InstId, '', '', '', @SrcForm, @SrcBillId, 1, GETDATE())");
List<SqlParam> paramRoot = new List<SqlParam>();
paramRoot.Add(new SqlParam("@RouteId", KDDbType.AnsiString, rootId));
paramRoot.Add(new SqlParam("@InstId", KDDbType.AnsiString, instId));
paramRoot.Add(new SqlParam("@SrcForm", KDDbType.AnsiString, srcTd.TableNumber));
paramRoot.Add(new SqlParam("@SrcBillId", KDDbType.Int32, srcBillId));
sqlRoot = new Kingdee.BOS.App.Data.SqlObject(strCreateRoot, paramRoot);
sqlObjList.Add(sqlRoot);
}
{
string strCreateNode = string.Format(@"INSERT INTO T_BF_INSTANCEENTRY
(FROUTEID, FINSTANCEID, FLINEID, FSTABLENAME, FSID, FTTABLENAME, FTID, FFIRSTNODE, FCREATETIME) VALUES
(@RouteId, @InstId, '', @SrcForm, @SrcBillId, @TgtForm, @TgtBillId, 0, GETDATE())");
List<SqlParam> paramRoot = new List<SqlParam>();
paramRoot.Add(new SqlParam("@RouteId", KDDbType.AnsiString, nodeId));
paramRoot.Add(new SqlParam("@InstId", KDDbType.AnsiString, instId));
paramRoot.Add(new SqlParam("@SrcForm", KDDbType.AnsiString, srcTd.TableNumber));
paramRoot.Add(new SqlParam("@SrcBillId", KDDbType.Int32, srcBillId));
paramRoot.Add(new SqlParam("@TgtForm", KDDbType.AnsiString, tgtTd.TableNumber));
paramRoot.Add(new SqlParam("@TgtBillId", KDDbType.Int32, tgtBillId));
sqlNode = new Kingdee.BOS.App.Data.SqlObject(strCreateNode, paramRoot);
sqlObjList.Add(sqlNode);
}
Kingdee.BOS.App.Data.DBUtils.ExecuteBatch(Context, sqlObjList);
}
/// <summary>
/// 查询所有关联节点
/// </summary>
/// <param name="srcTd"></param>
/// <param name="tgtTd"></param>
/// <param name="tgtBillId"></param>
/// <returns></returns>
private Dictionary<int, List<string>> SelectAllLinkNodes(Kingdee.BOS.BusinessEntity.BusinessFlow.TableDefine srcTd,
Kingdee.BOS.BusinessEntity.BusinessFlow.TableDefine tgtTd, int tgtBillId)
{
Dictionary<int, List<string>> srcBillIds = new Dictionary<int, List<string>>();
string strSelectNodes = "SELECT FSID,FINSTANCEID FROM T_BF_INSTANCEENTRY WHERE FSTABLENAME = @FSTABLENAME AND FTTABLENAME = @FTTABLENAME AND FTID = @FTID";
List<SqlParam> paramLists = new List<SqlParam>();
paramLists.Add(new SqlParam("@FSTABLENAME", KDDbType.AnsiString, srcTd.TableNumber));
paramLists.Add(new SqlParam("@FTTABLENAME", KDDbType.AnsiString, tgtTd.TableNumber));
paramLists.Add(new SqlParam("@FTID", KDDbType.Int32, tgtBillId));
using (var dr = Kingdee.BOS.App.Data.DBUtils.ExecuteReader(this.Context, strSelectNodes, paramLists))
{
while(dr.Read())
{
int sid = Kingdee.BOS.Util.ObjectUtils.Object2Int(dr["FSID"]);
string instId = Kingdee.BOS.Util.ObjectUtils.Object2String(dr["FINSTANCEID"]);
if (srcBillIds.ContainsKey(sid))
{
srcBillIds[sid].Add(instId);
}
else
{
srcBillIds.Add(sid, new List<string>() { instId });
}
}
}
return srcBillIds;
}
/// <summary>
/// 仅删除当前表的关联节点
/// 注意:由于这里使用的是自创建的流程,那么就直接根据实例ID整个删除;
/// 如果存在往历史流程插入节点的,则应该按照示例ID加上节点ID删除
/// 注意2:由于是整个流程删除,一般的对应也不应该存在下游节点(而且就算下游还存在,删除上游的关联关系依然流程图是有问题的,根节点系统不会重置)
/// </summary>
private void DeleteNode(string instanceId)
{
string strDeleInst = "DELETE T_BF_INSTANCE WHERE FINSTANCEID = @FINSTANCEID ";
List<SqlParam> paramInst = new List<SqlParam>();
paramInst.Add(new SqlParam("@FINSTANCEID", KDDbType.AnsiString, instanceId));
var sqlInst = new Kingdee.BOS.App.Data.SqlObject(strDeleInst, paramInst);
string strDeleNode = "DELETE T_BF_INSTANCEENTRY WHERE FINSTANCEID = @FINSTANCEID ";
List<SqlParam> paramNode = new List<SqlParam>();
paramNode.Add(new SqlParam("@FINSTANCEID", KDDbType.AnsiString, instanceId));
var sqlNode = new Kingdee.BOS.App.Data.SqlObject(strDeleNode, paramNode);
List<Kingdee.BOS.App.Data.SqlObject> sqlObjList = new List<Kingdee.BOS.App.Data.SqlObject>()
{
sqlInst, sqlNode
};
Kingdee.BOS.App.Data.DBUtils.ExecuteBatch(Context, sqlObjList);
}
}
}
```
【效果】
手动关联,删除关联正常

【备注】开发方案的缺陷代码已注明