ABAP

MKPF MSEG vs I_GoodsMovement — 자재이동 CDS 비교 #shorts #SAP #ABAP

▶ YouTube에서 보기

1. I_GoodsMovement 뷰의 정체와 등장 배경

SAP S/4HANA 환경에서 자재 이동(Goods Movement) 데이터를 다룰 때 가장 먼저 만나게 되는 표준 CDS 뷰 중 하나가 I_GoodsMovement입니다. 이 뷰는 자재 문서 헤더 테이블인 MKPF(Material Document Header)와 명세 라인 테이블인 MSEG(Document Segment: Material)를 가상으로 결합하여, 입고·출고·재고이전·재고조정 등 모든 재고 이동 트랜잭션을 단일 진입점으로 조회할 수 있도록 제공하는 Interface View(I-view)입니다.

전통적인 ABAP 개발에서는 MKPF와 MSEG를 직접 JOIN하거나 BAPI BAPI_GOODSMVT_GETITEMS를 호출해야 했습니다. 그러나 S/4HANA의 Virtual Data Model(VDM) 도입 이후, SAP는 모든 핵심 트랜잭션 데이터를 의미 있는 CDS 뷰 계층으로 노출시켰고, I_GoodsMovement는 그 중 자재이동 영역의 기본 빌딩 블록 역할을 합니다. Fiori 앱, Analytics Cloud, Embedded Analytics, RAP 기반 OData 서비스 등 다양한 채널이 이 뷰를 재사용합니다.

이 글에서는 I_GoodsMovement의 구조적 이해부터 실무 ABAP 프로그램에서의 조회 패턴, 그리고 대량 데이터 처리 시 성능 튜닝까지 단계별로 풀어냅니다.

2. 사전 점검 — 알고 있어야 할 배경 지식

본문을 따라가기 전에 다음 개념에 대한 기본기를 갖추는 것이 좋습니다. 첫째, MM(Materials Management) 모듈의 자재 문서 개념(이동 유형 101 입고, 201 출고, 311 재고이전 등)을 이해하고 있어야 합니다. 둘째, ABAP CDS 뷰의 기본 문법(@AbapCatalog, define view, association)에 익숙해야 하며, 셋째, Open SQL의 SELECT ... FROM cds_view 문법과 Inline Declaration(@DATA)을 사용할 수 있어야 합니다. ADT(ABAP Development Tools) 기반의 Eclipse 환경 사용 경험이 있으면 뷰 탐색에 큰 도움이 됩니다.

3. 환경 및 준비 사항

이 글의 예제는 다음 환경을 가정합니다. SAP S/4HANA 2022 또는 2023 On-Premise 에디션(또는 Cloud Private/Public Edition)에서 검증되었으며, ABAP Platform 2022 이상이면 대부분의 필드가 동일하게 노출됩니다. 개발 도구는 ADT(Eclipse) 최신 버전을 권장하며, 실행 권한은 표준 데이터 사용자 권한 외에 S_DEVELOP(개체 활성화), S_RFC(외부 호출 테스트), 그리고 자재 마스터 조회 권한(M_MATE_*)이 필요합니다.

본 뷰는 SAP가 출시한 표준 가상 데이터 모델의 일부이므로 별도 설치는 필요 없습니다. 다만 일부 Association(예: _MaterialDocumentYearAndItem)은 S/4HANA 버전에 따라 필드명이나 association 이름이 미세하게 다를 수 있으므로, ADT의 F2(요소 정보) 또는 Ctrl+Shift+F2(소스 표시)로 자신의 시스템 정의를 반드시 확인하는 습관을 권장합니다. 테스트용 자재 문서가 부족하다면 트랜잭션 MIGO로 몇 건의 101/201 이동을 미리 생성해두면 좋습니다.

4. MKPF/MSEG와 CDS 뷰가 맺는 관계의 본질

전통적인 자재 문서 데이터 모델을 그림으로 떠올려보면, MKPF는 문서의 "표지"이고 MSEG는 그 표지에 묶인 "내지"입니다. 한 장의 표지(MBLNR+MJAHR 조합)에 여러 명세 라인(ZEILE)이 매달려 있는 1:N 구조죠. 이 구조를 매번 손으로 JOIN하는 것은 비효율적이고, 더 큰 문제는 비즈니스 의미가 코드 곳곳에 흩어진다는 점입니다.

I_GoodsMovement는 이 둘을 라인 단위로 평탄화(flatten)하여 "한 행 = 한 자재 이동 명세"라는 단일 시맨틱을 제공합니다. 즉 MKPF의 전표일, 생성자, 참조번호 등 헤더 정보가 MSEG의 자재, 수량, 플랜트 같은 라인 정보와 함께 한 행에 보이도록 설계되어 있습니다. 비유하자면, 도서관에서 책의 표지와 본문 페이지를 일일이 뒤져 책 정보를 조합하는 대신, 잘 정리된 색인 카드 한 장으로 "어떤 책 몇 페이지에 무슨 내용이 있다"를 즉시 확인하는 방식이라 할 수 있습니다.

또한 이 뷰는 단순 JOIN이 아닌 의미론적 노출 계층입니다. 코드 값(예: BWART = '101')을 보면 그 옆에 GoodsMovementType이라는 의미적 필드명이 붙어 있고, Association을 통해 자재 마스터(_Material), 플랜트(_Plant), 이동유형(_GoodsMovementType), 회사코드(_CompanyCode) 등 관련 마스터 뷰로 즉시 점프할 수 있습니다. 결과적으로 개발자는 테이블 구조 대신 비즈니스 엔티티 관점에서 코드를 작성하게 됩니다.

5. 자주 쓰는 필드 한눈에 보기

표준 정의에서 자주 활용되는 핵심 필드를 정리하면 다음과 같습니다. 키 필드부터 살펴보면 MaterialDocument(MBLNR), MaterialDocumentYear(MJAHR), MaterialDocumentItem(ZEILE)이 한 행을 유일하게 식별합니다. 이동의 성격을 나타내는 GoodsMovementType(BWART)과 DebitCreditCode(SHKZG, S/H)는 입출고 방향 판단에 필수입니다.

수량과 가치 측면에서는 QuantityInEntryUnit(ERFMG), EntryUnit(ERFME), QuantityInBaseUnit(MENGE), MaterialBaseUnit(MEINS)과 TotalGoodsMvtAmtInCCCrcy(DMBTR), CompanyCodeCurrency(WAERS)가 분석의 중심에 놓입니다. 시점 정보는 PostingDate(BUDAT), DocumentDate(BLDAT), FiscalYear(GJAHR)가 있고, 조직 단위로는 Plant(WERKS), StorageLocation(LGORT), Material(MATNR), Batch(CHARG), Supplier(LIFNR), Customer(KUNNR)가 자주 쓰입니다.

여기에 더해 참조 문서를 가리키는 PurchaseOrder(EBELN), PurchaseOrderItem(EBELP), ReferenceDocument 계열 필드는 PO 입고/송장 매칭 시 핵심적인 연결고리 역할을 합니다.

6. 첫 번째 실전 예제 — 입출고 라인 한 번에 끌어오기

가장 간단한 패턴부터 시작합니다. 특정 플랜트에서 지정 기간 동안 발생한 모든 자재 이동을 조회하는 예제입니다. 이전 방식이라면 MKPF와 MSEG를 JOIN해야 했지만, I_GoodsMovement 한 줄로 끝납니다.

REPORT zr_gm_basic_lookup.

PARAMETERS:
  p_werks TYPE i_goodsmovement-plant         DEFAULT '1010',
  p_dfrom TYPE i_goodsmovement-postingdate   DEFAULT sy-datum - 30,
  p_dto   TYPE i_goodsmovement-postingdate   DEFAULT sy-datum.

START-OF-SELECTION.

  SELECT
      MaterialDocument,
      MaterialDocumentYear,
      MaterialDocumentItem,
      PostingDate,
      GoodsMovementType,
      DebitCreditCode,
      Material,
      Plant,
      StorageLocation,
      QuantityInBaseUnit,
      MaterialBaseUnit
    FROM I_GoodsMovement
    WHERE Plant       = @p_werks
      AND PostingDate BETWEEN @p_dfrom AND @p_dto
    ORDER BY PostingDate, MaterialDocument, MaterialDocumentItem
    INTO TABLE @DATA(lt_movements).

  cl_demo_output=>display( lt_movements ).

이 코드의 핵심은 두 가지입니다. 첫째, BUDAT 같은 원시 필드명 대신 PostingDate라는 비즈니스 명을 사용한다는 점이고, 둘째, JOIN/ON 절 없이도 헤더와 라인 정보가 한 행에 함께 노출된다는 점입니다. 결과 내부 테이블의 DebitCreditCodeS면 입고 성격, H면 출고 성격이라는 일반적인 해석을 적용할 수 있습니다.

7. 두 번째 예제 — 이동유형별 집계와 예외 처리

이번에는 이동유형(101 입고, 201 비용출고, 311 재고이전 등)별로 수량과 금액을 집계하면서, 데이터가 비어 있거나 권한 문제가 발생할 때를 함께 처리하는 패턴입니다. 실무에서는 이 형태가 월말 마감용 리포트의 기본 골격이 됩니다.

CLASS zcl_gm_monthly_summary DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES:
      BEGIN OF ty_summary,
        movement_type TYPE i_goodsmovement-goodsmovementtype,
        plant         TYPE i_goodsmovement-plant,
        qty_total     TYPE i_goodsmovement-quantityinbaseunit,
        amount_total  TYPE i_goodsmovement-totalgoodsmvtamtinccccrcy,
        line_count    TYPE i,
      END OF ty_summary,
      tt_summary TYPE STANDARD TABLE OF ty_summary WITH EMPTY KEY.

    METHODS run
      IMPORTING iv_plant       TYPE i_goodsmovement-plant
                iv_period_from TYPE i_goodsmovement-postingdate
                iv_period_to   TYPE i_goodsmovement-postingdate
      RETURNING VALUE(rt_data) TYPE tt_summary
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_gm_monthly_summary IMPLEMENTATION.
  METHOD run.
    TRY.
        SELECT
            GoodsMovementType               AS movement_type,
            Plant                           AS plant,
            SUM( QuantityInBaseUnit )       AS qty_total,
            SUM( TotalGoodsMvtAmtInCCCrcy ) AS amount_total,
            COUNT( * )                      AS line_count
          FROM I_GoodsMovement
          WHERE Plant       = @iv_plant
            AND PostingDate BETWEEN @iv_period_from AND @iv_period_to
          GROUP BY GoodsMovementType, Plant
          INTO TABLE @rt_data.

        IF rt_data IS INITIAL.
          MESSAGE |No goods movement found for plant { iv_plant }|
                  TYPE 'I'.
        ENDIF.

      CATCH cx_sy_open_sql_db INTO DATA(lx_sql).
        RAISE EXCEPTION lx_sql.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

주목할 부분은 GROUP BY를 DB 측에서 실행하여 ABAP 메모리로 라인을 모두 끌어오지 않는다는 점, 그리고 cx_sy_open_sql_db 예외를 잡아 호출자에게 전파해 트랜잭션 일관성을 지키는 패턴입니다.

8. Association 체이닝 — 자재명·공급사명 추가 JOIN 없이 조회

실무에서 가장 강력한 무기는 Association입니다. I_GoodsMovement의 점 표기법으로 자재명, 공급사명 등을 추가 JOIN 없이 가져올 수 있습니다. 다음은 PO 입고만 골라 자재명·공급사명까지 한 번에 조회하고, 결과 크기도 제한하는 프로덕션 패턴입니다.

CLASS zcl_po_receipt_reader DEFINITION
  PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES:
      BEGIN OF ty_receipt,
        material_doc   TYPE i_goodsmovement-materialdocument,
        doc_year       TYPE i_goodsmovement-materialdocumentyear,
        item           TYPE i_goodsmovement-materialdocumentitem,
        posting_date   TYPE i_goodsmovement-postingdate,
        po_number      TYPE i_goodsmovement-purchaseorder,
        material       TYPE i_goodsmovement-material,
        material_name  TYPE string,
        supplier       TYPE i_goodsmovement-supplier,
        supplier_name  TYPE string,
        qty            TYPE i_goodsmovement-quantityinbaseunit,
        uom            TYPE i_goodsmovement-materialbaseunit,
      END OF ty_receipt,
      tt_receipt TYPE STANDARD TABLE OF ty_receipt WITH EMPTY KEY.

    METHODS read_po_receipts
      IMPORTING iv_plant      TYPE i_goodsmovement-plant
                iv_date_from  TYPE i_goodsmovement-postingdate
                iv_date_to    TYPE i_goodsmovement-postingdate
                iv_max_rows   TYPE i DEFAULT 5000
      RETURNING VALUE(rt_out) TYPE tt_receipt.
ENDCLASS.

CLASS zcl_po_receipt_reader IMPLEMENTATION.
  METHOD read_po_receipts.
    SELECT
        gm~MaterialDocument          AS material_doc,
        gm~MaterialDocumentYear      AS doc_year,
        gm~MaterialDocumentItem      AS item,
        gm~PostingDate               AS posting_date,
        gm~PurchaseOrder             AS po_number,
        gm~Material                  AS material,
        mat~MaterialName             AS material_name,
        gm~Supplier                  AS supplier,
        sup~SupplierName             AS supplier_name,
        gm~QuantityInBaseUnit        AS qty,
        gm~MaterialBaseUnit          AS uom
      FROM I_GoodsMovement AS gm
        LEFT OUTER JOIN I_Material AS mat
          ON  mat~Material = gm~Material
        LEFT OUTER JOIN I_Supplier AS sup
          ON  sup~Supplier = gm~Supplier
      WHERE gm~Plant             = @iv_plant
        AND gm~PostingDate       BETWEEN @iv_date_from AND @iv_date_to
        AND gm~GoodsMovementType = '101'
        AND gm~PurchaseOrder    <> @space
      ORDER BY gm~PostingDate DESCENDING
      INTO CORRESPONDING FIELDS OF TABLE @rt_out
      UP TO @iv_max_rows ROWS.
  ENDMETHOD.
ENDCLASS.

UP TO n ROWS로 결과 크기를 명시적으로 제한해 메모리 폭주를 방지했고, LEFT OUTER JOIN을 통해 자재명이나 공급사명이 누락된 경우에도 본 데이터는 손실되지 않도록 했습니다.

9. 자주 마주치는 함정과 빠른 진단법

Q1. SELECT 결과가 SE16N에서 MSEG로 본 것보다 적게 나옵니다.
I_GoodsMovement는 DCL(Data Control Language)을 통한 권한 필터가 적용됩니다. 원천 테이블 그대로의 1:1 매핑을 원한다면 MSEG를 직접 조회하거나, 권한이 부여된 기술 사용자로 테스트해 차이를 확인하세요.

Q2. QuantityInBaseUnit이 음수로 나옵니다.
출고/취소 성격(DebitCreditCode = 'H')에서는 부호 처리가 적용됩니다. 절대값 집계가 필요하다면 ABS() 또는 CASE 식으로 분기하세요.

Q3. 대량 기간 조회가 너무 느립니다.
ST05(SQL Trace)로 실행계획을 확인하세요. SELECT * 회피, 필요한 컬럼만 명시, ABAP LOOP 내에서 자재 마스터를 단건 조회하는 안티패턴은 Association이나 한 번의 JOIN으로 합치는 것이 효과적입니다.

Q4. 클라우드(Public Cloud) 에디션에서도 동일하게 쓸 수 있나요?
I_GoodsMovement는 Released API로 분류된 경우가 많지만, 릴리스 상태(C1/C2)와 사용 가능 필드는 에디션마다 다를 수 있으므로 ADT의 API State 또는 SAP API Business Hub에서 확인하세요.

10. 더 나은 재고 분석으로 나아가기

이 글에서 다룬 I_GoodsMovement를 충분히 익혔다면, 자연스러운 확장 경로가 몇 가지 있습니다. 분석용 큐브 뷰인 C_GoodsMovement 계열을 활용한 Embedded Analytics 시나리오, RAP(BO Behavior Definition) 기반으로 자재 이동 생성/취소 서비스 노출, I_MaterialStock 같은 재고 잔량 뷰와의 결합을 통한 실시간 재고 대시보드 구현이 대표적입니다. OData v4와 결합해 Fiori Elements List Report를 만들어보는 것도 실무적인 확장 방향입니다.

댓글 0

아직 댓글이 없습니다.