金蝶云星空服务插件[python]开发小结

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

需求背景

金蝶云星空的销售单据下推后,同步创建BOS单据工事依赖书工事依赖书进行审核操作后,需要将部分数据明细,同步到其他三方平台。基于金蝶云星空的服务插件能力,实现以上功能。服务插件支持C#、Python,这里使用Python实现。Python在该系统中,是一种借壳执行的操作方式,故无法使用任何外部的import动作,只能引用内部的组件和实体对象。

开发方案

  1. 进入金蝶云星空集成开发平台,找到指定BOS单据的字段属性,确认好所需数据
  2. 开发python服务插件代码,注意字段的预加载
  3. 在指定的操作按钮(这里是[审核])中,注册服务插件
  4. 测试的数据中心执行成功后,再加入上机操作日志中
  5. 部署到正式数据中心

开发总结

  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'])
  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("调试信息")

参考代码

# -*- coding: utf-8 -*-
# ******************* python服务插件@工事依赖书下推三方平台接口创建工单 *******************
import clr
# cloud插件开发组件引用
clr.AddReference('System')
clr.AddReference('System.Core')
clr.AddReference('System.Data')
clr.AddReference("System.Web.Extensions")

clr.AddReference('Kingdee.BOS')
clr.AddReference('Kingdee.BOS.Core')
clr.AddReference('Kingdee.BOS.App')
clr.AddReference('Kingdee.BOS.App.Core')
clr.AddReference('Kingdee.BOS.App.LogService')
clr.AddReference('Kingdee.BOS.Contracts')
clr.AddReference('Kingdee.BOS.DataEntity')
clr.AddReference('Kingdee.BOS.ServiceHelper')
clr.AddReference('Kingdee.BOS.ServiceFacade.Common')
clr.AddReference('Newtonsoft.Json')


import sys
# cloud基础库实体对象
from Kingdee.BOS import *
from Kingdee.BOS.Core import *
from Kingdee.BOS.Core.Bill import *
from Kingdee.BOS.Core.DependencyRules import *
from Kingdee.BOS.Core.DynamicForm.PlugIn import *
from Kingdee.BOS.Core.DynamicForm.PlugIn.ControlModel import *
from Kingdee.BOS.Core.DynamicForm.PlugIn.Args import *
from Kingdee.BOS.Core.Metadata.FormElement import *
from Kingdee.BOS.Core.Log import *
from Kingdee.BOS.Log import *
from Kingdee.BOS.App.LogService import *
from System import *
from System.Data import *
from System.Threading import *
from System.Text import *
from System.Collections.Generic import *
from System.Linq import *
from System.IO import *
from System.Net import *
from System.Security.Cryptography import *
from System.Web.Script.Serialization import *
from System.Collections.Generic import Dictionary
from Kingdee.BOS.App.Data import *
from Kingdee.BOS.ServiceHelper import *
from Kingdee.BOS.Core.DynamicForm import *
from Kingdee.BOS.Core.Metadata.EntityElement import *
from Kingdee.BOS.Core.Metadata.FieldElement import *
from Kingdee.BOS.Orm.DataEntity import *
from Kingdee.BOS.ServiceFacade import *
from Kingdee.BOS.Util import *
from Newtonsoft.Json import *
from Newtonsoft.Json.Linq import *

sys.setdefaultencoding('utf-8')

# 字段预加载事件
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.