本文介绍了如何在工作流中实现单据下推操作,并特别说明了如何在下推过程中将源单的附件信息同步到目标单。文章首先概述了背景问题,然后详细阐述了实现思路,包括工作流插件的使用、单据转换规则的设定以及单据转换插件的编写。接着,文章详细描述了实现过程,包括准备工作、流程服务的设置、工作流插件的编写、单据转换规则的创建及单据转换插件的编写。文章通过代码示例展示了如何构造下推数据、设置下推参数、调用单据转换帮助类完成下推操作,并提供了完整的插件代码和详细的步骤说明。最后,文章还提到了目标单pkid的重要性及其对附件绑定的影响。
看完文章,点赞收藏,谢谢各位大佬!
一、背景
最近有伙伴在工作流中做下推操作(即进行单据转换),问我能不能在下推时,把附件面板上面的附件,由源单下推到目标单。
经过验证,我写了一个例子,介绍怎么在工作流中进行下推操作,并且下推的同时,把附件面板的数据下推到目标单。
二、实现思路
1 工作流
在工作流中做下推操作,可用工作流插件实现。
在审核节点中,在“同意”时,注册一个工作流插件,然后在工作流插件中使用ConvertServiceHelper(单据转换帮助类)进行单据转换。
2 单据转换规则
无论是在设计器页面中配置下推操作还是用代码调用ConvertServiceHelper,都需要创建一个单据转换规则。
如果是界面化配置的转换规则,只能进行一些单据头字段或者单据体字段的转换。
3 单据转换插件
为了能让 附件面板从源单下推到目标单,需要用单据转换插件来实现,因此需要编写和注册一个单据转换插件。
在单据转换插件中,从源单中获取到附件信息,然后用AttachmentServiceHelper(附件帮助类),把源单的附件信息绑定给目标单。
三、实现过程
1.准备工作
创建2个单据,用来做工作流和单据转换
2.流程服务云,创建工作流
新建工作流,
选择刚刚创建好的“源单” 单据,填写一些必填项目
创建之后如下图,进入工作流设计器
在审批节点中,在任务处理时执行,注册java插件
审核同意时,执行这个工作流插件:
配置节点参与人:
点击保存和发布之后,工作流就生效了
3.编写工作流插件代码
构造下推数据:
Long pkid = execution.getId();// 单据pkid /*String billNumber = execution.getEntityNumber();// 单据标识 String billNo = ((ExecutionEntityImpl) execution).getBillNo();// 单据编码*/ List<ListSelectedRow> selectedRows = new ArrayList(); ListSelectedRow selectedRow = new ListSelectedRow(pkid); selectedRows.add(selectedRow);
构造PushArgs对象,准备下推
// 生成下推参数PushArgs PushArgs pushArgs = new PushArgs(); // 必选,源单标识 pushArgs.setSourceEntityNumber("kdec_wrx_source"); // 必选,目标单标识 pushArgs.setTargetEntityNumber("kdec_wrx_target"); // 是否输出详细错误报告 pushArgs.setBuildConvReport(false); // 必选,设置需要下推的源单及分录内码 pushArgs.setSelectedRows(selectedRows);
下推,以及下推结束之后,打印下推结果,
注意:这里我调用的方法是pushAndSave,下推并保存。
因为在工作流里面,不能进行图形界面化的操作,所以我选择了下推并保存,
如果是在普通的单据页面上进行下推,开发者可以选择调用push方法,生成缓存的目标单据后,弹出到目标单据里面,让用户自己去保存。
// 调用单据转换帮助类,下推目标单 并保存 ConvertOperationResult pushResult = ConvertServiceHelper.pushAndSave(pushArgs); if ( pushResult.isSuccess() ) { System.out.println("DemoBOTPWorkflowPlugin: kdec_wrx_source 转换kdec_wrx_target 成功"); Set<Object> targetBillSet = pushResult.getTargetBillIds(); StringBuilder builder = new StringBuilder(); for (Object o : targetBillSet) { builder.append(o.toString() + ", "); } System.out.println("下推成功,目标单id:" + builder.toString()); } else { System.out.println("DemoBOTPWorkflowPlugin: kdec_wrx_source 转换kdec_wrx_target 失败"); }
完整代码如下
package kd.ecos.demo; import kd.bos.entity.botp.runtime.ConvertOperationResult; import kd.bos.entity.botp.runtime.PushArgs; import kd.bos.entity.datamodel.ListSelectedRow; import kd.bos.servicehelper.botp.ConvertServiceHelper; import kd.bos.workflow.api.AgentExecution; import kd.bos.workflow.engine.extitf.IWorkflowPlugin; import java.util.ArrayList; import java.util.List; import java.util.Set; // 工作流插件,任务处理时执行,同意时,产生下推操作 // 流程中产生下推操作,下推操作再进行附件面板下推 public class DemoBOTPWorkflowPlugin implements IWorkflowPlugin { @Override public void notify(AgentExecution execution) { System.out.println("DemoBOTPWorkflowPlugin, notify"); // 单据pkid Long pkid = Long.valueOf(execution.getBusinessKey()); // 单据标识 //String billNumber = execution.getEntityNumber(); // 单据编码 //String billNo = ((ExecutionEntityImpl) execution).getBillNo(); List<ListSelectedRow> selectedRows = new ArrayList(); ListSelectedRow selectedRow = new ListSelectedRow(pkid); selectedRows.add(selectedRow); // 生成下推参数PushArgs PushArgs pushArgs = new PushArgs(); // 必选,源单标识 pushArgs.setSourceEntityNumber("kdec_wrx_source"); // 必选,目标单标识 pushArgs.setTargetEntityNumber("kdec_wrx_target"); // 可选,自动保存 pushArgs.setAutoSave(true); // 可选,设置单据转换规则的id,如果没有设置,会自动匹配一个规则进行转换 pushArgs.setRuleId("1134727974310918144"); // 是否输出详细错误报告 pushArgs.setBuildConvReport(false); // 必选,设置需要下推的源单及分录内码 pushArgs.setSelectedRows(selectedRows); // 调用下推引擎,下推目标单并保存 ConvertOperationResult pushResult = ConvertServiceHelper.pushAndSave(pushArgs); if ( pushResult.isSuccess() ) { System.out.println("DemoBOTPWorkflowPlugin: kdec_wrx_source 转换kdec_wrx_target 成功"); Set<Object> targetBillSet = pushResult.getTargetBillIds(); StringBuilder builder = new StringBuilder(); for (Object o : targetBillSet) { builder.append(o.toString() + ", "); } System.out.println("DemoBOTPWorkflowPlugin:下推成功,目标单id:" + builder.toString()); } else { System.out.println("DemoBOTPWorkflowPlugin: kdec_wrx_source 转换kdec_wrx_target 失败"); } } }
4.创建单据转换规则
这一步,一共要配置几个地方:
(1)配置源单的单据头,和目标单的单据头进行转换
(2)配置源单的单据编码和组织,直接下推到目标单
(3)配置源单的一个文本字段,使用上 小写函数,把字段值转换为小写,下推到目标单
(4)注册单据转换插件
做一个单据头的转换
配置单据编码转换
配置普通文本字段,使用大写转换为小写的函数:
注册单据转换插件
5.编写单据转换插件代码
在afterConvert事件中,可以在单据转换之后,获取到目标单和源单信息
@Override public void afterConvert(AfterConvertEventArgs e) { ExtendedDataEntitySet targetExtDataEntitySet = e.getTargetExtDataEntitySet(); // 获取转换后的目标单 ExtendedDataEntity[] extendedDataEntities = targetExtDataEntitySet.FindByEntityKey("kdec_wrx_target"); }
必须给目标单设置一个pkid。
注意:如果目标单没有pkid,附件面板的数据是没有办法绑定到目标单据上的。
long id = DB.genGlobalLongId(); dynamicEntity.set("id", id);
注意:这个数据对象有没有id,取决于之前在工作流插件中,调用ConvertServiceHelper.push还是ConvertServiceHelper.pushAndSave。
如果是调用pushAndSave方法,由于单据转换结束时,这个单据已经保存了,所以是有id的。
用一个if代码块判断有没有id,如果没有,就设置一个进去,代码如下:
下面这个判断代码可不写,我为了防止流程中出现一些异常情况,还是加了一个判断id的if代码块:
if (dynamicEntity.get("id") instanceof Long && ((Long) dynamicEntity.get("id")) < 1) { ...... }
遍历目标单,获取对应的源单,如果是一对一的转换,源单的列表长度都是1
// 获取源单数据 List<DynamicObject> convertSource = (List<DynamicObject>) extendedDataEntity.getValue("ConvertSource"); // 遍历源单数据 for (DynamicObject dynamicObject : convertSource) { // 获取源单id,通过源单id获取源单的附件信息 long sourceId = dynamicObject.getLong("id"); List<Map<String, Object>> attachments = AttachmentServiceHelper.getAttachments("kdec_wrx_source", sourceId, "attachmentpanel"); // 把源单的附件信息设置给目标单 AttachmentUntil.uploadTargetAttachments("kdec_wrx_target", dynamicEntity.get("id"), "attachmentpanel", attachments); }
完整代码如下,AttachmentUntil代码见附件
package kd.ecos.demo; import kd.bos.dataentity.entity.DynamicObject; import kd.bos.db.DB; import kd.bos.entity.ExtendedDataEntity; import kd.bos.entity.ExtendedDataEntitySet; import kd.bos.entity.botp.plugin.AbstractConvertPlugIn; import kd.bos.entity.botp.plugin.args.*; import kd.bos.servicehelper.AttachmentServiceHelper; import kd.ecos.utils.AttachmentUntil; import java.util.List; import java.util.Map; public class DemoConvertPlugIn extends AbstractConvertPlugIn { /** * 单据转换后事件,最后执行 * * @param e * @remark 插件可以在这个事件中,对生成的目标单数据,进行最后的修改 */ @Override public void afterConvert(AfterConvertEventArgs e) { ExtendedDataEntitySet targetExtDataEntitySet = e.getTargetExtDataEntitySet(); // 获取转换后的目标单 ExtendedDataEntity[] extendedDataEntities = targetExtDataEntitySet.FindByEntityKey("kdec_wrx_target"); // 遍历目标单 for (ExtendedDataEntity extendedDataEntity : extendedDataEntities) { // 目标单数据 DynamicObject dynamicEntity = extendedDataEntity.getDataEntity(); // 判断有没有id,如果没有,要设置一个。在附件绑定时,附件面板不能绑定没有id的单据 // 这个单据转换的是用代码调起的,有没有id取决于调用 ConvertServiceHelper.push还是 ConvertServiceHelper.pushAndSave // 如果是调用pushAndSave方法,那么此时目标单已经保存,就不需要设置id,if里面的代码块会跳过 if (dynamicEntity.get("id") instanceof Long && ((Long) dynamicEntity.get("id")) < 1) { long id = DB.genGlobalLongId(); dynamicEntity.set("id", id); } // 获取源单数据 List<DynamicObject> convertSource = (List<DynamicObject>) extendedDataEntity.getValue("ConvertSource"); // 遍历源单数据 for (DynamicObject dynamicObject : convertSource) { // 获取源单id,通过源单id获取源单的附件信息 long sourceId = dynamicObject.getLong("id"); List<Map<String, Object>> attachments = AttachmentServiceHelper.getAttachments("kdec_wrx_source", sourceId, "attachmentpanel"); // 把源单的附件信息设置给目标单 AttachmentUntil.uploadTargetAttachments("kdec_wrx_target", dynamicEntity.get("id"), "attachmentpanel", attachments); } } } }
四、效果展示
新增“源单”单据,填写单据编号,文本写上大写字母,添加一些附件图片。
点击“保存”和“提交”按钮。
当前用户接收到了审批消息
进入消息中心,同意这条审核单据
查看日志打印,显示下推成功
查看单据转换结果:
查看源单状态,已经是“已审核”状态
查看目标单列表,发现已经下推成功
进入目标单详情,发现源单的两个附件已经绑定在目标单上了
进入开发平台,查看附件明细表:
发现两张单据确实已经绑定在了目标单的附件面板上
五、附件
工作流已上传:Proc_kdec_wrx_source_audit_1.zip,
注意:如果参与人配置的人员,在导入环境里面没有,需要重新配置工作流。
源代码已上传,页面的元数据以补丁包的方式上传,都在压缩包code_meta.zip里面。
code_meta.zip(134.17KB)
Proc_kdec_wrx_source_audit_1.z …(3.78KB)
推荐阅读