invoicing: select a Card Model in Payer form (#78015)
This commit is contained in:
parent
739aaf17cd
commit
4f5cb4552c
|
@ -29,6 +29,7 @@ from lingo.invoicing.models import (
|
|||
Payer,
|
||||
Regie,
|
||||
)
|
||||
from lingo.utils.wcs import get_wcs_options
|
||||
|
||||
|
||||
class ExportForm(forms.Form):
|
||||
|
@ -445,7 +446,29 @@ class RegieInvoiceFilterSet(django_filters.FilterSet):
|
|||
return queryset
|
||||
|
||||
|
||||
class PayerForm(forms.ModelForm):
|
||||
class NewPayerForm(forms.ModelForm):
|
||||
carddef_reference = forms.ChoiceField(
|
||||
label=_('Linked card model'),
|
||||
required=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Payer
|
||||
fields = [
|
||||
'label',
|
||||
'description',
|
||||
'carddef_reference',
|
||||
'payer_external_id_prefix',
|
||||
'payer_external_id_template',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
card_models = get_wcs_options('/api/cards/@list')
|
||||
self.fields['carddef_reference'].choices = [('', '-----')] + card_models
|
||||
|
||||
|
||||
class PayerForm(NewPayerForm):
|
||||
class Meta:
|
||||
model = Payer
|
||||
fields = [
|
||||
|
|
|
@ -35,6 +35,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from lingo.agendas.chrono import ChronoError, lock_events_check
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.utils.misc import LingoImportError, generate_slug
|
||||
from lingo.utils.wcs import get_wcs_matching_card_model
|
||||
|
||||
|
||||
class RegieImportError(Exception):
|
||||
|
@ -98,6 +99,15 @@ class Payer(models.Model):
|
|||
payer, created = cls.objects.update_or_create(slug=data['slug'], defaults=data)
|
||||
return created, payer
|
||||
|
||||
@property
|
||||
def carddef_name(self):
|
||||
if not self.carddef_reference:
|
||||
return
|
||||
result = get_wcs_matching_card_model(self.carddef_reference)
|
||||
if not result:
|
||||
return
|
||||
return result
|
||||
|
||||
|
||||
class Regie(models.Model):
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<h3>{% trans "Parameters" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Identifier:" %} {{ payer.slug }}</li>
|
||||
<li>{% trans "Card Model:" %} <code>{{ payer.carddef_reference }}</code></li>
|
||||
<li>{% trans "Card Model:" %} <code>{{ payer.carddef_name|default:'' }}</code></li>
|
||||
<li>{% trans "Prefix for payer external id:" %} <pre>{{ payer.payer_external_id_prefix }}</pre></li>
|
||||
<li>{% trans "Template for payer external id:" %} <pre>{{ payer.payer_external_id_template }}</pre></li>
|
||||
</ul>
|
||||
|
|
|
@ -21,7 +21,7 @@ from django.http import HttpResponse
|
|||
from django.urls import reverse
|
||||
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
|
||||
|
||||
from lingo.invoicing.forms import PayerForm, PayerMappingForm
|
||||
from lingo.invoicing.forms import NewPayerForm, PayerForm, PayerMappingForm
|
||||
from lingo.invoicing.models import Payer
|
||||
|
||||
|
||||
|
@ -36,13 +36,7 @@ payers_list = PayersListView.as_view()
|
|||
class PayerAddView(CreateView):
|
||||
template_name = 'lingo/invoicing/manager_payer_form.html'
|
||||
model = Payer
|
||||
fields = [
|
||||
'label',
|
||||
'description',
|
||||
'carddef_reference',
|
||||
'payer_external_id_prefix',
|
||||
'payer_external_id_template',
|
||||
]
|
||||
form_class = NewPayerForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-payer-detail', args=[self.object.pk])
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# lingo - payment and billing system
|
||||
# Copyright (C) 2023 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 json
|
||||
|
||||
from django.conf import settings
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from lingo.utils import requests
|
||||
|
||||
|
||||
def is_wcs_enabled(cls):
|
||||
return hasattr(settings, 'KNOWN_SERVICES') and settings.KNOWN_SERVICES.get('wcs')
|
||||
|
||||
|
||||
def get_wcs_services():
|
||||
if not is_wcs_enabled(None):
|
||||
return {}
|
||||
return settings.KNOWN_SERVICES.get('wcs')
|
||||
|
||||
|
||||
def get_wcs_json(wcs_site, path, log_errors=True):
|
||||
if wcs_site is None:
|
||||
# no site specified (probably an import referencing a not yet deployed
|
||||
# site)
|
||||
return {'err': 1, 'err_desc': 'no-wcs-site'}
|
||||
try:
|
||||
response = requests.get(
|
||||
path,
|
||||
remote_service=wcs_site,
|
||||
without_user=True,
|
||||
headers={'accept': 'application/json'},
|
||||
log_errors=log_errors,
|
||||
)
|
||||
response.raise_for_status()
|
||||
except RequestException as e:
|
||||
if e.response is not None:
|
||||
try:
|
||||
# return json if available (on 404 responses by example)
|
||||
return e.response.json()
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
return {'err': 1, 'data': None}
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_wcs_options(url):
|
||||
references = []
|
||||
for wcs_key, wcs_site in sorted(get_wcs_services().items(), key=lambda x: x[1]['title']):
|
||||
site_title = wcs_site.get('title')
|
||||
response_json = get_wcs_json(wcs_site, url)
|
||||
if response_json.get('err') == 1:
|
||||
continue
|
||||
response_json = response_json.get('data')
|
||||
if not response_json:
|
||||
continue
|
||||
for element in response_json:
|
||||
slug = element.get('slug')
|
||||
title = element.get('title')
|
||||
if len(get_wcs_services()) == 1:
|
||||
label = title
|
||||
else:
|
||||
label = '%s : %s' % (site_title, title)
|
||||
reference = '%s:%s' % (wcs_key, slug)
|
||||
references.append((reference, label))
|
||||
return references
|
||||
|
||||
|
||||
def get_wcs_matching_card_model(ref):
|
||||
card_models = get_wcs_options('/api/cards/@list')
|
||||
for carddef_reference, card_label in card_models:
|
||||
if carddef_reference == ref:
|
||||
return card_label
|
|
@ -1,3 +1,8 @@
|
|||
import copy
|
||||
import json
|
||||
import urllib.parse
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Payer, Regie
|
||||
|
@ -6,14 +11,51 @@ from tests.utils import login
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_add_payer(app, admin_user):
|
||||
WCS_CARDDEFS_DATA = [
|
||||
{'title': 'Card Model 1', 'slug': 'card_model_1', 'custom_views': [{'id': 'foo', 'text': 'bar'}]},
|
||||
{'title': 'Card Model 2', 'slug': 'card_model_2'},
|
||||
{'title': 'Card Model 3', 'slug': 'card_model_3'},
|
||||
]
|
||||
|
||||
|
||||
class MockedRequestResponse(mock.Mock):
|
||||
status_code = 200
|
||||
|
||||
def json(self):
|
||||
return json.loads(self.content)
|
||||
|
||||
|
||||
def get_data_from_url(url):
|
||||
if '/api/cards/@list' in url:
|
||||
return WCS_CARDDEFS_DATA
|
||||
return []
|
||||
|
||||
|
||||
def mocked_requests_send(request, **kwargs):
|
||||
request_url = urllib.parse.urlparse(request.url)
|
||||
data = copy.deepcopy(get_data_from_url(request_url.path))
|
||||
|
||||
if not isinstance(data, list):
|
||||
return MockedRequestResponse(content=json.dumps(data))
|
||||
|
||||
return MockedRequestResponse(content=json.dumps({'data': data}))
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_add_payer(mock_send, app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/')
|
||||
resp = resp.click('Payers')
|
||||
resp = resp.click('New payer')
|
||||
assert resp.context['form'].fields['carddef_reference'].widget.choices == [
|
||||
('', '-----'),
|
||||
('default:card_model_1', 'Card Model 1'),
|
||||
('default:card_model_2', 'Card Model 2'),
|
||||
('default:card_model_3', 'Card Model 3'),
|
||||
]
|
||||
resp.form['label'] = 'Foo bar'
|
||||
resp.form['description'] = 'a little description'
|
||||
resp.form['carddef_reference'] = 'foo'
|
||||
resp.form['carddef_reference'] = 'default:card_model_1'
|
||||
resp.form['payer_external_id_prefix'] = 'payer:'
|
||||
resp.form['payer_external_id_template'] = 'bar'
|
||||
resp = resp.form.submit()
|
||||
|
@ -22,12 +64,13 @@ def test_add_payer(app, admin_user):
|
|||
assert payer.label == 'Foo bar'
|
||||
assert payer.slug == 'foo-bar'
|
||||
assert payer.description == 'a little description'
|
||||
assert payer.carddef_reference == 'foo'
|
||||
assert payer.carddef_reference == 'default:card_model_1'
|
||||
assert payer.payer_external_id_prefix == 'payer:'
|
||||
assert payer.payer_external_id_template == 'bar'
|
||||
|
||||
|
||||
def test_edit_payer(app, admin_user):
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_edit_payer(mock_send, app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar', carddef_reference='foo')
|
||||
payer2 = Payer.objects.create(label='baz')
|
||||
|
||||
|
@ -35,8 +78,15 @@ def test_edit_payer(app, admin_user):
|
|||
resp = app.get('/manage/invoicing/payers/')
|
||||
resp = resp.click(href='/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.click(href='/manage/invoicing/payer/%s/edit/' % payer.pk)
|
||||
assert resp.context['form'].fields['carddef_reference'].widget.choices == [
|
||||
('', '-----'),
|
||||
('default:card_model_1', 'Card Model 1'),
|
||||
('default:card_model_2', 'Card Model 2'),
|
||||
('default:card_model_3', 'Card Model 3'),
|
||||
]
|
||||
resp.form['label'] = 'Foo bar baz'
|
||||
resp.form['slug'] = payer2.slug
|
||||
resp.form['carddef_reference'] = 'default:card_model_1'
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors['slug'] == ['Another payer exists with the same identifier.']
|
||||
|
||||
|
@ -46,6 +96,7 @@ def test_edit_payer(app, admin_user):
|
|||
payer.refresh_from_db()
|
||||
assert payer.label == 'Foo bar baz'
|
||||
assert payer.slug == 'baz2'
|
||||
assert payer.carddef_reference == 'default:card_model_1'
|
||||
|
||||
|
||||
def test_edit_payer_mapping(app, admin_user):
|
||||
|
@ -77,3 +128,21 @@ def test_delete_payer(app, admin_user):
|
|||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert '/manage/invoicing/payer/%s/delete/' % payer.pk not in resp
|
||||
resp = app.get('/manage/invoicing/payer/%s/delete/' % payer.pk, status=404)
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_detail_payer(mock_send, app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar')
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code></code>' in resp
|
||||
|
||||
payer.carddef_reference = 'unknown'
|
||||
payer.save()
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code></code>' in resp
|
||||
|
||||
payer.carddef_reference = 'default:card_model_1'
|
||||
payer.save()
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code>Card Model 1</code>' in resp
|
||||
|
|
Loading…
Reference in New Issue