PZ, next steps.
authorRadek Czajka <rczajka@rczajka.pl>
Tue, 12 Oct 2021 09:55:49 +0000 (11:55 +0200)
committerRadek Czajka <rczajka@rczajka.pl>
Tue, 12 Oct 2021 09:55:49 +0000 (11:55 +0200)
src/pz/admin.py
src/pz/bank.py
src/pz/migrations/0005_bankexportfeedback_bankexportfeedbackline.py [new file with mode: 0644]
src/pz/migrations/0006_bankorder.py [new file with mode: 0644]
src/pz/models.py

index 0be845b..6c51aef 100644 (file)
@@ -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(
+            '<a href="{}">Bank order</a> 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('<a href="{}">Download</a>'.format(
+            reverse('admin:pz_bankorder_download', args=[obj.pk])
+        ))
+
+    def get_urls(self):
+        urls = super().get_urls()
+        my_urls = [
+            path(
+                '<int:pk>/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)
index e1e91b7..e7788cd 100644 (file)
@@ -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(
+                '<a href="/admin/pz/directdebit/{}/change">{}</a>'.format(
+                    debit.pk, debit
+                )
+                for debit in no_dates
+            )
+            t += '. '
+        if no_amounts:
+            t += 'Amount not set for: '
+            t += ', '.join(
+                '<a href="/admin/pz/directdebit/{}/change">{}</a>'.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 (file)
index 0000000..b415599
--- /dev/null
@@ -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 (file)
index 0000000..b8e7d41
--- /dev/null
@@ -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')),
+            ],
+        ),
+    ]
index 8b6db0e..1a77217 100644 (file)
@@ -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)