""" 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 konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED from user.models import User, UserActionLogEntry from django.db import transaction from django import forms from django.utils.translation import gettext_lazy as _ from compensation.models import EcoAccount, EcoAccountDeduction from intervention.inputs import TextToClipboardInput from intervention.models import Intervention, InterventionDocument from konova.forms import BaseModalForm, NewDocumentForm from konova.utils.general import format_german_float from konova.utils.user_checks import is_default_group_only class ShareModalForm(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", } ) ) user_select = forms.ModelMultipleChoiceField( label=_("Add user to share with"), label_suffix="", help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."), required=False, queryset=User.objects.all(), widget=autocomplete.ModelSelect2Multiple( url="share-user-autocomplete", attrs={ "data-placeholder": _("Click for selection"), "data-minimum-input-length": 3, }, forward=["users"] ), ) 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 share_link = self.instance.get_share_link() self.share_link = self.request.build_absolute_uri(share_link) self.initialize_form_field( "url", self.share_link ) # Initialize users field # Disable field if user is not in registration or conservation group if is_default_group_only(self.request.user): self.disable_form_field("users") self._add_user_choices_to_field() def _add_user_choices_to_field(self): """ Transforms the instance's sharing users into a list for the form field Returns: """ 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): self.instance.update_sharing_user(self) 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): revocation = self.instance.add_revocation(self) self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED) return revocation class CheckModalForm(BaseModalForm): """ The modal form for running a check on interventions and their compensations """ 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 ) valid = None 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) self.valid = False def _are_deductions_valid(self): """ Performs validity checks on deductions and their eco-account Returns: """ deductions = self.instance.deductions.all() for deduction in deductions: checker = deduction.account.quality_check() for msg in checker.messages: self.add_error( "checked_comps", f"{deduction.account.identifier}: {msg}" ) return checker.valid return True def _are_comps_valid(self): """ Performs validity checks on all types of compensations Types of compensations are * regular Compensations * deductions from EcoAccounts Returns: """ comps = self.instance.compensations.all() comps_valid = True for comp in comps: checker = comp.quality_check() for msg in checker.messages: self.add_error( "checked_comps", f"{comp.identifier}: {msg}" ) comps_valid = checker.valid deductions_valid = self._are_deductions_valid() return deductions_valid and comps_valid def is_valid(self) -> bool: """ Perform a validity check based on quality_check() logic Returns: result (bool) """ super_valid = super().is_valid() # Perform check checker = self.instance.quality_check() for msg in checker.messages: self.add_error( "checked_intervention", msg ) all_comps_valid = self._are_comps_valid() intervention_valid = checker.valid return super_valid and intervention_valid and all_comps_valid def save(self): """ Saving logic Returns: """ with transaction.atomic(): self.instance.set_checked(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") # 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") 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() acc = self.cleaned_data["account"] 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 deductable_surface = acc.deductable_surface sum_surface_deductions = acc.get_deductions_surface() rest_surface = deductable_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 __create_deduction(self): """ Creates the deduction Returns: """ with transaction.atomic(): user_action_create = UserActionLogEntry.get_created_action(self.user) deduction = EcoAccountDeduction.objects.create( intervention=self.cleaned_data["intervention"], account=self.cleaned_data["account"], surface=self.cleaned_data["surface"], created=user_action_create, ) return deduction def save(self): deduction = self.__create_deduction() self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED) self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED, reset_recorded=False) return deduction class NewInterventionDocumentForm(NewDocumentForm): document_model = InterventionDocument