标签:
背景说明拣货实现算法,未实际验证,且实现非常简单,仅供参考,请勿照搬为实际应用
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.Core; using Kingdee.BOS.Core.Metadata; using Kingdee.BOS.Core.Metadata.ConvertElement; using Kingdee.BOS.Core.Metadata.ConvertElement.PlugIn; using Kingdee.BOS.Core.Metadata.ConvertElement.PlugIn.Args; using Kingdee.BOS.Core.Metadata.EntityElement; using Kingdee.BOS.Core.Metadata.FieldElement; using Kingdee.BOS.Orm; using Kingdee.BOS.Orm.DataEntity; using Kingdee.BOS.App; using Kingdee.BOS.App.Data; using Kingdee.BOS.Contracts; namespace JDSample.ServicePlugIn.BillConvert { public class S160330StockPickingConvPlug : AbstractConvertPlugIn { /// <summary> /// 单据体元数据 /// </summary> private Entity _entity = null; /// <summary> /// 物料字段元数据 /// </summary> private BaseDataField _fldMaterial = null; /// <summary> /// 仓库字段元数据 /// </summary> private BaseDataField _fldStock = null; /// <summary> /// 基本单位数量字段元数据 /// </summary> private BaseQtyField _fldBaseQty = null; /// <summary> /// 字段携带完毕,且关联关系已经顺利构建 /// </summary> /// <param name="e"></param> public override void OnAfterCreateLink(CreateLinkEventArgs e) { // 获取后面要用到的元素的元数据 this._entity = e.TargetBusinessInfo.GetEntity("FEntity"); this._fldMaterial = e.TargetBusinessInfo.GetField("FMaterialID") as BaseDataField; this._fldStock = e.TargetBusinessInfo.GetField("FStockID") as BaseDataField; this._fldBaseQty = e.TargetBusinessInfo.GetField("FBaseUnitQty") as BaseQtyField; // 读取已经生成的销售出库单:随后要对销售出库单,按单循环进行拣货与拆单据体行 ExtendedDataEntity[] billObjExts = e.TargetExtendedDataEntities.FindByEntityKey("FBillHead"); List<DynamicObject> billObjs = new List<DynamicObject>(); foreach (var billObjExt in billObjExts) { // 开始拣货 //this.FunVersion1(billObjExt.DataEntity); // 第一种实现代码:性能非常糟糕 //this.FunVersion2(billObjExt.DataEntity); // 第二种实现代码:性能同样糟糕 this.FunVersion3(billObjExt.DataEntity); // 第三种实现代码:推荐版本 billObjs.Add(billObjExt.DataEntity); } // 重新展开单据中包含的单据体行 // 特别说明:如果去掉此语句,新拆分的行,不会执行表单服务策略 e.TargetExtendedDataEntities.Parse(billObjs, e.TargetBusinessInfo); } /// <summary> /// 实现函数一: /// 在循环中读取数据库, /// 错误代码典型样本 /// </summary> /// <param name="billObj">生成的下游单据集合</param> private void FunVersion1(DynamicObject billObj) { DynamicObjectCollection rows = this._entity.DynamicProperty.GetValue(billObj) as DynamicObjectCollection; // 对单据体进行循环:逐行实现拣货 // 需从后往前循环,新拆分的行,避开循环 int rowCount = rows.Count; for (int i = rowCount - 1; i >= 0; i--) { DynamicObject currRow = rows[ i ]; // 当前行 // 取当前行物料:当前行没有填写物料,或者没有填写基本单位数量,不需要拣货,略过 DynamicObject materialObj = this._fldMaterial.DynamicProperty.GetValue(currRow) as DynamicObject; if (materialObj == null) continue; // 物料的MasterId:即时库存,使用的是物料的MasterId long materialMasterId = Convert.ToInt64(materialObj[FormConst.MASTER_ID]); // 性能问题点:在循环中执行SQL,读取数据: // 读取有库存的仓库,按数量排序(未考虑货主、保管者、批号等其他库存维度,实际解决方案不能照搬) string sql = "SELECT FSTOCKID, FQTY from T_STK_INVENTORY WHERE FMATERIALID = @FMATERIALID ORDER BY FQTY DESC"; SqlParam param = new SqlParam("@FMATERIALID", KDDbType.Int64, materialMasterId); var stockQtyList = DBUtils.ExecuteDynamicObject(this.Context, sql, null, null, System.Data.CommandType.Text, param); if (stockQtyList.Count == 0) {// 没有找到有库存的仓库,不需要拣货,略过此行 continue; } else {// 找到多个仓库有库存,需要灵活拣货 var newRows = this.DoStockPicking(rows, currRow, stockQtyList.ToList()); } } } /// <summary> /// 实现函数二: /// 把循环读取数据库,放在循环外一次性读取; /// 但在循环中嵌套了循环,性能同样糟糕; /// </summary> /// <param name="billObj">生成的下游单据集合</param> private void FunVersion2(DynamicObject billObj) { DynamicObjectCollection rows = this._entity.DynamicProperty.GetValue(billObj) as DynamicObjectCollection; // 优化后算法: // 先获取所有的物料,然后统一到即时库存中读取这些物料的库存量, // 一次性取数,避免在循环中读取数据库 HashSet<long> materialMasterIds = new HashSet<long>(); // 循环取得所有的物料 foreach (var currRow in rows) { // 取当前行物料:当前行没有填写物料,或者没有填写基本单位数量,不需要拣货,略过 DynamicObject materialObj = this._fldMaterial.DynamicProperty.GetValue(currRow) as DynamicObject; if (materialObj == null) continue; // 物料的MasterId:即时库存,使用的是物料的MasterId long materialMasterId = Convert.ToInt64(materialObj[FormConst.MASTER_ID]); if (materialMasterIds.Contains(materialMasterId) == false) { materialMasterIds.Add(materialMasterId); } } // 一次性读取所有物料的库存 string sql = @" SELECT T1.FMATERIALID, T1.FSTOCKID, T1.FQTY FROM T_STK_INVENTORY T1 INNER JOIN (table(fn_StrSplit(@FMATERIALID,',',1))) T2 ON (T1.FMATERIALID = T2.FID) ORDER BY T1.FMATERIALID, T1.FQTY DESC"; SqlParam param = new SqlParam("@FMATERIALID", KDDbType.udt_inttable, materialMasterIds.ToArray()); var allStockQtyList = DBUtils.ExecuteDynamicObject(this.Context, sql, null, null, System.Data.CommandType.Text, param); // 对单据体进行循环:逐行实现拣货 // 需从后往前循环,新拆分的行,避开循环 int rowCount = rows.Count; for (int i = rowCount - 1; i >= 0; i--) { DynamicObject currRow = rows[ i ]; // 当前行 // 取当前行物料:当前行没有填写物料,或者没有填写基本单位数量,不需要拣货,略过 DynamicObject materialObj = this._fldMaterial.DynamicProperty.GetValue(currRow) as DynamicObject; if (materialObj == null) continue; // 物料的MasterId:即时库存,使用的是物料的MasterId long materialMasterId = Convert.ToInt64(materialObj[FormConst.MASTER_ID]); // 搜索此物料的库存数据 : 物料 = 当前行的物料 // 性能点:在循环中,从一个集合中搜索与当前行匹配的数据,使循环次数成指数上升 var stockQtyList = (from p in allStockQtyList where ( Convert.ToInt64(p["FMaterialId"]) == materialMasterId) select p).ToList(); if (stockQtyList.Count == 0) {// 没有找到有库存的仓库,不需要拣货,略过此行 continue; } else {// 找到多个仓库有库存,需要灵活拣货 var newRows = this.DoStockPicking(rows, currRow, stockQtyList.ToList()); } } } /// <summary> /// 实现函数三: /// 充分考虑了性能问题: /// 1. 避免在循环中读取数据; /// 2. 预先建立数据字典,避免在循环中嵌套循环 /// 推荐的实现方案 /// </summary> /// <param name="billObj">生成的下游单据集合</param> private void FunVersion3(DynamicObject billObj) { DynamicObjectCollection rows = this._entity.DynamicProperty.GetValue(billObj) as DynamicObjectCollection; // 优化后算法: // 先获取所有的物料,然后统一到即时库存中读取这些物料的库存量, // 一次性取数,避免在循环中读取数据库 HashSet<long> materialMasterIds = new HashSet<long>(); // 循环取得所有的物料 foreach (var currRow in rows) { // 取当前行物料:当前行没有填写物料,或者没有填写基本单位数量,不需要拣货,略过 DynamicObject materialObj = this._fldMaterial.DynamicProperty.GetValue(currRow) as DynamicObject; if (materialObj == null) continue; // 物料的MasterId:即时库存,使用的是物料的MasterId long materialMasterId = Convert.ToInt64(materialObj[FormConst.MASTER_ID]); if (materialMasterIds.Contains(materialMasterId) == false) { materialMasterIds.Add(materialMasterId); } } // 一次性读取所有物料的库存 string sql = @" SELECT T1.FMATERIALID, T1.FSTOCKID, T1.FQTY FROM T_STK_INVENTORY T1 INNER JOIN (table(fn_StrSplit(@FMATERIALID,',',1))) T2 ON (T1.FMATERIALID = T2.FID) ORDER BY T1.FMATERIALID, T1.FQTY DESC"; SqlParam param = new SqlParam("@FMATERIALID", KDDbType.udt_inttable, materialMasterIds.ToArray()); var allStockQtyList = DBUtils.ExecuteDynamicObject(this.Context, sql, null, null, System.Data.CommandType.Text, param); // 优化后算法: // 对已经取得的物料库存数据,按物料进行分组,以便后面在循环中,快速取到本物料的库存 Dictionary<long, List<DynamicObject>> dctStockQty = new Dictionary<long, List<DynamicObject>>(); foreach (var stockQty in allStockQtyList) { long materialMasterId = Convert.ToInt64(stockQty["FMaterialId"]); if (dctStockQty.ContainsKey(materialMasterId) == false) { dctStockQty.Add(materialMasterId, new List<DynamicObject>()); } dctStockQty[materialMasterId].Add(stockQty); } // 对单据体进行循环:逐行实现拣货 // 需从后往前循环,新拆分的行,避开循环 int rowCount = rows.Count; for (int i = rowCount - 1; i >= 0; i--) { DynamicObject currRow = rows[ i ]; // 当前行 // 取当前行物料:当前行没有填写物料,或者没有填写基本单位数量,不需要拣货,略过 DynamicObject materialObj = this._fldMaterial.DynamicProperty.GetValue(currRow) as DynamicObject; if (materialObj == null) continue; // 物料的MasterId:即时库存,使用的是物料的MasterId long materialMasterId = Convert.ToInt64(materialObj[FormConst.MASTER_ID]); // 搜索此物料的库存数据 : 物料 = 当前行的物料 // 性能点:在循环中,从一个集合中搜索与当前行匹配的数据,使循环次数成指数上升 List<DynamicObject> stockQtyList = null; if (dctStockQty.TryGetValue(materialMasterId, out stockQtyList) == false || stockQtyList.Count == 0) {// 没有找到有库存的仓库,不需要拣货,略过此行 continue; } else {// 找到多个仓库有库存,需要灵活拣货 var newRows = this.DoStockPicking(rows, currRow, stockQtyList.ToList()); } } } /// <summary> /// 拣货的实现逻辑:只是简单的实现拣货逻辑,仅供参考 /// </summary> /// <param name="rows">单据体行集合</param> /// <param name="currRow">当前行</param> /// <param name="stockQtyList">物料即时库存数据</param> /// <returns>新拆分出来的行</returns> private List<DynamicObject> DoStockPicking( DynamicObjectCollection rows, DynamicObject currRow, List<DynamicObject> stockQtyList) { List<DynamicObject> newRows = new List<DynamicObject>(); // 取本次需出库的基本单位数量 decimal baseQty = Convert.ToDecimal(this._fldBaseQty.DynamicProperty.GetValue(currRow)); if (baseQty == 0) { return newRows; // 无数量,不需要拣货,略过 } // 读取单据数据的服务对象:提前准备好,后续需要不断使用 IViewService viewService = ServiceHelper.GetService<IViewService>(); // 首先判断第一个仓库库存是否足够:如够,直接使用此仓库出库 decimal stockQty = Convert.ToDecimal(stockQtyList[0]["FQty"]); if (stockQtyList.Count == 1 || stockQty > baseQty) { long stockId = Convert.ToInt64(stockQtyList[0]["FStockId"]); // 给仓库字段赋值 DynamicObject[] stockObjs = viewService.LoadFromCache( this.Context, new object[] { stockId }, this._fldStock.RefFormDynamicObjectType); this._fldStock.RefIDDynamicProperty.SetValue(currRow, stockId); this._fldStock.DynamicProperty.SetValue(currRow, stockObjs[0]); } else {// 需要使用多个仓库出库 // 首先把第一个仓库,填写到当前行,其他仓库,拆分新行出库 long stockId = Convert.ToInt64(stockQtyList[0]["FStockId"]); // 给仓库字段赋值:取仓库数据,是使用了缓存的,性能比较好 DynamicObject[] stockObjs = viewService.LoadFromCache( this.Context, new object[] { stockId }, this._fldStock.RefFormDynamicObjectType); this._fldStock.RefIDDynamicProperty.SetValue(currRow, stockId); this._fldStock.DynamicProperty.SetValue(currRow, stockObjs[0]); // 调整此仓库出库数量 this._fldBaseQty.DynamicProperty.SetValue(currRow, stockQty); baseQty = baseQty - stockQty; // 剩余数量从其他仓库出库 int stockIndex = 1; // 当前仓库指针 while (baseQty > 0 && stockIndex < stockObjs.Length) { // 拆分出新行出库 DynamicObject newRow = (DynamicObject)currRow.Clone(false, true); // 把新行,插入到单据中,排在当前行之后 rows.Insert(rows.IndexOf(currRow) + stockIndex, newRow); // 填写仓库、数量 long otherStockId = Convert.ToInt64(stockQtyList[stockIndex]["FStockId"]); decimal otherStockQty = Convert.ToDecimal(stockQtyList[stockIndex]["FQty"]); if (stockIndex == stockObjs.Length - 1) { // 最后一个仓库,全部剩余数量,从此仓库出库 otherStockQty = baseQty; } if (otherStockQty > 0) { // 给仓库字段赋值 DynamicObject[] otherStockObjs = viewService.LoadFromCache( this.Context, new object[] { otherStockId }, this._fldStock.RefFormDynamicObjectType); this._fldStock.RefIDDynamicProperty.SetValue(newRow, otherStockId); this._fldStock.DynamicProperty.SetValue(newRow, otherStockObjs[0]); // 调整此仓库出库数量 this._fldBaseQty.DynamicProperty.SetValue(newRow, otherStockQty); baseQty = baseQty - otherStockQty; } stockIndex++; // 仓库指针向后偏移:使用下一个仓库出库 } } return newRows; } } }
标签:
原文地址:http://blog.csdn.net/fyq891014/article/details/51260235