はじめに
この記事はのSAP Advent Calendar 202312月10日分の記事として執筆しています。
ま~ず見てくださ~い。↓
デーン!!!!!!
_人人人人人人人人人人人人人人人人人人人人人人人人人人_
 ̄Y^Y^Y^Y^Y^Y^^Y^Y^Y^Y^^Y^Y^Y^Y^^Y^Y^Y^Y^^Y^Y^Y^Y^^Y ̄
ドヤア!
というわけで、認定資格も取ったことなので、Fiori ElementsのList ReportとObject Pageを利用していい感じのSales Order Listでも作ってみました。
なんてったってCertifyされてるんで、シンプルな見る専アプリくらい余裕っしょw
いわゆるRAPという方法で開発しています。
デプロイまではしていません。
CDS Annotationを使ってEclipseで完結する方法と、EclipseでODataをPublishして以降は、BAS(ないしVS Code)でService ModelerやGuided Developmentを駆使してUIを作り込む方法とがありますが、今回は前者で作ってみました。エディター2つも使うのもめんどくさいので。。
(上記2つのFiori Elements開発手法をどのように使い分ければよいか、についてはこちらのAnswerがわかりやすいです。基本はCDS Annotationで作り、それができない or 1つのOData Serviceで複数のアプリを作りたい、といった場合にannotation modelerを使うと良いそうです。)
前提
- S/4HANAオンプレ版を使えること
- EclipseにABAP ADTをインストールしてあること
ファイル構成
Business Service
|-Service Bindings
S_SB_SO
|-Service Definition
Z_SD_SO
Core Data Services
|-Data Definitions
Z_C_SOH
Z_C_SOI
Z_C_SOTYPE_VH
Z_C_CUSTOMER_VH
Metadata Extension
ZM_ME_SOH
ZM_ME_SOI
Service Bindings
Service Definition
@EndUserText.label: 'Service Definition for SalesOrder'
define service Z_SD_SO {
expose Z_C_SOH;
expose Z_C_SOI;
expose Z_C_CUSTOMER_VH;
expose Z_C_SOType_VH;
}
Data Definitions
@AbapCatalog.sqlViewName: 'ZCSOH_231201'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'SalesOrderHeader'
@Metadata.allowExtensions: true
@OData.publish: true
@Search.searchable: true
define view Z_C_SOH as select from I_SalesOrder
inner join tvakt as TVAKT
on I_SalesOrder.SalesOrderType = tvakt.auart and tvakt.spras = 'E'
association [0..*] to I_SalesOrderPartner as _Partner_AG
on $projection.SalesOrder = _Partner_AG.SalesOrder and _Partner_AG.PartnerFunction = 'AG'
association [0..*] to I_SalesOrderPartner as _Partner_WE
on $projection.SalesOrder = _Partner_WE.SalesOrder and _Partner_WE.PartnerFunction = 'WE'
association [0..*] to Z_C_SOI as _SOI
on $projection.SalesOrder = _SOI.SalesOrder
association [0..1] to Z_C_CUSTOMER_VH as _Customer on $projection.SoldToParty = _Customer.Customer
{
@Search.defaultSearchElement: true
key I_SalesOrder.SalesOrder,
@Consumption.valueHelpDefinition: [{ entity: { name: 'Z_C_SOType_VH', element: 'SalesOrderType' } }]
@ObjectModel.text.element: ['SalesOrderTypeDescription']
I_SalesOrder.SalesOrderType,
tvakt.bezei as SalesOrderTypeDescription,
I_SalesOrder.SoldToParty,
@Consumption.valueHelpDefinition: [{ entity: { name: 'Z_C_CUSTOMER_VH', element: 'Customer' } }]
@ObjectModel.text.element: ['FullName_AG']
_Partner_AG.Customer as Customer_AG,
_Partner_AG.FullName as FullName_AG,
@Consumption.valueHelpDefinition: [{ entity: { name: 'Z_C_CUSTOMER_VH', element: 'Customer' } }]
@ObjectModel.text.element: ['FullName_WE']
_Partner_WE.Customer as Customer_WE,
_Partner_WE.FullName as FullName_WE,
I_SalesOrder.SalesOrderDate,
_SOI.SalesOrderItem,
_SOI.Material,
_SOI.MaterialName,
_SOI.Plant,
_SOI.StorageLocation,
_SOI.SalesOrderItemText,
/* Associations */
@ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
_Partner_AG,
_Partner_WE,
_SOI,
_Customer
}
@AbapCatalog.sqlViewName: 'ZCSOI'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'SalesOrderItem'
@Metadata.allowExtensions: true
@OData.publish: true
@Search.searchable: true
define view Z_C_SOI as select from I_SalesOrderItem
association [1..1] to Z_C_SOH as _SOH on $projection.SalesOrder = _SOH.SalesOrder
association [0..*] to I_MaterialText as _MaterialText on $projection.Material = _MaterialText.Material and _MaterialText.Language = 'E'
{
@Search.defaultSearchElement: true
key SalesOrder,
key SalesOrderItem,
Material,
_MaterialText.MaterialName,
Plant,
StorageLocation,
SalesOrderItemText,
@Semantics.quantity.unitOfMeasure : 'OrderQuantityUnit'
OrderQuantity,
OrderQuantityUnit,
@Semantics.amount.currencyCode : 'TransactionCurrency'
NetPriceAmount,
@Semantics.amount.currencyCode : 'TransactionCurrency'
NetAmount,
TransactionCurrency,
/* Associations */
@ObjectModel.association.type: [#TO_COMPOSITION_PARENT, #TO_COMPOSITION_ROOT]
_SOH,
_MaterialText
}
@AbapCatalog.sqlViewName: 'ZCCUSTOMERVH'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Customer Value Help'
define view Z_C_CUSTOMER_VH as select from I_Customer
{
key Customer,
CustomerName,
CustomerAccountGroup,
Country,
BusinessType
/* Associations */
}
@AbapCatalog.sqlViewName: 'ZCSOTYPEVH'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'SalesOrderType Value Help'
@Search.searchable: true
@ObjectModel.representativeKey: 'SalesOrderType'
@ObjectModel.resultSet.sizeCategory: #XS //ドロップダウンにする
define view Z_C_SOType_VH as select from tvak as TVAK
inner join tvakt as TVAKT
on TVAK.auart = tvakt.auart and tvakt.spras = 'E'
{
@Search.defaultSearchElement: true
@ObjectModel.text.element: ['SalesOrderTypeDescription']
key TVAK.auart as SalesOrderType,
TVAKT.bezei as SalesOrderTypeDescription
}
where tvakt.bezei is not initial
Metadata Extension
@Metadata.layer: #CORE
@UI: {
headerInfo: {
typeName: 'SOList',
typeNamePlural: 'Sales Orders',
title: { type: #STANDARD, value: 'SalesOrder' } ,
description: { value: 'Customer_AG' }
}
}
annotate view Z_C_SOH
with
{
@UI.facet: [
{
id: 'HeaderFacet',
type: #COLLECTION,
purpose: #STANDARD,
label: 'Order Information',
position: 10
},
// Header Info Group
{
id: 'BasicGroup',
parentId: 'HeaderFacet',
type: #FIELDGROUP_REFERENCE,
purpose: #STANDARD,
label: 'Basic Information',
targetQualifier: 'BasicFieldGroup' ,
position: 10
},
// Customer Group
{
id: 'CustomerGroup',
type: #FIELDGROUP_REFERENCE,
parentId: 'HeaderFacet',
purpose: #STANDARD,
label: 'Customer Group',
targetQualifier: 'CustomerFieldGroup',
position: 20
},
{
id: 'ItemFacet',
purpose: #STANDARD,
type: #LINEITEM_REFERENCE,
label: 'Item',
position: 20,
targetElement: '_SOI'
}
]
@UI.selectionField: [{position: 10 }]
@UI.lineItem: [{position:10 }]
@UI.identification: [{ position: 10, label: 'SalesOrder' }]
SalesOrder;
@UI: {
selectionField: [{position: 20 }],
lineItem: [{ position: 20, importance: #HIGH }],
fieldGroup: [{ qualifier: 'BasicFieldGroup', position: 20, importance: #MEDIUM }]
}
SalesOrderType;
@UI: {
selectionField: [{position: 30 }],
lineItem: [{ position: 30, importance: #HIGH }],
fieldGroup: [{ qualifier: 'CustomerFieldGroup', position: 10, importance: #MEDIUM }]
}
@EndUserText.label: 'Sold to Party'
Customer_AG;
@UI: {
selectionField: [{position: 40 }],
lineItem: [{ position: 40, importance: #HIGH }],
fieldGroup: [{ qualifier: 'CustomerFieldGroup', position: 20, importance: #MEDIUM }]
}
@EndUserText.label: 'Ship to Party'
Customer_WE;
@UI: {
lineItem: [{ position: 50, importance: #HIGH }]
}
SalesOrderItem;
@UI: {
lineItem: [{ position: 60, importance: #HIGH }]
}
Material;
}
@Metadata.layer: #CORE
@UI: {
headerInfo: {
typeName: 'Item',
typeNamePlural: 'Items',
title: { value: 'SalesOrderItem' },
description: { value: 'Material' }
}
}
annotate view Z_C_SOI
with
{
@UI.lineItem: [ { position: 10 } ]
SalesOrderItem;
@UI.lineItem: [ { position: 20 } ]
Material;
@UI.lineItem: [ { position: 30 } ]
MaterialName;
@UI.lineItem: [ { position: 40 } ]
Plant;
@UI.lineItem: [ { position: 50 } ]
SalesOrderItemText;
@UI.lineItem: [ { position: 60 } ]
OrderQuantity;
}
Interface Viewについて
こちらのブログでも紹介されている、標準のCDS ViewをInterfaceViewとして使っています。
実行時の挙動
Sales Order Typeはドロップダウンリスト形式で選択可能↓
工夫した点
その1:明細レベルでの一覧出力
やっぱ受注一覧作るのであれば、明細レベルで一覧出力したいですよね。
というわけなので、Z_C_SOHはZ_C_SOIとAssociationで関連付けして、明細の項目もListReportの一覧に出るようにしました。
その2:受注伝票番号と受注先・出荷先との紐付
受注に紐づく受注先(取引先機能:AG)と出荷先(取引先機能:WE)を列で出力してみました。SQLで言うと、子テーブルをJOINし子テーブルのPKを指定して列で出す感じですね。
その3:伝票タイプはドロップダウンリスト形式で選択
検索項目のうち一つはドロップダウンリストにしたかったので、伝票タイプをそのようにしています。伝票タイプの標準CDS Viewは探せなかったので、CDS ViewのZ_C_SOType_VH
だけはテーブルを直接指定しています。
アプリを作ってみた感想
まあ認定資格は仮免許みたいなもので、取ったからと言ってFiori開発がサクサクできるわけでは全く無かったです。試行錯誤して手を動かし、ハマり苦しみながらようやく完成までこぎ着けたって感じです。
実際に動くモノを作らないと、習熟ってできないんだなと思いました。
Fiori ElementsのFloorPlanの中ではおそらくList Reportが最もメジャーで、多くの初学者が最初に作り始めることになると思います。List ReportはクラシックなABAPで言うところのALVのレポートプログラムに相当するFloor Planだと思います。
Fiori Elementsはローコードツール的なものの一つだと思っていて、ABAPでレポートを作り込むより簡単なのかなーと思っていたのですが、そんなことはなかったです。
CDS Viewに一つ項目を追加すると検索結果がでなくなるようになったり、Object Pageに明細を表示させるのに小一時間ハマったりで、だいぶ苦戦しました。
検索ヘルプについても、クラシックなABAPだととくに意識せず勝手に補完してくれていたのですが、RAPだとCDS Viewで作り込まないといけないですよね。
RAPとかFioriElementsも、クラシックなABAPと比べて開発が簡単になります!とかではなくで、イチから覚え直さないとダメそうです。
ただ、@tamiさんの記事の解説が非常に丁寧で、やりたいことに近い記事が公開されていたので、とても参考になりました。
ここに感謝を申し上げます。
参考