diff --git a/inventory/forms.py b/inventory/forms.py index 309006c0..95d0388a 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -8,11 +8,15 @@ from django.core.validators import RegexValidator from django import forms from django.contrib.auth import get_user_model from phonenumber_field.phonenumber import PhoneNumber - +from .models import Status, Stage from .mixins import AddClassMixin from django.forms.models import inlineformset_factory -from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase -from django_ledger.forms.estimate import EstimateModelCreateForm as EstimateModelCreateFormBase +from django_ledger.forms.invoice import ( + InvoiceModelCreateForm as InvoiceModelCreateFormBase, +) +from django_ledger.forms.estimate import ( + EstimateModelCreateForm as EstimateModelCreateFormBase, +) from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase from django_ledger.forms.vendor import VendorModelForm @@ -38,10 +42,23 @@ from .models import ( # SaleQuotationCar, AdditionalServices, Staff, - Opportunity, Priority, Sources, Lead, Activity, Notes, CarModel,SaleOrder,CarMake + Opportunity, + Priority, + Sources, + Lead, + Activity, + Notes, + CarModel, + SaleOrder, + CarMake, ) from django_ledger import models as ledger_models -from django.forms import ModelMultipleChoiceField, ValidationError, DateInput,DateTimeInput +from django.forms import ( + ModelMultipleChoiceField, + ValidationError, + DateInput, + DateTimeInput, +) from django.utils.translation import gettext_lazy as _ import django_tables2 as tables from django.forms import formset_factory @@ -111,27 +128,22 @@ class CustomerForm(forms.Form): last_name = forms.CharField() national_id = forms.CharField(max_length=10) email = forms.EmailField() - phone_number = PhoneNumberField( - min_length=10, - max_length=10, - region="SA", - ) + 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( - min_length=10, - max_length=10, - region="SA", - ) + phone_number = PhoneNumberField(region="SA") crn = forms.CharField() vrn = forms.CharField() address = forms.CharField() contact_person = forms.CharField(required=False) logo = forms.ImageField(required=False) + + # class CustomerForm(forms.ModelForm, AddClassMixin): # class Meta: # model = Customer @@ -193,7 +205,9 @@ class CarForm( (obj.id_car_model, obj.get_local_name()) for obj in queryset ] if "vendor" in self.fields: - self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter(active=True) + self.fields["vendor"].queryset = ledger_models.VendorModel.objects.filter( + active=True + ) # queryset = self.fields["vendor"].queryset # self.fields["vendor"].choices = [ # (obj.pk, obj.get_local_name()) for obj in queryset @@ -303,11 +317,10 @@ class CarRegistrationForm(forms.ModelForm): fields = ["plate_number", "text1", "text2", "text3", "registration_date"] widgets = { - 'registration_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}), + "registration_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), } - # class VendorForm(VendorModelForm): # pass class VendorForm(forms.ModelForm): @@ -517,8 +530,6 @@ class WizardForm2(forms.Form): phone_number = PhoneNumberField( label=_("Phone Number"), - min_length=10, - max_length=10, widget=forms.TextInput( attrs={ "class": "form-control form-control-sm", @@ -598,7 +609,9 @@ class WizardForm3(forms.Form): class ItemForm(forms.Form): item = forms.ModelChoiceField( - queryset=ledger_models.ItemModel.objects.all(), label="Item", required=True, + queryset=ledger_models.ItemModel.objects.all(), + label="Item", + required=True, validators=[MinLengthValidator(5)], ) quantity = forms.DecimalField(label="Quantity", required=True) @@ -609,7 +622,9 @@ class ItemForm(forms.Form): class PaymentForm(forms.Form): invoice = forms.ModelChoiceField( - queryset=ledger_models.InvoiceModel.objects.all(), label="Invoice", required=False + queryset=ledger_models.InvoiceModel.objects.all(), + label="Invoice", + required=False, ) bill = forms.ModelChoiceField( queryset=ledger_models.BillModel.objects.all(), label="Bill", required=False @@ -626,13 +641,15 @@ class PaymentForm(forms.Form): label="Payment Method", required=True, ) - payment_date = forms.DateField(label="Payment Date", widget=DateInput(attrs={'type': 'date'}), required=True) + payment_date = forms.DateField( + label="Payment Date", widget=DateInput(attrs={"type": "date"}), required=True + ) def clean_amount(self): - invoice = self.cleaned_data['invoice'] - bill = self.cleaned_data['bill'] + invoice = self.cleaned_data["invoice"] + bill = self.cleaned_data["bill"] model = invoice if invoice else bill - amount = self.cleaned_data['amount'] + amount = self.cleaned_data["amount"] if amount + model.amount_paid > model.amount_due: raise forms.ValidationError("Payment amount is greater than amount due") if amount <= 0: @@ -652,103 +669,136 @@ class EmailForm(forms.Form): class LeadForm(forms.ModelForm): - id_car_make = forms.ModelChoiceField(label="Make", + id_car_make = forms.ModelChoiceField( + label="Make", queryset=CarMake.objects.filter(is_sa_import=True), - widget=forms.Select(attrs={"class": "form-control form-control-sm","hx-get":"","hx-include":"#id_id_car_make","hx-select":"#div_id_id_car_model","hx-target":"#div_id_id_car_model","hx-swap":"outerHTML"}), - required=True + widget=forms.Select( + attrs={ + "class": "form-control form-control-sm", + "hx-get": "", + "hx-include": "#id_id_car_make", + "hx-select": "#div_id_id_car_model", + "hx-target": "#div_id_id_car_model", + "hx-swap": "outerHTML", + "hx-on::before-request": "document.querySelector('#id_id_car_model').setAttribute('disabled', true)", + "hx-on::after-request": "document.querySelector('#id_id_car_model').removeAttribute('disabled')", + } + ), + required=True, ) - id_car_model = forms.ModelChoiceField(label="Model", queryset=CarModel.objects.none(),widget=forms.Select(attrs={"class": "form-control form-control-sm"}),required=True) + id_car_model = forms.ModelChoiceField( + label="Model", + queryset=CarModel.objects.none(), + widget=forms.Select(attrs={"class": "form-control form-control-sm"}), + required=True, + ) + class Meta: model = Lead fields = [ - 'first_name', - 'last_name', - 'email', - 'phone_number', - 'address', - 'id_car_make', - 'id_car_model', - 'year', - 'source', - 'channel', - 'staff', - 'priority', - ] + "first_name", + "last_name", + "email", + "phone_number", + "address", + "id_car_make", + "id_car_model", + "year", + "source", + "channel", + "staff", + "priority", + ] def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - + 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 = [ (obj.id_car_make, obj.get_local_name()) for obj in queryset ] + class ScheduleForm(forms.ModelForm): - scheduled_at = forms.DateTimeField(widget=DateTimeInput(attrs={'type': 'datetime-local'})) + scheduled_at = forms.DateTimeField( + widget=DateTimeInput(attrs={"type": "datetime-local"}) + ) + class Meta: model = Schedule - fields = ['purpose','scheduled_type', 'scheduled_at','duration', 'notes'] + fields = ["purpose", "scheduled_type", "scheduled_at", "duration", "notes"] class NoteForm(forms.ModelForm): class Meta: model = Notes - fields = ['note'] + fields = ["note"] class ActivityForm(forms.ModelForm): class Meta: model = Activity - fields = ['activity_type', 'notes'] + fields = ["activity_type", "notes"] class OpportunityForm(forms.ModelForm): class Meta: model = Opportunity - fields = ['customer', 'car', 'stage', 'probability', 'staff', 'closing_date'] + fields = ["customer", "car", "stage", "probability", "staff", "closing_date"] 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() - self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) + self.fields["cash_account"].widget = forms.HiddenInput() + self.fields["prepaid_account"].widget = forms.HiddenInput() + self.fields["unearned_account"].widget = forms.HiddenInput() + self.fields["date_draft"] = forms.DateField( + widget=DateInput(attrs={"type": "date"}) + ) def get_customer_queryset(self): - if 'customer' in self.fields: - self.fields['customer'].queryset = self.USER_MODEL.dealer.entity.get_customers() + if "customer" in self.fields: + self.fields[ + "customer" + ].queryset = self.USER_MODEL.dealer.entity.get_customers() + 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() - self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) + self.fields["cash_account"].widget = forms.HiddenInput() + self.fields["prepaid_account"].widget = forms.HiddenInput() + self.fields["unearned_account"].widget = forms.HiddenInput() + self.fields["date_draft"] = forms.DateField( + widget=DateInput(attrs={"type": "date"}) + ) + class SaleOrderForm(forms.ModelForm): class Meta: model = SaleOrder - fields = ['estimate','payment_method', 'comments'] - widgets = { - 'comments': forms.Textarea(attrs={'rows': 3}), + fields = ["estimate", "payment_method", "comments"] + widgets = { + "comments": forms.Textarea(attrs={"rows": 3}), } + class EstimateModelCreateForm(EstimateModelCreateFormBase): class Meta: model = ledger_models.EstimateModel - fields = ['title', 'customer', 'terms'] + fields = ["title","customer", "terms"] widgets = { - 'customer': forms.Select(attrs={ - 'id': 'djl-customer-estimate-customer-input', - 'class': 'input', - 'label': _('Customer'), - }), + "customer": forms.Select( + attrs={ + "id": "djl-customer-estimate-customer-input", + "class": "input", + "label": _("Customer"), + } + ), 'terms': forms.Select(attrs={ 'id': 'djl-customer-estimate-terms-input', 'class': 'input', @@ -757,23 +807,57 @@ class EstimateModelCreateForm(EstimateModelCreateFormBase): 'title': forms.TextInput(attrs={ 'id': 'djl-customer-job-title-input', 'class': 'input' + ' is-large', - 'label': _('Title'), }) } labels = { 'title': _('Title'), 'terms': _('Terms'), - 'customer': _('Customer'), + "customer": _("Customer"), } + def __init__(self, *args, entity_slug, user_model, **kwargs): - super(EstimateModelCreateForm, self).__init__(*args, entity_slug=entity_slug, user_model=user_model, **kwargs) + super(EstimateModelCreateForm, self).__init__( + *args, entity_slug=entity_slug, user_model=user_model, **kwargs + ) self.ENTITY_SLUG = entity_slug self.USER_MODEL = user_model - self.fields['customer'].queryset = self.get_customer_queryset() + self.fields["customer"].queryset = self.get_customer_queryset() def get_customer_queryset(self): return self.USER_MODEL.dealer.entity.get_customers() - +class OpportunityStatusForm(forms.Form): + status = forms.ChoiceField( + label="Status", + choices=Status.choices, + widget=forms.Select( + attrs={ + "class": "form-control form-control-sm", + "hx-get": "{% url 'opportunity_update_status' opportunity.id %}", + "hx-target": ".other-information", + "hx-select": ".other-information", + "hx-swap": "outerHTML", + "hx-on::after-request": "this.setAttribute('disabled','true')", + "disabled": "disabled", + } + ), + required=True, + ) + stage = forms.ChoiceField( + label="Stage", + choices=Stage.choices, + widget=forms.Select( + attrs={ + "class": "form-control form-control-sm", + "hx-target": ".other-information", + "hx-select": ".other-information", + "hx-swap": "outerHTML", + "hx-on::after-request": "this.setAttribute('disabled','true')", + "disabled": "disabled", + } + ), + required=True, + ) + diff --git a/inventory/migrations/0019_opportunity_lead.py b/inventory/migrations/0019_opportunity_lead.py new file mode 100644 index 00000000..f7e643d8 --- /dev/null +++ b/inventory/migrations/0019_opportunity_lead.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.17 on 2025-02-12 10:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0018_customer_user'), + ] + + operations = [ + migrations.AddField( + model_name='opportunity', + name='lead', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='inventory.lead'), + ), + ] diff --git a/inventory/migrations/0020_alter_opportunity_closing_date.py b/inventory/migrations/0020_alter_opportunity_closing_date.py new file mode 100644 index 00000000..3b6324fd --- /dev/null +++ b/inventory/migrations/0020_alter_opportunity_closing_date.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.17 on 2025-02-12 10:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0019_opportunity_lead'), + ] + + operations = [ + migrations.AlterField( + model_name='opportunity', + name='closing_date', + field=models.DateField(blank=True, null=True, verbose_name='Closing Date'), + ), + ] diff --git a/inventory/migrations/0021_alter_opportunity_lead.py b/inventory/migrations/0021_alter_opportunity_lead.py new file mode 100644 index 00000000..16cd0fb7 --- /dev/null +++ b/inventory/migrations/0021_alter_opportunity_lead.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.17 on 2025-02-12 10:37 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inventory', '0020_alter_opportunity_closing_date'), + ] + + operations = [ + migrations.AlterField( + model_name='opportunity', + name='lead', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunity', to='inventory.lead'), + ), + ] diff --git a/inventory/migrations/0022_opportunity_estimate.py b/inventory/migrations/0022_opportunity_estimate.py new file mode 100644 index 00000000..f5ba3a9a --- /dev/null +++ b/inventory/migrations/0022_opportunity_estimate.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.17 on 2025-02-12 14:09 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.DJANGO_LEDGER_ESTIMATE_MODEL), + ('inventory', '0021_alter_opportunity_lead'), + ] + + operations = [ + migrations.AddField( + model_name='opportunity', + name='estimate', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='opportunity', to=settings.DJANGO_LEDGER_ESTIMATE_MODEL), + ), + ] diff --git a/inventory/models.py b/inventory/models.py index 881c3010..f78ae7fd 100644 --- a/inventory/models.py +++ b/inventory/models.py @@ -1203,8 +1203,10 @@ class Lead(models.Model): customer.additional_info = {} customer.additional_info.update({"info":self.to_dict()}) self.customer = customer + self.status = Status.QUALIFIED customer.save() self.save() + return customer def get_latest_schedule(self): return self.schedules.order_by('-scheduled_at').first() @@ -1307,18 +1309,19 @@ class Opportunity(models.Model): related_name="owner", verbose_name=_("Owner"), ) + lead = models.OneToOneField("Lead",related_name="opportunity", on_delete=models.CASCADE,null=True,blank=True) probability = models.PositiveIntegerField(validators=[validate_probability]) - closing_date = models.DateField(verbose_name=_("Closing Date")) + closing_date = models.DateField(verbose_name=_("Closing Date"),null=True,blank=True) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) closed = models.BooleanField(default=False, verbose_name=_("Closed")) - + estimate = models.OneToOneField(EstimateModel, related_name="opportunity",on_delete=models.SET_NULL,null=True,blank=True) class Meta: verbose_name = _("Opportunity") verbose_name_plural = _("Opportunities") def __str__(self): - return f"{self.car.id_car_make.name} - {self.car.id_car_model.name} : {self.customer}" + return f"Opportunity for {self.customer.customer_name}" class Notes(models.Model): diff --git a/inventory/signals.py b/inventory/signals.py index 60cda31b..79f46ba3 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -664,21 +664,35 @@ def create_customer(sender, instance, created, **kwargs): print(f"Customer created: {name}") -@receiver(post_save, sender=models.Customer) +@receiver(post_save, sender=models.CustomerModel) def create_customer_user(sender, instance, created, **kwargs): if created: user = User.objects.create( username=instance.email, - email=instance.email, - password=None, - first_name=instance.first_name, - last_name=instance.last_name + email=instance.email, + first_name=instance.additional_info.get('first_name',''), + last_name=instance.additional_info.get('last_name','') ) - user.is_active = True - user.is_staff = True + user.is_active = False + user.is_staff = False user.save() instance.user = user instance.save() +# @receiver(post_save, sender=models.Customer) +# def create_customer_user(sender, instance, created, **kwargs): +# if created: +# user = User.objects.create( +# username=instance.email, +# email=instance.email, +# password=None, +# first_name=instance.first_name, +# last_name=instance.last_name +# ) +# user.is_active = True +# user.is_staff = True +# user.save() +# instance.user = user +# instance.save() # Create Item diff --git a/inventory/urls.py b/inventory/urls.py index 01cdcbb5..5c6e2cf4 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -143,6 +143,11 @@ urlpatterns = [ views.delete_opportunity, name="delete_opportunity", ), + path( + "crm/opportunities//opportunity_update_status/", + views.opportunity_update_status, + name="opportunity_update_status", + ), # path('crm/opportunities//logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'), path( "crm/notifications/", @@ -414,6 +419,7 @@ urlpatterns = [ name="estimate_detail", ), path("sales/estimates/create/", views.create_estimate, name="estimate_create"), + path("sales/estimates/create//", views.create_estimate, name="estimate_create_from_opportunity"), path( "sales/estimates//estimate_mark_as/", views.estimate_mark_as, diff --git a/inventory/views.py b/inventory/views.py index d24ce535..dc91836e 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -1256,35 +1256,39 @@ def add_activity_to_customer(request, pk): def CustomerCreateView(request): - if request.method == "POST": - customer_dict = { - x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" - } - dealer = get_user_type(request) - customer_name = ( - customer_dict["first_name"] - + " " - + customer_dict["middle_name"] - + " " - + customer_dict["last_name"] - ) - - instance = dealer.entity.create_customer( - customer_model_kwargs={ - "customer_name": customer_name, - "address_1": customer_dict["address"], - "phone": customer_dict["phone_number"], - "email": customer_dict["email"], - } - ) - customer_dict["pk"] = str(instance.pk) - instance.additional_info["customer_info"] = customer_dict - instance.additional_info["type"] = "customer" - instance.save() - messages.success(request, _("Customer created successfully.")) - return redirect("customer_list") - form = forms.CustomerForm() + if request.method == "POST": + dealer = get_user_type(request) + if dealer.entity.get_customers().filter(email=request.POST["email"]).exists(): + messages.error(request, _("Customer with this email already exists.")) + form = forms.CustomerForm(request.POST) + else: + customer_dict = { + x: request.POST[x] for x in request.POST if x != "csrfmiddlewaretoken" + } + customer_name = ( + customer_dict["first_name"] + + " " + + customer_dict["middle_name"] + + " " + + customer_dict["last_name"] + ) + instance = dealer.entity.create_customer( + customer_model_kwargs={ + "customer_name": customer_name, + "address_1": customer_dict["address"], + "phone": customer_dict["phone_number"], + "email": customer_dict["email"], + } + ) + customer_dict["pk"] = str(instance.pk) + instance.additional_info = {} + instance.additional_info["customer_info"] = customer_dict + instance.additional_info["type"] = "customer" + instance.save() + messages.success(request, _("Customer created successfully.")) + return redirect("customer_list") + return render(request, "customers/customer_form.html", {"form": form}) @@ -2405,11 +2409,11 @@ class EstimateListView(LoginRequiredMixin, ListView): # @csrf_exempt @login_required -def create_estimate(request): +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") @@ -2522,7 +2526,13 @@ def create_estimate(request): item_instance = ItemModel.objects.get(additioinal_info__car_info__hash=items) instance = models.Car.objects.get(hash=item) response = reserve_car(instance, request) - + + opportunity_id = data.get("opportunity_id") + if opportunity_id: + opportunity = models.Opportunity.objects.get(pk=int(opportunity_id)) + opportunity.estimate = estimate + opportunity.save() + url = reverse("estimate_detail", kwargs={"pk": estimate.pk}) return JsonResponse( { @@ -2536,6 +2546,12 @@ def create_estimate(request): entity_slug=entity.slug, user_model=entity.admin ) form.fields["customer"].queryset = entity.get_customers().filter(active=True) + + if pk: + opportunity = models.Opportunity.objects.get(pk=pk) + customer = opportunity.customer + form.initial['customer'] = customer + car_list = models.Car.objects.filter(dealer=dealer).exclude(status="reserved").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').distinct() context = { @@ -2552,6 +2568,7 @@ def create_estimate(request): } for x in car_list ], + "opportunity_id": opportunity_id } return render(request, "sales/estimates/estimate_form.html", context) @@ -2567,7 +2584,7 @@ class EstimateDetailView(LoginRequiredMixin, DetailView): if estimate.get_itemtxs_data(): calculator = CarFinanceCalculator(estimate) finance_data = calculator.get_finance_data() - print(finance_data.get("cars")) + kwargs["data"] = finance_data kwargs["invoice"] = ( InvoiceModel.objects.all().filter(ce_model=estimate).first() @@ -3147,14 +3164,11 @@ def lead_convert(request, pk): dealer = get_user_type(request) if lead.is_converted: messages.error(request, "Lead is already converted to customer.") - return redirect("opportunity_create",pk=lead.pk) - if not models.Car.objects.filter(id_car_make=lead.id_car_make,id_car_model=lead.id_car_model).first(): - messages.error(request, "Cannot convert lead to customer. Car model not found.") - return redirect("lead_list") - - lead.convert_to_customer(dealer.entity) - messages.success(request, "Lead converted to customer successfully!") - return redirect("opportunity_create",pk=lead.pk) + else: + customer = lead.convert_to_customer(dealer.entity) + 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") @login_required def schedule_lead(request, pk): @@ -3206,7 +3220,9 @@ def schedule_lead(request, pk): def send_lead_email(request, pk): lead = get_object_or_404(models.Lead, pk=pk) dealer = get_user_type(request) - lead.convert_to_customer(dealer.entity) + lead.status = models.Status.IN_PROGRESS + lead.save() + # lead.convert_to_customer(dealer.entity) if request.method == "POST": send_email( @@ -3307,6 +3323,17 @@ class OpportunityDetailView(DetailView): template_name = "crm/opportunities/opportunity_detail.html" context_object_name = "opportunity" + def get_context_data(self, **kwargs): + 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["stage"].widget.attrs["hx-get"] = url + form.fields["status"].initial = self.object.status + form.fields["stage"].initial = self.object.stage + context["status_form"] = form + return context + class OpportunityListView(ListView): model = models.Opportunity @@ -3317,8 +3344,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) @@ -3326,6 +3352,21 @@ def delete_opportunity(request, pk): messages.success(request, _("Opportunity deleted successfully.")) return redirect("opportunity_list") +def opportunity_update_status(request,pk): + opportunity = get_object_or_404(models.Opportunity, pk=pk) + status = request.GET.get("status") + stage = request.GET.get("stage") + if status: + opportunity.status = status + if stage: + opportunity.stage = stage + opportunity.save() + messages.success(request,"Opportunity status updated successfully") + # response = HttpResponse(render(request, "crm/opportunities/opportunity_detail.html"),{"opportunity":opportunity}) + response = HttpResponse(redirect("opportunity_detail",pk=opportunity.pk)) + response['HX-Refresh'] = 'true' + return response + # return render(request,"crm/opportunities/opportunity_detail.html",{"opportunity":opportunity}) # class OpportunityLogsView(LoginRequiredMixin, ListView): # model = models.OpportunityLog diff --git a/templates/base.html b/templates/base.html index 10f1ca12..699bdb97 100644 --- a/templates/base.html +++ b/templates/base.html @@ -36,7 +36,7 @@ - + @@ -57,38 +57,10 @@ {% endblock %} - + {% include "toast-alert.html" %}
{% include 'header.html' %} -
{% include "plans/expiration_messages.html" %} {% block period_navigation %} diff --git a/templates/crm/leads/lead_list.html b/templates/crm/leads/lead_list.html index cd61ed1e..539378b5 100644 --- a/templates/crm/leads/lead_list.html +++ b/templates/crm/leads/lead_list.html @@ -79,6 +79,18 @@ {{ _("Channel")|capfirst }}
+ +
+
+ {{ _("Stage")|capfirst }} +
+ + +
+
+ {{ _("Is Opportunity")|capfirst }} +
+ {{ _("Create date") }} @@ -152,6 +164,28 @@ {{ lead.staff|upper }} {{ lead.source|upper }} {{ lead.channel|upper }} + + {% if lead.opportunity.stage == "prospect" %} + {{ lead.opportunity.stage|upper }} + {% elif lead.opportunity.stage == "proposal" %} + {{ lead.opportunity.stage|upper }} + {% elif lead.opportunity.stage == "negotiation" %} + {{ lead.opportunity.stage|upper }} + {% elif lead.opportunity.stage == "closed_won" %} + {{ lead.opportunity.stage|upper }} + {% elif lead.opportunity.stage == "closed_lost" %} + {{ lead.opportunity.stage|upper }} + {% endif %} + + + {% if lead.opportunity %} + + View Details + + {% else %} + {{ _("No") }} + {% endif %} +
diff --git a/templates/crm/opportunities/opportunity_detail.html b/templates/crm/opportunities/opportunity_detail.html index 4e77a9af..e7dd3f31 100644 --- a/templates/crm/opportunities/opportunity_detail.html +++ b/templates/crm/opportunities/opportunity_detail.html @@ -1,1421 +1,1390 @@ {% extends 'base.html' %} {% load i18n static %} - {% block content %} - - -
-
-

O{{ _("Opportunity details")}}

-
-
- - - -
-
-
-
-
-
-
-
-
-

{{ opportunity.car.id_car_make.get_local_name }} - {{ opportunity.car.id_car_model.get_local_name }} - {{ opportunity.car.year }}

-
-
{{ opportunity.car.finances.total }} {{ _("SAR") }}
-
-
-
-
- {% if opportunity.car.id_car_make.logo %} - - {% endif %} -
-
-
{{ opportunity.staff.get_local_name}}
-