update
This commit is contained in:
parent
8a6d0dc999
commit
df4048e3a1
@ -23,10 +23,9 @@ from .models import (
|
|||||||
Payment,
|
Payment,
|
||||||
SaleQuotationCar,
|
SaleQuotationCar,
|
||||||
AdditionalServices,
|
AdditionalServices,
|
||||||
Staff
|
Staff,
|
||||||
|
|
||||||
)
|
)
|
||||||
from django_ledger.models import ItemModel
|
from django_ledger.models import ItemModel, InvoiceModel
|
||||||
from django.forms import ModelMultipleChoiceField, ValidationError
|
from django.forms import ModelMultipleChoiceField, ValidationError
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
@ -35,40 +34,42 @@ from django.forms import formset_factory
|
|||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
class AdditionalServiceForm(forms.ModelForm):
|
class AdditionalServiceForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AdditionalServices
|
model = AdditionalServices
|
||||||
fields = ['name', 'price','description','taxable', 'uom']
|
fields = ["name", "price", "description", "taxable", "uom"]
|
||||||
|
|
||||||
|
|
||||||
class PaymentForm(forms.ModelForm):
|
# class PaymentForm(forms.ModelForm):
|
||||||
class Meta:
|
# invoice = forms.ModelChoiceField(queryset=InvoiceModel.objects.all(),label="Invoice", required=True)
|
||||||
model = Payment
|
# class Meta:
|
||||||
fields = ['amount','payment_method', 'reference_number']
|
# model = Payment
|
||||||
|
# fields = ['amount','payment_method', 'reference_number']
|
||||||
|
|
||||||
|
|
||||||
class StaffForm(forms.ModelForm):
|
class StaffForm(forms.ModelForm):
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
required=True,
|
required=True,
|
||||||
label="Email",
|
label="Email",
|
||||||
widget=forms.EmailInput(attrs={"class": "form-control"})
|
widget=forms.EmailInput(attrs={"class": "form-control"}),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Staff
|
model = Staff
|
||||||
fields = ['name', 'arabic_name', 'phone_number', 'staff_type']
|
fields = ["name", "arabic_name", "phone_number", "staff_type"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
user_instance = kwargs.get('instance')
|
user_instance = kwargs.get("instance")
|
||||||
if user_instance and user_instance.user:
|
if user_instance and user_instance.user:
|
||||||
initial = kwargs.setdefault('initial', {})
|
initial = kwargs.setdefault("initial", {})
|
||||||
initial['email'] = user_instance.user.email
|
initial["email"] = user_instance.user.email
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
staff_instance = super().save(commit=False)
|
staff_instance = super().save(commit=False)
|
||||||
user = staff_instance.user
|
user = staff_instance.user
|
||||||
user.email = self.cleaned_data['email']
|
user.email = self.cleaned_data["email"]
|
||||||
if commit:
|
if commit:
|
||||||
user.save()
|
user.save()
|
||||||
staff_instance.save()
|
staff_instance.save()
|
||||||
@ -79,7 +80,15 @@ class StaffForm(forms.ModelForm):
|
|||||||
class DealerForm(forms.ModelForm):
|
class DealerForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Dealer
|
model = Dealer
|
||||||
fields = ['name', 'arabic_name', 'crn', 'vrn', 'phone_number', 'address', 'logo']
|
fields = [
|
||||||
|
"name",
|
||||||
|
"arabic_name",
|
||||||
|
"crn",
|
||||||
|
"vrn",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
|
"logo",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Customer Form
|
# Customer Form
|
||||||
@ -87,41 +96,57 @@ class CustomerForm(forms.ModelForm, AddClassMixin):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = [
|
fields = [
|
||||||
'first_name', 'middle_name', 'last_name', 'email',
|
"first_name",
|
||||||
'national_id', 'phone_number', 'address'
|
"middle_name",
|
||||||
|
"last_name",
|
||||||
|
"email",
|
||||||
|
"national_id",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CarForm(forms.ModelForm, AddClassMixin, ):
|
class CarForm(
|
||||||
|
forms.ModelForm,
|
||||||
|
AddClassMixin,
|
||||||
|
):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Car
|
model = Car
|
||||||
fields = [
|
fields = [
|
||||||
'vin', 'id_car_make', 'id_car_model',
|
"vin",
|
||||||
'year', 'id_car_serie', 'id_car_trim',
|
"id_car_make",
|
||||||
'stock_type', 'remarks', 'mileage', 'receiving_date', 'vendor'
|
"id_car_model",
|
||||||
|
"year",
|
||||||
|
"id_car_serie",
|
||||||
|
"id_car_trim",
|
||||||
|
"stock_type",
|
||||||
|
"remarks",
|
||||||
|
"mileage",
|
||||||
|
"receiving_date",
|
||||||
|
"vendor",
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
|
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
||||||
'remarks': forms.Textarea(attrs={'rows': 2}),
|
"remarks": forms.Textarea(attrs={"rows": 2}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
dealer = kwargs.pop('dealer', None)
|
dealer = kwargs.pop("dealer", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
if 'id_car_make' in self.fields:
|
if "id_car_make" in self.fields:
|
||||||
queryset = self.fields['id_car_make'].queryset.filter(is_sa_import=True)
|
queryset = self.fields["id_car_make"].queryset.filter(is_sa_import=True)
|
||||||
self.fields['id_car_make'].choices = [
|
self.fields["id_car_make"].choices = [
|
||||||
(obj.id_car_make, obj.get_local_name()) for obj in queryset
|
(obj.id_car_make, obj.get_local_name()) for obj in queryset
|
||||||
]
|
]
|
||||||
if 'id_car_model' in self.fields:
|
if "id_car_model" in self.fields:
|
||||||
queryset = self.fields['id_car_model'].queryset
|
queryset = self.fields["id_car_model"].queryset
|
||||||
self.fields['id_car_model'].choices = [
|
self.fields["id_car_model"].choices = [
|
||||||
(obj.id_car_model, obj.get_local_name()) for obj in queryset
|
(obj.id_car_model, obj.get_local_name()) for obj in queryset
|
||||||
]
|
]
|
||||||
if 'vendor' in self.fields:
|
if "vendor" in self.fields:
|
||||||
queryset = self.fields['vendor'].queryset
|
queryset = self.fields["vendor"].queryset
|
||||||
self.fields['vendor'].choices = [
|
self.fields["vendor"].choices = [
|
||||||
(obj.pk, obj.get_local_name()) for obj in queryset
|
(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 CarUpdateForm(forms.ModelForm, AddClassMixin):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Car
|
model = Car
|
||||||
fields = ['vendor', 'status', 'stock_type', 'mileage', 'receiving_date', 'remarks']
|
fields = [
|
||||||
|
"vendor",
|
||||||
|
"status",
|
||||||
|
"stock_type",
|
||||||
|
"mileage",
|
||||||
|
"receiving_date",
|
||||||
|
"remarks",
|
||||||
|
]
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
'receiving_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
|
"receiving_date": forms.DateTimeInput(attrs={"type": "datetime-local"}),
|
||||||
'remarks': forms.Textarea(attrs={'rows': 2}),
|
"remarks": forms.Textarea(attrs={"rows": 2}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
dealer = kwargs.pop('dealer', None)
|
dealer = kwargs.pop("dealer", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# if dealer and 'branch' in self.fields:
|
# 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
|
# (branch.id, branch.get_local_name()) for branch in self.fields['branch'].queryset
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
if 'vendor' in self.fields:
|
if "vendor" in self.fields:
|
||||||
queryset = self.fields['vendor'].queryset
|
queryset = self.fields["vendor"].queryset
|
||||||
if queryset:
|
if queryset:
|
||||||
self.fields['vendor'].choices = [
|
self.fields["vendor"].choices = [
|
||||||
(vendor.id, vendor.get_local_name()) for vendor in queryset
|
(vendor.id, vendor.get_local_name()) for vendor in queryset
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class CarFinanceForm(AddClassMixin, forms.ModelForm):
|
class CarFinanceForm(AddClassMixin, forms.ModelForm):
|
||||||
additional_finances = forms.ModelMultipleChoiceField(
|
additional_finances = forms.ModelMultipleChoiceField(
|
||||||
queryset=AdditionalServices.objects.all(),
|
queryset=AdditionalServices.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input'}),
|
widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}),
|
||||||
required=False
|
required=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CarFinance
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.instance.pk:
|
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):
|
def save(self, commit=True):
|
||||||
instance = super().save()
|
instance = super().save()
|
||||||
instance.additional_services.set(self.cleaned_data['additional_finances'])
|
instance.additional_services.set(self.cleaned_data["additional_finances"])
|
||||||
instance.save()
|
instance.save()
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class CarLocationForm(forms.ModelForm):
|
class CarLocationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CarLocation
|
model = CarLocation
|
||||||
fields = ['showroom', 'description']
|
fields = ["showroom", "description"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'description': forms.Textarea(attrs={'rows': 2, 'class': 'form-control'}),
|
"description": forms.Textarea(attrs={"rows": 2, "class": "form-control"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Custom Card Form
|
# Custom Card Form
|
||||||
class CustomCardForm(forms.ModelForm):
|
class CustomCardForm(forms.ModelForm):
|
||||||
custom_date = forms.DateTimeField(
|
custom_date = forms.DateTimeField(
|
||||||
widget=forms.DateInput(attrs={'type': 'date'}),
|
widget=forms.DateInput(attrs={"type": "date"}),
|
||||||
label=_("Custom Date"),
|
label=_("Custom Date"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomCard
|
model = CustomCard
|
||||||
fields = ['custom_number', 'custom_date']
|
fields = ["custom_number", "custom_date"]
|
||||||
|
|
||||||
|
|
||||||
# Car Registration Form
|
# Car Registration Form
|
||||||
class CarRegistrationForm(forms.ModelForm):
|
class CarRegistrationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CarRegistration
|
model = CarRegistration
|
||||||
fields = [
|
fields = ["car", "plate_number", "text1", "text2", "text3", "registration_date"]
|
||||||
'car', 'plate_number', 'text1', 'text2', 'text3', 'registration_date'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class VendorForm(forms.ModelForm):
|
class VendorForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Vendor
|
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 CarColorsForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CarColors
|
model = CarColors
|
||||||
fields = ['exterior', 'interior']
|
fields = ["exterior", "interior"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['exterior'].queryset = ExteriorColors.objects.all()
|
self.fields["exterior"].queryset = ExteriorColors.objects.all()
|
||||||
self.fields['exterior'].widget = forms.RadioSelect(attrs={'class': 'form-check-input'})
|
self.fields["exterior"].widget = forms.RadioSelect(
|
||||||
self.fields['exterior'].choices = [
|
attrs={"class": "form-check-input"}
|
||||||
(color.id, f"{color.get_local_name}") for color in ExteriorColors.objects.all().order_by('-name')
|
)
|
||||||
|
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"].queryset = InteriorColors.objects.all()
|
||||||
self.fields['interior'].widget = forms.RadioSelect(attrs={'class': 'form-check-input'})
|
self.fields["interior"].widget = forms.RadioSelect(
|
||||||
self.fields['interior'].choices = [
|
attrs={"class": "form-check-input"}
|
||||||
(color.id, f"{color.get_local_name}") for color in InteriorColors.objects.all().order_by('-name')
|
)
|
||||||
|
self.fields["interior"].choices = [
|
||||||
|
(color.id, f"{color.get_local_name}")
|
||||||
|
for color in InteriorColors.objects.all().order_by("-name")
|
||||||
]
|
]
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@ -237,7 +295,9 @@ class CarColorsForm(forms.ModelForm):
|
|||||||
interior = cleaned_data.get("interior")
|
interior = cleaned_data.get("interior")
|
||||||
|
|
||||||
if not exterior or not 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
|
return cleaned_data
|
||||||
|
|
||||||
@ -246,17 +306,17 @@ class QuotationForm(forms.ModelForm):
|
|||||||
cars = ModelMultipleChoiceField(
|
cars = ModelMultipleChoiceField(
|
||||||
queryset=Car.objects.none(), # Default empty queryset
|
queryset=Car.objects.none(), # Default empty queryset
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
label="Select Cars"
|
label="Select Cars",
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SaleQuotation
|
model = SaleQuotation
|
||||||
fields = ['customer', 'cars', 'remarks']
|
fields = ["customer", "cars", "remarks"]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields['cars'].queryset = Car.objects.filter(
|
self.fields["cars"].queryset = Car.objects.filter(
|
||||||
finances__isnull=False
|
finances__isnull=False
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@ -265,22 +325,34 @@ class OrganizationForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Organization
|
model = Organization
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'arabic_name', 'crn', 'vrn',
|
"name",
|
||||||
'phone_number', 'address', 'logo',
|
"arabic_name",
|
||||||
|
"crn",
|
||||||
|
"vrn",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
|
"logo",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
dealer = kwargs.pop('dealer', None)
|
dealer = kwargs.pop("dealer", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class RepresentativeForm(forms.ModelForm):
|
class RepresentativeForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Representative
|
model = Representative
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'arabic_name', 'id_number',
|
"name",
|
||||||
'phone_number', 'address', 'organization'
|
"arabic_name",
|
||||||
|
"id_number",
|
||||||
|
"phone_number",
|
||||||
|
"address",
|
||||||
|
"organization",
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
dealer = kwargs.pop('dealer', None)
|
dealer = kwargs.pop("dealer", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -289,132 +361,149 @@ class CarSelectionTable(tables.Table):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Car
|
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"
|
template_name = "django_tables2/bootstrap4.html"
|
||||||
|
|
||||||
|
|
||||||
class WizardForm1(forms.Form):
|
class WizardForm1(forms.Form):
|
||||||
|
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
widget=forms.EmailInput(attrs={
|
widget=forms.EmailInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': 'Email address',
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": "Email address",
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('You must add an email.'),
|
"required": _("You must add an email."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
password = forms.CharField(
|
password = forms.CharField(
|
||||||
widget=forms.PasswordInput(attrs={
|
widget=forms.PasswordInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _('Password'),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("Password"),
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('This field is required.'),
|
"required": _("This field is required."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
confirm_password = forms.CharField(
|
confirm_password = forms.CharField(
|
||||||
widget=forms.PasswordInput(attrs={
|
widget=forms.PasswordInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _('Confirm Password'),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("Confirm Password"),
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('This field is required.'),
|
"required": _("This field is required."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
terms = forms.BooleanField(
|
terms = forms.BooleanField(
|
||||||
|
widget=forms.CheckboxInput(
|
||||||
widget=forms.CheckboxInput(attrs={
|
attrs={
|
||||||
'class': 'form-check-input',
|
"class": "form-check-input",
|
||||||
'required': 'required',
|
"required": "required",
|
||||||
|
}
|
||||||
}),
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('You must accept the terms and privacy policy.'),
|
"required": _("You must accept the terms and privacy policy."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WizardForm2(forms.Form):
|
class WizardForm2(forms.Form):
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _('English Name'),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("English Name"),
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('Please enter an English Name.'),
|
"required": _("Please enter an English Name."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
arabic_name = forms.CharField(
|
arabic_name = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _('Arabic Name'),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("Arabic Name"),
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('Please enter an Arabic name.'),
|
"required": _("Please enter an Arabic name."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
phone_number = PhoneNumberField(
|
phone_number = PhoneNumberField(
|
||||||
min_length=10,
|
min_length=10,
|
||||||
max_length=10,
|
max_length=10,
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _('Phone'),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("Phone"),
|
||||||
}),
|
"required": "required",
|
||||||
region='SA',
|
}
|
||||||
|
),
|
||||||
|
region="SA",
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('This field is required.'),
|
"required": _("This field is required."),
|
||||||
'invalid': _('Phone number must be in the format 05xxxxxxxx'),
|
"invalid": _("Phone number must be in the format 05xxxxxxxx"),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WizardForm3(forms.Form):
|
class WizardForm3(forms.Form):
|
||||||
# CRN field with max length of 10
|
# CRN field with max length of 10
|
||||||
crn = forms.CharField(
|
crn = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _("Commercial Registration Number"),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("Commercial Registration Number"),
|
||||||
'maxlength': '10',
|
"required": "required",
|
||||||
}),
|
"maxlength": "10",
|
||||||
|
}
|
||||||
|
),
|
||||||
max_length=10,
|
max_length=10,
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': 'This field is required.',
|
"required": "This field is required.",
|
||||||
'max_length': 'Commercial Registration Number must be 10 characters.',
|
"max_length": "Commercial Registration Number must be 10 characters.",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# VRN field with max length of 15
|
# VRN field with max length of 15
|
||||||
vrn = forms.CharField(
|
vrn = forms.CharField(
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'placeholder': _("VAT Registration Number"),
|
"class": "form-control",
|
||||||
'required': 'required',
|
"placeholder": _("VAT Registration Number"),
|
||||||
'maxlength': '15',
|
"required": "required",
|
||||||
}),
|
"maxlength": "15",
|
||||||
|
}
|
||||||
|
),
|
||||||
max_length=15, #
|
max_length=15, #
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('This field is required.'),
|
"required": _("This field is required."),
|
||||||
'max_length': _('VAT Registration Number must be 15 characters.'),
|
"max_length": _("VAT Registration Number must be 15 characters."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
address = forms.CharField(
|
address = forms.CharField(
|
||||||
widget=forms.Textarea(attrs={
|
widget=forms.Textarea(
|
||||||
'class': 'form-control',
|
attrs={
|
||||||
'rows': '3',
|
"class": "form-control",
|
||||||
'required': 'required',
|
"rows": "3",
|
||||||
}),
|
"required": "required",
|
||||||
|
}
|
||||||
|
),
|
||||||
error_messages={
|
error_messages={
|
||||||
'required': _('This field is required.'),
|
"required": _("This field is required."),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@ -429,8 +518,38 @@ class WizardForm3(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class ItemForm(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)
|
quantity = forms.DecimalField(label="Quantity", required=True)
|
||||||
unit = forms.DecimalField(label="Unit", required=True)
|
unit = forms.DecimalField(label="Unit", required=True)
|
||||||
unit_cost = forms.DecimalField(label="Unit Cost", 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()
|
||||||
@ -126,15 +126,27 @@ urlpatterns = [
|
|||||||
path('sales/estimates/create/', views.create_estimate, name='estimate_create'),
|
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>/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('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
|
# Invoice
|
||||||
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
|
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>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
|
||||||
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'),
|
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/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('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, 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'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from inventory import models
|
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
|
from inventory.utilities.financials import get_financial_value
|
||||||
|
|
||||||
|
|
||||||
@ -49,3 +50,11 @@ def get_calculations(quotation):
|
|||||||
context["total_vat"] += k["vated"]
|
context["total_vat"] += k["vated"]
|
||||||
context["total_cost_vat"] = float(context["total_cost"])+float(context["total_vat"])
|
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)
|
||||||
@ -1,10 +1,12 @@
|
|||||||
from django.core.mail import send_mail
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
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.bank_account import BankAccountCreateForm,BankAccountUpdateForm
|
||||||
from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm
|
from django_ledger.forms.account import AccountModelCreateForm,AccountModelUpdateForm
|
||||||
from django_ledger.forms.estimate import EstimateModelCreateForm
|
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_ledger.io import roles
|
||||||
from django.contrib.admin.models import LogEntry
|
from django.contrib.admin.models import LogEntry
|
||||||
import logging
|
import logging
|
||||||
@ -48,7 +50,7 @@ from . import models, forms
|
|||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.contrib.auth.models import Group
|
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 django.contrib.auth.models import User
|
||||||
from allauth.account import views
|
from allauth.account import views
|
||||||
from django.db.models import Count, F, Value
|
from django.db.models import Count, F, Value
|
||||||
@ -1581,41 +1583,41 @@ def account_delete(request, pk):
|
|||||||
return redirect("account_list")
|
return redirect("account_list")
|
||||||
return render(request, "ledger/coa_accounts/account_delete.html", {"account": account})
|
return render(request, "ledger/coa_accounts/account_delete.html", {"account": account})
|
||||||
|
|
||||||
#Estimates
|
|
||||||
|
|
||||||
|
#Estimates
|
||||||
class EstimateListView(LoginRequiredMixin, ListView):
|
class EstimateListView(LoginRequiredMixin, ListView):
|
||||||
model = EstimateModel
|
model = EstimateModel
|
||||||
template_name = "sales/estimates/estimate_list.html"
|
template_name = "sales/estimates/estimate_list.html"
|
||||||
context_object_name = "estimates"
|
context_object_name = "estimates"
|
||||||
|
|
||||||
class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
# class EstimateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||||
model = EstimateModel
|
# model = EstimateModel
|
||||||
form_class = EstimateModelCreateForm
|
# form_class = EstimateModelCreateForm
|
||||||
template_name = "sales/estimates/estimate_form.html"
|
# template_name = "sales/estimates/estimate_form.html"
|
||||||
success_url = reverse_lazy("estimate_list")
|
# success_url = reverse_lazy("estimate_list")
|
||||||
success_message = "Estimate created successfully."
|
# success_message = "Estimate created successfully."
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
# def get_form_kwargs(self):
|
||||||
"""
|
# """
|
||||||
Override this method to pass additional keyword arguments to the form.
|
# Override this method to pass additional keyword arguments to the form.
|
||||||
"""
|
# """
|
||||||
entity = self.request.user.dealer.entity
|
# entity = self.request.user.dealer.entity
|
||||||
kwargs = super().get_form_kwargs()
|
# kwargs = super().get_form_kwargs()
|
||||||
kwargs['entity_slug'] = entity.slug
|
# kwargs['entity_slug'] = entity.slug
|
||||||
kwargs['user_model'] = entity.admin
|
# kwargs['user_model'] = entity.admin
|
||||||
return kwargs
|
# return kwargs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
# def get_context_data(self, **kwargs):
|
||||||
entity = self.request.user.dealer.entity
|
# entity = self.request.user.dealer.entity
|
||||||
kwargs['items'] = entity.get_items_all()
|
# kwargs['items'] = entity.get_items_all()
|
||||||
return super().get_context_data(**kwargs)
|
# return super().get_context_data(**kwargs)
|
||||||
def get_customer_queryset(self):
|
# def get_customer_queryset(self):
|
||||||
entity = self.request.user.dealer.entity
|
# entity = self.request.user.dealer.entity
|
||||||
return entity.get_customer_queryset()
|
# return entity.get_customer_queryset()
|
||||||
|
|
||||||
def form_valid(self, form):
|
# def form_valid(self, form):
|
||||||
form.instance.entity = self.request.user.dealer.entity
|
# form.instance.entity = self.request.user.dealer.entity
|
||||||
return super().form_valid(form)
|
# return super().form_valid(form)
|
||||||
|
|
||||||
# @csrf_exempt
|
# @csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
@ -1690,9 +1692,77 @@ class EstimateDetailView(LoginRequiredMixin, DetailView):
|
|||||||
kwargs["vate_amount"] = (total * vat.vat_rate)
|
kwargs["vate_amount"] = (total * vat.vat_rate)
|
||||||
kwargs["total"] = (total * vat.vat_rate) + total
|
kwargs["total"] = (total * vat.vat_rate) + total
|
||||||
kwargs["vat"] = vat.rate
|
kwargs["vat"] = vat.rate
|
||||||
|
kwargs["invoice"] = InvoiceModel.objects.all().filter(ce_model=estimate).first()
|
||||||
return super().get_context_data(**kwargs)
|
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):
|
class InvoiceListView(LoginRequiredMixin, ListView):
|
||||||
model = InvoiceModel
|
model = InvoiceModel
|
||||||
template_name = "sales/invoices/invoice_list.html"
|
template_name = "sales/invoices/invoice_list.html"
|
||||||
@ -1712,66 +1782,14 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||||||
invoice = kwargs.get("object")
|
invoice = kwargs.get("object")
|
||||||
|
|
||||||
if invoice.get_itemtxs_data():
|
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)
|
total = int(total)
|
||||||
vat = models.VatRate.objects.filter(is_active=True).first()
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
||||||
kwargs["vate_amount"] = (total * int(vat.vat_rate))
|
kwargs["vat_amount"] = (total * vat.vat_rate)
|
||||||
kwargs["total"] = (total * int(vat.vat_rate)) + total
|
kwargs["total"] = (total * vat.vat_rate) + total
|
||||||
kwargs["vat"] = vat.rate
|
kwargs["vat"] = vat.rate
|
||||||
return super().get_context_data(**kwargs)
|
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
|
@login_required
|
||||||
def invoice_mark_as(request, pk):
|
def invoice_mark_as(request, pk):
|
||||||
invoice = get_object_or_404(InvoiceModel, pk=pk)
|
invoice = get_object_or_404(InvoiceModel, pk=pk)
|
||||||
@ -1785,65 +1803,65 @@ def invoice_mark_as(request, pk):
|
|||||||
return redirect("invoice_detail", pk=invoice.pk)
|
return redirect("invoice_detail", pk=invoice.pk)
|
||||||
invoice.mark_as_approved(entity_slug=entity.slug,user_model=user)
|
invoice.mark_as_approved(entity_slug=entity.slug,user_model=user)
|
||||||
invoice.save()
|
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":
|
# elif mark == "complete":
|
||||||
# if not invoice.can_complete():
|
# if not invoice.can_complete():
|
||||||
# messages.error(request, "invoice is not ready for completion")
|
# messages.error(request, "invoice is not ready for completion")
|
||||||
return redirect("invoice_detail", pk=invoice.pk)
|
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)
|
estimate = get_object_or_404(EstimateModel, pk=pk)
|
||||||
if not estimate.can_review():
|
entity = request.user.dealer.entity
|
||||||
messages.error(request, "Estimate is not ready for review")
|
|
||||||
return redirect("estimate_detail", pk=estimate.pk)
|
|
||||||
msg = f"""
|
|
||||||
السلام عليكم
|
|
||||||
Dear {estimate.customer.customer_name},
|
|
||||||
|
|
||||||
أود أن أشارككم تقدير المشروع الذي ناقشناه. يرجى العثور على الوثيقة التفصيلية للمقترح المرفقة.
|
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()
|
||||||
|
|
||||||
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.
|
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)
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
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):
|
class InvoicePreviewView(LoginRequiredMixin, DetailView):
|
||||||
model = InvoiceModel
|
model = InvoiceModel
|
||||||
@ -1853,7 +1871,7 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
invoice = kwargs.get("object")
|
invoice = kwargs.get("object")
|
||||||
if invoice.get_itemtxs_data():
|
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)
|
total = int(total)
|
||||||
vat = models.VatRate.objects.filter(is_active=True).first()
|
vat = models.VatRate.objects.filter(is_active=True).first()
|
||||||
kwargs["vate_amount"] = (total * vat.vat_rate)
|
kwargs["vate_amount"] = (total * vat.vat_rate)
|
||||||
@ -1862,6 +1880,73 @@ class InvoicePreviewView(LoginRequiredMixin, DetailView):
|
|||||||
return super().get_context_data(**kwargs)
|
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):
|
class UserActivityLogListView(ListView):
|
||||||
model = models.UserActivityLog
|
model = models.UserActivityLog
|
||||||
template_name = 'dealers/activity_log.html'
|
template_name = 'dealers/activity_log.html'
|
||||||
@ -1875,6 +1960,38 @@ class UserActivityLogListView(ListView):
|
|||||||
return queryset
|
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)
|
||||||
@ -127,13 +127,13 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- more inner pages-->
|
<!-- more inner pages-->
|
||||||
</li>
|
</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 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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<!-- more inner pages-->
|
<!-- more inner pages-->
|
||||||
</li>
|
</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 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>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -37,13 +37,13 @@
|
|||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
{% if estimate.status == 'draft' %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
{% if estimate.status == 'in_review' %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
{% if estimate.status == 'approved' %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
<a href="{% url 'estimate_preview' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
|
<a href="{% url 'estimate_preview' estimate.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
24
templates/sales/invoices/invoice_create.html
Normal file
24
templates/sales/invoices/invoice_create.html
Normal 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 %}
|
||||||
|
|
||||||
@ -31,17 +31,47 @@
|
|||||||
<!-- ============================================-->
|
<!-- ============================================-->
|
||||||
<!-- <section> begin ============================-->
|
<!-- <section> begin ============================-->
|
||||||
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
|
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
|
||||||
<div class="container-small mt-3">
|
<div class="container-small mt-3">
|
||||||
<div class="d-flex justify-content-between align-items-end mb-4">
|
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||||
<h2 class="mb-0">Invoice</h2>
|
<h2 class="mb-0">Invoice</h2>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
{% if invoice.invoice_status == 'in_review' %}
|
{% 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>
|
<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 %}
|
{% endif %}
|
||||||
<a href="{% url 'invoice_preview' invoice.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">Preview</span></a>
|
{% 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>
|
</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="row g-4">
|
||||||
<div class="col-12 col-lg-3">
|
<div class="col-12 col-lg-3">
|
||||||
<div class="row g-4 g-lg-2">
|
<div class="row g-4 g-lg-2">
|
||||||
|
|||||||
@ -25,19 +25,43 @@
|
|||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<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 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.customer }}</td>
|
||||||
<td class="align-middle product white-space-nowrap">{{ invoice.invoice_status }}</td>
|
<td class="align-middle product white-space-nowrap text-success">
|
||||||
<td class="align-middle product white-space-nowrap">{{ invoice.date_approved }}</td>
|
{% 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="align-middle product white-space-nowrap">{{ invoice.created }}</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<a href="{% url 'invoice_detail' invoice.pk %}"
|
<a href="{% url 'invoice_detail' invoice.pk %}"
|
||||||
class="btn btn-sm btn-success">
|
class="btn btn-sm btn-success">
|
||||||
{% trans "view" %}
|
{% trans "View" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">{% trans "No Quotations Found" %}</td>
|
<td colspan="6" class="text-center">{% trans "No Invoice Found" %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
23
templates/sales/journals/journal_form.html
Normal file
23
templates/sales/journals/journal_form.html
Normal 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 %}
|
||||||
50
templates/sales/journals/journal_list.html
Normal file
50
templates/sales/journals/journal_list.html
Normal 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 %}
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">{{ _("Make Payment") }}</div>
|
<div class="card-header">{{ _("Make Payment") }}</div>
|
||||||
<div class="card-body">
|
<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 %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
|
<button type="submit" class="btn btn-primary">{% trans 'Save' %}</button>
|
||||||
|
|||||||
49
templates/sales/payments/payment_details.html
Normal file
49
templates/sales/payments/payment_details.html
Normal 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 %}
|
||||||
28
templates/sales/payments/payment_form.html
Normal file
28
templates/sales/payments/payment_form.html
Normal 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 %}
|
||||||
|
|
||||||
52
templates/sales/payments/payment_list.html
Normal file
52
templates/sales/payments/payment_list.html
Normal 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
43
templates/send-mail.html
Normal 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 %}
|
||||||
Loading…
x
Reference in New Issue
Block a user