【Python插件入门】第6篇:操作服务插件原创
金蝶云社区-CQ周玉立
CQ周玉立
201人赞赏了该文章 17182次浏览 未经作者许可,禁止转载编辑于2022年08月22日 17:25:45
封面

往期回顾:

【Python插件入门】第1篇:Python插件入门讲解

【Python插件入门】第2篇:基本开发过程介绍

【Python插件入门】第3篇:插件中如何进行数据操作

【Python插件入门】第4篇:单据表单插件

【Python插件入门】第5篇:单据列表插件


       前面介绍了表单插件和列表插件,属于Web层插件,那么我们来接触一下APP层插件--操作服务插件,这个插件学习起来更容易理解和掌握 ,因为他的事件相对比较单一,运行机制也没那么复杂,但是他的功能其实很强大,应用场景也非常广泛,是学习插件开发非常必要掌握的一个插件类型。

一、单据操作

        在讲解操作服务服务插件之前,我们必须要先来了解一下单据操作,如果说表单插件和列表插件是依赖于单据界面动作触发,那么操作服务插件就是依赖于单据操作触发。所以操作服务插件是绑定到单据操作上的,我们先来了解一下单据操作。系统内置160多种操作类型,支持单据菜单、列表菜单、单据体菜单调用实现功能,另外,还预置了空操作,更方便自定义操作的封装。单据操作可以理解成一个独立运行的任务,对于单据操作,主要还是对实体进行逻辑处理,有针对整个单据的,例如,保存、提交、审核、删除等,也有针对单据体分录实体的,例如新增分录、删除分录、批量填充等等,也有既能针对单据体分录也能针对整单处理的操作,例如状态转换等等。

  •     我们在使用的时候需要根据不同应用场景使用不同的操作类型,单据操作具备下图4个特性:

image.png

  • 单据操作列表查看、编辑操作截图:


image.png

二、操作服务插件简介

        在C#插件开发时,操作服务插件的基类是AbstractOperationServicePlugIn,绑定在操作上,所以操作服务插件针对的对象是操作,用于对于某个操作进行扩展或者对操作执行过程干预,可以与校验器配合使用。操作服务插件可以对标准业务对象没有实现的功能控制进行补充,也可以已有操作和服务未支持的功能进行自定义,甚至可以定义自己的操作类型。开发过程快速,并且复用性很高,一个操作服务插件可以封装成通用的功能,多单据使用,甚至多操作使用,结合前面已经介绍的表单插件和列表插件,我们来看一下操作服务插件的运行机制(参考下图)。

       从下图看来,我们把操作服务插件的执行过程理解成一个"流水线作业",会依次经历各个环节,这些环节就是操作服务插件中的事件,某一个事件不会独立触发,这是与表单插件列、表插件在运行过程上的显著区别,在二开操作服务插件中,我们可以在不同的环节进行干预,以此来影响整个执行过程达到我们想要的功能效果。image.png

  • 事务:操作服务插件中有部分环节是以事务机制运行,这也是我们常用来二开的部分,在事务运行过程中,会有时长下限制,默认是10min,所以我们不要在事务部分执行时间过长的任务,另外,事务中执行发生异常,支持回滚。

  • 操作服务插件常用成员介绍:要注意!!!操作服务插件没有View成员了!

    插件中同样有this.Context,这个在前面的篇章中介绍了,用法类似,不再过多介绍。

    this.BusinessInfo.GetForm().Id;#执行当前操作的单据FormId
    this.BusinessInfo.GetBillNoField().FieldName;#单据编号字段名
    this.BusinessInfo.GetBillStatusField().FieldName;#单据状态字段名
    this.BusinessInfo.GetBillTypeField().FieldName;#单据类型字段名
    this.BusinessInfo.MainOrgField.FieldName;#主业务组织字段名
    this.BusinessInfo.GetEntity("FBillHead").TableName;#单据头表名
    this.BusinessInfo.GetEntity("实体标识").TableName;#实体主表名
    this.BusinessInfo.GetEntity("实体标识").SplitTables;#实体所有拆分表

    this.BusinessInfo.GetField("字段标识");#获取字段元素
    #********************************************************************
    this.FormOperation.Operation;#当前执行的操作代码
    this.FormOperation.OperationName.GetString(2052);#当前执行的操作中文名称
    this.FormOperation.OperationId;#当前执行的操作Id
    this.FormOperation.PermissionItemId;#当前操作绑定的权限项Id
    this.FormOperation.ServicePlugins;#当前执行操作下所有的操作服务插件集合

    #********************************************************************
    this.Option;#当前操作执行时的选项参数,代码调用单据操作时,也可以通过此变量传入自定义参数
    this.Option.ContainsVariable("参数标识");#判断是否存在某个参数,返回true或false
    DIC=this.Option.GetVariables();#获取所有的参数,字典类型,根据参数标识获取

    a=DIC["参数标识"];#获取某个参数

    IsIgnoreWarning=DIC["IgnoreWarning"];#是否忽略警告提示,返回true或false
    selectRows=DIC["_BillOperationSelectedRows_"];#如果是单据体行操作可以从这里获取选中行
    for r in selectRows:
        billId=r["PrimaryKeyValue"];#单据ID
        billId=r["EntryPrimaryKeyValue"];#单据体分录ID,若为整单操作,此值为null

#添加引用,也可以通过如下方法获取选中行:

clr.AddReference('Kingdee.BOS.Core')

from Kingdee.BOS.Core.DynamicForm import *

#获取选中行:

    SelectedRows=OperationResultExt.GetBillOperationSelectedRows(this.Option);

  • 使用操作服务插件的优势:

    操作服务插件在单据列表和单据维护界面都可以触发。当开发表单插件和操作服务插件都可以实现的功能时,例如,单据审核之后执行一项任务,这时优先选择操作服务插件。

       ②代码量低,操作服务插件可在权限控制或者一系列校验之后再执行,而80%+的校验逻辑可以通过BOS配置实现。

       ③外部调用方便,不管是WebAPI调用还是其他插件调用,都可以很方便的调用。

       ④插件复用性强,例如,A单据审核之后要完成某个任务,B单据也要此功能,可以快速的复用过去。

三、注册操作服务插件

    操作服务插件是依赖于单据操作触发的,所以我们的操作服务插件是注册到单据操作下面的。

    这里提供一个引用比较全的Python操作服务插件示例,在附件中下载示例代码,复制到BOS里面注册!

  •     新建一个操作服务插件,以采购订单审核操作为例。

    image.png

四、操作服务插件事件介绍

    操作服务插件中的事件是有严格执行顺序的,看前面的运行时机图可以很清晰的看出事件执行顺序,这里不过多阐述。

  • 操作服务插件常用事件用法介绍:下面我们来看看二开常用的事件

#选项设置,在操作服务初始化时执行
#可以对操作的执行参数进行设置,比如是否是否启动事务,是否支持批量处理等
def OnPrepareOperationServiceOption(e):
    e.SupportTransaction=True;#是否启动事务,默认true
    e.SurportBatchTransaction=True;#是否支持批量处理,默认true


#字段预加载事件,这是一个非常必要使用的事件
#出于性能考虑,服务插件并不会加载单据完整的数据包,只有默认加载单据编号、单据ID等一些关键字段
#在插件中需要读取的其他字段信息,需要在此事件中先加载,方法也很简单。
#如果在后续事件中取单据字段时,报错提示字段标识不存在时,可以看是否在这里进行预加载
def OnPreparePropertys(e):
    e.FieldKeys.Add("FBillTypeID");
    e.FieldKeys.Add("字段标识");#这里使用的是字段标识,后面从数据包取值用的是绑定实体属性


#校验器干预,非必要,需要使用的时候可以实现这个事件
#可以这里添加自定义的校验器,或者对已有校验器进行取消干预
#如果校验器执行不通过,将不会继续往后执行,一般配置不能实现的自定义校验逻辑在此实现
#只有在APP层执行的操作才会默认执行校验器,例如,保存、提交、审核、删除等。
#其他在Web层执行界面交互的操作默认不会执行,例如下推、选单、复制等,可通过表单插件强行执行绑定在操作上的校验器。
#校验器需要单独定义一个类,将类的对象加载到此事件中使用,校验器类定义参考
#Python校验器开发案例
def OnAddValidators(e):
    e.Validators;#已有校验器


#执行操作事务前事件,通知插件对要处理的数据进行排序等预处理(事务外触发)
#此事件在操作校验之后、操作实现之前执行
#此事件在操作事务之前,即此事件中的数据库处理,不受操作的事务保护
def BeforeExecuteOperationTransaction(e):
    e.Cancel = False;#可以再这里做一些强制的校验,可取消操作继续执行
    if(e.Cancel==True):
        e.CancelMessage=("{0}被取消了!").format(this.FormOperation.OperationName.GetString(2052));

#操作事务前事件(事务内触发),支持回滚
#此事件在操作校验之后,在操作事务开始之后,在操作执行之前
#此事件中的数据库处理,受操作的事务保护,支持回滚
#通常此事件,可以用来做数据准备,在操作之前,获取操作执行前的数据,
#如果是保存操作,可以对单据数据包做修改,但如果发生异常,单据数据包不会回滚,可以在RollbackData事件中处理
#例如,审核操作的话,这里取单据状态字段还是审核中,删除操作的话,这里是最后一次能从数据库取到单据数据的事件
def BeginOperationTransaction(e):
    #e.CancelOperation=True;#取消操作
    #e.CancelFormService = True;#取消表单服务
    #读取执行本次操作的单据实体数据包,这个数据包里面能取到的字段取决于前面OnPreparePropertys预加载的字段
    #billObj可以理解成前面篇章讲解的单据头实体数据包,只不过字段不全而已,实体结构还是单据数据包的结构
    #为什么要循环呢?
    #因为操作是可以批量处理的,所以取到的是一个单据列表的形式,如果只有一张单据触发,e.DataEntitys里面就只有一个单据数据包
    for billObj in e.DataEntitys:
        billId=billObj["Id"];#单据ID
        billNo=billObj["BillNo"];#单据编号

#操作事物后事件(事务内触发)
#此事件在操作执行之后,操作的内部逻辑已经执行完毕,数据库的数据也已经更新成操作后的数据
#所以删除操作,在这里无法通过SQL从数据库查询单据数据
#此事件数据库处理,受操作的事务保护,支持回滚
#通常我们要保证单据操作正常执行完成之后再处理一些任务时,并且需要事务保护时,可以在此事件中完成。
#例如,最常见的,执行一个SQL更新,去更新相关单据的数据
def EndOperationTransaction(e):
    #这里获取单据数据包方式和前面一样
    for billObj in e.DataEntitys:
        billId=billObj["Id"];#单据ID
        billNo=billObj["BillNo"];#单据编号

#内部事务执行失败后,调用回滚数据事件(事务外触发)
#此事件只有在操作执行过程中发生异常时才触发
#操作异常时,回滚内存中的数据,在此处理
def RollbackData(e):
    for billObj in e.DataEntitys:
        billId=billObj["Id"];#单据ID
#执行操作事务后事件,通知插件对象执行其它事务无关的业务逻辑(事务外触发)
#此事件在操作执行后,操作的内部逻辑已经执行完毕,并且在事务提交之后
#事件中的数据库处理,不受操作的事务保护
#通常此事件,也可以做同步数据,但是此同步数据的成功与否,不需影响操作
#尽量不要在此事件中抛出异常。因为前面事务已经提交,后面抛出异常容易导致后续业务不正常。例如单据启用了工作流,提交成功后抛出异常,会导致工作流后续业务处理不正确。
#正确的做法是把异常信息作为提示信息放到OperationResult中,另外,如果是数据不合法校验,应该在事务前或事务中抛出异常。
def AfterExecuteOperationTransaction(e):
    #这里获取单据数据包方式和前面一样
    for billObj in e.DataEntitys:
        billId=billObj["Id"];#单据ID
        billNo=billObj["BillNo"];#单据编号
    #追加自定义提示信息,参考如下
    result=OperateResult();
    result.SuccessStatus=True;
    #result.PKValue = "单据ID";
    #result.Number = "单据编号";
    result.Message = "提示信息";
    this.OperationResult.OperateResult.Add(result);


==========================本篇正文结束=====================================

操作服务插件示例代码已经上传附件,老规矩,大家按需下载!

服务插件本身事件并不复杂,只是针对具体要实现的功能还需要多掌握一些具体功能实现方法,例如,最常见的单据完成某个操作之后执行一个SQL更新、自动下推一个单据、自动生成某个单据、调用第三方接口...等等。这些要通过实际案例来学习,大家也可以在社区多搜一搜相关的案例,后面的讲解文章也会尽量分享一些常用的示例代码。

感谢大家持续关注,点赞、评论、收藏,您的点赞、评论就是我前进的动力。

往期Python服务插件经典应用案例推荐:


下一篇:【Python插件入门】第7篇-简单账表服务插件

发布于 金蝶云星空BOS开发交流圈 社群

赞 201