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 절 없이도 헤더와 라인 정보가 한 행에 함께 노출된다는 점입니다. 결과 내부 테이블의 DebitCreditCode가 S면 입고 성격, 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
아직 댓글이 없습니다.