diff --git a/contracts/admin.py b/contracts/admin.py index 448b49370579948ec196cd923e8c1d328aee33de..0b72ac6e136d2b3f37db10ad2fb4f26021b0cfd0 100644 --- a/contracts/admin.py +++ b/contracts/admin.py @@ -53,25 +53,6 @@ class ContractIntentInline(admin.TabularInline): class ContractAdmin(MarkdownxGuardedModelAdmin): form = ContractAdminForm - fields = ( - "created_by", - "types", - "valid_start_date", - "valid_end_date", - "legal_state", - "public_state", - "paper_form_state", - "publishing_rejection_comment", - "tender_url", - "identifier", - "issues", - "notes", - "summary", - "primary_contract", - "agreement_url", - "filing_area", - ) - readonly_fields = ("created_by",) inlines = ( @@ -81,6 +62,34 @@ class ContractAdmin(MarkdownxGuardedModelAdmin): ContractIntentInline, ) + def get_fields(self, request, obj=None): + fields = [ + "created_by", + "types", + "valid_start_date", + "valid_end_date", + "legal_state", + "public_state", + "paper_form_state", + "publishing_rejection_comment", + "tender_url", + "identifier", + "issues", + "notes", + "summary", + "primary_contract", + "agreement_url", + "filing_area", + "expected_cost_year", + "expected_cost_month", + "expected_cost_hour", + ] + + if request.user.is_superuser or request.user.has_perm("approve", self): + fields.append("is_approved") + + return fields + def save_model(self, request, obj, form, change) -> None: if obj.created_by is None: obj.created_by = request.user @@ -101,10 +110,7 @@ class SigneeRepresentativeInline(admin.TabularInline): class SigneeAdmin(MarkdownxGuardedModelAdmin): form = SigneeAdminForm - inlines = ( - SigneeRepresentativeInline, - SigneeSignatureInline, - ) + inlines = (SigneeRepresentativeInline,) class ContracteeRepresentativeInline(admin.TabularInline): @@ -113,7 +119,7 @@ class ContracteeRepresentativeInline(admin.TabularInline): class ContracteeAdmin(MarkdownxGuardedModelAdmin): - inlines = (ContracteeRepresentativeInline, ContracteeSignatureInline) + inlines = (ContracteeRepresentativeInline,) # END Signing parties diff --git a/contracts/migrations/0003_contract_expected_cost_hour_and_more.py b/contracts/migrations/0003_contract_expected_cost_hour_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..c098ed3c5bd8cc679e1aad284c477a93ac5f574f --- /dev/null +++ b/contracts/migrations/0003_contract_expected_cost_hour_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.1.4 on 2023-02-21 05:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0002_contract_is_approved_alter_contract_valid_end_date'), + ] + + operations = [ + migrations.AddField( + model_name='contract', + name='expected_cost_hour', + field=models.IntegerField(blank=True, null=True, verbose_name='Očekávané výdaje (hodina)'), + ), + migrations.AddField( + model_name='contract', + name='expected_cost_month', + field=models.IntegerField(blank=True, null=True, verbose_name='Očekávané výdaje (měsíc)'), + ), + migrations.AddField( + model_name='contract', + name='expected_cost_year', + field=models.IntegerField(blank=True, null=True, verbose_name='Očekávané výdaje (rok)'), + ), + ] diff --git a/contracts/migrations/0004_alter_contract_options.py b/contracts/migrations/0004_alter_contract_options.py new file mode 100644 index 0000000000000000000000000000000000000000..5b6fd8af549d471295c74a6f2a310f15ed37bffc --- /dev/null +++ b/contracts/migrations/0004_alter_contract_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.4 on 2023-02-21 05:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0003_contract_expected_cost_hour_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='contract', + options={'permissions': (('approve', 'Schválit / zrušit schválení'),), 'verbose_name': 'Smlouva', 'verbose_name_plural': 'Smlouvy'}, + ), + ] diff --git a/contracts/migrations/0005_alter_contract_primary_contract.py b/contracts/migrations/0005_alter_contract_primary_contract.py new file mode 100644 index 0000000000000000000000000000000000000000..b777807d54ae05a27dfb107fb3079277e94b2534 --- /dev/null +++ b/contracts/migrations/0005_alter_contract_primary_contract.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.4 on 2023-02-21 06:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contracts', '0004_alter_contract_options'), + ] + + operations = [ + migrations.AlterField( + model_name='contract', + name='primary_contract', + field=models.ForeignKey(blank=True, help_text='Např. pro dodatky nebo objednávky u rámcových smluv.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subcontracts', to='contracts.contract', verbose_name='Primární smlouva'), + ), + ] diff --git a/contracts/models.py b/contracts/models.py index 126d358f065bd54824bde018f2838ab425f32544..7575f796be0a72235f5d580b28a47a18925ac169 100644 --- a/contracts/models.py +++ b/contracts/models.py @@ -1,3 +1,5 @@ +import math + from django.conf import settings from django.db import models from django_countries.fields import CountryField @@ -33,7 +35,38 @@ class RepresentativeMixin: return result -class Signee(models.Model): +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): name = models.CharField( max_length=256, verbose_name="Jméno", @@ -85,12 +118,6 @@ class Signee(models.Model): verbose_name="Organizační složka", ) - color = ColorField( - blank=True, - null=True, - verbose_name="Barva", - ) - class Meta: verbose_name = "Jiná smluvní strana" verbose_name_plural = "Ostatní smluvní strany" @@ -150,7 +177,7 @@ class SigneeRepresentative(RepresentativeMixin, models.Model): verbose_name_plural = "Zástupci" -class Contractee(models.Model): +class Contractee(ColorMixin, models.Model): name = models.CharField( max_length=256, default=settings.DEFAULT_CONTRACTEE_NAME, @@ -195,12 +222,6 @@ class Contractee(models.Model): verbose_name="Organizační složka", ) - color = ColorField( - blank=True, - null=True, - verbose_name="Barva", - ) - class Meta: verbose_name = "Naše smluvní strana" verbose_name_plural = "Naše smluvní strany" @@ -420,6 +441,7 @@ class Contract(models.Model): null=True, related_name="subcontracts", verbose_name="Primární smlouva", + help_text="Např. pro dodatky nebo objednávky u rámcových smluv." ) # WARNING: Dependent on the type! filing_area = models.ForeignKey( @@ -432,12 +454,26 @@ class Contract(models.Model): help_text="Obsah není veřejně přístupný.", ) # WARNING: Dependent on the type! + # 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)", ) + expected_cost_month = models.IntegerField( + blank=True, + null=True, + verbose_name="Očekávané výdaje (měsíc)", + ) + + expected_cost_hour = models.IntegerField( + blank=True, + null=True, + verbose_name="Očekávané výdaje (hodina)", + ) + is_approved = models.BooleanField( verbose_name="Je schválená", help_text=( @@ -449,6 +485,10 @@ class Contract(models.Model): class Meta: verbose_name = "Smlouva" verbose_name_plural = "Smlouvy" + + permissions = ( + ("approve", "Schválit / zrušit schválení"), + ) def __str__(self) -> str: return self.identifier diff --git a/contracts/templates/contracts/index.html b/contracts/templates/contracts/index.html index f868502e1a8105da495b457da712361986f33d02..9771b74f01162ed9d8a75f76c4dc9bb704a288ec 100644 --- a/contracts/templates/contracts/index.html +++ b/contracts/templates/contracts/index.html @@ -7,7 +7,7 @@ <thead> <tr> <td class="font-bold">Identifikace</td> - <td>Typ</td> + <td>Typy</td> <td>Právní stav</td> <td>Účinná od</td> <td>Účinná do</td> @@ -37,7 +37,7 @@ <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 %}" + 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 %} >{{ signature.signee.name }}</li> {% endfor %} diff --git a/contracts/templates/contracts/view_contract.html b/contracts/templates/contracts/view_contract.html index f6b0481e83e4d9f9f805c6e40d57e735f8b53795..5e99e8416408f42d90d3a218ce9d45b51edf1a1a 100644 --- a/contracts/templates/contracts/view_contract.html +++ b/contracts/templates/contracts/view_contract.html @@ -143,7 +143,8 @@ <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 bg-gray-200 duration-100 hover:bg-gray-300" + 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 %} > <strong>{{ signature.contractee.name }}</strong> {% if signature.contractee.department %} @@ -192,7 +193,8 @@ <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 bg-gray-200 duration-100 hover:bg-gray-300" + 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 %} > <strong>{{ signature.signee.name }}</strong> {% if signature.signee.department %} @@ -201,7 +203,7 @@ </a> </div> - {% if signee.is_legal_entity %} + {% if signature.signee.is_legal_entity %} {{ signature.signee.address_street_with_number }}<br> {{ signature.signee.address_zip }} {{ signature.signee.address_district }}<br> {{ signature.signee.get_address_country_display }}<br> @@ -213,7 +215,7 @@ IČO: {{ signature.signee.ico_number }}<br> {% endif %} - {% if not signee.is_legal_entity %} + {% if not signature.signee.is_legal_entity %} <span class="block mt-2"> <small class="font-thin">(Fyzická osoba, ukazujeme pouze obec.)</small><br> </span> diff --git a/contracts/views.py b/contracts/views.py index d9f31264e8b557cbaf9966ea56bfdc1c5a86d963..79b58145a3f61db1296289e99ed88291fc4f6606 100644 --- a/contracts/views.py +++ b/contracts/views.py @@ -9,7 +9,7 @@ from .models import Contract def index(request): contracts = Contract.objects.filter( - is_approved__is=True, + is_approved=True, public_state=Contract.PublicStates.YES, ).order_by("valid_start_date").all() paginator = Paginator(contracts, 25) @@ -36,7 +36,7 @@ def index(request): def view_contract(request, id: int): contract = Contract.objects.filter( - is_approved__is=True, + is_approved=True, public_state=Contract.PublicStates.YES, ).get(id=id)