ABAP

BKPF SELECT 금지 — CDS View 전환 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요와 이 글에서 다루는 범위

SAP S/4HANA 환경에서 회계 전표 헤더 정보를 조회할 때 과거에는 BKPF 테이블을 직접 SELECT 하는 방식이 일반적이었습니다. 그러나 가상 데이터 모델(VDM)이 도입된 이후로는 I_JournalEntry CDS View가 표준 접근 경로로 자리 잡았습니다. 이 글은 BKPF를 직접 다루던 ABAP 개발자가 VDM 기반 개발로 전환할 때 필요한 핵심 개념과 실전 코드를 단계별로 정리합니다.

  • I_JournalEntry의 구조와 BKPF 대비 장점 이해
  • 주요 필드(CompanyCode, AccountingDocument, FiscalYear 등) 의미 정리
  • ABAP에서 OPEN SQL 및 ABAP CDS 소비 예제 3단계
  • I_JournalEntryItem과 JOIN하는 실무 시나리오
  • 인덱스 최적화와 권한 객체 사용 시 주의점

사전 이해가 필요한 항목

이 글은 BKPF/BSEG 테이블 구조, ABAP CDS View의 기본 문법(@AbapCatalog, @AccessControl), 그리고 OPEN SQL의 SELECT ... FROM cds_view 구문에 대한 최소한의 이해를 전제로 합니다. FI 모듈의 전표(Document) 개념 — 전표 번호, 회계연도, 회사코드의 3중 키 — 도 알고 있어야 합니다.

실행 환경과 준비물

실습은 SAP S/4HANA 2022 또는 2023 온프레미스, 그리고 Public Cloud 2402 이상 릴리스를 기준으로 합니다. I_JournalEntry는 Released CDS View로 분류되어 있어 Cloud/On-Premise 모두에서 ABAP 코드, RAP(BO 확장), Analytics, OData 서비스에서 사용 가능합니다. 개발 도구는 ABAP Development Tools (ADT) for Eclipse 3.34 이상을 권장합니다.

  • 시스템: S/4HANA 2022 FPS01 이상 또는 Cloud 2402+
  • 패키지: 테스트용 로컬 패키지 또는 $TMP
  • 권한: F_BKPF_BUK (회사코드별 BKPF 조회), S_DEVELOP
  • 샘플 데이터: 회사코드 1010, 회계연도 2025의 전표가 최소 한 건 이상

핵심 개념과 동작 원리

BKPF는 SAP ERP 시절부터 존재하는 회계 전표 헤더 테이블입니다. 약 100여 개의 필드를 가지며, 회사코드(BUKRS) + 전표번호(BELNR) + 회계연도(GJAHR)가 클러스터드 인덱스를 구성합니다. 문제는 이 테이블이 매우 광범위한 컨텍스트에서 사용된다는 점입니다. 통화 변환, 텍스트, 권한 필터링, 역기표(Reversal) 연결 같은 후처리를 매번 ABAP 코드에서 반복적으로 작성해야 했습니다.

I_JournalEntry는 BKPF를 베이스로 하면서 이러한 후처리를 CDS 레이어에서 표준화한 Interface View입니다. 일종의 "정제된 BKPF"라고 비유할 수 있습니다. 원본 BKPF가 정유소에 도착한 원유라면, I_JournalEntry는 자동차 주유소에 공급 가능한 휘발유입니다.

VDM 계층에서 I_ 접두사는 Interface View를 의미합니다. 재사용 가능한 의미론적 필드 네이밍(예: CompanyCode, AccountingDocument)을 제공하며, 상위 C_ Consumption View나 OData 서비스로 노출될 때 일관된 명칭이 유지됩니다.

주요 필드 매핑을 정리하면 다음과 같습니다.

I_JournalEntry 필드BKPF 원본 필드의미
CompanyCodeBUKRS회사코드
AccountingDocumentBELNR전표번호
FiscalYearGJAHR회계연도
AccountingDocumentTypeBLART전표 유형 (SA, KR 등)
DocumentDateBLDAT전표일자
PostingDateBUDAT전기일
FiscalPeriodMONAT회계기간
DocumentReferenceIDXBLNR참조 문서 ID
TransactionCurrencyWAERS전표 통화
ReverseDocumentSTBLG역기표 전표번호

이 매핑 덕분에 ABAP 코드에서 bkpf-bukrs 대신 CompanyCode처럼 의미가 명확한 명칭을 사용할 수 있습니다. 또한 @AccessControl.authorizationCheck: #CHECK 설정으로 DCL(Data Control Language) 권한이 자동 적용되어, 별도의 AUTHORITY-CHECK 코드 없이도 회사코드 단위 권한 필터가 동작합니다.

1단계 — 기본 조회 예제

가장 단순한 형태로 특정 회사코드의 2025년 전표 헤더 50건을 읽어옵니다. BKPF 직접 SELECT와 동일한 시나리오를 I_JournalEntry로 변환한 코드입니다.

REPORT zr_je_basic_read.

DATA: lt_journal TYPE TABLE OF i_journalentry,
      lv_count   TYPE i.

SELECT companycode,
       accountingdocument,
       fiscalyear,
       accountingdocumenttype,
       postingdate,
       documentreferenceid,
       transactioncurrency
  FROM i_journalentry
  WHERE companycode  = '1010'
    AND fiscalyear   = '2025'
  ORDER BY postingdate DESCENDING
  INTO TABLE @lt_journal
  UP TO 50 ROWS.

lv_count = lines( lt_journal ).
WRITE: / |조회된 전표 헤더: { lv_count } 건|.

LOOP AT lt_journal ASSIGNING FIELD-SYMBOL(<fs_je>).
  WRITE: / <fs_je>-accountingdocument,
           <fs_je>-postingdate,
           <fs_je>-accountingdocumenttype,
           <fs_je>-transactioncurrency.
ENDLOOP.

WHERE 절에 CompanyCodeFiscalYear를 모두 명시한 것은 BKPF의 기본 인덱스 구조를 그대로 활용하기 위함입니다. AccountingDocument만으로 조회하면 풀스캔이 발생하므로 반드시 회사코드+회계연도 조합을 함께 지정합니다.

2단계 — 헤더와 라인 아이템 통합 조회, 에러 처리 포함

실무에서는 헤더만 보는 일이 드뭅니다. 라인 아이템(I_JournalEntryItem)과 조인해 전표 전체를 한 번에 가져오는 경우가 훨씬 많습니다. 다음 예제는 특정 기간에 전기된 매입 전표(BLART = 'KR')의 헤더와 라인을 함께 읽고, 통화/금액 합계를 검증하는 시나리오입니다.

REPORT zr_je_join_read.

TYPES: BEGIN OF ty_doc_summary,
         companycode        TYPE i_journalentry-companycode,
         accountingdocument TYPE i_journalentry-accountingdocument,
         fiscalyear         TYPE i_journalentry-fiscalyear,
         postingdate        TYPE i_journalentry-postingdate,
         transactioncurrency TYPE i_journalentry-transactioncurrency,
         total_debit        TYPE p LENGTH 15 DECIMALS 2,
         total_credit       TYPE p LENGTH 15 DECIMALS 2,
       END OF ty_doc_summary.

DATA: lt_summary TYPE TABLE OF ty_doc_summary.

TRY.
    SELECT h~companycode,
           h~accountingdocument,
           h~fiscalyear,
           h~postingdate,
           h~transactioncurrency,
           SUM( CASE WHEN i~debitcreditcode = 'S'
                     THEN i~amountintransactioncurrency
                     ELSE 0 END ) AS total_debit,
           SUM( CASE WHEN i~debitcreditcode = 'H'
                     THEN i~amountintransactioncurrency
                     ELSE 0 END ) AS total_credit
      FROM i_journalentry AS h
      INNER JOIN i_journalentryitem AS i
        ON  h~companycode        = i~companycode
        AND h~accountingdocument = i~accountingdocument
        AND h~fiscalyear         = i~fiscalyear
      WHERE h~companycode            = '1010'
        AND h~fiscalyear             = '2025'
        AND h~accountingdocumenttype = 'KR'
        AND h~postingdate            BETWEEN '20250101' AND '20250331'
      GROUP BY h~companycode,
               h~accountingdocument,
               h~fiscalyear,
               h~postingdate,
               h~transactioncurrency
      INTO TABLE @lt_summary.

    IF sy-subrc <> 0.
      MESSAGE |조회 결과가 없습니다.| TYPE 'I'.
      RETURN.
    ENDIF.

    LOOP AT lt_summary ASSIGNING FIELD-SYMBOL(<fs>).
      IF <fs>-total_debit <> <fs>-total_credit.
        WRITE: / |[경고] 차대변 불일치 - 전표 { <fs>-accountingdocument }|,
                 |차변 { <fs>-total_debit } / 대변 { <fs>-total_credit }|.
      ENDIF.
    ENDLOOP.

  CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
    MESSAGE lx_sql->get_text( ) TYPE 'E'.
ENDTRY.

이 코드는 GROUP BY와 CASE WHEN을 사용해 단일 SELECT로 차대변 합계까지 계산합니다. ABAP에서 LOOP를 돌면서 합산하는 방식보다 DB 푸시다운으로 처리되므로 HANA에서 훨씬 효율적입니다. cx_sy_open_sql_db 예외 처리는 DB 연결 장애나 SQL 변환 실패 시 안정적인 종료를 보장합니다.

3단계 — 프로덕션 수준의 캡슐화와 권한, 단위 테스트

실 프로덕션 코드에서는 SELECT를 그대로 노출하지 않고 클래스로 감싸는 것이 일반적입니다. 또한 ABAP Unit 테스트와 권한 명시까지 포함해야 합니다.

CLASS zcl_journal_reader DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF ty_je_header,
             companycode        TYPE i_journalentry-companycode,
             accountingdocument TYPE i_journalentry-accountingdocument,
             fiscalyear         TYPE i_journalentry-fiscalyear,
             postingdate        TYPE i_journalentry-postingdate,
             documenttype       TYPE i_journalentry-accountingdocumenttype,
             currency           TYPE i_journalentry-transactioncurrency,
           END OF ty_je_header.

    TYPES tt_je_header TYPE STANDARD TABLE OF ty_je_header
                       WITH NON-UNIQUE KEY accountingdocument.

    METHODS read_by_period
      IMPORTING iv_company_code TYPE bukrs
                iv_fiscal_year  TYPE gjahr
                iv_date_from    TYPE budat
                iv_date_to      TYPE budat
      RETURNING VALUE(rt_header) TYPE tt_je_header
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_journal_reader IMPLEMENTATION.
  METHOD read_by_period.

    AUTHORITY-CHECK OBJECT 'F_BKPF_BUK'
             ID 'BUKRS' FIELD iv_company_code
             ID 'ACTVT' FIELD '03'.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE cx_sy_open_sql_db.
    ENDIF.

    SELECT companycode,
           accountingdocument,
           fiscalyear,
           postingdate,
           accountingdocumenttype AS documenttype,
           transactioncurrency    AS currency
      FROM i_journalentry
      WHERE companycode = @iv_company_code
        AND fiscalyear  = @iv_fiscal_year
        AND postingdate BETWEEN @iv_date_from AND @iv_date_to
      INTO TABLE @rt_header.

  ENDMETHOD.
ENDCLASS.

이어서 ABAP Unit 테스트 골격은 다음과 같이 작성합니다. 실제 DB가 아닌 CDS Test Double Framework를 사용하면 테스트 데이터 격리가 가능합니다.

CLASS ltc_reader DEFINITION FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    DATA mo_cut TYPE REF TO zcl_journal_reader.

    METHODS:
      setup,
      should_return_empty_for_future FOR TESTING.
ENDCLASS.

CLASS ltc_reader IMPLEMENTATION.
  METHOD setup.
    mo_cut = NEW #( ).
  ENDMETHOD.

  METHOD should_return_empty_for_future.
    DATA(lt_result) = mo_cut->read_by_period(
                         iv_company_code = '1010'
                         iv_fiscal_year  = '2099'
                         iv_date_from    = '20990101'
                         iv_date_to      = '20991231' ).
    cl_abap_unit_assert=>assert_initial( lt_result ).
  ENDMETHOD.
ENDCLASS.

자주 발생하는 실수와 해결법

Q1. WHERE 절에 AccountingDocument만 넣었더니 성능이 매우 느립니다.
BKPF의 기본 인덱스는 CompanyCode + AccountingDocument + FiscalYear 조합입니다. 전표번호만으로는 인덱스를 활용할 수 없습니다. 반드시 회사코드와 회계연도를 함께 지정하거나, 필요 시 보조 인덱스를 검토하세요.

Q2. I_JournalEntry로 조회했더니 데이터가 일부 누락됩니다.
DCL이 적용되어 권한이 없는 회사코드의 데이터가 자동으로 필터링됐을 가능성이 큽니다. F_BKPF_BUK, F_BKPF_BLA, F_BKPF_BES 권한 객체를 점검하세요. 디버깅 시에는 SU53을 활용하면 부족한 권한을 빠르게 확인할 수 있습니다.

Q3. BSEG를 조인하려면 어떻게 하나요?
BSEG는 클러스터 테이블이라 직접 조인이 불가능했지만, 라인 아이템 데이터는 I_JournalEntryItem(또는 ACDOCA 기반)을 통해 접근합니다. ACDOCA는 통합 저널 테이블로 BSEG의 회계 데이터를 모두 포함하며, I_JournalEntryItem이 이를 노출합니다.

Q4. 통화 변환을 매번 직접 해야 할까요?
아닙니다. I_JournalEntryItem에는 AmountInCompanyCodeCurrency, AmountInGlobalCurrency 등 미리 변환된 금액 필드가 제공됩니다. CURR_CONV 함수를 직접 호출하기 전에 표준 필드 존재 여부를 먼저 확인하세요.

심화 학습으로 이어지는 주제

I_JournalEntry를 익혔다면 다음 단계는 Consumption View 계층(C_JournalEntry 계열), RAP 기반 Behavior Definition으로 전표 생성/변경 BO 다루기, 그리고 Analytics CDS View(Cube/Query)로 회계 KPI를 노출하는 흐름입니다. ACDOCA 기반의 Universal Journal 구조를 학습하면 관리회계(CO), 재무회계(FI), 자산회계(AA)가 통합되는 원리를 깊이 이해할 수 있습니다.

  • I_OperationalAcctgDocItemCube — 분석용 큐브 뷰
  • RAP Managed BO로 전표 입력 시나리오 구현
  • OData V4 서비스로 회계 전표 노출

댓글 0

아직 댓글이 없습니다.