Accounting System Integration Guide
Accounting System Integration Guide
Last Updated: April 9, 2026 Audience: Frontend developers, QA, accounting workflow owners Status: Current implementation reference
---
Overview
Alignmint uses journal_entry_lines as the operational accounting ledger. Manual accounting tools, donation flows, deposit flows, and several fundraising/accounting modules all write balanced journal entry lines through shared helpers in src/lib/db/journal-entries.ts.
The active system is frontend + Supabase based. Older Rails-style service examples and journal_entries-table-first diagrams are no longer authoritative.
---
Shared Write Contract
Primary helpers
| Helper | File | Purpose |
|---|---|---|
| `createJournalEntry()` | `src/lib/db/journal-entries.ts` | Create balanced journal entry lines with standard `JE-YYYY-0000001` numbering |
| `updateFullJournalEntry()` | `src/lib/db/journal-entries.ts` | Edit an existing grouped entry |
| `voidJournalEntry()` | `src/lib/db/journal-entries.ts` | Void a JE by marking original lines `is_voided = true` and creating `VOID-JE-*` reversal lines |
| `voidJournalEntryWithDependencies()` | `src/lib/db/journal-entry-voiding.ts` | Shared orchestration layer for JE voids that may actually need deposit-batch cleanup |
| `softDeleteDonation()` | `src/lib/db/donations.ts` | Void a donation and coordinate with the linked JE |
| `voidDepositBatch()` | `src/lib/db/deposits.ts` | High-level deposit void: reverse JE, void linked donations, revert deposit batch state |
Journal entry grouping
A journal entry is a logical group of rows in journal_entry_lines:
entry_number: shared entry number for all lines in the entry, such asJE-2026-0000471reference_type: per-line identifier with_Nsuffix, such asJE-2026-0000471_0
Void metadata
Void state is boolean-first and metadata-backed:
- Original lines:
is_voided = true - Reversal lines:
is_void_reversal = true - Audit fields:
voided_at,voided_by,void_reason - Linkage fields:
void_source_entry_numberon reversal rowsvoid_reversal_entry_numberon original rows
Do not build new logic around legacy -VOIDED or raw VOID- string parsing outside the shared helpers in src/lib/journalEntryVoid.ts.
Description nomenclature
Void status is communicated through boolean flags and the UI badge — never through the description text.
- Normal entries: plain business description, no system prefixes. Use
-as the standard separator between logical segments. - Voided originals: description stays unchanged;
is_voided = true+ UI badge shows state. - Void reversal lines: description follows
VOID: <original> - <reason> (Voided on <date>). This is the only context where theVOID:prefix belongs in a description, and it is produced exclusively byvoidJournalEntry(). - Partial reversals: if a flow compensates for only part of a JE batch, create a plain-language compensating JE and do not use
VOID:because it is not a trueis_void_reversalentry. - Donation notes:
VOIDED: <reason>is appended todonations.notesbysoftDeleteDonation(). This is a narrative note on the donation record, not a JE description.
Prefixes like VOIDED:, VOIDED -, or VOIDED-TRANCHE: must never appear on JE descriptions. New system-generated descriptions must not use em dashes, bracket prefixes, or colon-style prefixes outside the standard VOID: reversal format. Migrations and imports must use the boolean flags, not description-text hacks, to express void state. See 05-JOURNAL-ENTRY-MANAGER.md § Journal Entry Description Standard for the full rule set and approved description catalog.
---
Active Create/Edit Flows
Manual accounting surfaces
JournalEntryManager.tsxowns the dedicated accounting page create/edit form.JournalEntryEditDrawer.tsxis the shared JE detail/edit surface used from reports, donor drill-downs, reconciliation, and General Ledger.QuickEntryDrawer.tsxcreates simple balanced entries from shortcut flows.
These UIs are not fully consolidated into one component, but they do share the same persistence helpers:
- create ->
createJournalEntry() - edit ->
updateFullJournalEntry() - void ->
useVoidJournalEntry()->voidJournalEntryWithDependencies()
Cross-module JE creation
The following modules create journal entries through the shared JE helper instead of inventing custom numbering or ad hoc write paths:
- donations
- deposits
- bills
- pledges
- grants receivable
- sponsor fee allocation
- event/accounting integrations
---
Void Flow Rules
Journal entries
All user-facing JE void actions now flow through useVoidJournalEntry() in src/hooks/useSupabaseData.ts.
That hook calls voidJournalEntryWithDependencies() so every JE drawer surface behaves the same way:
1. If the entry is linked to a deposit batch, route the action through voidDepositBatch(). 2. Otherwise call voidJournalEntry() directly.
Deposit-linked entries
Deposit-linked JEs cannot be treated as standalone ledger reversals because the posted deposit batch also owns related donation state.
voidDepositBatch() remains the authoritative high-level path when the user is acting on the entire deposit batch because it:
1. finds the linked deposit JE 2. voids the JE reversal 3. voids linked donations with skipJeVoid = true 4. reverts the deposit batch to pending
Donation-level deposit void now has three branches:
1. Standalone posted deposit (deposit_items.status='active' count = 1): treat it like a direct donation entry, so donation-level void/edit is allowed. 2. Check deposits and newer regular deposits with stored posting context (deposit_items.revenue_account_id, purpose_id, fund_id present): create an additive reversing JE for just that donation amount, mark the linked deposit_item voided, recalculate active deposit totals, and keep the rest of the batch intact. 3. Ambiguous historical regular deposits (no stored posting context): still require whole-batch void because one donation cannot be mapped back to a unique posted revenue slice safely.
Donations
Donation voids now require a non-empty reason across both donation management entry points:
DonationsManager.tsxDonorsCRM.tsx
softDeleteDonation() behavior:
1. require a reason 2. resolve the linked JE 3. if the JE is directly mutable, void the JE first 4. if the donation belongs to a posted deposit and can be resolved safely, route through voidDepositDonation() 5. let JE cascade or skip-path mark the donation status = 'voided' 6. preserve existing donation notes and append VOIDED: ...
UI routing note: donation-context JE drawers in DonorsCRM.tsx / DonorGivingGrid.tsx now call the donation-level path (softDeleteDonation) instead of voidJournalEntryWithDependencies() so a user voiding one donation does not accidentally trigger voidDepositBatch() for the full deposit.
---
Accounting Controls
Period locks
Voiding respects accounting period locks at the JE-line level. If any line belongs to an organization/date that is locked, the void is rejected.
Reconciliation guard
Entries that are already in reconciliation are not voidable. The shared JE void helper rejects entries with reconciled or non-unreconciled reconciliation status.
Balanced reversals only
voidJournalEntry() validates the original entry is balanced before creating reversal lines.
Audit trail
Void operations record:
- who voided the transaction
- when it was voided
- why it was voided
- which reversal entry offset the original
This matches the intended accounting practice of voiding/reversing instead of deleting posted activity.
---
Reporting and Read-Side Behavior
Working balances and report totals
Balance-style queries exclude active void noise with boolean filters:
- exclude
is_voided = trueoriginals from active balances - exclude
is_void_reversal = truerows from active balances
Examples live in:
src/lib/db/financial-reports.tssrc/lib/db/ledger-reads.tssrc/lib/db/revenue-reconciliation.ts
Detail drawers
Detail surfaces still need to show void history. Those screens use:
fetchJournalEntryByLineId()applyVoidedEntryDisplay()journalEntryVoid.ts
This lets reports and donor drill-downs show the original transaction date plus the voided-on date without counting the entry in active balances.
---
Known Limitation
Void orchestration is still coordinated in application code rather than one database transaction spanning journal entries, deposits, and donations. The current implementation is materially safer and more consistent than the previous split paths, but it is not yet a single atomic DB-side workflow.
---
Source Files
src/lib/db/journal-entries.tssrc/lib/db/journal-entry-voiding.tssrc/lib/db/donations.tssrc/lib/db/deposits.tssrc/hooks/useSupabaseData.tssrc/components/shared/JournalEntryEditDrawer.tsxsrc/features/accounting/components/JournalEntryManager.tsx
Synced from IFMmvp-Frontend documentation: pages/accounting/02-ACCOUNTING-SYSTEM-INTEGRATION.md
Ready to Get Started?
See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.
Questions? Email us at steven@getalignmint.org