অধ্যায় ২৪ — 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 → CFAdjusting 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 == CrCross-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,500Worksheet — সব 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-checkCross-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_nobasis() অধ্যায় ১৮-এর 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 JSONgeneratePack(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)
COMMITSnapshot 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 থেকে — নতুন কিছু জমাতে হয় না।
Print — pack থেকে PDF
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 ≠ GL | checklist ফল → দরজা |
| 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_typefilter (একমাত্র engine-বদল);worksheet(period)— adjusted − adjustments; adjusting set config; reversal ও manual বাদ; দশ column-এর rows JSON; landscape print।২.
cross_checkstable + ছোট 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_staletrigger 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 lockWorksheet
১০ 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, hashDeveloper 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 ছিল।