From 2386cd1f7942888b1f381ab7d25014d4aadbfb92 Mon Sep 17 00:00:00 2001 From: Sam Dudley Date: Wed, 4 Dec 2024 15:42:47 +0000 Subject: [PATCH 1/2] Add superuser budget report to assist budget upload --- core/urls.py | 1 + core/views.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/core/urls.py b/core/urls.py index 922cbab45..50babc924 100644 --- a/core/urls.py +++ b/core/urls.py @@ -7,4 +7,5 @@ path("", views.index, name="index"), path("logout", views.logout, name="logout"), path("accessibility", views.AccessibilityPageView.as_view(), name="accessibility"), + path("report/budget-report", views.budget_report, name="report:budget_report"), ] diff --git a/core/views.py b/core/views.py index e3266e16c..ccf39aa61 100644 --- a/core/views.py +++ b/core/views.py @@ -1,10 +1,19 @@ +from functools import wraps +import datetime as dt +import csv + from django.contrib.auth.decorators import login_required +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.urls import reverse from django.views.generic.base import TemplateView from django_filters.views import FilterView from django_tables2.export.views import ExportMixin, TableExport from django_tables2.views import SingleTableMixin +from django.core.exceptions import PermissionDenied +from django.db.models.functions import Coalesce, Concat +from django.db.models import Sum, Value, Q, F +from django.db import models from core.utils.export_helpers import EXC_TAB_NAME_LEN from core.utils.generic_helpers import ( @@ -12,6 +21,7 @@ get_year_display, today_string, ) +from forecast.models import BudgetMonthlyFigure, FinancialPeriod @login_required() @@ -99,3 +109,76 @@ def logout(request): logout(request) return redirect(reverse("index")) + + +def report_view(func): + @wraps(func) + def view(request: HttpRequest, *args, **kwargs) -> HttpResponse: + if not request.user.is_superuser: + raise PermissionDenied + + report_name, header, results = func(request, *args, **kwargs) + + filename = f"{report_name}_{dt.datetime.now():%Y%m%d-%H%M%S}.csv" + response = HttpResponse( + content_type="text/csv", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + + writer = csv.writer(response) + writer.writerow(header) + for result in results: + writer.writerow(result) + + return response + + return view + + +@report_view +def budget_report(request: HttpRequest, *args, **kwargs): + periods = FinancialPeriod.objects.all() + + key_fields = [ + "financial_code__cost_centre__cost_centre_code", + "financial_code__natural_account_code__natural_account_code", + "financial_code__programme__programme_code", + "financial_code__analysis1_code__analysis1_code", + "financial_code__analysis2_code__analysis2_code", + "financial_code__project_code__project_code", + ] + + budget_per_month_annotation = { + period.period_short_name: Sum( + "amount", filter=Q(financial_period_id=period.pk), default=0 + ) + / 100 + for period in periods + } + + qs = ( + BudgetMonthlyFigure.objects.select_related("financial_code") + .filter( + archived_status__isnull=True, + financial_year=request.current_financial_year, + ) + .annotate(key=Concat(*key_fields, output_field=models.CharField())) + .values_list("key", *key_fields) + .annotate( + **budget_per_month_annotation | dict(total=Sum("amount", default=0) / 100), + ) + ) + + header = [ + "Key", + "Cost Centre", + "Natural Account", + "Programme", + "Analysis", + "Analysis2", + "Project", + *[period.period_short_name for period in periods], + "Total", + ] + + return "budget-report", header, qs From fd9c364c22c34da5087ab3394621a88a433763da Mon Sep 17 00:00:00 2001 From: Sam Dudley Date: Wed, 4 Dec 2024 15:44:12 +0000 Subject: [PATCH 2/2] fix --- core/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/views.py b/core/views.py index ccf39aa61..38a3775da 100644 --- a/core/views.py +++ b/core/views.py @@ -1,8 +1,12 @@ -from functools import wraps -import datetime as dt import csv +import datetime as dt +from functools import wraps from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied +from django.db import models +from django.db.models import Q, Sum +from django.db.models.functions import Concat from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.urls import reverse @@ -10,10 +14,6 @@ from django_filters.views import FilterView from django_tables2.export.views import ExportMixin, TableExport from django_tables2.views import SingleTableMixin -from django.core.exceptions import PermissionDenied -from django.db.models.functions import Coalesce, Concat -from django.db.models import Sum, Value, Q, F -from django.db import models from core.utils.export_helpers import EXC_TAB_NAME_LEN from core.utils.generic_helpers import (