
学习了两个多月,调试插件真的不是很方便,定位问题显得很难,因此查阅了大量的社区文章,结合自身对python的认知,做了个能定位错误位置的账表插件,同时也抛出一些问题,希望能得到一些帮助,首先上问题:
1 - python如何安装和调用第三方模块?金蝶云使用的是2.6版的ironpython,调用的方法我还不熟悉,没法使用第三方模块大大限制了python的功能,或者如何将ironPython换成3.4版本的(好像没必要,我比较喜欢的是3.5以上版本的dict有了顺序的功能)
2 - 如果调用的数据库表函数或者存储过程报错,定位到的是运行sql的语句,但报的错误完全看不出是sql错误,这里要如何实现能明显看出是sql错误
3 - 如何在更新表列的时候,在文本列中添加单引号(将sql语句完整写入表的一个列中)
c#我不太熟悉,有些系统应该提供有的功能我自己去实现了
然后是插件的具体情况:
描述:简单账表作为报表使用,这里有加一个功能:就是形成一个报表使用的记录表,定义一个存储过程,将报表的使用情况记录下来,用来管理报表的使用率(whoCheck函数)
插件只处理报表显示的功能,报表业务逻辑方面封装到数据库中的表函数或者存储过程中
具体代码:
```python
#-*- encoding:utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding("GB2312")
import clr
clr.AddReference("System")
clr.AddReference("Kingdee.BOS")
clr.AddReference("Kingdee.BOS.Core")
clr.AddReference("Kingdee.BOS.DataEntity")
clr.AddReference("Kingdee.BOS.App")
clr.AddReference("Kingdee.BOS.Contracts")
clr.AddReference("Kingdee.BOS.ServiceHelper")
from Kingdee.BOS import *
from Kingdee.BOS.Contracts import *
from Kingdee.BOS.Contracts.Report import *
from Kingdee.BOS.Core import *
from Kingdee.BOS.Core.Metadata import *
from Kingdee.BOS.Core.Report import *
from Kingdee.BOS.Core.SqlBuilder import *
from Kingdee.BOS.App.Data import *
from Kingdee.BOS.Orm.DataEntity import *
from System import *
from System.ComponentModel import *
from System.Collections.Generic import*
from System.Text import *
from System.Threading.Tasks import *
from Kingdee.BOS.ServiceHelper import *
""" 简单账表插件说明:
报错已经能到行数了,差不多了,有一个报错是金蝶云的问题,如果sql报错,反映不出来,你都不知道是C#函数问题还是sql报错
逻辑: 1 - 在形成报表过程中,将查询用户,查询参数等信息写入到一个表中,方便统计报表的使用率
2 - 插件仅做显示的逻辑,具体业务逻辑用存储过程或者表函数实现
"""
class SESSION:
""" 辅助类 新开发的报表要在__init__中填写报表名称,以及过滤参数的标识字符,在形成sql中用到
"""
def __init__(self, ctx=None, userID=None):
self.RPTNAME = "零售职员开单出库查询"
self.sessionid = DateTime.Now.ToString("yyyyMMddHHmmssfffff");
self.ctx = ctx
self.userID = userID
self.keys = ['F_JML_SDate','F_JML_EDate','F_JML_EMPTYPE'] # [key1,key2,...] 没设置过滤参数的留空
self.filters = [] # [v1,v2,...]会根据keys自动补上key对应的值
self.cols = [] # 用做报表列标题陈列,带有_的列标题,按组合表头处理
self.tempTableName = None # 如报错善后用的,貌似不太行。。。
def getFilters(self, key=None, filter=None, reset=0): # 过滤参数获取,过滤界面的字段作为KEY
""" 值类型/私有枚举类型,直接获得值
基础资料类型,通过id获取
"""
if reset == 1:
self.filters = []
if not self.filters and self.keys:
if filter is None :
raise Exception("插件错误:获取过滤参数需要输入filter参数")
pyfilter = filter.FilterParameter.CustomFilter;
if pyfilter is not None:
pass
self.filters.append(pyfilter["F_JML_SDate"].ToString())
self.filters.append(pyfilter["F_JML_EDate"].ToString())
#self.filters.append(pyfilter["F_JML_BRANCHID"]['id'].ToString()) # 基础资料获取ID
self.filters.append(pyfilter["F_JML_EMPTYPE"].ToString()) # 私有枚举类型,直接获得值
self.filters[-1] = 0 if self.filters[-1] == "" or self.filters[-1] is None else self.filters[-1]
if key and self.keys:
return self.filters[self.keys.index(key)]
else:
return self.filters
def getParameters(self):
return {key: self.filters[i] for i, key in enumerate(self.keys)}
def getColumns(self, tableName):
sql = ("""
SELECT T1.system_type_id as FDateTypeId , t3.name AS FDateType,t1.name AS FName
FROM sys.columns t1
INNER JOIN sys.objects t2 ON t2.object_id = t1.object_id
INNER JOIN sys.types t3 ON t3.user_type_id=t1.user_type_id
WHERE t2.name='{0}' AND t2.type='u'
""").format(tableName);
dcObj = DBUtils.ExecuteDynamicObject(self.ctx, sql);
cols = {} # {index:[单列] or index:{组合列}} -- 使用index作为key,是因为可以让字典变成按index顺序输出的字典(PY3.5+没这么麻烦)
splitName = '_'
localEid = self.ctx.UserLocale.LCID;
def ChildArgsList(columnName, showName, dataTypeId):
return [columnName, LocaleValue(showName, localEid), Enum.Parse(SqlStorageType, dataTypeId), True]
for item in dcObj:
columnName = item["FName"].ToString()
if columnName == str("FIDENTITYID"): # 隐藏不显示这个字段 FIDENTITYID
continue
# 判断是否是组合字段——使用分割符_判断
if columnName.find(splitName) > 0:
topName, subName = columnName.split(splitName, 1)
else:
topName = None
subName = columnName
# 区分组合列,单列
if topName:
if topName in [col['topName'] for k, col in cols.items() if isinstance(col, dict)]: # 组合名称已经创建,只需要加子列即可
for iCol, col in cols.items():
if isinstance(col, dict) and col.get('topName') == topName:
cols[iCol]['child'].append(ChildArgsList(columnName, subName, item["FDateTypeId"].ToString()))
break
else:
cols[len(cols)] = {
'topName':topName,
'child':[ChildArgsList(columnName, subName, item["FDateTypeId"].ToString())]
}
else:
cols[len(cols)] = ChildArgsList(columnName, subName, item["FDateTypeId"].ToString())
self.cols = cols
def whoCheck(self,VALUE): # 报表访问记录,怎样才能将特殊字符写入表的字符串列呢?
sql = """/*dialect*/ EXEC JML_P_RPT_RECODE_SET {0},'{1}',{2},'{3}','{4}'""".format(
self.sessionid,
self.RPTNAME,
self.userID,
self.formatSQLValue(self.getParameters()),
self.formatSQLValue(VALUE)
)
#paramList = List<SqlParam>();
#paramList.append(SqlParam("@SQL", KDDbType.String, str(VALUE)))
DBServiceHelper.Execute(self.ctx, sql)
def test(self, value): # 测试使用,写入测试数据到数据库
""" ctx = this.Context"""
dt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff")
DBServiceHelper.Execute(self.ctx, """/*dialect*/ INSERT INTO JML_LZF_LOG (T) VALUES('{0} - {1}')""".format(dt,self.formatSQLValue(value)))
def convert2sqltype(self, ctype):
""" 将C#的数据类型转换为sql server的数据类型"""
mappings = {
"System.Int64":"BIGINT",
"System.Int32":"INT",
"System.String":"VARCHAR(1000)",
"System.DateTime":"DATETIME",
"System.Date":"DATE",
"System.Decimal":"DECIMAL(18,4)",
}
return mappings.get(ctype,'VARCHAR(MAX)') # MAX这种方式可能会被金蝶云的sql解析语句报错
def formatSQLValue(self, value):
return str(value).replace("'","")
def GetCreateColumnsSql(self, cols):
""" 获取创建数据库表的列的字符串 即:(a int) 部分 #raise Exception(str(Encoding.GetEncodings()))
"""
maxColi = len(cols) - 1
create_table = []
for i, col in enumerate(cols):
columnName = col.ColumnName
dataType = self.convert2sqltype(col.DataType.ToString()) #Enum.Parse(SqlStorageType, col.DataType.ToString())
create_table.append(" [{0}] {1} ".format(columnName, dataType))
create_table[0] = '(' + create_table[0]
create_table[-1] = create_table[-1] + ')'
return ",".join(create_table)
def DropTempTable(self):
""" 这个功能还有问题,好像报错后不会删除已经生成的表"""
if self.tempTableName and DBServiceHelper.IsExistTable(self.ctx, self.tempTableName):
#DBUtils.DropSessionTemplateTable(self.ctx, self.tempTableName) # 会加#
ListDataServiceHelper.TryDropTable(self.ctx, self.tempTableName)
#
global sets
sets = SESSION(None, None)
def extract_tb(tb, limit = None):
""" 来自于 trackback 模块的逻辑改,因金蝶云的ipy2.6还不知道怎么引用外部模块 trackback,只能自己实现用于报错代码的追踪"""
if limit is None:
if hasattr(sys, 'tracebacklimit'):
limit = sys.tracebacklimit
list = []
n = 0
while tb is not None and (limit is None or n < limit):
lineno = tb.tb_lineno
co = tb.tb_frame.f_code
list.append("错误位置:文件名:{0}|行号:{1}|函数名:{2}".format(co.co_filename, lineno, co.co_name))
tb = tb.tb_next
n = n + 1
return list
class LogClass:
""" 报错装饰器,"""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
try:
result = self.func(*args, **kwargs)
except Exception as(e):
sets.DropTempTable() # 存在一个初始化先后的问题,由于内部条件限制(tempTable),initialize时候报错无碍
etype, value, tb = sys.exc_info()
err = "\n".join(extract_tb(tb))
raise Exception("函数{0}|{1}|{2}运行错误:\n{3}\n{4}".format(
self.func.func_name,
str(args),
str(kwargs),
err,
str(e)
))
return result
@LogClass
def Initialize():
""" 账表初始化
普通简单账表 ReportType.REPORTTYPE_NORMAL=0 树形帐表 ReportType.REPORTTYPE_TREE = 1 分页账表 ReportType.REPORTTYPE_MOVE = 2
"""
global sets
sets = SESSION(this.Context, str(this.Context.UserId)) # 每一次查询都会有一条记录
this.ReportProperty.ReportType = ReportType.REPORTTYPE_NORMAL;
this.ReportProperty.ReportName = LocaleValue(sets.RPTNAME, this.Context.UserLocale.LCID); # 报表名称,貌似不起作用
this.IsCreateTempTableByPlugin = True; # 插件创建临时表数据
this.ReportProperty.IsGroupSummary = True; # 支持分组汇总
this.ReportProperty.IsUIDesignerColumns = False; # ;#账表列头是否是通过BOSIDE设计,默认False
#this.ReportProperty.GroupSummaryInfoData.DefaultGroupbyString = "单据编号";
this.ReportProperty.IdentityFieldName="FIDENTITYID"; # 临时表的主键,默认字段名是 FIDENTITYID 是从1开始的整数序列, 必须从1开始、不能重复,不能跳号。否则报表页面会显示空白
@LogClass
def BuilderReportSqlAndTempTable(rptfilter,tableName):
""" 获取报表数据并写入临时表,tableName说是临时表,在数据库,建立的还是事实表
方式1:能select数据的,直接 select into tableName
方式2:存储过程不能直接 select into,需要先exec出数据,然后通过dataSet的列信息创建临时表,再将dataSet的数据导入这个临时表中
"""
sets.tempTableName = None
sets.getFilters(filter=rptfilter, reset=1)
sets.whoCheck(tableName)
#sets.test("BuilderReportSqlAndTempTable-0- {}".format(tableName))
##!!!!! FIDENTITYID 一定要从1开始,不然前台显示没数据!!!!
sql="""/*dialect*/
SELECT *
into {0}
FROM JML_TF_RPT_LS_EMP_SALE_AND_OUTSTOCK('{2}','{3}',{4},{1})
""".format(tableName, this.Context.UserId, *sets.filters);
#sql = """exec [JML_P_RPT_LS_MONTHLY_AWARD] 202308,{0},1""".format(*sets.filters)
#sets.whoCheck(sql) # sql中的字符'还不能很好的处理,这里就看看咯
ds = DBServiceHelper.ExecuteDataSet(this.Context, sql)
if ds.Tables.Count != 0: # 如果返回了数据集,将数据集导回数据库的指定表中
dt = ds.Tables[ds.Tables.Count - 1] # 默认最后一个数据集出数,不是数据集的语句不会在数据集中占索引
dt.TableName = tableName
#tmpTableName = DBUtils.CreateSessionTemplateTable(this.Context, tableName, sets.GetCreateColumnsSql(dt.Columns))
DBUtils.Execute(this.Context, "create table {0} ".format(tableName) + sets.GetCreateColumnsSql(dt.Columns))
DBUtils.BulkInserts(this.Context, dt) # 将dataTable的数据返插回数据库中的临时表中
# 获取列信息,方式1不能通过datatable的方式获取,因此到数据库直接查看表结构(数据库临时表#x是不能这样查的)
sets.tempTableName = tableName
sets.getColumns(tableName)
#sets.test("BuilderReportSqlAndTempTable-99")
# def GetSummaryColumnInfo(filter): # 设置汇总列
# var result = base.GetSummaryColumnInfo(filter);
# result.Add(new SummaryField("FQty", Kingdee.BOS.Core.Enums.BOSEnums.Enu_SummaryType.SUM));
# return result
@LogClass
def GetReportTitles(Filter):
reportTitles = ReportTitles();
#customFiler=Filter.FilterParameter.CustomFilter;
#reportTitles.AddTitle("F_JML_test1", sets.getFilters("F_JML_test1"))
#reportTitles.AddTitle("F_JML_Test2", "345")
return reportTitles;
@LogClass
def GetReportHeaders(Filter):
header = ReportHeader();
for iCol, col in sets.cols.items():
if isinstance(col, list):
listHeader = header.AddChild(*col)
else: # dict
if len(col['child']) == 1: # 只有一个子列的情况下还原回单独的列
args = col['child'][0]
args[1] = args[0] + '_' + args[1] # 还原显示名称为数据库列名
listHeader = header.AddChild(*args)
else:
listHeader = header.AddChild()
listHeader.Caption = LocaleValue(col['topName'])
for subCol in col['child']:
listHeader.AddChild(*subCol)
listHeader.ColIndex = iCol
#listHeader.IsHyperlink = true; // 支持超链接
return header;
def CloseReport():
this.DropTempTable();
```
效果图:
