본문 바로가기
ABAP

[ABAP] Part 1 - 시점별 재고 (CDS View + ALV 리포트)

by 키노s 2025. 7. 5.

실제로 SAP 유지보수를 하다면 과거 재고를 알고 싶어 하는 니즈가 많습니다.

 

여러분들도 한번쯤은 요청을 받은 내용일 것이라 생각합니다. 

이런 요구사항이 있을때는 제일 먼저 이야기 하는것인 MB5B가 있습니다. 스탠다드 프로그램이죠~

이것을 소개해 드리면 "이미 이런것이 있었군요. 라고 하며 확인 해 보겠다고 합니다."

하지만 십중팔구는 "좋긴한데요... 제가 원하는것과는 잘 안맞는것 같아요." 라고 답변이 옵니다.

 

물론 MB5B 좋은 레포트긴 하지만 아마도 활용도는 떨어집니다.

말이 길었지만 아래와 같은 이야기를 많이 할것입니다.

 

  • '기초 재고'의 의미: MB5B의 '기초 재고(Opening Stock)'는 조회 시작일의 00:00시 재고가 아니라, 전일 마감 재고를 의미합니다. 예를 들어 7월 5일을 조회 시작일로 지정하면, 리포트의 기초 재고는 7월 4일 마감 시점의 재고입니다.
  • 배치(Batch) 재고의 한계: 배치 관리 품목의 경우, 과거 시점의 특정 배치 번호 재고를 정확히 추적하기 어렵습니다. 
  • 특수 재고(Special Stock) 조회: 공급업체 위탁 재고(Consignment Stock), 프로젝트 재고(Project Stock) 등 특수 재고 유형을 선택해서 조회할 수 있지만, 복잡한 이동 거래가 얽혀 있는 경우 데이터의 정확성이 떨어지거나 사용자가 의도한 결과와 다를 수 있습니다.
  • 사용하기 불편한점, 원하는 것만 보여야 하는데 조회 조건부터 복잡합니다.

그래서 이번 내용은 CBO개발을 해 보겠습니다.

 

먼저, ABAP Development Tools (ADT) for Eclipse를 사용하여 CDS 뷰를 만드는 방법입니다.

 

 

1. 새 데이터 정의(DDL) 파일 생성

    • 왼쪽 Project Explorer 뷰에서 CDS 뷰를 생성할 패키지(Package)를 찾으세요.
    • 해당 패키지에 마우스 오른쪽 버튼을 클릭한 후, [ New → Other ABAP Repository Object ] 를 선택합니다.

    • Core Data Services 폴더를 열고 [ Data Definition ]을 선택한 후 Next를 클릭합니다.

  • Name(CDS 뷰 이름, ZCDS_Material_Stock)과 Description(시점별 재고)을 입력하고 Finish를 클릭합니다.
    템플릿 코드가 자동으로 생성됩니다.

2. CDS 뷰 코드 작성

 

  • 핵심 Annotation 추가:
    • @AbapCatalog.sqlViewName: SAP 시스템 내부(SE11)에서 사용할 SQL 뷰 이름을 지정합니다. (필수)
    • @AbapCatalog.compiler.compareFilter: true: CDS 뷰와 ABAP 프로그램 간의 필터 조건 일치 여부를 컴파일러가 확인합니다
    • @AbapCatalog.preserveKey: true: CDS 뷰의 키 필드 속성을 유지하고 전파합니다.
    • @AccessControl.authorizationCheck: #NOT_REQUIRED: 이 뷰에 접근할 때 자동 권한 체크를 하지 않습니다.
    • @EndUserText.label: '시점별 재고': 사용자에게 보이는 CDS 뷰의 이름입니다.
    • @Metadata.ignorePropagatedAnnotations: true: 하위 객체에서 자동으로 전파되는 메타데이터 어노테이션을 무시합니다.
  • 뷰 정의 (DEFINE VIEW):
    • DEFINE VIEW [뷰 이름] AS SELECT FROM [데이터 소스]: 어떤 뷰를 만들지, 그리고 어떤 테이블이나 다른 CDS 뷰를 조회할지 정의합니다.
  • 필드 목록 작성:
    • { } 안에 가져올 필드들을 쉼표(,)로 구분하여 나열합니다.
    • AS 키워드를 사용해 필드의 별칭(Alias)을 지정할 수 있습니다.
  • CDS View
@AbapCatalog.sqlViewName: 'Z_V_MAT_STOCK'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '시점별 재고'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.representativeKey: 'Material'

DEFINE VIEW ZCDS_MATERIAL_STOCK
  WITH PARAMETERS
    P_KeyDate : abap.dats, // 기준일 파라미터
    P_Language: spras      // 언어 키 파라미터

AS SELECT FROM C_MaterialStockAtPostingDate(P_KeyDate: $parameters.P_KeyDate) AS Stock
  -- 자재 기본 정보 연결
  ASSOCIATION [1..1] TO I_Material AS _Material ON $projection.Material = _Material.Material
  -- 자재 텍스트(내역) 연결
  ASSOCIATION [0..1] TO I_MaterialText AS _MaterialText ON $projection.Material = _MaterialText.Material
                                                       AND _MaterialText.Language = $parameters.P_Language
  -- 자재 평가(단가) 정보 연결
  ASSOCIATION [0..1] TO I_MaterialValuation AS _Valuation ON $projection.Material = _Valuation.Material
                                                        AND $projection.Plant    = _Valuation.ValuationArea
{
  key Stock.Material,
  key Stock.Plant,
  key Stock.StorageLocation,
  key Stock.Batch,

  _Material.OldMaterialNumber,
  _MaterialText.MaterialName,
  Stock.StockQuantity,
  _Valuation.MovingAveragePrice,
  CAST(Stock.StockQuantity * _Valuation.MovingAveragePrice AS abap.curr(15,2)) AS StockValue,
  _Valuation.Currency,
  cast('' as lgpla) as StorageBin,
  cast('' as char30) as BatchCharacteristic
}
WHERE Stock.StockQuantity <> 0

 

스탠다드로 제공되는 CDS뷰를 활용해서 만들어놨습니다.

 

3. ALV 리포트 프로그램 개발 (ABAP Source Code)

 

레포트에 코드로 대신하겠습니다.

*&---------------------------------------------------------------------*
*& Report Z_HISTORICAL_STOCK
*& @description 시점별 재고 조회 
*&---------------------------------------------------------------------*
REPORT z_historical_stock.

*--- 조회 조건 정의 (Selection Screen) ---*
PARAMETERS:
  p_keydt TYPE datum OBLIGATORY. "기준일

SELECT-OPTIONS:
  s_werks FOR z_i_historical_stock-Plant OBLIGATORY, "플랜트
  s_matnr FOR z_i_historical_stock-Material,         "자재번호
  s_lgort FOR z_i_historical_stock-StorageLocation,  "저장위치
  s_batch FOR z_i_historical_stock-Batch.            "배치번호 (수정)

*--- 메인 로직 시작 ---*
START-OF-SELECTION.

  " 1. 조회 조건 확인
  IF p_keydt IS INITIAL OR s_werks IS INITIAL.
    MESSAGE '기준일과 플랜트는 필수 입력 항목입니다.' TYPE 'S' DISPLAY LIKE 'E'.
    RETURN.
  ENDIF.

  " 2. CDS 뷰에서 데이터 조회
  SELECT *
    FROM ZCDS_MATERIAL_STOCK( p_keydate = @p_keydt, p_language = @sy-langu )
    WHERE
      Plant           IN @s_werks AND
      Material        IN @s_matnr AND
      StorageLocation IN @s_lgort AND
      Batch           IN @s_batch  " WHERE 조건에 배치번호 추가
    INTO TABLE @DATA(lt_stock_data).

  " 3. 조회 결과 확인 및 ALV 출력
  IF lt_stock_data IS INITIAL.
    MESSAGE '조회된 데이터가 없습니다.' TYPE 'S'.
    RETURN.
  ENDIF.

  TRY.
      " ALV 객체 생성 및 데이터 전달
      cl_salv_table=>factory(
        IMPORTING
          r_salv_table = DATA(lo_alv)
        CHANGING
          t_table      = lt_stock_data ).

      " ALV 기능 설정
      DATA(lo_functions) = lo_alv->get_functions( ).
      lo_functions->set_all( abap_true ).

      " ALV 컬럼 헤더 텍스트 최적화
      DATA(lo_columns) = lo_alv->get_columns( ).
      lo_columns->set_optimize( abap_true ).

      " ALV 화면에 출력
      lo_alv->display( ).

    CATCH cx_salv_msg INTO DATA(lx_salv_msg).
      MESSAGE lx_salv_msg->get_text( ) TYPE 'E'.
  ENDTRY.

 

 

아마 사용중인 HANA 라도 버전에 따라 제공되고 안되는 CDS View가 존재합니다.

위에 사용한 뷰가 없다면 만들어서 사용하면 됩니다.

 

코드만 간단히 소개 해 드릴겠습니다.

사용 할려는 뷰에 필드가 없거나 맞지 않아서  Z_I_MBEWDATA, Z_I_PLANTCURRENCY 2개를 만들어서 조합했습니다.

@AbapCatalog.sqlViewName: 'Z_V_MAT_STOCK'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '시점별 재고'
@Metadata.ignorePropagatedAnnotations: true
@ObjectModel.representativeKey: 'Material'

define view ZCDS_MATERIAL_STOCK
  with parameters
    P_KeyDate : abap.dats,
    P_Language: spras

as select from ZCDS_MATERIAL_STOCK_TF( P_Client: $session.client, P_KeyDate: $parameters.P_KeyDate ) as Stock

  association [0..1] to I_MaterialText    as _MaterialText on $projection.Material = _MaterialText.Material
                                                          and _MaterialText.Language = $parameters.P_Language
  association [0..1] to mara              as _Mara          on $projection.Material = _Mara.matnr
  association [0..1] to Z_I_MbewData      as _MbewData      on $projection.Material = _MbewData.matnr
                                                          and $projection.Plant    = _MbewData.bwkey
  association [0..1] to Z_I_PlantCurrency as _CurrencyInfo  on $projection.Plant = _CurrencyInfo.Plant

{
  key Stock.Material,
  key Stock.Plant,
  key Stock.StorageLocation,
  key Stock.Batch,

  _Mara.meins         as MaterialBaseUnit,
  _CurrencyInfo.Currency,

  _Mara.bismt         as OldMaterialNumber,
  _MaterialText.MaterialName,

  @Semantics.quantity.unitOfMeasure: 'MaterialBaseUnit'
  Stock.StockQuantity,

  @Semantics.amount.currencyCode: 'Currency'
  _MbewData.verpr         as MovingAveragePrice,

  @Semantics.amount.currencyCode: 'Currency'
  (Stock.StockQuantity * _MbewData.verpr) as StockValue,

  cast('' as lgpla)    as StorageBin,
  cast('' as char30)   as BatchCharacteristic
}
where Stock.StockQuantity <> 0

 

Z_I_MBEWDATA, Z_I_PLANTCURRENCY 

@AbapCatalog.sqlViewName: 'Z_V_MBEW_DATA'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'MBEW 필드 조회용'

define view Z_I_MbewData as select from mbew {
    // 키 필드
    key matnr, // 자재번호
    key bwkey, // 평가 영역 (플랜트)
    
    // 필요한 데이터 필드
    verpr      // 이동 평균 단가
}

@AbapCatalog.sqlViewName: 'Z_V_PLANT_CURR'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '플랜트-통화 매핑 뷰'

define view Z_I_PlantCurrency as select from t001k

  association [0..1] to t001 as _CompanyCode on t001k.bukrs = _CompanyCode.bukrs
{
  key t001k.bwkey as Plant,
      _CompanyCode.waers as Currency
}

 

나중에 CDS뷰를 활용하는 법과 생성하는 법을 따로 올리겠습니다.

 

감사합니다.