【ABAP】BAPI_PRICES_CONDITIONSでの条件マスタ登録は、初回投入時のみ！(BAPI_PRICES_CONDITIONS should be used only when the condition master is first input.)


こんにちは。

条件マスタの登録・更新用アドオンを担当することになり、バッチインプットでは困る仕様があったたためBAPIである、**BAPI_PRICES_CONDITIONS**の調査を行いました。
結果、このBAPIはマスタメンテナンスアドオンの要件を満たすのには厳しい条件があるため、このBAPIの利用は**条件マスタの初回投入のみにすべき**と判断しました。その理由および、一部あれ？という現象も確認できたので記載をしていきます。
また、条件マスタで使用する用語についても軽くまとめましたので、条件マスタってよく聞くけど何だろう、という方もご覧いただければ幸いです。


## 見出し

- 条件マスタ関連用語の基礎知識

- BAPI_PRICES_CONDITIONSの問題点

    - 同一キーデータの有効期間を調整しない

    - 変数キーの品目コードを18バイトで設定する必要がある

- まとめ

---

## 条件マスタ関連用語の基礎知識

条件マスタの用語はわかりづらいところがあるので、条件マスタ関連用語の基礎知識について簡単に記述します。細かく書くとそれだけで1本分の記事が書けるようなボリュームになりますので、省略して記載します。

<dl>
  <dt>条件マスタ(Condition Master)</dt>
  <dd> SAP独自の「条件テクニック」と呼ばれる方法で、品物やサービスに関する金額・率・送料・税などの情報を関連するデータを保持するマスタのこと。<br/>
※「条件マスタ」以外でも「条件テクニック」を使用しているマスタは存在する(出力、テキスト管理など）</dd><br />

  <dt>検索順序(Access Sequences)</dt>
  <dd>特定の条件タイプから有効な条件レコードを探すための検索のやり方を定義したもの。条件タイプに対して1つ検索条件を設定できる。検索順に条件テーブルを紐づけることで検索用項目パターンと検索順を定義する。<br />
  <br />
  検索パターン1で条件テーブルAxxxを見に行き対応するデータを探す<br />
  ↓<br/>
  対応するデータが見つかれば、設定されている価格や率を採用する<br />
  ↓<br/>
  見つからなければ次に設定されている検索パターン2で条件テーブルAyyyを見に行き対応するデータを探す<br /><br />

  上記を繰り返すことで、最終的に条件が合致した条件テーブルのエントリに紐づいている価格や率を採用する、といった対応が実現できる。<br/><br/>
  </dd>

  <dt>条件テーブル(Condition Table)</dt>
  <dd>「条件テクニック」の方法に従い、<strong>管理情報を保持するテーブル</strong>のこと。<strong>「A＋数字3桁」</strong>で命名される。パラメータ設定にて以下の設定を行うと自動的に条件テーブルが生成される。<br />
<dl>
  <dl>
    <dt>暗黙的に追加される項目</dt>
      <dd>
        <dl>
          <dt>アプリケーション</dt>
            <dd>「販売管理」「購買管理」「運賃」「税」など、用途を細分化するための項目。キー項目として設定される</dd>
          <dt>条件タイプ</dt>
            <dd>条件レコードがどの条件タイプから登録されたデータなのかを識別するための項目。キー項目として設定される</dd>  
      </dd>
  </dl>
  <dl>
    <dt>パラメータ設定画面から指定・選択することで追加される項目</dt>
      <dd>
        <dl>
          <dt>選択された項目（「変数キー」や「キー組合せ」に該当する項目）</dt>
            <dd>項目カタログから選択した項目がキー項目として設定される</dd>
          <dt>有効期間の有無</dt>
            <dd>有とすると、有効開始日と有効終了日が条件テーブルに追加される。<strong>有効終了日がキー項目として設定される</strong></dd>
          <dt>承認状況の有無</dt>
            <dd>有とすると、承認ステータス項目が条件テーブルに追加される</dd>
        </dl>
      </dd>
  </dl>
  <dt>条件レコード(Condition Record)</dt>
    <dd>条件テーブルに登録されているエントリのこと。ただし、<strong>条件テーブル自身には、価格や率およびスケールの実情報は保持していない。</strong>
        条件レコード番号で紐づけた価格情報・スケール情報のテーブル(<strong>下表1</strong>)にて、価格やスケール情報を保持している。</dd><br />

  <dt>スケール(Scale)</dt>
  <dd>受注や購入時の数量や金額を基準に基準となる金額や率を変更するための設定。大量注文時の単価値引や値引額・値引率アップなどといった用途に利用可能</dd><br />

  <dt>変数キー(Variable key)</dt>
  <dd>条件テーブルの個々のデータである、条件レコードにアクセスするためのキー項目。<strong>「キー組合せ」</strong>とも。標準トランザクションから詳細を押下すると確認可能</dd><br />

</dl>

**表1:価格情報・スケール情報の価格・率などの「実情報」を保持するテーブル**

|テーブルID |名称|
|:-:|:-:|
|KONH   |条件ヘッダ|
|KONP   |条件明細|
|KONM|数量スケール情報|
|KONW|価格スケール情報|

**表2:数量スケールのイメージ**

注文量に応じて単価を値下げする場合
※1000PC未満の場合はスケール上の単価ではなく、明細に設定されている通常単価が採用されます。

|スケール数量 |単価|
|:-:|:-:|
|1000(1000-4999PC)   |100JPY/1PC|
|5000(5000-9999PC)   |90JPY/1PC|
|10000(10000PC以上)|80JPY/1PC|

**表3:関連ワードの相関図**

上記に記載した各ワードを紐づけると以下のようになります。
* 条件タイプに対して検索順序が設定され、その検索順序内で条件テーブルが設定されています。
* その条件タイプの中には検索するキー項目を持った条件レコードが格納されています。
* 条件レコードで個々に採番された条件レコード番号で、実際の価格や率の情報を持ったKONH、KONPといったテーブルとつながっています。

※実は複数の条件タイプで1つの検索順序を使用できますし、そのため条件テーブルのキー項目には条件タイプが含まれているのですが、図では表現しきれないので省略しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/4a739f38-94c1-1294-fa8c-50a7c1ff07a7.png)
       
---

## BAPI_PRICES_CONDITIONSの問題点


### 同一キーデータの有効期間を調整しない

条件マスタは、有効開始日・終了日を持つデータで、ある一日で有効なデータは常に1つです。
また条件レコードは、有効開始日・終了日のうち、有効終了日のみがテーブルのキー項目に指定されています。

たとえば、すでに登録済みの条件マスタに対して以下のような対応を実施したとします。

####例1：既存条件マスタデータに、2020年10月からの価格改定を登録する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/a10651e2-e3bb-7b79-50a5-37f493569741.png)


- <dl><dt>実際のトランザクション画面上およびバッチインプットで登録した場合</dt>
<dd>既存データと有効開始日・有効終了日の期間が重複するデータが登録される場合、<br/>有効期間が重複しないよう、<strong>既存データの有効開始日・有効終了日を変更</strong>します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/b055dfdc-017d-56a1-5c2b-2e58effd5759.png)</dd>

- <dl><dt>BAPIで登録した場合</dt>
<dd><strong>有効終了日まで同一の既存データがあるとそこにデータを上書き</strong>します。
上書きされた2020年9月までのデータが消去される形になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/e80d6c96-af17-5265-09a2-2d8706782e39.png)</dd>


####例2：既存条件マスタデータにて有効な期間のうち、一定期間のみの価格改定内容を登録する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/35b9f075-1ae1-a1ce-d67d-c0e8d1fddcc6.png)


- <dl><dt>実際のトランザクション画面上およびバッチインプットで登録した場合</dt>
<dd>既存データの有効期間内に新しいデータが追加されたので、<strong>既存データを分割したうえで有効期間が重複しないよう、各データの有効開始日・有効終了日を変更</strong>します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/4c2cb43c-4e0b-1b4e-8131-93ada6c2ff8d.png)
</dd>

- <dl><dt>BAPIで登録した場合</dt>
<dd>既存データのことは考慮せず、追加されたデータをそのまま登録します。<strong>条件マスタの有効期間重複が発生</strong>します。
ちなみに、複数のデータで有効となっている日付を基準としてこの条件マスタを標準トランザクションで照会すると、内部エラーと表示され照会できなくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/a511b722-6fee-0d8a-88f3-a14e7c959e07.png)
</dd>

**「条件マスタのメンテナンスアドオンとして、VK11・VK12やMEK1・MEK2と同等機能をアドオン化する」**という要件に対して、**「条件マスタの登録・更新処理はBAPIで実装する」**というアドオン設計を行うとします。すると、

* 標準トランザクションが内部で処理している既存データの有効開始日・有効終了日の調整処理をアドオンで実装する必要がある
* 上記の実装に不具合があると、**既存データの上書き**や**期間重複のデータが作成**され、**業務影響の大きいインシデントに発展する可能性**がある

という、アドオン設計開発としてハイリスクな仕様となります。
こういった事象を避けるためには、同一キーのデータは常に上書きするところだけをうまく利用して、
**「BAPI_PRICES_CONDITIONSでの条件マスタ登録は初回投入のみにすべき」**としました。

バッチインプットであれば既存データの有効期間調整についてSAP標準にて対応するので、リスクの少ないアドオンを設計開発することができます。大量データ実行時の処理時間がかかるところを考慮しないといけませんが、業務上非常に重要である販売・仕入等の金額というマスタデータであれば、標準画面と同様のチェック処理を行ってくれる安心感には代えられないと思います。

---

### 変数キーの品目コードを18バイトで設定する必要がある

また、上記の調査中に困った事象が確認できましたので、こちらで記述します。
MMの場合、仕入品等の価格を決めるには仕入先や購買組織をキーにした購買情報から条件マスタを間接的に登録する場合が多いと思うのですが、今回は購買条件を使用せずにMMの条件マスタの登録が必要となったため、条件タイプPB00の登録をBAPIでやってみました。
以下がサンプルのコードとなります。

```abap:BAPI_PRICES_CONDITIONSサンプル
TYPES:
   tt_bapicondct  TYPE TABLE OF bapicondct,
   tt_bapicondhd  TYPE TABLE OF bapicondhd,
   tt_bapicondit  TYPE TABLE OF bapicondit,
   tt_bapicondqs  TYPE TABLE OF bapicondqs,
   tt_bapicondvs  TYPE TABLE OF bapicondvs,
   tt_bapiret2    TYPE TABLE OF bapiret2,
   tt_bapiknumhs  TYPE TABLE OF bapiknumhs,
   tt_mem_initial TYPE TABLE OF cnd_mem_initial.
DATA:
  it_bapicondct  TYPE tt_bapicondct,
  it_bapicondhd  TYPE tt_bapicondhd,
  it_bapicondit  TYPE tt_bapicondit,
  it_bapicondqs  TYPE tt_bapicondqs,
  it_bapicondvs  TYPE tt_bapicondvs,
  it_bapiret2    TYPE tt_bapiret2,
  it_bapiknumhs  TYPE tt_bapiknumhs,
  it_men_initial TYPE tt_mem_initial.

data(l_table_no) = '018'.
data(l_operation) = '009'.
data(l_cond_usage) ='A'.
data(l_applicatio) = 'M'.
data(l_cond_no) = '$000000001'. "新規登録時に使用するダミーの条件レコード番号
data(l_cond_type) = 'PB00'.
data(l_valid_from) = '20230104'.
data(l_valid_to) = '99991231'.

* 変数キー項目定義
data(varkey_lifnr) = |{ '100015'    alpha = in width = 10 }|.
data(varkey_matnr) = |{ 'T-M15A01'  alpha = in width = 40 }|.
data(varkey_EKORG) = |{ '1000'      width = 4 }|.
data(varkey_ESOKZ) = |{ '0'         width = 1 }|.
data(l_varkey) = |{ varkey_lifnr }{ varkey_matnr }{ varkey_ekorg }{ varkey_esokz }|.

it_bapicondct = VALUE #(
  ( operation = l_operation
    cond_usage = l_cond_usage
    table_no = l_table_no
    applicatio = l_applicatio
    cond_type = l_cond_type
    varkey = l_varkey
    valid_to = l_valid_to
    valid_from = l_valid_from
    cond_no = l_cond_no )
     ).
it_bapicondhd = VALUE #(
  ( operation = l_operation
    cond_no = l_cond_no
    created_by = sy-uname
    creat_date = sy-datum
    cond_usage = l_cond_usage
    table_no = l_table_no
    applicatio = l_applicatio
    cond_type = l_cond_type
    varkey = l_varkey
    valid_from = l_valid_from
    valid_to = l_valid_to )
    ).
it_bapicondit = VALUE #(
  ( operation = l_operation
    cond_no = l_cond_no
    cond_count = '01'
    applicatio = l_applicatio
    cond_type = l_cond_type
    scaletype = 'A'
*    scalebasin = ''
*    scale_qty = '0'
    cond_p_unt = '1'
    cond_unit = 'ST' "NOT PC
    calctypcon = 'C'
    cond_value = '1500'
    condcurr = 'JPY' )
    ).

CALL FUNCTION 'BAPI_PRICES_CONDITIONS'
*  EXPORTING
*    pi_initialmode = 'X'
*PI_BLOCKNUMBER =
*    PI_PHYSICAL_DELETION = abap_on "OFFにしても挙動関係なし
  TABLES
    ti_bapicondct  = it_bapicondct
    ti_bapicondhd  = it_bapicondhd
    ti_bapicondit  = it_bapicondit
    ti_bapicondqs  = it_bapicondqs
    ti_bapicondvs  = it_bapicondvs
    to_bapiret2    = it_bapiret2
    to_bapiknumhs  = it_bapiknumhs
    to_mem_initial = it_men_initial
  EXCEPTIONS
    update_error   = 1
    OTHERS         = 2.

cl_demo_output=>write(  sy-subrc ).
cl_demo_output=>write( it_bapiret2 ).

IF sy-subrc = 0.
  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
    EXPORTING
      wait = 'X'.
*   IMPORTING
*     RETURN        =    .
ELSE.
  MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.

cl_demo_output=>display( ).   
```
注目してほしいところは以下です。
変数キーという、条件テーブルのキー項目のうち、そのテーブル独自で設定されている項目を設定するところです。
` * 変数キー項目定義
data(varkey_lifnr) = |{ '100015'    alpha = in width = 10 }|.
data(varkey_matnr) = |{ 'T-M15A01'  alpha = in width = 40 }|.
data(varkey_EKORG) = |{ '1000'      width = 4 }|.
data(varkey_ESOKZ) = |{ '0'         width = 1 }|.
data(l_varkey) = |{ varkey_lifnr }{ varkey_matnr }{ varkey_ekorg }{ varkey_esokz }|.`

トランザクションMEK3（VK13などでも同様）では、対象の条件レコードを照会してから、詳細ボタンを押下することで下記のように確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/9b6d78bf-64d4-ad93-151b-8e3c836e0196.png)

デバッグモードで値が適切に設定されていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/807040ee-4b61-f4a5-1912-c3f239887f59.png)

IT_BAPIRET2の結果を確認すると、条件レコード番号（KNUMH）が0000008051で正常に登録されたようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/9119bd85-3133-6c96-3b97-b8cf8036e7b2.png)

しかし、今回の登録対象である条件テーブル：A018のデータを見てみると、品目マスタの後の項目、POrg（購買組織）と、Cat（購買情報レコードカテゴリ）に値が設定されていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/7bcf9485-2a4e-1a15-f5ad-c7575e77b594.png)

このデータを照会しようにも、MEK3では購買組織の入力が必須となっているため照会できません。つまり、標準トランザクションでは参照できない「ゴミデータ」を登録したことになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/43df0406-22f6-19ef-75ab-450ff37e1cd6.png)

少し考えてみてもしやと思ったので試してみましたが、このBAPIで品目コードを含む変数キーを設定する場合は、品目コードは18バイトで設定する必要があるようです。下記のように品目コードを18バイト長で設定してみたところ、条件レコード番号が0000008502にて、条件テーブルにて適切に値が設定されたことが確認できました。また、MEK3でも適切にデータの照会ができました。

`data(varkey_matnr) = |{ 'T-M15A01'  alpha = in width = 18 }|.`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/ad8912d5-8c46-20e3-4502-a526aef7fa92.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/8b2a7c81-4a47-984b-b9b5-2ae39584d2a3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/fd37352b-185f-966b-685d-93d956468df2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326392/74dcc376-1ae8-3c8b-c285-32ba1560785e.png)

SAP COMMUNITYやSAP BLOG等含めて、ウェブに上がっているこのBAPIのサンプルコードの多くはSD系条件タイプのもので、変数キーの品目コードの後続項目が承認ステータスのみとなっていました。この承認ステータスが使用されていない場合はブランク値が初期値となるため、品目コードの設定項目長が40バイトと設定しても挙動します。ただし、数字のみ品目コードで、前ゼロ埋めが発生すると挙動NGになるものと思いますが…。
とすると、品目コードを18バイトより多い文字数で定義したプロジェクトでは、今回のような変数キーを持った条件タイプの登録をBAPI_PRICES_CONDITIONSで行うことはできない、のでしょうか。

---

## まとめ
条件マスタ関連の用語と、BAPI_PRICES_CONDITIONSの挙動の問題点について記載しました。
すでにノートが出ている可能性もありますので、ノート検索が可能な方は探してみたり、OSS（今こう言うんでしたっけ…）に問い合わせしてみてください（認定は持っているのでSIDもあるのですが、ロックかかっており確認できませんでした。復活させてもらえないかなあ…）。
また、以下のサイトを参考にさせていただきました、ありがとうございました。

* この条件テーブルの変数キーの設定ならこのコードでも（運よく）動きます
[Sample code for BAPI_PRICES_CONDITIONS](https://answers.sap.com/questions/1307556/sample-code-for-bapipricesconditions.html)

* BAPI_PRICES_CONDITIONSの詳細がまとめられていてわかりやすいです。
[Document for bapi BAPI_PRICES_CONDITIONS](https://blogs.sap.com/2014/07/14/document-for-bapi-bapipricesconditions/)

今回の内容は以上です、ありがとうございました。