Ejemplo n.º 1
0
class DestructionReport(models.Model):
    title = models.CharField(
        max_length=200,
        verbose_name=_("title"),
        help_text=_("Title of the destruction report"),
    )
    process_owner = models.ForeignKey(
        to="accounts.User",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_("process owner"),
        help_text=
        _("Process owner of the destruction list for which the report was created"
          ),
    )
    content_pdf = PrivateMediaFileField(
        verbose_name=_("content pdf"),
        upload_to="reports/%Y/%m/",
        help_text=_("Content of the destruction report in PDF format"),
        blank=True,
        null=True,
    )
    content_csv = PrivateMediaFileField(
        verbose_name=_("content csv"),
        upload_to="reports/%Y/%m/",
        help_text=_("Content of the destruction report in CSV format"),
        blank=True,
        null=True,
    )
    destruction_list = models.ForeignKey(
        to="destruction.DestructionList",
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        verbose_name=_("destruction list"),
        help_text=_("Destruction list for which the report was created."),
    )

    class Meta:
        verbose_name = _("Destruction report")
        verbose_name_plural = _("Destruction reports")

    def __str__(self):
        return self.title

    def clean(self):
        if (self.process_owner and
                self.process_owner.role.type != RoleTypeChoices.process_owner):
            error_message = _(
                "Only a process owner can be associated with a destruction report"
            )
            raise ValidationError(error_message)

    def get_filename(self, extension="pdf"):
        attr = f"content_{extension}"
        return os.path.basename(getattr(self, attr).name)
Ejemplo n.º 2
0
class BestandsDeel(models.Model):
    uuid = models.UUIDField(
        unique=True, default=_uuid.uuid4, help_text="Unieke resource identifier (UUID4)"
    )
    informatieobject = models.ForeignKey(
        "EnkelvoudigInformatieObjectCanonical",
        on_delete=models.CASCADE,
        related_name="bestandsdelen",
    )
    volgnummer = models.PositiveIntegerField(
        help_text=_("Een volgnummer dat de volgorde van de bestandsdelen aangeeft.")
    )
    omvang = models.BigIntegerField(
        validators=[MinValueValidator(0)],
        help_text=_("De grootte van dit specifieke bestandsdeel."),
    )
    inhoud = PrivateMediaFileField(
        upload_to="part-uploads/%Y/%m/",
        blank=True,
        help_text=_("De (binaire) bestandsinhoud van dit specifieke bestandsdeel."),
    )

    class Meta:
        verbose_name = "bestands deel"
        verbose_name_plural = "bestands delen"
        unique_together = ("informatieobject", "volgnummer")

    def unique_representation(self):
        return f"({self.informatieobject.latest_version.unique_representation()}) - {self.volgnummer}"

    @property
    def voltooid(self) -> bool:
        return bool(self.inhoud.name)
Ejemplo n.º 3
0
class Deduction(models.Model):
    name = models.CharField(_('name'), max_length=255)
    notes = models.TextField(_('notes'), blank=True, null=True)
    receipt = PrivateMediaFileField(_('receipt'),
                                    blank=True,
                                    upload_to='receipts/%Y/%m')
    date = models.DateField(_('date'), default=date.today)
    amount = models.DecimalField(_('amount'), max_digits=10, decimal_places=2)

    class Meta:
        verbose_name = _('deduction')
        verbose_name_plural = _('deductions')
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
class File(models.Model):
    file = PrivateMediaFileField()
Ejemplo n.º 6
0
class Invoice(models.Model):
    client = models.ForeignKey('crm.Client', on_delete=models.PROTECT)
    date = models.DateField(
        _('date'), help_text=_('Include work up to (including) this day.'))

    generated = models.DateTimeField(editable=False, null=True)
    invoice_number = models.CharField(
        _('invoice number'),
        max_length=50,
        blank=True,
        unique=True,
        default=None,
        null=True,
        validators=[validators.RegexValidator(RE_INVOICE_NUMBER)])
    reference_number = models.CharField(_('reference number'),
                                        max_length=50,
                                        blank=True,
                                        null=True)
    due_date = models.DateTimeField(_('due date'), null=True, blank=True)
    pdf = PrivateMediaFileField(_('pdf'),
                                blank=True,
                                upload_to='invoices/%Y/%m')

    received = models.DateTimeField(_('received'), null=True, blank=True)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True, null=True)

    def __str__(self):
        return '{client} - {date}'.format(client=self.client, date=self.date)

    def get_absolute_url(self):
        return reverse('invoices:detail',
                       kwargs={'invoice_number': self.invoice_number})

    def get_previous(self, **kwargs):
        kwargs['client'] = self.client
        return self.get_previous_by_date(**kwargs)

    def generate_invoice_number(self, save=True):
        """
        We choose here to prefix the year that the invoice object was created,
        and number incrementally (+1) across the years.
        """
        prefix = self.created.year
        agg = self.__class__.objects.aggregate(Max('invoice_number'))
        max_number = agg['invoice_number__max'] or '201500000'
        match = RE_INVOICE_NUMBER.match(max_number)
        if not match:
            raise ValueError('Invalid invoice number for invoice %d', self.pk)
        max_number = int(max_number[4:])
        next_number = '{:05d}'.format(max_number + 1)
        self.invoice_number = '{prefix}{number}'.format(prefix=prefix,
                                                        number=next_number)
        if save:
            self.save()

    def generate(self):
        if self.generated is not None:
            return

        # collect the work entries
        try:
            previous = self.get_previous()
            lower = datetime.combine(previous.date, time(
                0, 0)) + timedelta(days=1)
        except self.__class__.DoesNotExist:
            previous = None
            lower = datetime(1970, 1, 1, 0, 0)
        lower = timezone.make_aware(lower)
        upper = timezone.make_aware(
            datetime.combine(self.date, time(23, 59, 59)))

        work_entries = WorkEntry.objects.filter(
            project__client=self.client,
            date__range=[lower, upper]).select_related('project')

        with transaction.atomic():
            for entry in work_entries:
                InvoiceItem.objects.create(
                    invoice=self,
                    project=entry.project,
                    rate=entry.project.base_rate
                    if not entry.project.flat_fee else entry.project.flat_fee,
                    amount=entry.duration.total_seconds() / 3600,
                    tax_rate=entry.project.vat,
                    source_object=entry,
                    remarks=entry.notes,
                    date=entry.date)

            # either created from hourly rate (work_entries) or manual invoice items
            if work_entries or self.invoiceitem_set.exists():
                self.generated = timezone.now()
                self.generate_invoice_number(save=False)
                self.reference_number = generate_invoice_reference(
                    self.client, self.invoice_number)
                self.save()

    def regenerate(self):
        if self.received is not None:
            logger.info('Not regenerating paid invoice %d, fix this manually',
                        self.pk)
            return

        if self.generated is not None:
            self.invoiceitem_set.all().delete()
            self.generated = None
            logger.info(
                'Regenerating invoice %d, potentially rewriting sent-out invoices.',
                self.pk)
        self.generate()

    def get_totals(self):
        totals = self.invoiceitem_set.annotate(base=F('rate') * F('amount'),
                                               tax=F('rate') * F('amount') *
                                               F('tax_rate')).aggregate(
                                                   Sum('base'), Sum('tax'))
        return totals

    def total_hours(self):
        items = self.invoiceitem_set.select_related('project').order_by(
            'project', 'tax_rate')
        return items.aggregate(Sum('amount'))['amount__sum']

    @property
    def total_no_vat(self):
        return self.get_totals()['base__sum']

    @property
    def total_vat(self):
        return self.get_totals()['tax__sum']

    @property
    def total_with_vat(self):
        totals = self.get_totals()
        return totals['base__sum'] + totals['tax__sum']

    @cached_property
    def vat_reverse_charge(self):
        return self.client.country != settings.SITE_COUNTRY
Ejemplo n.º 7
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")
Ejemplo n.º 8
0
class StufBGClient(SingletonModel):

    ontvanger_organisatie = models.CharField(_("organisatie"),
                                             max_length=200,
                                             blank=True)
    ontvanger_applicatie = models.CharField(_("applicatie"), max_length=200)
    ontvanger_administratie = models.CharField(_("administratie"),
                                               max_length=200,
                                               blank=True)
    ontvanger_gebruiker = models.CharField(_("gebruiker"),
                                           max_length=200,
                                           blank=True)
    zender_organisatie = models.CharField(_("organisatie"),
                                          max_length=200,
                                          blank=True)
    zender_applicatie = models.CharField(_("applicatie"), max_length=200)
    zender_administratie = models.CharField(_("administratie"),
                                            max_length=200,
                                            blank=True)
    zender_gebruiker = models.CharField(_("gebruiker"),
                                        max_length=200,
                                        blank=True)
    url = models.URLField(
        _("url"),
        blank=True,
        help_text="URL of the StUF-BG service to connect to.",
    )
    user = models.CharField(
        _("user"),
        max_length=200,
        blank=True,
        help_text="Username to use in the XML security context.",
    )
    password = models.CharField(
        _("password"),
        max_length=200,
        blank=True,
        help_text="Password to use in the XML security context.",
    )
    certificate = PrivateMediaFileField(
        upload_to="certificate/",
        blank=True,
        null=True,
        help_text=
        "The SSL certificate file used for client identification. If left empty, mutual TLS is disabled.",
    )
    certificate_key = PrivateMediaFileField(
        upload_to="certificate/",
        help_text=
        "The SSL certificate key file used for client identification. If left empty, mutual TLS is disabled.",
        blank=True,
        null=True,
    )

    class Meta:
        verbose_name = _("StUF-BG Client")

    def _get_headers(self):
        credentials = f"{self.user}:{self.password}".encode("utf-8")
        encoded_credentials = base64.b64encode(credentials).decode("utf-8")
        return {
            "Authorization": "Basic " + encoded_credentials,
            "Content-Type": "application/soap+xml",
        }

    def _get_request_base_context(self):
        return {
            "created": timezone.now(),
            "expired": timezone.now() + timedelta(minutes=5),
            "username": self.user,
            "password": self.password,
            "zender_organisatie": self.zender_organisatie,
            "zender_applicatie": self.zender_applicatie,
            "zender_administratie": self.zender_administratie,
            "zender_gebruiker": self.zender_gebruiker,
            "ontvanger_organisatie": self.ontvanger_organisatie,
            "ontvanger_applicatie": self.ontvanger_applicatie,
            "ontvanger_administratie": self.ontvanger_administratie,
            "ontvanger_gebruiker": self.ontvanger_gebruiker,
            "referentienummer": str(uuid.uuid4()),
            "tijdstip_bericht": dateformat.format(timezone.now(), "YmdHis"),
        }

    def _make_request(self, data):

        cert = ((self.certificate.path, self.certificate_key.path)
                if self.certificate and self.certificate_key else (None, None))

        with open(
                f"{settings.DJANGO_PROJECT_DIR}/xsd/bg0310/vraagAntwoord/bg0310_namespace.xsd",
                "r",
        ) as f:
            xmlschema_doc = etree.parse(f)
            xmlschema = etree.XMLSchema(xmlschema_doc)

        doc = etree.parse(BytesIO(bytes(data, encoding="UTF-8")))
        el = (doc.getroot().xpath(
            "soap:Body",
            namespaces={"soap": "http://schemas.xmlsoap.org/soap/envelope/"},
        )[0].getchildren()[0])
        if not xmlschema.validate(el):
            raise ValidationError(xmlschema.error_log.last_error.message)

        response = requests.post(
            self.url,
            data=data,
            headers=self._get_headers(),
            cert=cert,
        )

        return response

    def _make_historie_request(self, request_file, additional_context):
        request_context = self._get_request_base_context()
        request_context.update(additional_context)

        data = loader.render_to_string(request_file, request_context)

        return self._make_request(data)

    def get_persoon_request_data(self, bsn=None, filters=None):
        context = self._get_request_base_context()
        if bsn:
            context.update({"bsn": bsn})
        if filters:
            context.update(filters)

        template = "request/RequestIngeschrevenPersoon.xml"

        return loader.render_to_string(template, context)

    def get_nested_request_data(self, template, bsn):
        context = self._get_request_base_context()
        context.update({"bsn": bsn})

        return loader.render_to_string(template, context)

    def get_ingeschreven_persoon(self, bsn=None, filters=None):

        data = self.get_persoon_request_data(bsn=bsn, filters=filters)

        return self._make_request(data)

    def get_kind(self, bsn):

        data = self.get_nested_request_data("request/RequestKind.xml", bsn)

        return self._make_request(data)

    def get_ouder(self, bsn):

        data = self.get_nested_request_data("request/RequestOuder.xml", bsn)

        return self._make_request(data)

    def get_partner(self, bsn):

        data = self.get_nested_request_data("request/RequestPartner.xml", bsn)

        return self._make_request(data)

    def get_verblijf_plaats_historie(self, bsn, filters):
        additional_context = {"bsn": bsn}
        additional_context.update(filters)
        return self._make_historie_request(
            "request/RequestVerblijfPlaatsHistorie.xml",
            additional_context,
        )

    def get_partner_historie(self, bsn, filters):
        additional_context = {"bsn": bsn}
        additional_context.update(filters)
        return self._make_historie_request(
            "request/RequestPartnerHistorie.xml",
            additional_context,
        )

    def get_verblijfs_titel_historie(self, bsn, filters):
        additional_context = {"bsn": bsn}
        additional_context.update(filters)
        return self._make_historie_request(
            "request/RequestVerblijfsTitelHistorie.xml",
            additional_context,
        )

    def get_nationaliteit_historie(self, bsn, filters):
        additional_context = {"bsn": bsn}
        additional_context.update(filters)
        return self._make_historie_request(
            "request/RequestNationaliteitHistorie.xml",
            additional_context,
        )