金蝶云星空 V8.0 报表插件二次开发实战:物料收发汇总表 7 大核心方法深度解析
1. 报表插件开发环境与基础架构
金蝶云星空 V8.0 的报表插件开发基于 BOS 平台构建,采用 C# 语言进行扩展开发。物料收发汇总表作为供应链核心报表,其插件开发需要深入理解金蝶的报表服务框架。开发前需确保环境配置完整:
- 开发工具:Visual Studio 2019+(需安装金蝶云星空开发插件)
- 基础框架:继承
SysReportBaseService基类 - 核心程序集:
Kingdee.BOS.Core.dllKingdee.BOS.Report.dllKingdee.BOS.ServiceHelper.dll
典型插件类骨架结构如下:
[HotUpdate] public class StockSummaryRptService : SysReportBaseService { // 临时表名定义 private string tmpRptTbl = "TM_STK_RPTCOMMON"; private string tmpFullTbl; private string tmpQcpTbl; // 核算维度服务 private ISCMFlexService FlexService; // 初始化方法 public override void Initialize() { base.Initialize(); this.FlexService = BD.Contracts.ServiceFactory.GetSCMServiceForFIN(base.Context); // 其他初始化逻辑... } // 其他核心方法实现... }关键设计要点:
- 临时表命名需遵循
TM_前缀规范 - 数据库操作必须使用
DBUtils工具类 - 所有SQL需添加
/*dialect*/注释保证跨数据库兼容
2. 初始化阶段核心方法解析
2.1 Initialize 方法:插件生命周期的起点
Initialize是报表插件第一个执行的方法,主要职责包括:
public override void Initialize() { base.Initialize(); // 获取核算维度服务 this.FlexService = BD.Contracts.ServiceFactory.GetSCMServiceForFIN(base.Context); // 创建核算维度参数 this.LocArg = this.FlexService.CreateFlexStrValueArg(base.Context, 0); // 初始化显示字段映射 base.ReportProperty.DspInsteadColumnsInfo.DefaultDspInsteadColumns = new Dictionary<string, string>(); // 精度控制字段配置 var decimalFields = new List<DecimalControlField> { new DecimalControlField { ByDecimalControlFieldName = "FBASEQCQTY", DecimalControlFieldName = "FBASEUNITPRE" } }; base.ReportProperty.DecimalControlFields = decimalFields; }典型业务场景扩展:
- 当需要增加自定义核算维度时,可通过
CreateFlexStrValueArg方法扩展 - 精度控制字段配置影响金额计算时的四舍五入规则
2.2 RegisterRptBill 方法:单据注册机制
此方法确定报表统计的业务单据范围:
protected override void RegisterRptBill() { this.lstRptBill.Clear(); // 从系统配置获取需要统计的单据类型 var dt = DBUtils.ExecuteDataSet(this.Context, @"/*dialect*/SELECT * FROM T_BAS_UPDATESTOCKRPTSET WHERE FRPTTYPE = 'STK_StockSummaryRpt'").Tables[0]; foreach(DataRow row in dt.Rows) { var billPlugIn = this.GetBillPlugIn(row["FBILLTYPE"].ToString()); this.lstRptBill.Add(billPlugIn); } }配置技巧:
- 可通过后台表
T_BAS_UPDATESTOCKRPTSET动态配置统计范围 - 特殊业务场景可硬编码关键单据类型
3. 数据准备阶段关键技术
3.1 临时表创建策略
物料收发汇总表涉及三类核心临时表:
| 表类型 | 命名规范 | 用途 | 生命周期 |
|---|---|---|---|
| 期初表 | TM_STK_RPTCOMMONQCP | 存储期初结存数据 | 报表会话期间 |
| 发生表 | TM_STK_RPTCOMMON | 存储收发业务数据 | 报表会话期间 |
| 汇总表 | TM_STK_RPTCOMMONSUM | 存储汇总计算结果 | 报表会话期间 |
创建期初表的典型代码:
private void CreateQcpTbl() { this.tmpQcpTbl = "TM_STK_RPTCOMMONQCP"; var sql = new StringBuilder(); sql.AppendLine("CREATE TABLE " + this.tmpQcpTbl + " ("); sql.AppendLine(" FGUID VARCHAR(36) NULL,"); sql.AppendLine(" FIOPRICE DECIMAL(23,10) NULL DEFAULT(0),"); // 其他字段定义... sql.AppendLine(")"); using(var scope = new KDTransactionScope(TransactionScopeOption.Suppress)) { DBUtils.Execute(this.Context, sql.ToString()); scope.Complete(); } }性能优化建议:
- 字段类型严格匹配业务需求(如金额字段使用 DECIMAL(23,10))
- 大数据量时先创建表再创建索引
- 使用
TransactionScopeOption.Suppress避免事务嵌套
3.2 数据加工处理流程
物料收发汇总表的数据处理遵循以下流程:
期初数据准备:
INSERT INTO {tmpQcpTbl} SELECT NEWID() AS FGUID, FMATERIALID, SUM(FBASEQCQTY) AS FBASEQCQTY, CASE WHEN SUM(FBASEQCQTY)<>0 THEN SUM(FBASEQCAMOUNT)/SUM(FBASEQCQTY) ELSE 0 END AS FBASEQCPRICE FROM T_STK_INVENTORY WHERE FSTOCKORGID IN ({orgIds}) AND FDATE<'{startDate}' GROUP BY FMATERIALID发生业务数据处理:
public void BuildTransactionData() { var sql = $@"/*dialect*/ INSERT INTO {tmpFullTbl} SELECT t.FMATERIALID, SUM(CASE WHEN t.FTRANTYPE='IN' THEN t.FBASETRANQTY ELSE 0 END) AS FBASEINQTY, SUM(CASE WHEN t.FTRANTYPE='OUT' THEN t.FBASETRANQTY ELSE 0 END) AS FBASEOUTQTY FROM T_STK_TRANSACTION t WHERE t.FSTOCKORGID IN ({orgIds}) AND t.FDATE BETWEEN '{startDate}' AND '{endDate}' GROUP BY t.FMATERIALID"; DBUtils.Execute(this.Context, sql); }数据关联汇总:
SELECT m.FNUMBER AS 物料编码, ml.FNAME AS 物料名称, qc.FBASEQCQTY AS 期初数量, tr.FBASEINQTY AS 收入数量, tr.FBASEOUTQTY AS 发出数量, qc.FBASEQCQTY + tr.FBASEINQTY - tr.FBASEOUTQTY AS 结存数量 FROM {tmpQcpTbl} qc LEFT JOIN {tmpFullTbl} tr ON qc.FMATERIALID = tr.FMATERIALID LEFT JOIN T_BD_MATERIAL m ON qc.FMATERIALID = m.FMATERIALID LEFT JOIN T_BD_MATERIAL_L ml ON ml.FMATERIALID = m.FMATERIALID AND ml.FLOCALEID=2052
4. 过滤条件处理关键技术
4.1 SetFilter 方法:基础过滤条件
public override void SetFilter(Filter filter) { base.SetFilter(filter); // 获取界面传递的过滤参数 DynamicObject filterObj = filter.FilterParameter.CustomFilter; // 处理库存组织过滤 this.stkOrgIdCollect = filterObj["StockOrgId"].ToString(); // 处理日期范围 this.startDate = Convert.ToDateTime(filterObj["StartDate"]); this.endDate = Convert.ToDateTime(filterObj["EndDate"]); // 处理单据状态过滤 var statusList = filterObj["DocStatus"].ToString().Split(','); this.validStatus = string.Join("','", statusList); }特殊处理场景:
- 多选参数需转换为 SQL 适用的 IN 语句格式
- 日期参数需考虑时区转换
- 组织参数需处理权限过滤
4.2 GetClassFilterBillList 方法:单据类型过滤
protected override List<KeyValuePair<string, string>> GetClassFilterBillList(Filter filter) { var list = new List<KeyValuePair<string, string>>(); // 处理采购入库单过滤 if(!string.IsNullOrEmpty(filter["PurInBill"].ToString())) { var bills = filter["PurInBill"].ToString().Split(',').ToList(); list.AddRange(this.GetBillListPair(bills, "IN")); } // 处理销售出库单过滤 if(!string.IsNullOrEmpty(filter["SaleOutBill"].ToString())) { var bills = filter["SaleOutBill"].ToString().Split(',').ToList(); list.AddRange(this.GetBillListPair(bills, "OUT")); } return list; }5. 报表展示层关键技术
5.1 BuildRptTitle 方法:动态表头构建
protected override ReportTitles BuildRptTitle() { var titles = new ReportTitles(); // 主标题 titles.Add(new RptTitleInfo { TitleName = "物料收发汇总表", FontSize = 16, IsBold = true }); // 副标题(带过滤条件) string subTitle = $"统计期间:{this.startDate:yyyy-MM-dd} 至 {this.endDate:yyyy-MM-dd}"; if(!string.IsNullOrEmpty(this.stkOrgIdCollect)) { subTitle += $" | 库存组织:{GetOrgNames(this.stkOrgIdCollect)}"; } titles.Add(new RptTitleInfo { TitleName = subTitle, FontSize = 10 }); return titles; }5.2 GetPageDt 方法:分页数据获取
protected override DataTable GetPageDt(int pageIndex, int pageSize) { // 构建分页SQL string sql = $@"/*dialect*/ WITH TempResult AS ( SELECT ROW_NUMBER() OVER(ORDER BY m.FNUMBER) AS RowNum, m.FNUMBER AS MaterialCode, ml.FNAME AS MaterialName, -- 其他字段... FROM {tmpFullTbl} t JOIN T_BD_MATERIAL m ON t.FMATERIALID = m.FMATERIALID JOIN T_BD_MATERIAL_L ml ON ml.FMATERIALID = m.FMATERIALID AND ml.FLOCALEID=2052 ) SELECT * FROM TempResult WHERE RowNum BETWEEN {(pageIndex-1)*pageSize+1} AND {pageIndex*pageSize}"; return DBUtils.ExecuteDataSet(this.Context, sql).Tables[0]; }性能优化技巧:
- 使用 CTE 实现高效分页
- 关键字段建立索引
- 避免在分页查询中使用 * 通配符
6. 扩展开发实战技巧
6.1 自定义核算维度处理
当需要增加自定义核算维度时,需扩展以下方法:
// 在Initialize中初始化维度参数 this.CustomFlexArg = this.FlexService.CreateFlexStrValueArg(base.Context, 2); // 在SQL中关联维度数据 string GetJoinFelxSql() { return $@"LEFT JOIN T_BD_FLEXENTRY f ON f.FID = t.FFLEXID LEFT JOIN T_BD_FLEXENTRY_L fl ON fl.FID = f.FID AND fl.FLOCALEID=2052"; }6.2 成本价格计算策略
金蝶提供多种价格计算方式,可通过插件重写:
protected virtual string GetQcPriceSql() { switch(this.priceSource) { case "LatestIn": // 最新入库价 return @"SELECT TOP 1 FPRICE FROM T_STK_INSTOCKENTRY WHERE FMATERIALID={0} ORDER BY FDATE DESC"; case "Weighted": // 加权平均价 return @"SELECT CASE WHEN SUM(FQTY)<>0 THEN SUM(FAMOUNT)/SUM(FQTY) ELSE 0 END FROM T_STK_INVENTORY WHERE FMATERIALID={0}"; default: // 期初价 return @"SELECT FPRICE FROM T_BD_MATERIALPRICE WHERE FMATERIALID={0}"; } }7. 调试与性能优化
7.1 调试信息输出
[Conditional("DEBUG")] private void WriteDebugInfo(string message) { string sql = $@"INSERT INTO T_DEBUG_LOG(FID,FMESSAGE,FCREATEDATE) VALUES(NEWID(),'{message.Replace("'","''")}',GETDATE())"; DBUtils.Execute(this.Context, sql); }调试技巧:
- 使用
[Conditional]特性确保仅调试模式输出 - 记录关键SQL执行时间
- 使用临时表存储中间结果用于问题排查
7.2 性能优化方案
针对大数据量的优化策略:
索引优化:
private void CreateRptTableIndex() { string sql = $@"CREATE INDEX IX_TMP_STK_{this.tmpFullTbl}_MATERIAL ON {this.tmpFullTbl}(FMATERIALID)"; DBUtils.Execute(this.Context, sql); }分批处理:
public void ProcessLargeDataInBatch() { int batchSize = 100000; int processed = 0; while(true) { string sql = $@"/*dialect*/ INSERT INTO {tmpFullTbl} SELECT TOP {batchSize} * FROM T_STK_TRANSACTION WHERE FID NOT IN (SELECT FSOURCEID FROM {tmpFullTbl}) AND FDATE BETWEEN '{startDate}' AND '{endDate}'"; int affected = DBUtils.Execute(this.Context, sql); if(affected == 0) break; processed += affected; this.WriteDebugInfo($"已处理 {processed} 条数据"); } }内存缓存:
private static ConcurrentDictionary<string, DataTable> _cache = new ConcurrentDictionary<string, DataTable>(); public DataTable GetCachedData(string key) { return _cache.GetOrAdd(key, k => { string sql = $"SELECT * FROM T_BD_MATERIAL WHERE {k}"; return DBUtils.ExecuteDataSet(this.Context, sql).Tables[0]; }); }
通过以上7大核心方法的深度解析与实战示例,开发者可以全面掌握金蝶云星空物料收发汇总表插件的二次开发技术。在实际项目中,建议根据具体业务需求选择合适的扩展点,并始终关注性能优化与数据准确性。