本格的な帳票の作成(1)
概要
さて、ここまでは非常に簡単な例を示してきましたが、ここからは本格的な帳票を作成していきましょう。
今回はデータソースにはデータテーブルを利用します。この帳票は請求書です。AdventureWorksでは請求書を作るにあたって、次のようなテーブルを利用することとします。
なおテーブルはすべてではありませんし、列も利用するもののみを記述しています。それぞれのテーブルの概要は以下の通りです。
| スキーマ | テーブル | 概要 |
|---|---|---|
| Sales | SalesOrderHeader | 販売情報の概要 |
| Sales | SalesOrderDetail | 販売情報の製品別明細 |
| Production | Product | 製品マスター |
| Sales | Customer | 販売先の顧客 |
| Sales | Store | 顧客の店舗 |
| Person | Person | 顧客の担当者 |
| Person | Address | 請求先住所 |
| Person | StateProvince | 請求先住所の「州」 |
このデータをもとに、次のような帳票を作成します。

赤字部分が動的に設定する項目です。各項目のテーブルとのマッピングは次の通りです。
| 項目 | セル | テーブルおよび列 |
|---|---|---|
| 郵便番号 | B2 | Address.PostalCode |
| 住所 | B3 |
StateProvince.Name + ' ' Address.City + ' ' + Address.AddressLine1 + ' ' + Address.AddressLine2 + ' ' + |
| 店舗名 | B4 | Store.Name |
| 担当者名 | B6 | Person.FirstName + ' ' + Person.LastName |
| 請求日 | F2 | 請求書作成日 |
| 支払期限 | F3 | 請求日+30日 |
| No. | A13 | 明細の行番号 |
| 製品名 | B13 | Product.Name |
| 単価 | C13 | SalesOrderDetail.UnitPrice |
| 数量 | D13 | SalesOrderDetail.OrderQty |
| 割引率 | E13 | SalesOrderDetail.UnitPriceDiscount |
| 小計 | F13 | SalesOrderDetail.LineTotal |
| 税抜き価格 | F14 | SalesOrderHeader.SubTotal |
| 消費税 | F15 | SalesOrderHeader.TaxAmt |
| 送料 | F16 | SalesOrderHeader.Freight |
| 請求総額 | F18 | SalesOrderHeader.TotalDue |
なお住所が日本表記で、担当者名が英語表記となっています。日本語帳票なので住所は日本表記にしました。ただデータは英語です。アルファベットの氏名を姓名順で表示するのは気持ちが悪いので今回はこのようにしました。
上記項目のうち、Noから小計までは明細単位で行が増減します。
単一項目の設定
まずは明細以外の値を設定しましょう。
クエリの修正
まずはデータを取得するクエリを修正します。SelectInvoices.sqlを開き、以下の通り修正しましょう。
use AdventureWorks;
select
GETDATE() as InvitationDay,
DATEADD(DAY, 30, GETDATE()) as DueDate,
Address.PostalCode,
ISNULL(StateProvince.Name, '') + ' ' + ISNULL(Address.City, '') + ' ' + ISNULL(Address.AddressLine1, '') + ' ' + ISNULL(Address.AddressLine2, '') as CustomerAddress,
Store.Name as StoreName,
Person.FirstName + ' ' + Person.LastName as CustomerPerson,
SalesOrderHeader.SubTotal,
SalesOrderHeader.TaxAmt,
SalesOrderHeader.Freight,
SalesOrderHeader.TotalDue
from
Sales.SalesOrderHeader
inner join Person.Address
on SalesOrderHeader.BillToAddressID = Address.AddressID
inner join Person.StateProvince
on Address.StateProvinceID = StateProvince.StateProvinceID
inner join Sales.Customer
on Sales.SalesOrderHeader.CustomerID = Customer.CustomerID
inner join Sales.Store
on Customer.StoreID = Store.BusinessEntityID
inner join Person.Person
on Customer.PersonID = Person.BusinessEntityID
where
SalesOrderHeader.SalesOrderID = 44533
今回はSalesOrderHeader.SalesOrderIDに44533を固定で指定しています。
Excelテンプレートの修正
続いてExcelのテンプレートを修正しましょう。各項目について、次のようにセルに値を設定してください。
| 項目 | セル | テンプレート |
|---|---|---|
| 郵便番号 | B2 | {{Invoice.PostalCode}} |
| 住所 | B3 | {{Invoice.CustomerAddress}} |
| 店舗名 | B4 | {{Invoice.StoreName}} |
| 担当者名 | B6 | {{Invoice.CustomerPerson}} |
| 請求日 | F2 | {{Invoice.InvitationDay}} |
| 支払期限 | F3 | {{Invoice.DueDate}} |
| 税抜き価格 | F14 | {{Invoice.SubTotal}} |
| 消費税 | F15 | {{Invoice.TaxAmt}} |
| 送料 | F16 | {{Invoice.Freight}} |
| 請求総額 | F18 | {{Invoice.TotalDue}} |
では実行してみましょう。次のような結果が得られるはずです。
複数行項目の設定
さて、それでは商品ごとの明細行を設定しましょう。方法としては2つの選択肢があります。
- 明細用のDataTableを作って別名のデータソースとして登録する(もしくはDataSetに2つのDataTableを設定する)
- 明細を結合した複数行を返すクエリを利用する
今回は後ほど「ワークシートテンプレート」を利用したいため、後者で行います。
クエリの修正
では明細の取得をクエリに追加します。クエリを次のように修正しましょう。
use AdventureWorks;
select
GETDATE() as InvitationDay,
DATEADD(DAY, 30, GETDATE()) as DueDate,
Address.PostalCode,
ISNULL(StateProvince.Name, '') + ' ' + ISNULL(Address.City, '') + ' ' + ISNULL(Address.AddressLine1, '') + ' ' + ISNULL(Address.AddressLine2, '') as CustomerAddress,
Store.Name as StoreName,
Person.FirstName + ' ' + Person.LastName as CustomerPerson,
SalesOrderHeader.SubTotal,
SalesOrderHeader.TaxAmt,
SalesOrderHeader.Freight,
SalesOrderHeader.TotalDue,
Product.Name as ProductName,
SalesOrderDetail.UnitPrice,
SalesOrderDetail.OrderQty,
SalesOrderDetail.UnitPriceDiscount,
SalesOrderDetail.LineTotal
from
Sales.SalesOrderHeader
inner join Person.Address
on SalesOrderHeader.BillToAddressID = Address.AddressID
inner join Person.StateProvince
on Address.StateProvinceID = StateProvince.StateProvinceID
inner join Sales.Customer
on Sales.SalesOrderHeader.CustomerID = Customer.CustomerID
inner join Sales.Store
on Customer.StoreID = Store.BusinessEntityID
inner join Person.Person
on Customer.PersonID = Person.BusinessEntityID
inner join Sales.SalesOrderDetail
on SalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID
inner join Production.Product
on SalesOrderDetail.ProductID = Product.ProductID
where
SalesOrderHeader.SalesOrderID = 44533
お気づきかもしれませんが、明細以外の行は重複して取得されるため効率として必ずしも良くありません。
単票であればクエリとDataTableを分けるのがやはり良いでしょうが、今回は後ほど複数PDFの一括生成をできるだけ簡単な実装で実現したいため、取得データの重複は諦めることにしています。
Excelテンプレートの修正
クエリに引き続き、テンプレートも修正しましょう。各項目について、次のようにセルに値を設定してください。
| 項目 | セル | テンプレート |
|---|---|---|
| 製品名 | B13 | {{Invoice.ProductName}} |
| 単価 | C13 | {{Invoice.UnitPrice}} |
| 数量 | D13 | {{Invoice.OrderQty}} |
| 割引率 | E13 | {{Invoice.UnitPriceDiscount}} |
| 小計 | F13 | {{Invoice.LineTotal}} |
では実行してみましょう。
ん? ちょっとおかしいですね。先ほどのものと見比べてみましょう。
追加されたセルによって、もともとあったセルが上書きされてしまっていますね。
DioDocsでは複数行のデータ構造を作った場合、行単位で処理されているのではなく、セル単位で処理されているようです。したがって、デフォルトでは指定したセルの下に、値の数だけセルが上書きされます。
解決方法はいくつかありますが、今回は「グローバルオプション」の「挿入モード」を利用します。次のコードを追加してください。
workbook.AddDataSource("Invoice", dataTable);
//Init template global settings
workbook.Names.Add("TemplateOptions.InsertMode", "EntireRowColumn");
workbook.ProcessTemplate();
ProcessTemplateの前にグローバルセッティングを追加しています。では実行してみましょう。
これで下の行がつぶれず、スライドされるようになりましたね。とは言え、まだまだ粗があるので、細かな部分を修正していきましょう。

