Skip to content

Commit

Permalink
Added account ledger (#63)
Browse files Browse the repository at this point in the history
* Added account ledger

* removed balance and max_entry_date variables

* removed opening balance
  • Loading branch information
sameer-639 authored Apr 5, 2024
1 parent cac9531 commit ea9e6d6
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 3 deletions.
56 changes: 55 additions & 1 deletion pyluca/ledger.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional
from typing import List, Optional, NamedTuple
from datetime import datetime
from collections import defaultdict
import pandas as pd
Expand All @@ -8,6 +8,60 @@
from pyluca.journal import Journal


class InvalidLedgerEntry(Exception):
pass


class LedgerEntry(NamedTuple):
date: datetime
dr_amount: float
cr_amount: float
narration: str
balance: float
event_id: Optional[str]


class AccountLedger:
def __init__(self, account_name: str, balance_type: BalanceType):
self.account_name = account_name
self.balance_type = balance_type
self.__entries: List[LedgerEntry] = []

def add_entry(self, date: datetime, dr_amount: float, cr_amount: float, narration: str, event_id: Optional[str]):
if len(self.__entries) and date < self.__entries[-1].date:
raise InvalidLedgerEntry("Backdated entry can't be added")
balance = self.__entries[-1].balance if len(self.__entries) else 0
balance += dr_amount - cr_amount if self.balance_type == BalanceType.DEBIT else cr_amount - dr_amount
self.__entries.append(
LedgerEntry(
date=date,
dr_amount=dr_amount,
cr_amount=cr_amount,
narration=narration,
balance=balance,
event_id=event_id
)
)

def get_balance(self, as_of: Optional[datetime] = None) -> float:
if as_of is None:
return self.__entries[-1].balance

balance = 0
start, end = 0, len(self.__entries) - 1
while start <= end:
mid = (start + end) // 2
if self.__entries[mid].date <= as_of:
balance = self.__entries[mid].balance
start = mid + 1
else:
end = mid - 1
return balance

def get_entries(self) -> List[LedgerEntry]:
return self.__entries


class Ledger:
def __init__(self, journal: Journal, config: dict):
self.journal = journal
Expand Down
109 changes: 108 additions & 1 deletion pyluca/tests/test_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,69 @@
from unittest import TestCase
from pyluca.accountant import Accountant
from pyluca.journal import Journal, JournalEntry
from pyluca.ledger import Ledger
from pyluca.ledger import Ledger, AccountLedger, InvalidLedgerEntry
from pyluca.account_config import BalanceType
from pyluca.tests.test_aging import account_config

sample_ledger_entries = [
{
'date': datetime(2024, 3, 1),
'dr_amount': 0,
'cr_amount': 20000,
'narration': 'salary credited',
'balance': 20000,
'event_id': None
},
{
'date': datetime(2024, 3, 2),
'dr_amount': 5000,
'cr_amount': 0,
'narration': 'home loan emi',
'balance': 15000,
'event_id': None
},
{
'date': datetime(2024, 3, 3),
'dr_amount': 3000,
'cr_amount': 0,
'narration': 'bike emi',
'balance': 12000,
'event_id': None
},
{
'date': datetime(2024, 3, 4),
'dr_amount': 10000,
'cr_amount': 0,
'narration': 'Rent',
'balance': 2000,
'event_id': None
},
{
'date': datetime(2024, 3, 5),
'dr_amount': 0,
'cr_amount': 5000,
'narration': 'borrowed from friend',
'balance': 7000,
'event_id': None
},
{
'date': datetime(2024, 3, 6),
'dr_amount': 4000,
'cr_amount': 0,
'narration': 'SIP Investment',
'balance': 3000,
'event_id': None
},
{
'date': datetime(2024, 3, 6),
'balance': 2000,
'cr_amount': 0,
'dr_amount': 1000,
'event_id': None,
'narration': 'shopping'
}
]


class TestLedger(TestCase):
def test_ledger_aging(self):
Expand Down Expand Up @@ -122,3 +182,50 @@ def test_get_accounts_balance_as_of(self):
self.assertEqual(accounts_balance['CAR_EMI'], 3000)

self.assertEqual(ledger.get_balances(), ledger.get_balances(datetime(2022, 5, 2)))

def test_account_ledger(self):
ledger = AccountLedger("Savings", BalanceType.CREDIT)
ledger.add_entry(date=datetime(2024, 3, 1), dr_amount=0, cr_amount=20000, narration="salary credited",
event_id=None)
self.assertEqual(ledger.get_balance(), 20000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 2, 29)), 0)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 1)), 20000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 2)), 20000)
with self.assertRaises(InvalidLedgerEntry) as e:
ledger.add_entry(date=datetime(2024, 2, 29), dr_amount=5000, cr_amount=0, narration="loan emi",
event_id=None)
self.assertEqual(e.exception.__str__(), "Backdated entry can't be added")
ledger.add_entry(date=datetime(2024, 3, 2), dr_amount=5000, cr_amount=0, narration="home loan emi",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 3), dr_amount=3000, cr_amount=0, narration="bike emi",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 4), dr_amount=10000, cr_amount=0, narration="Rent",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 5), dr_amount=0, cr_amount=5000, narration="borrowed from friend",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 6), dr_amount=4000, cr_amount=0, narration="SIP Investment",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 6), dr_amount=1000, cr_amount=0, narration="shopping",
event_id=None)
self.assertEqual(ledger.get_balance(), 2000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 2)), 15000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 3)), 12000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 4)), 2000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 5)), 7000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 6)), 2000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 7)), 2000)
self.assertEqual([entry._asdict() for entry in ledger.get_entries()], sample_ledger_entries)

ledger = AccountLedger("Asset", BalanceType.DEBIT)
ledger.add_entry(date=datetime(2024, 3, 1), dr_amount=5000, cr_amount=0, narration="lent to friend",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 2), dr_amount=0, cr_amount=2000, narration="received 2000",
event_id=None)
ledger.add_entry(date=datetime(2024, 3, 3), dr_amount=3000, cr_amount=0, narration="Invested in stock",
event_id=None)
self.assertEqual(ledger.get_balance(), 6000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 2, 29)), 0)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 1)), 5000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 2)), 3000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 3)), 6000)
self.assertEqual(ledger.get_balance(as_of=datetime(2024, 3, 4)), 6000)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setuptools.setup(
name='pyluca',
version='2.6.2',
version='2.7.0',
author='datasignstech',
author_email='[email protected]',
description='Double entry accounting system',
Expand Down

0 comments on commit ea9e6d6

Please sign in to comment.