Oracle APEXのワークフローを使ってみる

# はじめに
この記事では、Oracle Live Labsのワークショップ[「Simplify Business Process Management Using APEX Workflows」](https://apexapps.oracle.com/pls/apex/r/dbpm/livelabs/run-workshop?p210_wid=3880)をもとに、Oracle APEXで
APEXアプリケーション「Doctor Appointments Made Easy！」を作成し、ワークフローの機能を使って医師の診察を予約・管理するシステムを実装してみます。

### ワークフローとは
ワークフローとは、一連の手続きルールに従って、ドキュメント、情報またはタスクが参加者間で移動するビジネス・プロセスの自動化のことです。([参考](https://docs.oracle.com/cd/F93580_01/htmdb/about-workflows.html))


### ワークフローの目的
ワークフローの主な目的は、複雑なビジネスロジックをシンプルで反復可能なステップに分解し、管理を簡素化・自動化することです。この記事では、APEXワークフローの有用性を、ヘルスケア業界で広く使われている実際のユースケースを通して紹介していこうと思います。

診断クリニックや病院の外来部門（OPD）において、診察や予約のスケジューリングと管理が困難なタスクとなります。ワークフローコンポーネントを使用して、ヘルスケアプロセスを効率的に処理する効率的なAPEXアプリケーションを構築していきます。

### このアプリケーションの目的
アプリケーションには以下の操作を行います。
* ビジネスプロセスの手動ステップと自動ステップの識別
* APEXアプリケーションを作成
* 医師を予約するためのワークフローを作成
* ワークフローにヒューマンタスクを追加する
* ワークフローにアクティビティとコネクションを追加
* APEXアプリケーションのページを作成

# アプリケーションを作成
APEXアプリケーション「Doctor Appointments Made Easy！」を設計することで、医師の診察を予約・管理する効率的なシステムを実装します。実現のために、以下のアプローチに従っていきます。
* ビジネスロジックを、管理可能で実行可能なステップに分解
* これらのステップの実行を制御するシーケンスと条件を特定
* このビジネスプロセスに関与する組織内の主要な人物を特定

まず、診療予約のビジネスプロセスをステップに分解し、各ステップに関連する登場人物や、ワークフローの概要を下記に表示しています。

|ステップ No. |ステップの説明 | ステップのタイプ|登場人物|
|---|---|---|---|
|1|医師の予約をスケジュール  |手動  |患者|
|2  |アプリケーションにログインし、患者の詳細を入力し、患者に代わって予約リクエストを送信  |手動  |病院管理担当者/スタッフ
|3  |希望の予約日時の医師の空き状況を確認してください。  |自動  |ワークフロー|
|4  |医師が忙しい場合は、患者に「予約なし」のメールを送信し、予約リクエストを終了します。  |自動  |ワークフロー|
|5  |医師が対応可能な場合は、医師が予約リクエストを確認するための「承認タスク」を発行します。  |自動  |ワークフロー|
|6  |アプリケーションにログインして、予約リクエストにアクセスします。  |手動  |医者|
|7  |予定申請を承認します。  |手動  |医者|
|8  |予定の申請を却下します。  |手動  |医者|
|9  |医師が拒否した場合は、患者に「予約なし」のメールを送信し、予約リクエストを終了します。    |自動|ワークフロー|
|10  |医師が承認した場合は、ステータスが「確認済み」の予約の詳細をシステムに入力します。  |自動|ワークフロー|
|11|患者が過去 7 日間に同じ医師の診察を受けていたかどうかを確認します。 |自動|ワークフロー|
|12  |5患者が以前に予約をしていた場合は、これをフォローアップ訪問としてマークし、料金を請求せず、患者が確認できるように支払い要求のない請求書を発行します。 |自動|ワークフロー|
|13  |新規受診の場合は、料金と支払い要求が記載された請求書を発行して、患者に確認してもらいます。  |自動|ワークフロー|
|14  |どちらの場合も、予約確認と請求書請求を患者に通知する電子メールを送信します。  |自動|ワークフロー|
|15  |アプリケーションにログインして、請求書を確認します。  |手動  |患者|
|16  |患者が特定の期間内に請求書を確認しない場合は、「予約なし」メールを送信し、予約リクエストをクローズします。  |自動|ワークフロー|
|17  |患者が請求書を確認したら、記録システムで予約ステータスを「PAID」に更新します  |自動|ワークフロー|
|18  |予約が完了するまで待ちます。 |自動|ワークフロー|
|19  |患者にフィードバック要求を提起します。  |自動|ワークフロー|
|20  |アプリケーションにログインし、フィードバックを提供します。  |手動  |患者|
|21  |患者が指定された期間内にフィードバック要求を完了しない場合は、予約要求を閉じます。  |自動|ワークフロー|
|22  |患者からフィードバックが寄せられた場合は、患者に「お礼」のメールを送信し、予約リクエストをクローズします。  |自動|ワークフロー|



### アプリケーションの設計
* ユーザーを作成します。
1. アプリケーションのホームページで、画面右上の「管理」アイコンをクリックし、ドロップダウン・リストから「ユーザーおよびグループの管理」を選択します。
1. 「複数のユーザーを作成」をクリックします。
1. アプリケーションには、次のユーザーを作成します。
    |ペルソナ |ユーザー名 | 
    |---|---|
    |医師|ロバート  |
    ||アナチャット |
    ||ジェーン  |
    ||ボー  |
    ||パット  |
    ||マルティヌス  |
    |患者|スージー  |
    ||ダリル  |
    ||ラジェッシュ  |
    |病院スタッフ|スティーブ  |
    
  
   * メールアドレスのリストの場合:以下の表のようにユーザーのメールアドレスを入力します。
   ```
    ROBERT@yourdomain.com    
      ANACHATT@yourdomain.com   
      JANE@yourdomain.com       
      BO@yourdomain.com         
      PAT@yourdomain.com       
      MARTIN@yourdomain.com     
      SUSIE@yourdomain.com     
      DARYL@yourdomain.com     
      RAJESH@yourdomain.com    
      STEVE@yourdomain.com
    ```
    ![APEXワークフロー_2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/58a50475-5fa9-270c-ed63-eb78cde02003.png)

    * ユーザー名: ユーザー名の一部として [@ドメインを除外する] を選択します。
   * 「パスワード」および「パスワードの確認」の場合: パスワードを入力します。
   * 「次へ」をクリックします。

* アプリケーションを作成
1. アプリケーションビルダーから、アプリケーションの作成ウィザードにて、[名前] に「Doctor Appointments Made Easy!」と入力します。
1. アプリケーションの作成をクリックします。
    ![APEXワークフロー_2-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/1308d18b-e50b-9571-6f8e-6bdc4196a730.png)


* サンプル データのインストール
1. SQLワークショップに移動し、「SQLスクリプト」をクリックします。
1. 「作成」をクリックします。
1. SQLコマンドをコピーしてエディタに貼りつけ、Doctor、Appointment、Patient Feedbackの表とシーケンスを作成します。
    ```
    begin
    execute immediate 'drop table appointment';
    execute immediate 'drop table doctor';
    execute immediate 'drop table patient_feedback';
    execute immediate 'drop sequence appoint_seq';
    execute immediate 'drop sequence patient_seq';
      exception
        when others then
            dbms_output.put_line('DB Objects not exist');
      end;
      /
    
      CREATE TABLE "APPOINTMENT"
       (    "BOOKING_ID" NUMBER(*,0),
        "PATIENT_USERNAME" VARCHAR2(40) NOT NULL ENABLE,
        "DOCTOR_NO" NUMBER(*,0) NOT NULL ENABLE,
        "SCHEDULE" TIMESTAMP (6) WITH TIME ZONE NOT NULL ENABLE,
        "PATIENT_EMAIL" VARCHAR2(40) NOT NULL ENABLE,
        "DOCTOR_EMAIL" VARCHAR2(40) NOT NULL ENABLE,
        "STATUS" VARCHAR2(10),
        "FEE" NUMBER,
        "WORKFLOW_ID" NUMBER,
         PRIMARY KEY ("BOOKING_ID")
      USING INDEX  ENABLE
       ) ;
    
      CREATE TABLE "DOCTOR"
       (    "DNO" NUMBER(*,0),
        "DNAME" VARCHAR2(20),
        "SPECIALIZATION" VARCHAR2(20),
        "QUALIFICATION" CHAR(15),
        "DOC_EMAIL" VARCHAR2(100) DEFAULT 'dname@abc.com',
         PRIMARY KEY ("DNO")
      USING INDEX  ENABLE
       ) ;
    
      CREATE TABLE "PATIENT_FEEDBACK"
       (    "ID" NUMBER(*,0),
        "PATIENT_NAME" VARCHAR2(40),
        "DOCTOR_NO" VARCHAR2(100),
        "APPOINTMENT" TIMESTAMP (6) WITH TIME ZONE,
        "FEEDBACK" VARCHAR2(4000),
        "RATING" NUMBER(*,0) NOT NULL ENABLE,
        "CREATED_AT" TIMESTAMP (6) WITH TIME ZONE,
        "UPDATED_AT" TIMESTAMP (6) WITH TIME ZONE
       ) ;
    
     CREATE SEQUENCE  "APPOINT_SEQ"  MINVALUE 1 MAXVALUE 9999999 INCREMENT BY 1 START WITH 901 CACHE 100 NOORDER  NOCYCLE  NOKEEP  NOSCALE  GLOBAL ;
    
     CREATE SEQUENCE  "PATIENT_SEQ"  MINVALUE 1 MAXVALUE 999999 INCREMENT BY 1 START WITH 201 CACHE 100 NOORDER  NOCYCLE  NOKEEP  NOSCALE  GLOBAL ;
    
      CREATE OR REPLACE EDITIONABLE TRIGGER "APPOINTMENT_T"
    before
    insert or update or delete on "APPOINTMENT"
    for each row
    begin
        if inserting then
            if :new.booking_id is null then
                :new.booking_id := appoint_seq.nextval;
            end if;
        end if;
    end;
    /
    ALTER TRIGGER "APPOINTMENT_T" ENABLE;
    
      CREATE OR REPLACE EDITIONABLE TRIGGER "PATIENT_FEEDBACK_T"
    before
    insert or update or delete on "PATIENT_FEEDBACK"
    for each row
    begin
        if inserting then
            if :new.id is null then
                :new.id := patient_seq.nextval;
            end if;
        end if;
    end;
    /
    ALTER TRIGGER "PATIENT_FEEDBACK_T" ENABLE;
    
    
    -- insert doc data
    insert into doctor values
    (10, 'ROBERT', 'ONCOLOGY','MS','robert@abc.com');
    
    insert into doctor values
    (20, 'BO', 'CARDIOLOGY', 'FRCS','bo@abc.com');
    
    insert into doctor values
    (30, 'JANE', 'ENT','MBBS','jane@abc.com');
    
    insert into doctor values
    (40, 'ANACHATT', 'PSYCHIATRY','MS','anne@abc.com');
    
    insert into doctor values
    (50, 'PAT', 'GENERAL','MBBS','pat@abc.com');
    
    insert into doctor values
    (60, 'MARTIN', 'RENOLOGY','MS','martin@abc.com');
    ```
    ![APEXワークフロー_2-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/4b2ad6cd-faff-a3f4-6dd6-60534bf3f096.png)


1. [Script Name (スクリプト名)] に「Create DB Tables」と入力し、[Run (実行)] をクリックします。

1. [即時実行] をクリックします。SQL文が処理され、サンプル表とデータが作成されます。
    ![APEXワークフロー_2-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/72c1b40b-5cdf-d719-d031-9c31db3f19f7.png)


* パッケージ仕様とパッケージ本文の作成

1. SQLワークショップに移動し、「SQLスクリプト」をクリックします。
1. 「作成」をクリックします。
1. 下記をコピー&ペーストして、eba_demo_wf_doc_apt.sql という名前のパッケージ仕様を作成します。
    ```
    set define '^' verify off
    prompt ...eba_demo_wf_doc_apt
    create or replace package eba_demo_wf_doc_apt authid current_user as
    
    function check_availability(
      p_doctor_id         in number,
      p_request_date      in timestamp with time zone)
    return varchar2;
    
    function confirm_appointment(
      p_doctor_id         in number,
      p_request_date      in timestamp with time zone,
      p_doctor_email      in varchar2,
      p_patient_name      in varchar2,
      p_patient_email     in varchar2,
      p_workflow_id       in number)
    return number;
    
    function update_fees(
      p_doctor_id         in number,
      p_request_date      in varchar2,
      p_booking_id        in number,
      p_patient_name      in varchar2)
    return number;
    
    procedure update_appointment(
      p_booking_id        in number,
      p_status            in varchar2 default null,
      p_fee               in number default null);
    
    end eba_demo_wf_doc_apt;
    /
    show err;
    ```
    ![APEXワークフロー_2-5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/b0aa488c-4db4-f691-be07-1e17233578dd.png)

1. [Script Name (スクリプト名)] に「Create Package Spec (パッケージ仕様の作成)」と入力し、[Run (実行)] をクリックします。
1. [即時実行] をクリックします。
1. 同様に、eba_demo_wf_doc_apt.plb という名前のパッケージ本文を作成します。
    ```
    set define '^' verify off
    prompt ...eba_demo_wf_doc_apt
    create or replace package body eba_demo_wf_doc_apt as
    
    function check_availability(
        p_doctor_id         in number,
        p_request_date      in timestamp with time zone)
    return varchar2
    is
        l_no      number := 0;
    begin
        select doctor_no
          into l_no
          from appointment
         where schedule  = p_request_date
           and doctor_no = p_doctor_id;
        return 'BUSY';
    exception
        when no_data_found then
            return 'AVAILABLE';
    end check_availability;
    
    function confirm_appointment(
        p_doctor_id         in number,
        p_request_date      in timestamp with time zone,
        p_doctor_email      in varchar2,
        p_patient_name      in varchar2,
        p_patient_email     in varchar2,
        p_workflow_id       in number)
    return number
    is
        l_booking_id   number;
    begin
       insert into appointment (
           patient_username,
           doctor_no,
           schedule,
           patient_email,
           doctor_email,
           status,
           workflow_id)   
       values (
           p_patient_name,
           p_doctor_id,
           p_request_date,
           p_patient_email,
           p_doctor_email,
           'CONFIRMED',
           p_workflow_id
           )
        returning booking_id into l_booking_id;
    
        return l_booking_id;
    end confirm_appointment;
    
    function update_fees(
        p_doctor_id         in number,
        p_request_date      in varchar2,
        p_booking_id        in number,
        p_patient_name      in varchar2)
    return number
    is
        l_fee          number := 500;
    begin
        -- check that this is not a case of followup
        select 0
          into l_fee from dual
         where exists (select patient_username
                         from appointment
                        where patient_username = p_patient_name
                          and doctor_no        = p_doctor_id
                          and schedule         >= to_timestamp_tz(p_request_date,'DD-MON-YYYY HH24:MI:SS') - 7
                          and status           = 'PAID');
        -- Update the appointment table to reflect the revised fee for the consultation
        update_appointment(
            p_booking_id    => p_booking_id,
            p_fee           => l_fee);
    
        return l_fee;
    exception
        when no_data_found then
            -- this is not a followup
            return l_fee;
    
    end update_fees;
    
    procedure update_appointment(
        p_booking_id        in number,
        p_status            in varchar2 default null,
        p_fee               in number default null)
    is
    begin
        update appointment
           set status     = coalesce(p_status, status),
               fee        = coalesce(p_fee,    fee)
         where booking_id = p_booking_id;
    end update_appointment;
    
    end eba_demo_wf_doc_apt;
    /
    show err;
    ```
1. 「スクリプト名」に「Create Package Body」と入力し、「実行」をクリックします。
    ![APEXワークフロー_2-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3058002/79a3083d-b2c3-54e6-d513-142789badb8d.png)

1. [即時実行] をクリックします。
# まとめ
これで、「Doctor Appointments Made Easy!」アプリケーションを設計するための最初のステップが完了しました。「Doctor Appointments Made Easy!」アプリケーションを作成し、アプリケーションユーザーを追加し、サンプルデータセットとパッケージを作成しました。次の記事では、Oracle APEXを使用して医師の予約ワークフローを作成するプロセスを作成します。
# 参考情報
 * [Oracle Live labs- Simplify Business Process Management Using APEX Workflows](https://apexapps.oracle.com/pls/apex/r/dbpm/livelabs/run-workshop?p210_wid=3880)