# 报表取数服务插件 — IronPython

源：[金蝶python插件示例/简单账表(采购日报表)取数服务插件示例.txt](../../../../../金蝶python插件示例/简单账表(采购日报表)取数服务插件示例/采购日报表(动态列)取数服务插件示例.txt)

```python
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.Core.Enums 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 *
#初始化，在此事件中设置报表的属性全局参数
def Initialize():
	this.ReportProperty.ReportType=ReportType.REPORTTYPE_NORMAL;
	this.IsCreateTempTableByPlugin=True;
	#是否支持分组汇总,在后面GetSummaryColumnInfo方法中添加汇总字段,
	#要在BOS中过滤框的汇总页签配置汇总信息,可参考:简单账表分组汇总设置
	this.ReportProperty.IsGroupSummary=True;
	#IsUIDesignerColumns=False,表示报表的列通过插件控制,后续在GetReportHeaders中构建列头
	#需要在BOS过滤框的显示隐藏列中维护,字段标识与临时表字段保持一致
	#账表列头构建更多详细说明参考:账表列构建
	this.ReportProperty.IsUIDesignerColumns=False;

#创建临时报表,正式进入账表取数sql拼接并取数，把账表取数结果放到创建的临时表中
#如果参数(this.IsCreateTempTableByPlugin=True)，即调用BuilderReportSqlAndTempTable构建临时表
#否则调用以下3个接口，完成账表取数逻辑的sql指令即：BuilderSelectFieldSQL、BuilderTempTableOrderBySQL、BuilderFormWhereSQL
#rptfilter:账表参数，可以从这里获取过滤条件等
#tableName:系统自动创建的账表临时表名，具备唯一性，最终报表页面展示的数据绑定此临时表，所以最终的报表结果数据要写入此临时表中
def BuilderReportSqlAndTempTable(rptfilter,tableName):
	#baseDataTemp=filter.BaseDataTempTable;#基础资料临时表;若设置了数据范围权限，该表会把根据数据范围过滤出来的内码存入临时表；
	#循环获取所有基础资料数据范围的数据，可用来拼接到报表SQL里面实现数据权限过滤
	#for b in baseDataTemp:
	#	baseType=b.BaseDataFormId;#基础资料FormId
	#	PKFldName=b.PKFieldName;#临时表中基础资料主键字段名,例如,FORGID
	#	baseTempTab=b.TempTable;#基础资料数据范围临时表名
	#filterStr=filter.FilterParameter.FilterString;#过滤框条件页签过滤表达式
	#过滤框快捷过滤页签的实体数据包，从这里面获取自定义的过滤字段值
	#DynamicObject类型,用前面讲的实体数据包操作方式取值，用绑定实体属性标识
	custFilter = rptfilter.FilterParameter.CustomFilter;
	if(custFilter==None):
		return;
	orgObj=custFilter["F_BPW_OrgId"];#获取组织
	whereOrgs="";
	if(orgObj<>None):
		orgId=("{0}").format(orgObj["Id"]);#组织ID
		whereOrgs=(" and a.FPURCHASEORGID in ({0}) ").format(orgId);#选择了组织，拼接组织过滤
	materials=custFilter["F_BPW_Materials"];#物料多选过滤
	matList=List[str]();
	if(materials<>None):
		for m in materials:
			materialNum="'"+str(m["F_BPW_Materials"]["Number"])+"'";#取出过滤框选择的多个物料编码
			matList.Add(materialNum);
	whereMat=(" and m.FNumber in ({0})").format(str.Join(",",matList)) if(matList.Count>0) else "";#拼接物料多选过滤
	beginDate=str(custFilter["F_BPW_BeginDate"]);#获取开始日期
	EndDate=str(custFilter["F_BPW_EndDate"]);#获取结束日期
	beginDate=str(DateTime.Parse(("{0}").format(beginDate)).AddDays(-1));#开始日期的前一天
	itemDate=beginDate;
	fldsSql=List[str]();#动态列头SQL,相当于Select后面的部分字段是动态拼接的，以此来实现动态列
	#从开始日期起，循环加1天，一直到结束日期，过滤界面要控制录入的开始日期必须<结束日期,在过滤界面注册表单插件即可实现
	while(itemDate<>EndDate):
		myDate=DateTime.Parse(("{0}").format(itemDate));
		fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);#根据每个日期构建唯一的列名
		ss=("{0}=SUM(Case when a.FDATE='{1}' then b.FQty else 0 end)").format(fldKey,myDate);#构建每天订单数量合计SQL
		fldsSql.Add(ss);#将SQL添加到动态列SQL集合中备用
		nextDate=myDate.AddDays(1);
		itemDate=str(nextDate);#迭代+1天
	myDate=DateTime.Parse(("{0}").format(itemDate));#这里是选择的截止日期，由于循环到最后一天跳出了，这里补充一天的数据
	fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);
	ss=("{0}=SUM(Case when a.FDATE='{1}' then b.FQty else 0 end)").format(fldKey,myDate);
	fldsSql.Add(ss);#将SQL添加到动态列SQL集合中备用
	#raise Exception(str.Join(',',fldsSql));#调试时，可用此行代码，看看构建的动态列SQL对不对
	#组装最终写入报表临时表的SQL
	#注意!!!: 最终临时表一定要有FIDENTITYID ，要从1开始，且不重复 ，不断号，不然前台显示空白!!!!
	sql=("""/*dialect*/ select row_Number() Over(order by a.orgName,a.FMaterialNum)  FIDENTITYID, a.*
into {0} 
from 
(select  orgL.FNAME orgName,m.FNUMBER FMaterialNum,mL.FNAME FmaterialName,mL.FSPECIFICATION SpecNum,unitL.FNAME unit,
{1}
from t_PUR_POOrder a
inner join T_ORG_ORGANIZATIONS org on org.FORGID=a.FPURCHASEORGID
inner join T_ORG_ORGANIZATIONS_L orgL on orgL.FORGID=org.FORGID and orgL.FLOCALEID=2052
inner join t_PUR_POOrderEntry b on a.FID=b.FID
inner join T_BD_MATERIAL m on m.FMATERIALID=b.FMATERIALID
inner join T_BD_MATERIAL_L mL on mL.FMATERIALID=m.FMATERIALID and mL.FLOCALEID=2052
inner Join T_BD_UNIT_L unitL on unitL.FUNITID=b.FUNITID and  unitL.FLOCALEID=2052
where a.FDATE>='{3}' and a.FDATE<='{4}' {2} {5}
group by orgL.FNAME,m.FNUMBER,mL.FNAME,mL.FSPECIFICATION,unitL.FNAME
) a 
  """).format(tableName,str.Join(',',fldsSql),whereOrgs,beginDate,EndDate,whereMat);
  	#raise Exception(sql);#可以通过此方法弹出Sql语句进行调试验证
	DBUtils.Execute(this.Context,sql);#执行SQL，将报表数据写入临时表

#构建账表列头
def GetReportHeaders(Filter):
	header=ReportHeader();
	localEid=this.Context.UserLocale.LCID;#获取当前语言环境代码,中文为2052
	header.AddChild("orgName",LocaleValue("采购组织",localEid));#字段名,列头标题,字段名与临时表中的字段名保持对应，相当于每一个列头对应临时表的哪个字段
	header.AddChild("FMaterialNum",LocaleValue("物料编码",localEid));
	header.AddChild("FmaterialName",LocaleValue("物料名称",localEid));
	header.AddChild("unit",LocaleValue("采购单位",localEid));
	header.AddChild("SpecNum",LocaleValue("规格型号",localEid));
	
	
	#下面根据过滤条件选择的日期区间，动态构建列头,和上面构建SQL字段的逻辑类似
	custFilter = Filter.FilterParameter.CustomFilter;
	beginDate=str(custFilter["F_BPW_BeginDate"]);#获取开始日期
	EndDate=str(custFilter["F_BPW_EndDate"]);#获取结束日期
	itemDate=beginDate;
	fldsSql=List[str]();
	while(itemDate<>EndDate):
		myDate=DateTime.Parse(("{0}").format(itemDate));
		fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);#这里的字段名要和前面构建的SQL字段对应
		header.AddChild(fldKey,LocaleValue(("每日订单数据&{0}年{1}月{2}日").format(myDate.Year,myDate.Month,myDate.Day),localEid),SqlStorageType.SqlDecimal);
		nextDate=myDate.AddDays(1);
		itemDate=str(nextDate);
	myDate=DateTime.Parse(("{0}").format(itemDate));
	fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);#这里的字段名要和前面构建的SQL字段对应
	header.AddChild(fldKey,LocaleValue(("每日订单数据&{0}年{1}月{2}日").format(myDate.Year,myDate.Month,myDate.Day),localEid),SqlStorageType.SqlDecimal);
#设置列的索引，使其可以按照以上列头构建的顺序显示
	colIndex=0;
	for child in header.GetChilds():
		if(child.GetChildCount()==0):
			child.ColIndex=colIndex;
			colIndex=colIndex+1;
		else:
			child.ColIndex = colIndex;
			colIndex=colIndex+1;
			for childHeader in child.GetChilds():
				childHeader.ColIndex=colIndex;
				colIndex=colIndex+1;
	return header;

#设置报表表头字段值
#这里主要是把过滤框设置的字段值，显示到报表表头
def GetReportTitles(Filter):
	reportTitles=ReportTitles();
	custFilter=Filter.FilterParameter.CustomFilter;#获取过滤框的数据包
	orgObj=custFilter["F_BPW_OrgId"];#获取组织
	beginDate=str(custFilter["F_BPW_BeginDate"]);#获取开始日期
	EndDate=str(custFilter["F_BPW_EndDate"]);#获取结束日期
	if(orgObj<>None):
		reportTitles.AddTitle("F_PAEZ_OrgId", orgObj["Name"]);
	reportTitles.AddTitle("F_PAEZ_BeginDate", beginDate);
	reportTitles.AddTitle("F_PAEZ_EndDate", EndDate);
	materials=custFilter["F_BPW_Materials"];#物料多选过滤
	matNums=list(obj["F_BPW_Materials"]["Number"] for obj in materials if(obj<>None));
	matNumsStr=','.join(matNums);
	filterStr=Filter.FilterParameter.FilterString;#过滤框条件页签/高级页签过滤表达式
	sortStr=Filter.FilterParameter.SortString;#获取排序
	reportTitles.AddTitle("F_PAEZ_MatNums", sortStr);
	return reportTitles;

#设置报表底部合计列
def GetSummaryColumnInfo(rptfilter):
	result=List[SummaryField]();
	#由于这里的数量字段是动态构建的，所以也需要动态添加合计列,字段名与前面保持一致
	custFilter = rptfilter.FilterParameter.CustomFilter;
	beginDate=str(custFilter["F_BPW_BeginDate"]);#获取开始日期
	EndDate=str(custFilter["F_BPW_EndDate"]);#获取结束日期
	itemDate=beginDate;
	fldsSql=List[str]();
	while(itemDate<>EndDate):
		myDate=DateTime.Parse(("{0}").format(itemDate));
		fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);#这里的字段名要和前面构建的SQL字段对应
		result.Add(SummaryField(fldKey,BOSEnums.Enu_SummaryType.SUM));
		nextDate=myDate.AddDays(1);
		itemDate=str(nextDate);
	myDate=DateTime.Parse(("{0}").format(itemDate));
	fldKey=("F{0}_{1}_{2}").format(myDate.Year,myDate.Month,myDate.Day);
	result.Add(SummaryField(fldKey,BOSEnums.Enu_SummaryType.SUM));
	return result;

#汇总字段计算逻辑，如果不是单纯的sum(xxx)，可以在此方法中处理，本示例未作处理
#！！！这个GetSummaryColumsSQL事件返回的内容，最终会拼接到 Select后面，不能return ""
#！！！注意：此方法，仅支持2024.11.21及后面的版本
#以前的版本，Python插件不会触发GetSummaryColumsSQL，所以一直没报错，以前按照这篇文章编写的代码可能会引发如下图的报错：
#如果不需要干预合并逻辑，那个不要使用这个事件，把该事件的代码删除，即可解决此报错！
#如果需要使用这个事件可以参考这个：简单账表Python插件支持GetSummaryColumsSQL方法
#def GetSummaryColumsSQL(summaryFields):

#报表关闭触发，通常在此处清理报表过程产生的临时表
def CloseReport():
	this.DropTempTable();
```
