
【场景】采购订单下推采购入库单,采购订单下推付款申请单
采购订单的全流程图

但是采购入库单看不到应付单,应付单看不到采购入库单
【原理】在系统的理解中,子单据只能看到当前对象的关联流程,比如看到以这个单下推的后续节点,以及跟这个单关联的上游;对于上游还做了什么其他业务是会被屏蔽调的;
如上图案例:采购订单推了入库单给库存模块的业务员,推了应付单给另一个模块的业务员;
本质上库存模块的业务员是不能关注应付单的信息(避免越权之类的场景)
目前系统上就是这么设计的;
【案例】目前仅二开实现在采购入库单,全流程跟踪能够查看应付单
实现方案:在采购入库单中,打开采购订单的全流程跟踪图;
核心逻辑
<1>找到需要打开流程图的源单内码和源单标识
<2>打开流程图
```csharp
using Kingdee.BOS;
using Kingdee.BOS.Business.Bill.Operation;
using Kingdee.BOS.BusinessEntity.BillTrack;
using Kingdee.BOS.BusinessEntity.BusinessFlow;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.Bill.PlugIn;
using Kingdee.BOS.Core.BusinessFlow.ServiceArgs;
using Kingdee.BOS.Core.DynamicForm;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.Core.Metadata.EntityElement;
using Kingdee.BOS.Core.SqlBuilder;
using Kingdee.BOS.Orm.DataEntity;
using Kingdee.BOS.ServiceHelper;
using Kingdee.BOS.Util;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Tmp.BusinessFlow.Tool.Operation
{
[Kingdee.BOS.Util.HotUpdate]
public class TrackAllWithSpecBill : AbstractBillPlugIn
{
/*
* 根据当前操作的单据内码,找到目标单的单据内码
*/
public Context Context
{
get { return this.View.Context; }
}
/// <summary>
/// 重写查看流程图事件
/// </summary>
/// <returns></returns>
public override void BarItemClick(BarItemClickEventArgs e)
{
if(!e.BarItemKey.EqualsIgnoreCase("tb_ShowSpecBillTrack"))
{
return;
}
/*
* 根据当前单据内码,切换到目标
* <1>直接源单,根据link直接获取
* <2>跨级源单,通过下推携带到目标单;或者通过加载全流程实例获取
* https://vip.kingdee.com/article/71661049120113152?productLineId=1
* 演示 单据界面,根据link获取上有单据加载源单内码
*/
if (this.View == null || this.View.Model.DataObject == null)
return;
var billBusinessInfo = this.View.BillBusinessInfo;
DynamicObject billObj = this.View.Model.DataObject;
//<1>根据link计算源单内码和源单标识
if(billBusinessInfo.GetForm().LinkSet.LinkEntitys== null || billBusinessInfo.GetForm().LinkSet.LinkEntitys.Count <=0)
{
return;
}
var linkSetEntity = this.View.BillBusinessInfo.GetForm().LinkSet.LinkEntitys[0];
var linkEntity = billBusinessInfo.GetEntity(linkSetEntity.Key);
var entity = billBusinessInfo.GetEntity(linkSetEntity.ParentEntityKey);
List<DynamicObject> entityObjs = new List<DynamicObject>();
if(entity is HeadEntity)
{
entityObjs.Add(billObj);
}
else if(entity is EntryEntity)
{
DynamicObjectCollection entityObjColl = billObj[entity.EntryName] as DynamicObjectCollection;
if (entityObjColl == null)
return;
foreach(var entityRowObj in entityObjColl)
{
entityObjs.Add(entityRowObj);
}
}
if (entityObjs.Count <= 0)
return;
string stableId = null;
List<long> billIds = new List<long>();
foreach(var entityRow in entityObjs)
{
DynamicObjectCollection linkObjColl = entityRow[linkEntity.EntryName] as DynamicObjectCollection;
if (linkObjColl == null || linkObjColl.Count <= 0)
continue;
foreach(var linkRow in linkObjColl)
{
string sTableName = linkRow["STableName"].ToString();
if(stableId != null && sTableName != stableId)
{
continue;
}
stableId = sTableName;
long sBillId = ObjectUtils.Object2Int64(linkRow["SBillId"]);
billIds.Add(sBillId);
}
}
//static TableDefine LoadTableDefine(Context ctx, string tableNumber)
var td = BusinessFlowServiceHelper.LoadTableDefine(this.View.Context, stableId);
string formId = td.FormId;
//<2>显示业务流程图
var viewParameter = GetBillFlows(formId, billIds);
if (viewParameter == null || viewParameter.Instances.Count == 0)
{
return;
}
this.ShowBusinessFlow(viewParameter);
}
/// <summary>
/// 计算流程图
/// </summary>
/// <param name="formId"></param>
/// <param name="billIds"></param>
/// <returns></returns>
private ViewBusinessFlowParameter GetBillFlows(string formId, List<long> billIds)
{
if (formId == null || billIds == null || billIds.Count <= 0)
{
return null;
}
//目标单元数据
FormMetadata metaData = MetaDataServiceHelper.Load(this.Context, formId) as FormMetadata;
TrackAllHelper helper = new TrackAllHelper(this.Context, metaData.BusinessInfo);
var result = helper.LoadBillView(billIds);
return result;
}
private void ShowBusinessFlow(ViewBusinessFlowParameter viewParameter)
{
// 交换区数据
this.View.Session[FormConst.ConvParamKey_ViewBusinessFlowParameter] = viewParameter;
this.View.Session["FormOperation"] = null;
DynamicFormShowParameter param = new DynamicFormShowParameter();
param.FormId = FormIdConst.BOS_TrackResultForm;
param.ParentPageId = this.View.PageId;
param.OpenStyle.ShowType = TrackHelper.GetShowType(this.View);
this.View.ShowForm(param);
}
}
/// <summary>
/// 全流程跟踪帮助类
/// 把此抽出来作为公有方法,供移动,和全流程跟踪刷新使用
/// </summary>
public class TrackAllHelper
{
private BusinessInfo BillBusinessInfo;
private Context Context;
public TrackAllHelper(Context ctx, BusinessInfo businessInfo)
{
Context = ctx;
BillBusinessInfo = businessInfo;
}
/// <summary>
/// 加载单据内码关联的所有分录内码
/// </summary>
/// <param name="billIds"></param>
/// <returns></returns>
private Dictionary<string, List<Tuple<long, long>>> LoadBillIds(List<long> billIds)
{
billIds = billIds.Distinct().ToList();
Dictionary<string, List<Tuple<long, long>>> allEntityIds = new Dictionary<string, List<Tuple<long, long>>>();
//记录实体 单据头内码
List<Tuple<long, long>> billIdTuples = new List<Tuple<long, long>>();
foreach (var billId in billIds)
{
billIdTuples.Add(new Tuple<long, long>(billId, billId));
}
Entity headEntity = BillBusinessInfo.Entrys.FirstOrDefault((entity) => entity is HeadEntity);
allEntityIds.Add(headEntity.Key.ToUpperInvariant(), billIdTuples);
var pkArray = billIds.Select(x => (object)x).ToArray();
DynamicObject[] billObjs = BusinessDataServiceHelper.Load(Context, pkArray, BillBusinessInfo.GetDynamicObjectType());
if (billObjs == null || billObjs.Length <= 0)
return allEntityIds;
//记录实体 单据体内码
foreach (var entity in BillBusinessInfo.Entrys)
{
//仅支持单据体
if (entity is HeadEntity || entity is SubHeadEntity || entity is SubEntryEntity)
continue;
string strEntityPropertyName = entity.DynamicProperty.Name;
var entryIdTuples = new List<Tuple<long, long>>();
foreach (var billObj in billObjs)
{
DynamicObjectCollection objCollections = billObj[strEntityPropertyName] as DynamicObjectCollection;
if (objCollections == null || objCollections.Count <= 0)
continue;
foreach (DynamicObject row in objCollections)
{
object id = row["Id"];
if (id == null)
{
continue;
}
long entryId = Convert.ToInt64(id);
if (entryId == 0)
{
continue;
}
entryIdTuples.Add(new Tuple<long, long>(Convert.ToInt64(billObj["Id"]), entryId));
}
}
allEntityIds.Add(entity.Key.ToUpperInvariant(), entryIdTuples);
}
return allEntityIds;
}
/// <summary>
/// 获取Link实体关联的实体
/// </summary>
/// <param name="metaData"></param>
/// <returns></returns>
public static List<Entity> GetLinkEntitys(BusinessInfo billBusinessInfo)
{
List<Entity> linkEntities = new List<Entity>();
if (billBusinessInfo.GetForm().LinkSet == null || (billBusinessInfo.GetForm().LinkSet.LinkEntitys == null))
return linkEntities;
billBusinessInfo.GetForm().LinkSet.LinkEntitys.ForEach(linkEntity =>
{
string linkEntryKey = "";
Entity entity = null;
linkEntryKey = linkEntity.ParentEntityKey;
entity = billBusinessInfo.GetEntity(linkEntryKey);
if (entity != null)
{
linkEntities.Add(entity);
}
});
return linkEntities;
}
/// <summary>
/// 加载单据内码关联所有实体的分
/// </summary>
/// <param name="billIds"></param>
/// <returns></returns>
public ViewBusinessFlowParameter LoadBillView(List<long> billIds)
{
Dictionary<string, List<Tuple<long, long>>> allEntityIds = LoadBillIds(billIds);
Entity headEntity = BillBusinessInfo.Entrys.FirstOrDefault((entity) => entity is HeadEntity);
ViewBusinessFlowParameter viewParam = new ViewBusinessFlowParameter();
viewParam.BillInfo = new ViewBusinessFlowBillInfo(BillBusinessInfo.GetForm().Id);
List<BusinessFlowInstance> instances = this.LoadAllInstances(allEntityIds, headEntity, billIds);
if (instances.IsEmpty() == false)
{
var queryChangeEntityParamValue = SystemParameterServiceHelper.GetParamter(Context, 0, 0, "BF_SystemParameter", "FQueryChangeEntity");
var isEnableCrossEntityQuery = ObjectUtils.Object2Bool(queryChangeEntityParamValue);
if (isEnableCrossEntityQuery)
{
this.ChangeEntityQuery(viewParam, ref instances);
}
}
viewParam.Instances.AddRange(instances);
return viewParam;
}
/// <summary>
/// 加载单据全部实体的全部业务流程实例
/// </summary>
/// <param name="allEntityIds"></param>
/// <param name="headEntity"></param>
/// <param name="billIds"></param>
/// <param name="billInfo">同步输出本次发起联查的单据体内码</param>
/// <returns></returns>
private List<BusinessFlowInstance> LoadAllInstances(Dictionary<string, List<Tuple<long, long>>> allEntityIds, Entity headEntity, List<long> billIds, BusinessInfo info = null)
{
List<BusinessFlowInstance> instances = new List<BusinessFlowInstance>();
string formId = info == null ? BillBusinessInfo.GetForm().Id : info.GetForm().Id;
// 逐个单据体寻找其业务流程实例
foreach (var item in allEntityIds)
{
Entity entity = info == null ? BillBusinessInfo.GetEntity(item.Key) : info.GetEntity(item.Key);
List<long> entryIds = new List<long>();
if (this.IsHeadEntity(entity))
{
entryIds.AddRange(billIds);
}
else
{
item.Value.ForEach((t) => entryIds.Add(t.Item2));
}
var entityInstances = this.LoadInstancesByEntryIds(formId, entity.Key, entryIds.ToArray());
var validateInstance = this.RemoveJointlessNodes(formId, entity.Key, entryIds.ToArray(), entityInstances);
instances.AddRange(validateInstance);
}
return instances;
}
#region 加载并合并流程实例
/// <summary>
/// 改为公共方法,使业务流程图也能使用
/// </summary>
/// <param name="viewParam"></param>
/// <param name="entityInstances"></param>
private void ChangeEntityQuery(ViewBusinessFlowParameter viewParam, ref List<BusinessFlowInstance> entityInstances)
{
if (entityInstances.IsEmpty()) return;
var currentInstances = new List<BusinessFlowInstance>();
var reachedInstIds = new HashSet<string>();
var reachedNodes = new HashSet<string>();
var reachedTableName = new HashSet<string>();//搜索过的表名
foreach (var item in entityInstances)
{
currentInstances.Add(item);
}
//跨实体查询,实例之间并不存在连接或对应关系,故这里可以根据表单批量处理,而不是逐个处理
while (currentInstances.Count > 0)
{
//得到需要搜索的节点,按节点表名进行分组
var groupNodesByTbName = new Dictionary<string, List<long>>();
foreach (var instItem in currentInstances)
{
reachedInstIds.Add(instItem.Id);
List<RouteTreeNode> lstNodes = this.GetAllNodes(instItem.FirstNode); //所有的节点
foreach (RouteTreeNode node in lstNodes)
{
if (node.Id != null && !reachedNodes.Contains(node.Id.CId)) //节点没有搜索过
{
List<long> lstId = new List<long>();
if (groupNodesByTbName.ContainsKey(node.Id.Tbl))
{
lstId = groupNodesByTbName[node.Id.Tbl];
}
else
{
groupNodesByTbName[node.Id.Tbl] = lstId;
}
lstId.Add(node.Id.EId);
reachedNodes.Add(node.Id.CId);
}
}
}
currentInstances.Clear();
//对节点进行搜索
var groupNodesByTbDefine = new Dictionary<TableDefine, List<long>>();
foreach (var currentNode in groupNodesByTbName)
{
var tbDefine = BusinessFlowServiceHelper.LoadTableDefine(Context, currentNode.Key);
groupNodesByTbDefine[tbDefine] = currentNode.Value;
reachedTableName.Add(currentNode.Key);
}
foreach (var currentNode in groupNodesByTbDefine)
{
var crossInstances = this.GetInstances(currentNode, reachedTableName); //得到跨实体流程
foreach (var itemInst in crossInstances)
{
if (reachedInstIds.Contains(itemInst.Id)) continue;
currentInstances.Add(itemInst);
}
}
//存在跨实体数据则使用根节点作为焦点
if (currentInstances.Count > 0)
{
viewParam.IsRootForceFormId = true;
entityInstances.AddRange(currentInstances);
}
}
}
/// <summary>
/// 得到所有节点
/// </summary>
/// <param name="parentNote"></param>
/// <returns></returns>
private List<RouteTreeNode> GetAllNodes(RouteTreeNode parentNote, int parentDepth = 0)
{
List<RouteTreeNode> allNode = new List<RouteTreeNode>();
allNode.Add(parentNote);
foreach (var child in parentNote.ChildNodes)
{
int currDepth = parentDepth;
RecursionLimitUtils.Run(ref currDepth, 200, "TrackAllFlows->GetAllNodes");
var childs = this.GetAllNodes(child, currDepth);
allNode.AddRange(childs);
}
return allNode;
}
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
private List<BusinessFlowInstance> GetInstances(KeyValuePair<TableDefine, List<long>> item, HashSet<string> reachedTableName)
{
//构建实体key和列名集合
var tbDefine = item.Key;
var entityKeyAndColumnKey = new Dictionary<string, string>();
var businessInfo = FormMetaDataCache.GetCachedFormMetaData(Context, tbDefine.FormId).BusinessInfo;
Entity noteEntity = businessInfo.GetEntity(tbDefine.EntityKey); //节点对应的实体
var tuple = this.GetFilterCondition(businessInfo, noteEntity, item.Value);
var pkName = businessInfo.GetForm().PkFieldName;
var headTbDefine = BusinessFlowServiceHelper.LoadTableDefine(Context, tbDefine.FormId, businessInfo.Entrys[0].Key, false);
if (headTbDefine != null)
{
if (!reachedTableName.Contains(headTbDefine.TableNumber))
{
entityKeyAndColumnKey[businessInfo.Entrys[0].Key] = pkName; //单据头
}
}
//构建参数
var queryParam = new QueryBuilderParemeter();
queryParam.FormId = businessInfo.GetForm().Id;
queryParam.FilterClauseWihtKey = tuple.Item1;
queryParam.SelectItems.Add(new SelectorItemInfo(pkName));
foreach (var entry in businessInfo.Entrys)
{
if (entry is EntryEntity && entry.Key != noteEntity.Key)
{
var entryTableDefine = BusinessFlowServiceHelper.LoadTableDefine(Context, tbDefine.FormId, entry.Key, false);
if (entryTableDefine == null) continue;
if (reachedTableName.Contains(entryTableDefine.TableNumber)) continue;
var columnKey = string.Format("{0}_{1}", entry.Key, entry.EntryPkFieldName);
entityKeyAndColumnKey[entry.Key] = columnKey;
queryParam.SelectItems.Add(new SelectorItemInfo(columnKey));
}
}
List<BusinessFlowInstance> lstInstances = new List<BusinessFlowInstance>();
if (entityKeyAndColumnKey.Count > 0)
{
//得到查询数据
DynamicObjectCollection objs;
if (tuple.Item2 == null)
{
objs = QueryServiceHelper.GetDynamicObjectCollection(Context, queryParam);
}
else
{
objs = QueryServiceHelper.GetDynamicObjectCollection(Context, queryParam, new List<SqlParam>() { tuple.Item2 });
}
//构建查询节点数据
var allEntityIds = new Dictionary<string, List<Tuple<long, long>>>();
foreach (var dic in entityKeyAndColumnKey)
{
var lstTuple = new List<Tuple<long, long>>();
allEntityIds[dic.Key] = lstTuple;
foreach (var obj in objs)
{
var billId = ObjectUtils.Object2Int64(obj[pkName]);
var entityId = ObjectUtils.Object2Int64(obj[dic.Value]);
if (entityId == 0) continue;
var tItem = Tuple.Create<long, long>(billId, entityId);
if (!lstTuple.Contains(tItem))
{
lstTuple.Add(tItem);
}
}
}
var lstBillId = allEntityIds.First().Value.Select(x => x.Item1).ToList();
lstInstances = this.LoadAllInstances(allEntityIds, businessInfo.Entrys[0], lstBillId, businessIn