Accounting

Sally's double-entry bookkeeping engine — chart of accounts, ledgers, voucher entry, bill and bank allocations, cost centres, document numbering, concurrency control, period locking, and soft-delete patterns.

Chart of Accounts

The chart of accounts is built on a hierarchical AccountGroup model. Each group has an optional parentId forming a tree structure.

Every account group belongs to one of four natures:

NatureNormal BalanceExamples
ASSETSDebitCash-in-Hand, Bank Accounts, Sundry Debtors, Fixed Assets
LIABILITIESCreditSundry Creditors, Loans, Capital Account
INCOMECreditSales, Interest Received, Other Income
EXPENSESDebitPurchases, Rent, Salaries, Depreciation

System groups (isSystem: true) are pre-seeded during company creation and cannot be deleted. Custom groups can be nested under any existing group.

Ledger Management

Ledgers are the fundamental accounting units. Each ledger belongs to an account group and inherits its nature.

Operations

  • Create — name (unique per company), group selection, opening balance + type (debit/credit), optional GSTIN/PAN/address for party ledgers
  • Edit — update name, group, opening balance. OCC version check prevents conflicting edits.
  • Delete — soft delete only. Ledgers with transactions cannot be permanently removed. Moves to trash with restore option.

Opening Balance

Each ledger carries an openingBalance and openingBalanceType (DEBIT or CREDIT). This represents the starting position when the company is set up or migrated from another system.

Voucher Types

Sally supports eight voucher types organized into two categories:

Directly Entered via Voucher Form

TypePurpose
PaymentMoney going out (cash/bank debit, party/expense credit)
ReceiptMoney coming in (cash/bank credit, party/income debit)
JournalNon-cash adjustments, provisions, closing entries
ContraFund transfers between cash and bank accounts

Created via Invoicing

Sales, Purchase, Credit Note, and Debit Note vouchers are auto-generated when invoices are created. They flow through the Invoicing module rather than the voucher entry form.

Voucher Entry Form

The full voucher form (/vouchers/new) provides multi-line debit/credit entry:

  • Multi-line entries — Add unlimited debit and credit rows. Each row has a ledger selector, amount, and optional narration.
  • Ledger autocomplete — Searchable dropdown with group filtering. Shows ledger name + group for disambiguation.
  • Balance check — Real-time validation ensures total debits equal total credits. The save button is disabled until balanced.
  • Date picker — Defaults to today. Triggers period-lock validation on change.
  • Narration — Free-text field for transaction description.
  • Reference number — Optional external reference (cheque number, UTR, etc.)

Quick Voucher Entry

For the most common scenarios, Sally provides a simplified form that pre-fills the voucher structure:

  • Quick Payment — Select who you're paying (party/expense ledger), which account to pay from (cash/bank), and the amount. Sally auto-creates the balanced debit/credit entries.
  • Quick Receipt — Select who is paying you, which account receives the funds, and the amount.

Quick forms reduce a 4-field-per-row multi-line entry to a simple 3-field form, ideal for daily cash transactions.

Bill Allocations

Bill allocations track individual receivable/payable references for AR/AP ageing. Every transaction against a party ledger creates or settles a bill.

Bill TypeMeaning
New RefCreates a new outstanding bill (e.g., a sales invoice creates a DR bill)
Against RefSettles an existing bill (e.g., receipt against a specific invoice)
AdvancePayment received/made before the bill is raised
On AccountGeneral payment without linking to a specific bill

Outstanding reports and ageing buckets (0–30, 31–60, 61–90, 90+ days) are computed from unadjusted bill records.

Bank Allocations

When a voucher entry involves a bank ledger, a bank allocation record captures instrument-level details:

  • Transaction type — NEFT, RTGS, UPI, Cheque/DD, IMPS, or other
  • Instrument number — cheque number, UTR, or UPI transaction ID
  • Instrument date — date on the instrument (may differ from voucher date for post-dated cheques)
  • Bank details — favouring name, bank party name, IFSC, account number

These allocations feed into bank reconciliation — matching imported statement lines against book entries by reference/amount.

Cost Centres

Cost centres provide an orthogonal categorization layer on top of the ledger structure. They are hierarchical (parent/child) and company-scoped.

  • Hierarchy — Create nested cost centres (e.g., “North Region” → “Delhi Office” → “Marketing Dept”)
  • Entry-level allocation — Each voucher entry can be split across multiple cost centres via CostCentreAllocation records
  • Amount splitting — A single ₹10,000 expense entry can be allocated: ₹6,000 to “Project A” and ₹4,000 to “Project B”

Document Numbering

Every voucher and invoice gets a unique sequential number, aware of the financial year and configurable in format.

How It Works

  1. Resolve the FY string from the transaction date (e.g., April 2025 → “2025-26”)
  2. Atomic UPSERT + INCREMENT on the DocumentCounter row keyed by (companyId, kind, fy)
  3. Format using the configured template

Format Template

Default: {prefix}/{fy}/{seq}

Example output: SAL/2025-26/0042

  • {prefix} — configurable per counter kind (e.g., SAL, PUR, JRN)
  • {fy} — financial year string (long “2025-26” or short “25-26”)
  • {seq} — zero-padded sequence number

Atomic Counter

The counter uses database-level atomic increment to prevent race conditions. Two simultaneous saves will always get different numbers. Burned numbers (from failed transactions) are acceptable by design.

Optimistic Concurrency Control

Every mutable entity carries a version integer field that increments on each update.

  • GET responses include the current version in the response body and ETag header
  • PATCH requests must echo the version via If-Match header or body field
  • If another user updated the entity between your GET and PATCH, the versions won't match
  • Returns 409 Conflict with details: expected vs. current version, entity type, entity ID

This prevents the “last write wins” problem — if two accountants edit the same voucher simultaneously, the second one gets a conflict and must refresh before retrying.

Period Lock Enforcement

Financial periods can be locked to prevent modifications to past data. The assertPeriodOpen(companyId, date) function is called by every mutation endpoint.

  • If the transaction date falls within a LOCKED or CLOSED period, the mutation is blocked with a 403 error
  • The UI performs a pre-check (getBlockingPeriod) to warn users before they submit
  • Only period status OPEN allows new transactions

Soft Delete Pattern

Accounting data is never permanently deleted through normal operations. Instead, entities are “soft deleted”:

  • deletedAt — timestamp when the item was trashed (null = active)
  • deletedById — user who performed the deletion
  • All queries include deletedAt: null filter by default
  • Trash — Settings → Trash shows all soft-deleted items with restore option
  • Permanent delete — available from trash after retention period (default 90 days)

This pattern applies to: Ledgers, Vouchers, Invoices, Stock Items, Stock Groups, Stock Journals, Godowns, Units, Batches, and more.