From f649194dab37d105b59cb304c321a9d31337f2bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexa=20Valentov=C3=A1?= <git@imaniti.org>
Date: Mon, 2 Sep 2024 19:33:30 +0200
Subject: [PATCH] improve imports, add ICO loading script

---
 .../commands/import_old_contracts.py          | 124 +++++++++++++++++-
 .../management/commands/load_from_ico.py      |  87 ++++++++++++
 contracts/urls.py                             |   1 +
 contracts/views.py                            |  59 ++++++++-
 4 files changed, 266 insertions(+), 5 deletions(-)
 create mode 100644 contracts/management/commands/load_from_ico.py

diff --git a/contracts/management/commands/import_old_contracts.py b/contracts/management/commands/import_old_contracts.py
index ea613d2..d58d9ed 100644
--- a/contracts/management/commands/import_old_contracts.py
+++ b/contracts/management/commands/import_old_contracts.py
@@ -410,6 +410,16 @@ class Command(BaseCommand):
 
         patterns = (
             (r"\s\s+", " "),
+            (r"(B|b)c\.|,\s(P|p)h\.(D|d)\.|(M|m)g(r|A|a)\.|(I|i)ng\.|PeadDr\.|PeaDr\.|(P|p)h(D|d)r\.", ""),
+            (r"\s(e|E)t\s", ""),
+            (r"\s\s+", " "),
+            (r"^ ", ""),
+            (r"^Alvarium, s\.r\.o\.", "Alvarium s.r.o."),
+            (r"^Politické hnutí Senátor 21$", "Politické hnutí SENÁTOR 21"),
+            (r"^Petr Springfield$", "Petr Springinsfeld"),
+            (r"RAINREKNAM", "RAILREKLAM"),
+            (r"^Šárka václavíková$", "Šárka Václavíková"),
+            (r"^Štepán Drtina$", "Štěpán Drtina"),
             (r"^(1\. Pirátská s\.r\.o|1\.Pirátská s\.r\.o\.)$", "1. Pirátská s.r.o."),
             (r"České pojišťovna, a.s.", "Česká pojišťovna, a.s."),
             (r"Datrolex, s.r.o.", "DATROLEX, s.r.o."),
@@ -422,12 +432,24 @@ class Command(BaseCommand):
                 ),
                 "Křesťanská a demokratická unie – Československá strana lidová",
             ),
+            (r"^ČSSD$", "Česká strana sociálně demokratická"),
+            (r"^Generali česká pojišťovna$", "Generali Česká pojišťovna a.s."),
             (r"LN - Audit s\.r\.o\." "LN-AUDIT s.r.o."),
             (r"Olga Richteová", "Olga Richterová"),
             (
                 r"^(politické hnutí Změna|PolitickéHnutí Změna)$",
                 "Politické hnutí Změna",
             ),
+            (r"^iveta", "Iveta"),
+            (r"^Jan Bohm$", "Jan Böhm"),
+            (
+                r"^Kooperativa$|^Kooperativa pojišťovna, a\.s\.$",
+                "Kooperativa pojišťovna, a.s., Vienna Insurance Group"
+            ),
+            (r"^Dominika P\. Michailidu$", "Dominika Poživilová Michailidu"),
+            (r"^Miluš ", "Miluše"),
+            (r"^Občanské sdružení o\.s\.$", "Občanské sdružení, o.s."),
+            (r"^Aneta Hedilova$", "Aneta Heidlová"),
             (r"^Systemický institut s\.r\.o\$", "Systemický institut, s.r.o."),
             (r"^Václav fořtík$", "Václav Fořtík"),
             (r"^Vodafone$", "Vodafone Czech Republic a.s."),
@@ -435,14 +457,28 @@ class Command(BaseCommand):
             (r"^Vojtech ", "Vojtěch "),
             (r"^Zdenek ", "Zdeněk "),
             (r" Bohmova$", " Bohmová"),
+            (r"^Vratislav filípek$", "Vratislav Filípek"),
+            (r"^W Czech development$", "W Czech Development s.r.o."),
+            (r"^Nadislav", "Ladislav"),
+            (r"^Nukáš", "Lukáš"),
+            (r"^Jan Nička$", "Jan Lička"),
+            (r"^Jan Nipavský$", "Jan Lipavský"),
+            (r"^Jan Noužek$", "Jan Loužek"),
+            (r"^Simona Nuftová$", "Simona Luftová"),
+            (r"^Václav Náska$", "Václav Láska"),
+            (r"^Michal Nanger$", "Michal Langer"),
+            (r"^Nadislav", "Ladislav"),
+            (r"^Naureen Hollge$|^Naureen Holge$|^Naureen Höllge$", "Laureen Höllge"),
+            (r"^Oldřich Nhotský$", "Oldřich Lhotský"),
             (r" (KUdláčková|Kudlláčková)$", " Kudláčková"),
             (r"^Jiří knotek$", "Jiří Knotek"),
             (r"^JIří Roubíček$", "Jiří Roubíček"),
             (r"^Koalice Vlasta\. z\.s\.$", "Koalice Vlasta, z.s."),
             (r"^Mikuáš ", "Mikuláš "),
+            (r"^Vítězslav Adamec, předseda KS Karlovarský kraj$", "Vítězslav Adamec"),
             (r"^Strana zelených$", "Strana Zelených"),
             (r"^Systemický institut s\.r\.o\.$", "Systemický institut, s.r.o."),
-            (r"^Adéla hradilová$", "Adéla Hradilová"),
+            (r"^Adéla hradilová$|^Adela Hradilova$", "Adéla Hradilová"),
         )
 
         for pattern in patterns:
@@ -468,6 +504,36 @@ class Command(BaseCommand):
             representative_model = SigneeSignatureRepresentative
             instance = model(name=name, address_country="Česká republika")
 
+        if model is Signee:
+            if (
+                "s.r.o" in instance.name
+                or "s. r. o." in instance.name
+                or "a.s." in instance.name
+                or "a. s." in instance.name
+                or "o.s." in instance.name
+                or "o. s." in instance.name
+                or "z.s." in instance.name
+                or "z. s." in instance.name
+                or "1" in instance.name
+                or "2" in instance.name
+                or "3" in instance.name
+                or "4" in instance.name
+                or "5" in instance.name
+                or "6" in instance.name
+                or "7" in instance.name
+                or "8" in instance.name
+                or "9" in instance.name
+                or "0" in instance.name
+                or len(instance.name.split(" ")) != 2
+                or instance.name in ("Strana Zelených",)
+            ):
+                instance.entity_type = instance.EntityTypes.LEGAL_ENTITY
+            else:
+                if instance.ico_number is None:
+                    instance.entity_type = instance.EntityTypes.NATURAL_PERSON
+                else:
+                    instance.entity_type = instance.EntityTypes.BUSINESS_NATURAL_PERSON
+
         for signing_party_key, signing_party_value in signing_party.items():
             if isinstance(signing_party_value, str):
                 signing_party_value = signing_party_value.strip()
@@ -555,7 +621,16 @@ class Command(BaseCommand):
 
                         continue
 
-                    instance.ico_number = str(signing_party_value)
+                    signing_party_value = str(signing_party_value)
+
+                    # Replace up to 5 leading zeroes
+                    for i in range(5):
+                        if signing_party_value.startswith("0"):
+                            self.normalization_count += 1
+
+                            signing_party_value = signing_party_value[1:]
+
+                    instance.ico_number = signing_party_value
                 case "zástupce":
                     if not isinstance(signing_party_value, str | list):
                         issue_count += 1
@@ -651,8 +726,48 @@ class Command(BaseCommand):
                     if signing_party_value in (
                         "Česká Pirátská strana",
                         "Česká pirátská strana",
-                    ):
+                        "Čeksá pirátská strana",
+                        "piráti",
+                        "Piráti"
+                    ) or signing_party_value == instance.name:
                         # Irrelevant
+                        self.normalization_count += 1
+                        continue
+
+                    if signing_party_value and model is Signee and instance.entity_type == instance.EntityTypes.NATURAL_PERSON:
+                        issue_count += 1
+                        contract.notes += f"Fyzická osoba má zadaný orgán: {signing_party_value}\n"
+                        issues.append(
+                            self.use_issue("Chybná organizační složka")
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} signed by a natural person "
+                                    f"has a department: {signing_party_value}."
+                                )
+                            )
+
+                        continue
+
+                    if signing_party_value in (
+                        "dle stanov a na základě plné moci",
+                    ):
+                        issue_count += 1
+                        contract.notes += f"Chybná organizační složka: {signing_party_value}\n"
+                        issues.append(
+                            self.use_issue("Chybná organizační složka")
+                        )
+
+                        if self.verbosity >= 2:
+                            self.stderr.write(
+                                self.style.NOTICE(
+                                    f"Contract {slug} has an invalid department: "
+                                    f"{signing_party_value}."
+                                )
+                            )
+
                         continue
 
                     instance.department = self.normalize_department(signing_party_value)
@@ -678,7 +793,8 @@ class Command(BaseCommand):
                 or "8" in instance.name
                 or "9" in instance.name
                 or "0" in instance.name
-                or len(instance.name.split(" ")) not in (2, 3)
+                or len(instance.name.split(" ")) != 2
+                or instance.name in ("Strana Zelených",)
             ):
                 instance.entity_type = instance.EntityTypes.LEGAL_ENTITY
             else:
diff --git a/contracts/management/commands/load_from_ico.py b/contracts/management/commands/load_from_ico.py
new file mode 100644
index 0000000..b0b7640
--- /dev/null
+++ b/contracts/management/commands/load_from_ico.py
@@ -0,0 +1,87 @@
+from django.core.management.base import BaseCommand
+from contracts.models import Signee
+import requests
+
+
+class Command(BaseCommand):
+    help = "Update signee information from IČO numbers."
+
+    def handle(self, *args, **options) -> None:
+        do_all = False
+
+        for signee in Signee.objects.filter(ico_number__isnull=False):
+            try:
+                ares_info = requests.get(
+                    f"https://ares.gov.cz/ekonomicke-subjekty-v-be/rest/ekonomicke-subjekty/{signee.ico_number}"
+                )
+
+                ares_info.raise_for_status()
+
+                formatted_ares_data = ares_info.json()
+
+                street = ""
+                
+                if "nazevUlice" in formatted_ares_data["sidlo"]:
+                    street += formatted_ares_data["sidlo"]["nazevUlice"]
+
+                if "cisloDomovni" in formatted_ares_data["sidlo"]:
+                    street += (" " + str(formatted_ares_data["sidlo"]["cisloDomovni"]))
+
+                if "cisloOrientacni" in formatted_ares_data["sidlo"]:
+                    street += ("/" + str(formatted_ares_data["sidlo"]["cisloOrientacni"]))
+
+                changed = False
+
+                attribute_sets = {
+                    "name": formatted_ares_data["obchodniJmeno"],
+                    "address_street_with_number": street,
+                    "address_district": formatted_ares_data["sidlo"]["nazevCastiObce"],
+                    "address_zip": formatted_ares_data["sidlo"]["psc"],
+                    "address_country": formatted_ares_data["sidlo"]["nazevStatu"]
+                }
+
+                for attribute, value in attribute_sets.items():
+                    if str(getattr(signee, attribute)) != str(value):
+                        changed = True
+                        self.stdout.write(f"\nUpdating {signee}'s {attribute}:\n\tOLD:\t{getattr(signee, attribute)}\n\tNEW:\t{value}\n")
+
+                if changed:
+                    if not do_all:
+                        change_opt = input("\nConfirm? [y/n/selective/all]: ")
+
+                        while change_opt not in ("y", "selective", "n", "all"):
+                            change_opt = input("\nConfirm? [y/n/selective/all]: ")
+
+                        if change_opt not in ("y", "selective"):
+                            if change_opt == "all":
+                                do_all = True
+
+                            continue
+
+                        attrs_to_change = []
+
+                        if change_opt == "y":
+                            attrs_to_change = attribute_sets.keys()
+                        else:
+                            attrs_to_change = input(
+                                "Which attrs? [name, address_street_with_number, address_district, "
+                                "address_zip, address_country - separated by commas]: "
+                            ).split(",")
+                    else:
+                        attrs_to_change = attribute_sets.keys()
+
+                    changed_attrs = []
+
+                    for attribute, value in attribute_sets.items():
+                        if attribute in attrs_to_change:
+                            changed_attrs.append(attribute)
+
+                            setattr(signee, attribute, value)
+
+                    signee.save()
+
+                    self.stdout.write(self.style.SUCCESS(f"\nChanged ({','.join(changed_attrs)})."))
+                    self.stdout.write("\n\n\n")
+
+            except Exception:
+                self.stdout.write(self.style.WARNING(f"Signee {signee}'s IČO appears to be invalid."))
\ No newline at end of file
diff --git a/contracts/urls.py b/contracts/urls.py
index 91c6fac..22a8770 100644
--- a/contracts/urls.py
+++ b/contracts/urls.py
@@ -11,6 +11,7 @@ urlpatterns = [
         name="search",
     ),
     path("contracts/<int:id>", views.view_contract, name="view_contract"),
+    path("contracts/<int:id>.json", views.view_contract_json, name="view_contract_json"),
     path(
         "contracts/filing-areas/<int:id>",
         views.view_contract_filing_area,
diff --git a/contracts/views.py b/contracts/views.py
index 2941353..bf550e3 100644
--- a/contracts/views.py
+++ b/contracts/views.py
@@ -5,7 +5,7 @@ from django.conf import settings
 from django.core.paginator import Paginator
 from django.db import models
 from django.db.models.functions import Lower
-from django.http import HttpResponse
+from django.http import HttpResponse, JsonResponse
 from django.shortcuts import get_object_or_404, render
 from django_downloadview import ObjectDownloadView
 from django_http_exceptions import HTTPExceptions
@@ -435,6 +435,63 @@ def view_signees(request):
 # END Submodel listing views
 
 
+# Basic contract view API
+def view_contract_json(request, id: int):
+    filter = models.Q(status=Contract.StatusTypes.APPROVED)
+
+    if not request.user.has_perm("contracts.view_confidential"):
+        filter = filter & (
+            models.Q(is_public=True)
+            | (
+                models.Q(created_by=request.user)
+                if not request.user.is_anonymous
+                else models.Value(True)
+            )
+        )
+
+    contract = get_object_or_404(
+        (get_objects_for_user(request.user, "contracts.view_contract").filter(filter)),
+        id=id,
+    )
+
+    return JsonResponse({
+        "id": contract.id,
+        "created_by": {
+            "id": contract.created_by_id,
+            "username": contract.created_by.username,
+        },
+        "created_on": contract.created_on,
+        "updated_on": contract.updated_on,
+        "status": contract.status,
+        "name": contract.name,
+        "id_number": contract.id_number,
+        "types": [
+            {
+                "id": type.id,
+                "name": type.name
+            }
+            for type in contract.types.all()
+        ],
+        "summary": contract.summary,
+        "valid_start_date": contract.valid_start_date,
+        "valid_end_date": contract.valid_end_date,
+        "is_valid": contract.is_valid,
+        "is_public": contract.is_public,
+        "publishing_rejection_comment": contract.publishing_rejection_comment,
+        "paper_form_state": contract.paper_form_state,
+        "paper_form_person_responsible": contract.paper_form_person_responsible,
+        "tender_url": contract.tender_url,
+        "issues": [
+            {
+                "id": issue.id,
+                "name": issue.name
+            }
+            for issue in contract.issues.all()
+        ]
+
+    })
+
+
 # ARES CORS proxy
 def get_ares_info(request, ico: int):
     if not request.user.is_staff:
-- 
GitLab