This commit is contained in:
Herculino Trotta
2024-10-09 00:31:21 -03:00
parent e78e4cc5e1
commit 3dde44b1cd
139 changed files with 4965 additions and 1004 deletions
+3
View File
@@ -0,0 +1,3 @@
from .accounts import *
from .account_groups import *
from .balance import *
+94
View File
@@ -0,0 +1,94 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from apps.accounts.forms import AccountGroupForm
from apps.accounts.models import AccountGroup
from apps.common.decorators.htmx import only_htmx
@login_required
@require_http_methods(["GET"])
def account_groups_list(request):
account_groups = AccountGroup.objects.all().order_by("id")
return render(
request, "account_groups/pages/list.html", {"account_groups": account_groups}
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def account_group_add(request, **kwargs):
if request.method == "POST":
form = AccountGroupForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Account Group added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("account_groups_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = AccountGroupForm()
return render(
request,
"account_groups/fragments/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def account_group_edit(request, pk):
account_group = get_object_or_404(AccountGroup, id=pk)
if request.method == "POST":
form = AccountGroupForm(request.POST, instance=account_group)
if form.is_valid():
form.save()
messages.success(request, _("Account Group updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("account_groups_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = AccountGroupForm(instance=account_group)
return render(
request,
"account_groups/fragments/edit.html",
{"form": form, "account_group": account_group},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def account_group_delete(request, pk):
account_group = get_object_or_404(AccountGroup, id=pk)
account_group.delete()
messages.success(request, _("Account Group deleted successfully"))
return HttpResponse(
status=204,
headers={"HX-Location": reverse("account_groups_list")},
)
+92
View File
@@ -0,0 +1,92 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from apps.accounts.forms import AccountForm
from apps.accounts.models import Account
from apps.common.decorators.htmx import only_htmx
@login_required
@require_http_methods(["GET"])
def accounts_list(request):
accounts = Account.objects.all().order_by("id")
return render(request, "accounts/pages/list.html", {"accounts": accounts})
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def account_add(request, **kwargs):
if request.method == "POST":
form = AccountForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Account added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("accounts_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = AccountForm()
return render(
request,
"accounts/fragments/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def account_edit(request, pk):
account = get_object_or_404(Account, id=pk)
if request.method == "POST":
form = AccountForm(request.POST, instance=account)
if form.is_valid():
form.save()
messages.success(request, _("Account updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Location": reverse("accounts_list"),
"HX-Trigger": "hide_offcanvas, toasts",
},
)
else:
form = AccountForm(instance=account)
return render(
request,
"accounts/fragments/edit.html",
{"form": form, "account": account},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def account_delete(request, pk):
account = get_object_or_404(Account, id=pk)
account.delete()
messages.success(request, _("Account deleted successfully"))
return HttpResponse(
status=204,
headers={"HX-Location": reverse("accounts_list")},
)
+93
View File
@@ -0,0 +1,93 @@
from decimal import Decimal
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import models
from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import render
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from apps.accounts.forms import AccountBalanceFormSet
from apps.accounts.models import Account, Transaction
from apps.common.decorators.htmx import only_htmx
@only_htmx
@login_required
def account_reconciliation(request):
def get_account_balance(account):
income = Transaction.objects.filter(
account=account, type=Transaction.Type.INCOME, is_paid=True
).aggregate(total=models.Sum("amount"))["total"] or Decimal("0")
expense = Transaction.objects.filter(
account=account, type=Transaction.Type.EXPENSE, is_paid=True
).aggregate(total=models.Sum("amount"))["total"] or Decimal("0")
return income - expense
initial_data = [
{
"account_id": account.id,
"account_name": account.name,
"decimal_places": account.currency.decimal_places,
"suffix": account.currency.suffix,
"prefix": account.currency.prefix,
"current_balance": get_account_balance(account),
}
for account in Account.objects.all().select_related("currency")
]
if request.method == "POST":
formset = AccountBalanceFormSet(request.POST, initial=initial_data)
if formset.is_valid():
with transaction.atomic():
for form in formset:
if form.is_valid():
account_id = form.cleaned_data["account_id"]
new_balance = form.cleaned_data["new_balance"]
account = Account.objects.get(id=account_id)
category = form.cleaned_data["category"]
tags = form.cleaned_data.get("tags", [])
if new_balance is None:
continue
current_balance = get_account_balance(account)
difference = new_balance - current_balance
if difference != 0:
new_transaction = Transaction.objects.create(
account=account,
type=(
Transaction.Type.INCOME
if difference > 0
else Transaction.Type.EXPENSE
),
amount=abs(difference),
date=timezone.localdate(timezone.now()),
reference_date=timezone.localdate(
timezone.now()
).replace(day=1),
description=_("Balance reconciliation"),
is_paid=True,
category=category,
)
new_transaction.tags.set(tags)
messages.success(
request, _("Account balances have been reconciled successfully.")
)
return HttpResponse(
status=204,
headers={"HX-Trigger": "transaction_updated, hide_offcanvas, toast"},
)
else:
formset = AccountBalanceFormSet(initial=initial_data)
return render(
request, "accounts/fragments/account_reconciliation.html", {"form": formset}
)