Skip to content

Commit

Permalink
Enable submission of repayment requests
Browse files Browse the repository at this point in the history
  • Loading branch information
FestplattenSchnitzel committed Oct 17, 2023
1 parent bc2f3c8 commit ec24e8e
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 1 deletion.
1 change: 1 addition & 0 deletions build/requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ recurring_ical_events~=1.0.2b0
cachetools~=5.2.0
python-dotenv~=0.21.0
pydantic~=2.4.2
schwifty~=2023.6.0
91 changes: 90 additions & 1 deletion sipa/blueprints/usersuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from collections import OrderedDict
import logging
from datetime import datetime
from decimal import Decimal
from schwifty import IBAN

from babel.numbers import format_currency
from flask import (
Expand All @@ -23,7 +25,8 @@
from sipa.forms import ContactForm, ChangeMACForm, ChangeMailForm, \
ChangePasswordForm, flash_formerrors, HostingForm, \
PaymentForm, ActivateNetworkAccessForm, TerminateMembershipForm, \
TerminateMembershipConfirmForm, ContinueMembershipForm
TerminateMembershipConfirmForm, ContinueMembershipForm, \
RequestRepaymentForm, RequestRepaymentConfirmForm
from sipa.mail import send_usersuite_contact_mail
from sipa.model.fancy_property import ActiveProperty
from sipa.utils import password_changeable, subscribe_to_status_page
Expand Down Expand Up @@ -622,3 +625,89 @@ def reset_wifi_password():
return render_template('generic_form.html',
page_title=gettext("Neues WLAN Passwort"),
form_args=form_args)


@bp_usersuite.route("/request-repayment", methods=["GET", "POST"])
@login_required
def request_repayment():
"""
Request a repayment of excess membership contributions
"""

form = RequestRepaymentForm()

if form.validate_on_submit():
return redirect(
url_for(
".request_repayment_confirm",
beneficiary=form.beneficiary.data,
iban=form.iban.data,
amount=form.amount.data,
)
)
elif form.is_submitted():
flash_formerrors(form)

form_args = {
"form": form,
"cancel_to": url_for(".index"),
"submit_text": gettext("Weiter"),
}

return render_template(
"generic_form.html",
page_title=gettext("Rücküberweisung beantragen"),
form_args=form_args,
)


@bp_usersuite.route("/request-repayment/confirm", methods=["GET", "POST"])
@login_required
def request_repayment_confirm():
"""
Request a repayment of excess membership contributions
"""

beneficiary = request.args.get("beneficiary", None)
iban = request.args.get("iban", None, lambda x: IBAN(x, validate_bban=True))
amount = request.args.get("amount", None, lambda x: Decimal(x))

form = RequestRepaymentConfirmForm()

if None not in (beneficiary, iban, amount):
balance = current_user.finance_information.balance.raw_value
iban_str = str(iban)

form.beneficiary.data = beneficiary
# Insert a space every 4 characters of the IBAN
form.iban.data = " ".join(
iban_str[i : i + 4] for i in range(0, len(iban_str), 4)
)
form.bic.data = str(iban.bic)
form.bank.data = iban.bank_name
form.amount.data = amount
form.estimated_balance.data = balance - amount
else:
return redirect(url_for(".request_repayment"))

if form.validate_on_submit():
# TODO Prevent submission of multiple requests for repayment,
# requires changes in Pycroft

# TODO Send request to Pycroft
flash(gettext("Rücküberweisungsantrag erfolgreich abgesendet."), "success")
return redirect(url_for(".index"))
elif form.is_submitted():
flash_formerrors(form)

form_args = {
"form": form,
"cancel_text": gettext("Zurück"),
"cancel_to": url_for(".request_repayment"),
}

return render_template(
"generic_form.html",
page_title=gettext("Rücküberweisung beantragen - Bestätigen"),
form_args=form_args,
)
85 changes: 85 additions & 0 deletions sipa/forms.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import re
from datetime import date
from decimal import Decimal
from operator import itemgetter

from flask_babel import gettext, lazy_gettext
from flask import flash
from flask_login import current_user
from flask_wtf import FlaskForm
from schwifty import IBAN
from schwifty.exceptions import SchwiftyException
from werkzeug.local import LocalProxy
from wtforms import (
BooleanField,
Expand All @@ -16,6 +19,7 @@
TextAreaField,
IntegerField,
DateField,
DecimalField,
)
from wtforms.validators import (
DataRequired,
Expand Down Expand Up @@ -275,6 +279,87 @@ class ChangeMACForm(FlaskForm):
)


def validate_iban(form, field: StringField) -> None:
try:
IBAN(field.data, validate_bban=True)
except SchwiftyException as e:
# TODO Figure out decent error messages
raise ValidationError(e.__doc__) from e

def validate_amount(form, field: DecimalField) -> None:
if current_user.finance_information.balance.raw_value < field.data:
raise ValidationError(lazy_gettext("Keine ausreichende Guthabendeckung vorhanden!"))


class RequestRepaymentForm(FlaskForm):
beneficiary = StrippedStringField(
label=lazy_gettext("Empfänger"),
validators=[DataRequired(lazy_gettext("Empfänger nicht angegeben!"))],
)

iban = StrippedStringField(
label=lazy_gettext("IBAN"),
validators=[DataRequired(lazy_gettext("IBAN nicht angegeben!")), validate_iban],
)

amount = (
DecimalField(
label=lazy_gettext("Betrag (EUR)"),
validators=[
DataRequired(lazy_gettext("Betrag nicht angegeben!")),
NumberRange(
min=Decimal('0.01'),
message=lazy_gettext("Betrag muss mindestens 0,01 € betragen!"),
),
validate_amount,
],
)
)


class RequestRepaymentConfirmForm(FlaskForm):
beneficiary = StrippedStringField(
label=lazy_gettext("Empfänger"),
render_kw={'readonly': True},
validators=[DataRequired(lazy_gettext("Empfänger nicht angegeben!"))],
)

iban = StrippedStringField(
label=lazy_gettext("IBAN"),
render_kw={'readonly': True},
validators=[DataRequired(lazy_gettext("IBAN nicht angegeben!")), validate_iban],
)

bic = StrippedStringField(
label=lazy_gettext("BIC"),
render_kw={'readonly': True},
)

bank = StrippedStringField(
label=lazy_gettext("Bank"),
render_kw={'readonly': True},
)

amount = (
DecimalField(
label=lazy_gettext("Betrag (EUR)"),
render_kw={'readonly': True},
validators=[
DataRequired(lazy_gettext("Betrag nicht angegeben!")),
NumberRange(
min=Decimal('0.01'),
message=lazy_gettext("Betrag muss mindestens 0,01 € betragen!"),
),
validate_amount,
],
)
)

estimated_balance = DecimalField(
label=lazy_gettext("Kontostand nach Rücküberweisung (EUR)"),
render_kw={'readonly': True},
)

class ActivateNetworkAccessForm(FlaskForm):
password = PasswordField(
label=lazy_gettext("Passwort"),
Expand Down

0 comments on commit ec24e8e

Please sign in to comment.