SAP接口编程之综合实例(一):资产负债表方案

编程入门 行业动态 更新时间:2024-10-09 04:26:24

SAP接口编程之综合实例(一):<a href=https://www.elefans.com/category/jswz/34/1766018.html style=资产负债表方案"/>

SAP接口编程之综合实例(一):资产负债表方案

SAP 对法定的会计报表,比如资产负债表,损益表等,有多种实现方案。这里给出一个集成 Excel 的实现方案。实现方案的主要优点是用户的自由度比较大,可以灵活设置,而且基本实现了一次开发,不同项目只需要部署就能使用。

1. 资产负债表实现方法说明

  • 在 Excel 中,使用 VBA 调用 SAP RFC 方法,导入公司代码下会计科目和科目汇总表 (英文叫 Trial balance, 包括会计科目的期初余额、本期发生额和期末余额),自动保存到 Excel 工作表,导入的数据格式如下:

数据字段说明:

  • 在 Excel 中自定义相应函数,实现数据显示。比如我们要实现的 “资产负债表” 格式如下:

以【货币资金】为例,D7 单元格的公式:=AccAmount(BS_Source!B:B,K7,BS_Source!J:J)AccAmount 是自定义的函数名,表示获取会计科目的期末余额。我们先不管这个函数参数是什么意思,总之是实现自动取数,文章后面会详细说明。

2. 数据通过 RFC 导入到 Excel

SAP 中自定义函数获取会计科目余额和发生额

SAP 提供的函数 BAPI_GL_GETGLACCPERIODBALANCES 只能提供单个科目的会计余额,我们要导入的是批量数据,所以需要自定义一个函数。写法可以多种,这里给出一个示例代码:

FUNCTION zbapi_getglaccperiodbalances.
*"----------------------------------------------------------------------
*"*"局部接口:
*"  IMPORTING
*"     VALUE(COMPANYCODE) LIKE  FAGLFLEXT-RBUKRS OPTIONAL
*"     VALUE(FISCALYEAR) LIKE  FAGLFLEXT-RYEAR OPTIONAL
*"     VALUE(PERIOD) LIKE  FAGLFLEXT-RPMAX OPTIONAL
*"     VALUE(CURRENCYTYPE) LIKE  BAPI1028_5-CURR_TYPE OPTIONAL
*"  EXPORTING
*"     VALUE(BALANCE_CARRIED_FORWARD) LIKE  BAPI1028_4-BALANCE
*"     VALUE(RETURN) LIKE  BAPIRETURN STRUCTURE  BAPIRETURN
*"  TABLES
*"      ACCOUNT_BALANCES STRUCTURE  ZBALANCES OPTIONAL
*"  EXCEPTIONS
*"      INIT_DATA
*"----------------------------------------------------------------------DATA: BEGIN OF lt_fag OCCURS 0,bukrs LIKE faglflext-rbukrs,  "公司代码ryear LIKE faglflext-ryear,   "会计年度racct LIKE faglflext-racct,   "会计科目drcrk LIKE faglflext-drcrk,   "借贷方hslvt LIKE faglflext-hslvt,hsl01 LIKE faglflext-hsl01,hsl02 LIKE faglflext-hsl02,hsl03 LIKE faglflext-hsl03,hsl04 LIKE faglflext-hsl04,hsl05 LIKE faglflext-hsl05,hsl06 LIKE faglflext-hsl06,hsl07 LIKE faglflext-hsl07,hsl08 LIKE faglflext-hsl08,hsl09 LIKE faglflext-hsl09,hsl10 LIKE faglflext-hsl10,hsl11 LIKE faglflext-hsl11,hsl12 LIKE faglflext-hsl12,hsl13 LIKE faglflext-hsl13,hsl14 LIKE faglflext-hsl14,hsl15 LIKE faglflext-hsl15,hsl16 LIKE faglflext-hsl16,END OF lt_fag,lt_ret LIKE zbalances OCCURS 0 WITH HEADER LINE.FIELD-SYMBOLS: <field>.DATA: lv_idx TYPE i,lv_times TYPE i,lv_int TYPE i,lv_rpmax(2) TYPE c,lv_fieldname TYPE string,lv_hslxx LIKE faglflext-hslvt,lv_waers LIKE t001-waers.* 公司本位币SELECT SINGLE waersINTO lv_waersFROM t001WHERE bukrs = companycode.SELECT rbukrs AS bukrsryearracctdrcrkhslvthsl01hsl02hsl03hsl04hsl05hsl06hsl07hsl08hsl09hsl10hsl11hsl12hsl13hsl14hsl15hsl16INTO TABLE lt_fagFROM faglflextWHERE rbukrs = companycodeand ryear = fiscalyear.SORT lt_fag BY bukrs ryear racct drcrk.LOOP AT lt_fag.AT NEW racct.CLEAR lt_ret.lt_ret-comp_code = lt_fag-bukrs.lt_ret-gl_account = lt_fag-racct.lt_ret-fisc_year = lt_fag-ryear.lt_ret-fis_period = period.lt_ret-currency = lv_waers.ENDAT.*   年初余额lt_ret-yr_begin_bal = lt_ret-yr_begin_bal + lt_fag-hslvt.    IF period = 12.lv_times = 16.ELSE.lv_times = period.ENDIF.*   期初余额lt_ret-per_begin_bal = lt_ret-per_begin_bal + lt_fag-hslvt.lv_times = lv_times - 1.DO lv_times TIMES.IF sy-index >= lv_times.EXIT.ENDIF.MOVE sy-index TO lv_rpmax.SHIFT lv_rpmax RIGHT DELETING TRAILING space.OVERLAY lv_rpmax WITH '00'.CONCATENATE 'HSL' lv_rpmax INTO lv_fieldname.ASSIGN COMPONENT lv_fieldname OF STRUCTURE lt_fag TO <field>.MOVE <field> TO lv_hslxx.lt_ret-per_begin_bal = lt_ret-per_begin_bal + lv_hslxx.ENDDO.IF period = 12.lv_times = 5.ELSE.lv_times = 1.ENDIF.*   期间发生额DO lv_times TIMES.lv_int = period + sy-index - 1.MOVE lv_int TO lv_rpmax.SHIFT lv_rpmax RIGHT DELETING TRAILING space.OVERLAY lv_rpmax WITH '00'.CONCATENATE 'HSL' lv_rpmax INTO lv_fieldname.ASSIGN COMPONENT lv_fieldname OF STRUCTURE lt_fag TO <field>.MOVE <field> TO lv_hslxx.IF lt_fag-drcrk = 'S'.
*       期间借方lt_ret-debits_per = lt_ret-debits_per + lv_hslxx.ELSE.
*       期间贷方lt_ret-credit_per = lt_ret-credit_per + lv_hslxx.ENDIF.ENDDO.AT END OF racct.APPEND lt_ret.ENDAT.ENDLOOP.LOOP AT lt_ret.lv_idx = sy-tabix.*   期间发生额(借方+贷方)lt_ret-period_amt = lt_ret-debits_per + lt_ret-credit_per.*   期末余额lt_ret-balance = lt_ret-per_begin_bal + lt_ret-period_amt.MODIFY lt_ret INDEX lv_idx.ENDLOOP.DELETE lt_ret WHERE yr_begin_bal = 0 AND per_begin_bal = 0 AND debits_per = 0 AND credit_per = 0.account_balances[] = lt_ret[].
ENDFUNCTION.

函数名: zbapi_getglaccperiodbalances。函数的参数包括公司代码,年度,期间,币别,根据参数所确定的条件,输出这些科目的期初余额,本期发生额,期末余额。代码简单,不多解释,也不是本文的重点。但顺便提一句,SAP 系统 ABAP 的编码就这么多,不像其他解决方案,可能需要大量 ABAP 编码。

RFC SAP_Connection 模块

从模块的角度给出代码。Excel 首先是SAP_Connection模块,负责连接 SAP 系统,登陆 (Logon) 和注销 (Logoff) 。

' 模块说明:
' 负责SAP的连接,连接成功后,SAPConnection对象保存连接的信息Option ExplicitDim sapLogon  As SAPLogonCtrl.SAPLogonControl
Public SAPConnection As SAPLogonCtrl.Connection
Public logonSuccessful As BooleanPublic Sub logon()logonSuccessful = FalseSet sapLogon = New SAPLogonControlSet SAPConnection = sapLogon.NewConnection()    Call SAPConnection.logon(0, False)If SAPConnection.IsConnected = tloRfcConnected ThenlogonSuccessful = TrueExit SubEnd If
End SubPublic Sub Logoff()If SAPConnection Is Nothing ThenExit SubEnd IfSAPConnection.LogofflogonSuccessful = FalseSet SAPConnection = Nothing
End Sub

RFC 调用 函数 zbapi_getglaccperiodbalances

调用 SAP zbapi_getglaccperiodbalances 函数,将数据写入工作表。
首先在 VBA 中实现 ABAP内表数据(函数的表参数)写入工作表的通用实现。考虑到代码效率,数据先在内存中处理后,一次性写到工作表:

' 模块说明:
' SAP FM的表参数类似internal table, 将数据输出值EXCEL工作表Option Explicit'--------------------------------------------------
' 将Table对象写入worksheet, 整体拷贝,速度优化
'--------------------------------------------------
Public Sub WriteTable(itab As SAPTableFactoryCtrl.Table, sht As Worksheet)Dim col As Long          ' column indexDim row As Long          ' row indexDim headerRange As Variant  '在Excel中根据itab的header大小,类型为Variant数组Dim itemsRange As Variant   '在Excel中根据itab的行数和列数,类型为Variant数组' 清除cells的内容sht.Cells.ClearContentsIf itab.RowCount = 0 ThenExit SubEnd If'-------------------------------------------------' 取消Excel的屏幕刷新和计算功能以加快速度'-------------------------------------------------Application.ScreenUpdating = FalseApplication.Calculation = xlCalculationManual'------------------------------' 将Table的Header写入Worksheet'------------------------------' 根据内表的列数,使用Range创建一个数组Dim headerstarts As RangeDim headerends As RangeSet headerstarts = sht.Cells(1, 1)Set headerends = sht.Cells(1, itab.ColumnCount)headerRange = sht.Range(headerstarts, headerends).Value' 将内表列名写入数组For col = 1 To itab.ColumnCountheaderRange(1, col) = itab.Columns(col).NameNext' 从数组一次性写入Excel,这样效率较高sht.Range(headerstarts, headerends).Value = headerRange'-------------------------------' 将Table的行项目写入Worksheet'-------------------------------' 根据内表的大小,使用Range创建数组Dim itemStarts As RangeDim itemEnds As RangeSet itemStarts = sht.Cells(2, 1)Set itemEnds = sht.Cells(itab.RowCount + 1, itab.ColumnCount)itemsRange = itab.Data' 一次性将数组写入Worksheetsht.Range(itemStarts, itemEnds).Value = itemsRange'---------------------------------' 恢复Excel的屏幕刷新和计算'---------------------------------Application.ScreenUpdating = TrueApplication.Calculation = xlCalculationAutomaticApplication.Calculate
End Sub

然后是调用 zbapi_getglaccperiodbalances 函数,代码在sap_get_acc_period_bal模块:

' 模块说明:
' 调用RFC获取公司代码会计科目的余额,写入工作表
'
' v1.0, 2015-6-14 by Stone Wang
' V1.1, 2015-6-26 by Stone WangOption ExplicitPublic Sub get_sap_acc_balances(cocd As String, fiscal_yr As String, period As String)' 导入SAP科目余额,写入BS_Source工作表Dim sapFunctions As SAPFunctionsOCX.sapFunctionsDim sapFunction As ObjectDim acBalanceTable As Object' 写入的工作表Dim sht As WorksheetSet sht = BSSourceIf logonSuccessful = False Then Exit SubSet sapFunctions = CreateObject("SAP.FUNCTIONS") ' Use function to call BAPISet sapFunctions.Connection = SAPConnectionOn Error GoTo err1:Set sapFunction = sapFunctions.Add("ZBAPI_GETGLACCPERIODBALANCES")' Function import parameterssapFunction.Exports("COMPANYCODE").Value = cocd       'Company codesapFunction.Exports("FISCALYEAR").Value = fiscal_yr   'Fiscal yearsapFunction.Exports("PERIOD").Value = periodsapFunction.Exports("CURRENCYTYPE").Value = "10"      'Currency type' Function table parametersSet acBalanceTable = sapFunction.Tables("ACCOUNT_BALANCES")If sapFunction.Call = True ThenIf acBalanceTable.RowCount > 0 Thensht.Activate'将internal table写入工作表Call WriteTable(acBalanceTable, sht)Elsesht.Activatesht.Cells.ClearContentssht.Range("A1").Value = "没有数据被导入,数据源可能为空!"End IfEnd IfSet acBalanceTable = NothingSet sapFunctions = NothingSet sapFunction = Nothingerr1:If Err.Number = 1001 ThenMsgBox "远程调用错误,请检查SAP中是否存在函数ZBAPI_GETGLACCPERIODBALANCES,是否允许远程调用等。", _vbExclamationExit SubEnd If
End Sub

另外,因为 Excel 自定义函数需要用到会计科目,从速度上考虑,把会计科目列表存放在一个工作表中,方便后续函数调用。会计科目列表调用
SAP 的 BAPI 来实现,这里直接从刚才导入的会计科目余额中提取,代码放在 account_gl_list模块 中:

' 模块说明:
' 从COASheet工作表获取公司代码下所有的会计科目清单
' 在UDF中,需要在会计科目的范围中循环,根据公司代码的科目清单,目的是提高效率Option ExplicitPublic Sub Generate_GL_List()' 根据BS_Source的科目,产生会计科目清单,剔除重复项(如果有的话)' 数据存放在GLList工作表中Dim row_count As Integerrow_count = BSSource.UsedRange.rows.count' 如果没有数据,则不处理If row_count <= 1 Then Exit SubWith GLListSheet' 清除之前的数据.Cells.ClearContents' 数据拷贝.Range("A1:A" & row_count + 1).Value = BSSource.Range("B1:B" & row_count + 1).Value' 消除重复项.Range("A:A").RemoveDuplicates' 根据会计科目排序.Columns("A:A").Sort key1:=.Range("A:A"), order1:=xlAscending, Header:=xlYesEnd With
End SubPublic Function get_gl_count() As Integer'------------------------------------------' 获取会计科目的数量' 会计科目根据BS_Source导入的数据加工而得'------------------------------------------Dim count As Integer'因为有表头,实际的会计科目数减一count = GLListSheet.UsedRange.rows.count - 1get_gl_count = count
End Function

用户操作界面设计

使用一个Excel工作表作为录入界面,执行上面的会计科目余额导入程序。

导入数据

当用点击【更新数据源】按钮,执行下面的代码,导入会计科目余额,至此,跟 SAP 数据交互完成。

' 模块说明:
' 从SAP系统导入会计科目余额' Version 1.0, 2015-6-14 by Stone WangOption ExplicitPublic Sub import_sap_data()' 校验(input validation)If Range("COMPANY_CODE").Value = "" ThenMsgBox "请输入公司代码.", vbExclamationExit SubEnd IfIf Range("FISCAL_YEAR").Value = "" ThenMsgBox "请输入会计年度.", vbExclamationExit SubEnd IfIf Range("PERIOD").Value = "" ThenMsgBox "请输入会计期间.", vbExclamationExit SubEnd If' 如果没有连接到SAP,连接SAPIf logonSuccessful <> True ThenCall logonEnd If' 导入会计科目余额Dim cocd As StringDim fiscal_year As StringDim period As Stringcocd = CStr(Range("COMPANY_CODE").Value)fiscal_year = CStr(Range("FISCAL_YEAR").Value)period = CStr(Range("PERIOD").Value)Call get_sap_acc_balances(cocd, fiscal_year, period)' 根据BS_Source产生科目清单' 将会计科目清单写入GLList工作表Call Generate_GL_List   Application.Calculate
End Sub

3. Excel 自定义函数

我打算自定义的函数叫做 AccAmount,是对 Excel SumIfs 函数的包装和功能增强。

SumIfs 函数的语法大体如下:

SumIfs(rangeA, criteria, rangeB)
根据 criteria(条件),在 rangeA 中找到相同的数据,然后将 rangeB 中对应行的数据加总,并作为返回值。

增强的原因是使用 SumIfs 函数,只能处理一个科目,因为资产负债表每个数据项可能来自多个科目,所以需要对这个函数进行修改,使得能处理多个科目。

我对资产负债表的数据项数据来源,定义了一种范围表达的格式。在科目范围中,用【分号】表示分隔的科目,用【…】表示连接的科目。举例如下:

- 1001000000..1099999999:表示 1001 开头的科目,即 1001000000 至1099999999
- 2241000000..2241999999;2703000000..2703999999:表示2241开头的科目以及2703开头的科目

有了这个定义格式,我们就实现 AccAmount() 函数:

AccAmount(rangeA, accCriteria, rangeB)

根据 accCriteria(会计科目条件)在 rangeA 中找到对应科目,然后将rangeB 中对应行的单元格数据加总,并将汇总的数据作为返回值。

科目范围分解为会计科目数组

刚才所述的会计科目范围,我们需要将其分解为单个的科目,存放在数组中,代码放在 account_split 模块之中:

'模块说明:
'会计科目的范围用分号表示不连续的范围,用..表示连续的范围
'会计科目范围分解出具体的会计科目,先根据分号分割,再从连续的科目中分解
'为了提高效率,循环的时候,读取公司代码的实际科目清单' Verion 1.1:
' Account_resolution函数增加了对输入参数为空的判断Option ExplicitPrivate Function split_account_by_semicolon(account_range As String) As String()'-------------------------------------------' 将科目范围根据"分号"分解,然后放在数组中' 数组中每一个item, 可能是单个科目,也可能是多个科目'-------------------------------------------Dim acc_array() As Stringacc_array = Split(account_range, ";") ' split函数返回的数组从0开始' 逗号分割的每一个数组元素前后可能存在空格,将空格去掉Dim i As IntegerFor i = LBound(acc_array) To UBound(acc_array)acc_array(i) = Trim(CStr(acc_array(i)))Nextsplit_account_by_semicolon = acc_array
End FunctionPrivate Function split_continous_account(continous_acc As String) As String()'将会计科目根据..进行分解'..表示连续的科目范围'如果有.., 但未找到合适科目,返回#EmptyForXXX#'acc_count: 会计科目数量Dim acc_count As Integeracc_count = get_gl_count()Dim acc_list() As StringIf acc_count >= 1 ThenReDim acc_list(1 To acc_count)ElseReDim acc_list(1 To 1)End IfDim dot_pos As Integer  ' position of double dots 双点的位置dot_pos = InStr(continous_acc, "..")    ' 查找 ".." ,返回位置Dim lower_acc As StringDim upper_acc As String' 一共需要处理三种情况:' 1) 有..号,有科目被选择' 2) 有..号,没有科目被选择' 3) 没有..号,表示单个科目If dot_pos > 0 Then ' Foundlower_acc = Left(continous_acc, dot_pos - 1)upper_acc = Right(continous_acc, Len(continous_acc) - dot_pos - 1)Dim account_range As RangeSet account_range = GLListSheet.Range("A2:A" & acc_count + 1)Dim i As IntegerDim item As VariantDim temp_acc As Stringi = 1  ' cocd_coa下标从1开始For Each item In account_rangetemp_acc = CStr(item)If temp_acc >= lower_acc And temp_acc <= upper_acc Thenacc_list(i) = CStr(item)i = i + 1End If' 确保会计科目表示按科目大小排序的If temp_acc > upper_acc ThenExit ForEnd IfNextIf i > 1 Then ' i > 1表示有科目包括在其中ReDim Preserve acc_list(1 To i - 1)Else ' 表示没有科目被选择ReDim Preserve acc_list(1 To 1)acc_list(1) = "#EmptyFor" & continous_acc & "#" ' 没有找到科目,empty错误End IfElseReDim acc_list(1 To 1)acc_list(1) = continous_accEnd Ifsplit_continous_account = acc_list
End FunctionPublic Function accounts_resolution(acc_range As String) As String()'----------------------------------------------------' 根据科目范围,将范围分解为单个会计科目,放在数组中'----------------------------------------------------' account_array()表示所有范围内科目,函数最后作为返回值Dim account_array() As String' 先判断参数acc_range是否为空If Len(acc_range) = 0 ThenReDim account_array(1 To 1)account_array(1) = "#Empty#"accounts_resolution = account_arrayExit FunctionEnd If' 如果参数acc_range不为空,正常处理Dim count As Integercount = get_gl_count()If count > 1 ThenReDim account_array(1 To count)ElseReDim account_array(1 To 1)End If' 先根据分号进行分解,分解后每一项表示单一科目或连续的科目Dim continous_acc() As Stringcontinous_acc = split_account_by_semicolon(acc_range)Dim i As Integeri = 1Dim item As VariantFor Each item In continous_acc' 再将连续的科目分解为具体的会计科目Dim acc_in_continuous_rng() As Stringacc_in_continuous_rng = split_continous_account(CStr(item))Dim acc As VariantFor Each acc In acc_in_continuous_rngaccount_array(i) = CStr(acc)i = i + 1NextNext' split_continuous_account()至少返回一个值' 没有分解成功返回:#EmptyForXXX#, i的值一定会>=2, 所以不用判断i的大小ReDim Preserve account_array(1 To i - 1)accounts_resolution = account_array
End Function

代码说明

  • split_account_by_semicolon 函数,用于将科目范围中 有分号 的分解,并存放在一个数组之中
  • split_continous_account 函数,用于将连续的科目分解,结果存放在数组中
  • accounts_resolution 函数,综合上面的两个函数,对科目范围进行分解,并将结果存放在数组中

定义 AccAmount 函数

将会计科目范围中的科目,循环调用 SumIfs 函数,结果加总,并作为返回值。

' @Author: Stone Wang
' @version 1.0
' @date: 2015-6-14
'
' 模块说明:
' 自定义函数,获取科目在某一列的合计,该函数是对EXCEL SUMIFs函数的增强Option ExplicitPublic Function AccAmount(ByVal acc_range As Range, _ByVal acc_criteria As String, _ByVal sum_range As Range) As Double' 获取account期间的余额' acc_range: account range' acc_criteria: account criteria' sum_range: sum rangeDim result As DoubleDim acc_array() As Stringacc_array = accounts_resolution(acc_criteria)Dim item As VariantDim current_acc As StringFor Each item In acc_arraycurrent_acc = CStr(item)If Len(current_acc) > 0 Thenresult = result + _WorksheetFunction.SumIfs(sum_range, acc_range, current_acc)End IfNextAccAmount = result
End Function

AccAmount 函数使用方法说明

设置专门的数据源定义区,比如货币资金,设置科目范围如下:

并且将 1001000000…1099999999 所在的单元格定义一个名称为 CASH,这样在资产负债表的货币资金行中,使用如下公式:

=AccAmount(BS_Source!B:B,CASH,BS_Source!J:J)

表示根据 CASH 单元格的科目范围(即 1001000000 至 1099999999),从 BS_Source 工作表的 B 列找到科目范围中有的科目,将相应行的 J 列数据汇总。

这样,定义了所有科目的公式之后,资产负债表就做好了。因为函数是给用户使用的,用户可以自由定义报表的各种细节,灵活性就非常好,自动化程度也高。

我曾在两个 SAP 实施项目中使用了这种方法来实现资产负债表、损益表和现金流量表。如果用户接受 Excel 作为数据展示的平台,我们可以用这个思路实现很多报表。

更多推荐

SAP接口编程之综合实例(一):资产负债表方案

本文发布于:2024-02-07 00:32:07,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1751730.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:资产负债表   实例   接口   方案   SAP

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!