天天看點

S/4 HANA中的ACDOCT和FAGLFLEXT

最近的幾個需求讓我對ACDOCT和FAGLFLEXT這兩個财務相關表(準确地說是視圖)産生了一些了解,同時也發現某些開發同行和業務顧問并沒有認識到這些東西。是以打算從技術角度來說明一下這兩個視圖在S4中的實際面貌。當然因為我并不了解FICO方面的任何業務知識,是以也隻是進行技術角度的說明。

本文内容基于 S/4 1709版本;某些内容可能和其他版本不是完全符合。

最近的幾個需求讓我對ACDOCT和FAGLFLEXT這兩個财務相關表(準确地說是視圖)産生了一些了解,同時也發現某些開發同行和業務顧問并沒有認識到這些東西。是以我打算從技術角度來說明一下這兩個視圖在S4中的實際面貌。當然因為我并不了解FICO方面的任何業務知識,是以也隻是進行技術角度的說明。

本文内容基于 S/4 1709版本;某些内容可能和其他版本不是完全符合,特别是具體的DDL代碼部分。

本文連結:http://www.cnblogs.com/hhelibeb/p/8629942.html

财務報表開發中經常會出現有關期初餘額和本期發生額的需求,傳統上擷取期初餘額的方式是從FAGLFLEXT或ACDOCT中擷取結轉餘額(HSLVT),再加上各期發生額(HSL01, HSL02....)。

比如要擷取公司1000的2018年3月的期初餘額,ABAP代碼的寫法是:

SELECT SUM( ( hslvt + hsl01 + hsl02 ) ) FROM faglflext WHERE rbukrs = '1000'
                                                         AND ryear  = '2018'
  INTO @DATA(l_balance).      

這隻是段簡單的示例代碼,實際需求中可能要按科目等條件取出各個期間分組彙總。并且,為了适應動态的查詢條件,通常要使用Field Symbol,在内表中對資料進行累加操作,頗為繁瑣。

那麼,HSLVT和HSL01, HSL02....等資料是從哪裡來的呢?如果把FAGLFLEXT和ACDOCT視為資料庫表,讀者可能會認為,它們是通過某些結轉操作,由系統彙總計算後得來并存儲到資料庫中的。但實際上,FAGLFLEXT和ACDOCT都隻是視圖,而非透明表。可以了解成,它們當中的資料不是資料庫中既有的,而是和各種報表程式一樣、是對其它表的資料進行實時處理後臨時生成的結果。

打開SE11可以看到:

S/4 HANA中的ACDOCT和FAGLFLEXT
S/4 HANA中的ACDOCT和FAGLFLEXT

可以看到,它們實際上是ABAP CDS視圖。如果不了解ABAP CDS視圖的話,可以暫時把它了解成使用SQL語言建立的資料模型,通過使用注解,還能為它賦予其它能力。

推薦通過Eclipse來檢視CDS視圖的定義,具體操作方式不提。

CDS視圖中的資料通常是從透明表中取得的,這裡以FAGLFLEXT為例:

觀察視圖的DDL源,尋找其中的FROM關鍵字:

@AbapCatalog.sqlViewName: 'FAGLFLEXT'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.usageType.serviceQuality: #X
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MIXED
@EndUserText.label: 'FAGLFLEXT (Compatibility View) '
@AbapCatalog.preserveKey: true

define view v_faglflext_view 
as
    select key rclnt, key ryear,
    key objnr00, key objnr01, key objnr02, key objnr03, key objnr04, key objnr05, key objnr06, key objnr07, key objnr08,
    key drcrk, key rpmax,
    activ, rmvct, rtcur, runit, awtyp,
    rldnr, rrcty, rvers, logsys, racct, cost_elem, rbukrs,
    rcntr, prctr, rfarea, rbusa, kokrs, segment, zzpaytype, zzinvunit, zzempnum, ps_posid, aufnr, kunnr, lifnr,  
//<$VF>
//<$FIELDS>  
//<$VF>
    scntr, pprctr, sfarea, sbusa, rassc, psegment,
    tslvt, tsl01, tsl02, tsl03, tsl04, tsl05, tsl06, tsl07, tsl08, tsl09, tsl10, tsl11, tsl12, tsl13, tsl14, tsl15, tsl16,
    hslvt, hsl01, hsl02, hsl03, hsl04, hsl05, hsl06, hsl07, hsl08, hsl09, hsl10, hsl11, hsl12, hsl13, hsl14, hsl15, hsl16,
    kslvt, ksl01, ksl02, ksl03, ksl04, ksl05, ksl06, ksl07, ksl08, ksl09, ksl10, ksl11, ksl12, ksl13, ksl14, ksl15, ksl16,
    oslvt, osl01, osl02, osl03, osl04, osl05, osl06, osl07, osl08, osl09, osl10, osl11, osl12, osl13, osl14, osl15, osl16,
    mslvt, msl01, msl02, msl03, msl04, msl05, msl06, msl07, msl08, msl09, msl10, msl11, msl12, msl13, msl14, msl15, msl16,
    timestamp
    from faglflext_bck where rrcty = '1'
union all
    select
    key rclnt,
    key cast(ryear as gjahr) as ryear,
    key cast(objnr00 as g_objnr) as objnr00,
    key cast(objnr01 as g_objnr) as objnr01,
    key cast(objnr02 as g_objnr) as objnr02,
    key cast(objnr03 as g_objnr) as objnr03,
    key cast(objnr04 as g_objnr) as objnr04,
    key cast(objnr05 as g_objnr) as objnr05,
    key cast(objnr06 as g_objnr) as objnr06,
    key cast(objnr07 as g_objnr) as objnr07,
    key cast(objnr08 as g_objnr) as objnr08,
    key cast(drcrk as shkzg) as drcrk,
    key cast(rpmax as rpmax) as rpmax,
    cast(activ as acti1) as activ,
    cast(rmvct as rmvct) as rmvct,
    cast(rtcur as rtcur) as rtcur,
    runit,
    cast(awtyp as awtyp) as awtyp,
    cast(rldnr as fagl_rldnr) as rldnr,
    cast(rrcty as rrcty) as rrcty,
    cast(rvers as rvers) as rvers,
    cast(logsys as logsys) as logsys,
    cast(racct as racct) as racct,
    cast(cost_elem as kstar) as cost_elem,
    cast(rbukrs as bukrs) as rbukrs,
    cast(rcntr as kostl) as rcntr,
    cast(prctr as prctr) as prctr,
    cast(rfarea as fkber) as rfarea,
    cast(rbusa as gsber) as rbusa,
    cast(kokrs as kokrs) as kokrs,
    cast(segment as fb_segment) as segment,//<$VF>
//<$FIELDS>
//<$VF>
    cast(scntr as skost) as scntr,
    cast(pprctr as pprctr) as pprctr,
    cast(sfarea as sfkber) as sfarea,
    cast(sbusa as pargb) as sbusa,
    cast(rassc as rassc) as rassc,
    cast(psegment as fb_psegment) as psegment,
    cast(tslvt as tslvt12) as tslvt,
    cast(tsl01 as tslxx12) as tsl01,
    cast(tsl02 as tslxx12) as tsl02,
    cast(tsl03 as tslxx12) as tsl03,
    cast(tsl04 as tslxx12) as tsl04,
    cast(tsl05 as tslxx12) as tsl05,
    cast(tsl06 as tslxx12) as tsl06,
    cast(tsl07 as tslxx12) as tsl07,
    cast(tsl08 as tslxx12) as tsl08,
    cast(tsl09 as tslxx12) as tsl09,
    cast(tsl10 as tslxx12) as tsl10,
    cast(tsl11 as tslxx12) as tsl11,
    cast(tsl12 as tslxx12) as tsl12,
    cast(tsl13 as tslxx12) as tsl13,
    cast(tsl14 as tslxx12) as tsl14,
    cast(tsl15 as tslxx12) as tsl15,
    cast(tsl16 as tslxx12) as tsl16,
    cast(hslvt as hslvt12) as hslvt,
    cast(hsl01 as hslxx12) as hsl01,
    cast(hsl02 as hslxx12) as hsl02,
    cast(hsl03 as hslxx12) as hsl03,
    cast(hsl04 as hslxx12) as hsl04,
    cast(hsl05 as hslxx12) as hsl05,
    cast(hsl06 as hslxx12) as hsl06,
    cast(hsl07 as hslxx12) as hsl07,
    cast(hsl08 as hslxx12) as hsl08,
    cast(hsl09 as hslxx12) as hsl09,
    cast(hsl10 as hslxx12) as hsl10,
    cast(hsl11 as hslxx12) as hsl11,
    cast(hsl12 as hslxx12) as hsl12,
    cast(hsl13 as hslxx12) as hsl13,
    cast(hsl14 as hslxx12) as hsl14,
    cast(hsl15 as hslxx12) as hsl15,
    cast(hsl16 as hslxx12) as hsl16,
    cast(kslvt as kslvt12) as kslvt,
    cast(ksl01 as kslxx12) as ksl01,
    cast(ksl02 as kslxx12) as ksl02,
    cast(ksl03 as kslxx12) as ksl03,
    cast(ksl04 as kslxx12) as ksl04,
    cast(ksl05 as kslxx12) as ksl05,
    cast(ksl06 as kslxx12) as ksl06,
    cast(ksl07 as kslxx12) as ksl07,
    cast(ksl08 as kslxx12) as ksl08,
    cast(ksl09 as kslxx12) as ksl09,
    cast(ksl10 as kslxx12) as ksl10,
    cast(ksl11 as kslxx12) as ksl11,
    cast(ksl12 as kslxx12) as ksl12,
    cast(ksl13 as kslxx12) as ksl13,
    cast(ksl14 as kslxx12) as ksl14,
    cast(ksl15 as kslxx12) as ksl15,
    cast(ksl16 as kslxx12) as ksl16,
    cast(oslvt as oslvt12) as oslvt,
    cast(osl01 as oslxx12) as osl01,
    cast(osl02 as oslxx12) as osl02,
    cast(osl03 as oslxx12) as osl03,
    cast(osl04 as oslxx12) as osl04,
    cast(osl05 as oslxx12) as osl05,
    cast(osl06 as oslxx12) as osl06,
    cast(osl07 as oslxx12) as osl07,
    cast(osl08 as oslxx12) as osl08,
    cast(osl09 as oslxx12) as osl09,
    cast(osl10 as oslxx12) as osl10,
    cast(osl11 as oslxx12) as osl11,
    cast(osl12 as oslxx12) as osl12,
    cast(osl13 as oslxx12) as osl13,
    cast(osl14 as oslxx12) as osl14,
    cast(osl15 as oslxx12) as osl15,
    cast(osl16 as oslxx12) as osl16,
    cast(mslvt as mslvt12) as mslvt,
    cast(msl01 as mslxx12) as msl01,
    cast(msl02 as mslxx12) as msl02,
    cast(msl03 as mslxx12) as msl03,
    cast(msl04 as mslxx12) as msl04,
    cast(msl05 as mslxx12) as msl05,
    cast(msl06 as mslxx12) as msl06,
    cast(msl07 as mslxx12) as msl07,
    cast(msl08 as mslxx12) as msl08,
    cast(msl09 as mslxx12) as msl09,
    cast(msl10 as mslxx12) as msl10,
    cast(msl11 as mslxx12) as msl11,
    cast(msl12 as mslxx12) as msl12,
    cast(msl13 as mslxx12) as msl13,
    cast(msl14 as mslxx12) as msl14,
    cast(msl15 as mslxx12) as msl15,
    cast(msl16 as mslxx12) as msl16,
    cast(timestamp as timestamp) as timestamp
    from FGL_FAGLFLEXT      

可以發現這個視圖結合了2個源。其一是FAGLFLEXT_BCK,查詢可知這是舊資料的備份表;另一個是FGL_FAGLFLEXT,

打開FGL_FAGLFLEXT,發現它還是個CDS視圖(因為完整定義過長,隻截取了部分代碼,下同):

@AbapCatalog.sqlViewName: 'FGLV_FAGLFLEXT'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.usageType.serviceQuality: #X
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MIXED
@EndUserText.label: 'Compatibility view for FAGLFLEXT'
define view FGL_FAGLFLEXT as select from FGL_GLTT2
{
key rclnt,
key ryear,
key cast(objnr00 as abap.int4(10)) as objnr00,
key cast(objnr01 as abap.int4(10)) as objnr01,
key cast(objnr02 as abap.int4(10)) as objnr02,
key cast(objnr03 as abap.int4(10)) as objnr03,
key cast(objnr04 as abap.int4(10)) as objnr04,
key cast(objnr05 as abap.int4(10)) as objnr05,
key cast(objnr06 as abap.int4(10)) as objnr06,
key cast(objnr07 as abap.int4(10)) as objnr07,
key cast(objnr08 as abap.int4(10)) as objnr08,
key drcrk,
key rpmax,
activ,
………………
………………
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hslvt) as hslvt,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl01) as hsl01,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl02) as hsl02,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl03) as hsl03,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl04) as hsl04,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl05) as hsl05,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl06) as hsl06,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl07) as hsl07,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl08) as hsl08,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl09) as hsl09,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl10) as hsl10,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl11) as hsl11,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl12) as hsl12,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl13) as hsl13,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl14) as hsl14,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl15) as hsl15,
@Semantics.amount.currencyCode: 'T882G.CURR1' sum(hsl16) as hsl16,
………………
………………
max(timestamp) as timestamp
}
group by
………………
………………      

可以看到,FGL_FAGLFLEXT中的HSLVT和HSL01, HSL02字段,是對FGL_GLTT2中相應字段的彙總得來的。對應語句:sum(hslvt) as hslvt。

打開FGL_GLTT2,會發現該視圖中的金額彙總自FGL_GLTT1,是以這裡就不貼FGL_GLTT2的定義了,直接看FGL_GLTT1:

@AbapCatalog.sqlViewName: 'FGLV_GLTT1'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.usageType.serviceQuality: #X
@ObjectModel.usageType.sizeCategory: #XL
@ObjectModel.usageType.dataClass: #MIXED
@EndUserText.label: 'G/L totals: create the period block'
define view FGL_GLTT1 as select from FGL_GLTT0
{
rclnt,
ryear,
0 as objnr00,
0 as objnr01,
0 as objnr02,
0 as objnr03,
0 as objnr04,
0 as objnr05,
0 as objnr06,
0 as objnr07,
0 as objnr08,
drcrk,

case poper
  when '000' then '016'
  else cast( lpad( cast ( 16 * (1 + div( cast(poper as abap.int4) - 1, 16) ) as abap.char(12) ), 3, '0') as abap.numc(3))
end as rpmax,
    activ, rmvct, rtcur,
    runit, awtyp, rldnr, rrcty, rvers, logsys, racct, cost_elem, rbukrs,
    rcntr, prctr, rfarea, rbusa, kokrs, segment,
    scntr, pprctr, sfarea, sbusa, rassc, psegment,  
//<$VF>
    fikrs, rfund, rgrant_nbr, rbudget_pd, sfund, sgrant_nbr, sbudget_pd,
    re_bukrs, re_account, vname, egrup, recid,
//<$FIELDS>
//<$VF>
………………
………………
case poper  when '000' then hsl else 0 end as hslvt,
case mod( cast(poper as abap.int4 ), 16) when  1 then hsl else 0 end as hsl01,
case mod( cast(poper as abap.int4 ), 16) when  2 then hsl else 0 end as hsl02,
case mod( cast(poper as abap.int4 ), 16) when  3 then hsl else 0 end as hsl03,
case mod( cast(poper as abap.int4 ), 16) when  4 then hsl else 0 end as hsl04,
case mod( cast(poper as abap.int4 ), 16) when  5 then hsl else 0 end as hsl05,
case mod( cast(poper as abap.int4 ), 16) when  6 then hsl else 0 end as hsl06,
case mod( cast(poper as abap.int4 ), 16) when  7 then hsl else 0 end as hsl07,
case mod( cast(poper as abap.int4 ), 16) when  8 then hsl else 0 end as hsl08,
case mod( cast(poper as abap.int4 ), 16) when  9 then hsl else 0 end as hsl09,
case mod( cast(poper as abap.int4 ), 16) when 10 then hsl else 0 end as hsl10,
case mod( cast(poper as abap.int4 ), 16) when 11 then hsl else 0 end as hsl11,
case mod( cast(poper as abap.int4 ), 16) when 12 then hsl else 0 end as hsl12,
case mod( cast(poper as abap.int4 ), 16) when 13 then hsl else 0 end as hsl13,
case mod( cast(poper as abap.int4 ), 16) when 14 then hsl else 0 end as hsl14,
case mod( cast(poper as abap.int4 ), 16) when 15 then hsl else 0 end as hsl15,
case mod( cast(poper as abap.int4 ), 16)
  when  0 then
    case poper
      when '000' then 0
      else hsl
    end
  else 0 end
as hsl16,
………………
………………

timestamp
}      

可以清楚地看到,原來結轉餘額HSLVT其實就是期間POPER = 000時的金額,HSL01,HSL02就是期間POPER = 001,002時的金額....

對應語句是:case poper when '000' then hsl else 0 end as hslvt,,和case mod( cast(poper as abap.int4 ), 16) when 1 then hsl else 0 end as hsl01

繼續找下去可以得知FGL_GLTT1的資料來自于表ACDOCA(Universal Journal Entry Line Items)。也就是說,每次從FAGLFLEXT中查詢資料,實際上相當于經過了FGL_GLSI_ACD->FGL_GLSI_ACD->FGL_GLTT1->FGL_GLTT2->FGL_FAGLFLEXT->V_FAGLFLEXT_DDL一系列中間層邏輯處理,其實際的資料源則是表ACDOCA。當然這些視圖中可能還包含其它細節..

以上是對FAGLFLEXT的分析,ACDOCT也是差不多的,具體的條件有點差别。

FAGLFLEXT和ACDOCT的存在展現了CDS所具備的強大能力。不過,畢竟它們隻是相容視圖,又包含這樣多的中間層...按照一般的原則,程式中的中間層越多,性能就越差。是以,在了解到它們中的資料的實際來源後,也許在某些情況下,直接從ACDOCA中通過聚合函數擷取期初餘額之類的東西會有更好的性能。

按這樣的思路改寫文初擷取1000公司在2018年3月的期初餘額的代碼,可以寫成:

SELECT SUM( hsl ) FROM acdoca WHERE rbukrs = '1000'
                                AND gjahr  = '2018'
                                AND poper  < '003'
  INTO @DATA(l_balance).      

是不是簡單了很多呢?

最後,再次重申,本文隻是從技術角度介紹下我對ACDOCT,FAGLFLEXT的了解。在實際業務中,這兩個視圖中的資料可能和分類賬、憑證狀态之類的東西有關...其中的資料與ACDOCA中的彙總資料并不一定在任何情況下都完全相等。

示例代碼:

從ACDOCA中擷取公司代碼1000,分類賬0L,科目1122020000的期初餘額、本期發生額、期末餘額、本年累計發生額:

REPORT ztest_balance.

DATA: p_gjahr TYPE acdoca-gjahr VALUE '2018',
      p_poper TYPE acdoca-poper VALUE '002'.

DATA: c_racct TYPE racct VALUE '1122020000'.

WITH +itab AS (
  SELECT
     CASE WHEN poper < @p_poper AND gjahr = @p_gjahr THEN hsl
                                                     ELSE 0
     END AS opening_balance,
     CASE WHEN poper = @p_poper AND gjahr = @p_gjahr THEN hsl
                                                     ELSE 0
     END AS current_balance,
     CASE WHEN poper <= @p_poper AND gjahr = @p_gjahr THEN hsl
                                                      ELSE 0
     END AS ending_balance,
     CASE WHEN poper > '000'    AND poper <= '012' AND gjahr = @p_gjahr THEN hsl
                                                                        ELSE 0
     END AS current_year_balance,
     racct
     FROM acdoca
     WHERE racct  = @c_racct
    AND gjahr  = @p_gjahr
       AND rbukrs = '1000'
       AND rldnr  = '0L' )
SELECT
  racct,
  SUM( opening_balance )       AS opening_balance,
  SUM( current_balance )       AS current_balance,
  SUM( ending_balance )        AS ending_balance,
  SUM( current_year_balance )  AS current_year_balance
  FROM +itab
  GROUP BY racct
  INTO TABLE @DATA(lt_balance).