Skip to content

Epic: period locking #7

@avandenberghe

Description

@avandenberghe

Context

Currently any user with a_accounts can post (or import) a journal entry with any entry_date — including back-dating into a period that's been signed off / closed. For the guild use case that's tolerable, but it's the most universal "real accounting" feature missing from the Phase 1 GL: once a period is closed, the books for that period must be immutable.

Where the guard lives — important

The lock guard goes in the service, NOT the controller. Specifically inside ledger::create_entry(). This is non-negotiable because:

If the implementation wires the check into a controller method, the source-agnostic invariant is broken. The single-line guard belongs in ledger::create_entry(), full stop.

Minimal shape

phpBB already has the right primitives for the smallest possible implementation:

  • One config keybbaccounts_period_lock_through (UINT, unix timestamp). Treat 0 as "no lock".
  • One ACP form field to set/clear it. Lives on the existing Reports or a new "Period control" sub-mode.
  • One guard in ledger::create_entry() — throw \InvalidArgumentException if entry_date <= bbaccounts_period_lock_through (when lock_through > 0).

That's the whole feature. Sub-100 LOC plus tests.

The non-obvious ripple: reverse_entry()

reverse_entry() currently preserves the original entry_date (correct under "reversal = undo the original transaction" semantics). The moment a period is locked, that conflicts with the lock invariant: reversing a locked-period entry would insert lines into a locked period, which is exactly what the lock is preventing.

Filed as its own child ticket: #91 — reverse_entry() reposts at time() when original is in a locked period. Don't fold it into the main "add lock_through" task — the easy-to-miss bit deserves its own PR slice with explicit tests.

Design fork to pin down

Single rolling lock-through date vs bbaccounts_periods table with arbitrary period rows that can each be locked/reopened independently.

Single-config-key is what every small package actually ships, and it's enough for "officer closes the season after loot is settled." The multi-period table only earns its keep if someone wants to lock 2026 but reopen Q4 2025 for an adjustment — never happens in a guild, common in real businesses. Recommendation: ship the config-key shape; keep the schema-migration path (config key → table) in mind but don't start there.

Acceptance

  • New config key bbaccounts_period_lock_through (UINT timestamp, default 0).
  • ACP form to set/clear the lock-through date.
  • ledger::create_entry() (service layer, not controller) rejects entries with entry_date <= lock_through when lock_through > 0.
  • CSV import surfaces the rejection per-row in the preview (inherited automatically since it goes through create_entry()).
  • Service tests covering: rejected post in locked period; allowed post past the lock; allowed post when lock = 0.
  • reverse_entry() locked-period rule lands separately as Task: reverse_entry() reposts at time() when original is in a locked period #11.

Decomposition (rough)

  1. Migration adding the config key.
  2. ACP form + handler (one new lang section, ~30 lines).
  3. Guard in create_entry() — service layer.
  4. Service tests for the guard.
  5. reverse_entry() rule via Task: reverse_entry() reposts at time() when original is in a locked period #11.

Dependency / sequencing note

Period locking is a prerequisite for #8 (period-end close): you can't roll P&L → retained earnings if the period it draws from can still mutate. The natural Phase 4 ordering is therefore: #89 P&L/BS report shapes (independent, cheapest)#87 period locking#91 reverse_entry rule#88 period-end close. #10 (VAT/invoicing) is independent and lives in its own extension.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions