22
33import calendar
44import math
5+ from collections .abc import Callable
56from decimal import Decimal
67from operator import attrgetter
78from typing import TYPE_CHECKING , cast
2627from .models import Interaction
2728
2829if TYPE_CHECKING :
29- from collections .abc import Callable
30-
3130 from django .http import HttpRequest
3231
3332 from weblate_web .payments .models import CustomerQuerySet
@@ -344,15 +343,14 @@ class IncomeView(CRMMixin, TemplateView): # type: ignore[misc]
344343 CHART_PADDING = 60
345344 MIN_CHART_VALUE = Decimal (1 )
346345
347- # Category colors shared across all charts (keyed by category value )
346+ # Category colors shared across all charts (keyed by category enum )
348347 CATEGORY_COLORS = {
349348 InvoiceCategory .HOSTING : "#417690" ,
350349 InvoiceCategory .SUPPORT : "#79aec8" ,
351350 InvoiceCategory .DEVEL : "#5b80b2" ,
352351 InvoiceCategory .DONATE : "#9fc5e8" ,
353352 }
354353
355- # Cached label-to-enum mapping (built once on first access)
356354 def get_year (self ) -> int :
357355 """Get the year from URL kwargs or default to current year."""
358356 return self .kwargs .get ("year" , timezone .now ().year )
@@ -466,7 +464,11 @@ def generate_svg_pie_chart(self, data: dict[InvoiceCategory, Decimal]) -> str:
466464 return "" .join (svg_parts )
467465
468466 def generate_svg_stacked_bar_chart ( # noqa: PLR0914
469- self , monthly_data : dict , invoices : list , year : int , month : int | None = None
467+ self ,
468+ monthly_data : dict [str , Decimal ],
469+ invoices : list [Invoice ],
470+ year : int ,
471+ month : int | None = None ,
470472 ) -> str :
471473 """
472474 Generate a stacked bar chart showing totals by category.
@@ -572,7 +574,7 @@ def filter_by_month(inv, idx):
572574
573575 def _get_invoices_and_totals (
574576 self , year : int , month : int | None = None
575- ) -> tuple [list , dict ]:
577+ ) -> tuple [list [ Invoice ] , dict [ int , Decimal ] ]:
576578 """Fetch invoices and pre-calculate totals (shared helper)."""
577579 query = Invoice .objects .filter (kind = InvoiceKind .INVOICE , issue_date__year = year )
578580 if month :
@@ -587,9 +589,9 @@ def _aggregate_income_by_period(
587589 self ,
588590 year : int ,
589591 month : int | None ,
590- filter_func : Callable [[object , str ], bool ],
592+ filter_func : Callable [[Invoice , str ], bool ],
591593 period_keys : list [str ],
592- ) -> tuple [dict [str , Decimal ], list ]:
594+ ) -> tuple [dict [str , Decimal ], list [ Invoice ] ]:
593595 """
594596 Aggregate income data filtered by a custom function.
595597
@@ -637,21 +639,23 @@ def get_income_data(
637639
638640 return category_data
639641
640- def get_monthly_data (self , year : int ) -> tuple [dict [str , Decimal ], list ]:
642+ def get_monthly_data (self , year : int ) -> tuple [dict [str , Decimal ], list [ Invoice ] ]:
641643 """Get monthly income data for the year."""
642644
643- def filter_by_month (inv : object , key : str ) -> bool :
645+ def filter_by_month (inv : Invoice , key : str ) -> bool :
644646 return inv .issue_date .month == int (key )
645647
646648 monthly_keys = [f"{ month :02d} " for month in range (1 , 13 )]
647649 return self ._aggregate_income_by_period (
648650 year , None , filter_by_month , monthly_keys
649651 )
650652
651- def get_daily_data (self , year : int , month : int ) -> tuple [dict [str , Decimal ], list ]:
653+ def get_daily_data (
654+ self , year : int , month : int
655+ ) -> tuple [dict [str , Decimal ], list [Invoice ]]:
652656 """Get daily income data for a specific month."""
653657
654- def filter_by_day (inv : object , key : str ) -> bool :
658+ def filter_by_day (inv : Invoice , key : str ) -> bool :
655659 return inv .issue_date .day == int (key )
656660
657661 num_days = calendar .monthrange (year , month )[1 ]
@@ -660,7 +664,7 @@ def filter_by_day(inv: object, key: str) -> bool:
660664
661665 def get_monthly_category_data (
662666 self , year : int
663- ) -> tuple [dict [str , dict [str , Decimal ]], list ]:
667+ ) -> tuple [dict [str , dict [str , Decimal ]], list [ Invoice ] ]:
664668 """Get monthly income data split by category for stacked chart."""
665669 invoices = list (
666670 Invoice .objects .filter (
0 commit comments