金蝶云星空 8.x 物料收发汇总表插件二次开发:5个核心方法重写与3类临时表实战
金蝶云星空8.x物料收发汇总表插件深度开发实战方法重写与临时表架构设计1. 报表插件开发环境与核心架构解析金蝶云星空8.x的报表插件开发基于SysReportBaseService基类这是一个高度封装的报表服务框架。物料收发汇总表作为供应链核心报表其二次开发需要深入理解三个关键架构层级数据采集层负责从库存交易单据如采购入库、销售出库、调拨单等抽取原始数据业务逻辑层处理期初计算、收发汇总、价格匹配等核心业务规则呈现层控制报表字段显示、分页逻辑及交互功能开发环境配置要点// 典型开发环境依赖项 PackageReference IncludeKingdee.BOS Version8.1.0.5 / PackageReference IncludeKingdee.BOS.ServiceHelper Version8.1.0.3 / PackageReference IncludeKingdee.BOS.Report Version8.1.0.2 /报表插件类基础结构public class MaterialSummaryReportService : SysReportBaseService { // 临时表命名约定 private const string TMP_QCP_TABLE TMP_STK_QCP_DATA; private const string TMP_TRANS_TABLE TMP_STK_TRANS_DATA; // 精度控制字段 private readonly ListDecimalControlField _decimalControls new ListDecimalControlField { new DecimalControlField { ByDecimalControlFieldName FBASEQCQTY, DecimalControlFieldName FBASEUNITPRE } }; }2. 五大核心方法重写实战2.1 Initialize方法维度初始化最佳实践Initialize方法是插件初始化的入口点需要特别处理核算维度初始化protected override void Initialize() { base.Initialize(); // 获取核算维度服务实例 this.FlexService BD.Contracts.ServiceFactory.GetSCMServiceForFIN(base.Context); // 创建维度参数0-库存维度1-财务维度 this.LocArg this.FlexService.CreateFlexStrValueArg(base.Context, 0); // 设置精度控制 base.ReportProperty.DecimalControlFields _decimalControls; // 初始化单据过滤器 this.lstRptBill new ListBillPlugIn(); }关键注意事项核算维度初始化必须在基类Initialize之后执行维度参数类型需与业务场景匹配库存/财务精度控制字段应在此时注入报表属性2.2 BuildRptTitle方法动态表头生成技巧物料收发汇总表需要根据过滤条件动态生成表头protected override ReportTitles BuildRptTitle() { var titles base.BuildRptTitle(); // 添加自定义标题行 titles.AddTitle(new RptTitleInfo { TitleType ReportTitleType.Custom, Caption $物料收发汇总表({this.GetOrgDate().ToString(yyyy-MM-dd)}), FontSize 14, Bold true }); // 添加过滤条件说明 if(!string.IsNullOrEmpty(this.stkOrgIdCollect)) { titles.AddTitle(new RptTitleInfo { TitleType ReportTitleType.Filter, Caption $库存组织{GetOrgNames(this.stkOrgIdCollect)} }); } return titles; }高级技巧使用RptTitleInfo的TitleType控制标题样式动态获取组织名称避免硬编码多语言支持通过ResManager.LoadString实现2.3 GetPageDt方法分页性能优化方案大数据量分页查询需要特殊优化protected override DataTable GetPageDt(int pageIndex) { // 分页查询SQL模板 string sql $ SELECT * FROM ( SELECT ROW_NUMBER() OVER(ORDER BY FMATERIALID) AS RowNum, t.* FROM {this.tmpFullTbl} t {BuildFilterSql()} ) AS Result WHERE RowNum BETWEEN {pageIndex * PageSize 1} AND {(pageIndex 1) * PageSize}; // 执行分页查询 return DBUtils.ExecuteDataSet(this.Context, sql).Tables[0]; }性能优化要点使用ROW_NUMBER()替代传统分页方案临时表需预先建立合适索引复杂过滤条件应参数化处理2.4 CreateQcpTbl方法期初数据处理逻辑期初表是物料收发汇总的核心基础CREATE TABLE #TMP_STK_QCP_DATA ( FGUID VARCHAR(36), FMATERIALID INT, FLOTNO NVARCHAR(255), FSTOCKORGID INT, FSTOCKID INT, FSTOCKLOCID INT, FBASEQCQTY DECIMAL(23,10), FBASEUNITPRE DECIMAL(23,10), FBASEQCAMOUNT DECIMAL(23,10), FSTOCKQCQTY DECIMAL(23,10), FSTOCKUNITPRE DECIMAL(23,10), FSTOCKQCAMOUNT DECIMAL(23,10), FAUXQCQTY DECIMAL(23,10), FAUXUNITPRE DECIMAL(23,10), FAUXQCAMOUNT DECIMAL(23,10) );期初计算逻辑string qcSql $ INSERT INTO {this.tmpQcpTbl} SELECT NEWID() AS FGUID, t.FMATERIALID, t.FLOTNO, t.FSTOCKORGID, t.FSTOCKID, t.FSTOCKLOCID, SUM(t.FBASEQCQTY) AS FBASEQCQTY, {GetQcPriceSql()} AS FBASEUNITPRE, SUM(t.FBASEQCQTY) * {GetQcPriceSql()} AS FBASEQCAMOUNT, /* 库存单位、辅助单位计算类似 */ FROM T_STK_INVENTORY t WHERE {BuildQcFilter()} GROUP BY t.FMATERIALID, t.FLOTNO, /* 其他分组字段 */;2.5 SetFilter方法复杂过滤条件处理过滤条件处理需要支持多种业务场景protected override void SetFilter(Filter filter) { base.SetFilter(filter); // 获取自定义过滤参数 DynamicObject filterObj filter.FilterParameter.CustomFilter; // 处理库存组织多选 this.stkOrgIdCollect filterObj[StockOrgId]?.ToString(); // 处理单据状态过滤 var billStatus filterObj[BillStatus] as Liststring; this.billStatusFilter billStatus ! null ? string.Join(,, billStatus.Select(s ${s})) : Created,Approved; // 特殊业务过滤如不统计调拨单 this.excludeTransfer Convert.ToBoolean(filterObj[ExcludeTransfer]); }过滤条件构建技巧使用参数化SQL防止注入复杂条件应拆分为多个方法注意处理NULL值情况3. 三类临时表架构设计与实战3.1 期初临时表(tmpQcpTbl)设计期初表存储物料在查询期间的期初结存数据关键字段包括字段名类型说明FMATERIALIDINT物料内码FSTOCKORGIDINT库存组织FBASEQCQTYDECIMAL(23,10)基本单位期初数量FBASEUNITPREDECIMAL(23,10)基本单位参考单价FBASEQCAMOUNTDECIMAL(23,10)基本单位期初金额创建脚本示例string createQcpTblSql $ CREATE TABLE {this.tmpQcpTbl} ( FGUID VARCHAR(36) PRIMARY KEY, FMATERIALID INT NOT NULL, FSTOCKORGID INT NOT NULL, FBASEQCQTY DECIMAL(23,10) DEFAULT 0, FBASEUNITPRE DECIMAL(23,10) DEFAULT 0, FBASEQCAMOUNT DECIMAL(23,10) DEFAULT 0, /* 其他字段 */ ); DBUtils.Execute(this.Context, createQcpTblSql);3.2 收发数据临时表(tmpFullTbl)设计收发数据表存储所有交易明细的汇总数据public void CreateFullTbl() { this.tmpFullTbl ServiceHelper.GetServiceIDBService() .CreateTemporaryTableName(base.Context); string sql $ SELECT m.FMATERIALID, m.FNUMBER AS FMATERIALNO, ml.FNAME AS FMATERIALNAME, /* 其他字段 */ SUM(CASE WHEN t.FTRANTYPEIN THEN t.FBASEQTY ELSE 0 END) AS FBASEINQTY, SUM(CASE WHEN t.FTRANTYPEOUT THEN t.FBASEQTY ELSE 0 END) AS FBASEOUTQTY, /* 金额计算 */ INTO {this.tmpFullTbl} FROM T_STK_INVENTORY t JOIN T_BD_MATERIAL m ON t.FMATERIALIDm.FMATERIALID JOIN T_BD_MATERIAL_L ml ON ml.FMATERIALIDm.FMATERIALID AND ml.FLOCALEID2052 /* 其他关联 */ WHERE {BuildTransFilter()} GROUP BY m.FMATERIALID, m.FNUMBER, ml.FNAME /* 分组字段 */; using(var scope new KDTransactionScope(TransactionScopeOption.Suppress)) { DBUtils.Execute(this.Context, sql); scope.Complete(); } }3.3 分页展示临时表(tmpRptTbl)设计分页表优化了前端展示性能-- 创建分页索引 string indexSql $ CREATE INDEX IX_TMP_RPT_{this.tmpRptTbl} ON {this.tmpRptTbl} (FMATERIALID, FSTOCKORGID); DBUtils.Execute(this.Context, indexSql); -- 分页数据准备 string prepareSql $ INSERT INTO {this.tmpRptTbl} SELECT q.FMATERIALID, q.FSTOCKORGID, q.FBASEQCQTY, q.FBASEQCAMOUNT, t.FBASEINQTY, t.FBASEOUTQTY, /* 计算结存 */ q.FBASEQCQTY t.FBASEINQTY - t.FBASEOUTQTY AS FBASEENDQTY FROM {this.tmpQcpTbl} q JOIN {this.tmpFullTbl} t ON q.FMATERIALIDt.FMATERIALID AND q.FSTOCKORGIDt.FSTOCKORGID;4. 高级开发技巧与性能调优4.1 大规模数据处理方案当处理百万级数据时需要特殊优化策略// 分批处理大数据量 public void ProcessLargeData() { int batchSize 100000; int processed 0; while(true) { string sql $ SELECT TOP {batchSize} * FROM T_STK_INVENTORY WHERE FMATERIALID NOT IN ( SELECT FMATERIALID FROM {this.tmpFullTbl} ); DataSet ds DBUtils.ExecuteDataSet(this.Context, sql); if(ds.Tables[0].Rows.Count 0) break; // 处理当前批次 ProcessBatchData(ds.Tables[0]); processed ds.Tables[0].Rows.Count; this.WriteDebugInfo($已处理 {processed} 条记录); } }4.2 索引优化策略临时表索引对性能影响显著表类型推荐索引说明期初表(FMATERIALID, FSTOCKORGID)加速关联查询收发表(FTRANTYPE, FDATE)优化日期范围查询分页表(RowNum)提升分页效率创建索引示例-- 复合索引示例 string createIndexSql $ CREATE INDEX IX_TMP_FULL_COMPOSITE ON {this.tmpFullTbl} (FMATERIALID, FSTOCKORGID, FTRANTYPE) INCLUDE (FBASEQTY, FBASEAMOUNT);4.3 调试与日志记录完善的日志有助于问题排查// 调试信息记录 private void WriteDebugInfo(string message) { string logPath Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Desktop), MaterialRptDebug.log); File.AppendAllText(logPath, ${DateTime.Now}: {message}\n); // 同时输出到金蝶调试窗口 if(this.Context.IsDebug()) { this.Context.LogWriter.WriteLog(message); } }5. 典型业务场景实现5.1 多维度汇总实现支持按库存组织、货主等多维度汇总-- 动态维度汇总SQL string GetGroupBySql() { var sb new StringBuilder(); if(this.groupByOrg) sb.Append(,FSTOCKORGID); if(this.groupByOwner) sb.Append(,FOWNERID); return sb.Length 0 ? sb.ToString().Substring(1) : FMATERIALID; } -- 应用示例 string summarySql $ SELECT {GetGroupBySql()}, SUM(FBASEQCQTY) AS FBASEQCQTY, SUM(FBASEINQTY) AS FBASEINQTY, SUM(FBASEOUTQTY) AS FBASEOUTQTY FROM {this.tmpFullTbl} GROUP BY {GetGroupBySql()};5.2 价格计算逻辑封装不同价格策略的灵活实现// 价格计算策略模式 public interface IPriceStrategy { decimal GetPrice(int materialId, DateTime date); } // 加权平均价实现 public class AvgPriceStrategy : IPriceStrategy { public decimal GetPrice(int materialId, DateTime date) { string sql $ SELECT FAVGPRICE FROM T_STK_INVENTORYPRICE WHERE FMATERIALID{materialId} AND FYEAR{date.Year} AND FPERIOD{date.Month}; return DBUtils.ExecuteScalardecimal(this.Context, sql); } } // 在报表中使用 var priceStrategy priceSource AVG ? new AvgPriceStrategy() : new LastPriceStrategy(); decimal price priceStrategy.GetPrice(materialId, acctDate);5.3 特殊业务规则处理处理禁用物料、负结存等特殊场景-- 过滤条件构建 string BuildSpecialFilter() { var filters new Liststring(); if(!this.showDisabled) filters.Add(m.FDOCSTATUSA); if(this.hideNegative) filters.Add((q.FBASEQCQTY t.FBASEINQTY - t.FBASEOUTQTY) 0); return filters.Count 0 ? string.Join( AND , filters) : 11; }6. 插件部署与调试技巧6.1 插件打包规范推荐的文件结构/MaterialSummaryReport ├── BusinessPlugIn/ │ ├── MaterialSummaryReportService.cs # 主服务类 │ └── PriceStrategies/ # 策略实现 ├── Resources/ │ ├── resx语言文件 # 多语言资源 │ └── SQL/ # SQL脚本 └── package.config # 部署清单6.2 远程调试配置Visual Studio调试配置示例PropertyGroup StartActionProgram/StartAction StartProgramC:\Program Files\IIS Express\iisexpress.exe/StartProgram StartArguments/path:C:\Kingdee\K3Cloud\WebSite /port:8080/StartArguments StartWorkingDirectoryC:\Kingdee\K3Cloud\WebSite/StartWorkingDirectory /PropertyGroup6.3 性能监控方案关键性能指标监控点// 性能计时工具类 public class PerfMonitor : IDisposable { private Stopwatch _sw; private string _operation; public PerfMonitor(string operation) { _operation operation; _sw Stopwatch.StartNew(); } public void Dispose() { _sw.Stop(); Debug.WriteLine(${_operation} 耗时: {_sw.ElapsedMilliseconds}ms); } } // 使用示例 using(new PerfMonitor(创建期初表)) { CreateQcpTbl(); }