lingo: notify new remote invoices (#13122)

This commit is contained in:
Serghei Mihai 2017-11-14 18:48:44 +01:00
parent f2719b3a68
commit 834e1e8612
4 changed files with 151 additions and 2 deletions

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
#
# lingo - basket and payment system
# Copyright (C) 2018 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from django.core.management.base import BaseCommand
from combo.apps.lingo.models import Regie
class Command(BaseCommand):
def handle(self, *args, **kwargs):
logger = logging.getLogger(__name__)
for regie in Regie.objects.exclude(webservice_url=''):
try:
regie.notify_new_remote_invoices()
except Exception, e:
logger.exception('error while notifying new remote invoices: %s', e)

View File

@ -32,14 +32,17 @@ from django.conf import settings
from django.db import models
from django.forms import models as model_forms, Select
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.utils import timezone, dateparse
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.utils.http import urlencode
from django.contrib.auth.models import User
from combo.data.fields import RichTextField
from combo.data.models import CellBase
from combo.data.library import register_cell_class
from combo.utils import NothingInCacheException, aes_hex_encrypt, requests
from combo.apps.notifications.models import Notification
EXPIRED = 9999
@ -203,6 +206,51 @@ class Regie(models.Model):
extra_fee=True,
user_cancellable=False).save()
def get_remote_pending_invoices(self):
if not self.is_remote():
return {}
url = self.webservice_url + '/users/with-pending-invoices/'
response = requests.get(url, remote_service='auto', cache_duration=0,
log_errors=False)
if not response.ok:
return {}
return response.json()['data']
def notify_invoice(self, user, invoice):
now = timezone.now()
remind_delta = timezone.timedelta(days=settings.LINGO_NEW_INVOICES_REMIND_DELTA)
invoice_id = 'invoice-%s:%s' % (self.slug, invoice['id'])
reminder_id = 'reminder-%s' % invoice_id
pay_limit_date = timezone.make_aware(dateparse.parse_datetime(invoice['pay_limit_date']))
active_items_cell = ActiveItems.objects.first()
if active_items_cell:
items_page_url = active_items_cell.page.get_online_url()
else:
items_page_url = ''
# notification should be created
if pay_limit_date > now:
notification = Notification.notify(user, _('Invoice %s to pay') % invoice['label'], id=invoice_id,
url=items_page_url, end_timestamp=invoice['pay_limit_date'])
if pay_limit_date <= now + remind_delta or not notification.acked:
# create remind notification
Notification.notify(user, _('Reminder: invoice %s to pay') % invoice['label'],
id=reminder_id, url=items_page_url,
end_timestamp=invoice['pay_limit_date'])
else:
Notification.forget(user, invoice_id)
Notification.forget(user, reminder_id)
def notify_new_remote_invoices(self):
pending_invoices = self.get_remote_pending_invoices()
for uuid, items in pending_invoices.iteritems():
try:
user = User.objects.get(username=uuid)
except User.DoesNotExist:
continue
for invoice in items['invoices']:
if Decimal(invoice['total_amount']) >= self.payment_min_amount:
self.notify_invoice(user, invoice)
class BasketItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)

View File

@ -285,6 +285,9 @@ COMBO_MAP_TILE_URLTEMPLATE = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
# default combo map attribution
COMBO_MAP_ATTRIBUTION = 'Map data &copy; <a href="https://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
# default delta, in days, for invoice remind notifications
LINGO_NEW_INVOICES_REMIND_DELTA = 7
# timeout used in python-requests call, in seconds
# we use 28s by default: timeout just before web server, which is usually 30s
REQUESTS_TIMEOUT = 28

View File

@ -1,6 +1,8 @@
import json
import mock
import pytest
from decimal import Decimal
from django.contrib.auth.models import User
from django.test.client import RequestFactory
@ -11,6 +13,7 @@ from django.test import Client
from combo.data.models import Page
from combo.apps.notifications.models import Notification, NotificationsCell
from combo.apps.lingo.models import Regie, ActiveItems
pytestmark = pytest.mark.django_db
@ -41,6 +44,19 @@ def login(username='admin', password='admin'):
resp = client.post('/login/', {'username': username, 'password': password})
assert resp.status_code == 302
@pytest.fixture
def regie():
try:
regie = Regie.objects.get(slug='remote')
except Regie.DoesNotExist:
regie = Regie()
regie.label = 'Remote'
regie.slug = 'remote'
regie.description = 'remote'
regie.payment_min_amount = Decimal(2.0)
regie.service = 'dummy'
regie.save()
return regie
def test_notification_api(user, user2):
notification = Notification.notify(user, 'notifoo')
@ -232,7 +248,6 @@ def test_notification_ws_check_urls():
assert reverse('api-notification-forget',
kwargs={'notification_id': 'noti1'}) == '/api/notification/forget/noti1/'
def test_notification_id_and_origin(user):
login()
@ -255,3 +270,52 @@ def test_notification_id_and_origin(user):
result = notify({'summary': 'foo', 'id': 'foo:foo', 'origin': 'bar'})
assert result['err'] == 0
@mock.patch('combo.apps.lingo.models.requests.get')
def test_notify_remote_items(mock_get, app, user, user2, regie):
datetime_format = '%Y-%m-%dT%H:%M:%S'
invoice_now = now()
creation_date = (invoice_now - timedelta(days=1)).strftime(datetime_format)
pay_limit_date = (invoice_now + timedelta(days=5)).strftime(datetime_format)
FAKE_PENDING_INVOICES = {
"data" : {"admin": {"invoices": [{'id': '01', 'label': '010101', 'total_amount': '10',
'created': creation_date, 'pay_limit_date': pay_limit_date},
{'id': '011', 'label': '0101011', 'total_amount': '1.5',
'created': creation_date, 'pay_limit_date': pay_limit_date}]},
'admin2': {'invoices': [{'id': '02', 'label': '020202', 'total_amount': '2.0',
'created': creation_date, 'pay_limit_date': pay_limit_date}]},
'foo': {'invoices': [{'id': 'O3', 'label': '030303', 'total_amount': '42',
'created': creation_date, 'pay_limit_date': pay_limit_date}]}
}
}
mock_response = mock.Mock(status_code=200, content=json.dumps(FAKE_PENDING_INVOICES))
mock_response.json.return_value = FAKE_PENDING_INVOICES
mock_get.return_value = mock_response
regie.notify_new_remote_invoices()
assert mock_get.call_count == 0
regie.webservice_url = 'http://example.org/regie' # is_remote
regie.save()
regie.notify_new_remote_invoices()
assert Notification.objects.filter(external_id__startswith='invoice-%s' % regie.slug).count() == 2
for notif in Notification.objects.all():
print notif, notif.external_id
assert Notification.objects.exclude(external_id__startswith='reminder-').count() == 2
for notif in Notification.objects.all():
assert notif.url == ''
page = Page(title='Active Items', slug='active_items', template_name='standard')
page.save()
cell = ActiveItems(page=page, placeholder='content', order=0)
cell.save()
# create remind notifications
regie.notify_new_remote_invoices()
assert Notification.objects.filter(external_id__startswith='reminder-').count() == 2
for notif in Notification.objects.all():
assert notif.url == page.get_online_url()
# be sure the are no more reminders created
regie.notify_new_remote_invoices()
assert Notification.objects.filter(external_id__startswith='reminder-').count() == 2
assert Notification.objects.count() == 4