備忘ですが、

誰かの役に立てばいい

【kintone】で請求書を作ってみる

f:id:bibouroq:20190422233117p:plain宛名ラベルを作ってみた時のようにHTMLで出力してみる。
ブラウザによってはレイアウトがずれるが、この方法なら比較的簡単に色々な帳票を発行できそう。

ライブラリ

  • Handsontable(v6.2.2)
  • Underscore.js(v1.9.1)

Underscore.jsのテンプレートメソッド

_.templateの引数にひな形となるHTMLを文字列で入れる。
HTMLへ渡すデータはHTML内に<%= … %>で記述する。

var data = {name: 'taro'};
var compiled = _.template("<p>hello: <%= name %><p>");
compiled(data);

HTML内にJavaScriptコードを書く時は<% … %>で囲む。
下では< li>...< /li>部分を動的に作成するよう_.eachを使っている。

var data = [{ name: 'jiro' },{ name: 'hanako' },{name: 'taro' }]
var compiled = _.template(
    "<ul>" +
    "<% _.each(list, function(item) { %>" + 
    "<li><%= item.name %></li>" +
    "<% }); %>" +
    "</ul>");
compiled({list: data});

HTML

アプリの設定→一覧のHTML部分。

<div>
 <input id="all" type="checkbox">全選択/全解除 </input>
 <input type="button" id="Abutton"></input>
</div>
<div id="sheet"></div>
<textarea style="display: none;" id="newWin">
 <html lang="ja">  
   <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>請求書</title>
    <style type="text/css">
        @page { margin: 0 }
        body { margin: 0 }
        .sheet {
          margin: 0;
          overflow: hidden;
          position: relative;
          box-sizing: border-box;
          page-break-after: always;
        }        
        /** Paper sizes **/
        body.A4           .sheet { width: 210mm; height: 296mm}
        
        /** Padding area **/
        .sheet.padding-10mm { padding: 10mm }
        .sheet.padding-15mm { padding: 15mm }
        .sheet.padding-20mm { padding: 20mm }
        .sheet.padding-25mm { padding: 25mm }
        
        /** For screen preview **/
        @media screen {
          body { background: #e0e0e0}
          .sheet {
            background: white;
            box-shadow: 0 .5mm 2mm rgba(0,0,0,.3);
            margin: 5mm;
          }
        }        
        /** Fix for Chrome issue #273306 **/
        @media print {
          body.A4 { width: 210mm }
        }        
        /** 請求書CSS省略 **/
     </style>
  </head>
  <body class="A4" id="out-def">
   <!-- テンプレート外部定義-->
   <script type="text/template" id="myTemplate">
    <% _.each(datalist,function(data){ %>
     <section class="sheet" id="originSheet">
      <div class="print">
       <div class="title">御請求書</div>
       <div class="row_1">請求日 <%= data.senddate %> </div>
       <div class="row_2">
        <div class="customer">
         <h2><%= data.customer %> 御中</h2>
        </div>
        <div class="address">
         <h3>○○○株式会社</h3>
         <ul>
          <li>〒000-0000
          <li>○○県○○市○○○0-0-0
          <li>TEL 00-0000-0000
          <li>FAX 00-0000-0000  
         </ul>
        </div>
       </div>
       <p>下記の通り、ご請求申し上げます。<p>
       <table class="total" border="1">
        <tr>
         <td style="text-align:center;">合計金額</td>
         <td style="text-align:right;"><%= data.total %>円</td>
        </tr>
       </table>
       <div>
        <table class="detail" border="1">
         <tr><th>内容</th><th>単価</th><th>数量</th><th>金額</th></tr>
         <%= data.detail %>
         <tr>
          <td rowspan="3" colspan="2"></td>
          <th> 小計 </th><td><%= data.subtotal %></td>
         </tr>
         <tr>
          <th> 消費税 </th><td><%= data.tax %></td>
         </tr>
         <tr>
          <th> 合計 </th><td><%= data.total %></td>
         </tr>
        </table>
       </div>
       <ul>
        <li>振込先</li>
        <li>名義:カ)○○○</li>
        <li>〇〇銀行 〇〇支店 普通 00000000</li>
       </ul>
        <p>※お振込み手数料は御社ご負担にてお願い致します。</p>
        </div>
      </div>
     </section>
    <% }); %>
   </script>
  </body>
 </html>
</textarea>

JavaScript

(function() {
    "use strict";
    let hot;
    let getRecords = function(callback, errorCallback) {
        let query = kintone.app.getQuery();
        kintone.api('/k/v1/records', 'GET', {app: kintone.app.getId(), query: query}, function(resp) {
            let myData = {};
            myData.records = [];
            for(let i = 0; i < resp.records.length; i ++) {
                let record = resp.records[i];
                record["リンク"] = '<a href="/k/' + kintone.app.getId() + '/show#record=' + record.$id.value + '&mode=edit">詳細</a>';
                myData.records.push(record);
            }
            callback(myData);
          },
          function(resp) {
            errorCallback(resp);
          }
        );
    };
    kintone.events.on(['app.record.index.show'], function(event) {
        if (event.viewId !== 一覧ID) return;
        let container = document.getElementById('sheet');
        let setting = [];
        setting = {
            data: [],
            colHeaders: ['選択','詳細','顧客名','請求日','合計'],
            columns: [
                {data: "check", type:'checkbox'},
                {data: "リンク", renderer: 'html', readOnly: true},
                {data: "顧客名.value", type: 'text', readOnly: true},
                {data: "請求日.value", type: 'date', dateFormat: 'YYYY-MM-DD', readOnly: true},
                {data: "合計.value", type: 'numeric', numericFormat: { pattern: '0,00', culture: 'ja-JP' }, readOnly: true},
            ],
            manualColumnResize: true,
            fixedRowsTop: 0
        };

        hot = new Handsontable(container, setting);
        getRecords(function(resp){
            hot.loadData(resp.records);
        });

        let newWin = document.getElementById("newWin").value;
        function show(datalist){
            let obj = window.open();
            obj.document.open();
            obj.document.write(newWin);
            obj.document.close();
            // underscore.js のテンプレート機能でデータを流し込む
            let template = obj.document.getElementById('myTemplate').textContent;
            let compiled = _.template(template);
        	let out_def = obj.document.getElementById('out-def');
                out_def.innerHTML = compiled({"datalist":datalist});
        	obj;
        }

        function create(record){
            let sendDate = record['請求日'].value.split('-');
	        let subTable = record.Table.value;
	        let detail = '';
	        let total = 0;
	        for(let i =0; i < subTable.length; i ++){
    	            detail += '<tr><td>' +
    	            subTable[i].value['品名'].value + '</td><td>' +
    	            parseInt(subTable[i].value['単価'].value).toLocaleString() + '</td><td>' +
    	            subTable[i].value['数量'].value + '</td><td>' +
    	            parseInt(subTable[i].value['金額'].value).toLocaleString() + '</td></tr>';
	        }
	        for(let i =0; i < 11 - subTable.length; i ++){
	            detail += '<tr><td></td><td></td><td></td><td></td></tr>';
	        }
            let list = {
                "customer": record['顧客名'].value,
		"senddate": sendDate[0] + '年' + sendDate[1] + '月' + sendDate[2] + '日',
		"total": parseInt(record['合計'].value).toLocaleString(),
		"detail": detail,
		"subtotal":parseInt(record['小計'].value).toLocaleString(),
		"tax":parseInt(record['消費税'].value).toLocaleString(),
             };
	     return list;
        }
        // 全選択/全削除チェック処理
        document.getElementById('all').onchange = function (check) {
        let col = hot.propToCol('check');
        hot.populateFromArray(0, col, [[check.target.checked]], hot.countRows() - 1, col, null, null, 'down');
        };
        //発行ボタン
	let button = document.getElementById("Abutton");
        button.value = "発行";
        button.addEventListener("click", function() {
            let records = hot.getSourceData();
            let checkedRows = [];
            for(let i = 0;i < records.length;i++){
                if(records[i].check){
                    checkedRows.push(records[i]);
                }
            }
            let datalist = [];
            for(let i = 0; i < checkedRows.length; i++){
                let data = create(checkedRows[i]);
                datalist.push(data);
            }
            show(datalist);
	});
    });
})();

別アプリにファイルを保存しておけばロゴもつけられそう。