SAP의 데이터 구조상 단순 명칭뿐만 아니라, 자재 문서(MATDOC)의 차/대 지시자(SHKZG)와 재고 유형 필드를 종합적으로 조합해야 정확한 파이프라인을 구축할 수 있습니다.
1. 비즈니스 유형별 GR / GI 구분
| 비즈니스 유형 | 구분 | 차/대 (SHKZG) | 대표 이동유형 (BWART) | SCM 관점의 매핑 설명 |
| Purchase (구매) | GR (입고) | S (+) | 101, 103, 105 | 외부 공급업체로부터 원부자재/상품 등을 입고 |
| Production (생산) | GR (입고) | S (+) | 101 (F) | 생산 오더를 통한 반제품/완제품의 창고 입고 |
| GI (출고) | H (-) | 261 | 생산 오더에 원부자재 투입 (소비) | |
| Sales (판매) | GI (출고) | H (-) | 601, 603 | 고객에게 제품/상품을 인도 (매출 원가 발생) |
| FoC (무상/샘플) | GR (입고) | S (+) | 511 | 공급업체로부터 무상(Free of Charge) 납품 수령 |
| GI (출고) | H (-) | 211, 331, SD(무상오더) | 코스트센터나 샘플 목적으로 고객/내부에 무상 제공 | |
| 감모 (Shrinkage) | GI (출고) | H (-) | 702 | 실사 결과 전산 대비 실물 부족(차이 조정 출고) |
| 폐기 (Scrap) | GI (출고) | H (-) | 551 | 유효기간 경과, 불량 등으로 인한 명시적 폐기 처리 |
| 기타 입출고 | GR (입고) | S (+) | 501, 561 | PO 없는 입고, 기초 재고 셋업 등 |
| GI (출고) | H (-) | 201, 221 | 프로젝트, 부서(코스트센터) 등 내부 비용 처리용 출고 |
💡 핵심 팁 (취소 건 처리): > 102(구매 입고 취소), 602(판매 출고 취소) 등 취소 이동유형은 반대 성격(GR의 취소는 GI)으로 맵핑하기보다는, 원래 성격(GR)의 마이너스(-) 수량으로 집계해야 SCM 상의 PSI 실적 왜곡(입고 뻥튀기 등)을 방지할 수 있습니다.
2. 가용 / 비가용 자재 상태값 (Stock Type & Batch Status)
SAP에서 SCM이 활용할 수 있는 '현재 쓸 수 있는 재고'를 판별하려면, 단순히 수량이 존재하는지뿐만 아니라 품질 및 보류 상태를 반드시 확인해야 합니다. 이는 주로 두 가지 관점에서 관리됩니다.
A. 재고 유형 (Stock Type)
자재 문서(MATDOC 또는 MSEG) 생성 시점이나 재고 테이블(MARD, MSKU 등)에 기록되는 상태값입니다. 보통 INSMK 필드로 구분합니다.
- 가용 재고 (Unrestricted-use Stock): * 상태값: INSMK = ' ' (공백)
- 의미: 물리적으로나 논리적으로 즉시 생산 투입 및 판매 출고가 가능한 상태입니다. SCM의 '가용 재고'로 직결됩니다.
- 품질 검사 중 재고 (Quality Inspection Stock): * 상태값: INSMK = '2'
- 의미: 입고는 되었으나 QM(품질관리) 모듈에서 검사가 진행 중인 재고입니다. 합격 판정(UD)이 나기 전까지는 생산/판매로 출고할 수 없으므로 비가용(Hold)으로 분류해야 합니다.
- 보류 재고 (Blocked Stock): * 상태값: INSMK = '3'
- 의미: 심각한 불량이나 법적/관리적 사유로 명시적으로 묶어둔 재고입니다. 절대 사용할 수 없는 비가용 재고입니다.
S/4HANA 환경의 MATDOC 테이블을 기준 데이터 소스로 하여, 성능과 확장성을 고려한 일별 수불부 집계 표준 ABAP 가동 프로그램(Executable Program) 상세 소스코드입니다.
이 프로그램은 앞서 설명한 역산(Backward Rolling) 알고리즘을 ABAP Internal Table과 호환되는 로직으로 구현하였으며, 결과를 현업이 바로 확인할 수 있도록 SALV(Simple ALV Object) 형태로 출력 화면까지 포함하고 있습니다.
ABAP 상세 소스코드 (S/4HANA 표준 가이드라인 적용)
*&---------------------------------------------------------------------*
*& Report ZR_DAILY_MATERIAL_LEDGER
*&---------------------------------------------------------------------*
*& 개요: MATDOC 기반 일별 가용/비가용 재고 수불 집계 프로그램
*& 수식: 기말재고 = 현재재고 - (익일부터 현재까지의 변동량)
*& 기초재고 = 기말재고 - 당일입고 + 당일출고
*&---------------------------------------------------------------------*
REPORT zr_daily_material_ledger MESSAGE-ID za.
TABLES: matdoc, mard.
*- 창고 상태별 상수 정의
CONSTANTS: gc_status_avail TYPE string VALUE 'AVAILABLE',
gc_status_qual TYPE string VALUE 'QUALITY',
gc_status_block TYPE string VALUE 'BLOCKED'.
*- 최종 출력용 구조체 정의
TYPES: BEGIN OF ty_report,
werks TYPE matdoc-werks,
matnr TYPE matdoc-matnr,
budat TYPE matdoc-budat,
status TYPE string,
s_beg TYPE matdoc-menge, " 기초재고
s_gr TYPE matdoc-menge, " 입고량
s_gi TYPE matdoc-menge, " 출고량
s_end TYPE matdoc-menge, " 기말재고
meins TYPE matdoc-meins,
END OF ty_report.
TYPES: tt_report TYPE STANDARD TABLE OF ty_report WITH DEFAULT KEY.
*- 현재 시점 베이스라인 재고 구조체
TYPES: BEGIN OF ty_curr_stock,
werks TYPE mard-werks,
matnr TYPE mard-matnr,
labst TYPE mard-labst, " 가용
insme TYPE mard-insme, " 품질검사
speme TYPE mard-speme, " 보류
END OF ty_curr_stock.
DATA: gt_report TYPE tt_report,
gs_report TYPE ty_report.
*----------------------------------------------------------------------*
* SELECTION SCREEN (조회 조건)
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-t01.
PARAMETERS: p_werks TYPE matdoc-werks OBLIGATORY MEMORY ID wrk.
SELECT-OPTIONS: s_matnr TYPE matdoc-matnr MEMORY ID mat.
SELECT-OPTIONS: s_budat TYPE matdoc-budat OBLIGATORY. " 수불 조회 기간
SELECTION-SCREEN END OF BLOCK b1.
*----------------------------------------------------------------------*
* INITIALIZATION / START-OF-SELECTION
*----------------------------------------------------------------------*
INITIALIZATION.
" 기본 조회 기간을 당월 1일부터 오늘까지로 세팅
s_budat-sign = 'I'.
s_budat-option = 'BT'.
s_budat-low = sy-datum(6) && '01'.
s_budat-high = sy-datum.
APPEND s_budat.
START-OF-SELECTION.
PERFORM get_raw_data.
PERFORM calculate_ledger.
PERFORM display_alv.
*&---------------------------------------------------------------------*
*& Form get_raw_data
*&---------------------------------------------------------------------*
FORM get_raw_data.
" 1. 조회 종료일 검증 (미래 일자 방지)
IF s_budat-high > sy-datum.
s_budat-high = sy-datum.
ENDIF.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form calculate_ledger
*&---------------------------------------------------------------------*
FORM calculate_ledger.
DATA: lt_curr_stock TYPE HASHED TABLE OF ty_curr_stock
WITH UNIQUE KEY werks matnr,
ls_curr_stock TYPE ty_curr_stock.
" 2. 현재 시점의 실시간 마스터 재고 데이터 확보 (MARD)
SELECT werks, matnr, SUM( labst ) AS labst, SUM( insme ) AS insme, SUM( speme ) AS speme
FROM mard
WHERE werks = @p_werks
AND matnr IN @s_matnr
GROUP BY werks, matnr
INTO TABLE @DATA(lt_mard_raw).
" 하디드 테이블로 이동하여 룩업 속도 최적화
lt_curr_stock = CORRESPONDING #( lt_mard_raw ).
" 3. 조회 시작일부터 '오늘'까지 발생한 모든 자재 문서(MATDOC) 조회
" 역산용 변동량을 완전하게 구하기 위해 고정적으로 sy-datum까지 조회해야 합니다.
SELECT werks, matnr, budat, bwart, shkzg, insmk, menge, meins
FROM matdoc
WHERE werks = @p_werks
AND matnr IN @s_matnr
AND budat BETWEEN @s_budat-low AND @sy-datum
INTO TABLE @DATA(lt_matdoc).
IF lt_matdoc IS INITIAL.
MESSAGE '해당 기간 내 자재 문서 이력이 존재하지 않습니다.' TYPE 'S' DISPLAY LIKE 'E'.
LEAVE LIST-PROCESSING.
ENDIF.
" 4. 일별 상태별 입출고 순수 수량 가공 (Internal Table 가공)
TYPES: BEGIN OF ty_daily_pipe,
werks TYPE matdoc-werks,
matnr TYPE matdoc-matnr,
budat TYPE matdoc-budat,
status TYPE string,
gr_qty TYPE matdoc-menge,
gi_qty TYPE matdoc-menge,
meins TYPE matdoc-meins,
END OF ty_daily_pipe.
DATA: lt_daily_pipe TYPE STANDARD TABLE OF ty_daily_pipe,
ls_daily_pipe TYPE ty_daily_pipe.
LOOP AT lt_matdoc ASSIGNING FIELD-SYMBOL(<fs_md>).
CLEAR ls_daily_pipe.
ls_daily_pipe-werks = <fs_md>-werks.
ls_daily_pipe-matnr = <fs_md>-matnr.
ls_daily_pipe-budat = <fs_md>-budat.
ls_daily_pipe-meins = <fs_md>-meins.
" 재고 유형 분기
CASE <fs_md>-insmk.
WHEN '2'. ls_daily_pipe-status = gc_status_qual.
WHEN '3'. ls_daily_pipe-status = gc_status_block.
WHEN OTHERS. ls_daily_pipe-status = gc_status_avail.
ENDCASE.
" GR(입고) / GI(출고) 및 취소 보정 매핑 로직
IF <fs_md>-shkzg = 'S'.
IF <fs_md>-bwart = '101' OR <fs_md>-bwart = '501' OR <fs_md>-bwart = '511'.
ls_daily_pipe-gr_qty = <fs_md>-menge.
ELSEIF <fs_md>-bwart = '262' OR <fs_md>-bwart = '602'. " 출고의 취소는 입고 기둥에 마이너스 처리하지 않고
ls_daily_pipe-gi_qty = - <fs_md>-menge. " 출고의 차감으로 처리하여 실적 왜곡 방지
ENDIF.
ELSEIF <fs_md>-shkzg = 'H'.
IF <fs_md>-bwart = '261' OR <fs_md>-bwart = '601' OR <fs_md>-bwart = '201' OR <fs_md>-bwart = '551'.
ls_daily_pipe-gi_qty = <fs_md>-menge.
ELSEIF <fs_md>-bwart = '102' OR <fs_md>-bwart = '502'. " 입고의 취소는 입고의 차감으로 처리
ls_daily_pipe-gr_qty = - <fs_md>-menge.
ENDIF.
ENDIF.
COLLECT ls_daily_pipe INTO lt_daily_pipe.
ENDLOOP.
" 5. 역산용 정렬 (자재별 -> 상태별 -> 날짜 역순 DESCENDING 정렬 필수)
SORT lt_daily_pipe BY werks matnr status budat DESCENDING.
" 6. 롤링 알고리즘 전개 (Backward Rolling Engine)
DATA: lv_running_stock TYPE matdoc-menge,
lv_init_flag TYPE abap_bool.
LOOP AT lt_daily_pipe INTO ls_daily_pipe.
" 자재나 상태가 바뀔 때마다 실시간 현재 재고 기준으로 런닝 밸런스 재설정
AT NEW status.
lv_init_flag = abap_true.
ENDAT.
IF lv_init_flag = abap_true.
CLEAR lv_running_stock.
READ TABLE lt_curr_stock INTO ls_curr_stock
WITH KEY werks = ls_daily_pipe-werks matnr = ls_daily_pipe-matnr.
IF sy-subrc = 0.
CASE ls_daily_pipe-status.
WHEN gc_status_avail. lv_running_stock = ls_curr_stock-labst.
WHEN gc_status_qual. lv_running_stock = ls_curr_stock-insme.
WHEN gc_status_block. lv_running_stock = ls_curr_stock-speme.
ENDCASE.
ENDIF.
lv_init_flag = abap_false.
ENDIF.
" 역산 코어 공식 적용
" 사용자가 요청한 리포트 범위 내의 일자 데이터만 결과 테이블에 적재
IF ls_daily_pipe-budat BETWEEN s_budat-low AND s_budat-high.
CLEAR gs_report.
gs_report-werks = ls_daily_pipe-werks.
gs_report-matnr = ls_daily_pipe-matnr.
gs_report-budat = ls_daily_pipe-budat.
gs_report-status = ls_daily_pipe-status.
gs_report-s_gr = ls_daily_pipe-gr_qty.
gs_report-s_gi = ls_daily_pipe-gi_qty.
gs_report-s_end = lv_running_stock.
gs_report-s_beg = gs_report-s_end - gs_report-s_gr + gs_report-s_gi.
gs_report-meins = ls_daily_pipe-meins.
INSERT gs_report INTO TABLE gt_report.
ENDIF.
" 다음 과거 일자의 계산을 위해 현재 일자의 변동량을 역산 반영 (입고는 빼고, 출고는 더함)
lv_running_stock = lv_running_stock - ls_daily_pipe-gr_qty + ls_daily_pipe-gi_qty.
ENDLOOP.
" 보고서 순서대로 재정렬 (날짜 오름차순)
SORT gt_report BY werks matnr status budat ASCENDING.
ENDFORM.
*&---------------------------------------------------------------------*
*& Form display_alv
*&---------------------------------------------------------------------*
FORM display_alv.
DATA: lo_alv TYPE REF TO cl_salv_table,
lo_cols TYPE REF TO cl_salv_columns_table.
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = lo_alv
CHANGING
t_table = gt_report ).
" ALV 기능 기본 활성화 (정렬, 필터 등)
lo_alv->get_functions( )->set_all( abap_true ).
" 컬럼 텍스트 최적화 설정
lo_cols = lo_alv->get_columns( ).
lo_cols->set_optimize( abap_true ).
" 개별 컬럼 라벨 커스텀 수정 예시
DATA(lo_col) = lo_cols->get_column( 'STATUS' ).
lo_col->set_long_text( '재고 가용 상태' ).
lo_col->set_medium_text( '재고상태' ).
lo_col = lo_cols->get_column( 'S_BEG' ).
lo_col->set_long_text( '기초재고수량' ).
lo_col->set_medium_text( '기초재고' ).
lo_col = lo_cols->get_column( 'S_GR' ).
lo_col->set_long_text( '당일입고량' ).
lo_col->set_medium_text( '입고량' ).
lo_col = lo_cols->get_column( 'S_GI' ).
lo_col->set_long_text( '당일출고량' ).
lo_col->set_medium_text( '출고량' ).
lo_col = lo_cols->get_column( 'S_END' ).
lo_col->set_long_text( '기말재고수량' ).
lo_col->set_medium_text( '기말재고' ).
" 화면 출력
lo_alv->display( ).
CATCH cx_salv_msg.
MESSAGE 'ALV 출력 중 오류가 발생했습니다.' TYPE 'E'.
ENDTRY.
ENDFORM.'ERP(SAP)' 카테고리의 다른 글
| [SAP] SAP BW(Business Warehouse) 개념 정의 (1) | 2026.05.28 |
|---|---|
| [SAP] SAP GUI 입력 시 띄어쓰기 들어가는 문제 해결 (0) | 2026.05.27 |
| [SAP SD] Sold-to Party / Shipt-to-Party / Bill-to Party / Payer 개념 정리 (0) | 2026.05.20 |
| [SAP] RFC modules, only parameters with passare allowed (0) | 2026.05.18 |
| 📅 SAP 날짜·주차 계산 함수(Function) 완벽 가이드 (0) | 2026.05.06 |
