CHAPTER 10 ・ 総合演習 / Lesson 37
請求と入金を突き合わせる
請求データと入金データを照合し、未入金・過入金・金額違いを一覧にします。経理の定番作業です。
このレッスンが終わったら
「請求と入金を、キーで突き合わせ、差異の種類ごとに分けて出力できる」と言える。
課題とデータを見る
月末になると、経理には定番の作業があります。「請求した分が、きちんと入金されたか」を確かめる作業です。請求書の控えと銀行の入金明細を見比べて、1件ずつ印を付けていきます。この作業を、突合(とつごう)と呼びます。
サクラ商事の9月分の請求は12件です。まず、請求の一覧を見てみましょう。
請求番号,得意先コード,請求日,請求額 INV-2025-0001,C001,2025-09-30,1320000 INV-2025-0002,C002,2025-09-30,484000 INV-2025-0003,C003,2025-09-30,792000 INV-2025-0004,C004,2025-09-30,231000 INV-2025-0005,C005,2025-09-30,605000 INV-2025-0006,C006,2025-09-30,159500 INV-2025-0007,C007,2025-09-30,968000 INV-2025-0008,C008,2025-09-30,374000 INV-2025-0009,C001,2025-09-30,88000 INV-2025-0010,C003,2025-09-30,440000 INV-2025-0011,C005,2025-09-30,275000 INV-2025-0012,C002,2025-09-30,132000
次が、銀行の入金明細です。
入金日,摘要,入金額 2025-10-28,INV-2025-0001 アオイデンキ,1320000 2025-10-29,INV-2025-0002 ミドリシヨウジ,484000 2025-10-29,INV-2025-0003 サクラインサツ,792000 2025-10-30,INV-2025-0004 タチバナコウギヨウ,230000 2025-10-30,INV-2025-0006 スミレウンユ,159500 2025-10-31,INV-2025-0007 カエデセッケイ,968000 2025-10-31,INV-2025-0008 アサヒケンザイ,374000 2025-10-31,INV-2025-0008 アサヒケンザイ,374000 2025-11-04,INV-2025-0010 サクラインサツ,484000 2025-11-05,ウケトリキン ニユウキン ミドリシヨウジ,132000
見比べると、手作業では困りそうな点がいくつも見つかります。
- 摘要は半角カナで、請求番号と社名がひと続きになっている
- 請求は12件なのに、入金は10行で、件数が合わない
- 同じ請求番号が2行ある。請求額と金額が違う行もある
- 請求番号が書かれていない行がある
この回では、これらの食い違いを自動で見つけて、種類ごとに分けた一覧を作ります。ただし、Pythonがするのは「一覧にすること」までです。入金の消込や、金額の修正は、人が確認してから行います。
設計:部品に分ける
いきなり書き始めず、まず作業を部品に分けます。部品ごとに作って確かめれば、間違いの場所もすぐ分かります。
- ① 読み込む請求・入金・得意先
- ② 番号を取り出す摘要から請求番号
- ③ 請求ごとにまとめる入金の合計と回数
- ④ 区分を付ける入金済み・不足など
- ⑤ 候補を出す番号のない入金
- ⑥ 出力と検算Excelに書いて読み直す
区分を付けるルールは、先に表にして決めておきます。ここがあいまいだと、コードも迷います。
| 区分 | 条件(入金合計と請求額を比べる) |
|---|---|
| 未入金 | 入金が0回 |
| 入金済み | 合計が請求額と同じ(2回に分けて払われても、合計が合えばよい) |
| 金額不足 | 合計が請求額より少ない |
| 過入金 | 1回の入金で、請求額より多い |
| 二重入金 | 2回以上の入金で、合計が請求額より多い |
円の金額は、小数が出ない整数のまま扱います(Lesson 04 で学んだ通り、端数が出ない計算は整数で十分です)。差額は「入金合計 − 請求額」とし、マイナスなら不足、プラスなら過入金です。
部品を1つずつ作る
部品②:摘要から請求番号を取り出す。 全角・半角をそろえてから(Lesson 02)、決まった形を探します(Lesson 20)。請求番号は、INV- のあとに4桁・ハイフン・4桁、という形です。
▶ 実行を押すと、ここに結果が出ます
見つからないときは None(なし)を返す作りにしました。全角で入力された番号も拾えることを、assert(Lesson 28)で確かめています。
部品③:請求ごとにまとめる。 入金明細を請求書と結合すると、何が起きるでしょう。先に試します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
二重に入金された請求があると、結合の結果が1行増えます(Lesson 23 の落とし穴です)。そこで、先に請求番号ごとに1行へまとめてから結合します。validate="one_to_one" を付けておけば、行が増える状態だとエラーで教えてくれます。
部品④:区分を付ける。 区分のルールを、ひとつの関数にします。入力と答えの表を作って、全部のパターンをテストします。
▶ 実行を押すと、ここに結果が出ます
最後の行は、2回に分けて払われても、合計が合えば「入金済み」になる、という確認です。こうした境界のケースを表に入れておくと、あとでルールを直したときに安心です。
部品⑤:請求番号のない入金に、候補を出す。 銀行の摘要にある社名(カナ)から得意先を推定します。そのために、対応表を用意します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
「得意先コードが同じで、請求額も同じ」請求書を探して、候補にしました。これは推定です。同じ金額の請求書が2枚あれば候補も2つ出ますし、的外れなこともあります。だから候補は「参考」として添えるだけにして、確定は人が行います。
組み合わせて完成させる
5つの部品を、1つにつなげます。部品はそのまま使い、つなぎ役の reconcile と、出力、検算を足しました。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
結果を、人の目で読みましょう。
- INV-0004(タチバナ工業):1,000円不足。振込手数料が引かれた可能性がありますが、理由は金額だけでは分かりません。取引先との取り決めを確認します。
- INV-0008(アサヒ建材):同じ日に、同額が2回入金されています。374,000円の二重入金です。返金するか、次回の請求と相殺するかは、担当者が決めます。
- INV-0010(サクラ印刷):44,000円多く入っています。多い理由は、データからは分かりません。
- 未入金の4件:うち INV-0012 は、請求番号のない入金(132,000円)が候補に挙がっています。
- 検算:入金の総額 5,317,500円は、請求に紐づいた分と、番号なしの分に、過不足なく分けられました(合わなければ
assertが止めてくれます)。
ブラウザでは、できたExcelをダウンロードできません。そこで、書き出したあとに読み直して、シート名と行数を確かめています。自分のパソコンで同じコードを動かせば、フォルダに 入金突合.xlsx が作られ、Excelで開けます。
自分で書く
完成したコードに、ルールを足したり、条件を変えたりしてみましょう。実務では、この「あとからの変更」が、毎月のように起きます。
お題① 不足額が1,000円以内のときは、振込手数料が引かれた可能性があります。区分を「手数料差引か」にする条件を、classify に足してください。(不足の判定の中で、差額を見て分けます)
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
diff < 0 の中で、さらに if diff >= -1000: を書いて、return "手数料差引か", diff とします。ちょうど -1000 のときも含めるのがポイントです。
答えの例を見る
def classify(billed, paid, count):
diff = paid - billed
if count == 0:
return "未入金", diff
if diff == 0:
return "入金済み", 0
if diff < 0:
if diff >= -1000:
return "手数料差引か", diff
return "金額不足", diff
if count >= 2:
return "二重入金", diff
return "過入金", diff
print(classify(231000, 230000, 1))
print(classify(231000, 229000, 1))お題② 支払期日を 2025-10-31 とします。これより後に入金された行について、入金日と入金額を表示してください。日付は文字のままだと比べにくいので、pd.to_datetime で日付にします。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
late = pay[pay["入金日"] > "2025-10-31"] で絞れます。入金日は、dt.strftime("%Y-%m-%d") で文字に直すと、日付だけの表示になります。
答えの例を見る
import pandas as pd
pay = pd.read_csv("payments.csv")
pay["入金日"] = pd.to_datetime(pay["入金日"])
late = pay[pay["入金日"] > "2025-10-31"]
for d, a in zip(late["入金日"].dt.strftime("%Y-%m-%d"), late["入金額"]):
print(d, a)お題③ 入金のあった請求番号の集合 paid_numbers があります。入金のない請求について、得意先コードごとの請求額の合計を、辞書で表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
billed[~billed["請求番号"].isin(paid_numbers)] で『入っていないもの』を取り出せます。そのあと groupby("得意先コード")["請求額"].sum().to_dict() です。
答えの例を見る
import pandas as pd
billed = pd.read_csv("invoices_billed.csv")
paid_numbers = {
"INV-2025-0001", "INV-2025-0002", "INV-2025-0003", "INV-2025-0004",
"INV-2025-0006", "INV-2025-0007", "INV-2025-0008", "INV-2025-0010",
}
unpaid = billed[~billed["請求番号"].isin(paid_numbers)]
print(unpaid.groupby("得意先コード")["請求額"].sum().to_dict())振り返りと、実務に持ち帰る
理解チェック請求書の表と入金の表を結合する前に、入金を請求番号ごとに「1行にまとめた」のはなぜでしょう?
理解チェック請求番号のない入金に、金額と社名から「候補」が1件だけ見つかりました。どう扱うのがよいでしょう?