本文介绍了动态字段系列中的新功能——聚合动态字段,该功能在Beta版发布于2021年10月,正式版发布于2021年11月。该功能主要用于解决客户在表头显示表体合计信息的需求,如数量合计、最大最小日期等,提供了按表达式规则聚合特征值计算并支持组内计算的功能。文中详细描述了设计界面、配置方法、表达式编写规则及相关函数,并展示了如何通过配置实现不同的聚合计算场景。同时,还提供了注意事项,包括表达式编写中的特定语法和函数使用。
<0>动态字段系列之聚合动态字段功能,Beta发布日期2021/10,正式发布日期2021/11,
不了解动态字段系列的可以先看原来的相关帖子,目前只支持单据
后续关于聚合动态字段的部分直接在这个帖子直接提问或者补充,将更多的案例和应用场景放在这个文章上
<1>背景:
客户在表头显示表体的合计信息,通常比如数量的合计,最大最小的日期等;在历史版本下通过以冗余字段或者打印二开干预,针对这类场景使用不方便;套打聚合动态字段应运而生
<2>定义:
套打聚合动态字段:针对数据进行分类并在分类中进行计算的动态字段场景,提供两个特性,支持按照表达式规则进行聚合特征值计算从而分类,支持按照在分类的数据包中进行组内计算。
或许看完这个定义还不怎么了解,先演示一个示例:
如下图所示,通过简单配置支持在表头中计算最大到货日期,输出统计数量,按条件输出统计数
<3>设计界面
设计器的入口与动态字段的一致,数据源右键增加实体动态字段,实体动态字段第二个页签进行配置,配置界面如下图所示:
分组字段标识:与动态字段标识一致,用作将数据绑定在控件上的输出标识,标识唯一(不能与业务对象和动态字段所冲突)
分组表达式:指定分组规则,在不指定时为所有数据位于一个分组;
聚合表达式:指定分组字段的计算规则,并将该值赋值到整组内的所有数据包上。
演示数据预览效果如下:
备注一:分组表达式+聚合表达式组成唯一的聚合字段,利用不同分组表达式实现单据统计,分组统计功能,参考上配置界面CTotalQty和GQty,在仅分组表达式不一致的情况下实现不同区域内的聚合计算,没有配置的为整个实体内统计(整单据体),否则按照配置规则进行聚合计算
备注二:聚合字段的实现本质上是将计算结果赋值到整组内的所有数据包上,也就是说所有数据行都能够显示对应的数据(请结合自己的需求进行配置);演示将聚合字段标识绑定到数据行,同样能够输出数据。
为什么会有这样的实现呢?如果你了解套打的实现逻辑,套打干预数据都是以数据源进行干预,也就是当且仅当在获取对应实体的数据时才能够进行聚合计算;当我们在控件绑定时(如文本控件),要了解到我们本质上是打印单据体的统计计算,而且如果单据换页打印时,第一页打印的是第一页首行分录的聚合字段,第二页打印的是第二页首行的聚合字段;能够更好的满足不同场景(如设置不同分组自动换页,表头绑定统计字段时,根据分组规则使表头也能根据分组统计)
备注三:注意动态字段所处于的实体,特别是在文本绑定跨实体动态字段目前需要使用GetDataSourcaValue()进行绑定,参考https://vip.kingdee.com/article/137611800287477504
备注四:如果是相同的分组处理,建议分组表达式完全相同,平台会优化计算将相同分组表达式的合并计算,减少中间数据生成的逻辑。
<4>表达式编写规则和相关函数
目前聚合动态字段的分组表达式以原动态字段语法进行计算,对应的数据处理是按当前行进行计算;而聚合动态字段的聚合表达式使用新的规则(python提供的集合访问处理函数+内层原动态字段的动态计算处理+聚合处理的平台封装函数实现)实现(从脚本编写的实现上与原保持一致);
由于测试需要,讲解一个python库函数的语法,用做数据演示
str.join(listObj)——支持将listObj中每个数据按str作为分隔符进行拼接;
(a)分组表达式与原动态字段计算逻辑一致,不做赘述;
(b)聚合表达式:访问字段的语法——直接访问,返回分组内的集合对象,需要搭配集合处理函数处理
示例:设置两表达式,一个直接绑定字段,另一个使用python的集合处理函数文本拼接进行演示;直接绑定字段的输出为List<object>,使用集合处理函数的能够按照分组内数据进行集合计算返回文本结果
(c)聚合表达式:访问子字段的语法——不支持以“.”直接访问,需要转换为原动态字段语法进行访问
前置知识一:python语法 map(lambda , listObj) 将集合对象listObj根据lambda表达式转为新的集合对象,类似C# 语法 IEnumerable.Select<TResult>得到一个IEnumerable<TResult>;
前置知识二:聚合表达式中的ActiveObject得到的是一个表达式适配器集合,其内层所有语法按照原动态字段的计算引擎计算(暂不支持原引擎的函数处理)
示例:将单位的编码进行拼接
直接使用”.”进行子属性访问,错误的语法提示如下(暂不考虑支持,避免开发代码冗余):
正确的表达式,利用ActiveObject和python的map进行集合计算得到新的集合,如下图所示直接使用map(lambda, ActiveObject)得到的是一个python引擎提供的集合对象(不用管语法,只需要知道实现了IList接口,后续平台提供的聚合函数也能以此作为参数);
(d)聚合表达式:行数据内的计算——依赖原动态字段计算引擎,使用map(lambda,ActiveObject)语法。
示例:取不同型号中的最小Pcs数(箱转Pcs),验证内层行数据支持计算
map(lambda x: str(round(x.FReqQty,0)) if x.FUnitId.FNumber == 'Pcs' else str(round(x.FReqQty * x.FUnitId.FConvertNumerator,0)),ActiveObject)
(e)聚合表达式,平台提供的库函数和python语法
求和:平台提供Sum()、SumPred();python提供 sum()
平台提供Sum(listObj)——传入参数为数值集合对象,返回decimal结果
Python提供sum(listObj)——参考表达式编辑器->服务说明->python函数->sum,不做赘述,返回结果的类型由python引擎输出
备注:为什么会提供平台版本:返回decimal数值,确保后续计算正常(不确定会有什么影响),历史分析过一例在数值精度太低后输出为1E-6的场景(原动态字段时强制将返回值转double,在聚合这边目前已这种方式转换,在计算过程中确保将每个数据转为decimal后再进行计算且返回decimal,避免在最大最小求和过程中以double float计算导致数据丢失),当然如果没有这样的场景时完全可以使用python提供的函数
前置知识三:Python语法集合过滤filter(lambda,listObj)——对集合进行过滤,返回满足lambda的对象集合
平台提供SumPred(listObj,predListObj)——传入参数listObj为数值集合对象,predListObj为判定集合,是否取当前行的数据进行计算(等价于过滤)
示例:演示过滤求和,仅计算单位为Pcs的数量求和
TestPython——
sum(map(lambda x: x.FReqQty, filter(lambda x: x.FUnitId.FNumber == 'Pcs', ActiveObject))),使用python的过滤filter和python的求和函数;
TestBosSum——
Sum(map(lambda x: x.FReqQty if x.FUnitId.FNumber == 'Pcs' else 0, ActiveObject))
使用平台的Sum函数,其中当单位非Pcs时使用0求和;(变通实现)
TestBosSumPred——
SumPred(FReqQty , map(lambda x:x.FUnitId.FNumber == 'Pcs', ActiveObject)),使用平台的SumPred函数,从语法上看起来应该更加简单
备注:每一个平台函数都提供了原函数版本和FuncPred()断言版本,支持做计算判断;
最大/最小值:平台函数Max()、Min()、MaxPred()、MinPred(),python提供函数max() min();
示例,按物料+型号分组,计算单位为Pcs的数量的最小值:
TestPython——
min(map(lambda x: x.FReqQty, filter(lambda x: x.FUnitId.FNumber == 'Pcs', ActiveObject))),使用python的过滤filter和python的min函数
TestBosMin——
Min(map(lambda x: x.FReqQty if x.FUnitId.FNumber == 'Pcs' else 2147483647, ActiveObject)),使用平台的Min函数,其中不满足的使用最大值替代
TestBosMinPred——
MinPred(FReqQty , map(lambda x:x.FUnitId.FNumber == 'Pcs', ActiveObject)),使用平台的MinPred函数
备注:平台提供的Sum函数仅支持数值,Max/Min函数支持数值和日期;python提供的库函数由python支持,平台不做调整
首行/末行函数:平台First()/Last()函数,判定LastPred()/FirstPred(),python [0] [-1]
示例,取最后一行单位为Pcs的数量:
TestPython——
map(lambda x: x.FReqQty, filter(lambda x: x.FUnitId.FNumber == 'Pcs', ActiveObject))[-1]
TestBos——
Last(map(lambda x: x.FReqQty, filter(lambda x: x.FUnitId.FNumber == 'Pcs', ActiveObject)))
TestBosPred——
LastPred(FReqQty , map(lambda x:x.FUnitId.FNumber == 'Pcs', ActiveObject))
去重函数:平台Distinct(),python set()
前置知识四:python set(listObj)去重,去重后乱序,
前置知识五:python sorted(listObj, key = lambda),支持按指定字段排序
前置知识六:python listObj.index(val),找到val值在集合中的序号
示例,将单据体去重的字段拼接显示到表头:
TestPython——
','.join(set(FEntryNote)),使用python直接去重拼接,去重后不再按照行号顺序
TestPython2——
','.join(sorted(set(FEntryNote), key =lambda x: list(FEntryNote).index(x))),使用python直接去重,并按照原数据找到对应的行号,按照行号排序,最后拼接
TestBos——
','.join(Distinct(FEntryNote)),使用平台的去重函数Distinct
<5>其他注意事项
(a)由于表达式直接访问字段时为集合对象,当想直接拼接输出时,必须指定使用组内的首行、尾行还是第几行(目前即使针对分组表达式也一样)
推荐阅读