From e26263e93fb9ebb59b58ec751d24c5b72e6120e0 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Mon, 27 Apr 2026 16:15:41 +0330 Subject: [PATCH] feat(reports): add localized workspace reports and exports --- apps/reports/__init__.py | 1 + apps/reports/api/__init__.py | 1 + apps/reports/api/serializers.py | 39 ++ apps/reports/api/urls.py | 20 + apps/reports/api/views.py | 110 ++++ apps/reports/apps.py | 7 + apps/reports/assets/fonts/Vazirmatn-Bold.ttf | Bin 0 -> 123036 bytes .../assets/fonts/Vazirmatn-Regular.ttf | Bin 0 -> 122752 bytes apps/reports/migrations/0001_initial.py | 47 ++ apps/reports/migrations/__init__.py | 1 + apps/reports/models.py | 83 +++ apps/reports/services/__init__.py | 15 + apps/reports/services/aggregation.py | 604 ++++++++++++++++++ apps/reports/services/export_i18n.py | 173 +++++ apps/reports/services/exporters.py | 410 ++++++++++++ apps/reports/tasks.py | 120 ++++ apps/reports/tests/__init__.py | 1 + apps/reports/tests/test_tasks.py | 217 +++++++ apps/reports/tests/test_views.py | 163 +++++ config/settings/base.py | 7 +- config/urls.py | 1 + requirements/base.txt | 17 +- 22 files changed, 2029 insertions(+), 8 deletions(-) create mode 100644 apps/reports/__init__.py create mode 100644 apps/reports/api/__init__.py create mode 100644 apps/reports/api/serializers.py create mode 100644 apps/reports/api/urls.py create mode 100644 apps/reports/api/views.py create mode 100644 apps/reports/apps.py create mode 100644 apps/reports/assets/fonts/Vazirmatn-Bold.ttf create mode 100644 apps/reports/assets/fonts/Vazirmatn-Regular.ttf create mode 100644 apps/reports/migrations/0001_initial.py create mode 100644 apps/reports/migrations/__init__.py create mode 100644 apps/reports/models.py create mode 100644 apps/reports/services/__init__.py create mode 100644 apps/reports/services/aggregation.py create mode 100644 apps/reports/services/export_i18n.py create mode 100644 apps/reports/services/exporters.py create mode 100644 apps/reports/tasks.py create mode 100644 apps/reports/tests/__init__.py create mode 100644 apps/reports/tests/test_tasks.py create mode 100644 apps/reports/tests/test_views.py diff --git a/apps/reports/__init__.py b/apps/reports/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/reports/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/reports/api/__init__.py b/apps/reports/api/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/reports/api/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/reports/api/serializers.py b/apps/reports/api/serializers.py new file mode 100644 index 0000000..10d37b0 --- /dev/null +++ b/apps/reports/api/serializers.py @@ -0,0 +1,39 @@ +from rest_framework import serializers + +from apps.reports.models import ReportExportJob +from apps.reports.services.aggregation import ALLOWED_PERIODS + + +class ReportExportCreateSerializer(serializers.Serializer): + workspace = serializers.UUIDField() + period = serializers.ChoiceField(choices=sorted(ALLOWED_PERIODS)) + from_date = serializers.DateField(required=False, allow_null=True) + to_date = serializers.DateField(required=False, allow_null=True) + user = serializers.UUIDField(required=False, allow_null=True) + client = serializers.UUIDField(required=False, allow_null=True) + project = serializers.UUIDField(required=False, allow_null=True) + tags = serializers.ListField( + child=serializers.UUIDField(), + required=False, + allow_empty=True, + ) + language = serializers.ChoiceField(choices=("en", "fa"), required=False, default="en") + export_type = serializers.ChoiceField(choices=ReportExportJob.ExportType.choices) + + +class ReportExportJobSerializer(serializers.ModelSerializer): + class Meta: + model = ReportExportJob + fields = ( + "id", + "workspace", + "export_type", + "status", + "filters", + "file_name", + "error_message", + "expires_at", + "completed_at", + "created_at", + ) + read_only_fields = fields diff --git a/apps/reports/api/urls.py b/apps/reports/api/urls.py new file mode 100644 index 0000000..0972113 --- /dev/null +++ b/apps/reports/api/urls.py @@ -0,0 +1,20 @@ +from django.urls import include, path +from rest_framework.routers import DefaultRouter + +from apps.reports.api.views import ( + ReportChartView, + ReportDayDetailsView, + ReportExportJobViewSet, + ReportTableView, +) + +router = DefaultRouter() +router.register(r"exports", ReportExportJobViewSet, basename="report-export-job") + +urlpatterns = [ + path("chart/", ReportChartView.as_view(), name="report-chart"), + path("table/", ReportTableView.as_view(), name="report-table"), + path("day-details/", ReportDayDetailsView.as_view(), name="report-day-details"), + path("", include(router.urls)), +] + diff --git a/apps/reports/api/views.py b/apps/reports/api/views.py new file mode 100644 index 0000000..37cc501 --- /dev/null +++ b/apps/reports/api/views.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from django.conf import settings +from django.http import FileResponse, Http404 +from django.urls import reverse +from django.utils import timezone +from drf_spectacular.utils import extend_schema +from rest_framework import mixins, status, viewsets +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from apps.reports.api.serializers import ( + ReportExportCreateSerializer, + ReportExportJobSerializer, +) +from apps.reports.models import ReportExportJob +from apps.reports.services import ( + build_chart_report, + build_day_details_report, + build_table_report, + load_report_filters, +) +from apps.reports.tasks import generate_report_export_task + + +class ReportChartView(APIView): + permission_classes = [IsAuthenticated] + + @extend_schema(responses=dict) + def get(self, request): + return Response(build_chart_report(request.user, request.query_params)) + + +class ReportTableView(APIView): + permission_classes = [IsAuthenticated] + + @extend_schema(responses=dict) + def get(self, request): + return Response(build_table_report(request.user, request.query_params)) + + +class ReportDayDetailsView(APIView): + permission_classes = [IsAuthenticated] + + @extend_schema(responses=dict) + def get(self, request): + return Response(build_day_details_report(request.user, request.query_params)) + + +class ReportExportJobViewSet( + mixins.CreateModelMixin, + mixins.ListModelMixin, + mixins.RetrieveModelMixin, + viewsets.GenericViewSet, +): + permission_classes = [IsAuthenticated] + serializer_class = ReportExportJobSerializer + + def get_queryset(self): + return ReportExportJob.objects.filter(requesting_user=self.request.user, is_deleted=False) + + @extend_schema(request=ReportExportCreateSerializer, responses=ReportExportJobSerializer) + def create(self, request, *args, **kwargs): + serializer = ReportExportCreateSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + filters = load_report_filters(request.user, serializer.validated_data) + + job = ReportExportJob.objects.create( + requesting_user=request.user, + workspace=filters.workspace, + export_type=serializer.validated_data["export_type"], + filters={ + "workspace": str(filters.workspace.id), + "period": filters.period, + "from_date": filters.from_date.isoformat(), + "to_date": filters.to_date.isoformat(), + "user": filters.user_id, + "client": filters.client_id, + "project": filters.project_id, + "tags": filters.tag_ids, + "language": serializer.validated_data.get("language", "en"), + }, + status=ReportExportJob.Status.PENDING, + ) + generate_report_export_task.delay(str(job.id)) + output = ReportExportJobSerializer(job) + return Response(output.data, status=status.HTTP_202_ACCEPTED) + + @action(detail=True, methods=["get"], url_path="download") + def download(self, request, pk=None): + job = self.get_object() + if job.status != ReportExportJob.Status.COMPLETED or not job.file: + raise Http404("Export file is not available.") + if job.expires_at and job.expires_at <= timezone.now(): + raise Http404("Export file has expired.") + response = FileResponse( + job.file.open("rb"), + as_attachment=True, + filename=job.file_name or job.file.name.split("/")[-1], + ) + return response + + +def build_export_action_url(job: ReportExportJob) -> str: + path = reverse("report-export-job-download", kwargs={"pk": job.id}) + if settings.BASE_URL: + return f"{settings.BASE_URL.rstrip('/')}{path}" + return path diff --git a/apps/reports/apps.py b/apps/reports/apps.py new file mode 100644 index 0000000..2168c4f --- /dev/null +++ b/apps/reports/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class ReportsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.reports" + diff --git a/apps/reports/assets/fonts/Vazirmatn-Bold.ttf b/apps/reports/assets/fonts/Vazirmatn-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efa9b095da01d299310bdcae293e24e657fe6da9 GIT binary patch literal 123036 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

Kg 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

&<*-b=tCLO;?PtaLsU-t5`wp~{w*TNa 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 WI)zwj9w^LmX&P6Ws)r<9QS`rF>Xy_1 literal 0 HcmV?d00001 diff --git a/apps/reports/assets/fonts/Vazirmatn-Regular.ttf b/apps/reports/assets/fonts/Vazirmatn-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..64e4a8189557a17b63fd7a77794207483e592972 GIT binary patch literal 122752 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 date: + days_since_sunday = (local_date.weekday() + 1) % 7 + return local_date - timedelta(days=days_since_sunday) + + +def _start_of_persian_week(local_date: date) -> date: + days_since_saturday = (local_date.weekday() + 2) % 7 + return local_date - timedelta(days=days_since_saturday) + + +def _localize_datetime(value: datetime) -> datetime: + if timezone.is_naive(value): + value = timezone.make_aware(value, timezone.get_current_timezone()) + return timezone.localtime(value) + + +def _user_display(user) -> str: + full_name = getattr(user, "full_name", "").strip() + if full_name and full_name != "Anonymous": + return full_name + return getattr(user, "mobile", str(user)) + + +def _format_duration_seconds(total_seconds: int) -> str: + total_seconds = max(int(total_seconds), 0) + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return f"{hours:02d}:{minutes:02d}:{seconds:02d}" + + +def _money_map() -> defaultdict[str, Decimal]: + return defaultdict(lambda: Decimal("0.00")) + + +def _serialize_money_totals(values: dict[str, Decimal]) -> list[dict]: + return [ + {"currency": currency, "amount": f"{amount.quantize(Decimal('0.01'))}"} + for currency, amount in sorted(values.items()) + if amount is not None + ] + + +def _add_income(bucket: defaultdict[str, Decimal], entry: TimeEntry): + if not entry.is_billable or not entry.hourly_rate: + return + duration_seconds = get_entry_duration_seconds(entry) + if duration_seconds <= 0: + return + hourly_rate = Decimal(entry.hourly_rate) + income = (hourly_rate * Decimal(duration_seconds) / Decimal(3600)).quantize(Decimal("0.01")) + bucket[(entry.currency or "USD")] += income + + +def get_entry_duration_seconds(entry: TimeEntry) -> int: + if entry.duration is not None: + return max(int(entry.duration.total_seconds()), 0) + if entry.end_time: + return max(int((entry.end_time - entry.start_time).total_seconds()), 0) + return 0 + + +def _parse_id_list(values: Iterable[str]) -> list[str]: + return [str(value).strip() for value in values if str(value).strip()] + + +@dataclass +class ReportFilters: + workspace: Workspace + period: str + from_date: date + to_date: date + user_id: str | None + client_id: str | None + project_id: str | None + tag_ids: list[str] + actor: object + is_workspace_scope: bool + language: str + + +class ReportFilterSerializer(serializers.Serializer): + workspace = serializers.UUIDField() + period = serializers.ChoiceField(choices=sorted(ALLOWED_PERIODS)) + from_date = serializers.DateField(required=False, allow_null=True) + to_date = serializers.DateField(required=False, allow_null=True) + user = serializers.UUIDField(required=False, allow_null=True) + client = serializers.UUIDField(required=False, allow_null=True) + project = serializers.UUIDField(required=False, allow_null=True) + tags = serializers.ListField( + child=serializers.UUIDField(), + required=False, + allow_empty=True, + ) + language = serializers.ChoiceField(choices=("en", "fa"), required=False, default="en") + + +def _resolve_period_bounds(period: str, from_date: date | None, to_date: date | None, *, language: str) -> tuple[date, date]: + today = timezone.localdate() + if language == "fa": + today_jalali = jdatetime.date.fromgregorian(date=today) + if period == PERIOD_THIS_WEEK: + start = _start_of_persian_week(today) + return start, start + timedelta(days=6) + if period == PERIOD_THIS_MONTH: + start_j = jdatetime.date(today_jalali.year, today_jalali.month, 1) + if today_jalali.month == 12: + next_month_j = jdatetime.date(today_jalali.year + 1, 1, 1) + else: + next_month_j = jdatetime.date(today_jalali.year, today_jalali.month + 1, 1) + return start_j.togregorian(), next_month_j.togregorian() - timedelta(days=1) + if period == PERIOD_THIS_YEAR: + start = jdatetime.date(today_jalali.year, 1, 1).togregorian() + if jdatetime.date(today_jalali.year, 1, 1).isleap(): + end = jdatetime.date(today_jalali.year, 12, 30).togregorian() + else: + end = jdatetime.date(today_jalali.year, 12, 29).togregorian() + return start, end + if period == PERIOD_HALF_YEAR_FIRST: + start = jdatetime.date(today_jalali.year, 1, 1).togregorian() + end = jdatetime.date(today_jalali.year, 6, 31).togregorian() + return start, end + if period == PERIOD_HALF_YEAR_SECOND: + start = jdatetime.date(today_jalali.year, 7, 1).togregorian() + if jdatetime.date(today_jalali.year, 1, 1).isleap(): + end = jdatetime.date(today_jalali.year, 12, 30).togregorian() + else: + end = jdatetime.date(today_jalali.year, 12, 29).togregorian() + return start, end + if period == PERIOD_THIS_WEEK: + start = _start_of_week(today) + return start, start + timedelta(days=6) + if period == PERIOD_THIS_MONTH: + start = today.replace(day=1) + if start.month == 12: + next_month = start.replace(year=start.year + 1, month=1, day=1) + else: + next_month = start.replace(month=start.month + 1, day=1) + return start, next_month - timedelta(days=1) + if period == PERIOD_THIS_YEAR: + return date(today.year, 1, 1), date(today.year, 12, 31) + if period == PERIOD_HALF_YEAR_FIRST: + return date(today.year, 1, 1), date(today.year, 6, 30) + if period == PERIOD_HALF_YEAR_SECOND: + return date(today.year, 7, 1), date(today.year, 12, 31) + if period == PERIOD_CUSTOM: + if not from_date or not to_date: + raise serializers.ValidationError("Custom period requires from_date and to_date.") + if from_date > to_date: + raise serializers.ValidationError("from_date cannot be after to_date.") + if (to_date - from_date).days > 30: + raise serializers.ValidationError("Custom period cannot exceed 31 days.") + return from_date, to_date + raise serializers.ValidationError("Unsupported report period.") + + +def load_report_filters(actor, raw_data) -> ReportFilters: + normalized = { + "workspace": raw_data.get("workspace"), + "period": raw_data.get("period", PERIOD_THIS_MONTH), + "from_date": raw_data.get("from_date") or raw_data.get("from"), + "to_date": raw_data.get("to_date") or raw_data.get("to"), + "user": raw_data.get("user"), + "client": raw_data.get("client"), + "project": raw_data.get("project"), + "tags": raw_data.get("tags") or raw_data.getlist("tags") if hasattr(raw_data, "getlist") else raw_data.get("tags"), + "language": raw_data.get("language", "en"), + } + if normalized["tags"] and not isinstance(normalized["tags"], list): + normalized["tags"] = [normalized["tags"]] + + serializer = ReportFilterSerializer(data=normalized) + serializer.is_valid(raise_exception=True) + validated = serializer.validated_data + + workspace = Workspace.objects.filter(id=validated["workspace"]).first() + if not workspace: + raise serializers.ValidationError("Workspace not found.") + if not has_workspace_capability(actor, workspace, WORKSPACE_VIEW): + raise serializers.ValidationError("You do not have access to this workspace.") + + from_date, to_date = _resolve_period_bounds( + validated["period"], + validated.get("from_date"), + validated.get("to_date"), + language=validated.get("language", "en"), + ) + + role = get_workspace_role(actor, workspace) + is_workspace_scope = role in {"owner", "admin"} + requested_user_id = str(validated["user"]) if validated.get("user") else None + if requested_user_id and not is_workspace_scope and requested_user_id != str(actor.id): + raise serializers.ValidationError("You cannot view another user's report.") + + user_id = requested_user_id if is_workspace_scope else str(actor.id) + + client_id = str(validated["client"]) if validated.get("client") else None + project_id = str(validated["project"]) if validated.get("project") else None + tag_ids = [str(tag_id) for tag_id in validated.get("tags", [])] + + if client_id and not Client.objects.filter(id=client_id, workspace=workspace).exists(): + raise serializers.ValidationError("Client does not belong to this workspace.") + if project_id and not Project.objects.filter(id=project_id, workspace=workspace).exists(): + raise serializers.ValidationError("Project does not belong to this workspace.") + if tag_ids: + existing_tag_ids = set(Tag.objects.filter(id__in=tag_ids, workspace=workspace).values_list("id", flat=True)) + if len(existing_tag_ids) != len(tag_ids): + raise serializers.ValidationError("One or more tags do not belong to this workspace.") + + return ReportFilters( + workspace=workspace, + period=validated["period"], + from_date=from_date, + to_date=to_date, + user_id=user_id, + client_id=client_id, + project_id=project_id, + tag_ids=tag_ids, + actor=actor, + is_workspace_scope=is_workspace_scope, + language=validated.get("language", "en"), + ) + + +def _base_queryset(filters: ReportFilters) -> QuerySet[TimeEntry]: + start_dt = timezone.make_aware(datetime.combine(filters.from_date, time.min), timezone.get_current_timezone()) + end_dt = timezone.make_aware(datetime.combine(filters.to_date + timedelta(days=1), time.min), timezone.get_current_timezone()) + + queryset = ( + TimeEntry.objects.filter( + workspace=filters.workspace, + is_deleted=False, + start_time__gte=start_dt, + start_time__lt=end_dt, + ) + .select_related("project", "project__client", "workspace", "user") + .prefetch_related(Prefetch("tags", queryset=Tag.objects.filter(is_deleted=False))) + .order_by("-start_time", "-created_at") + .distinct() + ) + + if filters.user_id: + queryset = queryset.filter(user_id=filters.user_id) + if filters.client_id: + queryset = queryset.filter(project__client_id=filters.client_id) + if filters.project_id: + queryset = queryset.filter(project_id=filters.project_id) + if filters.tag_ids: + queryset = queryset.filter(tags__id__in=filters.tag_ids) + + return queryset.distinct() + + +def _summary_from_entries(entries: list[TimeEntry]) -> dict: + total_seconds = 0 + billable_seconds = 0 + non_billable_seconds = 0 + income_by_currency = _money_map() + for entry in entries: + duration_seconds = get_entry_duration_seconds(entry) + total_seconds += duration_seconds + if entry.is_billable: + billable_seconds += duration_seconds + else: + non_billable_seconds += duration_seconds + _add_income(income_by_currency, entry) + return { + "total_seconds": total_seconds, + "billable_seconds": billable_seconds, + "non_billable_seconds": non_billable_seconds, + "total_duration": _format_duration_seconds(total_seconds), + "billable_duration": _format_duration_seconds(billable_seconds), + "non_billable_duration": _format_duration_seconds(non_billable_seconds), + "income_totals": _serialize_money_totals(income_by_currency), + } + + +def _entry_payload(entry: TimeEntry) -> dict: + local_start = _localize_datetime(entry.start_time) + local_end = _localize_datetime(entry.end_time) if entry.end_time else None + duration_seconds = get_entry_duration_seconds(entry) + income_by_currency = _money_map() + _add_income(income_by_currency, entry) + return { + "id": str(entry.id), + "description": entry.description, + "user": { + "id": str(entry.user_id), + "name": _user_display(entry.user), + "mobile": entry.user.mobile, + }, + "project": ( + { + "id": str(entry.project_id), + "name": entry.project.name, + "client": ( + {"id": str(entry.project.client_id), "name": entry.project.client.name} + if entry.project and entry.project.client + else None + ), + } + if entry.project + else None + ), + "tags": [{"id": str(tag.id), "name": tag.name, "color": tag.color} for tag in entry.tags.all()], + "start_time": local_start.isoformat(), + "end_time": local_end.isoformat() if local_end else None, + "duration_seconds": duration_seconds, + "duration": _format_duration_seconds(duration_seconds), + "is_billable": entry.is_billable, + "hourly_rate": str(entry.hourly_rate) if entry.hourly_rate is not None else None, + "currency": entry.currency, + "income_totals": _serialize_money_totals(income_by_currency), + } + + +def _bucket_label(filters: ReportFilters, bucket_date: date) -> str: + if filters.period in {PERIOD_THIS_WEEK, PERIOD_THIS_MONTH, PERIOD_CUSTOM}: + return bucket_date.isoformat() + return bucket_date.strftime("%Y-%m") + + +def _bucket_key(filters: ReportFilters, local_dt: datetime) -> tuple[str, date]: + if filters.period in {PERIOD_THIS_WEEK, PERIOD_THIS_MONTH, PERIOD_CUSTOM}: + bucket_date = local_dt.date() + return bucket_date.isoformat(), bucket_date + bucket_date = date(local_dt.year, local_dt.month, 1) + return bucket_date.strftime("%Y-%m"), bucket_date + + +def build_chart_report(actor, raw_filters) -> dict: + filters = load_report_filters(actor, raw_filters) + entries = list(_base_queryset(filters)) + summary = _summary_from_entries(entries) + buckets: dict[str, dict] = {} + + for entry in entries: + local_start = _localize_datetime(entry.start_time) + bucket_id, bucket_date = _bucket_key(filters, local_start) + bucket = buckets.setdefault( + bucket_id, + { + "bucket_key": bucket_id, + "bucket_label": _bucket_label(filters, bucket_date), + "total_seconds": 0, + "total_duration": "00:00:00", + }, + ) + bucket["total_seconds"] += get_entry_duration_seconds(entry) + + serialized_buckets = [] + for bucket in sorted(buckets.values(), key=lambda item: item["bucket_key"]): + bucket["total_duration"] = _format_duration_seconds(bucket["total_seconds"]) + serialized_buckets.append(bucket) + + return { + "scope": _scope_payload(filters), + "summary": summary, + "buckets": serialized_buckets, + } + + +def _scope_payload(filters: ReportFilters) -> dict: + user_payload = None + if filters.user_id: + target_user = User.objects.filter(id=filters.user_id).first() + if target_user: + user_payload = { + "id": str(target_user.id), + "name": _user_display(target_user), + "mobile": target_user.mobile, + } + + return { + "workspace": {"id": str(filters.workspace.id), "name": filters.workspace.name}, + "period": filters.period, + "from_date": filters.from_date.isoformat(), + "to_date": filters.to_date.isoformat(), + "user": user_payload, + "is_workspace_scope": filters.is_workspace_scope and not filters.user_id, + "filters": { + "client_id": filters.client_id, + "project_id": filters.project_id, + "tag_ids": filters.tag_ids, + }, + } + + +def _table_report_payload(filters: ReportFilters, entries: list[TimeEntry]) -> dict: + summary = _summary_from_entries(entries) + return { + "scope": _scope_payload(filters), + "summary": summary, + "days": _group_daily(entries), + "clients": _build_breakdown(entries, "clients"), + "projects": _build_breakdown(entries, "projects"), + "tags": _build_breakdown(entries, "tags"), + } + + +def _group_daily(entries: list[TimeEntry]) -> list[dict]: + by_day: dict[str, dict] = {} + for entry in entries: + local_start = _localize_datetime(entry.start_time) + day_key = local_start.date().isoformat() + day_bucket = by_day.setdefault( + day_key, + { + "date": day_key, + "billable_seconds": 0, + "non_billable_seconds": 0, + "total_seconds": 0, + "income": _money_map(), + }, + ) + duration_seconds = get_entry_duration_seconds(entry) + day_bucket["total_seconds"] += duration_seconds + if entry.is_billable: + day_bucket["billable_seconds"] += duration_seconds + else: + day_bucket["non_billable_seconds"] += duration_seconds + _add_income(day_bucket["income"], entry) + + rows = [] + for day_key in sorted(by_day.keys()): + bucket = by_day[day_key] + rows.append( + { + "date": bucket["date"], + "billable_seconds": bucket["billable_seconds"], + "non_billable_seconds": bucket["non_billable_seconds"], + "total_seconds": bucket["total_seconds"], + "billable_duration": _format_duration_seconds(bucket["billable_seconds"]), + "non_billable_duration": _format_duration_seconds(bucket["non_billable_seconds"]), + "total_duration": _format_duration_seconds(bucket["total_seconds"]), + "income_totals": _serialize_money_totals(bucket["income"]), + } + ) + return rows + + +def _build_breakdown(entries: list[TimeEntry], kind: str) -> list[dict]: + data: dict[str, dict] = {} + for entry in entries: + if kind == "clients": + if not entry.project or not entry.project.client: + continue + item_id = str(entry.project.client_id) + item_name = entry.project.client.name + elif kind == "projects": + if not entry.project: + continue + item_id = str(entry.project_id) + item_name = entry.project.name + else: + if not entry.tags.exists(): + continue + for tag in entry.tags.all(): + bucket = data.setdefault( + str(tag.id), + { + "id": str(tag.id), + "name": tag.name, + "billable_seconds": 0, + "non_billable_seconds": 0, + "total_seconds": 0, + "income": _money_map(), + }, + ) + duration_seconds = get_entry_duration_seconds(entry) + bucket["total_seconds"] += duration_seconds + if entry.is_billable: + bucket["billable_seconds"] += duration_seconds + else: + bucket["non_billable_seconds"] += duration_seconds + _add_income(bucket["income"], entry) + continue + + bucket = data.setdefault( + item_id, + { + "id": item_id, + "name": item_name, + "billable_seconds": 0, + "non_billable_seconds": 0, + "total_seconds": 0, + "income": _money_map(), + }, + ) + duration_seconds = get_entry_duration_seconds(entry) + bucket["total_seconds"] += duration_seconds + if entry.is_billable: + bucket["billable_seconds"] += duration_seconds + else: + bucket["non_billable_seconds"] += duration_seconds + _add_income(bucket["income"], entry) + + rows = [] + for item_id, bucket in sorted(data.items(), key=lambda item: item[1]["name"].lower()): + rows.append( + { + "id": bucket["id"], + "name": bucket["name"], + "billable_seconds": bucket["billable_seconds"], + "non_billable_seconds": bucket["non_billable_seconds"], + "total_seconds": bucket["total_seconds"], + "billable_duration": _format_duration_seconds(bucket["billable_seconds"]), + "non_billable_duration": _format_duration_seconds(bucket["non_billable_seconds"]), + "total_duration": _format_duration_seconds(bucket["total_seconds"]), + "income_totals": _serialize_money_totals(bucket["income"]), + } + ) + return rows + + +def build_table_report(actor, raw_filters) -> dict: + filters = load_report_filters(actor, raw_filters) + entries = list(_base_queryset(filters)) + return _table_report_payload(filters, entries) + + +def build_user_scoped_table_reports(actor, raw_filters) -> list[dict]: + filters = load_report_filters(actor, raw_filters) + if not (filters.is_workspace_scope and not filters.user_id): + return [] + + entries = list(_base_queryset(filters)) + grouped: dict[str, list[TimeEntry]] = {} + for entry in entries: + grouped.setdefault(str(entry.user_id), []).append(entry) + + sorted_groups = sorted( + grouped.items(), + key=lambda item: _user_display(item[1][0].user).lower(), + ) + + reports: list[dict] = [] + for user_id, user_entries in sorted_groups: + user_filters = replace(filters, user_id=user_id) + reports.append(_table_report_payload(user_filters, user_entries)) + return reports + + +def build_day_details_report(actor, raw_filters) -> dict: + filters = load_report_filters(actor, raw_filters) + day_value = raw_filters.get("day") or raw_filters.get("date") + target_day = parse_date(day_value) if day_value else None + if not target_day: + raise serializers.ValidationError("A valid day is required.") + if target_day < filters.from_date or target_day > filters.to_date: + raise serializers.ValidationError("Requested day is outside the filtered period.") + + entries = [ + entry + for entry in _base_queryset(filters) + if _localize_datetime(entry.start_time).date() == target_day + ] + summary = _summary_from_entries(entries) + return { + "scope": _scope_payload(filters), + "day": target_day.isoformat(), + "summary": summary, + "entries": [_entry_payload(entry) for entry in entries], + } diff --git a/apps/reports/services/export_i18n.py b/apps/reports/services/export_i18n.py new file mode 100644 index 0000000..f4eefea --- /dev/null +++ b/apps/reports/services/export_i18n.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +from dataclasses import dataclass +from datetime import date +from pathlib import Path +from typing import Iterable + +import jdatetime +from arabic_reshaper import reshape +from bidi.algorithm import get_display + +PERSIAN_DIGITS = str.maketrans("0123456789", "۰۱۲۳۴۵۶۷۸۹") +ARABIC_RANGES = ( + (0x0600, 0x06FF), + (0x0750, 0x077F), + (0x08A0, 0x08FF), + (0xFB50, 0xFDFF), + (0xFE70, 0xFEFF), +) + + +TRANSLATIONS = { + "en": { + "report_title": "Workspace Report", + "overall_sheet": "Overall Report", + "workspace": "Workspace", + "period": "Period", + "from_date": "From date", + "to_date": "To date", + "user": "User", + "all_users": "All users", + "generated_at": "Generated at", + "summary": "Summary", + "total_hours": "Total hours", + "billable_hours": "Billable hours", + "non_billable_hours": "Non-billable hours", + "income": "Income", + "daily_summary": "Daily Summary", + "clients": "Clients", + "projects": "Projects", + "tags": "Tags", + "date": "Date", + "name": "Name", + "total": "Total", + "no_data": "No data", + }, + "fa": { + "report_title": "گزارش فضای کاری", + "overall_sheet": "گزارش کلی", + "workspace": "فضای کاری", + "period": "بازه", + "from_date": "از تاریخ", + "to_date": "تا تاریخ", + "user": "کاربر", + "all_users": "همه کاربران", + "generated_at": "تاریخ تولید", + "summary": "خلاصه", + "total_hours": "کل ساعات", + "billable_hours": "ساعات کاری", + "non_billable_hours": "ساعات غیر کاری", + "income": "درآمد", + "daily_summary": "خلاصه روزانه", + "clients": "مشتریان", + "projects": "پروژه‌ها", + "tags": "تگ‌ها", + "date": "تاریخ", + "name": "نام", + "total": "جمع", + "no_data": "بدون داده", + }, +} + +PERIOD_LABELS = { + "en": { + "this_week": "This week", + "this_month": "This month", + "this_year": "This year", + "half_year_first": "First half of year", + "half_year_second": "Second half of year", + "period": "Custom period", + }, + "fa": { + "this_week": "این هفته", + "this_month": "این ماه", + "this_year": "امسال", + "half_year_first": "نیمه اول سال", + "half_year_second": "نیمه دوم سال", + "period": "بازه دلخواه", + }, +} + + +@dataclass(frozen=True) +class ExportLocale: + language: str + is_rtl: bool + font_regular: str + font_bold: str + + def t(self, key: str) -> str: + return TRANSLATIONS[self.language][key] + + def period_label(self, period: str) -> str: + return PERIOD_LABELS[self.language].get(period, period) + + def format_number(self, value: object, *, ascii_digits: bool = False) -> str: + text = str(value) + if self.language == "fa" and not ascii_digits: + return text.translate(PERSIAN_DIGITS) + return text + + def format_date(self, value: date | str | None, *, ascii_digits: bool = False) -> str: + if value is None: + return "-" + if isinstance(value, str): + value = date.fromisoformat(value) + if self.language == "fa": + jalali = jdatetime.date.fromgregorian(date=value) + return self.format_number(jalali.strftime("%Y/%m/%d"), ascii_digits=ascii_digits) + return value.strftime("%Y/%m/%d") + + def format_duration(self, value: str, *, ascii_digits: bool = False) -> str: + return self.format_number(value, ascii_digits=ascii_digits) + + def format_money_label(self, income_totals: list[dict], *, ascii_digits: bool = False) -> str: + if not income_totals: + return "-" + parts = [] + for item in income_totals: + parts.append(f"{self.format_number(item['amount'], ascii_digits=ascii_digits)} {item['currency']}") + return " | ".join(parts) + + def shape(self, text: object) -> str: + raw = str(text) + if not any(start <= ord(char) <= end for char in raw for start, end in ARABIC_RANGES): + return raw + return get_display(reshape(raw)) + + +def build_export_locale(language: str | None) -> ExportLocale: + resolved = language if language in {"en", "fa"} else "en" + assets_dir = Path(__file__).resolve().parent.parent / "assets" / "fonts" + return ExportLocale( + language=resolved, + is_rtl=resolved == "fa", + font_regular=str(assets_dir / "Vazirmatn-Regular.ttf"), + font_bold=str(assets_dir / "Vazirmatn-Bold.ttf"), + ) + + +def user_label(user_payload: dict | None, locale: ExportLocale, *, ascii_digits: bool = False) -> str: + if not user_payload: + return locale.t("all_users") + mobile = locale.format_number(user_payload.get("mobile") or "", ascii_digits=ascii_digits) + if mobile: + return f"{user_payload['name']} - {mobile}" + return str(user_payload["name"]) + + +def safe_sheet_title(title: str, used: Iterable[str]) -> str: + invalid = set('[]:*?/\\') + sanitized = "".join("-" if char in invalid else char for char in title).strip() or "Sheet" + base = sanitized[:31] + used_set = set(used) + if base not in used_set: + return base + index = 2 + while True: + suffix = f"-{index}" + candidate = f"{sanitized[:31 - len(suffix)]}{suffix}" + if candidate not in used_set: + return candidate + index += 1 diff --git a/apps/reports/services/exporters.py b/apps/reports/services/exporters.py new file mode 100644 index 0000000..845598f --- /dev/null +++ b/apps/reports/services/exporters.py @@ -0,0 +1,410 @@ +from __future__ import annotations + +import io +from datetime import datetime + +from openpyxl import Workbook +from openpyxl.styles import Alignment, Border, Font, PatternFill, Side +from openpyxl.utils import get_column_letter +from reportlab.lib import colors +from reportlab.lib.pagesizes import A4, landscape +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib.units import mm +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont +from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle + +from apps.reports.services.export_i18n import ExportLocale, safe_sheet_title, user_label + +HEADER_FILL = PatternFill("solid", fgColor="E7F2FF") +SECTION_FILL = PatternFill("solid", fgColor="F8FAFC") +BORDER = Border( + left=Side(style="thin", color="D0D7DE"), + right=Side(style="thin", color="D0D7DE"), + top=Side(style="thin", color="D0D7DE"), + bottom=Side(style="thin", color="D0D7DE"), +) + + +def _register_pdf_fonts(locale: ExportLocale) -> None: + registered = set(pdfmetrics.getRegisteredFontNames()) + if "Vazirmatn" not in registered: + pdfmetrics.registerFont(TTFont("Vazirmatn", locale.font_regular)) + if "Vazirmatn-Bold" not in registered: + pdfmetrics.registerFont(TTFont("Vazirmatn-Bold", locale.font_bold)) + + +def _apply_cell_style(cell, *, bold: bool = False, fill=None, rtl: bool = False) -> None: + cell.font = Font(name="Calibri", bold=bold, size=11) + cell.border = BORDER + cell.alignment = Alignment(horizontal="right" if rtl else "left", vertical="center") + if fill is not None: + cell.fill = fill + + +def _autosize_columns(worksheet) -> None: + widths: dict[int, int] = {} + for row in worksheet.iter_rows(): + for cell in row: + if cell.value is None: + continue + widths[cell.column] = max(widths.get(cell.column, 0), len(str(cell.value))) + for column_index, width in widths.items(): + worksheet.column_dimensions[get_column_letter(column_index)].width = min(max(width + 4, 12), 30) + + +def _money_label(locale: ExportLocale, income_totals: list[dict]) -> str: + return locale.format_money_label(income_totals) + + +def _money_label_excel(locale: ExportLocale, income_totals: list[dict]) -> str: + return locale.format_money_label(income_totals, ascii_digits=True) + + +def _section_headers(locale: ExportLocale) -> list[str]: + headers = [ + locale.t("name"), + locale.t("billable_hours"), + locale.t("non_billable_hours"), + locale.t("total_hours"), + locale.t("income"), + ] + return list(reversed(headers)) if locale.is_rtl else headers + + +def _rtl_row(locale: ExportLocale, row: list[str]) -> list[str]: + return list(reversed(row)) if locale.is_rtl else row + + +def _append_meta_block(worksheet, *, locale: ExportLocale, report_data: dict) -> None: + scope = report_data["scope"] + summary = report_data["summary"] + + worksheet.append([locale.t("report_title"), scope["workspace"]["name"]]) + worksheet.append([locale.t("workspace"), scope["workspace"]["name"]]) + worksheet.append([locale.t("period"), locale.period_label(scope["period"])]) + worksheet.append([locale.t("from_date"), locale.format_date(scope["from_date"], ascii_digits=True)]) + worksheet.append([locale.t("to_date"), locale.format_date(scope["to_date"], ascii_digits=True)]) + worksheet.append([locale.t("user"), user_label(scope.get("user"), locale, ascii_digits=True)]) + worksheet.append([locale.t("generated_at"), locale.format_date(datetime.now().date(), ascii_digits=True)]) + worksheet.append([]) + worksheet.append([locale.t("summary")]) + worksheet.append([locale.t("total_hours"), locale.format_duration(summary["total_duration"], ascii_digits=True)]) + worksheet.append([locale.t("billable_hours"), locale.format_duration(summary["billable_duration"], ascii_digits=True)]) + worksheet.append([locale.t("non_billable_hours"), locale.format_duration(summary["non_billable_duration"], ascii_digits=True)]) + worksheet.append([locale.t("income"), _money_label_excel(locale, summary["income_totals"])]) + + for row_index in range(1, worksheet.max_row + 1): + first_cell = worksheet.cell(row=row_index, column=1) + second_cell = worksheet.cell(row=row_index, column=2) + if row_index in {1, 9}: + _apply_cell_style(first_cell, bold=True, fill=HEADER_FILL, rtl=locale.is_rtl) + if second_cell.value: + _apply_cell_style(second_cell, bold=row_index == 1, fill=HEADER_FILL if row_index == 1 else None, rtl=locale.is_rtl) + elif first_cell.value: + _apply_cell_style(first_cell, bold=False, fill=SECTION_FILL if row_index == 8 else None, rtl=locale.is_rtl) + if second_cell.value: + _apply_cell_style(second_cell, rtl=locale.is_rtl) + + +def _append_daily_table(worksheet, *, locale: ExportLocale, report_data: dict) -> None: + worksheet.append([]) + worksheet.append([locale.t("daily_summary")]) + header_row = worksheet.max_row + 1 + worksheet.append( + _rtl_row( + locale, + [ + locale.t("date"), + locale.t("billable_hours"), + locale.t("non_billable_hours"), + locale.t("total_hours"), + locale.t("income"), + ], + ) + ) + for cell in worksheet[header_row]: + _apply_cell_style(cell, bold=True, fill=HEADER_FILL, rtl=locale.is_rtl) + + if not report_data["days"]: + worksheet.append([locale.t("no_data")]) + _apply_cell_style(worksheet.cell(row=worksheet.max_row, column=1), rtl=locale.is_rtl) + return + + for row in report_data["days"]: + worksheet.append( + _rtl_row( + locale, + [ + locale.format_date(row["date"], ascii_digits=True), + locale.format_duration(row["billable_duration"], ascii_digits=True), + locale.format_duration(row["non_billable_duration"], ascii_digits=True), + locale.format_duration(row["total_duration"], ascii_digits=True), + _money_label_excel(locale, row["income_totals"]), + ], + ) + ) + for cell in worksheet[worksheet.max_row]: + _apply_cell_style(cell, rtl=locale.is_rtl) + + worksheet.append( + _rtl_row( + locale, + [ + locale.t("total"), + locale.format_duration(report_data["summary"]["billable_duration"], ascii_digits=True), + locale.format_duration(report_data["summary"]["non_billable_duration"], ascii_digits=True), + locale.format_duration(report_data["summary"]["total_duration"], ascii_digits=True), + _money_label_excel(locale, report_data["summary"]["income_totals"]), + ], + ) + ) + for cell in worksheet[worksheet.max_row]: + _apply_cell_style(cell, bold=True, fill=SECTION_FILL, rtl=locale.is_rtl) + + +def _append_breakdown_table(worksheet, *, locale: ExportLocale, title_key: str, rows: list[dict]) -> None: + worksheet.append([]) + worksheet.append([locale.t(title_key)]) + header_row = worksheet.max_row + 1 + worksheet.append(_section_headers(locale)) + for cell in worksheet[header_row]: + _apply_cell_style(cell, bold=True, fill=HEADER_FILL, rtl=locale.is_rtl) + + if not rows: + worksheet.append([locale.t("no_data")]) + _apply_cell_style(worksheet.cell(row=worksheet.max_row, column=1), rtl=locale.is_rtl) + return + + for row in rows: + worksheet.append( + _rtl_row( + locale, + [ + row["name"], + locale.format_duration(row["billable_duration"], ascii_digits=True), + locale.format_duration(row["non_billable_duration"], ascii_digits=True), + locale.format_duration(row["total_duration"], ascii_digits=True), + _money_label_excel(locale, row["income_totals"]), + ], + ) + ) + for cell in worksheet[worksheet.max_row]: + _apply_cell_style(cell, rtl=locale.is_rtl) + + +def _render_excel_sheet(worksheet, *, locale: ExportLocale, report_data: dict) -> None: + if locale.is_rtl: + worksheet.sheet_view.rightToLeft = True + _append_meta_block(worksheet, locale=locale, report_data=report_data) + _append_daily_table(worksheet, locale=locale, report_data=report_data) + _append_breakdown_table(worksheet, locale=locale, title_key="clients", rows=report_data["clients"]) + _append_breakdown_table(worksheet, locale=locale, title_key="projects", rows=report_data["projects"]) + _append_breakdown_table(worksheet, locale=locale, title_key="tags", rows=report_data["tags"]) + _autosize_columns(worksheet) + + +def build_excel_report(*, report_data: dict, locale: ExportLocale, per_user_reports: list[dict] | None = None) -> bytes: + workbook = Workbook() + overall_sheet = workbook.active + overall_sheet.title = safe_sheet_title(locale.t("overall_sheet"), []) + _render_excel_sheet(overall_sheet, locale=locale, report_data=report_data) + + used_titles = {overall_sheet.title} + for user_report in per_user_reports or []: + user_title = safe_sheet_title(user_label(user_report["scope"].get("user"), locale, ascii_digits=True), used_titles) + worksheet = workbook.create_sheet(title=user_title) + _render_excel_sheet(worksheet, locale=locale, report_data=user_report) + used_titles.add(user_title) + + buffer = io.BytesIO() + workbook.save(buffer) + return buffer.getvalue() + + +def _paragraph(text: str, style: ParagraphStyle, locale: ExportLocale) -> Paragraph: + return Paragraph(locale.shape(text), style) + + +def _styled_table(data: list[list[str]], *, locale: ExportLocale, column_widths: list[float]) -> Table: + shaped_data = [ + [locale.shape(cell) if cell is not None else "" for cell in row] + for row in data + ] + table = Table(shaped_data, colWidths=column_widths, repeatRows=1) + table.setStyle( + TableStyle( + [ + ("FONTNAME", (0, 0), (-1, 0), "Vazirmatn-Bold" if locale.language == "fa" else "Helvetica-Bold"), + ("FONTNAME", (0, 1), (-1, -1), "Vazirmatn" if locale.language == "fa" else "Helvetica"), + ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#E7F2FF")), + ("TEXTCOLOR", (0, 0), (-1, -1), colors.HexColor("#0F172A")), + ("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")), + ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("ALIGN", (0, 0), (-1, -1), "RIGHT" if locale.is_rtl else "LEFT"), + ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F8FAFC")]), + ("TOPPADDING", (0, 0), (-1, -1), 6), + ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ] + ) + ) + return table + + +def _report_table_rows(locale: ExportLocale, rows: list[dict]) -> list[list[str]]: + if not rows: + return [_rtl_row(locale, [locale.t("no_data"), "", "", "", ""])] + return [ + _rtl_row( + locale, + [ + locale.format_date(row.get("date")) if row.get("date") else row["name"], + locale.format_duration(row["billable_duration"]), + locale.format_duration(row["non_billable_duration"]), + locale.format_duration(row["total_duration"]), + _money_label(locale, row["income_totals"]), + ], + ) + for row in rows + ] + + +def build_pdf_report(*, report_data: dict, locale: ExportLocale) -> bytes: + _register_pdf_fonts(locale) + font_regular = "Vazirmatn" + font_bold = "Vazirmatn-Bold" + + styles = getSampleStyleSheet() + title_style = ParagraphStyle( + "ReportTitle", + parent=styles["Heading1"], + fontName=font_bold, + fontSize=18, + leading=24, + alignment=2 if locale.is_rtl else 0, + textColor=colors.HexColor("#0F172A"), + ) + section_style = ParagraphStyle( + "ReportSection", + parent=styles["Heading3"], + fontName=font_bold, + fontSize=12, + leading=16, + alignment=2 if locale.is_rtl else 0, + textColor=colors.HexColor("#0F172A"), + ) + body_style = ParagraphStyle( + "ReportBody", + parent=styles["BodyText"], + fontName=font_regular, + fontSize=10, + leading=14, + alignment=2 if locale.is_rtl else 0, + textColor=colors.HexColor("#334155"), + ) + + buffer = io.BytesIO() + doc = SimpleDocTemplate( + buffer, + pagesize=landscape(A4), + leftMargin=14 * mm, + rightMargin=14 * mm, + topMargin=14 * mm, + bottomMargin=14 * mm, + ) + + scope = report_data["scope"] + summary = report_data["summary"] + story = [ + _paragraph(f"{locale.t('report_title')} - {scope['workspace']['name']}", title_style, locale), + Spacer(1, 6 * mm), + ] + + meta_rows = [ + [locale.t("workspace"), scope["workspace"]["name"]], + [locale.t("period"), locale.period_label(scope["period"])], + [locale.t("from_date"), locale.format_date(scope["from_date"])], + [locale.t("to_date"), locale.format_date(scope["to_date"])], + [locale.t("user"), user_label(scope.get("user"), locale)], + [locale.t("generated_at"), locale.format_date(datetime.now().date())], + ] + if locale.is_rtl: + meta_rows = [_rtl_row(locale, row) for row in meta_rows] + meta_rows = [[locale.shape(cell) for cell in row] for row in meta_rows] + meta_table = Table(meta_rows, colWidths=[doc.width * 0.24, doc.width * 0.76]) + meta_table.setStyle( + TableStyle( + [ + ("FONTNAME", (0, 0), (0, -1), font_bold), + ("FONTNAME", (1, 0), (1, -1), font_regular), + ("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#F8FAFC")), + ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")), + ("INNERGRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#CBD5E1")), + ("ALIGN", (0, 0), (-1, -1), "RIGHT" if locale.is_rtl else "LEFT"), + ("TOPPADDING", (0, 0), (-1, -1), 6), + ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ] + ) + ) + story.extend([meta_table, Spacer(1, 5 * mm)]) + + summary_data = [ + [locale.t("total_hours"), locale.format_duration(summary["total_duration"])], + [locale.t("billable_hours"), locale.format_duration(summary["billable_duration"])], + [locale.t("non_billable_hours"), locale.format_duration(summary["non_billable_duration"])], + [locale.t("income"), _money_label(locale, summary["income_totals"])], + ] + if locale.is_rtl: + summary_data = [_rtl_row(locale, row) for row in summary_data] + summary_data = [[locale.shape(cell) for cell in row] for row in summary_data] + summary_table = Table(summary_data, colWidths=[doc.width * 0.38, doc.width * 0.62]) + summary_table.setStyle( + TableStyle( + [ + ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#EFF6FF")), + ("BOX", (0, 0), (-1, -1), 0.5, colors.HexColor("#BFDBFE")), + ("INNERGRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#BFDBFE")), + ("FONTNAME", (0, 0), (0, -1), font_bold), + ("FONTNAME", (1, 0), (1, -1), font_regular), + ("ALIGN", (0, 0), (-1, -1), "RIGHT" if locale.is_rtl else "LEFT"), + ("TOPPADDING", (0, 0), (-1, -1), 6), + ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ] + ) + ) + story.extend([summary_table, Spacer(1, 6 * mm)]) + + sections = [ + ("daily_summary", report_data["days"], True), + ("clients", report_data["clients"], False), + ("projects", report_data["projects"], False), + ("tags", report_data["tags"], False), + ] + for title_key, rows, is_daily in sections: + story.append(_paragraph(locale.t(title_key), section_style, locale)) + story.append(Spacer(1, 2 * mm)) + header = _rtl_row( + locale, + [ + locale.t("date") if is_daily else locale.t("name"), + locale.t("billable_hours"), + locale.t("non_billable_hours"), + locale.t("total_hours"), + locale.t("income"), + ], + ) + table = _styled_table( + [header, *_report_table_rows(locale, rows)], + locale=locale, + column_widths=[ + doc.width * 0.26, + doc.width * 0.15, + doc.width * 0.17, + doc.width * 0.14, + doc.width * 0.28, + ], + ) + story.extend([table, Spacer(1, 5 * mm)]) + + doc.build(story) + return buffer.getvalue() diff --git a/apps/reports/tasks.py b/apps/reports/tasks.py new file mode 100644 index 0000000..d3ac32f --- /dev/null +++ b/apps/reports/tasks.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from celery import shared_task +from django.conf import settings +from django.core.files.base import ContentFile +from django.urls import reverse +from django.utils import timezone + +from apps.notifications.services import RedisNotificationStore +from apps.reports.models import ReportExportJob +from apps.reports.services import build_table_report, build_user_scoped_table_reports +from apps.reports.services.export_i18n import build_export_locale +from apps.reports.services.exporters import build_excel_report, build_pdf_report + + +def _build_export_action_url(job: ReportExportJob) -> str: + path = reverse("report-export-job-download", kwargs={"pk": job.id}) + if settings.BASE_URL: + return f"{settings.BASE_URL.rstrip('/')}{path}" + return path + + +@shared_task(name="reports.generate_export") +def generate_report_export_task(job_id: str): + job = ReportExportJob.objects.filter(id=job_id).first() + if not job: + return None + + job.mark_processing() + try: + locale = build_export_locale(job.filters.get("language")) + report_data = build_table_report(job.requesting_user, job.filters) + if job.export_type == ReportExportJob.ExportType.EXCEL: + per_user_reports = build_user_scoped_table_reports(job.requesting_user, job.filters) + content = build_excel_report(report_data=report_data, locale=locale, per_user_reports=per_user_reports) + suffix = "xlsx" + mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + else: + content = build_pdf_report(report_data=report_data, locale=locale) + suffix = "pdf" + mime_type = "application/pdf" + + file_name = f"workspace-report-{timezone.now().strftime('%Y%m%d-%H%M%S')}.{suffix}" + storage_name = f"reports/exports/{job.id}-{file_name}" + job.file.save(storage_name, ContentFile(content), save=False) + job.status = ReportExportJob.Status.COMPLETED + job.file_name = file_name + job.completed_at = timezone.now() + job.expires_at = job.completed_at + timezone.timedelta(days=settings.REPORT_EXPORT_RETENTION_DAYS) + job.error_message = "" + job.save( + update_fields=[ + "file", + "status", + "file_name", + "completed_at", + "expires_at", + "error_message", + "updated_at", + ] + ) + + action_url = _build_export_action_url(job) + + RedisNotificationStore.add( + str(job.requesting_user_id), + { + "type": "report_export_ready", + "title": "Report export is ready", + "message": f"Your {job.export_type.upper()} report for {job.workspace.name} is ready to download.", + "level": "success", + "action_url": action_url, + "entity_type": "report_export", + "entity_id": str(job.id), + "meta": { + "workspace_id": str(job.workspace_id), + "workspace_name": job.workspace.name, + "export_type": job.export_type, + "file_name": file_name, + "download_url": action_url, + "mime_type": mime_type, + }, + }, + ) + return str(job.id) + except Exception as exc: # noqa: BLE001 + job.mark_failed(str(exc)) + RedisNotificationStore.add( + str(job.requesting_user_id), + { + "type": "report_export_failed", + "title": "Report export failed", + "message": f"Your {job.export_type.upper()} report for {job.workspace.name} could not be generated.", + "level": "error", + "action_url": "/reports", + "entity_type": "report_export", + "entity_id": str(job.id), + "meta": { + "workspace_id": str(job.workspace_id), + "workspace_name": job.workspace.name, + "export_type": job.export_type, + }, + }, + ) + raise + + +@shared_task(name="reports.cleanup_expired_exports") +def cleanup_expired_report_exports_task(): + expired_jobs = ReportExportJob.objects.filter( + status=ReportExportJob.Status.COMPLETED, + expires_at__lte=timezone.now(), + ) + removed = 0 + for job in expired_jobs: + if job.file: + job.file.delete(save=False) + job.mark_expired() + removed += 1 + return removed diff --git a/apps/reports/tests/__init__.py b/apps/reports/tests/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/reports/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/apps/reports/tests/test_tasks.py b/apps/reports/tests/test_tasks.py new file mode 100644 index 0000000..497ae6d --- /dev/null +++ b/apps/reports/tests/test_tasks.py @@ -0,0 +1,217 @@ +from datetime import timedelta +from decimal import Decimal +from io import BytesIO + +import pytest +from django.core.files.base import ContentFile +from django.core.files.storage import default_storage +from django.utils import timezone +from openpyxl import load_workbook + +from apps.notifications.services import store as notification_store +from apps.reports.models import ReportExportJob +from apps.reports.tasks import cleanup_expired_report_exports_task, generate_report_export_task +from apps.time_entries.models import TimeEntry +from apps.users.models import User +from apps.workspaces.models import Workspace + + +class FakeRedis: + def pipeline(self): + return self + + def zadd(self, *args, **kwargs): + return self + + def hset(self, *args, **kwargs): + return self + + def sadd(self, *args, **kwargs): + return self + + def execute(self): + return [] + + def publish(self, *args, **kwargs): + return None + + def zrevrange(self, *args, **kwargs): + return [] + + def hget(self, *args, **kwargs): + return None + + def zrem(self, *args, **kwargs): + return 1 + + def hdel(self, *args, **kwargs): + return 1 + + def zcard(self, *args, **kwargs): + return 0 + + def smembers(self, *args, **kwargs): + return set() + + def srem(self, *args, **kwargs): + return 1 + + +@pytest.fixture() +def fake_redis(monkeypatch): + redis = FakeRedis() + monkeypatch.setattr(notification_store, "redis_client", redis) + return redis + + +@pytest.fixture() +def owner(db): + return User.objects.create_user(mobile="09129990001", password="secret123", first_name="Owner", last_name="User") + + +@pytest.fixture() +def teammate(db): + return User.objects.create_user(mobile="09129990002", password="secret123", first_name="Team", last_name="Mate") + + +@pytest.fixture() +def workspace(owner, teammate): + workspace = Workspace.objects.create(name="Exports", owner=owner) + workspace.memberships.create(user=teammate, role="member", is_active=True) + return workspace + + +@pytest.fixture() +def time_entry(workspace, owner): + return TimeEntry.objects.create( + workspace=workspace, + user=owner, + description="Export row", + start_time="2026-04-12T08:00:00+03:30", + end_time="2026-04-12T10:00:00+03:30", + duration=timedelta(hours=2), + is_billable=True, + hourly_rate=Decimal("15.00"), + currency="USD", + ) + + +@pytest.fixture() +def teammate_entry(workspace, teammate): + return TimeEntry.objects.create( + workspace=workspace, + user=teammate, + description="Team row", + start_time="2026-04-13T08:00:00+03:30", + end_time="2026-04-13T09:00:00+03:30", + duration=timedelta(hours=1), + is_billable=False, + currency="USD", + ) + + +def test_generate_export_creates_file_and_marks_job_complete(fake_redis, workspace, owner, time_entry): + job = ReportExportJob.objects.create( + requesting_user=owner, + workspace=workspace, + export_type=ReportExportJob.ExportType.EXCEL, + filters={ + "workspace": str(workspace.id), + "period": "this_month", + "from_date": "2026-04-01", + "to_date": "2026-04-30", + "user": str(owner.id), + "client": None, + "project": None, + "tags": [], + "language": "en", + }, + ) + + generate_report_export_task(str(job.id)) + job.refresh_from_db() + + assert job.status == ReportExportJob.Status.COMPLETED + assert bool(job.file) + assert default_storage.exists(job.file.name) + + +def test_generate_excel_export_adds_per_user_sheets_for_all_users_scope( + fake_redis, + workspace, + owner, + teammate, + time_entry, + teammate_entry, +): + job = ReportExportJob.objects.create( + requesting_user=owner, + workspace=workspace, + export_type=ReportExportJob.ExportType.EXCEL, + filters={ + "workspace": str(workspace.id), + "period": "this_month", + "from_date": "2026-04-01", + "to_date": "2026-04-30", + "user": None, + "client": None, + "project": None, + "tags": [], + "language": "en", + }, + ) + + generate_report_export_task(str(job.id)) + job.refresh_from_db() + + workbook = load_workbook(BytesIO(job.file.read())) + assert workbook.sheetnames[0] == "Overall Report" + assert any("Owner User" in sheet for sheet in workbook.sheetnames[1:]) + assert any("Team Mate" in sheet for sheet in workbook.sheetnames[1:]) + assert len(workbook.sheetnames) == 3 + + +def test_generate_pdf_export_supports_persian_locale(fake_redis, workspace, owner, time_entry): + job = ReportExportJob.objects.create( + requesting_user=owner, + workspace=workspace, + export_type=ReportExportJob.ExportType.PDF, + filters={ + "workspace": str(workspace.id), + "period": "this_month", + "from_date": "2026-04-01", + "to_date": "2026-04-30", + "user": str(owner.id), + "client": None, + "project": None, + "tags": [], + "language": "fa", + }, + ) + + generate_report_export_task(str(job.id)) + job.refresh_from_db() + + assert job.status == ReportExportJob.Status.COMPLETED + assert job.file.read(4) == b"%PDF" + + +def test_cleanup_expires_and_removes_files(fake_redis, workspace, owner): + job = ReportExportJob.objects.create( + requesting_user=owner, + workspace=workspace, + export_type=ReportExportJob.ExportType.EXCEL, + status=ReportExportJob.Status.COMPLETED, + filters={}, + expires_at=timezone.now() - timezone.timedelta(days=1), + ) + file_name = f"reports/exports/{job.id}-old.xlsx" + job.file.save(file_name, ContentFile(b"old-data"), save=False) + job.save(update_fields=["file", "updated_at"]) + + removed = cleanup_expired_report_exports_task() + job.refresh_from_db() + + assert removed == 1 + assert job.status == ReportExportJob.Status.EXPIRED + assert not default_storage.exists(file_name) diff --git a/apps/reports/tests/test_views.py b/apps/reports/tests/test_views.py new file mode 100644 index 0000000..f492141 --- /dev/null +++ b/apps/reports/tests/test_views.py @@ -0,0 +1,163 @@ +from datetime import date, timedelta +from decimal import Decimal + +import pytest +from rest_framework.test import APIClient + +from apps.clients.models import Client +from apps.projects.models import Project +from apps.tags.models import Tag +from apps.time_entries.models import TimeEntry +from apps.users.models import User +from apps.workspaces.models import Workspace, WorkspaceMembership + + +@pytest.fixture() +def api_client(): + return APIClient() + + +@pytest.fixture() +def owner(db): + return User.objects.create_user(mobile="09128880001", password="secret123", first_name="Owner") + + +@pytest.fixture() +def admin(db): + return User.objects.create_user(mobile="09128880002", password="secret123", first_name="Admin") + + +@pytest.fixture() +def member(db): + return User.objects.create_user(mobile="09128880003", password="secret123", first_name="Member") + + +@pytest.fixture() +def workspace(owner, admin, member): + workspace = Workspace.objects.create(name="Reports", owner=owner) + WorkspaceMembership.objects.create(workspace=workspace, user=admin, role=WorkspaceMembership.Role.ADMIN, is_active=True) + WorkspaceMembership.objects.create(workspace=workspace, user=member, role=WorkspaceMembership.Role.MEMBER, is_active=True) + return workspace + + +@pytest.fixture() +def client(workspace): + return Client.objects.create(workspace=workspace, name="Acme") + + +@pytest.fixture() +def project(workspace, client): + return Project.objects.create(workspace=workspace, name="Website", client=client) + + +@pytest.fixture() +def tag(workspace): + return Tag.objects.create(workspace=workspace, name="Design", color="#ffffff") + + +@pytest.fixture() +def time_entries(workspace, owner, member, project, tag): + entry_owner = TimeEntry.objects.create( + workspace=workspace, + user=owner, + project=project, + description="Owner work", + start_time="2026-04-10T08:00:00+03:30", + end_time="2026-04-10T10:00:00+03:30", + duration=timedelta(hours=2), + is_billable=True, + hourly_rate=Decimal("25.00"), + currency="USD", + ) + entry_owner.tags.add(tag) + entry_member = TimeEntry.objects.create( + workspace=workspace, + user=member, + project=project, + description="Member work", + start_time="2026-04-11T09:00:00+03:30", + end_time="2026-04-11T10:00:00+03:30", + duration=timedelta(hours=1), + is_billable=False, + currency="USD", + ) + entry_member.tags.add(tag) + return [entry_owner, entry_member] + + +def test_member_only_sees_own_chart_report(api_client, member, workspace, time_entries): + api_client.force_authenticate(user=member) + + response = api_client.get( + "/api/reports/chart/", + {"workspace": str(workspace.id), "period": "this_month"}, + ) + + assert response.status_code == 200 + assert response.data["summary"]["total_duration"] == "01:00:00" + + +def test_admin_can_request_combined_table_report(api_client, admin, workspace, time_entries): + api_client.force_authenticate(user=admin) + + response = api_client.get( + "/api/reports/table/", + {"workspace": str(workspace.id), "period": "this_month"}, + ) + + assert response.status_code == 200 + assert response.data["summary"]["total_duration"] == "03:00:00" + assert len(response.data["days"]) == 2 + + +def test_custom_period_longer_than_31_days_is_rejected(api_client, owner, workspace): + api_client.force_authenticate(user=owner) + + response = api_client.get( + "/api/reports/chart/", + { + "workspace": str(workspace.id), + "period": "period", + "from_date": "2026-01-01", + "to_date": "2026-02-15", + }, + ) + + assert response.status_code == 400 + + +def test_persian_this_month_uses_jalali_month_bounds(api_client, owner, workspace, project, monkeypatch): + api_client.force_authenticate(user=owner) + monkeypatch.setattr("apps.reports.services.aggregation.timezone.localdate", lambda: date(2026, 4, 27)) + + TimeEntry.objects.create( + workspace=workspace, + user=owner, + project=project, + description="Previous jalali month", + start_time="2026-04-20T08:00:00+03:30", + end_time="2026-04-20T09:00:00+03:30", + duration=timedelta(hours=1), + is_billable=False, + currency="USD", + ) + TimeEntry.objects.create( + workspace=workspace, + user=owner, + project=project, + description="Current jalali month", + start_time="2026-04-21T08:00:00+03:30", + end_time="2026-04-21T10:00:00+03:30", + duration=timedelta(hours=2), + is_billable=False, + currency="USD", + ) + + response = api_client.get( + "/api/reports/table/", + {"workspace": str(workspace.id), "period": "this_month", "language": "fa"}, + ) + + assert response.status_code == 200 + assert response.data["summary"]["total_duration"] == "02:00:00" + assert response.data["scope"]["from_date"] == "2026-04-21" diff --git a/config/settings/base.py b/config/settings/base.py index 3b5296a..943e5cc 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -46,6 +46,7 @@ LOCAL_APPS = [ "apps.tags", "apps.time_entries", "apps.notifications", + "apps.reports", ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS @@ -197,7 +198,7 @@ CACHES = { } } -CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/1") +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://127.0.0.1:6379/0") CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://127.0.0.1:6379/1") CELERY_ACCEPT_CONTENT = ["json"] CELERY_TASK_SERIALIZER = "json" @@ -230,7 +231,9 @@ NOTIFICATION_TOAST_LEVELS = tuple( if level.strip() ) -CELERY_IMPORTS = ("apps.users.tasks", "apps.notifications.tasks") +REPORT_EXPORT_RETENTION_DAYS = int(os.getenv("REPORT_EXPORT_RETENTION_DAYS", "7")) + +CELERY_IMPORTS = ("apps.users.tasks", "apps.notifications.tasks", "apps.reports.tasks") STORAGES = { diff --git a/config/urls.py b/config/urls.py index be25ddd..b575a98 100644 --- a/config/urls.py +++ b/config/urls.py @@ -22,6 +22,7 @@ urlpatterns = [ path('api/', include('apps.tags.api.urls'), name="tags"), path('api/', include('apps.time_entries.api.urls'), name="time_entries"), path("api/notifications/", include("apps.notifications.api.urls"), name="notifications"), + path("api/reports/", include("apps.reports.api.urls"), name="reports"), ] if settings.DEBUG: diff --git a/requirements/base.txt b/requirements/base.txt index 01b3169..6e58c63 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -29,12 +29,17 @@ psycopg[binary]>=3.2 django-auditlog==3.4.1 python-json-logger==3.3.0 -# Utilities -python-dateutil>=2.9 -requests - -# Image/file handling -Pillow>=10.3 +# Utilities +python-dateutil>=2.9 +requests +openpyxl>=3.1 +reportlab>=4.0 +jdatetime>=5.2 +arabic-reshaper>=3.0 +python-bidi>=0.6 + +# Image/file handling +Pillow>=10.3 # background tasks celery==5.4.0