ABAP

PM 오더 AUFK 직접 JOIN 이제 그만 #shorts #SAP #ABAP

PM 정비 오더와 I_MaintenanceOrder가 필요한 이유

설비 관리(Plant Maintenance, PM) 모듈은 SAP S/4HANA 운영 환경에서 가동률과 직결되는 핵심 도메인입니다. 발전소 터빈, 반도체 클린룸 장비, 자동차 도장 라인의 로봇 암 등 자산이 고장 나거나 예방 점검이 필요할 때마다 정비 오더(Maintenance Order)가 생성되어 작업 지시, 부품 출고, 인건비 정산, 가동 중단 시간 기록까지 한 번에 묶어 관리합니다. 전통적으로 이 데이터는 AUFK, AFKO, AFVC, IHPA, OBJK 같은 클래식 테이블에 흩어져 있어, 단순한 "지난주 라인 A에서 발생한 정비 오더 목록"조차도 6~7개 JOIN을 짜야 했습니다.

S/4HANA 1909 이후 SAP는 이 복잡성을 가상 데이터 모델(VDM, Virtual Data Model)로 추상화했고, 그 중심에 I_MaintenanceOrder라는 인터페이스 레벨 CDS View가 자리합니다. 이 글에서는 다음 항목을 단계적으로 다룹니다.

  • AUFK/AFKO와 I_MaintenanceOrder의 매핑 구조 이해
  • 설비(Equipment), 기능 위치(Functional Location)와의 Association 원리
  • 실무에서 자주 묻는 "가동 중단 시간 합계" 같은 분석 쿼리 작성
  • RAP/Fiori Elements와 연계할 때의 확장 패턴
  • 운영 환경에서의 권한, 성능, 클라이언트 핸들링 주의점

AUFK·AFKO와 PM 오더 마스터 구조

PM 정비 오더는 본질적으로 CO 모듈의 "오더(AUFK)"를 상속받아 PM 특유의 헤더(AFKO)와 운영 정보(AFVC, AFVV, IHPA, OBJK)로 확장된 구조입니다. 비유하자면 AUFK는 "공통 신분증"이고, AFKO는 "PM 직무 명함", AFVC는 "오늘의 작업 일정표"입니다.

  • AUFK — 오더 마스터 헤더(공통): AUFNR, AUART, WERKS, KOSTV, OBJNR
  • AFKO — 오더 생산/정비 헤더: AUFNR, GLTRP, GSTRP, FTRMS
  • AFVC — 오퍼레이션(공정): AUFPL, APLZL, ARBID, STEUS
  • IHPA — 오더 파트너(담당자): OBJNR, PARVW, PARNR
  • OBJK — 설비/기능위치 링크: OBKNR, EQUNR, TPLNR

여기서 핵심은 OBJNR(객체 번호)로, AUFK와 JEST(상태), JCDS(상태 변경 이력), IHPA(파트너)가 모두 OBJNR로 묶입니다. CDS View는 이 관계를 Association으로 풀어서 "OBJNR이 무엇인지" 신경 쓰지 않고도 상태값에 접근할 수 있게 합니다.

I_MaintenanceOrder의 정의와 필드 구성

I_MaintenanceOrder는 SAP가 표준으로 제공하는 Interface View로, 위 5~6개 테이블의 PM 필드를 단일 평면으로 노출합니다. 주요 키 필드는 MaintenanceOrder(=AUFNR)이며, 클라이언트(MANDT)는 시스템 필드로 자동 처리됩니다.

자주 사용되는 필드를 카테고리별로 정리하면 다음과 같습니다.

  • 식별자: MaintenanceOrder, MaintenanceOrderType, MaintenanceOrderDesc
  • 플랜트/위치: MaintenancePlant, MainWorkCenter, MaintenancePlanningPlant
  • 대상 객체: TechnicalObject, TechnicalObjectType, Equipment, FunctionalLocation
  • 일정: MaintOrdBasicStartDate, MaintOrdBasicEndDate, ActualStartDate, ActualEndDate
  • 비용/계정: CostCenter, CompanyCode, ControllingArea, Currency
  • 상태: MaintenanceOrderStatus(시스템상태 조합 문자열)

"기본 종료일과 실제 종료일의 차이가 곧 가동 중단 지연일"이라는 식의 KPI 계산이 가능해진다는 점이 클래식 테이블 직조회 대비 가장 큰 장점입니다.

설비(Equipment)와의 연결 원리

PM 오더는 보통 하나의 기술 객체(Technical Object)를 향합니다. 그 객체는 설비(EQUI 테이블, Equipment) 또는 기능 위치(IFLOT, Functional Location) 중 하나이며, 헤더의 ILOAN(설비 PM 헤더) 또는 OBJK 링크로 연결됩니다. I_MaintenanceOrder에서는 이를 단순하게 두 필드로 노출합니다.

  • Equipment — EQUNR 설비 코드 (비어 있을 수 있음)
  • FunctionalLocation — TPLNR 기능 위치 코드 (비어 있을 수 있음)
  • TechnicalObjectType — 'EQUI' 또는 'IFLO' 구분자

중요한 포인트는 Equipment 필드가 비어 있어도 FunctionalLocation에 값이 있을 수 있다는 점입니다. 예를 들어 "지하 1층 펌프실 라인"이라는 위치만 지정한 오더는 EQUI 마스터가 없습니다. 이 글의 실전 예제에서 LEFT OUTER 형태의 association을 사용하는 이유가 여기에 있습니다.

실전 예제: 설비 기반 정비 이력 조회 CDS View 작성

아래 예제는 특정 플랜트의 지난 분기 동안 발생한 정비 오더를 설비 코드별로 정리하는 커스텀 CDS View입니다. 이름은 표준과 충돌하지 않도록 Z_PM_EquipmentOverhaulLog로 작성합니다.

@AbapCatalog.sqlViewName: 'ZPMEQUIPOVHLOG'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'PM 설비 정비 이력 조회 View'
define view Z_PM_EquipmentOverhaulLog
  as select from I_MaintenanceOrder as mo
{
  key mo.MaintenanceOrder                         as OrderNumber,
      mo.MaintenanceOrderType                     as OrderType,
      mo.MaintenanceOrderDesc                     as OrderDescription,
      mo.MaintenancePlant                         as Plant,
      mo.Equipment                                as EquipmentId,
      mo.FunctionalLocation                       as FunctionalLoc,
      mo.MainWorkCenter                           as WorkCenter,
      mo.MaintOrdBasicStartDate                   as PlannedStart,
      mo.MaintOrdBasicEndDate                     as PlannedEnd,
      mo.ActualStartDate                          as ActualStart,
      mo.ActualEndDate                            as ActualEnd,
      cast( dats_days_between(
              mo.MaintOrdBasicEndDate,
              mo.ActualEndDate ) as abap.int4 )   as DelayInDays,
      mo.MaintenanceOrderStatus                   as SystemStatus
}
where mo.MaintenancePlant      = '1710'
  and mo.MaintOrdBasicStartDate >= '20260101'

ADT에서 Data Preview를 실행하면 평소라면 AUFK+AFKO+ILOAN+OBJK를 직접 JOIN해야 얻는 결과가 한 번에 표시됩니다. dats_days_between 함수로 계획 종료일 대비 실제 종료일의 지연일을 즉시 계산했다는 점이 클래식 SELECT 대비 큰 생산성 이득입니다.

Association 확장: I_Equipment, I_FunctionalLocation, I_Plant 연결

실무에서는 "설비 자체의 시리얼 번호와 제조사", "기능 위치의 상위 노드" 같은 정보를 함께 노출해야 하는 경우가 많습니다. 이때 표준 인터페이스 뷰 I_Equipment, I_FunctionalLocation, I_Plant를 Association으로 연결합니다.

@AbapCatalog.sqlViewName: 'ZPMEQUIPOVHEX'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'PM 설비 정비 이력 + 마스터 확장'
define view Z_PM_EquipmentOverhaulExt
  as select from I_MaintenanceOrder as mo
  association [0..1] to I_Equipment          as _Equip
    on  $projection.EquipmentId   = _Equip.Equipment
  association [0..1] to I_FunctionalLocation as _FLoc
    on  $projection.FunctionalLoc = _FLoc.FunctionalLocation
  association [0..1] to I_Plant              as _Plant
    on  $projection.Plant         = _Plant.Plant
{
  key mo.MaintenanceOrder              as OrderNumber,
      mo.MaintenanceOrderType          as OrderType,
      mo.Equipment                     as EquipmentId,
      _Equip.EquipmentName             as EquipmentName,
      _Equip.ManufacturerOfAsset       as Manufacturer,
      _Equip.SerialNumber              as SerialNo,
      mo.FunctionalLocation            as FunctionalLoc,
      _FLoc.FunctionalLocationName     as FunctionalLocName,
      mo.MaintenancePlant              as Plant,
      _Plant.PlantName                 as PlantName,
      mo.ActualStartDate               as ActualStart,
      mo.ActualEndDate                 as ActualEnd,
      _Equip,
      _FLoc,
      _Plant
}
where mo.MaintenanceOrderType in ( 'PM01', 'PM02', 'ZM01' )

주의할 점은 Equipment 또는 FunctionalLocation이 비어 있는 오더가 존재하므로 cardinality를 [0..1]로 선언해야 한다는 것입니다. [1..1]로 잘못 쓰면 일부 데이터가 INNER JOIN으로 전환되어 행이 누락될 수 있습니다.

ABAP 프로그램에서 I_MaintenanceOrder 활용

분석 쿼리를 ABAP 프로그램에서 호출할 때는 권한 검증과 패키지 크기, 결과 캐싱까지 함께 고려해야 합니다. 아래는 운영급 서비스 클래스에서 활용하는 패턴입니다.

CLASS zcl_pm_overhaul_reader DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES: BEGIN OF ty_overhaul_row,
             order_number  TYPE i_maintenanceorder-maintenanceorder,
             equipment_id  TYPE i_equipment-equipment,
             equipment_nm  TYPE i_equipment-equipmentname,
             actual_end    TYPE dats,
           END OF ty_overhaul_row,
           tt_overhaul_rows TYPE STANDARD TABLE OF ty_overhaul_row
                                 WITH EMPTY KEY.

    METHODS read_by_plant
      IMPORTING iv_plant       TYPE werks_d
                iv_date_from   TYPE dats
                iv_date_to     TYPE dats
      RETURNING VALUE(rt_data) TYPE tt_overhaul_rows
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_pm_overhaul_reader IMPLEMENTATION.
  METHOD read_by_plant.

    AUTHORITY-CHECK OBJECT 'I_IWERK'
             ID 'IWERK' FIELD iv_plant
             ID 'ACTVT' FIELD '03'.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE cx_sy_open_sql_db
        EXPORTING textid = cx_sy_open_sql_db=>sql_error.
    ENDIF.

    SELECT OrderNumber,
           EquipmentId,
           EquipmentName,
           ActualEnd
      FROM z_pm_equipmentoverhaulext
      WHERE Plant      =  @iv_plant
        AND ActualEnd BETWEEN @iv_date_from AND @iv_date_to
        AND EquipmentId <> @space
      ORDER BY ActualEnd DESCENDING
      INTO TABLE @rt_data
      UP TO 5000 ROWS.

  ENDMETHOD.
ENDCLASS.

운영 환경에서는 다음 세 가지를 함께 적용하는 것이 일반적입니다.

  • 권한: PM 권한 객체 I_IWERK(플랜트), I_AUART(오더 타입), I_INGRP(작업그룹)을 명시적으로 체크
  • 성능: UP TO n ROWS로 상한선을 두고, HANA Optimizer 힌트가 필요하면 CDS에 @Consumption.ranked: true 같은 어노테이션 활용
  • 테스트: ABAP Unit + CDS Test Double Framework로 I_MaintenanceOrder를 mock 처리

운영 실무에서의 주의 사항

Q1. I_MaintenanceOrder에서 상태값이 영어 코드로 나오는데 텍스트로 보고 싶다.
SystemStatus 필드는 TJ02T/TJ30T를 참조한 raw 코드 조합입니다. 사용자 상태 텍스트가 필요하다면 I_MaintenanceOrderStatus 또는 I_MaintOrderStatusOverview를 추가로 association하는 방식을 권장합니다. 직접 JEST를 JOIN하는 것은 가능하지만 VDM 정신과 어긋나므로 권장하지 않습니다.

Q2. 동일한 MaintenanceOrder가 여러 건으로 중복 조회된다.
OBJK나 AFVC를 직접 JOIN하면 오퍼레이션 수만큼 행이 늘어납니다. I_MaintenanceOrder는 헤더 단위라 중복이 발생하지 않지만, 직접 만든 View에서 I_MaintenanceOrderOperation까지 join하면 동일 현상이 나옵니다. 헤더 화면용에서는 별도의 헤더 뷰를, 오퍼레이션 화면용에서는 1:N association 형태로 분리하는 것이 일반적입니다.

Q3. Data Preview에서는 잘 나오는데 OData로 노출하니 권한 오류가 난다.
@AccessControl.authorizationCheck: #CHECK 설정과 DCL(Data Control Language) 뷰가 일치하지 않을 때 발생합니다. PM 모듈은 표준 DCL인 I_MaintenanceOrder에 PFCG 역할 SAP_BR_MAINTENANCE_PLANNER 같은 역할의 권한 객체가 매핑되어 있으므로, 커스텀 View에도 동일한 DCL을 redirect 형태로 상속받게 하는 패턴을 권장합니다.

이어서 살펴볼 만한 주제

I_MaintenanceOrder를 충분히 이해했다면 그 다음으로 다음 영역을 권장합니다. RAP(알에이피)에서 Behavior Definition을 작성해 Fiori Elements 기반의 정비 오더 변경 화면을 구현하거나, I_MaintenanceNotification·I_MaintenancePlan·I_TechnicalObject 같은 인접 도메인 뷰와 결합해 예방 정비(PrevM) KPI 대시보드를 구성하는 시나리오가 대표적입니다. 또한 Embedded Analytics 관점에서 C_ 접두사가 붙은 Consumption View를 활용하면 Analytical Query Browser에서 곧바로 시각화까지 이어집니다.

댓글 0

아직 댓글이 없습니다.