Periods & the close
The month-end close is the central workflow of accounting software, and generic dashboard components can't express it. They have no notion that a period has a state, and the state decides what the UI may offer.
1. Fiscal calendars#
type FiscalCalendar = {
/** 1–12. A June year-end means fiscalYearStartMonth = 7. */
fiscalYearStartMonth: number;
/** How periods are cut. */
type: "calendar" | "4-4-5" | "4-5-4" | "5-4-4" | "13-period";
/** Which calendar year labels the FY. A FY starting Jul 2026 may be "FY26" or "FY27". */
yearLabelConvention: "start" | "end";
/** Some entities run a 53-week year periodically. */
weekAlignedYearEnd?: boolean;
};
Never assume January. It's the most common fiscal bug in finance UI, and nothing flags it. A Q1 label that means Jan–Mar for an entity whose Q1 is Jul–Sep produces confident, wrong analysis.
Retail calendars (4-4-5 and friends) are common. They make every "month" a whole number of weeks so week-over-week comparison works. That means:
- A "month" is 28 or 35 days, not a calendar month.
- Period boundaries fall mid-calendar-month.
- Every 5–6 years there is a 53rd week, and the extra week distorts every year-over-year comparison in that year. Flag it. A component that shows
+8.4% YoYin a 53-week year without a note is misleading.
UI rules:
PeriodPickerrenders the fiscal label and the resolved dates, always:FY26 Q1 (Jul 1 – Sep 30, 2026).- Never render a bare
Q1,Sep, orLast quarter. - Comparative columns state their own resolved period; the header never says "Prior".
2. Period states#
A period is a date range with a state machine attached, and the state determines which actions exist.
┌──────────┐ ┌─────────────┐ ┌────────────┐ ┌────────┐ ┌───────┐
│ future ├───┤ open ├───┤ soft close ├───┤ closed ├───┤ filed │
└──────────┘ └─────────────┘ └────────────┘ └────────┘ └───────┘
↑ │
└────── reopen (approval) ───────┘
| State | Posting | Who can post | UI treatment |
|---|---|---|---|
| future | Blocked | — | Greyed in every picker; selecting it explains why |
| open | Free | Anyone with the role | Normal |
| soft close | Adjusting entries only | Controller and above | Amber banner; the entry form defaults to adjusting |
| closed | Blocked | Nobody without a reopen | Lock glyph; statements marked final |
| filed | Blocked | Nobody. Never reopened; corrections go through restatement, revision or a current-period adjustment. | Lock + "Filed" badge; restatement workflow only |
The PeriodStatus banner#
Every surface scoped to a period displays its state. It answers "why can't I post this?" before the user has to ask.
🔒 September 2026 · Closed Oct 4, 2026 by A. Rivera
Entries dated in this period must be posted to October and will
be flagged for cutoff review. [Request reopen]
The banner carries the state, who closed it and when, and the next action. A lock icon alone is a dead end.
Reopening#
- Requires an approval from a role that did not request it.
- Writes to the audit trail with a mandatory reason.
- Invalidates every statement generated from that period. Any export or saved report carrying that period is marked stale, because a PDF someone emailed to a lender is now wrong.
- The UI states the blast radius before confirming: "Reopening September will mark 4 published statements as superseded."
3. Cutoff#
Cutoff is the boundary problem, where a transaction is dated in one period and entered in another. Get it wrong and revenue or expense lands in the wrong period, the most common unintentional misstatement there is.
The CutoffWarning component fires when:
- An entry's transaction date falls in a closed or soft-closed period.
- An entry's date is within N days of a period boundary (configurable, default 5) and the period is in close.
- A bank transaction settles in one period but was initiated in another.
- An invoice dated in the prior period arrives after close.
The warning sits inline at the date field, never in a toast, and states the consequence and both legitimate remedies instead of only blocking:
This is dated Sep 29, which is in a closed period. It will post to October. If it belongs in September, request a reopen, or record it in October as an out-of-period adjustment.
4. The close pipeline#
Lopifi models the close as seven gated sections, in the order controllers run them. The same structure drives CloseChecklist, CloseStatus and the dashboard.
| # | Section | Gate — what "done" means |
|---|---|---|
| 1 | Cutoff & capture | All source documents in; AP and AR cutoff tested |
| 2 | Cash | Every bank and card account reconciled; exceptions explained |
| 3 | Subledgers | AR and AP agree to the GL control accounts |
| 4 | Schedules | Prepaids, accruals, deferred revenue, fixed assets rolled forward and tied |
| 5 | Adjusting entries | Accruals, depreciation, amortization, provisions, reclasses posted |
| 6 | Balance sheet tie-out | Every BS account supported by a schedule or reconciliation |
| 7 | Review & flux | Material variances explained; statements reviewed and signed off |
Rules:
- Sections gate in order. You cannot tie out the balance sheet before the schedules are rolled. The UI shows downstream sections as blocked, with the blocker named.
- Every check has an owner, a due date, and a state:
not started→in progress→blocked→done→reviewed. - A blocked item names its blocker and who owns it. "Blocked: waiting on Sep bank statement — Treasury."
- Sign-off is irreversible and audited. It uses
HoldToConfirmand writes who, when, and on what version of the numbers. - The close percentage is by weighted check, not by count. Ten trivial checks and one bank rec is not 91% done.
5. Reconciliation vs tie-out#
People mix these two controls up constantly. They answer different questions, and each has its own component.
| Bank reconciliation | Balance sheet tie-out | |
|---|---|---|
| Question | Does our cash record match the bank's? | Is this balance supported by evidence? |
| Compares | Book cash vs. bank statement | GL balance vs. a supporting schedule |
| Output | Reconciling items (timing differences) | A walk-down with a difference of zero or an explanation |
| Component | ReconciliationView |
AccountTieOut |
| Applies to | Cash and card accounts | Every balance sheet account |
The bank reconciliation summary#
The component renders the standard form, line for line:
Bank statement balance 412,880.14
Add: deposits in transit 8,420.00
Less: outstanding checks (22,140.55)
Add/less: other reconciling items 0.00
────────────
Adjusted bank balance 399,159.59
Book (GL) balance 399,159.59
════════════
Difference 0.00 ✓
The difference line is the whole component. It never scrolls out of view, because it's the number the user is working to zero. While it's non-zero, it's the primary state of the screen.
The tie-out walk-down#
1310 · Prepaid expenses
GL balance at Sep 30, 2026 84,220.00
Supporting schedule closing balance 84,220.00
────────────
Difference 0.00 ✓ Tied
Supported by: Prepaid schedule (14 items) · reviewed A. Rivera Oct 3
Every balance sheet account gets one. An account with no supporting evidence is a finding in itself, so AccountTieOut renders an explicit "Unsupported" state instead of an empty panel.
6. Reconciling items and exceptions#
A reconciling item is a legitimate timing difference. An exception is a break someone has to work, and the UI keeps the two apart.
| Kind | Meaning | Aging behavior |
|---|---|---|
| Deposit in transit | Booked, not yet on the bank statement | Should clear next period; flag if > 5 days |
| Outstanding check | Issued, not yet cashed | Flag if > 90 days; stale-dated after six months (a bank may refuse it, UCC §4-404). Still a liability — void and reissue, or escheat under state unclaimed-property law after the dormancy period. Never write it back to income |
| Bank-only item | Fee, interest, NSF the bank booked and we didn't | Requires a journal entry. Should never age. |
| Book-only item | We booked something the bank has no record of | Investigate immediately |
| Unmatched | No counterpart found | The actual exception |
Aging is the priority signal. A 3-day-old outstanding check is normal; a 200-day-old one is a control finding. ReconciliationView sorts exceptions oldest first by default, ahead of amount, because old breaks are what auditors find.
7. Adjusting entries#
These are the entries that make accrual accounting work. Each has its own shape, so AdjustingEntryForm offers them as templates instead of a blank journal entry.
| Type | Pattern | Auto-reverses? |
|---|---|---|
| Accrual | Dr Expense / Cr Accrued liability | Yes — day 1 of next period |
| Deferral | Dr Prepaid asset / Cr Expense | No — released by schedule |
| Depreciation | Dr Depreciation expense / Cr Accumulated depreciation | No |
| Amortization | Dr Amortization expense / Cr Accumulated amortization | No |
| Revenue recognition | Dr Deferred revenue / Cr Revenue | No — released by schedule |
| Provision / allowance | Dr Bad debt expense / Cr Allowance | No — trued up each period |
| Reclassification | Dr Account A / Cr Account B | Sometimes |
| True-up | Adjusts an estimate to actual | No |
Accruals auto-reverse. One that doesn't double-counts the expense when the invoice arrives and is coded to expense, and leaving it to the user's memory is how that happens. AdjustingEntryForm defaults autoReverse to true for accruals and shows the reversal date inline ("Reverses Oct 1, 2026"). The reversal is created at posting time as a scheduled entry, so a reviewer can see it in the next period before it posts.
8. Subsequent events#
Events after the period end but before the statements are issued (or available to be issued, for an entity that is not an SEC filer). There are two types. ASC 855 calls them recognized and nonrecognized subsequent events; IAS 10 calls them adjusting and non-adjusting.
| Type 1 — Adjusting | Type 2 — Non-adjusting | |
|---|---|---|
| Condition | Existed at the balance sheet date | Arose after |
| Example | A customer whose finances were already deteriorating at Sep 30 files for bankruptcy in October; a lawsuit about a September incident settles | A fire destroys a warehouse in October; the company issues debt or equity, or acquires a business |
| Treatment | Adjust the statements (ASC 855-10-25-1) | Disclose the nature and an estimate of the effect, if material (ASC 855-10-25-3, -50-2) |
SubsequentEventLog captures these during the review window with the type, the date discovered and the treatment decision. Classify an event wrong and you get either a misstatement or a missing disclosure.
9. Restatement#
This is what happens when a closed and filed period turns out to be materially wrong.
- A restatement ("Big R", ASC 250-10-45-23) is required when the error is material to the previously issued statements: they are reissued, the cumulative effect lands in opening retained earnings of the earliest period presented, and the error is disclosed.
- The prior period is never edited in place. A restatement entry is posted, and the comparative column is labeled "Restated" everywhere it appears, with no exceptions. A prior-year column that changed between two reports without a label destroys trust faster than the original error.
StatementTablesupports arestatedflag per period column, rendering the label in the header and a footnote naming the reason.- Errors immaterial to the prior period but material if corrected now revise the comparatives, labeled "As revised". Errors immaterial either way are corrected in the current period as an out-of-period adjustment. Materiality is assessed under both the rollover and iron-curtain approaches (SEC SAB No. 108), quantitatively and qualitatively (SAB No. 99). The materiality policy decides which, and the decision is recorded.
10. Period-scoped UI rules#
On top of the rules above (resolved dates on every label, state on every surface, superseded statements after a reopen), every component follows these:
- Every figure declares its period.
- A closed period's numbers can't change, so any affordance implying they can is removed outright.
- Comparatives share basis, framework, entity set and currency. Mixing is blocked at the type level, before anything renders.