diff --git a/inventory/forms.py b/inventory/forms.py index 519154fc..be5fd1ee 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -24,10 +24,13 @@ from .models import ( SaleQuotationCar, AdditionalServices, Staff, - Opportunity, DealStatus, Priority, DealSource +<<<<<<< HEAD +======= + Opportunity +>>>>>>> 8b00f9a40fc336f209f4ae6fb03785df6c97d265 ) -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 @@ -36,40 +39,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() @@ -80,7 +85,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 @@ -88,41 +101,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 ] @@ -130,15 +159,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: @@ -147,89 +183,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): @@ -238,7 +300,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 @@ -247,17 +311,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() @@ -266,22 +330,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) @@ -290,132 +366,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): @@ -430,13 +523,43 @@ 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) +<<<<<<< HEAD +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() +======= class OpportunityForm(forms.ModelForm): class Meta: model = Opportunity @@ -445,7 +568,8 @@ class OpportunityForm(forms.ModelForm): 'priority', 'source' ] widgets = { - 'deal_status': forms.Select(choices=DealStatus.choices), - 'priority': forms.Select(choices=Priority.choices), - 'source': forms.Select(choices=DealSource.choices), + 'deal_status': forms.Select(choices=Opportunity.DEAL_STATUS_CHOICES), + 'priority': forms.Select(choices=Opportunity.PRIORITY_CHOICES), + 'source': forms.Select(choices=Opportunity.DEAL_SOURCES_CHOICES), } +>>>>>>> 8b00f9a40fc336f209f4ae6fb03785df6c97d265 diff --git a/inventory/urls.py b/inventory/urls.py index b354a112..63326b25 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -133,15 +133,27 @@ urlpatterns = [ path('sales/estimates/create/', views.create_estimate, name='estimate_create'), path('sales/estimates//estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - path('send_email/', views.send_email, name='send_email'), + path('send_email//', views.send_email_view, name='send_email'), # Invoice path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), + path('sales/invoices//create/', views.invoice_create, name='invoice_create'), path('sales/invoices//', views.InvoiceDetailView.as_view(), name='invoice_detail'), path('sales/invoices//preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'), -# path('sales/estimates/create/', views.create_estimate, name='estimate_create'), path('sales/invoices//invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'), -# path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), -# path('send_email/', views.send_email, name='send_email'), + # path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), + # path('send_email/', views.send_email, name='send_email'), + + #Payment + path('sales/payments/', views.PaymentListView, name='payment_list'), + path('sales/payments//create/', views.PaymentCreateView, name='payment_create'), + path('sales/payments/create/', views.PaymentCreateView, name='payment_create'), + path('sales/payments//payment_details/', views.PaymentDetailView, name='payment_details'), + # path('sales/payments//update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), + # path('sales/payments//delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), + # path('sales/payments//preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), + # # Journal + # path('sales/journal//create/', views.JournalEntryCreateView.as_view(), name='journal_create'), + ] diff --git a/inventory/utils.py b/inventory/utils.py index 9c0a25bf..29153239 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -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 \ No newline at end of file + 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) \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 3664ac0b..9c1cbf39 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1,11 +1,13 @@ -from django.core.mail import send_mail + from django.core.paginator import Paginator from django.utils.decorators import method_decorator 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 @@ -50,7 +52,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 @@ -1583,41 +1585,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 @@ -1693,9 +1695,77 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): kwargs["vat_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" @@ -1715,66 +1785,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["vat_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) @@ -1788,66 +1806,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: - View Estimate - - تحياتي, - 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["vat_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" @@ -1856,7 +1874,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["vat_amount"] = (total * vat.vat_rate) @@ -1865,6 +1883,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' @@ -1878,9 +1963,41 @@ 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: + View Estimate + + تحياتي, + 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) def create_lead(request, pk): diff --git a/templates/header.html b/templates/header.html index a1a5af5b..400dbd75 100644 --- a/templates/header.html +++ b/templates/header.html @@ -133,7 +133,7 @@ -