CHAPTER 10 ・ 総合演習 / Lesson 37

請求と入金を突き合わせる

請求データと入金データを照合し、未入金・過入金・金額違いを一覧にします。経理の定番作業です。

目安 35分 ・ インストール不要

このレッスンが終わったら

「請求と入金を、キーで突き合わせ、差異の種類ごとに分けて出力できる」と言える。

STEP 1

課題とデータを見る

月末になると、経理には定番の作業があります。「請求した分が、きちんと入金されたか」を確かめる作業です。請求書の控えと銀行の入金明細を見比べて、1件ずつ印を付けていきます。この作業を、突合(とつごう)と呼びます。

サクラ商事の9月分の請求は12件です。まず、請求の一覧を見てみましょう。

invoices_billed.csvサンプルファイル
請求番号,得意先コード,請求日,請求額
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

次が、銀行の入金明細です。

payments.csvサンプルファイル
入金日,摘要,入金額
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がするのは「一覧にすること」までです。入金の消込や、金額の修正は、人が確認してから行います。

STEP 2

設計:部品に分ける

いきなり書き始めず、まず作業を部品に分けます。部品ごとに作って確かめれば、間違いの場所もすぐ分かります。

  1. ① 読み込む請求・入金・得意先
  2. ② 番号を取り出す摘要から請求番号
  3. ③ 請求ごとにまとめる入金の合計と回数
  4. ④ 区分を付ける入金済み・不足など
  5. ⑤ 候補を出す番号のない入金
  6. ⑥ 出力と検算Excelに書いて読み直す
6つの部品。②〜⑤は、それぞれ小さな関数にします。

区分を付けるルールは、先に表にして決めておきます。ここがあいまいだと、コードも迷います。

区分条件(入金合計と請求額を比べる)
未入金入金が0回
入金済み合計が請求額と同じ(2回に分けて払われても、合計が合えばよい)
金額不足合計が請求額より少ない
過入金1回の入金で、請求額より多い
二重入金2回以上の入金で、合計が請求額より多い

円の金額は、小数が出ない整数のまま扱います(Lesson 04 で学んだ通り、端数が出ない計算は整数で十分です)。差額は「入金合計 − 請求額」とし、マイナスなら不足、プラスなら過入金です。

STEP 3

部品を1つずつ作る

部品②:摘要から請求番号を取り出す。 全角・半角をそろえてから(Lesson 02)、決まった形を探します(Lesson 20)。請求番号は、INV- のあとに4桁・ハイフン・4桁、という形です。

部品②:請求番号を取り出す関数とテストコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

見つからないときは None(なし)を返す作りにしました。全角で入力された番号も拾えることを、assert(Lesson 28)で確かめています。

部品③:請求ごとにまとめる。 入金明細を請求書と結合すると、何が起きるでしょう。先に試します。

部品③:そのまま結合すると行が増えるpandas・サンプルファイル付き
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

二重に入金された請求があると、結合の結果が1行増えます(Lesson 23 の落とし穴です)。そこで、先に請求番号ごとに1行へまとめてから結合します。validate="one_to_one" を付けておけば、行が増える状態だとエラーで教えてくれます。

部品④:区分を付ける。 区分のルールを、ひとつの関数にします。入力と答えの表を作って、全部のパターンをテストします。

部品④:区分を付ける関数とテストコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます

最後の行は、2回に分けて払われても、合計が合えば「入金済み」になる、という確認です。こうした境界のケースを表に入れておくと、あとでルールを直したときに安心です。

部品⑤:請求番号のない入金に、候補を出す。 銀行の摘要にある社名(カナ)から得意先を推定します。そのために、対応表を用意します。

部品⑤:社名から得意先を推定して、候補を探すpandas・サンプルファイル付き
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

「得意先コードが同じで、請求額も同じ」請求書を探して、候補にしました。これは推定です。同じ金額の請求書が2枚あれば候補も2つ出ますし、的外れなこともあります。だから候補は「参考」として添えるだけにして、確定は人が行います。

STEP 4

組み合わせて完成させる

5つの部品を、1つにつなげます。部品はそのまま使い、つなぎ役の reconcile と、出力、検算を足しました。

完成:請求と入金の突合(Excel出力つき)pandas・サンプルファイル付き
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます

結果を、人の目で読みましょう。

  • 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で開けます。

STEP 5

自分で書く

完成したコードに、ルールを足したり、条件を変えたりしてみましょう。実務では、この「あとからの変更」が、毎月のように起きます。

お題① 不足額が1,000円以内のときは、振込手数料が引かれた可能性があります。区分を「手数料差引か」にする条件を、classify に足してください。(不足の判定の中で、差額を見て分けます)

お題①:少額の不足を「手数料差引か」にコードは書き換えられます
Ctrl / ⌘ + Enter でも実行
結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

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 で日付にします。

お題②:期日より後の入金pandas・サンプルファイル付き
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

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 があります。入金のない請求について、得意先コードごとの請求額の合計を、辞書で表示してください。

お題③:未入金の請求額を、得意先ごとに合計pandas・サンプルファイル付き
Ctrl / ⌘ + Enter でも実行

この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。

結果
▶ 実行を押すと、ここに結果が出ます
ヒントを見る

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())
STEP 6

振り返りと、実務に持ち帰る

理解チェック請求書の表と入金の表を結合する前に、入金を請求番号ごとに「1行にまとめた」のはなぜでしょう?

理解チェック請求番号のない入金に、金額と社名から「候補」が1件だけ見つかりました。どう扱うのがよいでしょう?