lingo: add cell to self declare and pay invoices (#13492)
This commit is contained in:
parent
003d1315be
commit
d107771bba
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import ckeditor.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('data', '0020_auto_20160928_1152'),
|
||||
('lingo', '0023_auto_20160928_1152'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SelfDeclaredInvoicePayment',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('placeholder', models.CharField(max_length=20)),
|
||||
('order', models.PositiveIntegerField()),
|
||||
('slug', models.SlugField(verbose_name='Slug', blank=True)),
|
||||
('extra_css_class', models.CharField(max_length=100, verbose_name='Extra classes for CSS styling', blank=True)),
|
||||
('public', models.BooleanField(default=True, verbose_name='Public')),
|
||||
('restricted_to_unlogged', models.BooleanField(default=False, verbose_name='Restrict to unlogged users')),
|
||||
('regie', models.CharField(max_length=50, verbose_name='Regie', blank=True)),
|
||||
('title', models.CharField(max_length=200, verbose_name='Title', blank=True)),
|
||||
('text', ckeditor.fields.RichTextField(null=True, verbose_name='Text', blank=True)),
|
||||
('groups', models.ManyToManyField(to='auth.Group', verbose_name='Groups', blank=True)),
|
||||
('page', models.ForeignKey(to='data.Page')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Self declared invoice payment',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -33,7 +33,7 @@ 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.core.exceptions import PermissionDenied
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.utils.http import urlencode
|
||||
|
||||
from ckeditor.fields import RichTextField
|
||||
|
@ -61,7 +61,7 @@ def build_remote_item(data, regie):
|
|||
return RemoteItem(id=data.get('id'), regie=regie,
|
||||
creation_date=data['created'],
|
||||
payment_limit_date=data['pay_limit_date'],
|
||||
display_id=data['display_id'],
|
||||
display_id=data.get('display_id'),
|
||||
total_amount=data.get('total_amount'),
|
||||
amount=data.get('amount'),
|
||||
subject=data.get('label'),
|
||||
|
@ -137,8 +137,13 @@ class Regie(models.Model):
|
|||
if not self.is_remote():
|
||||
return self.basketitem_set.get(pk=invoice_id)
|
||||
url = self.webservice_url + '/invoice/%s/' % invoice_id
|
||||
item = requests.get(url, user=user, remote_service='auto').json()
|
||||
return build_remote_item(item.get('data'), self)
|
||||
response = requests.get(url, user=user, remote_service='auto')
|
||||
if response.status_code == 404:
|
||||
raise ObjectDoesNotExist()
|
||||
response.raise_for_status()
|
||||
if response.json().get('data') is None:
|
||||
raise ObjectDoesNotExist()
|
||||
return build_remote_item(response.json().get('data'), self)
|
||||
|
||||
def get_invoice_pdf(self, user, invoice_id):
|
||||
"""
|
||||
|
@ -219,7 +224,7 @@ class RemoteItem(object):
|
|||
self.payment_limit_date = parser.parse(payment_limit_date)
|
||||
self.total_amount = Decimal(total_amount)
|
||||
self.amount = Decimal(amount)
|
||||
self.display_id = display_id
|
||||
self.display_id = display_id or self.id
|
||||
self.subject = subject
|
||||
self.has_pdf = has_pdf
|
||||
self.online_payment = online_payment
|
||||
|
@ -237,7 +242,7 @@ class RemoteItem(object):
|
|||
|
||||
@property
|
||||
def crypto_id(self):
|
||||
return aes_hex_encrypt(settings.SECRET_KEY, self.id)
|
||||
return aes_hex_encrypt(settings.SECRET_KEY, str(self.id))
|
||||
|
||||
|
||||
class Transaction(models.Model):
|
||||
|
@ -411,7 +416,7 @@ class Items(CellBase):
|
|||
|
||||
@classmethod
|
||||
def is_enabled(cls):
|
||||
return Regie.objects.count() > 0
|
||||
return Regie.objects.exclude(webservice_url='').count() > 0
|
||||
|
||||
def is_relevant(self, context):
|
||||
return (getattr(context['request'], 'user', None) and context['request'].user.is_authenticated())
|
||||
|
@ -419,9 +424,9 @@ class Items(CellBase):
|
|||
def get_default_form_class(self):
|
||||
fields = ['title', 'text']
|
||||
widgets = {}
|
||||
if Regie.objects.count() > 1:
|
||||
if Regie.objects.exclude(webservice_url='').count() > 1:
|
||||
regies = [('', _('All'))]
|
||||
regies.extend([(r.slug, r.label) for r in Regie.objects.all()])
|
||||
regies.extend([(r.slug, r.label) for r in Regie.objects.exclude(webservice_url='')])
|
||||
widgets['regie'] = Select(choices=regies)
|
||||
fields.insert(0, 'regie')
|
||||
return model_forms.modelform_factory(self.__class__, fields=fields, widgets=widgets)
|
||||
|
@ -431,8 +436,12 @@ class Items(CellBase):
|
|||
return [Regie.objects.get(slug=self.regie)]
|
||||
return Regie.objects.all()
|
||||
|
||||
def get_invoices(self, user):
|
||||
return []
|
||||
|
||||
def get_cell_extra_context(self, context):
|
||||
ctx = {'title': self.title, 'text': self.text}
|
||||
ctx = super(Items, self).get_cell_extra_context(context)
|
||||
ctx.update({'title': self.title, 'text': self.text})
|
||||
items = self.get_invoices(user=context['user'])
|
||||
items.sort(key=lambda i: i.creation_date, reverse=True)
|
||||
ctx.update({'items': items})
|
||||
|
@ -469,3 +478,20 @@ class ActiveItems(Items):
|
|||
for r in self.get_regies():
|
||||
items.extend(r.get_invoices(user))
|
||||
return items
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class SelfDeclaredInvoicePayment(Items):
|
||||
user_dependant = False
|
||||
template_name = 'lingo/combo/self-declared-invoice-payment.html'
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Self declared invoice payment')
|
||||
|
||||
def is_relevant(self, context):
|
||||
return self.is_enabled()
|
||||
|
||||
def render(self, context):
|
||||
context['synchronous'] = True
|
||||
context['page_path'] = context['request'].path
|
||||
return super(Items, self).render(context)
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{% load i18n %}
|
||||
{% if title %}<h2>{{ title }}</h2>{% endif %}
|
||||
{% if text %}<p>{{ text|safe }}</p>{% endif %}
|
||||
<form class="quixote" action="{% url 'lingo-self-invoice' cell_id=cell.id %}">
|
||||
<div class="widget">
|
||||
<div class="title">
|
||||
{% trans 'Invoice Number' %}
|
||||
<span class="required">*</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<input type="text" name="invoice-number" required="required">
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget">
|
||||
<div class="title">
|
||||
{% trans 'Invoice Amount' %}
|
||||
<span class="required">*</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<input type="text" name="invoice-amount" required="required" pattern="[1-9][0-9]*([,\.]?[0-9]+)?">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="page_path" value="{{page_path}}">
|
||||
<div class="buttons">
|
||||
<button>{% trans "Pay" %}</button>
|
||||
</div>
|
||||
</form>
|
|
@ -21,7 +21,7 @@ from combo.urls_utils import decorated_includes, manager_required
|
|||
from .views import (RegiesApiView, AddBasketItemApiView, PayView, CallbackView,
|
||||
ReturnView, ItemDownloadView, ItemView, CancelItemView,
|
||||
RemoveBasketItemApiView, ValidateTransactionApiView,
|
||||
CancelTransactionApiView)
|
||||
CancelTransactionApiView, SelfInvoiceView)
|
||||
from .manager_views import (RegieListView, RegieCreateView, RegieUpdateView,
|
||||
RegieDeleteView, TransactionListView, ManagerHomeView)
|
||||
|
||||
|
@ -56,4 +56,6 @@ urlpatterns = patterns('',
|
|||
ItemDownloadView.as_view(), name='download-item-pdf'),
|
||||
url(r'^lingo/item/(?P<regie_id>[\w,-]+)/(?P<item_crypto_id>[\w,-]+)/$',
|
||||
ItemView.as_view(), name='view-item'),
|
||||
url(r'^lingo/self-invoice/(?P<cell_id>\w+)/$', SelfInvoiceView.as_view(),
|
||||
name='lingo-self-invoice'),
|
||||
)
|
||||
|
|
|
@ -21,12 +21,13 @@ import logging
|
|||
import requests
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
|
||||
from django.http import HttpResponseForbidden, Http404
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils import timezone
|
||||
from django.utils.encoding import force_text
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import View, DetailView, ListView, TemplateView
|
||||
from django.conf import settings
|
||||
|
@ -45,7 +46,7 @@ except ImportError:
|
|||
UserSAMLIdentifier = None
|
||||
|
||||
from .models import (Regie, BasketItem, Transaction, TransactionOperation,
|
||||
LingoBasketCell)
|
||||
LingoBasketCell, SelfDeclaredInvoicePayment)
|
||||
|
||||
def get_eopayment_object(request, regie):
|
||||
options = regie.service_options
|
||||
|
@ -578,3 +579,47 @@ class CancelItemView(DetailView):
|
|||
except requests.exceptions.HTTPError:
|
||||
messages.error(request, _('An error occured when removing the item.'))
|
||||
return HttpResponseRedirect(get_basket_url())
|
||||
|
||||
|
||||
class SelfInvoiceView(View):
|
||||
http_method_names = ['get', 'options']
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(SelfInvoiceView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
obj = SelfDeclaredInvoicePayment.objects.get(id=kwargs['cell_id'])
|
||||
except SelfDeclaredInvoicePayment.DoesNotExist:
|
||||
raise Http404()
|
||||
invoice_id = request.GET.get('invoice-number', '')
|
||||
invoice_amount = request.GET.get('invoice-amount', '').replace(',', '.')
|
||||
msg = None
|
||||
url = None
|
||||
try:
|
||||
invoice_amount = Decimal(invoice_amount)
|
||||
except ArithmeticError:
|
||||
invoice_amount = '-'
|
||||
msg = _('Sorry, the provided amount is invalid.')
|
||||
else:
|
||||
for regie in obj.get_regies():
|
||||
try:
|
||||
invoice = regie.get_invoice(None, invoice_id)
|
||||
except ObjectDoesNotExist:
|
||||
continue
|
||||
if invoice.total_amount != invoice_amount:
|
||||
continue
|
||||
url = reverse('view-item', kwargs={
|
||||
'regie_id': regie.id, 'item_crypto_id': invoice.crypto_id})
|
||||
break
|
||||
else:
|
||||
msg = _('Sorry, no invoice were found with that number and amount.')
|
||||
if request.GET.get('ajax') == 'on':
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'url': url, 'msg': msg and force_text(msg)}))
|
||||
return response
|
||||
if url:
|
||||
return HttpResponseRedirect(url)
|
||||
messages.warning(request, msg)
|
||||
return HttpResponseRedirect(request.GET.get('page_path') or '/')
|
||||
|
|
|
@ -130,4 +130,39 @@ $(function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
$('.selfdeclaredinvoicepayment form').on('combo:load-invoice', window.displayPopup);
|
||||
$('.selfdeclaredinvoicepayment form').on('submit', function(event) {
|
||||
var url = $(this).attr('action');
|
||||
var data = $(this).serialize();
|
||||
var $form = $(this);
|
||||
$.ajax({url: url + '?ajax=on',
|
||||
xhrFields: { withCredentials: true },
|
||||
data: data,
|
||||
async: true,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
if (data.msg) {
|
||||
var err_msg = $('<p></p>');
|
||||
err_msg.text(data.msg);
|
||||
$(err_msg).dialog({
|
||||
dialogClass: 'alert',
|
||||
modal: true,
|
||||
width: 'auto',
|
||||
buttons: {
|
||||
'×': function() {
|
||||
$(this).dialog('destroy');
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
$form.data('url', data.url);
|
||||
$form.trigger('combo:load-invoice', event);
|
||||
},
|
||||
error: function(error) {
|
||||
window.console && console.log(':(', error);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,19 @@
|
|||
<div id="title"><h1>{{ page.title }}</h1></div>
|
||||
<div id="menu">{% block menu %}{% show_menu %}{% endblock %}</div>
|
||||
<div id="content">
|
||||
|
||||
{% block messages %}
|
||||
{% if messages %}
|
||||
<div id="messages">
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block combo-content %}
|
||||
{% placeholder "content" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.utils import timezone
|
|||
|
||||
from combo.utils import check_query, aes_hex_encrypt
|
||||
from combo.data.models import Page
|
||||
from combo.apps.lingo.models import Regie, ActiveItems
|
||||
from combo.apps.lingo.models import Regie, ActiveItems, SelfDeclaredInvoicePayment
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -173,3 +173,44 @@ def test_wrong_crypted_item(mock_get, remote_regie, app):
|
|||
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
||||
mock_get.return_value = mock_json
|
||||
resp = app.get('/lingo/item/%s/%s/' % (remote_regie.id, 'zrzer854sfaear45e6rzerzerzef'), status=404)
|
||||
|
||||
@mock.patch('combo.apps.lingo.models.requests.get')
|
||||
def test_self_declared_invoice(mock_get, app, remote_regie):
|
||||
mock_json = mock.Mock()
|
||||
mock_json.json.return_value = {'err': 0, 'data': INVOICES[0]}
|
||||
mock_get.return_value = mock_json
|
||||
|
||||
page = Page(title='xxx', slug='test-self-invoice', template_name='standard')
|
||||
page.save()
|
||||
cell = SelfDeclaredInvoicePayment(regie='remote', page=page, placeholder='content', order=0)
|
||||
cell.save()
|
||||
|
||||
resp = app.get('/test-self-invoice/')
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Sorry, no invoice were found with that number and amount.'
|
||||
|
||||
resp = app.get('/test-self-invoice/')
|
||||
resp.form['invoice-number'] = 'F201601'
|
||||
resp.form['invoice-amount'] = 'FOOBAR' # wrong format
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Sorry, the provided amount is invalid.'
|
||||
|
||||
resp = app.get('/test-self-invoice/')
|
||||
resp.form['invoice-number'] = 'F201602' # invalid number
|
||||
resp.form['invoice-amount'] = '123.45'
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Sorry, no invoice were found with that number and amount.'
|
||||
|
||||
resp = app.get('/test-self-invoice/')
|
||||
resp.form['invoice-number'] = 'F201601'
|
||||
resp.form['invoice-amount'] = '123.46' # invalid amount
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Sorry, no invoice were found with that number and amount.'
|
||||
|
||||
resp = app.get('/test-self-invoice/')
|
||||
resp.form['invoice-number'] = 'F201601'
|
||||
resp.form['invoice-amount'] = '123.45'
|
||||
resp = resp.form.submit()
|
||||
path = urlparse.urlparse(resp.location).path
|
||||
assert path.startswith('/lingo/item/%s/' % remote_regie.id)
|
||||
resp = resp.follow()
|
||||
|
|
Loading…
Reference in New Issue