이번엔 전통적인 ABAP로 구현해 보겠습니다.
특정 시점의 재고를 확인하기 위해서는 처음부터 모든 전표의 입고/출고를 계산하면 됩니다.
문제는 모든 전표를 계산하기에는 데이터가 많을수록 성능에 문제가 발생할 것입니다.
그래서 월 마감 재고와 알고자하는 날짜의 1일~ 검색일까지의 변동분을 전표 테이블에서 계산하면 됩니다.
이전일 PART 1 CDS VIEW 구현
2025.07.05 - [ABAP] - [ABAP] Part 1 - 시점별 재고 (CDS View + ALV 리포트)
핵심 로직: 월 마감 재고 + 해당 월 변동분 계산
단순히 과거부터 모든 자재 이동 내역(MSEG)을 읽어와 합산하면 데이터가 많을 경우 엄청난 성능 저하를 유발합니다. 가장 효율적인 방법은 아래와 같습니다.
기준일 재고 = (조회하려는 달의 직전 월말 재고) + (해당 월 1일부터 기준일까지의 변동 내역)
이 방식을 사용하면 읽어야 할 데이터의 양이 대폭 줄어들어 성능을 크게 향상시킬 수 있습니다.
- 직전 월말 재고: MARDH(일반 재고), MCHBH(배치 재고) 테이블 사용
- 해당 월 변동 내역: NSDM_V_MSEG (S/4HANA 호환성 뷰) 또는 MSEG+MKPF 테이블 사용
주요 구현 단계
- 조회 조건(Selection Screen) 정의: 기준일, 플랜트, 자재 등 조회 조건을 받습니다.
- 직전 월 마감 재고 조회: 기준일의 직전 월을 계산하여 MARDH, MCHBH 테이블에서 마감 재고를 가져와 기초 재고로 설정합니다.
- 해당 월 변동 내역 조회: 기준월 1일부터 기준일까지의 입출고 내역만 NSDM_V_MSEG 뷰에서 조회합니다.
- 최종 재고 계산: 기초 재고에 변동 내역을 더하고 빼서 기준일의 재고를 확정합니다.
- 마스터 데이터 추가: 계산된 결과에 자재명, 단가, 저장 Bin 등 추가 정보를 조합합니다.
- ALV 출력: 최종 결과를 ALV 그리드로 사용자에게 보여줍니다.
전체 소스 코드 (ZMMR1531)
아래는 위 로직을 모두 구현한 최종 프로그램 코드입니다.
*&---------------------------------------------------------------------*
*& Report zmmr1531
*& @description 특정 시점 재고 조회 (최종 완성)
*&---------------------------------------------------------------------*
REPORT zmmr1531.
TABLES: mard, mara, mchb.
*----------------------------------------------------------------------*
* 1. 데이터 타입 및 구조 정의
*----------------------------------------------------------------------*
TYPES: BEGIN OF ty_hist_stock,
matnr TYPE matnr,
werks TYPE werks_d,
lgort TYPE lgort_d,
charg TYPE charg_d,
menge TYPE labst,
END OF ty_hist_stock.
TYPES: BEGIN OF ty_movement,
matnr TYPE matnr,
werks TYPE werks_d,
lgort TYPE lgort_d,
charg TYPE charg_d,
menge TYPE menge_d,
shkzg TYPE shkzg,
END OF ty_movement.
TYPES: BEGIN OF ty_mbew,
matnr TYPE matnr,
bwkey TYPE bwkey,
verpr TYPE verpr,
END OF ty_mbew.
TYPES: BEGIN OF ty_mara_local,
matnr TYPE mara-matnr,
bismt TYPE mara-bismt,
END OF ty_mara_local.
TYPES: BEGIN OF ty_makt_local,
matnr TYPE makt-matnr,
maktx TYPE makt-maktx,
END OF ty_makt_local.
TYPES: BEGIN OF ty_t001k_local,
bwkey TYPE t001k-bwkey,
bukrs TYPE t001k-bukrs,
END OF ty_t001k_local.
TYPES: BEGIN OF ty_t001_local,
bukrs TYPE t001-bukrs,
waers TYPE t001-waers,
END OF ty_t001_local.
TYPES: BEGIN OF ty_mard_bin,
matnr TYPE mard-matnr,
werks TYPE mard-werks,
lgort TYPE mard-lgort,
lgpbe TYPE mard-lgpbe,
END OF ty_mard_bin.
TYPES: BEGIN OF ty_result,
matnr TYPE matnr,
bismt TYPE bismt,
maktx TYPE maktx,
charg TYPE charg_d,
lgpbe TYPE mard-lgpbe, " 저장 BIN
menge TYPE labst,
verpr TYPE verpr,
stock_value TYPE verpr,
waers TYPE waers,
werks TYPE werks_d,
lgort TYPE lgort_d,
END OF ty_result.
DATA: lt_result TYPE STANDARD TABLE OF ty_result,
ls_result TYPE ty_result,
lt_movements TYPE STANDARD TABLE OF ty_movement,
lt_mara TYPE STANDARD TABLE OF ty_mara_local,
lt_makt TYPE STANDARD TABLE OF ty_makt_local,
lt_mbew TYPE STANDARD TABLE OF ty_mbew,
lt_t001k TYPE STANDARD TABLE OF ty_t001k_local,
lt_t001 TYPE STANDARD TABLE OF ty_t001_local,
lt_mardh_stock TYPE STANDARD TABLE OF ty_hist_stock,
lt_mchbh_stock TYPE STANDARD TABLE OF ty_hist_stock,
lt_mard_bins TYPE STANDARD TABLE OF ty_mard_bin,
ls_mardh TYPE ty_hist_stock,
ls_mchbh TYPE ty_hist_stock,
ls_move TYPE ty_movement,
ls_stock TYPE ty_result,
ls_mara TYPE ty_mara_local,
ls_makt TYPE ty_makt_local,
ls_mbew TYPE ty_mbew,
ls_t001k TYPE ty_t001k_local,
ls_t001 TYPE ty_t001_local,
ls_mard_bin TYPE ty_mard_bin.
DATA: lo_alv TYPE REF TO cl_salv_table,
lx_salv_msg TYPE REF TO cx_salv_msg.
DATA: gv_comment1 TYPE c LENGTH 70,
gv_comment2 TYPE c LENGTH 70.
*----------------------------------------------------------------------*
* 2. 조회 조건 정의
*----------------------------------------------------------------------*
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE TEXT-001.
PARAMETERS:
p_keydt TYPE datum OBLIGATORY DEFAULT sy-datum.
SELECT-OPTIONS:
s_werks FOR mard-werks OBLIGATORY,
s_matnr FOR mara-matnr,
s_lgort FOR mard-lgort,
s_batch FOR mchb-charg,
s_lgpbe FOR mard-lgpbe.
" [수정] Zero 재고 포함 체크박스 (기본값: 언체크)
PARAMETERS p_zero TYPE c AS CHECKBOX.
SELECTION-SCREEN END OF BLOCK b1.
*SELECTION-SCREEN ULINE.
SELECTION-SCREEN COMMENT /1(70) gv_text1.
SELECTION-SCREEN COMMENT /1(70) gv_test2.
*&--------------------------------------------------------------------*
*& 화면 초기값 설정
*&--------------------------------------------------------------------*
INITIALIZATION.
s_werks-sign = 'I'.
s_werks-option = 'EQ'.
s_werks-low = '3782'.
APPEND s_werks.
s_werks-low = 'US1A'.
APPEND s_werks.
" [수정] 안내 텍스트 내용 변경
gv_text1 = '※ Zero 재고 포함 선택 시, 조회 속도가 느려질 수 있습니다.'.
gv_test2 = '※ 저장 BIN은 현재 위치로 조회됩니다.'.
*----------------------------------------------------------------------*
* 3. 데이터 조회 및 처리
*----------------------------------------------------------------------*
START-OF-SELECTION.
DATA: lv_key_year TYPE gjahr,
lv_key_month TYPE monat,
lv_prev_year TYPE gjahr,
lv_prev_month TYPE monat,
lv_month_start TYPE datum.
lv_key_year = p_keydt(4).
lv_key_month = p_keydt+4(2).
lv_prev_year = lv_key_year.
lv_prev_month = lv_key_month - 1.
IF lv_prev_month = 0.
lv_prev_month = 12.
lv_prev_year = lv_prev_year - 1.
ENDIF.
" 1. 직전 월 마감 재고 조회
SELECT matnr, werks, lgort, ' ' AS charg, labst AS menge
FROM mardh
INTO TABLE @lt_mardh_stock
WHERE matnr IN @s_matnr
AND werks IN @s_werks
AND lgort IN @s_lgort
AND lfgja = @lv_prev_year
AND lfmon = @lv_prev_month.
SELECT matnr, werks, lgort, charg, clabs AS menge
FROM mchbh
INTO TABLE @lt_mchbh_stock
WHERE matnr IN @s_matnr
AND werks IN @s_werks
AND lgort IN @s_lgort
AND charg IN @s_batch
AND lfgja = @lv_prev_year
AND lfmon = @lv_prev_month.
LOOP AT lt_mardh_stock INTO ls_mardh.
CLEAR ls_result.
MOVE-CORRESPONDING ls_mardh TO ls_result.
APPEND ls_result TO lt_result.
ENDLOOP.
LOOP AT lt_mchbh_stock INTO ls_mchbh.
CLEAR ls_result.
MOVE-CORRESPONDING ls_mchbh TO ls_result.
APPEND ls_result TO lt_result.
ENDLOOP.
" 2. 해당 월의 변동 내역만 조회
CONCATENATE lv_key_year lv_key_month '01' INTO lv_month_start.
SELECT matnr, werks, lgort, charg, menge, shkzg
FROM nsdm_v_mseg
INTO TABLE @lt_movements
WHERE matnr IN @s_matnr
AND werks IN @s_werks
AND lgort IN @s_lgort
AND charg IN @s_batch
AND budat_mkpf BETWEEN @lv_month_start AND @p_keydt.
" 3. 마감 재고에 변동 내역 합산
LOOP AT lt_movements INTO ls_move.
CLEAR ls_stock.
MOVE-CORRESPONDING ls_move TO ls_stock.
IF ls_move-shkzg = 'H'.
ls_stock-menge = -1 * ls_move-menge.
ELSE.
ls_stock-menge = ls_move-menge.
ENDIF.
COLLECT ls_stock INTO lt_result.
ENDLOOP.
" [수정] 'Zero 재고 포함'이 체크되지 않았을 때만(기본값) 제로 재고 제거
IF p_zero IS INITIAL. " p_zero <> 'X' 와 동일
DELETE lt_result WHERE menge IS INITIAL OR menge = 0.
ENDIF.
IF lt_result IS INITIAL.
MESSAGE '조회된 데이터가 없습니다.' TYPE 'S'.
RETURN.
ENDIF.
*----------------------------------------------------------------------*
* 4. 추가 마스터 데이터 조회
*----------------------------------------------------------------------*
SORT lt_result BY matnr werks.
IF lt_result IS NOT INITIAL.
SELECT matnr, bismt
FROM mara
INTO TABLE @lt_mara
FOR ALL ENTRIES IN @lt_result
WHERE matnr = @lt_result-matnr.
IF sy-subrc = 0.
SORT lt_mara BY matnr.
ENDIF.
SELECT matnr, maktx
FROM makt
INTO TABLE @lt_makt
FOR ALL ENTRIES IN @lt_result
WHERE matnr = @lt_result-matnr AND spras = @sy-langu.
IF sy-subrc = 0.
SORT lt_makt BY matnr.
ENDIF.
SELECT matnr, bwkey, verpr
FROM mbew
INTO TABLE @lt_mbew
FOR ALL ENTRIES IN @lt_result
WHERE matnr = @lt_result-matnr AND bwkey = @lt_result-werks.
IF sy-subrc = 0.
SORT lt_mbew BY matnr bwkey.
ENDIF.
SELECT bwkey, bukrs
FROM t001k
INTO TABLE @lt_t001k
FOR ALL ENTRIES IN @lt_result
WHERE bwkey = @lt_result-werks.
IF sy-subrc = 0.
SORT lt_t001k BY bwkey.
ENDIF.
IF lt_t001k IS NOT INITIAL.
SELECT bukrs, waers
FROM t001
INTO TABLE @lt_t001
FOR ALL ENTRIES IN @lt_t001k
WHERE bukrs = @lt_t001k-bukrs.
IF sy-subrc = 0.
SORT lt_t001 BY bukrs.
ENDIF.
ENDIF.
ENDIF.
*----------------------------------------------------------------------*
* 4.5. 저장 BIN 정보 조회
*----------------------------------------------------------------------*
IF lt_result IS NOT INITIAL.
DATA(lt_result_keys) = lt_result.
SORT lt_result_keys BY matnr werks lgort.
DELETE ADJACENT DUPLICATES FROM lt_result_keys COMPARING matnr werks lgort.
SELECT matnr, werks, lgort, lgpbe
FROM mard
INTO TABLE @lt_mard_bins
FOR ALL ENTRIES IN @lt_result_keys
WHERE matnr = @lt_result_keys-matnr
AND werks = @lt_result_keys-werks
AND lgort = @lt_result_keys-lgort.
IF sy-subrc = 0.
SORT lt_mard_bins BY matnr werks lgort.
ENDIF.
ENDIF.
*----------------------------------------------------------------------*
* 5. 최종 데이터 조합 및 ALV 출력
*----------------------------------------------------------------------*
FIELD-SYMBOLS: <fs_result> TYPE ty_result.
LOOP AT lt_result ASSIGNING <fs_result>.
READ TABLE lt_mara INTO ls_mara WITH KEY matnr = <fs_result>-matnr BINARY SEARCH.
IF sy-subrc = 0.
<fs_result>-bismt = ls_mara-bismt.
ENDIF.
READ TABLE lt_makt INTO ls_makt WITH KEY matnr = <fs_result>-matnr BINARY SEARCH.
IF sy-subrc = 0.
<fs_result>-maktx = ls_makt-maktx.
ENDIF.
READ TABLE lt_mbew INTO ls_mbew WITH KEY matnr = <fs_result>-matnr bwkey = <fs_result>-werks BINARY SEARCH.
IF sy-subrc = 0.
<fs_result>-verpr = ls_mbew-verpr.
<fs_result>-stock_value = <fs_result>-menge * ls_mbew-verpr.
ENDIF.
READ TABLE lt_t001k INTO ls_t001k WITH KEY bwkey = <fs_result>-werks BINARY SEARCH.
IF sy-subrc = 0.
READ TABLE lt_t001 INTO ls_t001 WITH KEY bukrs = ls_t001k-bukrs BINARY SEARCH.
IF sy-subrc = 0.
<fs_result>-waers = ls_t001-waers.
ENDIF.
ENDIF.
READ TABLE lt_mard_bins INTO ls_mard_bin WITH KEY matnr = <fs_result>-matnr
werks = <fs_result>-werks
lgort = <fs_result>-lgort
BINARY SEARCH.
IF sy-subrc = 0.
<fs_result>-lgpbe = ls_mard_bin-lgpbe.
ENDIF.
ENDLOOP.
" 저장 BIN 조회 조건 필터링
IF s_lgpbe[] IS NOT INITIAL.
DELETE lt_result WHERE lgpbe NOT IN s_lgpbe.
ENDIF.
TRY.
cl_salv_table=>factory( IMPORTING r_salv_table = lo_alv CHANGING t_table = lt_result ).
lo_alv->get_functions( )->set_all( abap_true ).
lo_alv->get_columns( )->set_optimize( abap_true ).
lo_alv->display( ).
CATCH cx_salv_msg INTO lx_salv_msg.
MESSAGE lx_salv_msg->get_text( ) TYPE 'E'.
ENDTRY.
참고로, ABAP 코드는 표기 편하기 위해 include로 분리하지는 않았습니다.
'ABAP' 카테고리의 다른 글
[ABAP] 애플리케이션 로그 마스터하기: BAL과 CBO 테이블 로깅, 언제 무엇을 써야 할까? (0) | 2025.07.13 |
---|---|
[ABAP] 출력 제어(Output Control) - Inbound Delivery 및 Invoice 생성하기 (0) | 2025.07.10 |
[ABAP] Part 1 - 시점별 재고 (CDS View + ALV 리포트) (0) | 2025.07.05 |
[ABAP] 표준 자재 검색(F4)에 나만의 탭 추가하기 (Append Search Help) (0) | 2025.07.02 |
[ABAP] 재고는 있는데 재고 부족? BAPI 이슈 해결 (0) | 2025.06.29 |