이 글에서 다루는 범위
S/4HANA에서 고객(Customer, KNA1)과 공급업체(Vendor, LFA1)는 더 이상 별개의 마스터 데이터가 아닙니다. 비즈니스 파트너(Business Partner, BP)라는 단일 엔티티로 통합되었고, 모든 외부 거래 주체는 BUT000 테이블을 중심으로 관리됩니다. 이 글에서는 I_BusinessPartner CDS 뷰가 어떻게 BUT000을 표면화하고, 고객·공급업체·일회성 파트너를 하나의 데이터 모델로 묶어내는지 살펴봅니다.
- BP 통합 모델과 BUT000 테이블 구조 이해
I_BusinessPartner의 필드 매핑과 어소시에이션 파악- 고객/공급업체 분리 조회 및 통합 조회 패턴 실습
- RAP 기반 소비 뷰에서의 활용과 권한 처리
- 대용량 BP 데이터 처리 시 성능 고려사항
읽기 전 알아두면 좋은 것들
이 글은 ABAP CDS 뷰 정의 문법(define view, association, annotation)에 대한 기본적인 이해가 있다는 전제로 진행됩니다. 또한 SAP ERP 시절 KNA1/LFA1 구조를 한 번이라도 다뤄봤다면 BP 통합의 의미가 더 잘 와닿을 것입니다. ADT(ABAP Development Tools) 설치, 가상 데이터 모델(VDM) 계층 구분(I_, C_, R_ 접두어), 그리고 SAP GUI에서 트랜잭션 BP를 한 번쯤 열어본 경험이 있으면 충분합니다.
실습 환경 및 시스템 전제
다음 환경을 기준으로 코드를 검증했습니다. 버전이 다를 경우 일부 필드 또는 어소시에이션 이름이 다를 수 있으므로, ADT의 Where-Used List로 실제 시스템의 뷰 정의를 먼저 확인하는 것을 권장합니다.
- SAP S/4HANA 2023 (FPS02 이상) 또는 BTP ABAP Environment 2402 릴리스
- ABAP Development Tools for Eclipse 3.40 이상
- 권한:
S_RS_AUTH,S_DEVELOP(개발 시), BP 조회용B_BUPA_GRP - 샘플 데이터: 트랜잭션
BP로 최소 3건의 BP(역할 FLCU00, FLVN00 각각) 사전 등록 - 패키지: 사용자 네임스페이스(예:
Z_BP_DEMO) 생성
BTP ABAP Environment(Steampunk)에서는 RAP 기반으로만 접근하므로 SE11 같은 클래식 트랜잭션이 비활성화되어 있습니다. 온프레미스 S/4HANA라면 SE16N에서 BUT000을 직접 열어 데이터 형태를 먼저 눈으로 확인하면 모델 이해가 빨라집니다.
BP 통합 모델의 동작 원리
비즈니스 파트너 모델은 "한 명의 거래 상대방은 여러 역할(Role)을 가질 수 있다"는 전제에서 출발합니다. 같은 회사가 우리에게 물건을 파는 공급업체이자 동시에 우리 제품을 사가는 고객일 수 있죠. ERP 시절에는 이 경우 KNA1과 LFA1에 각각 별도 마스터를 등록해야 했고, 이름·주소가 어긋나는 데이터 정합성 문제가 빈번했습니다.
S/4HANA의 BP 모델은 다음과 같이 계층화되어 있습니다.
BUT000 (중앙 BP 마스터) → BUT020/ADRC (주소) → BUT100 (역할) → KNA1/LFA1 (FI 통합용 그림자 테이블) → KNB1/LFB1 (회사코드별 확장)
핵심 키는 PARTNER(BP 번호, 10자리)이며, 이 키 하나로 위 모든 테이블을 조인할 수 있습니다. KNA1·LFA1은 이제 마스터의 사본 역할만 하며, BP가 변경되면 CVI(Customer-Vendor Integration) 동기화 메커니즘이 자동으로 그림자 테이블을 갱신합니다.
I_BusinessPartner는 이 BUT000을 가상 데이터 모델의 Interface View(I_ 계층)로 노출한 뷰입니다. 비유하자면, BUT000이 원재료 창고라면 I_BusinessPartner는 가공된 식자재 박스입니다. 박스 안에는 BP 번호, 카테고리(개인/조직/그룹), 분류, 생성자, 변경자 같은 표준 필드가 정리되어 있고, 주소(_DefaultAddress), 역할(_Role), 은행 정보(_BankDetails) 같은 어소시에이션이 연결되어 있어 SELECT 한 번으로 관련 데이터를 따라갈 수 있습니다.
BP 카테고리(BusinessPartnerCategory)는 다음 세 가지로 나뉩니다.
- 1 = Person: 개인 (이름/성 분리)
- 2 = Organization: 법인/회사
- 3 = Group: 그룹 (예: 부부 공동명의)
또한 그룹핑(BusinessPartnerGrouping)은 BP 번호 채번 규칙을 결정합니다. 예를 들어 BP01은 내부 채번, BPEX는 외부 채번 식으로 IMG에서 정의됩니다.
1단계: 기본 조회 예제
가장 단순한 형태로 BP 마스터를 조회하는 CDS 소비 뷰를 만들어 봅니다. 시나리오는 "최근 30일 내 생성된 조직 BP를 화면에 표시"입니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Recent Organization Partners'
@Metadata.allowExtensions: true
define view entity ZC_RecentOrgPartner
as select from I_BusinessPartner as bp
{
key bp.BusinessPartner as PartnerId,
bp.BusinessPartnerFullName as DisplayName,
bp.BusinessPartnerCategory as Category,
bp.BusinessPartnerGrouping as Grouping,
bp.CreationDate as CreatedOn,
bp.CreatedByUser as CreatedBy,
bp.BusinessPartnerIsBlocked as IsBlocked
}
where bp.BusinessPartnerCategory = '2'
and bp.CreationDate >= $session.system_date - 30;
이 정도면 Fiori Elements List Report에 바로 붙일 수 있습니다. $session.system_date는 현재 시스템 날짜를 반환하는 세션 변수로, 하드코딩된 날짜 대신 사용해야 캐싱이 효율적입니다.
2단계: 고객·공급업체 통합 조회 시나리오
실무에서 자주 마주치는 요구사항은 "BP 하나에 매겨진 고객 번호와 공급업체 번호를 한 줄에 같이 보여달라"는 것입니다. I_BusinessPartner 자체에는 KUNNR/LIFNR이 없으므로, I_Customer와 I_Supplier를 LEFT OUTER JOIN으로 결합해야 합니다.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Unified Partner 360 View'
define view entity ZC_Partner360View
as select from I_BusinessPartner as bp
left outer to one join I_Customer as cust
on cust.Customer = bp.BusinessPartner
left outer to one join I_Supplier as supp
on supp.Supplier = bp.BusinessPartner
association [0..1] to I_Country as _Country
on $projection.CountryCode = _Country.Country
{
key bp.BusinessPartner as PartnerId,
bp.BusinessPartnerFullName as PartnerName,
bp.BusinessPartnerCategory as Category,
// 고객 측 정보
cust.Customer as CustomerNumber,
cust.CustomerAccountGroup as CustomerGroup,
// 공급업체 측 정보
supp.Supplier as SupplierNumber,
supp.SupplierAccountGroup as SupplierGroup,
// 통합 플래그 (양쪽 다 있으면 'X')
case
when cust.Customer is not initial
and supp.Supplier is not initial
then 'X'
else ''
end as IsBothRoles,
bp.OrganizationFoundationDate as FoundedOn,
cast('' as land1) as CountryCode,
_Country
}
where bp.BusinessPartnerCategory = '2';
여기서 주의할 점은 to one 카디널리티 표기입니다. 동일 BP에 대해 KNA1/LFA1 레코드가 1건이라는 보장이 있어야 결과 행 수가 부풀려지지 않습니다. CVI 동기화가 정상이라면 1:1이 유지되지만, 마이그레이션 직후나 다중 판매조직 환경에서는 1:N이 될 수 있으니 운영 시스템 데이터로 검증이 필요합니다.
로깅·예외 처리는 ABAP 클래스 레이어에서 다음과 같이 처리하는 것이 일반적입니다.
METHOD fetch_partner_360.
TRY.
SELECT FROM zc_partner360view
FIELDS partnerid, partnername,
customernumber, suppliernumber, isbothroles
WHERE partnerid IN @it_partner_range
INTO TABLE @DATA(lt_result).
IF lt_result IS INITIAL.
MESSAGE i013(zbp_msg) WITH 'No partners matched' INTO DATA(lv_msg).
log->add_message( iv_severity = 'I' iv_text = lv_msg ).
RETURN.
ENDIF.
rt_partners = lt_result.
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
log->add_exception( ix_exc = lx_db iv_severity = 'E' ).
RAISE EXCEPTION TYPE zcx_bp_fetch_failed
EXPORTING previous = lx_db.
ENDTRY.
ENDMETHOD.
3단계: 프로덕션 적용 시 고려사항
운영 환경에서는 권한, 성능, 단위 테스트가 동시에 잡혀야 합니다. 다음은 PFCG 권한 객체와 연동되는 DCL(Data Control Language) 예시입니다.
@MappingRole: true
define role ZC_PARTNER360_ROLE {
grant select on ZC_Partner360View
where (CustomerGroup) = aspect pfcg_auth( B_BUPA_GRP, BRGRU, ACTVT = '03' )
or (SupplierGroup) = aspect pfcg_auth( B_BUPA_GRP, BRGRU, ACTVT = '03' );
}
성능 측면에서는 다음 세 가지를 권장합니다.
- 필드 화이트리스트:
SELECT *대신 필요한 컬럼만 명시 (HANA 컬럼 스토어 최적화) - BUT000 직접 접근 금지: 항상
I_BusinessPartner를 경유하여 권한·확장 필드 자동 반영 - Pagination: 화면 표시 시
UP TO n ROWS또는 OData$top/$skip활용
ABAP Unit 테스트는 CDS Test Double Framework(cl_cds_test_environment)로 작성합니다.
CLASS ltc_partner360 DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
CLASS-DATA env TYPE REF TO if_cds_test_environment.
CLASS-METHODS class_setup.
METHODS both_roles_flag_test FOR TESTING.
ENDCLASS.
CLASS ltc_partner360 IMPLEMENTATION.
METHOD class_setup.
env = cl_cds_test_environment=>create(
i_for_entity = 'ZC_PARTNER360VIEW' ).
ENDMETHOD.
METHOD both_roles_flag_test.
env->clear_doubles( ).
env->insert_test_data( VALUE i_businesspartner_t(
( businesspartner = '1000000001'
businesspartnercategory = '2' ) ) ).
SELECT SINGLE isbothroles FROM zc_partner360view
WHERE partnerid = '1000000001' INTO @DATA(lv_flag).
cl_abap_unit_assert=>assert_equals(
exp = 'X' act = lv_flag
msg = 'Both-role flag must be set when cust+supp exist' ).
ENDMETHOD.
ENDCLASS.
자주 마주치는 함정과 해결책
Q1. I_BusinessPartner에 KUNNR/LIFNR이 없어서 곤란합니다.
의도된 설계입니다. BP는 KUNNR과 1:1이 아닐 수 있기 때문에(같은 BP가 여러 판매조직에서 다른 KUNNR을 가질 수도 있음) 별도 뷰(I_Customer, I_Supplier)로 분리되어 있습니다. 위 2단계처럼 LEFT JOIN으로 가져오는 게 표준 패턴입니다.
Q2. CVI 동기화 오류로 BUT000과 KNA1의 BP명이 다릅니다.
트랜잭션 MDS_LOAD_COCKPIT으로 동기화 상태를 점검하고, FINS_CVI_MAP 또는 관련 SAP Note를 확인하세요. 임시방편으로는 BUT000을 단일 진실 공급원(SSoT)으로 신뢰하고, FI 모듈만 KNA1을 참조하도록 분리하는 것이 일반적입니다.
Q3. BusinessPartnerFullName이 비어 있습니다.
이 필드는 BP 카테고리에 따라 채워지는 출처가 다릅니다. 개인(1)은 FirstName + LastName, 조직(2)은 OrganizationBPName1~4의 조합입니다. 마이그레이션 데이터에서 조직 BP의 NAME_ORG1이 비어 있으면 풀네임도 빈 값이 됩니다. SELECT * FROM but000 WHERE name_org1 = ''로 후보를 찾아 일괄 보정하세요.
Q4. 성능이 갑자기 나빠졌습니다.
주소 어소시에이션(_DefaultAddress)이 ADRC를 추가 조인하면서 카티전 곱이 발생하는 경우가 많습니다. SAT(런타임 분석) 또는 PlanViz로 실제 실행 계획을 확인하고, 주소가 필요 없다면 어소시에이션을 SELECT 절에서 제거하세요. 또한 BusinessPartnerIsBlocked 필터는 인덱스를 안 타니, BP 번호 범위 필터를 먼저 적용하는 것이 좋습니다.
이어서 살펴볼 만한 주제
BP 모델을 충분히 이해했다면 RAP(Restful ABAP Programming Model)의 트랜잭션 뷰를 학습하여 BP 생성/수정 기능을 직접 구현해보는 것을 권장합니다. 또한 BP 확장 필드(Custom Fields)를 Key User Extensibility로 추가하는 방법, BP 출력 양식(Adobe Forms by Adobe), 그리고 OData 서비스 API_BUSINESS_PARTNER를 통한 외부 시스템 연동도 자연스러운 확장 경로입니다. MDG(Master Data Governance) 연계까지 가면 데이터 거버넌스 영역으로 시야가 넓어집니다.
더 깊이 알아볼 수 있는 자료
- SAP Help Portal — Business Partner (S/4HANA Cross Components)
- SAP Help Portal — Customer-Vendor Integration (CVI) Configuration
- SAP Help Portal — ABAP CDS Views Reference
- SAP API Business Hub — API_BUSINESS_PARTNER
- SAP Community — ABAP CDS Views Topic Page
- SAP Blogs — Business Partner Modeling Best Practices
댓글 0
아직 댓글이 없습니다.