From 9d0677e1701058ef163dce213b1d07c999112fc1 Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 26 Oct 2022 10:35:15 +0200
Subject: [PATCH 1/3] Bugfix

* fixes bug in excel report creation
* fixes order in laws of generated excel sheet
---
 analysis/utils/report.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/analysis/utils/report.py b/analysis/utils/report.py
index 63a06b84..9bbd7d3d 100644
--- a/analysis/utils/report.py
+++ b/analysis/utils/report.py
@@ -17,6 +17,7 @@ from compensation.models import Compensation, Payment, EcoAccountDeduction, EcoA
 from intervention.models import Intervention
 from konova.models import Geometry
 from konova.sub_settings.django_settings import BASE_DIR, DEFAULT_DATE_FORMAT
+from konova.sub_settings.lanis_settings import DEFAULT_SRID_RLP
 
 
 class TimespanReport:
@@ -103,9 +104,9 @@ class TimespanReport:
                     "iterable": self.evaluated_laws,
                     "attrs": [
                         "short_name",
+                        "num",
                         "num_checked",
                         "num_recorded",
-                        "num",
                     ]
                 },
                 "i_laws_checked": self.law_sum_checked,
@@ -333,7 +334,7 @@ class TimespanReport:
             return Geometry.objects.filter(
                 id__in=ids
             ).annotate(
-                geom_cast=Cast("geom", MultiPolygonField())
+                geom_cast=Cast("geom", MultiPolygonField(srid=DEFAULT_SRID_RLP))
             ).annotate(
                 num=NumGeometries("geom_cast")
             ).aggregate(

From 1f7f9c89643151913c96de8d9f4903dc52d7156e Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Wed, 26 Oct 2022 15:44:33 +0200
Subject: [PATCH 2/3] Command

* adds new 'generate_report' command
   * generates TimeSpanReports for given conservation offices
   * zips reports into archive
   * sents archive to ADMINS mails
---
 konova/management/commands/generate_report.py | 134 ++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100644 konova/management/commands/generate_report.py

diff --git a/konova/management/commands/generate_report.py b/konova/management/commands/generate_report.py
new file mode 100644
index 00000000..be0531ff
--- /dev/null
+++ b/konova/management/commands/generate_report.py
@@ -0,0 +1,134 @@
+"""
+Author: Michel Peltriaux
+Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
+Contact: michel.peltriaux@sgdnord.rlp.de
+Created on: 26.10.22
+
+"""
+import zipfile
+from io import BytesIO
+
+from django.core.mail import EmailMessage
+from django.utils import timezone
+from django.utils.datetime_safe import datetime
+
+from analysis.utils.excel.excel import TempExcelFile
+from analysis.utils.report import TimespanReport
+from codelist.models import KonovaCode
+from codelist.settings import CODELIST_CONSERVATION_OFFICE_ID
+from konova.management.commands.setup import BaseKonovaCommand
+from konova.sub_settings.django_settings import DEFAULT_FROM_EMAIL, ADMINS
+
+
+class Command(BaseKonovaCommand):
+    help = "Generates reports for conservation offices"
+
+    __from_key = "from"
+    __to_key = "to"
+    __for_key = "for"
+
+    from_date = None
+    to_date = None
+    offices = None
+
+    def add_arguments(self, parser):
+        try:
+            parser.add_argument(f"--{self.__from_key}", type=str, nargs="+")
+            parser.add_argument(f"--{self.__to_key}", type=str, nargs="+")
+            parser.add_argument(f"--{self.__for_key}", type=str, nargs="*")
+        except ValueError as e:
+            self._write_error(f"Argument error: {e}")
+            exit(-1)
+
+    def handle(self, *args, **options):
+        self.__check_arguments(options)
+        in_memory_zipped_reports = self.generate_reports_in_memory_zipped()
+        self.send_mail_to_admins(in_memory_zipped_reports)
+
+    def generate_reports_in_memory_zipped(self):
+        """
+        Generates in-memory reports and zips into in-memory zip file.
+
+        Returns:
+
+        """
+        memory_zip = BytesIO()
+        with zipfile.ZipFile(memory_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
+            for office in self.offices:
+                self._write_warning(f"  Process report for {office.long_name}...")
+                report = TimespanReport(office.id, self.from_date, self.to_date)
+                excel_file = TempExcelFile(report.excel_template_path, report.excel_map)
+                zf.writestr(zinfo_or_arcname=f"{office.long_name}_{self.from_date.date()}_{self.to_date.date()}.xlsx", data=excel_file.stream)
+        self._write_success(f"Reports generated for {self.offices.count()} offices and zipped.")
+        return memory_zip.getvalue()
+
+    def __check_arguments(self, options):
+        """
+        Checks given parameters for validity
+
+        Args:
+            options ():
+
+        Returns:
+
+        """
+        _from_value = options.get(self.__from_key, [None])[0]
+        _to_value = options.get(self.__to_key, [None])[0]
+        _for_value = options.get(self.__for_key, [])
+
+        # Check ISO dates
+        try:
+            _from_date = timezone.make_aware(datetime.fromisoformat(_from_value))
+            _to_date = timezone.make_aware(datetime.fromisoformat(_to_value))
+        except Exception as e:
+            self._write_warning(f"One of the dates is not in ISO format (YYYY-MM-DD). {e}")
+            exit(-1)
+
+        # Check conservation office IDs
+        _filter = {
+            "is_archived": False,
+            "is_leaf": True,
+            "code_lists__in": [CODELIST_CONSERVATION_OFFICE_ID],
+        }
+        offices = KonovaCode.objects.filter(**_filter)
+        if _for_value is not None and len(_for_value) != 0:
+            # Specifc offices requested
+            offices = offices.filter(short_name__in=_for_value)
+            all_requested_offices_exist = offices.count() == len(_for_value)
+            if not all_requested_offices_exist:
+                offices_short_name = set(offices.values_list("short_name", flat=True))
+                missing_ids = []
+                for val in _for_value:
+                    if val not in offices_short_name:
+                        missing_ids.append(val)
+                self._write_warning(
+                    f"Unknown offices: {missing_ids}"
+                )
+                exit(-1)
+
+        self.offices = offices
+        self.from_date = _from_date
+        self.to_date = _to_date
+
+    def send_mail_to_admins(self, attachment):
+        office_names = [office.long_name for office in self.offices]
+        admin_mails = [admin[1] for admin in ADMINS]
+
+        date_from_date = self.from_date.date()
+        date_to_date = self.to_date.date()
+
+        mail = EmailMessage(
+            subject="Konova reports generated",
+            body=f"Zipped reports attached from {date_from_date} to {date_to_date} for {office_names}.",
+            from_email=DEFAULT_FROM_EMAIL,
+            to=admin_mails,
+            attachments=[
+                (
+                    f"reports_{date_from_date}_{date_to_date}.zip",
+                    attachment,
+                    "application/zip"
+                )
+            ]
+        )
+        success = mail.send()
+        self._write_success(f"Mails with zip as attachment sent to {admin_mails}.")

From 415089084e7422fadb1a42c5b48802a4d34cb8ab Mon Sep 17 00:00:00 2001
From: mpeltriaux <michel.peltriaux@sgdnord.rlp.de>
Date: Fri, 25 Nov 2022 09:17:15 +0100
Subject: [PATCH 3/3] Command response dynamic

* adds a check whether the mail could be sent properly or not and changes the resulting response
---
 konova/management/commands/generate_report.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/konova/management/commands/generate_report.py b/konova/management/commands/generate_report.py
index be0531ff..51091871 100644
--- a/konova/management/commands/generate_report.py
+++ b/konova/management/commands/generate_report.py
@@ -131,4 +131,7 @@ class Command(BaseKonovaCommand):
             ]
         )
         success = mail.send()
-        self._write_success(f"Mails with zip as attachment sent to {admin_mails}.")
+        if success == 1:
+            self._write_success(f"Mails with zip as attachment sent to {admin_mails}.")
+        else:
+            self._write_error(f"Something went wrong during mail sending. Returned sending code was '{success}'")