ABAP

VBRK 직접 조회 그만 — FI 전기 CDS 뷰 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요

I_BillingDocument는 SAP S/4HANA의 표준 인터페이스 뷰(Interface View)로, 전통적인 SD 모듈의 청구 문서 헤더 테이블 VBRK를 가상 데이터 모델(VDM, Virtual Data Model)로 추상화한 핵심 CDS 뷰입니다. 이 글에서는 VBRK의 원시 컬럼이 어떻게 의미 있는 비즈니스 필드명으로 매핑되는지, 그리고 청구 문서가 SD-FI 통합 흐름에서 어떻게 회계 문서(BKPF/BSEG)와 연결되는지 단계별로 살펴봅니다. 매출 인식(Revenue Recognition), 미결제 청구서 분석, FI 전기 상태 모니터링 같은 실무 시나리오를 ABAP CDS 쿼리로 구현하는 패턴까지 다룹니다.

  • I_BillingDocument의 어노테이션 구조와 VBRK 컬럼 매핑 이해
  • 청구 문서 헤더(VBRK)와 라인 아이템(VBRP) 결합 패턴
  • FI Posting Status(RFBSK) 기반 청구 미반영 건 추출
  • I_BillingDocumentItem, I_AccountingDocument 조합으로 매출-회계 추적

배경과 동작 원리

SAP ERP 시절 청구 문서는 VBRK(헤더)와 VBRP(아이템) 두 테이블로 관리되었고, 개발자는 직접 SELECT문으로 컬럼을 가져와 가공해야 했습니다. 컬럼명이 VBELN, FKART, NETWR처럼 독일어 약자라 가독성이 낮고, 모듈 간 통합 로직(특히 FI 연계)을 매번 직접 작성해야 했습니다. S/4HANA에서 도입된 VDM은 이 문제를 세 가지 레이어로 해결합니다.

  • Basic View(I_*): VBRK/VBRP를 1:1로 추상화하면서 컬럼명을 BillingDocument, BillingDocumentType, TotalNetAmount처럼 명시적으로 변경
  • Composite View(C_*): 여러 Basic View를 결합해 분석 시나리오 제공
  • Consumption View: Fiori 앱이나 OData 서비스가 직접 소비

I_BillingDocument는 가장 하위 레이어인 Basic Interface View이며, 그 정의 안에는 VBRK가 단일 데이터 소스로 선언되어 있습니다. 다만 단순 1:1 매핑이 아니라, AccountingPostingStatus처럼 SD-FI 통합 의미를 갖는 코드값을 그대로 노출해 상위 뷰들이 회계 연계 로직을 일관되게 사용하도록 설계되어 있습니다. 청구 문서가 FI로 전기되면 VBRK-RFBSK 필드가 'C'(Posted)로 바뀌고, BKPF의 AWKEY 필드에 청구 문서 번호가 키로 들어가는 구조라, 이 흐름을 이해해야 매출 인식 자동화 로직을 설계할 수 있습니다.

핵심 구조

I_BillingDocument의 주요 필드를 VBRK 원본 컬럼과 매핑하면 다음과 같이 정리할 수 있습니다.

CDS 필드VBRK 컬럼의미
BillingDocumentVBELN청구 문서 번호 (Key)
BillingDocumentTypeFKART청구 유형 (F2, G2, L2 등)
BillingDocumentCategoryVBTYP문서 카테고리
BillingDocumentDateFKDAT청구일
AccountingPostingStatusRFBSKFI 전기 상태
TotalNetAmountNETWR순 금액
TransactionCurrencyWAERK거래 통화
SoldToPartyKUNAG주문 고객
CompanyCodeBUKRS회사 코드

이 매핑 표는 단순히 컬럼 이름이 바뀐 것 이상의 의미를 갖습니다. 예를 들어 AccountingPostingStatus는 도메인 값 'A'(Not yet processed), 'B'(Posting document not created), 'C'(Posting document has been created), 'D'(Posting document not required)를 가지며, 이 값이 'C'가 아닌 청구 문서는 매출이 아직 손익계산서에 반영되지 않은 상태로 해석됩니다. 월말 마감 분석에서 가장 중요한 KPI 중 하나가 RFBSK='A' 건수입니다.

실전 코드 예제

먼저 가장 기본적인 형태로, 특정 회사 코드와 청구 기간에 대한 청구 문서 목록을 조회하는 사용자 정의 CDS View를 살펴봅니다.

@AbapCatalog.sqlViewName: 'ZBILLOVRV01'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Billing Document Overview'
define view ZC_BillingOverview
  as select from I_BillingDocument as bill
{
  key bill.BillingDocument,
      bill.BillingDocumentType,
      bill.BillingDocumentDate,
      bill.CompanyCode,
      bill.SoldToParty,
      bill.AccountingPostingStatus,
      bill.TotalNetAmount,
      bill.TransactionCurrency,
      case bill.AccountingPostingStatus
        when 'C' then 'Posted to FI'
        when 'A' then 'Pending Posting'
        when 'B' then 'Posting Failed'
        else 'No Posting Required'
      end as PostingStatusText
}
where bill.CompanyCode      = '1010'
  and bill.BillingDocumentDate >= '20260101'

이 단계에서 핵심은 I_BillingDocument를 source로 선언했다는 점입니다. VBRK를 직접 참조하지 않아도 동일한 데이터를 안전하게 가져오며, 향후 SAP가 내부 테이블 구조를 바꿔도 인터페이스가 보호됩니다.

두 번째 단계에서는 라인 아이템(I_BillingDocumentItem)과 결합해 품목별 매출 분석을 수행합니다. 청구 문서 한 건이 여러 자재를 포함하므로, 헤더 금액이 아니라 아이템 단위의 NetAmount를 사용해야 정확한 매출 기여도를 계산할 수 있습니다.

@AbapCatalog.sqlViewName: 'ZBILLITM01'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Billing Item Revenue Detail'
define view ZC_BillingItemRevenue
  as select from I_BillingDocumentItem as item
  association [1..1] to I_BillingDocument as _Header
    on $projection.BillingDocument = _Header.BillingDocument
{
  key item.BillingDocument,
  key item.BillingDocumentItem,
      _Header.BillingDocumentDate,
      _Header.SoldToParty,
      _Header.AccountingPostingStatus,
      item.Material,
      item.BillingQuantity,
      item.BillingQuantityUnit,
      item.NetAmount,
      item.TransactionCurrency,
      case when item.BillingQuantity <> 0
           then division( item.NetAmount, item.BillingQuantity, 2 )
           else cast( 0 as abap.dec( 15, 2 ) )
      end as UnitPrice,
      _Header
}
where _Header.AccountingPostingStatus = 'C'

association을 활용한 점에 주목해야 합니다. JOIN 대신 association을 쓰면 상위 뷰에서 _Header.SalesOrganization 같은 경로식(path expression)으로 자유롭게 접근할 수 있고, 옵티마이저가 실제 필요한 컬럼만 fetch하도록 plan을 짭니다.

세 번째 단계는 실무에서 가장 흔한 시나리오인 SD-FI 연계 추적입니다. 청구 문서에서 회계 문서로 어떻게 이어지는지 BKPF(I_JournalEntry)와 결합해 보여줍니다.

@AbapCatalog.sqlViewName: 'ZBILLFI01'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Billing to FI Posting Trace'
define view ZC_BillingToFiTrace
  as select from I_BillingDocument as bill
  left outer join I_JournalEntry as je
    on  je.OriginalReferenceDocument     = bill.BillingDocument
    and je.OriginalReferenceDocumentType = 'VBRK'
    and je.CompanyCode                   = bill.CompanyCode
{
  key bill.BillingDocument,
      bill.BillingDocumentType,
      bill.BillingDocumentDate,
      bill.CompanyCode,
      bill.AccountingPostingStatus,
      bill.TotalNetAmount,
      je.AccountingDocument,
      je.FiscalYear,
      je.PostingDate,
      je.AccountingDocumentType,
      case when je.PostingDate is not initial
           then dats_days_between( bill.BillingDocumentDate, je.PostingDate )
           else cast( 0 as abap.int4 )
      end as PostingDelayDays
}
where bill.BillingDocumentDate >= '20260101'

이 쿼리의 핵심은 BKPF의 AWKEY 필드가 청구 문서 번호를 그대로 포함한다는 SD-FI 통합 규칙을 활용한 것입니다. I_JournalEntry는 BKPF/BSEG를 추상화한 뷰로, OriginalReferenceDocument가 BKPF-AWKEY에 해당합니다. AWTYP='VBRK'로 필터링하면 청구로 인해 생성된 회계 문서만 정확히 가져올 수 있습니다.

심화 활용

실무에서는 단순 조회를 넘어 복합 KPI 산출이 자주 필요합니다. 예를 들어 "지난 30일간 청구된 매출 중 FI로 전기되지 않은 비율"을 계산하는 분석 뷰는 다음과 같이 작성할 수 있습니다.

@AbapCatalog.sqlViewName: 'ZBILLKPI01'
@Analytics.dataCategory: #CUBE
@EndUserText.label: 'Unposted Billing KPI'
define view ZC_UnpostedBillingKpi
  as select from I_BillingDocument
{
  key CompanyCode,
  key SalesOrganization,

      @DefaultAggregation: #SUM
      TotalNetAmount as TotalBilledAmount,

      @DefaultAggregation: #SUM
      case when AccountingPostingStatus <> 'C'
           then TotalNetAmount
           else cast( 0 as abap.curr( 15, 2 ) )
      end as UnpostedAmount,

      @DefaultAggregation: #SUM
      case when AccountingPostingStatus <> 'C'
           then 1
           else 0
      end as UnpostedCount,

      TransactionCurrency
}
where BillingDocumentDate >= add_days( $session.system_date, -30 )
group by CompanyCode, SalesOrganization, TransactionCurrency

가격 조건(VK11/VK12로 유지되는 KONV/PRCD_ELEMENTS)이 청구 시점에 어떻게 반영되었는지 추적하려면 I_BillingDocumentItemPrcgElmnt를 함께 사용합니다. 이 뷰는 조건 유형(ConditionType)별로 할인, 부가세, 운임 같은 항목을 분리해 보여주므로 매출 분해 분석에 유용합니다. 청구 유형(BillingDocumentType)별 필터링은 보통 'F2'(인보이스), 'G2'(크레딧 메모), 'L2'(데빗 메모), 'S1'(취소 인보이스) 같은 코드로 수행하며, 매출 인식 시 취소분은 별도 처리해야 하므로 다음과 같은 패턴이 자주 등장합니다.

where BillingDocumentType in ( 'F2', 'G2', 'L2' )
  and CancelledBillingDocument is initial

트러블슈팅

현장에서 자주 마주치는 문제와 대응 방향을 정리하면 다음과 같습니다.

문제 1: AccountingPostingStatus가 'C'인데 BKPF에 매칭되는 회계 문서가 없다. 이 경우 청구 문서가 FI 전기 완료 후 회계 문서가 별도로 storno 처리된 사례일 가능성이 높습니다. I_JournalEntry의 ReversalReferenceDocument를 함께 확인해 역분개 흐름을 추적해야 합니다.

문제 2: I_BillingDocument 조회가 비정상적으로 느리다. WHERE 절에 BillingDocument(VBELN)나 CompanyCode 같은 인덱스 필드가 없는 경우, VBRK 전체 스캔이 발생합니다. SAP HANA의 컬럼 스토어 특성상 일반적으로 빠르지만, 수억 건 규모에서는 BillingDocumentDate 범위 조건을 반드시 함께 걸어 파티션 프루닝을 유도하는 것이 권장됩니다.

문제 3: TotalNetAmount가 통화 단위와 함께 표시되지 않는다. CDS에서 금액 필드는 @Semantics.amount.currencyCode 어노테이션으로 통화 필드와 연결해야 Fiori UI나 분석 도구가 정상적으로 통화 변환을 처리합니다. 사용자 정의 뷰에서 금액을 case문으로 가공할 때 이 어노테이션을 빠뜨리는 것이 흔한 실수입니다.

FAQ - I_BillingDocument와 I_BillingDocumentBasic의 차이는? Basic은 라이센스/노출 범위가 더 제한적이며 핵심 키 필드 위주로 구성됩니다. 일반적으로 사용자 정의 확장에서는 I_BillingDocument를 사용하는 것이 권장됩니다.

FAQ - VBRK를 직접 SELECT해도 되지 않나? 가능은 하지만, S/4HANA에서는 권한 체크(DCL), 시간 의존 데이터(Time-Dependent), 통화 변환이 CDS 레이어에서 통합 관리되므로 VBRK 직접 접근은 장기적으로 유지보수 비용을 높입니다.

FAQ - 청구 문서 취소는 어떻게 식별하나? CancelledBillingDocument 필드가 비어있지 않으면 취소된 문서이고, 동시에 BillingDocumentIsCancelled 플래그도 함께 확인하면 안전합니다.

성능과 모범 사례

대량 청구 데이터를 다룰 때 일반적으로 권장되는 패턴은 다음과 같습니다. 첫째, 사용자 정의 뷰에서 SELECT *를 피하고 실제 필요한 컬럼만 명시합니다. HANA 컬럼 스토어는 참조한 컬럼만 메모리에 적재하므로 폭이 넓은 VBRK에서는 효과가 큽니다. 둘째, BillingDocumentDate 범위 조건을 가능한 한 좁게 잡아 파티션 프루닝을 활성화합니다. 셋째, 조인 대신 association을 우선 사용해 옵티마이저가 path expression 평가를 지연시킬 수 있도록 합니다.

보안 관점에서는 DCL(Data Control Language) 역할을 정의해 회사 코드, 영업 조직 단위로 접근 권한을 제어하는 것이 일반적입니다. I_BillingDocument에는 이미 표준 DCL이 적용되어 있어 @AccessControl.authorizationCheck: #CHECK를 명시하면 자동으로 권한 객체 V_VBRK_FKA 등이 활성화됩니다.

분석 시나리오에서는 @Analytics.dataCategory: #CUBE 또는 #DIMENSION 어노테이션으로 뷰의 역할을 명확히 선언하고, 집계 가능한 필드에는 @DefaultAggregation 어노테이션을 부여해 SAP Analytics Cloud 같은 도구가 적절히 처리하도록 유도하는 것이 권장됩니다.

마무리

I_BillingDocument는 단순히 VBRK의 이름만 바꾼 뷰가 아니라, SD-FI 통합 의미와 권한, 통화 처리, 분석 메타데이터까지 캡슐화한 비즈니스 인터페이스입니다. 이 뷰를 출발점으로 I_BillingDocumentItem, I_BillingDocumentItemPrcgElmnt, I_JournalEntry를 결합하면 매출 인식, 미결제 청구 분석, 가격 조건 추적 같은 핵심 재무 시나리오를 표준화된 방식으로 구현할 수 있습니다. 후속으로는 RAP(ABAP RESTful Application Programming Model)에서 I_BillingDocument를 Behavior와 결합해 OData 서비스로 노출하거나, Embedded Analytics에서 Query View로 확장하는 패턴을 살펴보면 청구 데이터를 활용한 분석 자산을 더 견고하게 만들 수 있습니다. SAP S/4HANA 2022 이후로는 클라우드 에디션에서도 동일한 VDM 구조가 유지되므로, 한 번 익혀 둔 패턴은 온프레미스와 클라우드 모두에서 일반적으로 재사용할 수 있습니다.

댓글 0

아직 댓글이 없습니다.