add coa and update accounts

This commit is contained in:
ismail 2025-08-12 17:11:33 +03:00
parent 543d8702f3
commit 547f1072d0
11 changed files with 311 additions and 34 deletions

View File

@ -22,6 +22,11 @@ from django.shortcuts import get_object_or_404
from django.urls import reverse
from django_ledger.models import ItemTransactionModel,InvoiceModel,LedgerModel,EntityModel
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView
from django_ledger.forms.chart_of_accounts import (
ChartOfAccountsModelCreateForm,
ChartOfAccountsModelUpdateForm,
)
from django_ledger.forms.purchase_order import (
ApprovedPurchaseOrderModelUpdateForm,
BasePurchaseOrderModelUpdateForm,
@ -30,7 +35,7 @@ from django_ledger.forms.purchase_order import (
get_po_itemtxs_formset_class,
)
from django_ledger.views.purchase_order import PurchaseOrderModelModelViewQuerySetMixIn
from django_ledger.models import PurchaseOrderModel, EstimateModel, BillModel
from django_ledger.models import PurchaseOrderModel, EstimateModel, BillModel, ChartOfAccountModel
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView
from django.views.generic.base import RedirectView
@ -973,3 +978,112 @@ class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
# if not valid, return formset with errors...
return self.render_to_response(context=self.get_context_data(itemtxs_formset=itemtxs_formset))
return super(InvoiceModelUpdateView, self).post(request, **kwargs)
class ChartOfAccountModelModelBaseViewMixIn(LoginRequiredMixin, PermissionRequiredMixin):
queryset = None
permission_required = []
def get_queryset(self):
if self.queryset is None:
entity_model = self.request.dealer.entity
self.queryset = entity_model.chartofaccountmodel_set.all().order_by('-updated')
return super().get_queryset()
def get_redirect_url(self, *args, **kwargs):
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
'entity_slug': self.request.entity.slug})
class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListView):
template_name = 'chart_of_accounts/coa_list.html'
context_object_name = 'coa_list'
inactive = False
def get_queryset(self):
qs = super().get_queryset()
if self.inactive:
return qs.filter(active=False)
return qs.active()
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=None, **kwargs)
context['inactive'] = self.inactive
context['header_subtitle'] = self.request.entity.name
context['header_subtitle_icon'] = 'gravity-ui:hierarchy'
context['page_title'] = 'Inactive Chart of Account List' if self.inactive else 'Chart of Accounts List'
context['header_title'] = 'Inactive Chart of Account List' if self.inactive else 'Chart of Accounts List'
return context
class ChartOfAccountModelCreateView(ChartOfAccountModelModelBaseViewMixIn, CreateView):
template_name = 'chart_of_accounts/coa_create.html'
extra_context = {
'header_title': _('Create Chart of Accounts'),
'page_title': _('Create Chart of Account'),
}
def get_initial(self):
return {
'entity': self.request.entity,
}
def get_form(self, form_class=None):
return ChartOfAccountsModelCreateForm(
entity_model=self.request.entity,
**self.get_form_kwargs()
)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=None, **kwargs)
context['header_subtitle'] = f'New Chart of Accounts: {self.request.entity.name}'
context['header_subtitle_icon'] = 'gravity-ui:hierarchy'
return context
def get_success_url(self):
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
'entity_slug': self.request.entity.slug})
class ChartOfAccountModelUpdateView(ChartOfAccountModelModelBaseViewMixIn, UpdateView):
context_object_name = 'coa_model'
slug_url_kwarg = 'coa_slug'
template_name = 'chart_of_accounts/coa_update.html'
form_class = ChartOfAccountsModelUpdateForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
chart_of_accounts_model: ChartOfAccountModel = self.object
context['page_title'] = f'Update Chart of Account {chart_of_accounts_model.name}'
context['header_title'] = f'Update Chart of Account {chart_of_accounts_model.name}'
return context
def get_success_url(self):
return reverse('coa-list', kwargs={'dealer_slug': self.request.dealer.slug,
'entity_slug': self.request.entity.slug})
class CharOfAccountModelActionView(ChartOfAccountModelModelBaseViewMixIn,
RedirectView,
SingleObjectMixin):
http_method_names = ['get']
slug_url_kwarg = 'coa_slug'
action_name = None
commit = True
def get(self, request, *args, **kwargs):
kwargs['user_model'] = self.request.user
if not self.action_name:
raise ImproperlyConfigured('View attribute action_name is required.')
response = super(CharOfAccountModelActionView, self).get(request, *args, **kwargs)
coa_model: ChartOfAccountModel = self.get_object()
try:
getattr(coa_model, self.action_name)(commit=self.commit, **kwargs)
messages.add_message(request, level=messages.SUCCESS, extra_tags='is-success',
message=_('Successfully updated {} Default Chart of Account to '.format(
request.entity.name) +
'{}'.format(coa_model.name)))
except ValidationError as e:
messages.add_message(request,
message=e.message,
level=messages.ERROR,
extra_tags='is-danger')
return response

View File

@ -735,27 +735,27 @@ urlpatterns = [
name="bank_account_delete",
),
path(
"<slug:dealer_slug>/coa_accounts/",
"<slug:dealer_slug>/coa_accounts/<coa_pk>/",
views.AccountListView.as_view(),
name="account_list",
),
path(
"<slug:dealer_slug>/coa_accounts/<uuid:pk>/",
"<slug:dealer_slug>/coa_accounts/<coa_pk>/<uuid:pk>/",
views.AccountDetailView.as_view(),
name="account_detail",
),
path(
"<slug:dealer_slug>/coa_accounts/create/",
"<slug:dealer_slug>/coa_accounts/<coa_pk>/create/",
views.AccountCreateView.as_view(),
name="account_create",
),
path(
"<slug:dealer_slug>/coa_accounts/<uuid:pk>/update/",
"<slug:dealer_slug>/coa_accounts/<coa_pk>/<uuid:pk>/update/",
views.AccountUpdateView.as_view(),
name="account_update",
),
path(
"<slug:dealer_slug>/coa_accounts/<uuid:pk>/delete/",
"<slug:dealer_slug>/coa_accounts/<coa_pk>/<uuid:pk>/delete/",
views.account_delete,
name="account_delete",
),
@ -1090,6 +1090,29 @@ urlpatterns = [
path('<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/',
views.ChartOfAccountModelListView.as_view(),
name='coa-list'),
path('<slug:dealer_slug>/chart-of-accounts/<slug:entity_slug>/list/inactive/',
views.ChartOfAccountModelListView.as_view(inactive=True),
name='coa-list-inactive'),
path('<slug:dealer_slug>/<slug:entity_slug>/create/',
views.ChartOfAccountModelCreateView.as_view(),
name='coa-create'),
path('<slug:dealer_slug>/<slug:entity_slug>/detail/<slug:coa_slug>/',
views.ChartOfAccountModelListView.as_view(),
name='coa-detail'),
path('<slug:dealer_slug>/<slug:entity_slug>/update/<slug:coa_slug>/',
views.ChartOfAccountModelUpdateView.as_view(),
name='coa-update'),
# ACTIONS....
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-default/',
views.CharOfAccountModelActionView.as_view(action_name='mark_as_default'),
name='coa-action-mark-as-default'),
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-active/',
views.CharOfAccountModelActionView.as_view(action_name='mark_as_active'),
name='coa-action-mark-as-active'),
path('<slug:dealer_slug>/<slug:entity_slug>/action/<slug:coa_slug>/mark-as-inactive/',
views.CharOfAccountModelActionView.as_view(action_name='mark_as_inactive'),
name='coa-action-mark-as-inactive'),
# CASH FLOW STATEMENTS...
# Entities...
path(

View File

@ -152,6 +152,10 @@ from .override import (
BaseBillActionView as BaseBillActionViewBase,
InventoryListView as InventoryListViewBase,
InvoiceModelUpdateView as InvoiceModelUpdateViewBase,
ChartOfAccountModelCreateView as ChartOfAccountModelCreateViewBase,
ChartOfAccountModelListView as ChartOfAccountModelListViewBase,
ChartOfAccountModelUpdateView as ChartOfAccountModelUpdateViewBase,
CharOfAccountModelActionView as CharOfAccountModelActionViewBase,
)
from django_ledger.models import (
@ -168,6 +172,7 @@ from django_ledger.models import (
BillModel,
LedgerModel,
PurchaseOrderModel,
ChartOfAccountModel
)
from django_ledger.views.financial_statement import (
FiscalYearBalanceSheetView,
@ -4091,6 +4096,10 @@ class AccountListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
dealer = get_user_type(self.request)
accounts = dealer.entity.get_all_accounts()
return apply_search_filters(accounts, query)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["url_kwargs"] = self.kwargs
return context
class AccountCreateView(
@ -4156,10 +4165,17 @@ class AccountCreateView(
def get_success_url(self):
return reverse(
"account_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"]}
"account_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"], "coa_pk": self.kwargs["coa_pk"]}
)
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context["url_kwargs"] = self.kwargs
coa_pk = context["url_kwargs"]["coa_pk"]
try:
kwargs["coa_model"] = ChartOfAccountModel.objects.get(pk=coa_pk) or self.request.entity.get_default_coa()
except Exception:
kwargs["coa_model"] = self.request.entity.get_default_coa()
return context
class AccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
"""
Represents the detailed view for an account with additional context data related to account
@ -4217,6 +4233,7 @@ class AccountDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView)
"journal_entry__ledger__billmodel",
"journal_entry__ledger__invoicemodel",
)
context["url_kwargs"] = self.kwargs
return context
@ -4260,13 +4277,21 @@ class AccountUpdateView(
def get_success_url(self):
return reverse_lazy(
"account_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"]}
"account_list", kwargs={"dealer_slug": self.kwargs["dealer_slug"],"coa_pk":self.kwargs["coa_pk"]}
)
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context["url_kwargs"] = self.kwargs
coa_pk = context["url_kwargs"]["coa_pk"]
try:
kwargs["coa_model"] = ChartOfAccountModel.objects.get(pk=coa_pk) or self.request.entity.get_default_coa()
except Exception:
kwargs["coa_model"] = self.request.entity.get_default_coa()
return context
@login_required
@permission_required("django_ledger.delete_accountmodel")
def account_delete(request, dealer_slug, pk):
def account_delete(request, dealer_slug,coa_pk, pk):
"""
Handles the deletion of an account object identified by its primary key (pk). Ensures
that the user has the necessary permissions to perform the deletion. Successfully
@ -4285,7 +4310,7 @@ def account_delete(request, dealer_slug, pk):
account.delete()
messages.success(request, _("Account deleted successfully"))
return redirect("account_list", dealer_slug=dealer_slug)
return redirect("account_list", dealer_slug=dealer_slug, coa_pk=coa_pk)
# Sales list
@ -11140,4 +11165,16 @@ def ticket_update(request, ticket_id):
class ChartOfAccountModelListView(ChartOfAccountModelListViewBase):
template_name = 'chart_of_accounts/coa_list.html'
template_name = 'chart_of_accounts/coa_list.html'
permission_required = 'django_ledger.view_chartofaccountmodel'
class ChartOfAccountModelCreateView(ChartOfAccountModelCreateViewBase):
template_name = 'chart_of_accounts/coa_create.html'
permission_required = 'django_ledger.add_chartofaccountmodel'
class ChartOfAccountModelListView(ChartOfAccountModelListViewBase):
template_name = 'chart_of_accounts/coa_list.html'
permission_required = 'django_ledger.view_chartofaccountmodel'
class ChartOfAccountModelUpdateView(ChartOfAccountModelUpdateViewBase):
template_name = 'chart_of_accounts/coa_update.html'
permission_required = 'django_ledger.change_chartofaccountmodel'
class CharOfAccountModelActionView(CharOfAccountModelActionViewBase):
permission_required = 'django_ledger.change_chartofaccountmodel'

View File

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load django_ledger %}
{% load widget_tweaks %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-12 text-center mb-4">
<h1 class="display-4">{% trans 'Create Chart of Accounts' %}</h1>
</div>
<div class="col-lg-6 col-md-8">
<div class="card shadow-sm">
<div class="card-body p-4">
<form method="post" id="{{ form.get_form_id }}">
{% csrf_token %}
{# Bootstrap form rendering #}
<div class="mb-3">
{{ form.name.label_tag }}
{{ form.name|add_class:"form-control" }}
{% if form.name.help_text %}
<small class="form-text text-muted">{{ form.name.help_text }}</small>
{% endif %}
{% for error in form.name.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.description.label_tag }}
{{ form.description|add_class:"form-control" }}
{% if form.description.help_text %}
<small class="form-text text-muted">{{ form.description.help_text }}</small>
{% endif %}
{% for error in form.description.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="d-grid gap-2 mt-4">
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
<a href="{% url 'coa-list' request.dealer.slug request.entity.slug %}" class="btn btn-outline-dark">
{% trans 'Back' %}
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -10,17 +10,17 @@
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="display-4 mb-0">Chart of Accounts</h1>
<a href="{{ entity_model.get_coa_create_url }}" class="btn btn-success btn-lg">
<a href="{% url 'coa-create' request.dealer.slug request.entity.slug %}" class="btn btn-success btn-lg">
{% icon 'carbon:add-alt' 24 %} Add New
</a>
</div>
{% if not inactive %}
<a class="btn btn-warning mb-4" href="{{ entity_model.get_coa_list_inactive_url }}">
<a class="btn btn-warning mb-4" href="{% url 'coa-list-inactive' request.dealer.slug request.entity.slug %}">
{% trans 'Show Inactive' %}
</a>
{% else %}
<a class="btn btn-warning mb-4" href="{{ entity_model.get_coa_list_url }}">
<a class="btn btn-warning mb-4" href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
{% trans 'Show Active' %}
</a>
{% endif %}

View File

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load widget_tweaks %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-6 col-md-8">
<div class="card shadow-sm">
<div class="card-body">
<form action="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}" id="{{ form.form_id }}" method="post">
{% csrf_token %}
<div class="mb-3">
{{ form.name.label_tag }}
{{ form.name|add_class:"form-control" }}
{% if form.name.help_text %}
<small class="form-text text-muted">{{ form.name.help_text }}</small>
{% endif %}
{% for error in form.name.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.description.label_tag }}
{{ form.description|add_class:"form-control" }}
{% if form.description.help_text %}
<small class="form-text text-muted">{{ form.description.help_text }}</small>
{% endif %}
{% for error in form.description.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<div class="d-grid gap-2 mt-4">
<button class="btn btn-primary" type="submit">
Update
</button>
<a class="btn btn-outline-secondary"
href="{% url 'coa-list' request.dealer.slug request.entity.slug %}">
Back
</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -84,30 +84,30 @@
<div class="card-footer bg-transparent border-top-0">
<div class="d-flex flex-wrap gap-2">
<a href="{{ coa_model.get_update_url }}" class="btn btn-sm btn-outline-warning fw-bold">
<a href="{% url 'coa-update' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-outline-warning fw-bold">
<i class="fas fa-edit me-1"></i> {% trans 'Update' %}
</a>
<a href="{{ coa_model.get_account_list_url }}" class="btn btn-sm btn-outline-success fw-bold">
<a href="{% url 'account_list' request.dealer.slug coa_model.pk %}" class="btn btn-sm btn-outline-success fw-bold">
<i class="fas fa-book me-1"></i> {% trans 'Accounts' %}
</a>
<a href="{{ coa_model.get_create_coa_account_url }}" class="btn btn-sm btn-outline-info fw-bold">
<a href="{% url 'account_create' request.dealer.slug coa_model.pk %}" class="btn btn-sm btn-outline-info fw-bold">
<i class="fas fa-plus-circle me-1"></i> {% trans 'Add Account' %}
</a>
{% if coa_model.can_mark_as_default %}
<a href="{{ coa_model.mark_as_default_url }}" class="btn btn-sm btn-outline-danger fw-bold">
<a href="{% url 'coa-action-mark-as-default' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-outline-danger fw-bold">
<i class="fas fa-star me-1"></i> {% trans 'Mark as Default' %}
</a>
{% endif %}
{% if coa_model.can_deactivate %}
<a href="{{ coa_model.mark_as_inactive_url }}" class="btn btn-sm btn-outline-warning fw-bold">
<a href="{% url 'coa-action-mark-as-inactive' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-outline-warning fw-bold">
<i class="fas fa-toggle-off me-1"></i> {% trans 'Mark as Inactive' %}
</a>
{% elif coa_model.can_activate %}
<a href="{{ coa_model.mark_as_active_url }}" class="btn btn-sm btn-outline-success fw-bold">
<a href="{% url 'coa-action-mark-as-active' request.dealer.slug request.entity.slug coa_model.slug %}" class="btn btn-sm btn-outline-success fw-bold">
<i class="fas fa-toggle-on me-1"></i> {% trans 'Mark as Active' %}
</a>
{% endif %}

View File

@ -22,7 +22,7 @@
class="btn btn-sm btn-phoenix-secondary"
data-bs-dismiss="modal">{% trans 'No' %}</button>
<div class="btn btn-sm btn-phoenix-danger">
<form action="{% url 'account_delete' request.dealer.slug account.pk %}"
<form action="{% url 'account_delete' request.dealer.slug url_kwargs.coa_pk account.pk %}"
method="post">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-phoenix-danger">{% trans 'Yes' %}</button>
@ -135,7 +135,7 @@
<div class="mt-3 d-flex">
{% if perms.django_ledger.change_chartofaccountmodel %}
<a class="btn btn-sm btn-phoenix-primary me-1"
href="{% url 'account_update' request.dealer.slug account.pk %}">
href="{% url 'account_update' request.dealer.slug url_kwargs.coa_pk account.pk %}">
<!-- <i class="bi bi-pencil-square"></i> -->
<i class="fa-solid fa-pen-to-square"></i> {{ _("Edit") }}
</a>
@ -149,7 +149,7 @@
</a>
{% endif %}
<a class="btn btn-sm btn-phoenix-secondary"
href="{% url 'account_list' request.dealer.slug %}">
href="{% url 'account_list' request.dealer.slug url_kwargs.coa_pk %}">
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
<i class="fa-regular fa-circle-left"></i> {% trans 'Back to COA List' %}
</a>

View File

@ -24,7 +24,7 @@
{{ _("Add Account") }}
{% endif %}
<i class="fa-solid fa-book ms-2 text-primary"></i>
<i class="fa-solid fa-book ms-2 text-primary"></i>
</h3>
</div>
<div class="card-body bg-light-subtle">
@ -44,7 +44,7 @@
<i class="saveBtnIcon fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}
</button>
<a href="{% url 'account_list' request.dealer.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
<a href="{% url 'account_list' request.dealer.slug url_kwargs.coa_pk %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>

View File

@ -17,7 +17,7 @@
<div class="d-flex justify-content-between mb-2">
<h3 class=""> {% trans "Accounts" %}<i class="fa-solid fa-book ms-2 text-primary"></i></h3>
{% if perms.django_ledger.add_chartofaccountmodel %}
<a href="{% url 'account_create' request.dealer.slug %}"
<a href="{% url 'account_create' request.dealer.slug url_kwargs.coa_pk %}"
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Account' %}</a>
{% endif %}
</div>
@ -204,7 +204,7 @@
{% url "account_create" request.dealer.slug as create_account_url %}
{% include "empty-illustration-page.html" with value="account" url=create_account_url %}
{% endif %}
{% endblock %}
{% block customerJS %}
<script>

View File

@ -47,12 +47,12 @@
<span class="fas fa-ellipsis-h fs-10"></span>
</button>
<div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'account_detail' request.dealer.slug account.uuid %}"
<a href="{% url 'account_detail' request.dealer.slug url_kwargs.coa_pk account.uuid %}"
class="dropdown-item text-success-dark">{% trans "View Journal Entries" %}</a>
<div class="dropdown-divider"></div>
<button class="dropdown-item text-danger"
{% comment %} <div class="dropdown-divider"></div> {% endcomment %}
{% comment %} <button class="dropdown-item text-danger"
data-bs-toggle="modal"
data-bs-target="#deleteModal">{% trans "Delete" %}</button>
data-bs-target="#deleteModal">{% trans "Delete" %}</button> {% endcomment %}
</div>
</div>
</td>