ExcelテンプレートエンジンJETTを利用して請求書を出力しよう

# （お客様の声）
システム（入力画面）はあるのに「現場では見積書・請求書・納品書はExcelを使って手動で作成している」
この画面の端っこに「帳票出力」ボタンがあって押すと出力できたら良いなぁ

# はじめに
お客様には提案したいけど、導入が難しいと思ってしまっているエンジニア（Java）に、きっと朗報なナレッジとなります。[「JETT」](http://jett.sourceforge.net/)は、ちょっとしたルールを理解することで、ある程度の想像のつく多くの帳票をテンプレート化することができ、そして動的に出力することができます。

以下は、「請求書」をテンプレート化し、データを受け渡し変換出力したイメージとなります。

![INVOICE_IMAGE.png](https://qiita-image-store.s3.amazonaws.com/0/71427/34683386-2c1f-d3ba-4bba-9e784d72dd88.png)
サンプルコードは以下よりダウンロードできます。
[jett-example](https://github.com/k3286/jett-example.git)
実際にダウンロードし動かしながら、ご確認ください。

「[ExcelテンプレートエンジンJETTを利用してピボットテーブルでグラフを表示しよう](http://qiita.com/k3286/items/ad107055276931cf40b7)」の記事も確認ください。

# 帳票変換処理のコード
変換処理で使用するJETTのクラスは、この「ExcelTransformerクラス」だけであり、
transformメソッドを呼んでいます。
 
```java:ReportMaker.java
package com.github.k3286.report;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Workbook;

import net.sf.jett.transform.ExcelTransformer;

public class ReportMaker {

    /**
     * パラメータと、テンプレートファイル名を指定し、帳票変換を行う
     * @param params パラメータ
     * @param templateName テンプレートファイル名
     * @return 変換したWorkbook
     */
    public static Workbook toReport(Map<String, Object> params, String templateName) {
        Workbook workbook = null;
        InputStream is = null;
        try {
            is = ReportMaker.class.getResourceAsStream("/" + templateName);
            ExcelTransformer transformer = new ExcelTransformer();
            workbook = transformer.transform(is, params);
        } catch (InvalidFormatException | IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return workbook;
    }
}
```

# 動作させるテストクラス
請求書に表示するデータを設定しています、明細行も一緒に設定しています。
特別、こちらについては工夫は必要ありません。
変換処理は、データを「ReportMaker.toReport()メソッド」に渡してるだけです。

```java:InvoiceMakerTest.java
package com.github.k3286.report;

import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.poi.ss.usermodel.Workbook;
import org.junit.Test;

import com.github.k3286.dto.Invoice;
import com.github.k3286.dto.InvoiceDetail;

public class InvoiceMakerTest {

    private static BigDecimal TAX_RATE = new BigDecimal(0.08);

    /**
     * 請求書の出力デモ
     * @throws Exception
     */
    @Test
    public void invoice_test() throws Exception {

        Invoice inv = new Invoice();
        inv.setInvoiceNo("INV-00000001");
        inv.setClientPostCode("〒123-3333");
        inv.setClientAddress("東京都品川区東五反田１丁目６−３ 東京建物五反田ビル 108F");
        inv.setClientName("株式会社 松上電気");
        inv.setSalesRep("営業 太郎");
        inv.setInvoiceDate(new Date());

        // 明細行は5行にしておく
        for (int idx = 1; idx <= 5; idx++) {
            InvoiceDetail dtl = new InvoiceDetail();
            dtl.setItemName("サンプル明細ですよ " + idx);
            dtl.setUnitCost(BigDecimal.valueOf(10000));
            dtl.setQuantity(Double.valueOf(idx));
            dtl.setAmt(dtl.getUnitCost().multiply(//
                    BigDecimal.valueOf(dtl.getQuantity())));
            inv.getDetails().add(dtl);
        }
        BigDecimal total = BigDecimal.ZERO;
        for (InvoiceDetail dtl : inv.getDetails()) {
            total = total.add(dtl.getAmt());
        }
        // 立替金
        inv.setAdvancePaid(BigDecimal.valueOf(10800));
        // 税額
        inv.setTaxAmt(total.multiply(TAX_RATE));
        // 請求額（税込）
        inv.setInvoiceAmtTaxin(total.add(inv.getTaxAmt()).add(inv.getAdvancePaid()));
        // 備考
        inv.setNote("これは備考です、サンプルとして備考を記述し、"
                + "そして帳票に出力をしてみました。"
                + "折り返してくれるといいのですが、どうでしょうか");

        // 帳票変換
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("inv", inv);

        Workbook workbook = ReportMaker.toReport(map, "template_invoice.xlsx");

        // ファイル出力
        final String outPath = "output_invoice.xlsx";
        try (FileOutputStream fileOut = new FileOutputStream(outPath)) {
            workbook.write(fileOut);
            fileOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

# 帳票テンプレートで使用しているタグ
請求書のテンプレートファイルは「template_invoice.xlsx」です。
今回テンプレートファイルで使用するタグを説明します。
※ 詳細は[「The JETT Tag Library」](http://jett.sourceforge.net/tags/basics.html)

|タグ|用途|使用箇所|備考|
|:-----------|:------------|:-------------|:-----|
|\<jt:forEach\>|リストを展開し各要素を出力|明細|サンプルは、limitに10を指定している。展開の行数を指定する。帳票の明細行が固定と決まっており、印刷イメージ（改ページ）を崩したくない場合に利用する|
|$[SUM(セル)]|リストを展開したあとの数値の合計に使用する。指定したセルを基準に、展開されたセルまでを動的に数式化しSUM関数の引数に設定される|小計|$[SUM(Y26)] ⇒ SUM(Y26:Y30)|

# 応用へ

```java:ReportMaker.java（抜粋）
ReportMaker.class.getResourceAsStream("/" + templateName);
```
は、classes配下にあるテンプレートファイルを見ることになります。
この場合は、アプリケーションに組み込んだ形となるため、
テンプレートファイルの変更のたびに、再ビルドが必要となります。
運用を考慮すると、管理ユーザにとって大変です。

テンプレートファイルを外で管理する方法を考えます。
・テンプレートファイルを特定の物理パスに配置し、読み込む。
・テンプレートファイルをDBに格納し、読み込む。
・Google Driveにテンプレートファイルを配置し、読み込む。（ただしオンライン）
・etc.