Skip to Content
Go Realm v1 is released 🎉
Accountingবই (Print Edition)অধ্যায় ২৪: Trial Balance থেকে Statement

অধ্যায় ২৪ — Trial Balance থেকে Statement

Volume 1 · Part 3 — Financial Statements · Chapter 24

পূর্বশর্ত: অধ্যায় ১০ (Trial Balance, adjusted বনাম unadjusted), অধ্যায় ১৮ (মাসের শেষের চার job), অধ্যায় ২০–২৩ (চারটি statement, প্রতিটির assert)


১. Learning Objective

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

Accounting cycle-এর শেষ অংশ — unadjusted TB → adjusting entries → adjusted TB → statements → closing — এক নিঃশ্বাসে বলতে দশ-column worksheet পড়তে ও বানাতে: TB, adjustments, adjusted TB, Income Statement, Balance Sheet Adjusted আর unadjusted-এর মাঝের পার্থক্যকে ব্যবসার ভাষায় বলতে ("ক্ষতি 1,45,700 থেকে লাভ 90,800 — কেন") চারটি statement একসাথে বানানোর ক্রম — কেন Income Statement আগে, Cash Flow শেষে Cross-statement identity-র matrix — নয়টি সমীকরণ — একটা যাচাই-তালিকা হিসেবে লিখতে Worksheet-কে software-এ একটা function হিসেবে — adjusting entry-র set আলাদা করে — বানাতে Reporting pack: preconditions → statements → notes → cross-check → sign-off → freeze — এক orchestration Late entry-র পরে re-run: কী বদলাল, কে দেখবে — diff Print-এর জন্য pack-এর কাঠামো: cover, statements, notes, worksheet appendix

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


২. Concept Explanation

চারটি অধ্যায়, একটা পথ

অধ্যায় ২০–২৩ চারটি statement আলাদা করে লিখেছে — প্রতিটির নিজের data, layout, assert। বাস্তবে কেউ একটা statement বানায় না; মাসের শেষে সবগুলো একসাথে বানায়, একটা নির্দিষ্ট ক্রমে, একটা Trial Balance থেকে। এই অধ্যায় সেই পথ:

posted_lines (মাসের সব document) UNADJUSTED TRIAL BALANCE ← অধ্যায় ১০; "যা হয়েছে" │ + adjusting entries ← অধ্যায় ১৭, ১৮ (recognition, amortization, accrual), depreciation — অধ্যায় ২৫-এ পুরো তালিকা ADJUSTED TRIAL BALANCE ← "যা সত্যি" ├──▶ INCOME STATEMENT ← অধ্যায় ২০ 4xxx, 5xxx movement ├──▶ BALANCE SHEET ← অধ্যায় ২১ 1xxx, 2xxx, 3xxx closing + 3300 ├──▶ SOCE ← অধ্যায় ২৩ 3xxx movement + profit └──▶ CASH FLOW ← অধ্যায় ২২ দুটো Balance Sheet + Income Statement CROSS-CHECK → REVIEW → SIGN-OFF → period lock (অধ্যায় ২৬)

হিসাবরক্ষকরা এটাকে বলেন accounting cycle-এর শেষ ধাপ। Software-এ এটা একটা orchestration — একটা button, একটা ক্রম, প্রতিটি ধাপের ফল state-এ।

Worksheet — হিসাবরক্ষকের চিন্তার যন্ত্র

Software-এর আগে এই পথটা একটা কাগজে হাঁটা হতো — দশ column-এর worksheet:

Account │ Unadjusted TB │ Adjustments │ Adjusted TB │ Income Statement │ Balance Sheet │ Dr Cr │ Dr Cr │ Dr Cr │ Dr Cr │ Dr Cr

প্রতিটি account একটা সারি; বাঁ থেকে ডানে পড়লে সেই account-এর গল্প: কী ছিল, কী সমন্বয় হলো, কী দাঁড়াল, কোন statement-এ গেল। শেষ চার column-এর কৌশল: 4xxx/5xxx Income Statement column-এ, বাকি Balance Sheet-এ; Income Statement column-দুটোর পার্থক্যই লাভ, আর সেই লাভটা Balance Sheet column-এর Cr-এ বসালে Balance Sheet-ও মেলে — অধ্যায় ২১-এর 3300 কাগজে।

Software-এ worksheet ছাপার দরকার নেই — কিন্তু বানানোর দরকার আছে, দুটো কারণে। প্রথম, auditor “adjustment-এর আগে-পরে” দেখতে চান — কোন entry-গুলো মাসের শেষে হিসাবরক্ষকের সিদ্ধান্ত, কোনগুলো document থেকে এসেছে। দ্বিতীয়, এটা এই Part-এর সবচেয়ে ভালো debugging view: Balance Sheet মেলেনি — worksheet-এর কোন column থেকে অমিল শুরু?

Unadjusted — কেন দেখানো দরকার

Section ৪-এ দেখবেন: ডিসেম্বরের unadjusted Trial Balance বলে ক্ষতি 1,45,700; adjusted বলে লাভ 90,800। পার্থক্য 2,36,500 — সাতটা entry, সবগুলো ৩১ ডিসেম্বরে, সবগুলো হিসাবরক্ষকের হিসাব (recognition, accrual, amortization, depreciation), কোনোটাই বাইরের document নয়।

এটা ভয়ের কথা নয় — এটাই accrual accounting। কিন্তু management-এর জানা দরকার লাভের কতটা “ঘটনা”, কতটা “সিদ্ধান্ত”। যে মাসে adjustment-এর অঙ্ক লাভের চেয়ে বড়, সে মাসে adjustment-গুলোর basis review হওয়া দরকার — অধ্যায় ১৮-এর “basis” column এজন্যই ছিল।

Statement-এর ক্রম — কেন এই ক্রমে

১. Income Statement নিজে দাঁড়ায় — শুধু movement ২. Balance Sheet ১-এর net profit লাগে (3300) ৩. SOCE ১-এর profit, ২-এর opening/closing লাগে ৪. Cash Flow ১ আর দুটো ২ লাগে

Dependency-র ক্রম। Software-এ চারটি function-ই স্বাধীনভাবে ডাকা যায় (প্রতিটি নিজে movement()/closing() ডাকে) — কিন্তু pack বানানোর সময় এই ক্রমে, যাতে একই snapshot থেকে সব আসে। মাঝে কেউ post করলে Income Statement-এর 90,800 আর Balance Sheet-এর 3300 আলাদা হয়ে যাবে — pack-এর ভিতরে অসঙ্গতি। তাই pack একটা transaction-এর মতো: শুরুতে snapshot version নেওয়া (account_period_balances.computed_at বা একটা posted_lines-এর max id), শেষে যাচাই কিছু বদলায়নি; বদলালে আবার।

Cross-statement matrix — নয়টি সমীকরণ

প্রতিটি statement-এর নিজের assert ছিল। এবার statement-দের মাঝে:

# সমীকরণ কোন অধ্যায় ── ───────────────────────────────────────────────────────────────────── ─────────── ১ TB.Σdebit == TB.Σcredit (unadjusted, adjustments, adjusted — তিনটিতেই) ১০ ২ IS.net_profit == adjusted TB-র (IS Cr − IS Dr) ২০ ৩ BS.3300 == IS(FY start → as_of).net_profit ২১ ৪ BS.assets == BS.liabilities + BS.equity ২১ ৫ SOCE.closing == BS.equity (column-ধরে) ২৩ ৬ SOCE.profit_row == IS.net_profit ২৩ ৭ CF.total == BS(to).cash − BS(from−1).cash ২২ ৮ CF.indirect.section == CF.direct.section (তিনটি) ২২ ৯ CF.financing.capital/dividend == SOCE.capital/dividend rows (cash অংশ) ২২, ২৩

নয়টি সমীকরণ, প্রতিটি একটা লাইন কোড, প্রতিটি ব্যর্থ হলে কোন জোড়ায় ভুল তা বলে। এটাই section ৫-এর cross_checks — data হিসেবে, যাতে অধ্যায় ৪৯-এ নতুন statement যোগ হলে নতুন সারি যোগ হয়, কোড নয়।

Reporting pack — মাসের শেষের deliverable

মাসের শেষে যা বেরোয়, সেটা চারটি PDF নয় — একটা pack:

১. Cover কোম্পানি, period, তৈরির তারিখ, কে বানাল, কে review করল, status ২. Income Statement এই মাস, গত মাস, YTD, budget (থাকলে) ৩. Balance Sheet এই মাসের শেষ, গত মাসের শেষ, গত বছরের শেষ ৪. SOCE YTD ৫. Cash Flow এই মাস, YTD — indirect (direct appendix-এ) ৬. Notes প্রতিটি statement-এর note, Part 2-র report থেকে ৭. Appendix adjusted TB, worksheet, adjusting entries-এর তালিকা basis সহ, cross-check ফল

আর তিনটি বৈশিষ্ট্য যা pack-কে “চারটি PDF” থেকে আলাদা করে: একটাই snapshot (সব সংখ্যা একই মুহূর্তের), একটাই hash (statement_runs চারটির উপরে একটা), একটা status (draft → reviewed → final)। Final হলে period lock-এর সাথে বাঁধা (অধ্যায় ২৬) — final pack-এর period-এ আর post নয়।

Late entry — pack-এর পরে কিছু এল

Pack final হওয়ার আগে কেউ একটা bill post করল (period খোলা)। Pack-এর সংখ্যা বাসি। দুটো পথ:

ক. pack draft থাকলে: আবার চালান; diff দেখান — কোন লাইন কত বদলাল, কোন entry-তে খ. pack final হলে: period lock → post-ই হবে না; পরের period-এ (অধ্যায় ১৮-এর DESCO-র bill) — বা reopen, approved, log (অধ্যায় ২৬, ৪২)

Diff-টা গুরুত্বপূর্ণ: reviewer গতকাল যা দেখেছেন, আজ যা দেখছেন — পার্থক্য কোথায়। statement_runs-এর rows JSON থেকে লাইন-ধরে diff — সস্তা, আর review-এর অর্ধেক সময় বাঁচায়।


৩. Accounting Rule

পথ

unadjusted TB + adjusting entries = adjusted TB adjusted TB → IS (4xxx, 5xxx) + BS (1xxx, 2xxx, 3xxx + profit) IS → BS.3300 → SOCE ; IS + BS×2 → CF

Adjusting entry কী (পুরো তালিকা অধ্যায় ২৫)

period-এর শেষ দিনে, হিসাবরক্ষকের হিসাব থেকে (document থেকে নয়), সময়ের ফাঁক মেটাতে: recognition, amortization, accrual, depreciation, provision, reclass (loan current portion — পথ খ) source_type ∈ adjusting set; reversal (০১ তারিখ) adjusting নয় — period open-এর অংশ

Worksheet

IS column: REVENUE, EXPENSE → Cr − Dr = profit (YTD, কারণ TB closing) BS column: বাকি; profit Cr-এ বসালে Dr == Cr

Cross-check — নয়টি সমীকরণ (section ২-এর তালিকা) — সবগুলো সফল না হলে pack নয়।

Pack

একটা snapshot, একটা hash, একটা status (draft | reviewed | final) final ⇒ period locked; reopen ⇒ pack superseded, নতুন version

৪. Real Business Example

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

৩১ ডিসেম্বরের সাতটি adjusting entry (অধ্যায় ১৭, ১৮; depreciation অধ্যায় ২৫):

(ক) JV-12-0021 amortization Dr 5130 5,000 / Cr 1150 5,000 (খ) JV-12-0022 accrual DESCO Dr 5230 12,500 / Cr 2135 12,500 (গ) JV-12-0023 accrual ISP Dr 5240 8,000 / Cr 2135 8,000 (ঘ) JV-12-0024 accrual subcontr. Dr 5270 40,000 / Cr 2135 40,000 (ঙ) JV-12-0025 depreciation Dr 5290 8,000 / Cr 1515 6,667, Cr 1525 1,333 (চ) JV-12-0026 recognition নদী Dr 2180 1,20,000, Dr 1170 1,80,000 / Cr 4110 3,00,000 (ছ) JV-12-0027 recognition ABC Dr 2180 10,000 / Cr 4120 10,000 ──────── ──────── 3,83,500 3,83,500

Worksheet — সব account, দশ column (Trial Balance closing = ১ জুলাই থেকে, তাই IS column YTD):

WORKSHEET — ৩১ ডিসেম্বর ২০২৫ টাকা Code Account Unadjusted TB Adjustments Adjusted TB Income Stmt Balance Sheet Dr Cr Dr Cr Dr Cr Dr Cr Dr Cr ──── ─────────────── ───────── ───────── ──────── ──────── ───────── ───────── ───────── ──────── ───────── ───────── 1110 Cash in Hand 42,500 42,500 42,500 1115 Petty Cash 10,000 10,000 10,000 1120 Bank — Prime 13,67,665 13,67,665 13,67,665 1125 Bank — City 4,00,000 4,00,000 4,00,000 1130 A/R 11,500 11,500 11,500 1150 Prepaid 55,000 (ক) 5,000 50,000 50,000 1160 VAT Receivable 49,200 49,200 49,200 1170 Unbilled 0 (চ) 1,80,000 1,80,000 1,80,000 1510 Computer Equip. 2,40,000 2,40,000 2,40,000 1515 Acc. Dep. — Comp. 6,667 (ঙ) 6,667 13,333 13,333 1520 Furniture 72,000 72,000 72,000 1525 Acc. Dep. — Furn. 1,333 (ঙ) 1,333 2,667 2,667 2110 A/P 22,000 22,000 22,000 2120 Salary Payable 3,50,000 3,50,000 3,50,000 2135 Accrued Expenses 0 (খগঘ) 60,500 60,500 60,500 2140 VAT Payable 46,500 46,500 46,500 2150 TDS Payable 6,500 6,500 6,500 2160 Customer Advance 1,00,000 1,00,000 1,00,000 2180 Deferred Revenue 2,10,000 (চছ) 1,30,000 80,000 80,000 2510 Bank Loan 3,20,000 3,20,000 3,20,000 3100 Share Capital 10,00,000 10,00,000 10,00,000 3200 Retained Earnings 1,20,000 1,20,000 1,20,000 4110 Software Dev. 29,00,000 (চ) 3,00,000 32,00,000 32,00,000 4120 Maintenance 30,000 (ছ) 10,000 40,000 40,000 4130 Consulting 1,26,000 1,26,000 1,26,000 4180 Sales Returns 30,000 30,000 30,000 4190 Discount Allowed 3,600 3,600 3,600 4510 Interest Income 8,100 8,100 8,100 4540 Discount Received 1,840 1,840 1,840 5110 Dev. Salary 13,20,000 13,20,000 13,20,000 5120 Cloud & Hosting 1,50,000 1,50,000 1,50,000 5130 Software 5,000 (ক) 5,000 10,000 10,000 5210 Salary 7,80,000 7,80,000 7,80,000 5220 Rent 3,90,000 3,90,000 3,90,000 5230 Electricity 66,400 (খ) 12,500 78,900 78,900 5240 Internet 40,000 (গ) 8,000 48,000 48,000 5250 Supplies 21,400 21,400 21,400 5260 Travel 38,700 38,700 38,700 5270 Professional Fee 1,25,000 (ঘ) 40,000 1,65,000 1,65,000 5280 Bank Charges 3,975 3,975 3,975 5290 Depreciation 8,000 (ঙ) 8,000 16,000 16,000 5510 Interest Expense 30,000 30,000 30,000 5530 Bad Debt 12,000 12,000 12,000 ───────── ───────── ──────── ──────── ───────── ───────── ───────── ──────── ───────── ───────── TOTAL 52,60,440 52,60,440 3,83,500 3,83,500 55,08,940 55,08,940 30,97,575 33,75,940 24,11,365 21,33,000 Net profit (YTD) 2,78,365 2,78,365 ───────── ──────── ───────── ───────── 33,75,940 33,75,940 24,11,365 24,11,365

তিন জোড়া TB column মিলল (সমীকরণ ১)। Income Statement column-এর পার্থক্য 2,78,365 — Balance Sheet column-এর Cr-এ বসালে সেটাও মিলল (সমীকরণ ২, ৩, ৪ — এক লাইনে)। এক পাতায় অধ্যায় ১০, ২০, ২১।

Unadjusted বনাম adjusted — ব্যবসার ভাষায়

unadjusted adjustments adjusted YTD revenue (gross) 30,65,940 +3,10,000 33,75,940 YTD expenses 30,24,075 +73,500 30,97,575 YTD net profit 41,865 +2,36,500 2,78,365 শুধু ডিসেম্বর: net profit (1,45,700) +2,36,500 90,800

ডিসেম্বরের unadjusted ক্ষতি 1,45,700 — কারণ নদী গ্রুপের 3,00,000 কাজ হয়েছে কিন্তু invoice হয়নি (unbilled), ABC-র maintenance-এর টাকা আগেই এসেছে (deferred); দুটোই adjusting entry ছাড়া revenue নয়। উল্টো দিকে 73,500 খরচ — DESCO, ISP, subcontractor-এর bill আসেনি, license আর laptop-এর টাকা আগে গেছে — adjusting entry ছাড়া খরচ নয়। Adjustment ছাড়া statement মাসকে “cash-এর কাছাকাছি” দেখায়; adjustment-ই সেটাকে “কাজের” মাস বানায়। Management report-এ এই দুই সংখ্যা পাশাপাশি রাখা — একটা ভালো অভ্যাস।

চারটি statement — একটা pack, একটা snapshot

REPORTING PACK — ডিসেম্বর ২০২৫ snapshot: posted_lines max id 48,213, 04-01-2026 09:40 status: reviewed (হিসাবরক্ষক ০৩-০১, CFO ০৪-০১); final: period lock-এর সাথে (অধ্যায় ২৬) ২. INCOME STATEMENT ডিসে 90,800 · নভ (19,835) · YTD 2,78,365 অধ্যায় ২০ ৩. BALANCE SHEET ৩১ ডিসে A 23,48,865 = L 9,50,500 + E 13,98,365 · ৩০ নভে 23,61,815 অধ্যায় ২১ ৪. SOCE ১ জুলাই 11,20,000 + 2,78,365 = 13,98,365 অধ্যায় ২৩ ৫. CASH FLOW op 2,450 · inv (1,18,000) · fin (20,000) = (1,35,550) অধ্যায় ২২ ৬. NOTES ৯টি (Balance Sheet) + non-cash + নীতি ৭. APPENDIX adjusted TB, worksheet, adjusting entries (ক)–(ছ) basis সহ, cross-check

Cross-check — নয়টি সমীকরণ, সংখ্যায়

# বাঁ পাশ ডান পাশ ফল ── ─────────────────────────────────────── ─────────────────────────────────────── ──── ১ TB Σ Dr 55,08,940 TB Σ Cr 55,08,940 ✓ ২ IS.net_profit (YTD) 2,78,365 TB: IS Cr 33,75,940 − IS Dr 30,97,575 ✓ ৩ BS.3300 2,78,365 IS(01-07 → 31-12) 2,78,365 ✓ ৪ BS.assets 23,48,865 BS.L + E 9,50,500 + 13,98,365 ✓ ৫ SOCE.closing 10,00,000 / 1,20,000 / 2,78,365 BS.equity 3100 / 3200 / 3300 ✓ ৬ SOCE.profit_row 2,78,365 IS.net_profit 2,78,365 ✓ ৭ CF.total (1,35,550) BS cash 18,20,165 − 19,55,715 ✓ ৮ CF.indirect (2,450 / −1,18,000 / −20,000) CF.direct (একই) ✓ ৯ CF.financing capital 0, dividend 0 SOCE rows capital 0, dividend 0 ✓

নয়টি ✓ — pack review-এ যেতে পারে। একটাও ✗ হলে pack তৈরিই হয় না; ✗-এর সারি বলে কোন দুই statement-এর মাঝে ফাঁক, আর সেই অধ্যায়ের assert বলে কোথায়।

একটা late entry — diff

০৫-০১: ডিসেম্বরের একটা bill এল — travel 3,200, bill_date ২৯-১২, period খোলা, pack reviewed কিন্তু final নয়। Post হলো ডিসেম্বরে। Pack আবার চালানো হলো:

DIFF — pack v1 (04-01 09:40) → v2 (05-01 11:15) কারণ: PB-12-0031 (travel 3,200, অফিস মার্ট) Income Statement Travel & Conveyance 6,500 → 9,700 (+3,200) Operating expenses 2,75,700 → 2,78,900 Net profit (ডিসে) 90,800 → 87,600 (−3,200) Net profit (YTD) 2,78,365 → 2,75,165 Balance Sheet Accounts payable 22,000 → 25,200 Current year profit 2,78,365 → 2,75,165 (total assets অপরিবর্তিত; L +3,200, E −3,200) SOCE profit row 2,78,365 → 2,75,165 Cash Flow net profit 90,800 → 87,600; Δ A/P (op) +22,000 → +25,200; operating অপরিবর্তিত 2,450 Cross-check ৯/৯ ✓

Reviewer দেখলেন ছয়টা লাইন, একটা কারণ — দুই মিনিট। Diff ছাড়া পুরো pack আবার পড়তে হতো। আর লক্ষ করুন Cash Flow-এর operating অপরিবর্তিত — bill এসেছে, টাকা যায়নি; সমীকরণ ৭ তবু ✓। Cross-check বলে “সঙ্গতিপূর্ণ”, diff বলে “কী বদলাল” — দুটো আলাদা প্রশ্ন।


৫. Implementation — Software ও Database

Worksheet — একটা function, adjusting set আলাদা করে

worksheet(company, period): adj_types = adjusting source_types -- config: recognition, amortization, accrual, depreciation, -- provision, reclass (অধ্যায় ২৫-এর তালিকা) adjusted = closing(company, period.end) -- অধ্যায় ২১ adjustments = movement(company, period.start, period.end, filter: source_type IN adj_types AND posting_date == period.end) -- শুধু শেষ দিনের unadjusted = {a: adjusted[a] − adjustments[a]} প্রতিটি account: সারি = (unadjusted, adjustments, adjusted) → Dr/Cr column-এ (অধ্যায় ১০-এর signed → Dr/Cr) IS column যদি REVENUE/EXPENSE, নইলে BS column profit = Σ IS Cr − Σ IS Dr assert প্রতিটি column-জোড়া balanced; assert BS Dr == BS Cr + profit

তিনটি বিষয়। movement()-এ filter — অধ্যায় ২০-এর function-এ source_type filter যোগ করতে হবে; এটাই এই অধ্যায়ের একমাত্র engine-বদল। posting_date == period.end — reversal (০১ তারিখ) আর মাসের মাঝের manual JV adjusting নয়। Unadjusted = adjusted − adjustments — আলাদা query নয়, বিয়োগ; দুটো TB এক snapshot থেকে।

Adjusting entries-এর তালিকা — basis সহ

Appendix-এর জন্য:

SELECT e.entry_no, e.source_type, e.source_id, e.narration, l.account, l.debit, l.credit, basis(e) -- source_type-ধরে: accrual → accruals.basis; amortization → prepaid_items; -- recognition → contract position; depreciation → asset schedule FROM posted_lines l JOIN journal_entries e WHERE e.source_type IN adj_types AND e.posting_date = :period_end ORDER BY e.source_type, e.entry_no

basis() অধ্যায় ১৮-এর accrual-এর “basis” column-এর সাধারণীকরণ — প্রতিটি adjusting entry-র পিছনে একটা তালিকা/হিসাব। Auditor-এর প্রথম চাওয়া এটাই।

Cross-check — data

cross_checks id, code, description, left_expr, right_expr, tolerance (DECIMAL, default 0), severity (block | warn), chapter_ref 'TB_BALANCED' tb.sum_debit tb.sum_credit block 'IS_EQ_TB' is.net_profit(ytd) tb.is_credit − tb.is_debit block 'BS_3300_EQ_IS' bs.line('3300') is(fy_start, as_of).net_profit block 'BS_BALANCED' bs.total_assets bs.total_liabilities + bs.equity block 'SOCE_EQ_BS' soce.closing[c] bs.equity[c] (প্রতিটি column) block 'SOCE_EQ_IS' soce.rows.net_profit is.net_profit block 'CF_EQ_DCASH' cf.total bs(to).cash − bs(from−1).cash block 'CF_IND_EQ_DIR' cf.indirect.section[s] cf.direct.section[s] (৩টি) block 'CF_FIN_EQ_SOCE' cf.financing.capital/dividend soce.rows.capital/dividend (cash) warn -- non-cash হলে আলাদা

left_expr/right_expr — ছোট expression language (statement.line / statement.total), evaluate করে তুলনা; tolerance rounding-এর জন্য (হাজারে দেখালে ±1)। নতুন statement = নতুন সারি, কোড নয় — অধ্যায় ৪৯।

Reporting pack — orchestration

reporting_packs id, company_id, period_id, version snapshot_marker BIGINT -- posted_lines max id (বা account_period_balances.computed_at) status VARCHAR(20) -- generating | draft | reviewed | final | superseded generated_at, generated_by reviewed_at, reviewed_by, review_note finalized_at, finalized_by hash CHAR(64) -- সব statement_runs-এর hash-এর hash supersedes_id BIGINT FK NULL UNIQUE (company_id, period_id, version) reporting_pack_items pack_id, kind (is | bs | soce | cf | notes | tb | worksheet | adjustments | cross_check | diff), statement_run_id, payload JSON
generatePack(company, period, by): -- ১. preconditions (অধ্যায় ২৫-এর checklist-এর ফল) যাচাই: period.status ∈ (open, soft_closed) period_end_runs(period).all_steps == done -- reversal, recognition, amortization, accrual, depreciation bank reconciliation (period) == reconciled -- অধ্যায় ১২ subledger == GL: A/R, A/P, 1150, 2135, 2160, 1170, 2180 (অধ্যায় ১৪–১৯-এর যাচাই) nightly checks-এ block-severity ব্যর্থতা নেই ব্যর্থ → pack নয়; তালিকা ফেরত ("bank reconciliation বাকি: City Bank") -- ২. snapshot marker = max(posted_lines.id) BEGIN (repeatable read / snapshot isolation — অধ্যায় ৪৭) -- ৩. statements, ক্রমে tb = trialBalance(period); ws = worksheet(period) is = incomeStatement([period, prev, ytd]) bs = balanceSheet([period.end, prev.end, fy_prev_end]) soce = soce(fy_start, period.end) cf = cashFlow(period) [indirect + direct]; cf_ytd notes = statement_notes-এর প্রতিটি query, as_of -- ৪. cross-check results = প্রতিটি cross_checks সারি evaluate block ব্যর্থ → ROLLBACK, pack নয়, ফল সহ -- ৫. জমা assert max(posted_lines.id) == marker -- মাঝে কেউ post করেনি; নইলে আবার (retry) statement_runs ×4 (+ tb, ws); pack (version = আগেরটা + 1, status = draft, hash) আগের draft/reviewed pack → superseded diff(আগের pack, এই pack) → reporting_pack_items(kind = diff) COMMIT

Snapshot isolation-এর কারণ section ২-এ: চারটি statement এক মুহূর্তের। Database যদি না দেয় (অধ্যায় ৪৭), marker-এর যাচাই দিয়ে retry।

Status ও lock

draft → reviewed হিসাবরক্ষক review, note; reviewed_by ≠ generated_by (four-eyes; config) reviewed → final অনুমোদিত ব্যক্তি; একই transaction-এ period.status = 'closed' (অধ্যায় ২৬) final → superseded শুধু period reopen হলে (approved, logged) — নতুন version generate, পুরনোটা থাকে period-এ post হলে (open/soft_closed): ওই period-এর draft/reviewed pack → is_stale = true; UI-তে "regenerate"

Diff

diffPacks(old, new): প্রতিটি statement, প্রতিটি লাইন, প্রতিটি column: old.value ≠ new.value → (statement, line, column, old, new, Δ) কারণ: posted_lines WHERE id > old.snapshot_marker AND id <= new.snapshot_marker AND posting_date ∈ period → entry-র তালিকা, প্রতিটি কোন লাইন ছুঁয়েছে (account → statement line mapping — অধ্যায় ২০-এর resolve())

statement_runs.rows JSON থেকে — নতুন কিছু জমাতে হয় না।

cover (pack metadata, sign-off block) → IS (comparative), BS (comparative), SOCE, CF (indirect; direct appendix) → notes (নম্বর-ধরে; প্রতিটি note-এর নিচে উৎস report-এর তারিখ) → appendix: adjusted TB, worksheet (landscape), adjusting entries + basis, cross-check table, diff (থাকলে) header/footer: company, period, pack version, hash-এর প্রথম ৮ অক্ষর, "draft" watermark যদি final নয়

Watermark-টা ছোট কিন্তু জরুরি — draft pack ব্যাংকে চলে যাওয়া এই অধ্যায়ের বাস্তব দুর্ঘটনা।

রাতের যাচাই

৮০. প্রতিটি খোলা period: cross_checks সব block ✓ (worksheet + ৪ statement, hash জমা না দিয়ে — dry run) ৮১. final pack-এর period-এ posted_lines.id > pack.snapshot_marker → ০টি (lock ভাঙেনি) ৮২. draft/reviewed pack-এর period-এ নতুন posting → pack.is_stale = true (সেট হয়েছে?) ৮৩. adjusting entry (adj_types) যার posting_date ≠ period end → সতর্কতা (মাসের মাঝে accrual?)

Test হিসেবে

test "worksheet: তিন জোড়া balanced, profit Balance Sheet মেলায়": ws = worksheet(Dec) assert ws.unadj.dr == ws.unadj.cr == 52,60,440; ws.adj.dr == ws.adj.cr == 3,83,500; ws.adjusted.dr == 55,08,940 assert ws.profit == 2,78,365; ws.bs.dr == ws.bs.cr + ws.profit test "unadjusted = adjusted − adjustments, আলাদা query নয়": assert ws.unadjusted[5230] == 66,400; ws.adjustments[5230] == +12,500; ws.adjusted[5230] == 78,900 test "reversal adjusting নয়": assert JV-12-0001 (reversal, 01-12) ∉ ws.adjustments; JV-12-0022 (accrual, 31-12) ∈ ws.adjustments test "pack: নয়টি cross-check ✓, একটা hash": p = generatePack(Dec) assert all(p.cross_checks.ok); p.status == 'draft'; p.hash == hash(p.items.*.hash) test "precondition ব্যর্থ → pack নয়, কারণ": unreconcile(City Bank, Dec) assert raises generatePack(Dec) with "bank reconciliation: City Bank" test "snapshot: মাঝে post হলে retry, দুই statement একমত": concurrently: generatePack(Dec) ‖ post(Dec bill 3,200) p = result; assert p.is.net_profit(ytd) == p.bs.line('3300') -- কখনো 2,78,365 বনাম 2,75,165 নয় test "late entry → stale → regenerate → diff": p1 = generatePack(Dec); post(PB-12-0031, 3,200) assert p1.is_stale p2 = generatePack(Dec); d = diff(p1, p2) assert d.lines == 6-ish; d.causes == [PB-12-0031]; p1.status == 'superseded'; p2.version == 2 test "final pack → period lock; post reject": finalize(p2); assert period(Dec).status == 'closed' assert rejects post(Dec, anything) test "cross-check ✗ → pack নয়": force_insert(JV Dr 1120 / Cr 3200 10,000, source = manual) -- অধ্যায় ২৩-এর দরজা ভেঙে assert raises generatePack(Dec) with 'SOCE_EQ_BS' (বা অধ্যায় ২৩-এর (৩))

৬. Financial Statement Impact

এই অধ্যায়ে নতুন কোনো statement নেই — চারটিকে একটা জিনিসে বাঁধা হলো। যা বদলাল:

আগে: চারটি function, চারটি assert, চারটি hash — যে কেউ যেকোনোটা যেকোনো সময় এখন: একটা pack — একই snapshot, নয়টি cross-check, একটা hash, একটা status, period lock-এর সাথে বাঁধা

আর Part 2-র সব “রাতের যাচাই” (৩০, ৩৮, ৫১…) এখন pack-এর precondition — subledger GL-এর সাথে না মিললে statement-ই তৈরি হয় না। Part 2-তে ওগুলো ছিল “সতর্কতা”; এখানে “দরজা”। এই এক পরিবর্তনে Part 2-র প্রতিটি যাচাই একটা মাসিক deadline পেল।


৭. Common Developer Mistakes

ভুলকী ঘটেসঠিক পথ
চারটি statement চার সময়ে চালানো, pack-এ জোড়াIS-এর profit ≠ BS-এর 3300 (মাঝে post)এক snapshot; marker যাচাই; retry
Unadjusted TB আলাদা query (“adjusting entry বাদে”)দুটো TB দুই মুহূর্তের; বিয়োগ মেলে নাadjusted − adjustments
Reversal-কে adjusting ধরাworksheet-এ ০১ তারিখের entry; unadjusted ভুলposting_date == period.end AND source_type
Manual JV-কে adjusting ধরা (তারিখ ৩১ বলে)হিসাবরক্ষকের সব JV “adjustment”source_type set; manual আলাদা দেখান
Cross-check শুধু “Balance Sheet মেলে”statement-দের মাঝের ফাঁক ধরা পড়ে না (IS ≠ 3300)নয়টি সমীকরণ, data-তে
Cross-check কোডে hardcodeনতুন statement/লাইন → নতুন কোডcross_checks table
Precondition নেইbank reconciliation ছাড়াই pack; subledger ≠ GLchecklist ফল → দরজা
Pack final, period খোলাfinal-এর পরে post; ছাপা সংখ্যা বাসিfinalize ⇒ lock, এক transaction
Draft pack-এ watermark নেইdraft ব্যাংকেwatermark, hash footer
Late entry-র পরে “শুধু ওই statement” আবারpack-এর ভিতরে দুই snapshotপুরো pack regenerate, version +1
Diff নেইreviewer পুরো pack আবার পড়েন, বা পড়েন নালাইন-ধরে diff + কারণ entry
Adjusting entry-র basis নেইauditor: “এই 40,000 কিসের?“basis() source-ধরে; appendix
Worksheet IS column-কে “এই মাস” ভাবাTB closing YTD — মাসের profit নয়YTD; মাসের জন্য অধ্যায় ২০-এর movement
Superseded pack মুছে ফেলা”গতকাল যা দেখিয়েছিলেন” নেইরাখুন; version, supersedes_id

প্রথম আর অষ্টমটা মূল। “একই snapshot” — একটা transaction-এর কথা — না মানলে cross-check-গুলো মাঝে মাঝে ✗ হবে এবং কেউ বুঝবে না কেন; আর “final ⇒ lock” না মানলে pack-এর status-এর কোনো অর্থ নেই। Pack হলো period-এর “সত্য”-র একটা version — version-এর পরে সত্য বদলালে version-ও বদলাতে হবে।


৮. Exercises

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

১। জানুয়ারি ২০২৬-এর worksheet: অধ্যায় ২০–২২-এর exercise ১-এর জানুয়ারি; adjusting entries: reversal নয়; recognition নদী (invoice হলে unbilled → A/R — adjusting? না document?), amortization 5,000, accrual (DESCO 12,000, ISP 8,000), depreciation 8,000। unadjusted প্রথমে লিখুন, তারপর adjusted। জানুয়ারির unadjusted profit, adjusted profit। ২। Section ৪-এর worksheet থেকে শুধু ডিসেম্বরের Income Statement column (YTD নয়) কীভাবে পাবেন? দুটো worksheet-এর পার্থক্য? কোন column-এ পার্থক্য অর্থহীন? ৩। Late entry: ০৮-০১-এ জানা গেল ডিসেম্বরের subcontractor accrual 40,000 নয়, 46,000 হওয়া উচিত ছিল (bill 46,000 এল)। Pack reviewed, final নয়। দুটো পথ: (ক) ডিসেম্বরের accrual বদলানো (খ) জানুয়ারিতে 6,000 (অধ্যায় ১৮-এর নিয়ম)। কোনটা, কেন? diff কী দেখাবে? ৪। Cross-check ৭ ✓ কিন্তু ৮ ✗: indirect operating 2,450, direct operating 10,450; investing উল্টো। কারণ কী হতে পারে (অধ্যায় ২২)? worksheet-এর কোন সারি দেখবেন?

সেট খ — ক্রম ও নির্ভরতা

৫। চারটি statement-এর dependency graph আঁকুন। কোনটা parallel চালানো যায়? snapshot এক হলে parallel নিরাপদ কি? ৬। Precondition-এর তালিকা (section ৫) — কোনটা block, কোনটা warn হওয়া উচিত? "City Bank reconciliation বাকি, কিন্তু ব্যালেন্স তিন মাস ০" — block? ৭। Adjusting source_type-এর set-এ কী কী? প্রতিটির জন্য: কোন অধ্যায়ের job, posting_date কী, basis কোথায়, reversal হয় কি।

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

৮। worksheet.unadjusted = trialBalance(period) WHERE source_type != 'accrual' ৯। pack.hash = hash(is) — "বাকিগুলো তো ওখান থেকেই" ১০। finalize(pack) → status = final; period lock পরে "cron-এ" ১১। cross_check 'BS_BALANCED' tolerance 100 — "rounding" ১২। late entry: incomeStatement আবার চালিয়ে pack-এর IS item replace ১৩। worksheet IS column-এর profit == "ডিসেম্বরের লাভ 90,800"

সেট ঘ — নকশা

১৪। cross_checks-এর expression language — কতটুকু দরকার? `is.net_profit`, `bs.line('3300')`, `bs(to).cash − bs(from−1).cash` — একটা ছোট grammar লিখুন। SQL-এ করা যায়? কোথায় আটকাবে? ১৫। Snapshot isolation নেই এমন database-এ generatePack() — marker-যাচাই + retry যথেষ্ট? কতবার retry? ব্যস্ত দিনে (মাসের ৩ তারিখ, ৪০ জন post করছেন) কখনো সফল হবে? বিকল্প (account_period_balances থেকে, is_stale ছাড়া)? ১৬। Pack-এর PDF ২০ পাতা, ৫টা কোম্পানি × ১২ মাস। Rendering কোথায় — request-এ না queue-এ? hash কীসের উপর — rows JSON না PDF bytes? PDF আবার render করলে byte-এ এক হয়? ১৭। Multi-company (অধ্যায় ৬০-এর আগাম): প্রতিটি company-র pack আলাদা; group-এর consolidated pack (অধ্যায় ৬১)। reporting_packs-এ কী বদলাবে? cross_checks কি company-নিরপেক্ষ?

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


৯. Developer Challenge

ReportingPackService — অধ্যায় ২০–২৩-এর FinancialStatementService-এর উপরে orchestration: worksheet, চার statement, notes, cross-check, version, diff, sign-off, lock — এক button।

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

১. movement()-এ source_type filter (একমাত্র engine-বদল); worksheet(period) — adjusted − adjustments; adjusting set config; reversal ও manual বাদ; দশ column-এর rows JSON; landscape print।

২. cross_checks table + ছোট evaluator (exercise ১৪); নয়টি seed; tolerance, severity; ফল reporting_pack_items(kind = cross_check)-এ, ব্যর্থতায় কোন জোড়া ও কোন অধ্যায়ের assert দেখতে হবে।

৩. generatePack() — preconditions (period_end_runs, reconciliation, subledger == GL, nightly block-ব্যর্থতা), snapshot (isolation বা marker + retry, exercise ১৫), ক্রম, জমা, supersede, diff; is_stale trigger posting-এ।

৪. Status machine — draft → reviewed → final; four-eyes config; finalize() এক transaction-এ period lock (অধ্যায় ২৬-এর closePeriod()-কে ডাকে); reopen → superseded + নতুন version; কখনো delete নয়।

৫. diffPacks() — rows JSON থেকে লাইন-ধরে; কারণ entry-র তালিকা snapshot_marker-এর মাঝ থেকে; UI-তে “কী বদলাল, কেন” এক পাতা।

৬. Print — cover, sign-off block, watermark, hash footer, appendix (TB, worksheet, adjusting entries + basis, cross-check, diff); render queue-এ (exercise ১৬); PDF জমা reporting_pack_items-এ।

৭. রাতের যাচাই ৮০–৮৩ (৮০ dry-run — hash জমা না দিয়ে); test — section ৫-এর নয়টি, আর: (ক) ১২ মাসের pack একসাথে (year-end, অধ্যায় ২৬); (খ) precondition-এ warn বনাম block; (গ) দুটো user একই period-এ একসাথে generate — একটাই version।

৩ আর ৪ নম্বরটাই আসল পরীক্ষা। Snapshot — একটা pack-এর ভিতরে দুটো সত্য থাকতে পারবে না; আর status — final মানে lock, lock মানে final, দুটো এক transaction। এই দুটো ঠিক থাকলে অধ্যায় ২৬-এর period closing অর্ধেক লেখা হয়ে গেল, আর অধ্যায় ৪২-এর Period Lock architecture-এর প্রথম ব্যবহারকারী এই pack।


১০. Summary Card

পথ

posted_lines → unadjusted TB → + adjusting entries (period end, source_type set) → adjusted TB → IS → BS (+3300) → SOCE → CF → cross-check ×9 → review → final ⇒ period lock

Worksheet

১০ column: unadjusted | adjustments | adjusted | IS | BS unadjusted = adjusted − adjustments (এক snapshot); reversal ও manual adjusting নয় IS column YTD (TB closing); profit = IS Cr − IS Dr; BS Cr + profit == BS Dr ডিসেম্বর: unadjusted (1,45,700) → adjustments +2,36,500 → adjusted 90,800 — "ঘটনা" বনাম "সিদ্ধান্ত"

নয়টি cross-check

TB balanced · IS == TB(IS cols) · BS.3300 == IS(ytd) · A == L+E · SOCE.closing == BS.equity SOCE.profit == IS · CF.total == Δcash · CF.indirect == CF.direct (section) · CF.financing == SOCE (cash) data-তে (cross_checks), block/warn, tolerance; একটাও block ✗ ⇒ pack নয়

Pack

preconditions (period_end_runs, reconciliation, subledger == GL, nightly) → দরজা এক snapshot (isolation বা marker + retry) · এক hash · এক status (draft → reviewed → final) final ⇒ lock, এক transaction; late entry ⇒ stale ⇒ regenerate ⇒ version +1 ⇒ diff (লাইন + কারণ) print: cover, statements, notes, appendix (TB, worksheet, adjustments + basis, cross-check), watermark, hash

Developer checklist

□ movement() source_type filter; worksheet() = adjusted − adjustments □ adjusting source_type set config; posting_date == period end □ cross_checks table + evaluator; নয়টি seed □ generatePack(): preconditions → snapshot → ক্রম → cross-check → জমা → supersede → diff □ is_stale on posting; status machine; finalize ⇒ closePeriod() এক transaction □ diffPacks() rows JSON থেকে; কারণ entry □ print queue, watermark, hash footer, appendix □ রাতে: ৮০–৮৩

পরের অধ্যায়

Worksheet-এর মাঝের column — adjustments — এই অধ্যায়ে সাতটা entry ছিল, “কোথা থেকে এল” শুধু অধ্যায়-নম্বর দিয়ে। অধ্যায় ২৫ — Adjusting Entry: প্রতিটি প্রকার (accrual, deferral, depreciation, provision, recognition, reclass, estimate-এর সংশোধন) — কখন, কোন basis-এ, reversal হয় কি না, কে review করে — আর অধ্যায় ১৮-এ প্রতিশ্রুত মাসের শেষের checklist: period_end_runs, ক্রম, idempotency, আধাআধি ভাঙলে কী — যা এই অধ্যায়ের pack-এর precondition ছিল।