Skip to content

Conversation

Copy link

Copilot AI commented Oct 19, 2025

Overview

This PR converts LucaSchema from a single-entry accounting system to a proper double-entry accounting system, implementing the fundamental principle that every transaction consists of balanced debits and credits. This is a major breaking change requiring version 2.0.0.

What Changed

New Posting Entity

Introduced a new Posting schema that represents individual debit or credit entries:

interface Posting {
  accountId: string;      // UUID reference to account entity
  amount: number;         // Signed integer: positive = debit, negative = credit
  description?: string | null;
  order: number;         // Zero-based ordering for stable presentation
}

Transaction Schema Restructuring

Before (v1.x - Single-Entry):

{
  id: "...",
  payorId: "checking-account",
  payeeId: "grocery-store",
  amount: 65.32,
  date: "2024-01-15",
  description: "Weekly groceries",
  // ...
}

After (v2.0 - Double-Entry):

{
  id: "...",
  postings: [
    {
      accountId: "groceries-expense",
      amount: 6532,        // $65.32 in cents (debit)
      description: "Weekly groceries",
      order: 0
    },
    {
      accountId: "checking-account",
      amount: -6532,       // $65.32 in cents (credit)
      description: "Payment from checking",
      order: 1
    }
  ],
  date: "2024-01-15",
  description: "Grocery shopping at market",
  // ...
}

Key Features

  • Balanced Postings: Custom AJV validation keyword ensures the sum of all posting amounts equals zero
  • Signed Integers: Amounts stored as integers in minor units (cents) with sign indicating direction
  • Split Transactions: Support for complex transactions with 3+ postings
  • Account References: Postings reference account entities via accountId
  • Order Preservation: Explicit ordering field for stable presentation

Example Transactions

Simple Expense:

// Dr Groceries $65.32 / Cr Checking $65.32
postings: [
  { accountId: 'groceries-expense', amount: 6532, order: 0 },
  { accountId: 'checking-account', amount: -6532, order: 1 }
]

Income:

// Dr Checking $2000 / Cr Salary Income $2000
postings: [
  { accountId: 'checking-account', amount: 200000, order: 0 },
  { accountId: 'salary-income', amount: -200000, order: 1 }
]

Split Transaction:

// Dr Groceries $50, Dr Household $30 / Cr Checking $80
postings: [
  { accountId: 'groceries', amount: 5000, order: 0 },
  { accountId: 'household', amount: 3000, order: 1 },
  { accountId: 'checking-account', amount: -8000, order: 2 }
]

Breaking Changes

Removed Fields

  • payorId from Transaction and RecurringTransaction
  • payeeId from Transaction and RecurringTransaction
  • amount from Transaction and RecurringTransaction (replaced by postings array)

New Requirements

  • Transactions must have at least 2 postings
  • Sum of all posting amounts must equal zero
  • Amounts must be integers (no decimals)
  • Zero-amount postings are not allowed

Migration Guide

  1. Replace single amount with postings array containing balanced debits/credits
  2. Convert decimal amounts to integers (multiply by 100 for cents)
  3. Map payorId/payeeId to accountId in postings
  4. Add order field to each posting (0, 1, 2, ...)
  5. Ensure amounts balance to zero (sum of all posting amounts = 0)

Testing

  • Added 12 comprehensive double-entry validation tests
  • Total test count increased from 35 to 47
  • All tests passing with 82.35% branch coverage, 94.11% statement coverage
  • Validated balance checking, minimum posting requirements, and type constraints

Documentation

  • Updated README with double-entry accounting concepts and examples
  • Added detailed CHANGELOG with migration instructions
  • Updated all example JSON files with realistic double-entry transactions
  • Package version bumped to 2.0.0

Technical Implementation

  • Custom AJV keyword balancedPostings enforces sum-zero constraint
  • JSON Schema validation for integer amounts, UUID formats, and required fields
  • TypeScript type definitions fully updated with Posting interface
  • Both ESM and CJS builds include new posting schema

This implementation follows established accounting best practices and provides a solid foundation for double-entry bookkeeping in personal finance applications.

Original prompt

This section details on the original issue you should resolve

<issue_title>Convert Schema to Double-Entry</issue_title>
<issue_description># Convert LucaSchema from Single-Entry to Double-Entry Accounting

Summary

Convert LucaSchema from single-entry to double-entry accounting by introducing a Posting entity and restructuring the Transaction model to reference multiple postings where debits and credits balance according to double-entry principles.

Goals

  1. Introduce Posting Entity: Create a new Posting interface that represents individual debit/credit entries with proper account references
  2. Restructure Transaction Model: Modify the Transaction interface to reference postings instead of direct payorId/payeeId/amount fields
  3. Implement Double-Entry Validation: Add validation logic to ensure postings balance correctly (sum of all posting amounts equals zero)
  4. Maintain Schema Integrity: Ensure accounts are properly validated and maintain relationship with existing entity system
  5. Update Test Infrastructure: Modify test utilities to generate valid double-entry transactions

Technical Considerations

  • Amount Storage: Use signed integer amounts (recommended approach) rather than separate debit/credit type field to reduce complexity and potential inconsistencies
  • Account Validation: Ensure accountId references valid accounts from existing entity system where entityType="ACCOUNT"
  • Category Handling: Derive categories from primary debit/credit account or maintain as decorative field with validation warnings

Acceptance Criteria

New Schema Components

  • Create Posting interface with fields:
    • id: string (UUID) - optional if postings are embedded in transactions
    • accountId: string (UUID reference to entities where entityType="ACCOUNT")
    • amount: integer (signed, in minor units matching current schema)
    • description: string (optional, posting-level description)
    • order: number (for stable ordering within transaction)
  • Update Transaction interface to:
    • Replace payorId, payeeId, amount with postings: Posting[]
    • Keep existing fields: id, categoryId, date, description, transactionState, createdAt, updatedAt
  • Add corresponding JSON schemas in src/schemas/ for the new Posting entity
  • Update RecurringTransaction to follow the same double-entry pattern with template support

Validation Rules

  • Implement validation that ensures transaction.postings.length >= 2
  • Add validation that sum of all posting amounts equals zero: Σ(postings.amount) = 0
  • Ensure each posting references a valid accountId (must exist in entities where entityType="ACCOUNT")
  • Disallow zero-amount postings
  • Maintain existing validation for dates, UUIDs, and enum values
  • Add account type validation to warn when posting direction contradicts account's normal balance

Code Updates

  • Update createTestTransaction in test utilities to generate balanced postings
  • Update createTestRecurringTransaction to support posting templates with variables
  • Modify all test files in src/tests/ for new double-entry model
  • Update lucaValidator to include posting validation schemas
  • Add property-based test generators for balanced posting creation
  • Implement referential integrity validation (AccountLookup callback or schema validation)

Documentation & Examples

  • Update README.md with canonical examples for each transaction type:
    • Expense: Dr Groceries $65.32, Cr Checking Account $65.32
    • Income: Dr Checking Account $2000, Cr Salary Income $2000
    • Transfer: Dr Savings $500, Cr Checking $500
    • Split transaction example (3+ postings)
  • Update CHANGELOG.md marking as BREAKING change, list removed fields
  • Document validation invariants explicitly
  • Clarify that accounts are first-class entities, vendors/people are separate metadata

Test Coverage

  • Add property-based tests for balanced posting generation
  • Add tests for sum-zero validation (both positive and negative scenarios)
  • Add tests for minimum posting count validation
  • Add snapshot tests for deterministic transaction ordering
  • Test account type vs posting direction validation warnings
  • Ensure all existing test suites pass with new model

Build & Export

  • Update src/index.ts to export new Posting types
  • Ensure scripts/build.ts includes new schema files in distribution
  • Verify both ESM and CJS builds work with new schemas
  • Update TypeScript definitions for proper type support

Breaking Change Notice: This is a major breaking change requiring version 2.0.0. The payorId, payeeId, and amount fields will be removed from Transaction interface.</issue_description>

Comments on the Issue (you are @copilot in this section)

@jwaspin The purpose ...

Fixes #47


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI and others added 4 commits October 19, 2025 01:18
…ble-entry

Co-authored-by: jwaspin <6432180+jwaspin@users.noreply.github.com>
Co-authored-by: jwaspin <6432180+jwaspin@users.noreply.github.com>
Co-authored-by: jwaspin <6432180+jwaspin@users.noreply.github.com>
Co-authored-by: jwaspin <6432180+jwaspin@users.noreply.github.com>
Copilot AI changed the title [WIP] Convert LucaSchema to double-entry accounting Convert LucaSchema from Single-Entry to Double-Entry Accounting (v2.0.0) Oct 19, 2025
Copilot AI requested a review from jwaspin October 19, 2025 01:34
@jwaspin jwaspin changed the title Convert LucaSchema from Single-Entry to Double-Entry Accounting (v2.0.0) Convert LucaSchema from Single-Entry to Double-Entry Accounting (v2.1.0) Jan 5, 2026
@jwaspin
Copy link
Contributor

jwaspin commented Jan 5, 2026

@copilot major changes have been made since this PR was opened. The new 2.0.0 schema is heavily modified but this PR should still be able to be integrated. If you see an easy path forward to merge the changes while including the changes to convert to double-entry, then go for it, otherwise leave a comment saying I should just start a new branch.

Copy link
Author

Copilot AI commented Jan 5, 2026

@copilot major changes have been made since this PR was opened. The new 2.0.0 schema is heavily modified but this PR should still be able to be integrated. If you see an easy path forward to merge the...

After analyzing the changes, I recommend starting a new branch. Here's why:

Fundamental Incompatibilities:

  1. Accounting Model: Main branch (v2.0.0) uses single-entry account-based transactions with optional splits, while this PR implements full double-entry accounting with balanced postings. These are mutually exclusive approaches.

  2. Tech Stack: Main has been refactored from TypeScript to JavaScript with type generation, while this PR is TypeScript-based.

  3. Schema Structure:

    • Main: Transaction → Account + Splits
    • This PR: Transaction → Postings (balanced debits/credits)
  4. File Structure: Main removed most TypeScript files and restructured the entire codebase.

Conflict Resolution Challenges:

  • ~40+ files with conflicts
  • Core schemas completely redesigned
  • Test infrastructure entirely different
  • Build system changed (pnpm vs yarn, JS vs TS)

Starting fresh on the current v2.0.0 base would be cleaner and allow us to properly implement double-entry on top of the existing account structure if that's still the desired direction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Convert Schema to Double-Entry

2 participants