一、【业务需求】
采购入库单上的部门和其他出库单的部门都可以在物料收发明细表上体现,现在在直接调拨单上新增了调入部门和调出部门字段,或者分步式调出单加上调出部门字段,分步式调入单加上调入部门字段,如何在物料收发明细表上体现新加的部门字段
二、【分析处理】
针对物料收发明细报表二开字段一般有两种类型
1、添加基础资料辅助字段
2、添加标准报表中不存在的实体列,用于展示个别单据一些扩展的个性化的字段数据
类型1:这种情况直接BOS中扩展添加报表列和过滤框中添加对应显示隐藏列即可,系统会自动关联获取对应数据进行展示
类型2:物料收发明细报表中的各实体字段在报表取数的临时表中都需要有对应的列定义,每种单据的都会进行对应的取数填充,一些特殊的字段在个别单据不存在时则填写默认值,
可以理解为报表数据的临时表是一个大综合,集合了所有库存单据需要用于展示的字段,基于上面的处理逻辑,单据上扩展的字段需要在报表展示无法实现,需要二开实现。
三、【二开实现】
针对类型2,分析几种二开方案
1、继承报表服务端组件,重写GetReportData方法,操作DataTable添加需要展示的数据列名,并进行相应赋值,禁用系统原有的服务插件,添加启用新插件
protected override DataTable GetReportData(string tablename, IRptParams filter) { DataTable dt = base.GetReportData(tablename, filter); // 添加二开的列到DataTable, 赋值 }
参考:
2、【存货核算报表二开增加字段】
注意点:报表服务器插件只支持一个,不要继承报表基类插件SysReportBaseService, 务必继承系统原有插件,启用新插件
2、 上面方案1中处理简单,也是标准的二开操作,建议采用。有时候想用Python去快速处理,但是基于1中的继承二开方式就无法实现了,受到【表单插件对直接sql账表汇总字段的干预】 文章的启发,考虑用表单插件去干预数据展示,绕开继承和只能注册一个服务器插件的限制
说明:以上两种方式都需要在BOS中扩展添加报表列和过滤框中添加对应显示隐藏列,列名保持一致
核心代码参考:
# coding=utf-8 import clr clr.AddReference('System') clr.AddReference('System.Data') clr.AddReference('Kingdee.BOS') clr.AddReference('Kingdee.BOS.Core') clr.AddReference('Kingdee.BOS.App.Core') clr.AddReference('Kingdee.BOS.App') clr.AddReference('Kingdee.BOS.DataEntity') clr.AddReference('Kingdee.BOS.Business.DynamicForm') clr.AddReference('Kingdee.BOS.ServiceHelper') clr.AddReference('Kingdee.BOS.Contracts') from Kingdee.BOS.Log import Logger from Kingdee.BOS import * from Kingdee.BOS.Core import * from Kingdee.BOS.Contracts import * from System.Data import * from System import * from System.Collections import * from Kingdee.BOS.App.Data import * from System.Collections.Generic import List from System.Collections.Generic import Dictionary from Kingdee.BOS.Core.DynamicForm.PlugIn import * from System import StringComparison from Kingdee.BOS.ServiceHelper import * from Kingdee.BOS.Core.Metadata.EntityElement import * def AfterBindData(e): moveModel = this.Model dt = moveModel.DataSource depName = DataColumn("FDEPNAME") dt.Columns.Add(depName) destDepName = DataColumn("FDESTDEPNAME") dt.Columns.Add(destDepName) billNamesMap = Dictionary[str,str]() billNamesMap.Add('直接调拨单','T_STK_STKTRANSFERIN') billNamesMap.Add('分步式调出单','T_STK_STKTRANSFEROUT') billNamesMap.Add('分步式调入单','T_STK_STKTRANSFERIN') bills = List[str]() lstSqlObj = List[SqlObject]() tmpTable = DBServiceHelper.CreateTemporaryTableName(this.Context) BuildTmpTable(this.Context, tmpTable) tmpDt = DataTable(tmpTable) otColumn1 = DataColumn() otColumn1.ColumnName = "FFORMTABLENAME" tmpDt.Columns.Add(otColumn1) otColumn2 = DataColumn() otColumn2.ColumnName = "FFORMNAME" tmpDt.Columns.Add(otColumn2) otColumn3 = DataColumn() otColumn3.ColumnName = "FBILLNO" tmpDt.Columns.Add(otColumn3) otColumn4 = DataColumn() otColumn4.ColumnName = "FDEPNAME" tmpDt.Columns.Add(otColumn4) otColumn5 = DataColumn() otColumn5.ColumnName = "FDESTDEPNAME" tmpDt.Columns.Add(otColumn5) for i in range (0, dt.Rows.Count): fbillname = Convert.ToString(dt.Rows[i]["FBILLNAME"]) if fbillname != '' and billNamesMap.ContainsKey(fbillname) and bills.Contains(dt.Rows[i]["FBILLNO"]) == False: tmpDr = tmpDt.NewRow() tmpDr["FFORMTABLENAME"] = billNamesMap[fbillname] tmpDr["FFORMNAME"] = fbillname tmpDr["FBILLNO"] = dt.Rows[i]["FBILLNO"] tmpDr["FDEPNAME"] = "" tmpDr["FDESTDEPNAME"] = "" tmpDt.Rows.Add(tmpDr) bills.Add(Convert.ToString(dt.Rows[i]["FBILLNO"])) DBUtils.BulkInserts(this.Context, tmpDt) sql = '''MERGE INTO {0} IT USING ( SELECT T1.FBILLNO, DEPL.FNAME FROM {0} T1 JOIN T_STK_STKTRANSFERIN T2 ON T1.FBILLNO = T2.FBILLNO JOIN T_BD_DEPARTMENT_L DEPL ON T2.FNOTE = DEPL.FDEPTID WHERE FFORMTABLENAME = 'T_STK_STKTRANSFERIN' AND FFORMNAME = '直接调拨单' ) UT ON (IT.FBILLNO = UT.FBILLNO) WHEN MATCHED THEN UPDATE SET IT.FDEPNAME = UT.FNAME'''.format(tmpTable) paras = List[SqlParam]() sqlObject = SqlObject(sql, paras) lstSqlObj.Add(sqlObject) if lstSqlObj.Count > 0: DBUtils.ExecuteBatch(this.Context, lstSqlObj) sqlStr = 'SELECT * from ' + tmpTable dataDt = DBUtils.ExecuteDynamicObject(this.Context, sqlStr) for i in range (0, dt.Rows.Count): billNo = Convert.ToString(dt.Rows[i]["FBILLNO"]) list_result = filter(lambda x: x["FBILLNO"] == billNo, dataDt) clrlist = List[object](list_result) if clrlist.Count > 0: dt.Rows[i]["FDEPNAME"] = Convert.ToString(clrlist[0]["FDEPNAME"]) dt.Rows[i]["FDESTDEPNAME"] = Convert.ToString(clrlist[0]["FDESTDEPNAME"]) sqlStr = 'DROP TABLE ' + tmpTable DBUtils.Execute(this.Context, sqlStr) def BuildTmpTable(ctx, tempTableName): sqlTM = " ( FFORMTABLENAME NVARCHAR(50) NULL, FFORMNAME NVARCHAR(50) NULL, FBILLNO NVARCHAR(50) NULL, FDEPNAME NVARCHAR(255) NULL, FDESTDEPNAME NVARCHAR(255) NULL)" sql = " CREATE TABLE {0} {1} ".format(str(tempTableName), str(sqlTM)) sqls = List[str]() sqls.Add(sql) guid = Guid.NewGuid() idxName = guid.ToString("N").Substring(0, 10) sql = " CREATE INDEX IDX_BILLNO{0} ON {1} ({2}) ".format(str(idxName), str(tempTableName), "FBILLNO") sqls.Add(sql) DBUtils.ExecuteBatchWithTime(ctx, sqls, sqls.Count, 120)
针对方案2的二开方式,几个问题:
1、刚开始思路是:将代码逻辑写在OnFormatRowConditions事件(格式化行数据,每一行会运行一次),通过一个标志位控制事件只跑一次更新逻辑,C#插件没问题,py会导致内存溢出问题,无解。
2、表单插件AfterBindData事件中的DataSource只返回第一页的数据,对于报表数据量超过设置的每页显示数据量时,剩余的数据则不会在触发事件被更新,可以将每页显示数据量设置为可选的最大值,如果一页还是无法展示完全,第二页的数据将不会执行取数和赋值更新,这种二开方式无法解决。
3、取数要采用批量获取,批量更新的方式,避免循环访问数据库,影响性能。
4、测试如果出现无法加载数据,内存暴涨溢出,导致系统崩溃,如果定位是插件代码的问题,慎用该方案,还原采用方案1的二开方式。
代码参考.rar(1.59KB)
推荐阅读