【SAPUI5】Deep Insertを使ったデータの登録(1)

##Deep Insertとは
Deep Insertとは、ODataの更新方法の一種です。
ODataの更新には他にUPDATEメソッドがありますが、UPDATEはEntity Setごとに実行されます。受注などはヘッダと明細がセットなのでもし失敗したときにどちらか一方だけが登録されることは避けたいです。
Deep Insertは受注のように関連する複数のデータを一度に更新するための方法です。
今回の記事では、Deep Insertを使ってデータを更新するFioriアプリケーションを実装します。ボリュームが大きいので3回くらいに分ける予定です。

##Deep Insertを使った実装
受注のヘッダと明細を同時に登録するアプリケーションを作ってみます。
ODataの練習用に用意されている以下のテーブル、BAPIを利用します。

**テーブル**
SNWD_SO：受注ヘッダ
SNWD_SO_I：受注明細

**BAPI**
BAPI_EPM_SO_CREATE：受注登録用のBAPI

**全体のステップ**
1. ベースになるODataサービスの登録（今回）
2. [Deep Insertのための処理実装](https://qiita.com/tami/items/75c2ca3a2dc10b25d550)
3. [Fioriアプリケーションの登録](https://qiita.com/tami/items/9c251162b959bffe4c56)

##ベースになるODataサービスの実装
GETメソッドでデータを取得できるところまで作ります。

**ステップ**

- Entity Setの登録
- Associationの登録
- ODataサービスの登録
- データ取得メソッドの実装
- ODataサービスのテスト

###Entity Setの登録
新規のプロジェクトを作成し、SalesOrderとSalesOrderItemを登録します。
型は受注登録BAPIで使われる以下の構造をそのまま使っています。
BAPI_EPM_SO_HEADER、BAPI_EPM_SO_ITEM
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/3a232356-a888-7c2c-a32e-1bed970b37b2.png)

データを登録できるように、各項目のCreatable属性にチェックを入れます。（今回更新はしませんが、一応Updatable属性にもチェックを入れました）
SalesOrder
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/85548988-883c-49d5-d2ad-8da836868cc3.png)
SalesOrderItem
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/d575ddb8-504c-4bb5-38f9-d04015880d18.png)


###Associationの登録
ヘッダと明細の関連を登録します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/b70635f8-5670-4d80-c807-2b8672ed8eb5.png)
ヘッダに対して1つ以上の明細がつくので、Cardinalityは1...nとします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/fe31ed0e-3d51-199c-4665-34d6d409b956.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/9c40b758-15aa-bbdd-5791-24371d1e7da0.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/97813ac3-7b7b-e179-b4e8-17fbbcc2698a.png)

###ODataサービスの登録
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/3e881d2a-ba33-c629-e482-79b13c76e7ae.png)ボタンを押して、MPC, DPCクラスを生成します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/b96e0536-9cc2-3068-71f3-96fc03083cb3.png)
サービスを登録します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/07d6ad8d-e610-c945-458e-f1fb05cf66b3.png)
この環境はバックエンドとゲートウェイを兼ねているので、システムエイリアスにLOCALを指定します。（ゲートウェイの導入シナリオについては[こちら](https://qiita.com/tami/items/a87a6f67be928c78e4e8)の記事を参照）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/99f6c774-7c5a-480b-5a13-c28c75e0c7fa.png)

###データ取得メソッドの実装
Entity（単数）、Entity Set（複数）の取得用メソッドを実装します。
DPC_EXTとついたクラスをダブルクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/4ac7718b-522c-ea89-689d-e2f78d2a42df.png)

ヘッダを1件取得する（GET_ENTITY）

```abap
  METHOD salesorderset_get_entity.

    DATA: lv_so_id      TYPE bapi_epm_so_id,
          ls_headerdata TYPE bapi_epm_so_header,
          lt_return     TYPE TABLE OF bapiret2.

    "URIで指定された受注番号を取得
    READ TABLE it_key_tab INTO DATA(ls_key) WITH KEY name = 'SoId'.
    "内部書式に変換する（例：500000000 → 0500000000)
    CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
      EXPORTING
        input  = ls_key-value
      IMPORTING
        output = lv_so_id.
    "1件の受注を取得する
    CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
      EXPORTING
        so_id      = lv_so_id
      IMPORTING
        headerdata = ls_headerdata
      TABLES
        return     = lt_return.

    "エラーの場合、例外を発生させてメッセージを返す
    IF lt_return IS NOT INITIAL.
      me->mo_context->get_message_container( )->add_messages_from_bapi(
        EXPORTING
          it_bapi_messages          = lt_return                 " Return parameter table
      ).

      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          message_container = me->mo_context->get_message_container( ).
    ENDIF.

    "出力用の構造にデータをセット
    MOVE-CORRESPONDING ls_headerdata TO er_entity.

  ENDMETHOD.
```

ヘッダを複数件取得（GET_ENTITYSET）

```abap
  METHOD salesorderset_get_entityset.
    DATA: ls_row    TYPE bapi_epm_max_rows,
          lt_header TYPE TABLE OF bapi_epm_so_header,
          lt_return TYPE TABLE OF bapiret2,
          ls_entity LIKE LINE OF et_entityset.

    "bapimaxrow = top（表示件数） + skip（読み飛ばす件数）　
    "例：top:10, skip5 → 6件目以降のデータを10個表示
    ls_row-bapimaxrow = is_paging-top + is_paging-skip.
    CALL FUNCTION 'BAPI_EPM_SO_GET_LIST'
      EXPORTING
        max_rows     = ls_row
      TABLES
        soheaderdata = lt_header
        return       = lt_return.

    "エラーの場合、例外を発生させてメッセージを返す
    IF lt_return IS NOT INITIAL.
      me->mo_context->get_message_container( )->add_messages_from_bapi(
        EXPORTING
          it_bapi_messages          = lt_return                 " Return parameter table
      ).

      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          message_container = me->mo_context->get_message_container( ).
    ENDIF.

   "top, skipを考慮する場合
    IF is_paging-skip IS NOT INITIAL.
      DATA(lv_start) = is_paging-skip + 1. " 読み込み開始行
      DATA(lv_end) = ls_row-bapimaxrow.   " 読み込み終了行
      LOOP AT lt_header INTO DATA(ls_header) FROM lv_start TO lv_end.
        MOVE-CORRESPONDING ls_header TO ls_entity.
        APPEND ls_entity TO et_entityset.
      ENDLOOP.
    ELSE.
    "top, skipを考慮しない場合
      LOOP AT lt_header INTO ls_header.
        MOVE-CORRESPONDING ls_header TO ls_entity.
        APPEND ls_entity TO et_entityset.
      ENDLOOP.
    ENDIF.

  ENDMETHOD.
```
受注データは大量になるので、指定した件数だけ取得できるようtop, skipのクエリオプションに対応した作りにしておきます。（top, skipについては[こちら](https://qiita.com/tami/items/181cabc4cd8c51ce87b2)）


明細を取得（GET_ENTITYSET）
今回はヘッダにひもづく複数件の明細を取得するという使い方なので、GET_ENTITYSETメソッドのみ実装します。

```abap
  METHOD salesorderitemse_get_entityset.
    DATA: lv_so_id  TYPE bapi_epm_so_id,
          lt_item   TYPE TABLE OF bapi_epm_so_item,
          lt_return TYPE TABLE OF bapiret2.

   "受注番号を取得
    READ TABLE it_key_tab INTO DATA(ls_key) WITH KEY name = 'SoId'.
   "内部書式に変換
    CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
      EXPORTING
        input         = ls_key-value
     IMPORTING
       OUTPUT        = lv_so_id
              .

    "明細を取得
    CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
      EXPORTING
        so_id    = lv_so_id
*     IMPORTING
*       HEADERDATA       =
      TABLES
        itemdata = lt_item
        return   = lt_return.

    "エラーの場合、例外を発生させてメッセージを返す
    IF lt_return IS NOT INITIAL.
      me->mo_context->get_message_container( )->add_messages_from_bapi(
        EXPORTING
          it_bapi_messages          = lt_return                 " Return parameter table
      ).

      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
        EXPORTING
          message_container = me->mo_context->get_message_container( ).
    ENDIF.

    "出力用のテーブルにデータをセット
    MOVE-CORRESPONDING lt_item TO et_entityset.

  ENDMETHOD.
```
###ODataサービスのテスト
ここまでの実装が終わったら、サービスをテストしてみましょう。
トランザクション：/IWFND/GW_CLIENTを起動します。

####①受注ヘッダを取得
Request URIに以下のように入力します。先頭3件の受注を取得するという意味です。
`/sap/opu/odata/SAP/<サービス名>/SalesOrderSet?$top=3&$format=json`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/b3ec9866-8345-8b9b-3831-0c3fac5058c9.png)
3件の受注が返ってくれば成功です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/46cf1a6e-05b1-611f-688a-58527d7edfa6.png)

####②受注明細を取得
結果を下のほうにするスクロールすると、"ToSalesOrderItem"という項目があります。これはナビゲーションプロパティです。ここに書かれたuriを指定すると明細に遷移できるこいうことを教えてくれています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/e32749e6-e475-8d74-c1ea-f81177771852.png)
Request URIに以下のように入力します。
`/sap/opu/odata/SAP/<サービス名>/SalesOrderSet('500000001')/ToSalesOrderItem?$format=json`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/7d2bcfac-e054-16b1-7e4f-fd77b5012328.png)
受注にひもづく複数件の明細が取得できれば成功です。

####③ヘッダと明細をセットで取得
最後に、ヘッダと明細を並べて取得します。並べて取得するためにはクエリオプションexpandを使います。
Request URIに以下のように入力します。
`/sap/opu/odata/SAP/<サービス名>/SalesOrderSet('500000001')?$expand=ToSalesOrderItem&$format=json`

上にヘッダ、下に明細が表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/ce597169-fe0a-1892-8e99-119c42ee539b.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/346657c7-60f8-bc95-6553-85bff8ebdef4.png)

この形がDeepInsertの元になります。（そのままではありませんが）
次回はDeep Insertのためのメソッドを実装します。