অধ্যায় ১৪ — 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,000Invoice বন্ধ হলো (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
| ঘটনা | Debit | Credit |
|---|---|---|
| Receipt (পুরো / আংশিক / unapplied) | Bank | A/R [গ্রাহক] |
| Settlement discount সহ receipt | Bank (নিট), Discount Allowed | A/R [গ্রাহক] (পুরো) |
| Advance-এর সমন্বয় (অধ্যায় ১৯) | Customer Advance | A/R [গ্রাহক] |
| Write-off | Bad Debt Expense | A/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 entry | A/R দুবার credit | allocation 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 reopen | history জট | 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_allocationsschema, পাঁচটিsource_typeসহ। প্রতিটি source-এর জন্য “উল্টানো” মানে কী — allocation মোছা (audit সহ), নাকি reversal entry — আর কোনটা কখন?২.
allocate()— lock, যাচাই, allocation, সংরক্ষিতopen_amount/statusupdate — একটাই 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 + discountAging
বয়স = 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-এর তারিখ, পাওয়ার তারিখ, খরচের তারিখ — তিনটে আলাদা।