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

head2.gif 6.6.2 請求書の作成
 請求書を作るということは,指定された顧客に対する伝票を月ごとに集計するということにほかならない。先に説明したように,請求書を作るパターンとしては,(1)月ごとにまとめて請求書を作る,(2)伝票に対して単体で作る,という2通りがある。これらのパターンでどのような処理が必要になるのかを考えてゆく。

●月ごとにまとめて請求書を作る
 月ごとにまとめて請求書を作るということは,伝票のなかでONEBILLFLAGフィールドがFalseであるものを月ごとに集計し,その結果を請求書テーブルのレコードとして記録するということである。

 顧客ごとの集計日は,顧客情報テーブルのBILLDAYフィールドに記録されている(「6.2.2 顧客情報の編集―●データオブジェクトの構築―」ならびに「3.2.4 請求書情報テーブル」を参照のこと)。たとえば,BILLDAYフィールドの値が25であれば,25日を締め日とし,前月の26日から当月の25日までを期限として集計する。

 月ごとにまとめて請求書を作る処理は,1日1回,サーバー側で自動的に実行するプログラムに委ねることにする。たとえば,ある月の26日にそのプログラムが実行されたとすると,プログラムは次のように動作する。

  1. 顧客情報テーブルのなかから,BILLDAYフィールドの値がその前日(25日)である顧客を取り出す
  2. その顧客が取引先となっている伝票のうち,まだ請求書が作られていない(経理処理ずみの状態にあると同義)ものの合計を集計する
  3. 請求書情報テーブルに請求書のレコードを追加する

 上記のような処理を実行するために必要な作業は,「請求書を作成する対象となる顧客を抜き出す作業(上記1.の作業)」と「伝票をまとめて請求書を作る作業(上記2.と3.の作業)」の2つに大きく分けることができる。

○請求書を作成する対象となる顧客を抜き出す
 まず,請求書を作成する対象となる顧客を抜き出す必要がある。そこで,特定の締め日が設定されている顧客のみを抜き出すメソッドを,次のような形で用意する。

Public Function GetCustomersByBillDay(billday As Long) _
                                      As ADODB.Recordset
    ' 引数billdayに指定された締め日が設定されている顧客の一覧を返す
End Function

 上記で示したGetCustomersByBillDayメソッドは,引数billdayに指定された締め日が設定されている顧客の一覧をADODB.Recordsetオブジェクトとして返す。たとえば,“GetCustomersByBillDay(25)”として呼び出すと,締め日が25日に設定されている顧客の一覧を返すことになる。

 この処理はGetCustomersByBillDayメソッド内で次のSELECT文を実行することにより実現できる。

SELECT * FROM 顧客情報 
WHERE BILLDAY=引数billdayに指定された値
  AND DELETEDFLAG=0

 実際にDataObj.CustomerコンポーネントにGetCustomersByBillDayメソッドを実装したものがList 6-169である。


One Point!List 6-169に示したGetCustomersByBillDayメソッドは,List 6-50に示したDataObj.CustomerコンポーネントのGetRecordsメソッドと非常によく似ている。実際のところ,異なるのは実行するSELECT文のWHERE句以下だけである(24行目)。そのため,本来ならば,この2つのメソッドを共通化したほうが,開発時やデバッグ時のコストは軽減される。しかし,ここで共通化すると,いままで作ってきたビジネスロジックからのメソッドの呼び出しなども変更する必要が生じ,修正が広範囲に及ぶ。そのため,今回はこの2つのメソッドの共通化はしないことにする。
○集計の対象となる伝票を抜き出し,請求書情報テーブルにレコードを追加する
 次に,GetCustomersByBillDayメソッドを呼び出した結果として取得した顧客ごとに伝票を集計し,請求書情報テーブルに請求書となるレコードを追加する。

 集計の対象となる伝票は,「経理処理ずみである(請求書がまだ作られていない)」という条件を満たしていなければならない。その判定を怠ると,1つの伝票が複数の請求書の請求対象になり,二重請求になってしまう可能性がある。


One Point!  さらに,伝票の経理処理日が格納されているACCOUNTINGDATEフィールドの値が集計期間内であるかどうかを調べてもよいが,今回はACCOUNTINGDATEフィールドの値は調べず,請求書が作られていないすべての伝票(経理処理ずみの状態にあるすべての伝票)を対象とする。なぜなら,ACCOUNTINGDATEフィールドの値が集計期間内であるものだけを調べると,伝票が経理処理した期間によっては,伝票が請求書に含まれることがないまま埋もれてしまう可能性があるためである。
 たとえば,ある顧客のBILLIDフィールドの値が10であったとしよう。この場合,5月分の請求書は,5月11日に4月11日〜5月10日までの期間で集計される。同様に6月分の請求書は6月11日に集計され,5月11日〜6月10日の期間となる。このとき,5月11日に請求書を作成してからすぐ,BILLIDフィールドの値を15に変更したとしよう。すると6月分の請求書は6月16日に集計され,5月16日〜6月15日の期間となる。つまり,5月11日〜5月15日までの伝票はどの請求書にも含まれなくなる。この説明でわかるように,伝票の期間を区切ってしまうと,BILLIDフィールドの値をあとから変更したときに,一部の期間の伝票が請求書から漏れる可能性がある。そのため,このサンプルでは,あえてACCOUNTINGフィールドの値は調べず,請求書が作られていないすべての伝票を集計対象とする。
 ただし,期間を調べないことによって,期間外の伝票が請求書に含まれてしまう可能性もある。つまり,上の例においては,5月16日〜6月15日の期間に5月11日〜5月15日の伝票も一緒に含まれてしまうことになるだろう。しかし,期間外の伝票が請求書に含まれるのと,期間外の伝票は請求書に含まれないのとどちらがよいかといわれれば,前者のほうになる。なぜなら,期間外の伝票が請求書に含まれていれば,経理担当者がそれを見て誤りを発見し,請求書を修正するなり,作り直すなりすることができるからである。それに対して,請求書に記載されない伝票は,データベーステーブルの奥底に眠ってしまい,ユーザーがその存在に気づかない可能性が高くなり,修正する機会が失われてしまうおそれがある。

 List 6-169に示したGetCustomersByBillDayメソッドを呼び出すことによって,集計の対象となる顧客の顧客番号(IDフィールドの値)を取得できるため,実際の請求書を作成するには,取得した各顧客ごとに伝票を抜き出し,請求書情報テーブルに請求書となるレコードを追加すればよいことになる。その一連の処理内容は,次のようになる。

1. 指定された顧客を取引先とする伝票を抜き出す

 まずは,(1)指定された顧客を取引先とし,かつ,(2)経理処理ずみである,という2つの条件を満たす伝票を抜き出す。そのためには,「6.4.4 伝票情報の一覧の取得」で実装したDataObj.SlipコンポーネントのGetRecordsメソッド(List 6-133)を使えばよい。GetRecordsメソッドを呼び出すことで,指定された顧客が取引先となっており,かつ経理処理ずみである伝票の一覧がADODB.Recordsetオブジェクトとして取得できる。

 と,ここまで書き進めて,本文中では顧客に対する絞り込みの実装を予定していたのに,List 6-133ではその実装をしていないことに気づいた。大変申し訳ないのだが,DataObj.SlipコンポーネントのGetRecordsメソッド(List 6-133)をList 6-170のように修正させていただく。List 6-170には,顧客ごとに絞り込みをするため,引数の最後に顧客番号を指定するようにした。顧客番号として負の数が渡されたときには,顧客による絞り込みはしないことにする。

 なお,List 6-170で示した最後の引数は,“Optional ByVal CustomerID As Long = -1”となっている。Optional指定は,この変数を指定しなくてもよいということを意味し,“=”以降は,変数が省略されたときのデフォルト値となっている。DataObj.SlipコンポーネントのGetRecordsメソッドは,すでにBusiness.SlipコンポーネントのGetSlipsメソッド(List 6-135)の61行目で呼び出されている。そのため,GetRecordsメソッドの引数の数を変更すると,本来ならばGetSlipsメソッドの61行目の引数を変更する必要が生じる。しかし,Optional指定をしておけば,それらのプログラムを修正しなくてもすむ。


One Point! ただし,DataObjプロジェクトをコンパイルしたあと,Businessプロジェクトも再コンパイルする必要がある。なぜなら,メソッドの引数の数を変更した場合には,COMのインタフェースID(IID)が変更されるからである。つまり,再コンパイルするという作業を避けることはできない。もし,再コンパイルせずに実行すると,「型が一致しません」というエラーが発生する。ただし,COMオブジェクトを保持する変数を(1)“Dim 変数名 As Object”のようにObject型として宣言する,(2)“Dim 変数名 As Variant”のようにVariant型として宣言する,(3)何も宣言しない,といういずれかの場合には,エラーは発生しない。なぜなら,これらのケースではオブジェクトの型情報が実行時に調べられるため,型の不一致が生じないからである。

One Point!Optional宣言した変数は,型としてVariant型を指定することができない。また,Optional宣言は,引数の最後から順に指定しなければならない。たとえば,“Public FuncA(ByVal varA As Long, ByVal varB As Long, Optional ByVal varC As Long)”のように最後の引数をOptional宣言することはできるが,“Public FuncA(ByVal varA As Long, Optional ByVal varB As Long, varC As Long)”のように引数varBOptional宣言をし,そのあとの引数であるvarCOptional宣言を指定しないということはできない。この場合には,(1)引数varBとともに引数varCOptional宣言し,“Public FuncA(ByVal varA As Long, Optional ByVal varB As Long, Optional varC As Long)”のようにするか,あるいは(2)引数の表記順序を入れ替え,“Public FuncA(ByVal varA As Long, ByVal varC As Long, Optional varB As Long)”のようにする。

 とはいえ,List 6-135で示したBusiness.SlipコンポーネントのGetSlipsメソッドは,プレゼンテーション層から伝票の一覧を取得するときに使うものである。ここで引数を使って顧客ごとに絞り込むことができると,このアプリケーションを利用するユーザーが顧客ごとに伝票を絞り込んで参照できるようになり,便利である。そこで,List 6-135も併せてList 6-171のように修正しておくことにする。

2. 請求書を作成する

 1. で取得した伝票を集計し,請求書を作る。請求書は,請求書情報テーブルのレコードとして記録する。請求書情報テーブルには,Table 6-18に示したとおり,集計期間や作成日など,さまざまな情報が格納される。

 請求書情報テーブルに請求書となるレコードを追加する機能を備えたメソッドを,DataObj.BillコンポーネントにAddRecordという名前で実装すると,List 6-172のようになる。AddRecordメソッドは,次のような書式となっている。


Public Function AddRecord(ByVal CUSTOMERID As Long, _
                          ByVal STARTDATE As Date, _
                          ByVal ENDDATE As Date, _
                          ByVal SUBTOTAL As Currency, _
                          ByVal TAX As Currency, _
                          ByVal TOTAL As Currency) As Long
    ' 請求書テーブルに請求書のレコードを追加する
    ' 【引数】
    '   CUSTOMERID = 顧客の顧客番号
    '   STARTDATE = 請求書に記載する集計期間の開始日(この日を含む)
    '   ENDDATE = 請求書に記載する集計期間の終了日(この日を含む)
    '   SUBTOTAL = 請求書に記載する小計
    '   TAX = 請求書に記載する消費税額
    '   TOTAL = 請求書に記載する合計額
    ' 【戻り値】
    '   追加された請求書の請求書番号(IDフィールドの値)
End If

 AddRecordメソッドの引数は,順に,顧客の顧客番号,集計期間の開始日,集計期間の終了日,請求書に記載する小計額,請求書に記載する消費税額,請求書に記載する合計額,となっている。引数に指定された値に基づいて,AddRecordメソッドは請求書情報テーブルに新しいレコードを作成する。なお,List 6-172を見るとわかるが,AddRecordメソッドは,指定された集計期間内の伝票を集計して自動的に請求書を作るというものではなく,単に請求書のレコードを追加する機能しかもたない。集計処理はビジネスロジック側で処理するものとし,AddRecordメソッドは書き込むべき請求書の内容を引数として受け取り,その通りに請求書レコードを作るだけである。

 顧客の顧客番号は,1. の処理で得られるものを引き渡す。そして,小計,消費税額,合計額については,1.の処理で得られた伝票の合算額をそれぞれ引き渡す。この合計額はビジネスロジック側で伝票を集計することで算出する。集計期間の開始日と終了日については,請求書が作成される日に応じて適切なものを渡すことにする。たとえば,2000年5月25日を締め日として実行する場合には,集計期間の開始日には2000年4月26日,終了日には2000年5月25日を渡すことにする。この日付はビジネスロジック側で算出することにする。

3. 伝票を請求書処理ずみにする

 1. で取得した伝票の状態を「請求書処理ずみ」に変更する。それには,「6.5.2 承認依頼処理」で作成したDataObj.SlipコンポーネントのSet_MADEBILLFLAGメソッド(List 6-147)を使えばよい。Set_MADEBILLFLAGメソッドを呼び出すことによって,伝票のMADEBILLFLAGフィールドの値をTrueに設定し,伝票の状態を「請求書作成ずみ」にすることができる。

【おわび】前回示したList 6-147には,伝票情報テーブルのフィールド名とプログラムが対応していないという誤りがあった。現在掲載中のプログラムはこの点を修正してあるので,参照していただきたい。

4. 伝票と請求書とを結び付ける

 1. で取得した伝票を2. で作成した伝票と結び付ける。具体的な処理としては,伝票のBILLIDフィールドの値に請求書番号を,BILLDATEフィールドに伝票を作成した日時を,それぞれ設定する。請求書番号はList 6-172に示したDataObj.BillコンポーネントのAddRecordメソッドの戻り値として取得できる。

 この処理をするために,List 6-173に示すSetBillIDメソッドをDataObj.Slipコンポーネントに実装する。SetBillIDメソッドの引数は,伝票番号と請求書番号である。このメソッドは,第1引数で指定された伝票番号を持つ伝票のBILLIDフィールドに,第2引数で指定された請求書番号を設定することで伝票と請求書とを結びつける処理をする。

 なお,SetBillIDメソッドの実装を見るとわかるように,請求書番号の引数の型はLong型ではなくVariant型とした。Variant型であれば,引数にNullを渡すことによって,BILLIDフィールドの値をNullに設定することもできるからである。BILLIDフィールドの値をNullにする処理は,請求書を削除するときに必要となる(詳しくは,「6.6.6 請求書作成の取り消し」で説明する)。


One Point! 本稿中では,話の流れから分離することになったが,実際には3. の処理と4. の処理を分離する必要はなく,1つのメソッドにまとめてしまうほうがよい。なぜなら,3. と4. の処理は常に同時に実行されるからである。もし1つのメソッドにまとめるのであれば,SetBillIDメソッド(List 6-173)の内容をSet_MADEBILLFLAGメソッド(List 6-147)のなかに実装してしまうとよいだろう。
prevpg.gif Chapter 6 79/92 nextpg.gif