Skip to content

Commit

Permalink
Enable export of non-attributable transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
FestplattenSchnitzel committed Mar 15, 2024
1 parent f07a07c commit d6af3cf
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 1 deletion.
4 changes: 4 additions & 0 deletions pycroft/lib/finance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
take_actions_for_payment_in_default_users,
get_pid_csv,
)
from .retransfer import (
get_activities_to_return,
generate_activities_return_sepaxml,
)
from .transaction_crud import (
simple_transaction,
complex_transaction,
Expand Down
49 changes: 49 additions & 0 deletions pycroft/lib/finance/retransfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from collections.abc import Sequence
from datetime import datetime, timedelta

from sepaxml import SepaTransfer
from sqlalchemy import select
from sqlalchemy.orm import joinedload

from pycroft.helpers.utc import DateTimeTz
from pycroft.model import session
from pycroft.model.finance import BankAccountActivity


def get_activities_to_return() -> Sequence[BankAccountActivity]:
statement = (
select(BankAccountActivity)
.options(joinedload(BankAccountActivity.bank_account))
.filter(BankAccountActivity.transaction_id.is_(None))
.filter(BankAccountActivity.amount > 0)
.filter(
BankAccountActivity.imported_at
< DateTimeTz(datetime.utcnow().date() - timedelta(days=14))

Check failure on line 21 in pycroft/lib/finance/retransfer.py

View workflow job for this annotation

GitHub Actions / python-lint

Error

Argument 1 to "DateTimeTz" has incompatible type "date"; expected "datetime" [arg-type]
)
)

return session.session.scalars(statement).all()


def generate_activities_return_sepaxml(activities: list[BankAccountActivity]) -> bytes:
config = {
"name": "Studierendenrat der TU Dresden",
"IBAN": "DE61850503003120219540",
"BIC": "OSDDDE81",
"batch": False,
"currency": "EUR",
}
sepa = SepaTransfer(config, clean=False)

for activity in activities:
payment = {
"name": activity.other_name,
"IBAN": activity.other_account_number,
"BIC": activity.other_routing_number,
"amount": int(activity.amount * 100),
"execution_date": datetime.now().date(),
"description": f"Rücküberweisung nicht zuordenbarer Überweisung vom {activity.posted_on} mit Referenz {activity.reference}",
}
sepa.add_payment(payment)

return sepa.export()

Check failure on line 49 in pycroft/lib/finance/retransfer.py

View workflow job for this annotation

GitHub Actions / python-lint

Error

Returning Any from function declared to return "bytes" [no-any-return]
65 changes: 64 additions & 1 deletion web/blueprints/finance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"""
import typing as t
from decimal import Decimal
from collections.abc import Iterable
from collections.abc import Iterable, Sequence
from datetime import date
from datetime import timedelta, datetime
from functools import partial
from itertools import zip_longest, chain
from io import BytesIO

import wtforms
from fints.dialog import FinTSDialogError
Expand All @@ -34,6 +35,7 @@
request,
url_for,
make_response,
send_file,
)
from flask.typing import ResponseReturnValue
from flask_login import current_user
Expand Down Expand Up @@ -67,6 +69,8 @@
get_system_accounts,
ImportedTransactions,
match_activities,
get_activities_to_return,
generate_activities_return_sepaxml,
get_all_bank_accounts,
get_unassigned_bank_account_activities,
get_all_mt940_errors,
Expand Down Expand Up @@ -548,6 +552,65 @@ def bank_account_activities_match() -> ResponseReturnValue:
activities_team=matched_activities_team)


class ActivityEntry(t.TypedDict):
bank_account: str
name: str
valid_on: date
reference: str
amount: int


@bp.route("/bank-account-activities/return/")
@access.require("finance_change")
def bank_account_activities_return() -> ResponseReturnValue:
field_list: BooleanFieldList = []
activities: dict[str, ActivityEntry] = {}

for activity in get_activities_to_return():
activities[str(activity.id)] = {
"bank_account": activity.bank_account.name,
"name": activity.other_name,
"valid_on": activity.valid_on,
"reference": activity.reference,
"amount": activity.amount,
}

field_list.append((str(activity.id), BooleanField(str(activity.id), default=True)))

form: t.Any = _create_form(field_list)

return render_template(
"finance/bank_account_activities_return.html",
form=form(),
activities=activities,
)

@bp.route("/bank-account-activities/return/do/", methods=["POST"])
@access.require("finance_change")
def bank_account_activities_return_do() -> ResponseReturnValue:
field_list: BooleanFieldList = []
activities_to_return: Sequence[BankAccountActivity] = get_activities_to_return()

for activity in activities_to_return:
field_list.append((str(activity.id), BooleanField(str(activity.id), default=True)))

form: t.Any = _create_form(field_list)()

if form.validate_on_submit():
selected_activities: list[BankAccountActivity] = [
activity for activity in activities_to_return if form[str(activity.id)].data
]

sepa_xml: bytes = generate_activities_return_sepaxml(selected_activities)

return send_file(
BytesIO(sepa_xml),
as_attachment=True,
download_name=f"non-attributable-transactions-{datetime.now().date()}.xml",
)



class UserMatch(t.TypedDict):
purpose: str
name: str
Expand Down
46 changes: 46 additions & 0 deletions web/templates/finance/bank_account_activities_return.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{#
SPDX-FileCopyrightText: 2024 Gregor Düster <git@gdstr.eu>

SPDX-License-Identifier: Apache-2.0
#}
{% extends "layout.html" %}

{% set page_title = "Unzuordenbare Überweisungen zurücküberweisen" %}

{% block content %}
<form action="{{ url_for('.bank_account_activities_return_do') }}" method="POST">
<div class="row">
<div class="col-md-12">
{{ form.csrf_token }}
<table class="table table-striped table-responsive activities">
<thead>
<th></th>
<th>Bankkonto</th>
<th>Name</th>
<th>Gültig am</th>
<th>Verwendungszweck</th>
<th>Betrag</th>
</thead>
<tbody>
{% for field in form %}{% if field.type != 'CSRFTokenField' %}
<tr>
<td>{{ field }}</td>
<td>{{ activities[field.id]["bank_account"] }}</td>
<td>{{ activities[field.id]["name"] }}</td>
<td>{{ activities[field.id]["valid_on"] }}</td>
<td>{{ activities[field.id]["reference"] }}</td>
<td>{{ activities[field.id]["amount"] }} &euro;</td>
</tr>
{% endif %}{% endfor %}
</tbody>
</table>
</div>
</div>

<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-primary">SEPA-XML generieren</button>
</div>
</div>
</form>
{% endblock %}
1 change: 1 addition & 0 deletions web/templates/finance/bank_accounts_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ <h2 class="page-header">{{ _("Übersicht") }}</h2>
<section>
<h2 class="page-header">{{ _("Unzugeordnete Kontobewegungen") }}</h2>
<a href="{{ url_for('.bank_account_activities_match') }}" class="btn btn-primary">Kontobewegungen matchen</a>
<a href="{{ url_for('.bank_account_activities_return') }}" class="btn btn-outline-secondary">Unzugeordnete Kontobewegungen rücküberweisen</a>
{{ bank_account_activity_table.render('bank_accounts_activities') }}
</section>
{% endblock %}

0 comments on commit d6af3cf

Please sign in to comment.