見出し画像

月次支払いを自動化して、振込ボタンだけ人に残した——請求書19社ぶんから全銀ファイルを作る全工程

毎月、請求書のPDFが20枚前後たまります。先月は振込対象が19社ぶん、合計3,389,262円。これを1枚ずつ開いて、金額と振込先を読んで、銀行の振込データに打ち直す。1桁でも間違えたら、取り消しの効かないお金が動きます。

いまはこの工程のうち、請求書を集めてから振込ファイルができるまでが無人です。人がやるのは承認と実行の2回だけ。

先に言っておくと、この最後の2回は技術的に自動化できないから残しているのではありません。意図的に残しています。むしろ、そこに人を残すために、手前を全部機械に寄せたというのが正確です。

(連載『一人社長室』の第4回です。前回の記事末で予告した、月次支払いの話をします。)

月次支払い:どこまで機械で、どこから人か


工程は5つあります。

①集める — 請求書PDFを月ごとのフォルダに置く。新着ぶんだけが処理対象になる

②読む — AIが1枚ずつ「見て」読む。金額・振込先・支払期日をCSVに落とす

③照らす — 口座マスタと突合し、振込チェックリストと金額を検算し、払ってはいけない請求書を外す

④作る — 全銀ファイル(総合振込のデータ)を生成し、件数と合計を自己検算し、台帳にハッシュを記録する

⑤決める — 人が承認し、銀行にアップロードし、人が実行を押す

①〜④は無人です。⑤だけが人。この線をどこに引くかが、この記事のほぼ全部です。

請求書の自動読み取りが落ちる、3つの穴


②の「読む」は、PDFからテキストを抜けば終わりだと思っていました。全然終わりませんでした。

1つ目。1社の請求が2枚に分かれている。商品と配送料が別紙で来る取引先があります。1枚目だけ読むと365,729円。2枚合わせて402,029円。差は36,300円です。振込額が足りなければ、翌月に督促の連絡が来ます。

2つ目。請求額と送金額が違う。源泉徴収がある請求書には、数字が複数あります。いちばん大きく書いてあるのは請求額のほうで、実際に振り込むのは控除後の49,895円。大きい数字を取る、という素朴なルールが逆に効く場面です。

3つ目。そもそも払ってはいけない請求書が混ざる。もう別途振込依頼を出しているもの、中身は別の会社の立替でカード決済が済んでいるもの、マイナス残高(貸方)のもの。先月は4件ありました。

ここがいちばんの学びでした。難しいのは「払うものを正しく読む」ことではなく、「払わないものを見つける」ことのほうです。読み取り精度の話ばかりしていたころは、この4件に気づいていません。金額が1円単位まで正しくても、払ってはいけない請求書に正しく振り込んだら意味がない。

だからこの工程は、テキスト抽出ではなくAIに「見て」読ませています。2枚組かどうか、これは請求なのか控えなのか、はテキストの中には書かれていないからです。読解であって、抽出ではない。

直し方:銀行に上げる前に、振込ファイルを自分で検算する

ここからは、コピペで使える話をします。

総合振込のファイル(全銀フォーマット。拡張子は.fbaなど)は、1行120バイト固定のテキストです。行の先頭1文字でレコードの種類が決まります。

「1」…ヘッダ(振込元の情報)

「2」…データ(1行=1件の振込。81〜90桁目が振込金額)

「8」…トレーラ(合計件数と合計金額)

「9」…エンド

つまり、トレーラに合計が書いてあるということは、データ側を足し直せば自分で検算できるということです。銀行に上げてから気づくのでは遅いので、生成した直後に必ず通します。

import sys
lines = [l for l in open(path, "rb").read().replace(b"\r\n", b"\n").split(b"\n") if l]
count = total = 0
t_count = t_total = None
for i, l in enumerate(lines, 1):
    if len(l) != 120:
        print(f"NG {i}行目が120バイトではありません({len(l)}バイト)")
    if l[:1] == b"2":
        count += 1
        total += int(l[80:90])
    elif l[:1] == b"8":
        t_count = int(l[1:7])
        t_total = int(l[7:19])
print(f"明細   {count}件 / {total:,}円")
print(f"トレーラ {t_count}件 / {t_total:,}円")
print("OK 一致" if (count, total) == (t_count, t_total) else "NG 合計が一致しません")

正しいファイルを食わせるとこうなります。

明細   3件 / 570,424円
トレーラ 3件 / 570,424円
OK 一致

1社ぶん取りこぼしたファイルを作って試すと、こう出ます。

明細   2件 / 451,924円
トレーラ 3件 / 570,424円
NG 合計が一致しません

行長のチェックを入れているのは、日本語の受取人名が半角カナに変換されずに混ざると120バイトからずれるからです。文字コードで1週間溶かした話を前回書きましたが、支払いでも同じ穴が待っています。

※このスクリプトはダミーの全銀ファイルを作って実際に動かして確認しています。ただし金額欄の桁位置は総合振込の一般的な仕様で、細部は金融機関ごとに違うことがあります。最初の1回だけ、自分のファイルで桁位置を目で確かめてください。

二重振込を止めるのは、記憶ではなく台帳

もうひとつ。同じバッチを2回上げていないかは、人間の記憶で管理してはいけない種類の情報です。「先週これ、上げたっけ?」を思い出そうとしている時点で、もう危ない。

ファイルのハッシュを台帳に記録して、登録済みなら止めるだけです。

import csv, datetime, hashlib, pathlib, sys
fba = pathlib.Path(sys.argv[1])
ledger = pathlib.Path("ledger.csv")
h = hashlib.sha256(fba.read_bytes()).hexdigest()[:16]
done = set()
if ledger.exists():
    with ledger.open(encoding="utf-8") as f:
        done = {row[0] for row in csv.reader(f) if row}
if h in done:
    sys.exit(f"NG このバッチは登録済みです({h})。二重振込の可能性があります")
with ledger.open("a", newline="", encoding="utf-8") as f:
    csv.writer(f).writerow([h, fba.name, datetime.date.today().isoformat()])
print(f"OK 台帳に登録しました {h} {fba.name}")

同じファイルで2回叩くと、2回目はこうなります。

OK 台帳に登録しました 1a0f907d789ce6a9 PAYMENT.fba
NG このバッチは登録済みです(1a0f907d789ce6a9)。二重振込の可能性があります

十数行ですが、これで「二重振込していないこと」が記憶ではなく記録で言えるようになります。うちではこの台帳を、作る側と承認する側の2人で共有しています。片方だけが見ている台帳は、台帳ではなくメモです。

なぜ「実行」だけはAIに渡さないのか

「人が承認する」と一言で言いますが、中身は3つに分かれます。


①金額が合っているか — 合計・件数・口座の一致。これは目視より機械が強い。人がここを見ている限り、見落としは減りません

②もう払っていないか — 先週の自分の行動を人は覚えていない。記憶ではなく台帳で止める

③この支払いを今月やるのか — 資金繰り、取引先との関係、保留するかどうか。これは経営判断で、検算ではない

①と②は機械の仕事です。③だけが人の仕事。

「人の承認を残しています」と言いながら、実際は検算を人に押し付けている会社は多いと思います。あれは承認ではなく、作業の外注です。承認者に判断だけを渡すために、手前の検算・突合・整形を全部機械に寄せる。人ゲートを残すことと、人の手作業を残すことは、まったく別のことです。

AIで経理を自動化した話をすると、「じゃあ最後もAIが振り込むんですか」と聞かれます。それはやりません。取り消せない工程の手前には人を置く。ここは今後も変えないと思います。技術の問題ではなくて、間違えたときに謝りにいくのが人間だからです。


同じことが起きていないかの3点チェック

振込ファイルを作ったあと、合計金額を人が電卓で足し直していませんか。それは機械の仕事です

同じバッチを2回アップロードしても、何も止まらない状態になっていませんか。止めるのは記憶ではなく台帳です

承認者が見ているのは「金額の正しさ」ですか、「この支払いを今月やるかどうか」ですか。前者なら、承認ではなく検算をお願いしていることになります

で、いま思うこと

自動化の話をしていると、だいたい「どこまでAIにやらせるんですか」と聞かれます。でも実際に効いたのは、どこにAIを入れるかより、どこに人を残すかを先に決めたことでした。残す場所を決めると、そこ以外は迷わず機械に寄せられます。順番が逆だと、いつまでも「ここも人がチェックしたほうが安全かな」が増えていって、結局なにも速くならない。

次回は、いまうちで毎日動いている自動化を一覧で出します。台帳そのものを公開する回にする予定です。

この連載はマガジンにまとめています。

なぜここまで「人のゲート」にこだわるのかは、創刊号から読むとつながると思います。

日々の実務メモはX(@keisuke_musico)に書いています。そちらもよければ。

いいなと思ったら応援しよう!