EcoAccount withdraws

* adds functionality for withdraws from eco accounts in detail view of interventions and eco account as well
* adds get_surface() method to AbstractCompensation class to provide a simple getter for a sql calculation
* adds get_surface_withdraws() method to EcoAccount class to provide a simple getter for a sql calculation
* renames some routes to match coherent rout naming
* adds logic check on NewWithdrawForm
* renames templates/table directory to templates/form, since there are form-table templates inside --> more clarity
* adds new autocomplete routes to konova/urls.py for Interventions and EcoAccounts
* adds/updates translations
* adds/updates template comments
* updates requirements.txt
This commit is contained in:
mipel
2021-08-10 10:42:04 +02:00
parent 17d697da92
commit 72d61a23da
21 changed files with 388 additions and 121 deletions

View File

@@ -14,6 +14,7 @@ from django.db import transaction
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from compensation.models import EcoAccountWithdraw, EcoAccount
from intervention.models import Intervention, Revocation
from konova.forms import BaseForm, BaseModalForm
from konova.models import Document
@@ -423,3 +424,126 @@ class RunCheckForm(BaseModalForm):
self.instance.checked = user_action
self.instance.log.add(user_action)
self.instance.save()
class NewWithdrawForm(BaseModalForm):
""" Form for creating new withdraws
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 withdraws"),
queryset=EcoAccount.objects.filter(deleted=None, recorded__isnull=False),
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²"),
)
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 Withdraw")
self.form_caption = _("Enter the information for a new withdraw from a chosen eco-account")
self.is_intervention_initially = False
# Add a placeholder for field 'surface' without having to define the whole widget above
self.fields["surface"].widget.attrs["placeholder"] = "0,00"
# 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 withdraw 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
sum_surface = acc.get_surface()
sum_surface_withdraws = acc.get_surface_withdraws()
rest_surface = sum_surface - sum_surface_withdraws
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 withdraw of {} m². There are only {} m² left").format(acc.identifier, form_surface, 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)
# Create withdraw depending on Intervention or EcoAccount as the initial instance
if self.is_intervention_initially:
withdraw = EcoAccountWithdraw.objects.create(
intervention=self.instance,
account=self.cleaned_data["account"],
surface=self.cleaned_data["surface"],
created=user_action_create,
)
else:
withdraw = EcoAccountWithdraw.objects.create(
intervention=self.cleaned_data["intervention"],
account=self.instance,
surface=self.cleaned_data["surface"],
created=user_action_create,
)
return withdraw

View File

@@ -14,7 +14,7 @@
Only show add-button if no revocation exists, yet.
{% endcomment %}
{% if is_default_member and has_access and not intervention.legal.revocation %}
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:revocation-new' intervention.id %}" title="{% trans 'Add revocation' %}">
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:new-revocation' intervention.id %}" title="{% trans 'Add revocation' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'ban' %}
</button>
@@ -56,7 +56,7 @@
</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'intervention:revocation-remove' rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
<button data-form-url="{% url 'intervention:remove-revocation' rev.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove revocation' %}">
{% fa5_icon 'trash' %}
</button>
{% endif %}

View File

@@ -11,12 +11,10 @@
<div class="col-sm-6">
<div class="d-flex justify-content-end">
{% if is_default_member and has_access %}
<a href="{% url 'compensation:new' %}" title="{% trans 'Add new withdraw' %}">
<button class="btn btn-outline-default">
{% fa5_icon 'plus' %}
{% fa5_icon 'tree' %}
</button>
</a>
<button class="btn btn-outline-default btn-modal" data-form-url="{% url 'intervention:acc-new-withdraw' intervention.id %}" title="{% trans 'Add new withdraw' %}">
{% fa5_icon 'plus' %}
{% fa5_icon 'tree' %}
</button>
{% endif %}
</div>
</div>
@@ -32,6 +30,9 @@
<th scope="col">
{% trans 'Amount' %}
</th>
<th scope="col">
{% trans 'Created' %}
</th>
<th scope="col">
{% trans 'Action' %}
</th>
@@ -49,6 +50,7 @@
</a>
</td>
<td class="align-middle">{{ withdraw.surface|floatformat:2|intcomma }} m²</td>
<td class="align-middle">{{ withdraw.created.timestamp|default_if_none:""|naturalday}}</td>
<td>
{% if is_default_member and has_access %}
<button data-form-url="{% url 'compensation:withdraw-remove' withdraw.account.id withdraw.id %}" class="btn btn-default btn-modal" title="{% trans 'Remove Withdraw' %}">

View File

@@ -8,7 +8,7 @@ Created on: 30.11.20
from django.urls import path
from intervention.views import index_view, new_view, open_view, edit_view, remove_view, new_document_view, share_view, \
create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view
create_share_view, remove_revocation_view, new_revocation_view, run_check_view, log_view, new_withdraw_view
app_name = "intervention"
urlpatterns = [
@@ -23,7 +23,10 @@ urlpatterns = [
path('<id>/share', create_share_view, name='share-create'),
path('<id>/check', run_check_view, name='run-check'),
# Withdraws
path('<id>/withdraw/new', new_withdraw_view, name='acc-new-withdraw'),
# Revocation routes
path('<id>/revocation/new', new_revocation_view, name='revocation-new'),
path('revocation/<id>/remove', remove_revocation_view, name='revocation-remove'),
path('<id>/revocation/new', new_revocation_view, name='new-revocation'),
path('revocation/<id>/remove', remove_revocation_view, name='remove-revocation'),
]

View File

@@ -5,7 +5,7 @@ from django.http import HttpRequest
from django.shortcuts import render, get_object_or_404
from intervention.forms import NewInterventionForm, EditInterventionForm, ShareInterventionForm, NewRevocationForm, \
RunCheckForm
RunCheckForm, NewWithdrawForm
from intervention.models import Intervention, Revocation
from intervention.tables import InterventionTable
from konova.contexts import BaseContext
@@ -392,3 +392,23 @@ def log_view(request: HttpRequest, id: str):
}
context = BaseContext(request, context).context
return render(request, template, context)
@login_required
@default_group_required
def new_withdraw_view(request: HttpRequest, id: str):
""" Renders a modal form view for creating withdraws
Args:
request (HttpRequest): The incoming request
id (str): The intervention's id which shall get a new withdraw
Returns:
"""
intervention = get_object_or_404(Intervention, id=id)
form = NewWithdrawForm(request.POST or None, instance=intervention, user=request.user)
return form.process_request(
request,
msg_success=_("Withdraw added")
)