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


(お客様の声)

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

はじめに

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

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


サンプルコードは以下よりダウンロードできます。
jett-example
実際にダウンロードし動かしながら、ご確認ください。

ExcelテンプレートエンジンJETTを利用してピボットテーブルでグラフを表示しよう」の記事も確認ください。

帳票変換処理のコード

変換処理で使用するJETTのクラスは、この「ExcelTransformerクラス」だけであり、
transformメソッドを呼んでいます。

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()メソッド」に渡してるだけです。

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("東京都品川区東五反田1丁目6−3 東京建物五反田ビル 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」

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

応用へ

ReportMaker.java(抜粋)
ReportMaker.class.getResourceAsStream("/" + templateName);

は、classes配下にあるテンプレートファイルを見ることになります。
この場合は、アプリケーションに組み込んだ形となるため、
テンプレートファイルの変更のたびに、再ビルドが必要となります。
運用を考慮すると、管理ユーザにとって大変です。

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