39448-vm/core/views.py
2026-04-03 12:01:07 +00:00

149 lines
6.6 KiB
Python

from django.contrib import messages
from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from .forms import ActivityForm, QuoteLineForm, StageUpdateForm, WorkspaceIntakeForm
from .models import SalesWorkspace
def _base_shell_context():
return {
"project_name": "FlowDesk Sales",
"project_tagline": "Compact sales workspace for fast lead-to-quote execution",
"meta_description": (
"Single-screen sales workspace with compact navigation, opportunity tracking, "
"quotation drafting, projects, and agenda panels."
),
"nav_items": [
{"icon": "bi-grid-1x2-fill", "label": "Workspace", "url": "/"},
{"icon": "bi-people-fill", "label": "Contacts", "url": "/"},
{"icon": "bi-graph-up-arrow", "label": "Opportunities", "url": "/"},
{"icon": "bi-receipt-cutoff", "label": "Quotations", "url": "/"},
{"icon": "bi-kanban-fill", "label": "Projects", "url": "/"},
{"icon": "bi-calendar3", "label": "Agenda", "url": "/"},
{"icon": "bi-shield-lock-fill", "label": "Admin", "url": "/admin/"},
],
}
def _workspace_queryset(query=None):
queryset = SalesWorkspace.objects.prefetch_related("quote_lines", "activities")
if query:
queryset = queryset.filter(
Q(customer_name__icontains=query)
| Q(project_name__icontains=query)
| Q(project_number__icontains=query)
| Q(zipcode__icontains=query)
| Q(address__icontains=query)
| Q(opportunity_title__icontains=query)
)
return queryset
def home(request):
query = request.GET.get("q", "").strip()
if request.method == "POST":
create_form = WorkspaceIntakeForm(request.POST, prefix="workspace")
if create_form.is_valid():
workspace = create_form.save(commit=False)
workspace.stage = SalesWorkspace.Stage.NEW
workspace.save()
if workspace.next_meeting_at:
workspace.activities.create(
title="Initial meeting",
activity_type="meeting",
due_at=workspace.next_meeting_at,
owner=workspace.contact_name or "Sales",
notes="Auto-created from workspace intake.",
)
messages.success(request, f"{workspace.customer_name} workspace created.")
return redirect("workspace_detail", pk=workspace.pk)
messages.error(request, "Please review the highlighted fields and try again.")
else:
create_form = WorkspaceIntakeForm(prefix="workspace")
workspaces = list(_workspace_queryset(query)[:8])
upcoming_items = []
for workspace in workspaces:
upcoming_items.extend([item for item in workspace.activities.all() if not item.is_done])
upcoming_items.sort(key=lambda item: item.due_at)
all_workspaces = _workspace_queryset()
active_workspaces = [item for item in all_workspaces if item.stage not in {SalesWorkspace.Stage.WON, SalesWorkspace.Stage.LOST}]
context = {
**_base_shell_context(),
"meta_title": "FlowDesk Sales Workspace",
"search_query": query,
"create_form": create_form,
"workspaces": workspaces,
"workspace_count": all_workspaces.count(),
"active_count": len(active_workspaces),
"won_count": sum(1 for item in all_workspaces if item.stage == SalesWorkspace.Stage.WON),
"pipeline_total": sum((item.estimated_value for item in active_workspaces), 0),
"upcoming_items": upcoming_items[:5],
"stage_choices": SalesWorkspace.Stage.choices,
"layout_choices": SalesWorkspace.LayoutTemplate.choices,
}
return render(request, "core/index.html", context)
def workspace_detail(request, pk):
workspace = get_object_or_404(_workspace_queryset(), pk=pk)
context = {
**_base_shell_context(),
"meta_title": f"{workspace.customer_name} · Workspace",
"meta_description": f"Compact sales workspace for {workspace.customer_name} and project {workspace.project_name or workspace.project_number or workspace.opportunity_title}.",
"workspace": workspace,
"stage_form": StageUpdateForm(prefix="stage", current_stage=workspace.stage),
"quote_form": QuoteLineForm(prefix="quote"),
"activity_form": ActivityForm(prefix="activity"),
"quote_lines": workspace.quote_lines.all(),
"activities": workspace.activities.all(),
"upcoming_activities": [item for item in workspace.activities.all() if not item.is_done],
"completed_activities": [item for item in workspace.activities.all() if item.is_done],
}
return render(request, "core/workspace_detail.html", context)
def update_stage(request, pk):
workspace = get_object_or_404(SalesWorkspace, pk=pk)
form = StageUpdateForm(request.POST, prefix="stage", current_stage=workspace.stage)
if request.method == "POST" and form.is_valid():
workspace.stage = form.cleaned_data["stage"]
workspace.save(update_fields=["stage", "updated_at"])
messages.success(request, f"Stage updated to {workspace.get_stage_display()}.")
else:
messages.error(request, "Could not update the stage. Please choose a valid value.")
return redirect("workspace_detail", pk=workspace.pk)
def add_quote_line(request, pk):
workspace = get_object_or_404(SalesWorkspace, pk=pk)
form = QuoteLineForm(request.POST, prefix="quote")
if request.method == "POST" and form.is_valid():
quote_line = form.save(commit=False)
quote_line.workspace = workspace
quote_line.save()
if workspace.stage in {SalesWorkspace.Stage.NEW, SalesWorkspace.Stage.QUALIFIED}:
workspace.stage = SalesWorkspace.Stage.PROPOSAL
workspace.save(update_fields=["stage", "updated_at"])
messages.success(request, f"Added quote line: {quote_line.product_name}.")
else:
messages.error(request, "Please correct the quote line fields and try again.")
return redirect("workspace_detail", pk=workspace.pk)
def add_activity(request, pk):
workspace = get_object_or_404(SalesWorkspace, pk=pk)
form = ActivityForm(request.POST, prefix="activity")
if request.method == "POST" and form.is_valid():
activity = form.save(commit=False)
activity.workspace = workspace
activity.save()
messages.success(request, f"Activity scheduled: {activity.title}.")
else:
messages.error(request, "Please correct the activity fields and try again.")
return redirect("workspace_detail", pk=workspace.pk)