ABAP

T001W 직접 조회하면 큰일 — I_Plant CDS #shorts #SAP #ABAP

▶ YouTube에서 보기

I_Plant가 필요한 이유 — T001W 직접 조회의 함정

SAP S/4HANA 환경에서 플랜트(Plant, 공장/사업장) 정보를 조회할 때 여전히 많은 개발자가 습관적으로 T001W 테이블을 직접 SELECT합니다. 하지만 클라우드/온프레미스 하이브리드 시대에는 이 접근이 위험 요소를 안고 있습니다. T001W는 EWM, MM, SD, PP 모듈이 공유하는 물리 테이블이며, 필드 확장(Extension), 언어별 텍스트(T001W_KURZ 등), 권한 체크(Authorization Object M_MATE_WRK)와 분리되어 있기 때문입니다. SAP는 이러한 문제를 해결하기 위해 Released CDS View I_Plant를 공개했습니다.

이 글에서는 T001W 직접 SELECT 대비 I_Plant CDS 조회의 장점, I_Plant의 주요 필드와 연관(Association) 구조, 그리고 ABAP SQL·RAP·OData 각 계층에서의 실전 활용법을 단계별로 살펴봅니다. 물류/생산 실무에서 F4 Help, BAdI, Fiori 앱과 연결하는 패턴까지 다룹니다.

T001W 직접 조회의 구체적 문제점

T001W 직접 조회는 S/4HANA 마이그레이션 과정에서 가장 많이 검출되는 Custom Code Anti-Pattern 중 하나입니다. 아래 코드가 ECC에서는 정상 동작하지만 S/4HANA에서 권장되지 않는 이유를 살펴봅니다.

* ECC 방식 — 직접 테이블 조회 (권장하지 않음)
SELECT werks name1 land1
  FROM t001w
  INTO TABLE @DATA(lt_plants_old)
  WHERE werks IN ('0001', '0002', '1000').

" 문제 1: 뷰 레이어 없이 물리 테이블 직접 접근
" 문제 2: Authorization Object M_MATE_WRK 검사 미적용
" 문제 3: S/4HANA 향후 구조 변경 시 영향 받음
" 문제 4: WERKS/NAME1 같은 기술명으로 코드 가독성 낮음

SAP Readiness Check나 Custom Code Migration Worklist에서도 T001W 직접 SELECT가 검출되어 수정 대상으로 표시됩니다. 특히 S/4HANA Cloud(Public Edition)에서는 T001W 직접 접근 자체가 차단됩니다.

핵심 개념 — Released CDS View와 Virtual Data Model

T001W와 I_Plant의 관계를 이해하려면 SAP가 제시하는 "Virtual Data Model(VDM)"이라는 계층 구조를 알아야 합니다. VDM은 세 층으로 나뉩니다.

  1. Basic View (I_로 시작): 원천 테이블을 정규화·시맨틱 라벨링한 최하위 뷰. I_Plant가 여기 해당합니다.
  2. Composite View (C_로 시작): 여러 Basic View를 조합해 비즈니스 시나리오를 표현.
  3. Consumption View: Fiori/UI, OData 서비스에 직접 노출되는 최종 계층.

비유하자면 T001W가 "지하 창고의 원자재 상자"라면, I_Plant는 "상품 태그가 붙은 진열대"입니다. 창고 내부 구조가 바뀌어도 진열대의 라벨은 그대로 유지됩니다. 이것이 Release Contract의 핵심입니다.

I_Plant는 내부적으로 T001W를 조회하되 다음 요소를 자동으로 결합합니다.

  • 플랜트 주소(ADRC 테이블) — _Address Association
  • 회사코드(T001) — _CompanyCode Association
  • 국가/지역 마스터(T005, T005S) — _Country, _Region
  • MRP 컨트롤러, 판매 조직, 공정 위치 등 부가 마스터

따라서 개발자가 직접 JOIN을 짤 필요 없이 _Address-StreetName처럼 점(dot) 표기로 접근할 수 있습니다.

기본 SELECT 실전 예제 — 회사코드별 플랜트 조회

가장 단순한 형태의 조회부터 시작합니다. 특정 플랜트 목록을 가져와 회사코드와 국가를 함께 표시하는 예제입니다.

REPORT z_demo_plant_lookup.

SELECT plant,
       plantname,
       companycode,
       country,
       region,
       city
  FROM i_plant
  WHERE plant IN ('1010', '1710', '2010')
  INTO TABLE @DATA(lt_result).

LOOP AT lt_result INTO DATA(ls_row).
  WRITE: / ls_row-plant,
           ls_row-plantname,
           ls_row-companycode,
           ls_row-country,
           ls_row-city.
ENDLOOP.

주목할 점은 필드 이름이 WERKS가 아니라 Plant, NAME1이 아니라 PlantName이라는 것입니다. CDS는 기술 명칭을 비즈니스 시맨틱 이름으로 감싸주므로 코드 가독성이 크게 향상됩니다. City는 원래 T001W가 아닌 ADRC에 존재하지만, I_Plant가 내부에서 ADRC를 이미 조인해 제공합니다.

복합 조건 필터링과 클래스 기반 패턴

실무에서는 존재하지 않는 플랜트가 요청되거나, 권한이 없는 사용자가 조회를 시도할 수도 있습니다. 다음 예제는 클래스 기반으로 리팩터링하고 로그를 남기는 패턴입니다.

CLASS zcl_plant_finder DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES:
      BEGIN OF ty_plant_dto,
        plant_id       TYPE i_plant-plant,
        plant_name     TYPE i_plant-plantname,
        company_code   TYPE i_plant-companycode,
        street         TYPE string,
        country_key    TYPE i_plant-country,
      END OF ty_plant_dto,
      tt_plant_dto TYPE STANDARD TABLE OF ty_plant_dto WITH EMPTY KEY.

    METHODS fetch_by_company
      IMPORTING iv_bukrs        TYPE bukrs
      RETURNING VALUE(rt_result) TYPE tt_plant_dto
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_plant_finder IMPLEMENTATION.
  METHOD fetch_by_company.
    TRY.
        SELECT p~plant       AS plant_id,
               p~plantname   AS plant_name,
               p~companycode AS company_code,
               p~\_Address-StreetName AS street,
               p~country     AS country_key
          FROM i_plant AS p
          WHERE p~companycode = @iv_bukrs
          INTO TABLE @rt_result.

        IF rt_result IS INITIAL.
          MESSAGE i398(00) WITH 'Plant not found for company' iv_bukrs.
        ENDIF.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        cl_bali_log_services=>create_log(
          EXPORTING object    = 'ZPLANT'
                    subobject = 'READ'
                    text      = lx_sql->get_text( ) ).
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

p~\_Address-StreetName 문법은 Path Expression이라 부르며, ABAP 7.55 이상에서 사용 가능합니다. 하위 뷰까지 자동 JOIN이 걸립니다. 불필요한 Association 접근은 자동 JOIN을 유발하므로, 정말 필요한 필드만 SELECT하는 것이 성능상 올바릅니다.

성능·권한·테스트를 고려한 프로덕션 패턴

대량의 플랜트를 조회하거나 백그라운드 잡에서 사용할 때는 다음 세 가지를 고려해야 합니다.

CLASS zcl_plant_service DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS get_plants_for_companies
      IMPORTING it_company_range TYPE RANGE OF bukrs
      RETURNING VALUE(rt_plants) TYPE STANDARD TABLE OF i_plant
                                   WITH NON-UNIQUE SORTED KEY by_plant
                                        COMPONENTS plant.
ENDCLASS.

CLASS zcl_plant_service IMPLEMENTATION.
  METHOD get_plants_for_companies.
    " 1) 권한 사전 체크
    AUTHORITY-CHECK OBJECT 'M_MATE_WRK'
      ID 'WERKS' DUMMY
      ID 'ACTVT' FIELD '03'.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE cx_sy_authorization_error.
    ENDIF.

    " 2) 필요한 컬럼만 선택 (SELECT * 지양)
    SELECT plant,
           plantname,
           companycode,
           country,
           addressid
      FROM i_plant
      WHERE companycode IN @it_company_range
      ORDER BY plant
      INTO CORRESPONDING FIELDS OF TABLE @rt_plants
      UP TO 10000 ROWS.
  ENDMETHOD.
ENDCLASS.

테스트 시에는 CL_CDS_TEST_ENVIRONMENT를 활용해 I_Plant를 테스트 더블(Test Double)로 대체할 수 있습니다.

CLASS ltcl_plant_service DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    DATA: mo_env TYPE REF TO if_cds_test_environment.
    METHODS: setup, teardown, test_happy_path FOR TESTING.
ENDCLASS.

CLASS ltcl_plant_service IMPLEMENTATION.
  METHOD setup.
    mo_env = cl_cds_test_environment=>create(
      i_for_entity = 'I_PLANT' ).
  ENDMETHOD.
  METHOD teardown.
    mo_env->destroy( ).
  ENDMETHOD.
  METHOD test_happy_path.
    " 테스트 데이터를 주입하고 zcl_plant_service 호출 후 검증
    " 실제 DB 없이도 I_Plant 조회 로직을 격리 테스트 가능
    cl_abap_unit_assert=>assert_not_initial(
      act = mo_env
      msg = 'CDS Test Environment 초기화 실패' ).
  ENDMETHOD.
ENDCLASS.

흔한 실수와 트러블슈팅

"I_Plant not released for cloud" 오류
S/4HANA Cloud Public Edition에서는 특정 필드만 릴리스된 경우가 있습니다. ADT에서 뷰를 열고 API State가 "Use System-Internally"로 표시되면 클라우드 대상 개발에서는 사용할 수 없습니다. 대신 I_PlantStdVH(값 도움말용) 등 대체 뷰를 확인하세요.

Path Expression 컴파일 오류
ABAP 릴리스 버전이 7.54 이하이거나, Association 이름의 언더스코어를 이스케이프(\_)하지 않았을 가능성이 큽니다. SELECT 절에서는 p~\_Address-StreetName처럼 백슬래시가 필요합니다.

T001W와 I_Plant 건수 불일치
I_Plant는 특정 유효성 조건(예: 삭제 플래그, 국가 필터)이 자동 적용될 수 있습니다. 또한 클라이언트 의존성이 다르게 처리될 수 있으므로 SE16에서 T001W를 직접 비교하기 전에 @ClientDependent 어노테이션을 확인해야 합니다.

성능 이슈가 있다면 ST05 SQL Trace로 I_Plant가 실제로 어떤 JOIN을 만드는지 확인하는 것이 좋습니다.

OData V4 서비스로 노출하기 — RAP 패턴

I_Plant를 Fiori 앱이나 외부 시스템에서 소비하려면 RAP(Restful ABAP Programming Model)로 감싸 OData V4 서비스를 발행합니다.

@EndUserText.label: 'Plant Lookup Consumption View'
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
define root view entity ZC_PlantLookup
  provider contract transactional_query
  as projection on I_Plant
{
  key Plant           as PlantId,
      PlantName       as PlantName,
      CompanyCode     as CompanyCode,
      Country         as CountryCode,
      Region          as RegionCode,
      City            as CityName,
      /* Association 재노출 */
      _Address        : redirected to composition child ZC_PlantAddress
}

Service Definition과 Service Binding(OData V4, UI/A2X)을 생성하면 SAP Business Technology Platform의 Destination을 통해 외부 앱에서 플랜트 마스터를 REST API로 소비할 수 있습니다.

물류·생산 시스템 실무 활용 — F4 Help, BAdI, 분석 쿼리

I_Plant는 단독으로 쓰이는 경우보다 다른 비즈니스 객체와 결합되어 사용되는 경우가 더 많습니다.

Value Help(F4) 연결: I_PlantStdVH@Consumption.valueHelpDefinition 어노테이션으로 연결하면 Fiori Element 앱에서 자동으로 플랜트 검색창이 표시됩니다.

define view entity ZC_PurchaseRequisitionItem
  as projection on I_PurchaseRequisitionItem
{
  @Consumption.valueHelpDefinition: [{ entity: { name: 'I_PlantStdVH', element: 'Plant' } }]
  Plant,
  ...
}

BAdI에서 플랜트 유효성 검증: 구매 문서 저장 전 플랜트 국가 코드를 자동으로 채우는 BAdI 구현 예제입니다.

METHOD if_ex_me_process_po_cust~post.
  DATA: ls_plant_info TYPE i_plant.

  SELECT SINGLE plant, country, region
    FROM i_plant
    INTO @ls_plant_info
    WHERE plant = @im_header->get_parameter( 'PLANT' ).

  IF sy-subrc = 0.
    im_header->set_parameter(
      EXPORTING
        im_name  = 'COUNTRY'
        im_value = ls_plant_info-country ).
  ENDIF.
ENDMETHOD.

분석 쿼리 Dimension으로 활용: @Analytics.dataCategory: #DIMENSION 어노테이션 덕분에 I_Plant는 CDS Analytics Query에서 플랜트 Dimension으로 바로 사용 가능합니다. KPI 대시보드에서 플랜트별 재고, 생산량, 조달 현황을 드릴다운할 때 활용합니다.

I_StorageLocation, I_MaterialPlant와의 조합: I_Plant를 이해했다면 I_StorageLocation(창고 위치 마스터), I_CompanyCode(회사코드 CDS), I_MaterialPlant(자재-플랜트 결합 뷰)를 학습하면 물류 마스터의 CDS 계층을 대부분 커버할 수 있습니다. 이 뷰들은 모두 Association으로 서로 연결되어 있어, T001W 직접 JOIN 없이도 복잡한 물류 데이터 조회가 가능합니다.

댓글 0

아직 댓글이 없습니다.