From 3f0537fef7ceb797e32abbbb3fd8f8ba65962ad7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 2 Dec 2019 10:25:03 +0100
Subject: [PATCH 3/3] lingo: action mark_as_notified (#21626)

---
 combo/apps/lingo/manager_views.py             | 19 +++-
 combo/apps/lingo/models.py                    |  5 +-
 .../lingo/basketitem_error_list.html          |  4 +
 combo/apps/lingo/urls.py                      |  4 +-
 tests/test_lingo_manager.py                   | 96 +++++++++++++++++--
 5 files changed, 115 insertions(+), 13 deletions(-)

diff --git a/combo/apps/lingo/manager_views.py b/combo/apps/lingo/manager_views.py
index 2897b2b..af37308 100644
--- a/combo/apps/lingo/manager_views.py
+++ b/combo/apps/lingo/manager_views.py
@@ -18,13 +18,16 @@ import csv
 import datetime
 from dateutil import parser as date_parser
 
+from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse_lazy
 from django.db.models import Q
 from django.db.models.expressions import RawSQL
 from django.utils import six
 from django.utils.timezone import make_aware, now
-from django.views.generic import CreateView, UpdateView, ListView, DeleteView
+from django.views.generic import CreateView, UpdateView, ListView, DeleteView, View
 from django.http import HttpResponse
+from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404
 from django.template.response import TemplateResponse
 
 import eopayment
@@ -142,6 +145,20 @@ class BasketItemErrorListView(ListView):
         return queryset1.union(queryset2).order_by('-creation_date')
 
 
+class BasketItemMarkAsNotifiedView(View):
+    def get(self, request, *args, **kwargs):
+        item = get_object_or_404(
+            BasketItem.objects.exclude(source_url=''),
+            pk=kwargs['item_id'],
+            regie__webservice_url='',
+            notification_date__isnull=True,
+            cancellation_date__isnull=True,
+            payment_date__lt=now() - datetime.timedelta(minutes=300))
+        item.notify_payment(notify_origin=False)
+
+        return HttpResponseRedirect(reverse('lingo-manager-payment-error-list'))
+
+
 def download_transactions_csv(request):
     if request.method == 'POST':
         form = TransactionExportForm(data=request.POST)
diff --git a/combo/apps/lingo/models.py b/combo/apps/lingo/models.py
index 7bbb1ef..83b4ded 100644
--- a/combo/apps/lingo/models.py
+++ b/combo/apps/lingo/models.py
@@ -373,8 +373,9 @@ class BasketItem(models.Model):
                           data=json.dumps(message), headers=headers, timeout=15)
         r.raise_for_status()
 
-    def notify_payment(self):
-        self.notify('paid')
+    def notify_payment(self, notify_origin=True):
+        if notify_origin:
+            self.notify('paid')
         self.notification_date = timezone.now()
         self.save()
         self.regie.compute_extra_fees(user=self.user)
diff --git a/combo/apps/lingo/templates/lingo/basketitem_error_list.html b/combo/apps/lingo/templates/lingo/basketitem_error_list.html
index b410297..2f4f5e4 100644
--- a/combo/apps/lingo/templates/lingo/basketitem_error_list.html
+++ b/combo/apps/lingo/templates/lingo/basketitem_error_list.html
@@ -34,6 +34,10 @@
   <td>
     {% if object.transaction_status != 99 %}
       <a href="{% url 'lingo-manager-homepage' %}?q={{ object.bank_transaction_id }}">{% trans "See transaction" %}</a>
+      {% if object.source_url %}
+        <br />
+        <a href="{% url 'lingo-manager-basket-item-mark-as-notified' object.pk %}">{% trans "Mark as notified" %}</a>
+      {% endif %}
     {% endif %}
   </td>
 </tr>
diff --git a/combo/apps/lingo/urls.py b/combo/apps/lingo/urls.py
index a0d0a61..902d895 100644
--- a/combo/apps/lingo/urls.py
+++ b/combo/apps/lingo/urls.py
@@ -26,11 +26,13 @@ from .manager_views import (RegieListView, RegieCreateView, RegieUpdateView,
                             RegieDeleteView, TransactionListView, BasketItemErrorListView,
                             download_transactions_csv, PaymentBackendListView,
                             PaymentBackendCreateView, PaymentBackendUpdateView,
-                            PaymentBackendDeleteView)
+                            PaymentBackendDeleteView, BasketItemMarkAsNotifiedView)
 
 lingo_manager_urls = [
     url('^$', TransactionListView.as_view(), name='lingo-manager-homepage'),
     url('^payments/error/$', BasketItemErrorListView.as_view(), name='lingo-manager-payment-error-list'),
+    url(r'^item/(?P<item_id>\d+)/mark-as-notified/$',
+        BasketItemMarkAsNotifiedView.as_view(), name='lingo-manager-basket-item-mark-as-notified'),
     url('^transactions/download-csv/$', download_transactions_csv, name='lingo-manager-transactions-download'),
     url('^regies/$', RegieListView.as_view(), name='lingo-manager-regie-list'),
     url('^regies/add/$', RegieCreateView.as_view(), name='lingo-manager-regie-add'),
diff --git a/tests/test_lingo_manager.py b/tests/test_lingo_manager.py
index 05ab92c..2c7769c 100644
--- a/tests/test_lingo_manager.py
+++ b/tests/test_lingo_manager.py
@@ -3,9 +3,9 @@
 import datetime
 
 from django.contrib.auth.models import User
-from django.core.wsgi import get_wsgi_application
 from django.utils.timezone import now
-from webtest import TestApp
+
+import mock
 import pytest
 
 import eopayment
@@ -364,33 +364,54 @@ def test_basketitem_error_list(app, admin_user, payment_backend):
         bank_transaction_id='bank_id_5_bis',
         amount=55555)
     transaction5_bis.items.add(item5_bis)
-
-    # item with payment_date, no notification_date, but a cancellation_date, in the past
-    # => not displayed
+    # item with payment_date but no notification_date, in the past
+    # => displayed
     item6 = BasketItem.objects.create(
         user=user,
         regie=regie,
         subject='item 6',
-        source_url='http://example.net/6',
+        source_url='',  # without source_url
         amount=6,
         payment_date=date_in_past,
-        notification_date=None,
-        cancellation_date=date_now)
+        notification_date=None)
     transaction6 = Transaction.objects.create(
         order_id='order id 6',
         bank_transaction_id='bank_id_6',
         amount=6)
     transaction6.items.add(item6)
 
+    # item with payment_date, no notification_date, but a cancellation_date, in the past
+    # => not displayed
+    item7 = BasketItem.objects.create(
+        user=user,
+        regie=regie,
+        subject='item 7',
+        source_url='http://example.net/7',
+        amount=7,
+        payment_date=date_in_past,
+        notification_date=None,
+        cancellation_date=date_now)
+    transaction7 = Transaction.objects.create(
+        order_id='order id 7',
+        bank_transaction_id='bank_id_7',
+        amount=7)
+    transaction7.items.add(item7)
+
     app = login(app)
     resp = app.get('/manage/lingo/payments/error/', status=200)
-    assert list(resp.context['object_list']) == [item5, item3, item2]
+    assert list(resp.context['object_list']) == [item6, item5, item3, item2]
+    assert '<a href="%s">' % item6.source_url not in resp.text
     assert '<a href="%s">' % item5.source_url in resp.text
     assert '<a href="%s">' % item3.source_url in resp.text
     assert '<a href="%s">' % item2.source_url not in resp.text
+    assert '/manage/lingo/?q=%s' % transaction6.bank_transaction_id in resp.text
     assert '/manage/lingo/?q=%s' % transaction5.bank_transaction_id in resp.text
     assert '/manage/lingo/?q=%s' % transaction3.bank_transaction_id not in resp.text
     assert '/manage/lingo/?q=%s' % transaction22.bank_transaction_id not in resp.text
+    assert '/manage/lingo/item/%s/mark-as-notified/' % item6.pk not in resp.text
+    assert '/manage/lingo/item/%s/mark-as-notified/' % item5.pk in resp.text
+    assert '/manage/lingo/item/%s/mark-as-notified/' % item3.pk not in resp.text
+    assert '/manage/lingo/item/%s/mark-as-notified/' % item2.pk not in resp.text
 
 
 def test_basketitem_error_list_search(app, admin_user, payment_backend):
@@ -428,6 +449,63 @@ def test_basketitem_error_list_search(app, admin_user, payment_backend):
     assert list(resp.context['object_list']) == [item]
 
 
+def test_basketitem_mark_as_notified(app, admin_user, payment_backend):
+    regie = Regie.objects.create(
+        label='test-regie', slug='test-regie', payment_backend=payment_backend)
+    user = User.objects.create_user('dimebag', 'dime@bag.pan', 'pwd')
+
+    date_now = now()
+    date_in_past = date_now - datetime.timedelta(minutes=300)
+
+    item = BasketItem.objects.create(
+        user=user,
+        regie=regie,
+        subject='item',
+        source_url='http://example.net/',
+        amount=42,
+        payment_date=date_in_past,
+        notification_date=date_in_past)
+
+    app = login(app)
+
+    # notification_date is not None
+    resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=404)
+    # payment_date is not old enough
+    item.notification_date = None
+    item.payment_date = date_now
+    item.save()
+    resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=404)
+    # cancellation_date is not None
+    item.payment_date = date_in_past
+    item.cancellation_date = date_now
+    item.save()
+    resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=404)
+    # notification_date and cancellation_date are None, payment_date is old enough
+    # but webservice_url is not None
+    item.cancellation_date = None
+    item.save()
+    regie.webservice_url = 'http://example.net'
+    regie.save()
+    resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=404)
+    # notification_date and cancellation_date are None, payment_date is old enough
+    # but source_url is not set
+    regie.webservice_url = ''
+    regie.save()
+    item.source_url = ''
+    item.save()
+    resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=404)
+
+    # source_url is set
+    item.source_url = 'http://example.com'
+    item.save()
+    with mock.patch('combo.apps.lingo.models.BasketItem.notify') as mock_notify:
+        resp = app.get('/manage/lingo/item/%s/mark-as-notified/' % item.pk, status=302)
+    assert mock_notify.call_args_list == []
+    assert resp.location.endswith('/manage/lingo/payments/error/')
+    item.refresh_from_db()
+    assert item.notification_date is not None
+
+
 def test_configure_tipi_cell(app, admin_user):
     page = Page(title='tipi', slug='tipi', template_name='standard')
     page.save()
-- 
2.20.1

