ABAP

BKPF 직접 JOIN 큰일 — 지불 문서 CDS 뷰 실전 #shorts #SAP #ABAP

▶ YouTube에서 보기

이 글에서 다루는 내용과 도달 지점

이 글은 S/4HANA 재무 회계 영역에서 지불 문서(Payment Document)를 조회할 때 흔히 저지르는 실수인 BKPF 테이블 직접 JOIN 방식을 벗어나, SAP가 제공하는 표준 CDS 뷰 I_PaymentDocument를 활용해 데이터 접근 계층을 단순화하는 방법을 실전 관점에서 정리합니다. 재무 데이터의 특성상 권한 필터, 회계연도 계산, 회사 코드 분리, 취소 문서 처리 등 신경 써야 할 요소가 많은데, VDM(Virtual Data Model) 기반 CDS 뷰가 이런 부담을 어떻게 흡수하는지 살펴봅니다.

읽기 전에 갖추면 좋은 배경

ABAP OpenSQL 기본 문법(SELECT, JOIN, INTO TABLE)과 S/4HANA CDS 뷰의 개념(@AbapCatalog.sqlViewName, association)을 이미 접한 독자를 전제로 합니다. FI 모듈에서 BKPF(회계 문서 헤더), BSEG(회계 문서 라인), BSAK/BSIK(미결/기결 매입채무)의 역할을 개괄적으로 알고 있으면 이해가 빠릅니다.

테스트 환경과 준비 사항

  • SAP S/4HANA 2022 FPS02 (온프레미스) 및 S/4HANA Cloud Private Edition 2023
  • ABAP Platform 7.58 이상, ADT(Eclipse) 3.34 이상
  • 테스트 회사코드: 1010, 문서 유형: KZ(공급업체 지불), ZP(자동 지불 프로그램)
  • 권한 오브젝트: F_BKPF_BUK(회사코드), F_BKPF_KOA(계정 유형)

왜 BKPF 직접 조회를 그만두어야 하는가

전통적으로 ABAP 개발자는 지불 문서를 조회할 때 BKPF에서 문서 유형(BLART)이 KZ/ZP인 레코드를 뽑고, BSEG 또는 BSAK와 JOIN해 상세 라인을 채우는 방식을 씁니다. 이 방식은 다음 숨은 비용을 남깁니다.

  • 취소 문서 처리 누락: STBLG(취소 원 문서), STJAH(취소 회계연도) 필드를 매번 개발자가 필터링해야 합니다.
  • 권한 필터 부재: 순수 SELECT는 F_BKPF_BUK 권한을 자동 반영하지 않습니다.
  • 필드명 가독성: BUKRS, BELNR, GJAHR 같은 4~5자 코드명은 신입 개발자가 읽기 어렵습니다.
  • 대량 데이터 성능: BSEG 라인 아이템이 수천만 건 이상이라 부주의한 JOIN은 성능 저하로 이어집니다.

SAP VDM은 이런 문제를 흡수하기 위해 물리 테이블 위에 Basic → Composite → Consumption 계층을 쌓습니다. I_PaymentDocument는 Basic Interface View로, BKPF를 기반으로 지불 문서 성격의 필드(청산 여부, 지불 방법, 하우스 뱅크 등)를 확장한 형태입니다.

1단계 — BKPF 직접 조회에서 CDS 뷰로 넘어가기

시나리오: 회사코드 1010에서 2026년 6월에 발행된 지불 문서 목록 조회

" 기존 방식: BKPF 직접 조회 — 취소 문서 필터를 매번 명시해야 함
DATA: lt_bkpf_raw TYPE STANDARD TABLE OF bkpf.

SELECT bukrs, belnr, gjahr, blart, budat, waers, xblnr
  FROM bkpf
  INTO CORRESPONDING FIELDS OF TABLE @lt_bkpf_raw
  WHERE bukrs = '1010'
    AND blart IN ('KZ','ZP')
    AND budat BETWEEN '20260601' AND '20260630'
    AND stblg = ''.   " 취소 원문서 제외를 매번 명시

" CDS 방식: I_PaymentDocument — 취소 문서 내부 필터 포함
DATA lt_payment TYPE STANDARD TABLE OF i_paymentdocument.

SELECT CompanyCode,
       AccountingDocument,
       FiscalYear,
       AccountingDocumentType,
       PostingDate,
       TransactionCurrency,
       DocumentReferenceID
  FROM i_paymentdocument
  INTO CORRESPONDING FIELDS OF TABLE @lt_payment
  WHERE CompanyCode              = '1010'
    AND PostingDate  BETWEEN '20260601' AND '20260630'
    AND AccountingDocumentType IN ('KZ','ZP').

STBLG 필터가 사라진 것이 핵심입니다. VDM 뷰가 내부에서 취소 원문서를 걸러내는 조건을 담고 있기 때문입니다.

2단계 — 실무 시나리오: 거래처별 지불 집계와 예외 처리

CLASS zcl_vendor_payment_report DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF ty_vendor_summary,
             supplier          TYPE lifnr,
             company_code      TYPE bukrs,
             paid_amount       TYPE dmbtr,
             open_amount       TYPE dmbtr,
             currency          TYPE waers,
             document_count    TYPE i,
           END OF ty_vendor_summary.

    METHODS get_vendor_payments
      IMPORTING iv_company_code TYPE bukrs
                iv_date_from    TYPE budat
                iv_date_to      TYPE budat
      RETURNING VALUE(rt_result) TYPE STANDARD TABLE OF ty_vendor_summary
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_vendor_payment_report IMPLEMENTATION.
  METHOD get_vendor_payments.
    TRY.
        SELECT hdr~CompanyCode                AS company_code,
               itm~Supplier                    AS supplier,
               itm~TransactionCurrency         AS currency,
               SUM( CASE WHEN itm~IsCleared = 'X'
                         THEN itm~AmountInCompanyCodeCurrency
                         ELSE 0 END )          AS paid_amount,
               SUM( CASE WHEN itm~IsCleared = ''
                         THEN itm~AmountInCompanyCodeCurrency
                         ELSE 0 END )          AS open_amount,
               COUNT( * )                      AS document_count
          FROM i_paymentdocument AS hdr
          INNER JOIN i_journalentryitem AS itm
            ON  hdr~CompanyCode        = itm~CompanyCode
            AND hdr~AccountingDocument = itm~AccountingDocument
            AND hdr~FiscalYear         = itm~FiscalYear
          WHERE hdr~CompanyCode = @iv_company_code
            AND hdr~PostingDate BETWEEN @iv_date_from AND @iv_date_to
            AND itm~Supplier    <> ''
          GROUP BY hdr~CompanyCode,
                   itm~Supplier,
                   itm~TransactionCurrency
          INTO CORRESPONDING FIELDS OF TABLE @rt_result.

      CATCH cx_sy_open_sql_db INTO DATA(lx_db).
        MESSAGE lx_db->get_text( ) TYPE 'E'.
        RAISE EXCEPTION lx_db.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

IsCleared 플래그로 지불된 금액과 미결 금액을 한 번의 조회로 분리 집계합니다. BKPF/BSEG를 직접 JOIN했다면 AUGBL(청산 문서), AUGDT(청산일)를 별도로 계산해야 했겠지만, VDM 뷰는 청산 상태를 정규화된 컬럼으로 노출합니다.

3단계 — 프로덕션 관점: 소비 뷰와 단위 테스트

@AbapCatalog.sqlViewName: 'ZVPAYSUM'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Payment Summary by Vendor'
@VDM.viewType: #CONSUMPTION
define view entity Z_C_PaymentSummary
  as select from I_PaymentDocument as Payment
  association [0..*] to I_JournalEntryItem as _Items
    on  $projection.CompanyCode       = _Items.CompanyCode
    and $projection.AccountingDocument = _Items.AccountingDocument
    and $projection.FiscalYear         = _Items.FiscalYear
{
  key Payment.CompanyCode,
  key Payment.AccountingDocument,
  key Payment.FiscalYear,
      Payment.PostingDate,
      Payment.AccountingDocumentType,
      Payment.DocumentReferenceID,
      Payment.TransactionCurrency,
      _Items
}

@AccessControl.authorizationCheck: #CHECK를 지정하면 DCL로 정의된 권한 규칙이 자동 적용됩니다. 재무 도메인에서는 반드시 활성화하세요.

CLASS ltcl_payment_report DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.

  PRIVATE SECTION.
    DATA mo_cut     TYPE REF TO zcl_vendor_payment_report.
    DATA mo_sql_env TYPE REF TO if_osql_test_environment.

    METHODS setup.
    METHODS teardown.
    METHODS aggregate_paid_and_open FOR TESTING.
ENDCLASS.

CLASS ltcl_payment_report IMPLEMENTATION.
  METHOD setup.
    mo_sql_env = cl_osql_test_environment=>create(
      i_dependency_list = VALUE #(
        ( 'I_PAYMENTDOCUMENT' )
        ( 'I_JOURNALENTRYITEM' ) ) ).
    mo_cut = NEW zcl_vendor_payment_report( ).
  ENDMETHOD.

  METHOD teardown.
    mo_sql_env->clear_doubles( ).
  ENDMETHOD.

  METHOD aggregate_paid_and_open.
    DATA lt_payment TYPE STANDARD TABLE OF i_paymentdocument.
    mo_sql_env->insert_test_data( lt_payment ).
    DATA(lt_result) = mo_cut->get_vendor_payments(
      iv_company_code = '1010'
      iv_date_from    = '20260601'
      iv_date_to      = '20260630' ).
    cl_abap_unit_assert=>assert_not_initial( lt_result ).
  ENDMETHOD.
ENDCLASS.

자주 부딪히는 함정과 해결

Q1. I_PaymentDocument에 데이터가 나오지 않습니다.
A. 회계 문서 유형이 지불 성격이어야 노출됩니다. OBA7에서 문서 유형의 계정 유형 허용 설정을 확인하고, 릴리스 상태와 클라우드 화이트리스트를 점검하세요.

Q2. 필드가 BKPF와 이름이 달라서 매핑이 헷갈립니다.
A. 대표 매핑: BUKRS→CompanyCode, BELNR→AccountingDocument, GJAHR→FiscalYear, BUDAT→PostingDate, BLART→AccountingDocumentType. ADT F2로 빠르게 확인 가능합니다.

Q3. 성능이 오히려 느려졌습니다.
A. PostingDate, CompanyCode를 반드시 WHERE에 포함하고, SELECT * 대신 필요한 컬럼만 명시하세요. ST05로 실행 계획을 확인하는 습관을 들이세요.

Q4. 커스텀 필드를 추가하고 싶습니다.
A. 표준 I_PaymentDocument를 직접 수정하지 말고 extend view 또는 별도 Composite View를 만들어 결합하는 방식을 권장합니다.

이어서 살펴볼 만한 영역

이 글이 다룬 I_PaymentDocument는 FI VDM의 입구입니다. 이어서 I_JournalEntryItem, I_APDueItem, I_PaymentProposal 계열 뷰를 함께 살펴보면 재무 데이터 흐름 전체를 CDS 관점에서 재구성할 수 있습니다.

댓글 0

아직 댓글이 없습니다.