Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
17 changes: 7 additions & 10 deletions accounting/api/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@
from typing import List
from django.db.models import Sum, Avg
from rest_framework import status

from restauth.authorization import AuthBearer

account_router = Router(tags=['account'])


@account_router.get("/get_all", response=List[AccountOut])
def get_all(request):
return status.HTTP_200_OK, Account.objects.order_by('full_code')


@account_router.get('/get_one/{account_id}/', response={
200: AccountOut,
404: FourOFourOut,
Expand All @@ -27,18 +21,23 @@ def get_one(request, account_id: int):
return account
except Account.DoesNotExist:
return 404, {'detail': f'Account with id {account_id} does not exist'}
return status.HTTP_404_NOT_FOUND, {'detail': f'Account with id {account_id} does not exist'}


@account_router.get('/get_account_types/')
def get_account_types(request):
return {t[0]: t[1] for t in AccountTypeChoices.choices}


@account_router.get('/account-balance/{account_id}', response=GeneralLedgerOut)
def get_account_balance(request, account_id: int):
global balance, balanceUSD
account = get_object_or_404(Account, id=account_id)

balance = account.balance()
if account.parent != None:
balance = account.balance()
else:
balance=account.parent_balances

journal_entries = account.journal_entries.all()

Expand All @@ -53,7 +52,6 @@ def get_account_balances(request):
result.append({
'account': a.name, 'balance': list(a.balance())
})

return status.HTTP_200_OK, result


Expand Down Expand Up @@ -83,5 +81,4 @@ def __add__(self, other):
}, {
'currency': 'IQD',
'sum': self.balanceIQD
}]

}]
98 changes: 75 additions & 23 deletions accounting/models.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,95 @@
from typing import List, Any

from django.db import models
from django.db.models import Sum
from django.dispatch import receiver
from django.db.models.signals import post_save
from accounting.exceptions import AccountingEquationError

'''

Account
- parent
- type
- name
- code
- full_code

Transaction
- type
- description

JournalEntry
- account
- transaction
- amount
- currency

* Accounts should support multiple currencies
* Each Transaction should consist of two or more even numbered Journal Entries

'''


class AccountTypeChoices(models.TextChoices):
ASSETS = 'ASSETS', 'Assets'
LIABILITIES = 'LIABILITIES', 'Liabilities'
INCOME = 'INCOME', 'Income'
EXPENSES = 'EXPENSES', 'Expenses'


class TransactionTypeChoices(models.TextChoices):
invoice = 'invoice', 'Invoice'
income = 'income', 'Income'
expense = 'expense', 'Expense'
bill = 'bill', 'Bill'


class CurrencyChoices(models.TextChoices):
USD = 'USD', 'USD'
IQD = 'IQD', 'IQD'


class Balance:
def __init__(self, balances):
balanceIQD = 0
balanceUSD = 0
for i in balances:
if i['currency'] == 'USD':
balanceUSD = i['sum']
if i['currency'] == 'IQD':
balanceIQD = i['sum']

self.balanceUSD = balanceUSD
self.balanceIQD = balanceIQD

def __add__(self, other):
self.balanceIQD += other.balanceIQD
self.balanceUSD += other.balanceUSD
return [{
'currency': 'IQD',
'sum': self.balanceIQD
}, {
'currency': 'USD',
'sum': self.balanceUSD
}]

def __gt__(self, other):
balIQD = self.balanceIQD > other.balanceIQD
balUSD = self.balanceUSD > other.balanceUSD
return balIQD, balUSD

def __lt__(self, other):
balIQD = self.balanceIQD < other.balanceIQD
balUSD = self.balanceUSD < other.balanceUSD
return balIQD, balUSD

def is_zero(self):
balIQD = True if self.balanceIQD == 0 else False
balUSD = True if self.balanceUSD == 0 else False
return balIQD, balUSD


class Account(models.Model):
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL)
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, related_name='children')
type = models.CharField(max_length=255, choices=AccountTypeChoices.choices)
name = models.CharField(max_length=255)
code = models.CharField(max_length=20, null=True, blank=True)
full_code = models.CharField(max_length=25, null=True, blank=True)
extra = models.JSONField(default=dict, null=True, blank=True)

def __str__(self):
return f'{self.full_code} - {self.name}'

def balance(self):
return self.journal_entries.values('currency').annotate(sum=Sum('amount')).order_by()

Expand All @@ -78,6 +109,35 @@ def balance(self):
#
# if creating:
# self.refresh_from_db()
@property
def parent_balances(self):
global total_balance
parent_children = self.children.all()
for account in parent_children:
balance_acc = Balance(account.balance())
children = self.children.all()
for child_obj in children:
balance_acc_obj = Balance(child_obj.balance())
total_balance = balance_acc_obj.__add__(balance_acc)
return total_balance


# def save(
# self, force_insert=False, force_update=False, using=None, update_fields=None
# ):
# creating = not bool(self.id)
#
# if creating:
# self.code = self.id
# try:
# self.full_code = f'{self.parent.full_code}{self.id}'
# except AttributeError:
# self.full_code = self.id
#
# super(Account, self).save()
#
# if creating:
# self.refresh_from_db()


# @receiver(post_save, sender=Account)
Expand All @@ -87,27 +147,19 @@ def balance(self):
# instance.full_code = f'{instance.parent.full_code}{instance.id}'
# else:
# instance.full_code = f'{instance.id}'


class Transaction(models.Model):
type = models.CharField(max_length=255, choices=TransactionTypeChoices.choices)
description = models.CharField(max_length=255)

def validate_accounting_equation(self):
transaction_sum = self.journal_entries.aggregate(Sum('amount'))['sum']

if transaction_sum != 0:
raise AccountingEquationError


class JournalEntry(models.Model):
class Meta:
verbose_name_plural = 'Journal Entries'

account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='journal_entries')
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE, related_name='journal_entries')
amount = models.DecimalField(max_digits=19, decimal_places=2)
currency = models.CharField(max_length=3, choices=CurrencyChoices.choices)

def __str__(self):
return f'{self.amount} - {self.currency}'
return f'{self.amount} - {self.currency}'
22 changes: 2 additions & 20 deletions config/urls.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from ninja import NinjaAPI

from accounting.api import account_router, je_router, transaction_router
from restauth.api import auth_router

api = NinjaAPI(
title='Accounting for All',
version='0.2',
description='This is a model preview of a double entry accouting system.',
csrf=True,
csrf=True,
)
api.add_router('account/', account_router)
api.add_router('je/', je_router)
api.add_router('transaction/', transaction_router)
api.add_router('auth/', auth_router)

urlpatterns = [
path('admin/', admin.site.urls),
path("api/", api.urls),
]
]
Loading