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