Ledger帳簿とクレジットカード利用明細との照合確認

帳簿の照合確認

帳簿 へのデータ入力は、どんなに細心の注意を払って作業してもタイプミスをゼロにすることはできません。

ただし 日付 は月をまたいだりしない限り多少のミスは許容できます1

また 支払先 については、そもそもショップの正式名称、通り名などがクレジットカードの明細書上の略称と異なっていることがあります。支払先は(特に日本語では)「表記ゆれ」エラーが起こりがちな項目ですが、Ledger-cliでは対応策が用意されています2ので、これもさほど心配することはありません。

問題なのは 金額 。これは1円でも間違えると、あとあと面倒なことが起こります3。帳簿と利用明細書の金額をきちんと照合・確認することは経理・帳簿付けの基本中の基本ですが、「言うは易し行うは難し」です。

照合確認の方法

これまでは、 PCの画面上に明細書のPDFを表示しながら、その隣にLedgerファイルに基づいて算出した当該クレカの日々の支出額を表示し、その両者を 見比べる という方法をとっていました。

明細書はPDFですが印刷物イメージ。一方、コンピュータ上のスプレッドシートやリスト表示されたデータは明細書の印刷物イメージとはさまざまな点で別物です。知覚的に大きく異なるこの両者を照合するのは心理的にはかなり高負荷な作業です。

とりわけある一定の年齢以上になると、視力だけでなく眼の奥にある大事な臓器(脳)もこうした作業のチカラが落ちてきます。ということで、毎月毎月、明細書とLedgerの帳簿の計算結果がなかなか一致しません。

これまでそれなりに工夫はしてきました。たとえば印刷物イメージの明細書とをできるだけ似た形にして提示するとか、注視点の移動距離と頻度を下げるとか・・・。にもかかわらず、作業の苦痛はそろそろぼちぼち限界 😓


しばらく試行錯誤していましたが、最近になってようやく自分なりの方法が確立しました。それを紹介します。

明細書データのCSVデータをダウンロードして取得し
そこから日付と金額だけを抽出して
照合しやすいフォーマットのCSVファイルにする。
もう一方のLedgerデータもそれと同じフォーマットにする。

そうしておいて必殺colordiffを使う

まずは操作法の紹介から

クレカのサイトから利用明細書(CSV)をダウンロードします。

会社によって明細書のCSVファイルの形式はさまざまです。ここでは、私が使っているカード= Costco Global Card (Orico MasterCard) を例にとってお話します。

ターミナで次のコマンドを入力します4

$ date-amount抽出-f-orico明細.sh HOGExxxxxxxxxxxxx.csv

HOGE で始まる文字列はOricoのCSV形式の明細書です。

これを実行すると次のような日付と金額のデータだけの単純なフォーマットのファイルが生成されます。

20220227 600
20220227 930
20220227 950
20220301 6960
20220302 1110
  :

これが出来たら、次に diff-orico.sh スクリプトを起動します。

$ diff-orico.sh

このスクリプトは

  1. 明細書と同期間のクレカ支出データをLedgerを使って作ります
  2. それと明細書データとを照合します

もし2つのファイルに不一致があれば、 colordiff が次のような出力を表示します。

図1:  colordiffによるクレカ明細書(左)とLedger出力(右)

図1: colordiffによるクレカ明細書(左)とLedger出力(右)

1の読み方

右の列は、自分が入力した帳簿に基づいてLedgerで算出した日付と利用金額のデータです。左の列はダウンロードした明細書の日付と金額のデータです。こちらが常に「正解」になります。

1は次のようなことを示しています:

  • 3月3日 の右列=1,510円は 1,812円に修正しなければならない( 青い|
  • 3月5日 には2,300円の支出があったのに記帳漏れになっているので要追加(赤い<
  • 3月9日 の右列=1,140円は、このクレカによる支出ではないので削除する( 緑の>

左右不一致の箇所は、EmacsでLedgerファイルを開いて修正して行きます。修正作業後に diff-orico.sh を実行して次のような(味も素っ気もない)メッセージがでたら、めでたく照合・確認作業は終了です。

** Meisai と Ledger は完全に一致

照合・確認の所要時間は、明細書の行数が50行〜100行程度なら(エラー件数によりますが)10分程度でしょうか。たいへんな効率化です ✌️

スクリプトの概要紹介

2つのスクリプトとも短く簡単なものです。流し読みでお分かりいただけると思います。

パソコンの環境

パソコンはLenovo ThinkPad X230(MEM=8G, SSD=180G), OSはUbuntu20.04です。おもなソフトのバージョンは次のとおりです:

  • GNU bash, バージョン 5.0.17(1)-release (x86_64-pc-linux-gnu)
  • Emacs26.3
  • Ledger 3.1.3-20190331

ダウンロードした明細書CSVファイルの項目構成

各行は次のような構成になっています。

2022年3月2日,ダイエートウカイチバ,*,本人,2022年4月,アド,1,1,"\1,110",,,"\0","\1,110","\0"

どのクレカ会社でも、基本的にはほぼ同じような構成ですが、カタカナが半角だったり全角だったり、全レコードがダブルクォーテーションで囲まれていたりいなかったりとか、項目の順番が違っていたりします5

CSV明細書から日付と金額だけを抽出するスクリプト

date-amount抽出-f-orico明細.sh

(ソースコードは date-amount抽出-f-orico明細.sh コード節参照)

ダウンロードしたCSV明細書から、 日付と金額だけ を抽出してファイル化します。

  • 引数はOricoのCSVファイル名
  • diff-orico.sh (次節)で使えるフォーマットにして./tmp-Meisai-out.csv を生成・保存
  • ファイルの中身はこんな感じ:
2022/03/30,18000
2022/03/30,498
2022/03/30,650
2022/03/30,1681
2022/03/30,7643
2022/03/30,1279

このファイルでは、

  • 日付のフォーマットは %Y/%m/%d です
  • またCSVの区切データとしてカンマを使っています
  • 同一日の利用金額を昇順にソートすることもやっていません。ダウンロードしたCSVファイルのデータ順のままです6

なお、CSVファイルの数値データの中に3桁ごとのカンマが含まれていると、CSV区切記号としてのカンマと区別ができなくなります。それを回避するには 組込変数 FPAT (Fields Pattern)を使います。

これを使えば、次のように数値データにカンマが含まれていても、awkでカラム位置を指定すると、ちゃんと抽出できます。

awk -v FPAT='([^,]+)|(\"[^\"]+\")' '{print $9}' ./tmp-orico.csv


diff-orico.sh で2つのファイルを照合する

(コードは diff-orico.sh コード 節参照)

このスクリプトは2つの仕事をします。

  1. 一つはLedger-cliで、Oricoカードの明細書期間と同じ期間のregisterレポートを取得し、それを ./tmp-Ledger-out.csv ファイルにして保存すること
  2. もう一つは、先の date-amount抽出-f-orico明細.sh で生成した ./tmp-Meisai-out.csv と、 ./-Ledger-out.csv ファイルとを照合すること。

1. Ledgerパート

基本的なLedgerのクエリは ledger reg ^liab and orico になります。

これだけで済めばよいのですが、ちょっと面倒なことがあります。わたしはOricoカードの ETCカード をクルマに積んでいます。そのETCの締め日がOricoカードと同じではないのです。

Oricoは「毎月末締め」ですが、Orico ETCカードは「毎月15日締め」。たとえば支払日が4月27日の明細書では表1のようになります。

表 1: 支払日が4月27日の「利用期間」
カード 利用期間
Orico MasterCard 3月1日〜3月31日
Orico ETC 2月15日〜3月15日
  • Ledgerによる金額計算

    上に述べた理由で、金額計算パートを2つにしています。

    # oricoのメイン金額計算
    ledger reg  ^liab and orico and not \(@口座 or @nexco or @首都高\)\
           -b ${b_date} -e ${e_date}\
           --date-format="%Y%m%d"\
           --format "%d %(abs(quantity(scrub(display_amount))))\n"\
           --output ./tmp-main-orico.csv
    
    # 通行料金(首都高,NEXCO)の計算
    ledger reg  ^liab and orico and \(@nexco or @首都高\)\
           -b ${nexco_b_date} -e ${nexco_e_date}\
           --date-format="%Y%m%d"\
           --format "%d %(abs(quantity(scrub(display_amount))))\n"\
           --output ./tmp-toll.csv
    
  • 補足説明 1

    両クエリ式の3行目 --date-format="%Y%m%d" で、日付フォーマットを 20220301 風にします。

    2行目 --format "%d %(abs(quantity(scrub(display_amount))))\n" で、日付(%d)の後ろに金額(display_amount)を絶対値にして(マイナス符号を取って)表示しています。

  • 補足説明 2

    「メイン金額」のクエリ式の中の not \(@口座 or @nexco or @首都高\) ですが、 @口座 を除外しないと、次のような Oricoカードの「口座振替」(銀行口座からの引き落とし)トランザクションが含まれてしまいます。下のクエリを見てください。

    $ led p ^liab and orico and @口座 --tail 1
    2022/03/28 口座振替 Orico
        ; Invoice: invoices/orico/20220315-orico.pdf
        Liabilities:OricoCard                 49,887 JPY
        Assets:Bank
    

    なお、 @口座 というのは、支払先(payee)に「口座」という文字が含まれていることを意味します。

2. 照合パート

ここでは colordiff を用いて、 ./tmp-Meisai-out.csv と ./tmp-Ledger-out.csv の2つのファイルを比較します。 colordiffdiff のカラー版です。

実質的な中身は次の1行だけです。

colordiff -y --width=50 ./tmp-Meisai-out.csv ./tmp-Ledger-out.csv

オプションの意味は次のとおりです。

-y
--side-by-side ,つまり比較する2ファイルを左右に並べて(side-by-side)に表示します
--width=50
表示するカラム幅を50文字分にします

評価と今後の課題

評価

この簡単な工夫で、明細書と帳簿の照合・確認作業は従来よりもはるかに短時間で済むようになりました。

うまく行ったおもな要因は

  • 対象とするレコードを日付と金額だけに限定したこと
  • Ledger-cliの出力フォーマット機能の豊富さ
  • 組込変数FPATのおかげでCSVファイルをシェルスクリプトで扱いやすくなったこと
  • diffに色をつけたcolordiffの出力の見やすさ

今後の課題

細かい点について

  • 現行では2つの小さなスクリプトを起動していますが、これは一つにまとめる予定です
  • クレカごとに一つずつ専用の照合確認スクリプトを持つのが良策だと思います
  • 照合が短時間で終わるのは良いのですが、終わったときのご褒美メッセージがあまりにも素っ気なくて、何か 仕事をやった感 がありません。もう少し派手に、がんばったね!的なメッセージに変更したいです 😉

ちょっと大きな問題

  • 日付と金額だけではなく、 支払先 も照合の対象にする方がシステムとしては完成度が高いと思います
  • けれども残念ながら 日本語 で支払先を照合の対象にするのは、みずから底なし沼に足を踏み込むようなもの 😓
  • なにしろ日本語には、確立した 正書法 というものがありません。漢字、ひらがな、カタカナ、ローマ字と文字種がたくさんある上に、それぞれの文字種においても確定した表記ルールがありません。
  • いわば無政府状態、カオス、悪魔のような状態ですので、いずれの日にか日本国またはアカデミーが正書法を確立してくれるまでは手を出さない(出せない)!と思っています 😎

参考資料(ソースコード)

上で紹介した2つのシェルスクリプトのソースコード。

date-amount抽出-f-orico明細.sh コード

#!/bin/bash
set -eu
#
#   FPAT を用いたOrico invoiceのCSV変換, invoiceのPretty Print and save.
#   短縮版:date, amount のみを抽出する
#
orico_dir="(your-path-to-invoice-dir)/orico"
f_date=$(date "+%Y%m15")
#
case $# in
    0)
	read -r -p "CSVファイル名 : " keyin ;;
    1)
	keyin=$1 ;;
    * )
	echo "** error.  Do it again."
	exit 1 ;;
esac
#
    if [[ ! -e ${keyin} ]]; then
	echo "** ${keyin} not found. Do it again."
	exit 1
    fi
#
csv_fname=$keyin

#  NKFでsjis--utf変換
nkf -w --overwrite ${csv_fname}
#  ヘッダー(10行)と末尾の空行を削除する
tail -n +11 ${csv_fname} | sed -r "/^\r?$/d" > ./tmp-orico.csv

## カラム9(金額)を抽出し、カンマと円記号、ダブルクオートを削除
awk -v FPAT='([^,]+)|(\"[^\"]+\")' '{print $9}' ./tmp-orico.csv > ./tmp-9.csv
sed -i -e 's/,//g' -e 's/\\//g' -e 's/"//g' ./tmp-9.csv

## 日付(カラム1)を抽出
awk 'BEGIN {FS=",";OFS=","} {print $1}' ./tmp-orico.csv > ./tmp-1.csv
#  日付中の年,月,日を削除したデータを ./tmp-1.csv に書き戻す
sed -i -e 's/\([0-9]\+\)年\ \?\([0-9]\+\)月\ \?\([0-9]\+\)日/\1\/\2\/\3/g' ./tmp-1.csv
## 日付と金額をpasteして ./tmp-19.csv
paste -d" " ./tmp-1.csv ./tmp-9.csv > ./tmp-19.csv
#
while read -r line
do
    date=$(echo ${line} | cut -d" " -f1 | date -f - '+%Y%m%d')
    amount=$(echo ${line} | cut -d" " -f2)
    echo -e ${date} ${amount}
done < ./tmp-19.csv > ${orico_dir}/tmp-Meisai-out.csv
# 二重ソートは diff-orico.sh内で実施
# 作業ファイルの削除 ( tmp- の後ろが大文字のファイルは消さない)
rm ./tmp-[a-z,0-9]*
exit 0

diff-orico.sh コード

#!/bin/bash
set -eu
#
# Oricoカードの支払明細
#    明細(meisai)日付、金額(./tmp-Meisai-out.csv) と
#    Ledger の日付、金額(./tmp-Ledger -out.csv) とを diffする
meisai_date="20" # 明細発行日
curr_day=$(date '+%d')
#  期間変数を orico明細発行日である20日を過ぎたかどうかで決める
if [ $curr_day -ge "${meisai_date}" ]; then
    # Oricoのperiod変数
    b_date=$(date -d '1 month ago' "+%Y/%m/01")
    e_date=$(date "+%Y/%m/01")
    # NEXCOのperiod変数
    nexco_b_date=$(date -d '2 month ago' "+%Y/%m/15")
    nexco_e_date=$(date -d '1 month ago' "+%Y/%m/16") ## attn!
else
    echo '20日以前'
    # Oricoのperiod変数
    b_date=$(date -d '2 month ago' "+%Y/%m/01")
    e_date=$(date -d '1 month ago' "+%Y/%m/01")
    # NEXCOのperiod変数
    nexco_b_date=$(date -d '3 month ago' "+%Y/%m/15")
    nexco_e_date=$(date -d '2 month ago' "+%Y/%m/16") ## attn!
fi

## Ledger
# 通行料金の計算
ledger reg  ^liab and orico and \(@nexco or @首都高\)\
       -b ${nexco_b_date} -e ${nexco_e_date}\
       --date-format="%Y%m%d"\
       --format "%d %(abs(quantity(scrub(display_amount))))\n"\
       -o ./tmp-toll.csv

# oricoのメイン金額計算
ledger reg  ^liab and orico and not \(@口座 or @nexco or @首都高\)\
       -b ${b_date} -e ${e_date}\
       --date-format="%Y%m%d"\
       --format "%d %(abs(quantity(scrub(display_amount))))\n"\
       -o ./tmp-main-orico.csv

## Meisai  (oricoCSV変換.shで計算された ./tmp-Meisai-out.csv を使う
#  もとの形式は 2022/02/27,600  なので、整形する
sed -i -e 's/\///g' -e 's/,/ /g'  ./tmp-Meisai-out.csv
# ソート(同一日付内で金額の昇順に)
sort -k 1,1 -k 2n  ./tmp-Meisai-out.csv -o ./tmp-Meisai-out.csv
# 上の2ファイルを結合
cat ./tmp-toll.csv ./tmp-main-orico.csv > ./tmp-Ledger-out.csv
# ソート(同一日付内で金額の昇順に)
sort -k 1,1 -k 2n ./tmp-Ledger-out.csv -o ./tmp-Ledger-out.csv

## colordiffする
#  diffの終了コード取り出すためだけに、diffを空打ちする
if diff -q --width=50 ./tmp-Meisai-out.csv ./tmp-Ledger-out.csv\
	> /dev/null ; then
    echo '** Meisai と Ledger は完全に一致'
else
    printf "\e[1m%10s \t %16s\e[m\n" " <MEISAI>" "<LEDGER>"
    colordiff -y --width=50 ./tmp-Meisai-out.csv ./tmp-Ledger-out.csv
    #echo -e "\n"
fi
## 一時ファイル削除
rm tmp-toll.csv tmp-main-orico.csv
exit 0

Footnotes:


  1. 日付は実際のショッピング日とクレカ上の決済日とは異なることもありえます。 ↩︎

  2. Ledger-cliでは、支払先をタイプミスした時に警告またはエラーを発生させることができます。そのためめの --strict, --pedantic, --check-payees などのオプションが用意されています。Ledger帳簿内で支払先名称が「表記ゆらぎ」を起こす確率はかなり小さいと考えられます。詳しくはLedgerマニュアルを参照してください。 ↩︎

  3. Ledger-cliでは最悪の場合には次のようにして「調整」を行うことができます。くわしくはLedgerのマニュアルを見てください。

     ↩︎

  4. コマンド名が長ったらしいですが、Linuxターミナルにはコマンド入力補完機能があります。この date-amount抽出-f-orico明細.sh コマンドを実行するディレクトリ内では ./da の4ストロークだけで残りのつづりは完全に補完されます。またデータファイル名も保管されますので、最初の2文字の入力で30数文字あるデータファイル名が完全補完されます。 ↩︎

  5. CSV内データにカンマが含まれている場合には、全カラムをダブルクオートで囲むというルールがあるようですが、Oricoではカンマを含む項目だけがダブルクォーテーションで囲まれています。 ↩︎

  6. もちろんsortして揃えることも可能ですが、これとは別のスクリプトで別の使い方をする関係上、あえてデータフォーマットを1と同一にしていません。 ↩︎