ABAP

자산 이력 조회 실수 3가지 — ANEK 말고 이렇게 #shorts #SAP #ABAP

▶ YouTube에서 보기

개요 및 작성 의도

SAP S/4HANA의 자산 회계(FI-AA) 모듈에서 자산 거래 이력은 감사, 결산, 자산 가치 분석의 핵심 데이터입니다. 전통적으로 ABAP 개발자는 ANEK(자산 거래 헤더) 및 ANEP(자산 거래 라인)을 직접 조회해 왔지만, S/4HANA 환경에서는 가상 데이터 모델(VDM, Virtual Data Model) 기반의 CDS 뷰 I_AssetTransaction을 통한 접근이 권장됩니다. 이 글은 ABAP 개발자가 I_AssetTransaction CDS 뷰를 활용하여 자산 취득·이관·매각·감가상각 등 다양한 거래 유형(Movement Type)을 효율적으로 조회하는 방법을 다룹니다.

  • I_AssetTransaction CDS 뷰의 구조와 ANEK 매핑 관계 이해
  • 거래 유형(Asset Transaction Type)별 필터링 기법 습득
  • ANEK 직접 조회 대비 VDM 접근의 장단점 비교
  • 결산/감사 시나리오에서의 실전 ABAP 코드 패턴 작성
  • 성능 최적화 및 권한 체크 통합 패턴 적용

사전에 알아두면 좋은 내용

이 글은 ABAP 7.50 이상의 Open SQL/ABAP SQL 문법과 CDS 뷰 기본 구조(define view, association, annotation)에 익숙한 개발자를 대상으로 합니다. FI-AA의 기본 마스터 테이블(ANLA, ANLB, ANLC)과 거래 테이블(ANEK, ANEP, ANEA)의 역할 분담, 그리고 회사 코드(BUKRS)·자산 번호(ANLN1/ANLN2) 키 구조에 대한 이해가 선행되어야 합니다.

환경 및 사용 도구

이 글의 코드 예제는 다음 환경을 기준으로 검증되었습니다.

  • SAP S/4HANA 2022 (OP 2022, FPS02 이상) — Cloud 에디션의 경우 Private Edition 기준
  • ABAP Development Tools (ADT) for Eclipse 3.36 이상
  • ABAP 언어 버전: ABAP for Cloud Development 또는 Standard ABAP 7.57
  • FI-AA 모듈 활성화 및 New Asset Accounting (신 자산회계) 라이선스
  • 권한 객체: F_ANLA_BUK (회사코드별), S_RFC (RFC 호출 시)

S/4HANA 1909 이하 또는 ECC 환경에서는 I_AssetTransaction이 제공되지 않거나 필드 구성이 다를 수 있으므로, 해당 릴리스의 CDS View Browser(트랜잭션 코드 RSRTS_ODP_DIS 또는 ADT의 CDS Repository)에서 가용성을 먼저 확인하는 것이 좋습니다.

핵심 개념과 데이터 흐름

자산 회계의 거래 데이터는 헤더-라인 구조로 저장됩니다. ANEK은 거래 헤더(전표 번호, 전기일, 거래 유형, 참조 정보)를 담고, ANEP은 자산별 라인 아이템(금액, 감가상각 영역)을 담습니다. 이 둘은 회사코드(BUKRS), 자산번호(ANLN1, ANLN2), 회계연도(GJAHR), 거래번호(LNRAN)로 연결됩니다.

I_AssetTransaction은 ANEK을 기본 소스로 하여 자산 마스터(ANLA), 거래 유형 텍스트(TABWT) 등과의 association을 미리 정의해 둔 VDM 인터페이스 뷰입니다. 비유하자면 ANEK이 원목이라면, I_AssetTransaction은 이미 가공되어 표면이 다듬어지고 라벨이 붙은 가구입니다. 라벨(annotation)에는 단위, 통화, 의미(semantics) 정보가 포함되어 있어 Fiori 앱이나 Analytical Query에서 추가 가공 없이 사용할 수 있습니다.

I_ 접두사 뷰는 인터페이스(Interface) 뷰로, 안정적인 계약(stable contract)을 제공합니다. 반면 P_ 또는 C_ 접두사 뷰는 프라이빗 또는 컨슈머 뷰로, 상위 계층에서 비즈니스 로직을 추가합니다.

주요 거래 유형(ANBWA, Asset Transaction Type)은 다음과 같이 분류됩니다.

유형 코드의미대표 트랜잭션
100~199외부 취득 (Acquisition)F-90, ABZON
200~299매각 (Retirement)F-92, ABAON
300~399내부 이관 (Transfer)ABUMN
600~699감가상각 (Depreciation)AFAB
700~799가치 조정 (Write-up)ABZU

실제 운영 환경에서는 거래 유형 그룹(BWASL)과 거래 유형(ANBWA)을 함께 사용하여 의미적으로 분류합니다. I_AssetTransaction은 이러한 분류 필드를 모두 노출하며, 추가로 통화 변환·회계연도 변환 같은 헬퍼 association도 제공합니다.

실전 예제 1단계 — 기본 거래 이력 조회

가장 단순한 시나리오부터 시작합니다. 특정 회사코드(예: 1010)와 회계연도(2026)의 모든 자산 거래를 조회하여 거래 유형별로 정렬합니다.

REPORT z_asset_tx_basic.

DATA: lt_tx TYPE TABLE OF i_assettransaction,
      ls_tx TYPE i_assettransaction.

SELECT FROM i_assettransaction
  FIELDS companycode,
         masterfixedasset,
         fixedasset,
         fiscalyear,
         assettransactiontype,
         assetvaluedate,
         documentdate,
         referencedocumentitem
  WHERE companycode    = '1010'
    AND fiscalyear     = '2026'
  ORDER BY assettransactiontype, assetvaluedate
  INTO TABLE @lt_tx.

LOOP AT lt_tx INTO ls_tx.
  WRITE: / ls_tx-masterfixedasset,
           ls_tx-assettransactiontype,
           ls_tx-assetvaluedate.
ENDLOOP.

여기서 주목할 점은 필드명이 ANEK의 기술적 이름(BUKRS, ANLN1, GJAHR)이 아닌 의미 기반의 카멜케이스(CompanyCode, MasterFixedAsset, FiscalYear)로 노출된다는 것입니다. 이는 VDM의 명명 규칙으로, 다국어 라벨과 의미적 일관성을 보장합니다.

실전 예제 2단계 — 거래 유형별 분기 및 예외 처리

실무에서는 특정 거래 유형만 필터링하거나, 자산 마스터 정보를 함께 조회하는 경우가 많습니다. 아래는 매각(Retirement) 거래만 추출하고, 자산 마스터의 자산 클래스(Asset Class)와 함께 보여주는 예제입니다. 추가로 예외 상황(데이터 없음, 권한 없음)에 대한 로깅을 포함합니다.

CLASS zcl_asset_retirement_reader DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    TYPES: BEGIN OF ty_retire_row,
             company_code      TYPE bukrs,
             asset_number      TYPE anln1,
             sub_asset         TYPE anln2,
             asset_class       TYPE anlkl,
             tx_type           TYPE anbwa,
             value_date        TYPE bldat,
             reference_doc     TYPE belnr_d,
             posted_amount     TYPE dmbtr,
           END OF ty_retire_row,
           tt_retire_row TYPE STANDARD TABLE OF ty_retire_row WITH EMPTY KEY.

    METHODS read_retirements
      IMPORTING iv_bukrs    TYPE bukrs
                iv_year     TYPE gjahr
      RETURNING VALUE(rt_rows) TYPE tt_retire_row
      RAISING   cx_sy_open_sql_db.
ENDCLASS.

CLASS zcl_asset_retirement_reader IMPLEMENTATION.
  METHOD read_retirements.
    TRY.
        SELECT FROM i_assettransaction AS tx
          INNER JOIN i_fixedasset AS fa
            ON  tx~companycode     = fa~companycode
            AND tx~masterfixedasset = fa~masterfixedasset
            AND tx~fixedasset       = fa~fixedasset
          FIELDS tx~companycode        AS company_code,
                 tx~masterfixedasset   AS asset_number,
                 tx~fixedasset         AS sub_asset,
                 fa~assetclass         AS asset_class,
                 tx~assettransactiontype AS tx_type,
                 tx~assetvaluedate     AS value_date,
                 tx~accountingdocument AS reference_doc,
                 tx~assetpostingamountincocodecrcy AS posted_amount
          WHERE tx~companycode = @iv_bukrs
            AND tx~fiscalyear  = @iv_year
            AND tx~assettransactiontype BETWEEN '200' AND '299'
          INTO CORRESPONDING FIELDS OF TABLE @rt_rows.

        IF sy-subrc <> 0.
          MESSAGE i001(zfi_aa) WITH iv_bukrs iv_year.
        ENDIF.

      CATCH cx_sy_open_sql_db INTO DATA(lx_db).
        " 권한 또는 DB 예외 로깅
        cl_bali_message_setter=>create(
          severity = if_bali_constants=>c_severity_error
          text     = lx_db->get_text( )
        ).
        RAISE EXCEPTION lx_db.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

이 예제에서 거래 유형 200~299 구간을 BETWEEN으로 필터링한 부분은 매각 그룹 전체를 한 번에 처리하기 위한 패턴입니다. i_fixedasset과의 JOIN으로 자산 클래스를 함께 가져와 후속 분기 처리(예: 부동산은 별도 보고서 양식)에 활용할 수 있습니다.

실전 예제 3단계 — 프로덕션 성능 최적화 및 단위 테스트

대량 데이터를 다루는 결산 배치에서는 페이징, 패키지 처리, 그리고 권한 통합이 필수입니다. 다음은 1만 건 이상의 자산을 처리하는 야간 배치용 패턴입니다.

CLASS zcl_asset_tx_batch DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    CONSTANTS c_package_size TYPE i VALUE 5000.

    METHODS process_period
      IMPORTING iv_bukrs       TYPE bukrs
                iv_year        TYPE gjahr
                iv_period_from TYPE poper
                iv_period_to   TYPE poper.
  PRIVATE SECTION.
    METHODS log
      IMPORTING iv_msg TYPE string.
ENDCLASS.

CLASS zcl_asset_tx_batch IMPLEMENTATION.
  METHOD process_period.
    DATA lv_total TYPE i.

    " 1. 권한 사전 체크 - 회사코드 단위
    AUTHORITY-CHECK OBJECT 'F_ANLA_BUK'
      ID 'BUKRS' FIELD iv_bukrs
      ID 'ACTVT' FIELD '03'.
    IF sy-subrc <> 0.
      log( |No authorization for company code { iv_bukrs }| ).
      RETURN.
    ENDIF.

    " 2. 패키지 단위 커서 처리
    SELECT FROM i_assettransaction
      FIELDS companycode,
             masterfixedasset,
             fixedasset,
             assettransactiontype,
             assetvaluedate,
             assetpostingamountincocodecrcy,
             companycodecurrency
      WHERE companycode  = @iv_bukrs
        AND fiscalyear   = @iv_year
        AND fiscalperiod BETWEEN @iv_period_from AND @iv_period_to
      ORDER BY masterfixedasset, fixedasset, assetvaluedate
      INTO TABLE @DATA(lt_chunk)
      PACKAGE SIZE c_package_size.

      lv_total = lv_total + lines( lt_chunk ).

      " 3. 청크별 비즈니스 로직 호출
      NEW zcl_asset_tx_aggregator( )->aggregate( lt_chunk ).

      " 4. 메모리 해제
      CLEAR lt_chunk.
    ENDSELECT.

    log( |Processed { lv_total } asset transactions| ).
  ENDMETHOD.

  METHOD log.
    cl_bali_message_setter=>create(
      severity = if_bali_constants=>c_severity_info
      text     = iv_msg
    ).
  ENDMETHOD.
ENDCLASS.

그리고 ABAP Unit Test 클래스를 분리하여 거래 유형 분류 로직을 단위 테스트합니다.

CLASS ltcl_tx_classifier DEFINITION FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS classify_acquisition FOR TESTING.
    METHODS classify_retirement  FOR TESTING.
ENDCLASS.

CLASS ltcl_tx_classifier IMPLEMENTATION.
  METHOD classify_acquisition.
    DATA(lo_classifier) = NEW zcl_tx_classifier( ).
    cl_abap_unit_assert=>assert_equals(
      act = lo_classifier->classify( '100' )
      exp = 'ACQUISITION' ).
  ENDMETHOD.

  METHOD classify_retirement.
    DATA(lo_classifier) = NEW zcl_tx_classifier( ).
    cl_abap_unit_assert=>assert_equals(
      act = lo_classifier->classify( '210' )
      exp = 'RETIREMENT' ).
  ENDMETHOD.
ENDCLASS.

성능 측면에서 권장되는 패턴은 다음과 같습니다. 첫째, CompanyCodeFiscalYear는 ANEK의 기본 인덱스에 포함되어 있으므로 WHERE 절 최상단에 두는 것이 좋습니다. 둘째, 거래 유형 범위 필터는 ANBWA 인덱스를 활용할 수 있는 BETWEEN 형태로 작성합니다. 셋째, 자산 마스터 JOIN은 필요한 필드만 선택하여 컬럼 스토어의 장점을 살립니다.

흔히 마주치는 실수와 해결 방법

실무에서 I_AssetTransaction을 사용할 때 자주 발생하는 문제와 대응 방법을 정리합니다.

Q1. ANEK 직접 조회와 결과 건수가 다릅니다.
I_AssetTransaction은 내부적으로 일부 status 필드(예: 취소 표시 STORN)를 기준으로 필터링하거나, 특정 거래 유형을 제외할 수 있습니다. 정확한 차이는 ADT에서 CDS 정의를 열어 WHERE 절과 association 조건을 확인해야 합니다. 회계 감사 목적으로 전체 raw 데이터가 필요하다면 ANEK 직접 조회가 더 적합할 수 있습니다.

Q2. 통화 변환 시 소수점 오차가 발생합니다.
AssetPostingAmountInCoCodeCrcy는 회사코드 통화 기준 금액이며, 트랜잭션 통화 기준은 별도 필드(AssetPostingAmountInTransCrcy)입니다. 두 통화가 다른 경우 환율은 거래 시점(AssetValueDate)의 환율을 사용해야 하며, ABAP의 CONVERT_AMOUNT_TO_CURRENCY RFC가 아닌 CDS의 CURRENCY_CONVERSION 함수를 사용하는 것이 일반적으로 더 일관됩니다.

Q3. 권한 오류 없이 빈 결과만 반환됩니다.
I_AssetTransaction은 DCL(Data Control Language) 규칙이 적용된 뷰입니다. 즉, 권한 체크가 SELECT 단계에서 자동 적용되어 권한 없는 회사코드의 데이터는 자동 필터링됩니다. WITH PRIVILEGED ACCESS 절을 사용하면 우회 가능하지만, 감사 추적이 어려워지므로 배치 사용자에게 적절한 권한을 부여하는 것이 권장됩니다.

Q4. 거래 유형 텍스트가 영문으로만 표시됩니다.
거래 유형 텍스트는 TABWT 테이블에서 SPRAS(언어 키) 기준으로 조회됩니다. 사용자의 로그온 언어와 무관하게 한국어 텍스트가 필요하다면 I_AssetTransactionTypeText association을 명시적으로 호출하면서 언어 키를 'K'로 지정해야 합니다.

이어서 살펴볼 만한 주제

이 글에서 다룬 I_AssetTransaction은 자산 거래의 헤더 레벨 데이터입니다. 라인 아이템(감가상각 영역별 금액) 수준까지 분석하려면 I_AssetTransactionLineItem 또는 I_AssetValuationLineItem 뷰를 함께 활용해야 합니다. 또한 Fiori 앱 "자산 거래 보기(F0763)"의 백엔드 OData 서비스(C_AssetTransactionOverviewResults)는 I_AssetTransaction을 컨슈머 뷰로 감싸 분석 쿼리를 노출하므로, 커스텀 분석 앱 개발 시 좋은 출발점이 됩니다.

추가로 RAP(RESTful Application Programming Model)를 활용하여 자산 거래 데이터를 외부 시스템에 노출하거나, SAP Datasphere로 복제하여 보고서를 작성하는 시나리오도 자연스러운 확장 경로입니다.

관련 문서와 추천 자료

댓글 0

아직 댓글이 없습니다.