ABAP

품질 레코드 직접 조회 그만 #shorts #SAP #ABAP

개요와 이 글에서 다루는 내용

구매 부서가 외부 공급업체로부터 자재를 입고할 때 "이 자재는 어떤 검사 절차를 거쳐야 하고, 어느 공급업체가 어떤 품질 수준을 보장하는가"를 결정하는 마스터 데이터가 바로 품질 정보 레코드(Quality Info Record, QINF)입니다. SAP S/4HANA에서는 이 정보를 I_QualityInfoRecord CDS 뷰를 통해 일관된 형태로 노출합니다. 이 글에서는 QINF 테이블(QINF, QIMT)과 CDS 뷰의 매핑 관계를 분해하고, 공급업체-자재 조합의 검사 설정을 조회·검증·활용하는 실전 예제 세 단계를 진행합니다.

  • QINF 테이블 구조와 CDS 뷰가 노출하는 필드 의미 파악
  • Supplier · Material · Plant 키 조합으로 품질 정보 레코드 조회
  • Release(승인) 상태와 Block(차단) 사유를 활용한 입고 가능 판정 로직
  • RAP 기반 서비스 노출과 권한·성능 최적화 패턴 적용

읽기 전에 갖춰두면 좋은 배경 지식

이 글은 ABAP Core Data Services(CDS) View Entity 문법, 어소시에이션(association) 정의, SELECT를 통한 CDS 소비, 그리고 SAP QM(Quality Management) 모듈의 검사 유형(Inspection Type) 개념을 알고 있다는 전제로 작성됐습니다. 또한 구매(MM)에서 사용하는 Plant, Purchasing Organization 같은 조직 단위와 공급업체 마스터(LFA1) 구조에 익숙하면 흐름을 따라가기가 수월합니다.

환경, 릴리스, 사전 준비

예제는 SAP S/4HANA 2022 / 2023 온프레미스 또는 SAP S/4HANA Cloud Private Edition 환경을 기준으로 작성했습니다. 다음 도구·권한이 필요합니다.

  • ABAP Development Tools(ADT) for Eclipse 3.30 이상 권장
  • 패키지 권한과 S_DEVELOP 개발 권한, CDS Entity 활성화 권한
  • QM 모듈 라이선스 및 QINF 트랜잭션 권한(QI01 생성, QI03 조회)
  • 테이블 조회 권한: QINF(품질 정보 레코드 헤더), QIMT(검사 유형별 설정)
  • 표준 CDS 뷰 I_QualityInfoRecord, I_Supplier, I_Product가 활성 상태로 시스템에 존재해야 함

CDS 뷰가 활성화돼 있는지 확인하려면 ADT에서 Open CDS Source 기능으로 I_QualityInfoRecord를 직접 열어 보거나, SE16N에서 DDLDEPENDENCY를 조회해 의존 객체가 정상인지 점검합니다. 일부 필드는 비즈니스 기능(Business Function) 활성 여부에 따라 노출되지 않을 수 있으므로 사전에 SFW5로 QM_*, LOG_* 관련 스위치를 확인해 두는 것이 안전합니다.

핵심 개념: QINF가 다루는 데이터의 의미

품질 정보 레코드는 한 마디로 "이 공급업체가 이 자재를 이 공장에 납품할 때 적용할 검사 조건 묶음"입니다. 일반 구매정보레코드(EINA/EINE)가 가격·수량 협상 정보에 가깝다면, QINF는 품질 관점에서의 보조 마스터입니다. 데이터 구조는 두 계층으로 나뉩니다.

  • QINF (헤더): Supplier, Material, Plant 3-key 조합으로 한 건. 릴리스 상태, 차단 표시, 유효 기간, 인증 정보 등을 담습니다.
  • QIMT (검사 유형 항목): 한 헤더 아래에 여러 검사 유형(예: 01 입고 검사, 0101 출처 검사)을 매달아 검사 활성화 여부와 통제값을 보관합니다.

비유하자면 헤더는 "출입증의 기본 정보"이고 QIMT는 "출입증 뒷면에 인쇄된 출입 가능 구역 목록"입니다. CDS 뷰 I_QualityInfoRecord는 헤더 레벨을 노출하며, 검사 유형별 디테일은 I_QualityInfoRecordInspType 같은 자식 뷰로 어소시에이션 연결됩니다. 어노테이션 @ObjectModel.representativeKeyQualityInfoRecord 필드를 가리키며, 이 ID는 헤더 행마다 부여되는 GUID/번호입니다.

릴리스 상태(QualityInfoRecordReleaseStatus)는 입고 처리 시 분기점이 되는 가장 중요한 항목입니다. 값이 비어 있으면 미승인, '02'/'04'와 같은 코드면 단계별 승인 의미를 갖습니다(사이트별 커스터마이징에 따라 코드 의미가 달라질 수 있음). 차단 표시(IsBlockedForQuality, 사이트 따라 QualityInfoRecordIsBlocked)가 켜져 있으면 해당 조합은 입고 또는 검사 결과 사용에 제약을 받습니다.

도식으로 표현하면 다음과 같은 흐름입니다.

[Supplier] ---+
              |--> [QINF Header] -->[QIMT: Inspection Type 01]
[Material]----+         |          -->[QIMT: Inspection Type 0101]
              |         |          -->[QIMT: Inspection Type 08]
[Plant]   ----+         |
                        +-- ReleaseStatus / BlockIndicator / ValidTo

실전 예제 1단계: 기본 조회

가장 단순한 사용 패턴부터 시작합니다. 특정 공급업체-자재-공장 조합에 대해 QINF 헤더 한 건을 조회해 릴리스 상태를 확인합니다. CDS 뷰는 키 필드가 Supplier, Material, Plant로 정의돼 있으므로 SELECT SINGLE으로 충분합니다.

REPORT zr_qinf_basic_check.

DATA: lv_supplier TYPE i_qualityinforecord-supplier      VALUE '0000100245',
      lv_material TYPE i_qualityinforecord-material      VALUE 'MAT-STEEL-9001',
      lv_plant    TYPE i_qualityinforecord-plant         VALUE '1010'.

SELECT SINGLE
         supplier,
         material,
         plant,
         qualityinforecord,
         qualityinforecordreleasestatus,
         validityenddate
  FROM   i_qualityinforecord
  WHERE  supplier = @lv_supplier
    AND  material = @lv_material
    AND  plant    = @lv_plant
  INTO @DATA(ls_qinf).

IF sy-subrc = 0.
  WRITE: / |QINF ID: { ls_qinf-qualityinforecord }|,
         / |Release Status: { ls_qinf-qualityinforecordreleasestatus }|,
         / |Valid Until: { ls_qinf-validityenddate DATE = USER }|.
ELSE.
  WRITE: / 'QINF 레코드를 찾을 수 없습니다.'.
ENDIF.

주의할 점은 Material 필드가 S/4HANA에서 40자리 확장 자재(MATNR40) 타입이라는 것입니다. R/3 호환 18자리 변환이 필요하다면 CDS 뷰 I_Product를 어소시에이션으로 끌어와 ProductInternalID를 매핑해야 합니다.

실전 예제 2단계: 입고 가능 여부 판정과 로깅

실무에서는 단순 조회보다 "지금 이 공급업체로부터 자재 입고가 허용되는가"를 판정하는 로직이 자주 필요합니다. 릴리스 상태, 차단 표시, 유효 기간을 동시에 검사하고, 결과를 Application Log(BAL_LOG_*)에 기록하는 클래스 형태로 구성합니다.

CLASS zcl_qinf_gatekeeper DEFINITION
  PUBLIC FINAL CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES:
      BEGIN OF ty_check_result,
        is_allowed       TYPE abap_bool,
        rejection_reason TYPE string,
        qinf_id          TYPE i_qualityinforecord-qualityinforecord,
      END OF ty_check_result.

    METHODS check_goods_receipt
      IMPORTING iv_supplier      TYPE bu_partner
                iv_material      TYPE matnr40
                iv_plant         TYPE werks_d
                iv_posting_date  TYPE dats DEFAULT sy-datum
      RETURNING VALUE(rs_result) TYPE ty_check_result.
ENDCLASS.

CLASS zcl_qinf_gatekeeper IMPLEMENTATION.
  METHOD check_goods_receipt.
    SELECT SINGLE
             qualityinforecord,
             qualityinforecordreleasestatus,
             qualityinforecordisblocked,
             validitystartdate,
             validityenddate
      FROM   i_qualityinforecord
      WHERE  supplier = @iv_supplier
        AND  material = @iv_material
        AND  plant    = @iv_plant
      INTO @DATA(ls_qinf).

    IF sy-subrc <> 0.
      rs_result-is_allowed       = abap_false.
      rs_result-rejection_reason = |QINF 레코드 미존재({ iv_supplier }/{ iv_material }/{ iv_plant })|.
      RETURN.
    ENDIF.

    rs_result-qinf_id = ls_qinf-qualityinforecord.

    IF ls_qinf-qualityinforecordisblocked = abap_true.
      rs_result-rejection_reason = '품질 차단 표시가 활성화돼 있습니다.'.
    ELSEIF ls_qinf-validityenddate IS NOT INITIAL
       AND iv_posting_date > ls_qinf-validityenddate.
      rs_result-rejection_reason = |유효 기간 만료({ ls_qinf-validityenddate DATE = USER })|.
    ELSEIF ls_qinf-qualityinforecordreleasestatus IS INITIAL.
      rs_result-rejection_reason = '미승인 상태입니다.'.
    ELSE.
      rs_result-is_allowed = abap_true.
    ENDIF.

    " 거부 시 Application Log 기록 (간단화)
    IF rs_result-is_allowed = abap_false.
      MESSAGE i099(qm) WITH iv_supplier iv_material rs_result-rejection_reason
              INTO DATA(lv_msg) ##NEEDED.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

이 패턴의 장점은 입고 BAdI(MB_DOCUMENT_BADI) 또는 RAP 행위(action)에서 그대로 호출 가능하다는 점입니다. RETURN 직전에 메시지 로그 객체를 주입하면 운영 중 거부 원인을 추적할 수 있어 검수 담당자에게 큰 도움이 됩니다.

실전 예제 3단계: 프로젝션 뷰와 RAP 노출, 성능 고려

다수 자재의 품질 상태를 한 번에 분석해야 하는 대시보드 시나리오를 생각해 봅니다. 표준 뷰에서 필요한 필드만 골라 커스텀 프로젝션을 만들고, 어소시에이션을 통해 공급업체명과 자재명을 join한 뒤 OData 서비스로 노출합니다.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'QINF 입고가능 현황 프로젝션'
define view entity ZC_QinfReadiness
  as select from I_QualityInfoRecord as Qinf

  association [0..1] to I_Supplier as _Supplier
    on $projection.Supplier = _Supplier.Supplier

  association [0..1] to I_Product  as _Product
    on $projection.Material = _Product.Product
{
  key Qinf.Supplier,
  key Qinf.Material,
  key Qinf.Plant,
      Qinf.QualityInfoRecord                 as QinfId,
      Qinf.QualityInfoRecordReleaseStatus    as ReleaseStatus,
      Qinf.QualityInfoRecordIsBlocked        as IsBlocked,
      Qinf.ValidityEndDate                   as ValidUntil,

      case when Qinf.QualityInfoRecordIsBlocked = 'X' then 'BLOCKED'
           when Qinf.ValidityEndDate < $session.system_date then 'EXPIRED'
           when Qinf.QualityInfoRecordReleaseStatus = '' then 'PENDING'
           else 'READY'
      end                                    as ReadinessStatus,

      _Supplier.SupplierName                 as SupplierName,
      _Product._ProductText[1: Language = $session.system_language].ProductName
                                             as ProductName,

      _Supplier,
      _Product
}

성능 측면에서 유의할 사항이 몇 가지 있습니다. 첫째, QINF 테이블은 일반적으로 수만 건 수준이지만 그 자식 검사유형(QIMT)은 곱셈으로 증가합니다. 헤더만 필요한 화면에서 굳이 자식 association을 펼치지 않도록 $expand를 통제하세요. 둘째, case 식의 좌변에 컬럼을 두면 HANA 옵티마이저가 인덱스를 활용할 수 있도록 단순 비교 형태를 유지하는 것이 권장됩니다. 셋째, 일자 비교 시 $session.system_date는 사용자 시간대를 따르지 않으므로 글로벌 시스템에서는 변환 로직(CONV_TIMEZONE)을 검토해야 합니다.

RAP 서비스 정의는 다음과 같이 간단히 노출할 수 있습니다.

@EndUserText.label: 'QINF Readiness Service'
define service ZUI_QINF_READINESS {
  expose ZC_QinfReadiness as QinfReadiness;
}

권한은 @AccessControl.authorizationCheck: #CHECK와 DCL(Access Control)로 Plant 또는 Purchasing Organization 기준 권한 객체(M_QMAT_WRK, F_LFA1_BEK)를 연결합니다. 테스트는 ABAP Unit으로 cl_osql_test_environment를 활용해 I_QualityInfoRecord를 더블로 등록한 뒤, 다양한 상태 조합(블록/만료/미승인/정상)에 대해 ReadinessStatus가 기대 값으로 산출되는지 검증합니다. 이렇게 분리하면 운영 데이터 의존 없이 회귀 테스트가 가능합니다.

자주 빠지는 함정과 트러블슈팅

Q1. 조회 결과가 GUI(QI03)와 다르게 비어 나옵니다.
대부분 자재 변환 문제입니다. QI03 입력 필드는 외부 형식(예: '100-200')을 받지만 CDS 뷰는 내부 형식(MATNR40)으로 비교합니다. FM CONVERSION_EXIT_MATN1_INPUT 또는 I_ProductProductInternalID를 통해 변환한 뒤 조회하세요. Plant 코드의 leading zero 처리도 점검 대상입니다.

Q2. 릴리스 상태 코드 의미가 매뉴얼과 다릅니다.
QualityInfoRecordReleaseStatus의 도메인 값은 표준 제공값에 더해 고객 추가 코드를 가질 수 있습니다. 의미는 QM 모듈 IMG의 Define Release Statuses에서 확인하고, 하드코딩 대신 도메인 텍스트 뷰(I_*DomainValueText) 또는 사용자 정의 상수 클래스로 매핑하세요.

Q3. 대량 조회에서 응답이 느려집니다.
WHERE 절에 Supplier·Material·Plant 중 일부만 지정한 채 전체 컬럼을 select하면 풀스캔에 가까워집니다. 키 일부만 사용하는 시나리오라면 FOR ALL ENTRIES 또는 PathExpression을 통한 join으로 결합도를 낮추고, OData 호출 시 $select로 컬럼을 최소화하는 것이 일반적으로 권장됩니다. 또한 I_QualityInfoRecord는 텍스트성 association을 다수 갖고 있어 무분별한 $expand가 가장 큰 비용 요인입니다.

추가로 QINF가 활성화돼 있어도 자재 마스터의 검사 설정(MARC-INSMK 등)이 우선되는 경우가 있습니다. 입고 시 실제 검사 로트(QALS) 생성 여부는 자재-공장 레벨 설정과 QINF의 QIMT 항목 둘 다를 확인해야 정확한 진단이 가능합니다.

이어서 살펴보면 좋은 주제

이 글에서 다룬 헤더 뷰를 익혔다면 자연스러운 다음 단계는 검사 유형별 자식 뷰(I_QualityInfoRecordInspType 계열)와 검사 로트 데이터(I_InspectionLot)의 조인 분석입니다. RAP 비관리(Unmanaged) 시나리오로 QINF 생성·수정 BAPI(BAPI_QINFO_*)를 감싸 OData 서비스로 노출하는 패턴, 그리고 Embedded Analytics에서 @Analytics.dataCategory: #FACT를 적용해 공급업체별 품질 KPI 큐브를 만드는 작업도 실용성이 높습니다. SAP Build Process Automation으로 만료 임박 QINF 자동 알림을 보내는 워크플로도 운영 효과가 큰 후속 과제입니다.

더 깊이 알고 싶을 때 살펴볼 자료

댓글 0

아직 댓글이 없습니다.