class BesluitType(GeldigheidMixin, models.Model): """ Generieke aanduiding van de aard van een besluit. **Populatie** Alle besluittypen van de besluiten die het resultaat kunnen zijn van het zaakgericht werken van de behandelende organisatie(s). **Toelichting objecttype** Het betreft de indeling of groepering van besluiten naar hun aard, zoals bouwvergunning, ontheffing geluidhinder en monumentensubsidie. """ uuid = models.UUIDField( unique=True, default=_uuid.uuid4, help_text="Unieke resource identifier (UUID4)" ) omschrijving = models.CharField( _('omschrijving'), max_length=80, blank=True, help_text=_('Omschrijving van de aard van BESLUITen van het BESLUITTYPE.')) # TODO [KING]: wat is de waardenverzameling? omschrijving_generiek = models.CharField( _('omschrijving generiek'), max_length=80, blank=True, help_text=_('Algemeen gehanteerde omschrijving van de aard van BESLUITen van het BESLUITTYPE') ) # TODO [KING]: waardenverzameling gebaseerd op de AWB, wat betekend dat? besluitcategorie = models.CharField( _('besluitcategorie'), max_length=40, blank=True, help_text=_('Typering van de aard van BESLUITen van het BESLUITTYPE.') ) reactietermijn = DaysDurationField( _('reactietermijn'), blank=True, null=True, help_text=_('Het aantal dagen, gerekend vanaf de verzend- of publicatiedatum, waarbinnen verweer tegen ' 'een besluit van het besluittype mogelijk is.') ) publicatie_indicatie = models.BooleanField( _('publicatie indicatie'), null=False, help_text=_('Aanduiding of BESLUITen van dit BESLUITTYPE gepubliceerd moeten worden.') ) publicatietekst = models.TextField( _('publicatietekst'), blank=True, help_text=_('De generieke tekst van de publicatie van BESLUITen van dit BESLUITTYPE') ) publicatietermijn = DaysDurationField( _('publicatietermijn'), blank=True, null=True, help_text=_('Het aantal dagen, gerekend vanaf de verzend- of publicatiedatum, dat BESLUITen van dit ' 'BESLUITTYPE gepubliceerd moeten blijven.') ) toelichting = models.TextField( _('toelichting'), blank=True, help_text=_('Een eventuele toelichting op dit BESLUITTYPE.') ) catalogus = models.ForeignKey( 'datamodel.Catalogus', on_delete=models.CASCADE, verbose_name=_('catalogus'), help_text=_('De CATALOGUS waartoe dit BESLUITTYPE behoort.') ) informatieobjecttypes = models.ManyToManyField( 'datamodel.InformatieObjectType', blank=True, verbose_name=_('informatieobjecttype'), help_text=_('Het INFORMATIEOBJECTTYPE van informatieobjecten waarin besluiten van dit ' 'BESLUITTYPE worden vastgelegd.') ) resultaattypes = models.ManyToManyField( 'datamodel.ResultaatType', verbose_name=_('is resultaat van'), help_text=_('(inverse van:) Het BESLUITTYPE van besluiten die gepaard gaan ' 'met resultaten van het RESULTAATTYPE.') ) zaaktypes = models.ManyToManyField( 'datamodel.ZaakType', verbose_name=_('zaaktypes'), help_text=_('ZAAKTYPE met ZAAKen die relevant kunnen zijn voor dit BESLUITTYPE') ) class Meta: verbose_name = _('besluittype') verbose_name_plural = _('besluittypen') unique_together = ('catalogus', 'omschrijving') def __str__(self): """ Unieke aanduiding van CATALOGUS in combinatie met Besluittype-omschrijving """ return f'{self.catalogus} - {self.omschrijving}'
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}"
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)
class BesluitType(ETagMixin, GeldigheidMixin, ConceptMixin, models.Model): """ Generieke aanduiding van de aard van een besluit. **Populatie** Alle besluittypen van de besluiten die het resultaat kunnen zijn van het zaakgericht werken van de behandelende organisatie(s). **Toelichting objecttype** Het betreft de indeling of groepering van besluiten naar hun aard, zoals bouwvergunning, ontheffing geluidhinder en monumentensubsidie. """ uuid = models.UUIDField( unique=True, default=_uuid.uuid4, help_text="Unieke resource identifier (UUID4)" ) omschrijving = models.CharField( _("omschrijving"), max_length=80, blank=True, help_text=_("Omschrijving van de aard van BESLUITen van het BESLUITTYPE."), ) # TODO [KING]: wat is de waardenverzameling? omschrijving_generiek = models.CharField( _("omschrijving generiek"), max_length=80, blank=True, help_text=_( "Algemeen gehanteerde omschrijving van de aard van BESLUITen van het BESLUITTYPE" ), ) # TODO [KING]: waardenverzameling gebaseerd op de AWB, wat betekend dat? besluitcategorie = models.CharField( _("besluitcategorie"), max_length=40, blank=True, help_text=_("Typering van de aard van BESLUITen van het BESLUITTYPE."), ) reactietermijn = DaysDurationField( _("reactietermijn"), blank=True, null=True, help_text=_( "Een tijdsduur in ISO 8601 formaat, gerekend vanaf de verzend- of " "publicatiedatum, waarbinnen verweer tegen een besluit van het " "besluittype mogelijk is." ), ) publicatie_indicatie = models.BooleanField( _("publicatie indicatie"), null=False, help_text=_( "Aanduiding of BESLUITen van dit BESLUITTYPE gepubliceerd moeten worden." ), ) publicatietekst = models.TextField( _("publicatietekst"), blank=True, help_text=_( "De generieke tekst van de publicatie van BESLUITen van dit BESLUITTYPE" ), ) publicatietermijn = DaysDurationField( _("publicatietermijn"), blank=True, null=True, help_text=_( "Een tijdsduur in ISO 8601 formaat, gerekend vanaf de verzend- of " "publicatiedatum, dat BESLUITen van dit " "BESLUITTYPE gepubliceerd moeten blijven." ), ) toelichting = models.TextField( _("toelichting"), blank=True, help_text=_("Een eventuele toelichting op dit BESLUITTYPE."), ) catalogus = models.ForeignKey( "datamodel.Catalogus", on_delete=models.CASCADE, verbose_name=_("catalogus"), help_text=_( "URL-referentie naar de CATALOGUS waartoe dit BESLUITTYPE behoort." ), ) informatieobjecttypen = models.ManyToManyField( "datamodel.InformatieObjectType", blank=True, verbose_name=_("informatieobjecttypen"), related_name="besluittypen", help_text=_( "URL-referenties naar het INFORMATIEOBJECTTYPE van informatieobjecten waarin besluiten van dit " "BESLUITTYPE worden vastgelegd." ), ) resultaattypen = models.ManyToManyField( "datamodel.ResultaatType", verbose_name=_("is resultaat van"), help_text=_( "(inverse van:) Het BESLUITTYPE van besluiten die gepaard gaan " "met resultaten van het RESULTAATTYPE." ), ) zaaktypen = models.ManyToManyField( "datamodel.ZaakType", verbose_name=_("zaaktypen"), related_name="besluittypen", help_text=_( "ZAAKTYPE met ZAAKen die relevant kunnen zijn voor dit BESLUITTYPE" ), ) class Meta: verbose_name = _("besluittype") verbose_name_plural = _("besluittypen") unique_together = ("catalogus", "omschrijving") def __str__(self): """ Unieke aanduiding van CATALOGUS in combinatie met Besluittype-omschrijving """ return f"{self.catalogus} - {self.omschrijving}"
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}')