SAP BTP - Day3. Fiori Elements UI

이우철·2026년 4월 25일

SAP_BTP

목록 보기
4/11

[3일차] Fiori Elements UI + Annotation 마스터링

목표: 코드 없이 엔터프라이즈급 UI를 만든다. 백엔드 데이터와 Fiori 화면을 완전히 연결한다.
시나리오: List Report + Object Page로 "출장비 승인 앱"의 완전한 풀스택 로컬 앱 완성.
소요 시간: 이론 2시간 + 실습 3시간


3일차 학습 목표

이론:
  Fiori Elements 아키텍처와 Template 종류를 이해한다
  CDS Annotation의 원리를 알고 자유자재로 사용할 수 있다
  List Report와 Object Page의 자동 생성 원리를 안다
  검색, 필터, 정렬을 UI에서 자동으로 생성하는 방법을 안다

실습:
  Fiori 프로젝트 스캐폴딩 (SAP Fiori Generator)
  List Report 생성 및 Annotation 커스터마이징
  Object Page 추가 및 연관 데이터 표시
  로컬에서 풀스택 시나리오 (데이터 입력 → 목록 조회 → 승인) 완동작
  3일차 결과 Git 커밋

이론 세션 (2시간)


이론 1. Fiori Elements란?

Before Fiori: UI를 직접 만든다?

전통적인 SAP UI5 개발:

// 직접 Control 코딩으로 화면 만들기 (매우 번거로움)
var oTable = new sap.ui.table.Table({
  columns: [
    new sap.ui.table.Column({
      label: new sap.m.Label({ text: "출장 제목" }),
      template: new sap.m.Text({ text: "{title}" })
    }),
    // ... 계속 코드로 컬럼 정의
  ]
});

문제점:

  • 수백 줄의 보일러플레이트 코드 필요
  • UI 변경마다 코드 수정
  • 개발자 생산성 매우 낮음
  • 비개발자는 불가능

After Fiori Elements: Annotation으로 UI 선언

// annotation은 데이터 모델에 메타데이터만 추가
annotate TravelService.TravelRequests with @(
  UI: {
    ListPageConfiguration: {
      SelectionFields: ['status', 'requester'],
      LineItem: [
        { Value: title, Label: '제목' },
        { Value: destination, Label: '목적지' },
        { Value: amount, Label: '금액', Criticality: #Neutral },
      ]
    }
  }
);

장점:

  • 코드 없이 선언형 UI 정의
  • CDS 모델 바꾸면 UI 자동 반영
  • 비개발자도 Annotation 수정 가능
  • 엔터프라이즈 표준 따름 (SAP 권장 UX)

이론 2. Fiori Elements Template 종류

이 강좌: List Report + Object Page (완전한 CRUD 앱)

선택 기준:

  • List + Object: 마스터-디테일 관계 (예: 주문 목록 → 주문 상세 → 라인 항목)
  • Worklist: 간단한 할일 관리 (예: 할일 목록)
  • Analytical: 대시보드 (예: 매출 분석 차트)

이론 3. CDS Annotation의 원리

Annotation은 메타데이터

// 실제 데이터는 안 건드림 — 표시만 추가
annotate Travel.TravelRequests with @(
  // 이 블록들은 Annotation
  // 데이터 스키마는 변하지 않음
);

entity TravelRequests {
  // ← 이 부분은 변하지 않음
}

Annotation 계층 구조

UI (UI 표시 방식)
├── ListPageConfiguration (목록 화면)
│   ├── SelectionFields (검색/필터)
│   ├── LineItem (목록 컬럼)
│   └── PresentationVariant (정렬, 기본값)
├── FieldGroup (그룹 표시)
├── Facets (Object Page 탭)
└── ...

Common (공통 설정)
├── Label (레이블)
├── Description (설명)
├── ValueList (드롭다운 목록)
└── ...

Measures (측정값)
├── Unit (단위)
├── NumberOfFractionalDigits (소수 자릿수)
└── ...

이론 4. List Report → Object Page 흐름

UI 흐름:
1. Fiori 앱 시작
   ↓
2. List Report 화면 로드
   ├─ SelectionFields로 검색/필터 폼 자동 생성
   ├─ LineItem으로 목록 테이블 자동 생성
   └─ 기본 데이터 조회 (GET /TravelRequests)
   ↓
3. 사용자: 행 클릭
   ↓
4. Object Page 화면 로드 (ID 파라미터 포함)
   ├─ 상세 정보 로드 (GET /TravelRequests(ID))
   ├─ Facets로 탭 구성
   ├─ FieldGroup으로 필드 배치
   └─ $expand로 연관 데이터 함께 로드
   ↓
5. 사용자: 수정 또는 Action 실행
   ↓
6. 백엔드: PATCH 또는 Custom Action 처리
   ↓
7. UI: 결과 반영 (자동)

실습 세션 (3시간)


실습 1. Fiori 프로젝트 생성 (SAP Fiori Generator)

1-1. BAS에서 Fiori 프로젝트 생성

터미널에서 필요 라이브러리 설치

npm install -g yo @sap/generator-fiori

BAS Terminal에서:

cd /home/user/projects/sap-btp-travel-expense

# Fiori 프로젝트 생성 (대화형)
yo @sap/fiori

대화형 질문 응답:

? What do you want to do? 
→ Create a new Fiori project [선택]

? Which template do you want to use?
→ List Report Page [선택]

? Data Source
→ Use a Local CAP Project [선택]

? Select your CAP project folder
→ ./travel-expense-app [엔터]

? Which OData Service would you like to use?
→ /travel (TravelService) [선택]

? Which entity/table to display? 
→ TravelRequests [선택]

? Do you want to add Object Page?
→ Yes [선택]

? Enter a project name 
→ travel-expense-ui [엔터]

? Enter a module name
→ travel_expense_ui [엔터]

? Enter the namespace for your module
→ sap.example [변경 불필요 또는 custom 입력]

? Add empty i18n file?
→ Yes

? Configure advanced options?
→ No

생성 완료 후 구조:

travel-expense-app/
├── app/
│   └── travel-expense-ui/            ← 새로 생성된 Fiori 앱 (TypeScript 기반)
│       ├── webapp/
│       │   ├── i18n/                 ← 다국어 지원 파일
│       │   ├── test/                 ← 테스트 자동화 폴더
│       │   ├── Component.ts          ← 앱 컴포넌트 정의
│       │   ├── manifest.json         ← 앱 설정 메인 파일 (중요!)
│       │   ├── index.html            ← 로컬 테스트용 페이지
│       │   └── annotations.cds       ← UI 설정 (Local)
│       ├── package.json              ← UI 프로젝트 의존성 관리
│       ├── tsconfig.json             ← TypeScript 설정
│       └── ui5.yaml                  ← UI5 Tooling 서버/빌드 설정
├── db/                               ← 데이터 모델 (CDS)
├── srv/                              ← 서비스 정의 (CDS)
└── package.json (루트)                ← CAP 프로젝트 전체 관리

1-2. 의존성 설치 및 UI5 Tooling 확인

cd app/travel_expense_ui

npm install

잘 안되면 

npm install --legacy-peer-deps

(버전 체크 잠시 끄고...)

# UI5 버전 확인
npm list @sap/ux-ui5-tooling


실습 2. CDS Annotation 작성 (데이터 모델에 메타데이터 추가)

2-1. Annotation 파일 생성

srv/ 폴더에 새 파일 생성:

touch srv/annotations.cds

using TravelService from './travel-service';

// ════════════════════════════════════════════════════
// TravelRequests 엔티티 어노테이션
// ════════════════════════════════════════════════════

annotate TravelService.TravelRequests with @(
Capabilities.Insertable: true,
Capabilities.Deletable : true,

// ── List Report (검색/필터/목록) ──────────────────────────
UI: {
SelectionFields: [
status,
requester,
departureDate,
destination
],

LineItem: [
  { $Type: 'UI.DataFieldForAction', Action: 'TravelService.submit',  Label: '제출',   InvocationGrouping: #ChangeSet },
  { $Type: 'UI.DataFieldForAction', Action: 'TravelService.approve', Label: '승인',  InvocationGrouping: #ChangeSet },
  { $Type: 'UI.DataFieldForAction', Action: 'TravelService.reject',  Label: '반려',   InvocationGrouping: #ChangeSet },
  
  { Value: title,        Label: '제목', Importance: #High },
  { Value: destination,  Label: '목적지', Importance: #High },
  { Value: requester,    Label: '신청자', Importance: #Medium },
  { Value: totalAmount,  Label: '금액 (KRW)', Importance: #High },
  { 
    Value: status,       
    Label: '상태', 
    Criticality: (status = 'Approved' ? 3 : (status = 'Rejected' ? 1 : 2)),
    CriticalityRepresentation: #WithIcon
  }, 
  { Value: createdAt,    Label: '신청 일자', Importance: #Low },
],

Identification: [
  { $Type: 'UI.DataFieldForAction', Action: 'submit',  Label: '제출' },
  { $Type: 'UI.DataFieldForAction', Action: 'approve', Label: '승인' },
  { $Type: 'UI.DataFieldForAction', Action: 'reject',  Label: '반려' }
],

PresentationVariant: {
  SortOrder: [{ Property: ID, Descending: false }],
  Visualizations: ['@UI.LineItem']
},

// ── Object Page (상세 화면) ────────────────────────────
HeaderInfo: {
  TypeName: '출장 신청',
  TypeNamePlural: '출장 신청 목록',
  Title: { Value: title },
  Description: { Value: destination }
},

Facets: [
  {
    $Type: 'UI.ReferenceFacet',
    Label: '기본 정보',
    Target: '@UI.FieldGroup#Main'
  },
  {
    $Type: 'UI.ReferenceFacet',
    Label: '비용 항목',
    Target: 'items/@UI.LineItem'
  }
],

FieldGroup#Main: {
  Data: [
    { Value: title,          Label: '출장 제목' },
    { Value: destination,    Label: '목적지' },
    { Value: requester,      Label: '신청자' },
    { Value: status,         Label: '진행 상태' },
    { Value: departureDate,  Label: '출발 일자' },
    { Value: returnDate,     Label: '복귀 일자' },
    { Value: totalAmount,    Label: '총 예산 (KRW)' }
  ]
}

}
) {
// 필드 레벨 어노테이션 (드롭다운 강제)
status @Common.Label : '상태'
@Common.ValueListWithFixedValues : true
@Common.ValueList : {
Label: '상태 선택',
CollectionPath: 'TravelRequests',
Parameters: [
{ $Type: 'Common.ValueListParameterInOut', LocalDataProperty: status, ValueListProperty: 'status' }
]
};
requester @Common.Label : '신청자';
departureDate @Common.Label : '출발 일자';
destination @Common.Label : '목적지';
};

// ════════════════════════════════════════════════════
// ExpenseItems 엔티티 어노테이션
// ════════════════════════════════════════════════════

annotate TravelService.ExpenseItems with @(
UI: {
LineItem: [
{ Value: category, Label: '항목 유형' },
{ Value: itemTitle, Label: '설명' },
{ Value: amount, Label: '금액' },
{ Value: expenseDate, Label: '발생일' }
]
}
);


---

### 실습 3. Fiori 앱 로컬 실행 및 통합 테스트

#### 3-1. CAP 서버와 Fiori 앱 동시 실행

**터미널 1 — CAP 서버 (백엔드):**

```bash
cd /home/user/projects/sap-btp-travel-expense/travel-expense-app
cds watch

터미널 2 — Fiori 앱 (프론트엔드):

  • package.json 확인
윗부분 생략...
 "scripts": {
    "start": "fiori run --config ./ui5.yaml --open index.html",
    "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf"
  }

start 부분을 추가해 주세요.

cd /home/user/projects/sap-btp-travel-expense/app/travel_expense_ui
npm start

ui5.yaml 수정 -> 백앤드로 가는 통로 열어줌

# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json

specVersion: "4.0"
metadata:
  name: sap.example.travelexpenseui
type: application
server:
  customMiddleware:
    - name: fiori-tools-proxy
      afterMiddleware: compression
      configuration:
        ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
        backend:
          - path: /travel
            url: http://127.0.0.1:4004
        ui5:
          path:
            - /resources
            - /test-resources
          url: https://sapui5.hana.ondemand.com
    - name: fiori-tools-appreload
      afterMiddleware: compression
      configuration:
        port: 35729
        path: webapp
        delay: 300
    - name: fiori-tools-preview
      afterMiddleware: fiori-tools-appreload
      configuration:
        flp:
          theme: sap_horizon
    - name: ui5-tooling-transpile-middleware
      afterMiddleware: compression
      configuration:
        debug: true
        transformModulesToUI5:
          overridesToOverride: true
        excludePatterns:
          - /Component-preload.js
builder:
  customTasks:
    - name: ui5-tooling-transpile-task
      afterTask: replaceVersion
      configuration:
        debug: true
        transformModulesToUI5:
          overridesToOverride: true

포트 확인:


3-2. Fiori 앱에서 풀스택 시나리오 테스트

Step 1: List Report 로드

브라우저: http://localhost:8080
↓
검색 폼 자동 표시:
- 상태 (드롭다운)
- 신청자 (텍스트)
- 출발일 (날짜)
- 목적지 (텍스트)
↓
목록 테이블 표시:
┌──────────┬──────────┬────────┬────────────┬──────────┐
│ 제목      │ 목적지   │ 신청자  │ 금액       │ 상태     │
├──────────┼──────────┼────────┼────────────┼──────────┤
│ 도쿄...  │ 도쿄     │ 홍길동  │ 850,000   │ 제출됨   │
│ 싱가...  │ 싱가포르  │ 김영희  │ 2,100,000│ 초안     │
└──────────┴──────────┴────────┴────────────┴──────────┘

Step 2: 목록에서 행 클릭 → Object Page

클릭한 행:
"도쿄 고객사 미팅" (ID: 11111111-1111...)
↓
Object Page 로드:
┌─ 기본 정보 ─┬─ 비용 항목 ─┬─ 승인 이력 ─┬─ 부서 ─┐
│ 필드들      │ 테이블     │ 테이블      │ 정보  │
└─────────────┴────────────┴─────────────┴─────┘

기본 정보 탭:
  제목: 도쿄 고객사 미팅
  목적지: 도쿄, 일본
  신청자: 홍길동
  이메일: hong@company.com
  출발일: 2025-09-01
  귀국일: 2025-09-03
  총 출장비: 850,000 KRW
  상태: 제출됨


비용 항목 탭:
┌──────────────────┬───────────┬─────────┐
│ 항목명           │ 카테고리   │ 금액    │
├──────────────────┼───────────┼─────────┤
│ 인천-도쿄 항공권 │ 교통비    │ 450,000 │
│ 도쿄 호텔 2박   │ 숙박비    │ 320,000 │
│ 현지 식비       │ 식비      │ 80,000  │
└──────────────────┴───────────┴─────────┘

승인 이력 탭:
┌───────────────────┬──────────────────────────┐
│ 처리 방식          │ 의견                     │
├───────────────────┼──────────────────────────┤
│ 제출됨            │ 출장 신청이 제출되었습니다│
└───────────────────┴──────────────────────────┘

Step 3: 상태 변경 Action 실행

Object Page의 "승인" 버튼 클릭 (화면 상단 오른쪽):

Fiori가 자동으로 Custom Action UI 생성:
- Action: TravelRequests.approve()
- 입력 필드: comment (텍스트)
↓
승인 의견 입력 후 "승인" 클릭
↓
POST /travel/TravelRequests(ID)/approve
{
  "comment": "출장 목적이 명확합니다. 승인합니다."
}
↓
응답:
{
  "ID": "11111111-...",
  "title": "도쿄 고객사 미팅",
  "status": "Approved",  // ← 변경됨
  ...
}
↓
Fiori UI 자동 갱신:
- 목록 테이블에서 상태 색상 변경 (주황 → 녹색)
- Object Page 상태 업데이트
- 승인 이력 탭에 새 항목 추가

제출이 안된 상태에서 승인을 요청할 경우 오류가 뜬다

=> 상단 제출 버튼을 눌러 제출부터 진행해 본다. (프로세스 순서가 있으니까...)


실습 4. 목록에서 새 항목 추가

Fiori의 "Create" 버튼으로 새 출장비 신청 생성:

  • 준비 작업 : 신규 생성 기능 활성화
  1. 서비스 정의
    srv/travel-service.cds에서 해당 엔티티에 @odata.draft.enabled: true를 추가한다.
  2. UI 어노테이션
    srv/annotations.cds에 Capabilities.Insertable: true를 설정하여 생성 버튼을 노출한다.
  3. 데이터베이스 배포
    새로운 임시 테이블(Draft tables) 생성을 위해 터미널에서 cds deploy 명령어를 반드시 실행한다.
버튼 클릭
↓
Object Page (생성 모드) 로드
↓
필드 입력:
  제목: [입력]
  목적지: [입력]
  출발일: [선택]
  귀국일: [선택]
  ...
↓
"Save" 버튼 클릭
↓
POST /travel/TravelRequests
{
  "title": "부산 파트너사 방문",
  "destination": "부산",
  ...
}
↓
생성 성공 → List Report로 돌아옴
↓
새 항목이 목록에 표시됨

생성을 누르면 "오브젝트가 생성되었습니다" 라는 모달이 뜸.


실습 5. 검색 및 필터 활용

시나리오: "2025-09 이후 출발하는 승인된 출장만 보기"

List Report의 SearchBox:
1. 상태: "Approved" 선택
2. 출발일: "2025-08-01" 이후 선택
3. "Go" 클릭
↓
Fiori → CAP → OData 쿼리 자동 생성:
GET /travel/TravelRequests?
  $filter=status eq 'Approved' and departureDate ge 2025-08-01
  &$orderby=createdAt desc
↓
필터링된 결과만 표시


실습 6. Git 커밋

cd /home/user/projects/sap-btp-travel-expense

# root package.json 업데이트 (선택)
git add .

git commit -m "day3: Fiori Elements UI (List Report + Object Page) + Annotation 적용"

git push origin main

# 다음 날을 위한 브랜치
git checkout -b day4-start
git push origin day4-start
git checkout main

커스터마이징: 추가 Annotation 예시

사용자가 자주 원하는 커스터마이징

1. Object Page에 Action 버튼 추가

// srv/annotations.cds
annotate TravelService.TravelRequests with @(
  UI.HeaderInfo: {
    TypeName: '출장 신청',
    TypeNamePlural: '출장 신청들',
    Title: { Value: title }
  },
  
  // 버튼들
  UI.Identification: [
    { Value: 'submit',  Label: '제출',  Inline: true },
    { Value: 'approve', Label: '승인',  Inline: true },
    { Value: 'reject',  Label: '반려',  Inline: true }
  ]
);

2. 필드를 읽기 전용으로 만들기

annotate TravelService.TravelRequests with {
  totalAmount   @(UI.Hidden: true);  // 목록에서 숨김
  createdAt     @(UI.Hidden: false); // 항상 표시
};

// 또는 상태에 따라
annotate TravelService.TravelRequests with {
  totalAmount   @(Common.FieldControl: {
    $edmType: 'Edm.EnumMember',
    EnumMember: '{status} == "Approved" ? "ReadOnly" : "Editable"'
  });
};

3일차 마무리 체크

확인 항목:
[ ] BAS에서 yo @sap/fiori로 프로젝트 생성 완료
[ ] List Report 자동 생성 확인 (검색폼, 테이블)
[ ] Object Page 자동 생성 확인 (Facets, FieldGroup)
[ ] CAP 서버 + Fiori 앱 동시 실행 성공
[ ] 목록 조회, 항목 클릭, 상세 보기 작동
[ ] Create 버튼으로 새 항목 생성 가능
[ ] 필터/검색 기능 작동
[ ] Approve Action 실행 및 상태 변경 확인
[ ] 필터/정렬 자동 반영 확인
[ ] Git 커밋 완료

Q&A 주제 (강사에게 물어보기):
[ ] Fiori Elements와 Freestyle UI5의 선택 기준은?
[ ] Annotation의 복잡한 조건식은 어떻게 작성?
[ ] List Report의 성능 최적화 (대량 데이터)?

3일차 핵심 정리

오늘 배운 것:
┌─────────────────────────────────────────────┐
│  Fiori Elements = Annotation으로 UI 생성     │
│                                             │
│  3가지 주요 Annotation:                      │
│  - SelectionFields: 검색폼                   │
│  - LineItem: 목록 컬럼                       │
│  - Facets: Object Page 탭                    │
│                                             │
│  CAP의 OData + Fiori Elements               │
│  → 풀스택 엔터프라이즈 앱 자동 생성!         │
└─────────────────────────────────────────────┘

지금까지의 성과:
┌─────────────────────────────────────────────┐
│ DAY1: CAP 백엔드 ✓                          │
│ DAY2: 데이터 모델 고급화 ✓                   │
│ DAY3: Fiori UI 완성 ✓                       │
│                                             │
│ 현재: 로컬에서 완전한 풀스택 앱 완성!        │
│                                             │
│ 다음: 클라우드에 배포 (DAY4)                 │
└─────────────────────────────────────────────┘

내일 할 것:
→ XSUAA (인증) 설정
→ MTA 빌드 (멀티-타겟 애플리케이션)
→ Cloud Foundry에 배포
→ BTP Cockpit에서 앱 확인

다음: [4일차] 클라우드 배포 (XSUAA + MTA + CF Deploy)

profile
개발 정리 공간 - 업무일때도 있고, 공부일때도 있고...

1개의 댓글

comment-user-thumbnail
2026년 4월 27일
  • sqlite deploy
    cds deploy --to sqlite:db/travel.sqlite

  • yo install
    npm list -g yo @sap/generator-fiori --depth=0

답글 달기