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 verifies accounting integrity before locking the period, and creates proper closing journal entries 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
All journal entries within the period's date range must be in POSTED status. Draft or pending entries generate warnings that must be resolved (entries posted or deleted) before closing.
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 blocks closing entirely (not a warning -- a hard error).
4. Bank Reconciliation Check
Incomplete bank reconciliations within the period generate warnings. This is advisory (not blocking) because some reconciliations may legitimately span period boundaries, but the warning ensures the closing user is aware.
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:
1. Validates all checklist items
2. Updates the period status to CLOSED
3. Records the closing user and timestamp
4. Logs the event with optional closing notes
A force parameter is available for cases where warnings exist but the closing user has determined they are acceptable. Hard errors (trial balance mismatch) cannot be forced.
Year-End Closing Entries
The close_fiscal_year method performs the standard accounting year-end close:
Step 1: Close Revenue Accounts
All revenue accounts (Operating Income, Non-Operating Income) with non-zero balances are debited to zero their balances.
Step 2: Close Expense Accounts
All expense accounts (Operating Expense, Non-Operating Expense) with non-zero balances are credited to zero their balances.
Step 3: Transfer Net Income to Retained Earnings
The difference between total revenue and total expenses (net income or net loss) is posted to the Retained Earnings account, which is identified by the RETAINED_EARNINGS role in the chart of accounts.
Enforcement Integration
The closing journal entry is posted through JournalEngine.post_transaction with transaction type CLOSING. This means:
- All active guards evaluate the entry (BalanceGuard, ClosedPeriodGuard, etc.)
- An EnforcementDecision record is created
- The entry receives the same audit trail as any other journal entry
Period Lock
After the closing entry is posted, all fiscal periods for the year are closed. The ClosedPeriodGuard then prevents any future posting to those periods unless an AuditOverride is created.
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.
How CommunityPay Enforces This
- Period closing requires passing a multi-step validation checklist
- 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 to closed periods