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

##概要
今回は、Deep Insertをするための処理を作成します。

前回の記事：[Deep Insertを使ったデータの登録(1)](https://qiita.com/tami/items/98fdef77d7645429d402)
次回の記事：[Deep Insertを使ったデータの登録(3)](https://qiita.com/tami/items/9c251162b959bffe4c56)


Deep Insertをするためには、ODataのDPC拡張クラスでCREATE_DEEP_ENTITYメソッドを実装します。
Deep Insertをするとき、Fioriアプリケーション側から構造の中にテーブルを持った型でデータを渡します。（これをDeepな構造という）
この形で渡されたとき、自動的にCREATE_ENTITYではなくCREATE_DEEP_ENTITYが呼ばれます。

Deepな構造の例
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/993247b9-bd01-9db5-c0bd-696dfd33e0b3.png)


##ステップ

- Deep Insert用の処理を実装
- Deep Insertのテスト

###Deep Insert用の処理を実装
DPC拡張クラスで以下の処理を実装します。
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_DEEP_ENTITY

```abap
  METHOD /iwbep/if_mgw_appl_srv_runtime~create_deep_entity.
*   Deep entityの型
    DATA: BEGIN OF ls_salesorder.
        INCLUDE TYPE zcl_z_mob58_04_salesor_mpc_ext=>ts_salesorder.
    DATA: tosalesorderitem TYPE zcl_z_mob58_04_salesor_mpc_ext=>tt_salesorderitem,
          END OF ls_salesorder.
    DATA: ls_headerdata TYPE bapi_epm_so_header,
          lt_itemdata   TYPE TABLE OF bapi_epm_so_item,
          ls_itemdata   TYPE bapi_epm_so_item,
          lv_so_id      TYPE bapi_epm_so_id,
          lt_return     TYPE TABLE OF bapiret2.

    "EntitySet名により分岐
    CASE iv_entity_set_name.
      WHEN 'SalesOrderSet'.
        "アプリケーションから渡されたデータを読み込み
        io_data_provider->read_entry_data(
          IMPORTING
            es_data = ls_salesorder
        ).
        "ヘッダをBAPI受け渡し用の構造にセット
        MOVE-CORRESPONDING ls_salesorder TO ls_headerdata.
        "明細をBAPI受け渡し用のテーブルにセット
        LOOP AT ls_salesorder-tosalesorderitem INTO DATA(ls_item).
          MOVE-CORRESPONDING ls_item TO ls_itemdata.
          "delivery_dateにタイムスタンプを設定する
          GET TIME STAMP FIELD ls_itemdata-delivery_date.
          APPEND ls_itemdata TO lt_itemdata.
        ENDLOOP.

        CALL FUNCTION 'BAPI_EPM_SO_CREATE'
          EXPORTING
            headerdata    = ls_headerdata
*            persist_to_db = abap_true
          IMPORTING
            salesorderid  = lv_so_id
          TABLES
            itemdata      = lt_itemdata
            return        = lt_return.

        "エラーハンドリング
        READ TABLE lt_return WITH KEY type = 'E' TRANSPORTING NO FIELDS.
        IF sy-subrc = 0.
          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 = mo_context->get_message_container( ).
        ENDIF.

        "登録された受注の情報を取得
        CLEAR: ls_headerdata, lt_itemdata, ls_salesorder.
        CALL FUNCTION 'BAPI_EPM_SO_GET_DETAIL'
          EXPORTING
            so_id      = lv_so_id
          IMPORTING
            headerdata = ls_headerdata
          TABLES
            itemdata   = lt_itemdata
*           RETURN     =
          .

        "登録された情報を設定してアプリケーションへ返す
        MOVE-CORRESPONDING ls_headerdata TO ls_salesorder.
        me->copy_data_to_ref(
          EXPORTING
            is_data = ls_salesorder
          CHANGING
            cr_data = er_deep_entity
        ).

      WHEN OTHERS.
    ENDCASE.

  ENDMETHOD.
```

先頭で定義したls_salesorderという構造は、アプリケーションからDeepな構造でデータを受け取るためのものです。
大まかな処理の流れは以下のようになっています。
①アプリケーションから渡されたデータ（Deepな構造）を読み込み
②BAPIで受注を登録
③登録された情報を取得してアプリケーションに返す
このメソッドは同じODataサービスに属するすべてのEntity Setが利用する可能性があるため、Entity Setの名前によって処理を分岐させています。

###Deep Insertのテスト
Deep Insertで受注を登録してみます。
トランザクション：/IWFND/GW_CLIENTを起動します。

####①登録用のデータのコピー元として、ヘッダ+明細を取得する
Request URIに以下のように入力します。
`/sap/opu/odata/SAP/<サービス名>/SalesOrderSet('500000001')?$expand=ToSalesOrderItem&$format=json`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/902a877c-adf7-52dc-9082-c8d9899aa3c3.png)

####②登録用のデータを作成する
レスポンスデータをコピーして、以下の形に編集します。
2件の明細を持つデータとなっています。
値未設定の項目は、BAPIの仕様上登録時に設定されているとエラーになるためブランクにしています。

```json
{
  "SoId": "",
  "CreatedBy": "",
  "ChangedBy": "",
  "CreatedByBp": false,
  "ChangedByBp": false,
  "Note": "2019.3.17 MOV58",
  "BuyerId": "100000005",
  "BuyerName": "TECUM",
  "CurrencyCode": "EUR",
  "LifecycleStatus": "",
  "BillingStatus": "",
  "DeliveryStatus": "",
  "ToSalesOrderItem": [{
    "SoId": "",
    "SoItemPos": "",
    "ProductId": "HT-1040",
    "Note": "EPM DG: SO ID 0500000002 Item 0000000010",
    "CurrencyCode": "",
    "GrossAmount": "2963.10",
    "GrossAmountExt": "2963.1000",
    "NetAmount": "2490.00",
    "NetAmountExt": "2490.0000",
    "TaxAmount": "473.10",
    "TaxAmountExt": "473.1000",
    "Quantity": "3",
    "QuantityUnit": "EA"
  },
 {
    "SoId": "",
    "SoItemPos": "",
    "ProductId": "HT-1041",
    "Note": "EPM DG: SO ID 0500000002 Item 0000000010",
    "CurrencyCode": "",
    "GrossAmount" : "583.10",
    "GrossAmountExt" : "583.1000",
    "NetAmount" : "490.00",
    "NetAmountExt" : "490.0000",
    "TaxAmount" : "93.10",
    "TaxAmountExt" : "93.1000",
    "Quantity": "1",
    "QuantityUnit": "EA"
  } ]
}
```

####③POSTメソッドで登録する
②で編集したデータを左側のRequestの部分に貼り付けます。
Request URIに以下のように入力して、POSTメソッドを実行します。
`/sap/opu/odata/SAP/<サービス名>/SalesOrderSet`
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/edffbd72-1cc8-a0df-9234-e97accf12275.png)

登録された伝票番号が返ってくれば成功です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/2d17849e-f051-2da3-422b-15386d9ab509.png)

テーブルSNWD_SOを見てみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/276021/75bb51db-683e-e99a-4e41-41de73387973.png)

##トラブルシューティング
エラーになった場合はまずCREATE_DEEP_ENTITYのメソッドに入る前と入った後、どちらでエラーになっているかを確認します。メソッドに外部デバッガを置き、POSTを実行したときにメソッドが呼ばれれば後者、呼ばれなければ前者です。

- CREATE_DEEP_ENTITYに入る前のエラーの場合
 - Entity Setのプロパティで登録を許可しているか
 - URIを正しく指定しているか
 - 登録データの形式は正しいか（データ型、あるいはJSONフォーマットが正しいか）

- CREATE_DEEP_ENTITYに入った後のエラーの場合
 - 基本的にはBAPIのエラーなので、BAPIから返ってくるメッセージを確認する


##更新の場合はどうするか
データ登録の場合はCREATE_DEEP_ENTITYですが、親子関係のあるデータを更新したい場合はどうすればよいのでしょうか？
UPDATE_DEEP_ENTITYというメソッドはなく、以下のスレッドによれば2通りのやり方があるようです。
https://archive.sap.com/discussions/thread/3908823

①CREATE_DEEP_ENTITYを使う
メソッドの中で登録か更新かを判断して対応する
例：キーが渡されれば更新、渡されなければ登録

②$batch Processingを使う
複数のリクエストをまとめて処理する方法で、CHANGESET_BEGINというメソッドでまとめる単位を判断し、CHANGESET_PROCESSというメソッドで更新処理を行う
参考：https://archive.sap.com/documents/docs/DOC-57113

別の機会に、$batch Processingの実装もしてみたいと思います。