Commit ad729199 authored by Luko van der Maas's avatar Luko van der Maas

Add a bunch of UX and UI updates

parent 848af302
......@@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _
from members.models import Member
from payments import services, admin_views
from payments.forms import BankAccountAdminForm
from payments.forms import BankAccountAdminForm, BatchPaymentInlineAdminForm
from .models import Payment, BankAccount, Batch
......@@ -36,9 +36,9 @@ class PaymentAdmin(admin.ModelAdmin):
"""Manage the payments"""
list_display = ('created_at', 'amount', 'processing_date', 'type',
'paid_by_link', 'processed_by_link', 'in_batch', 'notes')
list_filter = ('type',)
list_select_related = ('paid_by', 'processed_by',)
'paid_by_link', 'processed_by_link', 'batch_link', 'notes')
list_filter = ('type','batch')
list_select_related = ('paid_by', 'processed_by', 'batch')
date_hierarchy = 'created_at'
fields = ('created_at', 'amount', 'type', 'processing_date',
'paid_by', 'processed_by', 'notes', 'batch')
......@@ -53,7 +53,7 @@ class PaymentAdmin(admin.ModelAdmin):
autocomplete_fields = ('paid_by', 'processed_by')
actions = ['process_cash_selected', 'process_card_selected',
'process_tpay_selected', 'process_wire_selected',
'add_to_batch', 'export_csv']
'add_to_new_batch', 'export_csv']
@staticmethod
def _member_link(member: Member) -> str:
......@@ -69,11 +69,32 @@ class PaymentAdmin(admin.ModelAdmin):
paid_by_link.admin_order_field = 'paid_by'
paid_by_link.short_description = _('paid by')
@staticmethod
def _batch_link(batch: Batch) -> str:
if batch:
return format_html("<a href='{}'>{}</a>",
batch.get_absolute_url(),
str(batch))
else:
return "-"
def batch_link(self, obj: Payment) -> str:
return self._batch_link(obj.batch)
paid_by_link.admin_order_field = 'paid_by'
paid_by_link.short_description = _('paid by')
def processed_by_link(self, obj: Payment) -> str:
return self._member_link(obj.processed_by)
processed_by_link.admin_order_field = 'processed_by'
processed_by_link.short_description = _('processed by')
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "batch":
print(Batch.objects.filter(processed=False))
kwargs["queryset"] = Batch.objects.filter(processed=False)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def changeform_view(self, request: HttpRequest, object_id: int = None,
form_url: str = '', extra_context: dict = None
) -> HttpResponse:
......@@ -94,7 +115,8 @@ class PaymentAdmin(admin.ModelAdmin):
if not obj:
return 'created_at', 'type', 'processing_date', 'processed_by', \
'batch'
if obj.type == Payment.TPAY:
if obj.type == Payment.TPAY and \
not (obj.batch and obj.batch.processed):
return 'created_at', 'amount', 'type', 'processing_date', \
'paid_by', 'processed_by', 'notes'
return super().get_readonly_fields(request, obj)
......@@ -154,7 +176,7 @@ class PaymentAdmin(admin.ModelAdmin):
process_wire_selected.short_description = _(
'Process selected payments (wire)')
def add_to_batch(self, request: HttpRequest,
def add_to_new_batch(self, request: HttpRequest,
queryset: QuerySet) -> None:
"""Add selected TPAY payments to a new batch"""
if request.user.has_perm('payments.process_batches'):
......@@ -170,9 +192,39 @@ class PaymentAdmin(admin.ModelAdmin):
f"No payments using Thalia Pay are selected, "
f"no batch is created"
)
add_to_batch.short_descriptionn = _(
add_to_new_batch.short_descriptionn = _(
"Add selected TPAY payments to a new batch")
def add_to_last_batch(self, request: HttpRequest,
queryset: QuerySet) -> None:
"""Add selected TPAY payments to a new batch"""
if request.user.has_perm('payments.process_batches'):
tpays = queryset.filter(type=Payment.TPAY)
if len(tpays) > 0:
batch = Batch.objects.last()
if not batch.processed:
batch.save()
tpays.update(batch=batch)
self.message_user(
request,
f"Successfully added {len(tpays)} payments to {batch}",
messages.SUCCESS
)
else:
self.message_user(
request,
f"The last batch {batch} is already processed",
messages.ERROR
)
else:
self.message_user(
request,
f"No payments using Thalia Pay are selected, "
f"no batch is created",
messages.ERROR
)
add_to_new_batch.short_descriptionn = _(
"Add selected TPAY payments to a new batch")
def _process_feedback(self, request, updated_payments: list) -> None:
"""Show a feedback message for the processing result"""
......@@ -258,9 +310,10 @@ class ValidAccountFilter(admin.SimpleListFilter):
class PaymentsInline(admin.TabularInline):
"""The inline for payments in the Batch admin"""
model = Payment
fields = ('amount', 'processing_date', 'paid_by',)
readonly_fields = ('amount', 'processing_date', 'paid_by',)
form = BatchPaymentInlineAdminForm
extra = 0
can_delete = False
@admin.register(Batch)
......@@ -269,6 +322,7 @@ class BatchAdmin(admin.ModelAdmin):
list_display = ('description', 'start_date', 'end_date', 'processing_date',
'processed',)
fields = ('description', 'processed', 'processing_date', 'total_amount',)
search_fields = ('description',)
def get_readonly_fields(self, request: HttpRequest, obj: Batch = None):
default_fields = ('processed', 'processing_date', 'total_amount',)
......@@ -287,9 +341,24 @@ class BatchAdmin(admin.ModelAdmin):
self.admin_site.admin_view(
admin_views.BatchExportAdminView.as_view()),
name='payments_batch_export'),
path('new_filled/',
self.admin_site.admin_view(
admin_views.BatchNewFilledAdminView.as_view()),
name='payments_batch_new_batch_filled'),
]
return custom_urls + urls
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if instance.batch is None or (instance.batch and not instance.batch.processed):
instance.batch = None
instance.save()
formset.save_m2m()
def changeform_view(self, request: HttpRequest, object_id: str = None,
form_url: str = '', extra_context: dict = None
) -> HttpResponse:
......@@ -298,13 +367,14 @@ class BatchAdmin(admin.ModelAdmin):
Only allow when the payment has not been processed yet
"""
obj = None
processed = False
if (object_id is not None and
request.user.has_perm('payments.process_batches')):
obj = Batch.objects.get(id=object_id)
if obj.processed:
obj = None
processed = obj.processed
return super().changeform_view(
request, object_id, form_url, {'batch': obj})
request, object_id, form_url, {'batch': obj,
'processed': processed})
@admin.register(BankAccount)
......
"""Admin views provided by the payments package"""
import csv
import datetime
from django.contrib import messages
from django.contrib.admin.utils import model_ngettext
......@@ -108,3 +109,29 @@ class BatchExportAdminView(View):
bankaccount.valid_from
])
return response
@method_decorator(staff_member_required, name='dispatch')
@method_decorator(permission_required('payments.process_batches'),
name='dispatch', )
class BatchNewFilledAdminView(View):
"""
View tht exports a batch
"""
def get(self, request, *args, **kwargs):
batch = Batch()
batch.save()
now = datetime.datetime.utcnow()
last_month_start = (datetime.datetime(now.year, now.month, 1) - datetime.timedelta(days=1)).replace(day=1)
last_month_end = datetime.datetime(now.year, now.month, 1, 23, 59) - datetime.timedelta(days=1)
payments = Payment.objects.filter(
type=Payment.TPAY,
batch=None,
processing_date__gte=last_month_start,
# processing_date__lte=last_month_end,
)
payments.update(batch=batch)
return redirect('admin:payments_batch_change', object_id=batch.id)
from django import forms
from payments.models import BankAccount
from payments.models import BankAccount, Payment
from payments.widgets import SignatureWidget
from django.utils.translation import gettext as _
......@@ -34,3 +34,20 @@ class BankAccountAdminForm(forms.ModelForm):
widgets = {
'signature': SignatureWidget(),
}
class BatchPaymentInlineAdminForm(forms.ModelForm):
"""
Custom admin form for Payments model
for the Batch inlines to add remove
from batch option
"""
remove_batch = forms.BooleanField(
required=False,
label=_('Remove payment from batch')
)
class Meta:
fields = ('amount', 'processing_date', 'paid_by',)
model = Payment
......@@ -86,10 +86,6 @@ class Payment(models.Model):
def processed(self):
return self.type != self.NONE
@property
def in_batch(self):
return self.batch is not None
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if self.type != self.NONE and not self.processing_date:
......@@ -120,8 +116,10 @@ class Payment(models.Model):
def _default_batch_description():
return f"your Thalia payments for {datetime.datetime.now().year}-" \
f"{datetime.datetime.now().month}"
now = datetime.datetime.utcnow()
last_month = datetime.datetime(now.year, now.month, 1) \
- datetime.timedelta(days=1)
return f"your Thalia payments for {last_month.year}-{last_month.month}"
class Batch(models.Model):
......@@ -149,6 +147,9 @@ class Batch(models.Model):
self.processing_date = timezone.now()
super().save(force_insert, force_update, using, update_fields)
def get_absolute_url(self):
return reverse('admin:payments_batch_change', args=[str(self.pk)])
@property
def start_date(self) -> datetime.datetime:
return self.payments_set.earliest('processing_date').processing_date
......
......@@ -14,7 +14,9 @@
{% block after_field_sets %}
{% if batch %}
<div class="submit-row payments-row">
<a data-href="{% url 'admin:payments_batch_process' pk=batch.pk %}" class="button process">{% trans "Process batch" %}</a>
{% if not processed %}
<a data-href="{% url 'admin:payments_batch_process' pk=batch.pk %}" class="button process">{% trans "Process batch" %}</a>
{% endif %}
<a data-href="{% url 'admin:payments_batch_export' pk=batch.pk %}" class="button process">{% trans "Export batch" %}</a>
</div>
{% endif %}
......
{% extends "admin/change_list.html" %}
{% load i18n admin_urls static compress %}
{% block object-tools-items %}
<li>
<a href="{% url 'admin:payments_batch_new_batch_filled' %}">{% trans "New batch from previous month of Thalia Pay payments" %}</a>
</li>
{{ block.super }}
{% endblock %}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment