CDS Viewで相対的な日付の差を計算し、Criticalityを設定する方法


## シナリオ
RAPで簡単なTo-Doアプリを作成します。期限(Due Date)までの日数によって、期限にCiriticalityを設定し、色を変えて表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/7933f826-1db1-b357-a083-421b86e0fd22.png)

実装のポイントは以下の4点です。
1. 現在の日付を取得する
1. 期限日との差を計算する
1. 期限日までの日数によってCriticalityの値を設定する
1. 期限日の項目にCriticalityを設定する

1～3はCDS Viewで計算し、4.はUIアノテーションで設定します。

## 実装
### 1. テーブル定義
```
@EndUserText.label : 'To-do list'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztodo {

  key client            : abap.clnt not null;
  key id                : sysuuid_x16 not null;
  title                 : text100;
  detail                : text100;
  due_date              : abap.dats;
  created_by            : abp_creation_user;
  created_at            : abp_creation_tstmpl;
  last_changed_by       : abp_locinst_lastchange_user;
  local_last_changed_at : abp_locinst_lastchange_tstmpl;
  last_changed_at       : abp_lastchange_tstmpl;

}
```

### 2. RAP Generatorでオブジェクトを生成
テーブルを指定して[RAP Generator](https://qiita.com/tami/items/a9f3d2d4afb28d53919b)で各オブジェクトを生成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/20d98cc9-4a13-2210-daf9-55eba0391fde.png)

### 3. ZR_のビューに計算項目を追加
ZR_（ベース）のCDS Viewに計算項目を追加します。

:::note warn
計算項目はProjection Viewには追加できないという制約があるため、ベースのCDS Viewに追加します。
:::

```
@AccessControl.authorizationCheck: #CHECK
@Metadata.allowExtensions: true
@EndUserText.label: '###GENERATED Core Data Service Entity'
define root view entity ZR_TODO
  as select from ztodo as Todo
{
  key id as Id,
  title as Title,
  detail as Detail,
  due_date as DueDate,
  
  //計算項目
  tstmp_current_utctimestamp() as TimeStampToday,
  tstmp_to_dats($projection.TimeStampToday, 'JAPAN', $session.client, 'NULL') as DateToday,
  dats_days_between($projection.DateToday, $projection.DueDate) as DateDifference,
  
  case
    when $projection.DateDifference < 0 then 1 //negative: overdue
    when $projection.DateDifference < 3 then 2 //critical
    else 3 end as Criticality, 
  //
  
  @Semantics.user.createdBy: true
  created_by as CreatedBy,
  @Semantics.systemDateTime.createdAt: true
  created_at as CreatedAt,
  @Semantics.user.localInstanceLastChangedBy: true
  last_changed_by as LastChangedBy,
  @Semantics.systemDateTime.localInstanceLastChangedAt: true
  local_last_changed_at as LocalLastChangedAt,
  @Semantics.systemDateTime.lastChangedAt: true
  last_changed_at as LastChangedAt
}
```

#### 3.1. 現在の日付を取得する
システム日付を使用する場合は`$session.system_date`を使うことができます。タイムゾーンを考慮した日付を取得する場合は、以下のように現在のタイムスタンプを取得し、それを日付型に変換するという2ステップが必要です。

①現在のタイムスタンプを取得する
```
  tstmp_current_utctimestamp() as TimeStampToday,
```
[tstmp_current_utctimestamp()](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abencds_timestamp_functions_v2.htm)というファンクションで現在のタイムスタンプ(UTC)を15桁のDEC型で取得します。出力は`20241001205610`のような形式になります。

②タイムスタンプをdats型(yyyymmdd)に変換する
```
  tstmp_to_dats($projection.TimeStampToday, 'JAPAN', $session.client, 'NULL')
```
[tstmp_to_dats(tstmp,tzone,clnt,on_error)](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abencds_date_time_conversions_v2.htm)というファンクションでタイムスタンプをdats型に変換します。引数には以下を指定します。

- tstmp: タイムスタンプ（15桁のDEC型）
- tzone: タイムゾーン（※）
- clnt: 実行クライアント
- on_error: エラー時の動作。`FATAL`（例外を発生）、`NULL`（NULLを設定）、`INITIAL`（初期値を設定）のいずれか

※上の例ではタイムゾーンに固定値を設定していますが、abap_user_timezone, abap_system_timezoneなどの[ファンクション](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abencds_timezone_functions_v2.htm)を使用して取得することもできます。

#### 3.3. 期限日との差を計算する
```
  dats_days_between($projection.DateToday, $projection.DueDate) as DateDifference,
```
[dats_days_between(date1,date2)](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abencds_date_functions_v2.htm)というファンクションで、date2からdate1を引いた日数を計算します。date1, date2にはdats型を指定します。

#### 3.4. 期限日までの日数によってCriticalityの値を設定する
```
  case
    when $projection.DateDifference < 0 then 1 //negative: overdue
    when $projection.DateDifference < 3 then 2 //critical
    else 3 end as Criticality, 
```
[case文](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abencds_case_expression_v2.htm)を使って期限日までの日数に応じたCriticalityを設定しています。ここでは、期限が過ぎたら1(Negative), 期限日まで3日を切ったら2(Critical)、それ以外の場合は3(Positive)を設定します。

Criticalityに使用できる値は以下のリポジトリで確認できます。
https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/UI.md#CriticalityType
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/ee06e278-d5da-ae74-b627-810c077c20e2.png)

F8キーを押して出力を確認すると、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bfc2103a-9f9b-0993-f4af-e925726600ad.png)

### 4. ドラフトテーブルを再生成
ビューに計算項目を追加したあとは、ドラフトテーブルにも同じ項目を追加する必要があります。このためBehavior Definitionを開き、ドラフトテーブルにカーソルを合わせてQuick Fix(Ctrl + 1)を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/2988f779-3ab7-d5c0-a47c-2d9b827da67d.png)

以下は再生成されたドラフトテーブルです。
```
@EndUserText.label : 'Draft Database Table for ZTODO_D'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table ztodo_d {

  key mandt          : mandt not null;
  key id             : sysuuid_x16 not null;
  title              : text100;
  detail             : text100;
  duedate            : abap.dats;
  timestamptoday     : abap.dec(15,0);
  datetoday          : abap.dats;
  datedifference     : abap.int4;
  criticality        : abap.int1;
  createdby          : abp_creation_user;
  createdat          : abp_creation_tstmpl;
  lastchangedby      : abp_locinst_lastchange_user;
  locallastchangedat : abp_locinst_lastchange_tstmpl;
  lastchangedat      : abp_lastchange_tstmpl;
  "%admin"           : include sych_bdl_draft_admin_inc;

}
```
### 5. コンサンプションビュー
コンサンプションビューにCriticalityの項目を追加します。
```
@Metadata.allowExtensions: true
@EndUserText.label: '###GENERATED Core Data Service Entity'
@AccessControl.authorizationCheck: #CHECK
define root view entity ZC_TODO
  provider contract transactional_query
  as projection on ZR_TODO
{
  key Id,
  Title,
  Detail,
  DueDate,
  Criticality, //追加
  CreatedBy,
  CreatedAt,
  LastChangedBy,
  LocalLastChangedAt,
  LastChangedAt
  
}
```

### 6. UIアノテーション
期限日(DueDate)にCriticalityを設定するため、UIアノテーションを以下のように追加します。
```
  @UI.lineItem: [ {
    position: 40,
    criticality: 'Criticality',
    criticalityRepresentation: #WITH_ICON    
  } ]
  DueDate;
  ```

  以上で、冒頭のような見え方になります。

## 補足
計算した日数の差によってステータスに値を設定し、ステータスをList Reportでの検索の条件にしたいというケースがあるかもしれません。計算項目での検索はパフォーマンスが落ちるため、特にデータが大量の場合は注意が必要です。