サンプルとして納品書を作成する
それでは、ここからDocurainを利用した帳票の開発例を説明します。比較対象として、Apache POI(とJava)を応用して作った場合も説明します。
作成する帳票
今回は、このようなシンプルな納品書を作成することとします。両者の住所氏名と、明細行と合計金額を動的に出力してみます。
Apache POIを使用した場合
まず、静的な部分のみを記述した以下のExcelテンプレートを用意しました。
動的にテキストを入れていく部分に、目印となる適当なテキストを入れています。
挿入するデータを表現するために、ここでは次のようなPOJOオブジェクトを使用することとしました(Lombokを使用して、コンストラクタとアクセサを自動生成しています)。
@Data
@AllArgsConstructor
public class InvoiceData {
private ContactInfo customer;
private ContactInfo shop;
private List<OrderLine> orderLineList;
private LocalDate issueDate;
}
@Data
@AllArgsConstructor
public class ContactInfo {
private String name;
private String address;
private String zip;
private String phone;
}
@Data
@AllArgsConstructor
public class OrderLine {
private String productName;
private int productPrice;
private int count;
}
このデータを使用して実際に帳票を出力するためのコードは以下です。
public class PoiInvoiceRenderer {
public static void renderAndSave(String outputPath, InvoiceData data) throws IOException {
// 帳票テンプレートExcelファイルの読み込み
Workbook wb = WorkbookFactory.create(PoiInvoiceRenderer.class.getResourceAsStream("/nouhinsho-poi.xlsx"));
Sheet sheet = wb.getSheetAt(0);
// 顧客
cellAt(sheet, 4, 2).setCellValue(data.getCustomer().getName() + " 様");
cellAt(sheet, 5, 2).setCellValue("〒" + data.getCustomer().getZip());
cellAt(sheet, 6, 2).setCellValue(data.getCustomer().getAddress());
cellAt(sheet, 7, 2).setCellValue("TEL: " + data.getCustomer().getPhone());
// 発行日
cellAt(sheet, 4, 8).setCellValue(Date.from(data.getIssueDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
// ショップ
cellAt(sheet, 6, 11).setCellValue(data.getShop().getName());
cellAt(sheet, 7, 11).setCellValue("〒" + data.getShop().getZip());
cellAt(sheet, 8, 11).setCellValue(data.getShop().getAddress());
cellAt(sheet, 9, 11).setCellValue("TEL: " + data.getShop().getPhone());
// 明細
int rowIdx = 12;
CellStyle currencyStyle = wb.createCellStyle();
DataFormat format = wb.createDataFormat();
currencyStyle.setDataFormat(format.getFormat("¥#,###"));
for(OrderLine line : data.getOrderLineList()) {
sheet.createRow(rowIdx);
Row row = sheet.getRow(rowIdx);
row.createCell(2, CellType.STRING).setCellValue(line.getProductName());
row.createCell(8, CellType.NUMERIC).setCellValue(line.getProductPrice());
row.createCell(9, CellType.NUMERIC).setCellValue(line.getCount());
row.createCell(10, CellType.FORMULA).setCellFormula("I" + (rowIdx + 1) + "*J" + (rowIdx + 1));
// style
cellAt(sheet, rowIdx, 8).setCellStyle(currencyStyle);
cellAt(sheet, rowIdx, 10).setCellStyle(currencyStyle);
sheet.addMergedRegion(new CellRangeAddress(rowIdx, rowIdx, 10, 11));
rowIdx++;
}
// 合計
sheet.createRow(rowIdx++);
Row row = sheet.createRow(rowIdx);
row.createCell(9, CellType.STRING).setCellValue("合計");
row.createCell(10, CellType.FORMULA).setCellFormula("SUM(K13:K" + (13 + data.getOrderLineList().size() - 1) + ")");
sheet.addMergedRegion(new CellRangeAddress(rowIdx, rowIdx, 10, 11));
CellStyle currencyAndBorder = wb.createCellStyle();
currencyAndBorder.setBorderBottom(BorderStyle.MEDIUM);
currencyAndBorder.setDataFormat(format.getFormat("¥#,###"));
cellAt(sheet, rowIdx, 9).setCellStyle(currencyAndBorder);
cellAt(sheet, rowIdx, 10).setCellStyle(currencyAndBorder);
row.createCell(11, CellType.STRING);
cellAt(sheet, rowIdx, 11).setCellStyle(currencyAndBorder);
// 例外スロー時のリソースの開放処理などは簡単のために省いています
wb.write(new FileOutputStream(new File(outputPath)));
wb.close();
}
private static Cell cellAt(Sheet sheet, int row, int col) {
return sheet.getRow(row).getCell(col);
}
}
public class Main {
public static void main(String[] args) throws IOException {
LinkedList<OrderLine> orderLines = new LinkedList<>();
orderLines.add(new OrderLine("128GB Micro SDカード", 3800, 2));
orderLines.add(new OrderLine("5インチ ガラス液晶フィルム", 1200, 1));
orderLines.add(new OrderLine("ノンフロン ダストブロワー 2パック", 800, 1));
InvoiceData invoice = new InvoiceData(
new ContactInfo(
"田中 太郎",
"東京都港区X-Y-Z 1-2-3",
"123-46587",
"091-2568-4514"
),
new ContactInfo(
"CYBER SHOP ABC",
"山形県酒田市A-B-C 1-2-3",
"598-123654",
"098-15468-41114"
),
orderLines,
LocalDate.of(2018, 11, 20)
);
PoiInvoiceRenderer.renderAndSave("./output.xlsx", invoice);
}
}
単純な内容であっても、値を一つずつ挿入していく必要があるために行数が増え、さらにそのほとんどがセルの行番号・列番号を指定するような可読性の低いコードになってしまっていることが分かると思います。例えば、先頭にもう1行何かを追加したいという要求があったとき、どう修正すればいいのでしょうか……あまり考えたくはないですね。
Docurainを使用した場合
Docurainを使用した場合は、帳票出力に必要なことはすべてExcelファイルに記述でき、コードを書く必要はありません。
まずは、差し込むデータの構造を定義しましょう。Docurainでは任意の構造のJSONデータを使用できますが、ここでは以下のような構造のデータを使うことにします。
{
"customer": {
"name": "田中 太郎",
"address": "東京都港区X-Y-Z 1-2-3",
"zip": "123-46587",
"phone": "091-2568-4514"
},
"shop": {
"name": "CYBER SHOP ABC",
"address": "山形県酒田市A-B-C 1-2-3",
"zip": "598-123654",
"phone": "098-15468-41114"
},
"orderLineList": [
{
"productName": "128GB Micro SDカード",
"productPrice": 3800,
"count": 2
},
{
"productName": "5インチ ガラス液晶フィルム",
"productPrice": 1200,
"count": 1
},
{
"productName": "ノンフロン ダストブロワー 2パック",
"productPrice": 800,
"count": 1
}
],
"issueDate": "2018-11-20"
}
このデータを出力するためのExcel帳票テンプレートは以下のようになります。
それぞれ、見慣れない記法がありますが、ほとんどは直観的に理解できるのではないでしょうか。簡単に説明すると、以下の通りになります。
-
$から始まるプレースホルダで値を挿入していく -
$ENTITYが与えられたJSONオブジェクトのルートとなる - A列に特殊なマクロを記述していく
-
#set()は新しい変数の定義 -
#foreachが配列要素の繰り返し処理 -
#foreachなどの構文が書かれた行は削除される -
#ROW()はそのセルの行番号に置換されるマクロ
このようなExcelテンプレートを作成し、JSONデータを用意してDocurainサービスのREST APIにリクエストを送信すれば、PDFやExcelといった形式のバイナリデータが返却されます。
今回作成した帳票は登録不要ですぐにお試しすることもできます。こちらのExcelテンプレートとJSONファイルをダウンロードし、以下のページにアクセスして「テンプレート」にExcelファイルを、「JSONデータ」にJSONファイルを指定して実行してみてください。指定した形式(PDFもしくはExcel)で帳票がダウンロードできるはずです。
そのほかの帳票
そのほか、Docurainから出力した帳票の例を紹介します。
自動車検査証(車検証)の例です。こういったたくさんの罫線で構成される帳票は多いですが、Excelであればそこまで苦もなく作ることができます。この帳票ならば、1~2時間もあれば十分に実装可能です。
バーコードやQRコードを含む例です。バーコードやQRコードは#QRCODE()、#EAN8()といったマクロをテキストとして矩形のシェイプに記述する方法で出力します。バーコード/QRコード出力も通常のテキスト出力と同程度の作業で出力することができます。
こちらの時刻表は弊社開発ブログ記事「私が本物のExcel時刻表ってやつを見せてあげますよ」にて詳しい作り方を解説しています。
時刻表をExcelで作るのは一見大変そうですが、行と列のグリッドを基礎にして編集していけるので通常のDTPソフトウェアで記述するよりむしろ簡単かもしれないと個人的には考えています。
ガントチャートの例です。条件付き書式をうまく利用することで、土日の背景色を設定したり、作業期間のバーを着色したりすることもできます。ガントチャート部分の描画にはDocurain独自の機能は使っていません。Excelの表現方法としてはややトリッキーな部類になるかと思いますが、しかしこの程度であれば開発者以外でも容易にメンテナンス可能なレベルだと思われます。
まとめ
長い記事でしたが、お読みいただきありがとうございました。
長年、さまざまなプロジェクトで帳票開発に携わってきた私ですが、Docurainは一つの理想形であると個人的には思っています。
弊社の開発メンバーの多くがDocurainのヘビーユーザーでもあります。我々が現場で帳票開発を行い、真に必要とされる機能を日々追加し、より便利なサービスへと成長を遂げています。もちろん、ユーザーの方々から受け取ったフィードバックを新しい機能として実装することも日々行っています。
Docurainはすぐに使い始めることができ、支払い方法を登録しない限りは何らかの請求が発生することはありません。もし、現在帳票開発に苦労している、もしくは、これから帳票開発を行う予定があるという方は、Docurainもお試しいただき、選択肢の一つに加えてもらえるとうれしく思います。

