Skip to content

Commit

Permalink
removed Reference class
Browse files Browse the repository at this point in the history
  • Loading branch information
epogrebnyak committed Jan 21, 2024
1 parent b8a086e commit 3b40d14
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 69 deletions.
4 changes: 2 additions & 2 deletions abacus0/engine/better_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from pydantic import BaseModel

from abacus import AbacusError, Amount, Entry # type: ignore
from abacus.engine.accounts import ( # type: ignore
from abacus.engine.accounts import (
Asset,
Capital,
Capital, # type: ignore
ContraAsset,
ContraCapital,
ContraExpense,
Expand Down
19 changes: 10 additions & 9 deletions core/test_uncore.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
Journal,
Move,
Pipeline,
Reference,
Regular,
Side,
T,
Expand All @@ -24,8 +23,8 @@

def test_contra_account_added_to_chart():
chart_dict = ChartDict().add(T.Income, "sales").offset("sales", "refunds")
assert chart_dict["sales"] == T.Income
assert chart_dict["refunds"] == Reference("sales")
assert chart_dict["sales"] == Regular(T.Income)
assert chart_dict["refunds"] == Contra(T.Income)


def test_invalid_contra_account_not_added_to_chart_by_method():
Expand Down Expand Up @@ -70,18 +69,20 @@ def test_chart_creation():
{
"cash": T.Asset,
"sales": T.Income,
"refunds": Reference("sales"),
"voids": Reference("sales"),
"equity": T.Capital,
"buyback": Reference("equity"),
}
},
{
"refunds": "sales",
"voids": "sales",
"buyback": "equity",
},
)
assert chart0.retained_earnings_account == "retained_earnings"
assert chart0.income_summary_account == "income_summary_account"


def test_journal_creation():
j = Journal.new(ChartDict(cash=T.Asset, contra_cash=Reference("cash")))
j = Journal.new(ChartDict().add(T.Asset, "cash").offset("cash", "contra_cash"))
assert j.set_isa("isa").set_re("re") == Journal(
{
"cash": Account(Regular(T.Asset)),
Expand Down Expand Up @@ -130,7 +131,7 @@ def journal(chart):

def test_journal_has_retained_earnings(chart):
j = Journal.from_chart(chart)
assert j[chart.retained_earnings_account].account_type == Regular(T.Capital)
assert j[chart.retained_earnings_account].flavor == Regular(T.Capital)


def test_balances(journal):
Expand Down
115 changes: 59 additions & 56 deletions core/uncore.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""Accounting chart, journal and reports."""

from abc import ABC
from collections import UserDict, namedtuple
from dataclasses import dataclass, field
from decimal import Decimal
from enum import Enum
from typing import Protocol


class Side(Enum):
Expand All @@ -22,7 +22,7 @@ class T(Enum):
Expense = "expense"


class AccountType(Protocol):
class AccountType(ABC):
@property
def side(self) -> Side:
...
Expand Down Expand Up @@ -69,21 +69,6 @@ def reverse(side: Side):
return Side.Debit


@dataclass
class Reference:
"""Reference class is used for contra account definition.
`points_to` refers to 'parent' account name."""

points_to: str


def assert_reference_exists(key, keys):
if key not in keys:
raise KeyError(
f"Account '{key}' not in chart, cannot add a contrа account to it."
)


# not used
@dataclass
class Label:
Expand All @@ -98,39 +83,53 @@ class Offset:
name: str


class ChartDict(UserDict[str, T | Reference]):
def assert_reference_exists(key, keys):
if key not in keys:
raise KeyError(
f"Account '{key}' not in chart, cannot add a contrа account to it."
)


@dataclass
class ChartDict:
regular_accounts: dict[str, T] = field(default_factory=dict)
contra_accounts: dict[str, str] = field(default_factory=dict)

def add(self, t: T, name: str):
"""Add regular account to chart."""
self[name] = t
self.regular_accounts[name] = t
return self

def offset(self, name: str, contra_name: str):
"""Offset an existing account `name` in chart with a new `contra_name`."""
assert_reference_exists(name, self.keys())
self[contra_name] = Reference(name)
assert_reference_exists(name, self.regular_accounts.keys())
self.contra_accounts[contra_name] = name
return self

def contra_pairs(self, t: T) -> list[tuple[str, str]]:
"""List contra accounts, result should similar to
`[('sales', 'refunds'), ('sales', 'voids')]`."""
return [
(value.points_to, name)
for name, value in self.items()
if isinstance(value, Reference) and self[value.points_to] == t
(name, contra_name)
for contra_name, name in self.contra_accounts.items()
if self.regular_accounts[name] == t
]

def regular_names(self, *ts: T) -> list[str]:
"""List regular account names by type."""
return [name for name, value in self.items() if value in ts]
return [name for name, t in self.regular_accounts.items() if t in ts]

def __getitem__(self, name: str) -> Contra | Regular:
"""Return regular or contra account for `name`."""
if name in self.regular_accounts:
return Regular(self.regular_accounts[name])
if name in self.contra_accounts:
return Contra(self.regular_accounts[self.contra_accounts[name]])
raise KeyError(name)

def account_type(self, name) -> Contra | Regular:
"""Return regular or contra account type based on `name`."""
what = self[name]
if isinstance(what, Reference):
t: T = self[what.points_to] # type: ignore
return Contra(t)
elif isinstance(what, T):
return Regular(what)
def items(self):
for k in list(self.regular_accounts.keys()) + list(self.contra_accounts.keys()):
yield k, self[k]


@dataclass
Expand Down Expand Up @@ -187,10 +186,14 @@ def sums(f):

@dataclass
class Account:
account_type: AccountType
flavor: AccountType
debits: list[Amount] = field(default_factory=list)
credits: list[Amount] = field(default_factory=list)

@property
def side(self) -> Side:
return self.flavor.side

def debit(self, amount: Amount) -> None:
"""Add debit amount to account."""
self.debits.append(amount)
Expand All @@ -206,17 +209,19 @@ def tuple(self):
def condense(self):
"""Return new account of same type with account balance
on proper side debit or credit side."""
match self.account_type.side:
match self.side:
case Side.Debit:
a, b = [self.balance()], []
case Side.Credit:
a, b = [], [self.balance()]
return self.__class__(self.account_type, a, b)
case _:
raise ValueError(self)
return self.__class__(self.flavor, a, b)

def balance(self):
"""Return account balance."""
a, b = self.tuple()
match self.account_type.side:
match self.side:
case Side.Debit:
return a - b
case Side.Credit:
Expand All @@ -232,8 +237,8 @@ def new(
chart_dict: ChartDict,
):
journal = cls()
for key in chart_dict.keys():
journal[key] = Account(chart_dict.account_type(key))
for key, account_type in chart_dict.items():
journal[key] = Account(account_type)
return journal

@classmethod
Expand Down Expand Up @@ -287,7 +292,7 @@ def tuples(self):

def subset(self, t: T):
cls = self.__class__
return cls({k: a for k, a in self.items() if a.account_type == Regular(t)})
return cls({k: a for k, a in self.items() if a.flavor == Regular(t)})


@dataclass
Expand All @@ -301,7 +306,7 @@ class Move:
def to_entry(self, journal):
account = journal[self.frm]
b = account.balance()
match account.account_type.side:
match account.side:
case Side.Debit:
return double_entry(self.to, self.frm, b)
case Side.Credit:
Expand Down Expand Up @@ -435,14 +440,14 @@ class TrialBalance(UserDict[str, tuple[Amount, Amount, Side]], Statement):
def new(cls, journal: Journal):
tb = cls()
for side in [Side.Debit, Side.Credit]:
for name, a in journal.items():
if a.account_type.side == side:
x, y = a.tuple()
for name, account in journal.items():
if account.side == side:
x, y = account.tuple()
tb[name] = x, y, side
return tb

def net(self):
"""Show net balance on one side of account."""
"""Show net balance on proper side of account."""
tb = self.__class__()
for n, (a, b, s) in self.items():
if s == Side.Debit:
Expand All @@ -452,25 +457,23 @@ def net(self):
return tb

def drop_null(self):
"""Drop accounts where balance is null.
These are usually temporary and intermediate accounts after closing."""
"""Drop accounts where balance is null."""
# These are usually temporary and intermediate accounts after closing.
tb = self.__class__()
for n, (a, b, s) in self.items():
if a + b != 0:
tb[n] = (a, b, s)
if (a + b) == 0:
continue
tb[n] = (a, b, s)
return tb

def brief(self) -> dict[str, tuple[Amount, Amount]]:
"""Hide information about side of accounts."""
"""Hide information about debit or credit side of accounts."""
return {n: (a, b) for n, (a, b, _) in self.items()}

def balances(self) -> dict[str, Amount]:
"""Return dictionary with account balances."""
return {n: a + b for n, (a, b, _) in self.net().items()}

def non_zero_balances(self) -> dict[str, Amount]:
return self.drop_null().balances()


def trial_balance(journal) -> TrialBalance:
return TrialBalance.new(journal)
Expand All @@ -488,8 +491,8 @@ def trial_balance(journal) -> TrialBalance:
journal = Journal.from_chart(chart)
journal.post_many(
[
double_entry("cash", "equity", 1200),
double_entry("buyback", "cash", 200),
double_entry("cash", "equity", 1200.5),
double_entry("buyback", "cash", 200.5),
[debit("ar", 70), credit("sales", 60), credit("vat", 10)],
double_entry("cash", "ar", 30),
[
Expand All @@ -502,13 +505,13 @@ def trial_balance(journal) -> TrialBalance:
]
)
t, i, b, final_t = statements(chart, journal)
# todo: test TrialBalance
print(t.brief())
print(b)
print(i)
print(i.current_profit)
print(final_t.drop_null().brief())

# Next:
# - test TrialBalance
# - viewers (separate file)
# - company with chart and transactions from experimental.py
4 changes: 2 additions & 2 deletions tests0/test_better_chart.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abacus.engine.accounts import ( # type: ignore
from abacus.engine.accounts import (
Asset,
Capital,
Capital, # type: ignore
ContraCapital,
IncomeSummaryAccount,
NullAccount,
Expand Down

0 comments on commit 3b40d14

Please sign in to comment.