This commit is contained in:
gitea 2024-12-31 15:40:09 +00:00
parent 8a6d0dc999
commit df4048e3a1
17 changed files with 907 additions and 327 deletions

View File

@ -23,10 +23,9 @@ from .models import (
Payment,
SaleQuotationCar,
AdditionalServices,
Staff
Staff,
)
from django_ledger.models import ItemModel
from django_ledger.models import ItemModel, InvoiceModel
from django.forms import ModelMultipleChoiceField, ValidationError
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
@ -35,40 +34,42 @@ from django.forms import formset_factory
User = get_user_model()
class AdditionalServiceForm(forms.ModelForm):
class Meta:
model = AdditionalServices
fields = ['name', 'price','description','taxable', 'uom']
fields = ["name", "price", "description", "taxable", "uom"]
class PaymentForm(forms.ModelForm):
class Meta:
model = Payment
fields = ['amount','payment_method', 'reference_number']
# class PaymentForm(forms.ModelForm):
# invoice = forms.ModelChoiceField(queryset=InvoiceModel.objects.all(),label="Invoice", required=True)
# class Meta:
# model = Payment
# fields = ['amount','payment_method', 'reference_number']
class StaffForm(forms.ModelForm):
email = forms.EmailField(
required=True,
label="Email",
widget=forms.EmailInput(attrs={"class": "form-control"})
widget=forms.EmailInput(attrs={"class": "form-control"}),
)
class Meta:
model = Staff
fields = ['name', 'arabic_name', 'phone_number', 'staff_type']
fields = ["name", "arabic_name", "phone_number", "staff_type"]
def __init__(self, *args, **kwargs):
user_instance = kwargs.get('instance')
user_instance = kwargs.get("instance")
if user_instance and user_instance.user:
initial = kwargs.setdefault('initial', {})
initial['email'] = user_instance.user.email
initial = kwargs.setdefault("initial", {})
initial["email"] = user_instance.user.email
super().__init__(*args, **kwargs)
def save(self, commit=True):
staff_instance = super().save(commit=False)
user = staff_instance.user
user.email = self.cleaned_data['email']
user.email = self.cleaned_data["email"]
if commit:
user.save()
staff_instance.save()
@ -79,7 +80,15 @@ class StaffForm(forms.ModelForm):
class DealerForm(forms.ModelForm):
class Meta:
model = Dealer
fields = ['name', 'arabic_name', 'crn', 'vrn', 'phone_number', 'address', 'logo']
fields = [
"name",
"arabic_name",
"crn",
"vrn",
"phone_number",
"address",
"logo",
]
# Customer Form
@ -87,41 +96,57 @@ class CustomerForm(forms.ModelForm, AddClassMixin):
class Meta:
model = Customer
fields = [
'first_name', 'middle_name', 'last_name', 'email',
'national_id', 'phone_number', 'address'
"first_name",
"middle_name",
"last_name",
"email",
"national_id",
"phone_number",
"address",
]
class CarForm(forms.ModelForm, AddClassMixin, ):
class CarForm(
forms.ModelForm,
AddClassMixin,
):
class Meta:
model = Car
fields = [
'vin', 'id_car_make', 'id_car_model',
'year', 'id_car_serie', 'id_car_trim',
'stock_type', 'remarks', 'mileage', 'receiving_date', 'vendor'
"vin",
"id_car_make",
"id_car_model",
"year",
"id_car_serie",
"id_car_trim",
"stock_type",
"remarks",
"mileage",
"receiving_date",
"vendor",
]
widgets = {
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'remarks': forms.Textarea(attrs={'rows': 2}),
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
"remarks": forms.Textarea(attrs={"rows": 2}),
}
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
dealer = kwargs.pop("dealer", None)
super().__init__(*args, **kwargs)
if 'id_car_make' in self.fields:
queryset = self.fields['id_car_make'].queryset.filter(is_sa_import=True)
self.fields['id_car_make'].choices = [
if "id_car_make" in self.fields:
queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True)
self.fields["id_car_make"].choices = [
(obj.id_car_make, obj.get_local_name()) for obj in queryset
]
if 'id_car_model' in self.fields:
queryset = self.fields['id_car_model'].queryset
self.fields['id_car_model'].choices = [
if "id_car_model" in self.fields:
queryset = self.fields["id_car_model"].queryset
self.fields["id_car_model"].choices = [
(obj.id_car_model, obj.get_local_name()) for obj in queryset
]
if 'vendor' in self.fields:
queryset = self.fields['vendor'].queryset
self.fields['vendor'].choices = [
if "vendor" in self.fields:
queryset = self.fields["vendor"].queryset
self.fields["vendor"].choices = [
(obj.pk, obj.get_local_name()) for obj in queryset
]
@ -129,15 +154,22 @@ class CarForm(forms.ModelForm, AddClassMixin, ):
class CarUpdateForm(forms.ModelForm, AddClassMixin):
class Meta:
model = Car
fields = ['vendor', 'status', 'stock_type', 'mileage', 'receiving_date', 'remarks']
fields = [
"vendor",
"status",
"stock_type",
"mileage",
"receiving_date",
"remarks",
]
widgets = {
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
'remarks': forms.Textarea(attrs={'rows': 2}),
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
"remarks": forms.Textarea(attrs={"rows": 2}),
}
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
dealer = kwargs.pop("dealer", None)
super().__init__(*args, **kwargs)
# if dealer and 'branch' in self.fields:
@ -146,89 +178,115 @@ class CarUpdateForm(forms.ModelForm, AddClassMixin):
# (branch.id, branch.get_local_name()) for branch in self.fields['branch'].queryset
# ]
if 'vendor' in self.fields:
queryset = self.fields['vendor'].queryset
if "vendor" in self.fields:
queryset = self.fields["vendor"].queryset
if queryset:
self.fields['vendor'].choices = [
self.fields["vendor"].choices = [
(vendor.id, vendor.get_local_name()) for vendor in queryset
]
class CarFinanceForm(AddClassMixin, forms.ModelForm):
additional_finances = forms.ModelMultipleChoiceField(
queryset=AdditionalServices.objects.all(),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
required=False
)
queryset=AdditionalServices.objects.all(),
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
required=False,
)
class Meta:
model = CarFinance
exclude = ['car', 'additional_finances','profit_margin', 'vat_amount', 'total', 'vat_rate','additional_services']
exclude = [
"car",
"additional_finances",
"profit_margin",
"vat_amount",
"total",
"vat_rate",
"additional_services",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields['additional_finances'].initial = self.instance.additional_services.all()
self.fields[
"additional_finances"
].initial = self.instance.additional_services.all()
def save(self, commit=True):
instance = super().save()
instance.additional_services.set(self.cleaned_data['additional_finances'])
instance.additional_services.set(self.cleaned_data["additional_finances"])
instance.save()
return instance
class CarLocationForm(forms.ModelForm):
class Meta:
model = CarLocation
fields = ['showroom', 'description']
fields = ["showroom", "description"]
widgets = {
'description': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
"description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
}
# Custom Card Form
class CustomCardForm(forms.ModelForm):
custom_date = forms.DateTimeField(
widget=forms.DateInput(attrs={'type': 'date'}),
widget=forms.DateInput(attrs={"type": "date"}),
label=_("Custom Date"),
)
class Meta:
model = CustomCard
fields = ['custom_number', 'custom_date']
fields = ["custom_number", "custom_date"]
# Car Registration Form
class CarRegistrationForm(forms.ModelForm):
class Meta:
model = CarRegistration
fields = [
'car', 'plate_number', 'text1', 'text2', 'text3', 'registration_date'
]
fields = ["car", "plate_number", "text1", "text2", "text3", "registration_date"]
class VendorForm(forms.ModelForm):
class Meta:
model = Vendor
fields = ['name', 'arabic_name', 'crn', 'vrn', 'email', 'phone_number', 'contact_person', 'address', 'logo' ]
fields = [
"name",
"arabic_name",
"crn",
"vrn",
"email",
"phone_number",
"contact_person",
"address",
"logo",
]
class CarColorsForm(forms.ModelForm):
class Meta:
model = CarColors
fields = ['exterior', 'interior']
fields = ["exterior", "interior"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['exterior'].queryset = ExteriorColors.objects.all()
self.fields['exterior'].widget = forms.RadioSelect(attrs={'class': 'form-check-input'})
self.fields['exterior'].choices = [
(color.id, f"{color.get_local_name}") for color in ExteriorColors.objects.all().order_by('-name')
self.fields["exterior"].queryset = ExteriorColors.objects.all()
self.fields["exterior"].widget = forms.RadioSelect(
attrs={"class": "form-check-input"}
)
self.fields["exterior"].choices = [
(color.id, f"{color.get_local_name}")
for color in ExteriorColors.objects.all().order_by("-name")
]
self.fields['interior'].queryset = InteriorColors.objects.all()
self.fields['interior'].widget = forms.RadioSelect(attrs={'class': 'form-check-input'})
self.fields['interior'].choices = [
(color.id, f"{color.get_local_name}") for color in InteriorColors.objects.all().order_by('-name')
self.fields["interior"].queryset = InteriorColors.objects.all()
self.fields["interior"].widget = forms.RadioSelect(
attrs={"class": "form-check-input"}
)
self.fields["interior"].choices = [
(color.id, f"{color.get_local_name}")
for color in InteriorColors.objects.all().order_by("-name")
]
def clean(self):
@ -237,7 +295,9 @@ class CarColorsForm(forms.ModelForm):
interior = cleaned_data.get("interior")
if not exterior or not interior:
raise forms.ValidationError(_("Both exterior and interior colors must be selected."))
raise forms.ValidationError(
_("Both exterior and interior colors must be selected.")
)
return cleaned_data
@ -246,17 +306,17 @@ class QuotationForm(forms.ModelForm):
cars = ModelMultipleChoiceField(
queryset=Car.objects.none(), # Default empty queryset
widget=forms.CheckboxSelectMultiple,
label="Select Cars"
label="Select Cars",
)
class Meta:
model = SaleQuotation
fields = ['customer', 'cars', 'remarks']
fields = ["customer", "cars", "remarks"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['cars'].queryset = Car.objects.filter(
self.fields["cars"].queryset = Car.objects.filter(
finances__isnull=False
).distinct()
@ -265,22 +325,34 @@ class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = [
'name', 'arabic_name', 'crn', 'vrn',
'phone_number', 'address', 'logo',
"name",
"arabic_name",
"crn",
"vrn",
"phone_number",
"address",
"logo",
]
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
dealer = kwargs.pop("dealer", None)
super().__init__(*args, **kwargs)
class RepresentativeForm(forms.ModelForm):
class Meta:
model = Representative
fields = [
'name', 'arabic_name', 'id_number',
'phone_number', 'address', 'organization'
"name",
"arabic_name",
"id_number",
"phone_number",
"address",
"organization",
]
def __init__(self, *args, **kwargs):
dealer = kwargs.pop('dealer', None)
dealer = kwargs.pop("dealer", None)
super().__init__(*args, **kwargs)
@ -289,132 +361,149 @@ class CarSelectionTable(tables.Table):
class Meta:
model = Car
fields = ['vin', 'year', 'id_car_make', 'id_car_model']
fields = ["vin", "year", "id_car_make", "id_car_model"]
template_name = "django_tables2/bootstrap4.html"
class WizardForm1(forms.Form):
email = forms.EmailField(
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': 'Email address',
'required': 'required',
}),
widget=forms.EmailInput(
attrs={
"class": "form-control",
"placeholder": "Email address",
"required": "required",
}
),
error_messages={
'required': _('You must add an email.'),
}
"required": _("You must add an email."),
},
)
password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': _('Password'),
'required': 'required',
}),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
"placeholder": _("Password"),
"required": "required",
}
),
error_messages={
'required': _('This field is required.'),
}
"required": _("This field is required."),
},
)
confirm_password = forms.CharField(
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': _('Confirm Password'),
'required': 'required',
}),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
"placeholder": _("Confirm Password"),
"required": "required",
}
),
error_messages={
'required': _('This field is required.'),
}
"required": _("This field is required."),
},
)
terms = forms.BooleanField(
widget=forms.CheckboxInput(attrs={
'class': 'form-check-input',
'required': 'required',
}),
widget=forms.CheckboxInput(
attrs={
"class": "form-check-input",
"required": "required",
}
),
error_messages={
'required': _('You must accept the terms and privacy policy.'),
}
"required": _("You must accept the terms and privacy policy."),
},
)
class WizardForm2(forms.Form):
name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('English Name'),
'required': 'required',
}),
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("English Name"),
"required": "required",
}
),
error_messages={
'required': _('Please enter an English Name.'),
}
"required": _("Please enter an English Name."),
},
)
arabic_name = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Arabic Name'),
'required': 'required',
}),
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("Arabic Name"),
"required": "required",
}
),
error_messages={
'required': _('Please enter an Arabic name.'),
}
"required": _("Please enter an Arabic name."),
},
)
phone_number = PhoneNumberField(
min_length=10,
max_length=10,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _('Phone'),
'required': 'required',
}),
region='SA',
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("Phone"),
"required": "required",
}
),
region="SA",
error_messages={
'required': _('This field is required.'),
'invalid': _('Phone number must be in the format 05xxxxxxxx'),
}
"required": _("This field is required."),
"invalid": _("Phone number must be in the format 05xxxxxxxx"),
},
)
class WizardForm3(forms.Form):
# CRN field with max length of 10
crn = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _("Commercial Registration Number"),
'required': 'required',
'maxlength': '10',
}),
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("Commercial Registration Number"),
"required": "required",
"maxlength": "10",
}
),
max_length=10,
error_messages={
'required': 'This field is required.',
'max_length': 'Commercial Registration Number must be 10 characters.',
}
"required": "This field is required.",
"max_length": "Commercial Registration Number must be 10 characters.",
},
)
# VRN field with max length of 15
vrn = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': _("VAT Registration Number"),
'required': 'required',
'maxlength': '15',
}),
widget=forms.TextInput(
attrs={
"class": "form-control",
"placeholder": _("VAT Registration Number"),
"required": "required",
"maxlength": "15",
}
),
max_length=15, #
error_messages={
'required': _('This field is required.'),
'max_length': _('VAT Registration Number must be 15 characters.'),
}
"required": _("This field is required."),
"max_length": _("VAT Registration Number must be 15 characters."),
},
)
address = forms.CharField(
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': '3',
'required': 'required',
}),
widget=forms.Textarea(
attrs={
"class": "form-control",
"rows": "3",
"required": "required",
}
),
error_messages={
'required': _('This field is required.'),
}
"required": _("This field is required."),
},
)
def clean(self):
@ -429,8 +518,38 @@ class WizardForm3(forms.Form):
class ItemForm(forms.Form):
item = forms.ModelChoiceField(queryset=ItemModel.objects.all(),label="Item", required=True)
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)
unit_sales_price = forms.DecimalField(label="Unit Sales Price", required=True)
class PaymentForm(forms.Form):
invoice = forms.ModelChoiceField(
queryset=InvoiceModel.objects.all(), label="Invoice", required=True
)
amount = forms.DecimalField(label="Amount", required=True)
payment_method = forms.ChoiceField(
choices=[
("cash", _("cash")),
("credit", _("credit")),
("transfer", _("transfer")),
("debit", _("debit")),
("SADAD", _("SADAD")),
],
label="Payment Method",
required=True,
)
payment_date = forms.DateField(label="Payment Date", required=True)
from django import forms
class EmailForm(forms.Form):
subject = forms.CharField(max_length=255)
message = forms.CharField(widget=forms.Textarea)
from_email = forms.EmailField()
to_email = forms.EmailField()

View File

@ -126,15 +126,27 @@ urlpatterns = [
path('sales/estimates/create/', views.create_estimate, name='estimate_create'),
path('sales/estimates/<uuid:pk>/estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'),
path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
path('send_email/<uuid:pk>', views.send_email, name='send_email'),
path('send_email/<uuid:pk>/', views.send_email_view, name='send_email'),
# Invoice
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'),
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'),
# path('sales/estimates/create/', views.create_estimate, name='estimate_create'),
path('sales/invoices/<uuid:pk>/invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# path('send_email/<uuid:pk>', views.send_email, name='send_email'),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# path('send_email/<uuid:pk>', views.send_email, name='send_email'),
#Payment
path('sales/payments/', views.PaymentListView, name='payment_list'),
path('sales/payments/<uuid:pk>/create/', views.PaymentCreateView, name='payment_create'),
path('sales/payments/create/', views.PaymentCreateView, name='payment_create'),
path('sales/payments/<uuid:pk>/payment_details/', views.PaymentDetailView, name='payment_details'),
# path('sales/payments/<uuid:pk>/update/', views.JournalEntryUpdateView.as_view(), name='payment_update'),
# path('sales/payments/<uuid:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'),
# path('sales/payments/<uuid:pk>/preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'),
# # Journal
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'),
]

View File

@ -1,7 +1,8 @@
import requests
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from inventory import models
from django.conf import settings
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _
from inventory.utilities.financials import get_financial_value
@ -48,4 +49,12 @@ def get_calculations(quotation):
context["total_cost"] += k["total_price"]
context["total_vat"] += k["vated"]
context["total_cost_vat"] = float(context["total_cost"])+float(context["total_vat"])
return context
return context
def send_email(from_, to_, subject, message):
subject = subject
message = message
from_email = from_
recipient_list = [to_]
send_mail(subject, message, from_email, recipient_list)

View File

@ -1,10 +1,12 @@
from django.core.mail import send_mail
from django.core.paginator import Paginator
from django.views.decorators.csrf import csrf_exempt
from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel
from django_ledger.models import EntityModel, InvoiceModel,BankAccountModel,AccountModel,JournalEntryModel,TransactionModel,EstimateModel,CustomerModel,LedgerModel
from django_ledger.forms.bank_account import BankAccountCreateForm,BankAccountUpdateForm
from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm
from django_ledger.forms.estimate import EstimateModelCreateForm
from django_ledger.forms.invoice import InvoiceModelCreateForm
from django_ledger.forms.journal_entry import JournalEntryModelCreateForm
from django_ledger.io import roles
from django.contrib.admin.models import LogEntry
import logging
@ -48,7 +50,7 @@ from . import models, forms
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.models import Group
from .utils import get_calculations
from .utils import get_calculations,send_email
from django.contrib.auth.models import User
from allauth.account import views
from django.db.models import Count, F, Value
@ -1581,41 +1583,41 @@ def account_delete(request, pk):
return redirect("account_list")
return render(request, "ledger/coa_accounts/account_delete.html", {"account": account})
#Estimates
#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."
# 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_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 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)
# def form_valid(self, form):
# form.instance.entity = self.request.user.dealer.entity
# return super().form_valid(form)
# @csrf_exempt
@login_required
@ -1690,9 +1692,77 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
kwargs["vate_amount"] = (total * vat.vat_rate)
kwargs["total"] = (total * vat.vat_rate) + total
kwargs["vat"] = vat.rate
kwargs["invoice"] = InvoiceModel.objects.all().filter(ce_model=estimate).first()
return super().get_context_data(**kwargs)
class EstimatePreviewView(LoginRequiredMixin, DetailView):
model = EstimateModel
context_object_name = "estimate"
template_name = "sales/estimates/estimate_preview.html"
def get_context_data(self, **kwargs):
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
total = sum(x.ce_cost_estimate for x in estimate.get_itemtxs_data()[0].all())
vat = models.VatRate.objects.filter(is_active=True).first()
kwargs["vat_amount"] = (total * vat.vat_rate)
kwargs["total"] = (total * vat.vat_rate) + total
kwargs["vat"] = vat.rate
return super().get_context_data(**kwargs)
@login_required
def estimate_mark_as(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
entity = estimate.entity
mark = request.GET.get('mark')
if mark:
if mark == "review":
if not estimate.can_review():
messages.error(request, "Estimate is not ready for review")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_review()
elif mark == "accepted":
if not estimate.can_approve():
messages.error(request, "Estimate is not ready for approval")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved()
elif mark == "completed":
if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion")
return redirect("estimate_detail", pk=estimate.pk)
# invoice = entity.create_invoice(customer_model=estimate.customer,
# terms=estimate.terms,
# cash_account=entity.get_default_coa_accounts().get(name="Cash"),
# prepaid_account=entity.get_default_coa_accounts().get(name="Accounts Receivable"),
# coa_model=entity.get_default_coa()
# )
# unit_items = estimate.get_itemtxs_data()[0]
# invoice_itemtxs = {
# i.item_model.item_number: {
# 'unit_cost': i.ce_unit_cost_estimate,
# 'quantity': i.ce_quantity,
# 'total_amount': i.ce_cost_estimate
# } for i in unit_items
# }
# invoice_itemtxs = invoice.migrate_itemtxs(itemtxs=invoice_itemtxs,
# commit=True,
# operation=InvoiceModel.ITEMIZE_APPEND)
# invoice.bind_estimate(estimate)
# invoice.mark_as_review()
# estimate.mark_as_completed()
# estimate.save()
# invoice.save()
estimate.save()
messages.success(request, "Estimate marked as " + mark.upper())
return redirect("estimate_detail", pk=estimate.pk)
# Invoice
class InvoiceListView(LoginRequiredMixin, ListView):
model = InvoiceModel
template_name = "sales/invoices/invoice_list.html"
@ -1712,66 +1782,14 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
total = sum(x.unit_cost for x in invoice.get_itemtxs_data()[0].all())
total = sum(x.unit_cost * x.quantity for x in invoice.get_itemtxs_data()[0].all())
total = int(total)
vat = models.VatRate.objects.filter(is_active=True).first()
kwargs["vate_amount"] = (total * int(vat.vat_rate))
kwargs["total"] = (total * int(vat.vat_rate)) + total
kwargs["vat"] = vat.rate
kwargs["vat_amount"] = (total * vat.vat_rate)
kwargs["total"] = (total * vat.vat_rate) + total
kwargs["vat"] = vat.rate
return super().get_context_data(**kwargs)
@login_required
def estimate_mark_as(request, pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
entity = estimate.entity
mark = request.GET.get('mark')
if mark:
if mark == "review":
if not estimate.can_review():
messages.error(request, "Estimate is not ready for review")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_review()
elif mark == "accepted":
if not estimate.can_approve():
messages.error(request, "Estimate is not ready for approval")
return redirect("estimate_detail", pk=estimate.pk)
estimate.mark_as_approved()
elif mark == "complete":
if not estimate.can_complete():
messages.error(request, "Estimate is not ready for completion")
return redirect("estimate_detail", pk=estimate.pk)
# estimate.mark_as_completed()
invoice = entity.create_invoice(customer_model=estimate.customer,
terms=estimate.terms,
cash_account=entity.get_default_coa_accounts().get(name="Cash"),
prepaid_account=entity.get_default_coa_accounts().get(name="Accounts Receivable"),
coa_model=entity.get_default_coa()
)
unit_items = estimate.get_itemtxs_data()[0]
invoice_itemtxs = {
i.item_model.item_number: {
'unit_cost': i.ce_unit_cost_estimate,
'quantity': i.ce_quantity,
'total_amount': i.ce_cost_estimate
} for i in unit_items
}
invoice_itemtxs = invoice.migrate_itemtxs(itemtxs=invoice_itemtxs,
commit=True,
operation=InvoiceModel.ITEMIZE_APPEND)
invoice.bind_estimate(estimate)
invoice.mark_as_review()
invoice.save()
estimate.save()
return redirect("invoice_detail", pk=invoice.pk)
estimate.save()
messages.success(request, "Estimate marked as " + mark.upper())
return redirect("estimate_detail", pk=estimate.pk)
@login_required
def invoice_mark_as(request, pk):
invoice = get_object_or_404(InvoiceModel, pk=pk)
@ -1785,66 +1803,66 @@ def invoice_mark_as(request, pk):
return redirect("invoice_detail", pk=invoice.pk)
invoice.mark_as_approved(entity_slug=entity.slug,user_model=user)
invoice.save()
ledger = entity.get_ledgers().filter(name=f"Invoice {str(invoice.pk)}").first()
if not ledger:
ledger = entity.create_ledger(name=f"Invoice {str(invoice.pk)}")
ledger.invoicemodel = invoice
ledger.save()
# elif mark == "complete":
# if not invoice.can_complete():
# messages.error(request, "invoice is not ready for completion")
return redirect("invoice_detail", pk=invoice.pk)
def send_email(request,pk):
def invoice_create(request,pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
if not estimate.can_review():
messages.error(request, "Estimate is not ready for review")
return redirect("estimate_detail", pk=estimate.pk)
msg = f"""
السلام عليكم
Dear {estimate.customer.customer_name},
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached.
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
شكراً لاهتمامكم بهذا الأمر.
Thank you for your attention to this matter.
Estimate Link:
<a href="https://localhost:8888/{reverse('estimate_preview', kwargs={'pk': estimate.pk})}">View Estimate</a>
تحياتي,
Best regards,
[Your Name]
[Your Position]
[Your Company Name]
[Your Contact Information]
"""
subject = f'Estimate-{estimate.estimate_number} Review'
message = msg
from_email = 'manager@tenhal.sa'
recipient_list = ['cutomer@tenhal.sa']
send_mail(subject, message, from_email, recipient_list)
messages.success(request, "Email sent successfully!")
estimate.mark_as_review()
return redirect("estimate_detail", pk=estimate.pk)
class EstimatePreviewView(LoginRequiredMixin, DetailView):
model = EstimateModel
context_object_name = "estimate"
template_name = "sales/estimates/estimate_preview.html"
entity = request.user.dealer.entity
form = InvoiceModelCreateForm(entity_slug=entity.slug,user_model=entity.admin)
if request.method == "POST":
form = InvoiceModelCreateForm(request.POST,entity_slug=entity.slug,user_model=entity.admin)
if form.is_valid():
invoice = form.save(commit=False)
invoice_model = entity.create_invoice(
customer_model=invoice.customer,
terms=invoice.terms,
cash_account=invoice.cash_account,
prepaid_account=invoice.prepaid_account,
coa_model=entity.get_default_coa()
)
ledger = entity.create_ledger(name=f"Invoice {str(invoice_model.pk)}")
invoice_model.ledgar = ledger
ledger.invoicemodel = invoice_model
ledger.save()
invoice_model.save()
unit_items = estimate.get_itemtxs_data()[0]
invoice_itemtxs = {
i.item_model.item_number: {
'unit_cost': i.ce_unit_cost_estimate,
'quantity': i.ce_quantity,
'total_amount': i.ce_cost_estimate
} for i in unit_items
}
invoice_itemtxs = invoice_model.migrate_itemtxs(itemtxs=invoice_itemtxs,
commit=True,
operation=InvoiceModel.ITEMIZE_APPEND)
invoice_model.bind_estimate(estimate)
invoice_model.mark_as_review()
estimate.mark_as_completed()
estimate.save()
invoice_model.save()
messages.success(request, "Invoice created successfully!")
return redirect("invoice_detail", pk=invoice_model.pk)
form.initial['customer'] = estimate.customer
context = {
"form": form,
"estimate": estimate,
}
return render(request, "sales/invoices/invoice_create.html", context)
def get_context_data(self, **kwargs):
estimate = kwargs.get("object")
if estimate.get_itemtxs_data():
total = sum(x.ce_cost_estimate for x in estimate.get_itemtxs_data()[0].all())
vat = models.VatRate.objects.filter(is_active=True).first()
kwargs["vate_amount"] = (total * vat.vat_rate)
kwargs["total"] = (total * vat.vat_rate) + total
kwargs["vat"] = vat.rate
return super().get_context_data(**kwargs)
class InvoicePreviewView(LoginRequiredMixin, DetailView):
model = InvoiceModel
context_object_name = "invoice"
@ -1853,7 +1871,7 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs):
invoice = kwargs.get("object")
if invoice.get_itemtxs_data():
total = sum(x.unit_cost for x in invoice.get_itemtxs_data()[0].all())
total = sum(x.unit_cost * x.quantity for x in invoice.get_itemtxs_data()[0].all())
total = int(total)
vat = models.VatRate.objects.filter(is_active=True).first()
kwargs["vate_amount"] = (total * vat.vat_rate)
@ -1862,6 +1880,73 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
return super().get_context_data(**kwargs)
# payments
def PaymentCreateView(request, pk=None):
invoice = InvoiceModel.objects.filter(pk=pk).first()
entity = request.user.dealer.entity
form = forms.PaymentForm()
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
amount = form.cleaned_data.get("amount")
invoice = form.cleaned_data.get("invoice")
if amount > invoice.amount_due:
messages.error(request, "Payment amount is greater than invoice amount due")
return redirect("payment_create", pk=invoice.pk)
if amount <= 0:
messages.error(request, "Payment amount must be greater than 0")
return redirect("payment_create", pk=invoice.pk)
ledger = None
try:
ledger = LedgerModel.objects.filter(name=f"Invoice {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",
)
cash_account = entity.get_default_coa_accounts().get(name="Cash")
accounts_receivable = entity.get_default_coa_accounts().get(name="Accounts Receivable")
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=accounts_receivable, # Credit Accounts Receivable
amount=amount, # Payment amount
tx_type='credit',
description="Payment Received",
)
invoice.make_payment(amount)
invoice.save()
messages.success(request, "Payment created successfully!")
return redirect("invoice_detail", pk=invoice.pk)
except Exception as e:
messages.error(request, f"Error creating payment: {str(e)}")
if invoice:
form.initial["invoice"] = invoice
return render(request, "sales/payments/payment_form.html", {"invoice": invoice,"form": form})
def PaymentListView(request):
entity = request.user.dealer.entity
journals = JournalEntryModel.objects.filter(ledger__entity=entity).all()
return render(request, "sales/payments/payment_list.html", {"journals": journals})
def PaymentDetailView(request, pk):
journal = JournalEntryModel.objects.filter(pk=pk).first()
return render(request, "sales/payments/payment_details.html", {"journal": journal})
# activity log
class UserActivityLogListView(ListView):
model = models.UserActivityLog
template_name = 'dealers/activity_log.html'
@ -1875,6 +1960,38 @@ class UserActivityLogListView(ListView):
return queryset
# email
def send_email_view(request,pk):
estimate = get_object_or_404(EstimateModel, pk=pk)
if not estimate.can_review():
messages.error(request, "Estimate is not ready for review")
return redirect("estimate_detail", pk=estimate.pk)
msg = f"""
السلام عليكم
Dear {estimate.customer.customer_name},
def record_payment(request):
invoice = get_object_or_404(InvoiceModel, pk=request.POST.get('invoice'))
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
I hope this email finds you well. I wanted to share with you the estimate for the project we discussed. Please find the detailed estimate document attached.
يرجى مراجعة المقترح وإعلامي إذا كانت لديك أي أسئلة أو مخاوف. إذا كانت كل شيء يبدو جيدًا، يمكننا المضي قدمًا في المشروع.
Please review the estimate and let me know if you have any questions or concerns. If everything looks good, we can proceed with the project.
شكراً لاهتمامكم بهذا الأمر.
Thank you for your attention to this matter.
Estimate Link:
<a href="https://localhost:8888/{reverse('estimate_preview', kwargs={'pk': estimate.pk})}">View Estimate</a>
تحياتي,
Best regards,
[Your Name]
[Your Position]
[Your Company Name]
[Your Contact Information]
"""
send_email("manager@tenhal.sa", 'user@tenhal.sa',f"Estimate-{estimate.estimate_number}", msg)
estimate.mark_as_review()
messages.success(request, "Email sent successfully!")
return redirect("estimate_detail", pk=estimate.pk)

View File

@ -127,13 +127,13 @@
</a>
<!-- more inner pages-->
</li>
<li class="nav-item"><a class="nav-link" href="#">
<li class="nav-item"><a class="nav-link" href="{% url 'invoice_list' %}">
<div class="d-flex align-items-center"><span class="nav-link-icon"><span class="fas fa-file-invoice"></span></span><span class="nav-link-text">{% trans "invoices"|capfirst %}</span>
</div>
</a>
<!-- more inner pages-->
</li>
<li class="nav-item"><a class="nav-link" href="#">
<li class="nav-item"><a class="nav-link" href="{% url 'payment_list' %}">
<div class="d-flex align-items-center"><span class="nav-link-icon"><span class="fas fa-money-check"></span></span><span class="nav-link-text">{% trans "payments"|capfirst %}</span>
</div>
</a>

View File

@ -37,13 +37,13 @@
<div class="d-flex align-items-center gap-2">
{% if estimate.status == 'draft' %}
<a href="{% url 'send_email' estimate.pk %}" class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">Send Estimate</span></a>
<button id="mark_as_sent_estimate" onclick="setFormAction('review')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Mark As Sent</span></button>
<button id="mark_as_sent_estimate" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Mark As Sent</span></button>
{% endif %}
{% if estimate.status == 'in_review' %}
<button id="accept_estimate" onclick="setFormAction('accepted')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Mark As Accepted</span></button>
{% endif %}
{% if estimate.status == 'approved' %}
<button id="accept_estimate" onclick="setFormAction('completed')" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Convert</span></button>
{% if estimate.status == 'approved' %}
<a href="{% url 'invoice_create' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Create Invoice</span></a>
{% endif %}
<a href="{% url 'estimate_preview' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
</div>

View File

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load i18n %}
{% block title %}{{ _("Invoice") }}{% endblock title %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ _("Add Invoice") }}</div>
<div class="card-body">
<form method="post" action="">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -28,20 +28,50 @@
</div>
</div>
</div>
<!-- ============================================-->
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="container-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">Invoice</h2>
<div class="d-flex align-items-center gap-2">
{% if invoice.invoice_status == 'in_review' %}
<button id="accept_invoice" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Accept</span></button>
{% endif %}
<a href="{% url 'invoice_preview' invoice.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
<div class="container-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">Invoice</h2>
<div class="d-flex align-items-center gap-2">
{% if invoice.invoice_status == 'in_review' %}
<button id="accept_invoice" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">Accept</span></button>
{% endif %}
{% if invoice.invoice_status == 'approved' %}
<a href="{% url 'payment_create' invoice.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Record Payment</span></a>
{% endif %}
<a href="{% url 'invoice_preview' invoice.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
</div>
</div>
<!-- ============================================-->
<div class="card mb-5">
<div class="card-body">
<div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
<div class="col-sm-auto">
<div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center">
<div class="d-flex bg-success-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-success-dark" data-feather="dollar-sign" style="width:24px; height:24px"></span></div>
<div>
<p class="fw-bold mb-1">Paid Amount</p>
<h4 class="fw-bolder text-nowrap">${{invoice.amount_paid}}</h4>
</div>
</div>
</div>
<div class="col-sm-auto">
<div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
<div class="d-flex bg-primary-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-primary-dark" data-feather="layout" style="width:24px; height:24px"></span></div>
<div>
<p class="fw-bold mb-1">Due Amount</p>
<h4 class="fw-bolder text-nowrap">${{invoice.amount_due}} </h4>
</div>
</div>
</div>
</div>
</div>
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2">
</div>
<!-- <section> begin ============================-->
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2">
<div class="row g-4">
<div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2">
@ -98,7 +128,7 @@
</div>
</div>
</div>
</div>
</div>
<div class="px-0">
<div class="table-responsive scrollbar">
<table id="invoice-table" class="table fs-9 text-body mb-0">

View File

@ -24,20 +24,44 @@
{% for invoice in invoices %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ invoice.invoice_number }}</td>
<td class="align-middle product white-space-nowrap">{{ invoice.customer }}</td>
<td class="align-middle product white-space-nowrap">{{ invoice.invoice_status }}</td>
<td class="align-middle product white-space-nowrap">{{ invoice.date_approved }}</td>
<td class="align-middle product white-space-nowrap">{{ invoice.customer }}</td>
<td class="align-middle product white-space-nowrap text-success">
{% if invoice.invoice_status == "approved" %}
<span class="badge badge-phoenix badge-phoenix-success">{{ invoice.invoice_status }}</span>
{% elif invoice.invoice_status == "canceled" %}
<span class="badge badge-phoenix badge-phoenix-danger">{{ invoice.invoice_status }}</span>
{% elif invoice.invoice_status == "draft" %}
<span class="badge badge-phoenix badge-phoenix-warning">{{ invoice.invoice_status }}</span>
{% elif invoice.invoice_status == "in_review" %}
<span class="badge badge-phoenix badge-phoenix-info">{{ invoice.invoice_status }}</span>
{% elif invoice.invoice_status == "paid" %}
<span class="badge badge-phoenix badge-phoenix-success">{{ invoice.invoice_status }}</span>
{% endif %}
</td>
<td class="align-middle product white-space-nowrap">
{% if invoice.invoice_status == "in_review" %}
{{ invoice.date_in_review }}
{% elif invoice.invoice_status == "approved" %}
{{ invoice.date_approved }}
{% elif invoice.invoice_status == "canceled" %}
{{ invoice.date_canceled }}
{% elif invoice.invoice_status == "draft" %}
{{ invoice.date_draft }}
{% elif invoice.invoice_status == "paid" %}
{{ invoice.date_paid }}
{% endif %}
</td>
<td class="align-middle product white-space-nowrap">{{ invoice.created }}</td>
<td class="text-center">
<a href="{% url 'invoice_detail' invoice.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
{% trans "View" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Quotations Found" %}</td>
<td colspan="6" class="text-center">{% trans "No Invoice Found" %}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n static %}
{% block title %}{{ _("Create Payment") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Create Payment" %}</h3>
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
<div class="row g-3">
{{ form|crispy }}
</div>
<!-- Buttons -->
<div class="mt-4 text-center">
<button type="submit" class="btn btn-success me-2">{% trans "Save" %}</button>
<a href="{% url 'payment_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Invoices") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Invoices" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Invoice Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Customer" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Status Date" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Created" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="list">
{% for journal in journals %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ invoice.invoice_number }}</td>
<td class="align-middle product white-space-nowrap">{{ journal.timestamp }}</td>
<td class="align-middle product white-space-nowrap">{{ journal. }}</td>
<td class="align-middle product white-space-nowrap">{{ journal. }}</td>
<td class="align-middle product white-space-nowrap">{{ journal. }}</td>
<td class="text-center">
<a href="{% url 'invoice_detail' invoice.pk %}"
class="btn btn-sm btn-success">
{% trans "view" %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Quotations Found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
</div>
</div>
</div>
{% endblock %}

View File

@ -10,7 +10,7 @@
<div class="card">
<div class="card-header">{{ _("Make Payment") }}</div>
<div class="card-body">
<form method="post" action="{% url 'payment_create' pk=quotation.pk %}">
<form method="post" action="{% url 'payment_create' pk=invoice.pk %}">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>

View File

@ -0,0 +1,49 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Tranactions") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<h3 class="text-center">{% trans "Tranactions" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "#" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Name" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Account Code" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Credit" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Debit" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Actions" %}</th>
</thead>
<tbody class="list">
{% for transaction in journal.transactionmodel_set.all %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td>{{ forloop.counter }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ transaction.created|date}}</td>
<td class="align-middle product white-space-nowrap">{{ transaction.account.name }}</td>
<td class="align-middle product white-space-nowrap">{{ transaction.account.code }}</td>
<td class="align-middle product white-space-nowrap">{{ transaction.description }}</td>
<td class="align-middle product white-space-nowrap">{% if transaction.tx_type == "credit" %}${{ transaction.amount }}{% endif %}</td>
<td class="align-middle product white-space-nowrap">{% if transaction.tx_type == "debit" %}${{ transaction.amount }}{% endif %}</td>
<td class="align-middle product white-space-nowrap"></td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Tranactions Found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% load crispy_forms_filters %}
{% load static %}
{% load i18n %}
{% block title %}{{ _("Make Payment") }}{% endblock title %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ _("Make Payment") }}</div>
<div class="card-body">
{% if invoice %}
<form method="post" action="{% url 'payment_create' pk=invoice.pk %}">
{% else %}
<form method="post" action="{% url 'payment_create' %}">
{% endif %}
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ _("Payments") }}{% endblock title %}
{% block content %}
<div class="container mt-4">
<a href="{% url 'payment_create' %}" class="btn btn-sm btn-success align-right">{% trans "Add Payment" %}</a>
<h3 class="text-center">{% trans "Payments" %}</h3>
<div class="mx-n4 px-4 mx-lg-n6 px-lg-6 bg-body-emphasis pt-7 border-y">
<div class="table-responsive mx-n1 px-1 scrollbar">
<table class="table fs-9 mb-0 border-top border-translucent">
<thead>
<tr>
<th>#</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Payment Number" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Invoice" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Timestamp" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Description" %}</th>
<th class="sort white-space-nowrap align-middle" scope="col">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody class="list">
{% for journal in journals %}
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
<td class="align-middle product white-space-nowrap py-0">{{ forloop.counter }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ journal.je_number }}</td>
{% if journal.ledger.invoicemodel %}
<td class="align-middle product white-space-nowrap py-0"><a href="{% url 'invoice_detail' journal.ledger.invoicemodel.pk %}">{{ journal.ledger.invoicemodel }}</a></td>
{% else %}
<td class="align-middle product white-space-nowrap py-0"></td>
{% endif %}
<td class="align-middle product white-space-nowrap py-0">{{ journal.timestamp }}</td>
<td class="align-middle product white-space-nowrap py-0">{{ journal.description }}</td>
<td class="text-center">
<a href="{% url 'payment_details' journal.pk %}" class="btn btn-sm btn-success">{% trans "View Tranactions" %}</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">{% trans "No Payments Found" %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
</div>
</div>
</div>
{% endblock %}

43
templates/send-mail.html Normal file
View File

@ -0,0 +1,43 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{{ _("Send Mail") }}{% endblock title %}
{% block content %}
<div class="col">
<div class="card email-content">
<div class="card-body">
<form class="d-flex flex-column h-100" method="post">
{% csrf_token %}
<div class="row g-3 mb-2">
<div class="col-4">
{{form.from_email}}
</div>
<div class="col-4">
{{form.to_email}}
</div>
<div class="col-12">
{{form.subject}}
</div>
</div>
<div class="mb-3 flex-1">
{{form.message}}
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex">
<label class="btn btn-link py-0 px-2 text-body fs-9" for="emailAttachment"> <span class="fa-solid fa-paperclip"></span></label>
<input class="d-none" id="emailAttachment" type="file" />
<label class="btn btn-link py-0 px-2 text-body fs-9" for="emailPhotos"><span class="fa-solid fa-image"></span></label>
<input class="d-none" id="emailPhotos" type="file" accept="image/*" />
</div>
<div class="d-flex">
<a href="{% url 'estimate_detail' estimate.pk %}" class="btn btn-link text-body fs-10 text-decoration-none">Discard</a>
<button class="btn btn-primary fs-10" type="submit">Send<span class="fa-solid fa-paper-plane ms-1"></span></button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock content %}