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

অধ্যায় ২০ — Income Statement

Volume 1 · Part 3 — Financial Statements · Chapter 20

পূর্বশর্ত: অধ্যায় ৩ (permanent বনাম temporary account), অধ্যায় ১০ (Trial Balance — opening / movement / closing), অধ্যায় ১৭ ও ১৮ (কোন মাসে কোন আয়-খরচ)


১. Learning Objective

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

Income Statement কী মাপে — "এই সময়কালে ব্যবসা কতটা কামাল" — আর কী মাপে না, তা বলতে Trial Balance-এর 4xxx ও 5xxx সারি থেকে একটা multi-step Income Statement হাতে বানাতে Gross revenue → net revenue → gross profit → operating profit → net profit — পাঁচটি ধাপ পড়তে Contra-revenue (4180, 4190), discount received (4540), non-operating (45xx, 55xx) — কোন লাইনে বসে, কেন এক মাস, YTD, গত মাস — প্রতিটি column একই function-এর একটা date range, তা দেখাতে কেন P&L account-এর closing balance নয়, period movement — আর কোথায় দুটো এক Statement-এর layout-কে COA থেকে আলাদা রাখতে: statement_lines — data, code নয় প্রতিটি লাইন থেকে account → ledger → entry → document পর্যন্ত drill-down নকশা করতে "% of revenue" column, rounding, তুলনামূলক column — report engine-এর ছোট সিদ্ধান্তগুলো নিতে Net profit-কে Balance Sheet (3300) আর Cash Flow (indirect method)-এর সাথে বাঁধতে

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


২. Concept Explanation

Part 2 যেখানে থামল

নয় মাস ধরে একটা software company-র প্রতিটি ঘটনা লিখেছি — invoice, receipt, bill, payment, accrual, prepaid, note। প্রতিটি অধ্যায়ের শেষে section ৬-এ একটা ছোট “INCOME STATEMENT” ছিল — দুই-তিন লাইন, সেই অধ্যায়ের account গুলো। এখন সেই টুকরোগুলো এক পাতায়: ডিসেম্বরে ব্যবসা কেমন চলল?

অধ্যায় ১-এর ছবিটা মনে করুন — Balance Sheet ছবি (একটা দিন), Income Statement ভিডিও (দুটো তারিখ)। ভিডিওর দৈর্ঘ্য যেকোনো হতে পারে: এক মাস, এক quarter, অর্থবছরের শুরু থেকে আজ (YTD), পুরো বছর। প্রতিটি সংখ্যা ওই দুই তারিখের মাঝের নড়াচড়া — শুরুর দিনের কোনো ব্যালেন্স এতে নেই।

নামগুলো এক: Income Statement, Profit & Loss (P&L), Statement of Profit or Loss (IFRS), লাভ-ক্ষতির হিসাব। এই বইয়ে “Income Statement”, কথ্যভাবে “P&L”।

একটা প্রশ্ন, একটা বিয়োগ

Revenue − Expense = Profit (বা Loss)

অধ্যায় ২-এর সমীকরণের ডান টুকরো। এতটুকুই হলে অধ্যায় লাগত না। লাগে কারণ পাঠক শুধু শেষ সংখ্যাটা চান না — চান জানতে লাভটা কোথা থেকে এল: মূল ব্যবসা থেকে, না ব্যাংকের সুদ থেকে? খরচ কি কাজের খরচ, না অফিস চালানোর? তাই বিয়োগটা ধাপে ধাপে করা হয়।

Multi-step — পাঁচটি ধাপ

Gross Revenue 4110 + 4120 + 4130 ← বিক্রি, invoice-এর অঙ্ক (VAT ছাড়া) − Returns & Allowances, Discounts 4180, 4190 ← contra-revenue ──────────────────────────────── NET REVENUE ← "আসল" বিক্রি ① − Direct Cost / Cost of Sales 51xx ← যা না করলে বিক্রিটা হতো না ──────────────────────────────── GROSS PROFIT ← প্রতি টাকা বিক্রিতে কত থাকে ② − Operating Expenses 52xx ← অফিস চালানোর খরচ ──────────────────────────────── OPERATING PROFIT ← মূল ব্যবসার লাভ ③ + Other Income 45xx, 4540 ← সুদ, ছাড়, asset বিক্রির লাভ − Other Expenses 55xx ← ঋণের সুদ, ক্ষতি ──────────────────────────────── PROFIT BEFORE TAX ④ − Income Tax ← বছর শেষে provision (অধ্যায় ২৬, ৫৫) ──────────────────────────────── NET PROFIT ← Equity-তে যা যোগ হবে ⑤

প্রতিটি subtotal একটা আলাদা প্রশ্নের উত্তর:

① Net revenue গ্রাহক শেষ পর্যন্ত কত রাখলেন (ফেরত-ছাড় বাদে) ② Gross profit কাজ করে কত থাকে — pricing ঠিক আছে? (gross margin %) ③ Operating profit মূল ব্যবসা নিজে দাঁড়ায়? ব্যাংক-সুদ, একবারের লাভ বাদে ④ Profit before tax কর দেওয়ার আগে ⑤ Net profit মালিকের

Single-step হলো এর সরল রূপ — সব আয় এক জায়গায়, সব খরচ এক জায়গায়, একটাই বিয়োগ। ছোট প্রতিষ্ঠান, tax return, কিছু দেশের ছক। দুটোর সংখ্যা এক, সাজানো আলাদা। Software-এ দুটোই একই data থেকে — layout-এর প্রশ্ন, section ৫।

কোন account কোন লাইনে — COA-ই বলে দেয়

অধ্যায় ৫-এর COA-তে ফাঁক রাখার তৃতীয় প্রতিদান (প্রথম দুটো: নতুন account ঢোকানো, code পড়েই প্রকার বোঝা): code-এর range-ই statement-এর লাইন।

4100–4199 Operating Revenue ─┐ 4180, 4190 contra │ ① 4500–4599 Non-operating Revenue │ ④ 4540 Discount Received ─┘ ④ (বা contra-expense — নীতি, section ৩) 5100–5199 Direct Cost ② 5200–5299 Operating Expense ③ 5500–5599 Non-operating Expense ④

কিন্তু code range hardcode করবেন না — অধ্যায় ৩-এর সেই সতর্কতা: পাঁচটি account_type hardcode নিরাপদ, তার নিচে কিছু নয়। Group account (4100, 5200) আর sub_type-ই mapping; section ৫-এ statement_lines table এটা data হিসেবে রাখে। কোনো account কোনো লাইনে না পড়লে report ভুল চুপচাপ হয় — এটা এই অধ্যায়ের সবচেয়ে বড় bug, section ৭।

Contra — ঋণাত্মক আয়, কিন্তু আয়ের ঘরে

অধ্যায় ১৪ ও ১৯-এ 4190 Discount Allowed আর 4180 Sales Returns আলাদা account রেখেছি, revenue কমাইনি — কারণ “কত বিক্রি, কত ফেরত” আলাদা দেখা দরকার। Income Statement-এই তার প্রতিদান:

Software Development Income 6,00,000 Maintenance & Support Income 10,000 Consulting Income 40,000 ───────── Gross Revenue 6,50,000 Less: Returns & Allowances (30,000) ← debit balance, revenue-র ঘরে, বন্ধনীতে Less: Discount Allowed 0 ───────── Net Revenue 6,20,000

Contra account-এর ব্যালেন্স তার প্রকারের উল্টো দিকে (revenue হয়ে debit)। Statement-এ সেটা আয়ের section-এই, ঋণাত্মক। Expense-এ নিয়ে গেলে gross margin মিথ্যা বলে — ফেরত আসা বিক্রি “বিক্রি” থেকে যায়।

Cash নয়, accrual — আবার

Income Statement-এ কোথাও cash নেই। ডিসেম্বরে সাগর লিমিটেডের ১,০০,০০০ advance এসেছে (অধ্যায় ১৯) — statement-এ শূন্য। নদী গ্রুপের ৩,০০,০০০ revenue (অধ্যায় ১৭) — টাকা আসেনি, statement-এ পুরোটা। Subcontractor-এর ৪০,০০০ — bill আসেনি, accrual (অধ্যায় ১৮) — statement-এ আছে।

Part 2-র প্রতিটি “কোন মাসে” সিদ্ধান্ত এই এক পাতায় এসে জমা হয়েছে। Income Statement-এর মান ঠিক ততটুকু, যতটুকু recognition আর cut-off ঠিক। Statement নিজে কিছু হিসাব করে না — শুধু যোগ করে। ভুল মাসে post হওয়া একটা bill statement-এ ধরা পড়বে না; পড়বে অধ্যায় ১০-এর তুলনামূলক report-এ (“গত মাসে বিদ্যুৎ ২৩,৪০০, এ মাসে ১৩,৩০০ কেন?”)।

Movement, closing নয় — এক সূক্ষ্মতা যা report engine ভাঙে

অধ্যায় ১০-এর Trial Balance-এর তিন জোড়া column: opening, movement, closing। Balance Sheet নেয় closing; Income Statement নেয় movement — শুধু ওই দুই তারিখের মাঝের debit ও credit।

5230 Electricity opening (১ ডিসে) movement Dr movement Cr closing (৩১ ডিসে) অর্থবছর জুলাই থেকে 65,600 25,300 12,000 78,900 ডিসেম্বরের Income Statement-এ: 25,300 − 12,000 = 13,300 ← movement YTD (জুলাই–ডিসেম্বর)-এ: 78,900 ← closing == জুলাই থেকে movement

দুটো এক হয় শুধু একটা ক্ষেত্রে: date range-এর শুরু অর্থবছরের প্রথম দিন — কারণ অধ্যায় ৩ অনুযায়ী temporary account বছরের শুরুতে শূন্য থেকে শুরু করে (অধ্যায় ২৬-এ closing entry)। বাকি সব ক্ষেত্রে closing দিলে আগের মাসগুলো ঢুকে পড়ে। Developer-দের এই ভুল এত সাধারণ যে section ৭-এ প্রথম সারিতে। নিয়ম সহজ: Income Statement = range-এর movement, সবসময়; range অর্থবছরের শুরু থেকে হলে সেটা এমনিতেই YTD।

তুলনা ছাড়া একটা সংখ্যা অর্থহীন

“ডিসেম্বরে লাভ ৯০,৮০০” — ভালো না খারাপ? জানার একটাই উপায়: পাশে আরেকটা column। রীতি:

এই মাস | গত মাস | YTD এই বছর | YTD গত বছর (বা budget — অধ্যায় ৩৩)

প্রতিটি column একই function, আলাদা date range। এটাই section ৫-এর নকশার কেন্দ্র — চারটি query নয়, একটা movement(from, to) চারবার। আর ”% of net revenue” column — প্রতিটি লাইন ÷ net revenue — gross margin, operating margin এখান থেকেই।

By nature বনাম by function — একটা নীতির সিদ্ধান্ত

খরচ দুইভাবে সাজানো যায়:

by nature (প্রকৃতি) by function (কাজ) ────────────────── ───────────────── Salary 3,50,000 Cost of sales 2,50,000 Rent 65,000 Admin expense 2,15,000 Depreciation 8,000 Selling expense 60,700 ...

IFRS (IAS 1) দুটোই মানে। আমাদের COA মূলত by nature (5210 Salary, 5220 Rent), তার উপরে একটা হালকা function-ভাগ (5100 Direct বনাম 5200 Operating) — ছোট service company-র জন্য যথেষ্ট। পুরো function-ভিত্তিক statement চাইলে account-এর সাথে cost center লাগে (অধ্যায় ৩৪) — একই 5210 Salary-র একাংশ cost of sales, একাংশ admin। এখানে account-এর group-ই যথেষ্ট; শুধু জেনে রাখুন এই সিদ্ধান্ত COA-র, report-এর নয়।

যা এই অধ্যায়ে নেই

Income tax provision বছর শেষে, অধ্যায় ২৬; হার ও নিয়ম অধ্যায় ৫৫ Other Comprehensive Income (revaluation, FX translation) — অধ্যায় ৫৪-র পরে; এই বইয়ে সংক্ষেপে EBITDA statutory লাইন নয়; management report — operating profit + 5290 + 5510 Segment / cost center P&L অধ্যায় ৩৪, ৩৫ Closing entry (4xxx/5xxx শূন্য করা, 3300 → 3200) অধ্যায় ২৬

৩. Accounting Rule

গঠন

Net Revenue = Σ Operating Revenue − Σ Contra-revenue Gross Profit = Net Revenue − Σ Direct Cost Operating Profit = Gross Profit − Σ Operating Expense Profit Before Tax = Operating Profit + Σ Other Income − Σ Other Expense Net Profit = Profit Before Tax − Income Tax

যাচাই — Trial Balance-এর সাথে

Net Profit == Σ (credit − debit) movement, সব REVENUE ও EXPENSE account, একই range == Trial Balance-এর "নিট লাভ Equity-তে" (অধ্যায় ১০, section ৬)

কোন সংখ্যা

Income Statement (from, to) = posted_lines WHERE posting_date ∈ [from, to] শুধু REVENUE, EXPENSE movement — opening নয়, closing নয় range-এর শুরু == অর্থবছরের শুরু ⇒ movement == closing (তখনই, শুধু তখনই)

চিহ্ন

account_type স্বাভাবিক দিক signed (Dr−Cr) statement-এ দেখাবে REVENUE credit ঋণাত্মক + (= credit − debit) contra-revenue debit ধনাত্মক − আয়ের ঘরে বন্ধনীতে EXPENSE debit ধনাত্মক + খরচের ঘরে (বিয়োগ হবে) contra-expense credit ঋণাত্মক − খরচের ঘরে বন্ধনীতে

কোন লাইনে — নীতি

Accountলাইনকেন
4110–4130Gross revenueমূল ব্যবসার বিক্রি
4180 Returns, 4190 Discount AllowedLess: from revenuecontra-revenue; gross margin সৎ থাকে
51xxDirect costবিক্রির সাথে সরাসরি বাঁধা
52xxOperating expenseচালানোর খরচ
4510 Interest, 4520 GainOther incomeমূল ব্যবসা নয়
4540 Discount ReceivedOther income (এই বইয়ে)বিকল্প নীতি: contra-expense; বেছে স্থির থাকুন
5510 Interest, 5520 LossOther expenseমূল ব্যবসা নয়
5530 Bad DebtOperating expenseবিক্রির ঝুঁকি — চালানোর খরচ
5290 DepreciationOperating expense(management report-এ EBITDA-র জন্য আলাদা দেখানো যায়)

অলঙ্ঘনীয়

প্রতিটি REVENUE / EXPENSE account ঠিক একটা লাইনে — unmapped account = report ভুল, চুপচাপ Statement কোনো হিসাব করে না — শুধু যোগ; মাস ঠিক করে recognition আর cut-off (অধ্যায় ১৭, ১৮) VAT কোথাও নেই — 2140 / 1160 Balance Sheet-এর (অধ্যায় ১৩)

৪. Real Business Example

ডিসেম্বর ২০২৫ — Trial Balance-এর 4xxx ও 5xxx

অধ্যায় ১০-এর query, period = ডিসেম্বর ২০২৫; শুধু REVENUE ও EXPENSE সারি। Opening column আছে — অর্থবছর ১ জুলাই থেকে — কিন্তু এই অধ্যায়ে সেটা পড়ব না; movement-ই সব। (জুলাই–অক্টোবরের সংখ্যাগুলো Part 2-র উদাহরণ থেকে যতটা আসে ততটা, বাকি ধরে নেওয়া — YTD column-এর জন্য।)

ADJUSTED TRIAL BALANCE (অংশ) — ডিসেম্বর ২০২৫ সংখ্যা: টাকা Code Account Opening Movement Dr Movement Cr Closing (১ ডিসে) (৩১ ডিসে) ──── ───────────────────────── ────────── ─────────── ─────────── ────────── 4110 Software Development Income 26,00,000 Cr — 6,00,000 32,00,000 Cr 4120 Maintenance & Support Income 30,000 Cr — 10,000 40,000 Cr 4130 Consulting Income 86,000 Cr — 40,000 1,26,000 Cr 4180 Sales Returns & Allowances 0 30,000 — 30,000 Dr 4190 Discount Allowed 3,600 Dr — — 3,600 Dr 4510 Interest Income 6,600 Cr — 1,500 8,100 Cr 4540 Discount Received 1,840 Cr — — 1,840 Cr 5110 Developer Salary — Billable 11,00,000 Dr 2,20,000 — 13,20,000 Dr 5120 Cloud & Hosting Cost 1,25,000 Dr 25,000 — 1,50,000 Dr 5130 Third-party Software 5,000 Dr 5,000 — 10,000 Dr 5210 Salary & Allowance 6,50,000 Dr 1,30,000 — 7,80,000 Dr 5220 Office Rent 3,25,000 Dr 65,000 — 3,90,000 Dr 5230 Electricity 65,600 Dr 25,300 12,000 78,900 Dr 5240 Internet & Telephone 40,000 Dr 16,000 8,000 48,000 Dr 5250 Office Supplies 17,200 Dr 4,200 — 21,400 Dr 5260 Travel & Conveyance 32,200 Dr 6,500 — 38,700 Dr 5270 Professional Fee 1,25,000 Dr 40,000 — 1,65,000 Dr 5280 Bank Charges 3,275 Dr 700 — 3,975 Dr 5290 Depreciation 8,000 Dr 8,000 — 16,000 Dr 5510 Interest Expense 25,000 Dr 5,000 — 30,000 Dr 5530 Bad Debt Expense 12,000 Dr — — 12,000 Dr

কয়েকটি সারি চেনা লাগবে:

4110 6,00,000 নদী গ্রুপ 3,00,000 (অধ্যায় ১৭, recognition) + ডিসেম্বরের অন্য invoice 3,00,000 4120 10,000 ABC Ltd-এর maintenance 1,20,000 ÷ 12 (অধ্যায় ১৩, deferred থেকে) 4180 30,000 করিম 20,000 + সালমা 10,000 — দুটো credit note (অধ্যায় ১৯) 5230 25,300 / 12,000 reversal, DESCO bill, accrual — নিট 13,300 (অধ্যায় ১৮) 5240 16,000 / 8,000 reversal, ISP bill, accrual — নিট 8,000 5130 5,000 license 60,000 ÷ 12 (অধ্যায় ১৮, prepaid থেকে) 5270 40,000 UI subcontractor accrual — bill জানুয়ারিতে (অধ্যায় ১৮) 5290 8,000 laptop 2,40,000 ÷ 36 + furniture 80,000 ÷ 60 (অধ্যায় ২৫-এ entry, ৩০-এ নীতি) 5110 / 5210 বেতন 3,50,000 — billable 2,20,000, বাকি 1,30,000 (ভাগ অধ্যায় ২৯-এ; এখানে ধরা) 4510 1,500 ব্যাংক সুদ; 5510 5,000 ঋণের কিস্তির সুদ (অধ্যায় ১২)

লক্ষ করুন 5230 আর 5240-এর movement দুই column-এ — reversal credit-এ, bill আর accrual debit-এ। Statement-এ নিট যাবে (13,300, 8,000); কিন্তু drill-down-এ তিনটি entry-ই দেখা যাবে। এটাই “কেন 5240-এ ১৬,০০০ debit অথচ খরচ ৮,০০০” — অধ্যায় ১৮ না পড়া কেউ এখানে থমকাবেন।

ধাপে ধাপে — হাতে

ধাপ ১ — প্রতিটি সারির নিট, statement-এর চিহ্নে:

REVENUE: credit − debit 4110: 6,00,000 − 0 = 6,00,000 4180: 0 − 30,000 = (30,000) ← contra, ঋণাত্মক EXPENSE: debit − credit 5230: 25,300 − 12,000 = 13,300 5240: 16,000 − 8,000 = 8,000

ধাপ ২ — লাইনে বসানো, subtotal:

Gross revenue 6,00,000 + 10,000 + 40,000 = 6,50,000 Net revenue 6,50,000 − 30,000 − 0 = 6,20,000 ① Direct cost 2,20,000 + 25,000 + 5,000 = 2,50,000 Gross profit 6,20,000 − 2,50,000 = 3,70,000 ② 59.7% Operating exp. 1,30,000 + 65,000 + 13,300 + 8,000 + 4,200 + 6,500 + 40,000 + 700 + 8,000 + 0 = 2,75,700 Operating profit 3,70,000 − 2,75,700 = 94,300 ③ 15.2% Other, নিট 1,500 + 0 − 5,000 = (3,500) Profit before tax 94,300 − 3,500 = 90,800 ④ 14.6% Income tax বছর শেষে = — Net profit = 90,800 ⑤

ধাপ ৩ — Trial Balance দিয়ে যাচাই:

Σ credit movement (4xxx, 5xxx) = 6,00,000 + 10,000 + 40,000 + 1,500 + 12,000 + 8,000 = 6,71,500 Σ debit movement (4xxx, 5xxx) = 30,000 + 2,20,000 + 25,000 + 5,000 + 1,30,000 + 65,000 + 25,300 + 16,000 + 4,200 + 6,500 + 40,000 + 700 + 8,000 + 5,000 = 5,80,700 credit − debit = 90,800 ✓

দুটো পথ, এক সংখ্যা। Layout যত জটিলই হোক, শেষ লাইনটা layout-নিরপেক্ষ — এটাই section ৫-এর রাতের যাচাই।

নভেম্বরের পাশে

একই query, range নভেম্বর। অধ্যায় ১৭–১৮ থেকে চেনা সংখ্যা: 4110-এ নদী গ্রুপ 1,20,000, 5230-এ দুই মাসের বিদ্যুৎ 23,400, 5270-এ 50,000, 4540-এ 1,840।

INCOME STATEMENT সফটওয়্যার কোম্পানি লিমিটেড ডিসেম্বর ২০২৫ (তুলনায় নভেম্বর ২০২৫, এবং ১ জুলাই – ৩১ ডিসেম্বর ২০২৫) টাকা ডিসে ২০২৫ % rev নভ ২০২৫ % rev YTD জুলাই–ডিসে % rev ────────── ───── ───────── ───── ────────────── ───── REVENUE Software Development Income 6,00,000 5,20,000 32,00,000 Maintenance & Support Income 10,000 10,000 40,000 Consulting Income 40,000 0 1,26,000 ────────── ───────── ────────── Gross Revenue 6,50,000 5,30,000 33,66,000 Less: Sales Returns & Allowances (30,000) 0 (30,000) Less: Discount Allowed 0 0 (3,600) ────────── ───────── ────────── NET REVENUE 6,20,000 100.0 5,30,000 100.0 33,32,400 100.0 DIRECT COST Developer Salary — Billable 2,20,000 2,20,000 13,20,000 Cloud & Hosting Cost 25,000 25,000 1,50,000 Third-party Software 5,000 5,000 10,000 ────────── ───────── ────────── 2,50,000 40.3 2,50,000 47.2 14,80,000 44.4 GROSS PROFIT 3,70,000 59.7 2,80,000 52.8 18,52,400 55.6 OPERATING EXPENSES Salary & Allowance 1,30,000 1,30,000 7,80,000 Office Rent 65,000 65,000 3,90,000 Electricity 13,300 23,400 78,900 Internet & Telephone 8,000 8,000 48,000 Office Supplies 4,200 3,100 21,400 Travel & Conveyance 6,500 9,800 38,700 Professional Fee 40,000 50,000 1,65,000 Bank Charges 700 575 3,975 Depreciation 8,000 8,000 16,000 Bad Debt Expense 0 0 12,000 ────────── ───────── ────────── 2,75,700 44.5 2,97,875 56.2 15,53,975 46.6 OPERATING PROFIT / (LOSS) 94,300 15.2 (17,875) (3.4) 2,98,425 9.0 OTHER INCOME / (EXPENSE) Interest Income 1,500 1,200 8,100 Discount Received 0 1,840 1,840 Interest Expense (5,000) (5,000) (30,000) ────────── ───────── ────────── (3,500) (1,960) (20,060) PROFIT / (LOSS) BEFORE TAX 90,800 14.6 (19,835) (3.7) 2,78,365 8.4 Income Tax — — — ────────── ───────── ────────── NET PROFIT / (LOSS) 90,800 14.6 (19,835) (3.7) 2,78,365 8.4 ══════════ ═════════ ══════════ Income tax provision বছর শেষে (৩০ জুন ২০২৬)। সংখ্যা নিকটতম টাকায়।

এই পাতাটা পড়া

নভেম্বরে ক্ষতি, ডিসেম্বরে লাভ — কেন? Revenue-র সারি: 5,20,000 → 6,00,000। কারণটা অধ্যায় ১৭-এ — নদী গ্রুপের project নভেম্বরে ২৫% সম্পূর্ণ (1,20,000), ডিসেম্বরে milestone (3,00,000)। খরচ প্রায় সমান দুই মাসে (2,50,000 + 2,75,700 বনাম 2,50,000 + 2,97,875)। Service company-র লাভ মাসে-মাসে দোলে, কারণ revenue recognition দোলে, খরচ দোলে না। এজন্যই YTD column — ছয় মাসে 2,78,365, ৮.৪% — মাসের দোলা মুছে যায়।

Gross margin 52.8% → 59.7%। Direct cost একই (2,50,000), revenue বেশি। Billable developer-দের ক্ষমতা নভেম্বরে পুরো ব্যবহার হয়নি — অথবা তাদের কাজ নভেম্বরে unbilled/deferred-এ ছিল আর ডিসেম্বরে recognized হলো। দ্বিতীয়টাই সত্যি (অধ্যায় ১৭)। Management-এর প্রশ্ন এখান থেকে শুরু, statement-এ উত্তর নেই — উত্তর contract position report-এ।

Electricity 23,400 → 13,300। অধ্যায় ১৮-এর গল্প — নভেম্বরে অক্টোবরের bill ঢুকেছিল (accrual করা হয়নি)। তুলনামূলক column-এ এটা চোখে পড়ে; একা ডিসেম্বরের statement-এ পড়ত না। এটাই অধ্যায় ১০-এর “তুলনামূলক report ভুল ধরার সবচেয়ে কার্যকর যন্ত্র” — Income Statement-এর গত মাসের column সেই যন্ত্র।

Operating loss (17,875) কিন্তু other income আছে — নভেম্বরে discount received 1,840 আর সুদ 1,200 লাভের অংশ, কিন্তু operating profit-এর নিচে। কেউ যদি 4540-কে operating revenue-তে রাখতেন, নভেম্বরের operating loss কম দেখাত — ব্যবসা যেমন চলছে তার চেয়ে ভালো। Multi-step-এর মূল কাজ এই আলাদা করা।

Depreciation 8,000, Bad debt 0, Sales returns 30,000 — তিনটি সারি, তিনটি অধ্যায়ের সিদ্ধান্তের ফল, যার কোনোটাতে টাকা নড়েনি। Income Statement-এর ৯০,৮০০ আর ডিসেম্বরের ব্যাংকের নড়াচড়া (advance +1,00,000, PDC −1,26,000, refund −3,100, …) — দুটো সংখ্যার কোনো মিল নেই, আর দুটোই ঠিক। অধ্যায় ২২ এই দুটোকে জোড়া লাগাবে: net profit থেকে শুরু, তারপর যা cash নয় তা বাদ, যা cash কিন্তু profit নয় তা যোগ।

একটা লাইনের নিচে কী

Statement-এর প্রতিটি সংখ্যা একটা তালিকার মাথা — অধ্যায় ১৪-এর “তিন স্তর” এখানেও:

Electricity 13,300 (ডিসেম্বর) └── 5230 13,300 ├── 01-12 JV-12-0001 reversal of JV-11-0012 (12,000) ├── 14-12 PB-12-0004 DESCO Nov bill 12,800 → purchase_invoices └── 31-12 JV-12-0022 accrual Dec (template) 12,500 → accrual, basis Sales Returns (30,000) └── 4180 ├── 02-12 CN-12-0001 করিম, SV-09-0003, not_delivered 20,000 → notes └── 15-12 CN-12-0002 সালমা, SV-10-0001, price_adj. 10,000 → notes

Statement → লাইন → account → ledger (অধ্যায় ৯) → entry (অধ্যায় ৭) → document (Part 2)। Auditor এই পথে হাঁটেন; management-ও। যে report-এ এই পথ নেই, সেটা “একটা সংখ্যা”, হিসাব নয় — অধ্যায় ১৮-এর একই কথা, এবার report-এর জন্য।


৫. Implementation — Software ও Database

দুটো জিনিস আলাদা: সংখ্যা আর সাজানো

Report engine-এর প্রথম নকশা-সিদ্ধান্ত: movement বের করা (data) আর লাইনে সাজানো (layout) দুটো আলাদা ধাপ, দুটো আলাদা table/function। Income Statement, Balance Sheet, cost center P&L, budget comparison — সবাই প্রথমটা share করে; দ্বিতীয়টা প্রতিটির নিজের।

posted_lines ──▶ movement(from, to) ──▶ {account_id: signed} ──▶ layout(statement) ──▶ লাইন, subtotal ↑ একটাই ↑ data (statement_lines)

ধাপ ১ — movement: একটা query, একটা range

-- একটি date range-এ REVENUE ও EXPENSE account-এর নিট নড়াচড়া SELECT a.id, a.code, a.account_type, a.is_contra, SUM(l.debit) AS period_debit, SUM(l.credit) AS period_credit, SUM(l.credit - l.debit) AS signed_cr -- statement-এর চিহ্ন: credit ধনাত্মক FROM posted_lines l JOIN accounts a ON a.id = l.account_id WHERE l.company_id = :company AND l.posting_date >= :from AND l.posting_date <= :to AND a.account_type IN ('REVENUE', 'EXPENSE') GROUP BY a.id, a.code, a.account_type, a.is_contra;

চারটি বিষয়:

১. posted_lines — অধ্যায় ৮-এর view; draft কখনো statement-এ নয়।

২. posting_date, document date নয় — অধ্যায় ৮ ও ১৫-এর সিদ্ধান্ত এখানে ফল দেয়: bill_date ২৮-১১, posting_date ০১-১২ — ডিসেম্বরের statement-এ।

৩. opening নেই। অধ্যায় ১০-এর query-র opening CTE এখানে ইচ্ছাকৃতভাবে বাদ। YTD চাইলে from = fiscal_year_start — আলাদা query নয়।

৪. চিহ্ন credit − debit — revenue ধনাত্মক, expense ঋণাত্মক, contra-revenue ঋণাত্মক। এক চিহ্নে রাখলে subtotal-এর যোগ সোজা: net profit = Σ signed_cr। উপস্থাপনায় expense section-এ −signed_cr দেখাবেন (খরচ ধনাত্মক)।

Snapshot থাকলে (অধ্যায় ৯-এর account_period_balances) পুরো মাসের জন্য এটা period_debit, period_credit থেকেই আসে — query-ও লাগে না; date range period-এর সীমানায় না মিললে (১৫ ডিসে – ১৫ জানু) posted_lines-এ ফিরুন। is_stale মানুন।

ধাপ ২ — layout: statement_lines

statements id, company_id kind VARCHAR(20) -- income_statement | balance_sheet | cash_flow name VARCHAR(100) -- 'Income Statement — multi-step' is_default BOOLEAN
statement_lines id, statement_id line_no INT -- ক্রম label VARCHAR(200) -- 'Software Development Income', 'GROSS PROFIT' kind VARCHAR(20) -- header | account | subtotal | blank indent SMALLINT parent_line_id BIGINT FK NULL -- subtotal কোন কোন লাইন যোগ করবে (গাছ) sign SMALLINT -- +1: signed_cr যেমন; −1: উল্টে (expense section-এ খরচ ধনাত্মক দেখাতে) show_pct BOOLEAN -- % of revenue column-এ আসবে? is_pct_base BOOLEAN -- এই লাইনই 100% (net revenue) — statement-এ ঠিক একটা
statement_line_accounts statement_line_id BIGINT FK match_kind VARCHAR(20) -- account | group | sub_type match_value VARCHAR(40) -- account.id / group account-এর id / 'DIRECT_COST'

Mapping-এর তিনটি উপায়, পছন্দের ক্রমে:

group 'Operating Revenue' লাইন = 4100 group-এর সব posting account ← COA-র গাছ; নতুন account নিজে ঢোকে sub_type 'Direct cost' = sub_type DIRECT_COST ← গাছ না মিললে account 4540 → 'Other income' ← ব্যতিক্রম, আলাদা করে

প্রথমটাই মূল — অধ্যায় ৬-এর hierarchy-র প্রতিদান: 4110-এর পাশে 4140 যোগ করলে statement নিজে থেকে ঠিক থাকে। Account-ধরে mapping করলে প্রতিটি নতুন account-এ কেউ ভুলে যাবে।

Layout-ই data — code নয়। Multi-step আর single-step দুটো statements সারি, একই movement()। বাংলাদেশ, ভারত, IFRS — ছকের পার্থক্য layout-এ; engine এক।

ধাপ ৩ — সাজানো

incomeStatement(company, statement, columns, by): -- columns: [(label, from, to)] যেমন [('ডিসে ২০২৫', 01-12, 31-12), -- ('নভ ২০২৫', 01-11, 30-11), -- ('YTD', fiscalYearStart(31-12), 31-12)] যাচাই: প্রতিটি (from, to) একই অর্থবছরের ভিতরে, নইলে সতর্কতা (অধ্যায় ২৬) প্রতিটি column c: mv[c] = movement(company, c.from, c.to) -- {account_id: signed_cr} প্রতিটি line l (line_no ক্রমে): যদি l.kind == 'account': accts = resolve(l) -- statement_line_accounts → posting account-এর set প্রতিটি column c: val[l][c] = l.sign × Σ mv[c][a] for a in accts যদি l.kind == 'subtotal': প্রতিটি column c: val[l][c] = Σ val[child][c] for child in children(l) -- sign আগেই লাগানো -- চিহ্ন: subtotal-এ children-এর মান যোগ হয়; expense লাইন sign −1 দিয়ে ঋণাত্মক -- হয়ে থাকলে 'Gross profit' = Σ (net revenue লাইন, −direct cost লাইন) সোজা যোগে আসে base = line WHERE is_pct_base প্রতিটি l, c WHERE l.show_pct: pct[l][c] = val[l][c] ÷ val[base][c] (base 0 হলে NULL) -- অলঙ্ঘনীয় যাচাই প্রতিটি column c: assert lastSubtotal[c] == Σ mv[c].values() -- layout-নিরপেক্ষ net profit unmapped = mv[c].keys() − Σ resolve(l) for all account lines যদি unmapped ≠ ∅: ব্যর্থ, তালিকা সহ ("account 4550 কোনো লাইনে নেই") return rows, রেন্ডারের জন্য

দুটো assert ঐচ্ছিক নয়। প্রথমটা layout-এর ভুল ধরে (একটা লাইন দুবার যোগ, subtotal-এ child বাদ); দ্বিতীয়টা mapping-এর ফাঁক। দুটোর কোনোটা ব্যর্থ হলে report দেখাবেন না — ভুল সংখ্যার চেয়ে না-দেখানো report ভালো।

সমস্যা: একটা account দুটো লাইনে?

resolve() set ফেরত দেয়; দুটো লাইনের set-এ একই account থাকলে দুবার যোগ হবে, আর প্রথম assert ধরবে। কিন্তু ধরার চেয়ে আটকানো ভালো:

saveStatement(statement): প্রতিটি posting account (REVENUE, EXPENSE): n = কতগুলো account-লাইন এটাকে resolve করে n == 0 → সতর্কতা "unmapped" (নতুন account, এখনো mapping নেই — চলে, কিন্তু report বন্ধ) n > 1 → reject "ambiguous" (group-এ আছে, আবার account-ধরে অন্য লাইনেও)

ব্যতিক্রমের নিয়ম: account-ধরে mapping group-ধরে mapping-কে override করে (4540 group 4500-এর ভিতরে, কিন্তু আলাদা লাইনে চাইলে) — resolve()-এ account-match আগে, তারপর group-match থেকে ওই account বাদ। এটা কোডে; নইলে override সম্ভবই নয়।

Rounding — কোথায়

হিসাব DECIMAL(18,4), পুরো পথ — অধ্যায় ১৩-এর নিয়ম উপস্থাপনা নিকটতম টাকা (বা হাজার, config) subtotal rounded child-দের যোগ নয় — unrounded যোগ, তারপর round

তৃতীয়টায় একটা ফাঁদ: unrounded যোগের round আর rounded-দের যোগ ১-২ টাকা আলাদা হতে পারে — পাঠক column যোগ করে মেলাতে গিয়ে থমকান। রীতি: subtotal সত্যি (unrounded থেকে), নিচে একটা note “সংখ্যা নিকটতম টাকায়; যোগফলে rounding-এর পার্থক্য থাকতে পারে”। হাজারে দেখালে এটা প্রতি পাতায় ঘটে।

% column: দুই দশমিক, base 0 হলে খালি (division by zero নয়, NULL)। Loss-এ ঋণাত্মক % — বন্ধনীতে।

তুলনামূলক column — একটাই function

এই মাস (period.start, period.end) গত মাস (prev.start, prev.end) YTD (fiscalYearStart(period.end), period.end) গত বছর একই মাস (period.start − 1 year, period.end − 1 year) গত বছর YTD (fiscalYearStart(period.end − 1 year), period.end − 1 year) Budget budget_lines থেকে — একই account, একই লাইন (অধ্যায় ৩৩)

প্রতিটি movement(from, to)-এর একটা call। “গত বছর” column-এ অর্থবছর-সীমানা: জুলাই ২০২৬-এর “গত বছর একই মাস” জুলাই ২০২৫ — অন্য fiscal year, কিন্তু movement-এর কাছে শুধু দুটো তারিখ; কিছু বদলায় না। এটাই movement-ভিত্তিক নকশার সুবিধা — closing-ভিত্তিক হলে গত বছরের column-এ closing entry-র পরে সব শূন্য দেখাত (অধ্যায় ২৬)।

Drill-down

প্রতিটি রেন্ডার করা সংখ্যার সাথে একটা লিংক:

account line → /ledger?accounts=[…]&from=&to= অধ্যায় ৯-এর ledger, একাধিক account ledger সারি → /entries/{id} অধ্যায় ৭ entry → source document (source_type, source_id) invoice / bill / note / accrual

Report-এর প্রতিটি লাইনে account_ids আর (from, to) রাখলেই হয় — আলাদা কিছু বানাতে হয় না। resolve()-এর set-টাই লিংকের parameter।

Period lock ও “ছাপা” statement

Statement যেকোনো সময় আবার চালানো যায়, আর খোলা period-এ সংখ্যা বদলাবে (অধ্যায় ১৭: “সেপ্টেম্বর আবার বদলাল”)। বন্ধ period-এ বদলাবে না — এটাই period lock-এর প্রতিশ্রুতি (অধ্যায় ২৬, ৪২)। তবু ছাপা/পাঠানো statement-এর একটা snapshot রাখুন:

statement_runs id, company_id, statement_id, from, to, columns (json) run_at, run_by rows JSON -- যা দেখানো হয়েছিল hash CHAR(64)

Auditor ছয় মাস পরে বলবেন “ডিসেম্বরে যা দেখিয়েছিলেন” — তখন আবার চালালে একই হওয়া উচিত; না হলে কে কখন বন্ধ period-এ post করল — অধ্যায় ৪৫-এর প্রশ্ন, আর statement_runs-এর hash সেটার প্রমাণ।

রাতের যাচাই

৫৭. প্রতিটি খোলা period: incomeStatement(period).net_profit == Σ movement(REVENUE, EXPENSE) (layout-নিরপেক্ষ) ৫৮. unmapped REVENUE/EXPENSE account (default statement-এ) → ০টি; নইলে সতর্কতা, report বন্ধ ৫৯. একটা account একাধিক লাইনে → ০টি ৬০. is_pct_base লাইন ঠিক একটা ৬১. বন্ধ period-এর statement_runs.hash == আবার চালানো hash (period lock ভাঙেনি) ৬২. contra account (4180, 4190) revenue section-এর লাইনে, expense-এ নয়

Test হিসেবে

test "মাসের statement-এ আগের মাস ঢোকে না": post(Nov, 5220, 65,000); post(Dec, 5220, 65,000) s = incomeStatement(Dec) assert s.line('Office Rent') == 65,000 -- 1,30,000 নয় assert incomeStatement(YTD to Dec).line('Office Rent') == 1,30,000 test "net profit layout-নিরপেক্ষ": প্রতিটি statement in [multi_step, single_step]: assert incomeStatement(Dec, statement).net_profit == 90,800 test "contra revenue-র ঘরে, ঋণাত্মক": s = incomeStatement(Dec) assert s.line('Sales Returns & Allowances') == −30,000 assert s.line('Gross Revenue') == 6,50,000; s.line('NET REVENUE') == 6,20,000 test "reversal + bill + accrual → নিট": assert s.line('Electricity') == 13,300 assert drilldown(s, 'Electricity').entries == [JV-12-0001, PB-12-0004, JV-12-0022] test "নতুন account group-এ → নিজে থেকে লাইনে": a = createAccount('4140 Training Income', parent = 4100) post(Dec, a, 20,000) assert incomeStatement(Dec).line('Gross Revenue') == 6,70,000 -- mapping ছোঁয়া হয়নি test "unmapped account → report ব্যর্থ, চুপ নয়": a = createAccount('4550 Misc Income', parent = 4500); unmap(4500) post(Dec, a, 1,000) assert raises incomeStatement(Dec) -- "unmapped: 4550" test "draft entry statement-এ নেই": draft(Dec, 5220, 99,999) assert incomeStatement(Dec).line('Office Rent') == 65,000 test "গত বছরের column closing entry-র পরেও ঠিক": close(FY2026) -- অধ্যায় ২৬ assert incomeStatement(Dec 2025).net_profit == 90,800

৬. Financial Statement Impact

এই অধ্যায়টাই একটা statement — তাই এখানে প্রশ্ন উল্টো: Income Statement বাকি তিনটিকে কোথায় ছোঁয়?

INCOME STATEMENT (ডিসেম্বর) net profit 90,800 ├──▶ BALANCE SHEET (৩১ ডিসেম্বর) অধ্যায় ২১ │ 3300 Current Year Profit 2,78,365 ← YTD net profit; এটা ছাড়া মিলবে না │ (Assets == Liabilities + Equity + YTD profit — অধ্যায় ১০-এর যাচাই) ├──▶ CASH FLOW STATEMENT (ডিসেম্বর), indirect method অধ্যায় ২২ │ শুরু: net profit 90,800 │ + depreciation 8,000 ← খরচ, cash নয় │ ± ΔA/R, Δunbilled, Δdeferred, Δaccrued, Δprepaid, ΔA/P … │ = operating cash flow └──▶ STATEMENT OF CHANGES IN EQUITY (জুলাই–ডিসেম্বর) অধ্যায় ২৩ opening equity + net profit 2,78,365 − drawings = closing equity

তিনটি সম্পর্কের প্রথমটা সবচেয়ে জরুরি, আর অধ্যায় ২১ সেখান থেকেই শুরু: Trial Balance-এর 4xxx/5xxx সারিগুলো Balance Sheet-এ নেই, তাদের নিট — YTD — 3300-এ। এই বইয়ে 3300 কোনো posting-এ বদলায় না; report-এর সময় হিসাব হয় (অধ্যায় ২৬-এ বছর শেষে একবার closing entry 3300 → 3200)। মাসের মাঝে কেউ 3300-এ হাতে post করলে Balance Sheet মিলবে না — allow_manual_posting = false (অধ্যায় ৫)।

দ্বিতীয়টা Part 2-র প্রতিটি “তিন সংখ্যার গল্প”-এর সমাধান — revenue 6,20,000, cash অন্য কিছু; পার্থক্যের প্রতিটি টাকার একটা Balance Sheet account আছে (A/R, deferred, accrued…), আর indirect method সেগুলোর Δ যোগ-বিয়োগ করে। Income Statement আর Balance Sheet ঠিক হলে Cash Flow নিজে থেকে ঠিক — অধ্যায় ২২-এর মূল কথা, আগাম।


৭. Common Developer Mistakes

ভুলকী ঘটেসঠিক পথ
মাসের P&L-এ closing_signedনভেম্বরের খরচ ডিসেম্বরে ঢোকে; YTD-র সমান “মাস”movement(from, to); range শুরু = FY শুরু হলে সেটাই YTD
Code range hardcode (code BETWEEN '5100' AND '5199')নতুন account / COA বদলে report চুপচাপ ভুলgroup / sub_type mapping, data-তে
Unmapped account বাদ পড়া, report চলেnet profit ভুল, কেউ জানে নাunmapped ⇒ ব্যর্থ; যাচাই ৫৮
Contra-revenue expense-এgross margin মিথ্যা; “revenue” ফেরত-সহrevenue section-এ, বন্ধনীতে
Discount received কখনো other income, কখনো contra-expenseমাস-ধরে তুলনা ভাঙেএকটা নীতি, layout-এ, স্থির
VAT revenue-তে (invoice-এর grand_total থেকে statement)revenue ১৫% ফোলেstatement GL থেকে; VAT 2140-এ (অধ্যায় ১৩)
Draft entry statement-এapprove-এর আগেই লাভposted_lines
document date-এ filterbill_date নভেম্বর, post ডিসেম্বর → দুই report দুই কথাposting_date
subtotal rounded লাইনের যোগনিচের note ছাড়াই ১ টাকার গরমিল; বা উল্টো — সত্যি subtotal, note নেইunrounded যোগ → round; note
% column-এ base 0 → crash / ∞নতুন company-র প্রথম মাসNULL, খালি ঘর
প্রতিটি column-এর আলাদা query/কোডচার copy, চার bug; budget column পঞ্চমএকটাই movement(from, to)
Statement-এ Retained Earnings/3300 লাইনP&L-এ equity accountশুধু REVENUE, EXPENSE; 3300 Balance Sheet-এর
Group account-ও যোগসব দুবার (অধ্যায় ১০-এর একই ভুল)is_group = false
Drill-down নেই”১৩,৩০০ কেন?” — উত্তর নেই, বিশ্বাস নেইলাইন → ledger → entry → document
ছাপা statement-এর snapshot নেইছয় মাস পরে সংখ্যা আলাদা, কেন — জানা নেইstatement_runs, hash; যাচাই ৬১

প্রথম আর তৃতীয়টা মূল। Closing দিয়ে P&L বানানো এমন ভুল যা প্রথম মাসে ধরা পড়ে না — জুলাইয়ে movement আর closing এক — আর আগস্টে সবাই বলে “লাভ দ্বিগুণ!”। Unmapped account আরও নীরব: কেউ 4550 বানাল, তাতে ১০,০০০ post হলো, statement ১০,০০০ কম দেখাল, Trial Balance নিখুঁত মিলল। Report যে সংখ্যা দেখায় তার চেয়ে জরুরি — report যা দেখায় না তা কি সে জানে?


৮. Exercises

সেট ক — হাতে বানানো

১। জানুয়ারি ২০২৬-এর movement (অধ্যায় ১৭–১৯ থেকে): 4110 1,80,000 (নদী), 4120 10,000, 5270-এ subcontractor-এর bill 46,000 কিন্তু reversal 40,000, 5230 reversal 12,500 ও bill 12,100 ও accrual 12,000, বাকি ডিসেম্বরের মতো। জানুয়ারির multi-step Income Statement লিখুন। Net profit? YTD (জুলাই–জানু)? ২। ১ নম্বরে subcontractor-এর লাইন 6,000 কেন, 46,000 নয়? কোন অধ্যায়ের কোন নিয়ম? ৩। Section ৪-এর ডিসেম্বরকে single-step-এ সাজান। কোন subtotal হারাল? কোন প্রশ্নের উত্তর আর দেওয়া যায় না? ৪। ডিসেম্বরে যদি সাগর লিমিটেডের advance 1,00,000-কে কেউ 4110-এ post করত (অধ্যায় ১৯-এর ভুল ১০), statement-এর কোন কোন লাইন ও % বদলাত? Trial Balance ধরত? ৫। EBITDA = operating profit + depreciation + (interest? — না, operating-এর নিচে)। ডিসেম্বর ও নভেম্বরের EBITDA। কোন লাইনগুলো লাগল — layout-এ এটা কীভাবে একটা "memo line" হিসেবে রাখবেন যা subtotal-এ যোগ হয় না?

সেট খ — কোন লাইনে, কেন

৬। প্রতিটির লাইন ও যুক্তি: (ক) 4520 Gain on Asset Disposal (খ) 5530 Bad Debt (গ) 5290 Depreciation (ঘ) 4540 Discount Received (ঙ) 5295 Cash Over/Short (চ) নতুন 5140 Subcontractor Cost — 5270 থেকে সরালে কী বদলায়? ৭। একটা trading company (অধ্যায় ২৭-এর আগাম): 5010 Cost of Goods Sold. Gross profit-এর অর্থ service company-র থেকে কীভাবে আলাদা? Inventory কোথায় — statement-এ না Balance Sheet-এ? ৮। Interest expense 5510 "operating"-এর নিচে; কিন্তু একটা leasing company-র জন্য সুদই মূল ব্যবসা। Layout কীভাবে বদলাবে — engine না data?

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

৯। SELECT SUM(credit−debit) FROM posted_lines WHERE posting_date <= '2025-12-31' AND account_type = 'REVENUE' — "ডিসেম্বরের revenue" ১০। WHERE a.code LIKE '4%' — revenue লাইন ১১। revenue = SUM(sales_invoices.grand_total) WHERE invoice_date IN December ১২। net_profit = SUM(lines WHERE label != 'header') — subtotal-ও লাইন ১৩। Sales Returns 30,000 → 'Operating Expenses' section-এ, ধনাত্মক ১৪। pct = line / net_revenue — নতুন company, ডিসেম্বরে revenue 0 ১৫। গত বছরের column: closing_signed AS OF 30-06-2025 — closing entry হয়ে গেছে

সেট ঘ — নকশা

১৬। statement_line_accounts-এ group-match আর account-match একই account-এ — override-এর নিয়ম কোডে লিখুন। কোন ক্রমে resolve? দুটো account-match একই account-এ দুই লাইনে হলে? ১৭। Budget column (অধ্যায় ৩৩-এর আগাম): budget_lines (account_id, period_id, amount)। movement()-এর মতো একটা budget(from, to) — একই {account: amount} আকারে? তারপর layout একই? variance column (actual − budget) কোথায় হিসাব হবে? ১৮। Cost center-ধরে P&L (অধ্যায় ৩৪): posted_lines-এ cost_center_id থাকলে movement()-এ একটা filter — নাকি আলাদা function? "সব cost center-এর যোগ == company P&L" — এটা কোন যাচাই? ১৯। Statement-এর একটা লাইন থেকে drill-down-এ ledger; ledger-এ opening balance দেখাবেন? (Income Statement-এর লাইনে opening নেই, কিন্তু 5230-এর ledger-এ জুলাই থেকে আছে।) UI-তে কী করবেন? ২০। statement_runs-এর hash আবার চালিয়ে না মিললে — কোন কোন কারণ সম্ভব? প্রতিটির জন্য কোথায় খুঁজবেন (অধ্যায় ৪২, ৪৫-এর আগাম)?

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


৯. Developer Challenge

একটি IncomeStatementService নকশা করুন — Part 3-র report engine-এর প্রথম টুকরো, যা অধ্যায় ২১ (Balance Sheet) ও ২২ (Cash Flow) আবার ব্যবহার করবে, আর অধ্যায় ৪৯-এ পূর্ণ Financial Report Engine হবে।

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

১. movement(company, from, to, filter) — একটাই function, posted_lines থেকে, posting_date-এ; snapshot (account_period_balances) থাকলে ও range period-সীমানায় মিললে সেখান থেকে, নইলে raw; is_stale মানে। ফেরত: {account_id: (debit, credit, signed_cr)}। Balance Sheet-এর জন্য একই function-এ closing(company, as_of) — কীভাবে share করবেন, কী আলাদা?

২. statements, statement_lines, statement_line_accounts — multi-step ও single-step দুটো layout data হিসেবে; group / sub_type / account mapping, override-এর ক্রম; saveStatement()-এ unmapped ও ambiguous যাচাই। একটা seed যা অধ্যায় ৫-এর COA-র জন্য default multi-step layout বানায়।

৩. incomeStatement(company, statement, columns) — যেকোনো সংখ্যক column, প্রতিটি একটা (from, to); % of base; unrounded subtotal → round; দুটো অলঙ্ঘনীয় assert (layout-নিরপেক্ষ net profit, unmapped)। Column preset: এই মাস / গত মাস / YTD / গত বছর একই মাস / গত বছর YTD — fiscalYearStart() অধ্যায় ১-এর fiscal_year_start থেকে।

৪. Drill-down — প্রতিটি রেন্ডার করা সংখ্যার সাথে (account_ids, from, to); ledger endpoint একাধিক account নেয় (অধ্যায় ৯-এর ledger-এ যোগ); ledger → entry → source document। Exercise ১৯-এর opening-এর প্রশ্ন।

৫. statement_runs — ছাপা/পাঠানো প্রতিটি statement-এর snapshot ও hash; বন্ধ period-এর hash আবার চালিয়ে মেলানো (যাচাই ৬১); না মিললে কোন entry-র জন্য — diff।

৬. Output — একই rows থেকে: screen (drill-down সহ), PDF (A4, তুলনামূলক column), CSV, আর একটা JSON যা অধ্যায় ৫০-এর API দেবে। Rendering-এ সংখ্যার format (বন্ধনী, হাজার-বিভাজক — 6,20,000 না 620,000 — config)।

৭. রাতের যাচাই ৫৭–৬২। ৫৭ ব্যর্থ হলে (statement-এর net profit ≠ Σ movement) কোন দুটো কারণ সম্ভব — layout না mapping — কীভাবে আলাদা করবেন?

৮. Test — section ৫-এর আটটি, আর: (ক) range অর্থবছর পার হলে (জুন–জুলাই) সতর্কতা কিন্তু সংখ্যা ঠিক; (খ) একই মাসে ১,০০০ entry — movement() এক query, N নয়; (গ) layout বদলালেও (লাইন সরালেও) net profit এক।

১ আর ৩ নম্বরটাই আসল পরীক্ষা। Part 3-র চারটি statement আর Part 4-এর প্রতিটি module-এর “Financial Statement Impact” — সবাই একটাই movement() / closing()-এর উপরে দাঁড়াবে। যদি অধ্যায় ২১-এ Balance Sheet লিখতে গিয়ে নতুন query লিখতে হয়, ১ নম্বর ভুল হয়েছে। আর ৩ নম্বরের দুটো assert — এই বইয়ের প্রতিটি report-এর একটা করে “layout-নিরপেক্ষ সত্য” আছে (Balance Sheet: Assets == L + E; Cash Flow: Δcash == ব্যাংকের নড়াচড়া); report engine সেটা প্রতিবার যাচাই করবে, নইলে দেখাবে না।


১০. Summary Card

কী মাপে

Income Statement = দুই তারিখের মাঝে Revenue − Expense movement, opening নয়, closing নয়; range শুরু == FY শুরু হলে movement == closing (YTD) cash কোথাও নেই — recognition আর cut-off (অধ্যায় ১৭, ১৮) যা বলে, statement তা যোগ করে

Multi-step

Gross Revenue − contra (4180, 4190) = NET REVENUE (100%) − Direct Cost (51xx) = GROSS PROFIT (gross margin) − Operating Expense (52xx) = OPERATING PROFIT + Other Income (45xx, 4540) − Other Exp (55xx) = PROFIT BEFORE TAX − Income Tax (বছর শেষে) = NET PROFIT → 3300, Balance Sheet

চিহ্ন

signed_cr = credit − debit revenue +, expense −, contra-revenue − (আয়ের ঘরে বন্ধনীতে) net profit = Σ signed_cr (সব REVENUE, EXPENSE) — layout যাই হোক

ডিসেম্বর ২০২৫

net revenue 6,20,000 → gross profit 3,70,000 (59.7%) → operating 94,300 → net 90,800 (14.6%) নভেম্বর (19,835) ক্ষতি — revenue recognition-এর দোলা; YTD 2,78,365 (8.4%)

নকশা

movement(from, to) একটাই — প্রতিটি column একটা call; snapshot থাকলে period_debit/credit layout = data: statements → statement_lines → statement_line_accounts (group > sub_type > account) দুটো assert: net profit layout-নিরপেক্ষ; unmapped account ⇒ report ব্যর্থ drill-down: লাইন → ledger(accounts, from, to) → entry → document statement_runs: ছাপা সংখ্যার snapshot + hash

Developer checklist

□ movement(), posted_lines, posting_date, opening নেই □ closing_signed কখনো মাসের P&L-এ নয় □ statement_lines data; group-mapping; override-এর ক্রম; saveStatement() যাচাই □ unmapped ⇒ ব্যর্থ, চুপ নয় □ contra revenue section-এ; 4540 নীতি স্থির □ column preset: মাস / গত মাস / YTD / গত বছর; fiscalYearStart() □ % of net revenue, base 0 → NULL; unrounded subtotal → round + note □ drill-down প্রতিটি সংখ্যায় □ statement_runs + hash □ 3300 হাতে post নয়; Balance Sheet-এ YTD profit □ রাতে: ৫৭–৬২

পরের অধ্যায়

Income Statement-এর শেষ লাইন — 90,800, YTD 2,78,365 — এখন একটা জায়গা চায়। অধ্যায় ২১ — Balance Sheet: Trial Balance-এর বাকি সারি (1xxx, 2xxx, 3xxx), এবার closing, একটা তারিখ — আর 3300-এ এই অধ্যায়ের YTD profit বসালে তবেই দুই পাশ মেলে। সাথে অধ্যায় ১৪ ও ১৯-এর reclassification (A/R-এর credit balance, A/P-এর debit balance), অধ্যায় ১১-এর overdraft, আর current/non-current-এর sub_type