Overview
CommunityPay enforces structured fiscal period management through the PeriodClosingService. Period closing is not a simple status toggle — it is a validated, atomic operation that checks accounting integrity before closing the period, and year-end closing entries post through the enforcement-guarded JournalEngine.
Once a period is closed, the ClosedPeriodGuard (GUARD_002 in the guard manifest) rejects any attempt to post journal entries into that period.
Period Closing Validation
Before a period can be closed, the service runs a multi-step validation checklist:
1. Period Status Check
The period must be in OPEN status. Already-closed or locked periods are rejected immediately.
2. Unposted Journal Entry Check
Journal entries within the period's date range are expected to be in POSTED status. Draft or pending entries generate a warning that names the count; each is deleted or re-entered through the JournalEngine, which is the only way an entry posts.
3. Trial Balance Verification
The service computes a full trial balance across all active accounts as of the period end date. Total debits must equal total credits within a $0.01 tolerance. An out-of-balance trial balance is an error, not a warning, and the close is refused unless it is forced (see below).
4. Bank Reconciliation Check
Incomplete bank reconciliations dated on or before the period end generate a warning by default, because some reconciliations may legitimately span period boundaries. An association can set its bank reconciliation policy to block, in which case an incomplete reconciliation is an error.
5. Pending Accrual Reversals
Accrual entries in the period that carry an auto-reverse date but have no posted reversal yet generate a warning. The reversal posts when its date arrives.
Period Closing Execution
When validation passes, the close_period method executes within a database transaction (@transaction.atomic). If any step fails, the entire operation rolls back — no partial closes.
The close operation:
- Validates all checklist items
- Updates the period status to
CLOSED - Records the closing user and timestamp
- Records the optional closing notes
- Runs a ledger integrity scan, so the evidence pack carries results as of the close
- Generates the Close Evidence Pack for the period
A force option closes the period although validation reported problems, including a trial balance error. Closing requires financial authority in the association, and the closing user and time stay on the period. The scan and the evidence pack run after the close completes; a failure in either does not undo the close.
Year-End Closing Entries
The year-end close runs through YearEndCloseService.close_fiscal_year. It closes each fund on its own:
Step 0: Fund Balance Reconciliation
Before anything posts, every active fund is reconciled. The close is refused if a fund fails to reconcile or lacks a fund balance account, unless a recorded audit override allows it.
Step 1: Close Revenue Accounts
For each fund with activity, the revenue accounts with non-zero balances are debited to zero their balances.
Step 2: Close Expense Accounts
For each fund, the expense accounts with non-zero balances are credited to zero their balances.
Step 3: Transfer the Net Result to the Fund's Balance Account
The difference between that fund's revenue and expenses is posted to that fund's balance account: the account with the OPERATING_FUND_BALANCE role for operating activity, and the one with the RESERVE_FUND_BALANCE role for reserve activity. The close writes one closing entry per fund, so a reserve result never closes into the operating fund.
Enforcement Integration
Each closing journal entry is posted through JournalEngine.post_transaction with transaction type CLOSING. This means:
- The guards that apply to the entry evaluate it (BalanceGuard, ClosedPeriodGuard, and the others the flow selects)
- An
EnforcementDecisionrecord is created - The entry receives the same audit trail as any other journal entry
Period Lock
After the closing entry is posted, the year's open fiscal periods are closed. The ClosedPeriodGuard then prevents any future posting to those periods unless an AuditOverride is created. A closed period can be reopened by someone with financial authority. A locked period cannot be reopened. Locking is for audit and compliance after annual reports or tax filings are complete, and it archives a board packet for the period.
Preview Capabilities
Before executing either operation, the service provides preview functions:
Period Closing Preview returns:
- Journal entry counts (posted vs. unposted)
- Bill and vendor payment counts for the period
- Trial balance data
- Validation results with pass/fail status
Year-End Preview returns:
- Revenue account detail with balances
- Expense account detail with balances
- Computed net income
- Period status overview (open vs. closed counts)
These previews allow the closing user to review the full impact before committing.
Guard Enforcement After Close
Once a period is closed, the ClosedPeriodGuard (GUARD_002) enforces the lock:
| Guard | Category | Behavior |
|---|---|---|
| ClosedPeriodGuard | TEMPORAL | Rejects any journal entry where entry_date falls within a closed period |
This guard is marked as required: True in the guard manifest, meaning it cannot be disabled. It can be overridden only through the AuditOverride mechanism with documented reason, authorized user, and expiration.
Period locking is the guard's job, in the application, on the date of the posting in front of it. The database does not lock a closed period. It does refuse to delete a line of a posted entry, refuses to change a posted line's account or amounts, and pins both legs of a posted reversal pair in place. Corrections to a closed period appear as reversal entries.
The Close Evidence Pack and the Annual Package
When a period closes, a Close Evidence Pack is generated: 12 evidence sections taken from existing services and 14 deterministic risk flags, hashed like every institutional packet. At year end, the annual financial statement package carries the statements and the notes to them with a manifest of hashes, and the report verification harness checks it against the posted entries.
How CommunityPay Enforces This
- Period closing runs a validation checklist covering period status, unposted entries, trial balance, bank reconciliations, and pending accrual reversals
- Year-end closing entries flow through JournalEngine with full guard evaluation
- All period closings execute within database transactions (atomic rollback on failure)
- ClosedPeriodGuard (GUARD_002) prevents any posting dated in a closed period; the lock is applied in the application, on the posting date
- A ledger integrity scan and a Close Evidence Pack run when a period closes