2025年8月

summary-icon摘要

由AI智能服务提供

本文是对Python插件开发中关于单据列表插件的详细介绍。文章首先简要回顾了表单插件的内容,并指出列表插件的必要性,指出其是Web服务层插件,依赖于单据列表界面触发。接着,文章详细讲解了列表插件的基本概念、重要成员属性(如ListView、ListModel及其相关功能),并提供了新建列表插件的注意事项和注册方法。此外,还介绍了列表插件中常用的事件及其用法,包括过滤事件、菜单点击事件、列表双击事件、单元格超链接点击事件以及列表条件格式化事件等,并给出了具体的事件处理方法和示例代码。

有用

反馈

往期回顾:

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

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

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

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


    前面讲了单据表单插件,相信大家对插件事件有了一定的掌握,今天讲一下单据列表插件。

一、列表插件简介

    上一篇讲表单插件时,提到单据列表是不会触发表单插件的,于是我们需要用列表插件来处理列表相关的一些功能。

    列表插件也是Web服务层插件,需要依赖于单据列表界面(ListView)才能触发,可应用于单据列表、基础资料列表中。

    在C#插件开发时,列表插件的基类是AbstractListPlugIn列表插件和表单插件都是界面类的插件,他们都需要依赖于界面触发,他们有一个共同的父类AbstractDynamicFormPlugIn,所以有些表单插件的用法在列表插件中也同样可以用。

二、列表插件重要成员介绍

    列表插件中同样有this.Context,这个在上一篇表单插件中介绍了,用法类似,不再过多介绍,这里主要介绍一下列表插件特殊的一些成员属性。

  • ListView :列表视图层对象,列表插件除了View之外,还有ListView,通过ListView可以提供一些列表特有的功能。

    this.ListView.BillBusinessInfo.GetForm().Id;#当前列表对应的单据标识
    this.ListView .OpenParameter;#列表入口参数
    this.ListView.OpenParameter.HideListMenu = True;#隐藏列表菜单
    this.ListView.OpenParameter.IsShowFilter = True;#打开列表自动弹出过滤框,默认为False
    this.ListView.OpenParameter.IsShowQuickFilter=False;#是否显示快捷过滤
    this.ListView.OpenParameter.FilterSchemeId;#当前过滤方案ID

    this.ListView.OpenParameter.IsTrackBillList();#是否上下查列表

    #获取自定义参数,可获取发布主控台的参数,例如,将不同单据类型发布成不同列表

    this.ListView.OpenParameter.GetCustomParameter("参数标识");

    this.ListView.SelectedRowsInfo;#当前列表上被选中的行记录,复选框勾选的记录集合
    this.ListView.CurrentSelectedRowInfo;#当前列表上当前选择行数据,即焦点行
    this.ListView.CurrentPageRowsInfo;#当前列表页所有单据的行信息

    #******************************************************************************************

    grid=this.View.GetControl[EntryGrid]("FList");#获取列表表格控件对象

    selectedRowIndexs=List[int]();
    selectedRowIndexs.Add(1);
    selectedRowIndexs.Add(3);
    grid.SelectRows(selectedRowIndexs.ToArray());#设置选中行

    grid.SetRowHeight(80);#设置行高

  • ListModel :列表数据模型,从这里可以获取列表的数据

    this.ListModel;#获取列表数据模型
    selectedRowsInfo = this.ListView.SelectedRowsInfo;
    this.ListModel.GetData(selectedRowsInfo);#获取选中的数据

    selectedRowsInfo.GetPrimaryKeyValues();#获取选中行所有单据ID,字符串数组
    selectedRowsInfo.GetEntryPrimaryKeyValues();#获取所有选中行所有明细ID,字符串数组

    this.ListModel.FieldKeyMap;#列表上显示的字段,其字段名 FieldName 和字段标识Key的对应关系
    this.ListModel.FilterParameter;#列表过滤参数对象
    this.ListModel.FilterParameter.CustomFilter;#列表过滤框实体数据包
    this.ListModel.FilterParameter.FilterRows;#列表条件过滤行数据
    this.ListModel.FilterParameter.FilterString;#列表条件过滤数据系统自动转换出的条件表达式
    this.ListModel.GlobalParameter;#单据参数配置中配置的单据全局参数
    this.ListModel.ParameterData;#用户参数实体数据包,选项菜单界面的配置数据
    this.ListModel.Header;#列表表头对象
    this.ListModel.Header.GetChilds();#列表所有的列头集合
    this.ListModel.StartRow;#开始行索引,从0开始

    this.ListModel.Refresh();#刷新列表
    this.ListModel.RefreshByFilter();#根据过滤条件,重新取数,刷新列表

三、新建一个列表插件

  • 注意:这里提供一个引用比较全的Python列表插件示例模板,在附件中下载示例代码,复制到BOS里面注册!

  • Python列表插件注册方法:如下图所示,以采购订单为例,其他单据类似

image.png

四、列表插件常用事件介绍

    Python插件中如何使用事件,看第4篇中的介绍。

  • 列表插件中的常用事件用法介绍:下面介绍一些列表插件中的常用事件

#列表插件过滤事件,加载列表时会触发
#用法1:对列表过条件进行干预,此用法最广
#用法2:修改快捷过滤和过滤框的条件过滤,案例:动态构建下拉列表-年份
def PrepareFilterParameter(e):
    custfilterObj=e.CustomFilter;#过滤框实体数据包
    filterStr=e.FilterString;#过滤框条件过滤表达式
    sortStr=e.SortString#排序字段表达式
    statusFilterStr=e.StatusFilterString;#状态字段过滤表达式
    #msg=("{0}").format(statusFilterStr);
    #this.View.ShowMessage(msg);
    myFilterStr=("FBillNo like '%{0}%' ").format("1");#单据编号包含1
    e.AppendQueryFilter(myFilterStr);#追加过滤条件

#列表菜单点击事件,列表菜单点击开始时触发
#此事件也是很常用的,可以在此事件中取消菜单的点击事件
#使用时一定要判断菜单标识!!!
def BarItemClick(e):
    key=e.BarItemKey.ToUpperInvariant();
    if(key=="TestBtn".ToUpperInvariant()):
        #e.Cancel=True;#取消菜单的点击,可以阻止后续功能的触发,可完成一些简单校验
        #msg=("菜单[{0}]点击被取消啦!").format(key);
        #this.View.ShowMessage(msg);
        return;
    
#列表菜单点击后事件,列表菜单点击完成后触发
#使用时一定要判断菜单标识!!!
#自定义菜单功能实现建议在此事件中完成
#应用案例:列表插件实现单据批改
def AfterBarItemClick(e):
    key=e.BarItemKey.ToUpperInvariant();
    if(key<>"TestBtn".ToUpperInvariant()):
        return;
    msg=("菜单[{0}]点击完成啦!").format(key);
    #this.View.ShowMessage(msg);
    selectedRowsInfo=this.ListView.SelectedRowsInfo;
    billIDs=selectedRowsInfo.GetPrimaryKeyValues();
    entryIDs=selectedRowsInfo.GetEntryPrimaryKeyValues();
    msg=("选中了[{0}]个单据,[{1}]条明细!").format(billIDs.Length,entryIDs.Length);
    this.View.ShowMessage(msg);

#列表双击事件
def ListRowDoubleClick(e):
    row=e.Row;#双击的行号
    colKey=e.ColKey;#双击单元格列标识,这里取到的是列表显示字段名,不能直接用来取单元格的值,可用来做单元格判断
    currentPageRowsInfo = this.ListView.CurrentPageRowsInfo;#列表当前页的数据
    startRow = this.ListModel.StartRow;#当前页开始序号
    num = row - startRow;#双击行所在当前页的行序号
    listSelectedRow = currentPageRowsInfo[num - 1];#当前双击行数据
    FID=listSelectedRow.PrimaryKeyValue;#双击行单据ID
    entryId=listSelectedRow.EntryPrimaryKeyValue;#双击行明细ID
    #通过QueryBuilderParemeter取数,可以获取当前单元格的值,这里顺便演示一下这种取数方法吧*****************
    queryParam=QueryBuilderParemeter();
    queryParam.FormId=this.View.BillBusinessInfo.GetForm().Id;
    queryParam.BusinessInfo=this.View.BillBusinessInfo;
    #billId=SelectorItemInfo(this.View.BillBusinessInfo.GetForm().PkFieldName);#单据内码
    #queryParam.SelectItems.Add(billId);
    #billNo=SelectorItemInfo("FBillNo");#单据编号
    #queryParam.SelectItems.Add(billNo);
    #supplierId=SelectorItemInfo("FSupplierId");#供应商内码
    #queryParam.SelectItems.Add(supplierId);
    #supplierName=SelectorRefItemInfo("FSupplierId.FName");#取基础资料属性字段写法不一样,要注意
    #supplierName.PropertyName="FSupplierId_FName";估计后台是SQL取数,字段名不能有".",要重命名
    #queryParam.SelectItems.Add(supplierName);
    #以上是通过QueryBuilderParemeter取数示例,下面就是取当前双击单元格的*************************************
    fldKey=colKey;
    if("." in colKey):#说明是基础资料的属性
        colItem=SelectorRefItemInfo(colKey);
        fldKey=colKey.Replace(".","_");
        colItem.PropertyName=fldKey;
        queryParam.SelectItems.Add(colItem);
    else:
        queryParam.SelectItems.Add(SelectorItemInfo(colKey));
    entity=this.View.BillBusinessInfo.GetEntity(listSelectedRow.EntryEntityKey);
    if(entity<>None):#列表显示了单据体
        queryParam.FilterClauseWihtKey=("{0}_{1}={2}").format(entity.Key,entity.EntryPkFieldName,entryId);
    else:#列表只显示了单据头
        queryParam.FilterClauseWihtKey=("{0}={1}").format(this.View.BillBusinessInfo.GetForm().PkFieldName,FID);
    dataRows=QueryServiceHelper.GetDynamicObjectCollection(this.Context, queryParam);#获取数据结果数据包
    colValue="";
    if(dataRows.Count>0):
        colValue=("{0}").format(dataRows[0][fldKey]);
    msg=("双击了第{0}行的[{1}]").format(row,colValue);
    this.View.ShowMessage(msg);
    e.Cancel=True;#取消双击事件,否则双击会打开单据,也可以在BOS中取消列表双击事件绑定的操作

#列表单元格超链接点击事件
#该事件中数据处理方式和上面的列表双击事件中几乎一致,这里不再重复讲解
def EntryButtonCellClick(e):
    row=e.Row;#点击超链接所在序号
    fldKey=e.FieldKey;#超链接所在列字段标识
    msg=("点击了第{0}行的[{1}]").format(row,fldKey);
    this.View.ShowMessage(msg);
    e.Cancel=True;#取消事件,单据编号会自动超链接打开单据

#列表条件格式化事件
#可在此事件中根据不同的条件判断设置颜色,BOS中也可以配置,这里可以实现更灵活的条件判断
def OnFormatRowConditions(args):
    if (args.DataRow.ColumnContains("FMaterialId_Ref")):
        #注意!!!这里根据特别演示基础资料属性字段判断设置颜色,因为【列表条件格式化】不支持配置基础资料属性字段作为条件字段
        matObj=args.DataRow.DynamicObject["FMaterialId_Ref"];#物料字段实体数据包,可以取到引用属性中添加了的属性字段
        matNum=("{0}").format(matObj["Number"])
        if(matNum[0:1]=="1"):#物料编码是1开头的显示颜色,这里是实体数据包取值,别忘了是用绑定实体属性标识
            fc=FormatCondition();
            fc.ForeColor="#FFFF9B98";#前景色
            #fc.BackColor="#FFFF9B98";#背景色
            args.FormatConditions.Add(fc);
    if(args.DataRow.ColumnContains("FDate")):
        #这里是普通字段判断设置颜色,首先需要判断列表是否显示了这个字段,否则没显示该字段时会报错,而且只有显示了这个字段才能判断设置颜色
        dateStr=str(args.DataRow["FDate"]);#创建人Id
        date=DateTime.Parse(dateStr).ToString("yyyy-MM-dd");
        #this.View.ShowMessage(date);
        if(date==DateTime.Now.ToString("yyyy-MM-dd")):#单据日期=今天的单据显示颜色
            fc=FormatCondition();
            #fc.ForeColor="#FFFF9B98";#前景色
            fc.BackColor="#0000FF";#背景色
            args.FormatConditions.Add(fc);


#单据界面执行单据操作前触发,例如,保存,提交,审核等,使用时一定要判断操作代码
#与表单插件类似,不同的是,列表里面是批量处理,通常需要获取勾选的所有单据,参考前面事件中的介绍
def BeforeDoOperation(e):
    opCode=e.Operation.FormOperation.Operation.ToUpperInvariant();#触发操作代码大写,例如保存:SAVE
    if(opCode=="DELETE"):
        e.Cancel=True;#可以取消触发操作
        this.View.ShowWarnningMessage("取消删除!");
#单据界面执行单据操作完成后触发,例如,保存,提交,审核等,使用时一定要判断操作代码
#与表单插件类似,不同的是,列表里面是批量处理,通常需要获取勾选的所有单据,参考前面事件中的介绍
def AfterDoOperation(e):
    opCode=e.Operation.Operation.ToUpperInvariant();#触发操作代码大写,例如保存:SAVE
    this.View.ShowMessage(str(opCode));


好了,列表插件的 常用事件就介绍到这里,了解这些事情也基本能满足常见需求了。

五、列表插件读取单据数据

    对于列表插件开发,通常是对单据进行批量处理,列表插件中读取单据数据的方法也不同,这里简单介绍一下。

    在列表插件中是不能直接读取到完整的单据数据包的,需要变通获取,一般来讲是读取勾选的数据,在列表插件的成员中可以读取到勾选的数据行,但是字段信息不全,所以思路都是根据勾选的构建过滤条件,再去查询单据。

  • 首先,我们再来回忆一下列表插件读取数据要用到的几个成员:

    currentSelectedRow=this.ListView.CurrentSelectedRowInfo;#当前列表上当前选择行数据,即焦点行,单行数据

    selectedRowsInfo=this.ListView.SelectedRowsInfo;#列表勾选的数据集合

    billIDs=selectedRowsInfo.GetPrimaryKeyValues();#可以用来构建单据过滤条件查询单据

    entryIDs=selectedRowsInfo.GetEntryPrimaryKeyValues();
    datas this.ListModel.GetData(selectedRowsInfo);#根据勾选的数据集获取数据包,DynamicObjectCollection类型
    if (datas.Count <= 0):
#根据这个数据包集合来判断列表是否有勾选的数据行
        this.View.ShowWarnningMessage("未选择任何行!");
        return;

    #有时候也需要根据列表的分页数据来获取

     currentPageRowsInfo = this.ListView.CurrentPageRowsInfo;#列表当前页的数据
     startRow = this.ListModel.StartRow;#当前页开始序号

  • 在列表插件中,一般都是基于以上成员变量来读取单据数据,读取数据的方法有下面几种

    ①通过QueryBuilderParemeter取数方法来读取单据数据,参考前面列表双击事件中的介绍,效率较高,推荐!

    ②直接从selectedRowsInfo数据包中获取字段值,参考OnFormatRowConditions事件中的示例代码。

        注意!这种方法只能获取当前列表显示出来的字段,以及单据编号、内码等一些关键字段信息。

        可以通过循环的方式逐行获取字段值,示例代码如下:

    for row in selectedRowsInfo:
        dr=row.DataRow;
        billNo=row.BillNo;#单据编号
        billId=row.PrimaryKeyValue;#单据ID
        entryId=row.EntryPrimaryKeyValue;#明细ID,显示了单据体才会有值
        entityKey=str(row.EntryEntityKey);#当前显示的单据体标识,可用来判断entryId是属于哪个单据体的
        #其他字段要先判断是否显示
        if(dr.ColumnContains("FMaterialId_Id")):#物料Id
            matId=dr.DynamicObject["FMaterialId_Id"];
        if(dr.ColumnContains("FMaterialId_Ref")):#物料数据包
            #可以从这里读取基础资料属性字段,基础资料的取值方式可以参考这里
            matObj=dr.DynamicObject["FMaterialId_Ref"];
            matNum=matObj["Number"];#物料编码
            matName=matObj["Name"];#物料名称
        if(dr.ColumnContains("FDate")):#日期,普通字段参考这里
            date=dr.DynamicObject["FDate"];

    ③或者通过数据包datas中获取数据,方法其实与第②种从selectedRowsInfo中取数类似。

    datas是一个DynamicObjectCollection类型的数据集,是不是很熟悉了呢,学到这里,解析这个应该问题不大了!

    datas和selectedRowsInfo中的字段标识一样,也是只能取到列表显示的字段,并且取数方法也类似。

    for row in datas:
        billNo=row["FBILLNO"];#单据编号
        billId=row["FID"];#单据ID
        #其他字段建议先判断是否存在
        if(row.DynamicObjectType.Properties.ContainsKey("FMaterialId_Id")):#物料Id
            matId=row["FMaterialId_Id"];
        if(row.DynamicObjectType.Properties.ContainsKey("FMaterialId_Ref")):#物料数据包
            #可以从这里读取基础资料属性字段,基础资料的取值方式可以参考这里
            matObj=row["FMaterialId_Ref"];
            matNum=matObj["Number"];#物料编码
            matName=matObj["Name"];#物料名称
        if(row.DynamicObjectType.Properties.ContainsKey("FDATE")):#日期,普通字段参考这里
            date=row["FDate"];

    ④基于取到的单据内码ID和单据明细ID,拼接SQL,直接从数据库查询数据。

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

列表插件示例代码已经上传附件,老规矩,大家按需下载!

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

下一篇:【Python插件入门】第6篇-操作服务插件



列表插件示例代码.rar(3.52KB)


summary-icon摘要

由AI智能服务提供

本文继续Python插件入门学习,进入代码学习阶段。文章首先回顾了前几篇的基础内容,强调了理解数据操作的重要性。接着介绍了表单插件,包括其简介、重要成员(如Context、View、Model)的属性和用法,并通过示例详细说明了如何新建一个表单插件和常用的插件事件(如AfterCreateModelData、AfterBindData等),最后通过代码示例展示了如何在表单插件中定义和使用这些事件来实现特定功能。

有用

反馈

往期回顾:

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

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

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


    大家好,继续我们的Python插件入门学习,从这一篇开始我们将正式开始进入Python插件代码学习阶段。终于可以一起来写代码啦!前面的几篇介绍了Python插件开发的整个过程,以及一些非常重要的基础知识,不容忽视哦!

温馨提醒】:有些小伙伴没有仔细看,没消化知识点就漫天问我问题,其实去消化了前面的知识,尤其是第3篇,只要理解了很多数据操作应该都会做了。大家如果真心想学会,又没有太多基础的话,不要急于求成,仔细阅读每一篇文章,一步一个脚印,理解好每一个知识点,最终才能灵活应用。

    言归正传,前面我们学习了Python插件代码的构成"三部曲",其中比较重要的一部分就是【方法/函数】,再结合上一篇我们学习的插件中对单据的数据包进行操作,就可以实现具体功能了,后面我们将学习各类插件,其实就是学习如何使用【方法/函数】当然,插件中更多的还是学习使用系统定义好的事件方法,今天我们先来看一看表单插件中的事件方法。


一、表单插件简介

    表单插件是Web服务层插件,需要依赖于单据维护界面(View)才能触发单据列表不会触发,曾经有小伙伴多次遇到单据下推,下游单据某些数据不能自动生成,就是因为单据转换过程本身是不触发实体服务规则、值更新事件以及表单插件的,遇到一些特别的开发场景需要触发表单插件、实体服务规则、值更新时,有时也会在后台构建单据的界面对象View来进行触发使用(以后会做这方面的分享),现在先讲一些表单插件的基本用法。

   那么那些业务对象可以使用表单插件呢?表单插件在单据、动态表单、基础资料中都可以注册。

   在熟知的C#插件中,表单插件的基类是AbstractBillPlugIn,C#插件开发时,需要建一个AbstractBillPlugIn的派生类,来实现表单插件,而在开发Python插件时,在注册表单插件的位置注册Python脚本系统就默认是表单插件了,不需要过多考虑基类,所以我们需要更关注需要实现的事件方法。

二、表单插件中的重要成员介绍

    插件基类为我们提供了许多重要的成员供开发时使用,下面介绍一下常用的一些属性和用法。

    在插件中可以通过this关键字来调用这些成员。

  • Context:上下文对象

    this.Context.UserId;#当前用户ID
    this.Context.UserName;#当前用户名称
    this.Context.UserPhone;#当前用户手机号
    this.Context.CurrentOrganizationInfo.ID;#当前组织Id
    this.Context.CurrentOrganizationInfo.Name;#当前组织名称
    this.Context.DBId;#当前数据中心Id
    this.Context.DataCenterNumber;#当前数据中心编码
    this.Context.DataCenterName;#当前数据中心名称
    this.Context.IpAddress;#客户端本机网络信息:IP、MAC等

  • View:视图层对象,可理解为当前界面,界面类插件特有,服务类的插件没有这个,View能为开发提供很多特有的功能。

    ①View的常用属性

    this.View.BillBusinessInfo;#单据的业务逻辑元数据,元数据包含的信息很多,下面列了一下常用的信息
    this.View.BillBusinessInfo.GetBillNoField().FieldName;#单据编号字段名
    this.View.BillBusinessInfo.GetBillStatusField().FieldName;#单据状态字段名
    this.View.BillBusinessInfo.GetBillTypeField().FieldName;#单据类型字段名
    this.View.BillBusinessInfo.GetForm().Id;#单据FormId
    this.View.BillBusinessInfo.MainOrgField.FieldName;#主业务组织字段名
    this.View.BillBusinessInfo.GetEntity("FBillHead").TableName;#单据头表名
    this.View.BillBusinessInfo.GetEntity("实体标识").TableName;#实体主表名
    this.View.BillBusinessInfo.GetEntity("实体标识").SplitTables;#实体所有拆分表

    #****************************************************************************************************

    this.View.OpenParameter;#表单入口参数
    this.View.OpenParameter.Status;#当前界面状态:0,新增;1,查看;2,修改;
    this.View.OpenParameter.GetCustomParameter("参数标识");#获取单据打开传入的参数

    #****************************************************************************************************

    this.View.ParentFormView;#获取父页面的View
    this.View.ParentFormView.BillBusinessInfo.GetForm().Id;#父页面的FormId,用来判断单据是从哪里进来的

    ②View的常用方法

    this.View.GetFormTitle();#获取单据标题
    this.View.SetFormTitle(LocaleValue("新标题"));#修改单据标题

    this.View.GetFormOperation("操作代码");#获取单据的一个操作实例对象
    this.View.InvokeFormOperation("操作代码");#触发单据的某个操作:保存、提交、审核、关闭 等!
    this.View.GetControl[控件类]("控件标识");#获取单据上的控件:按钮、菜单等,可用来设置控件的状态(可见性,锁定性等)
    this.View.InvokeFieldUpdateService("字段标识",行号);#触发字段值更新,单据头字段行号填0
    #触发实体服务规则,下面2行代码
    obj=BOSActionExecuteContext(this.View);
    this.View.RuleContainer.RaiseDataChanged("字段标识", 字段所在实体行数据包,obj);#触发实体服务规则

    #显示3种提示信息
    this.View.ShowMessage("绿色背景提示信息");#显示正常提示信息
    this.View.ShowWarnningMessage("黄色背景提示信息");#显示警告提示信息
    this.View.ShowErrMessage("红色背景提示信息");#显示错误提示信息

    #刷新界面数据,修改实体数据包后需要刷新重新读取,必须传标识,不建议粗暴刷新整个单据

    this.View.UpdateView("字段标识/单据体标识");#刷新界面数据

  • Model:单据数据模型,很重要,单据的实体数据包获取从这里开始,结合上1篇所讲的实体数据包。

    this.View.Model;#单据数据模型,很重要,单据的实体数据包获取从这里开始,结合上文
    this.View.Model.DataObject;#单据的完整数据包,相当于前面讲的单据头实体数据包

    this.View.Model.GetEntryCurrentRowIndex("单据体标识");#获取单据体当前焦点行号
    this.View.Model.GetEntryRowCount("单据体标识");#获取单据体行数
    this.View.Model.CreateNewEntryRow("单据体标识");#为单据体新增一行
    this.View.Model.BatchCreateNewEntryRow("单据体标识",x);#批量为单据体新增x行
    this.View.Model.InsertEntryRow("单据体标识", i);#在第i行前插入1行
    this.View.Model.DeleteEntryRow("单据体标识", i);#删除第i行
    this.View.Model.DeleteEntryData("单据体标识");#清空整个单据体数据

    #获取第x行(单据头不传x)某字段的值,不同字段类型返回不同类型数据,参考第3篇讲解

    this.View.Model.GetValue("字段标识",x);

    #更新第x行(单据头不传x)某字段的值,不同字段类型赋值不同类型数据,参考第3篇讲解

    this.View.Model.SetValue("字段标识","字段值",x);
    this.View.Model.SetItemValueByID("字段标识","内码id",x);#用资料内码id更新第x行资料字段的值
    this.View.Model.SetItemValueByNumber("字段标识","编码",x);#用资料编码更新第x行资料字段的值

上面对表单插件常用的一些属性和方法做了一下介绍,其实还有很多,这里篇幅有限,也列举不完,用得最多的基本就在这里了,我们可以发现,除了第3篇将的实体数据包操作之外,在表单插件中,我们有另外的方法对单据的数据进行操作需要注意的是,表单插件中的这些方法使用的都是标识,而不是绑定实体属性名了,当然,在表单插件中实体数据包操作同样适用,并且用的时候也很多,不要觉得表单插件这些方法便捷好理解,就"捡了芝麻丢了西瓜哦!"。

三、新建一个表单插件

    我们先新建一个Python表单插件,然后就可以进行下面讲解的内容进行测试验证啦!

  • 注意:我这里提供一个引用比较全的Python插件示例模板,在附件中可以下载示例代码,复制到BOS里面注册!

  • Python表单插件注册方法:如下图所示还是以采购订单为例,其他单据类似

    image.png

四、表单插件常用事件介绍

    有了前面讲解的这些单据数据操作方法是不是想赶紧验证一下呢,要在插件中触发这些代码,我们需要通过插件事件来作为入口,表单插件的事件也很多,这里介绍一下表单插件中最常用的一些事件,用来实现常见的需求场景够用啦!

  • 表单插件中的基本事件:自动触发,一定会触发,按顺序触发。

    插件中的事件在单据运行过程中产生的,所以一些事件有固定执行顺序,且在不同阶段才能获取到特定的数据。

    单据新增界面打开单据修改/查看打开事件描述
    PreOpenFormPreOpenForm准备单据打开
    OnInitializeServiceOnInitializeService加载单据插件等,这里可以动态调整插件执行顺序等
    OnSetBusinessInfoOnSetBusinessInfo绑定单据元数据
    OnSetLayoutInfoOnSetLayoutInfo设置单据界面显示信息,如布局等
    OnCreateDataBinderOnCreateDataBinder创建数据源绑定对象
    OnInitializeOnInitialize单据初始化完毕,在这里可以获取一些控件备用
    OnBillInitializeOnBillInitialize单据初始化完毕
    CreateNewData
    构建新单据初始实体数据,新增才会触发
    AfterCreateNewData
    新单据初始实体数据构建完毕,在此可修改单据默认数据
    AfterCreateModelData
    新单据实体数据构建到Model

    LoadData开始加载单据实体数据

    AfterLoadData加载完单据数据
    OnLoadOnLoad
    BeforeBindDataBeforeBindData开始绑定实体数据到界面控件
    AfterBindDataAfterBindData绑定实体数据到界面控件完毕,尽量不要在此事件中修改数据,仅修改控件的状态(可见性、锁定性、颜色等)
  • Python插件中如何使用事件

    ①事件就是定义好的方法,所以使用def关键字,后面跟上方法名称、参数列表即可,表单插件的事件一般没有返回值。

    ②事件方法参数:在参数中我们能获取到当前事件应该提供信息,表单插件事件一般只有1个参数,在BOS写会提示。

    ③参数名,用"e"即可。

    ④示例:def AfterCreateModelData(e): ,注意最后有英文冒号":"结尾,如果忘记Python的语法要点,看第2篇

  • 表单插件中的常用事件用法介绍:下面列出的事件中都可以获取到单据数据包

#单据新增界面打开时触发,可以获取到单据初始化之后的数据
#通常用于对单据新增进行默认值填充,例如,设置默认日期,填充单据体默认数据等

#应用案例:Python获取当前用户对应的员工
def AfterCreateModelData(e):
    billObj = this.Model.DataObject;#单据完整数据包,如需通过实体数据包操作读写数据,可从这里开始取
    this.View.Model.SetValue("FDate""2022-7-1");#设置默认日期
    #msg=("{0}").format(this.View.BillBusinessInfo.GetEntity("FBillHead").TableName);
    #this.View.ShowMessage(msg);

#界面数据绑定完毕后触发,通常在这里对字段控件状态进行设置
#例如,设置颜色、设置字段锁定性/可见性、设置下拉列表动态枚举选项
def AfterBindData(e):
    this.View.GetControl("FDate").Enabled=False;#False:锁定,True:解锁
    #this.View.GetControl("控件标识").Visible=False;#False:隐藏,True:显示

#单据头菜单点击事件,从参数e中获取菜单标识,来判断是哪个菜单发生了点击
#这里一定要加判断,只需要监听需要的菜单,多个菜单的话,使用if elif结构,Python中没有Switch结构

#应用案例:点击菜单弹出动态表单传递参数到子页面
def BarItemClick(e):
    key=e.BarItemKey.ToUpperInvariant();#菜单标识大写
    if(key=="TESTBTN1".ToUpperInvariant()):
        msg=("菜单[{0}]点击事件捕捉到了,可以继续菜单的功能啦!").format(key);
        this.View.ShowMessage(msg);
    elif(key=="TESTBTN2".ToUpperInvariant()):
        msg=("菜单[{0}]点击事件捕捉到了,可以继续菜单的功能啦!").format(key);
        this.View.ShowMessage(msg);

#单据体菜单点击事件,与BarItemClick类似,只不过这个是监听单据体菜单栏
def EntryBarItemClick(e):
    key=e.BarItemKey.ToUpperInvariant();
    if(key=="entryTESTBTN1".ToUpperInvariant()):
        msg=("单据体菜单[{0}]点击事件捕捉到了,可以继续菜单的功能啦!").format(key);
        this.View.ShowMessage(msg);
        #这里通常需要获取当前单据体选中的行)(前面有讲怎么获取),然后对这一行进行处理

#按钮控件/超链接控件点击事件
#例如,点击一个超链接,弹出某个单据等等,在此事件实现

#应用案例:子页面关闭返回数据到父页面
def ButtonClick(e):
    BtnKey=e.Key.ToUpperInvariant();#控件标识大写
    if(BtnKey=="F_ora_LINK".ToUpperInvariant()):
        msg=("按钮[{0}]点击事件捕捉到了,可以继续菜单的功能啦!").format(BtnKey);
        this.View.ShowMessage(msg);

#单据体行双击事件,使用时一定要判断单据体!!!
#只捕捉行双击:获取事件参数中的行号即可
#需要捕捉单元格双击:获取事件参数中的行号和字段标识
def EntityRowDoubleClick(e):
    entityKey=e.Key.ToUpperInvariant();#单据体标识大写
    row=e.Row;#双击行号,从0开始
    fldKey=e.ColKey.ToUpperInvariant();#双击单元格字段标识大写
    msg=("单据体[{0}]第[{1}]行,字段[{2}]双击啦!").format(entityKey,row,fldKey);
    this.View.ShowMessage(msg);
    
#单据体行点击事件,也就是单据体单行选择事件,使用时一定要判断单据体!!!
#通过此事件可以知道当前焦点行,通常需要开发切换行处理子单据体数据时使用
def EntityRowClick(e):
    entityKey=e.Key.ToUpperInvariant();#单据体标识大写
    row=e.Row;#选择行号,从0开始
    msg=("单据体[{0}]第[{1}]行被选中啦!").format(entityKey,row);
    this.View.ShowMessage(msg);


#单据体行删除事件,在这个事件里可以最后获取被删除的行数据
#使用时一定要判断单据体!!!
def AfterDeleteRow(e):
    entityKey=e.EntityKey.ToUpperInvariant();#单据体标识大写
    row=e.Row;#删除的行号,从0开始,注意:此时用此行号从单据实体数据包中取的数据已不是删除的行数据了
    deletRowObj=e.DataEntity;#删除的行数据包
    msg=("单据体[{0}]第[{1}]行被删除啦!").format(entityKey,row);
    this.View.ShowMessage(msg);

#值更新事件,当BOS的值更新与实体服务配置实现不了想要的功能时,就会用到插件值更新事件
#事件参数中可以获取:发生值更新的字段标识、行号、更新前字段值、更新后字段值
#注意!!!使用时一定要判断字段标识!!!字段在BOS中勾选【即时触发值更新】才会触发插件值更新事件
def DataChanged(e):
    fldKey=e.Field.Key.ToUpperInvariant();#字段标识大写
    if(fldKey<>"ABC"):#注意:示例代码是不等于,实际应使用等于进行判断
        row=e.Row;#字段所在的行号,从0开始,单据头字段为0
        oldValue=e.OldValue;#更新前字段值
        newValue=e.NewValue;#更新后的字段值
        msg=("第[{0}]行字段[{1}]从[{2}]更新成了[{3}]").format(row,fldKey,oldValue,newValue);
        this.View.ShowMessage(msg);


#资料字段选择前事件,通俗来讲:就是放大镜点击弹出选择列表前触发的事件
#用途1:此用法最多,就是对弹出的选择列表进行动态过滤,控制数据选择范围
#用途2:控制资料字段录入顺序,例如,可以控制没选供应商,就不能选物料,并弹出提示!
#用途3:非资料类型字段,启用编辑按钮(3个点...),点击时触发
def BeforeF7Select(e):
    #可以从事件参数中读取的信息
    fldKey=e.FieldKey.ToUpperInvariant();#触发的字段标识大写
    baseFormId=e.FormId;#资料字段的具体资料类型,例如物料,就是BD_Material
    row=e.Row;#字段所在的行号,从0开始,单据头字段为0
    #通过修改事件参数中的信息,可以实现的功能,一定要判断字段标识
    if(fldKey=="FMATERIALID"):
        supObj=this.View.Model.GetValue("FSupplierId");#使用字段标识,获取供应商
        billObj = this.Model.DataObject;#从单据实体数据包获取也一样,再演示1次,别忘了这种方法
        supObj=billObj["SupplierId"];#使用绑定实体属性
        if(supObj==None):
            this.View.ShowWarnningMessage("请先选择供应商");
            e.Cancel=True;#取消弹出选择列表
            return;#跳出事件方法
        e.IsShowUsed = False;#允许选择禁用的数据
        e.IsShowApproved = False;#允许选择未审核的数据,True表示
        myFilterStr=(" '{0}'='VEN00001' ").format(supObj["Number"]);#拼接过滤条件,按照SQL条件表达式写即可
        e.ListFilterParameter.Filter=myFilterStr;#这里示例:供应商编码='VEN00001',才会弹出物料,否则弹出空白列表

#通过编码给资料字段赋值前触发,插进赋值,或者直接将编码粘贴到字段里面
#在此事件可以对赋值编码进行校验,相当于对BeforeF7Select事件的一个补充
#BeforeF7Select主要是界面录入时控制数据选择列表,但是通过插件赋值是不会触发的
def BeforeSetItemValueByNumber(e):
    fldKey=e.BaseDataField.Key.ToUpperInvariant();#触发的字段标识大写
    Num=e.CurrentSetNumber;#当前赋值的资料编码
    row=e.Row;#字段所在的行号,从0开始,单据头字段为0
    e.IsShowUsed = False;#允许赋值禁用的数据
    e.IsShowApproved = False;#允许赋值未审核的数据,True表示
    e.Filter="";#同样可以拼接过滤条件进行数据范围控制
    
#单据界面执行单据操作前触发,例如,保存,提交,审核等,使用时一定要判断操作代码
#此事件在校验规则之前触发
def BeforeDoOperation(e):
    opCode=e.Operation.FormOperation.Operation.ToUpperInvariant();#触发操作代码大写,例如保存:SAVE
    if(opCode=="SAVE"):
        e.Cancel=True;#可以取消触发操作
        this.View.ShowWarnningMessage("取消保存!");
#单据界面执行单据操作完成后触发,例如,保存,提交,审核等,使用时一定要判断操作代码
def AfterDoOperation(e):
    opCode=e.Operation.Operation.ToUpperInvariant();#触发操作代码大写,例如保存:SAVE
    this.View.ShowMessage(str(opCode));


好了,表单插件事件就介绍到这里,表单插件事件还有一些没介绍,大家有兴趣的可以找资料研究一下,我这里介绍的都是常用的事件,基本能满足常见需求的实现了。

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

大家可以将附件中的Python表单插件示例下载下来,存好,作为一个模板,需要的时候就复制到BOS里面,一定要把用不上的事件删除,避免带来性能等其他干扰问题。

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

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

Python表单插件模板示例.rar(3.29KB)


summary-icon摘要

由AI智能服务提供

本文介绍了在插件开发中如何对单据数据进行操作,特别是如何在Python插件中处理单据的实体数据包。首先解释了单据的构成和业务对象的重要性,随后通过类比PPT来说明单据头、子单据头、单据体和子单据体之间的关系。接着详细讲解了如何在Python插件中操作DynamicObject数据包,包括读取和修改单据头、子单据头、单据体以及子单据体的数据,通过示例代码展示了如何获取和解析这些数据包中的字段值。最后强调了修改数据包时的注意事项,如只读字段的处理和赋值时数据类型的匹配。

有用

反馈

上一篇:第2篇:基本开发过程介绍  

    大家好,今天我们来讲一讲如何在插件中如何进行数据操作,实现一个开发功能,无非是对数据进行各种逻辑处理,那么我们要学会插件开发,必然要学会如何对单据的数据进行取值、赋值,所以这一篇是重中之重,毫不夸张的讲,如果不理解这一篇所讲解的内容,将很难学会Python插件开发,大家也不用担心,跟着我的思路一步一步来理解,一定能明白的!

大家一定要先看下面这2篇文章,特别是没有插件基础或者基础薄弱的小伙伴:

看了以上2篇文章,再结合本篇的讲解,这部分内容就很容易攻克啦!话不多说,下面进入正文...


一、单据的实体构成介绍

  • 业务对象

     对于金蝶云星空系统,大家应该知道的几个概念:业务领域、子系统、业务对象。系统业务功能的构成主要就是通过不同的业务对象组成的。我们先来看下业务对象,下图就是系统中场常见的业务对象类型。

image.png

  • 重新认识单据

    虽然系统中有这么多的业务对象类型,但核心都是以单据(也可以称为表单)为基础的,我们来重新认识一下单据的构成。

    下图是一个单据的主要构成,简单来讲单据就是由布局控件+实体字段构成。布局控件就是仅用于界面展示,不会存储实际物理数据,而实体字段除了有特定的展示效果之外,更重要的是构成了单据业务数据,会存储到数据库物理表中。我们做插件开发时,除了对界面展示进行一些简单的控制和修改,例如,设置锁定性、可见性、高度、颜色等。更多的还是对单据的数据进行各种处理,所以这一篇主要就是来介绍在插件中如果对单据实体数据进行操作。

image.png

看了这张图的分解,是不是有种感觉:"单据其实用来用去也就那么回事!"

  • 对实体的理解

我们聚焦到今天的重点--单据实体数据,看到红色的内容。系统中的实体就只有上图中的4种。对于实体,我们可以这样理解,实体是一层一层的,多个实体组合成了一个单据,就像做一页PPT(为了帮助理解,做一下类比,不恰当之处还望海涵,能理解知识就好):

  • 最外层有一个底页(相当于是单据头,代表了整个单据),最简单直接的,可以直接在底页中添加文本编辑框(相当于单据头字段);

  • 我们可以加入多个形状对整个 PPT的页面进行分区(加入子单据头,把单据头信息进行拆分),可以把一些信息内容进行归类,放到不同的形状里面(把字段加到子单据头);

  • 然后我们想在PPT中用一个表格来展示销售业绩,于是又插入了一个表格(相当于是单据体),然后对表格添加列,构成了表格的列头(单据体字段),假设我们加入了:产品名称、月份、销售金额。然后就是一行一行的产品销售数据展示出来了。

  • 这时发现,这个销售业绩数据不够细,只展示了每项产品的销售总金额,把每个产品分别销售给了哪些客户也展示出来吧。然后就对表格进行加工,把一行产品数据展示成多行,然后产品名称合并单元格,形成了一个树形表格,但是这个表格好像太长了,看起来太复杂。于是,可以把表格分成多个表,保留原来的产品销售汇总表,然后再把每个产品的客户销售明细都列出来(子单据体),分别对应每一行产品数据。这样把汇总数据和明细数据分开展示了。

看了上面的类比,大概可以知道这几种实体间的所属关系了吧。

  • 1个单据对应1个单据头

  • 1个单据头对应n个子单据头

  • 1个单据头对应n个单据体

  • 1个单据体对应n个子单据体(一般不建议这样设计)

  • 1个子单据体还可以挂n个下级子单据体,树形展开下去(一般不建议这样设计)

这里要注意一点:子单据头可以直接与单据头进行关联,存在一对一的关联关系,但是不同的单据体分录之间是没有直接关联关系的,只是属于同一单据而已。而子单据体分录必须和父单据体进行直接关联,然后才能关联到单据头,不能与其他实体之间直接关联,并且不同子单据体之间分录也没有直接关联关系。

通俗理解:单据头是父亲,子单据头是儿子,单据体是儿子,子单据体是孙子。父亲和儿子之间有直接关系,兄弟之间没有直接关系,爷爷和孙子之间也没有直接关系。

实体字段都是放在这些实体中的!所以在插件中操作单据数据的核心就是要对实体进行操作。

image.png

我们在BOS中打开一个单据来看下单据的实体结构,点开【批量编辑字段属性】即可查看实体结构,以采购订单为例:

image.png

image.png

  • 扩展知识

看到这里,我想很多小伙伴肯定会想到单据在数据库中的表结构,没错!单据的数据库表结构也是根据实体的结构来生成的,数据库表结构是另一方面的知识了,这个不是我们今天讲解的重点,这里作为扩展知识顺便分享一下,见下图。

image.png

二、Python插件中对实体数据包的操作

        在单据操作过程中,我们基本可以按下图来理解单据数据的流转过程。

image.png

所以我们读取/更新单据数据的方式也有三种:

①从界面上读取/更新,只能在界面类的插件中使用。

②从单据体的实体数据中读取/更新。

③直接通过SQL从数据库读取/更新。

在插件中对单据的数据进行操作时,大多数情况都是在实体数据这一层处理的。所以我们必须要掌握:

单据的实体数据包结构是怎样的?

如何获取单据的实体数据包?

如何从单据数据包取值?

如何修改单据的数据包?

如何将单据的数据包显示到界面上?

如何将数据包保存到数据库?

下面我们就来学习一下单据的实体数据包如何操作:

实体数据包主要是DynamicObject对象,可以称为动态数据对象,Python没有数据类型定义,但是当我们在Python插件中获取到单据的实体数据包之后,我们要知道取到的是一个DynamicObject对象。

  • 那么我们先来认识一下DynamicObject

看了前面建议必读的2篇文章之后,应该比较清楚了,这里再整合介绍一下:

  1. 丁老师的文章里面介绍到:DynamicObject相当于一个有层次结构的数据字典。有层次怎么理解?其实就DynamicObject里面有很多层,是通过键值对的方式构成的,而每个值是有不同的数据类型的,当然也会存在DynamicObject对象类型的值,所以就形成了DynamicObject多层展开的数据结构。字典的key对应的就是字段的实体属性标识。

  2. 如果觉得字典不好理解,可以看文章开头建议的另一篇文章中的介绍,初学者可以类比JSON对象来进行理解,可以通过实体属性标识对数据包里面的字段进行读写。DynamicObject可以相当于一个JSON对象,数据包里面的字段有多种类型,读写的时候,需要"一层一层剥开"去进行操作。与JSON不同之处在于,每一个DynamicObject都有自己特定的类型,对应了自己的一个结构,这个就是由单据体实体结构决定的,数据包里面的字段属性就是他包含的所有实体属性名,不能像JSON那样随意添加、删除字段属性。

  • 认识DynamicObject之后,我们来看一下如何一层一层解析

首先我们需要拿到单据头的实体数据包,相当于就拿到了最外层完整的单据数据包,获取单据实体数据包的方法有很多种,对于不同的插件,甚至事件不同,获取单据数据包的方法也不同。这个在后续各类型插件的讲解中会逐一介绍。但是拿到数据包之后的操作方法都是一样的,这里我们先以表单插件为例,看看Python插件中如何解析DynamicObject数据。

我们先建立一个Python表单插件作为演示,以采购订单的数据为例:

(完整示例代码可下载附件,这里只列出关键代码进行介绍)

  • 解析数据包时,必须知道对应的key,这个key是在BOS单据中查看的,先看前面的文章了解单据各种标识的介绍

  • 注意!读取子单据头、单据体、子单据体时,使用的是ORM实体名读取字段时,使用的是绑定实体属性。

我们根据数据包里面的这些key就读取/修改对应的值Value,别忘了,前面也讲了,不同的值也是有不同的数据类型的。

  • 解析代码格式:通过“DynamicObject数据变量["标识key"]”就可以读取到对应的值了(注意区别大小写)。

  • 获取单据头数据包:billObj就是一个DynamicObject数据变量,自定取的一个变量名,满足Python语法规范即可。

    billObj = this.View.Model.DataObject;#单据头的数据包,即单据的数据包,DynamicObject类型,可进一步解析

  • 读取单据编号:单据编号属于单据头的字段,直接从单据头数据包中读取,key是BillNo

    billNo=billObj["BillNo"];#字符串类型

  • 读取单据ID(内码):读取实体的内码Id,直接从实体数据包中读取,key固定是Id

    billId=billObj["Id"];#整数类型

  • 读取采购日期:采购日期属于单据头字段,直接从单据头数据包中读取,key是Date

       billNo=billObj["Date"];#日期类型

  • 读取供应商:供应商属于单据头字段,直接从单据头数据包中读取,key是SupplierId

       supplierObj=billObj["SupplierId"];#DynamicObject类型,基础资料、组织、辅助资料取值均是DynamicObject

注意:当字段类型是基础资料或者辅助资料时,直接获取到的是资料的数据包(相当于基础资料单据头的数据包),也是一个DynamicObject类型如果进一步获取基础资料的属性属性,再往下一层解析获取即可。

需要进一步注意的是:

①考虑到性能问题,单据不会加载完整的基础资料数据包,需要获取的字段先要在【引用属性】里面添加!

②读取资料字段DynamicObject里面的数据时候,需要判断字段是否为空,示例:if(supplierObj<>None)

  • 读取供应商编码:供应商编码属于供应商里面单据头的字段,要从供应商数据包中读取,通过查看供应商BOS单据可以知道,供应商编码的key是Number

       supplierNum=("{0}").format(supplierObj["Number"]);#字符串类型,直接转换成字符串使用

  • 读取供应商名称:供应商名称属于供应商里面单据头的字段,要从供应商数据包中读取,key是Name

       supplierName=("{0}").format(supplierObj["Name"]);#多语言文本,可以直接转换成字符串使用

  • 读取供应商ID(内码):资料字段的ID读取有2种方法,一种是从实体数据包中读取,另一种是从资料数据包中读取

        #①从实体数据包中直接读取,实体数据包中,单独构建了资料字段的ID,key是固定格式:"资料字段属性key_Id"

        #供应商所属实体是单据头,直接可以从单据头中读取,供应商的key是SupplierId,对应ID的key是:SupplierId_Id

        supplierId=("{0}").format(billObj["SupplierId_Id"]);#整数,可以直接转换成字符串使用

        #②从基础资料数据包中读取

       supplierId=("{0}").format(supplierObj["Id"]);#整数,可以直接转换成字符串使用

  • 读取供应商里面的付款条件:付款属于供应商里面财务信息子单据头的字段,要从供应商数据包中逐层获取

        #先获取供应商的财务信息子单据头,key是SupplierFinance,取的是一个DynamicObject集合,集合中只有1个元素

         supplierFinObj=supplierObj["SupplierFinance"];#DynamicObjectCollection类型

        #再从供应商-财务信息子单据头获取付款条件,付款条件字段的key是PayCondition

         payConditionId=supplierFinObj[0]["PayCondition_Id"];#付款条件Id,整数类型
         payConditionObj=supplierFinObj[0]["PayCondition"];#付款条件Id,整数类型

看了上面单据头供应商字段的逐层解析 ,有点感觉了吧,我们不继续往下层取了,回到采购订单。

  • 读取采购订单表头价税合计表头价税合计属于财务信息子单据头的字段

        #先从最外层单据头数据包中获取财务信息子单据头,key是POOrderFinance

         FinObj=billObj["POOrderFinance"];#DynamicObjectCollection类型

        #再从财务信息子单据头获取价税合计,key是BillAllAmount

         payConditionId=FinObj[0]["BillAllAmount"];#浮点数类型

前面的示例中提到了子单据头,子单据头实体取到的是一个DynamicObjectCollection

有点不一样,其实也很简单,DynamicObjectCollection是DynamicObject的集合,相当于是DynamicObject对象列表或者DynamicObject对象数组,它里面可以有1个或者多个DynamicObject。

而子单据头这个集合里面固定只有一个元素。使用的时候注意一下就行。

对于单据体,也是DynamicObjectCollection,且会有多个DynamicObject,每一个DynamicObject代表一行数据,所以,通常我们需要用循环来对单据体的每一行数据进行操作。

  • 读取采购订单明细:明细信息是一个单据体,它属于单据头,直接从最外层单据头数据包读取

       entity=billObj["POOrderEntry"];#订单明细单据体,DynamicObjectCollection类型

  • 循环读取采购订单明细:单据体行,又是一个DynamicObject,每1行当成一个个单据头来解析就行了

        for rObj in entity:
            entryId=rObj["Id"];#明细ID内码,单据未保存时,为0
            matObj=rObj["MaterialId"];#物料数据包,DynamicObject类型,可进一步解析
            matId=rObj["MaterialId_Id"];#物料Id获取方式1
            matId=matObj["Id"];#物料ID获取方式2
            matNum=matObj["Number"];#物料编码,字符串类型
            matNam=("{0}").format(matObj["Name"]);#物料名称,多语言文本,直接转换成字符串使用
            Qty=rObj["Qty"];#采购数量,浮点数类型
            taxPrice=rObj["TaxPrice"];#含税单价,浮点数类型

  • 读取交货明细子单据体:子单据体属于父分录单据体的行数据包,要从行数据包中读取

        取出来的也是DynamicObjectCollection,又当成1个单据体来解析就行。交货明细key:POOrderEntryDeliPlan

        子单据体读取方式1(不推荐,可以试试):

        for rObj in entity:
            subEntity=rObj["POOrderEntryDeliPlan"];#交货明细子单据体,DynamicObjectCollection类型
            for subRowObj in subEntity:
                subEntryId=subRowObj["Id"];#子单据体行ID内码,整数
                deliveryDate=subRowObj["PlanDeliveryDate"];#计划交货日期,日期类型

         子单据体读取方式2(推荐,常用方式):思路不变,只是通过子单据体元素的元数据获取

        #需要添加引入:from Kingdee.BOS.Core.Metadata.EntityElement import *

        #注意获取元素元数据时,使用的是标识FEntryDeliveryPlan

        subEn = this.View.BillBusinessInfo.GetEntity("FEntryDeliveryPlan");#SubEntryEntity类型

        for rObj in entity:
            subEntity = subEn.DynamicProperty.GetValue(rObj);#把父分录的行数据包传进去,子单据体数据包,DynamicObjectCollection类型
            for subRowObj in subEntity:
                subEntryId=subRowObj["Id"];#子单据体行ID内码,整数
                deliveryDate=subRowObj["PlanDeliveryDate"];#计划交货日期,日期类型

前面已经将实体数据包的读取操作讲完了,那怎么更新数据包呢?

实体数据包是在运行时存在,相当于内存中的数据结构,修改也很简单,直接赋值就可以了。

需要注意3点:

    ①对于只读类型的字段不能进行赋值操作,例如,基础资料属性字段,基础资料数据包里面的字段等。

    ②实体ID内码,这个是由系统统一管理的,不到万不得已,不要去修改,慎行!

    ③前面也反复强调不同值有不同的数据类型,赋值的时候要赋予对应的数据类型。例如,数量字段应该赋值小数,日期字段应该赋值日期,字符串字段应该赋值字符串,DynamicObject数据包字段应该赋值数据包等等。

        billObj["BillNo"]="test001";#修改单据编号
        for rObj in entity:
            matId=1001;#这里是随手写的一个整数,实际情况要写系统中可用的物料ID
            #根据物料Id获取物料数据包
            matFld=this.View.BillBusinessInfo.GetField("FMaterialId");#物料字段元素元数据,使用字段标识
            matObj=BusinessDataServiceHelper.LoadSingle(this.Context, matId, matFld.RefFormDynamicObjectType);
           #注意:对资料类型的字段赋值时,需要2行代码,既要给实体里面资料ID赋值,也要给资料数据包赋值

            rObj["MaterialId_Id"]=matId;#物料Id赋值
            rObj["MaterialId"]=matObj;#物料数据包赋值
            rObj["Qty"]=100;#修改采购数量
            rObj["TaxPrice"]=10;#修改含税单价

#如果是表单插件,修改完实体数据包之后,要执行UpdateView刷新界面,界面才会显示更新后的数据
        this.View.UpdateView("FBillNo");#刷新单据编号字段
        this.View.UpdateView("FPOOrderEntry");#刷新整个单据体

  • 实例化,代码构建一个DynamicObject

        对于DynamicObject对象,可以通过billObj.DynamicObjectType来获取它的具体数据类型。

        对于DynamicObjectCollection对象,可以通过entity.DynamicCollectionItemPropertyType来获取具体数据类型。

        当我们需要构建一个新的DynamicObject对象时,会用到。

        例如,为单据体构建一行新的数据(需要对逐个字段进行赋值,赋值过程不会触发实体服务规则和值更新):

        newRow=DynamicObject(entity.DynamicCollectionItemPropertyType);

        #对newRow里面的字段逐个赋值...省略

        entity.Add(newRow);


当然,给实体数据包赋值还有其他的方式,可以通过字段元素元数据来赋值,也更推荐使用这种方式。理解了上面实体数据操作方式之后,通过字段元数据的方式也很好理解。

这里不展开讲解了,可以继续看前面提到的文章参考学习:知识共享 - 单据数据包DynamicObject的结构及操作

这个文章里面,丁老师介绍得很详细,也提到了一些错误的赋值方法,一定要注意!


三、Python插件中对各类型的字段进行取数赋值操作

  • 前面对单据的实体结构和数据包进行了非常详细的讲解,相信你已经有自己的理解了,所有插件开发都会用到这个知识。

  • 前面的示例代码中,有一些我直接进行了字符串转换使用,确实在Python插件开发中,由于没有具体数据类型的定义,相当于都是"隐式转换",为了方便,我们会直接将取得的数据转换成字符串进行"通用"。

       这个方式是我个人的习惯,也许不是很规范,但用起来确实比较便捷。

  • 在Python插件中要对各类型的字段进行取数赋值操作,核心就是要知道每种类型的字段取出来是什么数据类型。


前面也反复强调了不同的key对应的值Value是有不同的数据类型的,那么怎么知道取到的是一个什么数据类型呢?

这个数据类型其实是和单据的字段类型有关,实践多了就知道了,多练习、多理解,多验证!

  •  下面对常见的字段类型,为大家开发使用提供参考:

字段分类字段类型实体中取出来的数据类型实体中取出来的数据值是否可直接转换成字符串使用
常规字段单据编号字符串原值
单据类型DynamicObject对象
单据状态字符串状态值
文本/多行文本字符串原值
多语言文本LocaleValue原值
日期日期原值
整数整数原值
小数浮点数原值
数量浮点数原值
单价浮点数原值
金额浮点数原值
下拉列表字符串枚举值
复选框布尔True/False

资料类

(F8字段)

组织DynamicObject对象
计量单位DynamicObject对象
用户/创建人/修改人DynamicObject对象
计量单位DynamicObject对象
数据分组DynamicObject对象
批次DynamicObject对象
多选辅助资料DynamicObjectCollection对象集合
多选基础资料DynamicObjectCollection对象集合
单选辅助资料DynamicObject对象
基础资料DynamicObject对象
多类别基础资料列表字符串字符串
多类别基础资料DynamicObject对象对象集合
实体类单据体DynamicObjectCollection对象集合
子单据体DynamicObjectCollection对象集合
子单据头DynamicObjectCollection对象集合


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

篇幅较长,写得也比较细,有点啰嗦,大家多理解,有基础的小伙伴可能会调侃:"建议从电脑开机开始讲!"

主要是作为入门讲解,对系统单据的数据构成不太熟悉的小伙伴还是有必要体系化的理解一遍的,后面就要开始讲插件啦。

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

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

示例Python 脚本.rar(2.13KB)


对接第三方系统常用方式

import clr clr.AddReference("System") clr.AddReference("System.Core") clr.AddReference("Kingdee.BOS")  clr.AddReference("Kingdee.BOS.Core")  clr.AddReference("Kingdee.BOS.DataEntity")  clr.AddReference("Newtonsoft.Json")  from Kingdee.BOS.Util import * from Kingdee.BOS.Core.DynamicForm import * from Kingdee.BOS.Core.DynamicForm.PlugIn import * from Kingdee.BOS.Core.DynamicForm.PlugIn.Args import * from Kingdee.BOS.Core.Metadata.FormElement import *  from System import * from System.Net import * from System.IO import * from System.Linq import * from System.Text import * from Newtonsoft.Json import * from Newtonsoft.Json.Linq import * class dog:   tenantId=0   relationshipId="12621517591749"   id=""   key="" def AfterExecuteOperationTransaction(e):   billid="";   Billno = "";   this.OperationResult.IsShowMessage = True;   #保存之后 Operation_Save   #提交之后 Operation_Submit   #审核之后 Operation_Audit   if this.FormOperation.OperationId == FormOperation.Operation_Save:       obj = e.DataEntitys;       for  item in obj:         billid = item["Id"].ToString();         Billno = item["BillNo"].ToString();         SendData(billid,Billno); #发送到第三方服务器          def post(url,postdata):   encode = "UTF-8";   webRequest = HttpWebRequest.Create(url);   webRequest.Method = "POST";   webRequest.ContentType = "application/json";    paraUrlCoded = postdata;   payload = Encoding.GetEncoding(encode.ToUpper()).GetBytes(paraUrlCoded);   webRequest.ContentLength = payload.Length;   writer = webRequest.GetRequestStream();   writer.Write(payload, 0, payload.Length);   writer.Close();   response = webRequest.GetResponse();   sr = StreamReader(response.GetResponseStream(), Encoding.GetEncoding(encode));   res = sr.ReadToEnd();    return res;  def SendData(billid, Billno):   url = "http://127.0.0.1:8052/SendData";   dog.id = billid;   result_str = post(url,JsonConvert.SerializeObject(dog));   postresult = JObject.Parse(result_str);   result = OperateResult();    if postresult != None:      if postresult["code"].ToString() == "200":      #注意解析返回格式:接口返回格式 {"code":"200","msg":"success"}       #TojObjectresult = JObject.Parse(postresult["result"].ToString());       result.SuccessStatus = True;       result.PKValue = billid;       result.Number = Billno;       result.Message = postresult["msg"].ToString(); #"【{0}】- 同步成功【主键值:{1}】【消息:{2}】!".format(Billno,TojObjectresult["pkid"],TojObjectresult["msg"]);       this.OperationResult.OperateResult.Add(result);       return;   else:     result.SuccessStatus = False;     result.PKValue = billid;     result.Number = Billno;     result.Message = "【{0}】- 同步失败 !".format(Billno);     this.OperationResult.OperateResult.Add(result);   return;


简单的python开发集合

记录一下python简单的插件开发- beforef7select&基础资料组织 列表只显示用户拥有的组织权限

记录一下python简单的插件开发- 单元格格式化事件&物料收发汇总格式化仓位编码

记录一下python简单的插件开发- 菜单按钮事件(表体菜单按钮事件)执行SQL语句

python插件 - 动态 简单账表 通过SQL存储过程输出列实现动态展示

Python插件 - 将生产订单二开字段携带至下级订单上 

Python插件 - 单据转换插件-WebApi下推单据 自定义入参插件 

简单Python插件 - 点击菜单弹窗动态表单录入数据返回父窗体 

Python插件 - 【服务插件】取文本字段值赋值基础资料字段

Python插件 - 保存、审核、提交 发送请求传输至第三方系统


作者:敏蝶老李头

来源:金蝶云社区

原文链接:https://vip.kingdee.com/article/452432069977609216?productLineId=1&lang=zh-CN

著作权归作者所有。未经允许禁止转载,如需转载请联系作者获得授权。