invoicing: views to manage Payers (#78015)
This commit is contained in:
parent
312d3d288b
commit
d194fd558d
|
@ -20,11 +20,20 @@ from django import forms
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from gadjo.forms.widgets import MultiSelectWidget
|
||||
|
||||
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Regie
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
DraftInvoice,
|
||||
DraftInvoiceLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
Payer,
|
||||
Regie,
|
||||
)
|
||||
|
||||
|
||||
class ExportForm(forms.Form):
|
||||
regies = forms.BooleanField(label=_('Regies'), required=False, initial=True)
|
||||
payers = forms.BooleanField(label=_('Payers'), required=False, initial=True)
|
||||
|
||||
|
||||
class ImportForm(forms.Form):
|
||||
|
@ -434,3 +443,30 @@ class RegieInvoiceFilterSet(django_filters.FilterSet):
|
|||
if value == 'no':
|
||||
return queryset.filter(paid_amount=0)
|
||||
return queryset
|
||||
|
||||
|
||||
class PayerForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Payer
|
||||
fields = [
|
||||
'label',
|
||||
'slug',
|
||||
'description',
|
||||
'carddef_reference',
|
||||
'payer_external_id_prefix',
|
||||
'payer_external_id_template',
|
||||
]
|
||||
|
||||
def clean_slug(self):
|
||||
slug = self.cleaned_data['slug']
|
||||
|
||||
if Payer.objects.filter(slug=slug).exclude(pk=self.instance.pk).exists():
|
||||
raise forms.ValidationError(_('Another payer exists with the same identifier.'))
|
||||
|
||||
return slug
|
||||
|
||||
|
||||
class PayerMappingForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Payer
|
||||
fields = ['user_fields_mapping']
|
||||
|
|
|
@ -68,6 +68,36 @@ class Payer(models.Model):
|
|||
class Meta:
|
||||
ordering = ['label']
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
self.slug = generate_slug(self)
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def base_slug(self):
|
||||
return slugify(self.label)
|
||||
|
||||
def export_json(self):
|
||||
return {
|
||||
'label': self.label,
|
||||
'slug': self.slug,
|
||||
'description': self.description,
|
||||
'carddef_reference': self.carddef_reference,
|
||||
'payer_external_id_prefix': self.payer_external_id_prefix,
|
||||
'payer_external_id_template': self.payer_external_id_template,
|
||||
'user_fields_mapping': self.user_fields_mapping,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def import_json(cls, data):
|
||||
data = copy.deepcopy(data)
|
||||
|
||||
payer, created = cls.objects.update_or_create(slug=data['slug'], defaults=data)
|
||||
return created, payer
|
||||
|
||||
|
||||
class Regie(models.Model):
|
||||
label = models.CharField(_('Label'), max_length=150)
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
{% trans "Regies" %}
|
||||
<p>{% trans "Invoicing regies." %}</p>
|
||||
</a>
|
||||
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-payer-list' %}">
|
||||
{% trans "Payers" %}
|
||||
<p>{% trans "Define here how payers are determined." %}</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
{% extends "lingo/invoicing/manager_payer_list.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-detail' payer.pk %}">{{ payer }}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Payer' %} - {{ payer }}</h2>
|
||||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a href="{% url 'lingo-manager-invoicing-payer-edit' pk=payer.pk %}" rel="popup">{% trans "Edit" %}</a></li>
|
||||
<li><a href="{% url 'lingo-manager-invoicing-payer-export' pk=payer.pk %}">{% trans 'Export' %}</a></li>
|
||||
<li><a href="{% url 'lingo-manager-invoicing-payer-delete' pk=payer.pk %}" rel="popup">{% trans "Delete" %}</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
{% 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>
|
||||
</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>
|
||||
{% endif %}
|
||||
<h3>{% trans "Parameters" %}</h3>
|
||||
<ul>
|
||||
<li>{% trans "Identifier:" %} {{ payer.slug }}</li>
|
||||
<li>{% trans "Card Model:" %} <code>{{ payer.carddef_reference }}</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>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "lingo/invoicing/manager_payer_list.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
{% if object.pk %}
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-detail' payer.pk %}">{{ payer }}</a>
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-edit' payer.pk %}">{% trans "Edit" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-add' %}">{% trans "New payer" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
{% if payer.pk %}
|
||||
<h2>{% trans "Edit payer" %} - {{ payer }}</h2>
|
||||
{% else %}
|
||||
<h2>{% trans "New payer" %}</h2>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Submit" %}</button>
|
||||
{% if object.pk %}
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-payer-detail' payer.pk %}">{% trans 'Cancel' %}</a>
|
||||
{% else %}
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans 'Cancel' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,48 @@
|
|||
{% extends "lingo/invoicing/manager_home.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans "Payers" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans 'Payers' %}</h2>
|
||||
<span class="actions">
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<ul class="extra-actions-menu">
|
||||
<li>
|
||||
<a href="{# url 'lingo-manager-invoicing-payer-import' #}">{% trans 'Import' %}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{# url 'lingo-manager-invoicing-payer-export' #}">{% trans 'Export' %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a rel="popup" href="{% url 'lingo-manager-invoicing-payer-add' %}">{% trans 'New payer' %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if object_list %}
|
||||
<div>
|
||||
<ul class="objects-list single-links">
|
||||
{% for payer in object_list %}
|
||||
<li>
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-detail' pk=payer.pk %}">
|
||||
{{ payer.label }}
|
||||
<span class="extra-info"> [{% trans "identifier:" %} {{ payer.slug }}]</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="big-msg-info">
|
||||
{% blocktrans %}
|
||||
This site doesn't have any payer yet. Click on the "New" button in the top
|
||||
right of the page to add a first one.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "lingo/invoicing/manager_payer_list.html" %}
|
||||
{% load i18n gadjo %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-detail' payer.pk %}">{{ payer }}</a>
|
||||
<a href="{% url 'lingo-manager-invoicing-payer-edit-mapping' payer.pk %}">{% trans "Edit mapping" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Edit mapping" %} - {{ payer }}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form|with_template }}
|
||||
<div class="buttons">
|
||||
<button>{% trans "Submit" %}</button>
|
||||
{% if object.pk %}
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-payer-detail' payer.pk %}">{% trans 'Cancel' %}</a>
|
||||
{% else %}
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-payer-list' %}">{% trans 'Cancel' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -18,6 +18,7 @@ from django.urls import path
|
|||
|
||||
from .views import campaign as campaign_views
|
||||
from .views import home as home_views
|
||||
from .views import payer as payer_views
|
||||
from .views import pool as pool_views
|
||||
from .views import regie as regie_views
|
||||
|
||||
|
@ -146,4 +147,35 @@ urlpatterns = [
|
|||
regie_views.regie_invoice_line_list,
|
||||
name='lingo-manager-invoicing-regie-invoice-line-list',
|
||||
),
|
||||
path('payers/', payer_views.payers_list, name='lingo-manager-invoicing-payer-list'),
|
||||
path(
|
||||
'payer/add/',
|
||||
payer_views.payer_add,
|
||||
name='lingo-manager-invoicing-payer-add',
|
||||
),
|
||||
path(
|
||||
'payer/<int:pk>/',
|
||||
payer_views.payer_detail,
|
||||
name='lingo-manager-invoicing-payer-detail',
|
||||
),
|
||||
path(
|
||||
'payer/<int:pk>/edit/',
|
||||
payer_views.payer_edit,
|
||||
name='lingo-manager-invoicing-payer-edit',
|
||||
),
|
||||
path(
|
||||
'payer/<int:pk>/mapping/',
|
||||
payer_views.payer_edit_mapping,
|
||||
name='lingo-manager-invoicing-payer-edit-mapping',
|
||||
),
|
||||
path(
|
||||
'payer/<int:pk>/delete/',
|
||||
payer_views.payer_delete,
|
||||
name='lingo-manager-invoicing-payer-delete',
|
||||
),
|
||||
path(
|
||||
'payer/<int:pk>/export/',
|
||||
payer_views.payer_export,
|
||||
name='lingo-manager-invoicing-payer-export',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -23,7 +23,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
|
||||
from lingo.agendas.chrono import get_check_status, get_subscriptions
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, InjectedLine, Regie
|
||||
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, InjectedLine, Payer, Regie
|
||||
from lingo.pricing.models import AgendaPricing, AgendaPricingNotFound, PricingError
|
||||
|
||||
|
||||
|
@ -315,11 +315,14 @@ def generate_invoices_from_lines(all_lines, pool):
|
|||
|
||||
def export_site(
|
||||
regies=True,
|
||||
payers=True,
|
||||
):
|
||||
'''Dump site objects to JSON-dumpable dictionnary'''
|
||||
data = {}
|
||||
if regies:
|
||||
data['regies'] = [x.export_json() for x in Regie.objects.all()]
|
||||
if payers:
|
||||
data['payers'] = [x.export_json() for x in Payer.objects.all()]
|
||||
return data
|
||||
|
||||
|
||||
|
@ -328,11 +331,12 @@ def import_site(data):
|
|||
key: collections.defaultdict(list)
|
||||
for key in [
|
||||
'regies',
|
||||
'payers',
|
||||
]
|
||||
}
|
||||
|
||||
with transaction.atomic():
|
||||
for cls, key in ((Regie, 'regies'),):
|
||||
for cls, key in ((Payer, 'payers'), (Regie, 'regies')):
|
||||
objs = data.get(key, [])
|
||||
for obj in objs:
|
||||
created, obj = cls.import_json(obj)
|
||||
|
|
|
@ -90,6 +90,20 @@ class ConfigImportView(FormView):
|
|||
x,
|
||||
),
|
||||
},
|
||||
'payers': {
|
||||
'create_noop': _('No payer created.'),
|
||||
'create': lambda x: ngettext(
|
||||
'A payer has been created.',
|
||||
'%(count)d payers have been created.',
|
||||
x,
|
||||
),
|
||||
'update_noop': _('No payer updated.'),
|
||||
'update': lambda x: ngettext(
|
||||
'A payer has been updated.',
|
||||
'%(count)d payers have been updated.',
|
||||
x,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
global_noop = True
|
||||
|
@ -113,8 +127,8 @@ class ConfigImportView(FormView):
|
|||
else:
|
||||
obj_results['messages'] = message2
|
||||
|
||||
(r_count,) = (len(results['regies']['all']),)
|
||||
if (r_count,) == (1,):
|
||||
(r_count, p_count) = (len(results['regies']['all']), len(results['payers']['all']))
|
||||
if (r_count, p_count) == (1, 0):
|
||||
# only one regie imported, redirect to regie page
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
|
@ -122,11 +136,20 @@ class ConfigImportView(FormView):
|
|||
kwargs={'pk': results['regies']['all'][0].pk},
|
||||
)
|
||||
)
|
||||
if (r_count, p_count) == (0, 1):
|
||||
# only one payer imported, redirect to payer page
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'lingo-manager-invoicing-payer-detail',
|
||||
kwargs={'pk': results['payers']['all'][0].pk},
|
||||
)
|
||||
)
|
||||
|
||||
if global_noop:
|
||||
messages.info(self.request, _('No data found.'))
|
||||
else:
|
||||
messages.info(self.request, results['regies']['messages'])
|
||||
messages.info(self.request, results['payers']['messages'])
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
# 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 datetime
|
||||
import json
|
||||
|
||||
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.models import Payer
|
||||
|
||||
|
||||
class PayersListView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_payer_list.html'
|
||||
model = Payer
|
||||
|
||||
|
||||
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',
|
||||
]
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-payer-detail', args=[self.object.pk])
|
||||
|
||||
|
||||
payer_add = PayerAddView.as_view()
|
||||
|
||||
|
||||
class PayerDetailView(DetailView):
|
||||
template_name = 'lingo/invoicing/manager_payer_detail.html'
|
||||
model = Payer
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['payer'] = self.object
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
payer_detail = PayerDetailView.as_view()
|
||||
|
||||
|
||||
class PayerEditView(UpdateView):
|
||||
template_name = 'lingo/invoicing/manager_payer_form.html'
|
||||
model = Payer
|
||||
form_class = PayerForm
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-payer-detail', args=[self.object.pk])
|
||||
|
||||
|
||||
payer_edit = PayerEditView.as_view()
|
||||
|
||||
|
||||
class PayerEditMappingView(UpdateView):
|
||||
template_name = 'lingo/invoicing/manager_payer_mapping_form.html'
|
||||
model = Payer
|
||||
form_class = PayerMappingForm
|
||||
|
||||
def get_success_url(self):
|
||||
return '%s#open:mapping' % reverse('lingo-manager-invoicing-payer-detail', args=[self.object.pk])
|
||||
|
||||
|
||||
payer_edit_mapping = PayerEditMappingView.as_view()
|
||||
|
||||
|
||||
class PayerDeleteView(DeleteView):
|
||||
template_name = 'lingo/manager_confirm_delete.html'
|
||||
model = Payer
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('lingo-manager-invoicing-payer-list')
|
||||
|
||||
|
||||
payer_delete = PayerDeleteView.as_view()
|
||||
|
||||
|
||||
class PayerExport(DetailView):
|
||||
model = Payer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
response = HttpResponse(content_type='application/json')
|
||||
today = datetime.date.today()
|
||||
attachment = 'attachment; filename="export_payer_{}_{}.json"'.format(
|
||||
self.get_object().slug, today.strftime('%Y%m%d')
|
||||
)
|
||||
response['Content-Disposition'] = attachment
|
||||
json.dump({'payers': [self.get_object().export_json()]}, response, indent=2)
|
||||
return response
|
||||
|
||||
|
||||
payer_export = PayerExport.as_view()
|
|
@ -22,3 +22,7 @@ def test_manager_invoicing_home(app, admin_user):
|
|||
'div#lingo-manager-main div a[href="%s"]' % reverse('lingo-manager-invoicing-regie-list')
|
||||
)
|
||||
assert anchor.text().startswith('Regies')
|
||||
anchor = resp.pyquery(
|
||||
'div#lingo-manager-main div a[href="%s"]' % reverse('lingo-manager-invoicing-payer-list')
|
||||
)
|
||||
assert anchor.text().strip().startswith('Payers')
|
||||
|
|
|
@ -4,7 +4,7 @@ import json
|
|||
import pytest
|
||||
from webtest import Upload
|
||||
|
||||
from lingo.invoicing.models import Regie
|
||||
from lingo.invoicing.models import Payer, Regie
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -25,6 +25,7 @@ def test_export_site(freezer, app, admin_user):
|
|||
site_json = json.loads(resp.text)
|
||||
assert site_json == {
|
||||
'regies': [],
|
||||
'payers': [],
|
||||
}
|
||||
|
||||
Regie.objects.create(label='Foo Bar')
|
||||
|
@ -34,6 +35,15 @@ def test_export_site(freezer, app, admin_user):
|
|||
site_json = json.loads(resp.text)
|
||||
assert len(site_json['regies']) == 1
|
||||
|
||||
resp = app.get('/manage/invoicing/export/')
|
||||
resp.form['regies'] = False
|
||||
resp.form['payers'] = False
|
||||
resp = resp.form.submit()
|
||||
|
||||
site_json = json.loads(resp.text)
|
||||
assert 'regies' not in site_json
|
||||
assert 'payers' not in site_json
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2023-06-02')
|
||||
def test_import_regie(app, admin_user):
|
||||
|
@ -93,3 +103,63 @@ def test_import_regie(app, admin_user):
|
|||
resp = resp.form.submit().follow()
|
||||
assert '3 regies have been created. No regie updated.' in resp.text
|
||||
assert Regie.objects.count() == 3
|
||||
|
||||
|
||||
@pytest.mark.freeze_time('2023-06-02')
|
||||
def test_import_payer(app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.click(href='/manage/invoicing/payer/%s/export/' % payer.pk)
|
||||
assert resp.headers['content-type'] == 'application/json'
|
||||
assert resp.headers['content-disposition'] == 'attachment; filename="export_payer_foo-bar_20230602.json"'
|
||||
payer_export = resp.text
|
||||
|
||||
# existing payer
|
||||
resp = app.get('/manage/invoicing/', status=200)
|
||||
resp = resp.click('Import')
|
||||
resp.form['config_json'] = Upload('export.json', payer_export.encode('utf-8'), 'application/json')
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.follow()
|
||||
assert 'No payer created. A payer has been updated.' not in resp.text
|
||||
assert Payer.objects.count() == 1
|
||||
|
||||
# new payer
|
||||
Payer.objects.all().delete()
|
||||
resp = app.get('/manage/invoicing/', status=200)
|
||||
resp = resp.click('Import')
|
||||
resp.form['config_json'] = Upload('export.json', payer_export.encode('utf-8'), 'application/json')
|
||||
resp = resp.form.submit()
|
||||
payer = Payer.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.follow()
|
||||
assert 'A payer has been created. No payer updated.' not in resp.text
|
||||
assert Payer.objects.count() == 1
|
||||
|
||||
# multiple payers
|
||||
payers = json.loads(payer_export)
|
||||
payers['payers'].append(copy.copy(payers['payers'][0]))
|
||||
payers['payers'].append(copy.copy(payers['payers'][0]))
|
||||
payers['payers'][1]['label'] = 'Foo bar 2'
|
||||
payers['payers'][1]['slug'] = 'foo-bar-2'
|
||||
payers['payers'][2]['label'] = 'Foo bar 3'
|
||||
payers['payers'][2]['slug'] = 'foo-bar-3'
|
||||
|
||||
resp = app.get('/manage/invoicing/', status=200)
|
||||
resp = resp.click('Import')
|
||||
resp.form['config_json'] = Upload('export.json', json.dumps(payers).encode('utf-8'), 'application/json')
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/')
|
||||
resp = resp.follow()
|
||||
assert '2 payers have been created. A payer has been updated.' in resp.text
|
||||
assert Payer.objects.count() == 3
|
||||
|
||||
Payer.objects.all().delete()
|
||||
resp = app.get('/manage/invoicing/', status=200)
|
||||
resp = resp.click('Import')
|
||||
resp.form['config_json'] = Upload('export.json', json.dumps(payers).encode('utf-8'), 'application/json')
|
||||
resp = resp.form.submit().follow()
|
||||
assert '3 payers have been created. No payer updated.' in resp.text
|
||||
assert Payer.objects.count() == 3
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Payer
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_add_payer(app, admin_user):
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/')
|
||||
resp = resp.click('Payers')
|
||||
resp = resp.click('New payer')
|
||||
resp.form['label'] = 'Foo bar'
|
||||
resp.form['description'] = 'a little description'
|
||||
resp.form['carddef_reference'] = 'foo'
|
||||
resp.form['payer_external_id_prefix'] = 'payer:'
|
||||
resp.form['payer_external_id_template'] = 'bar'
|
||||
resp = resp.form.submit()
|
||||
payer = Payer.objects.latest('pk')
|
||||
assert resp.location.endswith('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
assert payer.label == 'Foo bar'
|
||||
assert payer.slug == 'foo-bar'
|
||||
assert payer.description == 'a little description'
|
||||
assert payer.carddef_reference == 'foo'
|
||||
assert payer.payer_external_id_prefix == 'payer:'
|
||||
assert payer.payer_external_id_template == 'bar'
|
||||
|
||||
|
||||
def test_edit_payer(app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar', carddef_reference='foo')
|
||||
payer2 = Payer.objects.create(label='baz')
|
||||
|
||||
app = login(app)
|
||||
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)
|
||||
resp.form['label'] = 'Foo bar baz'
|
||||
resp.form['slug'] = payer2.slug
|
||||
resp = resp.form.submit()
|
||||
assert resp.context['form'].errors['slug'] == ['Another payer exists with the same identifier.']
|
||||
|
||||
resp.form['slug'] = 'baz2'
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
payer.refresh_from_db()
|
||||
assert payer.label == 'Foo bar baz'
|
||||
assert payer.slug == 'baz2'
|
||||
|
||||
|
||||
def test_edit_payer_mapping(app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
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"}'
|
||||
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'}
|
||||
|
||||
|
||||
def test_delete_payer(app, admin_user):
|
||||
payer = Payer.objects.create(label='Foo bar')
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/payer/%s/' % payer.pk)
|
||||
resp = resp.click(href='/manage/invoicing/payer/%s/delete/' % payer.pk)
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith('/manage/invoicing/payers/')
|
||||
assert Payer.objects.exists() is False
|
|
@ -0,0 +1,92 @@
|
|||
import copy
|
||||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Payer, Regie
|
||||
from lingo.invoicing.utils import export_site, import_site
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_import_export(app):
|
||||
Regie.objects.create(label='Foo Bar')
|
||||
Payer.objects.create(label='Foo Bar')
|
||||
|
||||
data = export_site()
|
||||
assert len(data['regies']) == 1
|
||||
assert len(data['payers']) == 1
|
||||
import_site(data={})
|
||||
assert Regie.objects.count() == 1
|
||||
assert Payer.objects.count() == 1
|
||||
|
||||
|
||||
def test_import_export_regies(app):
|
||||
payload = export_site()
|
||||
assert len(payload['regies']) == 0
|
||||
|
||||
regie = Regie.objects.create(label='Foo bar')
|
||||
|
||||
payload = export_site()
|
||||
assert len(payload['regies']) == 1
|
||||
|
||||
regie.delete()
|
||||
assert not Regie.objects.exists()
|
||||
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert Regie.objects.count() == 1
|
||||
regie = Regie.objects.first()
|
||||
assert regie.label == 'Foo bar'
|
||||
assert regie.slug == 'foo-bar'
|
||||
|
||||
# update
|
||||
update_payload = copy.deepcopy(payload)
|
||||
update_payload['regies'][0]['label'] = 'Foo bar Updated'
|
||||
import_site(update_payload)
|
||||
regie.refresh_from_db()
|
||||
assert regie.label == 'Foo bar Updated'
|
||||
|
||||
# insert another regie
|
||||
regie.slug = 'foo-bar-updated'
|
||||
regie.save()
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert Regie.objects.count() == 2
|
||||
regie = Regie.objects.latest('pk')
|
||||
assert regie.label == 'Foo bar'
|
||||
assert regie.slug == 'foo-bar'
|
||||
|
||||
|
||||
def test_import_export_payers(app):
|
||||
payload = export_site()
|
||||
assert len(payload['payers']) == 0
|
||||
|
||||
payer = Payer.objects.create(label='Foo bar', user_fields_mapping={'foo': 'bar'})
|
||||
|
||||
payload = export_site()
|
||||
assert len(payload['payers']) == 1
|
||||
|
||||
payer.delete()
|
||||
assert not Payer.objects.exists()
|
||||
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert Payer.objects.count() == 1
|
||||
payer = Payer.objects.first()
|
||||
assert payer.label == 'Foo bar'
|
||||
assert payer.slug == 'foo-bar'
|
||||
assert payer.user_fields_mapping == {'foo': 'bar'}
|
||||
|
||||
# update
|
||||
update_payload = copy.deepcopy(payload)
|
||||
update_payload['payers'][0]['label'] = 'Foo bar Updated'
|
||||
import_site(update_payload)
|
||||
payer.refresh_from_db()
|
||||
assert payer.label == 'Foo bar Updated'
|
||||
|
||||
# insert another payer
|
||||
payer.slug = 'foo-bar-updated'
|
||||
payer.save()
|
||||
import_site(copy.deepcopy(payload))
|
||||
assert Payer.objects.count() == 2
|
||||
payer = Payer.objects.latest('pk')
|
||||
assert payer.label == 'Foo bar'
|
||||
assert payer.slug == 'foo-bar'
|
||||
assert payer.user_fields_mapping == {'foo': 'bar'}
|
Loading…
Reference in New Issue