UI5

List Report 설정 90%가 놓치는 어노테이션 순서 #shorts #SAP #RAP

▶ YouTube에서 보기

List Report가 어노테이션을 해석하는 방식

SAP Fiori Elements의 List Report는 CDS(또는 OData) 메타데이터에 선언된 @UI 어노테이션을 런타임에 해석해 UI를 자동 렌더링합니다. 개발자가 XML View를 직접 작성하지 않아도 컬럼, 필터바, 헤더 영역이 메타데이터만으로 구성되는 이유가 여기에 있습니다. 이 글에서는 SalesOrder 시나리오를 통해 @UI.lineItem@UI.headerInfo를 깊이 있게 다루며, 어노테이션이 어떻게 List Report와 Object Page에 연결되는지 동작 원리부터 실전 적용까지 단계별로 설명합니다.

알아두면 좋은 사전 지식

이 글의 코드 예제를 따라가려면 ABAP CDS View 또는 RAP(BDEF) 작성 경험, OData V4 메타데이터 구조에 대한 기초 이해, 그리고 Fiori Elements Floorplan(List Report, Object Page)이 무엇인지 알고 있어야 합니다. SAP BAS(Business Application Studio) 또는 ADT(Eclipse) 사용 경험이 있으면 작업 흐름이 자연스럽게 이해됩니다. UI5 컨트롤(sap.m.Table 등)을 직접 다뤄본 경험이 있으면 어노테이션이 무엇을 자동화하는지 체감하기 좋습니다.

실습 환경과 준비물

예제는 다음 환경을 기준으로 검증되었습니다. 버전이 다른 경우 일부 속성명이나 렌더링 차이가 발생할 수 있으므로 사용 중인 SAP 환경을 먼저 확인하기를 권장합니다.

  • SAP S/4HANA 2023 또는 ABAP Platform Cloud 2402 이상
  • SAP Fiori Elements for OData V4 (UI5 1.120 LTS 권장)
  • ABAP Development Tools(ADT) 또는 SAP Business Application Studio
  • RAP(ABAP RESTful Application Programming Model) 기반 서비스 1개
  • 샘플 DB 테이블: zsalord(헤더), zsalord_item(아이템)

S/4HANA 2020 이하의 OData V2 + UI Annotation 기반 환경에서도 핵심 개념은 동일하게 적용되지만, DataFieldDefaultQuickInfo 같은 일부 신규 속성은 일반적으로 V4 기반에서 더 안정적으로 동작합니다.

핵심 개념: 어노테이션은 UI의 "설계도"다

List Report의 어노테이션은 흔히 건축 도면에 비유됩니다. CDS View가 "벽돌과 자재"라면, @UI 어노테이션은 "어떤 자재를 어디에 어떤 모양으로 배치할지" 알려주는 도면입니다. Fiori Elements는 시공사 역할을 하며, 도면만 받으면 동일한 품질의 건물(UI)을 자동으로 짓습니다.

핵심 어노테이션 그룹은 다음과 같이 역할을 나눠 이해하면 쉽습니다.

  • @UI.headerInfo: 오브젝트의 "명함" 역할. TypeName, Title, Description으로 객체 정체성을 표현
  • @UI.lineItem: List Report 테이블의 "컬럼 정의서". 어떤 필드가 어떤 순서로 보일지 결정
  • @UI.selectionField: 필터바에 노출될 필드 목록
  • @UI.facet: Object Page의 섹션 구조
  • @UI.identification: Object Page 헤더 아래 핵심 식별 필드

중요한 점은 이 어노테이션들이 "위치 인덱스(position)"를 기준으로 정렬된다는 사실입니다. position 10, 20, 30 식으로 간격을 두는 습관은 나중에 새 필드를 중간에 삽입할 때 충돌을 줄여줍니다. 또한 같은 필드에 여러 lineItem을 선언하면 서로 다른 Qualifier로 구분되어 변형 테이블을 만들 수도 있습니다.

동작 흐름은 이렇습니다. 브라우저가 OData 서비스의 $metadata를 요청 → Fiori Elements가 메타데이터의 UI.LineItem 컬렉션을 파싱 → 각 record의 $Type(DataField, DataFieldForAnnotation 등)을 보고 적절한 UI5 컨트롤로 변환 → 최종 sap.m.Table을 렌더링. 즉, 개발자가 작성한 어노테이션이 곧 메타데이터가 되고, 메타데이터가 곧 UI가 됩니다.

1단계 예제: @UI.lineItem 기본 구조 잡기

가장 단순한 SalesOrder 헤더 CDS View에 @UI.lineItem을 적용해 List Report 컬럼을 만들어봅니다. position만으로 순서를 제어하는 가장 기본 형태입니다.

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Sales Order Header'
@Metadata.allowExtensions: true
@Search.searchable: true

define root view entity ZC_SalesOrderHdr
  as select from zsalord as Hdr
{
  @UI.lineItem: [{ position: 10, label: '주문번호' }]
  @UI.selectionField: [{ position: 10 }]
  key Hdr.so_id           as SalesOrderId,

  @UI.lineItem: [{ position: 20, label: '고객사' }]
  @Search.defaultSearchElement: true
  Hdr.customer_name       as CustomerName,

  @UI.lineItem: [{ position: 30, label: '주문일' }]
  Hdr.order_date          as OrderDate,

  @UI.lineItem: [{ position: 40, label: '총액' }]
  @Semantics.amount.currencyCode: 'CurrencyCode'
  Hdr.gross_amount        as GrossAmount,

  Hdr.currency_code       as CurrencyCode,
  Hdr.order_status        as OrderStatus
}

위 정의만 있으면 List Report에는 SalesOrderId, CustomerName, OrderDate, GrossAmount 4개 컬럼이 자동 생성됩니다. CurrencyCode@Semantics.amount.currencyCode로 GrossAmount의 통화로 사용되며 별도 컬럼으로 표시되지는 않습니다.

2단계 예제: 시각적 상태와 반응형 우선순위

실무에서는 단순 텍스트 컬럼 외에 "주문 상태에 따라 색상 표시", "모바일에서는 일부 컬럼 숨김" 같은 요구가 끊임없이 발생합니다. criticalityimportance로 해결합니다.

define root view entity ZC_SalesOrderHdr
  as select from zsalord as Hdr
  association [0..1] to ZI_OrderStatusVH as _Status
    on $projection.OrderStatus = _Status.StatusCode
{
  @UI.lineItem: [{ position: 10, label: '주문번호', importance: #HIGH }]
  key Hdr.so_id            as SalesOrderId,

  @UI.lineItem: [{ position: 20, label: '고객사', importance: #HIGH }]
  Hdr.customer_name        as CustomerName,

  @UI.lineItem: [{ position: 30, label: '주문일', importance: #MEDIUM }]
  Hdr.order_date           as OrderDate,

  @UI.lineItem: [{
    position: 40,
    label: '상태',
    criticality: 'StatusCriticality',
    criticalityRepresentation: #WITH_ICON,
    importance: #HIGH
  }]
  Hdr.order_status         as OrderStatus,

  @UI.hidden: true
  case Hdr.order_status
    when 'A' then 3   "  정상 -> Positive(녹색)
    when 'B' then 2   "  대기 -> Critical(노랑)
    when 'C' then 1   "  취소 -> Negative(빨강)
    else 0            "  기타 -> Neutral
  end                       as StatusCriticality,

  @UI.lineItem: [{ position: 50, label: '총액', importance: #LOW }]
  @Semantics.amount.currencyCode: 'CurrencyCode'
  Hdr.gross_amount         as GrossAmount,

  Hdr.currency_code        as CurrencyCode,
  _Status
}

criticality는 0~5 정수값을 가지며 1=Negative, 2=Critical, 3=Positive, 5=Information으로 매핑됩니다. criticalityRepresentation: #WITH_ICON을 함께 지정하면 상태 아이콘이 텍스트 옆에 표시됩니다. importance는 화면 폭이 줄어들 때 어떤 컬럼이 먼저 숨겨질지 결정하며, #HIGH는 끝까지 살아남고 #LOW는 가장 먼저 팝인(Popin) 영역으로 이동합니다.

3단계 예제: headerInfo·액션·DataFieldForAnnotation 결합

프로덕션 수준에서는 List Report에서 행을 클릭했을 때 펼쳐지는 Object Page에 적절한 타이틀과 액션 버튼이 함께 표시되어야 합니다. @UI.headerInfo로 객체 정체성을, DataFieldForAction으로 버튼을, DataFieldForAnnotation으로 연결된 어노테이션을 임베드합니다.

@UI: {
  headerInfo: {
    typeName:       '판매주문',
    typeNamePlural: '판매주문 목록',
    title:        { type: #STANDARD, value: 'SalesOrderId' },
    description:  { type: #STANDARD, value: 'CustomerName' },
    imageUrl:     'CustomerLogoUrl'
  }
}
define root view entity ZC_SalesOrderHdr
  as select from zsalord as Hdr
  association [0..*] to ZC_SalesOrderItem as _Item
    on $projection.SalesOrderId = _Item.SalesOrderId
{
  @UI: {
    lineItem: [
      { position: 10, label: '주문번호', importance: #HIGH },
      { type: #FOR_ACTION, dataAction: 'ConfirmOrder',
        label: '주문 확정', position: 100 },
      { type: #FOR_ACTION, dataAction: 'CancelOrder',
        label: '주문 취소', position: 110 }
    ],
    identification: [{ position: 10, label: '주문번호' }]
  }
  key Hdr.so_id              as SalesOrderId,

  @UI.lineItem: [{
    position: 20,
    type: #FOR_ANNOTATION,
    annotationPath: '@UI.DataPoint#CustomerInfo'
  }]
  @UI.dataPoint: #CustomerInfo: {
    title: '고객사',
    value: 'CustomerName'
  }
  Hdr.customer_name          as CustomerName,

  @UI.lineItem: [{ position: 30, label: '주문일' }]
  Hdr.order_date             as OrderDate,

  @UI.lineItem: [{
    position: 40,
    label: '진행률',
    type: #AS_PROGRESS,
    value: 'CompletionPct',
    targetValue: 100,
    criticality: 'StatusCriticality'
  }]
  Hdr.completion_pct         as CompletionPct,

  @UI.hidden: true
  Hdr.status_criticality     as StatusCriticality,

  _Item
}

여기서 주목할 점은 세 가지입니다. 첫째, headerInfo.title이 Object Page 상단의 큰 텍스트가 되고 description이 그 아래 보조 텍스트로 표시됩니다. 둘째, lineItem 배열에 섞어 넣은 type: #FOR_ACTION 항목은 테이블 툴바의 버튼이 됩니다. 셋째, DataFieldForAnnotation은 단순 텍스트가 아니라 다른 어노테이션(여기서는 DataPoint)을 셀에 임베드하여 마이크로차트, 등급, 진행률 같은 풍부한 시각화를 가능하게 합니다.

또한 type: #AS_PROGRESS는 진행률 바를, #AS_RATING은 별점을, #AS_DATAPOINT는 숫자 KPI를 셀 내부에 렌더링합니다. 성능 측면에서는 lineItem에 과도하게 많은 DataFieldForAnnotation을 넣으면 metadata 파싱 비용이 늘어나므로, 정말 필요한 컬럼에만 적용하는 것을 권장합니다.

자주 마주치는 함정과 해결법

경험상 다음 패턴의 실수가 반복적으로 발생합니다.

  • position 중복: 두 필드에 같은 position을 부여하면 렌더링 순서가 비결정적입니다. 항상 10 단위로 띄워서 선언합니다.
  • criticality 필드를 컬럼으로 노출: StatusCriticality 같은 보조 필드에 @UI.hidden: true를 빼먹으면 숫자 컬럼이 그대로 보입니다.
  • headerInfo의 title과 키 필드 불일치: title은 사람이 읽을 수 있는 값을 권장하지만, 일반적으로 키 필드를 같이 description에 넣으면 가독성이 좋아집니다.
  • Behavior Definition과의 불일치: DataFieldForAction에 지정한 dataAction 이름이 RAP BDEF의 action 이름과 정확히 일치해야 합니다. 대소문자 차이로 401/404가 발생하는 경우가 많습니다.
  • 로컬 어노테이션 vs 메타데이터 확장: SAP 표준 서비스에 추가 컬럼을 넣을 때 CDS Metadata Extension(annotate view)을 사용하지 않고 원본을 수정하면 업그레이드 시 충돌이 납니다.

FAQ

Q1. lineItem에 정의했는데 컬럼이 안 보입니다.
A. (1) 서비스 바인딩에서 해당 엔티티가 노출됐는지, (2) $metadata에 UI.LineItem이 실제로 포함됐는지, (3) 사용자 변형(Variant)에서 컬럼이 숨김 처리되지 않았는지 확인하세요. 브라우저 캐시도 흔한 원인입니다.

Q2. criticality 색상이 적용되지 않습니다.
A. criticality는 문자열이 아니라 필드 경로(field path)여야 합니다. criticality: 'StatusCriticality'처럼 따옴표 안에 필드명을, 값 자체는 0~5 정수로 계산하세요.

Q3. Object Page 헤더에 이미지가 안 나옵니다.
A. imageUrl은 절대 URL 또는 OData 미디어 스트림 경로여야 하며, CORS와 인증 정책에 막히는 경우가 일반적입니다. 임시로 정적 이미지 URL로 테스트해 어노테이션 자체의 문제인지 분리해 보세요.

이어서 살펴보면 좋은 주제

이 글에서는 List Report의 컬럼 정의를 중심으로 다뤘지만, 실무 화면을 완성하려면 다음 주제로 확장하는 것을 권장합니다. @UI.facet으로 Object Page를 섹션화하기, @UI.chart로 Analytical List Page 구성하기, @Consumption.filter@UI.selectionField를 조합한 고급 필터바, 그리고 @UI.dataPoint의 트렌드/진행률 시각화. Side Effects 어노테이션을 통한 필드 간 자동 갱신, Draft 처리와 결합한 인라인 편집 시나리오도 자연스럽게 다음 학습 경로가 됩니다.

더 깊이 파고들 수 있는 링크 모음

댓글 0

아직 댓글이 없습니다.