import os
from datetime import date, datetime
from decimal import Decimal
from random import randint, choices, random
from zoneinfo import ZoneInfo

import django
# for easier visualization it is recommended to use pandas to render data...
# if pandas is not installed, you may install it with this command: pip install -U pandas
# pandas is not a dependecy of django_ledger...
import pandas as pd
from django.core.exceptions import ObjectDoesNotExist

# Set your django settings module if needed...
os.environ['DJANGO_SETTINGS_MODULE'] = 'dev_env.settings'

# if using jupyter notebook need to set DJANGO_ALLOW_ASYNC_UNSAFE as "true"
os.environ['DJANGO_ALLOW_ASYNC_UNSAFE'] = 'true'

# change your working directory as needed...
os.chdir('../')

django.setup()

from django_ledger.models.entity import EntityModel
from django_ledger.models.items import ItemModel
from django_ledger.models.invoice import InvoiceModel
from django_ledger.models.bill import BillModel
from django_ledger.models.estimate import EstimateModel
from django.contrib.auth import get_user_model
from django_ledger.io import roles, DEBIT, CREDIT
from django_ledger.io.io_library import IOBluePrint, IOLibrary

Get Your Entity Administrator UserModel

# change this to your preferred django username...
MY_USERNAME = 'ceo_user'
MY_PASSWORD = 'NeverUseMe|VeryInsecure!'
UserModel = get_user_model()

try:
    user_model = UserModel.objects.get(username__exact=MY_USERNAME)
except:
    user_model = UserModel(username=MY_USERNAME)
    user_model.set_password(MY_PASSWORD)
    user_model.save()

Get or Create an Entity Model

ENTITY_NAME = 'One Big Company, LLC'

entity_model = EntityModel.create_entity(
    name=ENTITY_NAME,
    admin=user_model,
    use_accrual_method=True,
    fy_start_month=1
)

entity_model

Chart of Accounts

Create a Default Chart of Accounts

  • Newly created EntityModel do not have a default Code of Accounts yet.

entity_model.has_default_coa()
default_coa_model = entity_model.create_chart_of_accounts(
    assign_as_default=True,
    commit=True,
    coa_name='My QuickStart CoA'
)
default_coa_model
entity_model.default_coa == default_coa_model

Populate Entity with Random Data (Optional)

Define a Start Date for Transactions

START_DTTM = datetime(year=2022, month=10, day=1, tzinfo=ZoneInfo('UTC'))
  • This action will populate the EntityModel with random data.

  • It will populate a Code of Accounts using a default pre-defined list.

  • This approach is for illustration, educational and testing purposes, not encouraged for new production entities.

entity_model.populate_random_data(start_date=START_DTTM)

EntityModel has now a Default Chart of Accounts

entity_model.has_default_coa()
default_coa_model = entity_model.get_default_coa()
default_coa_model

Chart of Accounts (CoA)

  • A Chart of Accounts is a user-defined list of accounts.

  • Each Entity Model must have at least one default Chart of Accounts.

Django Ledger support multiple chart of accounts.

another_coa_model = entity_model.create_chart_of_accounts(
    assign_as_default=False,
    commit=True,
    coa_name='My Empty Chart of Accounts'
)
another_coa_model

Accounts

Default CoA Accounts

default_coa_accounts_qs = entity_model.get_default_coa_accounts()
pd.DataFrame(default_coa_accounts_qs)

Get CoA Accounts by CoA Model

coa_accounts_by_coa_model_qs = entity_model.get_coa_accounts(coa_model=default_coa_model)
pd.DataFrame(coa_accounts_by_coa_model_qs)

No Accounts yet on this CoA…

coa_accounts_by_coa_model_qs = entity_model.get_coa_accounts(coa_model=another_coa_model)
pd.DataFrame(coa_accounts_by_coa_model_qs)

Get CoA Accounts by CoA Model UUID

  • May pass UUID instance instead of ChartOF AccountsModel…

coa_accounts_by_coa_uuid_qs = entity_model.get_coa_accounts(coa_model=default_coa_model.uuid)
pd.DataFrame(coa_accounts_by_coa_uuid_qs)

Get CoA Accounts by CoA Model Slug

  • If string is passed, will lookup by slug…

coa_accounts_by_coa_slug_qs = entity_model.get_coa_accounts(coa_model=default_coa_model.slug)
pd.DataFrame(coa_accounts_by_coa_slug_qs)

Get Accounts With Codes and CoA Model

  • Assumes default CoA if no coa_model is passed…

coa_accounts_by_codes_qs = entity_model.get_accounts_with_codes(code_list=['1010', '1050'])
pd.DataFrame(coa_accounts_by_codes_qs)

Empty ChartOfAccountModel…

coa_accounts_by_codes_qs = entity_model.get_accounts_with_codes(
    code_list=['1010', '1050'], 
    coa_model=another_coa_model
)
pd.DataFrame(coa_accounts_by_codes_qs)

Get All Accounts at Once

coa_qs, coa_map = entity_model.get_all_coa_accounts()

A dictionary, CoA Model -> Account List.

coa_map
pd.DataFrame(coa_map[default_coa_model])
pd.DataFrame(coa_map[another_coa_model])

Create Account Model

  • Creating AccountModel into empty “another_coa_model”…

account_model = entity_model.create_account(
    coa_model=another_coa_model,
    code=f'1{str(randint(10000, 99999))}',
    role=roles.ASSET_CA_INVENTORY,
    name='A cool account created from the EntityModel API!',
    balance_type=roles.DEBIT,
    active=True)
account_model
another_coa_accounts_qs = entity_model.get_coa_accounts(coa_model=another_coa_model)
pd.DataFrame(another_coa_accounts_qs)

Basic Django Ledger Usage

  • The LedgerModel name is whatever your heart desires.

  • Examples:

    • A month.

    • A customer.

    • A vendor.

    • A project.

  • The more ledgers are created, the more segregation and control over transactions is possible.

ledger_model = entity_model.create_ledger(name='My October 2023 Ledger', posted=True)

Create a Library

library = IOLibrary(name='quickstart-library')

Create and Register a BluePrint

@library.register
def sale_blueprint(
        sale_amount,
        contribution_margin_percent: float,
        description: str = None
) -> IOBluePrint:
    blueprint = IOBluePrint()
    cogs_amount = (1 - contribution_margin_percent) * sale_amount
    blueprint.debit(account_code='1010', amount=sale_amount, description=description)
    blueprint.credit(account_code='4010', amount=sale_amount, description=description)
    blueprint.credit(account_code='1200', amount=cogs_amount, description=description)
    blueprint.debit(account_code='5010', amount=cogs_amount, description=description)
    return blueprint

Get a Cursor

cursor = library.get_cursor(entity_model=entity_model, user_model=user_model)

Dispatch Some Instructions

cursor.dispatch('sale_blueprint',
                ledger_model='ledger-test-1',
                sale_amount=120.345,
                contribution_margin_percent=0.25,
                description='so cool')
cursor.dispatch('sale_blueprint',
                ledger_model='ledger-test-1',
                sale_amount=12.345,
                contribution_margin_percent=0.2,
                description='so cool')
cursor.dispatch('sale_blueprint',
                ledger_model=ledger_model,
                sale_amount=34.455,
                contribution_margin_percent=0.13,
                description='so cool')
cursor.dispatch('sale_blueprint',
                ledger_model='ledger-test-12',
                sale_amount=90.43,
                contribution_margin_percent=0.17,
                description='so cool')

Commit Your Instructions

Not recommended to post both ledger and journal entries. Posted transactions will immediately hit the books. result contains resulting ledger models, journal entries and transactions fro the committed

result = cursor.commit(
    post_new_ledgers=True,
    post_journal_entries=True,
    je_timestamp='2023-12-02 12:00'
)
# result

Get Financial Statement Report Data fro Ledger Model

Balance Sheet

bs_data = ledger_model.digest_balance_sheet(
    to_date=date(2023, 12, 31),
    entity_slug=entity_model
)

bs_data.get_balance_sheet_data()

Income Statement

is_data = ledger_model.digest_income_statement(
    from_date=date(2023, 1, 1),
    to_date=date(2023, 12, 31),
    entity_slug=entity_model
)
is_data.get_income_statement_data()

Cash Flow Statement

cfs_data = ledger_model.digest_cash_flow_statement(
    from_date=date(2023, 1, 1),
    to_date=date(2023, 12, 31),
    entity_slug=entity_model
)

cfs_data.get_cash_flow_statement_data()

All Statements in a Single Call

fin_digest = ledger_model.digest_financial_statements(
    from_date=date(2023, 1, 1),
    to_date=date(2023, 12, 31),
    entity_slug=entity_model
)

statement_data = fin_digest.get_financial_statements_data()
statement_data['balance_sheet']
statement_data['income_statement']
statement_data['cash_flow_statement']

Customers

Get Customers

customer_qs = entity_model.get_customers()
pd.DataFrame(customer_qs.values())

Create Customers

customer_model = entity_model.create_customer(customer_model_kwargs={
    'customer_name': 'Mr. Big',
    'description': 'A great paying customer!',
})

Vendors

Get Vendors

vendor_qs = entity_model.get_vendors()
pd.DataFrame(vendor_qs.values())

Create Vendor

vendor_model = entity_model.create_vendor(vendor_model_kwargs={
    'vendor_name': 'ACME LLC',
    'description': 'A Reliable Vendor!'
})

Invoices

Get Invoices

invoices_qs = entity_model.get_invoices()
pd.DataFrame(invoices_qs.values())

Create Invoice

invoice_model = entity_model.create_invoice(
    customer_model='C-0000000006',
    terms=InvoiceModel.TERMS_NET_30
)
invoice_model

Add Items to Invoices

invoices_item_models = invoice_model.get_item_model_qs()

# K= number of items...
K = 6

invoice_itemtxs = {
    im.item_number: {
        'unit_cost': round(random() * 10, 2),
        'quantity': round(random() * 100, 2),
        'total_amount': None
    } for im in choices(invoices_item_models, k=K)
}

# Choose operation ITEMIZE_APPEND to append itemtxs...
invoice_itemtxs = invoice_model.migrate_itemtxs(itemtxs=invoice_itemtxs,
                                                commit=True,
                                                operation=InvoiceModel.ITEMIZE_REPLACE)
invoice_itemtxs
invoice_model.amount_due

Bills

Get Bills

bills_qs = entity_model.get_bills()
pd.DataFrame(bills_qs.values())

Create Bill

bill_model = entity_model.create_bill(
    vendor_model='V-0000000002',
    terms=BillModel.TERMS_NET_60
)
bill_model

Add Items to Bills

bill_item_models = bill_model.get_item_model_qs()

K = 6

bill_itemtxs = {
    im.item_number: {
        'unit_cost': round(random() * 10, 2),
        'quantity': round(random() * 100, 2),
        'total_amount': None
    } for im in choices(bill_item_models, k=K)
}

# Choose operation ITEMIZE_APPEND to append itemtxs...
bill_itemtxs = bill_model.migrate_itemtxs(itemtxs=bill_itemtxs,
                                          commit=True,
                                          operation=BillModel.ITEMIZE_REPLACE)

bill_itemtxs
bill_model.amount_due

Purchase Orders

Get Purchase Orders

purchase_orders_qs = entity_model.get_purchase_orders()
pd.DataFrame(purchase_orders_qs.values())

Create Purchase Order

po_model = entity_model.create_purchase_order()

Add Items to Purchase Orders

po_item_models = po_model.get_item_model_qs()

K = 6

po_itemtxs = {
    im.item_number: {
        'unit_cost': round(random() * 10, 2),
        'quantity': round(random() * 100, 2),
        'total_amount': None
    } for im in choices(po_item_models, k=K)
}

# Choose operation ITEMIZE_APPEND to append itemtxs...
po_itemtxs = po_model.migrate_itemtxs(itemtxs=po_itemtxs,
                                      commit=True,
                                      operation=EstimateModel.ITEMIZE_REPLACE)

po_itemtxs
po_model.po_amount

Estimates/Contracts

Get Estimates/Contracts

estimates_qs = entity_model.get_estimates()
pd.DataFrame(estimates_qs.values())

Create Estimate

estimate_model = entity_model.create_estimate(
    estimate_title='A quote for new potential customer!',
    customer_model='C-0000000009',
    contract_terms=EstimateModel.CONTRACT_TERMS_FIXED
)

Add Items to Estimates

estimate_item_models = estimate_model.get_item_model_qs()

K = 6

estimate_itemtxs = {
    im.item_number: {
        'unit_cost': round(random() * 10, 2),
        'unit_revenue': round(random() * 20, 2),
        'quantity': round(random() * 100, 2),
        'total_amount': None
    } for im in choices(estimate_item_models, k=K)
}

# Choose operation ITEMIZE_APPEND to append itemtxs...
estimate_itemtxs = estimate_model.migrate_itemtxs(itemtxs=estimate_itemtxs,
                                                  commit=True,
                                                  operation=EstimateModel.ITEMIZE_REPLACE)

estimate_itemtxs
estimate_model.get_cost_estimate()
estimate_model.get_revenue_estimate()
estimate_model.get_profit_estimate()
estimate_model.get_gross_margin_estimate(as_percent=True)

Bank Accounts

Get Bank Accounts

bank_accounts_qs = entity_model.get_bank_accounts()
pd.DataFrame(bank_accounts_qs.values())

Create Bank Account

bank_account_model = entity_model.create_bank_account(name='A big bank account!',
                                                      account_type='checking')

Items

Unit of Measures

Get Unit of Measures

uom_qs = entity_model.get_uom_all()
pd.DataFrame(uom_qs.values())

Create a UOM

uom_model_ft = entity_model.create_uom(
    name='Linear Feet',
    unit_abbr='lin-ft'
)

Get Some UoMs

uom_model_unit = uom_qs.get(unit_abbr__exact='unit')
uom_model_man_hr = uom_qs.get(unit_abbr__exact='man-hour')

Expenses

Get Expense Items

expenses_qs = entity_model.get_items_expenses()
pd.DataFrame(expenses_qs.values())

Create Expense Item

expense_item_model = entity_model.create_item_expense(
    name='Premium Pencils',
    uom_model=uom_model_unit,
    expense_type=ItemModel.ITEM_TYPE_MATERIAL
)
expense_item_model.is_expense()

Services

Get Service Items

services_qs = entity_model.get_items_services()
pd.DataFrame(services_qs.values())

Create Service Item

service_model = entity_model.create_item_service(
    name='Yoga Class',
    uom_model=uom_model_man_hr
)
service_model.is_service()

Products

Get Product Items

products_qs = entity_model.get_items_products()
pd.DataFrame(products_qs.values())

Create Product Items

product_model = entity_model.create_item_product(
    name='1/2" Premium PVC Pipe',
    uom_model=uom_model_ft,
    item_type=ItemModel.ITEM_TYPE_MATERIAL
)
product_model.is_product()

Inventory

Get Inventory Items

inventory_qs = entity_model.get_items_inventory()
pd.DataFrame(inventory_qs.values())

Create Inventory Items

inventory_model = entity_model.create_item_inventory(
    name='A Home to Flip!',
    uom_model=uom_model_unit,
    item_type=ItemModel.ITEM_TYPE_LUMP_SUM
)
inventory_model.is_inventory()

Financial Statement PDF Reports

Set Up

  • Must enable PDF support by installing dependencies via pipenv.

    • pipenv install –categories pdf

Balance Sheet

bs_report = entity_model.get_balance_sheet_statement(
    to_date=date(2022, 12, 31),
    save_pdf=True,
    filepath='./'
)
# save_pdf=True saves the PDF report in the project's BASE_DIR.
# filename and filepath may also be specified...
# will raise not implemented error if PDF support is not enabled...

Balance Sheet Statement Raw Data

bs_report.get_report_data()

Income Statement

ic_report = entity_model.get_income_statement(
    from_date=date(2022, 1, 1),
    to_date=date(2022, 12, 31),
    save_pdf=True,
    filepath='./'
)
# save_pdf=True saves the PDF report in the project's BASE_DIR.
# filename and filepath may also be specified...
# will raise not implemented error if PDF support is not enabled...

Income Statement Raw Data

ic_report.get_report_data()

Cash Flow Statement

cf_report = entity_model.get_cash_flow_statement(
    from_date=date(2022, 1, 1),
    to_date=date(2022, 12, 31),
    save_pdf=True,
    filepath='./'
)
# save_pdf=True saves the PDF report in the project's BASE_DIR.
# filename and filepath may also be specified...

Cash Flow Statement Raw Data

cf_report.get_report_data()

All Financial Statements Data in a single Call

reports = entity_model.get_financial_statements(
    user_model=user_model,
    from_date=date(2022, 1, 1),
    to_date=date(2022, 12, 31),
    save_pdf=True,
    filepath='./'
)
# save_pdf=True saves the PDF report in the project's BASE_DIR.
# filename and filepath may also be specified...
reports.balance_sheet_statement.get_report_data()
reports.income_statement.get_report_data()
reports.cash_flow_statement.get_report_data()