ABAP

아직도 BKPF 직접 쓴다면 — CDS 뷰 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 이 글에서 배우는 것

SAP S/4HANA 환경에서 재무 회계 문서를 조회할 때, 많은 ABAP 개발자들이 여전히 BKPF(회계 문서 헤더)와 BSEG(회계 문서 항목)를 직접 JOIN하는 방식을 사용합니다. 그러나 S/4HANA에서는 이러한 접근이 성능 저하, 유지보수 어려움, 향후 릴리스 호환성 문제를 야기할 수 있습니다. 이 글에서는 대변 메모(Credit Memo) 데이터를 다룰 때 I_CreditMemo CDS 뷰를 활용하여 이러한 문제를 회피하는 방법을 다룹니다.

  • BKPF 직접 조회의 구조적 한계 이해
  • SAP Virtual Data Model(VDM) 계층에서 I_CreditMemo의 역할 파악
  • Association을 활용한 실무형 CDS 쿼리 작성
  • ABAP OPEN SQL로 대변 메모 조회 및 집계 구현
  • 성능·보안·확장성을 고려한 프로덕션 코드 작성

먼저 알고 있어야 하는 것

이 글을 원활히 따라가려면 다음 지식이 필요합니다. ABAP OPEN SQL 기본 문법(SELECT, WHERE, JOIN), ABAP CDS View의 기본 구조(@AbapCatalog, define view), SAP FI 모듈의 회계 문서 개념(문서 유형, 회사 코드, 회계 연도), 그리고 ADT(ABAP Development Tools in Eclipse) 사용 경험이 있으면 좋습니다. Association과 Composition 개념을 알면 5단계 예제 이해가 훨씬 수월합니다.

환경 및 준비물

이 글의 예제는 다음 환경을 기준으로 작성되었습니다.

  • SAP S/4HANA: 2022 온프레미스 또는 SAP S/4HANA Cloud 2023 이상 권장
  • ABAP Platform: 7.57 이상 (CDS Entity 문법 지원)
  • 개발 도구: ABAP Development Tools for Eclipse 2024.x 이상
  • 권한: S_DEVELOP(개발), S_TCODE, FI 문서 조회 권한(F_BKPF_BUK)
  • 테스트 데이터: 문서 유형 DG(Customer Credit Memo) 또는 KG(Vendor Credit Memo) 데이터가 포함된 회사 코드

SAP S/4HANA 1909 이하 버전에서는 일부 필드명이 다를 수 있으니, 개발 전 SE11 또는 ADT에서 뷰 구조를 확인하는 것이 일반적으로 권장됩니다. 또한 SAP Note를 통해 릴리스별 CDS 뷰 변경 이력을 확인하는 습관을 들이면 좋습니다.

왜 BKPF 직접 조회를 피해야 하는가

ECC 시대에는 회계 문서를 조회할 때 BKPF와 BSEG를 직접 JOIN하는 방식이 표준이었습니다. 하지만 S/4HANA로 오면서 몇 가지 근본적인 변화가 있었습니다.

첫째, S/4HANA는 ACDOCA(Universal Journal)라는 단일 원장을 도입했습니다. 기존 BSEG의 데이터 중 상당수는 이제 ACDOCA에 저장되며, BKPF는 헤더 정보만 담당합니다. 즉, BKPF와 BSEG만 JOIN해서는 원장 레벨의 완전한 정보를 얻을 수 없습니다.

둘째, Virtual Data Model(VDM)이라는 데이터 접근 계층이 도입되었습니다. VDM은 3단계로 구성됩니다.

  • Basic View (I_ 접두사): 원천 테이블을 정규화한 재사용 가능한 뷰
  • Composite View: 여러 Basic View를 조합한 비즈니스 엔터티
  • Consumption View (C_ 접두사): Fiori 앱 등 최종 소비를 위한 뷰

비유하자면, BKPF/BSEG는 도서관의 서고 깊숙한 곳에 있는 원본 자료실이고, I_CreditMemo는 사서가 미리 정리해 놓은 주제별 자료 코너입니다. 사용자가 매번 서고에 들어가 자료를 뒤지는 대신, 사서가 잘 정리해 놓은 코너에서 필요한 자료를 빠르게 찾을 수 있게 되는 셈입니다.

정리하면, BKPF 직접 조회는 (1) 필드 시맨틱 파악 부담, (2) 어소시에이션 재구현, (3) 향후 데이터 모델 변경 시 유지보수 리스크가 있습니다. I_CreditMemo는 이를 캡슐화합니다.

1단계: 기본 SELECT로 대변 메모 목록 조회

가장 간단한 시나리오부터 시작해봅니다. 특정 회사 코드에서 최근 회계 연도에 발생한 대변 메모 목록을 조회하는 예제입니다.

REPORT z_credit_memo_basic.

DATA: lt_credit_memos TYPE TABLE OF i_creditmemo,
      lv_company_code TYPE bukrs VALUE '1010',
      lv_fiscal_year  TYPE gjahr VALUE '2026'.

SELECT AccountingDocument,
       CompanyCode,
       FiscalYear,
       AccountingDocumentType,
       DocumentDate,
       PostingDate,
       CreatedByUser
  FROM i_creditmemo
  WHERE CompanyCode = @lv_company_code
    AND FiscalYear  = @lv_fiscal_year
  ORDER BY PostingDate DESCENDING
  INTO TABLE @lt_credit_memos
  UP TO 100 ROWS.

LOOP AT lt_credit_memos INTO DATA(ls_memo).
  WRITE: / ls_memo-accountingdocument,
           ls_memo-postingdate,
           ls_memo-accountingdocumenttype.
ENDLOOP.

여기서 주목할 점은 BKPF의 BLART(문서 유형) 필드를 직접 필터링하지 않아도 된다는 것입니다. I_CreditMemo는 이미 대변 메모 유형(예: DG, KG, DA)만 포함하도록 필터링되어 있습니다. 이것이 VDM의 핵심 가치, 즉 비즈니스 시맨틱의 캡슐화입니다.

2단계: Association 활용과 예외 처리

실무에서는 대변 메모 헤더 정보뿐 아니라 관련된 판매 오더(Sales Order), 파트너 정보, 라인 아이템 금액까지 함께 조회해야 하는 경우가 많습니다. 이때 CDS의 Association을 활용하면 강력한 조회가 가능합니다.

@AbapCatalog.sqlViewName: 'ZVCRDMEMOSALES'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Credit Memo with Sales Order Info'
define view Z_CreditMemoWithSalesOrder
  as select from I_CreditMemo as CreditMemo

  association [0..*] to I_CreditMemoItem as _Item
    on  $projection.AccountingDocument = _Item.AccountingDocument
    and $projection.CompanyCode        = _Item.CompanyCode
    and $projection.FiscalYear         = _Item.FiscalYear

  association [0..1] to I_SalesOrder as _SalesOrder
    on $projection.SalesOrderReference = _SalesOrder.SalesOrder
{
  key CreditMemo.AccountingDocument,
  key CreditMemo.CompanyCode,
  key CreditMemo.FiscalYear,
      CreditMemo.PostingDate,
      CreditMemo.AccountingDocumentType,
      CreditMemo.SalesOrderReference,
      _SalesOrder.SalesOrderType,
      _SalesOrder.SoldToParty,
      _Item
}

이 CDS 뷰를 ABAP에서 활용하고 예외 처리와 로깅을 곁들이면 다음과 같이 작성할 수 있습니다.

CLASS zcl_credit_memo_service DEFINITION PUBLIC.
  PUBLIC SECTION.
    METHODS get_memos_with_sales_order
      IMPORTING iv_company_code TYPE bukrs
                iv_posting_from TYPE budat
                iv_posting_to   TYPE budat
      RETURNING VALUE(rt_result) TYPE STANDARD TABLE OF z_creditmemowithsalesorder
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_credit_memo_service IMPLEMENTATION.
  METHOD get_memos_with_sales_order.
    TRY.
        SELECT * FROM z_creditmemowithsalesorder
          WHERE CompanyCode = @iv_company_code
            AND PostingDate BETWEEN @iv_posting_from AND @iv_posting_to
          INTO TABLE @rt_result.

        IF sy-subrc <> 0.
          MESSAGE i001(zfi) WITH iv_company_code.
        ENDIF.
      CATCH cx_sy_open_sql_db INTO DATA(lx_error).
        cl_bali_log_db_writer=>get_instance( )->save(
          io_log = create_error_log( lx_error ) ).
        RAISE EXCEPTION lx_error.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

3단계: 프로덕션 레벨 — 성능·보안·테스트

프로덕션 코드는 대량 데이터를 다루므로 성능 튜닝과 보안, 테스트 가능성을 함께 고려해야 합니다. 아래 예제는 회사 코드별 월간 대변 메모 총액을 집계하는 서비스로, Currency Conversion과 CDS Access Control까지 포함합니다.

@AbapCatalog.sqlViewName: 'ZVCRDMEMOAGG'
@AccessControl.authorizationCheck: #CHECK
@Analytics.dataCategory: #CUBE
@EndUserText.label: 'Monthly Credit Memo Aggregation'
define view Z_CreditMemoMonthlyAgg
  as select from I_CreditMemo as Memo
  association [0..*] to I_CreditMemoItem as _Item
    on  $projection.AccountingDocument = _Item.AccountingDocument
{
  key Memo.CompanyCode,
  key cast( substring( cast(Memo.PostingDate as abap.char(8)), 1, 6 )
       as abap.char(6) ) as PostingYearMonth,

      @Semantics.amount.currencyCode: 'CompanyCodeCurrency'
      sum( _Item.AmountInCompanyCodeCurrency ) as TotalCreditAmount,

      count( distinct Memo.AccountingDocument ) as DocumentCount,
      Memo.CompanyCodeCurrency
}
group by Memo.CompanyCode,
         Memo.PostingDate,
         Memo.CompanyCodeCurrency
CLASS zcl_credit_memo_report DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
    METHODS get_monthly_summary
      IMPORTING iv_company_code TYPE bukrs
                iv_year         TYPE gjahr
      RETURNING VALUE(rt_summary) TYPE ty_summary_tt.
  PRIVATE SECTION.
    TYPES: BEGIN OF ty_summary,
             yearmonth  TYPE char6,
             total_amt  TYPE dmbtr,
             doc_count  TYPE i,
           END OF ty_summary,
           ty_summary_tt TYPE STANDARD TABLE OF ty_summary WITH EMPTY KEY.
ENDCLASS.

CLASS zcl_credit_memo_report IMPLEMENTATION.
  METHOD get_monthly_summary.
    SELECT PostingYearMonth AS yearmonth,
           TotalCreditAmount AS total_amt,
           DocumentCount     AS doc_count
      FROM z_creditmemomonthlyagg
      WHERE CompanyCode = @iv_company_code
        AND PostingYearMonth LIKE @( |{ iv_year }%| )
      ORDER BY PostingYearMonth
      INTO TABLE @rt_summary.
  ENDMETHOD.

  METHOD if_oo_adt_classrun~main.
    DATA(lt_result) = get_monthly_summary(
      iv_company_code = '1010'
      iv_year         = '2026' ).
    out->write( lt_result ).
  ENDMETHOD.
ENDCLASS.

이 코드는 세 가지 프로덕션 원칙을 지킵니다. 첫째, 집계 로직을 ABAP이 아닌 CDS로 이관하여 HANA DB 레벨에서 실행합니다(code push-down). 둘째, @AccessControl.authorizationCheck: #CHECK로 DCL(Data Control Language) 기반 권한 체크가 자동 적용됩니다. 셋째, ABAP Unit 테스트에서 cl_osql_test_environment를 활용해 DB 의존성 없이 테스트할 수 있는 구조입니다.

자주 마주치는 실수와 해결 방법

Q1. I_CreditMemo에 특정 문서가 안 보입니다. A: I_CreditMemo는 회계 문서 유형이 대변 메모로 분류된 문서만 포함합니다. 커스터마이징에서 문서 유형이 대변 메모로 지정되지 않았거나(SPRO → 재무회계 → 문서 → 문서 유형), 문서 상태가 파킹/역기표된 경우 조회되지 않습니다. SE16N으로 BKPF의 BLARTSTBLG 필드를 먼저 확인하세요.

Q2. Association을 사용했는데 성능이 오히려 나빠졌어요. A: Association은 실제 참조되는 필드가 있을 때만 JOIN이 생성되지만, SELECT *를 쓰면 모든 Association이 강제로 확장됩니다. 반드시 필요한 필드만 명시적으로 SELECT하고, Association 필드는 _Item.AmountInCompanyCodeCurrency처럼 명시적으로 지정하세요. 또한 @ObjectModel.usageType.dataClass: #TRANSACTIONAL이 지정된 뷰는 대량 조회에 부적합할 수 있습니다.

Q3. 통화가 서로 다른 회사 코드를 함께 집계하면 값이 이상합니다. A: CDS 집계에서 통화는 반드시 @Semantics.amount.currencyCode 어노테이션과 함께 그룹핑되어야 합니다. 서로 다른 통화를 하나의 SUM으로 합치면 논리적으로 무의미한 값이 나옵니다. CURRENCY_CONVERSION 함수를 활용해 특정 통화로 변환 후 집계하거나, 통화별로 그룹핑하는 것이 일반적으로 권장됩니다.

기타 트러블슈팅: DCL이 활성화된 상태에서 조회 결과가 0건이라면 권한 문제일 가능성이 큽니다. SU53으로 권한 체크 실패 로그를 확인하고, @AccessControl.authorizationCheck: #NOT_REQUIRED로 임시 우회하지 말고 정식 PFCG 역할에 권한을 추가하는 것이 안전합니다.

이어서 살펴볼 만한 주제

  • ACDOCA 기반 CDS 뷰: I_JournalEntry, I_JournalEntryItem — 원장 레벨 상세 분석
  • RAP(RESTful Application Programming Model): I_CreditMemo를 Behavior와 결합해 OData 서비스로 노출
  • Analytical CDS View: @Analytics.dataCategory: #CUBE/#DIMENSION을 활용한 SAP Analytics Cloud 연동
  • Extensibility: @AbapCatalog.extensible: #EXTENSIBLE_ANY 및 CDS View Extend를 통한 커스텀 필드 추가
  • Released API: SAP S/4HANA Cloud에서 릴리스된 CDS View만 사용 가능하므로 릴리스 상태 확인 필수

댓글 0

아직 댓글이 없습니다.