@@ -555,20 +555,29 @@ def get_context_data(self, **kwargs):
555555 return context
556556
557557
558- # ── Revenue Report ────────────────────────────────────────────────────
558+ # ── Finances ────── ────────────────────────────────────────────────────
559559
560560
561- class RevenueReportView (SponsorshipAdminRequiredMixin , TemplateView ):
562- """Financial summary with revenue breakdowns and charts."""
561+ class FinancesView (SponsorshipAdminRequiredMixin , TemplateView ):
562+ """Financial overview with revenue breakdowns, trends, and charts."""
563563
564- template_name = "sponsors/manage/revenue_report .html"
564+ template_name = "sponsors/manage/finances .html"
565565
566566 def _year_summary (self , year ):
567567 """Return revenue stats for a single year."""
568- qs = Sponsorship .objects .filter (year = year , status__in = [Sponsorship .APPROVED , Sponsorship .FINALIZED ])
569- total = qs .aggregate (total = Sum ("sponsorship_fee" ))["total" ] or 0
570- count = qs .count ()
571- return {"year" : year , "total" : total , "count" : count , "avg" : total // count if count else 0 }
568+ base = Sponsorship .objects .filter (year = year )
569+ committed = base .filter (status__in = [Sponsorship .APPROVED , Sponsorship .FINALIZED ])
570+ total = committed .aggregate (total = Sum ("sponsorship_fee" ))["total" ] or 0
571+ finalized = base .filter (status = Sponsorship .FINALIZED ).aggregate (total = Sum ("sponsorship_fee" ))["total" ] or 0
572+ count = committed .count ()
573+ return {
574+ "year" : year ,
575+ "total" : total ,
576+ "finalized" : finalized ,
577+ "pending" : total - finalized ,
578+ "count" : count ,
579+ "avg" : total // count if count else 0 ,
580+ }
572581
573582 def _package_breakdown (self , year_qs ):
574583 """Return revenue grouped by package tier."""
@@ -578,29 +587,27 @@ def _package_breakdown(self, year_qs):
578587 .order_by ("-revenue" )
579588 )
580589 return [
581- {"name" : r ["package__name" ] or "Custom / No Package" , "revenue" : r ["revenue" ] or 0 , "count" : r ["count" ]}
582- for r in rows
590+ {"name" : r ["package__name" ] or "Custom" , "revenue" : r ["revenue" ] or 0 , "count" : r ["count" ]} for r in rows
583591 ]
584592
585593 def get_context_data (self , ** kwargs ):
586- """Return context with revenue breakdowns ."""
594+ """Return context with financial data for charts ."""
587595 context = super ().get_context_data (** kwargs )
596+ import json
588597
589- # Year selection
590- all_years = Sponsorship .objects .values_list ("year" , flat = True ).distinct ().order_by ("-year" )
598+ all_years = Sponsorship .objects .values_list ("year" , flat = True ).distinct ().order_by ("year" )
591599 all_years = [y for y in all_years if y ]
592600
593601 selected_year = self .request .GET .get ("year" )
594602 if selected_year :
595603 selected_year = int (selected_year )
596604 elif all_years :
597- selected_year = all_years [0 ]
605+ selected_year = all_years [- 1 ]
598606
599- # Year-over-year comparison
607+ # YoY data (chronological for charts)
600608 yoy = [self ._year_summary (y ) for y in all_years ]
601- max_yoy = max ((s ["total" ] for s in yoy ), default = 1 ) or 1
602609
603- # Current year detail
610+ # Selected year detail
604611 year_qs = Sponsorship .objects .filter (
605612 year = selected_year , status__in = [Sponsorship .APPROVED , Sponsorship .FINALIZED ]
606613 )
@@ -615,7 +622,15 @@ def get_context_data(self, **kwargs):
615622
616623 # Package breakdown
617624 by_package = self ._package_breakdown (year_qs )
618- max_pkg = max ((p ["revenue" ] for p in by_package ), default = 1 ) or 1
625+
626+ # Status breakdown (all statuses for selected year)
627+ all_year = Sponsorship .objects .filter (year = selected_year )
628+ status_counts = {
629+ "applied" : all_year .filter (status = Sponsorship .APPLIED ).count (),
630+ "approved" : all_year .filter (status = Sponsorship .APPROVED ).count (),
631+ "finalized" : all_year .filter (status = Sponsorship .FINALIZED ).count (),
632+ "rejected" : all_year .filter (status = Sponsorship .REJECTED ).count (),
633+ }
619634
620635 # Per-sponsorship detail table
621636 sponsorships = (
@@ -624,20 +639,40 @@ def get_context_data(self, **kwargs):
624639 for sp in sponsorships :
625640 sp .internal_total = sp .estimated_cost
626641
642+ # JSON data for Chart.js
643+ chart_data = {
644+ "yoy_labels" : [s ["year" ] for s in yoy ],
645+ "yoy_revenue" : [s ["total" ] for s in yoy ],
646+ "yoy_finalized" : [s ["finalized" ] for s in yoy ],
647+ "yoy_pending" : [s ["pending" ] for s in yoy ],
648+ "yoy_counts" : [s ["count" ] for s in yoy ],
649+ "yoy_avg" : [s ["avg" ] for s in yoy ],
650+ "pkg_labels" : [p ["name" ] for p in by_package ],
651+ "pkg_revenue" : [p ["revenue" ] for p in by_package ],
652+ "pkg_counts" : [p ["count" ] for p in by_package ],
653+ "status_labels" : ["Applied" , "Approved" , "Finalized" , "Rejected" ],
654+ "status_counts" : [
655+ status_counts ["applied" ],
656+ status_counts ["approved" ],
657+ status_counts ["finalized" ],
658+ status_counts ["rejected" ],
659+ ],
660+ }
661+
627662 context .update (
628663 {
629- "years" : all_years ,
664+ "years" : list ( reversed ( all_years )) ,
630665 "selected_year" : selected_year ,
631666 "total_revenue" : total_revenue ,
632667 "total_count" : total_count ,
633668 "avg_deal" : total_revenue // total_count if total_count else 0 ,
634669 "finalized_revenue" : finalized_revenue ,
635670 "approved_revenue" : approved_revenue ,
636671 "by_package" : by_package ,
637- "max_pkg_revenue" : max_pkg ,
638672 "yoy" : yoy ,
639- "max_yoy_revenue " : max_yoy ,
673+ "status_counts " : status_counts ,
640674 "sponsorships" : sponsorships ,
675+ "chart_data_json" : json .dumps (chart_data ),
641676 }
642677 )
643678 return context
0 commit comments