101NEO ワークフロー・チュートリアル

株式会社ワン・オー・ワン
チュートリアル・ワークフロー
Version 1.5
Note:
コピー並びに配布厳禁
内容は予告なく変更される場合があります
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
目次
はじめに ......................................................................................................................................................... 1
1. 設計........................................................................................................................................................... 2
1-1.
1-2.
1-3.
1-4.
概要............................................................................................................................................. 2
画面設計................................................................................................................................... 2
入出力項目の抽出................................................................................................................ 5
データベース設計.................................................................................................................. 5
2. アプリケーションの作成..................................................................................................................... 8
2-1. プロジェクトの作成 ................................................................................................................ 8
2-2. ログイン画面............................................................................................................................ 9
2-3. メイン画面...............................................................................................................................11
2-4. メニュー画面 ..........................................................................................................................11
2-5. 申請リストの一覧画面 .......................................................................................................12
2-6. 承認/否認対象リストの一覧画面...............................................................................14
2-7. 新しい申請書の作成画面 ................................................................................................16
2-8. 申請データの詳細画面 .....................................................................................................23
2-9. 承認/否認履歴画面.........................................................................................................29
2-10. 完了画面..............................................................................................................................30
2-11. エラー画面 ..........................................................................................................................31
2-12. 共通プログラム..................................................................................................................31
3. 動作確認 ...............................................................................................................................................36
3-1.
3-2.
3-3.
3-4.
3-5.
3-6.
ログイン ...................................................................................................................................36
新しい申請書の作成 ..........................................................................................................36
承認処理.................................................................................................................................37
申請データの取消/再登録............................................................................................38
承認/否認履歴の確認....................................................................................................38
ログアウトの実行.................................................................................................................39
付録 A. SQL................................................................................................................................................40
ワークフロー関連の SQL 文 ........................................................................................................40
申請データ関連の SQL 文 ...........................................................................................................40
マスター関連の SQL 文.................................................................................................................41
付録 B. コマンドリスト.............................................................................................................................43
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
はじめに
このチュートリアルでは、101NEOの組み込み機能であるワークフローエンジンを使用した申
請業務アプリケーションを作成します。
前提条件として、チュートリアル第1部および第2部を実施して101NEO Studioの基本的な使
い方、機能等について理解された方を対象としております。
本チュートリアルを実施する前に、マニュアルの第8章「ワークフローエンジン」をお読みいた
だき、ワークフローエンジン機能の全体像を把握してください。また、巻末にコマンドリストを
つけましたので、そちらも参照してください。
システム要件
このチュートリアルをすべて確認するためには、次のシステムおよび設定が必要です。
•
JDBC対応データベース(説明ではOracleを使用)
•
使用するデータベースにあわせた101NEO Studioのクラスパス設定
•
メールサーバー
•
Java Mail(mail.jar)とJAF(activation.jar)およびクラスパス設定
•
WebブラウザでCookieをブロックしない設定(セッション管理で使用)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 1
1. 設計
1-1. 概要
このチュートリアルでは、簡単な申請業務アプリケーションとして経費精算システムを作成し
ます。ユーザーによって申請された経費データに対して、承認者(経理と社長)が承認(もし
くは否認)をします。
起票者
承認者
申請ボタンを押すと、
承認依頼が承認者
に送信される
承認者
承認ボタンを押すと、
承認依頼が次の承
認者に送信される
承
認
最後の承認者が
承認ボタンを押
せば、承認完了
承
認
データベース
また、ユーザー認証のためのログイン処理、申請ステータスの確認画面、申請者や承認者
へのメール通知機能なども実装します。
1-2. 画面設計
1-2-1. 画面の数
次のような画面(合計10画面)を作成します。
①
ログイン画面
②
メイン画面(フレーム分割)
③
メニュー画面
④
申請リストの一覧画面
⑤
承認/否認対象リストの一覧画面
⑥
新しい申請書の作成画面
⑦
申請データの詳細画面
⑧
承認/否認履歴画面
⑨
完了画面
⑩
エラー画面
※メイン画面(②)の左フレームにメニュー(③)、右フレームにコンテンツ(④∼⑩)を表示し
ます。
1-2-2. ラフな画面デザイン
※白抜き部分は入力フィールドです。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 2
① ログイン画面
ユーザーID
パスワード
ログイン
② メイン画面(フレーム分割)
メニュー(①)
コンテンツ(④∼⑩)
③ メニュー画面
○○さん、ようこそ。
■新しい申請書の作成
■申請リストの一覧
■承認/否認対象リストの一覧
ログアウト
④ 申請リストの一覧画面
タイトル
登録日
申請開始日
申請期限
ステータス
タイトル1
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
申請中
タイトル2
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
申請中
タイトル3
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
申請中
⑤ 承認/否認対象リストの一覧画面
タイトル
申請者
登録日
申請開始日
申請期限
タイトル1
申請者1
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
タイトル2
申請者2
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
タイトル3
申請者3
2004-01-01 12:00
2004-01-01 12:00
2004-01-01 12:00
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 3
⑥ 新しい申請書の作成画面
タイトル
タイトル1
起票日
記票者
社長
2004年01月01日
記票者1
社長
日付
明細
科目
経理
経理1
領収書No
金額
2004年01月01日
明細1
科目1
1
10,000
2004年01月01日
明細2
科目2
2
10,000
2004年01月01日
明細3
科目3
3
10,000
総合計
申請
30,000
仮払金控除
0
差引支払額
30,000
リセット
⑦ 申請データの詳細画面
タイトル
タイトル1
起票日
記票者
社長
経理
2004年01月01日
記票者1
社長
経理1
日付
明細
科目
領収書No
金額
2004年01月01日
明細1
科目1
1
10,000
2004年01月01日
明細2
科目2
2
10,000
2004年01月01日
明細3
科目3
3
10,000
総合計
承認
30,000
仮払金控除
0
差引支払額
30,000
否認
⑧ 承認/否認履歴画面
タイトル
タイトル1
日時
承認者
承認/否認区分
2004-01-01 12:00
承認者1
承認
2004-01-01 12:00
承認者2
承認
閉じる
⑨ 完了画面
経費を精算しました。
申請リストの一覧
⑩ エラー画面
エラーが発生しました!
XXXXXXXXXXXXXXXXX
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 4
1-3. 入出力項目の抽出
1-3-1. 画面デザインから入出力項目を抽出
① ログイン画面
ユーザーIDおよびパスワードを入力します。
② 申請リストの一覧画面
タイトル、登録日、申請開始日、申請期限、ステータスをデータベースから取得し、一覧
表示します。
③ 承認/否認リストの一覧画面
タイトル、申請者、登録日、申請開始日、申請期限をデータベースから取得し、一覧表
示します。
④ 新しい申請書の作成画面
タイトル、経理、経費明細(日付、明細、科目、領収書No、金額)、仮払金控除を入力し
ます。
起票日、記票者、社長は自動的に設定されるようにします。
総合計および差引支払額は、経費明細の金額および仮払金控除をもとに自動計算さ
れるようにします(JavaScriptを使用)。
⑤ 申請データの詳細画面
タイトル、起票日、記票者、社長、経理、経費明細(日付、明細、科目、領収書No、金
額)、総合計、仮払金控除、差引支払額をデータベースから取得し、表示します。
⑥ 承認/否認履歴画面
タイトル、日時、承認者、承認/否認区分をデータベースから取得し、表示します。
1-4. データベース設計
1-4-1. データベース設計
① ワークフローエンジンの実体である3つの表を作成します。
これらの表は101NEOのワークフロー機能を使用するために必須の表であり、表名や列
名を変更することはできません。また、これらの表に対するトランザクションは101NEO
のワークフロー・コマンドを介して行われるので、開発者が独自のSQLで直接トランザク
ションを発行する必要はありません。
WF_Worksheet(書類)
PK
sheetID(書類ID)
userID(申請者ID)
sheetTitle(タイトル)
sheetKind(種別)
timeout(申請期間[時間])
accepterNo(承認者順位)
regDate(登録日時)
startDate(申請開始日時)
finishedDate(完了日時)
statusCD(状態コード)
表名
WF_Worksheet
Copyright © 2004
WF_Accepter(承認者リスト)
FK
sheetID(書類ID)
accepterNo(承認者順位)
accepterID(承認者ID)
WF_AcceptHistory(承認履歴)
FK
sheetID(書類ID)
accepterID(承認者ID)
regDate(承認/否認日時)
acceptKB(承認/否認区分)
説明
ワークフロー・プロセスに関する情報を格納します。申請を
新規に登録した時点でこの表にレコードが追加され、ワーク
フロー・プロセスが開始されます。また、申請の承認や否
認、申請期間オーバーといった状態の変化に応じて、レコー
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 5
ドが更新されます。
WF_Accepter
ワークフロー・プロセスにおいて指定された承認者を格納し
ます。
WF_AcceptHistory
承認もしくは否認処理の履歴を格納します。
② 申請データを格納する2つの表を作成します。101NEOのワークフローエンジンは申請
対象となるデータ自体は管理しませんので、開発者がアプリケーションに応じて適切な
表を定義する必要があります。
ここでは、申請データを管理するために2つの表を用意します。
Aapplication(申請データ)
PK
sheetID(書類ID)
total(総合計)
temporary(仮払金控除)
payment(差引支払額)
Application_Detail(申請明細データ)
PK
PK
表名
sheetID(書類ID)
row_no(行番号)
row_date(日付)
detail(明細)
item(科目)
receipt(領収書No)
amount(金額)
説明
Application
申請データに関する情報を格納します。
Application_Detail
申請データ中の明細項目に関する情報を格納します。
③ 経費申請アプリケーションで必要となる2つのマスター表を作成します。
Item(科目)
PK
id(科目ID)
item_cd(科目コード)
item_name(科目名)
表名
User_M(ユーザー)
PK
userid(ユーザーID)
password(パスワード)
username(ユーザー名)
mail(メールアドレス)
smtp_userid(SMTPユーザーID)
smtp_password(SMTPパスワード)
position(役職)
説明
Item
申請データの入力時に参照する科目(交通費、接待費など)
を格納します。
User_M
申請や承認を行うユーザーの情報を格納します。
1-4-2. スキーマ作成
各表を作成するためのSQL文を用意します。マスター表には適当なデータを挿入します。
※Oracle用のSQL文を付録に記載しておりますので参照してください。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 6
1-4-3. JDBCツール
ツールバーの一番右にあるボタン
をクリックするか、メニューの「表示」→「JDBC」をク
リックすると、「JDBCツール」が起動します。
JDBCツールでは、JDBCドライバー経由でアプリケーション仕様定義書の[database]セクシ
ョンに定義されているデータベースにアクセスして、簡単にデータの確認、更新等を行うこと
ができます。
1-4-4. User_M表の更新
User_M表には申請や承認を行うユーザーの情報を格納します。ログイン時やメール送信時
に使用するデータなどが格納されています。
更新したいフィールドをJDBCツール上でダブルクリックしします。更新後、画面右上の「情報
更新」ボタンをクリックします。
ユーザー名やパスワードなどは好みによって変えても構いません。メールに関するデータは
実行環境に合わせて適切な値に更新する必要があります。※User_M表には、positionが
‘president’(社長)であるユーザーを1人だけ必ず含めてください。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 7
2. アプリケーションの作成
2-1. プロジェクトの作成
2-1-1. プロジェクトファイルの指定
「プロジェクト」メニュー下の「新規」をクリックすると、次のようなダイアログが表示されます。
プロジェクト情報を管理するプロジェクトファイルを指定し、「適用」ボタンをクリックします。
2-1-2. プロジェクト情報の入力
次のようなダイアログが表示されるので、必要に応じて各項目を修正/入力して「適用」ボ
タンをクリックします。
ここでは、「初期表示ページ」に「login.html」と指定します。
2-1-3. アプリケーション仕様定義書(app.spj)
[database]
# 接続するデータベース環境に応じて適宜修正してください。
db = oracle.jdbc.driver.OracleDriver,jdbc:oracle:thin:@localhost:1521:
ora920,wf,wf
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 8
[reference]
WEBAPP=/servlet/workflow
[init_app]
# トランザクションモードに2を指定してワークフローを開始します。
put(trans_mode, 2)
call wf.Init($trans_mode)
put(WEBAPP, "http://localhost:8888/servlet/workflow")
Note:
wf.Init はワークフローの使用を開始するためのコマンドです。このコマンドを
[init_app]から呼び出すことで、ワークフローを使用することができるようになり
ます。
引数のトランザクションモードに 1 を設定した場合、ワークフロー内部でトラン
ザクションを発行してくれるので、ユーザーがトランザクション管理をする必要
はありません。これに対してトランザクションモードを 2 に設定した場合は、ア
プリケーション側でトランザクションを発行する必要があります。ワークフロー
エンジン内部ではトランザクションは一切発行しません。
このチュートリアルでは、ワークフローで管理している表以外にも、申請データ
自体を格納している表が存在します。これら両方の表に対する更新を1つのト
ランザクションとして管理するために、トランザクションモードを 2 に設定してア
プリケーション側でトランザクションを発行します。
2-2. ログイン画面
2-2-1. HTML(login.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>login</title>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>経費精算システム</b></font>
<hr>
<br>
<form method="post" action="@WEBAPP" name="form">
<table border="0" cellspacing="0" cellpadding="1" align="center">
<tr>
<td>ユーザーID</td>
<td><input type="text" name="userid" value="@ユーザーID" size="15"></td>
</tr>
<tr>
<td>パスワード</td>
<td>
<input type="password" name="password" value="@パスワード" size="15">
</td>
</tr>
</table>
<br>
<input type="submit" name="login" value="ログイン">
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 9
</form>
<!-- エラーメッセージの表示/非表示を制御するために、erase命令を使用
します。 -->
<!-- %erase @消去フラグ -->
<br>
<font color="Red"><b>@エラーメッセージ</b></font>
<!-- %end-erase -->
</center>
</body>
</html>
2-2-2. 仕様定義書(login.spj)
[reference]
ユーザーID = ?userid
パスワード = ?password
# エラーメッセージの有無により、erase命令に指定するフラグをセットします。
消去フラグ = ($err_msg = "")? true : false
エラーメッセージ = $err_msg
[init_page]
perform セッション変数チェック()
[セッション変数チェック]
call ExistSessionVariable(err_msg, err_msg, $err_msg, "")
[action]
login = ログイン
[ログイン]
# User_M表にアクセスして、入力されたユーザーIDとパスワードを持つユーザーが
# 存在するか調べます。
local(sql, rs)
call ReplaceMarker(sql, "
select
username
from
user_m
where
userid = '?userid'
and
password = '?password'
")
call QuerySQL(rs, db, $sql)
local(count)
call SQLResultCount(count, rs)
if ($count < 1)
# ユーザーが存在しない場合、エラーメッセージを指定して再度ログイン画面を
# 表示します。
put(err_msg, "ユーザーIDまたはパスワードが違います。")
put(nextpage, login.html)
else
# ユーザーが存在する場合、メイン画面に遷移します。
put(err_msg, "")
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 10
put(s_userid, ?userid)
call SQLResultElement(s_username, rs, 1, 1)
put(nextpage, main.html)
endif
2-3. メイン画面
2-3-1. HTML(main.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>main</title>
<script language="javascript" src="/common.js"></script>
</head>
<!-- フレームの左にメニュー画面を、右にコンテンツ画面を指定します。
コンテンツの初期画面は申請リストの一覧とします。 -->
<frameset cols="25%,75%" frameborder="1"
<!-- ページが移動するかウィンドウが閉じられたタイミング(onUnload)で、
JavaScriptのquit関数を実行します。 -->
onUnload='quit("@WEBAPP?cur_page=main.html&logout=")'>
<frame src="@WEBAPP?nextpage=menu.html" name="menu" scrolling="yes">
<frame src="@WEBAPP?nextpage=list1.html" name="content" scrolling="yes">
</frameset>
</html>
2-3-2. 仕様定義書(main.spj)
[action]
logout = ログアウト
[ログアウト]
# endコマンドを実行してセッションを終了します。これにより、すべての
# セッション変数が消滅します。
end()
# セッション終了後にログイン画面に戻る際に、エラーメッセージのための変数
# (err_msg)が必要になるので、ローカル変数として宣言します。
local(err_msg)
2-4. メニュー画面
2-4-1. HTML(menu.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>menu</title>
</head>
<body>
<font size="2">
@ユーザー名さん、ようこそ。
<br><br>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 11
<hr align="left">
<ul>
<li type="square"><a href="@WEBAPP?nextpage=new.html" target="content">
新しい申請書の作成</a>
<li type="square"><a href="@WEBAPP?nextpage=list1.html" target="content">
申請リストの一覧</a>
<li type="square"><a href="@WEBAPP?nextpage=list2.html" target="content">
承認/否認対象リストの一覧</a>
</ul>
<hr align="left">
<p align="right">
<!-- ログアウトのリンクをクリックすると、ログイン画面に遷移します。 -->
<a href="@WEBAPP?nextpage=login.html" target="_parent">ログアウト</a>
</p>
</font>
</body>
</html>
2-4-2. 仕様定義書(menu.spj)
[reference]
ユーザー名 = $s_username
2-5. 申請リストの一覧画面
2-5-1. HTML(list1.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>list1</title>
<style type="text/css">th,td{font-size:10pt}</style>
<script language="javascript" src="/common.js"></script>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>申請リストの一覧</b></font>
<hr>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th>タイトル</th>
<th width="110">登録日</th>
<th width="110">申請開始日</th>
<th width="110">申請期限</th>
<th width="110">ステータス</th>
</tr>
<!-- %repeat -->
<tr>
<td>
<!-- シートタイトルをクリックすると、詳細画面(detail.html)に遷移
します。 -->
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 12
<a href="@WEBAPP?nextpage=detail.html&sheetid=@シートID
&class=application">@シートタイトル</a>
</td>
<td>@登録日</td>
<td>@申請開始日</td>
<td>@申請期限</td>
<td>
<!-- ステータスをクリックすると、JavaScriptのopenWindow関数が実行
され、承認履歴が表示されます。 -->
<a href='javascript:openWindow("@WEBAPP?nextpage=history.html
&sheetid=@シートID")'>@ステータス</a>
</td>
</tr>
<!-- %end -->
</table>
</center>
</body>
</html>
2-5-2. 仕様定義書(list1.spj)
[reference]
シートID = $sheeted
シートタイトル = $sheettitle
登録日 = $regdate
申請開始日 = $startdate
申請期限 = $expiredate
ステータス = $status
%include check.spj
[init_page]
perform 申請期間チェック()
perform 申請リスト取得()
[申請リスト取得]
# データベースにアクセスして、ログインユーザーの申請書をすべて取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
sheetid,
sheettitle,
to_char(regdate, 'YYYY-MM-DD HH24:MI'),
to_char(startdate, 'YYYY-MM-DD HH24:MI'),
to_char(startdate + timeout / 24, 'YYYY-MM-DD HH24:MI'),
case statuscd
when 1 then '申請中'
when 2 then '承認済'
when 3 then '否認済'
when 4 then '取消済'
when 5 then '申請期間オーバー'
else '?'
end
from
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 13
wf_worksheet
where
userid = '$s_userid'
order by
regdate
")
call QuerySQL(rs, db, $sql)
local(sheetid, sheettitle, regdate, startdate, expiredate, status)
call SQLResultRowArray(rs, 1, 0, sheetid, sheettitle, regdate, startdate,
expiredate, status)
2-6. 承認/否認対象リストの一覧画面
2-6-1. HTML(list2.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>list2</title>
<style type="text/css">th,td{font-size:10pt}</style>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>承認/否認対象リストの一覧</b></font>
<hr>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th>タイトル</th>
<th width="110">申請者</th>
<th width="110">登録日</th>
<th width="110">申請開始日</th>
<th width="110">申請期限</th>
</tr>
<!-- %repeat -->
<tr>
<td>
<!-- シートタイトルをクリックすると、詳細画面(detail.html)に遷移
します。 -->
<a href="@WEBAPP?nextpage=detail.html&sheetid=@シートID
&class=approval">@シートタイトル</a>
</td>
<td>@申請者</td>
<td>@登録日</td>
<td>@申請開始日</td>
<td>@申請期限</td>
</tr>
<!-- %end -->
</table>
</center>
</body>
</html>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 14
2-6-2. 仕様定義書(list2.spj)
[reference]
シートID = $sheetid
シートタイトル = $sheettitle
申請者 = $username
登録日 = $regdate
申請開始日 = $startdate
申請期限 = $expiredate
%include check.spj
[init_page]
perform 申請期間チェック()
perform 承認否認対象リスト取得()
[承認否認対象リスト取得]
# データベースにアクセスして、ログインユーザーが承認処理を行うべき申請書
# をすべて取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
w.sheetid,
w.sheettitle,
u.username,
to_char(w.regdate, 'YYYY-MM-DD HH24:MI'),
to_char(w.startdate, 'YYYY-MM-DD HH24:MI'),
to_char(w.startdate + w.timeout / 24, 'YYYY-MM-DD HH24:MI')
from
wf_worksheet w,
wf_accepter a,
user_m u
where
w.sheetid = a.sheeted
and
w.accepterno = a.accepterno
and
w.userid = u.userid
and
a.accepterid = '$s_userid'
and
w.statuscd = 1
order by
w.regdate
")
call QuerySQL(rs, db, $sql)
local(sheetid, sheettitle, username, regdate, startdate, expiredate)
call SQLResultRowArray(rs, 1, 0, sheetid, sheettitle, username, regdate,
startdate, expiredate)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 15
2-7. 新しい申請書の作成画面
2-7-1. HTML(new.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>new</title>
<style type="text/css">th,td{font-size:10pt}</style>
<script language="javascript" src="/common.js"></script>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>新しい申請書の作成</b></font>
<hr>
<br>
<form method="post" action="@WEBAPP" name="form">
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr>
<th width="60" bgcolor="Orange">タイトル</th>
<td><input type="text" name="title" size="125" maxlength="50"></td>
</tr>
</table>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th>起票日</th>
<th width="150">記票者</th>
<th width="150">社長</th>
<th width="150">経理</th>
</tr>
<tr align="center">
<td>@起票年年@起票月月@起票日日</td>
<td>@記票者</td>
<td>@社長</td>
<td>
<select name="accepter">
<!-- 承認者のリストをrepeat命令で作成します。 -->
<!-- %repeat -->
<option value="@承認者ID">@承認者名
<!-- %end -->
</select>
</td>
</tr>
</table>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th width="185">日付</th>
<th>明細</th>
<th width="95">科目</th>
<th width="60">領収書No</th>
<th width="75">金額</th>
</tr>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 16
<!-- groupオプションで指定した回数分繰り返します。 -->
<!-- %repeat group=@グループ配列 -->
<tr align="center">
<td>
<select name="year">@年オプションリスト</select>年
<select name="month">@月オプションリスト</select>月
<select name="day">@日オプションリスト</select>日
</td>
<td><input type="text" name="detail" size="50" maxlength="30"></td>
<td><select name="item">@科目オプションリスト</select></td>
<td>
<input type="text" name="receipt" size="2" style="text-align:right">
</td>
<td>
<!-- 金額が変更されたタイミング(onChange)で、JavaScriptのrecast関数
を実行します。 -->
<input type="text" name="amount" size="10" style="text-align:right"
onChange="recast()">
</td>
</tr>
<!-- %end -->
</table>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr>
<td width="480"></td>
<td width="120" bgcolor="Orange"><b>総合計</b></td>
<td align="right">
<input type="text" name="total" size="15" readonly
style="text-align:right">
</td>
</tr>
<tr>
<td></td>
<td bgcolor="Orange"><b>仮払金控除</b></td>
<td align="right">
<!-- 仮払金控除が変更されたタイミング(onChange)で、JavaScriptの
recast関数を実行します。 -->
<input type="text" name="temporary" value="0" size="15"
style="text-align:right" onChange="recast()">
</td>
</tr>
<tr>
<td></td>
<td bgcolor="Orange"><b>差引支払額</b></td>
<td align="right">
<input type="text" name="payment" size="15" readonly
style="text-align:right">
</td>
</tr>
</table>
<br>
<!-- 「申請」と「リセット」の2つのボタンを用意します。 -->
<input type="submit" name="apply" value="申請">
<input type="reset" name="reset" value="リセット">
</form>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 17
</center>
</body>
</html>
2-7-2. 仕様定義書(new.spj)
[reference]
起票年 = $system_year
起票月 = $system_month
起票日 = $system_day
記票者 = $s_username
社長 = $president_name
承認者ID = $accepter_id
承認者名 = $accepter_name
グループ配列 = $group_array
年オプションリスト = $year_optionlist
月オプションリスト = $month_optionlist
日オプションリスト = $day_optionlist
科目オプションリスト = $item_optionlist
%include check.spj
%include mail.spj
[init_page]
perform システム時間取得()
perform 社長名取得()
perform 承認者候補リスト作成()
perform グループ配列作成()
perform 年一覧オプションタグ作成()
perform 月一覧オプションタグ作成()
perform 日一覧オプションタグ作成()
perform 科目一覧オプションタグ作成()
[システム時間取得]
# 現在時間を取得します。
local(date, ymd, system_year, system_month, system_day)
date = date()
call SeparateString(ymd, $date, ".")
system_year = elementat($ymd, 0)
system_month = elementat($ymd, 1)
system_day = elementat($ymd, 2)
[社長名取得]
# データベースにアクセスして、社長のユーザーIDとパスワードを取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
userid,
username
from
user_m
where
position = 'president'
")
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 18
call QuerySQL(rs, db, $sql)
call SQLResultRow(rs, 1, president_id, president_name)
[承認者候補リスト作成]
# データベースにアクセスして、承認者候補のリスト(ログインユーザーと社長以外)
# を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
userid,
username
from
user_m
where
userid != '$s_userid'
and
position != 'president'
order by
userid
")
call QuerySQL(rs, db, $sql)
local(accepter_id, accepter_name)
call SQLResultRowArray(rs, 1, 0, accepter_id, accepter_name)
[グループ配列作成]
# repeat命令のgroupオプションに指定する配列を作成します。
local(group_array)
group_array = mkseq(10, 0, 1)
[年一覧オプションタグ作成]
# 年一覧用のオプションタグを作成します。
# 去年/今年/来年の3年が対象になります。
local(brank, last_year, value, year_optionlist)
call MakeArray(brank, "")
compute(last_year, $system_year - 1)
value = mkseq(3, $last_year, 1)
call UnionArray(value, $brank, $value)
call TagOption(year_optionlist, $value, $value, $brank)
[月一覧オプションタグ作成]
# 月一覧用のオプションタグを作成します。
# 01月∼12月が対象になります。
local(brank, value, month_optionlist)
call MakeArray(brank, "")
call MakeArray(value, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12)
call UnionArray(value, $brank, $value)
call TagOption(month_optionlist, $value, $value, $brank)
[日一覧オプションタグ作成]
# 日一覧のオプションタグを作成します。
# 01日∼31日が対象になります。
local(brank, value, day_optionlist)
call MakeArray(brank, "")
call MakeArray(value, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 19
call UnionArray(value, $brank, $value)
call TagOption(day_optionlist, $value, $value, $brank)
[科目一覧オプションタグ作成]
# 科目一覧用のオプションタグを作成します。
# データベースにアクセスして、科目コードと科目名を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
item_cd,
item_name
from
item
order by
id
")
call QuerySQL(rs, db, $sql)
local(value, label, data, item_optionlist)
call SQLResultRowArray(rs, 1, 0, value, label)
call MakeArray(data, "brank")
call TagOption(item_optionlist, $value, $label, $data)
[action]
apply = 申請
[申請]
catch(エラー処理)
transaction(db)
perform シートID取得()
perform 申請データ追加()
perform 申請明細データ追加()
perform 申請書類登録()
commit(db)
catch()
perform メール送信()
put(nextpage, complete.html)
[シートID取得]
# データベースにアクセスして、シーケンスから新しい申請書のシートIDを取得
# します。
local(sql, rs)
call ReplaceMarker(sql, "
select
sheetid.nextval
from
dual
")
call QuerySQL(rs, db, $sql)
local(sheetid)
call SQLResultRow(rs, 1, sheetid)
[申請データ追加]
local(sql, rs, cnt)
# タイトルの入力欄が空白だったら、エラーページを表示します。
if (?title = "")
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 20
rollback(db)
put(err_msg, "タイトルを指定してください。")
put(nextpage, error.html)
break()
endif
# 仮払金控除に入力した値が数値でなかったら、エラーページを表示します。
if (isdecimal(?temporary) = false)
rollback(db)
put(err_msg, "仮払金控除に正しい数値を指定してください。")
put(nextpage, error.html)
break()
endif
# 総合計の入力欄が空白だったら(すなわち明細データが1つもなかったら)、
# エラーページを表示します。
if (?total = "")
rollback(db)
put(err_msg, "明細データがありません。")
put(nextpage, error.html)
break()
endif
# 上記エラーに該当しなければ、データベースにアクセスして申請データを挿入
# します。
call ReplaceMarker(sql, "
insert into application values (
'$sheetid',
?total,
?temporary,
?payment
)
")
call ExecuteSQL(cnt, db, $sql)
[申請明細データ追加]
# 入力された申請明細データを配列として格納し、その配列数分(すなわち明細
# データの行数分)繰り返し処理を行います。
local(year, month, day, detail, item, receipt, amount)
call SelectMultipleList(year, year)
call SelectMultipleList(month, month)
call SelectMultipleList(day, day)
call SelectMultipleList(detail, detail)
call SelectMultipleList(item, item)
call SelectMultipleList(receipt, receipt)
call SelectMultipleList(amount, amount)
local(len, i)
len = count_array($year)
put(i, 0)
perform 申請明細行挿入($i < $len)
[申請明細行挿入]
local(my_year, my_month, my_day, my_detail, my_item, my_receipt, my_amount)
my_year = elementat($year, $i)
my_month = elementat($month, $i)
my_day = elementat($day, $i)
my_detail = elementat($detail, $i)
my_item = elementat($item, $i)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 21
my_receipt = elementat($receipt, $i)
my_amount = elementat($amount, $i)
# 明細データ行の入力欄がすべて空白の場合、以下の処理を行います。
if ($my_year = "" && $my_month = "" && $my_day = "" && $my_detail = ""
&& $my_item = "" && $my_receipt = "" && $my_amount = "")
# 対象行が先頭行だった場合(すなわち明細データが1行もない場合)、エラー
# ページを表示します。
if ($i = 0)
rollback(db)
put(err_msg, "明細データがありません。")
put(nextpage, error.html)
break()
# それ以外の場合、ループを終了します。
else
call ReplaceMarker(i, $len)
return()
endif
endif
perform 入力データチェック()
local(my_date)
call Append(my_date, $my_year, "-", $my_month, "-", $my_day)
local(sql, rs, cnt)
# データベースにアクセスして、申請明細データを挿入します。
call ReplaceMarker(sql, "
insert into application_detail values (
'$sheetid',
$i,
to_date('$my_date', 'YYYY-MM-DD'),
'$my_detail',
'$my_item',
nullif('$my_receipt', ''),
$my_amount
)
")
call ExecuteSQL(cnt, db, $sql)
call Add(i, 1)
[入力データチェック]
# 日付の入力データをチェックします。
if ($my_year = "" ││ $my_month = "" ││ $my_day = "")
rollback(db)
put(err_msg, "日付を指定してください。")
put(nextpage, error.html)
break()
endif
# 詳細の入力データをチェックします。
if ($my_detail = "")
rollback(db)
put(err_msg, "明細を指定してください。")
put(nextpage, error.html)
break()
endif
# 科目の入力データをチェックします。
if ($my_item = "")
rollback(db)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 22
put(err_msg, "科目を指定してください。")
put(nextpage, error.html)
break()
endif
# 金額の入力データをチェックします。
if (isnumeric($my_amount) = false)
rollback(db)
put(err_msg, "金額に正しい数値を指定してください。")
put(nextpage, error.html)
break()
endif
[申請書類登録]
# 申請書類の登録にはwf.CreateProcessコマンドを使用します。このコマンドが実行
# された時点でWF_Worksheet表およびWF_Accepter表にレコードが登録され、申請が
開始されます。
call ReplaceMarker(requester, $s_userid)
call MakeArray(accepters, ?accepter, $president_id)
put(kind, "経費")
put(timeout, 24)
call wf.CreateProcess(db, $sheetid, $requester, $accepters, ?title, $kind,
$timeout)
[エラー処理]
# データベースエラーが発生した場合、そのエラーメッセージをエラーページに
# 表示します。
catch()
rollback(db)
err_msg = sql.get_message()
put(nextpage, error.html)
Note:
ここでは HTML 中の各<select>タグに指定するオプションリストを、TagOption
コマンドを使用して作成しています。通常、オプションリストは HTML の repeat
命令を用いて作成しますが、このページのように repeat 命令で作成された各
行に対してオプションリストを作成したい場合は、オプションリスト全体を
TagOption コマンドで作成します。
2-8. 申請データの詳細画面
2-8-1. HTML(detail.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>detail</title>
<style type="text/css">th,td{font-size:10pt}</style>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>申請データの詳細</b></font>
<hr>
<br>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 23
<form method="post" action="@WEBAPP" name="form">
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr>
<th width="60" bgcolor="Orange">タイトル</th>
<td>@タイトル</td>
</tr>
</table>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th>起票日</th>
<th width="150">記票者</th>
<th width="150">社長</th>
<th width="150">経理</th>
</tr>
<tr align="center">
<td>@起票年年@起票月月@起票日日</td>
<td>@記票者</td>
<td>@承認者2</td>
<td>@承認者1</td>
</tr>
</table>
<br>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th width="100">日付</th>
<th>明細</th>
<th width="95">科目</th>
<th width="60">領収書No</th>
<th width="75">金額</th>
</tr>
<!-- %repeat -->
<tr>
<td>@年年@月月@日日</td>
<td>@明細</td>
<td>@科目</td>
<td align="right">@領収書No</td>
<td align="right">@金額</td>
</tr>
<!-- %end -->
</table>
<table width="700" border="1" cellspacing="0" cellpadding="1">
<tr>
<td width="480"></td>
<td width="120" bgcolor="Orange"><b>総合計</b></td>
<td align="right">@総合計</td>
</tr>
<tr>
<td></td>
<td bgcolor="Orange"><b>仮払金控除</b></td>
<td align="right">@仮払金控除</td>
</tr>
<tr>
<td></td>
<td bgcolor="Orange"><b>差引支払額</b></td>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 24
<td align="right">@差引支払額</td>
</tr>
</table>
<br>
<!-- 申請者用のボタン(取消、再登録)と承認者用のボタン(承認、否認)の
表示は、erase命令を使用して制御します。 -->
<!-- %erase @消去フラグ1 -->
<input type="submit" name="cancel" value="取消">
<!-- %end-erase -->
<!-- %erase @消去フラグ2 -->
<input type="submit" name="re-register" value="再登録">
<!-- %end-erase -->
<!-- %erase @消去フラグ3 -->
<input type="submit" name="accept" value="承認">
<input type="submit" name="reject" value="否認">
<!-- %end-erase -->
</form>
</center>
</body>
</html>
2-8-2. 仕様定義書(detail.spj)
[reference]
タイトル = $sheettitle
記票者 = $username
承認者1 = $accepter1
承認者2 = $accepter2
起票年 = $regdate_year
起票月 = $regdate_month
起票日 = $regdate_day
総合計 = $total
仮払金控除 = $temporary
差引支払額 = $payment
消去フラグ1 = $erase_flag1
消去フラグ2 = $erase_flag2
消去フラグ3 = $erase_flag3
年 = $year
月 = $month
日 = $day
明細 = $detail
科目 = $item
領収書No = ($receipt = "")? "&nbsp;" : $receipt
金額 = $amount
%include check.spj
%include mail.spj
[init_page]
perform リクエストデータ保存()
perform 申請期間チェック()
perform 承認者リスト取得()
perform 申請データ取得()
perform 申請詳細データ取得()
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 25
[リクエストデータ保存]
# フォームのリクエストデータとして受け取ったシートIDを、セッション変数に
# 保存します。
put(sheetid, ?sheetid)
[承認者リスト取得]
# データベースにアクセスして、承認者名を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
username
from
user_m,
wf_accepter
where
userid = accepterid
and
sheetid = '?sheetid'
order by
accepterno
")
call QuerySQL(rs, db, $sql)
local(accepter, accepter1, accepter2)
call SQLResultRowArray(rs, 1, 0, accepter)
accepter1 = elementat($accepter, 0)
accepter2 = elementat($accepter, 1)
[申請データ取得]
# データベースにアクセスして、申請データを取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
sheettitle,
to_char(regdate, 'YYYY'),
to_char(regdate, 'MM'),
to_char(regdate, 'DD'),
username,
total,
temporary,
payment,
# 「取消」ボタン消去フラグを設定します。
case
when '?class' = 'approval' then 'true'
when statuscd = 1 then 'false'
when statuscd = 2 then 'true'
when statuscd = 3 then 'false'
when statuscd = 4 then 'true'
when statuscd = 5 then 'false'
end,
# 「再登録」ボタン消去フラグを設定します。
case
when '?class' = 'approval' then 'true'
when statuscd = 1 then 'true'
when statuscd = 2 then 'true'
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 26
when statuscd = 3 then 'false'
when statuscd = 4 then 'false'
when statuscd = 5 then 'false'
end,
# 「承認、否認」ボタン消去フラグを設定します。
case '?class'
when 'application' then 'true'
when 'approval' then 'false'
end
from
wf_worksheet w,
application a,
user_m u
where
w.sheetid = a.sheeted
and
w.userid = u.userid
and
w.sheetid = '?sheetid'
")
call QuerySQL(rs, db, $sql)
local(sheettitle, regdate_year, regdate_month, regdate_day, username,
total, temporary, payment, erase_flag1, erase_flag2, erase_flag3)
call SQLResultRow(rs, 1, sheettitle, regdate_year, regdate_month, regdate_day,
username, total,temporary, payment,erase_flag1, erase_flag2, erase_flag3)
[申請詳細データ取得]
# データベースにアクセスして、申請詳細データを取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
to_char(row_date, 'YYYY'),
to_char(row_date, 'MM'),
to_char(row_date, 'DD'),
detail,
item_name,
receipt,
amount
from
application_detail,
item
where
item = item_cd
and
sheetid = '?sheetid'
order by
row_no
")
call QuerySQL(rs, db, $sql)
local(year, month, day, detail, item, receipt, amount)
call SQLResultRowArray(rs, 1, 0, year, month, day, detail, item, receipt,
amount)
[action]
cancel = 取消, list1.html
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 27
re-register = 再登録, list1.html
accept = 承認, list2.html
reject = 否認, list2.html
[取消]
# 取消ボタンがクリックされたら、申請を取り消します。
# wf.CancelProcessコマンドを実行することにより、WF_Worksheet表の状態コード
# (statusCD)が‘4’(取消済)に更新されます。
transaction(db)
call wf.CancelProcess(db, $sheetid)
commit(db)
[再登録]
# 再登録ボタンがクリックされたら、申請を再登録します。
# wf.RestartProcessコマンドを実行することにより、WF_Worksheet表の状態コード
# (statusCD)が‘1’(申請中)に、申請開始日時(startDate)が現在日時に
# 更新されます。
transaction(db)
call wf.RestartProcess(db, $sheetid)
commit(db)
# 最初の承認者にメールを送信します。
perform メール送信()
[承認]
# 承認ボタンがクリックされたら、申請を承認します。
# wf.AcceptSheetコマンドを実行することにより、WF_AcceptHistory表に承認履歴
# が登録されます。また、最終承認者が承認を行った場合には、WF_Worksheet表の
# 状態コード(statusCD)が‘2’(承認済)に、完了日時(finishedDate)が現在
# 日時に更新されます。
transaction(db)
call wf.AcceptSheet(db, $sheetid, $s_userid)
commit(db)
# 次の承認者にメールを送信します。
# 全員に承認された場合は、申請者にメールを送信します。
perform メール送信()
[否認]
# 否認ボタンがクリックされたら、申請を否認します。
# wf.RejectSheetコマンドを実行することにより、WF_AcceptHistory表に否認履歴
# が登録されます。また、WF_Worksheet表の状態コード(statusCD)が‘3’(否認
# 済)に、完了日時(finishedDate)が現在日時に更新されます。
transaction(db)
call wf.RejectSheet(db, $sheetid, $s_userid)
commit(db)
# 申請者にメールを送信します。
perform メール送信()
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 28
2-9. 承認/否認履歴画面
2-9-1. HTML(history.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>history</title>
<style type="text/css">th,td{font-size:10pt}</style>
<script language="javascript" src="/common.js"></script>
</head>
<body>
<center>
<hr>
<font size="4" color="Blue"><b>承認/否認履歴</b></font>
<hr>
<br>
<table width="350" border="1" cellspacing="0" cellpadding="1">
<tr>
<th width="60" bgcolor="Orange">タイトル</th>
<td>@シートタイトル</th>
</tr>
</table>
<br>
<table width="350" border="1" cellspacing="0" cellpadding="1">
<tr bgcolor="Orange">
<th width="110">日時</th>
<th width="110">承認者</th>
<th>承認/否認区分</th>
</tr>
<!-- %repeat -->
<tr>
<td>@日時</td>
<td>@承認者</td>
<td>@承認否認区分</td>
</tr>
<!-- %end -->
</table>
<br>
<input type="button" name="close" value="閉じる" onClick="window.close()">
</center>
</body>
</html>
2-9-2. 仕様定義書(history.spj)
[reference]
シートタイトル = $sheettitle
日時 = $regdate
承認者 = $username
承認否認区分 = $acceptkb
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 29
[init_page]
perform シートタイトル取得()
perform 承認否認履歴取得()
[シートタイトル取得]
# データベースにアクセスして、シートのタイトルを取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
sheettitle
from
wf_worksheet
where
sheetid = '?sheetid'
")
call QuerySQL(rs, db, $sql)
local(sheettitle)
call SQLResultRow(rs, 1, sheettitle)
[承認否認履歴取得]
# データベースにアクセスして、承認否認履歴を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
to_char(regdate, 'YYYY-MM-DD HH24:MI'),
username,
case acceptkb
when 1 then '承認'
when 2 then '否認'
end
from
wf_accepthistory,
user_m
where
accepterid = userid
and
sheetid = '?sheetid'
order by
regdate
")
call QuerySQL(rs, db, $sql)
local(regdate, username, acceptkb)
call SQLResultRowArray(rs, 1, 0, regdate, username, acceptkb)
2-10. 完了画面
2-10-1. HTML(complete.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>complete</title>
</head>
<body>
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 30
経費を精算しました。
<br><br>
<a href="@WEBAPP?nextpage=list1.html">申請リストの一覧</a>
</body>
</html>
2-11. エラー画面
2-11-1. HTML(error.html)
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=Shift_JIS">
<title>error</title>
</head>
<body>
<font size="4" color="Red"><b>エラーが発生しました!</b></font>
<br><br>
<hr align="left">
@エラーメッセージ
<hr align="left">
</body>
</html>
2-11-2. 仕様定義書(error.spj)
[reference]
エラーメッセージ = $err_msg
2-12. 共通プログラム
2-12-1. JavaScript(common.js)
/*
申請データの「総合計」と「差引支払額」を再計算します。明細データの「金額」お
よび「仮払金控除」が変更されたタイミングで実行されます。
*/
function recast() {
var amount = 0;
var total = 0;
var temporary = window.document.form.temporary.value;
var payment = 0;
for (i = 0; i < window.document.form.amount.length; i++) {
amount = window.document.form.amount[i].value;
if (amount == "") {
amount = 0;
}
amount = parseInt(amount);
total += amount;
}
payment = total - temporary;
window.document.form.total.value = total;
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 31
window.document.form.payment.value = payment;
}
/*
引数に指定されたURLのページを別ウィンドウでオープンします。
*/
function openWindow(url) {
showModalDialog(url, "subwindow", "dialogWidth:400px;dialogHeight:300px");
}
/*
引数として指定されたURLの次のページに指定し、終了メッセージを表示します。
*/
function quit(url) {
location.href = url;
alert("ご利用ありがとうございました。");
}
2-12-2. 申請期間チェックの仕様定義書(check.spj)
[申請期間チェック]
# 申請期間をオーバーしているシートがないかチェックします。
local(temp_sheetid)
call wf.GetSheet(temp_sheetid, db, , 1)
Note:
wf.GetSheet コマンドは本来、対象となるシートを取得するためのコマンドです
が、このコマンドが実行されたタイミングですべての申請書の申請期間もチェ
ックされます。申請期間をオーバーしている申請書が見つかった場合は、
WF_Worksheet 表の該当レコードの状態コード(statusCD)が‘5’(申請期間オ
ーバー)に変更されます。
ここでは、シートを取得するためではなく、申請期間のチェックが目的でこのコ
マンドを実行しています。
2-12-3. メール送信の仕様定義書(mail.spj)
[メール送信]
perform ステータス取得()
# ステータスコードが1∼3(申請中/承認済/否認済)の場合のみ処理を続行します。
if ($status < 1 ││ 3 < $status)
return()
endif
perform 送信先ユーザー設定()
perform タイトル取得()
perform 件名設定()
perform メール本文作成()
perform SMTPサーバー情報取得()
perform SMTPサーバーオープン()
perform メッセージ送信()
perform SMTPサーバークローズ()
[ステータス取得]
# シートのステータスを取得します。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 32
# wf_GetSheetStatusコマンドを実行することにより、指定した書類ID(sheetID)
# の状態コード(statusCD)をWF_Worksheet表から取得します。
local(status)
call wf.GetSheetStatus(status, db, $sheetid)
[送信先ユーザー設定]
local(userid, username)
# ステータスが「申請中」の場合、承認者が送信先となります。
if ($status = 1)
perform 承認者取得()
# ステータスが「承認済/否認済」の場合、申請者が送信先となります。
else if ($status <= 3)
perform 申請者取得()
endif
[承認者取得]
# データベースにアクセスして、承認者を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
a.accepterid,
u.username
from
wf_worksheet w,
wf_accepter a,
user_m u
where
w.sheetid = a.sheeted
and
w.accepterno = a.accepterno
and
a.accepterid = u.userid
and
w.sheetid = '$sheetid'
")
call QuerySQL(rs, db, $sql)
call SQLResultRow(rs, 1, userid, username)
[申請者取得]
# データベースにアクセスして、申請者を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
w.userid,
u.username
from
wf_worksheet w,
user_m u
where
w.userid = u.userid
and
w.sheetid = '$sheetid'
")
call QuerySQL(rs, db, $sql)
call SQLResultRow(rs, 1, userid, username)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 33
[タイトル取得]
# データベースにアクセスして、シートのタイトルを取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
sheettitle
from
wf_worksheet
where
sheetid = '$sheetid'
")
call QuerySQL(rs, db, $sql)
local(sheettitle)
call SQLResultRow(rs, 1, sheettitle)
[件名設定]
# シートのステータスによって、メールのサブジェクトを設定します。
local(subject)
if ($status = 1)
put(subject, "【経費精算】承認処理のお願い")
else if ($status = 2)
put(subject, "【経費精算】申請が承認されました")
else if ($status = 3)
put(subject, "【経費精算】申請が否認されました")
endif
[メール本文作成]
# シートのステータスによって、メールの本文を設定します。
local(body)
if ($status = 1)
put(body,
"$usernameさん",
"$line_separator",
"$line_separator",
"経費精算「$sheettitle」が申請されました。",
"$line_separator",
"下記URLからログインして承認/否認処理を行ってください。"
"$line_separator",
"$line_separator",
"$WEBAPP"
)
else if ($status = 2)
put(body,
"$usernameさん",
"$line_separator",
"$line_separator",
"経費精算「$sheettitle」が承認されました。",
"$line_separator"
)
else if ($status = 3)
put(body,
"$usernameさん",
"$line_separator",
"$line_separator",
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 34
"経費精算「$sheettitle」が否認されました。",
"$line_separator"
)
endif
call ReplaceMarker(body, $body)
[SMTPサーバー情報取得]
# データベースにアクセスして、SMTPサーバーのユーザー情報を取得します。
local(sql, rs)
call ReplaceMarker(sql, "
select
mail,
smtp_userid,
smtp_password
from
user_m
where
userid = '$userid'
")
call QuerySQL(rs, db, $sql)
local(mail, smtp_userid, smtp_password)
call SQLResultRow(rs, 1, mail, smtp_userid, smtp_password)
[SMTPサーバーオープン]
# STMPサーバーをオープンします。
local(smtp)
smtp = mail.open_smtp($SMTPServer, $smtp_userid, $smtp_password)
[メッセージ送信]
# メッセージを送信します。
mail.send_message($smtp, $mail, , , "[email protected]", , $subject, $body)
[SMTPサーバークローズ]
# SMTPサーバーをクローズします。
mail.close_smtp($smtp)
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 35
3. 動作確認
編集したファイルをすべて保存し、実行ボタンをクリック、もしくは「実行」メニュー下の「実
行」をクリックすると101NEO Studioに内蔵された簡易HTTPサーバー、Servletコンテナを使
用して動作の確認ができます。
3-1. ログイン
次の画面でユーザーIDおよびパスワードを入力し、ログインします。
正常にログインすると次のような画面が表示されます。
3-2. 新しい申請書の作成
メニューの「新しい申請書の作成」をクリックすると次のような入力画面が表示されるので、
申請したい経費データを入力します。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 36
入力後「申請」ボタンを押すと、次のような完了画面が表示されます。
また、最初の承認者(経理)に次のようなメールが送られます。
3-3. 承認処理
「【経費精算】承認処理のお願い」というメールを受け取った承認者は、メールに記載されて
いるURLからシステムにログインします。
メニューの「承認/否認対象リストの一覧」をクリックすると、次のような画面が表示されま
す。
リスト内のタイトルのリンクをクリックすると、次のような申請データの詳細画面が表示され
ます。
内容を確認して「承認」ボタンをクリックします。
次の承認者(社長)も同様に承認処理を行います。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 37
すべての承認が済むと、次のようなメールが申請者に送られます。
申請リストの一覧では、「ステータス」フィールドが「承認済」に変わります。
もし、いずれかの承認者が否認した場合は、次のようなメールが申請者に送られます。
3-4. 申請データの取消/再登録
「申請中」のシートの取消しや、「否認済」や「申請期間オーバー」のシートの再登録は、申
請者自身が申請データの詳細画面で行うことができます。
3-5. 承認/否認履歴の確認
申請リストの一覧画面にあるステータスのリンクをクリックすると、次のような履歴画面が表
示されます。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 38
3-6. ログアウトの実行
メニューの「ログアウト」をクリックするかメインウィンドウを閉じると、次のような終了メッセー
ジが表示されます。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 39
付録A. SQL
ワークフロー関連のSQL文
drop table WF_AcceptHistory
/
drop table WF_Accepter
/
drop table WF_Worksheet
/
create table WF_Worksheet (
sheetID
varchar2(30)
primary key,
userID
varchar2(30)
not null,
sheetTitle
varchar2(120),
sheetKind
varchar2(10),
timeout
number(5),
accepterNo
number(2),
regDate
date
not null,
startDate
date,
finishedDate
date,
statusCD
number(2)
not null
)
/
create index userid_idx on WF_Worksheet(userID)
/
create table WF_Accepter (
sheetID
varchar2(30)
not null
references WF_Worksheet(sheetID),
accepterNo
number(2)
not null,
accepterID
varchar2(30)
not null
)
/
create table WF_AcceptHistory (
sheetID
varchar2(30)
not null
references WF_Worksheet(sheetID),
accepterID
varchar2(30)
not null,
regDate
date
not null,
acceptKB
number(2)
not null
)
/
申請データ関連のSQL文
drop table application
/
create table application (
sheetid
varchar2(30)
total
number
temporary
number
payment
number
)
/
drop table application_detail
Copyright © 2004
primary key,
not null,
not null,
not null
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 40
/
create table application_detail (
sheetid
varchar2(30)
row_no
number
row_date
date
detail
varchar2(200)
item
varchar2(30)
receipt
number,
amount
number
primary key (sheetid, row_no)
)
/
drop sequence sheetid
/
create sequence sheetid
/
not
not
not
not
not
null,
null,
null,
null,
null,
not null,
マスター関連のSQL文
drop table item
/
create table item (
id
item_cd
item_name
)
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
insert into item values
/
drop table user_m
/
create table user_m (
userid
password
username
mail
Copyright © 2004
number
varchar2(30),
varchar2(30)
primary key,
(1, '', '')
(2, 'travel', '旅費交通費')
(3, 'entertainment', '接待費')
(4, 'meeting', '会議費')
(5, 'training', '研修費')
(6, 'welfare', '福利厚生費')
(7, 'stationery', '事務用品代')
(8, 'book', '書籍代')
(9, 'tax', '租税')
(10, 'other', 'その他')
varchar2(30)
varchar2(30),
varchar2(30),
varchar2(30),
primary key,
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 41
smtp_userid
smtp_password
position
varchar2(30),
varchar2(30),
varchar2(30)
)
/
insert into user_m values ('user1', 'user1', 'ユーザー1',
'[email protected]', 'user1', 'user1', 'president')
/
insert into user_m values ('user2', 'user2', 'ユーザー2',
'[email protected]', 'user2', 'user2', 'auditor')
/
insert into user_m values ('user3', 'user3', 'ユーザー3',
'[email protected]', 'user3', 'user3', 'director')
/
insert into user_m values ('user4', 'user4', 'ユーザー4',
'[email protected]', 'user4', 'user4', 'sales')
/
commit
/
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 42
付録B. コマンドリスト
初期化
wf.Init
ワークフローエンジンの使用を開始
ワークフロープロセス(書類)の操作
wf.CreateProcess
ワークフロープロセス(書類)を新規に登録して申請を開始
wf.CreateDynamicProcess 承認者を動的に決定できるワークフロープロセス(書類)を新
規に登録して申請を開始
wf.CancelProcess
ワークフロープロセス(書類)を取り消し
wf.RestartProcess
ワークフロープロセス(書類)を再登録
wf.DeleteProcess
ワークフロープロセス(書類)をデータベースから削除
承認操作
wf.AcceptSheet
申請中のワークフロープロセス(書類)を「承認」
wf.RejectSheet
申請中のワークフロープロセス(書類)を「否認」
拡張ステータスの設定
wf.SetStatusEx
ワークフロープロセス(書類)の承認状態を拡張ステータスコ
ードで設定
ワークフロープロセス(書類)取得操作
wf.GetSheet
特定ユーザーを対象とするワークフロープロセス(書類)を取
得
wf.GetSheetEx
特定ユーザーを対象とする拡張ステータスコードを使用したワ
ークフロープロセス(書類)を取得
wf.GetSheetByStatus
特定の承認状態のワークフロープロセス(書類)を取得
ワークフロープロセス(書類)の承認状態取得操作
wf.GetSheetStatus
ワークフロープロセス(書類)の承認状態を取得
初期化
wf.Init
ワークフローエンジンの使用を開始
書式
call wf.Init($trans_mode)
パラメータ
trans_mode
トランザクションモード
1: ワークフローエンジン内部でトランザクションを発行(省略時)
2: アプリケーション側でトランザクションを発行
説明
ワークフローエンジンの使用を開始します。
このコマンドをアプリケーション仕様定義書の[init_app]セクションから呼び出すことでワー
クフローエンジンを使用することができるようになります。
トランザクションモードは、ワークフローコマンドの実行結果の確定をワークフローエンジ
ン内で行うか、アプリケーション側で行うかを設定します。
トランザクションモードを 1 に設定した場合は、ワークフローコマンドが実行された時点で
実行結果が確定します。
トランザクションモードを 2 に設定した場合は、アプリケーション側でトランザクションの
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 43
発行(transaction)及び確定(commit)を行う必要があります。
ワークフローエンジン内部ではトランザクションは発行しません。
ワークフロープロセス(書類)の操作
wf.CreateProcess
ワークフロープロセス(書類)を新規に登録して申請を開始
書式
call wf.CreateProcess(wfdb, $sheet, $requester, $accepter[, $title, $kind, $time])
パラメータ
wfdb
sheet
requester
accepter
title
kind
time
ワークフローDB のデータベース識別名
書類 ID
申請者 ID
承認者 ID もしくは承認者 ID の配列
書類タイトル(省略可)
書類種別(省略可)
申請期間(時間)(省略可)
説明
ワークフロープロセス(書類)を新規に登録して申請を開始します。
ワークフロープロセスの承認状態は「申請中」になり、最初の承認者の承認待ち状態にな
ります。
書類 ID には、30 バイトまでのユニークな識別名を指定できます。
(ワークフロー用データベーススキーマを変更することで制限を変更することも可能です)
書類タイトル、書類種別、申請期間は省略することができます。
申請期間は時間単位で設定します。省略した場合は無期限になります。
関連コマンド/関数
拡張コマンド wf.CreateDynamicProcess
承認者を動的に決定できるワークフロー
のプロセス(書類)を新規に登録して申請を開始
wf.CreateDynamicProcess
承認者を動的に決定できるワークフロープロセス(書類)を新規に登録して申請を開始
書式
call wf.CreateDynamicProcess(wfdb, $sheet, $requester, $obj[, $title, $kind, $time])
パラメータ
wfdb
sheet
requester
obj
title
kind
time
ワークフローDB のデータベース識別名
書類 ID
申請者 ID
jp.co.val.workflow.IAccepter を実装したインスタンス
書類タイトル(省略可)
書類種別(省略可)
申請有効期間(時間)(省略可)
説明
承認者を動的に決定できるワークフロープロセス(書類)を新規に登録して申請を開始し
ます。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 44
ワークフロープロセスの承認状態は「申請中」になり、最初の承認者を探すために
jp.co.val.workflow.IAccepter を実装したインスタンスの nextAccepter メソッドが呼び出され
ます。
承認が必要な度に承認者を得るために nextAccepter メソッドが呼び出されます。
このコマンドは、承認者をあらかじめ決定しておくことなく、申請の状況に応じて承認者を
変えることができるコマンドです。
書類 ID には、30 バイトまでのユニークな識別名を指定できます。
(ワークフロー用データベーススキーマを変更することで制限を変更することも可能です)
書類タイトル、書類種別、申請期間は省略することができます。
申請期間は時間単位で設定します。省略した場合は無期限になります。
関連コマンド/関数
拡張コマンド wf.CreateProcess
申請を開始
ワークフローのプロセス(書類)を新規に登録して
wf.CancelProcess
ワークフロープロセス(書類)を取り消し
書式
call wf.CancelProcess(wfdb, $sheet)
パラメータ
wfdb
sheet
ワークフローDB のデータベース識別名
書類 ID
説明
ワークフロープロセス(書類)を取り消します。
ワークフロープロセスの承認状態は「取消済」になります。
ただし、ワークフロープロセスのデータはデータベースから削除されません。
データベースから削除する場合は wf.DeleteProcess コマンドを呼び出します。
関連コマンド/関数
拡張コマンド wf.DeleteProcess
削除
ワークフローのプロセス(書類)をデータベースから
wf.RestartProcess
ワークフロープロセス(書類)を再登録
書式
call wf.RestartProcess(wfdb, $sheet)
パラメータ
wfdb
sheet
ワークフローDB のデータベース識別名
書類 ID
説明
ワークフロープロセスを再登録します。
ワークフロープロセスの承認状態は「申請中」になり、最初の承認者の承認待ちになりま
す。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 45
wf.DeleteProcess
ワークフロープロセス(書類)をデータベースから削除
書式
call wf.DeleteProcess(wfdb, $sheet)
パラメータ
wfdb
sheet
ワークフローDB のデータベース識別名
書類 ID
説明
ワークフロープロセス(書類)をデータベースから削除します。
関連コマンド/関数
拡張コマンド wf.CancelProcess
ワークフローのプロセス(書類)を取り消し
承認操作
wf.AcceptSheet
申請中のワークフロープロセス(書類)を「承認」
書式
call wf.AcceptSheet(wfdb, $sheet, $accepter)
パラメータ
wfdb
sheet
accepter
ワークフローDB のデータベース識別名
書類 ID
承認者 ID
説明
申請中のワークフロープロセス(書類)を「承認」します。
すべての承認者の承認が終了したときにワークフロープロセスの承認状態は「承認済」に
なります。
関連コマンド/関数
拡張コマンド wf.RejectSheet
申請中の書類を「否認」
wf.RejectSheet
申請中の書類を「否認」
書式
call wf.RejectSheet(wfdb, $sheet, $accepter)
パラメータ
wfdb
sheet
accepter
ワークフローDB のデータベース識別名
書類 ID
承認者 ID
説明
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 46
申請中のワークフロープロセス(書類)を「否認」します。
ワークフロープロセスの承認状態は「否認済」になります。
関連コマンド/関数
拡張コマンド wf.AcceptSheet
申請中の書類を「承認」
拡張ステータス設定
wf.SetStatusEx
ワークフロープロセス(書類)の承認状態を拡張ステータスコードで設定
書式
call wf.SetStatusEx(wfdb, $sheet, $status)
パラメータ
wfdb
sheet
status
ワークフローDB のデータベース識別名
書類 ID
拡張ステータスコード
説明
ワークフロープロセス(書類)(sheet)の承認状態を拡張ステータスコード(status)に更新し
ます。
拡張ステータスコードは 10 以上の値です。
承認状態コード、拡張ステータスコードに関しては「8-2-5.承認状態コード」を参照してくだ
さい。
ワークフロープロセス(書類)取得操作
wf.GetSheet
特定ユーザーを対象とするワークフロープロセス(書類)を取得
書式
call wf.GetSheet(sname, wfdb, $requester, $code [, $title])
パラメータ
sname
wfdb
requester
code
書類 ID 配列のセッション変数名 (出力パラメータ)
ワークフローDB のデータベース識別名
申請者 ID/承認者 ID
リクエストコード
1:
2:
3:
4:
5:
title
申請中(申請者 ID)
承認済(申請者 ID)
否認済(申請者 ID)
申請期間オーバー(申請者 ID)
未承認(承認者 ID)
書類タイトル(省略可)
説明
特定ユーザー(requester)を対象とするワークフロープロセス(書類)の中からリクエストコ
ードや書類タイトル(省略可)で検索し、該当するワークフロープロセスの書類 ID を配列と
してセッション変数(sname)に設定します。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 47
関連コマンド/関数
拡張コマンド wf.GetSheetEx
ワークフローから特定ユーザーを対象とする拡張
ステータスコードを使用した書類を取得
拡張コマンド wf.GetSheetByStatus ワークフローから特定の承認状態の書類を取得
Note:
リクエストコードは、承認状態コードと似ていますが異なるもので
す。
wf.GetSheetEx
特定ユーザーを対象とする拡張ステータスコードを使用したワークフロープロセス(書類)
を取得
書式
call wf.GetSheetEx(sname, wfdb, $requester, $code [, $title])
パラメータ
sname
wfdb
requester
code
title
書類 ID 配列のセッション変数名 (出力パラメータ)
ワークフローDB のデータベース識別名
申請者 ID/承認者 ID
拡張ステータスコード
書類タイトル(省略可)
説明
特定ユーザー(requester)を対象とするワークフロープロセス(書類)の中から拡張ステータ
スコードや書類タイトル(省略可)で検索し、該当する書類の書類 ID を配列としてセッショ
ン変数に設定します。
拡張ステータスコードは 10 以上の値です。
拡張ステータスコードに関しては「8-2-5.承認状態コード」を参照してください
関連コマンド/関数
拡張コマンド wf.GetSheetEx
ワークフローから特定ユーザーを対象とする拡張
ステータスコードを使用した書類を取得
拡張コマンド wf.GetSheetByStatus ワークフローから特定の承認状態の書類を取得
wf.GetSheetByStatus
特定の承認状態のワークフロープロセス(書類)を取得
書式
call wf.GetSheetByStatus(sname, wfdb, $status [, $title])
パラメータ
sname
wfdb
code
書類 ID 配列のセッション変数名 (出力パラメータ)
ワークフローDB のデータベース識別名
承認状態コード
1: 申請中
2: 承認済
3: 否認済
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 48
4: 取消済
5: 申請期間オーバー
その他: 拡張ステータスコード
title
書類タイトル(省略可)
説明
特定の承認状態(code)のワークフロープロセス(書類)の書類 ID を配列としてセッション変
数(sname)に設定します。
ワークフローエンジンがデフォルトで備えている承認状態以外の拡張ステータスも指定で
きます。
関連コマンド/関数
拡張コマンド wf.GetSheet
ワークフローから特定ユーザーを対象とする書類
を取得
拡張コマンド wf.GetSheetEx
ワークフローから特定ユーザーを対象とする拡張
ステータスコードを使用した書類を取得
3-6-1. ワークフロープロセス(書類)の承認状態取得操作
wf.GetSheetStatus
ワークフロープロセス(書類)の承認状態を取得
書式
call wf.GetSheetStatus(sname, wfdb, $sheet)
パラメータ
sname
セッション変数名 (出力パラメータ)
承認状態
-1: 書類ID未登録
0: 準備中
1: 申請中
2: 承認済
3: 否認済
4: 取消済
5: 申請期間オーバー
その他: 拡張ステータスコード
wfdb
sheet
ワークフローDB のデータベース識別名
書類 ID
説明
ワークフロープロセス(書類)(sheet)の承認状態をセッション変数(sname)に設定します。
Copyright © 2004
101 Co., Ltd. All Rights Reserved.
Tutorial Workflow
-
Page 49