Metadata-Version: 2.1
Name: abacus-minimal
Version: 0.11.0
Summary: Ledger in Python that follows corporate accounting rules.
License: MIT
Author: Evgeny Pogrebnyak
Author-email: e.pogrebnyak@gmail.com
Requires-Python: >=3.10,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
Description-Content-Type: text/markdown

# abacus-minimal

![PyPI - Version](https://img.shields.io/pypi/v/abacus-minimal?color=blue)

`abacus-minimal` is an accounting logic library that aims to be concise, correct and expressive in implementation of double entry book-keeping rules.

## Install

```bash
pip install abacus-minimal
```

Latest:

```bash
pip install git+https://github.com/epogrebnyak/abacus-minimal.git
```

## Minimal example

A company receives initial shareholder investment of 500 USD in cash,
acquires a stock of merchandise for 200 USD,
gets a lucky chance to sell all merchandise for 400 USD,
and gladly pays 150 USD as salaries to the staff.

We programmatically run the accounting workflow within
one reporting period for this company and show end of period reports.

```python
from abacus import Book, Chart, Entry

chart = Chart(
    retained_earnings="retained_earnings",
    current_earnings="current_earnings",
    assets=["cash", "inventory"],
    capital=["equity"],
    income=["sales"],
    expenses=["salaries", "cogs"],
)
book = Book(chart)
entries = [
    Entry("Initial shareholder funds").debit("cash", 500).credit("equity", 500),
    Entry("Acquired goods").debit("inventory", 200).credit("cash", 200),
    Entry("Accepted payment for goods").debit("cash", 400).credit("sales", 400),
    Entry("Shipped goods").debit("cogs", 200).credit("inventory", 200),
    Entry("Paid salaries").debit("salaries", 150).credit("cash", 150),
]
book.post_many(entries)
book.close()
print(book.income_statement)
print(book.balance_sheet)
# Some checks
assert book.balance_sheet.assets.total == 550
assert book.income_statement.net_earnings == 50
assert book.balances == {"cash": 550, "inventory": 0, "equity": 500, "retained_earnings": 50}
```

## Accounting concepts

`abacus-minimal` adheres to the following interpretation of double-entry book keeping rules.

1. The value of company property, or assets, equals to shareholder and creditor claims on the company, known as capital (or equity) and liabilities respectively:

```
Assets = Capital + Liabilities
```

2. Company current earnings, or profit, equal to income less expenses associated with
   generating the income:

```
Current earnings = Income - Expenses
```

3. Capital consists of paid-in capital (or shareholder equity), other capital accounts and
   retained earnings:

```
Capital = Shareholder Equity + Other Capital + Retained earnings
```

4. Current earnings accumulate to retained earnings:

```
Retained earnings = Retained earnings from previous period + Current earnings
```

5. Substituting we get a form of extended accounting equation:

```
Assets + Expenses = Shareholder Equity + Other Capital + Retained earnings + Income + Liabilities
```

6. Our book-keeping goal is to reflect business events as changes to the variables in this
   equation while maintaining the identity between the left and the right sides of the equation,
   as well as following the accounting principles for recognition, measurement and reporting.

7. Changing just one variable will break the accountign equation, so we allow postings,
   or entries that change two or more vairables, depending on the nature of the business transaction.
8. Debits and credits are a common framework to keep track of the changes in accounts. It is less
   fundimental than accounting identity, just a clever system to avoid at least some of accounting mistakes
   when expressing what variables in accounting identity you are changing.
9. Let as allow for every account to accumulate the changes as a list of debit amounts and a list of credit amounts.
   There will be accounts where the account balance is sum of debits less sum of credits ('debit normal'),
   and accounts where the balance is sum of credits less sum of debits ('credit normal').

10. Let us designate the assets and expenses accounts as 'debit normal' and capital, liabilities and income accounts
    as 'credit normal'. Now instead of saying "Please do not break the accounting identity when changing the variables"
    we can now say "When creating and posting an entry the sum of debit changes must be equal to the sum of credit changes",
    which is easier to track.

11. In a double entry you add the same amount to the debit of one account and to the credit of another account and
    the accounting identity never breaks. Profit!

12. We would not want a company that just accumulates profits and never pays divident to shareholders. Sometimes you would see
    at equation similar to:

```
Retained earnings (at period end) =  Retained earnings (at period start) + (Income - Expenses) - Dividend
```

To pay out the dividends the company will transfer part of retained earnings to dividend due libility after announcing the dividend, and will pay out the dividend due with cash at dividend disbursement.
(Think of two double entries that reflect these operations).

## Accounting workflow

The steps for using `abacus-minimal` follow the steps of a typical accounting cycle:

- create a chart of accounts,
- open ledger for the current reporting period,
- post account balances for the previous period,
- post entries that reflect business transactions within the period,
- post reconciliation and adjustment entries,
- close accounts at reporting period end,
- make post-close entries,
- show financial reports,
- save account balances data for the next reporting period.

Accounts survive these transformations quite differently, here is the best summary I can give:

| Step                                                      |  Assets   |  Expenses  | Shareholder Equity | Retained Earnings | Current Earnings |   Income   | Liabilities |
| --------------------------------------------------------- | :-------: | :--------: | :----------------: | :---------------: | :--------------: | :--------: | :---------: |
| Account type                                              | permanent | temporary  |     permanent      |     permanent     |    temporary     | temporary  |  permanent  |
| 1\. In empty ledger all accounts have zero balances       |     0     |     0      |         0          |         0         |        0         |     0      |      0      |
| 2\. Opening balances restore permanent accounts           |   **+**   |     0      |       **+**        |       **r**       |        0         |     0      |    **+**    |
| 3\. Business entries affect all accounts except earnings  |     +     |     e      |         +          |         r         |        0         |     i      |      +      |
| 4\. Closing income and expense to current earnings        |     +     | **closed** |         +          |         r         |     **i-e**      | **closed** |      +      |
| 5\. Closing current to retained earings                   |     +     |            |         +          |   **r + i -e**    |    **closed**    |            |      +      |
| 6\. Saving permanent account balances for the next period |     +     |            |         +          |         +         |                  |            |      +      |

## End-to-end example

In this example we use more `abacus-minimal` features including:

- contra accounts specification,
- opening ledger with account balances at the start of reporting period,
- posting multiple entries and using various types of syntax for an entry,
- showing proxy income statement and balance sheet before closing,
- saving and loading data to JSON files.

The complete code is in [readme.py](examples/readme.py).

### 1. Create chart of accounts

Steps involved:

- specify names of the current earnings and retained earnings accounts,
- add account names for assets, capital, liabilities, income and expenses,
- add contra accounts (e.g. `refunds` is a contra account to `sales`).

Code example:

```python
from abacus import Chart

chart = Chart(
    retained_earnings="retained_earnings",
    current_earnings="current_earnings",
    assets=["cash", "ar"],
    capital=["equity"],
    liabilities=["vat_payable"],
    income=["sales"],
    expenses=["salaries"],
)
chart.offset("sales", "refunds")
chart.name("ar", "Accounts receivable")
```

`Chart` class is a `pydantic` model, which means it is easily converted to a JSON file.
You can save or load a chart from a file.

```python
chart.save("chart.json")
chart = Chart.load("chart.json")
```

### 2. Start ledger

Steps involved:

- create a data structure that represents state of accounts (a 'book'),
- record account starting balances from the previous period,

Let's create a book with opening balances known from previous period:

```python
from abacus import Book

book = Book(chart)
opening_balances = {
    "cash": 10_000,
    "equity": 8_000,
    "retained_earnings": 2_000
    }
book.open(opening_balances)
```

At this point the book is ready to record new entries.

### 3. Post entries to ledger

Steps involved:

- record entries that represent business transactions,
- show state of ledger (as trial balance or as account balances) at any time.

Each entry has a title and directions to alter the accounts that are called debits and credits.
The sum of debits should match the sum of credits for a valid entry.
The `Entry` class provides several ways to record the composition of an entry as shown below.

```python
from abacus import Entry

entries = [
    Entry("Invoice with VAT").debit("ar", 6000).credit("sales", 5000).credit("vat_payable", 1000),
    Entry("Cash payment").debit("cash", 6000).credit("ar", 6000),
    Entry("Cashback").double(debit="refunds", credit="cash", amount=500),
    Entry("Paid salaries").set_amount(1500).debit("salaries").credit("cash"),
]

# Post entries to book
book.post_many(entries)
```

Note: there are no reconciliations, adjustments and post-close entries in this example.

### 4. Inspecting ledger

After posting entries you can inspect the trial balance or account balances:

```python
# Show trial balance and account balances
print(book.trial_balance)
print(book.balances)

# Check account balances match expected values
assert book.balances == {
    "cash": 14000,
    "ar": 0,
    "equity": 8000,
    "vat_payable": 1000,
    "sales": 5000,
    "refunds": 500,
    "salaries": 1500,
    "current_earnings": 0,
    "retained_earnings": 2000,
}
```

### 5. Closing accounts

Closing accounts at period end involves:

- closing contra accounts related to income and expense accounts, and
- closing income and expense accounts to retained earnings.

See section below for code for closing accounts.

Note: account closing was a surprisingly hard part of `abacus-minimal` code that I had to refactor several times.

### 6. Reporting financial statements

Financial reports are typically displayed after account closing,
but proxy reports are available before closing as well:

- **The income statement** will be the same before and after closing.
- **The balance sheet before closing** the will contain current earnings account
  and retained earnings from previous periods.
- **After closing** the current earnings account will be transferred to the retained earnings account and removed from the ledger; it will not appear in the balance sheet.

Expect to see a lot of dictionary-like data structures in code output below:

```python
print("=== Before closing ===")
print(book.income_statement)
print(book.balance_sheet)
assert book.balance_sheet.capital["current_earnings"] == 3000

# Close accounts at period end
book.close()

print("=== After closing ===")
print(book.income_statement)
print(book.balance_sheet)

# Check account balances match expected values
print(book.balances)
assert book.balances == {
    "cash": 14000,
    "ar": 0,
    "equity": 8000,
    "vat_payable": 1000,
    "retained_earnings": 5000,
}
```

### 7. Saving data for the next period

You can save the list of entries and the period end account balances to JSON files, unless these files already exist.
In that case you will need extra precaution – for example, save files to a different folder or under a different name.

```python
# Save JSON files
book.store.save("./entries.json")
book.balances.save("./end_balances.json")
```

## Architecture

### Core library

In `abacus-minimal` there is a small core library that consists of:

- `ChartDict` maps account names to their types,
- `Ledger` class maps account names to debit normal and credit normal T-accounts, and
- `Posting` type that represents a double or a multiple entry.

`Ledger` is created from `ChartDict` and incoming entries change the state of ledger.
`ChartDict` allows to create closing entries at accounting period end.
Trial balance, account balances, balance sheet and income statement reports
reflect the state of ledger.

### User interface

As a user you do not have to interact with the core directly. `abacus-minmal` exports `Chart`, `Entry` and `Book` classes
that we used in the examples.
The `Book` class holds together a chart, a store of entries, and a ledger and allows posting entries, closing the accounts,
creating reports and saving and loading JSON files.

### Limitations

Several assumptions and simplifications are used to make `abacus-minimal` easier to develop and reason about.

The key assumptions are:

- one currency,
- globally unique account names,
- one level of accounts in chart and no account aggregation for reports,
- no intermediate accounts,
- no treatment of other comprehensive income,
- no changes in equity and cash flow statements.

See [core.py](abacus/core.py) module docstring for more details.

## Alternatives

`abacus-minimal` takes a lot of inspiration from the following projects:

- [ledger](https://ledger-cli.org/),
  [hledger](https://github.com/simonmichael/hledger),
  [beancount](https://github.com/beancount/beancount)
  and other [plain text accounting tools](https://plaintextaccounting.org/),
- [medici](https://github.com/flash-oss/medici), a high performance ledger in JavaScript using Mongo database,
- [microbooks](https://microbooks.io/) API and [python-accounting](https://github.com/ekmungai/python-accounting), a production-grade project, tightly coupled to a database.

## Accounting knowledge

If you are totally new to accounting the suggested friendly course is <https://www.accountingcoach.com/>.

ACCA and CPA are the international and the US professional qualifications and IFRS and GAAP are the standards for accounting recognition, measurement and disclosure.

Part B-G in the [ACCA syllabus for the FFA exam](https://www.accaglobal.com/content/dam/acca/global/PDF-students/acca/f3/studyguides/fa-ffa-syllabusandstudyguide-sept23-aug24.pdf) talk about what `abacus-minimal` is designed for.

Textbooks:

- [list of free and open source textbooks](https://library.sacredheart.edu/opentextbooks/accounting)
- [Frank Wood "Business Accounting"](https://www.google.com/search?q=Frank+Wood+%22Business+Accounting)
- ["200 Years of Accounting History Dates and Events"](https://maaw.info/AccountingHistoryDatesAndEvents.htm)

## Project conventions

I use [`just` command runner](https://github.com/casey/just) to automate code maintenance tasks in this project.

`just test` and `just fix` scripts will run the following tools:

- `pytest`
- `mypy`
- `black` and `isort --float-to-top` (probably should replace with `ruff format`)
- `ruff check`
- `prettier` for markdown formatting
- `codedown` to extract Python code from README.md.

`examples/readme.py` is overwritten by the `just readme` command.

I use `poetry` as a package manager, but heard good things about `uv` that I want to try.

## Changelog

- `0.10.7` (2024-11-02) `Posting` type is a list of single entries.
- `0.10.5` (2024-10-27) Handles income statement and balances sheet before and after close.
- `0.10.0` (2024-10-24) Separates core, chart, entry and book code and tests.

## Roadmap

### Using upstream

Implanting `abacus-minimal` as a dependency to:

- [ ] [abacus-py][cli],
- [ ] [abacus-streamlit][app].

### New features

- [ ] Business event layer `Event("Invoice", net_amount=200, vat=40)`
- [ ] `Book.increase()` and `Book.decrease()` methods
- [ ] `Entry.explain()` method

### Application ideas

- [ ] real company - eg Walmart accounts
- [ ] business simulation layer - stream of entries
- [ ] more examples from textbooks
- [ ] chart repository and conversions between charts of accounts as requested in [#4][ras].
- [ ] a quiz based on Book class - play entries and expect the use picks correct debit and credit

[cli]: https://github.com/epogrebnyak/abacus
[app]: https://abacus.streamlit.app/
[ras]: https://github.com/epogrebnyak/abacus/issues/4

