invoicing: filter journal (#73688) #19
|
@ -15,10 +15,11 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import django_filters
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from lingo.invoicing.models import Campaign
|
||||
from lingo.invoicing.models import Campaign, DraftInvoiceLine, InvoiceLine, Regie
|
||||
|
||||
|
||||
class CampaignForm(forms.ModelForm):
|
||||
|
@ -44,3 +45,62 @@ class CampaignForm(forms.ModelForm):
|
|||
self.add_error(None, _('Another campaign overlapping this period already exists.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
def regie_queryset(request):
|
||||
return Regie.objects.all()
|
||||
|
||||
|
||||
class AbstractLineFilterSet(django_filters.FilterSet):
|
||||
regie = django_filters.ModelChoiceFilter(
|
||||
label=_('Regie'),
|
||||
queryset=regie_queryset,
|
||||
field_name='invoice__regie',
|
||||
)
|
||||
invoice_number = django_filters.NumberFilter(
|
||||
label=_('Invoice number'),
|
||||
field_name='invoice__number',
|
||||
)
|
||||
invoice_id = django_filters.NumberFilter(
|
||||
label=_('Invoice number'),
|
||||
)
|
||||
pk = django_filters.NumberFilter(
|
||||
label=_('PK'),
|
||||
)
|
||||
payer_external_id = django_filters.CharFilter(
|
||||
label=_('Payer (external ID)'),
|
||||
)
|
||||
user_external_id = django_filters.CharFilter(
|
||||
label=_('User (external ID)'),
|
||||
)
|
||||
status = django_filters.ChoiceFilter(
|
||||
label=_('Status'),
|
||||
choices=[
|
||||
('success', _('Success')),
|
||||
('warning', _('Warning')),
|
||||
('error', _('Error')),
|
||||
],
|
||||
widget=forms.RadioSelect,
|
||||
empty_label=_('all'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.pool = kwargs.pop('pool')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.pool.draft:
|
||||
del self.filters['invoice_number']
|
||||
else:
|
||||
del self.filters['invoice_id']
|
||||
|
||||
|
||||
class DraftInvoiceLineFilterSet(AbstractLineFilterSet):
|
||||
class Meta:
|
||||
model = DraftInvoiceLine
|
||||
fields = []
|
||||
|
||||
|
||||
class InvoiceLineFilterSet(AbstractLineFilterSet):
|
||||
class Meta:
|
||||
model = InvoiceLine
|
||||
fields = []
|
||||
|
|
|
@ -25,21 +25,24 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% url 'lingo-manager-invoicing-pool-journal' pk=object.pk pool_pk=pool.pk as journal_url %}
|
||||
<div>
|
||||
{% for line in lines %}
|
||||
{% ifchanged line.invoice_id %}
|
||||
{% if not forloop.first %}</ul>{% endif %}
|
||||
<h3 data-invoice-id="{{ line.invoice_id }}">
|
||||
{% if pool.draft %}
|
||||
{% blocktrans with number=line.invoice_id payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice TMP-{{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %}
|
||||
{% blocktrans with number=line.invoice_id payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice <a href="{{ journal_url }}?invoice_id={{ number }}">TMP-{{ number }}</a> addressed to <a href="{{ journal_url }}?payer_external_id={{ payer }}">{{ payer }}</a>, amount {{ amount }}€{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans with number=line.invoice.format_number payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice {{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %}
|
||||
{% blocktrans with invoice_number=line.invoice.format_number payer=line.invoice.payer amount=line.invoice.total_amount number=line.invoice.number regie_id=line.invoice.regie_id %}Invoice <a href="{{ journal_url }}?invoice_number={{ number }}®ie={{ regie_id }}">{{ invoice_number }}</a> addressed to <a href="{{ journal_url }}?payer_external_id={{ payer }}">{{ payer }}</a>, amount {{ amount }}€{% endblocktrans %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
<ul class="objects-list" data-invoice-id="{{ line.invoice_id }}">
|
||||
{% endifchanged %}
|
||||
<li>
|
||||
#{{ line.pk }} {{ line.user_name }} - {{ line.event_date|date:"d/m/Y" }} - {{ line.label }} ({{ line.total_amount }})
|
||||
<a href="{{ journal_url }}?pk={{ line.pk }}">#{{ line.pk }}</a>
|
||||
<a href="{{ journal_url }}?user_external_id={{ line.user_external_id }}">{{ line.user_name }}</a>
|
||||
- {{ line.event_date|date:"d/m/Y" }} - {{ line.label }} ({{ line.total_amount }})
|
||||
</li>
|
||||
{% if forloop.last %}</ul>{% endif %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -21,60 +21,79 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if pool.status == 'failed' %}
|
||||
<div class="pk-error">
|
||||
<p>{% trans "Error while running pool." %}</p>
|
||||
{% if pool.exception %}<pre>{{ pool.exception }}</pre>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<table class="main pools">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "PK" %}</th>
|
||||
<th>{% trans "Invoice PK" %}</th>
|
||||
<th>{% trans "Event" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
<th>{% trans "Unit amount" %}</th>
|
||||
<th>{% trans "Total amount" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Payer" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in lines %}
|
||||
<tr data-line-id="{{ line.pk }}">
|
||||
<td class="line_id">{{ line.pk }}</td>
|
||||
<td>{{ line.invoice_id|default:'' }}</td>
|
||||
<td>
|
||||
{{ line.event_date|date:"d/m/Y" }} - {{ line.label }}
|
||||
<br />
|
||||
({{ line.slug }})
|
||||
</td>
|
||||
<td>{{ line.quantity }}</td>
|
||||
<td>{{ line.unit_amount }}</td>
|
||||
<td>{{ line.total_amount }}</td>
|
||||
<td>{{ line.user_name }} ({{ line.user_external_id }})</td>
|
||||
<td>{{ line.payer_external_id }}</td>
|
||||
<td class="status">
|
||||
<span class="tag tag-{{ line.status }}">{{ line.get_status_display }}</span>
|
||||
{% if line.status != 'success' %}({{ line.get_error_display }}){% endif %}
|
||||
{% if line.from_injected_line_id %}({% trans "Injected" %}){% endif %}
|
||||
</td>
|
||||
<td><a class="details-toggle">{% trans "see details" %}</a></td>
|
||||
<div>
|
||||
{% if pool.status == 'failed' %}
|
||||
<div class="pk-error">
|
||||
<p>{% trans "Error while running pool." %}</p>
|
||||
{% if pool.exception %}<pre>{{ pool.exception }}</pre>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form class="journal-filters">
|
||||
{{ filterset.form.as_p }}
|
||||
<script>
|
||||
$(function() {
|
||||
$('form.journal-filters input,select').on('change',
|
||||
function() {
|
||||
$(this).parents('form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</form>
|
||||
<table class="main pools">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "PK" %}</th>
|
||||
<th>{% trans "Invoice number" %}</th>
|
||||
<th>{% trans "Event" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
<th>{% trans "Unit amount" %}</th>
|
||||
<th>{% trans "Total amount" %}</th>
|
||||
<th>{% trans "User" %}</th>
|
||||
<th>{% trans "Payer" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr data-details-for-line-id="{{ line.pk }}" style="display: none">
|
||||
<td colspan="10">
|
||||
{% trans "Pricing data:" %}
|
||||
<pre>{{ line.pricing_data|pprint }}</pre>
|
||||
{% trans "Event:" %}
|
||||
<pre>{{ line.event|pprint }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in lines %}
|
||||
<tr data-line-id="{{ line.pk }}">
|
||||
<td class="line_id">{{ line.pk }}</td>
|
||||
<td>
|
||||
{% if pool.draft and line.invoice %}
|
||||
TMP-{{ line.invoice_id }}
|
||||
{% elif line.invoice %}
|
||||
{{ line.invoice.format_number }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ line.event_date|date:"d/m/Y" }} - {{ line.label }}
|
||||
<br />
|
||||
({{ line.slug }})
|
||||
</td>
|
||||
<td>{{ line.quantity }}</td>
|
||||
<td>{{ line.unit_amount }}</td>
|
||||
<td>{{ line.total_amount }}</td>
|
||||
<td>{{ line.user_name }} ({{ line.user_external_id }})</td>
|
||||
<td>{{ line.payer_external_id }}</td>
|
||||
<td class="status">
|
||||
<span class="tag tag-{{ line.status }}">{{ line.get_status_display }}</span>
|
||||
{% if line.status != 'success' %}({{ line.get_error_display }}){% endif %}
|
||||
{% if line.from_injected_line_id %}({% trans "Injected" %}){% endif %}
|
||||
</td>
|
||||
<td><a class="details-toggle">{% trans "see details" %}</a></td>
|
||||
</tr>
|
||||
<tr data-details-for-line-id="{{ line.pk }}" style="display: none">
|
||||
<td colspan="10">
|
||||
{% trans "Pricing data:" %}
|
||||
<pre>{{ line.pricing_data|pprint }}</pre>
|
||||
{% trans "Event:" %}
|
||||
<pre>{{ line.event|pprint }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
$('a.details-toggle').on('click', function() {
|
||||
|
|
|
@ -38,7 +38,7 @@ from django.views.generic import (
|
|||
)
|
||||
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.forms import CampaignForm
|
||||
from lingo.invoicing.forms import CampaignForm, DraftInvoiceLineFilterSet, InvoiceLineFilterSet
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
DraftInvoice,
|
||||
|
@ -341,12 +341,18 @@ class PoolJournalView(DetailView):
|
|||
kwargs['object'] = self.campaign
|
||||
kwargs['pool'] = self.object
|
||||
line_model = InvoiceLine
|
||||
filter_model = InvoiceLineFilterSet
|
||||
if self.object.draft:
|
||||
line_model = DraftInvoiceLine
|
||||
kwargs['lines'] = line_model.objects.filter(pool=self.object).order_by('pk')
|
||||
self.object.error_count = len([line for line in kwargs['lines'] if line.status == 'error'])
|
||||
self.object.warning_count = len([line for line in kwargs['lines'] if line.status == 'warning'])
|
||||
self.object.success_count = len([line for line in kwargs['lines'] if line.status == 'success'])
|
||||
filter_model = DraftInvoiceLineFilterSet
|
||||
all_lines = line_model.objects.filter(pool=self.object).order_by('pk')
|
||||
self.object.error_count = len([line for line in all_lines if line.status == 'error'])
|
||||
self.object.warning_count = len([line for line in all_lines if line.status == 'warning'])
|
||||
self.object.success_count = len([line for line in all_lines if line.status == 'success'])
|
||||
data = self.request.GET or None
|
||||
line_filterset = filter_model(data=data, queryset=all_lines, pool=self.object)
|
||||
kwargs['lines'] = line_filterset.qs if data and [v for v in data.values() if v] else all_lines
|
||||
kwargs['filterset'] = line_filterset
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -123,3 +123,8 @@ table.pools span.tag {
|
|||
background: #f64474;
|
||||
}
|
||||
}
|
||||
|
||||
form.journal-filters li {
|
||||
display: inline;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ INSTALLED_APPS = (
|
|||
'eopayment',
|
||||
'gadjo',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
'lingo.agendas',
|
||||
'lingo.api',
|
||||
'lingo.invoicing',
|
||||
|
|
1
setup.py
1
setup.py
|
@ -165,6 +165,7 @@ setup(
|
|||
'requests',
|
||||
'eopayment>=1.60',
|
||||
'djangorestframework>=3.3, <3.13',
|
||||
'django-filter',
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass={
|
||||
|
|
|
@ -742,7 +742,11 @@ def test_journal_pool(app, admin_user):
|
|||
assert 'tag-error' not in resp
|
||||
|
||||
|
||||
def test_journal_pool_lines(app, admin_user):
|
||||
@pytest.mark.parametrize('draft', [True, False])
|
||||
def test_journal_pool_lines(app, admin_user, draft):
|
||||
invoice_model = DraftInvoice if draft else Invoice
|
||||
line_model = DraftInvoiceLine if draft else InvoiceLine
|
||||
|
||||
campaign = Campaign.objects.create(
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
|
@ -750,16 +754,27 @@ def test_journal_pool_lines(app, admin_user):
|
|||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=True,
|
||||
draft=draft,
|
||||
status='completed',
|
||||
)
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
invoice = DraftInvoice.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool)
|
||||
regie1 = Regie.objects.create(label='Foo1')
|
||||
regie2 = Regie.objects.create(label='Foo1')
|
||||
invoice1 = invoice_model.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie1, pool=pool, payer='payer:1'
|
||||
)
|
||||
invoice2 = invoice_model.objects.create(
|
||||
date_issue=datetime.date.today(), regie=regie2, pool=pool, payer='payer:2'
|
||||
)
|
||||
if not draft:
|
||||
invoice1.number = 42
|
||||
invoice1.save()
|
||||
invoice2.number = 35
|
||||
invoice2.save()
|
||||
|
||||
lines = [
|
||||
DraftInvoiceLine.objects.create(
|
||||
line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice,
|
||||
invoice=invoice1,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
total_amount=1,
|
||||
|
@ -767,7 +782,9 @@ def test_journal_pool_lines(app, admin_user):
|
|||
pool=pool,
|
||||
pricing_data={'foo': 'bar'},
|
||||
event={'event': 'foobar'},
|
||||
)
|
||||
user_external_id='user:1',
|
||||
payer_external_id='payer:1',
|
||||
),
|
||||
]
|
||||
errors = [
|
||||
('AgendaPricingNotFound', {}),
|
||||
|
@ -795,9 +812,8 @@ def test_journal_pool_lines(app, admin_user):
|
|||
]
|
||||
for error, error_details in errors:
|
||||
lines.append(
|
||||
DraftInvoiceLine.objects.create(
|
||||
line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
total_amount=1,
|
||||
|
@ -808,9 +824,27 @@ def test_journal_pool_lines(app, admin_user):
|
|||
'error_details': error_details,
|
||||
},
|
||||
event={'event': 'foobar'},
|
||||
user_external_id='user:1',
|
||||
payer_external_id='payer:2',
|
||||
)
|
||||
)
|
||||
|
||||
lines.append(
|
||||
line_model.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
invoice=invoice2,
|
||||
quantity=1,
|
||||
unit_amount=1,
|
||||
total_amount=1,
|
||||
status='success',
|
||||
pool=pool,
|
||||
pricing_data={'foo': 'bar'},
|
||||
event={'event': 'foobar2'},
|
||||
user_external_id='user:2',
|
||||
payer_external_id='payer:2',
|
||||
),
|
||||
)
|
||||
|
||||
def format_status(value):
|
||||
return (' '.join([v.strip() for v in value.split('\n')])).strip()
|
||||
|
||||
|
@ -917,6 +951,70 @@ def test_journal_pool_lines(app, admin_user):
|
|||
"{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', "
|
||||
"'check_type_group': 'foo-bar', 'reason': 'wrong-kind'}} {'event': 'foobar'}"
|
||||
)
|
||||
assert format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[13].pk).text()) == 'Success'
|
||||
assert (
|
||||
resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[13].pk).text().strip()
|
||||
== "{'foo': 'bar'} {'event': 'foobar2'}"
|
||||
)
|
||||
|
||||
# test filters
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'regie': regie1.pk},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
if draft:
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'invoice_id': invoice1.pk},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
else:
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'invoice_number': invoice1.number},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'pk': lines[0].pk},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'payer_external_id': 'payer:1'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'payer_external_id': 'payer:2'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 13
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'user_external_id': 'user:1'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 13
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'user_external_id': 'user:2'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'status': 'success'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 2
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'status': 'warning'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 1
|
||||
resp = app.get(
|
||||
'/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk),
|
||||
params={'status': 'error'},
|
||||
)
|
||||
assert len(resp.pyquery('tr td.status')) == 11
|
||||
|
||||
|
||||
def test_delete_pool(app, admin_user):
|
||||
|
|
Loading…
Reference in New Issue
plutôt django-filter>=2.4,<2.5 quelle que soit la version de Django (pour suivre Debian 11)