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

6.6.2 請求書の作成
●月ごとにまとめて請求書を作る

○月ごとの請求書を作成するためのビジネスロジックのメソッド
 次に,月ごとの請求書を作成するビジネスロジックのメソッドを作成する。ここでは,Business.Billコンポーネントに次のような書式のMakeBillsメソッドを実装することにしよう。

Public Sub MakeBills(ByVal billdate As Date)
    ' 指定された日付よりも小さい日付の伝票を集計し,請求書を作成する
End Sub

 引数billdateには,集計したい伝票の締め日を指定する。たとえば,引数billdateに2000年5月25日を指定するとすると,顧客情報テーブル内のBILLDAYフィールドの値が25である顧客に対する伝票を集計して請求書を作成することになる。

注意 顧客情報テーブルのBILLDAYフィールドの値はNULL値が許されるようにデータベースを設計してある(「3.2.4 請求書情報テーブル」を参照)。請求書の作成は,BILLDAYフィールドの値が特定の日付である顧客に対しての操作となるため,BILLDAYフィールドの値がNULL値である,つまり,顧客に対して締め日を設定していないものに関しては,請求書作成の対象とならないので注意してほしい。より安全性を高めるには,伝票を経理処理ずみにするBusiness.SillコンポーネントのAccountingSlipメソッド(List 6-167)において,伝票の取引先となっている顧客の締め日(BILLDAYフィールドの値)がNullである場合には,経理処理ずみにはできずエラーとする,という実装をしたようがよいかも知れない。そのような実装をしておけば,経理担当者は,顧客に対して請求書の締め日をまだ設定していなかったのだということに気づくことができるだろう。

 MakeBillsメソッドの引数billdateで,伝票の締め日を日付だけでなく年号や月も合わせたDate型として指定しているのには理由がある。それは,その月の最終日をメソッド内部で調べる必要があるからである。たとえば,2000年4月30日までの伝票を集計したい場合を考えてみよう。4月は30日までしかないため,締め日が30日に設定されている顧客と31日に設定されている顧客の2つに対しての処理をする必要がある。しかし,単に引数に30とだけ渡されたのでは,何月であるのかわからず,その判定ができない。年号も同様で,閏年なのか閏年でないのかによって2月の最終日が28日なのか29日なのかを判定する必要がある。以上のような理由により,伝票の締め日は単純に日付を表す数値ではなく,年号や月も含めたDate型で指定する必要があるのである。

 MakeBillsメソッドは,サーバー上で一定時間ごとに起動するスクリプトによって呼び出されるものとする。サーバー上でスクリプトを実行するためには,ATコマンドまたはタスク機能を利用すればよい。今回はタスク機能を使い,オフピーク時である深夜に処理を実行することにし,前日までの伝票を集計するようにする。たとえば,毎日深夜2時にこの集計処理を実行するのであれば,2000年4月1日の午前2時になると,引数に“2000/03/31”を指定してMakeBillsメソッドを呼び出し,31日が締め日となっている顧客の請求書を作成する。同様に,4月2日の午前2時になったら,引数に“2000/04/01”を指定してMakeBillsメソッドを呼び出し,4月1日が締め日となっている顧客の請求書を作成する。このように,MakeBillsメソッドの引数に「実行された日付−1」を渡すことで,請求書を毎日作成してゆく。メソッドを定期的に実行する方法や実際に動作させるスクリプトについては,すぐあとで説明する。

 MakeBillsメソッドでは,いままで作成してきたデータオブジェクトの各メソッドを組み合わせ,次のような処理をする。

1. 顧客の抜き出し
 先に実装したDataObj.CustomerコンポーネントのGetCustomersByBillDayメソッド(List 6-169)を使い,引数billdateに指定された日付が締め日に設定されている顧客を抜き出す。このとき,引数billdateに指定された日付が当月の月末であった場合には,その日を締め日とする顧客だけでなく,締め日が31日に設定されている顧客まで順に抜き出しの対象としなければならない。つまり,たとえば引数billdateに2000年2月29日が指定されたならば,締め日が29日に設定されている顧客に加え,30日と31日に設定されている顧客も抜き出しの対象とする必要がある。
 
2. 伝票の抜き出し
 経理承認ずみの伝票のなかから,1. で取得した顧客を取引先とする伝票を抜き出す。この処理を実現するには,DataObj.SlipコンポーネントのGetRecordsメソッド(List 6-170)を使えばよい。
 
3. 請求書の作成
 2. で取得した伝票を顧客ごとに集計したのち,先に実装したDataObj.BillコンポーネントのAddRecordメソッド(List 6-172)を呼び出すことで,請求書を作成する。
 
4. 伝票の状態を請求書作成ずみにする
 DataObj.SlipコンポーネントのSet_MADEBILLFLAGメソッド(List 6-147)を呼び出すことで,2. で取得した伝票の状態を「請求書作成ずみ」にする。
 
5. 伝票と請求書を関連付ける
 2. で取得した伝票と3. で作成した請求書とを関連付ける。この処理を実現するには,DataObj.SlipコンポーネントのSetBillIDメソッド(List 6-173)を用いる。

 以上で示した一連の処理を実装したものが,List 6-174である。List 6-174は,Business.Billコンポーネントに実装することを前提としている。そのため,実際に実装するためには,BusinessプロジェクトにBillという名前のクラスモジュールを新規に追加し,そのクラスモジュールにプログラムを記述することになる。BusinessプロジェクトにBillクラスモジュールを追加するには,Visual Basicの[プロジェクト]メニューから[クラスモジュールの追加]を選べばよい。追加したBillクラスモジュールのトランザクション設定は,デフォルトで「トランザクションが新しく必要」とするため,MTSTransactionModeプロパティには[RequiresNewTransaction]を設定しておく(Fig.6-92)。そのあと,作成したBillクラスモジュールにList 6-174で示したプログラムを入力することになる。

Fig.6-92 BusinessプロジェクトのBillクラスモジュールのMTSTransactionModeプロパティ
fig6_92

 List 6-174では,やや複雑な処理をしている。その処理を順に説明しておこう。

 まず,26〜32行目では,引数に与えられた日付から請求期間を計算している。27行目でDateValue関数を呼び出すことで,引数billdateに与えられた日時のうち,時間部分が除去される。そして,30〜32行目で請求期間を算出している。算出した請求期間は,startDate変数とendDate変数に格納する。startDate変数には,引数billdateに与えられた値から1ヶ月引き(30行目),さらに1日加えた値を格納する(31行目)。endDate変数には引数billdateに与えられた値をそのまま格納する(32行目)。これにより,たとえば引数billdateに2000年4月25日が与えられたときには,startDate変数に2000年3月26日,endDate変数に2000年4月25日が格納されるようになる。

 次の34〜52行目では,請求書を作成するときに利用する締め日を算出する。引数billdateに2000年4月25日が渡された場合の締め日は25日,2000年4月26日が渡された場合の締め日は26日,……という具合に,基本的に請求書を作成するときの締め日となるのは,billdate変数に渡された日付の日にちの部分である。しかし,何度か述べているとおり,実際には月末処理を考慮する必要がある。たとえば2000年4月30日が渡された場合,4月は30日までしかないので,このときには締め日が30日である顧客に加え,締め日が31日である顧客の請求書も作成しなければならない。その判定をしているのが,34〜52行目である。この部分では,請求書の締め日として扱う最初の日をstartBillDay変数に,最後の日をendBillDay変数に格納する。たとえば,引数billdateに2000年4月30日が渡された場合,startBillDay変数を30に,endBillDay変数を31に,それぞれ設定する。もし月末でない場合,たとえば,引数billdateに2000年4月25日が渡されたときなどにはstartBillDay変数,endBillDay変数ともに25に設定する。

 まず35行目では,startBillDay変数に引数billdateで渡された値の日にち部分を格納する。次に38行目と39行目では,引数billdateで渡された日付情報から翌月の第1日目を調べ,その日から1日分だけ引いた日付の日にち部分をendBillDay変数に格納している。この処理はややわかりにくいかもしれないが,「引数billdateで渡された日付の翌月第1日目から1日分だけ引いた日付」とは,「引数billdateに渡された月の月末の日付」と同じ意味である。つまり,38行目と39行目の処理によって,endBillDay変数には引数billdateに渡された月の月末の日付が格納されることになる。月末の日付を求めるには,いくつかの方法がある。1つは,38行目と39行目で示したように,「翌月の第1日目から1日分だけ引いた値が当月の月末である」という事実を利用する方法,もう1つは,2月は28日,4月/6月/9月/11月は30日,それ以外は31日という具合にプログラム側に判定ルーチンを組み込む方法である。前者は,DateAdd関数やDateSerial関数を使って計算できるので,比較的手軽である半面,速度的には遅い。後者は,いちいち日付の加算処理をしなくてもすむため高速である半面,Select Case文なりIf文なりで月を調べる必要があるうえ,2月については閏年の判定処理も組み込まなければならないので,若干ながらプログラミング上の手間がかかる。どちらを選択するかは,時と場合による。今回は,処理速度よりも実装の容易さを優先し,前者の方法で実装した。

 次に,42〜52行目の処理では,上記のように算出したstartBillDay変数の値とendBillDay変数の値が一致するかどうかを調べている。endBillDay変数には該当月の月末の日付が格納されているため,startBillDay変数の内容とendBillDay変数の内容とが一致するということは,startBillDay変数が月末を示していることを意味する。この場合にはendBillDay変数の値を強制的に31に設定する(46行目)。そうではなく,startBillDay変数とendBillDay変数の値が一致しないときには,51行目にあるようにendBillDay変数にstartBillDay変数の内容を代入してしまう。

 以上の処理によって,月末ではない日時が引数billdateに渡されたときには,startBillDay変数とendBillDay変数の双方に引数billdateで指定された日付の日にち部分が格納される。これに対して,引数billdateに月末の日付が渡されたときには,引数billdateに渡された日付の日にち部分をstartBillDay変数に,31をendBillDay変数に,それぞれ格納されることになる。

 請求書の対象となる顧客の抜き出し処理は,65行目にあるように,startBillDay変数に格納された値からendBillDay変数に格納された値までのForループ処理として実装されている。すでに述べたとおり,月末の場合にはstartBillDay変数の値が30になり(ただし,2月の場合には28や29になる),endBillDay変数の値が31になるので,このループ処理によって,月末以降の値が締め日として設定されている顧客も請求書の作成対象となるわけである。

 実際に請求書を作成しているのは,Forループ内にある66〜122行目である。まず67行目でDataObj.CustomerコンポーネントのGetCustomersByBillDayメソッド(List 6-169)を呼び出し,指定された締め日に設定されている顧客を抜き出す。そして次に,取得した顧客に対してWhileループ処理(69〜117行目)を実行している。このWhileループのなかでは,その顧客を取引先とする伝票を抜き出し(72行目),抜き出した伝票の小計額と消費税額と合計額を合算し(77〜88行目のWhileループ),請求書を作成し(91行目),伝票の状態を「請求書作成ずみ」として(98,99行目),請求書と結び付けている(102行目)。

 以上の説明からもわかるように,List 6-174MakeBillsメソッドはループ処理がやや多く,冗長であるのも事実である。このようなループ処理をしなくても,SELECT文でSUM関数を使えば,特定の伝票の合計額を一気に算出できるし,UPDATE文を使うことで特定の伝票の状態を一気に請求書処理ずみにしたり,特定の請求書と結び付けたりすることができる。また,実際そうしたほうが高速になる。しかし,そのようにすると,かなり複雑なSELECT文やUPDATE文を使う必要があり,ややわかりにくくなることから,今回はVisual Basicによるループ処理で実現するようにした。速度的に劣るのは事実であるが,MakeBillsメソッドは頻繁に呼び出されるものではなく,請求書を作るために1日1回しか呼び出されることがないので,さしてパフォーマンスを重視する必要もなかろう。


One Point! とはいうものの,取引件数(伝票の件数)が増大した場合や,締め日が特定の日付に偏った場合には,オフピーク時の夜間に終わっているはずの作業が翌朝の始業時間になっても終了しないという事故が発生する可能性は否定できない。実務で使う場合には,どれだけの処理をどれだけの時間内で終えることができるかをあらかじめ見積もり,また,実機でのテストも十分に行っておくべきである。

 ところで,List 6-174を参照するとわかるように,Business.BillコンポーネントのMakeBillsメソッドはプログラム内でセキュリティ制御をしていない。Business.BillコンポーネントのMakeBillsメソッドは,経理部の管理者であるAccountingAdminロールからのみ呼び出せるように,[コンポーネントサービス]管理ツールを使ってセキュリティを設定するものとする。

prevpg.gif Chapter 6 80/92 nextpg.gif