ABAP

AUFK 직접 vs CDS — PM 오더 실전 비교 #shorts #SAP #ABAP

이 글에서 다루는 범위와 도달점

SAP S/4HANA의 PM(Plant Maintenance) 모듈에서 정비 오더는 설비 고장, 예방 정비, 검사 작업을 관리하는 핵심 트랜잭션 객체입니다. 이 글은 I_MaintenanceOrder CDS 뷰가 레거시 AUFK 테이블을 어떻게 의미론적으로 재구성하는지 살펴보고, 실제 ABAP 코드에서 정비 오더 데이터를 조회·집계·상태 관리하는 방법을 실전 예제로 풀어냅니다.

먼저 알고 있어야 할 배경

이 글을 따라오려면 ABAP OO 기본 문법과 Open SQL(SELECT, JOIN, aggregate)에 익숙해야 합니다. CDS View 정의 방식(@AbapCatalog, association 개념)을 대략적으로 파악하고 있으면 이해가 빠릅니다. PM 트랜잭션(IW31/IW32/IW33)의 오더 생성·조회 흐름을 한 번이라도 경험해 보셨다면 필드 의미를 자연스럽게 연결할 수 있습니다.

실습 환경 및 준비물

본 예제는 SAP S/4HANA 2022 이상(Cloud Private Edition, On-Premise 공통)에서 검증되었습니다. Public Cloud 에디션에서는 Released 상태의 I_MaintenanceOrder를 그대로 사용할 수 있으며, ADT(ABAP Development Tools) 또는 Eclipse 3.36+ 환경을 권장합니다.

I_MaintenanceOrder가 감추는 것들

전통적으로 PM 오더 데이터를 조회하려면 개발자는 AUFK(오더 마스터 헤더), AFKO(오더 헤더 세부), AFVC(오퍼레이션), JEST(시스템 상태) 테이블을 직접 조인해야 했습니다. 각 테이블의 필드명은 AUFNR, AUART, ERNAM, IWERK처럼 축약형이라 도메인 지식 없이 SQL만 읽으면 의미를 파악하기 어렵습니다.

I_MaintenanceOrder는 이 복잡한 조인을 CDS View 레이어에서 하나의 의미론적 엔티티로 추상화합니다. 필드는 MaintenanceOrder, MaintenanceOrderType, CreatedByUser, MaintenancePlanningPlant처럼 자연어에 가까운 이름으로 재정의되고, 관련 마스터 데이터(설비, 기능위치, 담당자)와의 association이 사전 구성되어 있습니다.

비유하자면 AUFK는 부품 창고의 원자재 상자이고, I_MaintenanceOrder는 조립·라벨링을 마친 완제품 진열대입니다. 개발자는 진열대에서 필요한 필드만 골라 담으면 됩니다.

[Fiori/Analytics/RAP BO]
        │
        ▼
[I_MaintenanceOrder]  ← Basic View 계층
        │  associations
        ├── _MaintenanceOrderType
        ├── _PlanningPlant
        ├── _TechnicalObject (I_TechnicalObject)
        └── _SystemStatus
        │
        ▼
[AUFK] + [AFKO] + [AFIH] + [JEST]  ← DB 물리 테이블

또 한 가지 중요한 지점은 OrderCategory 필드입니다. AUFK 테이블에는 CO 내부오더, 프로세스 오더, PM 오더가 모두 저장되므로, AUTYP = 30(PM 오더) 필터가 필수입니다. I_MaintenanceOrder는 이 필터를 뷰 정의에서 이미 적용하여 개발자가 실수할 여지를 줄여 줍니다.

실전 예제 1단계: 기본 조회

가장 먼저, 특정 정비 계획 공장(Planning Plant)에서 생성된 정비 오더 목록을 조회하는 예제입니다. 클래식 방식(AUFK 직접 조회)과 CDS 방식을 비교해 봅니다.

" [Before] AUFK를 직접 조회하는 레거시 코드
DATA: lt_legacy_orders TYPE TABLE OF aufk.

SELECT aufnr, auart, ktext, erdat, ernam, werks
  FROM aufk
  INTO CORRESPONDING FIELDS OF TABLE @lt_legacy_orders
 WHERE autyp = '30'
   AND werks = 'PL01'
   AND erdat >= '20260101'.

" [After] I_MaintenanceOrder 사용
DATA: lt_pm_orders TYPE TABLE OF i_maintenanceorder.

SELECT MaintenanceOrder,
       MaintenanceOrderType,
       MaintenanceOrderDesc,
       CreationDate,
       CreatedByUser,
       MaintenancePlanningPlant
  FROM i_maintenanceorder
  INTO CORRESPONDING FIELDS OF TABLE @lt_pm_orders
 WHERE MaintenancePlanningPlant = 'PL01'
   AND CreationDate >= '20260101'.

실전 예제 2단계: 상태 필터와 예외 처리

실무에서 정비 오더 조회는 대개 "미완료(TECO 이전) 오더만" 혹은 "특정 담당자가 생성한 이번 달 오더" 같은 조건이 붙습니다. 이 단계에서는 시스템 상태(JEST)와 연결된 association을 활용하고, 결과가 없을 때의 예외 처리 및 로깅을 함께 구성합니다.

CLASS zcl_pm_order_reader DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_open_order,
             order_id        TYPE i_maintenanceorder-maintenanceorder,
             order_type      TYPE i_maintenanceorder-maintenanceordertype,
             description     TYPE i_maintenanceorder-maintenanceorderdesc,
             planning_plant  TYPE i_maintenanceorder-maintenanceplanningplant,
             created_by      TYPE i_maintenanceorder-createdbyuser,
             created_on      TYPE i_maintenanceorder-creationdate,
           END OF ty_open_order.

    METHODS get_open_orders
      IMPORTING iv_planning_plant TYPE werks_d
                iv_creator        TYPE syuname OPTIONAL
      RETURNING VALUE(rt_orders)  TYPE STANDARD TABLE OF ty_open_order
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_pm_order_reader IMPLEMENTATION.
  METHOD get_open_orders.
    TRY.
        SELECT MaintenanceOrder        AS order_id,
               MaintenanceOrderType    AS order_type,
               MaintenanceOrderDesc    AS description,
               MaintenancePlanningPlant AS planning_plant,
               CreatedByUser           AS created_by,
               CreationDate            AS created_on
          FROM i_maintenanceorder
         WHERE MaintenancePlanningPlant = @iv_planning_plant
           AND ( @iv_creator IS INITIAL OR CreatedByUser = @iv_creator )
           AND MaintOrderTechnicallyCompletedOn IS INITIAL
           AND DeletionIndicator = @abap_false
          INTO CORRESPONDING FIELDS OF TABLE @rt_orders.

        IF sy-subrc <> 0.
          MESSAGE i001(zpm) WITH iv_planning_plant.
        ENDIF.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        cl_bali_free_text_setter=>create(
          severity = if_bali_constants=>c_severity_error
          text     = |PM order read failed: { lx_sql->get_text( ) }| ).
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

실전 예제 3단계: 비용 집계와 프로덕션 최적화

이번에는 월별로 정비 오더 유형별 건수와 대기 시간(생성일부터 오늘까지)을 집계하는 리포트를 만듭니다. 대량 데이터를 다루므로 성능·권한·테스트 세 축을 함께 고려합니다.

CLASS zcl_pm_order_analytics DEFINITION PUBLIC FINAL.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_order_kpi,
             planning_plant TYPE werks_d,
             order_type     TYPE aufart,
             order_count    TYPE i,
             avg_age_days   TYPE decfloat16,
           END OF ty_order_kpi.

    CLASS-METHODS calc_monthly_kpi
      IMPORTING iv_year_month  TYPE numc6
      RETURNING VALUE(rt_kpi)  TYPE STANDARD TABLE OF ty_order_kpi.
ENDCLASS.

CLASS zcl_pm_order_analytics IMPLEMENTATION.
  METHOD calc_monthly_kpi.
    DATA(lv_from) = CONV d( |{ iv_year_month }01| ).
    DATA(lv_to)   = cl_reca_date=>get_last_of_month( lv_from ).

    SELECT MaintenancePlanningPlant AS planning_plant,
           MaintenanceOrderType     AS order_type,
           COUNT(*)                 AS order_count,
           AVG( CAST( days_between( CreationDate, @sy-datum ) AS abap.dec( 16, 4 ) ) )
                                    AS avg_age_days
      FROM i_maintenanceorder
     WHERE CreationDate BETWEEN @lv_from AND @lv_to
       AND DeletionIndicator = @abap_false
     GROUP BY MaintenancePlanningPlant, MaintenanceOrderType
      INTO CORRESPONDING FIELDS OF TABLE @rt_kpi.
  ENDMETHOD.
ENDCLASS.

프로덕션 관점에서 추가로 챙겨야 할 사항입니다.

  • 인덱스 활용: MaintenancePlanningPlant, CreationDate는 AUFK의 표준 인덱스로 커버되므로, 이 두 필드를 WHERE 절 앞부분에 두는 것을 권장합니다.
  • 단위 테스트: cl_osql_test_environment를 사용해 I_MaintenanceOrder를 목킹하면, 실제 AUFK 데이터 없이도 KPI 계산 로직을 검증할 수 있습니다.
  • 권한: PFCG 역할에 S_TABU_DIS(권한 그룹) 및 PM 관련 I_AUART가 없으면 CDS Access Control(DCL)에 의해 결과가 자동 필터링됩니다.
  • 버퍼링 주의: AUFK는 일반적으로 버퍼링이 활성화되지 않으므로 대량 조회 시 HANA의 결과 캐시에 의존합니다. 반복 호출 패턴이라면 애플리케이션 레벨 캐싱을 고려하세요.

자주 부딪히는 문제와 해결 팁

Q1. I_MaintenanceOrder에서 데이터가 조회되지 않습니다. AUFK에는 분명 존재하는데요.
가장 흔한 원인은 AUTYP가 30(PM)이 아닌 경우입니다. AUFK에는 CO 내부오더(20), 프로세스오더(40) 등이 함께 저장됩니다. 두 번째로는 DCL 권한 필터가 결과를 잘라내는 경우이므로, SU53 또는 ADT의 Data Preview에서 확인하세요.

Q2. TECO 완료된 오더만 뽑고 싶은데 필드가 잘 안 보입니다.
MaintOrderTechnicallyCompletedOn이 채워져 있으면 TECO 상태로 간주할 수 있습니다. 단, 완전한 시스템 상태를 원한다면 association _SystemStatus를 통해 JEST/TJ02T를 조인하거나, I_MaintenanceOrder_2(확장 뷰)의 상태 필드를 활용하는 것이 안전합니다.

Q3. 조회 성능이 예상보다 느립니다.
SELECT *는 지양하고 필요한 컬럼만 명시하세요. 또한 MaintenanceOrder(오더 번호)는 문자열 형태이므로 앞 0 채움(CONVERSION_EXIT_ALPHA_INPUT)을 잊으면 결과가 빈 채로 반환됩니다. ST05 트레이스를 통해 실제 SQL과 인덱스 사용 여부를 확인하는 것이 왕도입니다.

이어서 살펴볼 만한 주제

이 글에서 다룬 I_MaintenanceOrder는 Basic View 계층입니다. 실무에서는 다음 단계로 확장할 수 있습니다.

  • Composite View: C_MaintenanceOrder를 활용한 분석용 큐브 구성
  • RAP BO: I_MaintenanceOrderTP(Transactional Processing) 기반 오더 생성/변경 UI 개발
  • 연관 엔티티: I_MaintenanceOrderOperation(오퍼레이션), I_MaintenanceNotification(통지) 조합 리포트
  • Analytics: KPI 뷰를 CDS Query(@Analytics.query: true)로 노출하여 Fiori Analytical List Page 구축

댓글 0

아직 댓글이 없습니다.