이 글에서 다루는 문제와 도달점
S/4HANA 환경에서 생산 오퍼레이션 데이터를 조회할 때 여전히 많은 개발자가 AFVC 테이블을 직접 SELECT합니다. 이 방식은 클래식 ECC 시절의 관성이지만, S/4HANA에서는 표준 CDS 뷰 I_ProductionOrderOperation이 이미 정규화된 인터페이스를 제공합니다. 이 글은 왜 AFVC 직접 접근을 지양해야 하는지, 그리고 I_ProductionOrderOperation을 통해 어떻게 안전하고 확장 가능한 조회 로직을 짤 수 있는지 실전 예제와 함께 정리합니다.
I_ProductionOrderOperation의 주요 필드와 시맨틱 이해AFVC직접 조회의 위험 요소 식별- 계획 수량 대비 확정 수량 비교 리포트 구현
- 대량 데이터 처리 시 성능 튜닝 포인트
먼저 갖추어야 할 배경
이 글은 ABAP CDS 뷰의 기본 개념(DEFINE VIEW ENTITY, association, virtual element)과 S/4HANA Virtual Data Model(VDM) 계층(Basic / Composite / Consumption) 구분을 이미 알고 있다는 전제 하에 진행됩니다. 생산관리 모듈(PP)의 오더-오퍼레이션-컴포넌트 관계, 그리고 AUFK, AFKO, AFVC, AFVV, AFRU 등 클래식 테이블 간 관계에 대한 기초 지식이 있다면 이해가 훨씬 수월합니다.
대상 릴리스와 실습 환경
본문 예제는 SAP S/4HANA 2022 FPS02 이상(온프레미스) 및 S/4HANA Cloud, Public Edition 최근 릴리스를 기준으로 작성되었습니다. ADT(ABAP Development Tools) 3.38 이상, Eclipse 2023-09 이상 환경에서 검증했으며, ABAP 언어 버전은 ABAP for Cloud Development 또는 Standard ABAP 모두에서 동작하도록 #RELEASED C1 상태의 표준 CDS 뷰만 사용합니다.
- ADT 프로젝트: on-premise 시스템 또는 BTP ABAP Environment
- 권한:
C_AFKO_AWK,C_ROUT, PP 모듈 authorization object 세트 - 테스트 데이터: 생산오더 최소 100건, 오퍼레이션 500건 이상 권장
- 모니터링 도구: SAT, ST05, ABAP Profiler
온프레미스 릴리스가 2021 이하인 경우 일부 필드(예: ManufacturingOrderOperationUUID)가 존재하지 않을 수 있으므로 DDIC에서 필드 존재 여부를 먼저 확인해야 합니다.
왜 AFVC를 직접 읽으면 안 되는가
AFVC는 오퍼레이션의 마스터 데이터를 담는 클래식 테이블입니다. 실제 시간·수량은 AFVV에 분리되어 있고, 확정(confirmation) 데이터는 AFRU에 존재합니다. 그런데 AFVC를 직접 SELECT하는 코드에는 다음과 같은 구조적 문제가 있습니다.
비유하자면,
AFVC는 아파트 건축 도면의 원본 청사진이고I_ProductionOrderOperation은 준공 후 관리사무소가 정리한 세대별 요약 카드입니다. 청사진을 매번 펼쳐 보는 것보다, 관리사무소가 이미 계산해 둔 카드를 보는 편이 빠르고 안전합니다.
- 키 매핑 부담:
AFVC는 오더 헤더(AFKO)와 조인해야만AUFNR을 얻을 수 있습니다.AUFPL(Routing number)만 갖고 있어 매핑 로직이 늘 별도로 필요합니다. - 권한 체크 누락: 직접
SELECT는 PP 권한 오브젝트(예:C_AFKO_AWK) 체크가 자동으로 걸리지 않습니다. CDS 뷰는ACCESS CONTROL(DCL)을 통해 권한을 일관되게 강제할 수 있습니다. - 확장성 부족: SAP은 향후 필드 재배치나 신규 필드 추가 시 CDS 계층에서만 안정성을 보장한다고 안내합니다. 테이블 직접 접근 코드는 릴리스 업그레이드에서 깨질 위험이 있습니다.
- 시맨틱 상실:
AFVC-STEUS(제어 키),AFVC-VORNR(공정 번호) 같은 원본 필드는 시맨틱 이름이 없지만 CDS에서는OperationControlProfile,OperationInternalID처럼 자기설명적 이름을 갖습니다.
I_ProductionOrderOperation의 핵심 필드
I_ProductionOrderOperation은 VDM Interface 계층의 뷰로, AFVC·AFVV·AFKO를 내부적으로 조인해 하나의 오퍼레이션 라인으로 노출합니다. 이 글에서 자주 사용할 주요 필드는 다음과 같습니다.
ManufacturingOrder(구AUFNR): 생산오더 번호OperationInternalID/Operation(구VORNR): 공정 번호OperationText(구LTXA1): 공정 설명WorkCenterInternalID(구ARBID): 작업장 내부 IDProductionPlant(구WERKS): 플랜트OpTotalQuantity(구GAMNG계열): 총 계획 수량OpTotalConfirmedYieldQty(구ISMNW): 확정 양품 수량LastChangeDateTime: 마지막 변경 시점 (증분 처리 키)
실전 예제 1단계 — 최소한의 조회
먼저 특정 플랜트의 오퍼레이션을 오더별로 정렬해 읽는 기본 패턴입니다. 클래식 AFVC SELECT를 CDS로 옮기는 첫 단계입니다.
REPORT zpp_op_read_basic.
DATA lt_operations TYPE STANDARD TABLE OF i_productionorderoperation
WITH EMPTY KEY.
SELECT ManufacturingOrder,
OperationInternalID,
Operation,
OperationText,
WorkCenterInternalID,
ProductionPlant,
OpTotalQuantity,
OpTotalConfirmedYieldQty
FROM i_productionorderoperation
WHERE ProductionPlant = @'1010'
AND ManufacturingOrder IN @( VALUE range_t_aufnr(
( sign = 'I' option = 'BT'
low = '000010000000'
high = '000010009999' ) ) )
ORDER BY ManufacturingOrder, Operation
INTO TABLE @lt_operations
UP TO 500 ROWS.
cl_demo_output=>display( lt_operations ).
여기서 주목할 점은 AFKO-AFVC 조인을 직접 짜지 않았다는 것과, 필드 시맨틱이 명확해 리뷰어가 읽자마자 의도를 파악할 수 있다는 것입니다. ORDER BY는 오더 → 공정 순으로 정렬해 후속 처리에서 오퍼레이션 흐름을 순서대로 다루기 좋게 합니다.
실전 예제 2단계 — 계획 대비 실적 리포트
실무에서 가장 흔한 요구는 "각 오퍼레이션별 계획 수량과 확정 수량의 차이"를 보고하는 것입니다. 여기서는 I_ProductionOrder와 조인해 오더의 자재번호까지 함께 가져오고, 예외 상황(계획 0 나눗셈)과 로깅을 추가합니다.
CLASS zcl_pp_op_variance DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ty_variance,
manufacturing_order TYPE i_productionorderoperation-manufacturingorder,
operation TYPE i_productionorderoperation-operation,
operation_text TYPE i_productionorderoperation-operationtext,
material TYPE i_productionorder-material,
plant TYPE i_productionorderoperation-productionplant,
planned_qty TYPE i_productionorderoperation-optotalquantity,
confirmed_qty TYPE i_productionorderoperation-optotalconfirmedyieldqty,
variance_pct TYPE p LENGTH 7 DECIMALS 2,
END OF ty_variance,
tt_variance TYPE STANDARD TABLE OF ty_variance WITH EMPTY KEY.
METHODS get_variance
IMPORTING iv_plant TYPE werks_d
iv_date_from TYPE datum
RETURNING VALUE(rt_data) TYPE tt_variance
RAISING cx_sy_open_sql_db.
ENDCLASS.
CLASS zcl_pp_op_variance IMPLEMENTATION.
METHOD get_variance.
TRY.
SELECT op~ManufacturingOrder AS manufacturing_order,
op~Operation AS operation,
op~OperationText AS operation_text,
ord~Material AS material,
op~ProductionPlant AS plant,
op~OpTotalQuantity AS planned_qty,
op~OpTotalConfirmedYieldQty AS confirmed_qty
FROM i_productionorderoperation AS op
INNER JOIN i_productionorder AS ord
ON ord~ManufacturingOrder = op~ManufacturingOrder
WHERE op~ProductionPlant = @iv_plant
AND ord~MfgOrderPlannedStartDate >= @iv_date_from
ORDER BY op~ManufacturingOrder, op~Operation
INTO CORRESPONDING FIELDS OF TABLE @rt_data.
LOOP AT rt_data ASSIGNING FIELD-SYMBOL(<fs>).
IF <fs>-planned_qty IS NOT INITIAL.
<fs>-variance_pct = ( <fs>-confirmed_qty - <fs>-planned_qty )
/ <fs>-planned_qty * 100.
ELSE.
<fs>-variance_pct = 0.
ENDIF.
ENDLOOP.
CATCH cx_sy_open_sql_db INTO DATA(lx).
MESSAGE lx->get_text( ) TYPE 'E'.
RAISE EXCEPTION lx.
ENDTRY.
ENDMETHOD.
ENDCLASS.
INNER JOIN 대상은 클래식 테이블이 아니라 다시 CDS 뷰 I_ProductionOrder입니다. 이렇게 하면 조인 대상의 권한/필터도 CDS 계층에서 일관되게 관리됩니다. 계획 수량이 0인 오퍼레이션(예: 마일스톤 공정)에서는 나눗셈을 피해야 하므로 IS NOT INITIAL 가드를 반드시 넣습니다.
실전 예제 3단계 — 대량 처리와 성능
야간 배치에서 수십만 건 오퍼레이션을 다룰 때는 다음 원칙을 적용합니다.
- 인덱스 힌트에 의존하지 말고 필터 순서를 좁은 것부터 배치:
ProductionPlant→ 날짜 범위 →ManufacturingOrder - 대량 결과는
PACKAGE SIZE로 잘라 커서 처리 - 불필요한
SELECT *금지 — CDS는 뷰 확장 시 필드가 늘어나므로 명시적 필드 리스트 유지 - 추가 정보가 필요할 때만 association 활용(
_ProductionOrder.Material)
METHOD process_in_packages.
DATA lt_pkg TYPE STANDARD TABLE OF ty_op_min WITH EMPTY KEY.
SELECT ManufacturingOrder,
Operation,
WorkCenterInternalID,
OpTotalQuantity,
OpTotalConfirmedYieldQty
FROM i_productionorderoperation
WHERE ProductionPlant = @iv_plant
AND LastChangeDateTime >= @iv_delta_ts
INTO TABLE @lt_pkg PACKAGE SIZE 5000.
persist_batch( lt_pkg ).
COMMIT WORK.
CLEAR lt_pkg.
ENDSELECT.
ENDMETHOD.
LastChangeDateTime을 델타 키로 사용하면 증분 처리가 가능합니다. 이 필드는 AFVC에서 AEDAT/AEZEIT를 직접 다루던 것보다 안전합니다. HANA 환경에서는 옵티마이저가 자동으로 파티션 프루닝을 수행하지만, 통계 정보가 오래되면 통계 갱신(BASIS 담당) 요청이 필요할 수 있습니다. 성능 테스트 시에는 SAT/ST05로 실제 실행 계획을 확인해 CDS 뷰가 예상대로 push-down되는지 검증하는 것이 일반적으로 권장됩니다.
흔한 실수와 트러블슈팅 FAQ
Q1. 수량 필드만 SELECT했더니 단위를 알 수 없습니다.
A. 수량 필드에는 @Semantics.quantity.unitOfMeasure 어노테이션으로 단위 필드(ProductionUnit, 구 MEINH)가 연결되어 있습니다. 단위를 함께 조회하려면 명시적으로 SELECT해야 하며, UI에서 수량만 보여도 배치 계산 시에는 단위 검증이 필요합니다.
Q2. AFVC에는 있는데 CDS에는 없는 필드가 있습니다.
A. 일부 컨트롤 필드는 Interface 뷰에서 노출되지 않을 수 있습니다. 이럴 때는 상위 Composite 뷰 또는 Extension 가능 여부를 먼저 확인하고, 그래도 없으면 Private 뷰를 참조하기보다는 커스텀 CDS Extension으로 필요한 필드를 안전하게 추가하는 편이 권장됩니다. Private(P_) 뷰는 SAP 내부용이므로 릴리스 안정성이 보장되지 않는다고 안내됩니다.
Q3. 결과가 AFVC 직접 SELECT와 건수가 다릅니다.
A. I_ProductionOrderOperation은 삭제 플래그(LOEKZ)나 특정 상태의 오퍼레이션을 이미 필터링해 반환하는 경우가 있습니다. 원본 필드와 비교할 때는 DeletionIndicator 계열 필드가 노출되는지 확인하고 필요하면 조건을 명시적으로 완화합니다. 또한 CDS는 클라이언트 필드를 자동 처리하므로, CLIENT SPECIFIED를 사용하던 기존 로직과 결과가 달라 보일 수 있습니다.
Q4. Cloud 환경에서 뷰가 릴리스되지 않았다는 오류가 납니다.
A. ABAP for Cloud Development에서는 C1 상태로 릴리스된 오브젝트만 사용 가능합니다. ADT에서 뷰의 API State를 확인하고, 필요 시 대체 뷰나 표준 서비스 사용을 검토합니다.
이어서 살펴볼 만한 주제
같은 계열의 뷰인 I_ProductionOrderComponent(BOM 소요량)와 I_ProductionOrderConfirmation(확정 트랜잭션)을 함께 다루면 MES 데이터와의 정합성 리포트를 만들 수 있습니다. 이 글의 리포트를 RAP 기반 Fiori Elements 앱으로 발전시키려면 Consumption 뷰와 behavior definition을 활용하는 것이 다음 단계로 자연스럽습니다. CDS Access Control(DCL)로 플랜트 단위 권한을 강제하는 실습, 그리고 CDS-based Analytical Query로 KPI를 노출하는 시나리오도 이어서 학습할 만합니다.
더 읽어볼 문서와 링크
댓글 0
아직 댓글이 없습니다.