From 5fb53c16b8de6efee032dea734dd9047f33d3d54 Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Tue, 12 Oct 2021 11:55:49 +0200 Subject: [PATCH] PZ, next steps. --- src/pz/admin.py | 68 +++++++++++++- src/pz/bank.py | 94 +++++++++++++++++-- ...nkexportfeedback_bankexportfeedbackline.py | 32 +++++++ src/pz/migrations/0006_bankorder.py | 22 +++++ src/pz/models.py | 63 ++++++++++++- 5 files changed, 266 insertions(+), 13 deletions(-) create mode 100644 src/pz/migrations/0005_bankexportfeedback_bankexportfeedbackline.py create mode 100644 src/pz/migrations/0006_bankorder.py diff --git a/src/pz/admin.py b/src/pz/admin.py index 0be845b19..6c51aefaa 100644 --- a/src/pz/admin.py +++ b/src/pz/admin.py @@ -1,4 +1,8 @@ from django.contrib import admin +from django.contrib import messages +from django.shortcuts import get_object_or_404, redirect +from django.urls import path, reverse +from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from django.utils.timezone import now from fnpdjango.actions import export_as_csv_action @@ -9,6 +13,12 @@ from . import models admin.site.register(models.Fundraiser) admin.site.register(models.Campaign) + +class BankExportFeedbackLineInline(admin.TabularInline): + model = models.BankExportFeedbackLine + extra = 0 + + @admin.register(models.DirectDebit) class DirectDebitAdmin(admin.ModelAdmin): list_display = [ @@ -70,12 +80,25 @@ class DirectDebitAdmin(admin.ModelAdmin): }) ] readonly_fields = ['agree_contact', 'iban_valid', 'iban_warning'] + inlines = [BankExportFeedbackLineInline] - def set_bank_submission(m,r,q): + def set_bank_submission(m, r, q): q.update(bank_submission_date=now()) + + def create_bank_order(m, request, queryset): + bo = models.BankOrder.objects.create() + bo.debits.set(queryset) + messages.info(request, mark_safe( + 'Bank order created.'.format( + reverse('admin:pz_bankorder_change', args=[bo.pk]) + ) + )) + + actions = [ bank.bank_export, set_bank_submission, + create_bank_order, export_as_csv_action(), ] @@ -87,3 +110,46 @@ class DirectDebitAdmin(admin.ModelAdmin): return { 'payment_id': models.DirectDebit.get_next_payment_id(), } + + +@admin.register(models.BankExportFeedback) +class BankExportFeedbackAdmin(admin.ModelAdmin): + inlines = [BankExportFeedbackLineInline] + + + +@admin.register(models.BankOrder) +class BankOrderAdmin(admin.ModelAdmin): + fields = ['payment_date', 'debits', 'sent', 'download'] + filter_horizontal = ['debits'] + + def get_readonly_fields(self, request, obj=None): + fields = ['download'] + if obj is not None and obj.sent: + fields += ['debits', 'payment_date'] + return fields + + def download(self, obj): + return mark_safe('Download'.format( + reverse('admin:pz_bankorder_download', args=[obj.pk]) + )) + + def get_urls(self): + urls = super().get_urls() + my_urls = [ + path( + '/download/', + self.admin_site.admin_view(self.download_view), + name='pz_bankorder_download', + ), + ] + return my_urls + urls + + def download_view(self, request, pk): + order = get_object_or_404( + models.BankOrder, pk=pk) + try: + return bank.bank_order(order.payment_date, order.debits.all()) + except Exception as e: + messages.error(request, str(e)) + return redirect('admin:pz_bankorder_change', pk) diff --git a/src/pz/bank.py b/src/pz/bank.py index e1e91b770..e7788cdbd 100644 --- a/src/pz/bank.py +++ b/src/pz/bank.py @@ -1,5 +1,8 @@ import csv +from io import StringIO +from django.conf import settings from django.http import HttpResponse +from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -21,20 +24,93 @@ def bank_export(modeladmin, request, queryset): # TODO: ansi encoding for obj in queryset: - street_addr = obj.street - if obj.building: - street_addr += ' ' + obj.building - if obj.flat: - street_addr += ' m. ' + obj.flat - street_addr = street_addr.strip() writer.writerow([ obj.payment_id, - ' '.join([obj.first_name, obj.last_name]).strip(), - street_addr, + obj.full_name, + obj.street_address, ' '.join([obj.postal_code, obj.town]).strip(), obj.iban[2:10], obj.iban, - '9521877087', + settings.PZ_NIP, 'OF' ]) return response + + +def parse_export_feedback(f): + lines = csv.reader(StringIO(f.read().decode('cp1250'))) + for line in lines: + payment_id = line[0] + status = int(line[8]) + comment = line[9] + yield payment_id, status, comment + + +def bank_order(date, queryset): + response = HttpResponse(content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename=order.PLD' + rows = [] + + no_dates = [] + no_amounts = [] + + if date is None: + raise ValueError('Payment date not set yet.') + + for debit in queryset: + if debit.bank_acceptance_date is None: + no_dates.append(debit) + if debit.amount is None: + no_amounts.append(debit) + + if no_dates or no_amounts: + t = '' + if no_dates: + t += 'Bank acceptance not received for: ' + t += ', '.join( + '{}'.format( + debit.pk, debit + ) + for debit in no_dates + ) + t += '. ' + if no_amounts: + t += 'Amount not set for: ' + t += ', '.join( + '{}'.format( + debit.pk, debit + ) + for debit in no_amounts + ) + t += '. ' + raise ValueError(mark_safe(t)) + + for debit in queryset: + rows.append( + '{order_code},{date},{amount},{dest_bank_id},0,"{dest_iban}","{user_iban}",' + '"{dest_addr}","{user_addr}",0,{user_bank_id},' + '"/NIP/{dest_nip}/IDP/{payment_id}|/TXT/{payment_desc}||",' + '"","","01"'.format( + order_code=210, + date=date.strftime('%Y%m%d'), + + amount=debit.amount * 100, + dest_bank_id=settings.PZ_IBAN[2:10], + dest_iban=settings.PZ_IBAN, + user_iban=debit.iban, + dest_addr=settings.PZ_ADDRESS_STRING, + user_bank_id=debit.iban[2:10], + dest_nip=settings.PZ_NIP, + payment_id=debit.payment_id, + payment_desc=settings.PZ_PAYMENT_DESCRIPTION, + user_addr = '|'.join(( + debit.full_name, + '', + debit.street_address, + ' '.join((debit.postal_code, debit.town)) + )) + ) + ) + response.write('\r\n'.join(rows).encode('cp1250')) + + return response diff --git a/src/pz/migrations/0005_bankexportfeedback_bankexportfeedbackline.py b/src/pz/migrations/0005_bankexportfeedback_bankexportfeedbackline.py new file mode 100644 index 000000000..b4155992a --- /dev/null +++ b/src/pz/migrations/0005_bankexportfeedback_bankexportfeedbackline.py @@ -0,0 +1,32 @@ +# Generated by Django 2.2.19 on 2021-10-11 11:40 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('pz', '0004_auto_20210929_1938'), + ] + + operations = [ + migrations.CreateModel( + name='BankExportFeedback', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('csv', models.FileField(upload_to='pz/feedback/')), + ], + ), + migrations.CreateModel( + name='BankExportFeedbackLine', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.SmallIntegerField()), + ('comment', models.CharField(max_length=255)), + ('debit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pz.DirectDebit')), + ('feedback', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pz.BankExportFeedback')), + ], + ), + ] diff --git a/src/pz/migrations/0006_bankorder.py b/src/pz/migrations/0006_bankorder.py new file mode 100644 index 000000000..b8e7d4183 --- /dev/null +++ b/src/pz/migrations/0006_bankorder.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.19 on 2021-10-12 08:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pz', '0005_bankexportfeedback_bankexportfeedbackline'), + ] + + operations = [ + migrations.CreateModel( + name='BankOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_date', models.DateField(blank=True, null=True)), + ('sent', models.DateTimeField(blank=True, null=True)), + ('debits', models.ManyToManyField(to='pz.DirectDebit')), + ], + ), + ] diff --git a/src/pz/models.py b/src/pz/models.py index 8b6db0e35..1a77217b9 100644 --- a/src/pz/models.py +++ b/src/pz/models.py @@ -1,15 +1,17 @@ from django.db import models +from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ +from .bank import parse_export_feedback class Campaign(models.Model): name = models.CharField(_('name'), max_length=255, unique=True) description = models.TextField(_('description'), blank=True) - class Meta: + class Meta: verbose_name = _('campaign') verbose_name_plural = _('campaigns') - + def __str__(self): return self.name @@ -17,7 +19,7 @@ class Campaign(models.Model): class Fundraiser(models.Model): name = models.CharField(_('name'), max_length=255, unique=True) - class Meta: + class Meta: verbose_name = _('fundraiser') verbose_name_plural = _('fundraisers') @@ -70,6 +72,9 @@ class DirectDebit(models.Model): verbose_name = _('direct debit') verbose_name_plural = _('direct debits') + def __str__(self): + return self.payment_id + def save(self, **kwargs): self.iban_valid = not self.iban_warning() if self.iban else None super().save(**kwargs) @@ -93,6 +98,20 @@ class DirectDebit(models.Model): break return payment_id + @property + def full_name(self): + return ' '.join((self.first_name, self.last_name)).strip() + + @property + def street_address(self): + street_addr = self.street + if self.building: + street_addr += ' ' + self.building + if self.flat: + street_addr += ' m. ' + self.flat + street_addr = street_addr.strip() + return street_addr + def iban_warning(self): if not self.iban: return 'No IBAN' @@ -102,3 +121,41 @@ class DirectDebit(models.Model): return 'This IBAN number looks invalid' return '' iban_warning.short_description = '' + + +class BankExportFeedback(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + csv = models.FileField(upload_to='pz/feedback/') + + def save(self, **kwargs): + super().save(**kwargs) + for payment_id, status, comment in parse_export_feedback(self.csv): + debit = DirectDebit.objects.get(payment_id = payment_id) + b, created = self.bankexportfeedbackline_set.get_or_create( + debit=debit, + defaults={ + "status": status, + "comment": comment, + } + ) + if not created: + b.status = status + b.comment = comment + b.save() + if status == 1 and not debit.bank_acceptance_date: + debit.bank_acceptance_date = now().date() + debit.save() + + +class BankExportFeedbackLine(models.Model): + feedback = models.ForeignKey(BankExportFeedback, models.CASCADE) + debit = models.ForeignKey(DirectDebit, models.CASCADE) + status = models.SmallIntegerField() + comment = models.CharField(max_length=255) + + + +class BankOrder(models.Model): + payment_date = models.DateField(null=True, blank=True) + sent = models.DateTimeField(null=True, blank=True) + debits = models.ManyToManyField(DirectDebit, blank=True) -- 2.20.1