金蝶云星空python插件开发总结(持续更新...)

in 普通BLOG
0 评论 阅读量:40

平台介绍

金蝶云星空平台(企业版)进行插件开发,利用平台IronPython能力,开发python插件。python在平台中,是一种借壳执行的操作方式,故无法使用任何外部的import动作,只能引用内部的组件和实体对象。目前平台使用的是 IronPython 2.7.x 版本,开发时必需严格使用 [Python2.7] 语法及标准库,绝不可运用 Python3.x 的新特性,以免出现兼容性问题。

开发技巧

  1. 开发组件引用(clr.AddReference) 和 实体对象导入(from XXX import *) 尽量多、全,不用考虑性能问题,避免从哪里复制过来的代码,因为引用和导入的遗漏导致方法无法使用报错
  2. 区分字段属性的(标识)字段名绑定实体属性,字段预加载事件@OnPreparePropertys 中使用的是(标识),DataEntitys取值中使用的是绑定实体属性
  3. 测试过程中,尽管加上[操作前确认提示]、[操作成功后提示],测试中就会有弹窗反馈,就可以知道有没有走到服务插件代码中;另外需要注意!服务插件重新编辑保存后,单据要关闭重新打开后才生效!
  4. 有部分数据处理,如实在无法理解到基础库实体对象的方法如何使用,可以尝试在python中使用内置函数解决;如一些基础的字符串转换、处理,无需import导入的动作(服务插件中不支持导入)
  5. 实体类型Entity中不存在名为XXX的属性,注意看下属性是归属于当前单据,还是由其他属性引用带入。如果是由其他属性(元素类型为:基础资料)引用,可以将该属性打印或者保存,观察下数据结构再进行获取

    # 以下示例仅供参考
    F_PVRH_BaseProperty_qtr = ("{0}").format(FEItem['F_PVRH_Base2']['Specification']) # 产品编号
    F_PVRH_Creator = ("{0}").format(billObj['F_PVRH_CreatorId']['Name']) # 制单人名称
    F_PVRH_Base = ("{0}").format(billObj['F_PVRH_Base']['Name']) # 客户名称
    
    _name = first_bill_obj['Name'][2052] # 多语言文本示例 取得商品名称 2052=>中文
  6. 写日志到上机操作日志控制台

    log = LogObject()
    log.pkValue = bill_no + " start" # 唯一标志
    log.Description = bill_no + " 推送开始"
    log.OperateName = "推送开始"
    log.ObjectTypeId = this.BusinessInfo.GetForm().Id # 操作的业务对象ID
    log.SubSystemId = this.BusinessInfo.GetForm().SubsysId # 子系统Id
    log.Environment = OperatingEnvironment.BizOperate # 操作员
    LogServiceHelper.WriteLog(this.Context, log)

    二开案例.服务插件.写日志
    Python插件中记录上机操作日志

  7. 调试常用
  8. 表单类插件:this.View.ShowMessage("调试信息")
  9. 服务类插件:raise Exception("调试信息")
  10. 数据库操作示例
    在金蝶云星空中,ExecuteScalarExecuteExecuteDataSetExecuteDynamicObject是四个常用的数据库操作方法,它们的区别如下:
  11. ExecuteScalar
  12. 功能:执行SQL语句并返回结果集的第一行第一列的值。
  13. 适用场景:用于查询单一值,例如统计结果(如COUNTSUM等)。
  14. 返回值:指定的数据类型(如intstring等)。
  15. Python插件代码示例

     ctx = e.Context
     sql = "SELECT COUNT(1) FROM T_BD_MATERIAL"
     CNT = DBUtils.ExecuteScalar(ctx, sql, None)
     return int(CNT)
  16. Execute
  17. 功能:执行SQL语句(如INSERTUPDATEDELETE等),返回受影响的行数。
  18. 适用场景:用于执行修改数据库的操作,如插入、更新或删除数据。
  19. 返回值:整数,表示受影响的行数。
  20. Python插件代码示例

     sql = """
     /*dialect*/
     UPDATE T_XXXX_YYENTRY  -- 替换为实际的明细表名
     SET FNEWVALUE = '新值'  -- 替换为需要更新的字段名和值
     WHERE FDETAILID = '{0}'  -- 替换为明细ID字段名
     AND FID = {1}  -- 替换为主表ID字段名
     """.format(
       this.View.Model.GetValue("FDETAILID", 0).ToString(),  # 明细ID
       mainTableId  # 主表ID
     )
    affected_rows = DBUtils.Execute(this.Context, sql)
  21. ExecuteDataSet
  22. 功能:执行SQL语句并返回一个DataSet对象,包含一个或多个DataTable
  23. 适用场景:用于查询多行多列的数据,适合需要处理复杂结果集的场景。
  24. 返回值DataSet对象,可以通过Tables属性访问具体的数据表。
  25. Python插件代码示例

    sql = "/*dialect*/SELECT FMATERIALID, FNumber FROM T_BD_MATERIAL"
    ds = DBUtils.ExecuteDataSet(this.Context, sql)
    dt = ds.Tables[0]
  26. ExecuteDynamicObject
  27. 功能:执行SQL语句并返回一个dynamic对象,适合查询单行多列的数据。
  28. 适用场景:查询单行数据,且需要直接访问列值。
  29. 返回值:一个dynamic对象,其中每个属性对应查询结果的一列。
  30. Python插件代码示例

     sql = "/*dialect*/SELECT FMATERIALID, FNumber FROM T_BD_MATERIAL WHERE FMATERIALID = 1"
     result = DBUtils.ExecuteDynamicObject(this.Context, sql)
    
     first = next(iter(result), None)
     if first:
       saler_id = first["FMATERIALID"].ToString()
       create_date = first["FNumber"].ToString()
  31. 总结
  32. ExecuteScalar:适合查询单一值。
  33. Execute:适合执行修改操作,返回受影响行数。
  34. ExecuteDataSet:适合查询多行多列数据,返回DataSet
  35. ExecuteDynamicObject:适合查询单行多列数据,返回动态对象。
  36. 示例1

    from Kingdee.BOS.ServiceHelper import DBUtils
    
    def SomeFunction(e):
     # 查询销售订单信息
     sql = """
         /*dialect*/SELECT FSalerId,FCreateDate
         FROM T_SAL_ORDER
         WHERE FID = '{0}'
         """ . format(str(FID))
     ds = DBUtils.ExecuteDataSet(ctx, sql)
     saler_id = ds.Tables[0].Rows[0]["FSalerId"]
     create_date = ds.Tables[0].Rows[0]["FCreateDate"]

参考代码


# 字段预加载事件
def OnPreparePropertys(e):
    # 预加载字段-单据头
    e.FieldKeys.Add('FBillNo')
    e.FieldKeys.Add('F_PVRH_Base3')
    e.FieldKeys.Add('F_PVRH_Base') # 客户
    e.FieldKeys.Add('F_PVRH_Text') # 联系人
    e.FieldKeys.Add('F_PVRH_Text2') # 电话
    e.FieldKeys.Add('F_PVRH_Text1') # 地址

    # 预加载字段-单据体(明细信息)
    e.FieldKeys.Add('F_PVRH_Base2')
    e.FieldKeys.Add('F_PVRH_Qty')
    e.FieldKeys.Add('F_PVRH_Text15')    
    e.FieldKeys.Add('F_PVRH_Text16')
    e.FieldKeys.Add('F_PVRH_Remarks2')

# 执行操作事务后事件 AfterExecuteOperationTransaction
def AfterExecuteOperationTransaction(e):
    first_bill_obj = e.DataEntitys[0]
    bill_no = first_bill_obj['BillNo']
    
    # 上机日志
    LogObject1 = LogObject()
    LogObject1.pkValue = bill_no + " start"
    LogObject1.Description = bill_no + " 进入推送"
    LogObject1.OperateName = "推送进入"
    LogObject1.ObjectTypeId = this.BusinessInfo.GetForm().Id
    LogObject1.SubSystemId = this.BusinessInfo.GetForm().SubsysId
    LogObject1.Environment = OperatingEnvironment.BizOperate
    LogServiceHelper.WriteLog(this.Context, LogObject1)


    for billObj in e.DataEntitys:

        F_PVRH_Base = ("{0}").format(billObj['F_PVRH_Base']['Name']) # 一些数据类型需要进入下一级抓取
        F_PVRH_Base3 = ("{0}").format(billObj['F_PVRH_Base3']['Name'])
        postParam = {
            'FBillNo': billObj['BillNo'],
            'F_PVRH_Base': tidyString(F_PVRH_Base),
            'F_PVRH_Base3': tidyString(F_PVRH_Base3),
            'F_PVRH_Text': tidyString(billObj['F_PVRH_Text']),
            'F_PVRH_Text2': tidyString(billObj['F_PVRH_Text2']),
            'F_PVRH_Text1': tidyString(billObj['F_PVRH_Text1']),
            'F_PVRH_Remarks': tidyString(billObj['F_PVRH_Remarks']),
            'FEntity': [],
        }

        FEntity = billObj['FEntity'] # 单据体(明细信息)
        for FEItem in FEntity:
            F_PVRH_Base2 = ("{0}").format(FEItem['F_PVRH_Base2']['Name'])
            F_PVRH_BaseProperty_qtr = ("{0}").format(FEItem['F_PVRH_Base2']['Specification'])
            postParam['FEntity'].append({
                'F_PVRH_Base2': tidyString(F_PVRH_Base2),
                'F_PVRH_Qty': FEItem['F_PVRH_Qty'],
                'F_PVRH_Text15': tidyString(FEItem['F_PVRH_Text15']),    
                'F_PVRH_Text16': FEItem['F_PVRH_Text16'],
                'F_PVRH_Remarks2': FEItem['F_PVRH_Remarks2'],
            })

    try:
        postData = {
            'type': 'k3_business_bill',
            'data': postParam,
        }
        postData = JsonUtil.Serialize(postData)
        post('https://xxx.com/api/push', postData)
    except Exception as e:
        error_message = str(e)
        LogObject2 = LogObject()
        LogObject2.pkValue = bill_no + " end"
        LogObject2.Description = bill_no + " 结束推送,异常:" + error_message
        LogObject2.OperateName = "推送异常"
        LogObject2.ObjectTypeId = this.BusinessInfo.GetForm().Id
        LogObject2.SubSystemId = this.BusinessInfo.GetForm().SubsysId
        LogObject2.Environment = OperatingEnvironment.BizOperate
        LogServiceHelper.WriteLog(this.Context, LogObject2)
    finally:
        LogObject2 = LogObject()
        LogObject2.pkValue = bill_no + " end"
        LogObject2.Description = bill_no + " 结束推送"
        LogObject2.OperateName = "推送结束"
        LogObject2.ObjectTypeId = this.BusinessInfo.GetForm().Id
        LogObject2.SubSystemId = this.BusinessInfo.GetForm().SubsysId
        LogObject2.Environment = OperatingEnvironment.BizOperate
        LogServiceHelper.WriteLog(this.Context, LogObject2)

def post(url, postData):
    bytes = Encoding.ASCII.GetBytes(postData)
    webRequest = HttpWebRequest.Create(url)
    webRequest.Method = 'POST'
    webRequest.ContentType = 'application/json'
    webRequest.Timeout = 1000 * 60 * 10
    webRequest.ContentLength = bytes.Length

    webRequest.GetRequestStream().Write(bytes, 0, bytes.Length)
    webRequest.GetRequestStream().Flush()
    webRequest.GetRequestStream().Close()
    webResponse = webRequest.GetResponse()
    streamReader = StreamReader(webResponse.GetResponseStream(), Encoding.GetEncoding('utf-8'))
    result = streamReader.ReadToEnd()
    return result

def get(url):
    webRequest = HttpWebRequest.Create(url)
    webRequest.Method = 'GET'
    webResponse = webRequest.GetResponse() 
    streamReader = StreamReader(webResponse.GetResponseStream(), Encoding.GetEncoding('utf-8'))
    result = streamReader.ReadToEnd()
    return result

# 中文字符串转换成Unicode编码 避免在 Encoding.ASCII.GetBytes 方法中被转成乱码
def tidyString(s):
    return s.encode('unicode_escape').decode('ascii') if isinstance(s, str) else s

参考资料

插件实战开发-新手入门教程-服务插件
【新手入门】插件实操【分享汇总】
Python开发
服务插件 代表性"e.DataEntitys"取值
服务插件如何遍历单据体明细,获取单据体明细行内码?
#使用技巧#Python插件调试技巧及常见报错分析(新手必看,老手看不上的 干货)原创

Comments are closed.