ABAP

OB52 확인 없이 전기하면 생기는 실수 3가지 #shorts #SAP #ABAP

▶ YouTube에서 보기

📖 개요 및 이 글에서 다루는 내용

커스텀 전표 생성 배치가 밤새 돌다가 "전기기간이 마감되었습니다"로 수백 건이 실패해 있는 아침을 겪어본 적이 있다면 도움이 될 글입니다. BAPI 호출 후 실패를 수습하는 대신, 호출 전에 대상 회계기간이 열려 있는지 선검증하는 구조를 다룹니다.

  • 전기기간 통제가 어떤 테이블(T001B, T009B)로 동작하는지
  • FI_PERIOD_DETERMINE / FI_PERIOD_CHECK로 사전검증 로직 작성법
  • 대량 배치에서 검증 결과를 캐싱하는 패턴, FI/MM 기간 차이

📚 미리 알아두면 좋은 배경

중급 난이도의 글로, ABAP 함수모듈 호출과 예외 처리, 내부 테이블 활용 경험을 전제로 합니다. FI 전표 구조(BKPF/BSEG)와 BAPI_ACC_DOCUMENT_POST를 한 번이라도 호출해 본 경험이 있으면 따라가기 수월합니다.

🔧 환경 및 준비 사항

예제는 SAP ECC 6.0(EhP 이상)과 S/4HANA 온프레미스에서 동일하게 동작하는 클래식 함수모듈을 사용합니다. S/4HANA는 OB52 외에 Fiori 앱 "Manage Posting Periods"로도 관리 가능합니다(내부적으로 동일하게 T001B 갱신).

  • 개발 권한이 있는 SE38/SE80 또는 ADT(Eclipse) 환경
  • 테스트용 회사코드와 OB52 조회 권한
  • 확인용 트랜잭션: OB52(전기기간), OB29(회계연도변형), MMRV(MM 현재기간)

SAP BTP ABAP Environment(Steampunk)에서는 클래식 FM 직접 호출이 제한되므로, 이 글의 패턴은 온프레미스/Private Cloud 클래식 확장 관점입니다.

💡 핵심 개념: 전기기간 통제 구조

전기기간 검증을 건물 출입 통제에 비유하면 이해가 쉽습니다. 회계연도변형(T009B)은 "달력" 역할로, 전기일(BUDAT)이 몇 년도 몇 기(期)에 해당하는지를 결정합니다. 4-4-5 변형이나 이월연도(shifted fiscal year)를 쓰는 회사라면 8월 8일 전표가 반드시 8기가 아닐 수 있으므로, 전기일→기간 변환을 직접 계산하지 말고 시스템에 맡겨야 합니다.

전기기간변형(T001B)은 "출입 허가 명단"입니다. 회사코드에 할당된 전기기간변형(T001-OPVAR) 아래에서, 계정유형별로 열려 있는 기간 구간을 관리합니다.

  • 계정유형(MKOAR): + (전체), A(자산), D(고객), K(공급업체), S(G/L), M(자재)
  • 구간 1(FRPE1~TOPE1): 권한그룹(BRGRU)으로 보호 가능한 구간. 결산 중 특정 사용자만 전기 허용할 때 사용
  • 구간 2(FRPE2~TOPE2): 모든 사용자에게 열린 구간
  • 구간 3: S/4HANA에서 CO 기표(FI-CO 통합) 통제용으로 추가된 구간

즉 "기간이 열려 있는가"는 실제로 "이 회사코드·계정유형에 대해, 이 연도·기간이 구간 1 또는 2에 포함되는가"라는 복합 질문입니다. T001B를 직접 SELECT해서 구현하는 것은 권장되지 않습니다. 계정유형 '+' 폴백, 계정번호 구간, 연도 경계 처리 같은 표준 로직을 놓치기 쉽기 때문입니다. 표준 함수모듈 FI_PERIOD_CHECK가 이 판정을 캡슐화하고 있습니다.

혼동하기 쉬운 것이 MM 자재기간입니다. 자재 이동/송장검증은 T001B와 별개로 MARV 테이블을 따르며 MMPV로 월전환합니다. "OB52는 열려 있는데 MIGO가 막힌다"는 상황이 이 때문입니다. 자재 관련 전기가 섞인 전표라면 MM 기간도 별도 확인이 필요합니다.

💻 실전 코드 3단계

1단계 — 기본: 전기일로 기간을 결정하고 오픈 여부 확인

먼저 전기일과 회사코드로 회계연도·기간을 결정하고(회계연도변형 반영), 그 결과로 기간 오픈 여부를 검사합니다.

DATA: lv_gjahr TYPE gjahr,
      lv_monat TYPE monat.

" 1) 전기일 -> 회계연도/기간 결정 (T009B 기반, 직접 계산 금지)
CALL FUNCTION 'FI_PERIOD_DETERMINE'
  EXPORTING
    i_budat        = p_budat        " 예: 20260808
    i_bukrs        = p_bukrs        " 예: 1000
  IMPORTING
    e_gjahr        = lv_gjahr
    e_monat        = lv_monat
  EXCEPTIONS
    fiscal_year    = 1
    period         = 2
    t009b_notfound = 3
    OTHERS         = 4.
IF sy-subrc <> 0.
  MESSAGE e001(zfi) WITH p_budat p_bukrs. " 회계연도변형 설정 오류
  RETURN.
ENDIF.

" 2) 해당 기간이 계정유형 K(공급업체)에 대해 열려 있는지 확인
CALL FUNCTION 'FI_PERIOD_CHECK'
  EXPORTING
    i_bukrs          = p_bukrs
    i_gjahr          = lv_gjahr
    i_monat          = lv_monat
    i_koart          = 'K'
  EXCEPTIONS
    error_period     = 1   " 기간 자체가 닫힘
    error_period_acc = 2   " 해당 계정(유형) 구간에서 닫힘
    invalid_input    = 3
    OTHERS           = 4.
IF sy-subrc = 0.
  WRITE: / |{ lv_gjahr }/{ lv_monat } 기간 전기 가능|.
ELSE.
  WRITE: / |{ lv_gjahr }/{ lv_monat } 기간 마감 - 전표 생성 스킵|.
ENDIF.

공급업체·G/L 라인이 함께 들어간 전표라면 관련 계정유형(K, S) 각각을 검사해야 안전합니다. 하나라도 닫혀 있으면 전기가 실패합니다.

2단계 — 실무 시나리오: 판매오더 후속 전표 배치에서 사전검증과 로깅

정산 대상 판매오더 수천 건을 읽어 BAPI_ACC_DOCUMENT_POST로 전표를 만드는 야간 배치를 가정합니다. 핵심은 건별로 FM을 반복 호출하지 않고, 회사코드+기간 조합 단위로 한 번만 검증해 재사용하고, 마감 건은 로그에 남기고 건너뛰는 것입니다.

TYPES: BEGIN OF ty_period_res,
         bukrs TYPE bukrs, gjahr TYPE gjahr,
         monat TYPE monat, koart TYPE koart,
         is_open TYPE abap_bool,
       END OF ty_period_res.

DATA lt_cache TYPE HASHED TABLE OF ty_period_res
     WITH UNIQUE KEY bukrs gjahr monat koart.

LOOP AT lt_settle_items INTO DATA(ls_item).   " 정산 대상 목록
  DATA(lv_gjahr) = VALUE gjahr( ).
  DATA(lv_monat) = VALUE monat( ).

  CALL FUNCTION 'FI_PERIOD_DETERMINE'
    EXPORTING i_budat = ls_item-budat  i_bukrs = ls_item-bukrs
    IMPORTING e_gjahr = lv_gjahr       e_monat = lv_monat
    EXCEPTIONS OTHERS = 1.
  IF sy-subrc <> 0.
    perform_log( 'E' ls_item-vbeln |기간 결정 실패| ).
    CONTINUE.
  ENDIF.

  " 회사코드+연도+기간+계정유형 조합은 캐시에서 먼저 조회
  READ TABLE lt_cache INTO DATA(ls_cache)
       WITH TABLE KEY bukrs = ls_item-bukrs gjahr = lv_gjahr
                      monat = lv_monat      koart = 'D'.
  IF sy-subrc <> 0.
    CALL FUNCTION 'FI_PERIOD_CHECK'
      EXPORTING i_bukrs = ls_item-bukrs i_gjahr = lv_gjahr
                i_monat = lv_monat      i_koart = 'D'
      EXCEPTIONS error_period = 1 error_period_acc = 2 OTHERS = 3.
    ls_cache = VALUE #( bukrs = ls_item-bukrs gjahr = lv_gjahr
                        monat = lv_monat koart = 'D'
                        is_open = COND #( WHEN sy-subrc = 0
                          THEN abap_true ELSE abap_false ) ).
    INSERT ls_cache INTO TABLE lt_cache.
  ENDIF.

  IF ls_cache-is_open = abap_false.
    perform_log( 'W' ls_item-vbeln |{ lv_gjahr }/{ lv_monat } 마감 스킵| ).
    CONTINUE.
  ENDIF.

  post_document( ls_item ).   " 검증 통과 건만 BAPI 호출
ENDLOOP.

perform_log는 BAL_LOG_CREATE/BAL_LOG_MSG_ADD 기반 애플리케이션 로그(SLG1)로 구현하는 것이 일반적입니다. 어떤 건이 어떤 기간 때문에 스킵됐는지 로그에 남으면, 운영자는 OB52 조치 후 스킵 건만 재실행하면 됩니다.

3단계 — 프로덕션: 재사용 가능한 검증 클래스와 안전장치

여러 배치가 같은 검증을 쓰게 되면 전역 클래스로 캡슐화합니다. 정적 버퍼로 성능을 확보하고, FM 호출부를 protected 메서드로 분리해 단위 테스트에서 테스트 더블로 대체할 수 있게 합니다.

CLASS zcl_fi_period_guard DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS check_open
      IMPORTING iv_bukrs        TYPE bukrs
                iv_budat        TYPE budat
                iv_koart        TYPE koart DEFAULT '+'
      RETURNING VALUE(rv_open)  TYPE abap_bool
      RAISING   zcx_fi_period.          " 설정 오류는 예외로 구분
  PROTECTED SECTION.
    METHODS call_period_check            " 테스트 더블 대체 지점
      IMPORTING iv_bukrs TYPE bukrs iv_gjahr TYPE gjahr
                iv_monat TYPE monat iv_koart TYPE koart
      RETURNING VALUE(rv_subrc) TYPE syst_subrc.
  PRIVATE SECTION.
    CLASS-DATA gt_buffer TYPE HASHED TABLE OF ty_period_res
               WITH UNIQUE KEY bukrs gjahr monat koart.
ENDCLASS.

사전검증은 필터일 뿐이므로 BAPI_ACC_DOCUMENT_POST의 RETURN 오류 처리는 유지해야 합니다(검증 후 전기 사이 OB52 변경 가능). 장시간 배치는 일정 건수마다 버퍼를 비우고, 구간 1이 권한그룹(BRGRU)으로 보호됐다면 배치 사용자의 F_BKPF_BUP 권한도 점검하십시오.

⚠️ 흔한 실수와 트러블슈팅 FAQ

Q1. FI_PERIOD_CHECK는 통과했는데 실제 전기에서 "기간 마감" 오류가 납니다.

검증에 쓴 계정유형과 실제 전표 라인의 계정유형이 다른지 확인하십시오. '+'만 검사하면 A/D/K/S 개별 구간이 닫힌 경우를 놓칩니다. 검증과 전기 사이 OB52 변경, 또는 자재 라인의 MARV(MM 기간) 차단도 원인일 수 있습니다.

Q2. 전기일에서 기간을 (월 = 기간)으로 직접 계산해도 되지 않나요?

K4(월력) 변형만 쓰는 시스템에서는 우연히 맞지만, 4-4-5 변형·이월연도·특별기간(13~16기)이 있는 회사코드에서는 틀립니다. FI_PERIOD_DETERMINE으로 T009B 기반 변환을 거치는 것이 안전합니다.

Q3. OB52에서는 분명 열려 있는데 특정 사용자만 실패합니다.

구간 1에 권한그룹(BRGRU)이 걸려 있고 구간 2는 이미 다음 기간으로 넘어간 상태가 전형적입니다. F_BKPF_BUP 권한이 없어 구간 1 혜택을 못 받는 경우이니 SU53으로 확인해보십시오.

🚀 더 확장해볼 만한 주제

이 패턴을 익혔다면 BAPI_ACC_DOCUMENT_CHECK 기반 사전 시뮬레이션, SLG1 로그 설계, 병렬(aRFC) 배치의 버퍼 공유 문제로 확장해 보시길 권합니다. S/4HANA라면 구간 3(CO 통제)과 RAP 기반 전표 생성 시 검증 위치도 검토해볼 만합니다.

📚 함께 보면 좋은 문서

댓글 0

아직 댓글이 없습니다.