add bills

This commit is contained in:
gitea 2025-01-19 11:22:50 +00:00
parent c277e73dec
commit ffb560e565
7 changed files with 308 additions and 97 deletions

View File

@ -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

View File

@ -482,6 +482,14 @@ urlpatterns = [
name="bill_detail",
),
path("items/bills/<uuid:pk>/delete/", views.BillDeleteView, name="bill_delete"),
path(
"items/bills/<uuid:pk>/in_review/",views.InReviewBillView.as_view(), name="in_review_bill"
),
path(
"items/bills/<uuid:pk>/in_approve/",views.ApprovedBillModelView.as_view(), name="in_approve_bill"
),
path('items/bills/<uuid:pk>/mark_as_approved/', views.bill_mark_as_approved, name='bill_mark_as_approved'),
path('items/bills/<uuid:pk>/mark_as_paid/', views.bill_mark_as_paid, name='bill_mark_as_paid'),
]

View File

@ -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()

View File

@ -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

View File

@ -4,7 +4,7 @@
{% block title %}{{ _("View Bill") }}{% endblock title %}
{% block content %}
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal fade" id="mark_as_approved_Modal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header bg-primary">
@ -19,10 +19,10 @@
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
{% comment %} <form id="confirmForm" method="POST" action="{% url 'bill_mark_as' bill.pk %}?mark=accept" class="d-inline">
<form id="confirmForm" method="POST" action="{% url 'bill_mark_as_approved' bill.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form> {% endcomment %}
</form>
</div>
</div>
</div>
@ -44,10 +44,10 @@
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
{% comment %} <form id="confirmForm" method="POST" action="{% url 'payment_mark_as_paid' bill.pk %}" class="d-inline">
<form id="confirmForm" method="POST" action="{% url 'bill_mark_as_paid' bill.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form> {% endcomment %}
</form>
</div>
</div>
</div>
@ -60,14 +60,17 @@
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="d-flex align-items-center gap-2">
{% if bill.bill_status == 'in_review' %}
<button id="accept_bill" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Accept' %}</span></button>
{% if bill.is_draft %}
<a href="{% url 'in_review_bill' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Review Bill' %}</span></a>
{% endif %}
{% if bill.bill_status == 'approved' %}
<a href="{% url 'payment_create' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Record Payment' %}</span></a>
{% endif %}
{% if not bill.is_paid %}
<button id="mark_bill_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Paid' %}</span></button>
{% if bill.is_review %}
<button id="mark_bill_as_approved" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_approved_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Approved' %}</span></button>
{% endif %}
{% if bill.is_approved %}
<a href="{% url 'payment_create' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Record Payment' %}</span></a>
{% endif %}
{% if bill.is_approved %}
<button id="mark_bill_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Paid' %}</span></button>
{% endif %}
</div>
</div>

View File

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "Bill" %}{% endblock title %}
{% block content %}
<div class="row my-4">
<h2>{% trans "Bill" %}</h2>
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-sm btn-primary">
{% if bill.is_draft %}
{% trans "Mark As Review" %}
{% elif bill.is_review or bill.is_approved %}
{% trans "Save" %}
{% endif %}
</button>
<a href="{% url 'bill_detail' pk=bill.pk %}" class="btn btn-sm btn-secondary">{% trans "Cancel" %}</a>
</form>
</div>
{% endblock %}

View File

@ -12,18 +12,18 @@
</style>
{% endblock extra_css %}
{% block content %}
<div class="row {% if invoice.invoice_status == 'paid' %}paid{% endif %}">
<div class="row {% if model.is_paid %}paid{% endif %}">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
{% if invoice.invoice_status == 'paid' %}
{% if model.is_paid %}
<div class="card-header">{{ _("Payment Already Made") }}</div>
{% else %}
<div class="card-header">{{ _("Make Payment") }}</div>
{% endif %}
<div class="card-body">
{% if invoice %}
<form method="post" action="{% url 'payment_create' pk=invoice.pk %}">
{% if model %}
<form method="post" action="{% url 'payment_create' pk=model.pk %}">
{% else %}
<form method="post" action="{% url 'payment_create' %}">
{% endif %}