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

অধ্যায় ১৪ — Accounts Receivable

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

পূর্বশর্ত: অধ্যায় ১৩ (Sales ও Revenue), অধ্যায় ৯ (General Ledger — control account ও subledger), অধ্যায় ১১ (Cash ও Bank)


১. Learning Objective

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

A/R control account আর customer subledger-এর সম্পর্ক — তিন স্তরে — ব্যাখ্যা করতে Receipt allocation: কোন টাকা কোন invoice-এর বিপরীতে — তা নকশা করতে আংশিক payment, এক receipt-এ বহু invoice, বেশি payment — তিনটিই সামলাতে Settlement discount-এর entry লিখতে এবং কেন সেটা revenue-র সময় নয়, তা বলতে Unapplied receipt (on-account) কী, আর কেন সেটা বেশিদিন থাকা বিপজ্জনক, তা বলতে Aging report তৈরি করতে — invoice-ভিত্তিক, as-of তারিখে Customer statement তৈরি করতে Bad debt write-off-এর entry লিখতে (allowance-এর ধারণা অধ্যায় ৫৯-এর জন্য রেখে) Credit limit ও credit hold-এর যুক্তি নকশা করতে তিন-মুখী reconciliation: GL == subledger == open invoice − unapplied — চালাতে

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


২. Concept Explanation

Invoice আর টাকার মাঝের সময়

অধ্যায় ১৩-এ invoice issue হলো, A/R-এ ৪,৯৪,৫০০ বসল, তেইশ দিন পরে টাকা এল, A/R খালি হলো। সহজ। কিন্তু বাস্তবে:

গ্রাহক তিনটা invoice-এর টাকা একসাথে দেয় — কিন্তু মোটের চেয়ে কম আরেকজন অর্ধেক দেয়, বাকিটা "পরের মাসে" একজন বেশি দিয়ে ফেলে একজন ১০ দিনে দিয়ে ২% ছাড় নেয় একজন ছয় মাসেও দেয় না, তারপর ব্যবসা বন্ধ করে দেয়

Accounts Receivable হলো এই পুরো মাঝের সময়টা সামলানোর ব্যবস্থা — কে কত দেবে, কোন invoice-এর জন্য, কতদিন ধরে বাকি, আর কতটা আদৌ পাওয়া যাবে।

তিন স্তর

অধ্যায় ৯-এ control account আর subledger দেখেছি। A/R-এ আসলে তিনটি স্তর:

স্তর ১ GL 1130 Accounts Receivable 1,44,900 স্তর ২ Subledger গ্রাহক-ধরে ব্যালেন্স করিম ট্রেডার্স 1,38,000 রহিম এন্টারপ্রাইজ 6,900 স্তর ৩ Open items invoice-ধরে বকেয়া SV-09-0003 করিম 1,38,000 (due 15-10) SV-09-0008 রহিম 6,900 (due 28-10)

তিনটি স্তর সবসময় সমান যোগফলে থাকতে হবে। স্তর ১ ও ২ অধ্যায় ৯-এ মিলিয়েছি; স্তর ৩ নতুন — আর এটাই aging, statement, allocation — সব কিছুর ভিত্তি।

স্তর ৩ থাকতে হয় কারণ “করিম ১,৩৮,০০০ দেবে” যথেষ্ট নয়; জানতে হবে কোন invoice-এর জন্য, কবে থেকে বকেয়া। দুটো ৫০,০০০ টাকার invoice — একটা গতকালের, একটা ছয় মাস আগের — subledger-এ একই ১,০০,০০০ দেখায়, কিন্তু ব্যবসার জন্য সম্পূর্ণ আলাদা পরিস্থিতি।

Developer-এর দৃষ্টিতে: GL হলো aggregate, subledger হলো GROUP BY customer, আর open items হলো কাঁচা সারি — invoice আর receipt-এর মাঝের link। এই link-টাই allocation।

Receipt allocation — কোন টাকা কোন invoice-এর

একটা receipt এলে দুটো কাজ: journal entry (অধ্যায় ১১-এর Dr Bank, Cr A/R [গ্রাহক]) — আর allocation: এই টাকাটা কোন কোন invoice-এর বিপরীতে।

receipt 1,00,000 করিম ├──▶ SV-09-0003 1,38,000 open → 1,00,000 allocated, 38,000 open থাকল └──▶ (বাকি 0)

Allocation-এর তিনটি রূপ:

আংশিক — একটা invoice-এর কিছু অংশ:

invoice 1,38,000 receipt 1,00,000 → allocated 1,00,000 open 38,000

এক receipt, বহু invoice:

receipt 90,000 → SV-07-0015 80,000 (পুরোটা) → SV-09-0008 6,900 (পুরোটা) → বাকি 3,100 ← ?

বেশি payment — যা কোনো invoice-এর সাথে মেলে না। এটাকে বলে unapplied বা on-account receipt:

3,100 → গ্রাহকের subledger-এ credit, কোনো invoice ছাড়া পরের invoice এলে তার সাথে allocate হবে অথবা ফেরত দিতে হবে

Unapplied receipt-এর journal entry সাধারণ receipt-এর মতোই (Cr A/R [রহিম]) — GL-এর দিক থেকে কিছু আলাদা নয়। শুধু স্তর ৩-এ এটা কোনো invoice-এর সাথে বাঁধা নেই। এই কারণেই স্তর ৩-এর সূত্রটা এমন:

subledger balance == SUM(open invoice) − SUM(unapplied receipt)

Unapplied receipt বেশিদিন থাকা মানে গ্রাহক টাকা দিয়েছেন কিন্তু তার invoice এখনো “বকেয়া” দেখাচ্ছে — তিনি তাগাদা পাবেন, আর রাগ করবেন। অধ্যায় ১২-এর 2165 Unidentified Receipts-এর মতোই: রাতের যাচাইয়ে ৩০ দিনের বেশি পুরনো unapplied থাকলে সতর্কতা।

গ্রাহক না বললে কোন invoice-এ?

গ্রাহক প্রায়ই বলেন না কোন invoice-এর টাকা। তখন দুটো নীতি:

FIFO সবচেয়ে পুরনো open invoice আগে সাধারণ, aging পরিষ্কার রাখে গ্রাহকের কথা remittance advice-এ যা লেখা থাকলে সবসময় এটাই জেতে

Auto-allocate FIFO-তে করুন, কিন্তু ব্যবহারকারী বদলাতে পারবেন — কারণ গ্রাহক কখনো কখনো ইচ্ছে করে একটা বিতর্কিত invoice বাদ রেখে বাকিগুলোর টাকা দেন।

Settlement discount — টাকা তোলার সময়ের ছাড়

অধ্যায় ১৩-এর table-এর দ্বিতীয় column। শর্ত “২/১০ net ৩০”: ১০ দিনে দিলে ২% কম, নইলে ৩০ দিনে পুরোটা।

invoice 1,80,000 (net of advance) গ্রাহক ১০ দিনের মধ্যে দিলেন: 1,80,000 × 98% = 1,76,400 Dr 1120 Bank 1,76,400 Dr 4190 Discount Allowed 3,600 ← contra-revenue Cr 1130 A/R [সালমা] 1,80,000

তিনটি বিষয়:

১. A/R পুরো 1,80,000 credit হয় — invoice সম্পূর্ণ মিটে গেছে ২. Revenue (4110) স্পর্শ হয় না — আয় ছিল invoice-এর দিনে, তখন ছাড়ের কথা জানা ছিল না ৩. ছাড়টা আলাদা account-এ: 4190 Discount Allowed (contra-revenue, Income Statement-এ revenue থেকে বিয়োগ) বা 5xxx expense — নীতির প্রশ্ন

Allocation-এ discount-টাও একটা অংশ: invoice 1,80,000 = receipt 1,76,400 + discount 3,600। Allocation-এর যোগফল invoice-কে পুরো বন্ধ করে।

VAT-এর উপর ছাড়ের প্রভাব দেশভেদে আলাদা (কোথাও VAT-ও কমে, credit note লাগে) — অধ্যায় ৫৫।

Aging — কতদিনের বকেয়া

Aging report প্রতিটি open invoice-কে বয়স অনুযায়ী bucket-এ ফেলে:

bucket মানে ────── ──── Current due date এখনো আসেনি 1–30 due-র পরে ১–৩০ দিন 31–60 61–90 90+ গুরুতর — bad debt-এর প্রার্থী

দুটো সিদ্ধান্ত যা অনেক system ভুল করে:

বয়স due date থেকে, invoice date থেকে নয়। ৩০ দিনের credit term-এ আজকের invoice ৩০ দিন পরেও “current”।

As-of তারিখে। “৩০ সেপ্টেম্বরের aging” মানে ৩০ সেপ্টেম্বর পর্যন্ত issue হওয়া invoice, ৩০ সেপ্টেম্বর পর্যন্ত আসা receipt দিয়ে allocate করে, ৩০ সেপ্টেম্বরের হিসাবে বয়স। আজ চালালে আজকের সংখ্যা আসবে না — তখনকার সংখ্যা আসবে। নইলে গত মাসের aging আজ আবার চালালে বদলে যায়, আর auditor জিজ্ঞেস করেন কেন।

Aging-এর মোট অবশ্যই GL 1130-এর সাথে মিলতে হবে (unapplied সমন্বয় করে)। না মিললে report ভুল, GL নয়।

Customer statement

গ্রাহককে পাঠানো “আপনার হিসাব” — তার subledger ledger, তারিখ-ধরে:

STATEMENT — করিম ট্রেডার্স — অক্টোবর ২০২৫ তারিখ বিবরণ Debit Credit Balance 01-10 Opening 1,38,000 18-10 Receipt RV-10-0003 1,00,000 38,000 31-10 Closing 38,000 বকেয়া invoice: SV-09-0003 15-09 1,38,000 paid 1,00,000 open 38,000 (16 দিন overdue)

দুই অংশ: লেনদেনের ইতিহাস (subledger) আর open items (স্তর ৩)। দ্বিতীয়টাই গ্রাহক দেখেন — “আমি কী দেব”।

Statement হলো reconciliation-এর ভিত্তি (অধ্যায় ৪৪): গ্রাহকের খাতায় আপনি একজন supplier — তার A/P-এর সাথে আপনার A/R মেলা উচিত।

Bad debt — যা আর আসবে না

গ্রাহকের ব্যবসা বন্ধ, ঠিকানা নেই, ছয় মাস কোনো উত্তর নেই। ১২,০০০ টাকার invoice-টা asset হিসেবে বসে আছে, কিন্তু asset-এর কোনো মূল্য নেই। Write-off:

Dr 5530 Bad Debt Expense 12,000 Cr 1130 A/R [মেঘনা ট্রেডার্স] 12,000

Invoice বন্ধ হলো (allocation: write-off ১২,০০০), গ্রাহকের ব্যালেন্স শূন্য, খরচ Income Statement-এ।

এটা সরলতম রূপ (direct write-off)। বড় প্রতিষ্ঠান আগে থেকেই একটা allowance রাখে — “aging-এর 90+ bucket-এর ৫০% সম্ভবত আসবে না” — সেটা অধ্যায় ৫৯। এখন শুধু নিয়ম: write-off একটা allocation, আর তার নিজের অনুমোদন লাগে (অধ্যায় ৪৮) — কারণ এটা দিয়েই চুরি ঢাকা যায়: টাকা নিয়ে invoice write-off করে দাও।

Credit limit ও hold

গ্রাহকের কাছে কত বাকি থাকতে দেবেন — সেটা একটা সীমা:

credit_limit 5,00,000 open balance 3,80,000 নতুন invoice 1,50,000 → 5,30,000 > সীমা → block বা অনুমোদন 90+ overdue আছে? → credit hold: নতুন invoice নয়

এটা accounting নয়, credit control — কিন্তু তথ্যটা A/R থেকেই আসে, আর যাচাইটা invoice issue-র সময় (অধ্যায় ১৩-এর issueInvoice()-এ একটা validation)।


৩. Accounting Rule

Entry

ঘটনাDebitCredit
Receipt (পুরো / আংশিক / unapplied)BankA/R [গ্রাহক]
Settlement discount সহ receiptBank (নিট), Discount AllowedA/R [গ্রাহক] (পুরো)
Advance-এর সমন্বয় (অধ্যায় ১৯)Customer AdvanceA/R [গ্রাহক]
Write-offBad Debt ExpenseA/R [গ্রাহক]
Unapplied ফেরতA/R [গ্রাহক]Bank

Journal entry-তে যা কখনো হয় না

Receipt-এ Revenue (আয় invoice-এ, একবার) Discount-এ Revenue কমানো (আলাদা account)

Allocation

প্রতিটি open invoice-এর জন্য: open = grand_total − SUM(allocated receipt) − SUM(discount) − SUM(credit note) − SUM(write-off) allocation-এর যোগফল ≤ receipt-এর অঙ্ক receipt − allocated = unapplied

তিন-মুখী সূত্র

GL 1130 == SUM(subledger) == SUM(open invoice) − SUM(unapplied)

Aging

বয়স = as_of_date − due_date (invoice_date নয়) as-of তারিখের পরের receipt গোনা হবে না SUM(aging) == GL 1130 (as-of) + unapplied

৪. Real Business Example

অক্টোবর ২০২৫ — টাকা তোলার মাস

১ অক্টোবরে open items:

Invoice গ্রাহক তারিখ due মোট open ─────── ───── ───── ─── ─── ──── SV-07-0015 রহিম এন্টারপ্রাইজ 20-07 19-08 80,000 80,000 ← cheque ফেরত (অধ্যায় ১১) SV-09-0003 করিম ট্রেডার্স 15-09 15-10 1,38,000 1,38,000 SV-09-0008 রহিম এন্টারপ্রাইজ 28-09 28-10 6,900 6,900 SV-24-0311 মেঘনা ট্রেডার্স 11-2024 12-2024 12,000 12,000 ← গত বছরের ──────── GL 1130 2,36,900 আর: সালমা আক্তারের advance 50,000 (2160 — A/R-এ নয়)

০৫-১০ — সালমাকে invoice ২,০০,০০০ + VAT, শর্ত ২/১০ net ৩০; advance সমন্বয়

SV-10-0001 Dr 1130 A/R [সালমা] 2,30,000 Cr 4110 Software Dev. Income 2,00,000 Cr 2140 VAT Payable 30,000 JV-10-0002 advance adjustment (অধ্যায় ১৯-এর আগাম) Dr 2160 Customer Advance 50,000 Cr 1130 A/R [সালমা] 50,000 allocation: SV-10-0001 2,30,000 − advance 50,000 = open 1,80,000

১২-১০ — সালমা ৭ দিনের মধ্যে দিলেন: ১,৮০,০০০ × ৯৮%

RV-10-0001 Dr 1120 Bank — Prime 1,76,400 Dr 4190 Discount Allowed 3,600 Cr 1130 A/R [সালমা] 1,80,000 allocation: SV-10-0001 receipt 1,76,400 + discount 3,600 → open 0 → paid

১৮-১০ — করিম ১,০০,০০০ দিলেন (আংশিক)

RV-10-0003 Dr 1120 Bank — Prime 1,00,000 Cr 1130 A/R [করিম] 1,00,000 allocation: SV-09-0003 1,00,000 → open 38,000 → partially_paid

২২-১০ — রহিম ৯০,০০০ দিলেন, বললেন “সব মিটিয়ে দিলাম”

RV-10-0004 Dr 1120 Bank — Prime 90,000 Cr 1130 A/R [রহিম] 90,000 allocation (FIFO): SV-07-0015 80,000 → open 0 → paid SV-09-0008 6,900 → open 0 → paid unapplied 3,100 ← রহিমকে জানাতে হবে: ফেরত, নাকি পরের invoice-এ?

রহিম “সব মিটিয়েছেন” ভেবেছেন ৯০,০০০; আসলে ৮৬,৯০০। ৩,১০০ তার subledger-এ credit — কোনো invoice ছাড়া।

৩১-১০ — মেঘনা ট্রেডার্স: ব্যবসা বন্ধ, write-off অনুমোদিত

JV-10-0005 (অনুমোদন: finance manager) Dr 5530 Bad Debt Expense 12,000 Cr 1130 A/R [মেঘনা] 12,000 allocation: SV-24-0311 write-off 12,000 → open 0 → written_off

৩১ অক্টোবর — তিন স্তর মেলান

স্তর ১ — GL 1130:

opening 2,36,900 + SV-10-0001 2,30,000 − JV-10-0002 (advance) 50,000 − RV-10-0001 1,80,000 − RV-10-0003 1,00,000 − RV-10-0004 90,000 − JV-10-0005 (write-off) 12,000 ───────── closing 34,900

স্তর ২ — subledger:

করিম ট্রেডার্স 38,000 Dr রহিম এন্টারপ্রাইজ 3,100 Cr ← unapplied সালমা আক্তার 0 মেঘনা ট্রেডার্স 0 ────── 34,900 Dr ✓

স্তর ৩ — open items:

SV-09-0003 করিম open 38,000 unapplied রহিম (3,100) ────── 34,900 ✓

তিনটি স্তর একই সংখ্যায়। এটা রাতের যাচাইয়ের একটা সারি — প্রতিদিন, মাস শেষে নয়।

Aging — ৩১ অক্টোবর

A/R AGING — as of 31-10-2025 গ্রাহক Current 1–30 31–60 61–90 90+ মোট ───── ─────── ──── ───── ───── ─── ─── করিম ট্রেডার্স 38,000 38,000 (SV-09-0003, due 15-10, 16 দিন) রহিম (unapplied) (3,100) ────── 38,000 34,900 ✓ == GL

মেঘনা ৯০+-এ ছিল — write-off-এর পরে নেই। রহিমের ৮০,০০০ ৬১–৯০-এ ছিল — এখন paid। একটা মাসে aging কতটা বদলায় — এটাই এই report-এর কাজ: কোথায় নজর দিতে হবে।

তুলনার জন্য, ৩০ সেপ্টেম্বরের aging (as-of, তখনকার হিসাবে):

করিম 1,38,000 Current রহিম 6,900 Current + 80,000 31–60 (due 19-08, 42 দিন) মেঘনা 12,000 90+ ───────── 2,36,900 ✓

আজ চালালেও ৩০ সেপ্টেম্বরের report এটাই দেখাবে — অক্টোবরের receipt গোনা হবে না।

রহিমের statement

STATEMENT OF ACCOUNT — রহিম এন্টারপ্রাইজ — অক্টোবর ২০২৫ তারিখ বিবরণ Debit Credit Balance 01-10 Opening (SV-07-0015, SV-09-0008) 86,900 Dr 22-10 Receipt RV-10-0004 90,000 3,100 Cr 31-10 Closing 3,100 Cr আপনার হিসাবে 3,100 টাকা জমা আছে। পরের invoice-এ সমন্বয় হবে, অথবা ফেরত চাইলে জানান।

শেষ লাইনটা ছাড়া statement অসম্পূর্ণ — unapplied টাকা গ্রাহককে জানাতে হয়


৫. Implementation — Software ও Database

Allocation table — স্তর ৩-এর হৃদয়

receipt_allocations id BIGINT PK company_id BIGINT FK invoice_id BIGINT FK -- sales_invoices source_type VARCHAR(20) -- receipt | discount | credit_note | write_off | advance source_id BIGINT -- payments.id, credit_notes.id, ... amount DECIMAL(18,4) -- invoice-এর open কমায় allocated_at TIMESTAMP allocated_by BIGINT allocation_date DATE -- aging-এর as-of-এর জন্য CHECK (amount > 0) INDEX (invoice_id) INDEX (source_type, source_id)

একটা receipt ৯০,০০০ → দুটো সারি (৮০,০০০ + ৬,৯০০); বাকি ৩,১০০ কোনো সারিতে নেই — সেটাই unapplied:

unapplied(receipt) = receipt.amount − SUM(allocations WHERE source = receipt)

sales_invoices-এ দুটো column যোগ হয়, derived কিন্তু সংরক্ষিত:

sales_invoices (+) amount_settled DECIMAL(18,4) NOT NULL DEFAULT 0 -- SUM(allocations) open_amount DECIMAL(18,4) NOT NULL -- grand_total − amount_settled

সংরক্ষণ করা হচ্ছে aging query দ্রুত করার জন্য; কিন্তু প্রতিটি allocation-এর সাথে একই transaction-এ update, আর রাতে allocation table থেকে পুনর্গণনা করে মিলিয়ে দেখা।

Allocate — একটা receipt-কে invoice-এ বাঁধা

allocate(receipt, [(invoice, amount)...], by): -- একটাই transaction lock receipt, lock প্রতিটি invoice (FOR UPDATE) -- অধ্যায় ৪৭ যাচাই: receipt.status == 'posted' প্রতিটি invoice.customer_id == receipt.party_id SUM(amount) ≤ unapplied(receipt) প্রতিটি: amount ≤ invoice.open_amount প্রতিটি (invoice, amount): INSERT receipt_allocations (invoice, 'receipt', receipt.id, amount, allocation_date = receipt.doc_date) invoice.amount_settled += amount invoice.open_amount −= amount invoice.status = open_amount == 0 ? 'paid' : 'partially_paid' -- journal entry নেই! GL আগেই ঠিক ছিল (receipt post-এ)

শেষ লাইনটা গুরুত্বপূর্ণ। Allocation GL-এ কিছু বদলায় নাDr Bank, Cr A/R [রহিম] receipt post-এর সময়েই হয়ে গেছে। Allocation শুধু স্তর ৩-এর কাজ। এই কারণেই allocation পরে বদলানো যায় (ভুল invoice-এ বাঁধলে) কোনো reversal ছাড়া — শুধু allocation সারি মুছে নতুন সারি, audit log সহ।

ব্যতিক্রম: settlement discount। সেটা GL বদলায় (4190), তাই discount-সহ receipt post করার সময়েই entry-তে discount line থাকে, আর allocation-এ source_type = 'discount' একটা সারি।

Auto-allocate FIFO

autoAllocate(receipt): remaining = unapplied(receipt) invoices = open invoices of receipt.party, ORDER BY due_date, invoice_date plan = [] প্রতিটি inv in invoices, remaining > 0 থাকা পর্যন্ত: a = min(remaining, inv.open_amount) plan.append((inv, a)); remaining −= a return plan -- ব্যবহারকারীকে দেখান, অনুমোদনে allocate()

Remittance advice থাকলে plan-টা সেখান থেকে; FIFO শুধু default।

Discount-এর যুক্তি

Receipt তৈরির সময়, allocation plan থেকে:

প্রতিটি (inv, amount) in plan: যদি inv.discount_pct > 0 এবং receipt.doc_date ≤ inv.invoice_date + inv.discount_days এবং amount == inv.open_amount × (1 − discount_pct): -- পুরোটা মেটাচ্ছে discount = inv.open_amount − amount entry-তে যোগ: Dr 4190 discount allocation-এ যোগ: (inv, 'discount', discount)

গ্রাহক নিজে ছাড় কেটে দিলে (১,৭৬,৪০০ পাঠালেন) system-কে বুঝতে হবে এটা ১,৮০,০০০-এর invoice-এর ছাড়সহ payment, আংশিক payment নয়। শর্তটাই সেটা বলে দেয়। শর্ত না মিললে (১২ দিনে দিলেন) — আংশিক payment, ৩,৬০০ open থাকে, গ্রাহককে জানান।

Aging query — as-of

WITH open_items AS ( SELECT i.id, i.customer_id, i.due_date, i.grand_total − COALESCE((SELECT SUM(a.amount) FROM receipt_allocations a WHERE a.invoice_id = i.id AND a.allocation_date <= :as_of), 0) AS open_amt FROM sales_invoices i WHERE i.company_id = :c AND i.status <> 'draft' AND i.status <> 'cancelled' AND i.invoice_date <= :as_of ), unapplied AS ( SELECT p.party_id AS customer_id, SUM(p.amount) − COALESCE(SUM(a.amount), 0) AS unapplied_amt FROM payments p LEFT JOIN receipt_allocations a ON a.source_type = 'receipt' AND a.source_id = p.id AND a.allocation_date <= :as_of WHERE p.kind = 'receipt' AND p.party_type = 'customer' AND p.status = 'posted' AND p.doc_date <= :as_of GROUP BY p.party_id ) SELECT c.name, SUM(CASE WHEN :as_of <= o.due_date THEN open_amt END) AS current, SUM(CASE WHEN :as_of − o.due_date BETWEEN 1 AND 30 THEN open_amt END) AS d1_30, SUM(CASE WHEN :as_of − o.due_date BETWEEN 31 AND 60 THEN open_amt END) AS d31_60, SUM(CASE WHEN :as_of − o.due_date BETWEEN 61 AND 90 THEN open_amt END) AS d61_90, SUM(CASE WHEN :as_of − o.due_date > 90 THEN open_amt END) AS d90_plus, −COALESCE(u.unapplied_amt, 0) AS unapplied FROM open_items o JOIN customers c ON c.id = o.customer_id LEFT JOIN unapplied u ON u.customer_id = o.customer_id WHERE o.open_amt > 0 GROUP BY c.name, u.unapplied_amt;

allocation_date <= :as_of — এটাই as-of-এর রহস্য। ৩০ সেপ্টেম্বরের aging-এ অক্টোবরের allocation নেই, তাই রহিমের ৮০,০০০ তখনো open।

সংরক্ষিত open_amount এখানে ব্যবহার হচ্ছে না — কারণ সেটা আজকের সংখ্যা। As-of report সবসময় allocation table থেকে।

Write-off

writeOff(invoice, reason, approved_by): যাচাই: invoice.open_amount > 0 approved_by-র write-off অনুমতি আছে (অধ্যায় ৪৮) reason বাধ্যতামূলক entry = JV, source_type = 'write_off' Dr 5530 Bad Debt Expense open_amount Cr 1130 A/R (party) open_amount post(entry) INSERT receipt_allocations (invoice, 'write_off', entry.id, open_amount) invoice.open_amount = 0; status = 'written_off'

Write-off-এর পরে টাকা এলে? Entry উল্টো (Dr Bank, Cr 5530 বা Cr 4530 Bad Debt Recovered) — invoice আর open হয় না।

তিন-মুখী reconciliation — রাতের যাচাই

১৯. GL 1130 (posted_lines) == SUM(subledger balance by customer) -- অধ্যায় ৯ == SUM(sales_invoices.open_amount WHERE open) − SUM(unapplied receipts) ২০. প্রতিটি invoice: open_amount == grand_total − SUM(receipt_allocations) -- সংরক্ষিত বনাম গণিত ২১. status সামঞ্জস্য: open_amount == 0 ⟺ status IN ('paid', 'written_off', 'cancelled') 0 < open < total ⟺ status == 'partially_paid' ২২. unapplied receipt, ৩০ দিনের বেশি পুরনো → সতর্কতা ২৩. allocation-এর customer ≠ receipt-এর customer → ০ সারি

২৩ নম্বরটা মনে হবে অসম্ভব — allocate() তো যাচাই করে। কিন্তু migration, সরাসরি SQL, bug — অধ্যায় ৭-এর কথা: application-এর যাচাই যথেষ্ট নয়, রাতে আবার দেখুন।

Credit control

issueInvoice() -এ, post-এর আগে: exposure = customer.subledger_balance + inv.grand_total যদি exposure > customer.credit_limit: যদি by-র override অনুমতি নেই: reject("credit limit") নইলে: log override যদি customer.has_overdue(days > 90) এবং customer.credit_hold: reject("credit hold")

Test হিসেবে

test "allocation GL বদলায় না": receive(রহিম, 90,000) g1 = balance(1130) allocate(receipt, [(SV-07-0015, 80,000), (SV-09-0008, 6,900)]) assert balance(1130) == g1 assert unapplied(receipt) == 3,100 test "discount-সহ receipt invoice পুরো বন্ধ করে": inv = issue(সালমা, 1,80,000 open, 2/10) receive(সালমা, 1,76,400, date = +7 দিন, allocate → inv) assert inv.status == 'paid' assert Δ 4190 == +3,600 assert Δ 4110 == 0 test "as-of aging পরের receipt গোনে না": issue(inv, 15-09, due 15-10, 1,38,000) receive(18-10, 1,00,000 → inv) assert aging(as_of = 30-09)[inv] == 1,38,000, bucket current assert aging(as_of = 31-10)[inv] == 38,000, bucket 1–30 test "তিন স্তর সমান": ... যেকোনো কাজের পরে assert gl(1130) == Σ subledger == Σ open − Σ unapplied test "অন্য গ্রাহকের invoice-এ allocate করা যায় না": assert rejects allocate(receipt(রহিম), [(invoice(করিম), 1)])

৬. Financial Statement Impact

INCOME STATEMENT (অক্টোবর) 4110 Software Dev. Income 2,00,000 4190 Discount Allowed (3,600) ← contra-revenue 5530 Bad Debt Expense 12,000 BALANCE SHEET (৩১ অক্টোবর) 1130 Accounts Receivable 34,900 ← net of unapplied 2160 Customer Advance 0 (সালমারটা সমন্বয় হয়েছে) CASH FLOW (operating) গ্রাহক থেকে প্রাপ্তি 3,66,400 (1,76,400 + 1,00,000 + 90,000)

একটা সূক্ষ্মতা: রহিমের ৩,১০০ unapplied A/R-এর ভিতরে credit হিসেবে আছে, তাই 1130 দেখাচ্ছে ৩৪,৯০০ (৩৮,০০০ − ৩,১০০)। কড়া হিসাবে এটা একটা liability (গ্রাহককে ফেরত দেওয়ার দেনা)। Balance Sheet-এ গ্রাহক-ধরে credit balance গুলো liability-তে reclassify করা ভালো অভ্যাস — অধ্যায় ১১-এর overdraft-এর মতোই: ব্যালেন্স যেদিকে, ঘর সেদিকে।

Aging আর allowance-এর সম্পর্ক (অধ্যায় ৫৯): 90+ bucket যত বড়, Balance Sheet-এর A/R তত কম বিশ্বাসযোগ্য।


৭. Common Developer Mistakes

ভুলকী ঘটেসঠিক পথ
Allocation table নেই, শুধু invoice.paid = trueআংশিক, বহু-invoice, unapplied — কিছুই সম্ভব নয়receipt_allocations
Allocation-এ journal entryA/R দুবার creditallocation GL স্পর্শ করে না
Unapplied টাকা “কোথাও” রাখাগ্রাহক তাগাদা পান, টাকা দিয়েওsubledger-এ credit, statement-এ দেখান, ৩০ দিনে সতর্কতা
Discount-এ Revenue কমানোinvoice-এর মাসের আয় পরের মাসে বদলায়4190 Discount Allowed
Aging invoice_date থেকেcredit term-এর ভিতরেই “overdue”due_date থেকে
Aging আজকের open_amount থেকেগত মাসের report আজ বদলে যায়as-of: allocation_date <= :as_of
Aging মোট ≠ GL, কেউ দেখে নাreport ভুল, বিশ্বাস হারায়রাতের যাচাই ১৯
open_amount সংরক্ষিত, পুনর্গণনা নেইএকটা bug-এ চিরতরে ভুলরাতে allocation থেকে মেলান
Write-off যে কেউ করতে পারেচুরি ঢাকার সহজ পথআলাদা অনুমতি, কারণ, audit
Write-off-এর পরে টাকা এলে invoice reopenhistory জটBad Debt Recovered, invoice বন্ধই
Auto-allocate FIFO বদলানো যায় নাবিতর্কিত invoice-এ টাকা চলে যায়plan দেখান, ব্যবহারকারী বদলান
Credit limit invoice post-এর পরে যাচাইinvoice হয়ে গেছে, আর ফেরানো যায় নাissueInvoice()-এর validation-এ
গ্রাহকের credit balance asset-এliability asset-এর ভিতরে লুকায়Balance Sheet-এ reclassify

প্রথমটাই মূল — paid একটা boolean নয়, একটা table। যে system-এ invoice-এ is_paid column আছে, সেখানে আংশিক payment এলেই কেউ একটা paid_amount column যোগ করে, তারপর দুটো invoice-এর এক receipt এলে receipt_id যোগ করে, তারপর… ছয় মাস পরে সেটা একটা allocation table হয়, শুধু খারাপভাবে।


৮. Exercises

সেট ক — allocation ও entry

১। গ্রাহকের তিনটা open invoice: A 40,000 (due 01-09), B 25,000 (due 15-09), C 60,000 (due 01-10)। ১০-১০-এ 70,000 এল, কিছু বলেননি। FIFO allocation লিখুন। কোনটা paid, কোনটা partial, কত open? ২। ১ নম্বরে গ্রাহক remittance-এ লিখেছেন "B ও C-এর জন্য"। এবার? A কেন বাদ — এটা কি সন্দেহজনক? ৩। Invoice 1,15,000 (VAT সহ), শর্ত ২/১০। গ্রাহক ৮ দিনে 1,12,700 পাঠালেন। entry ও allocation। Revenue কি বদলাল? ৪। ৩ নম্বরে গ্রাহক ১৪ দিনে 1,12,700 পাঠালেন। এবার? গ্রাহককে কী বলবেন? ৫। গ্রাহক 50,000 বেশি দিয়েছেন, ফেরত চাইলেন। entry লিখুন। allocation table-এ কী হবে? ৬। ৯ মাস পুরনো 8,000 টাকার invoice write-off। entry। দুই মাস পরে গ্রাহক 8,000 পাঠালেন। entry।

সেট খ — aging

৭। ৩১-১০-এ open items: P due 20-10 15,000 Q due 05-11 30,000 R due 25-08 12,000 S due 02-08 9,000 unapplied receipt (একই গ্রাহকের) (5,000) bucket-ধরে aging লিখুন। মোট কত? GL 1130 কত হওয়া উচিত? ৮। ৭ নম্বরের data, কিন্তু as-of ৩০-০৯। R-এর একটা 4,000 টাকার receipt ছিল ১৫-১০-এ (৭-এ 12,000 তার পরের open)। ৩০-০৯-এ R কত, কোন bucket? ৯। Aging মোট 2,40,000, GL 1130 2,52,000। পার্থক্য 12,000। সম্ভাব্য কারণ তিনটি লিখুন, আর প্রতিটি যাচাইয়ের query-র যুক্তি।

সেট গ — কোনটা ভুল

১০। allocate() একটা JV post করে: Dr A/R / Cr A/R — "invoice বন্ধ করতে" ১১। aging query: `CURRENT_DATE - invoice_date` ১২। Receipt 90,000, allocation 86,900, বাকি 3,100 "Misc Income"-এ ১৩। Settlement discount: Dr Bank 1,76,400 / Cr Sales 3,600 / Cr A/R 1,80,000 (লক্ষ করুন — এটা balanced নয়; কী ভুল, আর কী ভুল হলে balanced হতো?) ১৪। write_off() যে কোনো user চালাতে পারে, reason optional ১৫। Credit limit যাচাই receipt-এর সময়

সেট ঘ — নকশা

১৬। receipt_allocations-এ source_type পাঁচটা। প্রতিটির জন্য: GL বদলায় কি? কোন document থেকে আসে? উল্টানো যায় কি, কীভাবে? ১৭। একটা receipt ভুল invoice-এ allocate হয়েছে, গত মাসে, আর সেই মাসের aging report auditor-কে দেওয়া হয়ে গেছে। ঠিক করবেন কীভাবে? as-of aging কি বদলাবে? বদলানো কি উচিত? ১৮। Statement পাঠানোর পরে গ্রাহক বলছেন "আমার হিসাবে আপনার একটা invoice নেই, আর একটা receipt আপনার নেই।" দুই পাশের পার্থক্য খোঁজার ধাপ লিখুন — অধ্যায় ১২-এর reconciliation-এর সাথে মিল কোথায়? ১৯। বহু-currency গ্রাহক (অধ্যায় ৫৪-এর আগাম): USD invoice, BDT receipt। allocation কোন currency-তে? পার্থক্য (exchange gain/loss) কোথায়?

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


৯. Developer Challenge

একটি ReceivablesService নকশা করুন — receipt allocation থেকে aging, statement, write-off, credit control পর্যন্ত।

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

১. receipt_allocations schema, পাঁচটি source_type সহ। প্রতিটি source-এর জন্য “উল্টানো” মানে কী — allocation মোছা (audit সহ), নাকি reversal entry — আর কোনটা কখন?

২. allocate() — lock, যাচাই, allocation, সংরক্ষিত open_amount/status update — একটাই transaction। দুজন একই receipt একসাথে allocate করলে কী হবে? দুজন একই invoice-এ দুটো receipt একসাথে?

৩. Settlement discount-এর সনাক্তকরণ — গ্রাহক ছাড় কেটে পাঠালে system কীভাবে বুঝবে এটা ছাড়সহ পুরো payment, আংশিক payment নয়? শর্তের ১ দিন পরে এলে কী? ছাড়ের সিদ্ধান্ত কে নেবেন — system, নাকি মানুষ?

৪. As-of aging — query, bucket-এর সীমা config-এ, unapplied আলাদা column, মোট == GL যাচাই। ৫০০ গ্রাহক, ৫ বছরের invoice — কত দ্রুত চলবে? কী index লাগবে?

৫. Customer statement — দুই অংশ (ইতিহাস + open items), unapplied-এর বার্তা, আর “statement পাঠানো হয়েছে” record রাখা (কবে, কোন ব্যালেন্সে) — যাতে গ্রাহকের বিতর্কে ফিরে দেখা যায়।

৬. writeOff() — অনুমতি, কারণ, entry, allocation, আর write-off-এর পরে টাকা এলে। allowance পদ্ধতির (অধ্যায় ৫৯) জন্য এখন কী রেখে দেবেন যাতে পরে ভাঙতে না হয়?

৭. Credit control — limit, hold, override অনুমতি, override-এর log। exposure-এ কি unapplied বাদ যাবে? PDC (অধ্যায় ১১) হাতে থাকলে?

৮. রাতের পাঁচটি যাচাই (section ৫)। ১৯ নম্বর ব্যর্থ হলে — তিনটি স্তরের কোনটা ভুল তা কীভাবে বের করবেন?

১ আর ৪ নম্বরটাই আসল পরীক্ষা। Allocation হলো স্তর ৩ — GL-এর নিচের একটা স্তর যা GL বদলায় না অথচ ব্যবসার সব প্রশ্নের উত্তর সেখানে। আর as-of aging হলো “report-এর সংখ্যা সময়ের সাথে বদলায় না” — এই নীতির প্রথম বাস্তব রূপ, যা অধ্যায় ৪৯-এর Report Engine-এর ভিত্তি।


১০. Summary Card

তিন স্তর

GL 1130 == Σ subledger (গ্রাহক) == Σ open invoice − Σ unapplied প্রতিদিন যাচাই

Allocation

receipt post → Dr Bank / Cr A/R [গ্রাহক] (GL) allocate → receipt_allocations সারি (GL নয়) unapplied = receipt − Σ allocated (subledger-এ credit) FIFO default, remittance advice জেতে

Settlement discount

Dr Bank (নিট) Dr 4190 Discount Allowed Cr A/R (পুরো) Revenue অপরিবর্তিত; allocation = receipt + discount

Aging

বয়স = as_of − due_date (invoice_date নয়) as-of: allocation_date <= as_of Σ aging == GL (as-of)

Write-off

Dr 5530 Bad Debt Expense Cr A/R [গ্রাহক] allocation source = write_off; আলাদা অনুমতি + কারণ পরে টাকা এলে: Bad Debt Recovered, invoice reopen নয়

Developer checklist

□ paid boolean নয় — receipt_allocations table □ allocation GL স্পর্শ করে না (discount ছাড়া) □ open_amount সংরক্ষিত + রাতে পুনর্গণনা □ unapplied: subledger credit, statement-এ বার্তা, ৩০ দিনে সতর্কতা □ discount সনাক্তকরণ: তারিখ + অঙ্ক == open × (1 − pct) □ aging due_date থেকে, as-of, Σ == GL □ statement: ইতিহাস + open items, পাঠানোর record □ write-off: অনুমতি, কারণ, audit □ credit limit/hold issueInvoice()-এ □ গ্রাহকের credit balance → Balance Sheet-এ liability □ রাতে: তিন স্তর, open == গণিত, status সামঞ্জস্য, unapplied বয়স, cross-customer

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

অধ্যায় ১৫ — Purchase: বিক্রির পুরো ছবিটা এখন দাঁড়িয়ে গেছে — invoice, revenue, VAT, A/R, allocation, aging। পরের অধ্যায়ে সবকিছু উল্টে দেব: supplier-এর bill আসে, খরচ (বা asset) হয়, VAT এবার আপনার পাওনা, আর দেনা বসে Accounts Payable-এ। কাঠামো একই, দিক উল্টো — আর কয়েকটা নতুন সমস্যা: bill-এর নম্বর আপনার নয়, একই bill দুবার আসে, আর bill-এর তারিখ, পাওয়ার তারিখ, খরচের তারিখ — তিনটে আলাদা।