diff --git a/inventory/forms.py b/inventory/forms.py index 79d2889e..6e75333a 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -1,5 +1,5 @@ from django.core.cache import cache -from django.contrib.auth.models import Permission +from django.contrib.auth.models import Permission from django.contrib.auth.models import Group from appointment.models import Appointment, Service, StaffMember from django.urls import reverse @@ -39,14 +39,14 @@ from .models import ( InteriorColors, # SaleQuotation, CarLocation, - + Representative, - + # SaleQuotationCar, AdditionalServices, Staff, Opportunity, - + Lead, Activity, Notes, @@ -56,7 +56,7 @@ from .models import ( DealerSettings ) from django_ledger import models as ledger_models -from django.forms import ( +from django.forms import ( DateInput, DateTimeInput, ) @@ -86,7 +86,7 @@ class StaffForm(forms.ModelForm): label="Email", widget=forms.EmailInput(attrs={"class": "form-control form-control-sm"}), ) - + service_offered = forms.ModelMultipleChoiceField( label="Services Offered", widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check-input"}), @@ -130,22 +130,17 @@ class DealerForm(forms.ModelForm): class CustomerForm(forms.Form): first_name = forms.CharField() - middle_name = forms.CharField() last_name = forms.CharField() - national_id = forms.CharField(max_length=10) - email = forms.EmailField() - phone_number = PhoneNumberField(region="SA") - address = forms.CharField() - - -class OrganizationForm(forms.Form): - name = forms.CharField() arabic_name = forms.CharField() email = forms.EmailField() phone_number = PhoneNumberField(region="SA") - crn = forms.CharField() - vrn = forms.CharField() + national_id = forms.CharField(max_length=10,required=False) + crn = forms.CharField(required=False) + vrn = forms.CharField(required=False) address = forms.CharField() + + +class OrganizationForm(CustomerForm): contact_person = forms.CharField(required=False) logo = forms.ImageField(required=False) @@ -448,7 +443,7 @@ class CarSelectionTable(tables.Table): class WizardForm1(forms.Form): - hx_attrs = { + hx_attrs = { "hx-post":"", "hx-target": "#wizardValidationForm1", "hx-select": "#wizardValidationForm1", @@ -516,10 +511,10 @@ class WizardForm1(forms.Form): ), error_messages={ "required": _("You must accept the terms and privacy policy."), - }, + }, ) - - + + def clean_email(self): email = self.cleaned_data.get("email") if email: @@ -528,7 +523,7 @@ class WizardForm1(forms.Form): _("An account with this email already exists.") ) return email - + def clean_confirm_password(self): password = self.cleaned_data.get("password") confirm_password = self.cleaned_data.get("confirm_password") @@ -569,7 +564,7 @@ class WizardForm2(forms.Form): phone_number = PhoneNumberField( label=_("Phone Number"), widget=forms.TextInput( - attrs={ + attrs={ "placeholder": _("Phone"), } ), @@ -578,7 +573,7 @@ class WizardForm2(forms.Form): "required": _("This field is required."), "invalid": _("Phone number must be in the format 05xxxxxxxx"), }, - required=True, + required=True, ) @@ -741,6 +736,8 @@ class LeadForm(forms.ModelForm): "address", "id_car_make", "id_car_model", + "crn", + "vrn", "year", "source", "channel", @@ -761,7 +758,7 @@ class LeadForm(forms.ModelForm): class ScheduleForm(forms.ModelForm): scheduled_at = forms.DateTimeField( widget=DateTimeInput(attrs={"type": "datetime-local"}) - ) + ) class Meta: model = Schedule fields = ["purpose", "scheduled_type", "scheduled_at", "duration", "notes",] @@ -788,7 +785,7 @@ class OpportunityForm(forms.ModelForm): class InvoiceModelCreateForm(InvoiceModelCreateFormBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self.fields["cash_account"].widget = forms.HiddenInput() self.fields["prepaid_account"].widget = forms.HiddenInput() self.fields["unearned_account"].widget = forms.HiddenInput() @@ -806,7 +803,7 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase): class BillModelCreateForm(BillModelCreateFormBase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + self.fields["cash_account"].widget = forms.HiddenInput() self.fields["prepaid_account"].widget = forms.HiddenInput() self.fields["unearned_account"].widget = forms.HiddenInput() @@ -877,8 +874,8 @@ class OpportunityStatusForm(forms.Form): "hx-select": ".other-information", "hx-swap": "outerHTML", "hx-on::after-request": "this.setAttribute('disabled','true')", - "disabled": "disabled", - } + "disabled": "disabled", + } ), required=True, ) @@ -887,7 +884,7 @@ class OpportunityStatusForm(forms.Form): choices=Stage.choices, widget=forms.Select( attrs={ - "class": "form-control form-control-sm", + "class": "form-control form-control-sm", "hx-target": ".other-information", "hx-select": ".other-information", "hx-swap": "outerHTML", @@ -895,9 +892,9 @@ class OpportunityStatusForm(forms.Form): "disabled": "disabled", } ), - required=True, - ) - + required=True, + ) + class GroupForm(forms.ModelForm): class Meta: model = CustomGroup @@ -912,11 +909,11 @@ class PermissionForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) cache.set('permissions_queryset', Permission.objects.filter(content_type__app_label__in=["inventory","django_ledger"]), 60*60) - + class Meta: model = Permission fields = ["name"] - + class UserGroupForm(forms.ModelForm): name = forms.ModelMultipleChoiceField( queryset= CustomGroup.objects.all(), @@ -927,10 +924,10 @@ class UserGroupForm(forms.ModelForm): model = CustomGroup fields = ["name"] -class DealerSettingsForm(forms.ModelForm): +class DealerSettingsForm(forms.ModelForm): class Meta: model = DealerSettings fields = "__all__" class LeadTransferForm(forms.Form): - transfer_to = forms.ModelChoiceField(label="to",queryset=Staff.objects.all()) \ No newline at end of file + transfer_to = forms.ModelChoiceField(label="to",queryset=Staff.objects.all()) \ No newline at end of file diff --git a/inventory/migrations/0053_lead_crn_lead_vrn.py b/inventory/migrations/0053_lead_crn_lead_vrn.py new file mode 100644 index 00000000..baed021a --- /dev/null +++ b/inventory/migrations/0053_lead_crn_lead_vrn.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.17 on 2025-03-01 21:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0052_lead_lead_type'), + ] + + operations = [ + migrations.AddField( + model_name='lead', + name='crn', + field=models.CharField(blank=True, max_length=10, null=True, unique=True, verbose_name='Commercial Registration Number'), + ), + migrations.AddField( + model_name='lead', + name='vrn', + field=models.CharField(blank=True, max_length=15, null=True, unique=True, verbose_name='VAT Registration Number'), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index 78492a91..4736bae7 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -454,14 +454,14 @@ class Car(models.Model): self.status = CarStatusChoices.SOLD self.save() Activity.objects.create(dealer=dealer,content_object=self, notes="Car Sold",created_by=request.user,activity_type=ActionChoices.SALE_CAR) - - def cancel_reservation(self): + + def cancel_reservation(self): if self.reservations.exists(): self.reservations.all().delete() def cancel_transfer(self): if self.transfer_logs.exists(): self.transfer_logs.all().delete() - + def to_dict(self): return { "vin": self.vin, @@ -580,9 +580,9 @@ class CarFinance(models.Model): verbose_name=_("Discount Amount"), default=Decimal("0.00"), ) - + @property - def total(self): + def total(self): return self.selling_price @property @@ -947,7 +947,7 @@ class StaffTypes(models.TextChoices): class Staff(models.Model, LocalizedNameMixin): staff_member = models.OneToOneField(StaffMember, on_delete=models.CASCADE, related_name="staff") - dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="staff") + dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="staff") name = models.CharField(max_length=255, verbose_name=_("Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) phone_number = PhoneNumberField(region="SA", verbose_name=_("Phone Number")) @@ -957,23 +957,26 @@ class Staff(models.Model, LocalizedNameMixin): objects = StaffUserManager() + @property + def email(self): + return self.staff_member.user.email @property def user(self): return self.staff_member.user - + @property - def groups(self): + def groups(self): return [x.customgroup for x in self.user.groups.all()] - - + + def clear_groups(self): return self.user.groups.clear() - + def add_group(self,group): try: self.user.groups.add(group) except Exception as e: - pass + pass class Meta: verbose_name = _("Staff") verbose_name_plural = _("Staff") @@ -1197,6 +1200,12 @@ class Lead(models.Model): channel = models.CharField( max_length=50, choices=Channel.choices, verbose_name=_("Channel") ) + crn = models.CharField( + max_length=10, unique=True, verbose_name=_("Commercial Registration Number"), blank=True, null=True + ) + vrn = models.CharField( + max_length=15, unique=True, verbose_name=_("VAT Registration Number"), blank=True, null=True + ) address = models.CharField(max_length=50, verbose_name=_("address")) staff = models.ForeignKey( Staff, @@ -1251,7 +1260,7 @@ class Lead(models.Model): @property def full_name(self): return f"{self.first_name} {self.last_name}" - def convert_to_customer(self,entity): + def convert_to_customer(self,entity,lead): customer = entity.get_customers().filter(email=self.email).first() if entity and not customer: customer = entity.create_customer( @@ -1262,10 +1271,13 @@ class Lead(models.Model): "phone": self.phone_number, "email": self.email, } - ) - + ) + customer.additional_info.update({"info":self.to_dict()}) - customer.additional_info.update({"type":"customer"}) + if lead.lead_type == "organization": + customer.additional_info.update({"type":"organization"}) + else: + customer.additional_info.update({"type":"customer"}) customer.save() self.customer = customer self.status = Status.QUALIFIED @@ -1415,7 +1427,7 @@ class Email(models.Model): from_email = models.TextField(verbose_name=_("From Email"),null=True,blank=True) to_email = models.TextField(verbose_name=_("To Email"),null=True,blank=True) subject = models.TextField(verbose_name=_("Subject"),null=True,blank=True) - message = models.TextField(verbose_name=_("Message"),null=True,blank=True) + message = models.TextField(verbose_name=_("Message"),null=True,blank=True) status = models.CharField(max_length=20, choices=EmailStatus.choices, verbose_name=_("Status"),default=EmailStatus.OPEN) created_by = models.ForeignKey( User, on_delete=models.DO_NOTHING, related_name="emails_created" @@ -1749,7 +1761,7 @@ class UserActivityLog(models.Model): def __str__(self): return f"{self.user.email} - {self.action} - {self.timestamp}" -class SaleOrder(models.Model): +class SaleOrder(models.Model): estimate = models.ForeignKey( EstimateModel, on_delete=models.CASCADE, @@ -1772,12 +1784,12 @@ class SaleOrder(models.Model): comments = models.TextField(blank=True, null=True) formatted_order_id = models.CharField(max_length=10, unique=True, editable=False) created = models.DateTimeField(auto_now_add=True) - + class Meta: ordering = ['-created'] - + def save(self, *args, **kwargs): - if not self.formatted_order_id: + if not self.formatted_order_id: last_order = SaleOrder.objects.order_by('-id').first() if last_order: next_id = last_order.id + 1 @@ -1792,17 +1804,17 @@ class SaleOrder(models.Model): @property def full_name(self): return f"{self.customer.customer_name}" - + @property def price(self): return self.car.finances.selling_price - + @property def items(self): if self.estimate.get_itemtxs_data(): return self.estimate.get_itemtxs_data()[0] return [] - + @property def customer(self): return self.estimate.customer @@ -1811,27 +1823,27 @@ class CustomGroup(models.Model): name = models.CharField(max_length=100) dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="groups") group = models.OneToOneField("auth.Group", verbose_name=_(""), on_delete=models.CASCADE) - + @property def users(self): return self.group.user_set.all() - + @property def permissions(self): return self.group.permissions.all() - + def clear_permissions(self): self.group.permissions.clear() - + def add_permission(self, permission): try: self.group.permissions.add(permission) except Permission.DoesNotExist: - pass - + pass + def __str__(self): return self.name - + def set_default_manager_permissions(self): self.clear_permissions() try: @@ -1839,7 +1851,7 @@ class CustomGroup(models.Model): self.add_permission(perm) except Exception as e: pass - + # def set_default_inventory_permissions(self): # self.clear_permissions() # allowed_models = ["car","carequipment","interiorcolors","exteriorcolors","carcolors","carlocation","customcard"] @@ -1849,14 +1861,14 @@ class CustomGroup(models.Model): # allowed_models = ["car","carfinance","carlocation","customcard"] # allowed_models_ledger = ["accountmodel","chartofaccountmodel","customcard","billmodel"] # self.set_permissions(allowed_models=allowed_models,other_perms=['view_carfinance']) - # self.set_permissions(app="django_ledger",allowed_models=allowed_models_ledger) - + # self.set_permissions(app="django_ledger",allowed_models=allowed_models_ledger) + def set_default_permissions(self): self.clear_permissions() if self.name == "Manager": self.set_permissions(app="inventory",allowed_models=["car","carfinance","carlocation","customcard"]) self.set_permissions(app="django_ledger",allowed_models=["accountmodel","chartofaccountmodel","customcard","billmodel"]) - elif self.name == "Inventory": + elif self.name == "Inventory": self.set_permissions(allowed_models=["car","carequipment","interiorcolors","exteriorcolors","carcolors","carlocation","customcard"] ,other_perms=['view_carfinance']) elif self.name == "Sales": @@ -1869,9 +1881,9 @@ class CustomGroup(models.Model): elif self.name == "Agent": # Todo : set permissions for agent pass - - def set_permissions(self,app="inventory", allowed_models=[],other_perms=[]): + + def set_permissions(self,app="inventory", allowed_models=[],other_perms=[]): try: for perm in Permission.objects.filter(content_type__app_label=app,content_type__model__in=allowed_models): self.add_permission(perm) @@ -1880,19 +1892,19 @@ class CustomGroup(models.Model): self.add_permission(perm) except Exception as e: pass - - -class DealerSettings(models.Model): - dealer = models.OneToOneField(Dealer, on_delete=models.CASCADE, related_name="settings",null=True, blank=True) + + +class DealerSettings(models.Model): + dealer = models.OneToOneField(Dealer, on_delete=models.CASCADE, related_name="settings",null=True, blank=True) invoice_cash_account = models.ForeignKey(AccountModel,related_name="invoice_cash", on_delete=models.SET_NULL, null=True, blank=True) invoice_prepaid_account = models.ForeignKey(AccountModel,related_name="invoice_prepaid", on_delete=models.SET_NULL, null=True, blank=True) invoice_unearned_account = models.ForeignKey(AccountModel,related_name="invoice_unearned", on_delete=models.SET_NULL, null=True, blank=True) - + bill_cash_account = models.ForeignKey(AccountModel,related_name="bill_cash", on_delete=models.SET_NULL, null=True, blank=True) bill_prepaid_account = models.ForeignKey(AccountModel,related_name="bill_prepaid", on_delete=models.SET_NULL, null=True, blank=True) bill_unearned_account = models.ForeignKey(AccountModel,related_name="bill_unearned", on_delete=models.SET_NULL, null=True, blank=True) additional_info = models.JSONField(default=dict,null=True,blank=True) - + def __str__(self): return f"Settings for {self.dealer}" \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 927b7c10..94577fb6 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -10,7 +10,7 @@ from calendar import month_name from pyzbar.pyzbar import decode from urllib.parse import urlparse, urlunparse ##################################################################### -from django.db.models.deletion import RestrictedError +from django.db.models.deletion import RestrictedError # Django from django.db.models import Q from django.conf import settings @@ -28,7 +28,7 @@ from django.db.models import Count, F, Value from django.urls import reverse, reverse_lazy from django.utils import timezone, translation from django.db.models.functions import Coalesce -from django.contrib.auth.models import Permission +from django.contrib.auth.models import Permission from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.core.files.storage import default_storage @@ -137,7 +137,7 @@ logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) class Hash(Func): - function = 'get_hash' + function = 'get_hash' def switch_language(request): language = request.GET.get("language", "en") @@ -179,12 +179,12 @@ def dealer_signup(request, *args, **kwargs): form1 = forms.WizardForm1() form2 = forms.WizardForm2() form3 = forms.WizardForm3() - + if request.method == "POST": - if "Hx-Request" in request.headers: - form1 = forms.WizardForm1(request.POST) + if "Hx-Request" in request.headers: + form1 = forms.WizardForm1(request.POST) return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) - + data = json.loads(request.body) wf1 = data.get("wizardValidationForm1") wf2 = data.get("wizardValidationForm2") @@ -207,7 +207,7 @@ def dealer_signup(request, *args, **kwargs): user = User.objects.create(username=email, email=email) user.set_password(password) user.save() - + StaffMember.objects.create(user=user) models.Dealer.objects.create( user=user, name=name, @@ -219,7 +219,7 @@ def dealer_signup(request, *args, **kwargs): ) return JsonResponse( {"message": "User created successfully."}, status=200 - ) + ) except Exception as e: return JsonResponse({"error": str(e)}, status=400) return render(request,"account/signup-wizard.html",{"form1": form1, "form2": form2, "form3": form3}) @@ -309,7 +309,6 @@ class HomeView(TemplateView): "total_selling_price": 0, "total_profit": 0, }) - return context class TestView(TemplateView): @@ -516,7 +515,7 @@ class AjaxHandlerView(LoginRequiredMixin, View): logger.info( f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}" - ) + ) if not car_make: return JsonResponse( @@ -724,7 +723,7 @@ class CarColorCreate(LoginRequiredMixin, CreateView): def form_valid(self, form): car = get_object_or_404(models.Car, pk=self.kwargs["car_pk"]) - form.instance.car = car + form.instance.car = car return super().form_valid(form) def get_success_url(self): @@ -744,7 +743,7 @@ class CarListView(LoginRequiredMixin, ListView): context = super().get_context_data(**kwargs) dealer = get_user_type(self.request) cars = models.Car.objects.filter(dealer=dealer).order_by("receiving_date") - + context["stats"] = { 'all': cars.count(), 'available':cars.filter(status='available').count(), @@ -764,19 +763,19 @@ class CarListView(LoginRequiredMixin, ListView): if make and model: make_ = models.CarMake.objects.get(id_car_make=int(make)) model_ = models.CarModel.objects.get(id_car_model=int(model)) - context['year'] = models.Car.objects.filter(id_car_make=make_,id_car_model=model_).values_list('year').distinct() + context['year'] = models.Car.objects.filter(id_car_make=make_,id_car_model=model_).values_list('year').distinct() return context def get_queryset(self): dealer = get_user_type(self.request) - qs = super().get_queryset() + qs = super().get_queryset() qs = qs.filter(dealer=dealer) - status = self.request.GET.get('status') + status = self.request.GET.get('status') search = self.request.GET.get('search') make = self.request.GET.get('make',None) model = self.request.GET.get('model',None) year = self.request.GET.get('year',None) car_status = self.request.GET.get('car_status',None) - + if status: qs=qs.filter(status=status) if search: @@ -791,7 +790,7 @@ class CarListView(LoginRequiredMixin, ListView): if year: query &= Q(year=year) if car_status: - query &= Q(status=car_status) + query &= Q(status=car_status) qs = qs.filter(query) return qs @@ -1247,14 +1246,14 @@ class CustomerDetailView(LoginRequiredMixin, DetailView): dealer = get_user_type(self.request) entity = dealer.entity context = super().get_context_data(**kwargs) - + estimates = entity.get_estimates().filter(customer=self.object) invoices = entity.get_invoices().filter(customer=self.object) # txs = entity. transactions(customer=self.object) total = estimates.count() + invoices.count() context["estimates"] = estimates context["invoices"] = invoices - context["total"] = total + context["total"] = total return context def add_note_to_customer(request, customer_id): @@ -1296,14 +1295,13 @@ def CustomerCreateView(request): form = forms.CustomerForm(request.POST) dealer = get_user_type(request) - if form.is_valid(): + if form.is_valid(): if dealer.entity.get_customers().filter(email=form.cleaned_data["email"]).exists(): messages.error(request, _("Customer with this email already exists.")) else: # Create customer name customer_name = ( f"{form.cleaned_data['first_name']} " - f"{form.cleaned_data['middle_name']} " f"{form.cleaned_data['last_name']}" ) customer_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken"} @@ -1346,8 +1344,6 @@ def CustomerUpdateView(request, pk): customer_name = ( customer_dict["first_name"] + " " - + customer_dict["middle_name"] - + " " + customer_dict["last_name"] ) @@ -1367,7 +1363,7 @@ def CustomerUpdateView(request, pk): user.save() except Exception as e: raise Exception(e) - + instance.save() messages.success(request, _("Customer updated successfully.")) return redirect("customer_list") @@ -1384,12 +1380,12 @@ def CustomerUpdateView(request, pk): @login_required def delete_customer(request, pk): customer = get_object_or_404(models.CustomerModel, pk=pk) - user = User.objects.get(email=customer.email) + user = User.objects.get(email=customer.email) customer.active = False user.is_active = False customer.save() user.save() - + messages.success(request, _("Customer deleted successfully.")) return redirect("customer_list") @@ -1457,15 +1453,15 @@ class GroupListView(LoginRequiredMixin, ListView): paginate_by = 10 template_name = "groups/group_list.html" - def get_queryset(self): + def get_queryset(self): dealer = get_user_type(self.request) return dealer.groups.all() class GroupDetailView(LoginRequiredMixin, DetailView): model = models.CustomGroup template_name = "groups/group_detail.html" - context_object_name = "group" - + context_object_name = "group" + class GroupCreateView( LoginRequiredMixin, @@ -1501,7 +1497,7 @@ class GroupUpdateView( def form_valid(self, form): dealer = get_user_type(self.request) - instance = form.save(commit=False) + instance = form.save(commit=False) instance.group.name = f"{dealer.pk}_{instance.name}" instance.save() return super().form_valid(form) @@ -1521,7 +1517,7 @@ def GroupPermissionView(request, pk): for i in permissions: group.add_permission(Permission.objects.get(id=int(i))) messages.success(request, _("Permission added successfully.")) - return redirect("group_detail", pk=group.pk) + return redirect("group_detail", pk=group.pk) form = forms.PermissionForm(initial={"name": group.permissions}) return render(request,"groups/group_permission_form.html",{"group": group, "form": form}) @@ -1529,18 +1525,18 @@ def GroupPermissionView(request, pk): def UserGroupView(request, pk): staff = get_object_or_404(models.Staff, pk=pk) - + if request.method == "POST": - form = forms.UserGroupForm(request.POST) - groups = request.POST.getlist("name") + form = forms.UserGroupForm(request.POST) + groups = request.POST.getlist("name") staff.clear_groups() - for i in groups: + for i in groups: cg = models.CustomGroup.objects.get(id=int(i)) staff.add_group(cg.group) - + messages.success(request, _("Group added successfully.")) return redirect("user_detail", pk=staff.pk) - + form = forms.UserGroupForm(initial={"name": staff.groups}) form.fields['name'].queryset = models.CustomGroup.objects.filter(dealer=staff.dealer) return render(request,"users/user_group_form.html",{"staff": staff, "form": form}) @@ -1591,7 +1587,7 @@ class UserCreateView( email = form.cleaned_data["email"] password = "Tenhal@123" - + user = User.objects.create_user(username=form.cleaned_data["name"], email=email, password=password) user.is_staff = True user.save() @@ -1606,7 +1602,7 @@ class UserCreateView( staff.add_group(group) staff.save() return super().form_valid(form) - + class UserUpdateView( LoginRequiredMixin, @@ -1629,7 +1625,7 @@ class UserUpdateView( form.fields['email'].disabled = True return form def get_initial(self): - initial = super().get_initial() + initial = super().get_initial() initial['email'] = self.object.staff_member.user.email initial['service_offered'] = self.object.staff_member.services_offered.all() return initial @@ -1639,16 +1635,16 @@ class UserUpdateView( self.object.staff_member.services_offered.clear() else: for service in services: - self.object.staff_member.services_offered.add(service) - + self.object.staff_member.services_offered.add(service) + staff = form.save(commit=False) staff.name = form.cleaned_data["name"] staff.arabic_name = form.cleaned_data["arabic_name"] staff.phone_number = form.cleaned_data["phone_number"] - staff.staff_type = form.cleaned_data["staff_type"] + staff.staff_type = form.cleaned_data["staff_type"] staff.save() return super().form_valid(form) - + def UserDeleteview(request, pk): staff = get_object_or_404(models.Staff, pk=pk) staff.staff_member.delete() @@ -1700,15 +1696,16 @@ def OrganizationCreateView(request): if CustomerModel.objects.filter(email=request.POST["email"]).exists(): messages.error(request, _("An organization with this email already exists.")) return redirect("organization_create") - + organization_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" } dealer = get_user_type(request) - - instance = dealer.entity.create_customer( + name = organization_dict["first_name"] + " " + organization_dict["last_name"] + customer = dealer.entity.create_customer( + commit=False, customer_model_kwargs={ - "customer_name": organization_dict["name"], + "customer_name": name, "address_1": organization_dict["address"], "phone": organization_dict["phone_number"], "email": organization_dict["email"], @@ -1718,12 +1715,12 @@ def OrganizationCreateView(request): if image: file_name = default_storage.save("images/{}".format(image.name), image) file_url = default_storage.url(file_name) - + organization_dict["logo"] = file_url - organization_dict["pk"] = str(instance.pk) - instance.additional_info["organization_info"] = organization_dict - instance.additional_info["type"] = "organization" - instance.save() + organization_dict["pk"] = str(customer.pk) + customer.additional_info.update({"customer_info": organization_dict}) + customer.additional_info.update({"type": "organization"}) + customer.save() messages.success(request, _("Organization created successfully.")) return redirect("organization_list") else: @@ -1732,44 +1729,45 @@ def OrganizationCreateView(request): def OrganizationUpdateView(request,pk): - organization = get_object_or_404(CustomerModel, pk=pk) + organization = get_object_or_404(CustomerModel, pk=pk) if request.method == "POST": form = forms.OrganizationForm(request.POST) - + organization_dict = { x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" - } + } dealer = get_user_type(request) instance = dealer.entity.get_customers().get( - pk=organization.additional_info["organization_info"]["pk"] + pk=organization.additional_info["customer_info"]["pk"] ) - instance.customer_name = organization_dict["name"] + name = organization_dict["first_name"] + " " + organization_dict["last_name"] + instance.customer_name = name instance.address_1 = organization_dict["address"] instance.phone = organization_dict["phone_number"] instance.email = organization_dict["email"] - + image = request.FILES.get("logo") if image: file_name = default_storage.save("images/{}".format(image.name), image) - file_url = default_storage.url(file_name) + file_url = default_storage.url(file_name) organization_dict["logo"] = file_url else: - organization_dict["logo"] = organization.additional_info["organization_info"]["logo"] - + organization_dict["logo"] = organization.additional_info["customer_info"]["logo"] + organization_dict["pk"] = str(instance.pk) - instance.additional_info["organization_info"] = organization_dict + instance.additional_info["customer_info"] = organization_dict instance.additional_info["type"] = "organization" instance.save() messages.success(request, _("Organization created successfully.")) return redirect("organization_list") else: - form = forms.OrganizationForm( - initial=organization.additional_info["organization_info"] or {} + form = forms.OrganizationForm( + initial=organization.additional_info["customer_info"] or {} ) - # form.fields.pop("logo", None) + # form.fields.pop("logo", None) return render(request, "organizations/organization_form.html", {"form": form}) - + # class OrganizationDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView): # model = models.Organization @@ -2047,7 +2045,7 @@ def create_estimate(request,pk=None): dealer = get_user_type(request) entity = dealer.entity - if request.method == "POST": + if request.method == "POST": # try: data = json.loads(request.body) title = data.get("title") @@ -2057,13 +2055,13 @@ def create_estimate(request,pk=None): items = data.get("item", []) quantities = data.get("quantity", []) - + if not all([items, quantities]): return JsonResponse( {"status": "error", "message": "Items and Quantities are required"}, status=400, - ) - + ) + if isinstance(quantities, list): if "0" in quantities: return JsonResponse( @@ -2084,7 +2082,7 @@ def create_estimate(request,pk=None): if int(quantities) > models.Car.objects.filter(hash=items,status='available').count(): return JsonResponse( {"status": "error", "message": "Quantity must be less than or equal to the number of cars in stock"}, - ) + ) estimate = entity.create_estimate( estimate_title=title, customer_model=customer, contract_terms=terms ) @@ -2105,7 +2103,7 @@ def create_estimate(request,pk=None): items_txs = [] for item in items_list: car_instance = ItemModel.objects.filter(additional_info__car_info__hash=item.get("item_id")).all() - + for i in car_instance[:int(quantities[0])]: items_txs.append( { @@ -2148,19 +2146,19 @@ def create_estimate(request,pk=None): for item in estimate_itemtxs.keys(): item_instance = ItemModel.objects.filter(item_number=item).first() instance = models.Car.objects.get(vin=item_instance.name) - reserve_car(instance, request) + reserve_car(instance, request) else: item_instance = ItemModel.objects.filter(additioinal_info__car_info__hash=items).first() instance = models.Car.objects.get(hash=item) response = reserve_car(instance, request) - + opportunity_id = data.get("opportunity_id") if opportunity_id != "None": opportunity = models.Opportunity.objects.get(pk=int(opportunity_id)) opportunity.estimate = estimate - opportunity.save() - + opportunity.save() + url = reverse("estimate_detail", kwargs={"pk": estimate.pk}) return JsonResponse( { @@ -2174,14 +2172,14 @@ def create_estimate(request,pk=None): entity_slug=entity.slug, user_model=entity.admin ) form.fields["customer"].queryset = entity.get_customers().filter(active=True,additional_info__type="customer") - + if pk: opportunity = models.Opportunity.objects.get(pk=pk) customer = opportunity.customer form.initial['customer'] = customer - + car_list = models.Car.objects.filter(dealer=dealer,colors__isnull=False,finances__isnull=False,status="available").annotate(color=F('colors__exterior__rgb'),color_name=F('colors__exterior__name')).values_list( - 'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').annotate(hash_count=Count('hash')).distinct() + 'id_car_make__name', 'id_car_model__name','id_car_serie__name','id_car_trim__name','color','color_name','hash').annotate(hash_count=Count('hash')).distinct() context = { "form": form, "items": [ @@ -2200,7 +2198,7 @@ def create_estimate(request,pk=None): "opportunity_id": pk if pk else None, "customer_count": entity.get_customers().count() } - + return render(request, "sales/estimates/estimate_form.html", context) @@ -2214,7 +2212,7 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): if estimate.get_itemtxs_data(): calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() - + kwargs["data"] = finance_data kwargs["invoice"] = ( InvoiceModel.objects.all().filter(ce_model=estimate).first() @@ -2238,10 +2236,10 @@ def create_sale_order(request, pk): item.item_model.additional_info['car_info']['status'] = 'sold' item.item_model.save() except KeyError: - pass + pass models.Car.objects.get(vin=item.item_model.name).mark_as_sold(request) - - messages.success(request, "Sale Order created successfully") + + messages.success(request, "Sale Order created successfully") return redirect("estimate_detail", pk=pk) form = forms.SaleOrderForm() @@ -2363,7 +2361,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): if invoice.get_itemtxs_data(): calculator = CarFinanceCalculator(invoice) - finance_data = calculator.get_finance_data() + finance_data = calculator.get_finance_data() kwargs["data"] = finance_data kwargs["payments"] = JournalEntryModel.objects.filter( ledger=invoice.ledger @@ -2464,10 +2462,10 @@ def invoice_create(request, pk): ledger.save() invoice.save() - + calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() - + invoice_itemtxs = { i.get("item_number"): { "unit_cost": i.get("total_vat"), @@ -2549,11 +2547,11 @@ def PaymentCreateView(request, pk): messages.error(request,"fully paid") return redirect(redirect_url, pk=model.pk) if model.amount_paid + amount > model.amount_due: - messages.error(request,"Amount exceeds due amount") + messages.error(request,"Amount exceeds due amount") return redirect(redirect_url, pk=model.pk) - + try: - if invoice: + if invoice: set_invoice_payment(dealer, entity, invoice, amount, payment_method) elif bill: set_bill_payment(dealer, entity, bill, amount, payment_method) @@ -2703,9 +2701,9 @@ def lead_create(request): if form.is_valid(): instance = form.save(commit=False) dealer = get_user_type(request) - instance.dealer = dealer + instance.dealer = dealer instance.staff = form.cleaned_data.get("staff") - + # creating customer in ledger customer = dealer.entity.get_customers().filter(email=instance.email).first() if not customer: @@ -2716,7 +2714,7 @@ def lead_create(request): "address_1": instance.address, "phone": instance.phone_number, "email": instance.email, - "sales_tax_rate": 0.15, + "sales_tax_rate": 0.15, } ) customer_info = { @@ -2725,7 +2723,9 @@ def lead_create(request): "address": instance.address, "phone_number": str(instance.phone_number), "email": instance.email, - } + "crn": form.cleaned_data["crn"], + "vrn": form.cleaned_data["vrn"], + } customer.additional_info.update({"customer_info": customer_info }) customer.additional_info.update({"type":"lead"}) customer.save() @@ -2753,7 +2753,7 @@ class LeadUpdateView(UpdateView): success_url = reverse_lazy("lead_list") def get_form(self, form_class=None): - form = super().get_form(form_class) + form = super().get_form(form_class) form.fields["id_car_model"].queryset = form.instance.id_car_make.carmodel_set.all() return form @@ -2761,7 +2761,7 @@ class LeadUpdateView(UpdateView): @login_required def LeadDeleteView(request,pk): lead = get_object_or_404(models.Lead, pk=pk) - try: + try: User.objects.get(email=lead.customer.email).delete() lead.customer.delete() except Exception as e: @@ -2791,12 +2791,12 @@ def add_note_to_lead(request, pk): def add_note_to_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) if request.method == "POST": - notes = request.POST.get("notes") + notes = request.POST.get("notes") if not notes: - messages.error(request, "Notes field is required.") + messages.error(request, "Notes field is required.") else: models.Notes.objects.create(content_object=opportunity, created_by=request.user,note=notes) - messages.success(request, "Note added successfully!") + messages.success(request, "Note added successfully!") return redirect("opportunity_detail", pk=opportunity.pk) @@ -2835,8 +2835,8 @@ def lead_convert(request, pk): dealer = get_user_type(request) if hasattr(lead, "opportunity"): messages.error(request, "Lead is already converted to customer.") - else: - customer = lead.convert_to_customer(dealer.entity) + else: + customer = lead.convert_to_customer(dealer.entity,lead) models.Opportunity.objects.create(dealer=dealer,customer=customer,lead=lead,probability=50,stage=models.Stage.PROSPECT,staff=lead.staff,status=models.Status.QUALIFIED) messages.success(request, "Lead converted to customer successfully!") return redirect("lead_list") @@ -2860,7 +2860,7 @@ def schedule_lead(request, pk): service = Service.objects.filter(name=instance.scheduled_type).first() if not service: messages.error(request, "Service not found!") - return redirect("lead_list") + return redirect("lead_list") try: appointment_request = AppointmentRequest.objects.create( @@ -2873,7 +2873,7 @@ def schedule_lead(request, pk): except ValidationError as e: messages.error(request, str(e)) return redirect("schedule_lead", pk=lead.pk) - + client = get_object_or_404(User, email=lead.email) # Create Appointment Appointment.objects.create( @@ -2902,7 +2902,7 @@ def lead_transfer(request,pk): if form.is_valid(): lead.staff = form.cleaned_data["transfer_to"] lead.save() - messages.success(request, "Lead transferred successfully!") + messages.success(request, "Lead transferred successfully!") else: messages.error(request, f"Invalid form data: {str(form.errors)}") return redirect("lead_list") @@ -2919,12 +2919,12 @@ def send_lead_email(request, pk,email_pk=None): response = HttpResponse(redirect("lead_detail", pk=lead.pk)) response['HX-Redirect'] = reverse('lead_detail', args=[lead.pk]) return response - + lead.status = models.Status.CONTACTED lead.save() # lead.convert_to_customer(dealer.entity) - if request.method == "POST": + if request.method == "POST": email_pk = request.POST.get('email_pk') if email_pk not in [None,"None",""]: email = get_object_or_404(models.Email, pk=int(email_pk)) @@ -2940,7 +2940,7 @@ def send_lead_email(request, pk,email_pk=None): ) dealer = get_user_type(request) models.Activity.objects.create(dealer=dealer,content_object=lead, notes="Email sent",created_by=request.user,activity_type=models.ActionChoices.EMAIL) - messages.success(request, _("Email sent successfully!")) + messages.success(request, _("Email sent successfully!")) return redirect("lead_list") msg = f""" السلام عليكم @@ -2982,7 +2982,7 @@ def add_activity_to_lead(request, pk): if request.method == "POST": form = forms.ActivityForm(request.POST) if form.is_valid(): - activity = form.save(commit=False) + activity = form.save(commit=False) activity.content_object = lead activity.created_by = request.user activity.save() @@ -3005,8 +3005,8 @@ class OpportunityCreateView(CreateView, LoginRequiredMixin): def get_initial(self): initial = super().get_initial() if self.kwargs.get('pk',None): - lead = models.Lead.objects.get(pk=self.kwargs.get('pk')) - + lead = models.Lead.objects.get(pk=self.kwargs.get('pk')) + initial['customer'] = lead.customer return initial @@ -3037,11 +3037,11 @@ class OpportunityDetailView(DetailView): context = super().get_context_data(**kwargs) form = forms.OpportunityStatusForm() url = reverse("opportunity_update_status", args=[self.object.pk]) - form.fields["status"].widget.attrs["hx-get"] = url + form.fields["status"].widget.attrs["hx-get"] = url form.fields["stage"].widget.attrs["hx-get"] = url - form.fields["status"].initial = self.object.status + form.fields["status"].initial = self.object.status form.fields["stage"].initial = self.object.stage - context["status_form"] = form + context["status_form"] = form context["notes"] = models.Notes.objects.filter(content_type__model="opportunity", object_id=self.object.id) context["activities"] = models.Activity.objects.filter(content_type__model="opportunity", object_id=self.object.id) email_qs = models.Email.objects.filter(content_type__model="opportunity", object_id=self.object.id) @@ -3061,7 +3061,7 @@ class OpportunityListView(ListView): def get_queryset(self): dealer = get_user_type(self.request) return models.Opportunity.objects.filter(dealer=dealer).all() - + @login_required def delete_opportunity(request, pk): opportunity = get_object_or_404(models.Opportunity, pk=pk) @@ -3227,7 +3227,7 @@ class BillDetailView(LoginRequiredMixin, DetailView): bill = kwargs.get("object") if bill.get_itemtxs_data(): txs = bill.get_itemtxs_data()[0] - + transactions = [ { "item": x, @@ -3242,7 +3242,7 @@ class BillDetailView(LoginRequiredMixin, DetailView): kwargs["transactions"] = transactions kwargs["grand_total"] = grand_total - + return super().get_context_data(**kwargs) @@ -3323,7 +3323,7 @@ def bill_mark_as_paid(request, pk): bill.mark_as_paid(user_model=dealer.entity.admin) bill.save() bill.ledger.lock_journal_entries() - bill.ledger.post_journal_entries() + bill.ledger.post_journal_entries() bill.ledger.post() bill.ledger.save() messages.success(request, _("Bill marked as paid successfully.")) @@ -3461,7 +3461,7 @@ class OrderListView(ListView): def get_queryset(self): dealer = get_user_type(self.request) - qs = super().get_queryset() + qs = super().get_queryset() return qs.filter(estimate__entity=dealer.entity) # email @@ -3861,7 +3861,7 @@ def DealerSettingsView(request,pk): dealer = get_user_type(request) if request.method == 'POST': form = forms.DealerSettingsForm(request.POST, instance=dealer_setting) - if form.is_valid(): + if form.is_valid(): instance = form.save(commit=False) instance.dealer = dealer instance.save() @@ -3873,7 +3873,7 @@ def DealerSettingsView(request,pk): form.fields['invoice_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) form.fields['invoice_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_RECEIVABLES) form.fields['invoice_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_DEFERRED_REVENUE) - + form.fields['bill_cash_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_CASH) form.fields['bill_prepaid_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.ASSET_CA_PREPAID) form.fields['bill_unearned_account'].queryset = dealer.entity.get_all_accounts().filter(role=roles.LIABILITY_CL_ACC_PAYABLE) diff --git a/templates/crm/leads/lead_list.html b/templates/crm/leads/lead_list.html index c5646718..869b0b04 100644 --- a/templates/crm/leads/lead_list.html +++ b/templates/crm/leads/lead_list.html @@ -26,7 +26,7 @@ - + + - + -
{{ _("Lead Name")|capfirst }}{{ _("Lead Name")|capfirst }}
@@ -109,7 +109,7 @@
-
{{lead.full_name}} @@ -132,12 +132,12 @@
{{ lead.id_car_make.get_local_name }} - {{ lead.id_car_model.get_local_name }} {{ lead.year }} {{ lead.email }} {{ lead.phone_number }} -
+
+