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)