この特集のトップページへ
Chapter 6:ビジネスロジックの設計



6.5.3 承認,却下処理
●伝票の承認
 次に,伝票の承認処理を実装してゆく。伝票の承認処理は,次の2つの処理の組み合わせとなる(Fig.6-88)。

  1. CONSENTEDFLAGフィールドの値をTrueに,REQ_CONSENTFLAGフィールドの値をFalseにする

  2. 在庫情報テーブルに出庫を示すレコードを加える(「3.2.6 在庫の処理」ならびに「6.3.5 在庫の処理」を参照)

 2.の処理は,伝票に結び付けられている各明細に対して,すでに実装ずみのDataObj.StockコンポーネントのAddRecordメソッド(List 6-69)を使って追加すればよい。

 実際にBusiness.SlipコンポーネントにConsentSlipという名前のメソッドとして実装したものがList 6-159である。

 List 6-159に示したConsentSlipメソッドは,引数に承認ずみにしたい伝票の伝票番号と,承認ずみにするときのコメントを渡す。すると,53〜56行目にある処理によって,CONSENTEDFLAGフィールドの値がTrueに,REQ_CONSENTFLAGフィールドの値がFalseに設定される。その後,66行目で伝票に付随する明細のレコードを取得し,72〜80行目にあるWhileループ処理内で在庫情報テーブルに出庫を示すレコードを加える処理をする。実際に在庫情報テーブルに出庫予定のレコードを加えているのが75行目にあるDataObj.StockコンポーネントのAddRecordメソッド(List 6-69)の呼び出しである。

 なお,List 6-159ConsentSlipメソッドは,44〜51行目にあるように,SalesManagerSalesAdminAllAdminの各ロールに属するユーザーのみが操作できるようになっている。このセキュリティ判定部では,ロールによる複雑な条件判断をしているわけではないので,44〜51行目を省いてプログラム側でロールを判定するのをやめ,[コンポーネントサービス]管理ツールによってロールによるセキュリティを設定してもかまわない。

Fig.6-88 在庫情報テーブルに出庫を示すレコードを加える
fig6_88

●伝票の承認取り消し
 List 6-159に示したConsentSlipメソッドを呼び出すことによって伝票が承認ずみとなるが,誤操作によって承認ずみにしてしまう可能性も考えられるので,承認ずみのものを承認依頼中の状態に戻すメソッドが必要となる。承認した伝票の状態を「承認依頼中」に戻すには,承認ずみとした処理と逆の操作をすればよい。つまり,次に示す2つの処理を組み合わせることになる。

  1. CONSENTEDFLAGフィールドの値をFalseに,REQ_CONSENTFLAGフィールドの値をTrueにする

  2. 在庫情報テーブルに加えた出庫を示すレコードを削除する

 このうち,1.の処理については,DataObj.SlipコンポーネントのSet_REQ_CONSENTFLAGメソッド(List 6-142)とSet_CONSENTEDFLAGメソッド(List 6-143)を使えば,処理できるので問題ない。問題となるのは,2.の処理である。

 すでにDataObj.Stockメソッドには,「6.3.5 在庫の処理」において,在庫情報テーブル中の指定したレコードを削除するためのDeleteRecordメソッドを実装してある(List 6-84)。しかし,このDeleteRecordメソッドは,引数に在庫情報テーブルのレコードID(IDフィールドの値)をとるため,事前に在庫情報テーブル中のどのレコードを削除するのか,つまり,ある伝票から作られた出庫予定のレコードはどれなのかを知る必要がある。


One Point!6.3.5 在庫の処理」では,レコードそのものを削除するDeleteRecordメソッド(List 6-84)だけでなく,レコードを削除ずみにする(DELETEDFLAGフィールドの値をTrueにする)ためのSetDeletedFlagメソッド(List 6-83)も用意した。そのため,DeleteRecordメソッドではなく,SetDeletedFlagメソッドを使ってレコードの削除を実現することもできる。しかしそれだと,伝票の状態を「承認ずみ」から「承認依頼中」に戻すたびにレコードがどんどん増え続けてしまうため,伝票から作られた出庫予定のレコードを削除するときにはDeleteRecordメソッドを使ってレコードそのものを削除する方法をとることにする。

 そこで,指定された伝票番号から作られた在庫情報テーブル中の出庫予定を示すレコードを返すGetRecordsBySlipIDというメソッドを,DataObj.Stockコンポーネントに実装する(List 6-160)。GetRecordsBySlipIDメソッドは,引数に伝票番号を指定すると,その伝票から作られた在庫情報テーブル中の出庫予定レコード群をADODB.Recordsetオブジェクトとして返す。指定された伝票番号を持つ伝票から作られたかどうかを判定するには,SLIPIDフィールドの値が伝票番号に一致するかどうかによって調べている(25行目)。

 このGetRecordsBySlipIDメソッドを使えば,伝票から作られた在庫情報テーブル中の出庫予定を示すレコードが得られる。そのため,得られたそれぞれのレコードに対し,DeleteRecordメソッド(List 6-84)を呼び出すことで,伝票の状態を「承認ずみ」としたときに作られた在庫情報テーブル中の出庫予定を示すレコードを削除することができる。

 実際に,以上で説明したメソッドを組み合わせ,伝票の承認取り消しをする処理をCancelConsentSlipという名前のメソッドとしてBusiness.Slipコンポーネントに実装したものが,List 6-161である。

 List 6-161に示したCancelConsentSlipメソッドは,引数に伝票番号をとり,その伝票番号で指定された伝票の状態を「承認ずみ」から「承認依頼中」に戻すというものである。52〜61行目にあるのが,ユーザーの権限の確認である。ここでは,伝票を起票したユーザーか承認したユーザーまたはSalesAdminロールまたはAllAdminロールに属するユーザー以外が呼び出したときにはエラーとしている。63〜67行目がCONSENTFLAGフィールドの値をFalseにし,REQ_CONSENTFLAGフィールドの値をTrueにする処理となっている。

 その後,伝票から作られた在庫情報テーブル中の出庫予定レコードを削除するために,73行目でDataObj.StockコンポーネントのGetRecordsBySlipIDメソッド(List 6-160)を呼び出し,伝票から作られた出庫予定レコードをADODB.Recordsetオブジェクトとして取得している。そして75〜80行目のWhileループ内では,DataObj.StockコンポーネントのDeleteRecordメソッド(List 6-84)を使って,取得した出庫予定を順に削除している。


One Point!List 6-161の75〜80行目の処理は効率的であるとはいえない。なぜなら,GetRecordsBySlipIDメソッドで伝票から作られた出庫予定情報を取得しているものの,その情報をビジネスロジックで利用しているわけではなく,削除するという目的のためだけに使っているからである。それならば,GetRecordsBySlipIDメソッドで出庫予定のレコードセットを返すのではなく,そのなかで出庫予定レコードを削除してしまったほうが効率がよい。このサンプルでそのような方法をとらなかった理由は2つある。1つは,すでにDataObj.Stockコンポーネントに実装したDeleteRecordメソッド(List 6-84)を使えば,削除処理が簡単になり,実装が楽になるからである。もう1つは,伝票から作成された出庫予定レコードを一覧にする機能は,後述する発送処理内でも必要になるので,ここで実装しておけば流用できるからである。このように,2つの理由は双方とも,開発者にとって実装が楽な方法を選んだにすぎない。効率性を求めるのであれば,このプログラミングスタイルはお勧めしない。
●一定金額以下だったならば自動的に承認するようにする
 最後に,一定金額以下だったならば,上司による承認を仰がず,直接承認ずみにする機構を実装する。この機能を実現するためには,伝票が承認依頼されたときに伝票の総額が一定額を超えなければ,承認依頼にする処理と承認ずみにする処理の2つをまとめて実行すればよい。

 問題は,「一定金額をどうやって決めるか」ということである。この問題を解決するためには,[コンポーネントサービス]管理ツールからコンポーネントのプロパティの[アクティブ化]ページを開いたときに指定できる[コンストラクタ文字列]を使うことにしよう(Fig.6-89)。

Fig.6-89 コンストラクタ文字列に承認が不要な伝票の最大金額を設定する
fig6_89

 すでにDataObj.*コンポーネントにおいては,コンストラクタ文字列はデータベースの接続文字列として使っている。しかし,Business.*コンポーネントではコンストラクタ文字列を使ってないので,コンストラクタ文字列を一定金額を指定する場所として使っても問題はない。


One Point! もちろん,コンストラクタ文字列は,ある機能に使ったら別の機能には使えないという意味ではない。文字列を解析して分割して利用する処理を実装すれば,DataObj.*コンポーネントのコンストラクタ文字列の設定を,一定金額を指定する場所として使うこともできる。たとえば,コンストラクタ文字列の形式を「DataBase=データベース接続文字列/Const=上司の承認が不要な伝票の最大金額」のようにスラッシュなどで各要素を区切るものと定めておけば,コンストラクタ文字列を受け取ったプログラムがその文字列を分解して利用すればよいだけの話である。

 そこで,Business.SlipコンポーネントにList 6-162に示すプログラムを実装する。List 6-162のプログラムは,IObjectConstructインタフェースを実装し,IObjectConstruct_Constructイベントにおいて,コンストラクタ文字列をg_MaxTotal変数に格納するというものである。これにより,Business.Slipコンポーネントのプロパティの[アクティブ化]ページで指定したコンストラクタ文字列が,g_MaxTotal変数に格納される。なお,List 6-162の10行目を見るとわかるように,コンストラクタ文字列に数値に変換できない値が与えられた場合には,その設定を無視し,g_MaxTotal変数には0を格納することにした。

 次に,すでにList 6-151に示した承認依頼処理をするRequestConsentメソッドを,List 6-163のように変更する。変更した箇所は,リストの赤色で示した132〜135行目の部分である。ここで伝票の合計金額(TOTALフィールドの値)がg_MaxTotal変数に格納されている値よりも等しいか小さければ,List 6-159に示したConsentSlipメソッドを呼び出し,さらに承認処理するようにした。


One Point!List 6-163の132〜135行目の部分はObjectContextオブジェクトのSetCompleteメソッドを呼び出したあとに実装される点に注意したい。ObjectContextオブジェクトのSetCompleteメソッドを呼び出した時点でトランザクションの状態が決定されるから,List 6-163に示したRequestConsentメソッドと132〜135行目で呼び出しているConsentSlipメソッドの処理は同じトランザクションで実行されるわけではない。つまり,ConsentSlipメソッド内でトランザクションがアボートしたとしても,RequestConsentメソッド内での処理はロールバックされない。

 ただし,このままだとList 6-159に示したConsentSlipメソッドの49〜51行目でロールを判定したときにエラーとなってしまうため,List 6-159List 6-164のように変更する。List 6-164で変更した箇所はロールの判定処理で,伝票の合計金額がg_MaxTotal以下で,かつ伝票の起票者が呼び出したときには,ロールの判定処理は飛ばすようにした(51〜57行目)。

 以上のようにプログラムを追加ならびに変更することによって,Business.Slipコンポーネントのプロパティにおける[アクティブ化]ページで[コンストラクタ文字列]に数値を指定しておけば,その数値以下の合計金額の伝票は上司の承認を仰がなくとも承認されるという機能が実現されたことになる。

prevpg.gif Chapter 6 74/92 nextpg.gif