A minimal, yet valid double-entry accounting system in Python.
I think it's a great idea to mock-up a mini GL ERP to really get a foundational understanding of how the accounting in ERP works!
I teach accounting information systems... I'd be tempted to use abacus as a way of simply showing the structure of a simple AIS.
Hey, what a cool job, thanks much. Do you plan to make a possibility for RAS accounting?
pip install git+https://github.com/epogrebnyak/abacus.git
from abacus import Chart, Entry, BalanceSheet
chart = Chart(
assets=["cash"],
expenses=["overhead"],
equity=["equity", "retained_earnings"],
liabilities=["dividend_payable"],
income=["sales"],
contra_accounts={"sales": (["discounts"], "net_sales")},
)
entries = [
Entry("cash", "equity", 500), # started a company...
Entry("cash", "sales", 150), # selling thin air
Entry("discounts", "cash", 30), # with a discount
Entry("overhead", "cash", 50), # and overhead expense
]
balance_sheet = (
chart.make_ledger()
.process_entries(entries)
.close("retained_earnings")
.process_entry(dr="retained_earnings", cr="dividend_payable", amount=35)
.balance_sheet()
)
# check what we've got
assert balance_sheet == BalanceSheet(
assets={"cash": 570},
capital={"equity": 500, "retained_earnings": 35},
liabilities={"dividend_payable": 35},
)
This code is saved in minimal.py
- We start with a chart of accounts of five types: assets, equity, liabilities, income and expenses.
from abacus import Chart, Entry
chart = Chart(
assets=["cash", "receivables", "goods_for_sale"],
expenses=["cogs", "sga"],
equity=["equity", "re"],
liabilities=["divp", "payables"],
income=["sales"],
)
- Next, create a general ledger based on chart of accounts.
ledger = chart.make_ledger()
- Add accounting entries using account names from the chart of accounts.
e1 = Entry(dr="cash", cr="equity", amount=1000) # pay capital
e2 = Entry(dr="goods_for_sale", cr="cash", amount=250) # acquire goods
e3 = Entry(cr="goods_for_sale", dr="cogs", amount=200) # sell goods
e4 = Entry(cr="sales", dr="cash", amount=400)
e5 = Entry(cr="cash", dr="sga", amount=50) # administrative expenses
ledger = ledger.process_entries([e1, e2, e3, e4, e5])
- Close accounts at period end, produce income statement and balance sheet.
# Create income statement
income_statement = ledger.income_statement()
print(income_statement)
# Close ledger and publsh balance sheet
closed_ledger = ledger.close("re")
balance_sheet = closed_ledger.balance_sheet()
print(balance_sheet)
IncomeStatement(income={'sales': 400}, expenses={'cogs': 200, 'sga': 50})
BalanceSheet(
assets={"cash": 1100, "receivables": 0, "goods_for_sale": 50},
capital={"equity": 1000, "re": 150},
liabilities={"divp": 0, "payables": 0}
)
- Print balance sheet and income statement to screen with verbose account names and rich formatting.
from abacus import RichViewer
rename_dict = {
"re": "Retained earnings",
"divp": "Dividend due",
"cogs": "Cost of goods sold",
"sga": "Selling, general and adm. expenses",
}
cv = RichViewer(rename_dict, width=60)
cv.print(balance_sheet)
cv.print(income_statement)
Check out readme.py
for a complete code example
featuring contraccounts (eg depreciation) and dividend payout.
This code is intended as an educational device that informs users about principles of double-entry accounting, accounting information systems (AIS) and good coding practices in Python.
abacus
should be usable as 'headless' general ledger
engine that accepts a chart of accounts, accounting entries
and produces a balance sheet and an income statement.
abacus
may also enable simulations where you generate a stream
of business events and condense it to a financial report.
Below are some simplifying assumptions made for this code:
-
Account structure is flat, there are no subaccounts. This allows to represent ledger as a dictionary, while in a real system you will need a tree-like data structure to introduce subaccounts.
-
Every entry involves exactly two accounts, there are no compound entries.
-
No cash flow and changes in capital are reported.
-
There are no journals - all records go directly to general ledger.
-
Accounting entry is slim - it has no information other than debit and credit accounts and entry amount. Thus we have no extra information for managment accounting or detailed tax calculations.
-
Accounts balances can go to negative where they should not and there are little checks for entry validity.
-
XML likely to be a format for accounting reports interchange, while JSON is intended for
abacus
. -
We use just one currency.
-
Closing contra accounts and closing income summary account is stateful and needs careful reasoning and implementation. More safeguards can be implemented.
-
Account balances stay on one side, and do not migrate from one side to another. Credit accounts have credit balance, debit accounts have debit balance, and income summary account is a credit account.
-
Entries are stored in a queue and ledger state is calculated based on a previous state and a list of entries to be proccessed.
-
The chart of accounts can be fairly complex, up to level of being GAAP/IAS compliant.
-
Chart of accounts may include contra accounts. Temporary contra accounts for income (eg discounts) and expense (can't think of an example) are cleared at period end and permanent contra accounts (eg accumulated depreciation) are carried forward.
-
You can give a name to typical dr/cr account pairs and use this name to record transactions.
-
The code is covered by some tests, linted and type annotated.
-
Data structures used are serialisable, so imputs and outputs can be stored and retrieved.
-
Modern Python features such as subclasssing and pattern matching help to make code cleaner. For example, classes like
Asset
,Expense
,Capital
,Liability
,Income
to pass information about account types and hold debits and credits. -
This is experimental software. The upside is that we can make big changes fast. On a downside we do not learn (or earn) from users. Also we do not compete with SAP, Oralce, Intuit,
hledger
, orgnucash
in making a complete software product.
... is much appreciated. I like the idea that compact code for an accounting ledger is possible, but there is so much other people might know or contribute to this idea.
Please comnent in issues, reddit or Telegram.