diff --git a/inventory/forms.py b/inventory/forms.py index 4d513bb7..3ce4188c 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -1,4 +1,5 @@ from django_countries.widgets import CountrySelectWidget +from django_ledger.models.bill import BillModel from phonenumber_field.formfields import PhoneNumberField from django.core.validators import MinLengthValidator from django.core.validators import RegexValidator @@ -544,7 +545,10 @@ class ItemForm(forms.Form): class PaymentForm(forms.Form): invoice = forms.ModelChoiceField( - queryset=InvoiceModel.objects.all(), label="Invoice", required=True + queryset=InvoiceModel.objects.all(), label="Invoice", required=False + ) + bill = forms.ModelChoiceField( + queryset=BillModel.objects.all(), label="Bill", required=False ) amount = forms.DecimalField(label="Amount", required=True) payment_method = forms.ChoiceField( @@ -562,15 +566,17 @@ class PaymentForm(forms.Form): def clean_amount(self): invoice = self.cleaned_data['invoice'] + bill = self.cleaned_data['bill'] + model = invoice if invoice else bill amount = self.cleaned_data['amount'] - if amount < invoice.amount_due: - raise forms.ValidationError("Payment amount is greater than invoice amount due") + if amount + model.amount_paid > model.amount_due: + raise forms.ValidationError("Payment amount is greater than amount due") if amount <= 0: raise forms.ValidationError("Payment amount must be greater than 0") - if invoice.amount_due == invoice.amount_paid or invoice.invoice_status == "paid": + if model.is_paid(): raise forms.ValidationError("Invoice is already paid") - if amount > invoice.amount_due: - raise forms.ValidationError("Payment amount is greater than invoice amount due") + if amount > model.amount_due: + raise forms.ValidationError("Payment amount is greater than amount due") return amount diff --git a/inventory/urls.py b/inventory/urls.py index 5e2e23f5..aefc18a4 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -482,6 +482,14 @@ urlpatterns = [ name="bill_detail", ), path("items/bills//delete/", views.BillDeleteView, name="bill_delete"), + path( + "items/bills//in_review/",views.InReviewBillView.as_view(), name="in_review_bill" + ), + path( + "items/bills//in_approve/",views.ApprovedBillModelView.as_view(), name="in_approve_bill" + ), + path('items/bills//mark_as_approved/', views.bill_mark_as_approved, name='bill_mark_as_approved'), + path('items/bills//mark_as_paid/', views.bill_mark_as_paid, name='bill_mark_as_paid'), ] diff --git a/inventory/utils.py b/inventory/utils.py index 135450fb..0fb8acfb 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -1,6 +1,9 @@ from django.shortcuts import redirect from django.contrib import messages from django.utils import timezone +from django_ledger.models.journal_entry import JournalEntryModel +from django_ledger.models.ledger import LedgerModel +from django_ledger.models.transactions import TransactionModel import requests from inventory import models from django.conf import settings @@ -179,3 +182,130 @@ def get_financial_values(model): "vat_amount": vat_amount, "vat": vat.rate, } + + +def set_invoice_payment(dealer, entity, invoice, amount, payment_method): + vat_amount = 0 + total_amount = 0 + + if invoice.terms == "on_receipt": + for x in invoice.get_itemtxs_data()[0].all(): + vat_amount += models.Car.objects.get( + vin=x.item_model.name + ).finances.vat_amount * Decimal(x.quantity) + total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) + + grand_total = total_amount - Decimal(vat_amount) + + ledger = LedgerModel.objects.filter( + name__icontains=str(invoice.pk), entity=entity + ).first() + journal = JournalEntryModel.objects.create( + posted=False, + description=f"Payment for Invoice {invoice.invoice_number}", + ledger=ledger, + locked=False, + origin="Payment", + ) + credit_account = entity.get_default_coa_accounts().get(name="Sales Revenue") + debit_account = None + if payment_method == "cash": + debit_account = entity.get_default_coa_accounts().get(name="Cash", active=True) + elif payment_method == "credit": + debit_account = entity.get_default_coa_accounts().get( + name="Accounts Receivable", active=True + ) + else: + debit_account = entity.get_default_coa_accounts().get( + name="Cash in Bank", active=True + ) + + vat_payable_account = entity.get_default_coa_accounts().get( + name="VAT Payable", active=True + ) + TransactionModel.objects.create( + journal_entry=journal, + account=debit_account, # Debit Cash + amount=amount, # Payment amount + tx_type="debit", + description="Payment Received", + ) + + TransactionModel.objects.create( + journal_entry=journal, + account=credit_account, # Credit Accounts Receivable + amount=grand_total, # Payment amount + tx_type="credit", + description="Payment Received", + ) + + if vat_amount > 0: + TransactionModel.objects.create( + journal_entry=journal, + account=vat_payable_account, # Credit VAT Payable + amount=vat_amount, + tx_type="credit", + description="VAT Payable on Invoice", + ) + + invoice.make_payment(amount) + invoice.save() + + +def set_bill_payment(dealer, entity, bill, amount, payment_method): + vat_amount = 0 + total_amount = 0 + + if bill.terms == "on_receipt": + for x in bill.get_itemtxs_data()[0].all(): + vat_amount += models.Car.objects.get( + vin=x.item_model.name + ).finances.cost_price * Decimal(x.quantity) + total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) + + # grand_total = total_amount - Decimal(vat_amount) + + journal = JournalEntryModel.objects.create( + posted=False, + description=f"Payment for bill {bill.bill_number}", + ledger=bill.ledger, + locked=False, + origin="Payment", + ) + + cash_account = entity.get_default_coa_accounts().get(name="Cash", active=True) + + account_payable = entity.get_default_coa_accounts().get( + name="Accounts Payable", active=True + ) + + # vat_payable_account = entity.get_default_coa_accounts().get( + # name="VAT Payable", active=True + # ) + TransactionModel.objects.create( + journal_entry=journal, + account=cash_account, # Debit Cash + amount=amount, # Payment amount + tx_type="debit", + description="Payment Received", + ) + + TransactionModel.objects.create( + journal_entry=journal, + account=account_payable, # Credit Accounts Receivable + amount=amount, # Payment amount + tx_type="credit", + description="Payment Received", + ) + + # if vat_amount > 0: + # TransactionModel.objects.create( + # journal_entry=journal, + # account=vat_payable_account, # Credit VAT Payable + # amount=vat_amount, + # tx_type="credit", + # description="VAT Payable on bill", + # ) + + bill.make_payment(amount) + bill.save() diff --git a/inventory/views.py b/inventory/views.py index 704c968f..ee24882e 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -3,6 +3,7 @@ from django.core.paginator import Paginator from django.forms import DateField, DateInput, HiddenInput, TextInput from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt +from django_ledger.forms.bill import ApprovedBillModelUpdateForm, InReviewBillModelUpdateForm from django_ledger.models import ( EntityModel, InvoiceModel, @@ -80,6 +81,8 @@ from .utils import ( reserve_car, send_email, get_user_type, + set_bill_payment, + set_invoice_payment, ) from django.contrib.auth.models import User from allauth.account import views @@ -2423,6 +2426,9 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView): def PaymentCreateView(request, pk=None): invoice = InvoiceModel.objects.filter(pk=pk).first() + bill = BillModel.objects.filter(pk=pk).first() + model = invoice if invoice else bill + redirect_url = 'invoice_detail' if invoice else 'bill_detail' dealer = get_user_type(request) entity = dealer.entity form = forms.PaymentForm() @@ -2431,89 +2437,36 @@ def PaymentCreateView(request, pk=None): if form.is_valid(): amount = form.cleaned_data.get("amount") invoice = form.cleaned_data.get("invoice") + bill = form.cleaned_data.get("bill") payment_method = form.cleaned_data.get("payment_method") + model = invoice if invoice else bill + if not model.is_approved(): + model.mark_as_approved(user_model=entity.admin) try: - vat_amount = 0 - total_amount = 0 - - if invoice.terms == "on_receipt": - for x in invoice.get_itemtxs_data()[0].all(): - vat_amount += models.Car.objects.get( - vin=x.item_model.name - ).finances.vat_amount * Decimal(x.quantity) - total_amount += Decimal(x.unit_cost) * Decimal(x.quantity) - - grand_total = total_amount - Decimal(vat_amount) - - ledger = LedgerModel.objects.filter( - name=str(invoice.pk), entity=entity - ).first() - journal = JournalEntryModel.objects.create( - posted=False, - description=f"Payment for Invoice {invoice.invoice_number}", - ledger=ledger, - locked=False, - origin="Payment", - ) - credit_account = entity.get_default_coa_accounts().get( - name="Sales Revenue" - ) - debit_account = None - if payment_method == "cash": - debit_account = entity.get_default_coa_accounts().get( - name="Cash", active=True - ) - elif payment_method == "credit": - debit_account = entity.get_default_coa_accounts().get( - name="Accounts Receivable", active=True - ) - else: - debit_account = entity.get_default_coa_accounts().get( - name="Cash in Bank", active=True - ) - - vat_payable_account = entity.get_default_coa_accounts().get( - name="VAT Payable", active=True - ) - TransactionModel.objects.create( - journal_entry=journal, - account=debit_account, # Debit Cash - amount=amount, # Payment amount - tx_type="debit", - description="Payment Received", - ) - - TransactionModel.objects.create( - journal_entry=journal, - account=credit_account, # Credit Accounts Receivable - amount=grand_total, # Payment amount - tx_type="credit", - description="Payment Received", - ) - - if vat_amount > 0: - TransactionModel.objects.create( - journal_entry=journal, - account=vat_payable_account, # Credit VAT Payable - amount=vat_amount, - tx_type="credit", - description="VAT Payable on Invoice", - ) - - invoice.make_payment(amount) - invoice.save() + if invoice: + set_invoice_payment(dealer,entity,invoice,amount,payment_method) + elif bill: + set_bill_payment(dealer,entity,bill,amount,payment_method) + return redirect(redirect_url, pk=model.pk) except Exception as e: messages.error(request, f"Error creating payment: {str(e)}") else: - messages.error(request, f"Invalid form data: {str(form.errors)}") - return redirect("invoice_detail", pk=invoice.pk) + messages.error(request, f"Invalid form data: {str(form.errors)}") + # return redirect(redirect_url, pk=model.pk) form = forms.PaymentForm() - form.initial["amount"] = invoice.amount_due + form.initial["amount"] = model.amount_due - model.amount_paid + + if model: + if isinstance(model, InvoiceModel): + form.initial["invoice"] = model + form.fields['bill'].widget = HiddenInput() + + elif isinstance(model, BillModel): + form.initial["bill"] = model + form.fields['invoice'].widget = HiddenInput() - if invoice: - form.initial["invoice"] = invoice return render( - request, "sales/payments/payment_form.html", {"invoice": invoice, "form": form} + request, "sales/payments/payment_form.html", {"model": model, "form": form} ) @@ -2926,6 +2879,96 @@ class BillDetailView(LoginRequiredMixin, DetailView): return super().get_context_data(**kwargs) +class InReviewBillView(LoginRequiredMixin, UpdateView): + model = BillModel + form_class = InReviewBillModelUpdateForm + template_name = "ledger/bills/bill_update_form.html" + success_url = reverse_lazy("bill_list") + success_message = _("Bill updated successfully.") + context_object_name = "bill" + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + dealer = get_user_type(self.request) + kwargs["entity_model"] = dealer.entity + kwargs["user_model"] = dealer.entity.admin + return kwargs + + def get_success_url(self): + return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + + def form_valid(self, form): + dealer = get_user_type(self.request) + form.instance.entity = dealer.entity + self.object.mark_as_review() + return super().form_valid(form) + +class ApprovedBillModelView(LoginRequiredMixin, UpdateView): + model = BillModel + form_class = ApprovedBillModelUpdateForm + template_name = "ledger/bills/bill_update_form.html" + success_url = reverse_lazy("bill_list") + success_message = _("Bill updated successfully.") + context_object_name = "bill" + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + dealer = get_user_type(self.request) + kwargs["entity_model"] = dealer.entity + kwargs["user_model"] = dealer.entity.admin + return kwargs + + def get_success_url(self): + return reverse_lazy("bill_detail", kwargs={"pk": self.kwargs["pk"]}) + + def form_valid(self, form): + dealer = get_user_type(self.request) + form.instance.entity = dealer.entity + if not self.object.is_approved(): + self.object.mark_as_approved(user_model=dealer.entity.admin) + return super().form_valid(form) + + +def bill_mark_as_approved(request,pk): + bill = get_object_or_404(BillModel,pk=pk) + if request.method == "POST": + dealer = get_user_type(request) + if bill.is_approved(): + messages.error(request, _("Bill is already approved.")) + return redirect("bill_detail",pk=bill.pk) + bill.mark_as_approved(user_model=dealer.entity.admin) + bill.save() + messages.success(request, _("Bill marked as approved successfully.")) + return redirect("bill_detail",pk=bill.pk) + +def bill_mark_as_paid(request,pk): + bill = get_object_or_404(BillModel,pk=pk) + if request.method == "POST": + dealer = get_user_type(request) + if bill.is_paid(): + messages.error(request, _("Bill is already paid.")) + return redirect("bill_detail",pk=bill.pk) + if bill.amount_due == bill.amount_paid: + bill.mark_as_paid(user_model=dealer.entity.admin) + bill.save() + bill.ledger.lock_journal_entries() + bill.ledger.post_journal_entries() + + bill.ledger.post() + bill.ledger.save() + messages.success(request, _("Bill marked as paid successfully.")) + else: + messages.error(request, _("Amount paid is not equal to amount due.")) + return redirect("bill_detail",pk=bill.pk) + + # def get_context_data(self, **kwargs): + # dealer = get_user_type(self.request) + # context = super().get_context_data(**kwargs) + # context['entity_model'] = dealer.entity + # context['user_model'] = dealer.entity.admin + + # return context + # class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): # model = BillModel # form_class = BillModelCreateForm diff --git a/templates/ledger/bills/bill_detail.html b/templates/ledger/bills/bill_detail.html index 1b78e795..d259c91e 100644 --- a/templates/ledger/bills/bill_detail.html +++ b/templates/ledger/bills/bill_detail.html @@ -4,7 +4,7 @@ {% block title %}{{ _("View Bill") }}{% endblock title %} {% block content %} - @@ -60,14 +60,17 @@

{% trans 'Bill' %}

- {% if bill.bill_status == 'in_review' %} - + {% if bill.is_draft %} + {% trans 'Review Bill' %} {% endif %} - {% if bill.bill_status == 'approved' %} - {% trans 'Record Payment' %} - {% endif %} - {% if not bill.is_paid %} - + {% if bill.is_review %} + + {% endif %} + {% if bill.is_approved %} + {% trans 'Record Payment' %} + {% endif %} + {% if bill.is_approved %} + {% endif %}
diff --git a/templates/ledger/bills/bill_update_form.html b/templates/ledger/bills/bill_update_form.html new file mode 100644 index 00000000..e43c596c --- /dev/null +++ b/templates/ledger/bills/bill_update_form.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load crispy_forms_filters %} +{% block title %}{% trans "Bill" %}{% endblock title %} +{% block content %} +
+

{% trans "Bill" %}

+
+ {% csrf_token %} + {{ form|crispy }} + + {% trans "Cancel" %} +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/sales/payments/payment_form.html b/templates/sales/payments/payment_form.html index 91e1f0c9..1a319b58 100644 --- a/templates/sales/payments/payment_form.html +++ b/templates/sales/payments/payment_form.html @@ -12,18 +12,18 @@ {% endblock extra_css %} {% block content %} -
+
- {% if invoice.invoice_status == 'paid' %} + {% if model.is_paid %}
{{ _("Payment Already Made") }}
{% else %}
{{ _("Make Payment") }}
{% endif %}
- {% if invoice %} -
+ {% if model %} + {% else %} {% endif %}