diff --git a/inventory/forms.py b/inventory/forms.py index 8790a225..62427cf7 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -24,6 +24,7 @@ from .models import ( AdditionalServices ) +from django_ledger.models import ItemModel from django.forms import ModelMultipleChoiceField, ValidationError from django.utils.translation import gettext_lazy as _ import django_tables2 as tables @@ -403,3 +404,11 @@ class WizardForm3(forms.Form): if password and confirm_password and password != confirm_password: raise forms.ValidationError("Passwords do not match.") + + +class ItemForm(forms.Form): + item = forms.ModelChoiceField(queryset=ItemModel.objects.all(),label="Item", required=True) + quantity = forms.DecimalField(label="Quantity", required=True) + unit = forms.DecimalField(label="Unit", required=True) + unit_cost = forms.DecimalField(label="Unit Cost", required=True) + unit_sales_price = forms.DecimalField(label="Unit Sales Price", required=True) \ No newline at end of file diff --git a/inventory/urls.py b/inventory/urls.py index d2011de3..f16f5012 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -105,6 +105,24 @@ urlpatterns = [ path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'), path('representatives//update/', views.RepresentativeUpdateView.as_view(), name='representative_update'), path('representatives//delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'), + + #Ledger URLS + #Bank Account + path('bank_accounts/', views.BankAccountListView.as_view(), name='bank_account_list'), + path('bank_accounts//', views.BankAccountDetailView.as_view(), name='bank_account_detail'), + path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'), + path('bank_accounts//update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'), + path('bank_accounts//delete/', views.bank_account_delete, name='bank_account_delete'), + # Account + path('coa_accounts/', views.AccountListView.as_view(), name='account_list'), + path('coa_accounts//', views.AccountDetailView.as_view(), name='account_detail'), + path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'), + path('coa_accounts//update/', views.AccountUpdateView.as_view(), name='account_update'), + path('coa_accounts//delete/', views.account_delete, name='account_delete'), + # Estimate + path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'), + path('estimates//', views.EstimateDetailView.as_view(), name='estimate_detail'), + path('sales/estimates/create/', views.create_estimate, name='estimate_create'), ] diff --git a/inventory/views.py b/inventory/views.py index 3ebcd2ce..6b2aa4bf 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,9 +1,13 @@ -from django_ledger.models import EntityModel, InvoiceModel +from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel + +from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm +from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm +from django_ledger.forms.estimate import EstimateModelCreateForm + import logging import json import datetime from decimal import Decimal -from django_ledger.models import TransactionModel, AccountModel,JournalEntryModel from django.shortcuts import HttpResponse from django.template.loader import render_to_string # from weasyprint import HTML @@ -1492,3 +1496,222 @@ def payment_create(request, pk): else: form = forms.PaymentForm() return render(request, "sales/payments/payment_create.html", {"quotation": quotation,"form": form}) + + + + +#Ledger + + +#BANK ACCOUNT +class BankAccountListView(LoginRequiredMixin, ListView): + model = BankAccountModel + template_name = "ledger/bank_accounts/bank_account_list.html" + context_object_name = "bank_accounts" + + def get_queryset(self): + return BankAccountModel.objects.filter(entity_model=self.request.user.dealer.entity) + +class BankAccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = BankAccountModel + form_class = BankAccountCreateForm + template_name = "ledger/bank_accounts/bank_account_form.html" + success_url = reverse_lazy("bank_account_list") + success_message = "Bank account created successfully." + + def form_valid(self, form): + form.instance.entity_model = self.request.user.dealer.entity + return super().form_valid(form) + + def get_form_kwargs(self): + """ + Override this method to pass additional keyword arguments to the form. + """ + entity = self.request.user.dealer.entity + kwargs = super().get_form_kwargs() + kwargs['entity_slug'] = entity.slug # Get entity_slug from URL + kwargs['user_model'] = entity.admin # Get user_model from the request + return kwargs + +class BankAccountDetailView(LoginRequiredMixin, DetailView): + model = BankAccountModel + template_name = "ledger/bank_accounts/bank_account_detail.html" + context_object_name = "bank_account" + +class BankAccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = BankAccountModel + form_class = BankAccountUpdateForm + template_name = "ledger/bank_accounts/bank_account_form.html" + success_url = reverse_lazy("bank_account_list") + success_message = "Bank account updated successfully." + + def get_form_kwargs(self): + """ + Override this method to pass additional keyword arguments to the form. + """ + entity = self.request.user.dealer.entity + kwargs = super().get_form_kwargs() + kwargs['entity_slug'] = entity.slug # Get entity_slug from URL + kwargs['user_model'] = entity.admin # Get user_model from the request + return kwargs + +@login_required +def bank_account_delete(request, pk): + bank_account = get_object_or_404(BankAccountModel, pk=pk) + if request.method == "POST": + bank_account.delete() + messages.success(request, "Bank account deleted successfully.") + return redirect("bank_account_list") + return render(request, "ledger/bank_accounts/bank_account_delete.html", {"bank_account": bank_account}) + + + +# Accounts + +class AccountListView(LoginRequiredMixin, ListView): + model = AccountModel + template_name = "ledger/coa_accounts/account_list.html" + context_object_name = "accounts" + + def get_queryset(self): + entity = self.request.user.dealer.entity + qs = entity.get_all_accounts() + return qs + + +class AccountCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = AccountModel + form_class = AccountModelCreateForm + template_name = "ledger/coa_accounts/account_form.html" + success_url = reverse_lazy("account_list") + success_message = "Account created successfully." + + def form_valid(self, form): + form.instance.entity_model = self.request.user.dealer.entity + form.instance.depth = 0 + return super().form_valid(form) + + + def get_form_kwargs(self): + """ + Override this method to pass additional keyword arguments to the form. + """ + entity = self.request.user.dealer.entity + kwargs = super().get_form_kwargs() + + kwargs['coa_model'] = entity.get_default_coa() + return kwargs + +class AccountDetailView(LoginRequiredMixin, DetailView): + model = AccountModel + template_name = "ledger/coa_accounts/account_detail.html" + context_object_name = "account" + +class AccountUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): + model = AccountModel + form_class = AccountModelUpdateForm + template_name = "ledger/coa_accounts/account_form.html" + success_url = reverse_lazy("account_list") + success_message = "Account updated successfully." + +@login_required +def account_delete(request, pk): + account = get_object_or_404(AccountModel, pk=pk) + if request.method == "POST": + account.delete() + messages.success(request, "Account deleted successfully.") + return redirect("account_list") + return render(request, "ledger/coa_accounts/account_delete.html", {"account": account}) + +#Estimates + +class EstimateListView(LoginRequiredMixin, ListView): + model = EstimateModel + template_name = "sales/estimates/estimate_list.html" + context_object_name = "estimates" + +class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): + model = EstimateModel + form_class = EstimateModelCreateForm + template_name = "sales/estimates/estimate_form.html" + success_url = reverse_lazy("estimate_list") + success_message = "Estimate created successfully." + + def get_form_kwargs(self): + """ + Override this method to pass additional keyword arguments to the form. + """ + entity = self.request.user.dealer.entity + kwargs = super().get_form_kwargs() + kwargs['entity_slug'] = entity.slug + kwargs['user_model'] = entity.admin + return kwargs + + def get_context_data(self, **kwargs): + entity = self.request.user.dealer.entity + kwargs['items'] = entity.get_items_all() + return super().get_context_data(**kwargs) + def get_customer_queryset(self): + entity = self.request.user.dealer.entity + return entity.get_customer_queryset() + + def form_valid(self, form): + form.instance.entity = self.request.user.dealer.entity + return super().form_valid(form) + +@csrf_exempt +def create_estimate(request): + entity = request.user.dealer.entity + + if request.method == 'POST': + try: + data = json.loads(request.body) + title = data['title'] + customer_id = data['customer'] + terms = data['terms'] + customer = entity.get_customers().filter(pk=customer_id).first() + + estimate = entity.create_estimate( + estimate_title=title, + customer_model=customer, + contract_terms=terms) + + items = data.get('item[]', []) + items = [entity.get_items_all().filter(pk=item).first() for item in items] + quantities = data.get('quantity[]', []) + unit_costs = data.get('unitCost[]', []) + unit_sales_prices = data.get('unitSalesPrice[]', []) + total_costs = data.get('totalCost[]', []) + total_revenues = data.get('totalRevenue[]', []) + + estimate_itemtxs = { + item.item_number: { + 'unit_cost': float(unit_costs[i]), + 'unit_revenue': float(unit_sales_prices[i]), + 'quantity': float(quantities[i]), + 'total_amount': float(total_costs[i]) + } + for i, item in enumerate(items) + } + estimate.migrate_itemtxs( + itemtxs=estimate_itemtxs, + commit=True, + operation=EstimateModel.ITEMIZE_APPEND + ) + url = reverse_lazy('estimate_detail', kwargs={'pk': estimate.pk}) + return JsonResponse({'status': 'success', 'message': 'Estimate created successfully!','url':url}) + except Exception as e: + return JsonResponse({'status': 'error', 'message': f'An error occurred while processing the request.{e}'}, status=400) + + form = EstimateModelCreateForm(entity_slug=entity.slug,user_model=entity.admin) + context = {'form': form,"items":entity.get_items_all()} + return render(request, 'sales/estimates/estimate_form.html', context) + +class EstimateDetailView(LoginRequiredMixin, DetailView): + model = EstimateModel + template_name = "sales/estimates/estimate_detail.html" + context_object_name = "estimate" + + def get_context_data(self, **kwargs): + kwargs["total_unit_price"] = self.object.get_itemtxs_data()[0].first().ce_unit_cost_estimate * self.object.get_itemtxs_data()[0].first().ce_quantity + return super().get_context_data(**kwargs) \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 992afe40..36d4dad6 100644 --- a/templates/base.html +++ b/templates/base.html @@ -803,6 +803,132 @@ } + {% block extra_js %}{% endblock extra_js %} + + + + @@ -822,7 +948,6 @@ - \ No newline at end of file diff --git a/templates/ledger/bank_accounts/bank_account_detail.html b/templates/ledger/bank_accounts/bank_account_detail.html new file mode 100644 index 00000000..1cf3b952 --- /dev/null +++ b/templates/ledger/bank_accounts/bank_account_detail.html @@ -0,0 +1,73 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %}{{ _("View Bank Account") }}{% endblock title %} + +{% block content %} + + + +
+
+
+

{{ _("Bank Account Details") }}

+
+
+
+
+

{{ _("Bank Account Name") }}: {{ bank_account.name }}

+

{{ _("Cash Account") }}: {{ bank_account.cash_account }}

+
+
+

{{ _("Amount") }}: {{ bank_account.amount }}

+
+
+
+ +
+
+{% endblock %} diff --git a/templates/ledger/bank_accounts/bank_account_form.html b/templates/ledger/bank_accounts/bank_account_form.html new file mode 100644 index 00000000..402dbd5a --- /dev/null +++ b/templates/ledger/bank_accounts/bank_account_form.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% load i18n %} +{% load crispy_forms_filters %} +{% block title %}{% trans "bank account" %}{% endblock title %} + +{% block content %} +
+ +
+
+

+ {% if customer.created %} + + {{ _("Edit Bank Account") }} + {% else %} + + {{ _("Add Bank Account") }} + {% endif %} +

+
+
+
+ {% csrf_token %} + {{ form|crispy }} + {% for error in form.errors %} +
{{ error }}
+ {% endfor %} +
+ + {% trans "Cancel" %} +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/ledger/bank_accounts/bank_account_list.html b/templates/ledger/bank_accounts/bank_account_list.html new file mode 100644 index 00000000..31fa17c6 --- /dev/null +++ b/templates/ledger/bank_accounts/bank_account_list.html @@ -0,0 +1,126 @@ +{% extends "base.html" %} +{% load i18n %} +{% block title %}{% trans "Bank Accounts" %}{% endblock title %} +{% block customers %} + + {% trans "Customers"|capfirst %} + (current) + +{% endblock %} +{% block content %} +
+
+
+ +
+
+
+
+
+ + + {% if request.GET.q %} + + + + {% endif %} +
+
+
+
+
+ + +
+
+
+
+
{% trans "Customers List" %}
+
+
+ + + + + + + + + + {% for account in bank_accounts %} + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Name" %}{% trans "Type" %}{% trans "Actions" %}
{{ account.name }}{{ account.cash_account }} + + {% trans "view" %} + +
+ {% trans "No customers found." %} +
+
+ + {% if is_paginated %} + + {% endif %} +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/ledger/coa_accounts/account_detail.html b/templates/ledger/coa_accounts/account_detail.html new file mode 100644 index 00000000..9023e89c --- /dev/null +++ b/templates/ledger/coa_accounts/account_detail.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %}{{ _("View Account") }}{% endblock title %} + +{% block content %} + + + +
+
+
+

{{ _("Bank Account Details") }}

+
+
+
+
+

{{ _("Account Name") }}: {{ account.name }}

+

{{ _("Account Code") }}: {{ account.code }}

+
+
+

{{ _("Balance Type") }}: {{ account.balance_type }}

+

{{ _("Active") }}: {{ account.active }}

+
+
+
+ +
+
+{% endblock %} diff --git a/templates/ledger/coa_accounts/account_form.html b/templates/ledger/coa_accounts/account_form.html new file mode 100644 index 00000000..7277ed55 --- /dev/null +++ b/templates/ledger/coa_accounts/account_form.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% load i18n %} +{% load crispy_forms_filters %} +{% block title %}{% trans "account" %}{% endblock title %} + +{% block content %} +
+ +
+
+

+ {% if account.created %} + + {{ _("Edit Account") }} + {% else %} + + {{ _("Add Account") }} + {% endif %} +

+
+
+
+ {% csrf_token %} + {{ form|crispy }} + {% for error in form.errors %} +
{{ error }}
+ {% endfor %} +
+ + {% trans "Cancel" %} +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/ledger/coa_accounts/account_list.html b/templates/ledger/coa_accounts/account_list.html new file mode 100644 index 00000000..5454c9f3 --- /dev/null +++ b/templates/ledger/coa_accounts/account_list.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} +{% load i18n %} +{% block title %}{% trans "Accounts" %}{% endblock title %} +{% block customers %} + + {% trans "Accounts"|capfirst %} + (current) + +{% endblock %} +{% block content %} +
+
+
+ +
+
+
+
+
+ + + {% if request.GET.q %} + + + + {% endif %} +
+
+
+
+
+ + +
+
+
+
+
{% trans "Account List" %}
+
+
+ + + + + + + + + + + + {% for account in accounts %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Name" %}{% trans "Code" %}{% trans "Balance Type" %}{% trans "Active" %}{% trans "Actions" %}
{{ account.name }}{{ account.code }}{{ account.balance_type }}{{ account.active }} + + {% trans "view" %} + +
+ {% trans "No account found." %} +
+
+ + {% if is_paginated %} + + {% endif %} +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/sales/estimates/estimate_detail.html b/templates/sales/estimates/estimate_detail.html new file mode 100644 index 00000000..06bd2829 --- /dev/null +++ b/templates/sales/estimates/estimate_detail.html @@ -0,0 +1,147 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block title %}{{ _("View Estimate") }}{% endblock title %} + +{% block content %} + + +
+
+
+

Estimate

+
+ + +
+
+
+
+
+
+
+
+
+
Estimate No :
+
+
+

#{{estimate.estimate_number}}

+
+
+
+
+
+
+
Estimate Date :
+
+
+

{{estimate.created}}

+
+
+
+
+
+
+
+
+
Customer :
+

{{estimate.customer.customer_name}}

+
+
+
Email :
+

{{estimate.customer.email}}

+
+
+
+
+
+
+
Estimate Status :
+
+ {% if estimate.status == 'draft' %} + Draft + {% elif estimate.status == 'sent' %} + Sent + {% elif estimate.status == 'approved' %} + Approved + {% elif estimate.status == 'declined' %} + Declined + {% endif %} +
+
+
+
+
+
+
+
+ + + + + + + + + + + + {% for item in estimate.get_itemtxs_data.0 %} + + + + + + + + {% endfor %} + + + + + +
#ItemQuantityUnit PriceTotal
{{forloop.counter}}{{item.item_model.name}}{{item.ce_quantity}}{{item.ce_unit_cost_estimate}}{{item.ce_total_amount}}
Grand Total + 0.00 +
+
+ +
+
+ + + +
+ + +{% endblock %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file diff --git a/templates/sales/estimates/estimate_form.html b/templates/sales/estimates/estimate_form.html new file mode 100644 index 00000000..0af7239b --- /dev/null +++ b/templates/sales/estimates/estimate_form.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% load crispy_forms_filters %} +{% load i18n static %} + +{% block title %}{{ _("Create Estimate") }}{% endblock title %} + +{% block content %} +
+

{% trans "Create Estimate" %}

+
+ {% csrf_token %} +
+ {{ form|crispy }} +
+
+

Unit Items

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
+ + +
+ + {% trans "Cancel" %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/sales/estimates/estimate_list.html b/templates/sales/estimates/estimate_list.html new file mode 100644 index 00000000..62cce2b1 --- /dev/null +++ b/templates/sales/estimates/estimate_list.html @@ -0,0 +1,50 @@ +{% extends "base.html" %} +{% load i18n static %} + +{% block title %}{{ _("Quotations") }}{% endblock title %} + +{% block content %} +
+

{% trans "Quotations" %}

+
+ +
+ + + + + + + + + + + + + {% for estimate in estimates %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Estimate Number" %}{% trans "Customer" %}{% trans "Status" %}{% trans "Status Date" %}{% trans "Created" %}{% trans "Actions" %}
{{ estimate.estimate_number }}{{ estimate.customer }}{{ estimate.status }}{{ estimate.get_status_action_date }}{{ estimate.created }} + + {% trans "view" %} + +
{% trans "No Quotations Found" %}
+
+
+
+
+
+{% endblock %} \ No newline at end of file