有客户需求,需要在销售订单审核时,自动下推发货通知单。发货通知单里面有仓位字段,并不能从销售订单自动携带过来,需要代码自行填写。
仓位字段是弹性域字段,其赋值比一般字段赋值,要复杂的多,想要在代码中随机分配一个仓位,顺利生成发货通知单,是很困难的。
本文尝试分享如何为自动下推生成的发货通知单,随机分配仓位,跳过仓位必设的障碍。当然,用户收到发货通知单,需要出库时,必须根据实际情况修正仓位。
示例代码分为两部分,必须同时复制到插件中,才能顺利编译。编译OK,但是没有测试运行,如有应用者使用过程中发现有问题及时反馈。
代码段1:销售订单审核操作插件,自动下推生成了发货通知单,未保存前,此时需要填写仓位。
[code]
///
/// 为发货通知单的出货仓库,随机分配一个出货仓位
///
/// 发货通知单元数据包
/// 发货通知单数据包数组
private void UpdateStockLoc(
Kingdee.BOS.Core.Metadata.BusinessInfo info,
Kingdee.BOS.Orm.DataEntity.DynamicObject[] billObjs)
{
// 取发货通知单的明细信息元数据
Kingdee.BOS.Core.Metadata.EntityElement.Entity entity = info.GetEntity("FEntity");
// 取发货通知单的出货仓库字段元数据(基础资料类型)
Kingdee.BOS.Core.Metadata.FieldElement.BaseDataField stockFld = info.GetField("FStockID") as Kingdee.BOS.Core.Metadata.FieldElement.BaseDataField;
// 取发货通知单的出货仓位字段元数据(弹性域类型)
Kingdee.BOS.Core.Metadata.FieldElement.RelatedFlexGroupField stockLocFld = info.GetField("FStockLocID") as Kingdee.BOS.Core.Metadata.FieldElement.RelatedFlexGroupField;
// 建立一个数据字典,把数据行与所选仓库关联起来,方便根据仓库,快速找到使用这些仓库的行
// Dictionary<仓库内码,List<使用这些仓库的行>>
Dictionary
// 循环判断各行仓库赋值情况,收集需要随机分配出库仓位的行
foreach (var billObj in billObjs)
{
var entryRows = entity.DynamicProperty.GetValue(billObj) as DynamicObjectCollection;
foreach (var row in entryRows)
{
Kingdee.BOS.Orm.DataEntity.DynamicObject stock = stockFld.DynamicProperty.GetValue(row) as Kingdee.BOS.Orm.DataEntity.DynamicObject;
// 本行未填仓库,无需分配出货仓位
if (stock == null) continue;
bool isOpenLocation = (bool)stock["IsOpenLocation"];
// 仓库未启用仓位管理,无需分配出货仓位
if (isOpenLocation == false) continue;
long stockId = Convert.ToInt64(stock[0]);
// 把本行,与其他采用了相同仓库的行,归结到一起
if (stockId != 0)
{
List
if (dctStockToRows.TryGetValue(stockId, out sameStockRows) == false)
{
sameStockRows = new List
dctStockToRows[stockId] = sameStockRows;
}
sameStockRows.Add(row);
}
}
}
// 对收集到需要填写仓位的行,进行仓位随机分配填写
foreach (var item in dctStockToRows)
{
List
Kingdee.BOS.Orm.DataEntity.DynamicObject stock = stockFld.DynamicProperty.GetValue(rows[0]) as Kingdee.BOS.Orm.DataEntity.DynamicObject;
// 为本仓库,产生一个新的仓位数据包
Kingdee.BOS.Orm.DataEntity.DynamicObject stockLoc = new DynamicObject(stockLocFld.RefFormDynamicObjectType);
// 取仓库启用的全部仓位维度
var locItems = stock["StockFlexItem"] as DynamicObjectCollection;
// 为仓位的各个维度,填写默认值
foreach (var locItem in locItems)
{
// 仓位维度,在仓位数据包中的属性名
string flexKey = "F" + Convert.ToString(locItem["FlexId_Id"]);
if (stockLocFld.RefFormDynamicObjectType.Properties.Contains(flexKey) == false) continue;
// 本仓库,本仓位维度,所有的可选值
var items = locItem["StockFlexDetail"] as DynamicObjectCollection;
if (items.Count > 0)
{
// 为各仓位维度,设置为第一个可选值
stockLoc[flexKey] = items[0]["FlexEntryId"];
stockLoc[flexKey + "_Id"] = items[0]["FlexEntryId_id"];
}
}
// 存储仓位数据包
stockLoc = CustFlexSaveService.SaveFlexData(this.Context, stockLocFld, stockLoc);
// 把仓位数据包,填写到各数据行上
foreach (var row in rows)
{
stockLocFld.DynamicProperty.SetValue(row, stockLoc);
stockLocFld.RefIDDynamicProperty.SetValue(row, stockLoc[0]);
}
}
}
[/code]
代码段2:提供一个静态帮助类,存储弹性域数据包
[code]
///
/// 自定义开发,弹性域字段值保存服务类
///
///
public static class CustFlexSaveService
{
///
/// 保存弹性域字段的数据包,确保在数据库产生一个组合值,返回的数据包中为保存成功的弹性域组合
///
/// 数据库上下文
/// 单据上的弹性域字段,如仓位字段
/// 单据上弹性域字段的数据包
///
///
/// 弹性域字段说明:
/// 1. 弹性域字段,由多个不确定的维度组合而成;
/// 2. 每个维度都会有一定的可选值;
/// 3. 使用时,弹性域各个维度值确定后,就会形成了一个唯一的弹性域维度值组合,并对应产生一个唯一内码;
/// 4. 单据上存储的弹性域字段值,仅存放其弹性域维度值组合对应的唯一内码;
/// 5. 如仓位,可能包括栋、层、排这三个维度,第1栋、第1层、第1排就是一个弹性域值组合,对应有个内码(如100005)
/// 6. 如出库单使用了仓位,用到了第1栋、第1层、第1排这个值组合,则单据上仓位字段,仅保存组合内码(如100005)
///
/// 为什么要提供特别的函数,保存弹性域字段值数据包?
/// 1. 对于用户来说,最直观看到的是弹性域的维度,如仓位的栋、层、排;
/// 2. 因此用户对弹性域赋值,首先会对各维度赋值,如确定为第1栋、第1层、第1排;
/// 3. 用户给维度填了值,但是系统并没有自动根据维度值组合,产生组合内码,并提交到数据;
/// 4. 单据上引用的弹性域字段值,依然是空值,保存后,重新加载单据,加载弹性域组合会失败,所有的维度值丢失;
///
/// 本函数,首先判断数据库是否已经给定的组组合,如果存在,则读取,否则,创建组合,存储后返回;
///
public static Kingdee.BOS.Orm.DataEntity.DynamicObject SaveFlexData(
Kingdee.BOS.Context ctx,
Kingdee.BOS.Core.Metadata.FieldElement.RelatedFlexGroupField flexField,
Kingdee.BOS.Orm.DataEntity.DynamicObject flexFldDataObj)
{
// 首先判断参数是否合理,如果参数不合理,无法进行后续处理
if (flexField == null)
{
// 后面逻辑必须用到弹性域字段
return flexFldDataObj;
}
if (flexFldDataObj == null)
{
return flexFldDataObj;
}
// 拼接维度值,如果拼接失败,说明无维度值,直接返回
string flexValString = string.Empty;
if (FlexValToString(flexFldDataObj, out flexValString) == false)
{
return flexFldDataObj;
}
// 判断值组合是否已经存在?
// 通过BOS平台提供的服务,读取弹性域组合对应的组合编码,如果数据库不存在此组合,则返回为0
Kingdee.BOS.App.Core.FlexService flexService = new Kingdee.BOS.App.Core.FlexService();
long id = flexService.GetFlexDataId(ctx, flexFldDataObj, flexField.BDFlexType.FormId);
if (id == 0)
{
// 不存在此组合,需要主动创建
// 调用BOS平台提供的标准保存服务,直接保存此组合,并接受保存成功后返回的数据包
Kingdee.BOS.Contracts.ISaveService saveService = Kingdee.BOS.App.ServiceHelper.GetService
var objs = saveService.Save(ctx, new Kingdee.BOS.Orm.DataEntity.DynamicObject[] { flexFldDataObj });
if (objs != null && objs.Length > 0)
{
// 回填保存成功返回的数据包内码
flexFldDataObj[0] = objs[0][0];
}
}
else if (id != -1)
{
// 存在此组合,直接把内码填写到数据包上
flexFldDataObj[0] = id;
}
// 返回已经填写了内码的原始数据包
return flexFldDataObj;
}
///
/// 拼接各维度值为一个长字符串,并输出。如果各维度均没有填写值,则返回false,表示值拼接失败
///
/// 弹性域字段数据包
/// 输出拼接的值组合字符串
///
/// 此函数可以按照统一的格式,为每种弹性域字段值,产生一个维度值组合字符串;
/// 因此,批量处理时,可以据此把保存后返回的弹性域数据包(有内码),与原始数据包(无内码)对应起来
///
public static bool FlexValToString(Kingdee.BOS.Orm.DataEntity.DynamicObject flexFldDataObj,
out string flexValString)
{
// 维度组合是否为空串?默认为空,只有遇到了第一个不为空的维度后,才会被设为false
bool isNull = true;
StringBuilder sb = new StringBuilder();
foreach (var item in flexFldDataObj.DynamicObjectType.Properties)
{
// 内码("Id")会在保存前后,产生变化,由0变为100005,不需要拼接输出
// 维度值内码("_Id")与维度值数据包有重复,而且绝对不为空(最多为0),不好判断是否填写,所以取值时,使用维度值数据包
if (item.Name != "Id" && !item.Name.EndsWith("_Id"))
{
string val;
// 取本列值
object colValue = item.GetValue(flexFldDataObj);
// 把本列值转化为字符串
if (colValue == null)
{
val = "";
}
else if (colValue is Kingdee.BOS.Orm.DataEntity.DynamicObject)
{// 如果本列是复杂数据包,仅输出其内码
object pkvalue = ((Kingdee.BOS.Orm.DataEntity.DynamicObject)colValue)[0];
val = pkvalue == null ? "" : Convert.ToString(pkvalue);
}
else
{
val = Convert.ToString(colValue);
}
// 如果本列不为空,则整个组合不为空
if (!string.IsNullOrWhiteSpace(val))
{
isNull = false;
}
sb.Append("||").Append(val);
}
}
flexValString = sb.ToString();
// 输出是否有值
if (isNull)
{
// 无值,返回失败
return false;
}
else
{
return true;
}
}
}
[/code]
推荐阅读