Skip to Content
Go Realm v1 is released 🎉
Accountingবই (Print Edition)অধ্যায় ১২: Bank Reconciliation

অধ্যায় ১২ — Bank Reconciliation

Volume 1 · Part 2 — Core Business Accounting · Chapter 12

পূর্বশর্ত: অধ্যায় ১১ (Cash ও Bank), অধ্যায় ৯ (General Ledger)


১. Learning Objective

এই অধ্যায় শেষে আপনি পারবেন:

Bank statement কেন আপনার ledger-এর আয়না — এবং কেন তার Dr/Cr উল্টো, তা ব্যাখ্যা করতে খাতা আর statement-এর পার্থক্যের চার শ্রেণি চিনতে কোন পার্থক্যে journal entry লাগে, কোনটায় শুধু অপেক্ষা — তা আলাদা করতে দুই দিক থেকে Bank Reconciliation Statement তৈরি করতে ও মেলাতে statement-এর line আর ledger-এর line মেলানো (matching) — 1:1, 1:many, many:1 — বুঝতে না-মেলা item পরের মাসে বয়ে নেওয়া (carry forward) সামলাতে statement import করার idempotent নকশা করতে একটি auto-matching engine-এর নিয়ম ও তার সীমা নকশা করতে reconciled line কেন তালাবদ্ধ, এবং সেটা software-এ কীভাবে প্রয়োগ হয় — তা বলতে reconciliation কী কী ভুল ধরে, আর কী কী কখনো ধরে না — তা চিনতে

সময়: পড়া ৫৫ মিনিট + অনুশীলন ৬০ মিনিট।


২. Concept Explanation

দুটো খাতা, একটাই টাকা

আপনার Prime Bank account-এর হিসাব দুই জায়গায় লেখা হয় — আপনার ledger-এ (1120), আর ব্যাংকের নিজের ledger-এ। দুটো খাতা দুজন আলাদা মানুষ লেখেন, আলাদা সময়ে, আলাদা তথ্য দেখে। তাই মাস শেষে দুটোতে একই সংখ্যা থাকা প্রায় অসম্ভব

আপনার ledger (1120) ব্যাংকের statement ─────────────────── ───────────────── আপনি যা জানেন ব্যাংক যা জানে cheque লেখার দিন cheque কাটার দিন জমা দেওয়ার দিন clear হওয়ার দিন charge জানেন না charge কেটে নিয়েছে সুদ জানেন না সুদ দিয়ে দিয়েছে

Bank Reconciliation হলো দুটো সংখ্যার মাঝের প্রতিটি টাকার হিসাব দেওয়া — কোনটা কেন আলাদা, আর কোনটা ঠিক করতে হবে। শেষে দুই দিক থেকে একটাই সংখ্যায় পৌঁছাতে হবে।

এটা অধ্যায় ১০-এর Trial Balance-এর পরে আপনার দ্বিতীয় সবচেয়ে গুরুত্বপূর্ণ যাচাই — আর একদিক থেকে বেশি শক্তিশালী। Trial Balance নিজের খাতাকে নিজের সাথে মেলায়; reconciliation খাতাকে বাইরের সত্যের সাথে মেলায়। অধ্যায় ১০-এ যে ছয়টা ভুল Trial Balance ধরতে পারে না বলেছিলাম, তার কয়েকটা — বাদ পড়া entry, দুবার লেখা entry, ভুল অঙ্ক — bank account-এর ক্ষেত্রে reconciliation ঠিকই ধরে ফেলে।

আয়না — statement-এর Dr/Cr উল্টো কেন

প্রথমবার bank statement পড়া developer-রা প্রায় সবাই একই জায়গায় হোঁচট খান: টাকা জমা দিলে statement-এ লেখা থাকে Cr, আর আপনার ledger-এ সেটা Dr

কারণটা অধ্যায় ৩-এর প্রকার থেকেই আসে। আপনার কাছে ব্যাংকে রাখা টাকা একটা asset। ব্যাংকের কাছে আপনার টাকা একটা liability — সে আপনাকে ওটা ফেরত দিতে বাধ্য। Statement লেখা হয় ব্যাংকের খাতা থেকে, তাই:

ঘটনা আপনার ledger (asset) ব্যাংকের খাতা (liability) ───── ──────────────────── ───────────────────────── টাকা জমা Dr (asset বাড়ল) Cr (ব্যাংকের দেনা বাড়ল) টাকা তোলা Cr (asset কমল) Dr (ব্যাংকের দেনা কমল) ব্যালেন্স Dr balance Cr balance

তাই নিয়মটা: statement-এর Cr = আপনার Dr, statement-এর Dr = আপনার Cr। Import করার সময় এই উল্টানোটা একবার, একটাই জায়গায় করবেন — নইলে পুরো matching উল্টো হয়ে যাবে।

অনেক ব্যাংক Dr/Cr-এর বদলে “Withdrawal / Deposit” লেখে — সেটা সহজ। এই অধ্যায়ে আমরা সেই ভাষাই ব্যবহার করব।

পার্থক্যের চার শ্রেণি

দুটো খাতার পার্থক্য যত রকমই দেখাক, শেষ পর্যন্ত চার শ্রেণিতে পড়ে। শ্রেণিটাই বলে দেয় কী করতে হবে:

শ্রেণি ১ — খাতায় আছে, ব্যাংকে এখনো নেই (timing)

outstanding cheque cheque লিখেছেন, এখনো ব্যাংক থেকে কাটেনি deposit in transit cheque জমা দিয়েছেন, এখনো clear হয়নি

খাতা ঠিক, ব্যাংক শুধু পিছিয়ে। কিছু করার নেই — অপেক্ষা। পরের মাসে মিলে যাবে।

শ্রেণি ২ — ব্যাংকে আছে, খাতায় এখনো নেই

bank charges ব্যাংক কেটে নিয়েছে, আপনি জানতেন না interest credit ব্যাংক সুদ দিয়েছে direct debit ঋণের কিস্তি, বীমা — ব্যাংক নিজে কেটেছে direct credit গ্রাহক transfer করেছে, আপনাকে বলেনি dishonoured cheque জমা দেওয়া cheque ফেরত গেছে

ব্যাংক ঠিক, খাতা পিছিয়ে। প্রতিটির জন্য journal entry লাগবে। Statement-ই এখানে source document।

শ্রেণি ৩ — ভুল, যেকোনো পাশে

আপনার ভুল ৫৪,০০০ এর জায়গায় ৪৫,০০০ লিখেছেন একই receipt দুবার লিখেছেন ভুল bank account-এ লিখেছেন ব্যাংকের ভুল অন্য কারো cheque আপনার account থেকে কেটেছে একই deposit দুবার credit করেছে

আপনার ভুল হলে সংশোধনী entry; ব্যাংকের ভুল হলে ব্যাংককে জানান, খাতায় কিছু নয় — ব্যাংক ঠিক করলে statement-এ উল্টো line আসবে।

শ্রেণি ৪ — অজানা

statement-এ একটা ৪০,০০০ টাকার withdrawal, আপনি চেনেন না

এটা সবচেয়ে গুরুত্বপূর্ণ শ্রেণি, কারণ এখানেই জালিয়াতি ধরা পড়ে। কেউ একটা cheque জাল করেছে, বা কারো card তথ্য চুরি হয়েছে। এটা মেলাবেন না, “misc” বলে entry দেবেন না — অনুসন্ধান করুন। না জানা পর্যন্ত এটা unmatched থাকবে, আর reconciliation অসম্পূর্ণ থাকবে।

চার শ্রেণি এক নজরে:

শ্রেণিকে পিছিয়েকী করবেনউদাহরণ
১ timingব্যাংকঅপেক্ষাoutstanding cheque, deposit in transit
২ bank-onlyখাতাjournal entrycharge, সুদ, direct debit/credit, dishonour
৩ ভুলযেকোনোসংশোধনী entry / ব্যাংককে জানানঅঙ্ক উল্টে যাওয়া, দুবার লেখা
৪ অজানাঅনুসন্ধান, entry নয়অচেনা withdrawal

Reconciliation Statement — দুই দিক থেকে

প্রচলিত রূপটা দুই ভাগে। দুটোই একই সংখ্যায় পৌঁছাতে হবে — সেটাই প্রমাণ যে প্রতিটি পার্থক্যের হিসাব দেওয়া হয়েছে:

BANK RECONCILIATION STATEMENT — <তারিখ> ব্যাংকের দিক থেকে statement-এর closing balance B (−) outstanding cheques শ্রেণি ১ (+) deposits in transit শ্রেণি ১ (±) ব্যাংকের ভুল শ্রেণি ৩ ───────────────────────────────────────────── = adjusted bank balance X খাতার দিক থেকে ledger-এর closing balance L (+) ব্যাংক credit করেছে, খাতায় নেই শ্রেণি ২ (−) ব্যাংক debit করেছে, খাতায় নেই শ্রেণি ২ (±) আপনার ভুল শ্রেণি ৩ ───────────────────────────────────────────── = adjusted book balance X ← একই সংখ্যা

লক্ষ করুন কোন শ্রেণি কোন দিকে যায়:

শ্রেণি ১ (timing) → ব্যাংকের দিকে সমন্বয়, খাতায় entry নয় শ্রেণি ২ (bank-only) → খাতার দিকে সমন্বয়, প্রতিটিতে entry

Adjusted book balance-ই আসল সংখ্যা। Balance Sheet-এ এটাই যাবে — statement-এর সংখ্যা নয়। কারণ outstanding cheque-এর টাকা আপনার আর নেই, ব্যাংক যতই সেটা দেখাক।

Reconciliation আসলে matching

উপরের statement-টা ফলাফল। কাজটা হলো line ধরে ধরে মেলানো — ledger-এর প্রতিটি line-এর বিপরীতে statement-এর একটা line খোঁজা:

LEDGER (1120) STATEMENT ───────────── ───────── 05-08 Dr 5,75,000 ABC chq ◀──────▶ 06-08 Dep 5,75,000 CLG DEP 10-08 Cr 1,50,000 chq 00412 ◀── ✗ (নেই — outstanding) 28-08 Cr 3,50,000 salary ◀──────▶ 28-08 Wdl 3,50,000 SALARY TRF (নেই — bank only) ✗ ──▶ 31-08 Wdl 575 MAINT CHG

মেলানো সবসময় এক-এক নয়:

1 : 1 একটা cheque, একটা statement line সবচেয়ে সাধারণ 1 : many একটা deposit slip-এ তিনটা cheque জমা, ledger-এ তিন line, ব্যাংক একটা line-এ 2,45,000 credit করল statement-এ এক many : 1 ব্যাংক একটা withdrawal-কে দুই line-এ ledger-এ এক line, দেখাল (মূল + charge) statement-এ দুই

তাই matching-এর একক line নয়, group: এক পাশে কয়েকটা line, অন্য পাশে কয়েকটা line, দুই পাশের যোগফল সমান।

মেলানোর পরে যা থাকে:

ledger-এ unmatched → শ্রেণি ১ (timing) বা শ্রেণি ৩ (আপনার ভুল) statement-এ unmatched → শ্রেণি ২ (entry দিন) বা শ্রেণি ৩/৪ (অনুসন্ধান)

Carry forward — গত মাসের অমিল এ মাসে মেলে

জুলাইয়ে একটা cheque লিখেছিলেন, আগস্টে ব্যাংক থেকে কাটল। জুলাইয়ের reconciliation-এ সেটা outstanding ছিল; আগস্টের statement-এ তার line এল। আগস্টে মেলানোর সময় জুলাইয়ের unmatched ledger line-গুলোও প্রার্থী।

জুলাই reconciliation: outstanding #00398 40,000 ← unmatched থাকল আগস্ট statement: 03-08 CHQ 00398 40,000 ← এবার মিলল

এর মানে reconciliation একটা মাসের কাজ নয় — একটা চলমান অবস্থা। প্রতিটি ledger line হয় matched, নয় unmatched; মাস শুধু একটা কাটার রেখা যেখানে দাঁড়িয়ে হিসাব দেন।

এর একটা পরিণতি: প্রথম reconciliation শুরু করতে একটা opening লাগে — কোন তারিখে statement আর ledger শেষবার মিলেছিল, আর সেদিন কোন কোন item unmatched ছিল। নতুন software-এ migration করার সময় এটাই সবচেয়ে বেশি ভুল হয় (অধ্যায় ৬২)।

Reconciled মানে তালাবদ্ধ

একটা ledger line যখন statement-এর line-এর সাথে মিলে গেছে, তখন ব্যাংক নিজে সাক্ষী দিয়েছে যে ওই টাকা ওই দিনে ওভাবেই নড়েছে। এরপর ওই line-এর সাথে কিছু করা মানে সাক্ষ্যের সাথে বিরোধ।

অধ্যায় ৮-এ post করা entry এমনিতেই অপরিবর্তনীয়। কিন্তু reversal তো করা যায়। Reconciled line-এর reversal-এ বাড়তি সতর্কতা লাগে:

reversal চাইলেন একটা matched line-এর → আগে unmatch করতে হবে (কারণ লিখে) → তারপর reversal → reversal-এর নতুন line আবার unmatched হয়ে reconciliation-এ ফিরবে

কেন এত কড়াকড়ি? কারণ reconciliation সম্পূর্ণ হওয়া মানে “এই তারিখ পর্যন্ত bank ঠিক” — একটা প্রতিবেদন যার উপর মানুষ ভরসা করে। কেউ পরে চুপচাপ একটা matched line উল্টে দিলে সেই প্রতিবেদনটা মিথ্যা হয়ে যায়, আর কেউ জানেও না।

কত ঘন ঘন

মাসে একবার ন্যূনতম — statement আসার পরে সপ্তাহে একবার ভালো — ভুল ছোট থাকতেই ধরা পড়ে প্রতিদিন bank feed থাকলে — matching প্রায় স্বয়ংক্রিয়

সময়ের সাথে reconciliation-এর কষ্ট বাড়ে, কমে না। তিন মাসের জমে থাকা statement মেলানো তিনগুণ নয়, দশগুণ কঠিন — কারণ পুরনো unmatched item-এর কারণ মনে থাকে না।

যা ধরে, যা ধরে না

Reconciliation যা ধরে:

✓ bank account ছোঁয়া কোনো entry বাদ পড়েছে ✓ একই receipt/payment দুবার লেখা হয়েছে ✓ অঙ্ক ভুল (54,000 বনাম 45,000) ✓ ভুল bank account-এ লেখা হয়েছে (1120-র জায়গায় 1125) ✓ ব্যাংকের ভুল ✓ জালিয়াতি — এমন লেনদেন যা আপনি করেননি ✓ হারিয়ে যাওয়া cheque (মাসের পর মাস outstanding)

Reconciliation যা কখনো ধরে না:

✗ bank পাশ ঠিক, অন্য পাশ ভুল account-এ (Dr Office Rent-এর জায়গায় Dr Electricity — bank-এর 65,000 ঠিকই মিলবে) ✗ যে লেনদেনে bank/cash নেই (invoice, accrual, depreciation) ✗ ঠিক অঙ্ক, ঠিক bank, কিন্তু ভুল party (A/R-এ করিমের জায়গায় রহিম — bank-এর দিক থেকে অভিন্ন)

অধ্যায় ১০-এর সেই সতর্কতাই আবার: যাচাইটা যা দেখে, শুধু তাই ধরে। Reconciliation দেখে bank পাশ; অন্য পাশের জন্য অন্য যাচাই লাগে — subledger reconciliation (অধ্যায় ১৪, ১৬, ৪৪) আর তুলনামূলক report।


৩. Accounting Rule

মূল নিয়ম

adjusted bank balance == adjusted book balance reconciliation সম্পূর্ণ ⟺ statement-এর প্রতিটি line-এর হিসাব আছে (matched, অথবা entry দিয়ে matched)

শ্রেণি অনুযায়ী কাজ

itemশ্রেণিব্যাংকের দিকেখাতার দিকেentry?
outstanding cheque(−)না
deposit in transit(+)না
bank charge(−)Dr Bank Charges / Cr Bank
interest credit(+)Dr Bank / Cr Interest Income
direct debit (ঋণ, বীমা)(−)Dr Loan (বা Expense) / Cr Bank
direct credit (গ্রাহক)(+)Dr Bank / Cr A/R (party)
direct credit, অচেনা২/৪(+)Dr Bank / Cr Unidentified Receipts
dishonoured cheque(−)Dr A/R (party) / Cr Bank
আপনার অঙ্কের ভুল(±)সংশোধনী entry, পার্থক্যের অঙ্কে
ব্যাংকের ভুল(±)না — ব্যাংককে জানান
অচেনা withdrawalনা — অনুসন্ধান

Balance Sheet-এ কোন সংখ্যা

adjusted book balance ✓ statement balance ✗ (outstanding cheque-এর টাকা আপনার নয়)

তালা

matched line → reversal-এর আগে unmatch বাধ্যতামূলক, কারণ সহ completed reconciliation → ওই তারিখের আগের কোনো line-এর match বদলানো যাবে না

অলঙ্ঘনীয়

কখনো "পার্থক্যের অঙ্কে" একটা entry দিয়ে মিলিয়ে দেবেন না। প্রতিটি পার্থক্যের একটা কারণ আছে; কারণটাই entry, পার্থক্যটা নয়।

৪. Real Business Example

আগস্ট ২০২৫ — Prime Bank-এর statement এল

অধ্যায় ১১-এ 1120-এর ledger শেষ হয়েছিল ৮,৮৪,৮০০ টাকায়। সেপ্টেম্বরের প্রথম সপ্তাহে ব্যাংক statement পাঠাল:

PRIME BANK LTD — ACCOUNT STATEMENT A/C 4471 (CA) 01-08-2025 to 31-08-2025 DATE PARTICULARS WITHDRAWAL DEPOSIT BALANCE ───── ─────────── ────────── ─────── ─────── 01-08 BALANCE B/F 11,50,000 01-08 TRF TO CITY BANK SND 8890 3,00,000 8,50,000 03-08 CHQ 00398 40,000 8,10,000 06-08 CLG DEP CHQ 118823 ABC LTD 5,75,000 13,85,000 19-08 CLG DEP CHQ 220145 80,000 14,65,000 20-08 EFT IN — S. AKTER 1,20,000 15,85,000 21-08 CHQ RETURN 220145 INSUFF FUND 80,000 15,05,000 21-08 CHQ RETURN CHARGE 250 15,04,750 25-08 LOAN INSTL A/C 5501 25,000 14,79,750 28-08 SALARY TRF BULK 3,50,000 11,29,750 31-08 A/C MAINT CHARGE 575 11,29,175 31-08 INTEREST CREDIT 1,200 11,30,375 ───── ───────── ──────── 7,95,825 7,76,200 31-08 CLOSING BALANCE 11,30,375

খাতা বলে ৮,৮৪,৮০০, ব্যাংক বলে ১১,৩০,৩৭৫। পার্থক্য ২,৪৫,৫৭৫। এই সংখ্যাটার নিজের কোনো অর্থ নেই — এটা কয়েকটা আলাদা কারণের যোগফল। এবার একটা একটা করে বের করি।

ধাপ ১ — Opening মেলান

আগে দেখুন মাসের শুরুতে দুটো খাতা কোথায় ছিল:

জুলাই শেষে ledger 11,10,000 statement B/F 11,50,000 পার্থক্য 40,000

জুলাইয়ের reconciliation-এ একটা outstanding cheque ছিল — #00398, ৪০,০০০, ১৫ জুলাইয়ের ভাড়া (অধ্যায় ১০-এর PV-07-0004)। ব্যাংক তখনো কাটেনি, তাই ব্যাংকের সংখ্যা ৪০,০০০ বেশি। Opening মিলছে — পার্থক্যের পুরোটাই একটা চেনা কারণে।

এই ধাপটা বাদ দিলে পুরো reconciliation ভুল দিকে যাবে। অধ্যায় ১০-এর সেই একই যুক্তি: opening আগে, period পরে।

ধাপ ২ — line ধরে মেলান

Ledger-এর প্রতিটি line (জুলাইয়ের carry forward সহ) statement-এর সাথে:

LEDGER LINE STATEMENT LINE ফল ─────────── ────────────── ── PV-07-0004 15-07 Cr 40,000 #00398 03-08 CHQ 00398 40,000 ✓ carry forward মিলল CV-08-0001 01-08 Cr 3,00,000 01-08 TRF TO CITY 3,00,000 ✓ CV-08-0003 05-08 Dr 5,75,000 06-08 CLG DEP ABC 5,75,000 ✓ একদিন পরে clear PV-08-0001 10-08 Cr 1,50,000 #00412 — ✗ outstanding RV-08-0003 18-08 Dr 80,000 19-08 CLG DEP 80,000 ✓ JV-08-0005 22-08 Cr 80,200 21-08 CHQ RETURN 80,000 ┐ 21-08 RETURN CHG 250 ┘ ✗ 80,200 ≠ 80,250 PV-08-0004 28-08 Cr 3,50,000 28-08 SALARY TRF 3,50,000 ✓ — 20-08 EFT IN 1,20,000 ✗ bank only — 25-08 LOAN INSTL 25,000 ✗ bank only — 31-08 MAINT CHG 575 ✗ bank only — 31-08 INTEREST 1,200 ✗ bank only

পাঁচটা মিলল, ছয়টা জায়গায় প্রশ্ন। এবার প্রতিটি প্রশ্নকে শ্রেণিতে ফেলুন:

#00412 1,50,000 খাতায় আছে, ব্যাংকে নেই শ্রেণি ১ → অপেক্ষা 80,200 বনাম 80,250 দুই পাশেই আছে, অঙ্ক আলাদা শ্রেণি ৩ → কার ভুল? EFT IN 1,20,000 ব্যাংকে আছে, খাতায় নেই শ্রেণি ২ → কে পাঠাল? LOAN 25,000 ব্যাংকে আছে, খাতায় নেই শ্রেণি ২ → entry MAINT 575 ব্যাংকে আছে, খাতায় নেই শ্রেণি ২ → entry INTEREST 1,200 ব্যাংকে আছে, খাতায় নেই শ্রেণি ২ → entry

ধাপ ৩ — প্রতিটি প্রশ্নের উত্তর

৮০,২০০ বনাম ৮০,২৫০ — ৫০ টাকার পার্থক্য। অধ্যায় ১১-এ মনে করিয়ে দিয়েছিলাম: ব্যাংক ফোনে “প্রায় ২০০” বলেছিল, খাতায় ২০০ লেখা হয়েছিল। আসল charge ২৫০। আপনার ভুল — ৫০ টাকার সংশোধনী entry লাগবে। এটা কীভাবে match হবে তা নিচে।

EFT IN ১,২০,০০০ — S. AKTER। A/R subledger খুঁজুন: সালমা আক্তার নামে একজন গ্রাহকের ঠিক ১,২০,০০০ টাকার একটা invoice খোলা আছে। নিশ্চিত হয়ে এটা তার পাওনার বিপরীতে receipt। নিশ্চিত না হলে? তাহলে A/R-এ নয় — 2165 Unidentified Receipts-এ, পরে চিহ্নিত হলে সরানো হবে। আন্দাজে কারো নামে টাকা বসাবেন না — ভুল গ্রাহকের পাওনা মুছে যাবে, আর আসল গ্রাহক তাগাদা পেতে থাকবেন।

LOAN INSTL ২৫,০০০। ঋণের schedule দেখুন: এই কিস্তিতে মূল ২০,০০০, সুদ ৫,০০০।

MAINT CHG ৫৭৫, INTEREST ১,২০০। সোজা — bank charge আর সুদ।

ধাপ ৪ — খাতায় entry

শ্রেণি ২ ও ৩-এর প্রতিটির জন্য entry। Posting date statement-এর তারিখ (আগস্টের period খোলা থাকলে); source হলো reconciliation নিজেই:

JV-08-0007 20-08 সালমা আক্তার — EFT receipt Dr 1120 Bank — Prime 1,20,000 Cr 1130 A/R [সালমা আক্তার] 1,20,000 JV-08-0008 25-08 ঋণের কিস্তি, আগস্ট Dr 2510 Bank Loan — Long Term 20,000 Dr 5510 Interest Expense 5,000 Cr 1120 Bank — Prime 25,000 JV-08-0009 31-08 Account maintenance charge Dr 5280 Bank Charges 575 Cr 1120 Bank — Prime 575 JV-08-0010 31-08 Interest credit, আগস্ট Dr 1120 Bank — Prime 1,200 Cr 4510 Interest Income 1,200 JV-08-0011 21-08 Cheque return charge — সংশোধনী (২০০ লেখা, আসল ২৫০) Dr 5280 Bank Charges 50 Cr 1120 Bank — Prime 50

শেষটা লক্ষ করুন — সংশোধনী entry-টা পার্থক্যের অঙ্কে (৫০), মূল entry-কে reverse করে নতুন করে নয়। কারণ মূল entry-র ৮০,০০০ অংশ সম্পূর্ণ ঠিক ছিল; শুধু charge-টা কম ছিল।

Entry-গুলো post হওয়ার পরে ledger:

আগের closing 8,84,800 + JV-08-0007 EFT সালমা 1,20,000 − JV-08-0008 ঋণের কিস্তি 25,000 − JV-08-0009 maint charge 575 + JV-08-0010 সুদ 1,200 − JV-08-0011 return charge সংশোধন 50 ───────── adjusted book balance 9,80,375

ধাপ ৫ — নতুন entry গুলো match করুন

নতুন পাঁচটা line এখন statement-এর সাথে মিলবে:

JV-08-0007 Dr 1,20,000 ◀──▶ 20-08 EFT IN 1,20,000 ✓ JV-08-0008 Cr 25,000 ◀──▶ 25-08 LOAN INSTL 25,000 ✓ JV-08-0009 Cr 575 ◀──▶ 31-08 MAINT CHG 575 ✓ JV-08-0010 Dr 1,200 ◀──▶ 31-08 INTEREST 1,200 ✓ JV-08-0005 Cr 80,200 ┐ 21-08 CHQ RETURN 80,000 ┐ JV-08-0011 Cr 50 ┘ ◀──▶ 21-08 RETURN CHG 250 ┘ ✓ group: 80,250 = 80,250

শেষটা একটা many : many group — ledger-এ দুই line, statement-এ দুই line, দুই পাশের যোগফল ৮০,২৫০। এক-এক করে মেলানো যেত না; group ছাড়া এই মামলা বন্ধ হতো না।

এখন statement-এর প্রতিটি line matched। শুধু ledger-এ একটা unmatched — #00412, যা স্বাভাবিক।

ধাপ ৬ — Reconciliation Statement

BANK RECONCILIATION STATEMENT Prime Bank CA 4471 — ৩১ আগস্ট ২০২৫ ব্যাংকের দিক থেকে Balance as per bank statement 11,30,375 (−) Cheques issued, not yet presented #00412 10-08 PV-08-0001 1,50,000 (1,50,000) (+) Deposits not yet credited — ──────────────────────────────────────────────────────────── Adjusted bank balance 9,80,375 খাতার দিক থেকে Balance as per ledger (before adjustments) 8,84,800 (+) Credited by bank, not in ledger EFT — সালমা আক্তার 1,20,000 Interest credit 1,200 1,21,200 (−) Debited by bank, not in ledger Loan instalment 25,000 Account maintenance charge 575 Cheque return charge — short 50 (25,625) ──────────────────────────────────────────────────────────── Adjusted book balance 9,80,375 ✓

দুই দিক থেকে একই সংখ্যা — ৯,৮০,৩৭৫। Reconciliation সম্পূর্ণ। Balance Sheet-এ Prime Bank দেখাবে ৯,৮০,৩৭৫, statement-এর ১১,৩০,৩৭৫ নয়।

সেপ্টেম্বরে carry forward:

unmatched ledger line: PV-08-0001 #00412 1,50,000 (outstanding since 10-08)

সেপ্টেম্বরের statement-এ CHQ 00412 এলে এটা মিলবে। না এলে? তিন মাস পরেও না এলে সরবরাহকারীকে জিজ্ঞেস করুন — cheque হারিয়ে গেছে হয়তো। ছয় মাস পরে cheque-টা আইনত বাসি (stale); তখন উল্টে দিতে হবে (Dr Bank, Cr A/P) আর নতুন cheque দিতে হবে।

একটা জিনিস reconciliation ধরল না

সব মিলে গেল। কিন্তু অধ্যায় ১০-এর সেই বিদ্যুৎ বিলের মতো একটা ভুল এই মাসেও লুকিয়ে থাকতে পারত: ধরুন ২৮-০৮-এর ৩,৫০,০০০ বেতনটা ভুল করে Dr 5220 Office Rent দিয়ে লেখা হয়েছিল। Bank পাশ ৩,৫০,০০০ Cr — statement-এর সাথে নিখুঁত মিলবে। Reconciliation কিছুই বলবে না। এটা bank-এর যাচাই, খরচের নয়।


৫. Implementation — Software ও Database

Statement import — bank-এর line গুলো আপনার database-এ

সবার আগে statement-টা একটা table-এ আনতে হবে, ব্যাংক যে format-এই দিক (CSV, Excel, MT940, CAMT.053, API feed):

bank_statement_lines id BIGINT PK cash_bank_account_id BIGINT FK -- kind = bank import_batch_id BIGINT FK line_no INT -- statement-এ ক্রম value_date DATE -- ব্যাংক যেদিন কার্যকর করেছে description TEXT reference VARCHAR(100) NULL -- cheque নম্বর, transfer ref (parse করা) amount_signed DECIMAL(18,4) -- আপনার দৃষ্টিতে: deposit +, withdrawal − balance_after DECIMAL(18,4) NULL -- ব্যাংকের running balance, থাকলে line_hash CHAR(64) NOT NULL -- idempotency match_status VARCHAR(20) -- unmatched | matched | excluded match_group_id BIGINT FK NULL UNIQUE (cash_bank_account_id, line_hash) INDEX (cash_bank_account_id, value_date) INDEX (cash_bank_account_id, match_status)

তিনটি সিদ্ধান্ত এখানে:

১. amount_signed — আয়না উল্টানো একবারই। Import-এর সময় deposit-কে +, withdrawal-কে − করুন, আপনার দৃষ্টিতে। এরপর matching-এ ledger-এর debit - credit আর statement-এর amount_signed সরাসরি তুলনীয় — কোনো if ছাড়া।

২. line_hash — একই file দুবার import হলেও একই line দুবার ঢুকবে না।

line_hash = SHA256(value_date, amount_signed, description, balance_after, line_no)

balance_after আর line_no থাকায় একই দিনে একই অঙ্কের দুটো আসল লেনদেন (দুটো ৫০০ টাকার charge) আলাদা থাকে, অথচ একই file আবার import করলে UNIQUE constraint চুপচাপ থামিয়ে দেয়। ON CONFLICT DO NOTHING — এটাই idempotent import (অধ্যায় ৪৬)।

৩. import_batch_id — কে, কখন, কোন file। ভুল file import হলে পুরো batch-টা (যদি কোনো line matched না হয়) মুছে ফেলা যায়।

bank_statement_imports id BIGINT PK cash_bank_account_id BIGINT FK imported_by BIGINT imported_at TIMESTAMP source_file_name VARCHAR(255) source_format VARCHAR(20) -- csv | mt940 | camt053 | api period_from DATE period_to DATE opening_balance DECIMAL(18,4) -- statement যা বলেছে closing_balance DECIMAL(18,4) line_count INT

Import-এর পরে একটা যাচাই: opening_balance + SUM(amount_signed) == closing_balance। না মিললে file-টা অসম্পূর্ণ বা parse ভুল — matching শুরুই করবেন না।

Match group — মেলানোর একক

Section ২-এ বলেছি matching-এর একক line নয়, group:

bank_recon_match_groups id BIGINT PK cash_bank_account_id BIGINT FK reconciliation_id BIGINT FK NULL -- কোন reconciliation-এ বন্ধ হলো match_type VARCHAR(10) -- auto | manual matched_by BIGINT NULL matched_at TIMESTAMP ledger_total DECIMAL(18,4) statement_total DECIMAL(18,4) note TEXT NULL CHECK (ledger_total = statement_total)
bank_recon_match_ledger_lines bank_recon_match_statement_lines match_group_id BIGINT FK match_group_id BIGINT FK journal_line_id BIGINT FK statement_line_id BIGINT FK UNIQUE (journal_line_id) UNIQUE (statement_line_id)

দুটো UNIQUE গুরুত্বপূর্ণ — একটা line একটাই group-এ থাকতে পারে। একই cheque দুটো statement line-এর সাথে মেলানো database-ই ঠেকাবে।

CHECK (ledger_total = statement_total) — group তৈরির সময়েই যোগফল সমান হতে হবে। ৮০,২০০ বনাম ৮০,২৫০ group-টা ৫০ টাকার entry post হওয়ার আগে তৈরি করাই যাবে না। এটা ইচ্ছাকৃত: “প্রায় মিলছে” বলে কিছু নেই।

Ledger-এর দিকে journal_lines-এ দুটো column যোগ হয়:

journal_lines (+) reconciled_at TIMESTAMP NULL -- match group-এ ঢোকার সময় match_group_id BIGINT FK NULL

এতে “1120-এর unmatched line” একটা সরল query: WHERE account_id = 1120 AND match_group_id IS NULL

Reconciliation — একটা তারিখে দাঁড়িয়ে হিসাব

bank_reconciliations id BIGINT PK cash_bank_account_id BIGINT FK as_of_date DATE statement_balance DECIMAL(18,4) -- ব্যাংক যা বলেছে ledger_balance DECIMAL(18,4) -- সব adjustment-এর পরে unmatched_ledger_dr DECIMAL(18,4) -- deposits in transit unmatched_ledger_cr DECIMAL(18,4) -- outstanding cheques status VARCHAR(20) -- open | completed completed_by BIGINT NULL completed_at TIMESTAMP NULL UNIQUE (cash_bank_account_id, as_of_date)

সম্পূর্ণ (completed) হওয়ার শর্ত — দুটো:

১. as_of_date পর্যন্ত statement-এর কোনো unmatched line নেই (ব্যাংকের প্রতিটি কথার হিসাব দেওয়া হয়েছে) ২. statement_balance + unmatched_ledger_dr (deposits in transit) - unmatched_ledger_cr (outstanding cheques) == ledger_balance

দ্বিতীয়টা section ৪-এর “দুই দিক থেকে একই সংখ্যা”-র সূত্র রূপ। প্রথম শর্ত না মানলে দ্বিতীয়টা কাকতালীয়ভাবে মিলে যেতে পারে — তাই দুটোই লাগে।

Unmatched ledger line থাকা reconciliation আটকায় না — সেটাই শ্রেণি ১। কিন্তু সেগুলো তালিকায় থাকবে, বয়সসহ।

Auto-matching engine

মানুষ line ধরে মেলাতে পারে, কিন্তু মাসে পাঁচশ লেনদেন হলে তা কেউ করবে না। Engine-এর কাজ নিশ্চিত জোড়াগুলো নিজে মেলানো, আর সন্দেহজনক গুলো মানুষের জন্য রেখে দেওয়া।

autoMatch(account, upto_date): L = unmatched ledger lines (account, posting_date <= upto_date, আগের মাসের carry forward সহ) S = unmatched statement lines (account, value_date <= upto_date) -- নিয়ম ১: reference + অঙ্ক (cheque নম্বর) প্রতিটি s in S যার reference আছে: c = L-এ যাদের instrument_no == s.reference এবং signed == s.amount_signed যদি |c| == 1: group(c, s, auto) -- নিয়ম ২: অঙ্ক + তারিখের জানালা, একটাই প্রার্থী প্রতিটি s in S (এখনো unmatched): c = L-এ যাদের signed == s.amount_signed এবং |posting_date - s.value_date| <= 5 দিন যদি |c| == 1: group(c, s, auto) যদি |c| > 1: রেখে দিন — মানুষ ঠিক করবে -- নিয়ম ৩: এক statement line = কয়েকটা ledger line (deposit slip) প্রতিটি s in S (এখনো unmatched, deposit): c = L-এ Dr line, posting_date in [s.value_date - 5, s.value_date] subset খুঁজুন যার যোগফল == s.amount_signed, আকার <= 5 যদি ঠিক একটা subset পাওয়া যায়: group(subset, s, auto) -- বাকি সব unmatched: মানুষের জন্য

তিনটি নীতি এই engine-এ:

নিশ্চিত না হলে মেলাবেন না দুটো ৫০,০০০ টাকার cheque একই সপ্তাহে — কোনটা কোন line? ভুল auto-match একটা না-মেলার চেয়ে অনেক খারাপ, কারণ সেটা "মিলে গেছে" বলে লুকিয়ে থাকে। নিয়মের ক্রম শক্ত থেকে নরম reference-ভিত্তিক মিল সবচেয়ে নির্ভরযোগ্য, আগে চালান। অঙ্ক-ভিত্তিক মিল দুর্বল, পরে। subset খোঁজায় সীমা রাখুন ৫০টা line-এর মধ্যে যোগফল-মেলা subset খোঁজা exponential। আকার ≤ ৫, তারিখের জানালা ছোট — নইলে engine ঝুলে যাবে, আর কাকতালীয় মিল বাড়বে।

মানুষের হাতে যা থাকে, তার জন্য একটা পর্দা: বাঁয়ে unmatched ledger, ডানে unmatched statement, দুই পাশে কয়েকটা বেছে “match” — যোগফল সমান না হলে button নিষ্ক্রিয়।

Bank-only line থেকে entry

শ্রেণি ২-এর item-এর জন্য reconciliation পর্দা থেকেই entry তৈরি হওয়া উচিত — statement line বেছে “post as…”:

postFromStatementLine(s, account_id, party = null): entry = JV, posting_date = s.value_date (period খোলা থাকলে, নইলে খোলা period-এর প্রথম দিন) source_type = 'bank_statement_line', source_id = s.id যদি s.amount_signed > 0: Dr bank gl account s.amount_signed Cr account_id (party) s.amount_signed নইলে: Dr account_id (party) -s.amount_signed Cr bank gl account -s.amount_signed post(entry) group([entry-র bank line], [s], auto) -- সাথে সাথেই matched

কোন description-এ কোন account — এটা একটা নিয়মের table, কোডে if "CHARGE" in description নয়:

bank_statement_rules cash_bank_account_id BIGINT FK NULL -- NULL = সব bank pattern VARCHAR(200) -- 'MAINT CHARGE', 'INT CREDIT', 'LOAN INSTL%' account_id BIGINT FK -- 5280, 4510, 2510 ... party_type / party_id -- থাকলে priority INT

Engine নিয়ম মিললে প্রস্তাব করবে, নিজে post করবে না — মানুষ এক click-এ অনুমোদন দেবেন। ঋণের কিস্তির মূল/সুদ ভাগ, বা অচেনা EFT-র party — এগুলো নিয়মের table জানে না।

অচেনা receipt2165 Unidentified Receipts (LIABILITY)। এই account-এর ব্যালেন্স রাতের যাচাইয়ে থাকবে: ৩০ দিনের বেশি পুরনো line থাকলে সতর্কতা। কারণ যে টাকা কারো নামে নেই, সেই গ্রাহক তাগাদা পাচ্ছেন আর রাগ করছেন।

তালা — reconciled line-এ হাত দেওয়া

reverse(entry): যদি entry-র কোনো line-এর match_group_id IS NOT NULL: reject("line X reconciled on <date>; unmatch first, with reason") unmatch(group, reason, by): যদি group.reconciliation_id IS NOT NULL এবং সেই reconciliation.status == 'completed': reject("belongs to completed reconciliation <date>; reopen first") group-এর সব line-এর match_group_id = NULL, reconciled_at = NULL audit log: group, reason, by, at -- অধ্যায় ৪৫ group.status = 'unmatched' -- মুছবেন না, ইতিহাস reopen(reconciliation, reason, by): শুধু বিশেষ অনুমতিতে (অধ্যায় ৪৮) status = 'open', audit log

স্তরগুলো লক্ষ করুন — match, তার উপরে completed reconciliation, তার উপরে period lock (অধ্যায় ৪২)। প্রতিটি স্তর খুলতে আলাদা কারণ আর আলাদা অনুমতি।

Report ও যাচাই

Reconciliation Statement — section ৪-এর ছকটা bank_reconciliations + unmatched line থেকে সরাসরি তৈরি হয়; আলাদা করে সংরক্ষণের দরকার নেই, তবে completed হওয়ার মুহূর্তের একটা snapshot (PDF) রাখা ভালো — audit-এ চাইবে।

Unmatched items aging:

SELECT l.voucher_no, l.posting_date, l.debit, l.credit, CURRENT_DATE - l.posting_date AS age_days FROM posted_lines l WHERE l.account_id = :bank_gl AND l.match_group_id IS NULL ORDER BY l.posting_date;

রাতের স্বাস্থ্য-যাচাইয়ে:

১১. bank ledger-এ ৯০ দিনের বেশি পুরনো unmatched Cr line → সতর্কতা (হারানো cheque?) ১২. bank ledger-এ ৭ দিনের বেশি পুরনো unmatched Dr line → সতর্কতা (deposit clear হয়নি?) ১৩. statement-এ ৩০ দিনের বেশি পুরনো unmatched line → সতর্কতা (reconciliation পিছিয়ে) ১৪. 2165 Unidentified Receipts-এ ৩০ দিনের বেশি পুরনো line → সতর্কতা ১৫. শেষ completed reconciliation ৪৫ দিনের বেশি আগে → সতর্কতা

Test হিসেবে

test "একই statement দুবার import করলে line দ্বিগুণ হয় না": import(file) n1 = count(statement_lines) import(file) n2 = count(statement_lines) assert n1 == n2 test "reference মিললে auto-match হয়, দুটো প্রার্থী থাকলে হয় না": payment(bank, cheque '00412', 1,50,000) statement_line('CHQ 00412', -1,50,000) autoMatch() assert matched(ওই payment) payment(bank, 50,000, date 10-08) payment(bank, 50,000, date 12-08) statement_line('WDL', -50,000, date 11-08) autoMatch() assert unmatched(দুটোই) -- মানুষের জন্য test "group-এর যোগফল না মিললে তৈরি হয় না": assert rejects group(ledger = [80,200], statement = [80,000, 250]) test "reconciled line reverse করা যায় না, unmatch ছাড়া": match(line, statement_line) assert rejects reverse(line.entry) unmatch(group, reason = 'wrong match') assert ok reverse(line.entry) test "reconciliation সম্পূর্ণ হয় শুধু statement সব matched হলে": ... একটা statement line unmatched রেখে assert rejects complete(reconciliation) postFromStatementLine(সেই line, 5280) assert ok complete(reconciliation) assert reconciliation.statement_balance + unmatched_ledger_dr - unmatched_ledger_cr == reconciliation.ledger_balance

৬. Financial Statement Impact

Reconciliation নিজে কোনো statement-এ কিছু বদলায় না — এটা যাচাই। কিন্তু reconciliation থেকে জন্মানো entry গুলো বদলায়:

Income Statement 5280 Bank Charges +625 (575 + 50) 5510 Interest Expense +5,000 4510 Interest Income +1,200 Balance Sheet 1120 Bank — Prime 9,80,375 ← adjusted book, statement নয় 1130 A/R −1,20,000 (সালমার পাওনা মিটল) 2510 Bank Loan −20,000 2165 Unidentified Receipts (থাকলে — liability, কারণ কারো টাকা)

সবচেয়ে গুরুত্বপূর্ণ: Balance Sheet-এ Bank-এর সংখ্যা adjusted book balance — ৯,৮০,৩৭৫। কেউ যদি “ব্যাংক তো ১১,৩০,৩৭৫ বলছে, সেটাই লিখি” বলেন — না। ওই ১,৫০,০০০ সরবরাহকারীর হাতে থাকা cheque-এ বাঁধা; ওটা আপনার টাকা নয়।

Reconciliation না করলে কী হয়? Balance Sheet-এ Bank দেখাবে ৮,৮৪,৮০০ — যার মধ্যে সালমার ১,২০,০০০ নেই (A/R ফুলে আছে), ঋণের কিস্তি নেই (Loan বেশি দেখাচ্ছে), সুদ নেই। প্রতিটি সংখ্যা একটু একটু ভুল, আর Trial Balance নিখুঁত মিলছে।


৭. Common Developer Mistakes

ভুলকী ঘটেসঠিক পথ
Balance Sheet-এ statement balanceoutstanding cheque-এর টাকা নিজের বলে দেখায়adjusted book balance
পার্থক্যের অঙ্কে একটা “adjustment” entryকারণ চিরতরে হারায়, জালিয়াতি লুকায়প্রতিটি item-এর নিজের entry
Timing item-এর জন্য entryপরের মাসে দুবার গোনা হয়শ্রেণি ১-এ entry নয়, অপেক্ষা
শুধু অঙ্ক মিললেই auto-matchদুটো একই অঙ্কের line ভুল জোড়ায়একটাই প্রার্থী হলে; নইলে মানুষ
Import-এ line_hash নেইএকই file দুবার = সব line দ্বিগুণhash + UNIQUE + ON CONFLICT DO NOTHING
আয়না উল্টানো একাধিক জায়গায়কোথাও ভুলে গেলে সব উল্টোimport-এ একবার, amount_signed
Matched line নিঃশব্দে reversecompleted reconciliation মিথ্যা হয়ে যায়unmatch বাধ্যতামূলক, কারণ সহ
Opening না মিলিয়ে শুরুগত মাসের অমিল এ মাসে ভুল ব্যাখ্যা পায়আগে opening, তারপর line
Carry forward নেইজুলাইয়ের cheque আগস্টে মেলানোর প্রার্থী নয়সব unmatched line, মাস নির্বিশেষে
অচেনা receipt আন্দাজে গ্রাহকের নামেভুল গ্রাহকের পাওনা মুছে যায়2165 Unidentified, চিহ্নিত হলে সরান
if "CHARGE" in description কোডেনতুন ব্যাংক, নতুন ভাষা — কোড বদলাতে হয়bank_statement_rules table
Subset-match-এ সীমা নেইengine ঝুলে যায়, কাকতালীয় মিল বাড়েআকার ≤ ৫, তারিখের জানালা
”প্রায় মিলছে” group৫০ টাকার পার্থক্য চাপা পড়েCHECK (ledger_total = statement_total)
Reconciliation-কে মাসিক কাজ ভাবাতিন মাস জমলে কেউ আর করে নাচলমান অবস্থা; feed থাকলে দৈনিক

প্রথম দুটো সবচেয়ে বিপজ্জনক, কারণ দুটোই তাড়াতাড়ি মিলিয়ে ফেলার প্রলোভন থেকে আসে। Reconciliation-এর মূল্য তার কষ্টেই — প্রতিটি পার্থক্যের কারণ খুঁজতে হয়। যে software সেই কষ্ট এড়ানোর একটা button দেয় (“auto-adjust difference”), সে reconciliation-কে নিরর্থক করে দেয়।


৮. Exercises

সেট ক — শ্রেণি চিনুন

প্রতিটির শ্রেণি (১–৪), কোন দিকে সমন্বয়, আর entry লাগবে কিনা:

১। ২৭ তারিখে সরবরাহকারীকে দেওয়া cheque, statement-এ নেই। ২। Statement-এ "SMS ALERT CHG 115", খাতায় নেই। ৩। ৩০ তারিখে জমা দেওয়া cheque, statement-এ নেই। ৪। Statement-এ ৩৫,০০০ টাকার একটা withdrawal, কেউ চেনে না। ৫। খাতায় ৭২,০০০, statement-এ ২৭,০০০ — একই cheque। ৬। Statement-এ গ্রাহকের ৯০,০০০ EFT, খাতায় নেই, গ্রাহক চেনা। ৭। Statement-এ গ্রাহকের ৯০,০০০ EFT, খাতায় নেই, কে পাঠাল জানা নেই। ৮। একই deposit statement-এ দুবার credit হয়েছে। ৯। খাতায় একই receipt দুবার লেখা, statement-এ একবার। ১০। জমা দেওয়া cheque ফেরত এসেছে, statement-এ দুই line (মূল + charge)।

সেট খ — Reconciliation Statement তৈরি করুন

১১। City Bank (1125), ৩১ আগস্ট: ledger closing 4,00,000 statement closing 4,12,600 statement-এ কিন্তু খাতায় নেই: cheque book charge 400 interest credit 1,000 খাতায় কিন্তু statement-এ নেই: ৩০-০৮ cheque দেওয়া #10021, 12,000 দুই দিক থেকে reconciliation statement লিখুন। কোন entry লাগবে? adjusted balance কত? Balance Sheet-এ City Bank কত দেখাবে? ১২। সেপ্টেম্বরের statement-এ #10021 এল — কিন্তু ২১,০০০ টাকায়। সরবরাহকারীর bill দেখে নিশ্চিত হলেন cheque-টা আসলেই ২১,০০০-এর ছিল; খাতায় ১২,০০০ লেখা হয়েছিল। (ক) এটা কোন শ্রেণি? (খ) পার্থক্য ৯,০০০ — অধ্যায় ১০-এর সূত্র কী বলে? (গ) কোন entry লাগবে? (ঘ) সেপ্টেম্বরে match group-টা কেমন হবে — কোন কোন line? (ঙ) আগস্টের completed reconciliation কি ভুল ছিল? কেন নয়? ১৩। একটা account-এ opening-ই মিলছে না: ledger 5,00,000, statement B/F 5,60,000। জুলাইয়ের reconciliation-এ outstanding cheque ছিল 35,000। বাকি 25,000 কোথায়? ধাপে ধাপে কী খুঁজবেন?

সেট গ — Matching

১৪। Ledger unmatched: Dr 40,000 (05-08), Dr 40,000 (07-08), Dr 25,000 (07-08)। Statement: Dep 40,000 (06-08), Dep 65,000 (08-08)। auto-match engine কী করবে, ধাপে ধাপে? শেষে কী unmatched থাকবে? ১৫। Statement-এ একটা line 1,05,000 Dep। Ledger-এ ওই সপ্তাহে Dr line: 50,000, 30,000, 25,000, 55,000, 20,000। কয়টা subset যোগফলে 1,05,000 হয়? engine কী করা উচিত? ১৬। একটা matched cheque-এর reversal দরকার (সরবরাহকারী ভুল)। কী কী ধাপ, কোন ক্রমে, আর প্রতিটিতে কে অনুমতি দেবেন? ১৭। একটা ledger line ৭ মাস ধরে unmatched Cr 18,000 (cheque)। কী করবেন? entry লিখুন।

সেট ঘ — নকশা

১৮। তিনটা ব্যাংকের তিন রকম CSV — column-এর নাম আলাদা, তারিখের format আলাদা, একটা Dr/Cr column দেয়, একটা signed amount। import layer-টা কীভাবে নকশা করবেন যেন নতুন ব্যাংক যোগ করতে কোড না, config বদলাতে হয়? ১৯। line_hash-এ balance_after রাখলে একটা সমস্যা: ব্যাংক যদি পুরনো line-এর পরে একটা back-dated line ঢোকায় (হয়), তার পরের সব balance_after বদলে যায়। তখন কী হবে? বিকল্প কী? ২০। Period lock (অধ্যায় ৪২): আগস্ট বন্ধ, কিন্তু সেপ্টেম্বরে আগস্টের statement এসে দেখা গেল একটা 575 charge post হয়নি। posting_date কী হবে? reconciliation as_of_date কী হবে? দুটো একই না হলে সূত্রটা কীভাবে মিলবে? ২১। Bank feed প্রতি ঘণ্টায় line পাঠায়। reconciliation "মাসিক completed" ধারণাটা তখন কেমন হবে? দৈনিক completed? নাকি "as_of" ছাড়া চলমান matched/unmatched-ই যথেষ্ট?

উত্তর আছে Workbook-এর Answer Key, অধ্যায় ১২-এ।


৯. Developer Challenge

একটি BankReconciliationService নকশা করুন — import থেকে completed reconciliation পর্যন্ত।

যা যা নকশা করবেন:

১. Import pipeline — format-নিরপেক্ষ একটা normalized line (value_date, amount_signed, description, reference), per-bank parser config, line_hash idempotency, আর import-পরবর্তী opening + Σ == closing যাচাই। একটা parser ভুল হলে (সব অঙ্ক উল্টো) আপনি কীভাবে টের পাবেন, batch মুছতে পারবেন কি?

২. Match group model — bank_recon_match_groups ও দুই পাশের line table, UNIQUECHECK সহ। many:many group তৈরির API কেমন? যোগফল না মিললে কী ফেরত দেবেন যাতে UI বলতে পারে “৫০ টাকা কম”?

৩. Auto-match engine — অন্তত তিনটি নিয়ম, ক্রমসহ, আর প্রতিটির “একটাই প্রার্থী” শর্ত। subset-search-এর সীমা কী, আর সীমা ছাড়ালে কী করবেন? engine-এর ফলাফল কি সরাসরি group হবে, নাকি “প্রস্তাব” যা মানুষ অনুমোদন দেবেন? দুটোর trade-off লিখুন।

৪. postFromStatementLine()bank_statement_rules দিয়ে account প্রস্তাব, party নির্ধারণ (চেনা হলে A/R, অচেনা হলে 2165), period বন্ধ থাকলে posting_date-এর নিয়ম, আর post-এর সাথে সাথে auto-match।

৫. তালার তিন স্তর — match, completed reconciliation, period lock। প্রতিটি খুলতে কী লাগে (কারণ, অনুমতি, audit)? reverse() কোন স্তরে আটকাবে?

৬. complete(reconciliation) — দুটো শর্ত (statement সব matched; সূত্র মেলে), ব্যর্থ হলে কোনটা কেন তা বলে দেওয়া, আর সফল হলে একটা অপরিবর্তনীয় snapshot।

৭. Unmatched aging report ও রাতের পাঁচটি যাচাই (section ৫)। stale cheque (৬ মাস) স্বয়ংক্রিয়ভাবে reverse হবে, নাকি শুধু সতর্কতা? সিদ্ধান্ত নিন, যুক্তি লিখুন।

৮. Migration — একটা চলমান company-কে এই system-এ আনার দিন কোন কোন তথ্য লাগবে যাতে প্রথম reconciliation সম্ভব হয়? (ইঙ্গিত: শেষ reconciled তারিখ, সেদিনের statement balance, সেদিনের outstanding item-এর তালিকা।) এগুলো কোথায় রাখবেন?

৩ আর ৫ নম্বরটাই আসল পরীক্ষা। Auto-match engine-এ “নিশ্চিত না হলে মেলাবেন না” নীতিটা কোডে ফোটানো — প্রতিটি নিয়মে |candidates| == 1 — এটাই একটা ভালো engine আর একটা বিপজ্জনক engine-এর পার্থক্য। আর তালার স্তরগুলো হলো Part 5-এর পুরো architecture-এর (period lock, reversal, audit trail) প্রথম বাস্তব রূপ।


১০. Summary Card

আয়না

statement Cr / Deposit = আপনার Dr statement Dr / Withdrawal = আপনার Cr উল্টানো একবারই, import-এ → amount_signed

চার শ্রেণি

১ timing খাতায় আছে, ব্যাংকে নেই অপেক্ষা outstanding chq, deposit in transit ২ bank-only ব্যাংকে আছে, খাতায় নেই entry charge, সুদ, direct dr/cr, dishonour ৩ ভুল যেকোনো পাশে সংশোধনী / জানান অঙ্ক উল্টে যাওয়া, দুবার ৪ অজানা — অনুসন্ধান অচেনা withdrawal

Reconciliation Statement

statement balance − outstanding chq + deposits in transit = X ledger balance + bank credits − bank debits ± ভুল = X (একই) Balance Sheet-এ → adjusted book balance, statement নয়

Matching

একক = group (many : many), দুই পাশের যোগফল সমান এক line, এক group — UNIQUE carry forward: সব unmatched line প্রার্থী, মাস নির্বিশেষে completed ⟺ statement সব matched এবং সূত্র মেলে

Auto-match নীতি

reference + অঙ্ক → অঙ্ক + তারিখ → subset (≤ 5) প্রতিটিতে: একটাই প্রার্থী হলে, নইলে মানুষ নিশ্চিত না হলে মেলাবেন না

তালা

matched line → reversal-এর আগে unmatch, কারণ সহ completed recon → reopen লাগে, বিশেষ অনুমতি period lock → অধ্যায় ৪২

যা ধরে না

bank পাশ ঠিক, অন্য পাশ ভুল account / ভুল party bank ছোঁয়া নয় এমন লেনদেন

Developer checklist

□ bank_statement_lines, amount_signed আপনার দৃষ্টিতে □ line_hash + UNIQUE + ON CONFLICT DO NOTHING □ import-এর পরে opening + Σ == closing □ match group: many:many, CHECK(ledger_total = statement_total) □ journal_lines.match_group_id, UNIQUE □ auto-match: |candidates| == 1, নইলে মানুষ □ subset-search-এ আকার ও তারিখের সীমা □ bank_statement_rules table, কোডে string match নয় □ অচেনা receipt 2165-এ, আন্দাজে party নয় □ reverse() matched line-এ reject □ unmatch কারণ সহ, audit log, group মুছবেন না □ complete(): দুটো শর্ত, snapshot □ unmatched aging: Cr > 90 দিন, Dr > 7 দিন □ stale cheque ৬ মাসে reverse □ migration: শেষ reconciled তারিখ + outstanding তালিকা

পরবর্তী অধ্যায়

অধ্যায় ১৩ — Sales ও Revenue: Cash ও Bank-এ টাকা কীভাবে আসে-যায় আর সেটা কীভাবে যাচাই হয় — দুটোই এখন জানা। এবার টাকা আসার সবচেয়ে বড় কারণটায় যাব: বিক্রি। পরের অধ্যায়ে দেখব invoice কীভাবে journal entry হয়, VAT কোথায় যায়, আর সেই প্রশ্নটা যা অধ্যায় ১১-এ উঁকি দিয়েছিল — আয় কখন “হয়”: invoice-এর দিনে, delivery-র দিনে, নাকি টাকা পাওয়ার দিনে?