invoicing: user mapping on payer model (#78015)
This commit is contained in:
parent
4f5cb4552c
commit
abddf1175f
|
@ -492,4 +492,20 @@ class PayerForm(NewPayerForm):
|
|||
class PayerMappingForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Payer
|
||||
fields = ['user_fields_mapping']
|
||||
fields = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.instance.cached_carddef_json:
|
||||
return
|
||||
for key, label in self.instance.user_variables:
|
||||
self.fields[key] = forms.ChoiceField(
|
||||
label=label,
|
||||
choices=[('', '-----')] + [(k, v) for k, v in self.instance.carddef_fields.items()],
|
||||
required=False,
|
||||
)
|
||||
|
||||
def save(self):
|
||||
self.instance.user_fields_mapping = {k: self.cleaned_data[k] for k, v in self.instance.user_variables}
|
||||
self.instance.save()
|
||||
return self.instance
|
||||
|
|
|
@ -35,7 +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
|
||||
from lingo.utils.wcs import get_wcs_json, get_wcs_matching_card_model, get_wcs_services
|
||||
|
||||
|
||||
class RegieImportError(Exception):
|
||||
|
@ -77,6 +77,28 @@ class Payer(models.Model):
|
|||
self.slug = generate_slug(self)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
if 'update_fields' in kwargs:
|
||||
# don't populate the cache
|
||||
return
|
||||
|
||||
def populate_cache():
|
||||
if self.carddef_reference:
|
||||
parts = self.carddef_reference.split(':')
|
||||
wcs_key, card_slug = parts[:2]
|
||||
wcs_site = get_wcs_services().get(wcs_key)
|
||||
card_schema = get_wcs_json(wcs_site, 'api/cards/%s/@schema' % card_slug, log_errors='warn')
|
||||
|
||||
if not card_schema:
|
||||
return
|
||||
|
||||
if card_schema.get('err') == 1:
|
||||
return
|
||||
|
||||
self.cached_carddef_json = card_schema
|
||||
self.save(update_fields=['cached_carddef_json'])
|
||||
|
||||
populate_cache()
|
||||
|
||||
@property
|
||||
def base_slug(self):
|
||||
return slugify(self.label)
|
||||
|
@ -108,6 +130,32 @@ class Payer(models.Model):
|
|||
return
|
||||
return result
|
||||
|
||||
@property
|
||||
def carddef_fields(self):
|
||||
if not self.cached_carddef_json:
|
||||
return
|
||||
return {f['varname']: f['label'] for f in self.cached_carddef_json.get('fields') if f['varname']}
|
||||
|
||||
@property
|
||||
def user_variables(self):
|
||||
return [
|
||||
('first_name', _('First name')),
|
||||
('last_name', _('Last name')),
|
||||
('demat', _('Demat')),
|
||||
('direct_debit', _('Direct debit')),
|
||||
]
|
||||
|
||||
@property
|
||||
def user_fields(self):
|
||||
result = []
|
||||
for key, label in self.user_variables:
|
||||
value = ''
|
||||
if self.user_fields_mapping.get(key):
|
||||
varname = self.user_fields_mapping.get(key)
|
||||
value = self.carddef_fields.get(varname) or ''
|
||||
result.append((label, value))
|
||||
return result
|
||||
|
||||
|
||||
class Regie(models.Model):
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
|
|
|
@ -21,56 +21,68 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-settings" aria-selected="true" id="tab-settings" role="tab" tabindex="0">{% trans "Settings" %}</button>
|
||||
<button aria-controls="panel-mapping" aria-selected="false" id="tab-mapping" role="tab" tabindex="-1">{% trans "Mapping" %}</button>
|
||||
<button aria-controls="panel-usage" aria-selected="false" id="tab-usage" role="tab" tabindex="-1">{% trans "Used in regies" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-settings" id="panel-settings" role="tabpanel" tabindex="0">
|
||||
{% if payer.description %}
|
||||
<h3>{% trans "Description" %}</h3>
|
||||
<p>{{ payer.description|linebreaksbr }}</p>
|
||||
{% with carddef_name=payer.carddef_name|default:'' %}
|
||||
<div class="section">
|
||||
<div class="pk-tabs">
|
||||
<div class="pk-tabs--tab-list" role="tablist">
|
||||
<button aria-controls="panel-settings" aria-selected="true" id="tab-settings" role="tab" tabindex="0">{% trans "Settings" %}</button>
|
||||
{% if carddef_name %}
|
||||
<button aria-controls="panel-mapping" aria-selected="false" id="tab-mapping" role="tab" tabindex="-1">{% trans "Mapping" %}</button>
|
||||
{% endif %}
|
||||
<h3>{% trans "Parameters" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Identifier:" %} {{ payer.slug }}</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>
|
||||
<button aria-controls="panel-usage" aria-selected="false" id="tab-usage" role="tab" tabindex="-1">{% trans "Used in regies" %}</button>
|
||||
</div>
|
||||
<div class="pk-tabs--container">
|
||||
|
||||
<div aria-labelledby="tab-mapping" hidden="" id="panel-mapping" role="tabpanel" tabindex="0">
|
||||
<div class="panel--buttons">
|
||||
<a class="pk-button" href="{% url 'lingo-manager-invoicing-payer-edit-mapping' pk=payer.pk %}" rel="popup">{% trans "Edit mapping" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-usage" hidden="" id="panel-usage" role="tabpanel" tabindex="0">
|
||||
{% if regies %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for regie in regies %}
|
||||
<li>
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-detail' pk=regie.pk %}">
|
||||
{{ regie.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<div aria-labelledby="tab-settings" id="panel-settings" role="tabpanel" tabindex="0">
|
||||
{% if payer.description %}
|
||||
<h3>{% trans "Description" %}</h3>
|
||||
<p>{{ payer.description|linebreaksbr }}</p>
|
||||
{% endif %}
|
||||
<h3>{% trans "Parameters" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Identifier:" %} {{ payer.slug }}</li>
|
||||
<li>{% trans "Card Model:" %} <code>{{ carddef_name }}</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>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This Payer is not used yet.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
{% if carddef_name %}
|
||||
<div aria-labelledby="tab-mapping" hidden="" id="panel-mapping" role="tabpanel" tabindex="0">
|
||||
<dl>
|
||||
{% for label, value in payer.user_fields %}
|
||||
<dt><b>{% blocktrans %}{{ label }}:{% endblocktrans %}</b></dt>
|
||||
<dd><pre>{{ value }}</pre></dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
<div class="panel--buttons">
|
||||
<a class="pk-button" href="{% url 'lingo-manager-invoicing-payer-edit-mapping' pk=payer.pk %}" rel="popup">{% trans "Edit mapping" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div aria-labelledby="tab-usage" hidden="" id="panel-usage" role="tabpanel" tabindex="0">
|
||||
{% if regies %}
|
||||
<ul class="objects-list single-links">
|
||||
{% for regie in regies %}
|
||||
<li>
|
||||
<a href="{% url 'lingo-manager-invoicing-regie-detail' pk=regie.pk %}">
|
||||
{{ regie.label }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans trimmed %}
|
||||
This Payer is not used yet.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import copy
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
from unittest import mock
|
||||
|
||||
|
@ -17,6 +18,16 @@ WCS_CARDDEFS_DATA = [
|
|||
{'title': 'Card Model 3', 'slug': 'card_model_3'},
|
||||
]
|
||||
|
||||
WCS_CARDDEF_SCHEMAS = {
|
||||
'card_model_1': {
|
||||
'name': 'Card Model 1',
|
||||
'fields': [
|
||||
{'label': 'Field A', 'varname': 'fielda', 'type': 'string'},
|
||||
{'label': 'Field B', 'varname': 'fieldb', 'type': 'string'},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MockedRequestResponse(mock.Mock):
|
||||
status_code = 200
|
||||
|
@ -28,6 +39,9 @@ class MockedRequestResponse(mock.Mock):
|
|||
def get_data_from_url(url):
|
||||
if '/api/cards/@list' in url:
|
||||
return WCS_CARDDEFS_DATA
|
||||
m_schema = re.match(r'/api/cards/([a-z0-9_]+)/@schema', url)
|
||||
if m_schema:
|
||||
return WCS_CARDDEF_SCHEMAS.get(m_schema.group(1)) or {}
|
||||
return []
|
||||
|
||||
|
||||
|
@ -71,7 +85,7 @@ def test_add_payer(mock_send, 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')
|
||||
payer = Payer.objects.create(label='Foo bar', carddef_reference='foo:bar')
|
||||
payer2 = Payer.objects.create(label='baz')
|
||||
|
||||
app = login(app)
|
||||
|
@ -99,17 +113,34 @@ def test_edit_payer(mock_send, app, admin_user):
|
|||
assert payer.carddef_reference == 'default:card_model_1'
|
||||
|
||||
|
||||
def test_edit_payer_mapping(app, admin_user):
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_edit_payer_mapping(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 '/manage/invoicing/payer/%s/mapping/' % payer.pk not in resp
|
||||
payer.carddef_reference = 'default:card_model_1'
|
||||
payer.save()
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.click(href='/manage/invoicing/payer/%s/mapping/' % payer.pk)
|
||||
resp.form['user_fields_mapping'] = '{"foo": "bar"}'
|
||||
assert len(resp.context['form'].fields) == 4
|
||||
choices = [('', '-----'), ('fielda', 'Field A'), ('fieldb', 'Field B')]
|
||||
assert resp.context['form'].fields['first_name'].choices == choices
|
||||
assert resp.context['form'].fields['last_name'].choices == choices
|
||||
assert resp.context['form'].fields['demat'].choices == choices
|
||||
assert resp.context['form'].fields['direct_debit'].choices == choices
|
||||
resp.form['first_name'] = 'fielda'
|
||||
resp.form['last_name'] = 'fieldb'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/payer/%s/#open:mapping' % payer.pk)
|
||||
payer.refresh_from_db()
|
||||
assert payer.user_fields_mapping == {'foo': 'bar'}
|
||||
assert payer.user_fields_mapping == {
|
||||
'first_name': 'fielda',
|
||||
'last_name': 'fieldb',
|
||||
'demat': '',
|
||||
'direct_debit': '',
|
||||
}
|
||||
|
||||
|
||||
def test_delete_payer(app, admin_user):
|
||||
|
@ -137,7 +168,13 @@ def test_detail_payer(mock_send, app, admin_user):
|
|||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code></code>' in resp
|
||||
|
||||
payer.carddef_reference = 'unknown'
|
||||
payer.carddef_reference = 'unknown:unknown'
|
||||
payer.user_fields_mapping = {
|
||||
'first_name': 'fielda',
|
||||
'last_name': 'fieldb',
|
||||
'demat': '',
|
||||
'direct_debit': '',
|
||||
}
|
||||
payer.save()
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code></code>' in resp
|
||||
|
@ -146,3 +183,5 @@ def test_detail_payer(mock_send, app, admin_user):
|
|||
payer.save()
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert 'Card Model: <code>Card Model 1</code>' in resp
|
||||
assert resp.pyquery('dl dt').text() == 'First name: Last name: Demat: Direct debit:'
|
||||
assert resp.pyquery('dl dd').text() == 'Field A Field B '
|
||||
|
|
Loading…
Reference in New Issue