Exemplo n.º 1
0
class Group(models.Model):
    name = models.CharField(_("name"), max_length=100)

    subgroup_field_1 = models.CharField(max_length=50, blank=True)
    subgroup_field_2 = models.CharField(max_length=50, blank=True, default="baz")

    subgroup = GegevensGroepType(
        {"field_1": subgroup_field_1, "field_2": subgroup_field_2},
        optional=("field_2",),
    )
Exemplo n.º 2
0
class Person(models.Model):
    name = models.CharField(_("name"), max_length=50)

    address_street = models.CharField(_("street name"), max_length=255)
    address_number = models.CharField(_("house number"), max_length=10)

    address = GegevensGroepType({
        'street': address_street,
        'number': address_number,
    })

    group = models.ForeignKey('Group', null=True, on_delete=models.CASCADE)
Exemplo n.º 3
0
class Person(ETagMixin, models.Model):
    name = models.CharField(_("name"), max_length=50)

    address_street = models.CharField(_("street name"), max_length=255)
    address_number = models.CharField(_("house number"), max_length=10)

    address = GegevensGroepType(
        {"street": address_street, "number": address_number}, required=False
    )

    group = models.ForeignKey("Group", null=True, on_delete=models.CASCADE)

    hobbies = models.ManyToManyField("Hobby", related_name="people", blank=True)
Exemplo n.º 4
0
class Zaak(APIMixin, models.Model):
    """
    Modelleer de structuur van een ZAAK.

    Een samenhangende hoeveelheid werk met een welgedefinieerde aanleiding
    en een welgedefinieerd eindresultaat, waarvan kwaliteit en doorlooptijd
    bewaakt moeten worden.
    """

    uuid = models.UUIDField(unique=True,
                            default=uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")

    # Relate 'is_deelzaak_van'
    # De relatie vanuit een zaak mag niet verwijzen naar
    # dezelfde zaak d.w.z. moet verwijzen naar een andere
    # zaak. Die andere zaak mag geen relatie ?is deelzaak
    # van? hebben (d.w.z. deelzaken van deelzaken worden
    # niet ondersteund).
    hoofdzaak = models.ForeignKey(
        "self",
        limit_choices_to={"hoofdzaak__isnull": True},
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="deelzaken",
        verbose_name="is deelzaak van",
        help_text=_("URL-referentie naar de ZAAK, waarom verzocht is door de "
                    "initiator daarvan, die behandeld wordt in twee of meer "
                    "separate ZAAKen waarvan de onderhavige ZAAK er één is."),
    )

    identificatie = models.CharField(
        max_length=40,
        blank=True,
        help_text="De unieke identificatie van de ZAAK binnen de organisatie "
        "die verantwoordelijk is voor de behandeling van de ZAAK.",
        validators=[alphanumeric_excluding_diacritic],
    )
    bronorganisatie = RSINField(
        help_text="Het RSIN van de Niet-natuurlijk persoon zijnde de "
        "organisatie die de zaak heeft gecreeerd. Dit moet een geldig "
        "RSIN zijn van 9 nummers en voldoen aan "
        "https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef")
    omschrijving = models.CharField(
        max_length=80,
        blank=True,
        help_text="Een korte omschrijving van de zaak.")
    toelichting = models.TextField(max_length=1000,
                                   blank=True,
                                   help_text="Een toelichting op de zaak.")
    zaaktype = models.ForeignKey(
        "catalogi.ZaakType",
        on_delete=models.CASCADE,
        help_text=
        "URL-referentie naar het ZAAKTYPE (in de Catalogi API) in de CATALOGUS waar deze voorkomt",
    )
    registratiedatum = models.DateField(
        help_text="De datum waarop de zaakbehandelende organisatie de ZAAK "
        "heeft geregistreerd. Indien deze niet opgegeven wordt, "
        "wordt de datum van vandaag gebruikt.",
        default=date.today,
    )
    verantwoordelijke_organisatie = RSINField(
        help_text=
        "Het RSIN van de Niet-natuurlijk persoon zijnde de organisatie "
        "die eindverantwoordelijk is voor de behandeling van de "
        "zaak. Dit moet een geldig RSIN zijn van 9 nummers en voldoen aan "
        "https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef")

    startdatum = models.DateField(
        help_text="De datum waarop met de uitvoering van de zaak is gestart")
    einddatum = models.DateField(
        blank=True,
        null=True,
        help_text="De datum waarop de uitvoering van de zaak afgerond is.",
    )
    einddatum_gepland = models.DateField(
        blank=True,
        null=True,
        help_text="De datum waarop volgens de planning verwacht wordt dat de "
        "zaak afgerond wordt.",
    )
    uiterlijke_einddatum_afdoening = models.DateField(
        blank=True,
        null=True,
        help_text="De laatste datum waarop volgens wet- en regelgeving de zaak "
        "afgerond dient te zijn.",
    )
    publicatiedatum = models.DateField(
        _("publicatiedatum"),
        null=True,
        blank=True,
        help_text=_(
            "Datum waarop (het starten van) de zaak gepubliceerd is of wordt."
        ),
    )

    producten_of_diensten = ArrayField(
        models.URLField(_("URL naar product/dienst"), max_length=1000),
        default=list,
        help_text=_(
            "De producten en/of diensten die door de zaak worden voortgebracht. "
            "Dit zijn URLs naar de resources zoals die door de producten- "
            "en dienstencatalogus-API wordt ontsloten. "
            "De producten/diensten moeten bij het zaaktype vermeld zijn."),
        blank=True,
    )

    communicatiekanaal = models.URLField(
        _("communicatiekanaal"),
        blank=True,
        max_length=1000,
        help_text=
        _("Het medium waarlangs de aanleiding om een zaak te starten is ontvangen. "
          "URL naar een communicatiekanaal in de VNG-Referentielijst van communicatiekanalen."
          ),
    )

    vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField(
        _("vertrouwlijkheidaanduiding"),
        help_text=
        _("Aanduiding van de mate waarin het zaakdossier van de ZAAK voor de openbaarheid bestemd is."
          ),
    )

    betalingsindicatie = models.CharField(
        _("betalingsindicatie"),
        max_length=20,
        blank=True,
        choices=BetalingsIndicatie.choices,
        help_text=_("Indicatie of de, met behandeling van de zaak gemoeide, "
                    "kosten betaald zijn door de desbetreffende betrokkene."),
    )
    laatste_betaaldatum = models.DateTimeField(
        _("laatste betaaldatum"),
        blank=True,
        null=True,
        help_text=_(
            "De datum waarop de meest recente betaling is verwerkt "
            "van kosten die gemoeid zijn met behandeling van de zaak."),
    )

    zaakgeometrie = GeometryField(
        blank=True,
        null=True,
        help_text="Punt, lijn of (multi-)vlak geometrie-informatie.",
    )

    verlenging_reden = models.CharField(
        _("reden verlenging"),
        max_length=200,
        blank=True,
        help_text=
        _("Omschrijving van de reden voor het verlengen van de behandeling van de zaak."
          ),
    )
    verlenging_duur = DaysDurationField(
        _("duur verlenging"),
        blank=True,
        null=True,
        help_text=_(
            "Het aantal werkbare dagen waarmee de doorlooptijd van de "
            "behandeling van de ZAAK is verlengd (of verkort) ten opzichte "
            "van de eerder gecommuniceerde doorlooptijd."),
    )
    verlenging = GegevensGroepType({
        "reden": verlenging_reden,
        "duur": verlenging_duur
    })

    opschorting_indicatie = models.BooleanField(
        _("indicatie opschorting"),
        default=False,
        blank=True,
        help_text=_(
            "Aanduiding of de behandeling van de ZAAK tijdelijk is opgeschort."
        ),
    )
    opschorting_reden = models.CharField(
        _("reden opschorting"),
        max_length=200,
        blank=True,
        help_text=
        _("Omschrijving van de reden voor het opschorten van de behandeling van de zaak."
          ),
    )
    opschorting = GegevensGroepType({
        "indicatie": opschorting_indicatie,
        "reden": opschorting_reden
    })

    selectielijstklasse = models.URLField(
        _("selectielijstklasse"),
        blank=True,
        max_length=1000,
        help_text=
        _("URL-referentie naar de categorie in de gehanteerde 'Selectielijst Archiefbescheiden' die, gezien "
          "het zaaktype en het resultaattype van de zaak, bepalend is voor het archiefregime van de zaak."
          ),
    )

    # Archiving
    archiefnominatie = models.CharField(
        _("archiefnominatie"),
        max_length=40,
        null=True,
        blank=True,
        choices=Archiefnominatie.choices,
        help_text=
        _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden."
          ),
    )
    archiefstatus = models.CharField(
        _("archiefstatus"),
        max_length=40,
        choices=Archiefstatus.choices,
        default=Archiefstatus.nog_te_archiveren,
        help_text=
        _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden."
          ),
    )
    archiefactiedatum = models.DateField(
        _("archiefactiedatum"),
        null=True,
        blank=True,
        help_text=
        _("De datum waarop het gearchiveerde zaakdossier vernietigd moet worden dan wel overgebracht moet "
          "worden naar een archiefbewaarplaats. Wordt automatisch berekend bij het aanmaken of wijzigen van "
          "een RESULTAAT aan deze ZAAK indien nog leeg."),
    )

    objects = ZaakQuerySet.as_manager()

    class Meta:
        verbose_name = "zaak"
        verbose_name_plural = "zaken"
        unique_together = ("bronorganisatie", "identificatie")

    def __str__(self):
        return self.identificatie

    def save(self, *args, **kwargs):
        if not self.identificatie:
            self.identificatie = generate_unique_identification(
                self, "registratiedatum")

        if (self.betalingsindicatie == BetalingsIndicatie.nvt
                and self.laatste_betaaldatum):
            self.laatste_betaaldatum = None

        super().save(*args, **kwargs)

    @property
    def current_status_uuid(self):
        status = self.status_set.order_by("-datum_status_gezet").first()
        return status.uuid if status else None

    def unique_representation(self):
        return f"{self.bronorganisatie} - {self.identificatie}"
Exemplo n.º 5
0
class InformatieObject(models.Model):
    identificatie = models.CharField(
        max_length=40,
        validators=[alphanumeric_excluding_diacritic],
        blank=True,
        default="",
        help_text="Een binnen een gegeven context ondubbelzinnige referentie "
        "naar het INFORMATIEOBJECT.",
        db_index=True,
    )
    bronorganisatie = RSINField(
        max_length=9,
        help_text="Het RSIN van de Niet-natuurlijk persoon zijnde de "
        "organisatie die het informatieobject heeft gecreëerd of "
        "heeft ontvangen en als eerste in een samenwerkingsketen "
        "heeft vastgelegd.",
        db_index=True,
    )
    # TODO: change to read-only?
    creatiedatum = models.DateField(
        help_text="Een datum of een gebeurtenis in de levenscyclus van het "
        "INFORMATIEOBJECT.")
    titel = models.CharField(
        max_length=200,
        help_text="De naam waaronder het INFORMATIEOBJECT formeel bekend is.",
    )
    vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField(
        blank=True,
        help_text="Aanduiding van de mate waarin het INFORMATIEOBJECT voor de "
        "openbaarheid bestemd is.",
    )
    auteur = models.CharField(
        max_length=200,
        help_text="De persoon of organisatie die in de eerste plaats "
        "verantwoordelijk is voor het creëren van de inhoud van het "
        "INFORMATIEOBJECT.",
    )
    status = models.CharField(
        _("status"),
        max_length=20,
        blank=True,
        choices=Statussen.choices,
        help_text=
        _("Aanduiding van de stand van zaken van een INFORMATIEOBJECT. "
          "De waarden 'in bewerking' en 'ter vaststelling' komen niet "
          "voor als het attribuut `ontvangstdatum` van een waarde is voorzien. "
          "Wijziging van de Status in 'gearchiveerd' impliceert dat "
          "het informatieobject een duurzaam, niet-wijzigbaar Formaat dient te hebben."
          ),
    )
    beschrijving = models.TextField(
        max_length=1000,
        blank=True,
        help_text="Een generieke beschrijving van de inhoud van het "
        "INFORMATIEOBJECT.",
    )
    ontvangstdatum = models.DateField(
        _("ontvangstdatum"),
        null=True,
        blank=True,
        help_text=_(
            "De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht "
            "te registreren voor INFORMATIEOBJECTen die van buiten de "
            "zaakbehandelende organisatie(s) ontvangen zijn. "
            "Ontvangst en verzending is voorbehouden aan documenten die "
            "van of naar andere personen ontvangen of verzonden zijn "
            "waarbij die personen niet deel uit maken van de behandeling "
            "van de zaak waarin het document een rol speelt."),
    )
    verzenddatum = models.DateField(
        _("verzenddatum"),
        null=True,
        blank=True,
        help_text=_(
            "De datum waarop het INFORMATIEOBJECT verzonden is, zoals "
            "deze op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel "
            "inkomende als uitgaande INFORMATIEOBJECTen. Eenzelfde "
            "informatieobject kan niet tegelijk inkomend en uitgaand zijn. "
            "Ontvangst en verzending is voorbehouden aan documenten die "
            "van of naar andere personen ontvangen of verzonden zijn "
            "waarbij die personen niet deel uit maken van de behandeling "
            "van de zaak waarin het document een rol speelt."),
    )
    indicatie_gebruiksrecht = models.NullBooleanField(
        _("indicatie gebruiksrecht"),
        blank=True,
        default=None,
        help_text=_(
            "Indicatie of er beperkingen gelden aangaande het gebruik van "
            "het informatieobject anders dan raadpleging. Dit veld mag "
            "`null` zijn om aan te geven dat de indicatie nog niet bekend is. "
            "Als de indicatie gezet is, dan kan je de gebruiksrechten die "
            "van toepassing zijn raadplegen via de GEBRUIKSRECHTen resource."),
    )

    # signing in some sort of way
    # TODO: De attribuutsoort mag niet van een waarde zijn voorzien
    # als de attribuutsoort ?Status? de waarde ?in bewerking?
    # of ?ter vaststelling? heeft.
    ondertekening_soort = models.CharField(
        _("ondertekeningsoort"),
        max_length=10,
        blank=True,
        choices=OndertekeningSoorten.choices,
        help_text=_(
            "Aanduiding van de wijze van ondertekening van het INFORMATIEOBJECT"
        ),
    )
    ondertekening_datum = models.DateField(
        _("ondertekeningdatum"),
        blank=True,
        null=True,
        help_text=
        _("De datum waarop de ondertekening van het INFORMATIEOBJECT heeft plaatsgevonden."
          ),
    )

    _informatieobjecttype_url = models.URLField(
        _("extern informatieobjecttype"),
        blank=True,
        max_length=1000,
        help_text=
        _("URL-referentie naar extern INFORMATIEOBJECTTYPE (in een andere Catalogi API)."
          ),
    )
    _informatieobjecttype = models.ForeignKey(
        "catalogi.InformatieObjectType",
        on_delete=models.CASCADE,
        help_text=_(
            "URL-referentie naar het INFORMATIEOBJECTTYPE (in de Catalogi API)."
        ),
        null=True,
        blank=True,
    )
    informatieobjecttype = FkOrURLField(
        fk_field="_informatieobjecttype",
        url_field="_informatieobjecttype_url",
        help_text=
        "URL-referentie naar het INFORMATIEOBJECTTYPE (in de Catalogi API).",
    )

    objects = InformatieobjectQuerySet.as_manager()

    IDENTIFICATIE_PREFIX = "DOCUMENT"

    class Meta:
        verbose_name = "informatieobject"
        verbose_name_plural = "informatieobject"
        unique_together = ("bronorganisatie", "identificatie")
        abstract = True

    def __str__(self) -> str:
        return self.identificatie

    def save(self, *args, **kwargs):
        if not self.identificatie:
            self.identificatie = generate_unique_identification(
                self, "creatiedatum")
        super().save(*args, **kwargs)

    def clean(self):
        super().clean()
        validate_status(status=self.status,
                        ontvangstdatum=self.ontvangstdatum,
                        instance=self)

    ondertekening = GegevensGroepType({
        "soort": ondertekening_soort,
        "datum": ondertekening_datum
    })

    def unique_representation(self):
        return f"{self.bronorganisatie} - {self.identificatie}"
Exemplo n.º 6
0
class EnkelvoudigInformatieObject(AuditTrailMixin, APIMixin, InformatieObject,
                                  CMISClientMixin):
    """
    Stores the content of a specific version of an
    EnkelvoudigInformatieObjectCanonical

    The model is split into two parts to support versioning, now a single
    `EnkelvoudigInformatieObjectCanonical` can exist with multiple different
    `EnkelvoudigInformatieObject`s, which can be retrieved by filtering
    """

    canonical = models.ForeignKey(EnkelvoudigInformatieObjectCanonical,
                                  on_delete=models.CASCADE)
    uuid = models.UUIDField(default=_uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")

    # NOTE: Don't validate but rely on externally maintened list of Media Types
    # and that consumers know what they're doing. This prevents updating the
    # API specification on every Media Type that is added.
    formaat = models.CharField(
        max_length=255,
        blank=True,
        help_text='Het "Media Type" (voorheen "MIME type") voor de wijze waarop'
        "de inhoud van het INFORMATIEOBJECT is vastgelegd in een "
        "computerbestand. Voorbeeld: `application/msword`. Zie: "
        "https://www.iana.org/assignments/media-types/media-types.xhtml",
    )
    taal = models.CharField(
        max_length=3,
        help_text="Een ISO 639-2/B taalcode waarin de inhoud van het "
        "INFORMATIEOBJECT is vastgelegd. Voorbeeld: `nld`. Zie: "
        "https://www.iso.org/standard/4767.html",
    )

    bestandsnaam = models.CharField(
        _("bestandsnaam"),
        max_length=255,
        blank=True,
        help_text=_("De naam van het fysieke bestand waarin de inhoud van het "
                    "informatieobject is vastgelegd, inclusief extensie."),
    )

    inhoud = PrivateMediaFileField(upload_to="uploads/%Y/%m/",
                                   storage=private_media_storage_cmis)
    # inhoud = models.FileField(upload_to='uploads/%Y/%m/')
    link = models.URLField(
        max_length=200,
        blank=True,
        help_text="De URL waarmee de inhoud van het INFORMATIEOBJECT op te "
        "vragen is.",
    )

    # these fields should not be modified directly, but go through the `integriteit` descriptor
    integriteit_algoritme = models.CharField(
        _("integriteit algoritme"),
        max_length=20,
        choices=ChecksumAlgoritmes.choices,
        blank=True,
        help_text=_(
            "Aanduiding van algoritme, gebruikt om de checksum te maken."),
    )
    integriteit_waarde = models.CharField(
        _("integriteit waarde"),
        max_length=128,
        blank=True,
        help_text=_("De waarde van de checksum."),
    )
    integriteit_datum = models.DateField(
        _("integriteit datum"),
        null=True,
        blank=True,
        help_text=_("Datum waarop de checksum is gemaakt."),
    )

    integriteit = GegevensGroepType({
        "algoritme": integriteit_algoritme,
        "waarde": integriteit_waarde,
        "datum": integriteit_datum,
    })

    versie = models.PositiveIntegerField(
        default=1,
        help_text=_(
            "Het (automatische) versienummer van het INFORMATIEOBJECT. Deze begint bij 1 als het "
            "INFORMATIEOBJECT aangemaakt wordt."),
    )
    begin_registratie = models.DateTimeField(
        auto_now=True,
        help_text=_(
            "Een datumtijd in ISO8601 formaat waarop deze versie van het INFORMATIEOBJECT is aangemaakt of "
            "gewijzigd."),
        db_index=True,
    )

    # When dealing with remote EIO, there is no pk or canonical instance to derive
    # the lock status from. The getters and setters then use this private attribute.
    _locked = False
    objects = AdapterManager()

    class Meta:
        # No bronorganisatie/identificatie unique-together constraint, otherwise new versions of a document cannot be
        # saved to the database.
        unique_together = [("uuid", "versie")]
        verbose_name = _("Document")
        verbose_name_plural = _("Documenten")
        indexes = [models.Index(fields=["canonical", "-versie"])]
        ordering = ["canonical", "-versie"]

    def __init__(self, *args, **kwargs):
        kwargs.pop("_request",
                   None)  # see hacky workaround in EIOSerializer.create
        super().__init__(*args, **kwargs)

    @property
    def locked(self) -> bool:
        if self.pk or self.canonical is not None:
            return bool(self.canonical.lock)
        return self._locked

    @locked.setter
    def locked(self, value: bool) -> None:
        # this should only be called for remote objects, as other objects derive the
        # lock status from the canonical object
        assert self.canonical is None, "Setter should only be called for remote objects"
        self._locked = value

    def save(self, *args, **kwargs) -> None:
        if not settings.CMIS_ENABLED:
            return super().save(*args, **kwargs)
        else:
            model_data = model_to_dict(self)
            # If the document doesn't exist, create it, otherwise update it
            try:
                # sanity - check - assert the doc exists in CMIS backend
                self.cmis_client.get_document(drc_uuid=self.uuid)
                # update the instance state to the storage backend
                EnkelvoudigInformatieObject.objects.filter(
                    uuid=self.uuid).update(**model_data)
                # Needed or the current django object will contain the version number and the download url
                # from before the update and this data is sent back in the response
                modified_document = EnkelvoudigInformatieObject.objects.get(
                    uuid=self.uuid)
                self.versie = modified_document.versie
                self.inhoud = modified_document.inhoud
            except exceptions.DocumentDoesNotExistError:
                EnkelvoudigInformatieObject.objects.create(**model_data)

    def delete(self, *args, **kwargs):
        if not settings.CMIS_ENABLED:
            return super().delete(*args, **kwargs)
        else:
            if self.has_gebruiksrechten():
                eio_instance_url = self.get_url()
                gebruiksrechten = Gebruiksrechten.objects.filter(
                    informatieobject=eio_instance_url)
                for gebruiksrechten_doc in gebruiksrechten:
                    gebruiksrechten_doc.delete()
            self.cmis_client.delete_document(self.uuid)

    def destroy(self):
        if settings.CMIS_ENABLED:
            self.delete()
        else:
            self.canonical.delete()

    def has_references(self):
        if settings.CMIS_ENABLED:
            if (BesluitInformatieObject.objects.filter(
                    _informatieobject=self.canonical).exists()
                    or ZaakInformatieObject.objects.filter(
                        _informatieobject=self.canonical).exists()):
                return True
            else:
                return False
        else:
            if (self.canonical.besluitinformatieobject_set.exists()
                    or self.canonical.zaakinformatieobject_set.exists()):
                return True
            else:
                return False

    def get_url(self):
        eio_path = reverse(
            "enkelvoudiginformatieobject-detail",
            kwargs={
                "version": "1",
                "uuid": self.uuid
            },
        )
        return make_absolute_uri(eio_path)

    def has_gebruiksrechten(self):
        if settings.CMIS_ENABLED:
            eio_url = self.get_url()
            return Gebruiksrechten.objects.filter(
                informatieobject=eio_url).exists()
        else:
            return self.canonical.gebruiksrechten_set.exists()
Exemplo n.º 7
0
class ResultaatType(models.Model):
    """
    Het betreft de indeling of groepering van resultaten van zaken van hetzelfde
    ZAAKTYPE naar hun aard, zoals 'verleend', 'geweigerd', 'verwerkt', et cetera.

    Toelichting objecttype
    Elke zaak heeft een resultaat. In een aantal gevallen valt dit resultaat samen met een besluit:
    ‘Evenementenvergunning verleend’, ‘Energiesubsidie geweigerd’, et cetera. Het komt echter
    ook voor dat zaken worden afgehandeld zonder dat er een besluit wordt genomen. Dit is
    bijvoorbeeld het geval bij aangiften (geboorte, verhuizing), meldingen (openbare ruimte), maar
    ook bij het intrekken van een aanvraag. Het resultaat van een zaak is van groot belang voor de
    archivering: het resultaattype bepaalt mede of de zaak en het bijbehorende dossier moeten
    worden vernietigd (na enige termijn) of blijvend bewaard moeten worden (en na enige termijn
    ‘overgebracht’ worden naar een archiefbewaarplaats). Met RESULTAATTYPE worden de
    mogelijke resultaten benoemd bij het desbetreffende zaaktype. Daarmee is het archiefregime
    bepaald voor het gehele zaakdossier: alle informatie over en documenten bij de zaken van het
    ZAAKTYPE.
    In uitzonderingsgevallen kan er sprake van zijn dat documenten van een bepaald
    INFORMATIEOBJECTTYPE in zaakdossiers bij zaken van het ZAAKTYPE een afwijkend
    archiefregime hebben ten opzichte van het zaakdossier. Privacy-gevoeligheid kan er reden
    voor zijn om documenten van een ZAAKINFORMATIEOBJECTTYPE eerder te vernietigen dan het
    zaakdossier als geheel. Specifieke wetgeving, zoals die voor de BAG, leidt er daarentegen toe
    dat een Omgevingsvergunning (activiteit bouwen) ten eeuwige dage bewaard moet blijven
    terwijl het zaakdossier na 20 jaar vernietigd dient te worden. De relatiesoort ‘RESULTAATTYPE
    bepaalt afwijkend archiefregime van ZAAK-INFORMATIEOBJECT-TYPE’ geeft de mogelijkheid
    deze uitzonderingsgevallen te documenteren.
    """

    uuid = models.UUIDField(unique=True,
                            default=uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")
    zaaktype = models.ForeignKey(
        "catalogi.ZaakType",
        verbose_name=_("is relevant voor"),
        on_delete=models.CASCADE,
        related_name="resultaattypen",
        help_text=
        _("URL-referentie naar het ZAAKTYPE van ZAAKen waarin resultaten van dit RESULTAATTYPE bereikt kunnen worden."
          ),
    )

    # core data - used by ZRC to calculate archival-related dates
    omschrijving = models.CharField(
        _("omschrijving"),
        max_length=20,
        help_text=_(
            "Omschrijving van de aard van resultaten van het RESULTAATTYPE."),
    )
    resultaattypeomschrijving = models.URLField(
        _("resultaattypeomschrijving"),
        max_length=1000,
        help_text=
        _("Algemeen gehanteerde omschrijving van de aard van resultaten van het RESULTAATTYPE. "
          "Dit moet een URL-referentie zijn naar de referenlijst van generieke "
          "resultaattypeomschrijvingen. Im ImZTC heet dit 'omschrijving generiek'"
          ),
    )
    omschrijving_generiek = models.CharField(
        _("omschrijving generiek"),
        max_length=20,
        blank=True,
        editable=False,
        help_text=
        _("Gecachete tekstuele waarde van de generieke resultaattypeomschrijving."
          ),
    )

    # TODO: validate that this matches the Zaaktype.procestype
    selectielijstklasse = models.URLField(
        _("selectielijstklasse"),
        max_length=1000,
        help_text=
        _("URL-referentie naar de, voor het archiefregime bij het RESULTAATTYPE relevante, "
          "categorie in de Selectielijst Archiefbescheiden (RESULTAAT in de Selectielijst API) "
          "van de voor het ZAAKTYPE verantwoordelijke overheidsorganisatie."),
    )

    # derived fields from selectielijstklasse
    archiefnominatie = models.CharField(
        _("archiefnominatie"),
        default="",
        choices=Archiefnominatie.choices,
        max_length=20,
        blank=True,
        help_text=_(
            "Aanduiding die aangeeft of ZAAKen met een resultaat van "
            "dit RESULTAATTYPE blijvend moeten worden bewaard of "
            "(op termijn) moeten worden vernietigd. Indien niet expliciet "
            "opgegeven wordt dit gevuld vanuit de selectielijst."),
    )

    archiefactietermijn = RelativeDeltaField(
        _("archiefactietermijn"),
        null=True,
        blank=True,
        help_text=_(
            "De termijn, na het vervallen van het bedrjfsvoeringsbelang, "
            "waarna het zaakdossier (de ZAAK met alle bijbehorende "
            "INFORMATIEOBJECTen) van een ZAAK met een resultaat van dit "
            "RESULTAATTYPE vernietigd of overgebracht (naar een "
            "archiefbewaarplaats) moet worden. Voor te vernietigen "
            "dossiers betreft het de in die Selectielijst genoemde "
            "bewaartermjn. Voor blijvend te bewaren zaakdossiers "
            "betreft het de termijn vanaf afronding van de zaak tot "
            "overbrenging (de procestermijn is dan nihil)."),
    )

    # TODO: validate dependencies between fields
    brondatum_archiefprocedure_afleidingswijze = models.CharField(
        _("afleidingswijze brondatum"),
        max_length=20,
        choices=Afleidingswijze.choices,
        help_text=_("Wijze van bepalen van de brondatum."),
    )
    # TODO: this could/should be validated against a remote OAS 3.0!
    brondatum_archiefprocedure_datumkenmerk = models.CharField(
        _("datumkenmerk"),
        max_length=80,
        blank=True,
        help_text=_("Naam van de attribuutsoort van het procesobject dat "
                    "bepalend is voor het einde van de procestermijn."),
    )
    brondatum_archiefprocedure_einddatum_bekend = models.BooleanField(
        _("einddatum bekend"),
        default=False,
        help_text=_(
            "Indicatie dat de einddatum van het procesobject gedurende "
            "de uitvoering van de zaak bekend moet worden. Indien deze "
            "nog niet bekend is en deze waarde staat op `true`, dan "
            "kan de zaak (nog) niet afgesloten worden."),
    )
    brondatum_archiefprocedure_objecttype = models.CharField(
        _("objecttype"),
        max_length=80,
        blank=True,
        choices=ZaakobjectTypes.choices,
        help_text=
        _("Het soort object in de registratie dat het procesobject representeert."
          ),
    )
    # TODO: standardize content so that consumers understand this?
    brondatum_archiefprocedure_registratie = models.CharField(
        _("registratie"),
        max_length=80,
        blank=True,
        help_text=
        _("De naam van de registratie waarvan het procesobject deel uit maakt."
          ),
    )
    brondatum_archiefprocedure_procestermijn = RelativeDeltaField(
        _("procestermijn"),
        null=True,
        blank=True,
        help_text=_(
            "De periode dat het zaakdossier na afronding van de zaak "
            "actief gebruikt en/of geraadpleegd wordt ter ondersteuning "
            "van de taakuitoefening van de organisatie. Enkel relevant "
            "indien de afleidingswijze 'termijn' is."),
    )

    brondatum_archiefprocedure = GegevensGroepType(
        {
            "afleidingswijze": brondatum_archiefprocedure_afleidingswijze,
            "datumkenmerk": brondatum_archiefprocedure_datumkenmerk,
            "einddatum_bekend": brondatum_archiefprocedure_einddatum_bekend,
            "objecttype": brondatum_archiefprocedure_objecttype,
            "registratie": brondatum_archiefprocedure_registratie,
            "procestermijn": brondatum_archiefprocedure_procestermijn,
        },
        optional=("datumkenmerk", "einddatum_bekend", "objecttype",
                  "registratie"),
        none_for_empty=True,
    )

    # meta-information - this is mostly informative
    toelichting = models.TextField(
        _("toelichting"),
        blank=True,
        help_text=_(
            "Een toelichting op dit RESULTAATTYPE en het belang hiervan "
            "voor ZAAKen waarin een resultaat van dit RESULTAATTYPE wordt geselecteerd."
        ),
    )

    # 'old' fields, not actively used at the moment

    bepaalt_afwijkend_archiefregime_van = models.ManyToManyField(
        "catalogi.ZaakInformatieObjectType",
        verbose_name=_("bepaalt afwijkend archiefregime van"),
        through="catalogi.ZaakInformatieobjectTypeArchiefregime",
        blank=True,
        related_name="resultaattypes",
        help_text=
        _("Informatieobjecten van een ZAAKINFORMATIEOBJECTTYPE bij zaken van een ZAAKTYPE waarvan, op grond van "
          "resultaten van een RESULTAATTYPE bij dat ZAAKTYPE, de archiveringskenmerken afwijken van de "
          "archiveringskenmerken van het ZAAKTYPE."),
    )
    heeft_verplichte_zot = models.ManyToManyField(
        "catalogi.ZaakObjectType",
        verbose_name=_("heeft verplichte"),
        blank=True,
        help_text=_(
            "De ZAAKOBJECTTYPEn die verplicht gerelateerd moeten zijn aan ZAAKen van dit ZAAKTYPE voordat een "
            "resultaat van dit RESULTAATTYPE kan worden gezet."),
    )
    heeft_verplichte_ziot = models.ManyToManyField(
        "catalogi.ZaakInformatieObjectType",
        verbose_name=_("heeft verplichte zaakinformatie objecttype"),
        blank=True,
        related_name="resultaattypen",  # TODO needs a better related name
        help_text=
        _("De INFORMATIEOBJECTTYPEn die verplicht aanwezig moeten zijn in het zaakdossier van ZAAKen van dit "
          "ZAAKTYPE voordat een resultaat van dit RESULTAATTYPE kan worden gezet."
          ),
    )
    heeft_voor_brondatum_archiefprocedure_relevante = models.ForeignKey(
        "catalogi.Eigenschap",
        verbose_name=_("heeft voor brondatum archiefprocedure relevante"),
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        help_text=_(
            "De EIGENSCHAP die bepalend is voor het moment waarop de Archiefactietermijn start voor een ZAAK "
            "met een resultaat van dit RESULTAATTYPE."),
    )

    class Meta:
        unique_together = ("zaaktype", "omschrijving")
        verbose_name = _("resultaattype")
        verbose_name_plural = _("resultaattypen")

    def save(self, *args, **kwargs):
        """
        Save some derived fields into local object as a means of caching.
        """
        if not self.omschrijving_generiek and self.resultaattypeomschrijving:
            response = requests.get(self.resultaattypeomschrijving).json()
            self.omschrijving_generiek = response["omschrijving"]

        # derive the default archiefnominatie
        if not self.archiefnominatie and self.selectielijstklasse:
            selectielijstklasse = self.get_selectielijstklasse()
            self.archiefnominatie = selectielijstklasse["waardering"]

        if not self.archiefactietermijn and self.selectielijstklasse:
            selectielijstklasse = self.get_selectielijstklasse()
            self.archiefactietermijn = selectielijstklasse["bewaartermijn"]

        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.zaaktype} - {self.omschrijving}"

    def get_selectielijstklasse(self):
        if not hasattr(self, "_selectielijstklasse"):
            # selectielijstklasse should've been validated at this point by either
            # forms or serializers
            response = requests.get(self.selectielijstklasse)
            response.raise_for_status()
            self._selectielijstklasse = response.json()
        return self._selectielijstklasse
Exemplo n.º 8
0
class ZaakType(APIMixin, ConceptMixin, GeldigheidMixin, models.Model):
    """
    Het geheel van karakteristieke eigenschappen van zaken van eenzelfde soort

    Toelichting objecttype
    Het betreft de indeling of groepering van zaken naar hun aard, zoals “Behandelen aanvraag
    bouwvergunning” en “Behandelen aanvraag ontheffing parkeren”. Wat in een individueel geval
    een zaak is, waar die begint en waar die eindigt, wordt bekeken vanuit het perspectief van de
    initiator van de zaak (burger, bedrijf, medewerker, etc.). Het traject van (aan)vraag cq.
    aanleiding voor de zaak tot en met de levering van de producten/of diensten die een passend
    antwoord vormen op die aanleiding, bepaalt de omvang en afbakening van de zaak en
    daarmee van het zaaktype. Hiermee komt de afbakening van een zaaktype overeen met een
    bedrijfsproces: ‘van klant tot klant’. Dit betekent ondermeer dat onderdelen van
    bedrijfsprocessen geen zelfstandige zaken vormen. Het betekent ook dat een aanleiding die
    niet leidt tot de start van de uitvoering van een bedrijfsproces, niet leidt tot een zaak (deze
    wordt behandeld in het kader van een reeds lopende zaak).
    Zie ook de toelichtingen bij de relatiesoorten ‘ZAAKTYPE is deelzaaktype van ZAAKTYPE’ en
    ‘ZAAKTYPE heeft gerelateerd ZAAKTYPE’ voor wat betreft zaaktypen van deelzaken
    respectievelijk gerelateerde zaken.
    """

    uuid = models.UUIDField(unique=True,
                            default=uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")
    identificatie = models.CharField(
        _("identificatie"),
        max_length=50,
        blank=True,
        help_text=
        _("Unieke identificatie van het ZAAKTYPE binnen de CATALOGUS waarin het ZAAKTYPE voorkomt."
          ),
        validators=[alphanumeric_excluding_diacritic],
        db_index=True,
    )
    zaaktype_omschrijving = models.CharField(
        _("omschrijving"),
        max_length=80,
        help_text=_("Omschrijving van de aard van ZAAKen van het ZAAKTYPE."),
    )
    # TODO [KING]: waardenverzameling zoals vastgelegt in CATALOGUS, wat is deze waardeverzameling dan?
    zaaktype_omschrijving_generiek = models.CharField(
        _("omschrijving generiek"),
        max_length=80,
        blank=True,
        help_text=
        _("Algemeen gehanteerde omschrijving van de aard van ZAAKen van het ZAAKTYPE"
          ),
    )
    vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField(
        _("vertrouwelijkheidaanduiding"),
        help_text=
        _("Aanduiding van de mate waarin zaakdossiers van ZAAKen van "
          "dit ZAAKTYPE voor de openbaarheid bestemd zijn. Indien de zaak bij het "
          "aanmaken geen vertrouwelijkheidaanduiding krijgt, dan wordt deze waarde gezet."
          ),
    )
    doel = models.TextField(
        _("doel"),
        help_text=
        _("Een omschrijving van hetgeen beoogd is te bereiken met een zaak van dit zaaktype."
          ),
    )
    aanleiding = models.TextField(
        _("aanleiding"),
        help_text=_("Een omschrijving van de gebeurtenis die leidt tot het "
                    "starten van een ZAAK van dit ZAAKTYPE."),
    )
    toelichting = models.TextField(
        _("toelichting"),
        blank=True,
        help_text=_(
            "Een eventuele toelichting op dit zaaktype, zoals een beschrijving "
            "van het procesverloop op de hoofdlijnen."),
    )
    indicatie_intern_of_extern = models.CharField(
        _("indicatie intern of extern"),
        max_length=6,
        choices=InternExtern.choices,
        help_text=_(
            "Een aanduiding waarmee onderscheid wordt gemaakt tussen "
            "ZAAKTYPEn die Intern respectievelijk Extern geïnitieerd worden. "
            "Indien van beide sprake kan zijn, dan prevaleert de externe initiatie."
        ),
    )
    handeling_initiator = models.CharField(
        _("handeling initiator"),
        max_length=20,
        help_text=
        _("Werkwoord dat hoort bij de handeling die de initiator verricht bij dit zaaktype. "
          "Meestal 'aanvragen', 'indienen' of 'melden'. Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.handeling_initiator"
          ),
    )
    onderwerp = models.CharField(
        _("onderwerp"),
        max_length=80,
        help_text=
        _("Het onderwerp van ZAAKen van dit ZAAKTYPE. In veel gevallen nauw gerelateerd aan de product- of "
          "dienstnaam uit de Producten- en Dienstencatalogus (PDC). Bijvoorbeeld: 'Evenementenvergunning', "
          "'Geboorte', 'Klacht'. Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.onderwerp"
          ),
    )
    handeling_behandelaar = models.CharField(
        _("handeling behandelaar"),
        max_length=20,
        help_text=
        _("Werkwoord dat hoort bij de handeling die de behandelaar verricht bij het afdoen van ZAAKen van "
          "dit ZAAKTYPE. Meestal 'behandelen', 'uitvoeren', 'vaststellen' of 'onderhouden'. "
          "Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.handeling_behandelaar"
          ),
    )
    doorlooptijd_behandeling = DurationField(
        _("doorlooptijd behandeling"),
        help_text=_(
            "De periode waarbinnen volgens wet- en regelgeving een ZAAK van het ZAAKTYPE "
            "afgerond dient te zijn, in kalenderdagen."),
    )
    servicenorm_behandeling = DurationField(
        _("servicenorm behandeling"),
        blank=True,
        null=True,
        help_text=
        _("De periode waarbinnen verwacht wordt dat een ZAAK van het ZAAKTYPE afgerond wordt conform "
          "de geldende servicenormen van de zaakbehandelende organisatie(s)."),
    )
    opschorting_en_aanhouding_mogelijk = models.BooleanField(
        _("opschorting/aanhouding mogelijk"),
        help_text=_(
            "Aanduiding die aangeeft of ZAAKen van dit mogelijk ZAAKTYPE "
            "kunnen worden opgeschort en/of aangehouden."),
    )
    verlenging_mogelijk = models.BooleanField(
        _("verlenging mogelijk"),
        help_text=_(
            "Aanduiding die aangeeft of de Doorlooptijd behandeling van "
            "ZAAKen van dit ZAAKTYPE kan worden verlengd."),
    )
    verlengingstermijn = DurationField(
        _("verlengingstermijn"),
        blank=True,
        null=True,
        help_text=_(
            "De termijn (typisch een aantal dagen) waarmee de Doorlooptijd "
            "behandeling van ZAAKen van dit ZAAKTYPE kan worden verlengd. Mag "
            "alleen een waarde bevatten als verlenging mogelijk is."),
    )

    trefwoorden = ArrayField(
        models.CharField(_("trefwoord"), max_length=30),
        blank=True,
        default=list,
        help_text=
        _("Een trefwoord waarmee ZAAKen van het ZAAKTYPE kunnen worden gekarakteriseerd."
          ),
        db_index=True,
    )
    publicatie_indicatie = models.BooleanField(
        _("publicatie indicatie"),
        help_text=
        _("Aanduiding of (het starten van) een ZAAK dit ZAAKTYPE gepubliceerd moet worden."
          ),
    )
    publicatietekst = models.TextField(
        _("publicatietekst"),
        blank=True,
        help_text=_(
            "De generieke tekst van de publicatie van ZAAKen van dit ZAAKTYPE."
        ),
    )
    verantwoordingsrelatie = ArrayField(
        models.CharField(_("verantwoordingsrelatie"), max_length=40),
        blank=True,
        default=list,
        help_text=
        _("De relatie tussen ZAAKen van dit ZAAKTYPE en de beleidsmatige en/of financiële verantwoording."
          ),
    )
    versiedatum = models.DateField(
        _("versiedatum"),
        help_text=
        _("De datum waarop de (gewijzigde) kenmerken van het ZAAKTYPE geldig zijn geworden"
          ),
    )

    #
    # groepsattribuutsoorten
    #
    # TODO: should have shape validator, because the API resources need to conform
    producten_of_diensten = ArrayField(
        models.URLField(_("URL naar product/dienst"), max_length=1000),
        help_text=
        _("Het product of de dienst die door ZAAKen van dit ZAAKTYPE wordt voortgebracht."
          ),
        blank=True,
        default=list,
    )

    # TODO: validate shape & populate?
    selectielijst_procestype = models.URLField(
        _("selectielijst procestype"),
        blank=True,
        help_text=_(
            "URL-referentie naar een vanuit archiveringsoptiek onderkende groep processen met dezelfde "
            "kenmerken (PROCESTYPE in de Selectielijst API)."),
    )
    selectielijst_procestype_jaar = models.PositiveIntegerField(
        help_text=_("Het jaartal waartoe het procestype behoort."),
        blank=True,
        null=True,
    )
    referentieproces_naam = models.CharField(
        _("referentieprocesnaam"),
        max_length=80,
        help_text=_("De naam van het Referentieproces."),
    )
    referentieproces_link = models.URLField(
        _("referentieproceslink"),
        blank=True,
        help_text=_("De URL naar de beschrijving van het Referentieproces"),
    )
    referentieproces = GegevensGroepType(
        {
            "naam": referentieproces_naam,
            "link": referentieproces_link
        },
        optional=("link", ),
    )

    #
    # relaties
    #
    deelzaaktypen = models.ManyToManyField(
        "self",
        symmetrical=False,
        blank=True,
        related_name="hoofdzaaktypen",
        help_text=_(
            "De ZAAKTYPE(n) waaronder ZAAKen als deelzaak kunnen voorkomen bij "
            "ZAAKen van dit ZAAKTYPE."),
    )
    catalogus = models.ForeignKey(
        "catalogi.Catalogus",
        # verbose_name=_("maakt deel uit van"),
        on_delete=models.CASCADE,
        help_text=_(
            "URL-referentie naar de CATALOGUS waartoe dit ZAAKTYPE behoort."),
    )

    objects = SyncAutorisatieManager()

    IDENTIFICATIE_PREFIX = "ZAAKTYPE"

    class Meta:
        verbose_name = _("Zaaktype")
        verbose_name_plural = _("Zaaktypen")

    def __str__(self):
        return "{} ({})".format(
            self.zaaktype_omschrijving,
            "CONCEPT" if self.concept else self.versiedatum)

    @transaction.atomic
    def save(self, *args, **kwargs):
        # sync after creating new objects
        if not self.pk:
            transaction.on_commit(AutorisatieSpec.sync)

        if not self.identificatie:
            self.identificatie = generate_unique_identification(
                self, "versiedatum")

        if not self.verlenging_mogelijk:
            self.verlengingstermijn = None
        elif not self.verlengingstermijn:
            raise ValueError(
                "'verlengingstermijn' must be set if 'verlenging_mogelijk' is set."
            )

        super().save(*args, **kwargs)

    def clean(self):
        from ..utils import compare_relativedeltas, get_overlapping_zaaktypes

        super().clean()

        if self.verlenging_mogelijk and not self.verlengingstermijn:
            raise ValidationError(
                "'verlengingstermijn' moet ingevuld zijn als 'verlenging_mogelijk' gezet is."
            )

        # self.doorlooptijd_behandeling is empty if there are validation errors,
        # which would trigger a TypeError on the comparison
        if (self.doorlooptijd_behandeling
                and self.servicenorm_behandeling  # noqa
                and compare_relativedeltas(
                    self.servicenorm_behandeling,
                    self.doorlooptijd_behandeling)):  # noqa
            raise ValidationError(
                "'Servicenorm behandeling' periode mag niet langer zijn dan "
                "de periode van 'Doorlooptijd behandeling'.")

        if self.catalogus_id and self.datum_begin_geldigheid:
            query = get_overlapping_zaaktypes(
                self.catalogus,
                self.zaaktype_omschrijving,
                self.datum_begin_geldigheid,
                self.datum_einde_geldigheid,
                self,
            )

            # regel voor zaaktype omschrijving
            if query.exists():
                raise ValidationError(
                    "Zaaktype versies (dezelfde omschrijving) mogen geen "
                    "overlappende geldigheid hebben.")

    def get_absolute_api_url(self, request=None, **kwargs) -> str:
        kwargs["version"] = "1"
        return super().get_absolute_api_url(request=request, **kwargs)
Exemplo n.º 9
0
class ZaakType(APIMixin, ConceptMixin, GeldigheidMixin, models.Model):
    """
    Het geheel van karakteristieke eigenschappen van zaken van eenzelfde soort

    Toelichting objecttype
    Het betreft de indeling of groepering van zaken naar hun aard, zoals “Behandelen aanvraag
    bouwvergunning” en “Behandelen aanvraag ontheffing parkeren”. Wat in een individueel geval
    een zaak is, waar die begint en waar die eindigt, wordt bekeken vanuit het perspectief van de
    initiator van de zaak (burger, bedrijf, medewerker, etc.). Het traject van (aan)vraag cq.
    aanleiding voor de zaak tot en met de levering van de producten/of diensten die een passend
    antwoord vormen op die aanleiding, bepaalt de omvang en afbakening van de zaak en
    daarmee van het zaaktype. Hiermee komt de afbakening van een zaaktype overeen met een
    bedrijfsproces: ‘van klant tot klant’. Dit betekent ondermeer dat onderdelen van
    bedrijfsprocessen geen zelfstandige zaken vormen. Het betekent ook dat een aanleiding die
    niet leidt tot de start van de uitvoering van een bedrijfsproces, niet leidt tot een zaak (deze
    wordt behandeld in het kader van een reeds lopende zaak).
    Zie ook de toelichtingen bij de relatiesoorten ‘ZAAKTYPE is deelzaaktype van ZAAKTYPE’ en
    ‘ZAAKTYPE heeft gerelateerd ZAAKTYPE’ voor wat betreft zaaktypen van deelzaken
    respectievelijk gerelateerde zaken.
    """

    uuid = models.UUIDField(unique=True,
                            default=uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")
    zaaktype_identificatie = models.PositiveIntegerField(  # N5, integer with max_length of 5
        _("identificatie"),
        validators=[MaxValueValidator(99999)],
        help_text=
        _("Unieke identificatie van het ZAAKTYPE binnen de CATALOGUS waarin het ZAAKTYPE voorkomt."
          ),
    )
    zaaktype_omschrijving = models.CharField(
        _("omschrijving"),
        max_length=80,
        help_text=_("Omschrijving van de aard van ZAAKen van het ZAAKTYPE."),
    )
    # TODO [KING]: waardenverzameling zoals vastgelegt in CATALOGUS, wat is deze waardeverzameling dan?
    zaaktype_omschrijving_generiek = models.CharField(
        _("omschrijving generiek"),
        max_length=80,
        blank=True,
        help_text=
        _("Algemeen gehanteerde omschrijving van de aard van ZAAKen van het ZAAKTYPE"
          ),
    )
    vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField(
        _("vertrouwelijkheidaanduiding"),
        help_text=
        _("Aanduiding van de mate waarin zaakdossiers van ZAAKen van "
          "dit ZAAKTYPE voor de openbaarheid bestemd zijn. Indien de zaak bij het "
          "aanmaken geen vertrouwelijkheidaanduiding krijgt, dan wordt deze waarde gezet."
          ),
    )

    # TODO [KING]: waardenverzameling zie Zaaktypecatalogus, is dat de
    # catalogus die bij dit zaaktype hoort? Wat is de categorie dan?
    # see also: https://github.com/VNG-Realisatie/gemma-zaken/issues/695
    zaakcategorie = models.CharField(
        _("zaakcategorie"),
        max_length=40,
        blank=True,
        help_text=_("Typering van de aard van ZAAKen van het ZAAKTYPE."),
    )

    doel = models.TextField(
        _("doel"),
        help_text=
        _("Een omschrijving van hetgeen beoogd is te bereiken met een zaak van dit zaaktype."
          ),
    )
    aanleiding = models.TextField(
        _("aanleiding"),
        help_text=_("Een omschrijving van de gebeurtenis die leidt tot het "
                    "starten van een ZAAK van dit ZAAKTYPE."),
    )
    toelichting = models.TextField(
        _("toelichting"),
        blank=True,
        help_text=_(
            "Een eventuele toelichting op dit zaaktype, zoals een beschrijving "
            "van het procesverloop op de hoofdlijnen."),
    )
    indicatie_intern_of_extern = models.CharField(
        _("indicatie intern of extern"),
        max_length=6,
        choices=InternExtern.choices,
        help_text=_(
            "Een aanduiding waarmee onderscheid wordt gemaakt tussen "
            "ZAAKTYPEn die Intern respectievelijk Extern geïnitieerd worden. "
            "Indien van beide sprake kan zijn, dan prevaleert de externe initiatie."
        ),
    )
    handeling_initiator = models.CharField(
        _("handeling initiator"),
        max_length=20,
        help_text=
        _("Werkwoord dat hoort bij de handeling die de initiator verricht bij dit zaaktype. "
          "Meestal 'aanvragen', 'indienen' of 'melden'. Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.handeling_initiator"
          ),
    )
    onderwerp = models.CharField(
        _("onderwerp"),
        max_length=80,
        help_text=
        _("Het onderwerp van ZAAKen van dit ZAAKTYPE. In veel gevallen nauw gerelateerd aan de product- of "
          "dienstnaam uit de Producten- en Dienstencatalogus (PDC). Bijvoorbeeld: 'Evenementenvergunning', "
          "'Geboorte', 'Klacht'. Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.onderwerp"
          ),
    )
    handeling_behandelaar = models.CharField(
        _("handeling behandelaar"),
        max_length=20,
        help_text=
        _("Werkwoord dat hoort bij de handeling die de behandelaar verricht bij het afdoen van ZAAKen van "
          "dit ZAAKTYPE. Meestal 'behandelen', 'uitvoeren', 'vaststellen' of 'onderhouden'. "
          "Zie ook het IOB model op "
          "https://www.gemmaonline.nl/index.php/Imztc_2.1/doc/attribuutsoort/zaaktype.handeling_behandelaar"
          ),
    )
    doorlooptijd_behandeling = DaysDurationField(
        _("doorlooptijd behandeling"),
        help_text=_(
            "De periode waarbinnen volgens wet- en regelgeving een ZAAK van het ZAAKTYPE "
            "afgerond dient te zijn, in kalenderdagen."),
    )
    servicenorm_behandeling = DaysDurationField(
        _("servicenorm behandeling"),
        blank=True,
        null=True,
        help_text=
        _("De periode waarbinnen verwacht wordt dat een ZAAK van het ZAAKTYPE afgerond wordt conform "
          "de geldende servicenormen van de zaakbehandelende organisatie(s)."),
    )
    opschorting_en_aanhouding_mogelijk = models.BooleanField(
        _("opschorting/aanhouding mogelijk"),
        help_text=_(
            "Aanduiding die aangeeft of ZAAKen van dit mogelijk ZAAKTYPE "
            "kunnen worden opgeschort en/of aangehouden."),
    )
    verlenging_mogelijk = models.BooleanField(
        _("verlenging mogelijk"),
        help_text=_(
            "Aanduiding die aangeeft of de Doorlooptijd behandeling van "
            "ZAAKen van dit ZAAKTYPE kan worden verlengd."),
    )
    verlengingstermijn = DaysDurationField(
        _("verlengingstermijn"),
        blank=True,
        null=True,
        help_text=_(
            "De termijn in dagen waarmee de Doorlooptijd behandeling van "
            "ZAAKen van dit ZAAKTYPE kan worden verlengd. Mag alleen een waarde "
            "bevatten als verlenging mogelijk is."),
    )

    trefwoorden = ArrayField(
        models.CharField(_("trefwoord"), max_length=30),
        blank=True,
        default=list,
        help_text=
        _("Een trefwoord waarmee ZAAKen van het ZAAKTYPE kunnen worden gekarakteriseerd."
          ),
    )
    # TODO [KING]: ?? waardenverzameling: De classificatiecode in het gehanteerde
    # archiveringsclassificatiestelsel, gevolgd door een spatie en –
    # tussen haakjes - de gebruikelijke afkorting van de naam van het gehanteerde classificatiestelsel.
    archiefclassificatiecode = models.CharField(
        _("archiefclassificatiecode"),
        max_length=20,
        blank=True,
        null=True,
        help_text=_(
            "De systematische identificatie van zaakdossiers van dit ZAAKTYPE overeenkomstig logisch gestructureerde "
            "conventies, methoden en procedureregels."),
    )
    # TODO [KING]: waardenverzameling heeft de volgende regel, momenteel valideren we hier niets,
    # maar wellicht kan het wel: Indien het om een zaaktype in een catalogus voor een specifieke organisatie gaat,
    # dan de naam van een Organisatorische eenheid of Medewerker overeenkomstig het RGBZ.
    # Hoe weten we of een catalogus van een specifieke organisatie is? Als we Catalogus.contactpersoon_beheer_naam
    # gebruiken dan is dit veld overbodig want dan gebruiken we gewoon
    # ZaakType.catalogus.contactpersoon_beheer_naam
    verantwoordelijke = models.CharField(
        _("verantwoordelijke"),
        max_length=50,
        help_text=_(
            "De (soort) organisatorische eenheid of (functie van) medewerker die verantwoordelijk is voor "
            "de uitvoering van zaken van het ZAAKTYPE."),
    )
    publicatie_indicatie = models.BooleanField(
        _("publicatie indicatie"),
        help_text=
        _("Aanduiding of (het starten van) een ZAAK dit ZAAKTYPE gepubliceerd moet worden."
          ),
    )
    publicatietekst = models.TextField(
        _("publicatietekst"),
        blank=True,
        help_text=_(
            "De generieke tekst van de publicatie van ZAAKen van dit ZAAKTYPE."
        ),
    )
    verantwoordingsrelatie = ArrayField(
        models.CharField(_("verantwoordingsrelatie"), max_length=40),
        blank=True,
        default=list,
        help_text=
        _("De relatie tussen ZAAKen van dit ZAAKTYPE en de beleidsmatige en/of financiële verantwoording."
          ),
    )
    versiedatum = models.DateField(
        _("versiedatum"),
        help_text=
        _("De datum waarop de (gewijzigde) kenmerken van het ZAAKTYPE geldig zijn geworden"
          ),
    )

    #
    # groepsattribuutsoorten
    #
    # TODO: should have shape validator, because the API resources need to conform
    producten_of_diensten = ArrayField(
        models.URLField(_("URL naar product/dienst"), max_length=1000),
        help_text=
        _("Het product of de dienst die door ZAAKen van dit ZAAKTYPE wordt voortgebracht."
          ),
    )

    # TODO: validate shape & populate?
    selectielijst_procestype = models.URLField(
        _("selectielijst procestype"),
        blank=True,
        help_text=_(
            "URL-referentie naar een vanuit archiveringsoptiek onderkende groep processen met dezelfde "
            "kenmerken (PROCESTYPE in de Selectielijst API)."),
    )

    formulier = models.ManyToManyField(
        "catalogi.Formulier",
        verbose_name=_("formulier"),
        blank=True,
        help_text=_(
            "Formulier Het formulier dat ZAAKen van dit ZAAKTYPE initieert."),
    )

    referentieproces_naam = models.CharField(
        _("referentieprocesnaam"),
        max_length=80,
        help_text=_("De naam van het Referentieproces."),
    )
    referentieproces_link = models.URLField(
        _("referentieproceslink"),
        blank=True,
        help_text=_("De URL naar de beschrijving van het Referentieproces"),
    )
    referentieproces = GegevensGroepType(
        {
            "naam": referentieproces_naam,
            "link": referentieproces_link
        },
        optional=("link", ),
    )

    broncatalogus = models.ForeignKey(
        "catalogi.BronCatalogus",
        verbose_name=_("broncatalogus"),
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        help_text=_("De CATALOGUS waaraan het ZAAKTYPE is ontleend."),
    )
    bronzaaktype = models.ForeignKey(
        "catalogi.BronZaakType",
        verbose_name=("bronzaaktype"),
        blank=True,
        null=True,
        on_delete=models.CASCADE,
        help_text=_(
            "Het zaaktype binnen de CATALOGUS waaraan dit ZAAKTYPE is ontleend."
        ),
    )

    #
    # relaties
    #
    is_deelzaaktype_van = models.ManyToManyField(
        "catalogi.ZaakType",
        verbose_name=_("is deelzaaktype van"),
        blank=True,
        related_name="zaak_typen_is_deelzaaktype_van",
        help_text=
        _("De ZAAKTYPEn (van de hoofdzaken) waaronder ZAAKen van dit ZAAKTYPE als deelzaak kunnen voorkomen."
          ),
    )

    catalogus = models.ForeignKey(
        "catalogi.Catalogus",
        verbose_name=_("maakt deel uit van"),
        on_delete=models.CASCADE,
        help_text=_(
            "URL-referentie naar de CATALOGUS waartoe dit ZAAKTYPE behoort."),
    )

    class Meta:
        verbose_name = _("Zaaktype")
        verbose_name_plural = _("Zaaktypen")
        ordering = ("catalogus", "zaaktype_identificatie")

        filter_fields = (
            "catalogus",
            "publicatie_indicatie",
            "verlenging_mogelijk",
            "opschorting_en_aanhouding_mogelijk",
            "indicatie_intern_of_extern",
            "vertrouwelijkheidaanduiding",
        )
        ordering_fields = filter_fields
        search_fields = (
            "zaaktype_identificatie",
            "zaaktype_omschrijving",
            "zaaktype_omschrijving_generiek",
            "zaakcategorie",
            "doel",
            "aanleiding",
            "onderwerp",
            "toelichting",
        )

    def __str__(self):
        return "{} - {}".format(self.catalogus, self.zaaktype_identificatie)

    def save(self, *args, **kwargs):
        if not self.verlenging_mogelijk:
            self.verlengingstermijn = None
        elif not self.verlengingstermijn:
            raise ValueError(
                "'verlengingstermijn' must be set if 'verlenging_mogelijk' is set."
            )

        super().save(*args, **kwargs)

    def clean(self):
        super().clean()

        if self.verlenging_mogelijk and not self.verlengingstermijn:
            raise ValidationError(
                "'verlengingstermijn' moet ingevuld zijn als 'verlenging_mogelijk' gezet is."
            )

        # self.doorlooptijd_behandeling is empty if there are validation errors,
        # which would trigger a TypeError on the comparison
        if (self.doorlooptijd_behandeling
                and self.servicenorm_behandeling  # noqa
                and self.servicenorm_behandeling >
                self.doorlooptijd_behandeling):  # noqa
            raise ValidationError(
                "'Servicenorm behandeling' periode mag niet langer zijn dan "
                "de periode van 'Doorlooptijd behandeling'.")

        if self.catalogus_id:
            query = ZaakType.objects.filter(
                Q(catalogus=self.catalogus),
                Q(zaaktype_omschrijving=self.zaaktype_omschrijving),
                Q(datum_einde_geldigheid=None)
                | Q(datum_einde_geldigheid__gte=self.datum_begin_geldigheid
                    ),  # noqa
            )
            if self.datum_einde_geldigheid is not None:
                query = query.filter(
                    datum_begin_geldigheid__lte=self.datum_einde_geldigheid)

            # regel voor zaaktype omschrijving
            if query.exclude(pk=self.pk).exists():
                raise ValidationError(
                    "Zaaktype-omschrijving moet uniek zijn binnen de CATALOGUS."
                )

        self._clean_geldigheid(self)

    def get_absolute_api_url(self, request=None, **kwargs) -> str:
        kwargs["version"] = "1"
        return super().get_absolute_api_url(request=request, **kwargs)
Exemplo n.º 10
0
class EnkelvoudigInformatieObject(APIMixin, InformatieObject):
    """
    Stores the content of a specific version of an
    EnkelvoudigInformatieObjectCanonical

    The model is split into two parts to support versioning, now a single
    `EnkelvoudigInformatieObjectCanonical` can exist with multiple different
    `EnkelvoudigInformatieObject`s, which can be retrieved by filtering
    """

    canonical = models.ForeignKey(
        EnkelvoudigInformatieObjectCanonical, on_delete=models.CASCADE
    )
    uuid = models.UUIDField(
        default=_uuid.uuid4, help_text="Unieke resource identifier (UUID4)"
    )

    # NOTE: Don't validate but rely on externally maintened list of Media Types
    # and that consumers know what they're doing. This prevents updating the
    # API specification on every Media Type that is added.
    formaat = models.CharField(
        max_length=255,
        blank=True,
        help_text='Het "Media Type" (voorheen "MIME type") voor de wijze waarop'
        "de inhoud van het INFORMATIEOBJECT is vastgelegd in een "
        "computerbestand. Voorbeeld: `application/msword`. Zie: "
        "https://www.iana.org/assignments/media-types/media-types.xhtml",
    )
    taal = models.CharField(
        max_length=3,
        help_text="Een ISO 639-2/B taalcode waarin de inhoud van het "
        "INFORMATIEOBJECT is vastgelegd. Voorbeeld: `nld`. Zie: "
        "https://www.iso.org/standard/4767.html",
    )

    bestandsnaam = models.CharField(
        _("bestandsnaam"),
        max_length=255,
        blank=True,
        help_text=_(
            "De naam van het fysieke bestand waarin de inhoud van het "
            "informatieobject is vastgelegd, inclusief extensie."
        ),
    )
    inhoud = PrivateMediaFileField(upload_to="uploads/%Y/%m/")
    # inhoud = models.FileField(upload_to='uploads/%Y/%m/')
    link = models.URLField(
        max_length=200,
        blank=True,
        help_text="De URL waarmee de inhoud van het INFORMATIEOBJECT op te "
        "vragen is.",
    )

    # these fields should not be modified directly, but go through the `integriteit` descriptor
    integriteit_algoritme = models.CharField(
        _("integriteit algoritme"),
        max_length=20,
        choices=ChecksumAlgoritmes.choices,
        blank=True,
        help_text=_("Aanduiding van algoritme, gebruikt om de checksum te maken."),
    )
    integriteit_waarde = models.CharField(
        _("integriteit waarde"),
        max_length=128,
        blank=True,
        help_text=_("De waarde van de checksum."),
    )
    integriteit_datum = models.DateField(
        _("integriteit datum"),
        null=True,
        blank=True,
        help_text=_("Datum waarop de checksum is gemaakt."),
    )

    integriteit = GegevensGroepType(
        {
            "algoritme": integriteit_algoritme,
            "waarde": integriteit_waarde,
            "datum": integriteit_datum,
        }
    )

    versie = models.PositiveIntegerField(
        default=1,
        help_text=_(
            "Het (automatische) versienummer van het INFORMATIEOBJECT. Deze begint bij 1 als het "
            "INFORMATIEOBJECT aangemaakt wordt."
        ),
    )
    begin_registratie = models.DateTimeField(
        auto_now=True,
        help_text=_(
            "Een datumtijd in ISO8601 formaat waarop deze versie van het INFORMATIEOBJECT is aangemaakt of "
            "gewijzigd."
        ),
    )

    class Meta:
        unique_together = ("uuid", "versie")
Exemplo n.º 11
0
class Zaak(APIMixin, models.Model):
    """
    Modelleer de structuur van een ZAAK.

    Een samenhangende hoeveelheid werk met een welgedefinieerde aanleiding
    en een welgedefinieerd eindresultaat, waarvan kwaliteit en doorlooptijd
    bewaakt moeten worden.
    """
    uuid = models.UUIDField(unique=True,
                            default=uuid.uuid4,
                            help_text="Unieke resource identifier (UUID4)")

    # Relate 'is_deelzaak_van'
    # De relatie vanuit een zaak mag niet verwijzen naar
    # dezelfde zaak d.w.z. moet verwijzen naar een andere
    # zaak. Die andere zaak mag geen relatie ?is deelzaak
    # van? hebben (d.w.z. deelzaken van deelzaken worden
    # niet ondersteund).
    hoofdzaak = models.ForeignKey(
        'self',
        limit_choices_to={'hoofdzaak__isnull': True},
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='deelzaken',
        verbose_name='is deelzaak van',
        help_text=_("De verwijzing naar de ZAAK, waarom verzocht is door de "
                    "initiator daarvan, die behandeld wordt in twee of meer "
                    "separate ZAAKen waarvan de onderhavige ZAAK er één is."))

    identificatie = models.CharField(
        max_length=40,
        blank=True,
        help_text='De unieke identificatie van de ZAAK binnen de organisatie '
        'die verantwoordelijk is voor de behandeling van de ZAAK.',
        validators=[alphanumeric_excluding_diacritic])
    bronorganisatie = RSINField(
        help_text='Het RSIN van de Niet-natuurlijk persoon zijnde de '
        'organisatie die de zaak heeft gecreeerd. Dit moet een geldig '
        'RSIN zijn van 9 nummers en voldoen aan '
        'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef')
    omschrijving = models.CharField(
        max_length=80,
        blank=True,
        help_text='Een korte omschrijving van de zaak.')
    toelichting = models.TextField(max_length=1000,
                                   blank=True,
                                   help_text='Een toelichting op de zaak.')
    zaaktype = models.URLField(
        _("zaaktype"),
        help_text="URL naar het zaaktype in de CATALOGUS waar deze voorkomt",
        max_length=1000)
    registratiedatum = models.DateField(
        help_text='De datum waarop de zaakbehandelende organisatie de ZAAK '
        'heeft geregistreerd. Indien deze niet opgegeven wordt, '
        'wordt de datum van vandaag gebruikt.',
        default=date.today)
    verantwoordelijke_organisatie = RSINField(
        help_text=
        'Het RSIN van de Niet-natuurlijk persoon zijnde de organisatie '
        'die eindverantwoordelijk is voor de behandeling van de '
        'zaak. Dit moet een geldig RSIN zijn van 9 nummers en voldoen aan '
        'https://nl.wikipedia.org/wiki/Burgerservicenummer#11-proef')

    startdatum = models.DateField(
        help_text='De datum waarop met de uitvoering van de zaak is gestart')
    einddatum = models.DateField(
        blank=True,
        null=True,
        help_text='De datum waarop de uitvoering van de zaak afgerond is.',
    )
    einddatum_gepland = models.DateField(
        blank=True,
        null=True,
        help_text='De datum waarop volgens de planning verwacht wordt dat de '
        'zaak afgerond wordt.',
    )
    uiterlijke_einddatum_afdoening = models.DateField(
        blank=True,
        null=True,
        help_text='De laatste datum waarop volgens wet- en regelgeving de zaak '
        'afgerond dient te zijn.')
    publicatiedatum = models.DateField(
        _("publicatiedatum"),
        null=True,
        blank=True,
        help_text=_(
            "Datum waarop (het starten van) de zaak gepubliceerd is of wordt.")
    )

    producten_of_diensten = ArrayField(
        models.URLField(_("URL naar product/dienst"), max_length=1000),
        default=list,
        help_text=_(
            "De producten en/of diensten die door de zaak worden voortgebracht. "
            "Dit zijn URLs naar de resources zoals die door de producten- "
            "en dienstencatalogus-API wordt ontsloten. "
            "De producten/diensten moeten bij het zaaktype vermeld zijn."))

    communicatiekanaal = models.URLField(
        _("communicatiekanaal"),
        blank=True,
        max_length=1000,
        help_text=
        _("Het medium waarlangs de aanleiding om een zaak te starten is ontvangen. "
          "URL naar een communicatiekanaal in de VNG-Referentielijst van communicatiekanalen."
          ))

    vertrouwelijkheidaanduiding = VertrouwelijkheidsAanduidingField(
        _("vertrouwlijkheidaanduiding"),
        help_text=
        _("Aanduiding van de mate waarin het zaakdossier van de ZAAK voor de openbaarheid bestemd is."
          ))

    betalingsindicatie = models.CharField(
        _("betalingsindicatie"),
        max_length=20,
        blank=True,
        choices=BetalingsIndicatie.choices,
        help_text=_("Indicatie of de, met behandeling van de zaak gemoeide, "
                    "kosten betaald zijn door de desbetreffende betrokkene."))
    laatste_betaaldatum = models.DateTimeField(
        _("laatste betaaldatum"),
        blank=True,
        null=True,
        help_text=_(
            "De datum waarop de meest recente betaling is verwerkt "
            "van kosten die gemoeid zijn met behandeling van de zaak."))

    zaakgeometrie = GeometryField(
        blank=True,
        null=True,
        help_text="Punt, lijn of (multi-)vlak geometrie-informatie.")

    verlenging_reden = models.CharField(
        _("reden verlenging"),
        max_length=200,
        blank=True,
        help_text=
        _("Omschrijving van de reden voor het verlengen van de behandeling van de zaak."
          ))
    verlenging_duur = DaysDurationField(
        _("duur verlenging"),
        blank=True,
        null=True,
        help_text=_(
            "Het aantal werkbare dagen waarmee de doorlooptijd van de "
            "behandeling van de ZAAK is verlengd (of verkort) ten opzichte "
            "van de eerder gecommuniceerde doorlooptijd."))
    verlenging = GegevensGroepType({
        'reden': verlenging_reden,
        'duur': verlenging_duur,
    })

    opschorting_indicatie = models.BooleanField(
        _("indicatie opschorting"),
        default=False,
        help_text=_(
            "Aanduiding of de behandeling van de ZAAK tijdelijk is opgeschort."
        ))
    opschorting_reden = models.CharField(
        _("reden opschorting"),
        max_length=200,
        blank=True,
        help_text=
        _("Omschrijving van de reden voor het opschorten van de behandeling van de zaak."
          ))
    opschorting = GegevensGroepType({
        'indicatie': opschorting_indicatie,
        'reden': opschorting_reden,
    })

    selectielijstklasse = models.URLField(
        _("selectielijstklasse"),
        blank=True,
        max_length=1000,
        help_text=
        _("URL-referentie naar de categorie in de gehanteerde 'Selectielijst Archiefbescheiden' die, gezien "
          "het zaaktype en het resultaattype van de zaak, bepalend is voor het archiefregime van de zaak."
          ))

    relevante_andere_zaken = ArrayField(models.URLField(
        _("URL naar andere zaak"), max_length=1000),
                                        blank=True,
                                        default=list)

    # Archiving
    archiefnominatie = models.CharField(
        _("archiefnominatie"),
        max_length=40,
        null=True,
        blank=True,
        choices=Archiefnominatie.choices,
        help_text=
        _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden."
          ))
    archiefstatus = models.CharField(
        _("archiefstatus"),
        max_length=40,
        choices=Archiefstatus.choices,
        default=Archiefstatus.nog_te_archiveren,
        help_text=
        _("Aanduiding of het zaakdossier blijvend bewaard of na een bepaalde termijn vernietigd moet worden."
          ))
    archiefactiedatum = models.DateField(
        _("archiefactiedatum"),
        null=True,
        blank=True,
        help_text=
        _("De datum waarop het gearchiveerde zaakdossier vernietigd moet worden dan wel overgebracht moet "
          "worden naar een archiefbewaarplaats. Wordt automatisch berekend bij het aanmaken of wijzigen van "
          "een RESULTAAT aan deze ZAAK indien nog leeg."))

    class Meta:
        verbose_name = 'zaak'
        verbose_name_plural = 'zaken'
        unique_together = ('bronorganisatie', 'identificatie')

    def __str__(self):
        return self.identificatie

    def save(self, *args, **kwargs):
        if not self.identificatie:
            self.identificatie = str(uuid.uuid4())

        if self.betalingsindicatie == BetalingsIndicatie.nvt and self.laatste_betaaldatum:
            self.laatste_betaaldatum = None

        super().save(*args, **kwargs)

    @property
    def current_status_uuid(self):
        status = self.status_set.order_by('-datum_status_gezet').first()
        return status.uuid if status else None

    def get_brondatum(self,
                      afleidingswijze: str,
                      datum_kenmerk: str = None,
                      objecttype: str = None,
                      procestermijn: str = None) -> date:
        """
        To calculate the Archiefactiedatum, we first need the "brondatum" which is like the start date of the storage
        period.

        :param afleidingswijze:
            One of the `Afleidingswijze` choices.
        :param datum_kenmerk:
            A `string` representing an arbitrary attribute name. Currently only needed when `afleidingswijze` is
            `eigenschap` or `zaakobject`.
        :param objecttype:
            A `string` representing an arbitrary objecttype name. Currently only needed when `afleidingswijze` is
            `zaakobject`.
        :param procestermijn:
            A `string` representing an ISO8601 period that is considered the process term of the Zaak. Currently only
            needed when `afleidingswijze` is `termijn`.
        :return:
            A specific date that marks the start of the storage period, or `None`.
        """
        if afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.afgehandeld:
            return self.einddatum

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.hoofdzaak:
            # TODO: Document that hoofdzaak can not an external zaak
            return self.hoofdzaak.einddatum if self.hoofdzaak else None

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.eigenschap:
            if not datum_kenmerk:
                raise DetermineProcessEndDateException(
                    _('Geen datumkenmerk aanwezig om de eigenschap te achterhalen voor het bepalen van de brondatum.'
                      ))

            eigenschap = self.zaakeigenschap_set.filter(
                _naam=datum_kenmerk).first()
            if eigenschap:
                if not eigenschap.waarde:
                    return None

                try:
                    return parse_isodatetime(eigenschap.waarde).date()
                except ValueError:
                    raise DetermineProcessEndDateException(
                        _('Geen geldige datumwaarde in eigenschap "{}": {}').
                        format(datum_kenmerk, eigenschap.waarde))
            else:
                raise DetermineProcessEndDateException(
                    _('Geen eigenschap gevonden die overeenkomt met het datumkenmerk "{}" voor het bepalen van de '
                      'brondatum.').format(datum_kenmerk))

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.ander_datumkenmerk:
            # The brondatum, and therefore the archiefactiedatum, needs to be determined manually.
            return None

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.zaakobject:
            if not objecttype:
                raise DetermineProcessEndDateException(
                    _('Geen objecttype aanwezig om het zaakobject te achterhalen voor het bepalen van de brondatum.'
                      ))
            if not datum_kenmerk:
                raise DetermineProcessEndDateException(
                    _('Geen datumkenmerk aanwezig om het attribuut van het zaakobject te achterhalen voor het bepalen '
                      'van de brondatum.'))

            for zaak_object in self.zaakobject_set.filter(
                    object_type=objecttype):
                object = zaak_object._get_object()
                if datum_kenmerk in object:
                    try:
                        return parse_isodatetime(object[datum_kenmerk]).date()
                    except ValueError:
                        raise DetermineProcessEndDateException(
                            _('Geen geldige datumwaarde in attribuut "{}": {}'
                              ).format(datum_kenmerk, object[datum_kenmerk]))

            raise DetermineProcessEndDateException(
                _('Geen attribuut gevonden die overeenkomt met het datumkenmerk "{}" voor het bepalen van de '
                  'brondatum.').format(datum_kenmerk))

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.termijn:
            if self.einddatum is None:
                # TODO: Not sure if we should raise an error instead.
                return None
            if procestermijn is None:
                raise DetermineProcessEndDateException(
                    _('Geen procestermijn aanwezig voor het bepalen van de brondatum.'
                      ))
            try:
                return self.einddatum + isodate.parse_duration(procestermijn)
            except (ValueError, TypeError) as e:
                raise DetermineProcessEndDateException(
                    _('Geen geldige periode in procestermijn: {}').format(
                        procestermijn))

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.gerelateerde_zaak:
            # TODO: Determine what this means...
            raise NotImplementedError

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.ingangsdatum_besluit:
            # TODO: Relation from Zaak to Besluit is not implemented yet...
            raise NotImplementedError

        elif afleidingswijze == BrondatumArchiefprocedureAfleidingswijze.vervaldatum_besluit:
            # TODO: Relation from Zaak to Besluit is not implemented yet...
            raise NotImplementedError

        raise ValueError(f'Onbekende "Afleidingswijze": {afleidingswijze}')