D365 FO 科目维度余额计算
D365 FO提供了一系列的类用来做维度余额的计算。
凭证的数据存在GeneralJournalAccountEntry和GeneralJournaEntry表里,维度值是表GeneralJournalAccountEntry的LedgerDimension.
LedgerDimension对应的值在表DimensionAttributeValueCombination里,而具体维度的值又在不同的表里。
有一些维度组合值是实际业务在生成凭证的时候就使用了,而有一些维度组合是为了分析时用的,比如实际业务发生的过程中,部门,成本中心和利润中心都录入了
所以凭证GeneralJournalAccountEntry里存的是部门,成本中心和利润中心的组合值,但是需要如果需要按照成本中心和利润中心的组合进行分析,这个值在凭证表里并没有保存。
为了实现维度组合的计算,AX2012用了财务维度集的概念,定时把GeneralJournalAccountEntry的数据按照中某个维度组合的数据写入到DimensionFocusBalance表里,方便查询任意组合财务维度的值。
总账->会计科目表->维度->财务维度集
对于新建的财务维度集要重建余额,也要定期更新余额,要不然最新的凭证记录不在DimensionFocusBalance里,调用系统的类计算维度余额也就查询不到正确的结果了。
1.使用LedgerBalanceBase查询
根据某个维度值的组合,查找某个期间的发生额,可以用如下类。
这里以LedgerBalanceDimAttrValueComboAmounts类为例,查询某个科目在某个利润和成本中心组合下的期间发生额。
1 LedgerBalanceDimAttrValueComboAmounts ledgerBalanceDimAttrValueComboAmounts = LedgerBalanceDimAttrValueComboAmounts::construct(); 2 RecId recId = DimensionDynamicAccountResolver::newResolver("51010102-001--007-010").resolve(); 3 4 ledgerBalanceDimAttrValueComboAmounts.parmAccountingDateRange(dateStartMth(systemDateGet()), dateEndMth(systemDateGet())); 5 ledgerBalanceDimAttrValueComboAmounts.parmIncludeRegularPeriod(true); 6 ledgerBalanceDimAttrValueComboAmounts.calculateBalance(DimensionAttributeValueCombination::find(recId)); 7 info (num2Str(ledgerBalanceDimAttrValueComboAmounts.getAccountingCurrencyBalance(), 0, 2, 0 ,0));
如果想查询某个具体的维度组合的值对应的期间发生额,可以用这些类进行查询。
2.使用试算平衡表
如果想一次性获取某个财务维度集里包含的所有维度组合的值,通过方法一就显得笨拙了,不可能一个个维度组合起来再一个个查询出来。
参考试算平衡表的做法。
路径:总账->查询和报表->试算平衡表
如果要计算财务维度集ProfitCost某个期间段的金额情况,可以用如下代码:
1 private LedgerTrialBalanceTmp getMainAccountBalances( 2 MainAccountNum _mainAccountId, 3 StartDate _startDate, 4 EndDate _endDate, 5 Name _dimensionHierarchyName) 6 { 7 ttsbegin; 8 9 //更新财务维度集余额 10 DimensionFocusUpdateBalance::updateBalance( 11 DimensionHierarchy::findByTypeAndName(DimensionHierarchyType::Focus, 12 _dimensionHierarchyName)); 13 14 LedgerTrialBalanceTmp trialbalanceTmp; 15 delete_from trialBalanceTmp; 16 17 LedgerTrialBalanceContract trialBalanceContract = new LedgerTrialBalanceContract(); 18 trialBalanceContract.parmFromDate(_startDate); 19 trialBalanceContract.parmToDate(_endDate); 20 trialBalanceContract.parmIncludeOpening(true); 21 trialBalanceContract.parmIncludeClosingAdjustments(false); 22 trialBalanceContract.parmIncludeClosingTransactions(false); 23 24 //设置过账层 25 List list = new List(Types::Integer); 26 list.addEnd(0); 27 trialBalanceContract.parmPostingLayers(list); 28 trialBalanceContract.parmPrimaryDimensionFocus(_dimensionHierarchyName); 29 //设置过滤科目 30 Map map = new Map(Types::Int64, Types::String); 31 map.insert(DimensionAttribute::findByName("MainAccount").RecId, _mainAccountId); 32 trialBalanceContract.parmDimensionRangeMap(map); 33 34 LedgerTrialBalanceDP trialBalanceDP = new LedgerTrialBalanceDP(); 35 trialBalanceDP.parmDataContract(trialBalanceContract); 36 trialBalanceDP.setTrialBalanceTmpTable(trialBalanceTmp); 37 38 trialBalanceDP.processReport(); 39 ttscommit; 40 return trialbalanceTmp; 41 }
调用示例
1 private void callMainAccountBalances() 2 { 3 LedgerTrialBalanceTmp trialbalanceTmp = this.getMainAccountBalances( 4 "51010406", 5 dateStartMth(systemDateGet()), 6 dateEndMth(systemDateGet()), 7 "ProfitCost"); 8 while select trialbalanceTmp 9 { 10 info(trialbalanceTmp.DimensionValues[1] + " " + 11 trialbalanceTmp.DimensionValues[2] + " " + 12 trialbalanceTmp.DimensionValues[3] + " " + 13 num2Str(trialbalanceTmp.EndingBalance, 0,2, 0 ,0) + 14 num2Str(trialbalanceTmp.AmountDebit, 0,2, 0, 0)); 15 } 16 }
D365 FO,目前版本10.0.0.10根据某个维度过滤的代码,感觉有点问题,不知道是微软特意这么设计还是bug。
比如想按照某个科目过滤,可以用Map把科目传进LedgerTrialBalanceContract,代码在执行的时候是通过表LedgerTransAccountTmp的fillFromDimSetBalWithDimRanges方法取得要过滤的LedgerDimension集合。
问题在代码的第215行
r.value(SysQuery::range(_startDate, _endDate));
这行代码是按照调用方的开始和结束日期来过滤的,问题在于,试算平衡表是要看期初金额的,这样过滤的话,如果调用方输入的期间内,map里指定的科目维度没有发生交易,期初都出不来了。
不清楚微软fillFromDimSetBalWithDimRanges这个方法设计的初衷,作为调用方来说,我的期望是,如果没传Map进行过滤的时候能返回某个科目维度的记录,那么传了这个科目维度进行过滤的话,也应该能出来。
不能因为过滤了,就导致不出现这条记录了。
修改也就简单,扩展一下这个方法,把逻辑改的跟不过滤的逻辑保持一致,把_startDate改成periodStartDate
date periodStartDate = LedgerFiscalCalendar::findOpeningStartDateByDate(Ledger::fiscalCalendar(CompanyInfo::current()), _startDate);