Skip to content

Commit 75c84ef

Browse files
committed
✨ [feat][backend] read_account_balance function
1 parent b950344 commit 75c84ef

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

backend/kayman/crud/account.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from collections.abc import Sequence
2+
from datetime import datetime
23
from decimal import Decimal
34

5+
from sqlalchemy import func
46
from sqlmodel import Integer, Session, cast, select
57

68
from kayman.schemas.account import Account, AccountBase, AccountCreate, AccountUpdate
9+
from kayman.schemas.transaction import Transaction
710

811

912
def create_account(session: Session, account: AccountCreate) -> AccountBase:
@@ -17,6 +20,17 @@ def read_account(session: Session, account_id: int) -> Account | None:
1720
return session.get(Account, account_id)
1821

1922

23+
def read_account_balance(
24+
session: Session, account_id: int, at: datetime | None = None
25+
) -> Decimal:
26+
scalar = select(func.coalesce(func.sum(Transaction.amount), 0)).where(
27+
Transaction.account_id == account_id
28+
)
29+
if at is not None:
30+
scalar = scalar.where(Transaction.created_at < at)
31+
return Decimal(session.exec(scalar).one())
32+
33+
2034
def read_accounts(
2135
session: Session, account_ids: list[int] | None = None, for_update: bool = False
2236
) -> Sequence[Account]:

backend/kayman/tests/crud/test_account.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import random
22
import re
3+
from datetime import UTC, datetime
34
from decimal import Decimal
45
from unittest.mock import patch
56

@@ -10,12 +11,13 @@
1011
_verify_account_ids,
1112
create_account,
1213
read_account,
14+
read_account_balance,
1315
read_accounts,
1416
update_account_balances,
1517
update_accounts,
1618
)
1719
from kayman.schemas.account import Account
18-
from kayman.tests.factories import AccountFactory
20+
from kayman.tests.factories import AccountFactory, TransactionFactory
1921

2022

2123
def test_create_account(session: Session):
@@ -44,6 +46,90 @@ def test_read_account_not_found(session: Session):
4446
assert read_account(session, 1) is None
4547

4648

49+
def test_read_account_balance_empty_account(session: Session):
50+
account = AccountFactory()
51+
52+
assert read_account_balance(session, account.id) == Decimal(0)
53+
54+
55+
def test_read_account_balance_nonexistent_account(session: Session):
56+
assert read_account_balance(session, 9999) == Decimal(0)
57+
58+
59+
def test_read_account_balance_sums_all_txns_when_at_is_none(session: Session):
60+
account = AccountFactory()
61+
TransactionFactory(
62+
account=account,
63+
amount=Decimal("10.50"),
64+
created_at=datetime(2026, 1, 5, tzinfo=UTC),
65+
)
66+
TransactionFactory(
67+
account=account,
68+
amount=Decimal("-3.25"),
69+
created_at=datetime(2026, 2, 1, tzinfo=UTC),
70+
)
71+
TransactionFactory(
72+
account=account,
73+
amount=Decimal("7"),
74+
created_at=datetime(2026, 3, 1, tzinfo=UTC),
75+
)
76+
77+
assert read_account_balance(session, account.id) == Decimal("14.25")
78+
79+
80+
def test_read_account_balance_at_includes_only_txns_before_cutoff(session: Session):
81+
account = AccountFactory()
82+
TransactionFactory(
83+
account=account,
84+
amount=Decimal("100"),
85+
created_at=datetime(2025, 12, 1, tzinfo=UTC),
86+
)
87+
TransactionFactory(
88+
account=account,
89+
amount=Decimal("50"),
90+
created_at=datetime(2025, 12, 31, tzinfo=UTC),
91+
)
92+
TransactionFactory(
93+
account=account,
94+
amount=Decimal("999"),
95+
created_at=datetime(2026, 2, 1, tzinfo=UTC),
96+
)
97+
98+
assert read_account_balance(
99+
session, account.id, at=datetime(2026, 1, 1, tzinfo=UTC)
100+
) == Decimal("150")
101+
102+
103+
def test_read_account_balance_at_is_exclusive(session: Session):
104+
account = AccountFactory()
105+
cutoff = datetime(2026, 1, 1, tzinfo=UTC)
106+
TransactionFactory(
107+
account=account,
108+
amount=Decimal("5"),
109+
created_at=datetime(2025, 12, 31, tzinfo=UTC),
110+
)
111+
TransactionFactory(account=account, amount=Decimal("10"), created_at=cutoff)
112+
113+
assert read_account_balance(session, account.id, at=cutoff) == Decimal("5")
114+
115+
116+
def test_read_account_balance_isolates_account(session: Session):
117+
account_a = AccountFactory()
118+
account_b = AccountFactory()
119+
TransactionFactory(
120+
account=account_a,
121+
amount=Decimal("10"),
122+
created_at=datetime(2026, 1, 5, tzinfo=UTC),
123+
)
124+
TransactionFactory(
125+
account=account_b,
126+
amount=Decimal("999"),
127+
created_at=datetime(2026, 1, 5, tzinfo=UTC),
128+
)
129+
130+
assert read_account_balance(session, account_a.id) == Decimal("10")
131+
132+
47133
def test_read_accounts(session: Session):
48134
accounts = AccountFactory.create_batch(10)
49135
db_accounts = read_accounts(session)

0 commit comments

Comments
 (0)