import math from django.conf import settings from django.db import models from markdownx.models import MarkdownxField from shared.models import NameStrMixin from users.models import User class RepresentativeMixin: @property def name(self): raise NotImplementedError @property def function(self): raise NotImplementedError def __str__(self) -> str: result = self.name if self.function is not None: result += f", {self.function}" return result class Signee(models.Model): name = models.CharField( max_length=256, verbose_name="Jméno", ) class EntityTypes(models.TextChoices): NATURAL_PERSON = "natural_person", "Fyzická osoba" LEGAL_ENTITY = "legal_entity", "Právnická osoba" BUSINESS_NATURAL_PERSON = "business_natural_person", "Podnikající fyzická osoba" OTHER = "other", "Jiné" 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="Veřejné pouze, když typ není nastaven na fyzickou osobu.", ) # WARNING: Legal entity status dependent! address_district = models.CharField( max_length=256, verbose_name="Obec", ) address_zip = models.CharField( max_length=16, verbose_name="PSČ", help_text="Veřejné pouze, když typ není nastaven na fyzickou osobu.", ) # WARNING: Legal entity status dependent! address_country = models.CharField( max_length=256, verbose_name="Země", default=settings.DEFAULT_COUNTRY, ) ico_number = models.CharField( max_length=16, blank=True, null=True, verbose_name="IČO", ) # WARNING: Legal entity status dependent! date_of_birth = models.DateField( blank=True, null=True, verbose_name="Datum narození", ) # WARNING: Legal entity status dependent! department = models.CharField( max_length=128, blank=True, null=True, 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" def __str__(self) -> str: result = self.name if self.ico_number is not None: result += f" ({self.ico_number})" if self.date_of_birth is not None: result += f" ({self.date_of_birth})" if self.department is not None: result += f", {self.department}" representative_names = [] for representative in self.representatives.all(): representative_names.append(representative.name) if len(representative_names) != 0: result += " - zástupci " + ", ".join(representative_names) return result class SigneeRepresentative(RepresentativeMixin, models.Model): signee = models.ForeignKey( Signee, on_delete=models.CASCADE, related_name="representatives", verbose_name="Smluvní strana", ) name = models.CharField( max_length=256, verbose_name="Jméno", ) function = models.CharField( max_length=256, blank=True, null=True, verbose_name="Funkce", ) class Meta: verbose_name = "Zástupce" verbose_name_plural = "Zástupci" class Contractee(models.Model): name = models.CharField( max_length=256, default=settings.DEFAULT_CONTRACTEE_NAME, verbose_name="Jméno", ) address_street_with_number = models.CharField( max_length=256, default=settings.DEFAULT_CONTRACTEE_STREET, verbose_name="Ulice, č.p.", ) address_district = models.CharField( max_length=256, default=settings.DEFAULT_CONTRACTEE_DISTRICT, verbose_name="Obec", ) address_zip = models.CharField( max_length=16, default=settings.DEFAULT_CONTRACTEE_ZIP, verbose_name="PSČ", ) address_country = models.CharField( max_length=256, verbose_name="Země", default=settings.DEFAULT_COUNTRY, ) ico_number = models.CharField( max_length=16, blank=True, null=True, default=settings.DEFAULT_CONTRACTEE_ICO_NUMBER, verbose_name="IČO", ) department = models.CharField( max_length=128, blank=True, null=True, 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" def __str__(self) -> str: result = self.name if self.department is not None: result += f", {self.department}" representative_names = [] for representative in self.representatives.all(): representative_names.append(representative.name) if len(representative_names) != 0: result += " - zástupci " + ", ".join(representative_names) return result class ContracteeRepresentative(RepresentativeMixin, models.Model): contractee = models.ForeignKey( Contractee, on_delete=models.CASCADE, related_name="representatives", verbose_name="Smluvní strana", ) name = models.CharField( max_length=256, verbose_name="Jméno", ) function = models.CharField( max_length=256, blank=True, null=True, verbose_name="Funkce", ) class Meta: verbose_name = "Zástupce" verbose_name_plural = "Zástupci" class ContractType(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", ) class Meta: verbose_name = "Typ smlouvy" verbose_name_plural = "Typy smlouvy" class ContractIssue(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", ) class Meta: verbose_name = "Problém se smlouvou" verbose_name_plural = "Problémy se smlouvami" class ContractFilingArea(NameStrMixin, models.Model): name = models.CharField( max_length=32, verbose_name="Jméno", ) person_responsible = models.CharField( max_length=256, verbose_name="Odpovědná osoba", ) class Meta: verbose_name = "Spisovna" verbose_name_plural = "Spisovny" class Contract(models.Model): # BEGIN Automatically set fields created_by = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name="uploaded_contracts", verbose_name="Vytvořena uživatelem", help_text="Informace není veřejně přístupná. Pokud vytváříš novou smlouvu, budeš to ty.", ) # WARNING: exclude in admin public_status_set_by = models.ForeignKey( User, on_delete=models.SET_NULL, blank=True, null=True, related_name="public_status_altered_contracts", verbose_name="Zveřejněno / nezveřejněno uživatelem", help_text="Obsah není veřejně přístupný.", ) # WARNING: exclude in admin all_parties_sign_date = models.DateField( verbose_name="Datum podpisu všech stran", blank=True, null=True, ) # WARNING: Exclude in admin, autofill # END Automatically set fields # BEGIN Approval fields class ApprovalStates(models.TextChoices): NO = "no", "Neschválená" YES = "yes", "Schválená" 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", ) types = models.ManyToManyField( ContractType, verbose_name="Typ", ) summary = models.TextField( max_length=256, blank=True, null=True, verbose_name="Sumarizace obsahu smlouvy", ) 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á" INVALID = "invalid", "Neplatná" class PublicStates(models.TextChoices): YES = "yes", "Veřejná" NO = "no", "Neveřejná" class PaperFormStates(models.TextChoices): SENT = "sent", "Odeslaný" STORED = "stored", "Uložený" TO_SHRED = "to_shred", "Ke skartaci" SHREDDED = "shredded", "Skartovaný" LOST = "lost", "Ztracený" legal_state = models.CharField( max_length=13, choices=LegalStates.choices, verbose_name="Stav právního ujednání", ) public_state = models.CharField( max_length=7, choices=PublicStates.choices, verbose_name="Veřejnost smlouvy", ) paper_form_state = models.CharField( max_length=8, choices=PaperFormStates.choices, verbose_name="Stav fyzického dokumentu", ) publishing_rejection_comment = models.CharField( max_length=65536, blank=True, null=True, verbose_name="Důvod nezveřejnění", help_text="Obsah není veřejně přístupný.", ) # WARNING: public status dependent tender_url = models.URLField( max_length=256, blank=True, null=True, verbose_name="Odkaz na výběrové řízení", ) agreement_url = models.URLField( max_length=256, blank=True, null=True, verbose_name="Odkaz na schválení", ) # WARNING: Dependent on the type! issues = models.ManyToManyField( ContractIssue, blank=True, verbose_name="Problémy", help_text='Veřejně nazváno "Poznámky".', ) class CostUnits(models.TextChoices): HOUR = "hour", "Hodina" MONTH = "month", "Měsíc" YEAR = "year", "Rok" TOTAL = "total", "Celkem" cost_amount = models.PositiveIntegerField( blank=True, null=True, verbose_name="Náklady (Kč)" ) cost_unit = models.CharField( max_length=5, choices=CostUnits.choices, blank=True, null=True, verbose_name="Jednotka nákladů" ) filing_area = models.ForeignKey( ContractFilingArea, on_delete=models.SET_NULL, blank=True, null=True, related_name="filed_contracts", verbose_name="Spisovna", help_text="Obsah není veřejně přístupný.", ) primary_contract = models.ForeignKey( "Contract", on_delete=models.SET_NULL, # TODO: Figure out if we want this behavior blank=True, 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! notes = MarkdownxField( blank=True, null=True, verbose_name="Poznámky", help_text="Poznámky jsou viditelné pro všechny, kteří mohou smlouvu spravovat.", ) class Meta: verbose_name = "Smlouva" verbose_name_plural = "Smlouvy" permissions = (("approve", "Schválit / zrušit schválení"),) def get_public_files(self): return ContractFile.objects.filter( contract=self, is_public=True, ).all() def __str__(self) -> str: return self.name class ContractFile(NameStrMixin, models.Model): name = models.CharField( max_length=128, blank=True, null=True, verbose_name="Jméno", ) is_public = models.BooleanField( verbose_name="Veřejně dostupný", default=False, ) file = models.FileField( verbose_name="Soubor", ) contract = models.ForeignKey( Contract, on_delete=models.CASCADE, related_name="files", verbose_name="Soubory", ) class Meta: verbose_name = "Soubor" verbose_name_plural = "Soubory" class ContracteeSignature(models.Model): contractee = models.ForeignKey( Contractee, on_delete=models.CASCADE, related_name="signatures", verbose_name="Smluvní strana", ) contract = models.ForeignKey( Contract, on_delete=models.CASCADE, related_name="contractee_signatures", verbose_name="Podpisy našich smluvních stran", ) date = models.DateField( verbose_name="Datum podpisu", ) class Meta: verbose_name = "Podpis naší smluvní strany" verbose_name_plural = "Podpisy našich smluvních stran" def __str__(self) -> str: representative_names = [] for representative in self.contractee.representatives.all(): representative_names.append(representative.name) representatives = ", ".join(representative_names) result = self.contractee.name if len(representatives) != 0: result += f" - zastoupena {representatives}" result += f", {self.date}" return result class SigneeSignature(models.Model): signee = models.ForeignKey( Signee, on_delete=models.CASCADE, related_name="signatures", verbose_name="Smluvní strana", ) contract = models.ForeignKey( Contract, on_delete=models.CASCADE, related_name="signee_signatures", verbose_name="Podpisy jiných smluvních stran", ) date = models.DateField( verbose_name="Datum podpisu", ) class Meta: verbose_name = "Podpis jiné smluvní strany" verbose_name_plural = "Podpisy ostatních smluvních stran" def __str__(self) -> str: representative_names = [] for representative in self.signee.representatives.all(): representative_names.append(representative.name) representatives = ", ".join(representative_names) result = self.signee.name if len(representatives) != 0: result += f" - zastoupena {representatives}" result += f", {self.date}" return result class ContractIntent(NameStrMixin, models.Model): name = models.CharField( max_length=128, verbose_name="Jméno", ) url = models.URLField( max_length=256, verbose_name="Odkaz", ) contract = models.ForeignKey( Contract, on_delete=models.CASCADE, related_name="intents", verbose_name="Smlouva", ) class Meta: verbose_name = "Záměr" verbose_name_plural = "Záměry"