UI5 Custom Control Excel Upload (UI5 Custom Control Spreadsheet Upload) を使ってみた

## はじめに
先日、Fiori elementsでExcelデータをアップロードするためのオープンソースのコンポーネント、**ui5-cc-excelupload**（※）が登場しました。SAP Communityメンバーの[Marian Zeis](https://people.sap.com/mariannnn)氏によって作られたものです。

https://blogs.sap.com/2023/02/17/simplifying-excel-upload-in-fiori-elements-the-open-source-and-easy-to-use-ui5-custom-control/

使い方はこちらのドキュメントに書かれています。
https://docs.spreadsheet-importer.com/

この記事では、ui5-cc-exceluploadを使ってみた結果について共有します。

※2023/8/10更新
ライブラリの名称は"**ui5-cc-spreadsheetimporter**"に変わりました。

## ui5-cc-exceluploadとは何か
ui5-cc-exceluploadはSAPUI5のReuse Componentです。Reuse Component とは、独立して開発・デプロイができるアプリケーションで、他のアプリに部品として組み込むことができます。
[npmのパッケージ](https://www.npmjs.com/package/ui5-cc-excelupload)として提供されており、既存のFiori elementsアプリにインストールして使います。
独立したコンポーネントのため、それ自体を先にデプロイして他のアプリで共用することもできますし([Central Deployment](https://marianfoo.github.io/ui5-cc-excelUpload/pages/CentralDeployment/))、各アプリにバンドルしてデプロイすることもできます([Decentralised deployment](https://marianfoo.github.io/ui5-cc-excelUpload/pages/GettingStarted/#decentralised-deployment))。

## 特徴
ui5-cc-exceluploadは以下の機能を備えています。
- List Report / Object Pageからのアップロード
- ドラフトあり / なしのアップロード
- 必須項目のチェック
- アップロード前、およびバックエンドに送信前にロジック拡張が可能
- 複数バージョンのサポート（メインのアプリケーションにui5-cc-exceluploadをバンドルしてデプロイすることにより、アプリごとに異なるバージョンのui5-cc-exceluploadを使える）

参考：[Main Features](https://marianfoo.github.io/ui5-cc-excelUpload/#main-features)

## 機能概要
### 基本的な動作
以下はObject Pageのテーブルにアップロードする例です。
画面にアップロードボタンが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7e833a7a-82f3-45e5-d38a-5e9c9624cd3f.png)
ボタンを押すとダイアログが表示されます。"Download Template"ボタンから、テンプレートをダウンロードすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/85d61ef8-fb42-c094-1ef5-dcfc89c359ba.png)
テンプレートはODataのメタデータを参照して生成され、デフォルトでは全項目が入っています（カスタマイズ可能）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/be9a924b-fa10-d6df-bd09-9b6f5ad54da4.png)
"Browse"ボタンからファイルを選択するとアップロードファイルがチェックされ、問題がなければ"Upload Successful"というメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/25ecfb33-d934-26da-cedc-f93fffb87a11.png)
"Upload"ボタンを押すとバックエンドにデータが送信され、画面に反映されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/52b74bf4-6bed-6e02-d532-259aec61fe99.png)

### 設定方法
ui5-cc-exceluploadは、Fiori elementsのページを拡張してアップロード用のボタンを配置することにより実装します。設定方法は2つあります。この後で実際に設定してみます。

- マニュアルで設定（モジュールのインストール、拡張、ソースコードの実装を自分で行う）
- [yo Generator](https://marianfoo.github.io/ui5-cc-excelUpload/pages/Generator/)で設定

### カスタマイズ
コンポーネントに渡すオプションパラメータによって、以下の設定が可能です。
参考：[Configuration](https://marianfoo.github.io/ui5-cc-excelUpload/pages/Configuration/)

|オプション  |説明  |
|---|---|
|columns  |テンプレートファイルに含まれるカラム（配列）  |
|excelFileName  |テンプレートファイルの名称  |
|tableId  |Object Pageにテーブルが複数ある場合に、対象のテーブルのIDを指定する  |
|odataType  |アップロード対象のエンティティのタイプを指定（デフォルトでは対象のテーブルから取得される。特定できずエラーになった場合に指定する）  |
|mandatoryFields  |アップロード時に必須になる項目（配列）  |
|fieldMatchType  |テンプレートのヘッダに使われるラベルの設定方法。label（ラベルのみ）か、labelTypeBrackets（ラベル[項目ID]）を選択する  |
|activateDraft |アップロード後にドラフトを有効化するかどうか。デフォルトではfalseで、ドラフト状態でアップロードされる|

### 拡張
以下の2つの拡張ポイントが用意されています。
参考：[Events](https://marianfoo.github.io/ui5-cc-excelUpload/pages/Events/)

1. アップロード前にデータをチェックする
2. バックエンドに送信する前にデータを変更する

## やってみたこと
1. マニュアルで設定
1. yo Generatorで設定
1. 設定を追加

ベースは以下のGitリポジトリにあるCAPのプロジェクトです。（OData V4、ドラフトあり）
https://github.com/miyasuta/flex-orders
開発環境はVS Codeを使用しました。

### 1. マニュアルで設定
まず、Fiori toolsを使ってList Reportを生成しますが、このときにデータソースを"Use a Local CAP Project"ではなく"Connect to an OData Service"（またはConnect to a System）にするのがポイントです。つまり、CAPの一部ではなく独立したUIアプリケーションとして作成するということです。そうしないとui5-toolingを使う設定が入っていないので、すぐにui5-cc-exceluploadを使うことができません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b523768a-7fdb-2801-b291-4884a49c2eda.png)
アプリケーションが登録できたら、以下の設定を行います。

#### 1.1. ui5-cc-exceluploadをインストール
```
npm install ui5-cc-excelupload
```
#### 1.2. package.jsonの設定（UI5 Tooling V3の場合は不要）
ui5.dependenciesセクションに追加
```json
  "ui5": {
    "dependencies": [
      ...,
      "ui5-cc-excelupload"
    ]
  }
```
build用スクリプトに"--all"を追加
ビルド結果にui5-cc-exceluploadのモジュールが含まれるようにする
```json
"build": "ui5 build --config=ui5.yaml --all --clean-dest --dest dist",
```

#### 1.3. manifest.jsonの設定
resourceRootsとcomponentUsageを追加する
```json
    "sap.ui5": {
        "resourceRoots": {
            "cc.excelUpload.v0.9.0": "./thirdparty/customControl/excelUpload/v0/9/0"
        },
        "componentUsages": {
            "excelUpload": {
                "name": "cc.excelUpload.v0.9.0"
            }
        },
```

以上がui5-cc-exceluploadを使うための事前準備です。以下ではObject PageからExcelアップロードを行えるように設定を行います。ここではマニュアルで設定していますが、Fiori toolsのGuided Developmentを使ってボタンおよびコントローラーを追加することもできます。

#### 1.4. manifest.jsonで拡張の設定
Object Pageのoptions.settings.contentに以下の設定を追加します。Guided Developmentで生成した場合は` "enabled": "{ui>/isEditable}",`の設定が入らないのでマニュアルで追加が必要です。
```json
                "OrdersObjectPage": {
                    "type": "Component",
                    "id": "OrdersObjectPage",
                    "name": "sap.fe.templates.ObjectPage",
                    "options": {
                        "settings": {
                            "editableHeaderContent": false,
                            "entitySet": "Orders",
                            "navigation": {
                                "to_Items": {
                                    "detail": {
                                        "route": "ItemsObjectPage"
                                    }
                                }
                            },
                            "content": {
                                "header": {
                                    "actions": {
                                        "excelUploadButton": {
                                            "id": "excelUploadButtonButton",
                                            "text": "Excel Upload",
                                            "press": "miyasuta.orders2.custom.ObjectPageExtController.openExcelUploadDialog",
                                            "enabled": "{ui>/isEditable}",
                                            "requiresSelection": false
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
```

#### 1.5. カスタムコードの実装
カスタムコードを以下のように実装します。設定が必要な場合はcomponentDataに指定します。

```javascript:
sap.ui.define([],
function (){
    "use strict";
    return {
        openExcelUploadDialog: async function(oEvent) {
            this._view.setBusyIndicatorDelay(0)
            this._view.setBusy(true)
            if (!this.excelUpload) {
                this.excelUpload = await this._controller.getAppComponent().createComponent({
                    usage: "excelUpload",
                    async: true,
                    componentData: {
                        context: this
                    }
                })
            }
            this.excelUpload.openExcelUploadDialog()
            this._view.setBusy(false)
        }
    };
});

```

### 2. yo Generatorで設定
次は、yo Generatorを使用して設定する方法です。yo Generatorは1.で行ったほとんどのステップを自動で実行してくれます。

#### 2.1. Generatorをインストール
```
npm i -g yo
npm i -g generator-ui5-excelupload
```

#### 2.2. Generatorを使用
対象のプロジェクトに移動し、以下のコマンドを実行します。
```
yo ui5-excelupload
```
いくつかの質問がされるので、答えていきます。
※画面ショットでは"Which entity Set?"に対して"Items"と回答していますが、Object Pageで明細をアップロードしたい場合はヘッダレベルのエンティティを指定する必要がありました。間違えてしまっても、あとでmanifest.jsonを修正すればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c3a35260-83c9-b0c8-8ec5-35dc2d61ca87.png)

#### 2.3. イベントハンドラのネームスペースを修正
2023/3/4追記
generator-ui5-exceluploadのバージョン0.2.2で以下の問題は修正されているので、対応不要です。

manifest.jsonのアクション拡張の設定で、"press"のイベントハンドラのネームスペースが"ui4.cc.v4.samplev4exelupload"となっているので自分のプロジェクトに合わせて修正が必要でした（2/24時点）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7501f4b3-97e3-d91f-4b75-884543d61a4d.png)

#### 2.4. buildコマンドの確認
package.jsonのbuildコマンドを確認すると、"--all"がついていませんでした。UI5 ToolingのV3でない場合は追加が必要です。
```json
    "build": "ui5 build --config=ui5.yaml --clean-dest --dest dist",
```

### 3. 設定を追加
List Reportのページにも1.と同様の設定を行い、ボタンを追加しました。これに対して以下の設定を行います。
- テンプレートの項目を指定
- 必須チェックを追加
- アップロード後にドラフトを有効化する

デフォルトのテンプレートは以下のようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/72a1bc70-317f-ec2a-b2ea-72f8c2ff3426.png)

デフォルトではドラフト状態でアップロードされるので、アップロード直後は以下のように見えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/71c22547-df1a-8c8d-a2ec-04cf16d7fd15.png)

コントローラーで以下のように設定を入れます。

```javascript
                this.excelUpload = await this._controller.getAppComponent().createComponent({
                    usage: "excelUpload",
                    async: true,
                    componentData: {
                        context: this,
                        activateDraft: true, //ドラフトを有効化
                        columns: ["description", "customer_ID"], //テンプレートの項目
                        mandatoryFields: ["customer_ID"] //必須項目

                    }
                })
```

結果、テンプレートは以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/6cabc55a-21a0-eed1-3c87-afe5b1df2e85.png)

必須項目を入力せずにアップロードするとエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/840e4507-9a51-0985-78c6-b5a2b9fe025a.png)

アップロード後、ドラフトが有効化された状態になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bba9ae52-df84-3c27-c7aa-fc057be6d700.png)

## 気づき
実装してみて気づいた点を挙げます。

### 1. 複数レコードを同時にアップロードすると、CAP側での採番がうまくいかない
今回使用したCAPのプロジェクトでは、ヘッダ、明細ともに表示用のIDを内部採番していました。そのロジックは、「最大のIDを取得し、1加算する」（明細の場合は伝票内で最大の明細IDを取得して加算）という簡単なものでした。
アップロード時、バッチリクエストで複数のデータが同時に飛ぶためCAPのイベントハンドラが同時に呼ばれ、IDが重複してしまいました。

これを回避するためには、ヘッダ、明細それぞれで以下のような対応が必要と考えます。
- ヘッダ：IDの採番は[DBのシークエンスを使用](https://blogs.sap.com/2020/07/04/cap-using-hana-db-sequence/)して重複が起こらないようにする
- 明細：ui5-cc-exceluploadの拡張を利用して、アップロード前にフロントエンド側で採番を行う。CAP側はIDが指定されていない場合のみ採番を行う

### 2. List Reportから実行の場合、"Go"ボタンを押してからでないと機能しない
"Go"ボタンを押さずにExcel Uploadの"Download Template"ボタンを押すと、何も起きません。コンソールを見るとエラーが起きています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5d791b75-8783-1823-2ed2-784ba6cc8ee6.png)
Goボタンが押される前はテーブルにまだバインドが行われておらず、必要な情報が取ってこられないためでした。
この件はすでにIssueとして上げられていました。~~現時点では対応方法は見つかっていないとのことです。~~
https://github.com/marianfoo/ui5-cc-excelUpload/issues/18

2023/3/4追記
Goを押す前にボタンを押した場合、以下のようなダイアログが出るようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/26b3576a-5598-f3c2-8cf1-8d777f2040f6.png)

## おわりに
「List ReportからExcelでデータをアップロードする」という機能は、標準で装備されていてもいいくらい、どのプロジェクトでも欲しいものではないでしょうか。そんなツールを一人で作り上げ、使いやすいドキュメントまで整備してくださったMarian Zeis氏に感謝です。