본문 바로가기
ABAP

[ABAP] Part 2 - 시점별 재고 ( 전통적인ABAP + ALV 리포트)

by 키노s 2025. 7. 5.

이번엔 전통적인 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 테이블 사용

주요 구현 단계

  1. 조회 조건(Selection Screen) 정의: 기준일, 플랜트, 자재 등 조회 조건을 받습니다.
  2. 직전 월 마감 재고 조회: 기준일의 직전 월을 계산하여 MARDH, MCHBH 테이블에서 마감 재고를 가져와 기초 재고로 설정합니다.
  3. 해당 월 변동 내역 조회: 기준월 1일부터 기준일까지의 입출고 내역만 NSDM_V_MSEG 뷰에서 조회합니다.
  4. 최종 재고 계산: 기초 재고에 변동 내역을 더하고 빼서 기준일의 재고를 확정합니다.
  5. 마스터 데이터 추가: 계산된 결과에 자재명, 단가, 저장 Bin 등 추가 정보를 조합합니다.
  6. 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로 분리하지는 않았습니다.