개요 및 핵심 포인트
SAP S/4HANA 환경에서 MRP(Material Requirements Planning) 실행 결과로 생성된 계획 오더(Planned Order)는 PLAF 테이블에 저장됩니다. 과거에는 PLAF 테이블을 직접 SELECT 하거나 BAPI_PLANNEDORDER_GET_DETAIL과 같은 함수를 호출해야 했지만, S/4HANA에서는 가상 데이터 모델(VDM)인 I_PlannedOrder CDS View를 통해 보다 표준화되고 안정적인 방식으로 접근할 수 있습니다. 이 글에서는 해당 뷰의 구조, 동작 원리, 그리고 실무에서 활용하기 위한 단계별 예제를 다룹니다.
- I_PlannedOrder CDS View의 필드 구성과 PLAF 테이블 매핑 이해
- MRP 결과 데이터를 ABAP / OData / 분석 도구에서 조회하는 방법
- 계획 오더 조회 시 성능과 권한 처리에 대한 일반적 권장 사항
- 실제 시나리오에서 발생하는 예외 케이스와 처리 패턴
읽기 전 갖추면 좋은 배경
이 글은 ABAP CDS View의 기본 문법과 어노테이션 개념을 알고 있는 개발자를 대상으로 합니다. 더불어 SAP PP(Production Planning) 모듈의 MRP 흐름, 즉 자재 수요가 어떻게 계획 오더로 변환되어 PLAF에 적재되는지, 그리고 이후 생산 오더(AUFK/AFKO) 또는 구매 요청(EBAN)으로 전환되는 라이프사이클에 대한 기본 이해가 있다면 본문 이해가 한층 수월합니다. ADT(Eclipse용 ABAP Development Tools) 사용 경험도 권장됩니다.
실행 환경과 사전 준비
예제 검증은 다음 환경을 기준으로 합니다.
- SAP S/4HANA 2022 이상 (On-Premise 또는 Private Cloud Edition)
- ABAP Platform 2022, Eclipse 2024-03 + ABAP Development Tools 3.40 이상
- 권한 객체: M_MTDI_ORG (조직 권한), S_TABU_NAM (PLAF 직접 접근 시), I_PlannedOrder 뷰는 표준 DCL(Data Control Language)로 권한이 적용됨
- 테스트 데이터: MD01N(자재 단일 MRP) 또는 MD02(다단계 MRP) 실행 후 PLAF에 생성된 계획 오더
S/4HANA Cloud Public Edition에서는 PLAF 테이블 직접 접근이 제한되므로, 반드시 Released CDS View 또는 OData API(API_PLANNED_ORDER 등)를 사용하는 것이 일반적으로 권장됩니다. 또한 클라이언트 의존(Client-dependent) 데이터이므로 멀티 테넌트 환경에서는 클라이언트 핸들링에 유의해야 합니다.
I_PlannedOrder의 동작 원리와 데이터 모델
I_PlannedOrder는 SAP VDM 계층 구조에서 Basic Interface View(접두어 I_)에 해당합니다. 즉, 데이터베이스 테이블(PLAF, T399D 등)을 직접 노출하는 가장 하위 계층의 재사용 가능 뷰이며, 상위의 Composite View나 Consumption View가 이 뷰를 조합해 비즈니스 시나리오에 맞춘 데이터를 제공합니다.
비유하자면 PLAF 테이블은 공장 창고에 쌓인 부품 박스라면, I_PlannedOrder는 그 박스에 라벨을 붙이고 분류해서 누구나 알아보기 쉽게 진열대에 올려둔 것에 가깝습니다. 또한 단순 노출에 그치지 않고 다음과 같은 부가 처리를 포함합니다.
- 키 필드 표준화: PLAF-PLNUM → PlannedOrder (영문 의미 기반 명칭)
- 어소시에이션 제공: _Material, _Plant, _MRPArea, _ProductionVersion 등으로 마스터 데이터 조인이 간편
- 변환/계산 필드: 단위, 통화, 일자 등의 데이터 타입을 의미론적으로 명확하게 노출
- DCL 기반 권한 일관성: AccessControl을 통해 권한 객체와 자동 연결
주요 필드는 다음과 같이 매핑됩니다.
| CDS 필드 | PLAF 컬럼 | 설명 |
|---|---|---|
| PlannedOrder | PLNUM | 계획 오더 번호 |
| Material | MATNR | 자재 번호 |
| Plant | PLWRK | 플랜트 |
| MRPArea | BERID | MRP 영역 |
| PlannedOrderType | PAART | 계획 오더 유형 |
| TotalPlannedQuantity | GSMNG | 총 계획 수량 |
| PlannedOrderStartDate | PSTTR | 시작일 |
| PlannedOrderEndDate | PEDTR | 종료일 |
| OrderIsFirm | KZAUS | 고정 여부 |
이러한 의미 기반 필드명 덕분에 새로 합류한 개발자가 PLAF의 약어를 일일이 외우지 않아도 의도를 빠르게 파악할 수 있다는 점이 큰 장점입니다.
1단계 — 기본 조회 예제
가장 먼저 단순한 ABAP SQL을 통해 특정 플랜트의 계획 오더를 조회합니다. CDS 표준 뷰를 SELECT 대상에 그대로 사용할 수 있습니다.
REPORT zr_planned_order_basic.
DATA: lt_plaf TYPE TABLE OF i_plannedorder.
SELECT PlannedOrder,
Material,
Plant,
TotalPlannedQuantity,
BaseUnit,
PlannedOrderStartDate,
PlannedOrderEndDate
FROM i_plannedorder
WHERE Plant = '1010'
AND PlannedOrderStartDate >= @( cl_abap_context_info=>get_system_date( ) )
INTO TABLE @lt_plaf
UP TO 100 ROWS.
LOOP AT lt_plaf INTO DATA(ls_row).
WRITE: / ls_row-plannedorder,
ls_row-material,
ls_row-totalplannedquantity,
ls_row-plannedorderstartdate.
ENDLOOP.
위 예제는 플랜트 1010의 향후 계획 오더 최대 100건을 가져옵니다. ABAP SQL이 CDS 뷰를 직접 인식하므로 DDL Source 이름을 그대로 FROM 절에 넣으면 됩니다.
2단계 — 어소시에이션 활용과 예외 처리
실무에서는 단순 조회만으로 부족합니다. 자재 마스터의 명칭, BOM 정보, MRP 컨트롤러 등 부가 정보가 함께 필요합니다. 이때 어소시에이션을 활용하면 별도의 JOIN 없이 직관적인 경로 표현으로 데이터를 가져올 수 있습니다. 동시에 데이터 없음, 권한 부족, 단위 변환 오류 등의 예외 케이스를 로깅으로 잡아둡니다.
CLASS zcl_planned_order_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_planned_view,
plnum TYPE i_plannedorder-plannedorder,
matnr TYPE i_plannedorder-material,
matnr_text TYPE string,
plant TYPE i_plannedorder-plant,
qty TYPE i_plannedorder-totalplannedquantity,
mrp_controller TYPE string,
END OF ty_planned_view,
tt_planned_view TYPE STANDARD TABLE OF ty_planned_view.
METHODS read_open_orders
IMPORTING iv_plant TYPE werks_d
RETURNING VALUE(rt_result) TYPE tt_planned_view
RAISING cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_planned_order_reader IMPLEMENTATION.
METHOD read_open_orders.
TRY.
SELECT po~PlannedOrder AS plnum,
po~Material AS matnr,
mat~MaterialName AS matnr_text,
po~Plant AS plant,
po~TotalPlannedQuantity AS qty,
mat~MRPResponsible AS mrp_controller
FROM i_plannedorder AS po
LEFT OUTER JOIN i_product AS mat
ON mat~Product = po~Material
WHERE po~Plant = @iv_plant
AND po~OrderIsFirm = ''
INTO TABLE @rt_result.
IF sy-subrc <> 0 OR lines( rt_result ) = 0.
cl_demo_output=>write( |플랜트 { iv_plant } 의 미고정 계획 오더 없음| ).
ENDIF.
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
cl_demo_output=>write( |DB 오류: { lx_db->get_text( ) }| ).
RAISE EXCEPTION lx_db.
ENDTRY.
ENDMETHOD.
ENDCLASS.
여기서는 I_Product 뷰와 조인해 자재명과 MRP 담당자를 한 번에 가져오며, OrderIsFirm 필드로 사용자가 수동 고정한 오더는 제외합니다. 예외는 cx_sy_open_sql_db로 잡아 호출자에게 다시 던지는 안전한 패턴을 적용했습니다.
3단계 — 프로덕션 수준의 커스텀 뷰와 OData 노출
운영 시스템에서는 I_PlannedOrder를 그대로 노출하기보다, 비즈니스 요구에 맞춘 Composite View를 작성하고 이를 OData로 게시하는 형태가 일반적입니다. 다음은 지연 위험이 있는 계획 오더만 필터링하는 커스텀 뷰 예제입니다.
@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: '지연 위험 계획 오더'
@Metadata.allowExtensions: true
@Search.searchable: true
define view entity ZC_PlannedOrderAtRisk
as select from I_PlannedOrder as PlOrd
association [0..1] to I_Product as _Product
on $projection.Material = _Product.Product
{
key PlOrd.PlannedOrder,
PlOrd.Material,
_Product.MaterialName,
PlOrd.Plant,
PlOrd.MRPArea,
PlOrd.TotalPlannedQuantity,
PlOrd.BaseUnit,
PlOrd.PlannedOrderStartDate,
PlOrd.PlannedOrderEndDate,
case
when PlOrd.PlannedOrderEndDate < $session.system_date
then 'OVERDUE'
when dats_days_between( $session.system_date,
PlOrd.PlannedOrderEndDate ) <= 3
then 'RISK'
else 'OK'
end as RiskStatus,
_Product
}
where PlOrd.OrderIsFirm = ''
이 뷰에 RAP(RESTful Application Programming Model)의 Service Definition과 Service Binding을 추가하면 Fiori Elements 앱이나 외부 시스템에서 OData v4로 즉시 호출할 수 있습니다. 성능과 보안을 위해 다음 항목을 점검하는 것이 좋습니다.
- 인덱스: PLAF는 PLNUM이 기본 키이므로 MATNR, PLWRK 기반 조회 시 보조 인덱스 또는 ABAP 측 적절한 WHERE 절 사용
- 패키지 사이즈: 대량 조회 시 PACKAGE SIZE 또는 OPEN CURSOR로 분할 처리
- DCL: 표준 권한 외 부가 필터가 필요하면 별도 DCL을 정의하고 단위 테스트(CL_DCL_TEST_HELPER 활용) 수행
- 단위 테스트: ABAP Unit + CDS Test Double Framework로 PLAF 데이터를 모킹해 회귀 검증
자주 마주치는 문제와 해결 패턴
Q1. I_PlannedOrder 조회 결과가 SE16N으로 PLAF를 본 것보다 적습니다.
A. 표준 DCL이 권한 객체(M_MTDI_ORG 등) 기준으로 행을 필터링하기 때문입니다. 권한 사용자 또는 SU53 결과를 확인하고, 필요 시 PFCG에서 플랜트/MRP 영역 권한을 부여해야 합니다.
Q2. 어소시에이션 _Material을 사용했는데 일부 행에서 NULL이 나옵니다.
A. 어소시에이션은 기본적으로 LEFT OUTER LIKE 조인으로 동작하며, 매칭되지 않는 자재(예: 삭제 플래그, 다른 조직 레벨)는 NULL로 처리됩니다. 비즈니스 의미에 따라 INNER JOIN 형태로 강제하려면 WHERE _Material.Product IS NOT INITIAL 조건을 추가합니다.
Q3. S/4HANA Cloud Public Edition에서 I_PlannedOrder를 직접 SELECT 하면 에러가 납니다.
A. Public Edition은 Released 상태가 아닌 CDS는 화이트리스트 외라 거부됩니다. 대신 API_PLANNED_ORDER OData 서비스 또는 Released 버전의 후속 뷰(예: 분석용 뷰)를 활용해야 합니다. Released 상태는 ADT의 API State 탭이나 SAP API Business Hub에서 확인할 수 있습니다.
추가로 자주 발생하는 함정으로는, 시간대(UTC vs 시스템 시간) 차이로 인한 종료일 비교 오류, 클라이언트 의존 필드의 누락 처리, 그리고 PLAF에 남은 “삭제 표시(DELKZ)” 상태 오더를 그대로 가져와 후속 로직이 잘못 동작하는 케이스가 있습니다. WHERE 절에 OrderIsDeleted = '' 조건을 명시적으로 두는 것이 안전합니다.
이어서 살펴볼 만한 주제
I_PlannedOrder를 충분히 이해했다면, MRP 결과의 다른 측면도 함께 모델링해보길 권합니다. 우선 계획 오더의 컴포넌트(자재 소요량)는 I_PlannedOrderComponent 뷰로 제공되며, 이를 부모-자식 관계로 묶으면 BOM 전개 결과를 분석할 수 있습니다. 또한 MRP 결과 전체 뷰는 I_MaterialPlanningData 계열에서 다루며, 계획 오더가 전환된 이후의 생산 오더는 I_ProductionOrder, 구매 요청은 I_PurchaseRequisition으로 추적할 수 있습니다. 더 나아가 RAP 비즈니스 객체로 묶어 Fiori Elements 리스트 리포트를 만드는 시나리오도 자연스러운 확장입니다.
더 깊이 학습할 수 있는 자료
댓글 0
아직 댓글이 없습니다.