Skip to main content

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 as JE-2026-0000471
  • reference_type: per-line identifier with _N suffix, such as JE-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_number on reversal rows
  • void_reversal_entry_number on 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 the VOID: prefix belongs in a description, and it is produced exclusively by voidJournalEntry().
  • 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 true is_void_reversal entry.
  • Donation notes: VOIDED: <reason> is appended to donations.notes by softDeleteDonation(). 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.tsx owns the dedicated accounting page create/edit form.
  • JournalEntryEditDrawer.tsx is the shared JE detail/edit surface used from reports, donor drill-downs, reconciliation, and General Ledger.
  • QuickEntryDrawer.tsx creates 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.tsx
  • DonorsCRM.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 = true originals from active balances
  • exclude is_void_reversal = true rows from active balances

Examples live in:

  • src/lib/db/financial-reports.ts
  • src/lib/db/ledger-reads.ts
  • src/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.ts
  • src/lib/db/journal-entry-voiding.ts
  • src/lib/db/donations.ts
  • src/lib/db/deposits.ts
  • src/hooks/useSupabaseData.ts
  • src/components/shared/JournalEntryEditDrawer.tsx
  • src/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

Ready to get started?Start Plus Trial