Context
Once period locking (#87) is in, the natural follow-on is the period-end close mechanic — the once-a-year (or once-a-season) operation every accounting package runs to:
- Sum the net of all P&L accounts (revenue + expense) over the period.
- Post a single closing journal entry that zeroes those accounts and credits the result to a retained-earnings equity account.
- Lock the period (sets
bbaccounts_period_lock_through to the close date).
After close, the P&L accounts start the new period at zero, the retained-earnings account carries the cumulative result, and the locked period becomes immutable.
Why it matters for bbAccounts
Without close, P&L accounts (Revenue 4000, Expenses 5000) accumulate forever — meaningful for a single short period but increasingly noisy as years go by. A guild that runs season-by-season will want to roll over at the season boundary so each season's reports start clean.
Depends on
Design notes
New seeded equity account. The Phase 1 seed has 3010 Opening Balances but no retained-earnings account. The close needs 3020 Retained Earnings (or similar) — added in a Phase 4 migration as additive seed.
Close is one journal entry. For a period ending 2026-12-31:
- Each P&L account gets a line for its closing balance, in the OPPOSITE direction (revenue accounts get DR for their CR balance; expense accounts get CR for their DR balance).
- The contra-line is
3020 Retained Earnings, summing to net income (CR if profit, DR if loss).
entry_date = the period-end date. reference_type='auto', reference_source='bbaccounts.close', description "Close of period YYYY-MM-DD".
Multi-pool aware. Each currency pool closes independently — one close entry per pool with its own retained-earnings account (per pool, since accounts can't cross pools).
Idempotency. Re-running close for a period that's already closed should be a no-op + clear error message ("period already closed at YYYY-MM-DD"). Track via the existence of a journal entry with reference_source='bbaccounts.close' for that date.
ACP UI
New "Period close" sub-mode under Reports (or a new Periods top-level mode):
- Pick close date (date input).
- Preview pane shows the close entry that would be posted (one row per P&L account + the retained-earnings line) for each pool.
- Confirm button posts the entry through
ledger::create_entry() then sets bbaccounts_period_lock_through to the close date in the same transaction.
Acceptance
Context
Once period locking (#87) is in, the natural follow-on is the period-end close mechanic — the once-a-year (or once-a-season) operation every accounting package runs to:
bbaccounts_period_lock_throughto the close date).After close, the P&L accounts start the new period at zero, the retained-earnings account carries the cumulative result, and the locked period becomes immutable.
Why it matters for bbAccounts
Without close, P&L accounts (Revenue 4000, Expenses 5000) accumulate forever — meaningful for a single short period but increasingly noisy as years go by. A guild that runs season-by-season will want to roll over at the season boundary so each season's reports start clean.
Depends on
Design notes
New seeded equity account. The Phase 1 seed has
3010 Opening Balancesbut no retained-earnings account. The close needs3020 Retained Earnings(or similar) — added in a Phase 4 migration as additive seed.Close is one journal entry. For a period ending 2026-12-31:
3020 Retained Earnings, summing to net income (CR if profit, DR if loss).entry_date= the period-end date.reference_type='auto',reference_source='bbaccounts.close', description "Close of period YYYY-MM-DD".Multi-pool aware. Each currency pool closes independently — one close entry per pool with its own retained-earnings account (per pool, since accounts can't cross pools).
Idempotency. Re-running close for a period that's already closed should be a no-op + clear error message ("period already closed at YYYY-MM-DD"). Track via the existence of a journal entry with
reference_source='bbaccounts.close'for that date.ACP UI
New "Period close" sub-mode under Reports (or a new Periods top-level mode):
ledger::create_entry()then setsbbaccounts_period_lock_throughto the close date in the same transaction.Acceptance
3020 Retained Earningsequity account per currency pool.create_entry()(so all invariants apply).