python-accounting

repository·main·Indexed 41 days ago

https://github.com/ekmungai/python-accounting

A comprehensive double-entry bookkeeping library for Python 3.9+ designed to generate IFRS and GAAP compliant financial reports. It supports multi-entity management, account categorization, taxation, and ledger integrity protection. Key reporting capabilities include Income Statements, Balance Sheets, Cashflow Statements, as well as Aging Schedules and Account Statements for receivables and payables.

Tokens
8.9K
Snippets
24
Records
31
Agent score
52%

What's inside python-accounting

  1. Overview of Python Accounting features

    main

    Python Accounting is a Double Entry Bookkeeping library focused on generating Financial Reports compatible with IFRS and GAAP standards.

    Key capabilities include:

    • Multi-Entity Support: Manage multiple companies/entities.
    • Financial Reporting: Generates standard reports, Account Statements, and Schedules for Receivables (Clients) and Payables (Suppliers).
    • Aging Schedules: Provides schedules for outstanding balances grouped by configurable time periods (e.g., Current, 31-90 days, 91-180 days).
    • Integrity: Protects transactions against tampering via direct database changes to ensure Ledger integrity.
    • Accounting Features: Supports Account Categorization, Transaction assignment, Opening Balances, and Taxation.
  2. How to create and post Transactions

    main

    Transactions in python-accounting follow a double-entry system using a Transaction object and one or more LineItem objects.

    Workflow:

    1. Create the Transaction object (e.g., CashSale, ClientInvoice, CashPurchase, SupplierBill, ClientReceipt, or JournalEntry).
    2. session.add(transaction) and session.flush() to prepare it.
    3. Create LineItem objects for the other side of the entry, specifying the account_id, amount, and optionally quantity and tax_id.
    4. session.add(line_item) and session.flush().
    5. Associate the line item with the transaction: transaction.line_items.add(line_item).
    6. Crucial: Call transaction.post(session) to actually record the transaction in the Ledger.
    from python_accounting.transactions import CashSale
    from python_accounting.models import LineItem
    from datetime import datetime 
    
    # 1. Create Transaction
    cash_sale = CashSale(
        narration="Cash Sale Transaction",
        transaction_date=datetime.now(),
        account_id=bank_account.id,
        entity_id=entity.id,
    )
    session.add(cash_sale)
    session.flush() 
    
    # 2. Create Line Item
    cash_sale_line_item = LineItem(
        narration="Cash Sale line item",
        account_id=revenue_account.id,
        amount=100,
        tax_id=output_tax.id,
        entity_id=entity.id,
    )
    session.add(cash_sale_line_item)
    session.flush()
    
    # 3. Link and Post
    cash_sale.line_items.add(cash_sale_line_item)
    session.add(cash_sale)
    cash_sale.post(session) # This posts the Transaction to the Ledger
  3. How double-entry accounting is enforced in Transactions

    main

    The library enforces the double-entry principle by automatically posting amounts in a Transaction's LineItems to the opposite side of the ledger from the main Account.

    Each transaction type has a fixed setting determining which side the main account is posted to. While you can technically create Transaction objects directly, it is strongly discouraged because it bypasses the validation rules built into derived transaction classes (like JournalEntry), which can lead to incorrect bookkeeping.

    To ensure correct behavior, always use the specialized transaction classes provided by the library.

  4. Use Compound Journal Entries for complex transactions

    main

    A Compound Journal Entry is used when you need to post line item amounts to different sides of the ledger (some debits, some credits) within a single transaction.

    To create a compound entry, you must:

    1. Set compound=True on the JournalEntry.
    2. Specify main_account_amount (the amount to be posted to the main account).
    3. Use the credited property on individual LineItem objects to specify if that specific line item should be credited or debited (defaults to debit).

    Critical Requirement: The total of all amounts posted to the debit side must exactly equal the total of all amounts posted to the credit side, otherwise the transaction will throw an error.

    from datetime import datetime 
    from python_accounting.models import Transaction, LineItem
    from python_accounting.transactions import JournalEntry
    
    # Setup a compound entry
    journal_entry = JournalEntry(
        narration="Compound Transaction",
        transaction_date=datetime.now(),
        account_id=bank_account.id,
        entity_id=entity.id,
        compound=True, 
        credited=False, 
        main_account_amount=25, # Amount for the main account
    )
    
    # Line item that will be credited
    client_item = LineItem(
        narration="Client item",
        account_id=client_account.id,
        credited=True, # Explicitly credit this account
        amount=30,
        entity_id=entity.id,
    )
    
    # Line item that will be debited (default)
    supplier_item = LineItem(
        narration="Supplier item",
        account_id=supplier_account.id,
        amount=15,
        entity_id=entity.id,
    )
    
    # ... add to session and journal_entry.post(session)
  5. Generate Financial Reports

    main

    The library provides three main financial reports via the python_accounting.reports module:

    • IncomeStatement: Shows revenues, expenses, and net profit/loss.
    • BalanceSheet: Shows assets, liabilities, and equity.
    • CashflowStatement: Shows operating, investing, and financing cash flows.
    from python_accounting.reports import IncomeStatement, BalanceSheet, CashflowStatement
    
    # Income Statement
    income_statement = IncomeStatement(session)
    print(income_statement)
    
    # Balance Sheet
    balance_sheet = BalanceSheet(session)
    print(balance_sheet)
    
    # Cashflow Statement
    cashflow_statement = CashflowStatement(session)
    print(cashflow_statement)
  6. Define Taxes

    main

    Taxes are defined using the Tax model, which links a tax name and code to a specific account_id (usually a CONTROL type account) and an entity_id.

    from python_accounting.models import Tax
    
    output_tax = Tax(
        name="Output Vat",
        code="OTPT",
        account_id=tax_account.id,
        rate=20,
        entity_id=entity.id,
    )
    input_tax = Tax(
        name="Input Vat",
        code="INPT",
        account_id=tax_account.id,
        rate=10,
        entity_id=entity.id,
    )
    session.add_all([output_tax, input_tax])
    session.commit()
  7. Set up an Entity and Currency

    main

    An Entity represents the company or organization being tracked. When you commit an Entity to the session, it automatically sets up a Reporting Period. A Currency must be associated with an Entity to define the reporting currency for that entity.

    from python_accounting.database.session import get_session
    from python_accounting.models import Entity, Currency
    
    with get_session(engine) as session:
        entity = Entity(name="Example Company")
        session.add(entity)
        session.commit() # This automatically sets up a Reporting Period for the Entity
    
        currency = Currency(name="US Dollars", code="USD", entity_id=entity.id)
        session.add(currency)
        session.commit()
  8. Create Tax Inclusive Transactions

    main

    A Tax Inclusive transaction is one where the tax amount is already included in the amount specified in the LineItem.

    To implement this, follow the same procedure as a Tax Exclusive transaction, but you must explicitly set the tax_inclusive parameter to True on the LineItem object. The library will then back-calculate the base amount and the tax portion from the provided amount.

    from datetime import datetime 
    from python_accounting.models import Transaction, LineItem
    from python_accounting.transactions import CashSale
    
    # Assuming bank_account, revenue_account, output_tax, and entity are already initialized
    with get_session(engine) as session:
        cash_sale = CashSale(
            narration="Cash Sale Transaction",
            transaction_date=datetime.now(),
            account_id=bank_account.id,
            entity_id=entity.id,
        )
        session.add(cash_sale)
        session.flush() 
    
        cash_sale_line_item = LineItem(
            narration="Cash Sale line item",
            account_id=revenue_account.id,
            amount=100,          # Total amount including tax
            tax_id=output_tax.id,
            tax_inclusive=True,  # <--- Enable Tax Inclusive mode
            entity_id=entity.id,
        )
        session.add(cash_sale_line_item)
        session.flush()
    
        cash_sale.line_items.add(cash_sale_line_item)
        session.add(cash_sale)
        cash_sale.post(session)
    
        # Results for a 10% tax rate:
        # bank_account balance increases by 100
        # revenue_account balance decreases by ~90.91 (100 / 1.1)
        # tax_account balance decreases by ~9.09
  9. Initialize the Python Accounting database

    main

    To use the library, you must first configure the database connection using the config object and initialize the database schema using SQLAlchemy's Base.metadata.create_all. This step creates the necessary tables for your accounting data.

    from python_accounting.config import config
    from python_accounting.models import Base
    from sqlalchemy import create_engine
    
    database = config.database
    engine = create_engine(database["url"])
    
    Base.metadata.create_all(engine) # run migrations to create tables
  10. Configure the Chart of Accounts

    main

    The Chart of Accounts is built by creating Account objects with specific account_type values. Common account types include:

    • Account.AccountType.CONTROL (e.g., for Taxes)
    • Account.AccountType.BANK (e.g., for Bank Accounts)
    • Account.AccountType.OPERATING_REVENUE (e.g., for Revenue)
    • Account.AccountType.RECEIVABLE (e.g., for Clients)
    • Account.AccountType.PAYABLE (e.g., for Suppliers)
    • Account.AccountType.OPERATING_EXPENSE (e.g., for Opex)
    • Account.AccountType.DIRECT_EXPENSE (e.g., for Cost of Goods Sold)
    • Account.AccountType.NON_CURRENT_ASSET (e.g., for Fixed Assets)
    from python_accounting.models import Account
    
    tax_account = Account(
        name="Tax Account",
        account_type=Account.AccountType.CONTROL,
        currency_id=currency.id,
        entity_id=entity.id,
    )
    bank_account = Account(
        name="Bank Account",
        account_type=Account.AccountType.BANK,
        currency_id=currency.id,
        entity_id=entity.id,
    )
    # ... add other accounts ...
    
    session.add_all([
    tax_account, 
    bank_account, 
    # ...
    ])
    session.commit()
  11. Assigning Receipts to Invoices

    main

    To clear an outstanding invoice (e.g., a ClientInvoice) using a payment (e.g., a ClientReceipt), use the Assignment model. This allows for partial clearing of balances.

    • transaction.cleared(session): Returns the amount of the transaction that has been cleared.
    • transaction.balance(session): Returns the remaining unpaid/unassigned amount.
    from python_accounting.models import Assignment
    from datetime import datetime
    
    # Create an assignment to partially clear an invoice
    assignment = Assignment(
        assignment_date=datetime.now(),
        transaction_id=client_receipt.id,
        assigned_id=client_invoice.id,
        assigned_type=client_invoice.__class__.__name__,
        entity_id=entity.id,
        amount=15,
    )
    session.add(assignment)
    session.commit()
    
    print(client_invoice.cleared(session)) # Returns 15
    print(client_receipt.balance(session)) # Returns remaining balance