invoicing: user mapping on payer model (#78015)

This commit is contained in:
Lauréline Guérin 2023-06-08 15:41:45 +02:00
parent 4f5cb4552c
commit abddf1175f
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 165 additions and 50 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 %}

View File

@ -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 '