Sailing - 备忘 - 对初始化器的反思
Sailing
目前方案及困境
初始化器这个名字取得不好. 其实, 除了初始化实体数据的工作外, 它还可以更新实体内容; 除了从空白开始创建实体外, 还可以从其他实体作为参考来对实体赋值.
总结一下目前初始化器的使用场景, 在单据的新建, 从其他单据新建, 选择商品后添加, 下推, 根据上游单据更新等处都使用了初始化器.
- 单据新建
- 直接调用初始化器, 初始化器中初始化化主表和明细表
- 单据转换
- 从其他单据新建, 选择主表
以主表ID所指向的完整实体为参考, 初始化主表和明细表,
- 从其他单据新建, 选择主表和明细表
以明细表所指向的实体(多个)为参考, 初始化明细表, 并根据其中一个主表来初始化
- 添加明细, 选择主表
- 添加明细, 选择主表和明细表
- 下推直接生成
- 根据上游单据更新主表和明细...
以上是目前已经使用初始化器完成的功能. 还有其他场景也需要类似初始化的功能, 例如数据智能导入和财务智能凭证.
- 财务智能凭证
- 一个对象 -> 平面对象或一个对象
- 多个对象 -> 平面对象或一个对象
- 一个对象 -> 平面对象或多个对象
- 多个对象 -> 平面对象或多个对象
- 数据导入
- 一个表单+多个平面表 -> 一个对象 (某个单据导入, 一个sheet页放条款, 一个sheet页放商品明细)
- 一个平面表 -> 多个对象 (预录单数据导入, 预录单导入格式中, 主表信息也冗余放在明细中)
- 单据转换
- 一个对象 -> 多个对象 (下推分单)
- 多个对象 -> 一个对象 (明细单从外销合同选单新建)
总结一下上述场景中的数据来源:
- 有多个数据源
- 每个数据源可能是一个对象, 也可能是一个数组(平面表)
- 每个对象可能是简单实体, 也可能带有层次结构的复杂实体.
总结一下数据目标:
- 只会有一个数据目标
- 这个目标可能是一个对象, 也可能是一个数组(平面表)
- 每个对象可能是简单实体, 也可能带有层次结构的复杂实体.
而各种场景下转换规则的变化十分大.
明细单选外销合同后新建(来源数据组织成一个对象数组, 目标单据为一个对象)
- 聚合对象数组的根对象内容, 生成明细单根实体;
- 聚合对象数组的[明细]对象内容, 生成明细单明细实体;
- 将上一步中生成的对象数组, 根据分组key(外销合同号), 装配到明细单根实体中;
明细单选外销合同后新建(来源数据组织成两个对象数组, 一个数据源根实体数组, 一个数据源明细实体数组; 目标单据为一个对象)
- 对数据源根实体数组分组, 没有分组依据, 也就是所有的分为一组;
- 遍历分组, 对每个分组聚合数据源, 生成根对象;
- 遍历数据源明细实体数组, 对每条明细生成目标明细, 并装配入前一个对象;
外销合同下推生成采购合同, 分单. (来源数据为一个对象(数组), 目标单据为一个对象数据)
- 对对象数组的[明细]对象内容, 根据[生产厂家]分组;
- 对每个分组生成一个采购合同;
- 采购合同头使用分组内的聚合信息;
- 采购合同明细使用分组中的明细;
明细单新建后, 再从合同中选择添加商品(来源数据组织成一个对象数组, 目标为明细对象数组)
导入预录单数据(来源数据为一个对象数组, 目标数据为一个复杂对象数组)
- 对来源数据分组(根据报关单号);
- 对每个分组:
- 聚合前几列信息, 生成预录单根实体;
- 遍历分组中的项, 生成预录单明细实体;
导入某个单据模板(来源数据为一个表单(单一对象), 一个对象数组(商品列表)和第三层对象数组(组件列表)的组合, 目标数据为一个复杂对象)
- 取第一个数据来源, 单个对象;
- 用这个数据源生成目标根对象;
- 取第二个数据来源, 对象数组;
- 遍历生成明细对象;
- 装配到目标根对象中;
- 取第三个数据来源, 对象数组;
- 根据商品编码分组;
- 遍历每个组件分组, 生成组件对象数组;
- 根据分组key, 装配到商品对象中;
简单的智能凭证(来源数据为N组对象, 目标数据为一个复杂对象)
- 生成凭证根实体对象;
- 取一个数据来源;
- 对这个数据来源过滤;
- 对这个数据来源分组;
- 对每个分组生成凭证明细;
- 把凭证明细装配到凭证根实体中;
批量智能凭证(来源数据为多个N组对象, 目标数据为多个复杂对象)
- 遍历来源数据;
- 接下去同简单的智能凭证;
新方案探索
以上这些生成规则中, 比较麻烦的一个步骤是二级甚至是三级子对象的装配. 这恰恰是现有初始化器的优势.
现有初始化器根据目标结构来进行导航, 正对每个不同的字段类型, 调用恰当的初始化规则, 如果是一对一子对象, 则新建对象; 如果是一对多集合对象, 则新建对象并加入集合.
能不能继续使用这个结构? 还能解决上面的场景?
其实这个结构的一个不足是无法生成多个目标对象, 如果可以解决这个问题, 那么其他应该都能适应, 用下面的场景验证下:
明细单选外销合同后新建(来源数据组织成一个对象数组, 目标单据为一个对象)
- 对外销合同数组分组, 分组依据为空, 所有合同一个分组;
- 遍历分组, 每个分组产生一个明细单实体; 用first等聚合规则来取值初始化字段; (考虑到first十分常用, 是否有简化的办法?)
- 明细单的商品明细字段:
- 在同一个上下文中, 用qsexpr导航到外销合同明细(数组的数组), 获取所有合同的所有的商品;
- 遍历上述商品, 调用初始化规则集;
明细单选外销合同后新建(来源数据组织成两个对象数组, 一个外销合同数组, 一个外销合同明细实体数组; 目标单据为一个对象)
- 对外销合同数组分组, 分组依据为空, 所有合同一个分组;
- 遍历分组, 每个分组产生一个明细单实体; 用first等聚合规则来取值初始化字段; (考虑到first十分常用, 是否有简化的办法?)
- 明细单的商品明细字段:
- 数据源切换到使用外销合同明细数组;
- 遍历外销合同明细, 调用规则;
外销合同下推生成采购合同, 分单(来源数据为一个对象(数组), 目标单据为一个对象数据)
- 数据源为那一个外销合同对象, 用qsexpr导航到明细商品列表;
- 对上述数据分组, 分组依据为生产厂家;
- 对每个分组, 生成一个采购订单实体;
a. 用first聚合函数获得某行, 用owner导航到外销合同主表, 然后调用规则初始化;
b. 切换数据源到根对象, 然后调用规则初始化;
- 初始化采购合同明细时:
- 遍历分组内的外销合同商品明细, 并调用规则;
明细单新建后, 再从合同中选择添加商品(来源数据组织成一个对象数组, 目标为明细对象数组)
- 数据源用qsexpr导航到选中的商品;
- 遍历商品, 并调用规则;
导入预录单数据(来源数据为一个对象数组, 目标数据为一个复杂对象数组)
- 对数据源分组, 分组依据为报关单号;
- 遍历分组, 每个分组产生一个预录单对象;
- 初始化预录单明细时:
- 对分组内的每条数据, 产生一条明细, 并调用规则;
导入某个单据模板(来源数据为一个表单(单一对象), 一个对象数组(商品列表)和第三层对象数组(组件列表)的组合, 目标数据为一个复杂对象)
- 切换到第一个数据源;
- 调用规则生成根实体;
- 初始化第二层对象时:
- 数据源切换到第二个;
- 遍历数据源, 每条生成一个, 并调用规则;
- 初始化到第三层对象时:
- 数据源切换到第三个;
- 过滤数据源, 仅保留商品编码==上下文中的商品编码的;
- 遍历数据源, 每条生成一个, 并调用规则;
简单的智能凭证(来源数据为N组对象, 目标数据为一个复杂对象)
- 切换到第一组数据源;
- 有选择的过滤数据源;
- 有选择的分组数据源;
- 如果分组了, 遍历分组; 如果没分组, 遍历数据源;
- 调用规则;
批量智能凭证(来源数据为多个N组对象, 目标数据为多个复杂对象)
暂不考虑, 通过为每份数据源调用一遍规则来实现;
从上述规则执行过程的描述来看, 新方案和原有初始化器最大的区别在于对数据源的处理.
新方案允许实体节点规则和集合节点规则可以更自由的确定数据源.
- 切换数据源
- 在上下文中根据path导航
- 切换另一个数据源, 并根据path导航
- 对数据源过滤(可选)
- 对上一步中的数据源根据指定条件过滤. 条件表达式中可以带有上下文信息.
- 如果不过滤, 则使用完整的数据源参与下一步处理.
- 对数据源分组(可选)
- 正常模式: 根据指定分组线索对上一步中的数据源分组, 分组线索表达式中可以带有上下文信息.
- 合并模式: 实际上的效果是把所有的数据分在同一个默认分组中.
- 拆分模式: 为每条数据创建了一个单独的分组.
- 遍历每个分组(如果是一对一节点, 则只允许一个分组, 超过一个就报错), 执行以下动作:
- 对分组内数据过滤(可选)
- 对每个分组中的数据, 根据执行的条件过滤. 条件表达式中也可以带有上下文信息.
- 如果不过滤, 则使用分组内的完整数据源参与下一步处理.
- 创建对应的目标对象
新方案的另一个改进是需要支持多个多种数据源. 一次转换会涉及到多个数据源, 每个数据源可能是单一实体, 实体数组, 二维表对象中的一种.
- 数据源使用
:root
来访问。任何场景都可以使用:root
访问传入的原始数据源。
- script规则中,
s.XXX
表示分组后的当前某项,s也是一个数组;
- script规则中,
f.XXX
表示s中第一行数据的XXX,是s.first().XXX
的简写形式。
- script规则中,
this
则表示上一级未分组过的数据。
新方案下的各场景规则设置:
明细单选外销合同后新建(来源数据组织成一个对象数组, 目标单据为一个对象)
- 明细单根对象(entity): 数据源[:root], 分组模式[合并]
- 明细单数量合计(field): script[#sum(s.商品明细.金额)]
- 起运港(field): copy[起运港] //复制模式下, 默认去分组中的第一行数据的XXX. 下同
- 成本预测(entity): 数据源[成本预测], 分组模式[合并]
- 商品明细(collection):
- (entity): 数据源[商品明细], 分组模式[拆分]
- XX(field): copy[xxx]
- XX(field): copy[xxx]
明细单选外销合同后新建(来源数据组织成两个对象数组, 一个外销合同数组, 一个外销合同明细实体数组; 目标单据为一个对象)
- 明细单根对象(entity): 数据源[:root.外销合同], 分组模式[合并]
- 明细单数量合计(field): script[#sum(:root.外销合同明细.金额)]
- 起运港(field): copy[起运港]
- 成本预测(entity): 数据源[成本预测], 分组模式[合并]
- XX(field): script[#sum(s.外销合同明细)]
- 商品明细(collection):
- (entity): 数据源[:root.外销合同明细], 分组模式[拆分]
- XX(field): copy[xxx]
- XX(field): copy[xxx]
明细单新建后, 再从合同中选择添加商品(来源数据组织成一个对象数组, 目标为明细对象数组)
- 明细单商品(entity): 数据源[:root.外销合同明细], 分组模式[拆分]
- XX(field): copy[xxx]
- XX(field): copy[xxx]
外销合同下推生成采购合同, 分单(来源数据为一个对象(数组), 目标单据为一个对象数据)
- 采购订单根对象(entity): 数据源[:root.商品明细], 分组模式[普通 group=生产厂家]
- 销货方(field): copy[生产厂家]
- 出口国家(field): copy[owner.出口国家]
- 商品明细(collection):
- (entity): 数据源[] 分组模式[拆分]
- XX(field): copy[xxx]
- XX(field): copy[xxx]
导入预录单数据(来源数据为一个对象数组, 目标数据为一个复杂对象数组)
- 预录单数据(entity): 数据源[:root.第一页], 分组模式[普通 group=报关单号]
- 报关单号(field): copy[报关单号]
- 报关明细(collection):
- (entity): 数据源[] 分组模式[拆分]
- XX(field): copy[xxx]
- XX(field): copy[xxx]
导入某个单据模板(来源数据为: 第一页-一个表单(单一对象), 第二页-一个成本核算部分信息, 第三页-一个对象数组(商品列表), 第四页-第三层对象数组(组件列表)的组合, 目标数据为一个复杂对象)
- 根实体(entity): 数据源[:root.第一页], 分组模式[合并]
- XX(field): copy[xxx]
- XX(field): script[#first(:root.第二个).XX]
- 成本核算(entity): 数据源[:root.第二个], 分组模式[合并]
- 商品(collection):
- (entity): 数据源[:root.第三个] 分组模式[拆分]
- XX(field): copy[xxx]
- 附加信息(entity): 数据源[], 分组模式[合并]
- 组件信息(collection):
- (entity): 数据源[:root.第四个 filter=>s.商品编码==#first(商品编码)] , 分组模式[拆分]
简单的智能凭证(来源数据为N组对象, 目标数据为一个复杂对象)
- 凭证分录(entity): 数据源[:root], 分组模式[合并]
- 凭证分录(entity): 数据源[:root.XX], 分组模式[拆分]
- 凭证分录(entity): 数据源[:root.XX.XX], 分组模式[拆分]
- 凭证分录(entity): 数据源[:root.XX.XX], 分组模式[普通 owner.编码]
批量智能凭证(来源数据为多个N组对象, 目标数据为多个复杂对象)
暂不考虑, 通过为每份数据源调用一遍规则来实现;
其他事项
- 多个相关字段, 同时初始化.
- 目前, 单据转换中自建了一套层次结构设置, 每层各自指定了初始化器, 而没有用到初始化器内部的层次结构. 探讨这两者的利弊.