update the note in lead and vendor disable

This commit is contained in:
ismail 2025-05-20 18:17:13 +03:00
parent faf4eba38a
commit 5c4ea15fdf
12 changed files with 243 additions and 110 deletions

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.7 on 2025-05-20 12:45
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0002_vendor_active'),
]
operations = [
migrations.AlterField(
model_name='opportunity',
name='customer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='opportunities', to='inventory.customer'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.1.7 on 2025-05-20 12:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0003_alter_opportunity_customer'),
]
operations = [
migrations.AddField(
model_name='opportunity',
name='slug',
field=models.SlugField(blank=True, help_text='Unique slug for the opportunity.', null=True, unique=True, verbose_name='Slug'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 5.1.7 on 2025-05-20 13:45
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inventory', '0004_opportunity_slug'),
]
operations = [
migrations.AddField(
model_name='notes',
name='dealer',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='inventory.dealer'),
preserve_default=False,
),
]

View File

@ -1447,7 +1447,8 @@ class Lead(models.Model):
def full_name(self): def full_name(self):
return f"{self.first_name} {self.last_name}" return f"{self.first_name} {self.last_name}"
def convert_to_customer(self): def convert_to_customer(self):
self.status = Status.QUALIFIED self.status = Status.NEGOTIATION
self.is_converted = True
self.save() self.save()
return self.get_customer_model() return self.get_customer_model()
def get_status(self): def get_status(self):
@ -1587,7 +1588,7 @@ class Opportunity(models.Model):
Dealer, on_delete=models.CASCADE, related_name="opportunities" Dealer, on_delete=models.CASCADE, related_name="opportunities"
) )
customer = models.ForeignKey( customer = models.ForeignKey(
CustomerModel, on_delete=models.CASCADE, related_name="opportunities",null=True,blank=True Customer, on_delete=models.CASCADE, related_name="opportunities",null=True,blank=True
) )
car = models.ForeignKey( car = models.ForeignKey(
Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car") Car, on_delete=models.SET_NULL, null=True, blank=True, verbose_name=_("Car")
@ -1611,15 +1612,22 @@ class Opportunity(models.Model):
created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created")) created = models.DateTimeField(auto_now_add=True, verbose_name=_("Created"))
updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated")) updated = models.DateTimeField(auto_now=True, verbose_name=_("Updated"))
estimate = models.OneToOneField(EstimateModel, related_name="opportunity",on_delete=models.SET_NULL,null=True,blank=True) estimate = models.OneToOneField(EstimateModel, related_name="opportunity",on_delete=models.SET_NULL,null=True,blank=True)
slug = models.SlugField(null=True, blank=True, unique=True,verbose_name=_("Slug"),
help_text=_("Unique slug for the opportunity."))
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(f"opportunity {self.customer.first_name} {self.customer.last_name}")
super(Opportunity, self).save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _("Opportunity") verbose_name = _("Opportunity")
verbose_name_plural = _("Opportunities") verbose_name_plural = _("Opportunities")
def __str__(self): def __str__(self):
return f"Opportunity for {self.customer.customer_name}" return f"Opportunity for {self.customer.first_name} {self.customer.last_name}"
class Notes(models.Model): class Notes(models.Model):
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name="notes")
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.UUIDField() object_id = models.UUIDField()
content_object = GenericForeignKey("content_type", "object_id") content_object = GenericForeignKey("content_type", "object_id")

View File

@ -83,6 +83,11 @@ urlpatterns = [
views.CustomerDetailView.as_view(), views.CustomerDetailView.as_view(),
name="customer_detail", name="customer_detail",
), ),
path(
"customers/<slug:slug>/add-note/",
views.add_note_to_customer,
name="add_note_to_customer",
),
path( path(
"customers/<slug:slug>/update/", "customers/<slug:slug>/update/",
views.CustomerUpdateView.as_view(), views.CustomerUpdateView.as_view(),
@ -94,11 +99,6 @@ urlpatterns = [
views.OpportunityCreateView.as_view(), views.OpportunityCreateView.as_view(),
name="create_opportunity", name="create_opportunity",
), ),
path(
"customers/<slug:slug>/add-note/",
views.add_note_to_customer,
name="add_note_to_customer",
),
path("crm/leads/create/", views.lead_create, name="lead_create"), path("crm/leads/create/", views.lead_create, name="lead_create"),
path( path(
"crm/leads/<slug:slug>/view/", views.LeadDetailView.as_view(), name="lead_detail" "crm/leads/<slug:slug>/view/", views.LeadDetailView.as_view(), name="lead_detail"
@ -109,13 +109,21 @@ urlpatterns = [
path("crm/leads/", views.LeadListView.as_view(), name="lead_list"), path("crm/leads/", views.LeadListView.as_view(), name="lead_list"),
path( path(
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update" "crm/leads/<slug:slug>/update/", views.LeadUpdateView.as_view(), name="lead_update"
), ),
path("crm/leads/<slug:slug>/delete/", views.LeadDeleteView, name="lead_delete"), path("crm/leads/<slug:slug>/delete/", views.LeadDeleteView, name="lead_delete"),
path("crm/leads/<int:pk>/lead-convert/", views.lead_convert, name="lead_convert"), path("crm/leads/<slug:slug>/lead-convert/", views.lead_convert, name="lead_convert"),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note_to_lead"),
path('crm/leads/<int:pk>/update-note/', views.update_note, name='update_note_to_lead'),
path("crm/leads/<int:pk>/delete-note/", views.delete_note, name="delete_note_to_lead"), path("crm/leads/<int:pk>/delete-note/", views.delete_note, name="delete_note_to_lead"),
path(
"crm/<int:pk>/update-note/",
views.update_note,
name="update_note",
),
path(
"crm/<str:content_type>/<slug:slug>/add-note/",
views.add_note,
name="add_note",
),
path( path(
"crm/<int:pk>/update-task/", "crm/<int:pk>/update-task/",
views.update_task, views.update_task,
@ -152,13 +160,12 @@ urlpatterns = [
name="schedule_cancel", name="schedule_cancel",
), ),
path( path(
"crm/leads/<int:pk>/transfer/", "crm/leads/<slug:slug>/transfer/",
views.lead_transfer, views.lead_transfer,
name="lead_transfer", name="lead_transfer",
), ),
path( path(
"crm/opportunities/<int:pk>/add_note/", "crm/opportunities/<slug:slug>/add_note/",
views.add_note_to_opportunity, views.add_note_to_opportunity,
name="add_note_to_opportunity", name="add_note_to_opportunity",
), ),
@ -168,17 +175,22 @@ urlpatterns = [
name="opportunity_create", name="opportunity_create",
), ),
path( path(
"crm/opportunities/<int:pk>/create/", "crm/opportunities/<slug:slug>/create/",
views.OpportunityCreateView.as_view(),
name="lead_opportunity_create",
),
path(
"crm/opportunities/<slug:slug>/create/",
views.OpportunityCreateView.as_view(), views.OpportunityCreateView.as_view(),
name="opportunity_create", name="opportunity_create",
), ),
path( path(
"crm/opportunities/<int:pk>/", "crm/opportunities/<slug:slug>/",
views.OpportunityDetailView.as_view(), views.OpportunityDetailView.as_view(),
name="opportunity_detail", name="opportunity_detail",
), ),
path( path(
"crm/opportunities/<int:pk>/edit/", "crm/opportunities/<slug:slug>/edit/",
views.OpportunityUpdateView.as_view(), views.OpportunityUpdateView.as_view(),
name="update_opportunity", name="update_opportunity",
), ),
@ -193,7 +205,7 @@ urlpatterns = [
name="delete_opportunity", name="delete_opportunity",
), ),
path( path(
"crm/opportunities/<int:pk>/opportunity_update_status/", "crm/opportunities/<slug:slug>/opportunity_update_status/",
views.opportunity_update_status, views.opportunity_update_status,
name="opportunity_update_status", name="opportunity_update_status",
), ),

View File

@ -22,7 +22,7 @@ from django.http.response import StreamingHttpResponse
# Django # Django
from django.db.models import Q from django.db.models import Q
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import IntegrityError, transaction
from django.db.models import Func from django.db.models import Func
from django.contrib import messages from django.contrib import messages
from django.http import Http404, JsonResponse, HttpResponseForbidden from django.http import Http404, JsonResponse, HttpResponseForbidden
@ -1953,7 +1953,7 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
@login_required @login_required
def add_note_to_customer(request, pk): def add_note_to_customer(request, slug):
""" """
This function allows authenticated users to add a note to a specific customer. The This function allows authenticated users to add a note to a specific customer. The
note creation is handled by a form, which is validated after submission. If the form note creation is handled by a form, which is validated after submission. If the form
@ -1970,7 +1970,7 @@ def add_note_to_customer(request, pk):
POST request, it renders the note form template with context including POST request, it renders the note form template with context including
the form and customer. the form and customer.
""" """
customer = get_object_or_404(models.Customer, pk=pk) customer = get_object_or_404(models.Customer, slug=slug)
if request.method == "POST": if request.method == "POST":
form = forms.NoteForm(request.POST) form = forms.NoteForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -1979,7 +1979,7 @@ def add_note_to_customer(request, pk):
note.created_by = request.user note.created_by = request.user
note.save() note.save()
return redirect("customer_detail", pk=customer.pk) return redirect("customer_detail", slug=customer.slug)
else: else:
form = forms.NoteForm() form = forms.NoteForm()
return render( return render(
@ -2206,9 +2206,16 @@ class VendorCreateView(
success_message = _("Vendor created successfully") success_message = _("Vendor created successfully")
def form_valid(self, form): def form_valid(self, form):
if vendor:= models.Vendor.objects.filter(email=form.instance.email).first():
if not vendor.active:
messages.error(self.request, _("Vendor Account with this email is Deactivated,Please Contact Admin"))
else:
messages.error(self.request, _("Vendor with this email already exists"))
return redirect("vendor_create")
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
form.instance.dealer = dealer form.instance.dealer = dealer
form.instance.save() form.instance.save()
return super().form_valid(form) return super().form_valid(form)
@ -4597,6 +4604,7 @@ class LeadDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
context["transfer_form"] = forms.LeadTransferForm() context["transfer_form"] = forms.LeadTransferForm()
context["activity_form"] = forms.ActivityForm() context["activity_form"] = forms.ActivityForm()
context["staff_task_form"] = forms.StaffTaskForm() context["staff_task_form"] = forms.StaffTaskForm()
context["note_form"] = forms.NoteForm()
return context return context
@ -4798,7 +4806,7 @@ def LeadDeleteView(request, slug):
@login_required @login_required
def add_note_to_lead(request, pk): def add_note_to_lead(request, slug):
""" """
Adds a note to a specific lead. This view is accessible only to authenticated Adds a note to a specific lead. This view is accessible only to authenticated
users. The function handles the POST request to create a new note associated users. The function handles the POST request to create a new note associated
@ -4814,7 +4822,7 @@ def add_note_to_lead(request, pk):
note creation or renders the note form template for GET or invalid POST requests. note creation or renders the note form template for GET or invalid POST requests.
:rtype: HttpResponse :rtype: HttpResponse
""" """
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, slug=slug)
if request.method == "POST": if request.method == "POST":
form = forms.NoteForm(request.POST) form = forms.NoteForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -4830,7 +4838,7 @@ def add_note_to_lead(request, pk):
@login_required @login_required
def add_note_to_opportunity(request, pk): def add_note_to_opportunity(request, slug):
""" """
Add a note to a specific opportunity identified by its primary key. Add a note to a specific opportunity identified by its primary key.
@ -4844,7 +4852,7 @@ def add_note_to_opportunity(request, pk):
:type pk: int :type pk: int
:return: A redirect response to the detailed view of the opportunity. :return: A redirect response to the detailed view of the opportunity.
""" """
opportunity = get_object_or_404(models.Opportunity, pk=pk) opportunity = get_object_or_404(models.Opportunity, slug=slug)
if request.method == "POST": if request.method == "POST":
notes = request.POST.get("notes") notes = request.POST.get("notes")
if not notes: if not notes:
@ -4852,7 +4860,7 @@ def add_note_to_opportunity(request, pk):
else: else:
models.Notes.objects.create(content_object=opportunity, created_by=request.user,note=notes) 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) return redirect("opportunity_detail", slug=opportunity.slug)
@login_required @login_required
@ -4910,14 +4918,15 @@ def delete_note(request, pk):
""" """
note = get_object_or_404(models.Notes, pk=pk, created_by=request.user) note = get_object_or_404(models.Notes, pk=pk, created_by=request.user)
lead_pk = note.content_object.pk lead_pk = note.content_object.pk
lead = models.Lead.objects.get(pk=lead_pk)
note.delete() note.delete()
messages.success(request, _("Note deleted successfully.")) messages.success(request, _("Note deleted successfully."))
return redirect("lead_detail", pk=lead_pk) return redirect("lead_detail", slug=lead.slug)
@login_required @login_required
@permission_required("inventory.change_lead", raise_exception=True) @permission_required("inventory.change_lead", raise_exception=True)
def lead_convert(request, pk): def lead_convert(request, slug):
""" """
Converts a lead into a customer and creates a corresponding opportunity. Converts a lead into a customer and creates a corresponding opportunity.
@ -4934,20 +4943,20 @@ def lead_convert(request, pk):
:return: An HTTP response redirecting to the lead list view. :return: An HTTP response redirecting to the lead list view.
:rtype: HttpResponse :rtype: HttpResponse
""" """
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, slug=slug)
dealer = get_user_type(request) dealer = get_user_type(request)
if hasattr(lead, "opportunity"): if hasattr(lead, "opportunity"):
messages.error(request, _("Lead is already converted to customer")) messages.error(request, _("Lead is already converted to customer"))
else: else:
customer = lead.convert_to_customer() customer = lead.convert_to_customer()
models.Opportunity.objects.create(dealer=dealer,customer=customer,lead=lead,probability=50,stage=models.Stage.PROSPECT,staff=lead.staff,status=models.Status.QUALIFIED) models.Opportunity.objects.create(dealer=dealer,customer=customer,lead=lead,probability=50,stage=models.Stage.NEGOTIATION,staff=lead.staff)
messages.success(request, _("Lead converted to customer successfully")) messages.success(request, _("Lead converted to customer successfully"))
return redirect("lead_list") return redirect("lead_list")
@login_required @login_required
@permission_required("inventory.add_lead", raise_exception=True) @permission_required("inventory.add_lead", raise_exception=True)
def schedule_lead(request, pk): def schedule_lead(request, slug):
""" """
Handles the scheduling of a lead for an appointment. Handles the scheduling of a lead for an appointment.
@ -4970,7 +4979,7 @@ def schedule_lead(request, pk):
messages.error(request, _("You do not have permission to schedule lead")) messages.error(request, _("You do not have permission to schedule lead"))
return redirect("lead_list") return redirect("lead_list")
dealer = get_user_type(request) dealer = get_user_type(request)
lead = get_object_or_404(models.Lead, pk=pk, dealer=dealer) lead = get_object_or_404(models.Lead, slug=slug, dealer=dealer)
if request.method == "POST": if request.method == "POST":
form = forms.ScheduleForm(request.POST) form = forms.ScheduleForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -4998,7 +5007,7 @@ def schedule_lead(request, pk):
) )
except ValidationError as e: except ValidationError as e:
messages.error(request, str(e)) messages.error(request, str(e))
return redirect("schedule_lead", pk=lead.pk) return redirect("schedule_lead", slug=lead.slug)
client = get_object_or_404(User, email=lead.email) client = get_object_or_404(User, email=lead.email)
# Create Appointment # Create Appointment
@ -5022,7 +5031,7 @@ def schedule_lead(request, pk):
@login_required @login_required
@permission_required("inventory.change_lead", raise_exception=True) @permission_required("inventory.change_lead", raise_exception=True)
def lead_transfer(request, pk): def lead_transfer(request, slug):
""" """
Handles the transfer of a lead to a different staff member. This view is accessible Handles the transfer of a lead to a different staff member. This view is accessible
only to authenticated users with the 'inventory.change_lead' permission. If the only to authenticated users with the 'inventory.change_lead' permission. If the
@ -5034,7 +5043,7 @@ def lead_transfer(request, pk):
:param pk: The primary key of the lead to be transferred. :param pk: The primary key of the lead to be transferred.
:return: An HTTP redirect response to the lead list view. :return: An HTTP redirect response to the lead list view.
""" """
lead = get_object_or_404(models.Lead, pk=pk) lead = get_object_or_404(models.Lead, slug=slug)
if request.method == "POST": if request.method == "POST":
form = forms.LeadTransferForm(request.POST) form = forms.LeadTransferForm(request.POST)
if form.is_valid(): if form.is_valid():
@ -5194,24 +5203,14 @@ class OpportunityCreateView(CreateView,SuccessMessageMixin, LoginRequiredMixin):
template_name = "crm/opportunities/opportunity_form.html" template_name = "crm/opportunities/opportunity_form.html"
success_message = "Opportunity created successfully." success_message = "Opportunity created successfully."
def get_context_data(self, **kwargs): def get_initial(self):
context = super().get_context_data(**kwargs) initial = super().get_initial()
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
return context if self.kwargs.get("slug", None):
lead = models.Lead.objects.get(slug=self.kwargs.get("slug"),dealer=dealer)
# def get_form_kwargs(self): initial["lead"] = lead
# kwargs = super().get_form_kwargs() initial['stage'] = models.Stage.PROPOSAL
# dealer = get_user_type(self.request) return initial
# kwargs["car"].queryset = models.Car.objects.filter(dealer=dealer,)
# return kwargs
# def get_initial(self):
# initial = super().get_initial()
# if self.kwargs.get("pk", None):
# lead = models.Lead.objects.get(pk=self.kwargs.get("pk"))
# initial["customer"] = lead.customer
# return initial
def form_valid(self, form): def form_valid(self, form):
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
@ -5252,7 +5251,7 @@ class OpportunityUpdateView(LoginRequiredMixin,SuccessMessageMixin, UpdateView):
success_message = "Opportunity updated successfully." success_message = "Opportunity updated successfully."
def get_success_url(self): def get_success_url(self):
return reverse_lazy("opportunity_detail", kwargs={"pk": self.object.pk}) return reverse_lazy("opportunity_detail", kwargs={"slug": self.object.slug})
class OpportunityDetailView(LoginRequiredMixin, DetailView): class OpportunityDetailView(LoginRequiredMixin, DetailView):
@ -5360,7 +5359,7 @@ def delete_opportunity(request, pk):
@login_required @login_required
def opportunity_update_status(request, pk): def opportunity_update_status(request, slug):
""" """
Update the status and/or stage of a specific Opportunity instance. This is a Update the status and/or stage of a specific Opportunity instance. This is a
view function, which is generally tied to a URL endpoint in a Django application. view function, which is generally tied to a URL endpoint in a Django application.
@ -5385,7 +5384,7 @@ def opportunity_update_status(request, pk):
frontend behavior. frontend behavior.
:rtype: HttpResponse :rtype: HttpResponse
""" """
opportunity = get_object_or_404(models.Opportunity, pk=pk) opportunity = get_object_or_404(models.Opportunity, slug=slug)
status = request.GET.get("status") status = request.GET.get("status")
stage = request.GET.get("stage") stage = request.GET.get("stage")
if status: if status:
@ -5394,7 +5393,7 @@ def opportunity_update_status(request, pk):
opportunity.stage = stage opportunity.stage = stage
opportunity.save() opportunity.save()
messages.success(request,_("Opportunity status updated successfully")) messages.success(request,_("Opportunity status updated successfully"))
response = HttpResponse(redirect("opportunity_detail",pk=opportunity.pk)) response = HttpResponse(redirect("opportunity_detail",slug=opportunity.slug))
response['HX-Refresh'] = 'true' response['HX-Refresh'] = 'true'
return response return response
@ -7784,6 +7783,7 @@ def add_activity(request,content_type,slug):
else: else:
messages.error(request, _("Activity form is not valid")) messages.error(request, _("Activity form is not valid"))
return redirect(f"{content_type}_detail", slug=slug) return redirect(f"{content_type}_detail", slug=slug)
def add_task(request,content_type,slug): def add_task(request,content_type,slug):
try: try:
model = apps.get_model(f'inventory.{content_type}') model = apps.get_model(f'inventory.{content_type}')
@ -7824,4 +7824,43 @@ def update_task(request,pk):
tasks = models.Tasks.objects.filter( tasks = models.Tasks.objects.filter(
content_type__model="lead", object_id=lead.id content_type__model="lead", object_id=lead.id
) )
return render(request,'crm/leads/lead_detail.html',{'lead':lead,'tasks':tasks}) return render(request,'crm/leads/lead_detail.html',{'lead':lead,'tasks':tasks})
def add_note(request,content_type,slug):
try:
model = apps.get_model(f'inventory.{content_type}')
except LookupError:
raise Http404("Model not found")
obj = get_object_or_404(model, slug=slug)
dealer = get_user_type(request)
if request.method == "POST":
form = forms.NoteForm(request.POST)
if form.is_valid():
note = form.save(commit=False)
note.dealer = dealer
note.content_object = obj
note.created_by = request.user
note.save()
messages.success(request, _("Note added successfully"))
else:
print(form.errors)
messages.error(request, _("Note form is not valid"))
return redirect(f"{content_type}_detail", slug=slug)
def update_note(request,pk):
note = get_object_or_404(models.Notes, pk=pk)
lead = get_object_or_404(models.Lead, pk=note.content_object.id)
dealer = get_user_type(request)
if request.method == "POST":
note.note = request.POST.get('note')
note.save()
messages.success(request, _("Note updated successfully"))
return redirect(f"lead_detail", slug=lead.slug)
else:
messages.error(request, _("Note form is not valid"))
notes = models.Notes.objects.filter(
content_type__model="lead", object_id=lead.id,dealer=dealer
)
return render(request,'crm/leads/lead_detail.html',{'lead':lead,'notes':notes})

View File

@ -230,7 +230,7 @@
<div class="modal fade" id="exampleModal" tabindex="-1" aria-hidden="true"> <div class="modal fade" id="exampleModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<form class="modal-content" action="{% url 'lead_transfer' lead.pk %}" method="post"> <form class="modal-content" action="{% url 'lead_transfer' lead.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Reassign Lead To Another Employee</h5> <h5 class="modal-title" id="exampleModalLabel">Reassign Lead To Another Employee</h5>
@ -301,10 +301,7 @@
<div class="tab-pane fade" id="tab-notes" role="tabpanel" aria-labelledby="notes-tab"> <div class="tab-pane fade" id="tab-notes" role="tabpanel" aria-labelledby="notes-tab">
<div class="mb-1 d-flex align-items-center justify-content-between"> <div class="mb-1 d-flex align-items-center justify-content-between">
<h3 class="mb-4" id="scrollspyNotes">{{ _("Notes") }}</h3> <h3 class="mb-4" id="scrollspyNotes">{{ _("Notes") }}</h3>
<a id="addBtn" href="#" class="btn btn-sm btn-phoenix-primary mb-3 btn-sm" data-url="{% url 'add_note_to_lead' lead.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>"> <button class="btn btn-phoenix-primary btn-sm" type="button" onclick="reset_form()" data-bs-toggle="modal" data-bs-target="#noteModal"><span class="fas fa-plus me-1"></span>{{ _("Add Note") }}</button>
<span class="fas fa-plus me-1"></span>
{% trans 'Add Note' %}
</a>
</div> </div>
<div class="border-top border-bottom border-translucent" id="leadDetailsTable"> <div class="border-top border-bottom border-translucent" id="leadDetailsTable">
@ -331,9 +328,18 @@
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created|naturalday|capfirst }}</td> <td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created|naturalday|capfirst }}</td>
<td class="align-middle text-end white-space-nowrap pe-0 action py-2"> <td class="align-middle text-end white-space-nowrap pe-0 action py-2">
{% if note.created_by == request.user %} {% if note.created_by == request.user %}
<a id="updateBtn" href="#" class="btn btn-sm btn-phoenix-primary me-2" data-url="{% url 'update_note_to_lead' note.pk %}" data-bs-toggle="modal" data-bs-target="#noteModal" data-note-title="{{ _("Update") }}<i class='fas fa-pen-square text-primary ms-2'></i>"> <a id="updateBtn"
<i class="fas fa-pen"></i> href="#"
</a> onclick="updateNote(this)"
class="btn btn-sm btn-phoenix-primary me-2"
data-pk="{{ note.pk }}"
data-note="{{ note.note|escapejs }}"
data-url="{% url 'update_note' note.pk %}"
data-bs-toggle="modal"
data-bs-target="#noteModal"
data-note-title="{{ _('Update') }}<i class='fas fa-pen-square text-primary ms-2'></i>">
{{ _("Update") }}
</a>
<button class="btn btn-phoenix-danger btn-sm delete-btn" <button class="btn btn-phoenix-danger btn-sm delete-btn"
data-url="{% url 'delete_note_to_lead' note.pk %}" data-url="{% url 'delete_note_to_lead' note.pk %}"
data-message="Are you sure you want to delete this note?" data-message="Are you sure you want to delete this note?"
@ -535,7 +541,7 @@
</div> </div>
{% include 'modal/delete_modal.html' %} {% include 'modal/delete_modal.html' %}
<!-- add update Modal --> <!-- add update Modal -->
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true"> {% comment %} <div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md"> <div class="modal-dialog modal-md">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0"> <div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
@ -549,7 +555,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> {% endcomment %}
<!-- activity Modal --> <!-- activity Modal -->
{% include "components/activity_modal.html" with content_type="lead" slug=lead.slug %} {% include "components/activity_modal.html" with content_type="lead" slug=lead.slug %}
@ -559,7 +565,7 @@
<div class="modal-dialog modal-md"> <div class="modal-dialog modal-md">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0"> <div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
<h4 class="modal-title" id="noteModalLabel">{% trans 'Task' %}</h4> <h4 class="modal-title" id="taskModalLabel">{% trans 'Task' %}</h4>
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close"> <button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
<span class="fas fa-times"></span> <span class="fas fa-times"></span>
</button> </button>
@ -574,34 +580,44 @@
</div> </div>
</div> </div>
</div> </div>
<!-- note Modal -->
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-md">
<div class="modal-content">
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
<h4 class="modal-title" id="noteModalLabel">{% trans 'Note' %}</h4>
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
<span class="fas fa-times"></span>
</button>
</div>
<div class="modal-body">
<form action="{% url 'add_note' 'lead' lead.slug %}" method="post" class="add_note_form">
{% csrf_token %}
{{ note_form|crispy }}
<button type="submit" class="btn btn-success w-100">{% trans 'Save' %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock content %} {% endblock content %}
{% block customJS %} {% block customJS %}
<script> <script>
function updateNote(e) {
let url = e.getAttribute('data-url')
let note = e.getAttribute('data-note')
document.querySelector('#id_note').value = note
let form = document.querySelector('.add_note_form')
form.action = url
}
function reset_form() {
document.querySelector('#id_note').value = ""
let form = document.querySelector('.add_note_form')
form.action = "{% url 'add_note' 'lead' lead.slug %}"
}
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const noteModal = document.getElementById("noteModal");
const modalTitle = document.getElementById("noteModalLabel");
const modalBody = noteModal.querySelector(".modal-body");
noteModal.addEventListener("show.bs.modal", function (event) {
const button = event.relatedTarget;
const url = button.getAttribute("data-url");
const title = button.getAttribute("data-note-title");
fetch(url)
.then((response) => response.text())
.then((html) => {
modalBody.innerHTML = html;
modalTitle.innerHTML = title;
})
.catch((error) => {
modalBody.innerHTML = '<p class="text-danger">{% trans 'Error loading form. Please try again later' %}.</p>';
console.error("Error loading form:", error);
});
});
});
let Toast = Swal.mixin({ let Toast = Swal.mixin({
toast: true, toast: true,
position: "top-end", position: "top-end",
@ -723,5 +739,6 @@
titleText: msg titleText: msg
}); });
} }
});
</script> </script>
{% endblock customJS %} {% endblock customJS %}

View File

@ -240,7 +240,7 @@
</td> {% endcomment %} </td> {% endcomment %}
<td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight"> <td class="align-middle white-space-nowrap text-body-tertiary text-opacity-85 fw-semibold text-body-highlight">
{% if lead.opportunity %} {% if lead.opportunity %}
<a href="{% url 'opportunity_detail' lead.opportunity.id %}"> <a href="{% url 'opportunity_detail' lead.opportunity.slug %}">
<span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span> <span class="badge badge-phoenix badge-phoenix-success">Opportunity {{ lead.opportunity.lead}} <i class="fa-solid fa-arrow-up-right-from-square"></i></span>
</a> </a>
{% endif %} {% endif %}
@ -260,15 +260,15 @@
</button> </button>
<div class="dropdown-menu dropdown-menu-end py-2"> <div class="dropdown-menu dropdown-menu-end py-2">
{% if perms.inventory.change_lead %} {% if perms.inventory.change_lead %}
<a href="{% url 'lead_update' lead.id %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a> <a href="{% url 'lead_update' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
{% endif %} {% endif %}
<button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.id }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')"> <button class="dropdown-item text-primary" onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
{% trans "Update Actions" %} {% trans "Update Actions" %}
</button> </button>
<a href="{% url 'send_lead_email' lead.id %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a> <a href="{% url 'send_lead_email' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Send Email" %}</a>
<a href="{% url 'schedule_lead' lead.id %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a> <a href="{% url 'schedule_lead' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Schedule Event" %}</a>
{% if not lead.opportunity %} {% if not lead.opportunity %}
<a href="{% url 'lead_convert' lead.id %}" class="dropdown-item text-success-dark">{% trans "Convert" %}</a> <a href="{% url 'lead_opportunity_create' lead.slug %}" class="dropdown-item text-success-dark">{% trans "Convert to Opportunity" %}</a>
{% endif %} {% endif %}
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
{% if perms.inventory.delete_lead %} {% if perms.inventory.delete_lead %}

View File

@ -3,7 +3,7 @@
{% if form.instance.pk %} {% if form.instance.pk %}
<form method="post" action="{% url 'update_note_to_lead' note.pk %}" enctype="multipart/form-data"> <form method="post" action="{% url 'update_note_to_lead' note.pk %}" enctype="multipart/form-data">
{% else %} {% else %}
<form method="post" action="{% url 'add_note_to_lead' lead.pk %}" enctype="multipart/form-data"> <form method="post" action="{% url 'add_note_to_lead' lead.slug %}" enctype="multipart/form-data">
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}

View File

@ -18,7 +18,7 @@
<a class="dropdown-item" href="{% url 'estimate_create_from_opportunity' opportunity.pk %}">{{ _("Create Quotation")}}</a> <a class="dropdown-item" href="{% url 'estimate_create_from_opportunity' opportunity.pk %}">{{ _("Create Quotation")}}</a>
{% endif %} {% endif %}
</li> </li>
<li><a class="dropdown-item" href="{% url 'update_opportunity' opportunity.pk %}">Update Opportunity</a></li> <li><a class="dropdown-item" href="{% url 'update_opportunity' opportunity.slug %}">Update Opportunity</a></li>
<li><a class="dropdown-item text-danger" href="">Delete Opportunity</a></li> <li><a class="dropdown-item text-danger" href="">Delete Opportunity</a></li>
</ul> </ul>
</div> </div>
@ -79,7 +79,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card"> {% comment %} <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="mb-5">{{ _("Other Information")}}</h4> <h4 class="mb-5">{{ _("Other Information")}}</h4>
<div class="row g-3"> <div class="row g-3">
@ -101,7 +101,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> {% endcomment %}
</div> </div>
</div> </div>
<div class="col-xl-7 col-xxl-8"> <div class="col-xl-7 col-xxl-8">
@ -330,7 +330,7 @@
</div> </div>
<div class="tab-pane fade" id="tab-notes" role="tabpanel" aria-labelledby="notes-tab"> <div class="tab-pane fade" id="tab-notes" role="tabpanel" aria-labelledby="notes-tab">
<h2 class="mb-4">Notes</h2> <h2 class="mb-4">Notes</h2>
<form action="{% url 'add_note_to_opportunity' opportunity.pk %}" method="post"> <form action="{% url 'add_note_to_opportunity' opportunity.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
<textarea class="form-control mb-3" id="notes" rows="4" name="notes" required> </textarea> <textarea class="form-control mb-3" id="notes" rows="4" name="notes" required> </textarea>
<button type="submit" class="btn btn-primary mb-3">Add Note</button> <button type="submit" class="btn btn-primary mb-3">Add Note</button>
@ -366,7 +366,7 @@
<button class="btn btn-link p-0 ms-3 fs-9 text-primary fw-bold text-decoration-none"><span class="fas fa-sort me-1 fw-extra-bold fs-10"></span>Sorting</button> <button class="btn btn-link p-0 ms-3 fs-9 text-primary fw-bold text-decoration-none"><span class="fas fa-sort me-1 fw-extra-bold fs-10"></span>Sorting</button>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<a href="{% url 'schedule_lead' opportunity.lead.id %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Meeting </a> <a href="{% url 'schedule_lead' opportunity.lead.slug %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Meeting </a>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3">
@ -394,7 +394,7 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<a href="{% url 'schedule_lead' opportunity.lead.id %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Call</a> <a href="{% url 'schedule_lead' opportunity.lead.slug %}" class="btn btn-primary"><span class="fa-solid fa-plus me-2"></span>Add Call</a>
</div> </div>
</div> </div>
<div class="border-top border-bottom border-translucent" id="leadDetailsTable" data-list='{"valueNames":["name","description","create_date","create_by","last_activity"],"page":5,"pagination":true}'> <div class="border-top border-bottom border-translucent" id="leadDetailsTable" data-list='{"valueNames":["name","description","create_date","create_by","last_activity"],"page":5,"pagination":true}'>
@ -553,5 +553,5 @@
</div> </div>
</div> </div>
</div> </div>
{% include "components/activity_modal.html" with content_type="opportunity" pk=opportunity.pk %} {% include "components/activity_modal.html" with content_type="opportunity" slug=opportunity.slug %}
{% endblock %} {% endblock %}

View File

@ -96,10 +96,10 @@
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a class="btn btn-sm btn-phoenix-primary" href="{% url 'opportunity_detail' opportunity.pk %}"> <a class="btn btn-sm btn-phoenix-primary" href="{% url 'opportunity_detail' opportunity.slug %}">
{{ _("View Details") }} <i class="fa-solid fa-eye ms-2"></i> {{ _("View Details") }} <i class="fa-solid fa-eye ms-2"></i>
</a> </a>
<a class="btn btn-sm btn-phoenix-success" href="{% url 'update_opportunity' opportunity.pk %}"> <a class="btn btn-sm btn-phoenix-success" href="{% url 'update_opportunity' opportunity.slug %}">
{{ _("Update") }} <i class="fa-solid fa-pen ms-2"></i> {{ _("Update") }} <i class="fa-solid fa-pen ms-2"></i>
</a> </a>
</div> </div>

View File

@ -1,6 +1,6 @@
{% load i18n static crispy_forms_filters %} {% load i18n static crispy_forms_filters %}
<form method="post" action="{% url 'add_note_to_customer' customer.pk %}" enctype="multipart/form-data"> <form method="post" action="{% url 'add_note_to_customer' customer.slug %}" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<button type="submit" class="btn btn-sm btn-success w-100">{{ _("Add") }}</button> <button type="submit" class="btn btn-sm btn-success w-100">{{ _("Add") }}</button>