| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- from __future__ import annotations
- from django.shortcuts import render, get_object_or_404, redirect
- from django.contrib.admin.views.decorators import staff_member_required
- from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
- import django_filters as filters
- from django.contrib import messages
- from django.urls import reverse
- from django.contrib.auth import get_user_model
- from datetime import datetime
- from django.contrib.contenttypes.models import ContentType
- from admin_frontend.templatetags.public_urls import public_route
- from admin_frontend.nav import _nav_items
- from .models import Lead
- @staff_member_required
- @public_route(label="Leads", order=22, icon="envelope")
- def leads_list(request):
- """List Leads captured from the public site with filtering and pagination."""
- class LeadFilter(filters.FilterSet):
- name = filters.CharFilter(field_name="name", lookup_expr="icontains", label="Name")
- email = filters.CharFilter(field_name="email", lookup_expr="icontains", label="Email")
- phone = filters.CharFilter(field_name="phone", lookup_expr="icontains", label="Phone")
- org = filters.CharFilter(field_name="organization__code", lookup_expr="icontains", label="Org Code")
- source = filters.ChoiceFilter(field_name="source", label="Source", choices=Lead.LeadSource.choices)
- created_at = filters.DateTimeFromToRangeFilter(
- field_name="created_at",
- label="Created between",
- widget=filters.widgets.RangeWidget(
- attrs={
- "type": "datetime-local",
- "style": "color-scheme: light;",
- "class": "border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
- }
- ),
- )
- class Meta:
- model = Lead
- fields = ["name", "email", "phone", "org", "source", "created_at"]
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- try:
- from crispy_forms.helper import FormHelper
- from crispy_forms.layout import Layout, Field
- helper = FormHelper()
- helper.form_tag = False
- helper.layout = Layout(
- Field("name", template="crispy/field_nowrap.html"),
- Field("email", template="crispy/field_nowrap.html"),
- Field("phone", template="crispy/field_nowrap.html"),
- Field("org", template="crispy/field_nowrap.html"),
- Field("source", template="crispy/field_nowrap.html"),
- Field("created_at", template="crispy/field_nowrap.html"),
- )
- self.form.helper = helper
- except Exception:
- pass
- qs = Lead.objects.select_related("organization").order_by("-created_at")
- lead_filter = LeadFilter(request.GET, queryset=qs)
- paginator = Paginator(lead_filter.qs, 10)
- page = request.GET.get("page")
- try:
- page_obj = paginator.page(page)
- except PageNotAnInteger:
- page_obj = paginator.page(1)
- except EmptyPage:
- page_obj = paginator.page(paginator.num_pages)
- context = {
- "nav": _nav_items(),
- "leads": page_obj.object_list,
- "page_obj": page_obj,
- "filter": lead_filter,
- }
- return render(request, "admin_frontend/leads_list.html", context)
- @staff_member_required
- def lead_detail(request, pk: int):
- lead = get_object_or_404(Lead.objects.select_related("organization"), pk=pk)
- def _parse_pickup_details(message: str):
- data = {"address": "", "preferred": "", "materials": ""}
- try:
- for line in (message or "").splitlines():
- if line.lower().startswith("address:"):
- data["address"] = line.split(":", 1)[1].strip()
- elif line.lower().startswith("preferred:"):
- data["preferred"] = line.split(":", 1)[1].strip()
- elif line.lower().startswith("materials:"):
- data["materials"] = line.split(":", 1)[1].strip()
- except Exception:
- pass
- return data
- suggested = _parse_pickup_details(getattr(lead, "message", "") or "")
- # Fetch any uploaded Documents attached to this Lead
- try:
- from recycle_core.models import Document
- ct = ContentType.objects.get_for_model(Lead)
- documents = Document.objects.filter(content_type=ct, object_id=lead.id).order_by("-created_at")
- except Exception:
- documents = []
- User = get_user_model()
- drivers = User.objects.all().order_by("username")
- context = {"nav": _nav_items(), "lead": lead, "drivers": drivers, "suggested": suggested, "documents": documents}
- return render(request, "admin_frontend/lead_detail.html", context)
- @staff_member_required
- def lead_convert_to_pickup(request, pk: int):
- lead = get_object_or_404(Lead.objects.select_related("organization"), pk=pk)
- if request.method != "POST":
- return redirect("public_frontend_admin:lead_detail", pk=pk)
- from recycle_core.models import Customer, CustomerSite, PickupOrder
- address = (request.POST.get("address") or "").strip()
- scheduled_at_raw = (request.POST.get("scheduled_at") or "").strip()
- driver_id = (request.POST.get("driver") or "").strip()
- if not address:
- messages.error(request, "Address is required to create a pickup.")
- return redirect("public_frontend_admin:lead_detail", pk=pk)
- scheduled_at = None
- if scheduled_at_raw:
- try:
- scheduled_at = datetime.fromisoformat(scheduled_at_raw)
- except Exception:
- messages.error(request, "Invalid date/time format for schedule.")
- return redirect("public_frontend_admin:lead_detail", pk=pk)
- # Find or create customer for this lead under the same organization
- org = lead.organization
- customer = None
- if lead.email:
- customer = Customer.objects.filter(organization=org, email__iexact=lead.email).first()
- if customer is None:
- customer = Customer.objects.filter(organization=org, name__iexact=lead.name).first()
- if customer is None:
- customer = Customer.objects.create(
- organization=org,
- name=lead.name,
- email=lead.email,
- phone=lead.phone,
- billing_address="",
- )
- # Create a site for the pickup
- site = CustomerSite.objects.create(
- customer=customer,
- name="",
- address=address,
- contact_name=lead.name,
- contact_phone=lead.phone,
- contact_email=lead.email,
- )
- pickup = PickupOrder.objects.create(
- organization=org,
- customer=customer,
- site=site,
- status=PickupOrder.STATUS_REQUESTED,
- scheduled_at=scheduled_at,
- )
- # Optionally assign a driver and mark scheduled
- if driver_id:
- try:
- User = get_user_model()
- driver = User.objects.get(pk=int(driver_id))
- pickup.assigned_driver = driver
- if scheduled_at:
- pickup.status = PickupOrder.STATUS_SCHEDULED
- pickup.save(update_fields=["assigned_driver", "status"])
- except Exception:
- pass
- messages.success(request, f"Created pickup #{pickup.id} for customer '{customer.name}'.")
- return redirect("recycle_core:pickups_list")
|