개요 및 핵심 체크포인트
SAP S/4HANA에서 컨트롤링(CO) 모듈을 다룰 때 가장 빈번하게 마주치는 마스터 데이터 중 하나가 바로 원가 요소(Cost Element)입니다. 전통적인 ECC 시절에는 CSKA/CSKB/CSKS 같은 별도 테이블로 관리되던 원가 요소가, S/4HANA에서는 GL 계정과 통합되면서 I_CostElement CDS 뷰를 통해 표준화된 인터페이스로 노출됩니다. 이 글에서는 CSKA 테이블의 본질부터 시작해서, I_CostElement 뷰의 내부 구조, Primary와 Secondary 원가 요소의 차이, 그리고 ABAP 코드에서 실전 조회 패턴까지 단계적으로 정리합니다.
- CSKA가 어떤 데이터를 담는 테이블인지 컨트롤링 영역과의 관계로 이해
- I_CostElement CDS 뷰의 주요 필드와 association 구조 파악
- CostElementCategory(원가 요소 카테고리) 코드값별 실무 의미 분류
- Primary vs Secondary 원가 요소를 GL 계정 관점에서 구분
- SELECT/JOIN/WHERE 절에서 I_CostElement를 안전하게 활용하는 패턴 습득
- 제조원가·인건비·감가상각 시나리오에서 자주 발생하는 실수 회피
이 글을 읽기 전에 갖춰두면 좋은 배경
SAP CO 모듈의 기본 개념(Controlling Area, Cost Center, Internal Order)을 알고 있다는 전제하에 진행합니다. ABAP CDS 문법(@AbapCatalog, association, define view)에 한 번이라도 노출된 경험이 있으면 코드를 읽는 속도가 빨라집니다. 또한 GL 계정 마스터(SKA1/SKB1)와 S/4HANA에서의 GL-CostElement 통합(Universal Journal, ACDOCA) 흐름을 어렴풋이라도 알고 있다면 Primary/Secondary 구분이 한층 명확하게 와닿습니다.
실행 환경 및 필요한 권한 사전 점검
이 글의 예제 코드는 SAP S/4HANA 2022 (FPS02) 이상 또는 S/4HANA Cloud Public Edition 2308 이후 릴리스를 기준으로 작성되었습니다. ECC 6.0 EHP8 환경에는 I_CostElement 뷰가 존재하지 않으니, 동일 데이터를 다뤄야 할 경우 CSKA/CSKB를 직접 SELECT 해야 합니다. 개발 도구로는 ABAP Development Tools(ADT) for Eclipse 2024-06 이상을 권장합니다. 트랜잭션 권한으로는 S_DEVELOP(개발 객체), S_RS_AUTH(분석 뷰 접근), 그리고 CO 데이터 조회를 위한 K_CSKS/K_KOKR(컨트롤링 영역) 권한 객체가 필요할 수 있습니다. CDS 뷰 자체는 일반적으로 SE16/SE16N 대신 ADT의 Data Preview(F8) 기능으로 확인합니다.
또한 CDS_TEST_DOUBLE_FRAMEWORK 사용을 위해 ABAP Unit 권한, 그리고 운영계 배포 시 Authorization Default for CDS Entity(S_DATASET의 행 단위 권한과는 별개) 설정을 함께 검토해야 합니다.
CSKA 테이블과 I_CostElement의 본질 이해
먼저 CSKA가 무엇인지부터 명확히 짚고 갑시다. CSKA는 Cost Elements (Data Dependent on Chart of Accounts)로, 차트 오브 어카운트(Chart of Accounts) 단위로 관리되는 원가 요소 마스터 데이터입니다. 키 필드는 KTOPL(차트 오브 어카운트)과 KSTAR(원가 요소 번호)입니다. 반면 CSKB는 컨트롤링 영역과 유효 기간별로 관리되는 데이터를 담는 테이블로, 키가 KOKRS(Controlling Area) + KSTAR + DATBI(유효 종료일)입니다.
비유하자면 CSKA는 "원가 요소의 본적지(차트 오브 어카운트 레벨의 호적)"이고, CSKB는 "원가 요소의 거주지 변동 이력(컨트롤링 영역별 시간 슬라이스)"입니다. S/4HANA로 넘어오면서 가장 큰 변화는, Primary 원가 요소가 더 이상 별도 마스터로 신규 생성되지 않고 GL 계정 마스터(SKA1/SKB1)의 속성으로 흡수되었다는 점입니다. 즉 GL 계정을 만들면서 "이 계정의 종류(GL Account Type)"를 'Primary Costs or Revenue'로 지정하면 그 자체가 Primary 원가 요소가 됩니다. Secondary 원가 요소는 여전히 컨트롤링 내부 배부(Assessment, Distribution, Activity Allocation)용으로 'Secondary Costs' 타입의 GL 계정으로 생성됩니다.
I_CostElement는 이러한 통합 모델 위에서 CSKA/CSKB/SKA1/SKB1을 종합적으로 노출하는 Basic Interface View입니다. 명명 규칙 I_ 접두어는 SAP Virtual Data Model(VDM)에서 Interface View(기본 뷰 계층)를 의미하며, 그 위에 Composite View(C_) 또는 Consumption View(C_)가 쌓이는 구조입니다.
원가 요소 카테고리(CostElementCategory)는 원가 요소의 사용 목적을 분류합니다. 실무에서 자주 쓰이는 값은 다음과 같습니다.
- 01 - Primary costs/cost-reducing revenues (재료비, 인건비 등 일반 비용)
- 03 - Accrual/deferral via percentage method
- 04 - Accrual/deferral via target=actual
- 11 - Revenues (수익)
- 12 - Sales deductions (매출 차감)
- 21 - Internal settlement (내부 결산)
- 22 - External settlement (외부 결산, 오더에서 자산/프로젝트로)
- 31 - Order/project results analysis
- 41 - Overhead rates (간접비율)
- 42 - Assessment (배부)
- 43 - Internal activity allocation (내부 활동 배부)
이 중 01과 11이 Primary 영역, 21·22·42·43이 대표적인 Secondary 영역에 해당합니다.
실전 코드 1단계 — 기본 SELECT 예제
가장 기본적인 형태로, 특정 컨트롤링 영역의 모든 원가 요소를 조회하는 예제부터 시작합니다. 시나리오는 "컨트롤링 영역 1000(글로벌)에서 현재 유효한 원가 요소 목록을 가져오기"입니다.
REPORT zr_costelem_basic_lookup.
DATA: lt_cost_elements TYPE TABLE OF I_CostElement,
lv_target_area TYPE kokrs VALUE '1000'.
SELECT CostElement,
ControllingArea,
CostElementCategory,
ValidityStartDate,
ValidityEndDate,
CostElementDescription
FROM I_CostElement
WHERE ControllingArea = @lv_target_area
AND ValidityEndDate >= @sy-datum
AND ValidityStartDate <= @sy-datum
INTO TABLE @lt_cost_elements
UP TO 200 ROWS.
LOOP AT lt_cost_elements ASSIGNING FIELD-SYMBOL(<ls_ce>).
WRITE: / <ls_ce>-CostElement,
<ls_ce>-CostElementCategory,
<ls_ce>-CostElementDescription.
ENDLOOP.
핵심은 두 가지입니다. 첫째, 유효성 기간(ValidityStartDate/ValidityEndDate)을 항상 함께 필터링해야 합니다. CSKB가 시간 슬라이스 테이블이기 때문에 같은 원가 요소가 여러 행으로 존재할 수 있고, 필터 없이 조회하면 만료된 데이터까지 섞입니다. 둘째, 컨트롤링 영역은 권한과 직결되므로 가능한 한 명시적으로 지정해야 합니다.
실전 코드 2단계 — 제조원가 분석 시나리오
이번에는 실무 시나리오로, "특정 회사 코드의 제조원가 관련 Primary 원가 요소만 추출하고, GL 계정 정보와 결합해 보고서로 출력"하는 예제를 만들어 봅니다. 에러 처리와 로깅을 포함합니다.
CLASS zcl_mfg_cost_analyzer DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_mfg_cost_row,
cost_element TYPE racct,
gl_account_name TYPE txt50_skat,
category TYPE kataz,
account_group TYPE ktoks,
is_primary TYPE abap_bool,
END OF ty_mfg_cost_row,
tt_mfg_cost_rows TYPE STANDARD TABLE OF ty_mfg_cost_row
WITH EMPTY KEY.
METHODS fetch_manufacturing_costs
IMPORTING iv_controlling_area TYPE kokrs
iv_company_code TYPE bukrs
RETURNING VALUE(rt_rows) TYPE tt_mfg_cost_rows
RAISING cx_sy_open_sql_db.
PRIVATE SECTION.
METHODS log_warning
IMPORTING iv_message TYPE string.
ENDCLASS.
CLASS zcl_mfg_cost_analyzer IMPLEMENTATION.
METHOD fetch_manufacturing_costs.
TRY.
SELECT ce~CostElement AS cost_element,
skat~GLAccountName AS gl_account_name,
ce~CostElementCategory AS category,
ska~GLAccountGroup AS account_group,
CASE ce~CostElementCategory
WHEN '01' THEN @abap_true
WHEN '11' THEN @abap_true
ELSE @abap_false
END AS is_primary
FROM I_CostElement AS ce
INNER JOIN I_GLAccountInChartOfAccounts AS ska
ON ska~GLAccount = ce~CostElement
INNER JOIN I_GLAccountText AS skat
ON skat~GLAccount = ska~GLAccount
AND skat~ChartOfAccounts = ska~ChartOfAccounts
AND skat~Language = @sy-langu
WHERE ce~ControllingArea = @iv_controlling_area
AND ce~CostElementCategory IN ( '01', '11' )
AND ce~ValidityEndDate >= @sy-datum
AND ce~ValidityStartDate <= @sy-datum
AND ska~GLAccountGroup LIKE 'MFG%'
INTO TABLE @rt_rows.
IF rt_rows IS INITIAL.
log_warning( |No mfg cost elements for area { iv_controlling_area }| ).
ENDIF.
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
log_warning( lx_db->get_text( ) ).
RAISE EXCEPTION lx_db.
ENDTRY.
ENDMETHOD.
METHOD log_warning.
cl_bali_message_setter=>create(
severity = if_bali_constants=>c_severity_warning
text = iv_message ).
ENDMETHOD.
ENDCLASS.
여기서 주목할 점은 I_CostElement를 I_GLAccountInChartOfAccounts(SKA1 기반)와 INNER JOIN한다는 것입니다. S/4HANA에서 Primary 원가 요소는 GL 계정과 1:1로 매핑되므로 JOIN 키는 CostElement = GLAccount입니다. 또한 GLAccountGroup LIKE 'MFG%'으로 제조원가 계정 그룹만 필터링하는 패턴은 실무에서 매우 자주 쓰입니다.
실전 코드 3단계 — 프로덕션 수준의 인건비/감가상각 통합 조회
프로덕션 환경에서는 성능과 테스트 가능성이 중요합니다. 다음은 CDS 뷰를 직접 정의해 인건비(Personnel Cost)와 감가상각(Depreciation) 관련 원가 요소를 통합 조회하는 Composite View 예제입니다.
@AbapCatalog.sqlViewName: 'ZCV_OPEXCOSTELE'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'OPEX Cost Element Composite'
@VDM.viewType: #COMPOSITE
define view ZC_OpexCostElementOverview
as select from I_CostElement as Ce
association [0..1] to I_GLAccountInChartOfAccounts as _GLAcct
on $projection.CostElement = _GLAcct.GLAccount
and $projection.ChartOfAccounts = _GLAcct.ChartOfAccounts
{
key Ce.ControllingArea,
key Ce.CostElement,
Ce.ChartOfAccounts,
Ce.CostElementCategory,
Ce.ValidityStartDate,
Ce.ValidityEndDate,
case Ce.CostElementCategory
when '01' then 'PRIMARY'
when '11' then 'REVENUE'
when '21' then 'SECONDARY_INTERNAL'
when '22' then 'SECONDARY_EXTERNAL'
when '42' then 'SECONDARY_ASSESSMENT'
when '43' then 'SECONDARY_ACTIVITY'
else 'OTHER'
end as CostNature,
case
when _GLAcct.GLAccountGroup like 'PERS%' then 'PERSONNEL'
when _GLAcct.GLAccountGroup like 'DEPR%' then 'DEPRECIATION'
when _GLAcct.GLAccountGroup like 'MFG%' then 'MANUFACTURING'
else 'OTHER'
end as OpexBucket,
_GLAcct.GLAccountGroup as AccountGroup,
_GLAcct
}
where Ce.ValidityEndDate >= $session.system_date
and Ce.ValidityStartDate <= $session.system_date
이 Composite View는 두 가지 보강을 합니다. 첫째, CostElementCategory 코드값을 사람이 읽을 수 있는 CostNature로 변환해 다운스트림 보고서가 코드값을 외울 필요가 없게 합니다. 둘째, GL 계정 그룹(GLAccountGroup)을 기반으로 운영비용(OPEX) 버킷을 분류해 인건비/감가상각/제조원가를 구분합니다. $session.system_date를 활용해 유효성 필터를 뷰 레벨에 내장한 것도 핵심 포인트입니다.
ABAP Unit 테스트는 CDS_TEST_ENVIRONMENT를 활용해 I_CostElement에 테스트 더블을 주입하면 됩니다. 보안 측면에서는 @AccessControl.authorizationCheck: #CHECK를 명시해 DCL(Data Control Language)에서 컨트롤링 영역 권한을 자동 적용하도록 했습니다.
흔히 발생하는 실수와 트러블슈팅 FAQ
Q1. SE16에서 CSKA를 조회했는데 데이터가 없습니다. S/4HANA에서는 Primary 원가 요소가 GL 계정 마스터로 흡수되었기 때문에, CSKA에는 Secondary 원가 요소와 마이그레이션된 일부 레거시 데이터만 남아 있는 경우가 많습니다. 통합 뷰가 필요하면 I_CostElement를 사용하거나, GL 계정 측에서 I_GLAccountInChartOfAccounts의 GLAccountType = 'P'(Primary) 또는 'S'(Secondary) 필터로 접근하세요.
Q2. 같은 원가 요소가 여러 행으로 나옵니다. CSKB가 시간 분할(time-dependent) 테이블이라 유효 기간이 다른 여러 슬라이스가 존재합니다. ValidityStartDate <= sy-datum AND ValidityEndDate >= sy-datum 조건을 빠뜨리면 중복으로 보입니다. 또한 컨트롤링 영역이 여러 개인 글로벌 환경에서는 ControllingArea 필터도 함께 걸어야 합니다.
Q3. JOIN 했더니 성능이 급격히 떨어집니다. I_CostElement는 내부적으로 여러 테이블을 결합한 뷰이므로, 추가 JOIN을 무분별하게 걸면 옵티마이저가 최적 플랜을 잡지 못합니다. 권장 패턴은 (1) WHERE 절에 키 필드(ControllingArea, CostElement, ValidityEndDate)를 최대한 명시, (2) 필요 컬럼만 SELECT, (3) LIKE 'MFG%' 같은 와일드카드는 가능하면 = 비교로 대체, (4) 대량 처리에는 PACKAGE SIZE 커서를 사용하는 것입니다.
그 외에도 자주 보이는 함정으로는, ECC 마인드로 KSTAR/KOKRS 필드명을 그대로 쓰려 하는 경우(CDS에서는 CostElement, ControllingArea), CostElementCategory를 INT로 비교하는 경우(실제 타입은 CHAR(2)), 그리고 DCL 권한 미설정으로 운영계에서 공백 결과가 나오는 경우 등이 있습니다.
한 단계 더 깊이 들어가고 싶다면
I_CostElement를 충분히 이해했다면, 자연스럽게 이어지는 주제는 (1) I_GLAccountLineItem / I_JournalEntryItem을 통한 실제 전표 라인 분석, (2) Universal Journal(ACDOCA) 기반의 통합 보고 모델, (3) I_CostCenter·I_InternalOrder와의 결합을 통한 코스트 오브젝트 분석, (4) Analytical Query(@Analytics.query: true)를 활용한 SAP Analytics Cloud 연계입니다. RAP(Restful ABAP Programming Model) 관점에서는 I_CostElement 위에 BDEF를 얹어 Fiori Elements 앱으로 노출하는 패턴도 학습할 만합니다.
더 파볼 만한 자료 모음
- SAP Help Portal - SAP S/4HANA Product Documentation
- help.sap.com - ABAP CDS View Entities Overview
- help.sap.com - SAP S/4HANA Cloud Finance Documentation
- help.sap.com - S/4HANA 2022 Cost Element & GL Account Integration
- SAP API Business Hub - I_CostElement OData/CDS Reference
- SAP Community - ABAP CDS and Controlling Discussions
- SAP Blogs - Universal Journal and Cost Element Migration Topics
댓글 0
아직 댓글이 없습니다.