lingo: notify new remote invoices (#13122)
This commit is contained in:
parent
83d2de7030
commit
26beade166
|
@ -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)
|
|
@ -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,71 @@ 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 get_notification_namespace(self):
|
||||
return 'invoice-%s' % self.slug
|
||||
|
||||
def get_notification_id(self, invoice):
|
||||
return '%s:%s' % (self.get_notification_namespace(), invoice['id'])
|
||||
|
||||
def get_notification_reminder_id(self, invoice):
|
||||
return '%s:reminder-%s' % (self.get_notification_namespace(), invoice['id'])
|
||||
|
||||
def notify_invoice(self, user, invoice):
|
||||
now = timezone.now()
|
||||
remind_delta = timezone.timedelta(days=settings.LINGO_NEW_INVOICES_REMIND_DELTA)
|
||||
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_id = self.get_notification_id(invoice)
|
||||
notification_reminder_id = self.get_notification_reminder_id(invoice)
|
||||
if pay_limit_date < now:
|
||||
# invoice is out of date
|
||||
Notification.forget(user, notification_id)
|
||||
Notification.forget(user, notification_reminder_id)
|
||||
else:
|
||||
# invoice can be paid
|
||||
if pay_limit_date > now + remind_delta:
|
||||
message = _('Invoice %s to pay') % invoice['label']
|
||||
else:
|
||||
message = _('Reminder: invoice %s to pay') % invoice['label']
|
||||
notification_id = notification_reminder_id
|
||||
Notification.notify(user,
|
||||
summary=message,
|
||||
id=notification_id,
|
||||
url=items_page_url,
|
||||
end_timestamp=pay_limit_date)
|
||||
return notification_id
|
||||
|
||||
def notify_new_remote_invoices(self):
|
||||
pending_invoices = self.get_remote_pending_invoices()
|
||||
notification_ids = []
|
||||
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:
|
||||
notification_ids.append(
|
||||
self.notify_invoice(user, invoice))
|
||||
# clear old notifications for invoice not in the source anymore
|
||||
Notification.objects.namespace(self.get_notification_namespace())\
|
||||
.exclude(external_id__in=notification_ids) \
|
||||
.forget()
|
||||
|
||||
|
||||
class BasketItem(models.Model):
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
|
||||
|
|
|
@ -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 © <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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -42,6 +45,21 @@ def login(username='admin', password='admin'):
|
|||
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')
|
||||
assert Notification.objects.count() == 1
|
||||
|
@ -255,3 +273,105 @@ 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=20)).strftime(datetime_format)
|
||||
new_pay_limit_date = (invoice_now + timedelta(days=5)).strftime(datetime_format)
|
||||
FAKE_PENDING_INVOICES = {
|
||||
"data":
|
||||
{
|
||||
"admin": {
|
||||
"invoices": [
|
||||
{
|
||||
'id': '01',
|
||||
'label': '010101',
|
||||
'total_amount': '10',
|
||||
'amount': '10',
|
||||
'created': creation_date,
|
||||
'pay_limit_date': pay_limit_date,
|
||||
'has_pdf': False,
|
||||
},
|
||||
{
|
||||
'id': '011',
|
||||
'label': '0101011',
|
||||
'total_amount': '1.5',
|
||||
'amount': '1.5',
|
||||
'created': creation_date,
|
||||
'pay_limit_date': pay_limit_date,
|
||||
'has_pdf': False,
|
||||
}
|
||||
]
|
||||
},
|
||||
'admin2': {
|
||||
'invoices': [
|
||||
{
|
||||
'id': '02',
|
||||
'label': '020202',
|
||||
'total_amount': '2.0',
|
||||
'amount': '2.0',
|
||||
'created': creation_date,
|
||||
'pay_limit_date': pay_limit_date,
|
||||
'has_pdf': False,
|
||||
}
|
||||
]
|
||||
},
|
||||
'foo': {
|
||||
'invoices': [
|
||||
{
|
||||
|
||||
'id': 'O3',
|
||||
'label': '030303',
|
||||
'total_amount': '42',
|
||||
'amount': '42',
|
||||
'created': creation_date,
|
||||
'pay_limit_date': pay_limit_date,
|
||||
'has_pdf': False,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
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).visible().new().count() == 2
|
||||
assert Notification.objects.filter(external_id__startswith='invoice-%s:reminder-' % regie.slug).count() == 0
|
||||
assert Notification.objects.count() == 2
|
||||
for notif in Notification.objects.all():
|
||||
assert notif.url == '', notif.id
|
||||
|
||||
page = Page(title='Active Items', slug='active_items', template_name='standard')
|
||||
page.save()
|
||||
cell = ActiveItems(page=page, placeholder='content', order=0)
|
||||
cell.save()
|
||||
|
||||
for user in FAKE_PENDING_INVOICES['data']:
|
||||
for invoice in FAKE_PENDING_INVOICES['data'][user]['invoices']:
|
||||
invoice['pay_limit_date'] = new_pay_limit_date
|
||||
|
||||
# create remind notifications
|
||||
regie.notify_new_remote_invoices()
|
||||
assert Notification.objects.exclude(external_id__startswith='invoice-%s:reminder-' % regie.slug) \
|
||||
.visible().count() == 0
|
||||
assert Notification.objects.filter(external_id__startswith='invoice-%s:reminder-' % regie.slug) \
|
||||
.visible().new().count() == 2
|
||||
assert Notification.objects.count() == 4
|
||||
|
||||
# url appeared on new new reminder notifications
|
||||
assert len([notif for notif in Notification.objects.all() if notif.url == page.get_online_url()]) == 2
|
||||
|
||||
# be sure the are no more reminders created
|
||||
regie.notify_new_remote_invoices()
|
||||
assert Notification.objects.count() == 4
|
||||
|
|
Loading…
Reference in New Issue