Skip to main content
An is a calendar month assigned to transactions by posting date. prevents further postings and snapshots its balances for reporting. Alforse uses a three-state lifecycle and requires the close checklist to pass before closure.

The close lifecycle

A period’s close status (periodCloseStatus) advances in one direction unless explicitly reopened. It is independent of the legacy Active/Inactive flag, which marks the current period for lazy auto-creation.
A period must be Locked before it can be Closed — closing an Open period is refused with “Period must be locked before closing.” The transitions live in accounting.service.ts (lock, unlock, close and reopen of an accounting period), each rejecting a move from the wrong starting state, and a database trigger (enforce_financial_period_lifecycle) enforces the same order underneath.

What a locked or closed period stops

The gate lives in the accounting service’s period lookup, which every posting path calls before it writes: Database triggers are the hard backstop underneath the service check: protect_closed_period_document_date rejects dating an unposted invoice or payment into a Closed period, protect_finalized_journal_period keeps a period’s range from changing under posted journals, and the period-control rules refuse to delete or move a out of a Closed period (“Cannot delete journal …: accounting period is closed”). Locked semantics need actor identity, so they are enforced at the service layer.

The close checklist

Closing runs through a persisted checklist — a company-level template of task definitions (periodCloseTaskDefinition) plus per-period instances (periodCloseTask) materialized the first time you open a period’s close screen. Nine system tasks seed with every company (they can be deactivated but never deleted). Each task has a type (Auto or Action; custom tasks can also be Manual) and Auto tasks carry a severity. Auto tasks fail closed: an Auto task whose evaluator can’t be found is treated as failing, not silently passed, so the close stays gated and the reason stays visible.

Severities gate differently

An Auto task’s severity (periodCloseTaskSeverity) decides whether a failing check blocks the close or merely warns: The seeded Auto checks, from computePeriodReadiness: The other three seeded tasks are Actions: Lock the period, Review negative on-hand inventory, and Review financial statements. There is no separate “Close the period” task; closing is the button once the checklist passes.

Completing and skipping tasks

An Action/Manual task is marked Done with the completeTask intent (optional notes). Any non-Blocker task can be skipped with the skipTask intent, which always requires a non-empty reason — and the service additionally refuses to skip a Blocker task. Auto tasks are never manually completed; their state is derived from the live readiness check every time the checklist loads. The close itself (closePeriodWithChecklist) re-evaluates the whole checklist server-side. It refuses to close unless every required task resolves to Done or Skipped and no Blocker check is failing, surfacing the specific blocking task in the error. Never trust a disabled button: the gate is enforced on the server, and the lifecycle trigger is the final backstop.

Reopening

Reopening flips a Closed period back to Open, recording a reason with the same command, clears its closedAt/closedBy, and deletes the balance snapshots the close wrote (its own and any later snapshot that embedded it, since snapshots are cumulative through their period’s end date). After reopening, postings into the month resume and the balance reads fall back to a full-history scan until the period is closed again. A period that isn’t Closed can’t be reopened (“Period is not closed”). Unlocking is the milder inverse: it moves Locked back to Open so operational documents can post again, clearing lockedAt/lockedBy. It is refused while the period still has active close evidence. It writes no snapshots and touches no balances.

Fiscal periods and the calendar

Periods are generated per fiscal year — twelve monthly periods from the company’s fiscal start month, via createFiscalYearPeriods (idempotent; it skips any period number already present). Each period carries a fiscalYear and periodNumber derived from that start month: a fiscal year is named for the calendar year it ends in, so a July-start FY beginning in 2025 is FY2026. Once the calendar is committed — any posting exists, or any period is Locked/Closed — the fiscal start month locks (getFiscalCalendarCommitted), because re-labeling already-reported periods would rewrite history. An empty, Open period can still be deleted (deleteAccountingPeriod) and regenerated; a period with journals posted to it, or in any non-Open state, cannot.