From 1494a19730a3bee08ebf62b4291454a13b3c2f75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org>
Date: Thu, 23 Feb 2023 23:42:31 +0100
Subject: [PATCH] implement AO requests - new fields and name changes,
 categorization, select2 searching, state and other minor changes

---
 contracts/admin.py                            |  91 +++++++---
 contracts/forms.py                            |  55 +++++-
 ...ve_contract_expected_cost_hour_and_more.py | 125 ++++++++++++++
 ...003_rename_cost_type_contract_cost_unit.py |  18 ++
 .../0004_alter_contract_approval_state.py     |  18 ++
 contracts/models.py                           | 157 ++++++++----------
 contracts/templates/contracts/index.html      |  13 +-
 .../templates/contracts/view_contract.html    |  65 ++++----
 contracts/urls.py                             |  29 +++-
 contracts/views.py                            |   2 +-
 env.example                                   |   2 +-
 requirements/base.txt                         |   2 +-
 12 files changed, 425 insertions(+), 152 deletions(-)
 create mode 100644 contracts/migrations/0002_remove_contract_expected_cost_hour_and_more.py
 create mode 100644 contracts/migrations/0003_rename_cost_type_contract_cost_unit.py
 create mode 100644 contracts/migrations/0004_alter_contract_approval_state.py

diff --git a/contracts/admin.py b/contracts/admin.py
index d943e02..c94b2bb 100644
--- a/contracts/admin.py
+++ b/contracts/admin.py
@@ -59,36 +59,77 @@ class ContractAdmin(MarkdownxGuardedModelAdmin):
         ContractIntentInline,
     )
 
-    def get_fields(self, request, obj=None):
-        fields = [
-            "identifier",
-            "types",
-            "summary",
-            "valid_start_date",
-            "valid_end_date",
-            "legal_state",
-            "public_state",
-            "publishing_rejection_comment",
-            "paper_form_state",
-            "tender_url",
-            "agreement_url",
-            "issues",
-            "expected_cost_year",
-            "expected_cost_month",
-            "expected_cost_hour",
-            "filing_area",
-            "primary_contract",
-            "notes",
-            "created_by",
+    def get_fieldsets(self, request, obj=None):
+        fieldsets = [
+            (
+                "Základní informace",
+                {
+                    "fields": [
+                        "name",
+                        "id_number",
+                        "types",
+                        "summary",
+                        "public_state",
+                        "publishing_rejection_comment",
+                        "primary_contract",
+                    ]
+                }
+            ),
+            (
+                "Data",
+                {
+                    "fields": [
+                        "valid_start_date",
+                        "valid_end_date",
+                    ]
+                }
+            ),
+            (
+                "Administrativa",
+                {
+                    "fields": [
+                        "legal_state",
+                        "paper_form_state",
+                        "filing_area",
+                    ]
+                }
+            ),
+            (
+                "Odkazy",
+                {
+                    "fields": [
+                        "tender_url",
+                        "agreement_url",
+                    ]
+                }
+            ),
+            (
+                "Náklady",
+                {
+                    "fields": [
+                        "cost_amount",
+                        "cost_unit",
+                    ]
+                }
+            ),
+            (
+                "Doplňující informace",
+                {
+                    "fields": [
+                        "issues",
+                        "notes",
+                    ]
+                }
+            )
         ]
 
         if request.user.is_superuser or request.user.has_perm("approve", self):
-            fields.insert(
-                fields.index("summary"),
-                "is_approved",
+            fieldsets[0][1]["fields"].insert(
+                fieldsets[0][1]["fields"].index("publishing_rejection_comment"),
+                "approval_state",
             )
 
-        return fields
+        return fieldsets
 
     def save_model(self, request, obj, form, change) -> None:
         if obj.created_by is None:
diff --git a/contracts/forms.py b/contracts/forms.py
index 1ee82f7..4fc9958 100644
--- a/contracts/forms.py
+++ b/contracts/forms.py
@@ -1,7 +1,10 @@
+import djhacker
+import dal.autocomplete
+
 from django import forms
 from webpack_loader.loader import WebpackLoader
 
-from .models import Contractee, Signee
+from .models import Contract, ContracteeSignature, SigneeSignature
 
 
 class ContractAdminForm(forms.ModelForm):
@@ -20,3 +23,53 @@ class SigneeAdminForm(forms.ModelForm):
             "shared/shared.js",
             "shared/admin_signee_form.js",
         )
+
+
+# BEGIN Autocompleted Contract fields
+
+for foreign_key_field in (
+    Contract.filing_area,
+    Contract.primary_contract,
+):
+    djhacker.formfield(
+        foreign_key_field,
+        forms.ModelChoiceField,
+        widget=dal.autocomplete.ModelSelect2(
+            url="contracts:select2_djhacker_contract_autocomplete"
+        )
+    )
+
+
+for many_to_many_field in (
+    Contract.types,
+    Contract.issues,
+):
+    djhacker.formfield(
+        many_to_many_field,
+        forms.ModelChoiceField,
+        widget=dal.autocomplete.ModelSelect2Multiple(
+            url="contracts:select2_djhacker_contract_autocomplete"
+        )
+    )
+
+# END Autocompleted Contract fields
+
+# BEGIN Autocompleted ContracteeSignature / SigneeSignature fields
+
+djhacker.formfield(
+    ContracteeSignature.contractee,
+    forms.ModelChoiceField,
+    widget=dal.autocomplete.ModelSelect2(
+        url="contracts:select2_djhacker_contractee_signature_autocomplete"
+    )
+)
+
+djhacker.formfield(
+    SigneeSignature.signee,
+    forms.ModelChoiceField,
+    widget=dal.autocomplete.ModelSelect2(
+        url="contracts:select2_djhacker_signee_signature_autocomplete"
+    )
+)
+
+# END Autocompleted ContracteeSignature / SigneeSignature fields
diff --git a/contracts/migrations/0002_remove_contract_expected_cost_hour_and_more.py b/contracts/migrations/0002_remove_contract_expected_cost_hour_and_more.py
new file mode 100644
index 0000000..61a2178
--- /dev/null
+++ b/contracts/migrations/0002_remove_contract_expected_cost_hour_and_more.py
@@ -0,0 +1,125 @@
+# Generated by Django 4.1.4 on 2023-02-23 19:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='contract',
+            name='expected_cost_hour',
+        ),
+        migrations.RemoveField(
+            model_name='contract',
+            name='expected_cost_month',
+        ),
+        migrations.RemoveField(
+            model_name='contract',
+            name='expected_cost_year',
+        ),
+        migrations.RemoveField(
+            model_name='contract',
+            name='identifier',
+        ),
+        migrations.RemoveField(
+            model_name='contract',
+            name='is_approved',
+        ),
+        migrations.RemoveField(
+            model_name='contractee',
+            name='color',
+        ),
+        migrations.RemoveField(
+            model_name='contracteerepresentative',
+            name='role',
+        ),
+        migrations.RemoveField(
+            model_name='signee',
+            name='color',
+        ),
+        migrations.RemoveField(
+            model_name='signee',
+            name='is_legal_entity',
+        ),
+        migrations.RemoveField(
+            model_name='signeerepresentative',
+            name='role',
+        ),
+        migrations.AddField(
+            model_name='contract',
+            name='approval_state',
+            field=models.CharField(choices=[('unknown', 'Nová'), ('no', 'Odmítnutá'), ('yes', 'Přijatá')], default='unknown', help_text='Může měnit jen schvalovatel. Pokud je smlouva veřejná, se stavem "Přijatá" se vypustí ven.', max_length=7, verbose_name='Stav schválení'),
+        ),
+        migrations.AddField(
+            model_name='contract',
+            name='cost_amount',
+            field=models.IntegerField(blank=True, null=True, verbose_name='Náklady (Kč)'),
+        ),
+        migrations.AddField(
+            model_name='contract',
+            name='cost_type',
+            field=models.CharField(blank=True, choices=[('hour', 'Hodina'), ('month', 'Měsíc'), ('year', 'Rok'), ('total', 'Celkem')], max_length=5, null=True, verbose_name='Jednotka nákladů'),
+        ),
+        migrations.AddField(
+            model_name='contract',
+            name='id_number',
+            field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Identifikační číslo'),
+        ),
+        migrations.AddField(
+            model_name='contract',
+            name='name',
+            field=models.CharField(default='', max_length=128, verbose_name='Název'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='contractee',
+            name='role',
+            field=models.CharField(blank=True, max_length=256, null=True, verbose_name='Role'),
+        ),
+        migrations.AddField(
+            model_name='signee',
+            name='entity_type',
+            field=models.CharField(choices=[('physical_person', 'Fyzická osoba'), ('legal_entity', 'Právnická osoba'), ('business_natural_person', 'Podnikající fyzická osoba')], default='legal_entity', help_text='Důležité označit správně! Fyzickým osobám nepublikujeme adresu.', max_length=23, verbose_name='Typ'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='signee',
+            name='role',
+            field=models.CharField(blank=True, max_length=256, null=True, verbose_name='Role'),
+        ),
+        migrations.AlterField(
+            model_name='contract',
+            name='legal_state',
+            field=models.CharField(choices=[('valid', 'Platná'), ('invalid', 'Neplatná')], max_length=13, verbose_name='Stav právního ujednání'),
+        ),
+        migrations.AlterField(
+            model_name='contract',
+            name='paper_form_state',
+            field=models.CharField(choices=[('sent', 'Odeslaná'), ('stored', 'Uložená'), ('to_shred', 'Ke skartaci'), ('shredded', 'Skartovaná'), ('lost', 'Ztracená')], max_length=8, verbose_name='Stav fyzického dokumentu'),
+        ),
+        migrations.AlterField(
+            model_name='contract',
+            name='valid_end_date',
+            field=models.DateField(blank=True, null=True, verbose_name='Konec účinnosti'),
+        ),
+        migrations.AlterField(
+            model_name='contract',
+            name='valid_start_date',
+            field=models.DateField(blank=True, null=True, verbose_name='Začátek účinnosti'),
+        ),
+        migrations.AlterField(
+            model_name='signee',
+            name='address_street_with_number',
+            field=models.CharField(help_text='Veřejné pouze, když typ není nastaven na fyzickou osobu.', max_length=256, verbose_name='Ulice, č.p.'),
+        ),
+        migrations.AlterField(
+            model_name='signee',
+            name='address_zip',
+            field=models.CharField(help_text='Veřejné pouze, když typ není nastaven na fyzickou osobu.', max_length=16, verbose_name='PSČ'),
+        ),
+    ]
diff --git a/contracts/migrations/0003_rename_cost_type_contract_cost_unit.py b/contracts/migrations/0003_rename_cost_type_contract_cost_unit.py
new file mode 100644
index 0000000..4c866da
--- /dev/null
+++ b/contracts/migrations/0003_rename_cost_type_contract_cost_unit.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.4 on 2023-02-23 21:08
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0002_remove_contract_expected_cost_hour_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='contract',
+            old_name='cost_type',
+            new_name='cost_unit',
+        ),
+    ]
diff --git a/contracts/migrations/0004_alter_contract_approval_state.py b/contracts/migrations/0004_alter_contract_approval_state.py
new file mode 100644
index 0000000..15b71cd
--- /dev/null
+++ b/contracts/migrations/0004_alter_contract_approval_state.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.4 on 2023-02-23 21:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contracts', '0003_rename_cost_type_contract_cost_unit'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='contract',
+            name='approval_state',
+            field=models.CharField(blank=True, choices=[('no', 'Odmítnutá'), ('yes', 'Přijatá')], help_text='Může měnit jen schvalovatel. Pokud je smlouva veřejná, se stavem "Přijatá" se vypustí ven.', max_length=7, null=True, verbose_name='Stav schválení'),
+        ),
+    ]
diff --git a/contracts/models.py b/contracts/models.py
index 685240a..b7f8179 100644
--- a/contracts/models.py
+++ b/contracts/models.py
@@ -1,6 +1,5 @@
 import math
 
-from colorfield.fields import ColorField
 from django.conf import settings
 from django.db import models
 from markdownx.models import MarkdownxField
@@ -18,68 +17,37 @@ class RepresentativeMixin:
     def function(self):
         raise NotImplementedError
 
-    @property
-    def role(self):
-        raise NotImplementedError
-
     def __str__(self) -> str:
         result = self.name
 
         if self.function is not None:
             result += f", {self.function}"
 
-        if self.role is not None:
-            result += f" ({self.role})"
-
         return result
 
 
-class ColorMixin(models.Model):
-    color = ColorField(
-        blank=True,
-        null=True,
-        verbose_name="Barva",
-    )
-
-    @property
-    def color_is_light(self) -> bool:
-        if self.color is None:
-            raise ValueError
-
-        # https://stackoverflow.com/a/29643643 - Thanks to John1024 and vallentin!
-        # https://stackoverflow.com/a/58270890 - Thanks to Kardi Teknomo!
-
-        hex_color = self.color.lstrip("#")
-        rgb_color = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
-
-        [r, g, b] = rgb_color
-
-        hsp = math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b))
-
-        if hsp > 127.5:
-            return True
-        else:
-            return False
-
-    class Meta:
-        abstract = True
-
-
-class Signee(ColorMixin, models.Model):
+class Signee(models.Model):
     name = models.CharField(
         max_length=256,
         verbose_name="Jméno",
     )
 
-    is_legal_entity = models.BooleanField(
-        verbose_name="Je právnická osoba",
-        help_text="Důležité označit správně! Pokud není osoba právnická, zveřejňujeme pouze obec a zemi.",
+    class EntityTypes(models.TextChoices):
+        NATURAL_PERSON = "physical_person", "Fyzická osoba"
+        LEGAL_ENTITY = "legal_entity", "Právnická osoba"
+        BUSINESS_NATURAL_PERSON = "business_natural_person", "Podnikající fyzická osoba"
+
+    entity_type = models.CharField(
+        max_length=23,
+        choices=EntityTypes.choices,
+        verbose_name="Typ",
+        help_text="Důležité označit správně! Fyzickým osobám nepublikujeme adresu.",
     )
 
     address_street_with_number = models.CharField(
         max_length=256,
         verbose_name="Ulice, č.p.",
-        help_text="Viditelné pouze u právnických osob.",
+        help_text="Veřejné pouze, když typ není nastaven na fyzickou osobu.",
     )  # WARNING: Legal entity status dependent!
 
     address_district = models.CharField(
@@ -90,7 +58,7 @@ class Signee(ColorMixin, models.Model):
     address_zip = models.CharField(
         max_length=16,
         verbose_name="PSČ",
-        help_text="Viditelné pouze u právnických osob.",
+        help_text="Veřejné pouze, když typ není nastaven na fyzickou osobu.",
     )  # WARNING: Legal entity status dependent!
 
     address_country = models.CharField(
@@ -119,6 +87,13 @@ class Signee(ColorMixin, models.Model):
         verbose_name="Organizační složka",
     )
 
+    role = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Role",
+    )
+
     class Meta:
         verbose_name = "Jiná smluvní strana"
         verbose_name_plural = "Ostatní smluvní strany"
@@ -166,19 +141,12 @@ class SigneeRepresentative(RepresentativeMixin, models.Model):
         verbose_name="Funkce",
     )
 
-    role = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Role",
-    )
-
     class Meta:
         verbose_name = "Zástupce"
         verbose_name_plural = "Zástupci"
 
 
-class Contractee(ColorMixin, models.Model):
+class Contractee(models.Model):
     name = models.CharField(
         max_length=256,
         default=settings.DEFAULT_CONTRACTEE_NAME,
@@ -224,6 +192,13 @@ class Contractee(ColorMixin, models.Model):
         verbose_name="Organizační složka",
     )
 
+    role = models.CharField(
+        max_length=256,
+        blank=True,
+        null=True,
+        verbose_name="Role",
+    )
+
     class Meta:
         verbose_name = "Naše smluvní strana"
         verbose_name_plural = "Naše smluvní strany"
@@ -265,13 +240,6 @@ class ContracteeRepresentative(RepresentativeMixin, models.Model):
         verbose_name="Funkce",
     )
 
-    role = models.CharField(
-        max_length=256,
-        blank=True,
-        null=True,
-        verbose_name="Role",
-    )
-
     class Meta:
         verbose_name = "Zástupce"
         verbose_name_plural = "Zástupci"
@@ -346,8 +314,35 @@ class Contract(models.Model):
 
     # END Automatically set fields
 
-    identifier = models.CharField(
+    # BEGIN Approval fields
+
+    class ApprovalStates(models.TextChoices):
+        NO = "no", "Odmítnutá"
+        YES = "yes", "Přijatá"
+
+    approval_state = models.CharField(
+        max_length=7,
+        choices=ApprovalStates.choices,
+        blank=True,
+        null=True,
+        verbose_name="Stav schválení",
+        help_text=(
+            "Může měnit jen schvalovatel. Pokud je smlouva "
+            "veřejná, se stavem \"Přijatá\" se vypustí ven."
+        ),
+    )
+
+    # END Approval fields
+
+    name = models.CharField(
         max_length=128,
+        verbose_name="Název",
+    )
+
+    id_number = models.CharField(
+        max_length=128,
+        blank=True,
+        null=True,
         verbose_name="Identifikační číslo",
     )
 
@@ -363,25 +358,19 @@ class Contract(models.Model):
         verbose_name="Sumarizace obsahu smlouvy",
     )
 
-    is_approved = models.BooleanField(
-        verbose_name="Je schválená",
-        help_text=(
-            "Může měnit jen schvalovatel. Pokud je smlouva "
-            "veřejná, zaškrtnutím se vypustí ven."
-        ),
-    )
-
     valid_start_date = models.DateField(
+        blank=True,
+        null=True,
         verbose_name="Začátek účinnosti",
     )
     valid_end_date = models.DateField(
+        blank=True,
+        null=True,
         verbose_name="Konec účinnosti",
     )
 
     class LegalStates(models.TextChoices):
         VALID = "valid", "Platná"
-        EFFECTIVE = "effective", "Účinná"
-        NOT_EFFECTIVE = "not_effective", "Neúčinná"
         INVALID = "invalid", "Neplatná"
 
     class PublicStates(models.TextChoices):
@@ -410,7 +399,7 @@ class Contract(models.Model):
     paper_form_state = models.CharField(
         max_length=8,
         choices=PaperFormStates.choices,
-        verbose_name="Stav papírové formy",
+        verbose_name="Stav fyzického dokumentu",
     )
 
     publishing_rejection_comment = models.CharField(
@@ -442,24 +431,24 @@ class Contract(models.Model):
         help_text='Veřejně nazváno "Poznámky".',
     )
 
-    # NOTE: Could we make this into expected_cost_type and expected_cost_amount?
-
-    expected_cost_year = models.IntegerField(
-        blank=True,
-        null=True,
-        verbose_name="Očekávané výdaje (rok)",
-    )
+    class CostUnits(models.TextChoices):
+        HOUR = "hour", "Hodina"
+        MONTH = "month", "Měsíc"
+        YEAR = "year", "Rok"
+        TOTAL = "total", "Celkem"
 
-    expected_cost_month = models.IntegerField(
+    cost_amount = models.IntegerField(
         blank=True,
         null=True,
-        verbose_name="Očekávané výdaje (měsíc)",
+        verbose_name="Náklady (Kč)"
     )
 
-    expected_cost_hour = models.IntegerField(
+    cost_unit = models.CharField(
+        max_length=5,
+        choices=CostUnits.choices,
         blank=True,
         null=True,
-        verbose_name="Očekávané výdaje (hodina)",
+        verbose_name="Jednotka nákladů"
     )
 
     filing_area = models.ForeignKey(
@@ -496,7 +485,7 @@ class Contract(models.Model):
         permissions = (("approve", "Schválit / zrušit schválení"),)
 
     def __str__(self) -> str:
-        return self.identifier
+        return self.name
 
 
 class ContractFile(NameStrMixin, models.Model):
diff --git a/contracts/templates/contracts/index.html b/contracts/templates/contracts/index.html
index b71d4d8..de90661 100644
--- a/contracts/templates/contracts/index.html
+++ b/contracts/templates/contracts/index.html
@@ -6,9 +6,9 @@
     <table class="table table-auto w-full table--striped table--bordered">
         <thead>
             <tr>
-                <td class="font-bold">Identifikace</td>
+                <td class="font-bold">Název</td>
                 <td>Typy</td>
-                <td>Právní stav</td>
+                <td>Platná</td>
                 <td>Účinná od</td>
                 <td>Účinná do</td>
                 <td>Podepsána s</td>
@@ -21,7 +21,7 @@
                         <a
                             class="underline"
                             href="{% url "contracts:view_contract" contract.id %}"
-                        >{{ contract.identifier }}</a>
+                        >{{ contract.name }}</a>
                     </td>
                     <td>
                         <ul class="flex flex-wrap gap-1.5">
@@ -30,15 +30,16 @@
                             {% endfor %}
                         </ul>
                     </td>
-                    <td>{{ contract.get_legal_state_display }}</td>
+                    <td>
+                        <i class="{% if contract.legal_state == contract.PublicStates.YES.0 %}ico--checkmark{% else %}ico--cross{% endif %}"></i>
+                    </td>
                     <td class="whitespace-nowrap">{{ contract.valid_start_date }}</td>
                     <td class="whitespace-nowrap">{{ contract.valid_end_date }}</td>
                     <td>
                         <ul class="flex flex-wrap gap-1.5">
                             {% for signature in contract.signee_signatures.all %}
                                 <li
-                                    class="p-1.5 rounded-sm whitespace-nowrap cursor-pointer {% if not signature.signee.color %}bg-gray-200 duration-100 hover:bg-gray-300{% endif %}{% if signature.signee.color and not signature.signee.color_is_light %} text-white{% endif %}"
-                                    {% if signature.signee.color %}style="background-color:{{ signature.signee.color }}"{% endif %}
+                                    class="p-1.5 rounded-sm whitespace-nowrap cursor-pointer bg-gray-200 duration-100 hover:bg-gray-300"
                                 >{{ signature.signee.name }}</li>
                             {% endfor %}
                         </ul>
diff --git a/contracts/templates/contracts/view_contract.html b/contracts/templates/contracts/view_contract.html
index 1a77825..94a06ce 100644
--- a/contracts/templates/contracts/view_contract.html
+++ b/contracts/templates/contracts/view_contract.html
@@ -2,12 +2,18 @@
 {% load subtract %}
 
 {% block content %}
-    <h1 class="head-alt-lg mb-10">{{ contract.identifier }}</h1>
+    <h1 class="head-alt-lg mb-10">{{ contract.name }}</h1>
 
     <h2 class="text-xl font-bold mb-5"><i class="ico--info mr-3"></i>Základní informace</h2>
 
     <table class="table table-auto w-full table--striped table--bordered mb-10">
         <tbody>
+            {% if contract.id_number %}
+                <tr>
+                    <td class="w-1/5 !p-2.5">Identifikační číslo</td>
+                    <td class="w-4/5 !p-2.5">{{ contract.id_number }}</td>
+                </tr>
+            {% endif %}
             <tr>
                 <td class="w-1/5 !p-2.5">Typy</td>
                 <td class="w-4/5 !p-2.5">
@@ -29,7 +35,7 @@
                     <td class="w-4/5 !p-2.5">
                         <a
                             href="{% url "contracts:view_contract" contract.primary_contract.id %}"
-                        >{{ contract.primary_contract.identifier }}</a>
+                        >{{ contract.primary_contract.name }}</a>
                     </td>
                 </tr>
             {% endif %}
@@ -116,27 +122,16 @@
                     {% endwith %}
                 </td>
             </tr>
-            {% if contract.expected_cost_year %}
-                <tr>
-                    <td class="w-1/5 !p-2.5">Očekávané náklady (rok)</td>
-                    <td class="w-4/5 !p-2.5">
-                        {{ contract.expected_cost_month }} Kč
-                    </td>
-                </tr>
-            {% endif %}
-            {% if contract.expected_cost_month %}
+            {% if contract.cost_amount %}
                 <tr>
-                    <td class="w-1/5 !p-2.5">Očekávané náklady (měsíc)</td>
+                    <td class="w-1/5 !p-2.5">Náklady</td>
                     <td class="w-4/5 !p-2.5">
-                        {{ contract.expected_cost_month }} Kč
-                    </td>
-                </tr>
-            {% endif %}
-            {% if contract.expected_cost_hour %}
-                <tr>
-                    <td class="w-1/5 !p-2.5">Očekávané náklady (hodina)</td>
-                    <td class="w-4/5 !p-2.5">
-                        {{ contract.expected_cost_hour }} Kč
+                        {{ contract.cost_amount }} Kč
+                        {% if contract.cost_unit != contract.CostUnits.TOTAL.0 %}
+                            / {{ contract.get_cost_unit_display }}
+                        {% else %}
+                            celkem
+                        {% endif %}
                     </td>
                 </tr>
             {% endif %}
@@ -176,13 +171,15 @@
                         <address class="mb-3">
                             <div class="mb-1">
                                 <a
-                                    class="inline-block p-1.5 mb-1 rounded-sm whitespace-nowrap cursor-pointer not-italic hover:no-underline {% if not signature.contractee.color %}bg-gray-200 duration-100 hover:bg-gray-300{% endif %}{% if signature.contractee.color and not signature.contractee.color_is_light %} text-white{% endif %}"
-                                    {% if signature.contractee.color %}style="background-color:{{ signature.contractee.color }}"{% endif %}
+                                    class="inline-block p-1.5 mb-1 rounded-sm whitespace-nowrap cursor-pointer not-italic hover:no-underline bg-gray-200 duration-100 hover:bg-gray-300"
                                 >
                                     <strong>{{ signature.contractee.name }}</strong>
                                     {% if signature.contractee.department %}
                                         - {{ signature.contractee.department }}
                                     {% endif %}
+                                    {% if signature.contractee.role %}
+                                        ({{ signature.contractee.role }})
+                                    {% endif %}
                                 </a>
                             </div>
 
@@ -226,17 +223,27 @@
                         <address class="mb-3">
                             <div class="mb-1">
                                 <a
-                                    class="inline-block p-1.5 mb-1 rounded-sm whitespace-nowrap cursor-pointer not-italic hover:no-underline {% if not signature.signee.color %}bg-gray-200 duration-100 hover:bg-gray-300{% endif %}{% if signature.signee.color and not signature.signee.color_is_light %} text-white{% endif %}"
-                                    {% if signature.signee.color %}style="background-color:{{ signature.signee.color }}"{% endif %}
+                                    class="inline-block p-1.5 mb-1 rounded-sm whitespace-nowrap cursor-pointer not-italic hover:no-underline bg-gray-200 duration-100 hover:bg-gray-300"
                                 >
                                     <strong>{{ signature.signee.name }}</strong>
                                     {% if signature.signee.department %}
                                         - {{ signature.signee.department }}
                                     {% endif %}
+                                    {% if signature.signee.role %}
+                                        ({{ signature.signee.role }})
+                                    {% endif %}
                                 </a>
                             </div>
 
-                            {% if signature.signee.is_legal_entity %}
+                            <div class="mb-1">
+                                {{ signature.signee.get_entity_type_display }}
+
+                                {% if signature.signee.entity_type == signature.signee.EntityTypes.NATURAL_PERSON.0 %}
+                                    <span class="text-gray-300">(zobrazujeme pouze obec)</span>
+                                {% elif %}
+                            </div>
+
+                            {% if signature.signee.entity_type == signature.signee.EntityTypes.NATURAL_PERSON.0 %}
                                 {{ signature.signee.address_street_with_number }}<br>
                                 {{ signature.signee.address_zip }} {{ signature.signee.address_district }}<br>
                                 {{ signature.signee.get_address_country_display }}<br>
@@ -247,12 +254,6 @@
                             {% if signature.signee.ico_number %}
                                 IČO: {{ signature.signee.ico_number }}<br>
                             {% endif %}
-
-                            {% if not signature.signee.is_legal_entity %}
-                                <span class="block mt-2">
-                                    <small class="font-thin">(Fyzická osoba, ukazujeme pouze obec.)</small><br>
-                                </span>
-                            {% endif %}
                         </address>
 
                         <div class="block mb-2">
diff --git a/contracts/urls.py b/contracts/urls.py
index e649007..130a892 100644
--- a/contracts/urls.py
+++ b/contracts/urls.py
@@ -1,9 +1,36 @@
+import dal.autocomplete
+
 from django.urls import path
 
-from . import views
+from . import views, models
 
 app_name = "contracts"
 urlpatterns = [
     path("", views.index, name="index"),
     path("contracts/<int:id>", views.view_contract, name="view_contract"),
+    path(
+        "contracts/autocomplete",
+        dal.autocomplete.Select2QuerySetView.as_view(model=models.Contract),
+        name="select2_djhacker_contract_autocomplete",
+    ),
+    path(
+        "contracts/signees/signatures/autocomplete",
+        dal.autocomplete.Select2QuerySetView.as_view(model=models.SigneeSignature),
+        name="select2_djhacker_signee_signature_autocomplete",
+    ),
+    path(
+        "contracts/contractees/signatures/autocomplete",
+        dal.autocomplete.Select2QuerySetView.as_view(model=models.ContracteeSignature),
+        name="select2_djhacker_contractee_signature_autocomplete",
+    ),
+    #path(
+        #"contracts/signees/autocomplete",
+        #dal.autocomplete.Select2QuerySetView.as_view(model=models.Signee),
+        #name="select2_djhacker_signee_autocomplete",
+    #),
+    #path(
+        #"contracts/contractees/autocomplete",
+        #dal.autocomplete.Select2QuerySetView.as_view(model=models.Contractee),
+        #name="select2_djhacker_contractee_autocomplete",
+    #),
 ]
diff --git a/contracts/views.py b/contracts/views.py
index fd204c4..ecaf2da 100644
--- a/contracts/views.py
+++ b/contracts/views.py
@@ -48,7 +48,7 @@ def view_contract(request, id: int):
         {
             "site_url": settings.SITE_URL,
             "user": request.user,
-            "title": contract.identifier,
+            "title": contract.name,
             "description": "",  # TODO
             "contract": contract,
         },
diff --git a/env.example b/env.example
index 081cf4a..3a72fc2 100644
--- a/env.example
+++ b/env.example
@@ -8,7 +8,7 @@ OIDC_RP_REALM_URL="http://localhost:8080/realms/master/"
 OIDC_RP_CLIENT_ID=contracts
 OIDC_RP_CLIENT_SECRET=VCn4LVAUc6RGLSup7VaAKsmrKUbWguaP
 
-DEFAULT_COUNTRY="CZ"
+DEFAULT_COUNTRY="Česká Republika"
 
 DEFAULT_CONTRACTEE_NAME="Česká pirátská strana"
 DEFAULT_CONTRACTEE_STREET="Na Moráni 360/3"
diff --git a/requirements/base.txt b/requirements/base.txt
index 8c286e8..f9d2f4d 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -2,11 +2,11 @@ django==4.1.4
 django-admin-interface==0.24.2
 django-autocomplete-light==3.9.4
 django-database-url==1.0.3
+djhacker==0.2.3
 psycopg2-binary==2.9.5
 django-webpack-loader==1.8.0
 nodeenv==1.7.0
 pirates==0.6.0
-django-colorfield==0.8.0
 django-markdownx==4.0.0b1
 django-environ==0.9.0
 django-http-exceptions==1.4.0
-- 
GitLab