The changes for the new

This commit is contained in:
2026-03-24 19:21:25 +05:30
parent c04395afc9
commit b54439a4c2
6 changed files with 391 additions and 2 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ db.sqlite3
/media/
/staticfiles/
.env
venv/

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.3 on 2026-03-14 19:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0009_user_partner'),
]
operations = [
migrations.AlterField(
model_name='user',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@@ -0,0 +1,23 @@
# Generated by Django 6.0.3 on 2026-03-14 19:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('events', '0008_event_is_partner_event_event_partner'),
]
operations = [
migrations.AlterField(
model_name='event',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='eventimages',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.3 on 2026-03-14 19:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('master_data', '0002_eventtype_event_type_icon'),
]
operations = [
migrations.AlterField(
model_name='eventtype',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@@ -1,15 +1,26 @@
from decimal import Decimal
from urllib.parse import quote_plus
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.forms.models import model_to_dict
from django.contrib.auth import get_user_model
from django.db import models as django_models
from django.db.models import Sum, F, Max, Min, DecimalField
from django.utils import timezone
from datetime import timedelta
from datetime import timedelta, datetime, date
from rest_framework.views import APIView
from partner.models import Partner
from partner.models import (
Partner,
PARTNER_TYPE_CHOICES,
STATUS_CHOICES,
KYC_DOCUMENT_TYPE_CHOICES,
)
from events.models import Event
from bookings.models import TicketMeta, Booking, TicketType
from mobile_api.utils import validate_token_and_get_user
from eventify_logger.services import log
@@ -55,6 +66,265 @@ def _partner_to_dict(partner, request=None):
return data
def _iso_z_from_date(d):
if not d:
return None
if isinstance(d, datetime):
if timezone.is_naive(d):
d = timezone.make_aware(d, timezone.get_current_timezone())
return d.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
if isinstance(d, date):
return f"{d.isoformat()}T00:00:00.000Z"
return None
def _format_event_time(t):
if not t:
return None
return t.strftime("%H:%M")
def _partner_type_display(partner):
return dict(PARTNER_TYPE_CHOICES).get(partner.partner_type, partner.partner_type)
def _partner_status_display(partner):
return dict(STATUS_CHOICES).get(partner.status, partner.status)
def _partner_logo_url(partner):
return (
"https://ui-avatars.com/api/?name="
f"{quote_plus(partner.name)}&background=0D8ABC&color=fff"
)
def _company_address_line(partner):
parts = [
partner.address,
partner.city,
partner.state,
partner.pincode,
partner.country,
]
return ", ".join(p.strip() for p in parts if p and str(p).strip()) or None
def _verification_status_label(partner):
if partner.is_kyc_compliant and partner.kyc_compliance_status == "approved":
return "Verified"
if partner.kyc_compliance_status == "rejected":
return "Rejected"
if partner.kyc_compliance_status == "pending":
return "Pending"
return partner.get_kyc_compliance_status_display()
def _risk_score(partner):
scores = {
"high_risk": 78,
"medium_risk": 45,
"low_risk": 12,
"approved": 12,
"pending": 50,
"rejected": 95,
}
return scores.get(partner.kyc_compliance_status, 50)
def _kyc_status_upper(partner):
mapping = {
"approved": "APPROVED",
"pending": "PENDING",
"rejected": "REJECTED",
"high_risk": "HIGH_RISK",
"low_risk": "LOW_RISK",
"medium_risk": "MEDIUM_RISK",
}
return mapping.get(partner.kyc_compliance_status, str(partner.kyc_compliance_status).upper())
def _kyc_document_type_label(partner):
if partner.kyc_compliance_document_type == "other" and partner.kyc_compliance_document_other_type:
return partner.kyc_compliance_document_other_type
if partner.kyc_compliance_document_type:
return dict(KYC_DOCUMENT_TYPE_CHOICES).get(
partner.kyc_compliance_document_type,
partner.kyc_compliance_document_type,
)
return "Document"
def _build_kyc_documents(partner, request):
"""Single KYC row from Partner model fields (no separate KYC table)."""
if not (
partner.kyc_compliance_document_file
or partner.kyc_compliance_document_type
or partner.kyc_compliance_document_number
):
return []
type_label = _kyc_document_type_label(partner)
name = f"{type_label} - {partner.name}"
if partner.kyc_compliance_document_file:
if request:
url = request.build_absolute_uri(partner.kyc_compliance_document_file.url)
else:
url = partner.kyc_compliance_document_file.url
else:
url = None
return [
{
"id": f"kyc-{partner.id}",
"partnerId": f"p{partner.id}",
"type": type_label.upper() if type_label else "DOCUMENT",
"name": name,
"url": url,
"status": _kyc_status_upper(partner),
"mandatory": True,
"reviewedBy": "Admin" if partner.kyc_compliance_status == "approved" else None,
"reviewedAt": None,
"uploadedBy": partner.primary_contact_person_name,
"uploadedAt": None,
}
]
def _build_partner_detail_payload(partner, request):
"""Shape partner block for PartnerDetailWithEventsAPI."""
ptype = _partner_type_display(partner)
events_qs = Event.objects.filter(partner=partner)
events_count = events_qs.count()
active_deals = events_qs.filter(event_status__in=["live", "published"]).count()
booking_agg = (
Booking.objects.filter(ticket_meta__event__partner=partner)
.aggregate(
total_revenue=Sum(
F("quantity") * F("price"),
output_field=DecimalField(max_digits=16, decimal_places=2),
),
last_booking=Max("updated_date"),
)
)
total_revenue = booking_agg["total_revenue"] or Decimal("0")
last_booking_date = booking_agg["last_booking"]
last_event_date = events_qs.aggregate(m=Max("created_date"))["m"]
last_activity = None
for candidate in (last_booking_date, last_event_date):
if candidate is None:
continue
if last_activity is None or candidate > last_activity:
last_activity = candidate
tags = [ptype]
if partner.is_kyc_compliant:
tags.append("KYC Verified")
return {
"id": f"p{partner.id}",
"name": partner.name,
"type": ptype,
"status": _partner_status_display(partner),
"logo": _partner_logo_url(partner),
"primaryContact": {
"name": partner.primary_contact_person_name,
"email": partner.primary_contact_person_email,
"phone": partner.primary_contact_person_phone,
"role": f"{ptype} Manager",
},
"companyDetails": {
"legalName": partner.name,
"taxId": partner.kyc_compliance_document_number or None,
"website": partner.website_url or None,
"address": _company_address_line(partner),
},
"metrics": {
"activeDeals": active_deals,
"totalRevenue": float(total_revenue),
"openBalance": 0,
"lastActivity": _iso_z_from_date(last_activity),
"eventsCount": events_count,
},
"tags": tags,
"notes": partner.kyc_compliance_reason or None,
"joinedAt": None,
"verificationStatus": _verification_status_label(partner),
"riskScore": _risk_score(partner),
}
def _build_partner_events_payload(partner, events, request):
"""partnerEvents[] from Event + TicketMeta / Booking aggregates."""
if not events:
return []
event_ids = [e.id for e in events]
cap_by_event = dict(
TicketMeta.objects.filter(event_id__in=event_ids)
.values("event_id")
.annotate(total=Sum("maximum_quantity"))
.values_list("event_id", "total")
)
booking_rows = (
Booking.objects.filter(ticket_meta__event_id__in=event_ids)
.values("ticket_meta__event_id")
.annotate(
sold=Sum("quantity"),
revenue=Sum(
F("quantity") * F("price"),
output_field=DecimalField(max_digits=16, decimal_places=2),
),
)
)
booking_by_event = {row["ticket_meta__event_id"]: row for row in booking_rows}
min_price_rows = (
TicketType.objects.filter(ticket_meta__event_id__in=event_ids, is_active=True)
.values("ticket_meta__event_id")
.annotate(mp=Min("price"))
)
min_price_by_event = {row["ticket_meta__event_id"]: row["mp"] for row in min_price_rows}
out = []
for e in events:
row = booking_by_event.get(e.id, {})
sold = row.get("sold") or 0
revenue = row.get("revenue") or Decimal("0")
mp = min_price_by_event.get(e.id)
ticket_price = float(mp) if mp is not None else 0.0
total_tickets = cap_by_event.get(e.id) or 0
title = (e.title or "").strip() or e.name
venue = (e.venue_name or "").strip() or e.place
category = e.event_type.event_type if e.event_type_id else ""
out.append(
{
"id": f"evt-{e.id}",
"partnerId": f"p{partner.id}",
"title": title,
"description": e.description or None,
"date": _iso_z_from_date(e.start_date),
"time": _format_event_time(e.start_time),
"venue": venue,
"category": category,
"ticketPrice": ticket_price,
"totalTickets": int(total_tickets) if total_tickets else 0,
"ticketsSold": int(sold),
"revenue": float(revenue),
"status": e.event_status.upper() if e.event_status else None,
"submittedAt": None,
"createdAt": _iso_z_from_date(e.created_date),
}
)
return out
@method_decorator(csrf_exempt, name="dispatch")
class PartnerCreateAPI(APIView):
"""
@@ -166,6 +436,63 @@ class PartnerListAPI(APIView):
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class PartnerDetailWithEventsAPI(APIView):
"""
Get full partner detail for UI: partner, kycDocuments, dealTerms, ledger, partnerEvents.
Body: token, username, partner_id (required).
dealTerms and ledger are placeholders ([]) until dedicated models exist.
"""
def post(self, request):
try:
user, token, data, error_response = validate_token_and_get_user(request)
if error_response:
return error_response
partner_id = data.get("partner_id")
if not partner_id:
return JsonResponse(
{"status": "error", "message": "partner_id is required."},
status=400,
)
try:
partner = Partner.objects.get(id=partner_id)
except Partner.DoesNotExist:
return JsonResponse(
{"status": "error", "message": "Partner not found."},
status=404,
)
events = list(
Event.objects.filter(partner=partner)
.select_related("event_type")
.order_by("-created_date")
)
payload = {
"status": "success",
"partner": _build_partner_detail_payload(partner, request),
"kycDocuments": _build_kyc_documents(partner, request),
"dealTerms": [],
"ledger": [],
"partnerEvents": _build_partner_events_payload(partner, events, request),
}
log(
"info",
"Partner detail with events",
request=request,
user=user,
logger_data={"partner_id": partner.id},
)
return JsonResponse(payload, status=200)
except Exception as e:
log("error", "Partner detail with events exception", request=request, logger_data={"error": str(e)})
return JsonResponse({"status": "error", "message": str(e)}, status=500)
@method_decorator(csrf_exempt, name="dispatch")
class PartnerUpdateAPI(APIView):
"""

View File

@@ -3,6 +3,7 @@ from django.urls import path
from partner.api import (
PartnerCreateAPI,
PartnerListAPI,
PartnerDetailWithEventsAPI,
PartnerUpdateAPI,
PartnerDeleteAPI,
PartnerUpdateKYCDocumentsAPI,
@@ -22,6 +23,7 @@ from partner.api import (
urlpatterns = [
path("create/", PartnerCreateAPI.as_view(), name="partner_create"),
path("list/", PartnerListAPI.as_view(), name="partner_list"),
path("detail-with-events/", PartnerDetailWithEventsAPI.as_view(), name="partner_detail_with_events"),
path("update/", PartnerUpdateAPI.as_view(), name="partner_update"),
path("delete/", PartnerDeleteAPI.as_view(), name="partner_delete"),
path("update-kyc-documents/", PartnerUpdateKYCDocumentsAPI.as_view(), name="partner_update_kyc_documents"),