HTML請求書でインボイス方式に対応した消費税の端数処理をする

以前つくったHTML請求書は、コードがぐちゃぐちゃになって自分でもよく分からない状態だったので、テーブル内の計算処理部分を作り直しました。

javascriptでは、どうしても小数の計算がおかしくなってしまいます。消費税の端数処理のために、以前作成した請求書ではdecimal.jsを使いましたが、今回はbignumber.jsを使います。BigNumberでの切捨て処理は、色々な方法があり、その方法によって処理後の型が違うため、BigNumberの方が難しい気がします。数値を3桁区切りにするのは楽になり、コードが読みやすくなりました。

完成イメージ

機能

付けた機能は、以前のHTML請求書と同じです。

  1. 入力をリアルタイムで反映
  2. 消費税計算
  3. 金額を3桁区切り
  4. テーブル行の追加と削除
  5. テーブル行の入れ替え

インボイスの記載事項

出典:国税庁 インボイス制度の概要
  1. インボイス発行事業者の名称及び登録番号
  2. 課税売上を行った取引年月日
  3. 取引の内容(軽減税率適用の場合は、軽減税率の対象品目である旨)
  4. 税率ごとに区分して合計した対価の額(税抜き又は税込み)及び適用税率
  5. 消費税額等(端数処理は一請求書当たり、税率ごとに1回ずつ)
  6. 書類の交付を受ける事業者の氏名又は名称

今回作成したHTML請求書では、軽減税率の対象品目である旨は、品目ごとに税率を記載することで対応します。

消費税の端数処理

この消費税の端数処理が、インボイス方式に対応するのに最も難しい所だと思います。

消費税の端数処理は、その都度、品目ごとに(今回ならテーブル1行ごとに)1円未満の端数処理することは認められていません。
対象額の総額に対して、消費税率を掛けて端数処理をしなければいけません。
つまり、請求書1通に対して、税率10%と8%、それぞれ1回だけしか1円未満の端数処理はできません。

また、1円未満の端数処理の方法は、切上げ、切捨て、四捨五入、どれを採用してもかまいません。

サンプル

動作確認は、Chromeのみ。

【単価】【数量】欄に、【半角数字】以外を入力すると、エラーになり計算できません。(小数は可)

端数処理は、【切捨て】です。

サンプルは、こちら

ソースコード

  • jquery-3.6.0.min.js
  • jquery-ui.min.js
  • bignumber.js
  • Semantic UI (CSS)

を使用しています。

【単価】【数量】【税率】が分かれば、すべての計算ができるので、input属性は最小限にしています。

【単価】【数量】欄に、【半角数字】以外を入力すると、エラーになり計算できません。(小数は可)

端数処理は、【切捨て】です。

免責事項

計算の正確性、安全性、適時性等を保証するものではありません。プログラム及び計算結果に不具合があった場合であっても、本サイトは一切責任を負いません。本サイトを利用すること、または、本サイトを利用できなかったことに関して発生した損害について、本サイトは一切責任を負いません。本サイトを利用することにより、免責事項の内容について同意したものとみなします。

HTML

相変わらず、HTMLとCSSはわからないので、適当です。

テーブルに行を追加する部分は、編集しやすいようにHTMLファイル内に記述しています。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./semantic.min.css">
    <style>
        .container {
            font-family: 'BIZ UDPMincho';
            font-weight: bold;
        }

        td[name="kingaku"] {
            text-align: right;
        }

        button:hover {
            cursor: pointer;
        }

        tbody tr:hover {
            cursor: move;
        }
    </style>
</head>
<body>
    <div class="ui container">
        <div style="margin: 40px;">
            <b>
                <span style="font-size: 2em;">
                    | 合計: &yen;
                    <span name="total_top"></span>.- 
                </span>
                <span style="font-size: 1.5em;">(税込)</span>
                <span style="font-size: 2em;">|</span>
            </b>
            <span style="font-size: 1.2em;">
                |内消費税: 
                <span name="total_tax"></span>
                 .- |
            </span>
        </div>

        <div class="ui form">
            <div class="field">
                <table id="myTable" class="ui orange celled striped table">
                    <thead style="text-align: center;">
                        <tr>
                            <th style="width: 40%;">品名</th>
                            <th style="width: 15%;">単価(税抜)</th>
                            <th style="width: 10%;">数量</th>
                            <th style="width: 20%;">金額</th>
                            <th style="width: 10%;">税率</th>
                            <th style="width: 5%;"></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td style="width: 40%;">
                                <input type="text" name="name">
                            </td>
                            <td style="width: 15%;">
                                <input type="text" name="tanka">
                            </td>
                            <td style="width: 10%;">
                                <input type="text" name="suuryou" value="1">
                            </td>
                            <td style="width: 20%; text-align: right;">
                                <span name="kingaku" ></span>
                            </td>
                            <td style="width: 10%;">
                                <select name="select_tax">
                                    <option value="0.1">10%</option>
                                    <option value="0.08">8%</option>
                                    <option value="0">非課税</option>
                                    <option value="0">免税</option>
                                    <option value="0">対象外</option>
                                </select></td>
                            <td style="width: 5%;">
                                <button class="remove ui mini inverted red button">X</button>
                            </td>
                        </tr>
                        <tr>
                            <td style="width: 40%;">
                                <input type="text" name="name">
                            </td>
                            <td style="width: 15%;">
                                <input type="text" name="tanka">
                            </td>
                            <td style="width: 10%;">
                                <input type="text" name="suuryou" value="1">
                            </td>
                            <td style="width: 20%; text-align: right;">
                                <span name="kingaku"></span>
                            </td>
                            <td style="width: 10%;">
                                <select name="select_tax">
                                    <option value="0.1">10%</option>
                                    <option value="0.08">8%</option>
                                    <option value="0">非課税</option>
                                    <option value="0">免税</option>
                                    <option value="0">対象外</option>
                                </select></td>
                            <td style="width: 5%;">
                                <button class="remove ui mini inverted red button">X</button>
                            </td>
                        </tr>
                    </tbody>
                    <tfoot style="text-align: center;">
                        <tr style="background-color: lightgray;">
                            <td></td>
                            <td colspan="2">小計(税抜)</td>
                            <td style="text-align: right;"><b><span name="sum_kingaku"></span></b></td>
                            <td colspan="2"></td>
                        </tr>
                        <tr>
                            <td style="text-align: right;">【10%対象:<span name="sum_kingaku_tax10"></span>】</td>
                            <td colspan="2">消費税(10%)</td>
                            <td style="text-align: right;"><b><span name="sum_tax10"></span></b></td>
                            <td colspan="2"></td>
                        </tr>
                        <tr>
                            <td style="text-align: right;">【8%対象:<span name="sum_kingaku_tax8"></span>】</td>
                            <td colspan="2">消費税(8%)</td>
                            <td style="text-align: right;"><b><span name="sum_tax8"></span></b></td>
                            <td colspan="2"></td>
                        </tr>
                        <tr style="background-color: lightpink;">
                            <td></td>
                            <td colspan="2"><b>合計(税込)</b></td>
                            <td style="text-align: right;"><b><span name="total"></span></b></td>
                            <td colspan="2"></td>
                        </tr>
                    </tfoot>
                </table>
                <div style="text-align: center; margin: 50px;">
                    <button class="ui button green" id="addRow">+ 行 追加</button>
                </div>
            </div>
        </div>
    </div>
    <script src="./jquery-3.6.0.min.js"></script>
    <script src="./jquery-ui.min.js"></script>
    <script src="./bignumber.js"></script>
    <script src="./keisan-bn.js"></script>
    <script>
        $(function(){
            $('tbody').sortable();

            $('#addRow').click(function(){
                const html =`
                    <tr>
                        <td style="width: 40%;">
                            <input type="text" name="name">
                        </td>
                        <td style="width: 15%;">
                            <input type="text" name="tanka">
                        </td>
                        <td style="width: 10%;">
                            <input type="text" name="suuryou" value="1">
                        </td>
                        <td style="width: 20%; text-align: right;">
                            <span name="kingaku"></span>
                        </td>
                        <td style="width: 10%;">
                            <select name="select_tax">
                                <option value="0.1">10%</option>
                                <option value="0.08">8%</option>
                                <option value="0">非課税</option>
                                <option value="0">免税</option>
                                <option value="0">対象外</option>
                            </select></td>
                        <td style="width: 5%;">
                            <button class="remove ui mini inverted red button">X</button>
                        </td>
                    </tr>`; 
                $('tbody').append(html);
            });
        });
    </script>
</body>
</html>

javascript

bignumber.jsのダウンロードは、こちら

bignumber.jsの公式APIは、こちら

function keisan() {
    let tanka = [];         // 単価リスト
    let suuryou = [];       // 数量リスト
    let kingaku = [];       // 金額リスト
    let select_tax = [];    // 税率リスト
    let total = new BigNumber(0);               // 請求額(税込)
    let total_tax = new BigNumber(0);           // 消費税総額( 10% + 8% )
    let sum_kingaku = new BigNumber(0);         // 小計(税抜)
    let sum_kingaku_tax10 = new BigNumber(0);   // 金額合計(10%対象額)
    let sum_kingaku_tax8 = new BigNumber(0);    // 金額合計(8%対象額)
    let sum_tax10 = new BigNumber(0);           // 消費税合計(10%)
    let sum_tax8 = new BigNumber(0);            // 消費税合計(8%)

    // jQuery
    // 単価をリストに追加
    $('input[name="tanka"]').each(function() {
        tanka.push($(this).val());
    });

    // 数量をリストに追加
    $('input[name="suuryou"]').each(function() {
        suuryou.push($(this).val());
    });
    
    // 税率をリストに追加
    $('select[name="select_tax"]').each(function() {
        select_tax.push($(this).val());
    });

    // 単価リストの要素数が0でない場合(== tbodyに行がある場合)
    if (tanka.length != 0) {
        // テーブルの行数だけ計算
        for (let i = 0; i < tanka.length; i++) {
            // 単価と数量がどちらも空でない場合
            if ((tanka[i] != "") && (suuryou[i] != "")) {
                let BN_tanka = new BigNumber(tanka[i]);         // 単価をBigNumberに
                let BN_suuryou = new BigNumber(suuryou[i]);     // 数量をBigNumberに
                let BN_kingaku = new BigNumber(0);              // 金額を計算するためにBigNumberに
                // テーブル行の金額を計算
                // 1円未満を切捨て
                BN_kingaku = BN_tanka.times(BN_suuryou).integerValue(BigNumber.ROUND_FLOOR);
                // 金額リストに追加
                kingaku.push(BN_kingaku);
                // テーブル行の金額欄に代入
                // toFormat()は、3桁区切り(String化される)
                document.getElementsByName("kingaku")[i].innerHTML = kingaku[i].toFormat();
                // 小計を計算
                sum_kingaku = sum_kingaku.plus(kingaku[i]);
                // テーブルの小計欄に代入
                document.getElementsByName("sum_kingaku")[0].innerHTML = sum_kingaku.toFormat();

                // 税率が10%の場合
                if (select_tax[i] == 0.1) {
                    // 10%対象額に金額を追加
                    sum_kingaku_tax10 = sum_kingaku_tax10.plus(kingaku[i]);
                    // 8%対象額に0を追加
                    sum_kingaku_tax8 = sum_kingaku_tax8.plus(0);

                    // 消費税合計(10%)に消費税額を追加
                    // この段階で消費税の端数処理をしてはダメ
                    sum_tax10 = sum_tax10.plus(kingaku[i].times(select_tax[i]));
                    // 消費税合計(8%)に0を追加
                    sum_tax8 = sum_tax8.plus(kingaku[i].times(0));

                // 税率が8%の場合
                } else if (select_tax[i] == 0.08) {
                    sum_kingaku_tax10 = sum_kingaku_tax10.plus(0);
                    sum_kingaku_tax8 = sum_kingaku_tax8.plus(kingaku[i]);

                    sum_tax10 = sum_tax10.plus(kingaku[i].times(0));
                    sum_tax8 = sum_tax8.plus(kingaku[i].times(select_tax[i]));

                // 税率が0の場合
                } else {
                    sum_kingaku_tax10 = sum_kingaku_tax10.plus(0);
                    sum_kingaku_tax8 = sum_kingaku_tax8.plus(0);

                    sum_tax10 = sum_tax10.plus(kingaku[i].times(0));
                    sum_tax8 = sum_tax8.plus(kingaku[i].times(0));

                }
                // 10%対象額欄に代入
                document.getElementsByName("sum_kingaku_tax10")[0].innerHTML = sum_kingaku_tax10.toFormat();
                // 8%対象額欄に代入
                document.getElementsByName("sum_kingaku_tax8")[0].innerHTML = sum_kingaku_tax8.toFormat();
                // 消費税合計(10%)に消費税額(10%)を代入(1円未満切捨て)
                document.getElementsByName("sum_tax10")[0].innerHTML = sum_tax10.integerValue(BigNumber.ROUND_FLOOR).toFormat();
                // 消費税合計(8%)に消費税額(8%)を代入(1円未満切捨て)
                document.getElementsByName("sum_tax8")[0].innerHTML = sum_tax8.integerValue(BigNumber.ROUND_FLOOR).toFormat();            
                // 消費税総額( 10% + 8% )に10%と8%の消費税合計額を代入
                // 10%と8%をそれぞれ端数処理(1円未満切捨て)
                total_tax = (sum_tax10.integerValue(BigNumber.ROUND_FLOOR)).plus(sum_tax8.integerValue(BigNumber.ROUND_FLOOR));
                // 消費税総額( 10% + 8% )欄に代入
                document.getElementsByName("total_tax")[0].innerHTML = total_tax.toFormat();

                // 請求額(税込)を計算して、代入
                total = sum_kingaku.plus(total_tax);
                document.getElementsByName("total")[0].innerHTML = total.toFormat();
                document.getElementsByName("total_top")[0].innerHTML = total.toFormat();

            // 単価 or(and) 数量が空の場合
            } else {
                // 金額リストに空を代入
                kingaku[i] = "";
                // テーブルの金額欄を空に
                document.getElementsByName("kingaku")[i].innerHTML = "";

                // 小計に0を追加
                sum_kingaku = sum_kingaku.plus(0);
                document.getElementsByName("sum_kingaku")[0].innerHTML = sum_kingaku.toFormat();

                // 対象額(10%と8%)それぞれに0を追加
                sum_kingaku_tax10 = sum_kingaku_tax10.plus(0);
                document.getElementsByName("sum_kingaku_tax10")[0].innerHTML = sum_kingaku_tax10.toFormat();
                sum_kingaku_tax8 = sum_kingaku_tax8.plus(0);
                document.getElementsByName("sum_kingaku_tax8")[0].innerHTML = sum_kingaku_tax8.toFormat();

                // 消費税合計(10%と8%)それぞれに0を追加
                // 消費税端数処理
                sum_tax10 = sum_tax10.plus(0);
                document.getElementsByName("sum_tax10")[0].innerHTML = sum_tax10.integerValue(BigNumber.ROUND_FLOOR).toFormat();
                sum_tax8 = sum_tax8.plus(0);
                document.getElementsByName("sum_tax8")[0].innerHTML = sum_tax8.integerValue(BigNumber.ROUND_FLOOR).toFormat();          

                // 消費税総額( 10% + 8% )を計算して代入
                // 消費税端数処理
                total_tax = (sum_tax10.integerValue(BigNumber.ROUND_FLOOR)).plus(sum_tax8.integerValue(BigNumber.ROUND_FLOOR));
                document.getElementsByName("total_tax")[0].innerHTML = total_tax.toFormat();

                // 請求額(税込)を計算して、代入
                total = sum_kingaku.plus(total_tax);
                document.getElementsByName("total")[0].innerHTML = total.toFormat();
                document.getElementsByName("total_top")[0].innerHTML = total.toFormat();

            }
        }
    // テーブル行をすべて削除した場合
    // すべての欄を空に
    } else {
        document.getElementsByName("sum_kingaku")[0].innerHTML = "";
        document.getElementsByName("sum_kingaku_tax10")[0].innerHTML = "";
        document.getElementsByName("sum_kingaku_tax8")[0].innerHTML = "";
        document.getElementsByName("sum_tax10")[0].innerHTML = "";
        document.getElementsByName("sum_tax8")[0].innerHTML = "";            
        document.getElementsByName("total_tax")[0].innerHTML = "";
        document.getElementsByName("total")[0].innerHTML = "";
        document.getElementsByName("total_top")[0].innerHTML = "";
    }
}

// jQuery
$(function(){
    // テーブル行削除
    $(document).on('click', '.remove', function() {
        $(this).parents('tr').remove();

        keisan();
    });

    // リアルタイム計算
    $('#myTable').on('input', function() {
        keisan();
    });
})