অধ্যায় ২৬ — Opening, Closing ও Period Closing
Volume 1 · Part 3 — Financial Statements · Chapter 26
পূর্বশর্ত: অধ্যায় ১ (accounting_periods, fiscal_year_start), অধ্যায় ৩ (temporary account শূন্য হয়), অধ্যায় ৯ (OB voucher, snapshot), অধ্যায় ২৩ (3200/3300-এর দরজা), অধ্যায় ২৪–২৫ (pack, checklist)
১. Learning Objective
এই অধ্যায় শেষে আপনি পারবেন:
Period-এর জীবনচক্র — open → soft-closed → closed → locked — প্রতিটি অবস্থায় কী পারা যায়, কে বদলায়, কী log হয়
ক্রমের নিয়ম: আগেরটা বন্ধ না হলে পরেরটা নয়; পরেরটা বন্ধ থাকলে আগেরটা খোলা যায় না — কেন
Reopen — কখন বৈধ, কী হারায় (pack superseded, snapshot stale), কী থাকে (log)
বছরের শেষে closing entry — temporary → 3300 → 3200 — দুই ধাপ, একবার, idempotent, উল্টানো যায়
Pre-closing আর post-closing Trial Balance — দুটোই দরকার, কেন
Closing entry কোথায় বসে যাতে জুনের Income Statement শূন্য না দেখায় — closing period / period 13
Opening — continuous ledger বনাম OB carry-forward; প্রথম দিনের OB entry (migration-এর আগাম)
Audit adjustment বছর শেষের মাস পরে — ১৩তম period, মাসের pack অক্ষত, বছরের pack version 2
Comparative column বছর পেরিয়ে — closing entry-র পরেও গত বছরের Income Statement ঠিক
Proprietorship-এ drawings → capital; company-তে dividend closing-এর আগেসময়: পড়া ৪৫ মিনিট + অনুশীলন ৫০ মিনিট।
২. Concept Explanation
Period — একটা state machine
অধ্যায় ১-এর accounting_periods.status তিনটি ছিল: open, closed, locked। অধ্যায় ২৪-এর pack একটা মাঝের ধাপ চাইল। পুরো জীবনচক্র:
future ──▶ open ──▶ soft_closed ──▶ closed ──▶ locked
▲ │ │
└───────────┴─────────────┘ reopen (approved, logged) locked থেকে কখনো নয়
অবস্থা কে post করতে পারে কী ঘটায়
─────────── ───────────────────────────────────── ─────────────────────────────────
future কেউ নয় (তারিখ এখনো আসেনি; PDC-র due date ব্যতিক্রম নয় — post clearing-এ)
open সবাই (permission-মতো) স্বাভাবিক
soft_closed শুধু adjusting (অধ্যায় ২৫) ও approved checklist চলছে / pack review-এ; document আর নয়
closed কেউ নয় pack final; Balance Sheet "ছাপা"
locked কেউ নয়, reopen-ও নয় audit শেষ; চিরস্থায়ীsoft_closed-এর দরকার কেন: checklist (অধ্যায় ২৫) চলার সময় কেউ bill post করলে ধাপ ৩-এর যাচাই বাসি — অথচ হিসাবরক্ষকের adjusting entry দরকার। তাই দুই স্তর: document বন্ধ, হিসাব খোলা।
ক্রম — period-রা একা নয়
নিয়ম ১: period N close করা যাবে শুধু যদি N−1 closed (বা locked)
নিয়ম ২: period N reopen করা যাবে শুধু যদি N+1 … শেষ পর্যন্ত সব open (বা future)দ্বিতীয়টা কম স্পষ্ট, বেশি জরুরি। নভেম্বর reopen করে একটা bill post করলে নভেম্বরের closing বদলায় → ডিসেম্বরের opening বদলায় → ডিসেম্বরের Balance Sheet বদলায় — অথচ ডিসেম্বর closed, pack final, ব্যাংকে গেছে। Balance Sheet-এর “closing = opening + movement” (অধ্যায় ১০) মানে একটা period-এর সংখ্যা তার আগের সব period-এর উপর দাঁড়িয়ে। তাই পিছনে যেতে হলে সামনের সব খুলতে হয় — অথবা পিছনে না গিয়ে সামনে post (অধ্যায় ১৮-এর DESCO-র bill নভেম্বরে, অক্টোবরে নয়)।
বাস্তবে reopen বিরল: material error, period-এর pack এখনো বাইরে যায়নি, পরের period খোলা। বাকি সব — চলতি period-এ, posting_date আজ, narration-এ “relates to Nov” (অধ্যায় ৪৩)।
Closing entry — বছর শেষে একবার
অধ্যায় ৩-এর প্রতিশ্রুতি: temporary account (4xxx, 5xxx) বছর শেষে শূন্য, নিট 3200-এ। এই বইয়ে দুই ধাপে (অধ্যায় ২১, ২৩-এ 3300 আলাদা রাখার কারণ):
ধাপ ১ — temporary → 3300:
প্রতিটি REVENUE account: Dr (তার credit balance) }
প্রতিটি EXPENSE account: Cr (তার debit balance) } একটা entry, বহু line
Cr 3300 Current Year Profit (পার্থক্য = বছরের লাভ) } ক্ষতি হলে Dr 3300
ধাপ ২ — 3300 → 3200:
Dr 3300 / Cr 3200 Retained Earnings (লাভ)
proprietorship: + Dr 3100 Capital / Cr 3400 Drawings (drawings capital-এ)এক entry-তেও করা যায় (সরাসরি 3200-এ); দুই ধাপে রাখার লাভ — ledger-এ “বছরের লাভ” একটা সংখ্যা হিসেবে দেখা যায়, আর SOCE-র transfer row (অধ্যায় ২৩) স্পষ্ট। ফল:
closing-এর পরে: 4xxx, 5xxx সব শূন্য; 3300 শূন্য; 3200 += বছরের লাভ; Balance Sheet অপরিবর্তিত (equity-র ভিতরে সরল)
পরের বছর: Income Statement শূন্য থেকে; movement() এমনিতেই (নিচের সমস্যা ছাড়া)কখন? বছর শেষের দিন নয় — audit-এর পরে, মাস দুই-তিন পরে। মাঝে “বছর শেষ কিন্তু close হয়নি” অবস্থা — 3300 computed (অধ্যায় ২১) সেই সময়েও ঠিক Balance Sheet দেয়। Closing entry report-এর জন্য দরকার নয়; দরকার ledger পরিষ্কার রাখতে আর “এ বছর” শব্দটার সীমানা টানতে।
Closing entry-র জায়গা — জুনের Income Statement যেন শূন্য না হয়
সমস্যা: closing entry-র posting_date ৩০ জুন। অধ্যায় ২০-এর movement(01-06, 30-06) জুনের Income Statement বানাতে গিয়ে closing entry-ও গুনবে — 4110-এ বছরের পুরো credit-এর সমান debit — জুনের revenue ঋণাত্মক। তিনটি সমাধান:
ক. movement()-এ source_type = 'closing_entry' বাদ (IS-এর জন্য সবসময়)
খ. closing entry একটা আলাদা period-এ — period_no 13 "Closing", তারিখ ৩০ জুন, is_closing = true;
IS-এর query period-ধরে হলে 13 বাদ; তারিখ-ধরে হলে ক-ও লাগে
গ. closing entry-র তারিখ ০১ জুলাই (পরের বছরের প্রথম দিন) — জুলাইয়ের IS ঋণাত্মক — নাএই বইয়ে ক + খ দুটোই: closing entry period 13-এ, source_type = 'closing_entry', আর movement() default-এ closing_entry বাদ (Balance Sheet-এর closing()-এ বাদ নয় — 3200 ঠিক থাকতে হবে)। Period 13 আরেকটা কাজেও লাগে — নিচে।
Pre-closing আর post-closing Trial Balance
pre-closing TB (৩০ জুন, closing entry ছাড়া) সব account; 4xxx/5xxx বছরের মোট; অধ্যায় ২৪-এর worksheet এটা থেকেই
post-closing TB (৩০ জুন, closing entry সহ) শুধু permanent; 4xxx/5xxx শূন্য; 3200 নতুন
যাচাই: post-closing TB == পরের বছরের ১ জুলাইয়ের opening (অধ্যায় ১০-এর "জুন-জুলাই" যাচাই)দুটোই ছাপা হয় — pre-closing auditor-এর জন্য (বছরের কাজ), post-closing পরের বছরের শুরুর জন্য।
১৩তম period — বছরের পরে বছরের entry
Audit সেপ্টেম্বরে; auditor বলেন “৩০ জুনের provision 50,000 কম”। জুন closed, pack final, বারো মাসের pack-ও। কোথায় post?
জুনে (reopen) বারো মাসের pack, জুনের pack — সব superseded; জুলাই-আগস্টের opening বদলায়; না
সেপ্টেম্বরে বছরের Balance Sheet ভুলই থাকে (৩০ জুনে provision নেই); আইনত সেটাই ছাপা হবে; না
period 13, ৩০ জুন বছরের Balance Sheet ঠিক (৩০ জুন closing-এ আছে); জুনের মাসিক pack অক্ষত
(মাসের IS period-ধরে, 13 বাদ); বছরের pack version 2; জুলাইয়ের opening বদলায় —
কিন্তু জুলাই তখনো open (audit চলছে) — ঠিক আছেPeriod 13: is_adjustment = true, start = end = ৩০ জুন, status নিজের। শুধু adjusting/audit entry, approved। বছরের IS (period 1–13) আর মাসের IS (period 1–12 আলাদা) — দুটোর পার্থক্যই “audit adjustments” — note-এ। Closing entry-ও এই period-এ, সবার শেষে।
Opening — ledger চলমান, না বহন
দুটো ধারা:
continuous ledger closing(as_of) = Σ posted_lines WHERE date <= as_of — প্রথম দিন থেকে; OB entry শুধু প্রথম দিনে
carry-forward প্রতি বছরের শুরুতে OB entry (গত বছরের post-closing TB); বছরের query বছরের ভিতরেএই বইয়ে continuous — সরল, একটা সত্য, অধ্যায় ২১-এর closing() এটাই ধরে। দাম: দশ বছর পরে closing() দশ বছরের line যোগ করে — অধ্যায় ৯-এর snapshot (account_period_balances) সেটা মেটায়: period 1-এর opening_signed = গত বছরের post-closing; query snapshot থেকে, raw শুধু চলতি period-এ। ফল carry-forward-এর মতো, সত্য continuous-এর। Carry-forward-এর আসল ব্যবহার: প্রথম দিন — system-এ আসার দিন আগের সব হিসাবের একটা OB entry (অধ্যায় ৯-এর OB voucher); বিস্তারিত অধ্যায় ৬২ (migration)।
OB entry-র জায়গা: period 0 (is_opening = true), তারিখ অর্থবছর শুরুর আগের দিন — যাতে বছরের movement()-এ না ঢোকে, closing()-এ ঢোকে। Period 13-এর আয়না।
Lock — audit-এর পরে চিরতরে
closed থেকে locked: audit report সই, statement জমা। Locked period reopen হয় না — কোনো permission-এই না। ভুল পাওয়া গেলে prior-period adjustment (অধ্যায় ২৩) চলতি বছরে, restated comparative। এটা technical নয়, আইনি — ছাপা statement বদলায় না; software শুধু সেটা মানে। Lock-এর পরে snapshot-ও immutable (is_stale কখনো true হবে না — হলে bug)।
যা এই অধ্যায়ে নেই
Period lock-এর architecture (permission, API, concurrency, bypass-এর audit trail) অধ্যায় ৪২
Migration-এর OB (subledger-সহ, open item-সহ, mid-year) অধ্যায় ৬২
Multi-company year-end (একই দিনে ৫টা) অধ্যায় ৬০
Tax provision-এর হিসাব (closing-এর আগে শেষ adjusting) অধ্যায় ৫৫৩. Accounting Rule
Period status
future → open → soft_closed → closed → locked; reopen: soft_closed/closed → open (approved, logged); locked: কখনো নয়
open: সব; soft_closed: adjusting + approved; closed / locked: কিছু নয়
close N ⇐ N−1 closed; reopen N ⇐ N+1 … সব open/futureClosing entry
কখন: audit-এর পরে, period 13-এ, তারিখ = FY শেষ দিন, সবার শেষে
ধাপ ১: Dr প্রতিটি REVENUE (balance), Cr প্রতিটি EXPENSE (balance), Cr/Dr 3300 (নিট)
ধাপ ২: Dr 3300 / Cr 3200 (লাভ); ক্ষতি উল্টো; proprietorship: Dr 3100 / Cr 3400
source_type = 'closing_entry'; idempotency_key = ('closing_entry', fy); উল্টানো = reopenYear()
ফল: 4xxx, 5xxx, 3300 = 0; 3200 += লাভ; Balance Sheet মোট অপরিবর্তিতReport-এ closing entry
movement() (Income Statement, SOCE-র profit) → closing_entry বাদ; period 13 বাদ (মাসের report-এ)
closing() (Balance Sheet) → সব সহ
বছরের IS = period 1–13; মাসের IS = নিজের period; পার্থক্য = audit adjustments (note)Trial Balance
pre-closing: closing entry ছাড়া; post-closing: সহ; post-closing == পরের বছরের openingOpening
continuous ledger; snapshot-এ period 1 opening = গত post-closing
প্রথম দিন: OB entry, period 0, তারিখ FY শুরুর আগের দিন, balanced (অধ্যায় ৬২)Period 13 / 0
13: is_adjustment, start = end = FY শেষ; শুধু adjusting/audit + closing entry; approved
0: is_opening, start = end = FY শুরু − 1; শুধু OB৪. Real Business Example
FY2026 (জুলাই ২০২৫ – জুন ২০২৬) — বছরের শেষ
ধরা যাক জানুয়ারি–জুন ২০২৬-এ লাভ 2,40,000 (অধ্যায় ২৩-এর exercise ১); বছরের মোট revenue (সব 4xxx, নিট) 68,40,000, expense (সব 5xxx) 63,21,635 → লাভ 5,18,365 (= ডিসেম্বর পর্যন্ত 2,78,365 + 2,40,000)। মালিক এ বছর পুঁজি দেননি, dividend নেই।
তারিখের ক্রম:
৩০-০৬-২০২৬ বছরের শেষ দিন; জুন open
০৩-০৭ জুনের checklist (অধ্যায় ২৫) — reversal ০১-০৭ হয়ে গেছে; জুন soft_closed
০৮-০৭ জুনের pack v1 reviewed → final; জুন closed; জুলাই open (০১-০৭ থেকেই)
১০-০৭ বছরের pack v1 (period 1–12) — draft; auditor-কে; 3300 computed 5,18,365, 3200 1,20,000
২০-০৯ auditor: ৩০ জুনে warranty provision 50,000 নেই (অধ্যায় ৫৯-এর প্রকার); audit fee accrual 75,000 কম
২২-০৯ period 13 (FY2026, তারিখ ৩০-০৬) খোলা; দুটো audit adjustment post, approved CFO + auditor ref
২৫-০৯ বছরের pack v2 (period 1–13): লাভ 5,18,365 − 1,25,000 = 3,93,365; reviewed → final
২৫-০৯ closeYear(FY2026): closing entry period 13-এ; period 13 closed
৩০-০৯ audit report সই; FY2026-এর সব period lockedPre-closing Trial Balance — ৩০ জুন ২০২৬, period 1–13 (অংশ)
Code Account Dr Cr
──── ───────────────────────── ────────── ──────────
… (permanent accounts) … …
2135 Accrued Expenses 1,35,500 ← জুনের 60,500 + audit fee 75,000
2190 Warranty Provision 50,000 ← period 13
3100 Share Capital 10,00,000
3200 Retained Earnings 1,20,000 ← এখনো গত বছরের
3300 Current Year Profit 0 ← TB-তে নেই; report-এ computed 3,93,365
4110 Software Development Income 64,20,000
4120 Maintenance & Support Income 1,20,000
4130 Consulting Income 2,46,000
4180 Sales Returns & Allowances 54,000
… (বাকি 4xxx, 5xxx) … …
5270 Professional Fee 4,05,000 ← + 75,000 audit fee (period 13)
5540 Warranty Expense 50,000 ← period 13
────────── ──────────
Σ REVENUE (নিট) 68,40,000
Σ EXPENSE 64,46,635
লাভ (period 1–13) 3,93,365মাসিক pack-গুলোর যোগ (period 1–12) 5,18,365; বছরের (1–13) 3,93,365; পার্থক্য 1,25,000 = period 13 — বছরের pack-এর note: “audit adjustments: provision 50,000, audit fee 75,000”।
Closing entry — period 13, ২৫-০৯, তারিখ ৩০-০৬
JV-13-0003 source = closing_entry, idempotency_key = ('closing_entry', 2026), period 13
ধাপ ১
Dr 4110 Software Development Income 64,20,000
Dr 4120 Maintenance & Support Income 1,20,000
Dr 4130 Consulting Income 2,46,000
Dr 4510, 4540 … …
Cr 4180 Sales Returns & Allowances 54,000 ← contra: debit balance, তাই Cr
Cr 4190 Discount Allowed …
Cr 5110 Developer Salary — Billable 26,40,000
Cr 5120 … 5540 (প্রতিটি expense, তার balance) …
Cr 3300 Current Year Profit 3,93,365 ← Σ Dr − Σ Cr
ধাপ ২
Dr 3300 Current Year Profit 3,93,365
Cr 3200 Retained Earnings 3,93,365একটা entry, ৩০-এর বেশি line, balanced (Σ revenue Dr == Σ expense Cr + লাভ)। Drawings নেই (company)। Dividend থাকলে closing-এর আগে ঘোষণা — distributable-এর হিসাব (অধ্যায় ২৩) closing-এর আগের 3300 + 3200 থেকে।
Post-closing — একই দিন, ৩০ জুন, closing entry সহ
4xxx, 5xxx সব 0
3300 0
3200 1,20,000 + 3,93,365 = 5,13,365
Balance Sheet (৩০-০৬-২০২৬) equity: 10,00,000 + 5,13,365 + 0 = 15,13,365 — closing-এর আগেও এটাই ছিল (10,00,000 + 1,20,000 + 3,93,365)
post-closing TB == জুলাই ২০২৬-এর opening ✓ (account_period_balances FY2027 period 1 rebuild)Balance Sheet-এর মোট বদলায়নি — equity-র ভিতরে 3,93,365 এক ঘর থেকে অন্য ঘরে। Closing entry কোনো report-এর সংখ্যা বদলায় না (SOCE-র transfer row ছাড়া) — শুধু ledger গোছায়।
জুলাই ২০২৬ — নতুন বছর
Income Statement (জুলাই ২০২৬): 4110 জুলাইয়ের invoice/recognition — শূন্য থেকে; closing entry period 13-এ, বাদ
Income Statement (জুন ২০২৬): আগের মতোই — movement() closing_entry বাদ দেয়; period 12 শুধু
Balance Sheet (৩১-০৭-২০২৬): 3200 5,13,365; 3300 জুলাইয়ের লাভ (computed, FY2027 শুরু থেকে)
গত বছরের column (IS): movement(01-07-2025, 30-06-2026) — closing_entry বাদ — 5,18,365 (1–12) বা 3,93,365 (1–13, config)শেষ লাইনটা অধ্যায় ২০-এর test — “গত বছরের column closing entry-র পরেও ঠিক” — এখানে প্রমাণ।
একটা reopen — কেন হলো না
০৫-০৮: জুলাইয়ের বই দেখতে গিয়ে হিসাবরক্ষক পেলেন — মে মাসের একটা travel bill 4,200 post হয়নি। মে closed, জুন closed, জুলাই open। নিয়ম ২: মে reopen করতে জুন-ও খুলতে হবে — জুনের pack final, বছরের pack auditor-এর হাতে। না। পথ: জুলাইয়ে post, narration “May 2026 bill, received late”, 5260 জুলাইয়ে 4,200 বেশি — অধ্যায় ১৮-এর DESCO-র নিয়ম। 4,200 material নয়; হলে period 13-এ (audit adjustment হিসেবে, auditor-এর সাথে)।
৫. Implementation — Software ও Database
accounting_periods — অধ্যায় ১-এর table, বাড়ানো
accounting_periods (+)
period_no SMALLINT -- 0 (opening) | 1..12 | 13 (adjustment/closing)
status VARCHAR(20) -- future | open | soft_closed | closed | locked
is_opening BOOLEAN -- period 0
is_adjustment BOOLEAN -- period 13
soft_closed_at/by, closed_at/by, locked_at/by
CHECK (period_no BETWEEN 0 AND 13)
UNIQUE (company_id, fiscal_year, period_no)period_status_log
id, period_id, from_status, to_status, at, by, reason TEXT, approved_by NULL, pack_id NULLপ্রতিটি transition একটা সারি — reopen-এর reason বাধ্যতামূলক। অধ্যায় ৪৫-এর audit trail-এর প্রথম গ্রাহক।
Transition — নিয়ম কোডে
transition(period, to, by, reason = NULL):
from = period.status
allowed = {
(future, open), (open, soft_closed), (soft_closed, open), (soft_closed, closed),
(closed, open), -- reopen: approval
(closed, locked)
}
যাচাই: (from, to) ∈ allowed; নইলে reject -- locked থেকে কিছু নয়
যদি to == 'closed':
যাচাই: prev(period).status ∈ (closed, locked) বা period_no == 0/1-এর প্রথম -- নিয়ম ১
যাচাই: reporting_packs WHERE period, status = 'final' আছে (period 0/13 ব্যতিক্রম) -- অধ্যায় ২৪
যদি to == 'open' AND from ∈ (soft_closed, closed): -- reopen
যাচাই: সব later(period).status ∈ (open, future) -- নিয়ম ২
যাচাই: reason NOT NULL; approver ≠ by; permission 'period.reopen' (অধ্যায় ৪৮)
final pack → superseded; snapshot(period …) is_stale = true
যদি to == 'locked':
যাচাই: from == closed; fiscal year-এর সব period closed (lock বছর-ধরে, মাস-ধরে নয়)
UPDATE period.status; INSERT period_status_logfinalize(pack) (অধ্যায় ২৪) এই function-কে ডাকে to = closed — একই transaction।
post() — period-এর দরজা
অধ্যায় ৮-এর post()-এ যাচাই (এতদিন “period খোলা” বলে আসছি; এখন পুরো):
periodFor(company, posting_date, hint = NULL):
p = accounting_periods WHERE start <= date <= end AND period_no BETWEEN 1 AND 12
যদি hint == 'adjustment': p = period 13 WHERE fiscal_year = FY(date) -- audit entry স্পষ্ট করে বলে
যদি hint == 'opening': p = period 0
return p
canPost(entry):
p = periodFor(entry.posting_date, entry.period_hint)
যদি p.status == 'open': ✓
যদি p.status == 'soft_closed': entry.source_type ∈ adjusting set বা entry.approved_for_soft_close → ✓; নইলে reject
যদি p.status ∈ (closed, locked, future): reject ("period Dec 2025 closed; post in current period or request reopen")
period 0: শুধু source_type = 'opening_balance'; period 13: শুধু adjusting set + closing_entry + audit_adjustmentReject-এর বার্তায় কী করতে হবে — অধ্যায় ২৫-এর blocked_reason-এর রীতি।
closeYear() — একবার, idempotent, উল্টানো যায়
closeYear(company, fy, by):
যাচাই: period 1..12 সব closed; period 13 open; বছরের pack (1–13) final
dividend_declarations (fy) সব posted; কোনো draft entry নেই
key = ('closing_entry', fy)
যদি journal_entries WHERE idempotency_key = key: return (আগেই হয়েছে)
bal = closing(company, fy.end) filter REVENUE, EXPENSE -- pre-closing, period 13 সহ
lines = []
প্রতিটি a in bal WHERE bal[a] ≠ 0:
lines += (a, debit = max(−bal[a].signed_dr, 0) …) -- credit balance → Dr, debit balance → Cr
profit = Σ credit balances − Σ debit balances
lines += (3300, profit > 0 ? credit : debit, |profit|)
e1 = post(JV(period = 13, date = fy.end, source = closing_entry, key, lines)) -- ধাপ ১
e2 = post(JV(period = 13, date = fy.end, source = closing_entry, key = ('closing_transfer', fy),
Dr 3300 / Cr 3200 |profit| (ক্ষতি উল্টো)
+ proprietorship: Dr 3100 / Cr 3400 (drawings balance))) -- ধাপ ২
transition(period 13, closed, by)
rebuildSnapshot(company, FY next, period 1) -- opening = post-closing
assert closing(fy.end)[REVENUE, EXPENSE, 3300] == 0
assert closing(fy.end)[3200] == আগের 3200 + profitLine-এর সংখ্যা — সব 4xxx/5xxx এক entry-তে (৩০–২০০ line) — ledger-এ প্রতিটি account-এ একটা “closing” line, drill-down সহজ; অধ্যায় ২৫-এর exercise ৬-এর যুক্তি এখানেও।
reopenYear() — closing entry উল্টে
reopenYear(company, fy, by, reason):
যাচাই: fy locked নয়; পরের FY-এর কোনো period closed নয় (নিয়ম ২ বছর-স্তরে); approval
e1, e2 = entries WHERE key IN (('closing_entry', fy), ('closing_transfer', fy))
reverse(e2), reverse(e1) -- অধ্যায় ৪৩-এর reversal, period 13-এ, key ('reversal', e.id)
transition(period 13, open); বছরের pack → superseded; snapshot next FY stale
logReversal — নতুন entry, পুরনোটা থাকে (immutable, অধ্যায় ১৯)। আবার closeYear() চালালে নতুন key? না — একই key-তে আগের entry আছে (reversed)। Key-তে reversed_at IS NULL শর্ত, বা key-তে attempt নম্বর: ('closing_entry', fy, n)। দ্বিতীয়টা সরল।
movement() ও closing() — closing entry-র সাথে
অধ্যায় ২০-এর movement()-এ default:
movement(company, from, to, filter = {}, include_closing = false):
WHERE …
AND (:include_closing OR source_type NOT IN ('closing_entry'))
AND (:include_adjustment_period OR period.period_no <> 13) -- মাসের report-এ 13 বাদ; বছরের-এ সহ (config)
closing(company, as_of): movement(NULL, as_of, include_closing = true, include_adjustment_period = true)দুটো flag; Balance Sheet সব সহ, Income Statement/SOCE-profit বাদ। currentYearProfit() (অধ্যায় ২১) movement() দিয়ে — closing-এর পরে স্বয়ংক্রিয় 0 নয়! closing_entry বাদ দিলে movement() পুরো বছরের লাভই দেখাবে। তাই:
currentYearProfit(company, as_of):
যদি closing entry (FY(as_of)) posted: return 0 -- 3200-এ চলে গেছে
নইলে: Σ movement(fy_start, as_of, REVENUE+EXPENSE).signed_crঅধ্যায় ২১-এ এই শর্তের প্রতিশ্রুতি ছিল — এখানে তার কারণ: flag-এর জন্য closing entry movement()-এ অদৃশ্য, তাই “লাভ চলে গেছে” কথাটা সূত্র নিজে জানে না, বলে দিতে হয়।
Opening balance entry — প্রথম দিন
postOpeningBalances(company, fy, lines, by):
যাচাই: company-র কোনো posted entry নেই (প্রথম দিন), বা migration mode (অধ্যায় ৬২)
Σ debit == Σ credit (নইলে পার্থক্য 3200-এ নয় — কারণ খুঁজুন; অধ্যায় ৬২-এর suspense)
REVENUE/EXPENSE line নেই (mid-year migration ব্যতিক্রম, অধ্যায় ৬২)
period 0 (is_opening), date = fy.start − 1, source = opening_balance, key ('opening_balance', fy)
control account-এর line-এ subledger detail বাধ্যতামূলক (A/R → open invoices, অধ্যায় ৬২)
transition(period 0, closed) -- সাথে সাথেPeriod তৈরি — বছর খোলা
openFiscalYear(company, fy):
যাচাই: আগের FY-এর period 12 অন্তত soft_closed (দুটো বছর একসাথে খোলা — জুলাই-সেপ্টেম্বর — স্বাভাবিক)
INSERT periods 0 (closed, খালি), 1..12 (1 open, বাকি future), 13 (future)
fiscal_year নামকরণ অধ্যায় ১-এর conventionমাসের শুরুতে future → open — স্বয়ংক্রিয় (তারিখে) বা হাতে; একসাথে দুটোর বেশি মাস open থাকলে সতর্কতা (checklist পিছিয়ে)।
রাতের যাচাই
৯০. closed/locked period-এ posted_lines যার created_at > closed_at → ০টি (দরজা ভাঙেনি)
৯১. open period-এর আগে কোনো open period → সতর্কতা (ক্রম; ২টার বেশি open → সতর্কতা)
৯২. closed FY-এ: closing(fy.end)[REVENUE, EXPENSE, 3300] == 0; post-closing TB == next FY period 1 opening
৯৩. period 13-এ source_type ∉ (adjusting set, audit_adjustment, closing_entry) → ০টি; period 0-তে ≠ opening_balance → ০টি
৯৪. locked period-এর snapshot is_stale = true → ০টি (হলে bug — কেউ post করেছে)
৯৫. period_status_log-এ reopen যার reason খালি বা approved_by NULL → ০টিTest হিসেবে
test "ক্রম: আগেরটা বন্ধ না হলে পরেরটা নয়":
assert rejects transition(Dec, closed) while Nov.status == open
test "reopen: পরেরটা বন্ধ থাকলে না":
close(Nov); close(Dec)
assert rejects transition(Nov, open)
reopen(Dec, reason, approver); reopen(Nov, reason, approver) → ✓; Dec pack superseded
test "soft_closed: adjusting হ্যাঁ, bill না":
transition(Dec, soft_closed)
assert rejects post(bill, Dec); assert post(accrual, Dec)
test "closeYear idempotent, উল্টানো যায়":
closeYear(2026); n = count(entries); closeYear(2026); assert count(entries) == n
assert closing(30-06)[4110] == 0; closing(30-06)[3200] == 1,20,000 + 3,93,365
reopenYear(2026); assert closing(30-06)[4110] == 64,20,000 Cr; 3200 == 1,20,000
closeYear(2026); ✓ আবার (key attempt 2)
test "closing entry জুনের Income Statement বদলায় না":
is_before = incomeStatement(Jun 2026); closeYear(2026)
assert incomeStatement(Jun 2026) == is_before
assert incomeStatement(Jul 2026).line('Software Dev') == শুধু জুলাইয়ের
test "Balance Sheet closing-এর আগে-পরে এক":
bs1 = balanceSheet(30-06); closeYear(2026); bs2 = balanceSheet(30-06)
assert bs1.total_equity == bs2.total_equity; bs2.line('3300') == 0; bs2.line('3200') == bs1.line('3200') + bs1.line('3300')
test "period 13: মাসের pack অক্ষত, বছরের pack বদলায়":
post(period 13, audit adjustment 1,25,000)
assert incomeStatement(Jun 2026).net_profit অপরিবর্তিত
assert incomeStatement(FY2026, include_adjustment_period).net_profit == 5,18,365 − 1,25,000
test "post-closing TB == পরের বছরের opening":
closeYear(2026)
assert trialBalance(FY2027, period 1).opening == closing(30-06-2026) প্রতিটি permanent account; 4xxx/5xxx opening 0
test "locked: কিছু নয়, কারো জন্য নয়":
lock(FY2026)
assert rejects transition(Jun 2026, open) even with 'period.reopen' permission
assert rejects post(Jun 2026, anything)
test "গত বছরের column closing-এর পরে ঠিক":
closeYear(2026)
assert incomeStatement(Dec 2026, columns = [this, last_year_same]).last_year.net_profit == 90,800৬. Financial Statement Impact
Closing entry-র উদ্দেশ্যই “কোনো statement না বদলানো”:
Income Statement বদলায় না — movement() closing_entry বাদ; বছরের IS = 1–13, মাসের = নিজের period
Balance Sheet মোট বদলায় না; equity-র ভিতরে 3300 → 3200
SOCE একটা row: "Transfer to retained earnings" — profit column −, RE column +, total 0
Cash Flow কিছু নয় — non_cash category (অধ্যায় ২২), দুটো কাটাকাটিআর period lock-এর ছাপ প্রতিটি statement-এ: closed period-এর statement আর বদলায় না — statement_runs.hash আবার চালালে মেলে (অধ্যায় ২০-এর যাচাই ৬১ — এই অধ্যায়ে তার প্রতিশ্রুতি পূরণ হলো)। Locked period-এর জন্য এটা চিরস্থায়ী; সেটাই “ছাপা statement”-এর মানে।
৭. Common Developer Mistakes
| ভুল | কী ঘটে | সঠিক পথ |
|---|---|---|
| Closing entry ৩০ জুনে সাধারণ period-এ | জুনের Income Statement শূন্য/ঋণাত্মক | period 13 + movement() বাদ |
| Closing entry ০১ জুলাইয়ে | জুলাইয়ের IS ঋণাত্মক; গত বছরের 3200 ভুল তারিখে | ৩০ জুন, period 13 |
| movement()-এ closing বাদ, কিন্তু closing()-এও | Balance Sheet-এ 3200 কখনো বাড়ে না | দুই flag; closing() সব সহ |
| currentYearProfit() closing-এর পরে বছরের লাভ দেখায় | Balance Sheet-এ লাভ দুবার (3200 + 3300) | closing posted হলে 0 |
| Closing entry হাতে, প্রতি account আলাদা JV | ৩০টা entry, একটা বাদ, 3200 ভুল | closeYear(), এক entry, key |
| Closing entry দুবার | 3200 দ্বিগুণ | idempotency_key; reopen-এ attempt নম্বর |
| Reopen করলে closing entry মুছে ফেলা | audit trail নেই | reversal, নতুন entry |
| Closed period-এ post “admin” দিয়ে | pack-এর সংখ্যা বাসি, hash ভাঙে, কেউ জানে না | দরজা post()-এ; admin-ও নয়; reopen procedure |
| Reopen পিছনের period, সামনেরটা closed রেখে | সামনের opening বদলায়, ছাপা Balance Sheet মিথ্যা | নিয়ম ২ |
| Audit adjustment জুনে reopen করে | ১২ মাসের pack superseded; জুলাই-আগস্ট নড়ে | period 13 |
| Audit adjustment সেপ্টেম্বরে | বছরের Balance Sheet ভুল ছাপা | period 13, তারিখ ৩০ জুন |
| Period 13 সাধারণ document-এর জন্যও খোলা | ”জুনের bill” সেপ্টেম্বরে period 13-এ — ১২ মাসের pack আর বছরের pack মেলে না, কেউ জানে না কেন | শুধু adjusting/audit; যাচাই ৯৩ |
| Locked reopen-এর backdoor | আইনি সমস্যা | locked = কখনো নয়; prior-period adjustment চলতি বছরে |
| OB entry FY-এর প্রথম দিনে | বছরের movement()-এ OB ঢোকে; জুলাইয়ের IS-এ গত বছরের সব | period 0, আগের দিন |
| OB-র অমিল 3200-এ plug | migration-এর ভুল retained earnings-এ চিরকাল | balanced বাধ্যতামূলক; suspense + অধ্যায় ৬২ |
| Snapshot rebuild না করে বছর খোলা | জুলাইয়ের opening ০ | closeYear() → rebuildSnapshot(next, 1) |
| Period status শুধু UI-তে (API bypass) | integration post করে closed-এ | post()-এ; অধ্যায় ৪২ |
প্রথম আর অষ্টমটা মূল। Closing entry-র জায়গা ভুল হলে বছরের শেষ মাস চিরকাল অদ্ভুত দেখাবে, আর কেউ বুঝবে না কেন; closed period-এ “admin” post — এই একটা backdoor থাকলে অধ্যায় ২০–২৫-এর সব hash, pack, lock, status অর্থহীন। Period close হলো এই বইয়ের একমাত্র জায়গা যেখানে “না” মানে না — permission নয়, নীতি।
৮. Exercises
সেট ক — হাতে
১। রহমান ট্রেডার্স (অধ্যায় ২৩): ৩১-১২-২০২৫-এ closing entry — revenue 18,00,000
(দুটো account), expense 15,90,000 (পাঁচটা), drawings 2,20,000, capital
14,80,000 (adjustment-সহ)। দুই ধাপ লিখুন। post-closing TB-র 3100?
২। Section ৪-এর FY2026: যদি ২০-০৬-এ interim dividend 1,00,000 ঘোষণা হতো
(অধ্যায় ২৩-এর exercise ১) — closing entry বদলায়? 3200 closing কত?
distributable-এর যাচাই কোন সংখ্যায়?
৩। ক্ষতির বছর: revenue 40,00,000, expense 46,00,000। closing entry দুই ধাপ।
3200 আগে 1,20,000 — পরে? ঋণাত্মক retained earnings Balance Sheet-এ
কীভাবে দেখাবেন?
৪। Period 13-এ audit adjustment 1,25,000 post হলো, তারপর closing। জুনের
মাসিক IS, বছরের IS (1–12), বছরের IS (1–13), Balance Sheet ৩০-০৬ —
চারটির লাভ/3300 কত? কোনটা ছাপা হয় কোথায়?সেট খ — নিয়ম
৫। নিয়ম ২ (reopen)-এর একটা উদাহরণ সংখ্যায়: নভেম্বর reopen করে 10,000-এর
bill post — ডিসেম্বরের Balance Sheet-এর কোন লাইন কত বদলাত, ডিসেম্বর
closed থাকলে কে জানত?
৬। soft_closed-এ "approved_for_soft_close" — কোন document-এর দরকার হতে
পারে (adjusting নয়, কিন্তু মাসের)? কে approve করে? এটা কি নিয়মের ফাঁক?
৭। দুটো বছর একসাথে open (জুলাই–সেপ্টেম্বর): FY2026 period 13 open, FY2027
period 1–3 open। কোন কোন report দুই বছরের data মেশাতে পারে? snapshot-এর
is_stale কীভাবে ছড়ায় (period 13-এ post → FY2027 সব stale)?
৮। Locked-এর পরে ভুল: FY2026-এ 2,00,000-এর revenue দুবার post হয়েছিল, ধরা
পড়ল FY2027-এর নভেম্বরে। কী করবেন (অধ্যায় ২৩)? FY2026-এর ছাপা statement?
FY2027-এর comparative column?সেট গ — কোনটা ভুল
৯। UPDATE accounting_periods SET status = 'open' WHERE id = … — "admin script"
১০। closing entry: প্রতি account আলাদা JV, source = 'manual', তারিখ ৩০-০৬
১১। closeYear(): DELETE FROM posted_lines WHERE account_type IN (REVENUE, EXPENSE) AND fy = …
১২। OB entry তারিখ ০১-০৭, period 1
১৩। period 13-এ জুনের দেরিতে আসা bill (document) post — "৩০ জুনের তো"
১৪। movement() সবসময় closing_entry বাদ — Balance Sheet-এও
১৫। reopen: closing entry-র status = 'cancelled'সেট ঘ — নকশা
১৬। transition()-এর allowed set data-তে না কোডে? company-ধরে আলাদা হতে
পারে (কারো soft_closed নেই)? নিয়ম ১, ২ কি কখনো config?
১৭। closeYear() ২০০ line-এর এক entry — অধ্যায় ৮-এর post() validation
(balanced, period, account active) সব চলে; 3300-এর দরজা (অধ্যায় ২৩)
source_type = closing_entry-কে চেনে। কিন্তু কেউ API দিয়ে source_type =
closing_entry বলে মাঝ-বছরে JV করলে? closeYear()-এর বাইরে এই source
কীভাবে নিষিদ্ধ?
১৮। Multi-company year-end: ৫ company, একই FY। closeYear() ৫ বার — একটায়
ব্যর্থ (pack final নয়)। বাকিগুলো? group-এর consolidated (অধ্যায় ৬১)
কোন period-এ?
১৯। Snapshot (account_period_balances) আর continuous ledger: ১০ বছর, ৫ লাখ
line। closing(as_of) কত দ্রুত? period 1-এর opening snapshot থেকে +
চলতি period raw — কোন query? OB entry-র জায়গা এতে কী প্রভাব?
২০। Period lock-এর "না মানে না" — কিন্তু database-এ super user আছে।
অধ্যায় ৪২, ৪৫-এর আগাম: কী কী স্তরে আটকাবেন (app, trigger, hash,
append-only log)? কোন স্তর ভাঙা যায়, কোনটা ধরা পড়ে?উত্তর আছে Workbook-এর Answer Key, অধ্যায় ২৬-এ।
৯. Developer Challenge
PeriodService — period state machine, বছর খোলা-বন্ধ, closing entry, opening balance — Part 3-র শেষ টুকরো, আর Part 5-এর অধ্যায় ৪২-এর ভিত্তি।
যা যা নকশা করবেন:
১.
accounting_periods(0–13, পাঁচ status, is_opening/is_adjustment),period_status_log;transition()— allowed set, নিয়ম ১ ও ২, pack-এর শর্ত, reopen-এ reason + approver, lock বছর-ধরে;finalize(pack)-এর সাথে এক transaction।২.
post()-এperiodFor()+canPost()— status-ধরে, period 0/13-এর source-সীমা, soft_closed-এ adjusting; reject-এর বার্তায় “কী করতে হবে”; API-তেও একই দরজা (exercise ১৭)।৩.
closeYear()— pre-closing balance থেকে এক entry, key, দুই ধাপ, proprietorship mode, snapshot rebuild, দুটো assert;reopenYear()— reversal, attempt key, supersede;openFiscalYear()।৪.
movement()-এ দুই flag (closing_entry, adjustment period);closing()সব সহ;currentYearProfit()closing-এর পরে 0; অধ্যায় ২০–২৪-এর test আবার সব পাস — একটাও বদলাতে হবে না (হলে flag-এর default ভুল)।৫. Period 13 — audit adjustment document (
audit_adjustments: auditor ref, approved দুজন), বছরের pack (1–13) v2, মাসের pack অক্ষত; বছরের IS-এর note “audit adjustments”।৬.
postOpeningBalances()— period 0, balanced বাধ্যতামূলক, control account-এ subledger detail; অধ্যায় ৬২-র জন্য কী রাখলেন (mid-year, open items, FX)।৭. রাতের যাচাই ৯০–৯৫; test — section ৫-এর দশটি, আর: (ক) দুটো user একসাথে closeYear() — একটাই entry; (খ) reopen → post → close → hash আলাদা, log-এ কারণ; (গ) ১০ বছরের data-য় closing(as_of) snapshot দিয়ে < ১ সেকেন্ড (exercise ১৯)।
২ আর ৪ নম্বরটাই আসল পরীক্ষা। দরজা — closed period-এ post করার কোনো পথ নেই, admin-ও নয়, API-ও নয়, script-ও নয় (database super user ছাড়া — সেটা অধ্যায় ৪৫-এর কাজ); আর flag — closing entry যোগ করে Part 3-র একটা test-ও না ভাঙলে Part 3-র engine সম্পূর্ণ। এই দুটো নিয়ে Volume 1 শেষ।
১০. Summary Card
Period
future → open → soft_closed → closed → locked; reopen (approved, reason, log) closed/soft_closed → open; locked কখনো নয়
open: সব; soft_closed: adjusting; closed/locked: কিছু নয় — admin-ও নয়
close N ⇐ N−1 closed; reopen N ⇐ N+1… সব open (closing = opening + movement — পিছনে বদলালে সামনে বদলায়)Closing entry
কখন: audit-এর পরে, period 13, তারিখ FY শেষ, সবার শেষে; একবার (key); উল্টানো = reversal
ধাপ ১: Dr প্রতিটি REVENUE, Cr প্রতিটি EXPENSE, Cr 3300 (নিট) ধাপ ২: Dr 3300 / Cr 3200 (+ Dr 3100 / Cr 3400)
ফল: 4xxx, 5xxx, 3300 = 0; 3200 += লাভ; কোনো statement-এর সংখ্যা বদলায় না (SOCE transfer row ছাড়া)
movement() closing_entry বাদ (IS); closing() সহ (BS); currentYearProfit() closing-এর পরে 0
pre-closing TB (auditor) · post-closing TB == পরের বছরের openingPeriod 0 / 13
0: OB entry, FY শুরুর আগের দিন, balanced, শুধু opening_balance — প্রথম দিন / migration (অধ্যায় ৬২)
13: audit adjustment + closing entry, FY শেষ দিন; মাসের pack অক্ষত; বছরের pack (1–13) v2; document নয়Opening
continuous ledger (এক সত্য) + snapshot (period 1 opening = গত post-closing) — carry-forward-এর গতি, continuous-এর সত্যDeveloper checklist
□ periods 0–13, পাঁচ status; period_status_log; transition() নিয়ম ১, ২; lock বছর-ধরে
□ post(): periodFor(), canPost() status-ধরে; period 0/13 source-সীমা; API-তেও
□ closeYear(): এক entry, key, দুই ধাপ, proprietorship; reopenYear(): reversal, attempt key
□ movement() দুই flag; closing() সব সহ; currentYearProfit() closing-এর পরে 0
□ audit_adjustments document; বছরের pack 1–13
□ postOpeningBalances(): period 0, balanced, subledger detail
□ openFiscalYear(); rebuildSnapshot(next, 1)
□ রাতে: ৯০–৯৫Part 3 শেষ — Volume 1 শেষ
অধ্যায় ২০ থেকে ২৬ — সাতটি অধ্যায়, একটা Trial Balance থেকে চারটি statement, তারপর সেগুলোকে এক pack-এ, তার আগে adjusting entry-র checklist, তার পরে period-এর দরজা। পুরোটা একটা engine-এ: movement(from, to) আর closing(as_of) — দুটো function, চারটি layout, নয়টি cross-check, একটা snapshot, একটা hash, একটা lock।
আর পিছনে তাকালে — Volume 1: অধ্যায় ১-এর “Accounting কী” থেকে অধ্যায় ২৬-এর “period locked”। একটা software company, জুলাই ২০২৫ থেকে জুন ২০২৬, প্রতিটি লেনদেন একটা journal entry, প্রতিটি entry একটা ledger-এ, প্রতিটি মাস একটা Trial Balance, প্রতিটি Trial Balance চারটি statement, প্রতিটি statement-এর প্রতিটি সংখ্যার নিচে একটা তালিকা, আর প্রতিটি তালিকার প্রতিটি টাকার একটা document। এটাই accounting — আর এটাই Volume 1-এর প্রতিটি অধ্যায়ের Developer Challenge একসাথে: একটা GL, একটা posting rule, একটা report engine।
Part 3-র exit criterion (Projects ও Exit Criteria): একটা journal entry দেখে বলতে পারা — Income Statement, Balance Sheet, না Cash Flow, কোন লাইনে, কোন মাসে। অধ্যায় ২২-এর section ৬-এর তালিকাটা সেই পরীক্ষা।
Volume 2 — Accounting in Business Applications এখান থেকে শুরু: অধ্যায় ২৭-এ Inventory — এই বইয়ে এতদিন যা ছিল না: stock, COGS, valuation — আর একই engine-এ একটা নতুন subledger।