4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Certificationも取ったことだし、とりあえずList Reportでいい感じのSales Order Listでも作ってみた

Posted at

はじめに

この記事はのSAP Advent Calendar 202312月10日分の記事として執筆しています。

ま~ず見てくださ~い。↓

デーン!!!!!!
_人人人人人人人人人人人人人人人人人人人人人人人人人人_
image.png
 ̄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

image.png

Service Definition

S_SD_SO
@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

Z_C_SOH
@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
}

Z_C_SOI
@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
}

Z_C_CUSTOMER_VH
@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 */
}

Z_C_SOType_VH
@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

Z_ME_SOH
@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;
}
Z_ME_SOI
@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として使っています。

image.png

I_SalesOrder

I_SalesOrderPartner

I_SalesOrderItem

I_CUSTOMER

実行時の挙動

Z_C_SOHを選択してPreview↓
image.png

初期表示時はこんな感じ↓
image.png

Sales Order Typeはドロップダウンリスト形式で選択可能↓
image.png

受注先・出荷先には検索ヘルプを導入↓
image.png

Goを押下して検索結果を表示↓
image.png

明細をクリックするとObject Pageに遷移する↓
image.png

工夫した点

その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さんの記事の解説が非常に丁寧で、やりたいことに近い記事が公開されていたので、とても参考になりました。
ここに感謝を申し上げます。

参考

4
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?