60f03591ef
* adds EditEcoAccountForm * adds placeholders for some form fields * changes comment card in detail view into rlp-grayish * adds eco account detail view comment box * removes unnecessary loading of dal scripts in view.html * refactors generated identifier for data objects (10 digits to 6 uppercase letter-digit combination) * improves generate_random_string() method by adding more options for generation of strings * adds/updates translations
385 lines
13 KiB
Python
385 lines
13 KiB
Python
"""
|
|
Author: Michel Peltriaux
|
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
|
Created on: 27.09.21
|
|
|
|
"""
|
|
from dal import autocomplete
|
|
from django.contrib.auth.models import User
|
|
from django.db import transaction
|
|
from django import forms
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from compensation.models import EcoAccount, EcoAccountDeduction
|
|
from intervention.inputs import TextToClipboardInput
|
|
from intervention.models import Revocation, RevocationDocument, Intervention
|
|
from konova.forms import BaseModalForm
|
|
from konova.settings import ZB_GROUP, ETS_GROUP
|
|
from konova.utils.general import format_german_float
|
|
from konova.utils.messenger import Messenger
|
|
from konova.utils.user_checks import in_group
|
|
from user.models import UserActionLogEntry, UserAction
|
|
|
|
|
|
class ShareInterventionModalForm(BaseModalForm):
|
|
url = forms.CharField(
|
|
label=_("Share link"),
|
|
label_suffix="",
|
|
help_text=_("Send this link to users who you want to have writing access on the data"),
|
|
required=False,
|
|
widget=TextToClipboardInput(
|
|
attrs={
|
|
"readonly": True,
|
|
"class": "form-control",
|
|
}
|
|
)
|
|
)
|
|
users = forms.MultipleChoiceField(
|
|
label=_("Shared with"),
|
|
label_suffix="",
|
|
required=True,
|
|
help_text=_("Remove check to remove access for this user"),
|
|
widget=forms.CheckboxSelectMultiple(
|
|
attrs={
|
|
"class": "list-unstyled",
|
|
}
|
|
),
|
|
choices=[]
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Share")
|
|
self.form_caption = _("Share settings for {}").format(self.instance.identifier)
|
|
self.template = "modal/modal_form.html"
|
|
|
|
# Make sure an access_token is set
|
|
if self.instance.access_token is None:
|
|
self.instance.generate_access_token()
|
|
|
|
self._init_fields()
|
|
|
|
def _init_fields(self):
|
|
""" Wraps initializing of fields
|
|
|
|
Returns:
|
|
|
|
"""
|
|
# Initialize share_link field
|
|
self.share_link = self.request.build_absolute_uri(
|
|
reverse("intervention:share", args=(self.instance.id, self.instance.access_token,))
|
|
)
|
|
self.initialize_form_field(
|
|
"url",
|
|
self.share_link
|
|
)
|
|
|
|
# Initialize users field
|
|
# Remove field if user is not in registration or conservation group
|
|
if not in_group(self.request.user, ZB_GROUP) and not in_group(self.request.user, ETS_GROUP):
|
|
del self.fields["users"]
|
|
else:
|
|
users = self.instance.users.all()
|
|
choices = []
|
|
for n in users:
|
|
choices.append(
|
|
(n.id, n.username)
|
|
)
|
|
self.fields["users"].choices = choices
|
|
u_ids = list(users.values_list("id", flat=True))
|
|
self.initialize_form_field(
|
|
"users",
|
|
u_ids
|
|
)
|
|
|
|
def save(self):
|
|
accessing_users = User.objects.filter(
|
|
id__in=self.cleaned_data["users"]
|
|
)
|
|
self.instance.users.set(accessing_users)
|
|
|
|
|
|
class NewRevocationModalForm(BaseModalForm):
|
|
date = forms.DateField(
|
|
label=_("Date"),
|
|
label_suffix=_(""),
|
|
help_text=_("Date of revocation"),
|
|
widget=forms.DateInput(
|
|
attrs={
|
|
"type": "date",
|
|
"data-provide": "datepicker",
|
|
"class": "form-control",
|
|
},
|
|
format="%d.%m.%Y"
|
|
)
|
|
)
|
|
file = forms.FileField(
|
|
label=_("Document"),
|
|
label_suffix=_(""),
|
|
required=False,
|
|
help_text=_("Must be smaller than 15 Mb"),
|
|
widget=forms.FileInput(
|
|
attrs={
|
|
"class": "form-control-file"
|
|
}
|
|
)
|
|
)
|
|
comment = forms.CharField(
|
|
required=False,
|
|
max_length=200,
|
|
label=_("Comment"),
|
|
label_suffix=_(""),
|
|
help_text=_("Additional comment, maximum {} letters").format(200),
|
|
widget=forms.Textarea(
|
|
attrs={
|
|
"cols": 30,
|
|
"rows": 5,
|
|
"class": "form-control",
|
|
}
|
|
)
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Add revocation")
|
|
self.form_caption = ""
|
|
self.form_attrs = {
|
|
"enctype": "multipart/form-data", # important for file upload
|
|
}
|
|
|
|
def save(self):
|
|
with transaction.atomic():
|
|
created_action = UserActionLogEntry.objects.create(
|
|
user=self.user,
|
|
action=UserAction.CREATED
|
|
)
|
|
edited_action = UserActionLogEntry.objects.create(
|
|
user=self.user,
|
|
action=UserAction.EDITED
|
|
)
|
|
revocation = Revocation.objects.create(
|
|
date=self.cleaned_data["date"],
|
|
comment=self.cleaned_data["comment"],
|
|
created=created_action,
|
|
)
|
|
self.instance.modified = edited_action
|
|
self.instance.save()
|
|
self.instance.log.add(edited_action)
|
|
self.instance.legal.revocation = revocation
|
|
self.instance.legal.save()
|
|
|
|
if self.cleaned_data["file"]:
|
|
RevocationDocument.objects.create(
|
|
title="revocation_of_{}".format(self.instance.identifier),
|
|
date_of_creation=self.cleaned_data["date"],
|
|
comment=self.cleaned_data["comment"],
|
|
file=self.cleaned_data["file"],
|
|
instance=revocation
|
|
)
|
|
return revocation
|
|
|
|
|
|
class RunCheckModalForm(BaseModalForm):
|
|
checked_intervention = forms.BooleanField(
|
|
label=_("Checked intervention data"),
|
|
label_suffix="",
|
|
widget=forms.CheckboxInput(),
|
|
required=True,
|
|
)
|
|
checked_comps = forms.BooleanField(
|
|
label=_("Checked compensations data and payments"),
|
|
label_suffix="",
|
|
widget=forms.CheckboxInput(),
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("Run check")
|
|
self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
|
|
|
|
def is_valid(self) -> bool:
|
|
""" Perform a validity check based on quality_check() logic
|
|
|
|
Returns:
|
|
result (bool)
|
|
"""
|
|
super_result = super().is_valid()
|
|
# Perform check
|
|
msgs = self.instance.quality_check()
|
|
for msg in msgs:
|
|
self.add_error(
|
|
"checked_intervention",
|
|
msg
|
|
)
|
|
return super_result and (len(msgs) == 0)
|
|
|
|
def save(self):
|
|
""" Saving logic
|
|
|
|
Returns:
|
|
|
|
"""
|
|
with transaction.atomic():
|
|
user_action = UserActionLogEntry.objects.create(
|
|
user=self.user,
|
|
action=UserAction.CHECKED
|
|
)
|
|
# Replace old checked
|
|
if self.instance.checked:
|
|
self.instance.checked.delete()
|
|
self.instance.checked = user_action
|
|
self.instance.log.add(user_action)
|
|
self.instance.save()
|
|
|
|
# Send message to the SSO server
|
|
messenger = Messenger(
|
|
self.instance.users.all(),
|
|
type="INFO",
|
|
)
|
|
messenger.send_object_checked(
|
|
self.instance.identifier,
|
|
self.user,
|
|
)
|
|
|
|
|
|
class NewDeductionModalForm(BaseModalForm):
|
|
""" Form for creating new deduction
|
|
|
|
Can be used for Intervention view as well as for EcoAccount views.
|
|
|
|
Parameter 'instance' can be an intervention, as well as an ecoAccount.
|
|
An instance check handles both workflows properly.
|
|
|
|
"""
|
|
account = forms.ModelChoiceField(
|
|
label=_("Eco-account"),
|
|
label_suffix="",
|
|
help_text=_("Only recorded accounts can be selected for deductions"),
|
|
queryset=EcoAccount.objects.filter(deleted=None),
|
|
widget=autocomplete.ModelSelect2(
|
|
url="accounts-autocomplete",
|
|
attrs={
|
|
"data-placeholder": _("Eco-account"),
|
|
"data-minimum-input-length": 3,
|
|
"readonly": True,
|
|
}
|
|
),
|
|
)
|
|
surface = forms.DecimalField(
|
|
min_value=0.00,
|
|
decimal_places=2,
|
|
label=_("Surface"),
|
|
label_suffix="",
|
|
help_text=_("in m²"),
|
|
widget=forms.NumberInput(
|
|
attrs={
|
|
"class": "form-control",
|
|
"placeholder": "0,00",
|
|
}
|
|
)
|
|
)
|
|
intervention = forms.ModelChoiceField(
|
|
label=_("Intervention"),
|
|
label_suffix="",
|
|
help_text=_("Only shared interventions can be selected"),
|
|
queryset=Intervention.objects.filter(deleted=None),
|
|
widget=autocomplete.ModelSelect2(
|
|
url="interventions-autocomplete",
|
|
attrs={
|
|
"data-placeholder": _("Intervention"),
|
|
"data-minimum-input-length": 3,
|
|
}
|
|
),
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.form_title = _("New Deduction")
|
|
self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")
|
|
self.is_intervention_initially = False
|
|
|
|
# Check for Intervention or EcoAccount
|
|
if isinstance(self.instance, Intervention):
|
|
# Form has been called with a given intervention
|
|
self.initialize_form_field("intervention", self.instance)
|
|
self.disable_form_field("intervention")
|
|
self.is_intervention_initially = True
|
|
elif isinstance(self.instance, EcoAccount):
|
|
# Form has been called with a given account --> make it initial in the form and read-only
|
|
self.initialize_form_field("account", self.instance)
|
|
self.disable_form_field("account")
|
|
else:
|
|
raise NotImplementedError
|
|
|
|
def is_valid(self):
|
|
""" Custom validity check
|
|
|
|
Makes sure the deduction can not contain more surface than the account still provides
|
|
|
|
Returns:
|
|
is_valid (bool)
|
|
"""
|
|
super_result = super().is_valid()
|
|
if self.is_intervention_initially:
|
|
acc = self.cleaned_data["account"]
|
|
else:
|
|
acc = self.instance
|
|
|
|
if not acc.recorded:
|
|
self.add_error(
|
|
"account",
|
|
_("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
|
|
)
|
|
return False
|
|
|
|
# Calculate valid surface
|
|
sum_surface = acc.get_surface()
|
|
sum_surface_deductions = acc.get_deductions_surface()
|
|
rest_surface = sum_surface - sum_surface_deductions
|
|
form_surface = float(self.cleaned_data["surface"])
|
|
is_valid_surface = form_surface < rest_surface
|
|
if not is_valid_surface:
|
|
self.add_error(
|
|
"surface",
|
|
_("The account {} has not enough surface for a deduction of {} m². There are only {} m² left").format(
|
|
acc.identifier,
|
|
format_german_float(form_surface),
|
|
format_german_float(rest_surface),
|
|
),
|
|
)
|
|
return is_valid_surface and super_result
|
|
|
|
def save(self):
|
|
with transaction.atomic():
|
|
# Create log entry
|
|
user_action_edit = UserActionLogEntry.objects.create(
|
|
user=self.user,
|
|
action=UserAction.EDITED
|
|
)
|
|
user_action_create = UserActionLogEntry.objects.create(
|
|
user=self.user,
|
|
action=UserAction.CREATED
|
|
)
|
|
self.instance.log.add(user_action_edit)
|
|
self.instance.modified = user_action_edit
|
|
self.instance.save()
|
|
|
|
# Create deductions depending on Intervention or EcoAccount as the initial instance
|
|
if self.is_intervention_initially:
|
|
deduction = EcoAccountDeduction.objects.create(
|
|
intervention=self.instance,
|
|
account=self.cleaned_data["account"],
|
|
surface=self.cleaned_data["surface"],
|
|
created=user_action_create,
|
|
)
|
|
else:
|
|
deduction = EcoAccountDeduction.objects.create(
|
|
intervention=self.cleaned_data["intervention"],
|
|
account=self.instance,
|
|
surface=self.cleaned_data["surface"],
|
|
created=user_action_create,
|
|
)
|
|
return deduction |