bottleneckcrm/backend/leads/serializer.py

346 lines
9.6 KiB
Python

from rest_framework import serializers
from accounts.models import Account
from common.serializer import (
AttachmentsSerializer,
LeadCommentSerializer,
OrganizationSerializer,
ProfileSerializer,
TagsSerializer,
TeamsSerializer,
UserSerializer,
)
from common.utils import LEAD_STATUS
from contacts.serializer import ContactSerializer
from leads.models import Lead, LeadPipeline, LeadStage
class LeadSerializer(serializers.ModelSerializer):
contacts = ContactSerializer(read_only=True, many=True)
assigned_to = ProfileSerializer(read_only=True, many=True)
created_by = UserSerializer()
tags = TagsSerializer(read_only=True, many=True)
lead_attachment = AttachmentsSerializer(read_only=True, many=True)
teams = TeamsSerializer(read_only=True, many=True)
lead_comments = LeadCommentSerializer(read_only=True, many=True)
class Meta:
model = Lead
fields = (
"id",
# Core Lead Information
"title",
"salutation",
"first_name",
"last_name",
"email",
"phone",
"job_title",
"website",
"linkedin_url",
# Sales Pipeline
"status",
"source",
"industry",
"rating",
"opportunity_amount",
"currency",
"probability",
"close_date",
# Address
"address_line",
"city",
"state",
"postcode",
"country",
# Assignment
"assigned_to",
"teams",
# Activity
"last_contacted",
"next_follow_up",
"description",
# Related
"contacts",
"lead_attachment",
"lead_comments",
"tags",
# System
"created_by",
"created_at",
"is_active",
"company_name",
# Kanban
"stage",
"kanban_order",
)
class LeadCreateSerializer(serializers.ModelSerializer):
probability = serializers.IntegerField(
max_value=100, required=False, allow_null=True
)
opportunity_amount = serializers.DecimalField(
max_digits=12, decimal_places=2, required=False, allow_null=True
)
close_date = serializers.DateField(required=False, allow_null=True)
def __init__(self, *args, **kwargs):
request_obj = kwargs.pop("request_obj", None)
super().__init__(*args, **kwargs)
if self.initial_data and self.initial_data.get("status") == "converted":
self.fields["email"].required = True
self.fields["first_name"].required = False
self.fields["last_name"].required = False
self.fields["salutation"].required = False
self.org = request_obj.profile.org
class Meta:
model = Lead
fields = (
# Core Lead Information
"title",
"salutation",
"first_name",
"last_name",
"email",
"phone",
"job_title",
"website",
"linkedin_url",
# Sales Pipeline
"status",
"source",
"industry",
"rating",
"opportunity_amount",
"currency",
"probability",
"close_date",
# Address
"address_line",
"city",
"state",
"postcode",
"country",
# Activity
"last_contacted",
"next_follow_up",
"description",
# System
"company_name",
"is_active",
)
def create(self, validated_data):
# Default currency from org if not provided and has opportunity_amount
if not validated_data.get("currency") and validated_data.get(
"opportunity_amount"
):
request = self.context.get("request")
if request and hasattr(request, "profile") and request.profile.org:
validated_data["currency"] = request.profile.org.default_currency
return super().create(validated_data)
class LeadCreateSwaggerSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
fields = [
# Core Lead Information
"title",
"salutation",
"first_name",
"last_name",
"email",
"phone",
"job_title",
"website",
"linkedin_url",
# Sales Pipeline
"status",
"source",
"industry",
"rating",
"opportunity_amount",
"probability",
"close_date",
# Address
"address_line",
"city",
"state",
"postcode",
"country",
# Assignment & Related
"assigned_to",
"teams",
"contacts",
"tags",
# Activity
"last_contacted",
"next_follow_up",
"description",
# System
"company_name",
]
class CreateLeadFromSiteSwaggerSerializer(serializers.Serializer):
apikey = serializers.CharField()
title = serializers.CharField()
first_name = serializers.CharField()
last_name = serializers.CharField()
phone = serializers.CharField()
email = serializers.CharField()
source = serializers.CharField()
description = serializers.CharField()
class LeadDetailEditSwaggerSerializer(serializers.Serializer):
comment = serializers.CharField()
lead_attachment = serializers.FileField()
class LeadCommentEditSwaggerSerializer(serializers.Serializer):
comment = serializers.CharField()
class LeadUploadSwaggerSerializer(serializers.Serializer):
leads_file = serializers.FileField()
# ============================================
# Kanban Serializers
# ============================================
class LeadStageSerializer(serializers.ModelSerializer):
"""Serializer for lead stages."""
lead_count = serializers.SerializerMethodField()
class Meta:
model = LeadStage
fields = [
"id",
"name",
"order",
"color",
"stage_type",
"maps_to_status",
"win_probability",
"wip_limit",
"lead_count",
"created_at",
"updated_at",
]
read_only_fields = ("id", "created_at", "updated_at", "org")
def get_lead_count(self, obj):
return obj.leads.count()
class LeadPipelineSerializer(serializers.ModelSerializer):
"""Serializer for lead pipelines with nested stages."""
stages = LeadStageSerializer(many=True, read_only=True)
stage_count = serializers.SerializerMethodField()
lead_count = serializers.SerializerMethodField()
class Meta:
model = LeadPipeline
fields = [
"id",
"name",
"description",
"is_default",
"is_active",
"stages",
"stage_count",
"lead_count",
"created_at",
"updated_at",
]
read_only_fields = ("id", "created_at", "updated_at", "org")
def get_stage_count(self, obj):
return obj.stages.count()
def get_lead_count(self, obj):
return Lead.objects.filter(stage__pipeline=obj).count()
class LeadPipelineListSerializer(serializers.ModelSerializer):
"""Simplified pipeline serializer for lists."""
stage_count = serializers.SerializerMethodField()
lead_count = serializers.SerializerMethodField()
class Meta:
model = LeadPipeline
fields = [
"id",
"name",
"description",
"is_default",
"is_active",
"stage_count",
"lead_count",
"created_at",
]
def get_stage_count(self, obj):
return obj.stages.count()
def get_lead_count(self, obj):
return Lead.objects.filter(stage__pipeline=obj).count()
class LeadKanbanCardSerializer(serializers.ModelSerializer):
"""Lightweight serializer for kanban cards (minimal fields for performance)."""
assigned_to = ProfileSerializer(read_only=True, many=True)
full_name = serializers.SerializerMethodField()
class Meta:
model = Lead
fields = [
"id",
"title",
"full_name",
"company_name",
"email",
"rating",
"opportunity_amount",
"currency",
"status",
"stage",
"kanban_order",
"next_follow_up",
"is_follow_up_overdue",
"assigned_to",
"created_at",
]
def get_full_name(self, obj):
return str(obj)
class LeadMoveSerializer(serializers.Serializer):
"""Serializer for moving leads in kanban."""
stage_id = serializers.UUIDField(required=False, allow_null=True)
status = serializers.ChoiceField(choices=LEAD_STATUS, required=False)
kanban_order = serializers.DecimalField(
max_digits=15, decimal_places=6, required=False
)
above_lead_id = serializers.UUIDField(required=False, allow_null=True)
below_lead_id = serializers.UUIDField(required=False, allow_null=True)
def validate(self, attrs):
# Must provide either stage_id or status
if not attrs.get("stage_id") and not attrs.get("status"):
raise serializers.ValidationError(
"Either stage_id or status must be provided"
)
return attrs