案例背景:
采购订单 -> 收料通知单 -> 采购入库单 -> 应付单 -> 付款申请单,采购订单经过 多步下推 ,才生成付款申请单。
现在需要根据付款申请单的单据内码,寻找其源头的采购订单;
打开采购订单列表,展示出来搜索出的源头采购订单;
技术难点:
1. 经过了 多步骤下推 ,如何从付款申请单逐层往上追溯,找到源头采购订单?
2. 如果获取这样的追溯关系?
3. 中间环节应付单,对采购物料明细进行合并,重新归纳了付款计划,在此情景下如何追溯?
实现方案:
1. 通过付款申请单的关联主实体 - 付款明细,根据业务流程实例数据,寻找到源头的应付单
2. 通过应付单的的关联主实体 - 物料明细,读取业务流程实例数据,寻找到源头的采购订单
案例设计:
在付款申请单上,添加一个菜单:联查采购订单(tbSearchSrcPO);
然后挂上本插件,即可验证效果
示例代码:
采购订单 -> 收料通知单 -> 采购入库单 -> 应付单 -> 付款申请单,采购订单经过 多步下推 ,才生成付款申请单。
现在需要根据付款申请单的单据内码,寻找其源头的采购订单;
打开采购订单列表,展示出来搜索出的源头采购订单;
技术难点:
1. 经过了 多步骤下推 ,如何从付款申请单逐层往上追溯,找到源头采购订单?
2. 如果获取这样的追溯关系?
3. 中间环节应付单,对采购物料明细进行合并,重新归纳了付款计划,在此情景下如何追溯?
实现方案:
1. 通过付款申请单的关联主实体 - 付款明细,根据业务流程实例数据,寻找到源头的应付单
2. 通过应付单的的关联主实体 - 物料明细,读取业务流程实例数据,寻找到源头的采购订单
案例设计:
在付款申请单上,添加一个菜单:联查采购订单(tbSearchSrcPO);
然后挂上本插件,即可验证效果
示例代码:
已经在本地验证通过,代码较长
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Kingdee.BOS;
using Kingdee.BOS.Util;
using Kingdee.BOS.BusinessEntity.BusinessFlow;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.Bill;
using Kingdee.BOS.Core.Bill.PlugIn;
using Kingdee.BOS.Core.Bill.PlugIn.Args;
using Kingdee.BOS.Core.BusinessFlow.ServiceArgs;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.Core.Metadata.EntityElement;
using Kingdee.BOS.Core.List;
using Kingdee.BOS.Core.SqlBuilder;
using Kingdee.BOS.ServiceHelper;
namespace JDSample.FormPlugIn.Bill
{
[Description("搜索付款申请单的源头采购订单")]
public class S151202SearchSrcBillEdit : AbstractBillPlugIn
{
public override void AfterBarItemClick(AfterBarItemClickEventArgs e)
{
if (e.BarItemKey.EqualsIgnoreCase("tbSearchSrcPO"))
{// 搜索源头采购订单
if (this.View.Model.GetPKValue() == null )
{
this.View.ShowMessage("请先保存单据!");
return;
}
long payAppBillId = Convert.ToInt64(this.View.Model.GetPKValue());
this.DoSearchSrcPO(payAppBillId);
}
}
/// <summary>
/// 搜索源头采购订单,并显示列表
/// </summary>
/// <param name="payAppBillId">付款申请单单据内码</param>
private void DoSearchSrcPO(long payAppBillId)
{
// 读取付款申请单.明细内码
HashSet<long> payAppEntityIds = this.LoadEntityIds(
"CN_PAYAPPLY", "FPAYAPPLYENTRY", new long[] { payAppBillId });
if (payAppEntityIds.Count == 0)
{
this.View.ShowMessage("付款申请单的明细单据体没有数据,没有源单!");
return;
}
// 读取付款申请单的业务流程实例
BusinessFlowInstanceCollection payAppInsts = this.LoadBFInstances(
"CN_PAYAPPLY", "FPAYAPPLYENTRY", payAppEntityIds.ToArray());
if (payAppInsts.Count == 0)
{
this.View.ShowMessage("付款申请单的明细单据体是手工创建的,没有源单!");
return;
}
// 取付款申请单源头的应付单.付款计划
HashSet<long> apPlanEntityIds = this.GetSrcEntityIdsByInsts(payAppInsts,
"AP_Payable", "FEntityPlan",
"CN_PAYAPPLY", "FPAYAPPLYENTRY", payAppEntityIds);
if (apPlanEntityIds.Count == 0)
{
this.View.ShowMessage("没有找到源头的应付单,线索中断,不能继续追查采购订单");
return;
}
// 根据应付单.付款计划内码,找到应付单.物料明细内码
HashSet<long> apBillIds = this.LoadBillIds("AP_Payable", "FEntityPlan", apPlanEntityIds.ToArray());
HashSet<long> apDetailIds = this.LoadEntityIds("AP_Payable", "FEntityDetail", apBillIds.ToArray());
// 加载应付单.物料明细的业务流程实例
BusinessFlowInstanceCollection apDetailInsts = this.LoadBFInstances(
"AP_Payable", "FEntityDetail", apDetailIds.ToArray());
// 取应付单源头的采购订单物料明细内码
HashSet<long> poEntityIds = this.GetSrcEntityIdsByInsts(apDetailInsts,
"PUR_PurchaseOrder", "FPOOrderEntry",
"AP_Payable", "FEntityDetail", apDetailIds);
if (poEntityIds.Count == 0)
{
this.View.ShowMessage("没有找到源头的采购订单!");
return;
}
// 取源头的采购订单内码
HashSet<long> poBillIds = this.LoadBillIds("PUR_PurchaseOrder", "FPOOrderEntry", poEntityIds.ToArray());
// 显示出搜索出来的采购订单列表
this.ShowPOList(poBillIds.ToArray());
}
/// <summary>
/// 根据单据体内码,读取其单据内码
/// </summary>
/// <param name="formId"></param>
/// <param name="entityKey"></param>
/// <param name="entityIds"></param>
/// <returns></returns>
private HashSet<long> LoadBillIds(string formId, string entityKey, long[] entityIds)
{
HashSet<long> billIds = new HashSet<long>();
if (entityKey.EqualsIgnoreCase("FBillHead"))
{// 需要读取的实体为单据头,直接返回实体内码集合,无需到数据库读取
foreach (var entityId in entityIds)
{
billIds.Add(entityId);
}
}
else
{
// 读取单据的元数据
FormMetadata meta = MetaDataServiceHelper.Load(this.Context, formId) as FormMetadata;
Entity entity = meta.BusinessInfo.GetEntity(entityKey);
// 构建取数服务参数,仅加载单据内码字段
QueryBuilderParemeter queryParam = new QueryBuilderParemeter();
queryParam.FormId = formId;
queryParam.BusinessInfo = meta.BusinessInfo;
queryParam.SelectItems.Add(new SelectorItemInfo(meta.BusinessInfo.GetForm().PkFieldName));
queryParam.FilterClauseWihtKey = string.Format("{0}_{1} in ({2})",
entityKey, entity.EntryPkFieldName, string.Join(",", entityIds));
// 读取数据
var rows = QueryServiceHelper.GetDynamicObjectCollection(this.Context, queryParam);
// 把取到的单据内码返回
foreach(var row in rows)
{
long billId = Convert.ToInt64(row[0]);
if (billIds.Contains(billId) == false)
{
billIds.Add(billId);
}
}
}
return billIds;
}
/// <summary>
/// 读取指定单据的实体内码返回
/// </summary>
/// <param name="formId">单据formId</param>
/// <param name="entityKey">实体Key</param>
/// <param name="billIds">单据内码集合</param>
/// <returns></returns>
private HashSet<long> LoadEntityIds(string formId, string entityKey, long[] billIds)
{
HashSet<long> entityIds = new HashSet<long>();
if (entityKey.EqualsIgnoreCase("FBillHead"))
{// 需要读取的实体为单据头,直接返回单据内码集合,无需到数据库读取
foreach (var billId in billIds)
{
entityIds.Add(billId);
}
}
else
{
// 读取单据的元数据
FormMetadata meta = MetaDataServiceHelper.Load(this.Context, formId) as FormMetadata;
Entity entity = meta.BusinessInfo.GetEntity(entityKey);
// 构建取数服务参数,仅加载实体内码字段
QueryBuilderParemeter queryParam = new QueryBuilderParemeter();
queryParam.FormId = formId;
queryParam.BusinessInfo = meta.BusinessInfo;
queryParam.SelectItems.Add(
new SelectorItemInfo(string.Format("{0}_{1}", entityKey, entity.EntryPkFieldName)));
queryParam.FilterClauseWihtKey = string.Format("{0} in ({1})",
meta.BusinessInfo.GetForm().PkFieldName, string.Join(",", billIds));
// 读取数据
var rows = QueryServiceHelper.GetDynamicObjectCollection(this.Context, queryParam);
// 把取到的实体内码返回
foreach(var row in rows)
{
entityIds.Add(Convert.ToInt64(row[0]));
}
}
return entityIds;
}
/// <summary>
/// 到业务流程实例中,基于目标单,寻找指定单据类型的源单
/// </summary>
/// <param name="insts">业务流程实例</param>
/// <param name="srcFormId">源单formId</param>
/// <param name="srcEntityKey">源单实体key</param>
/// <param name="targetFormId">目标单formId</param>
/// <param name="targetEntityKey">目标实体key</param>
/// <param name="targetEntityIds">目标单实体内码集合</param>
/// <returns></returns>
private HashSet<long> GetSrcEntityIdsByInsts(BusinessFlowInstanceCollection insts,
string srcFormId, string srcEntityKey,
string targetFormId, string targetEntityKey, HashSet<long> targetEntityIds)
{
HashSet<long> srcEntityIds = new HashSet<long>();
if (insts == null || insts.Count == 0)
{
return srcEntityIds;
}
// 到业务流程实例中,寻找目标单所在的节点
string targetTableNumber = this.LoadTableNumber(targetFormId, targetEntityKey);
List<RouteTreeNode> targetNodes = new List<RouteTreeNode>();
foreach (var inst in insts)
{
var nodes = inst.SerarchTargetFormNodes(targetTableNumber);
foreach(var node in nodes)
{
long targetEntityId = node.Id.EId;
if (targetEntityIds.Contains(targetEntityId))
{
targetNodes.Add(node);
}
}
}
// 基于目标单所在的节点,反查指定的源单
string srcTableNumber = this.LoadTableNumber(srcFormId, srcEntityKey);
foreach (var targetNode in targetNodes)
{
// 不断往上追溯,寻找指定类型的源单
RouteTreeNode parentNode = targetNode.ParentNode;
while (parentNode != null)
{
if (parentNode.Id.Tbl.EqualsIgnoreCase(srcTableNumber))
{// 此父节点,就是需要的源单类型,取值
if (srcEntityIds.Contains(parentNode.Id.EId) == false)
{
srcEntityIds.Add(parentNode.Id.EId);
}
}
parentNode = parentNode.ParentNode;
}
}
return srcEntityIds;
}
/// <summary>
/// 获取单据关联主实体的业务流程实例:业务流程实例记录了每行单据体的来龙去脉
/// </summary>
/// <param name="formId">单据</param>
/// <param name="entityKey">关联主实体Key</param>
/// <param name="entityIds">关联主实体内码集合</param>
/// <returns></returns>
private BusinessFlowInstanceCollection LoadBFInstances(string formId, string entityKey, long[] entityIds)
{
LoadInstancesByEntityIdArgs args = new LoadInstancesByEntityIdArgs(formId, entityKey, entityIds);
var instances = BusinessFlowDataServiceHelper.LoadInstancesByEntityId(this.Context, args);
return instances;
}
/// <summary>
/// 获取单据体的表格编码
/// </summary>
/// <param name="formId">单据FormId</param>
/// <param name="entityKey">实体Key;如果关联主实体是单据头,请传入FBillHead</param>
/// <returns>新建的数据库,从未保存过此业务单据时,表格编码可能为空</returns>
/// <remarks>
/// 在业务流程跟踪关系中,每个单据的单据体都有一个唯一的表格编码。
/// 默认是单据体的物理表格名。
/// 但在此物料表格被多个单据公用时,会在物理表名后面增加序号以示区分
/// </remarks>
private string LoadTableNumber(string formId, string entityKey)
{
var tableDefine = BusinessFlowServiceHelper.LoadTableDefine(this.Context, formId, entityKey);
if (tableDefine == null)
{
return string.Empty;
}
else
{
return tableDefine.TableNumber;
}
}
/// <summary>
/// 显示采购订单列表
/// </summary>
/// <param name="poBillIds"></param>
private void ShowPOList(long[] poBillIds)
{
ListShowParameter showParam = new ListShowParameter();
showParam.FormId = "PUR_PurchaseOrder";
showParam.ListFilterParameter.Filter =
string.Format(" FID in ({0}) ", string.Join(",", poBillIds));
this.View.ShowForm(showParam);
}
}
}