invoicing: list non invoiced lines (#73741) #22
|
@ -9,6 +9,7 @@
|
|||
{% block appbar %}
|
||||
<h2>{% trans "Campaigns" %}</h2>
|
||||
<span class="actions">
|
||||
<a href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' %}">{% trans 'Non invoiced lines' %}</a>
|
||||
<a rel="popup" href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans 'New campaign' %}</a>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
{% extends "lingo/invoicing/manager_campaign_list.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' %}">{% trans "Non invoiced lines" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Non invoiced lines" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<table class="main lines">
|
||||
<thead>
|
||||
<tr>
|
||||
<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 object_list %}
|
||||
<tr data-line-id="{% if line.status == 'injected' %}injected{% else %}line{% endif %}-{{ line.pk }}">
|
||||
<td>
|
||||
{{ line.event_date|date:"d/m/Y" }} - {{ line.label }}
|
||||
<br />
|
||||
{% if line.status == 'error'%}
|
||||
<a href="{% url 'lingo-manager-invoicing-pool-journal' pk=line.campaign_id pool_pk=line.pool_id %}?pk={{ line.pk }}">({{ line.slug }})</a>
|
||||
{% else %}
|
||||
({{ line.slug }})
|
||||
{% endif %}
|
||||
</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 }}">{% spaceless %}
|
||||
{% if line.status == 'error'%}
|
||||
{% trans "Error" %}
|
||||
{% else %}
|
||||
{% trans "Injected" %}
|
||||
{% endif %}
|
||||
{% endspaceless %}</span>
|
||||
{% if line.status == 'error'%}
|
||||
({{ line.error_display }})
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if line.status != 'injected' %}<a class="details-toggle" href="#">{% trans "see details" %}</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% if line.status != 'injected' %}
|
||||
<tr data-details-for-line-id="line-{{ 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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% include "gadjo/pagination.html" %}
|
||||
</div>
|
||||
<script>
|
||||
$(function() {
|
||||
$(document).on('click', 'a.details-toggle', function(event) {
|
||||
var line_id = $(this).parents('tr').data('line-id');
|
||||
var $details = $('tr[data-details-for-line-id=' + line_id + ']');
|
||||
if ($details.is(':visible')) {
|
||||
$(this).text('{% trans "see details" %}');
|
||||
$details.hide();
|
||||
} else {
|
||||
$(this).text('{% trans "hide details" %}');
|
||||
$details.show();
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -39,7 +39,7 @@
|
|||
});
|
||||
</script>
|
||||
</form>
|
||||
<table class="main pools">
|
||||
<table class="main lines">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "PK" %}</th>
|
||||
|
|
|
@ -94,4 +94,9 @@ urlpatterns = [
|
|||
views.line_set_error_status,
|
||||
name='lingo-manager-invoicing-line-set-error-status',
|
||||
),
|
||||
path(
|
||||
'campaigns/non-invoiced-lines/',
|
||||
views.non_invoiced_line_list,
|
||||
name='lingo-manager-invoicing-non-invoiced-line-list',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -19,8 +19,9 @@ import datetime
|
|||
import json
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.postgres.fields import JSONField
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, IntegerField, OuterRef, Subquery, Value
|
||||
from django.db.models import CharField, Count, IntegerField, OuterRef, Subquery, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
@ -43,6 +44,7 @@ from lingo.invoicing.models import (
|
|||
Campaign,
|
||||
DraftInvoice,
|
||||
DraftInvoiceLine,
|
||||
InjectedLine,
|
||||
InvoiceLine,
|
||||
Pool,
|
||||
Regie,
|
||||
|
@ -495,3 +497,54 @@ class LineSetErrorStatusView(DetailView):
|
|||
|
||||
|
||||
line_set_error_status = LineSetErrorStatusView.as_view()
|
||||
|
||||
|
||||
class NonInvoicedLineListView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_non_invoiced_line_list.html'
|
||||
paginate_by = 100
|
||||
|
||||
def get_queryset(self):
|
||||
fields = [
|
||||
'pk',
|
||||
'event_date',
|
||||
'slug',
|
||||
'label',
|
||||
'quantity',
|
||||
'unit_amount',
|
||||
'total_amount',
|
||||
'user_external_id',
|
||||
'payer_external_id',
|
||||
'user_name',
|
||||
'event',
|
||||
'pricing_data',
|
||||
'status',
|
||||
'pool_id',
|
||||
]
|
||||
qs1 = InvoiceLine.objects.filter(status='error', error_status='').values(*fields)
|
||||
qs2 = (
|
||||
InjectedLine.objects.filter(invoiceline__isnull=True)
|
||||
.annotate(
|
||||
user_name=Value('', output_field=CharField()),
|
||||
event=Value({}, output_field=JSONField()),
|
||||
pricing_data=Value({}, output_field=JSONField()),
|
||||
status=Value('injected', output_field=CharField()),
|
||||
pool_id=Value(0, output_field=IntegerField()),
|
||||
)
|
||||
.values(*fields)
|
||||
)
|
||||
qs = qs1.union(qs2).order_by('event_date', 'user_external_id', 'label', 'pk')
|
||||
return qs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
pools = Pool.objects.filter(draft=False).in_bulk()
|
||||
for line in context['object_list']:
|
||||
if line['status'] == 'error':
|
||||
line['error_display'] = InvoiceLine(
|
||||
status=line['status'], pricing_data=line['pricing_data']
|
||||
).get_error_display()
|
||||
line['campaign_id'] = pools[line['pool_id']].campaign_id
|
||||
return context
|
||||
|
||||
|
||||
non_invoiced_line_list = NonInvoicedLineListView.as_view()
|
||||
|
|
|
@ -106,7 +106,7 @@ div.test-tool-result .infonotice h3 {
|
|||
|
||||
#panel-pools span.tag,
|
||||
h2#pool-title span.tag,
|
||||
table.pools span.tag {
|
||||
table.lines span.tag {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
border-radius: 1ex;
|
||||
|
|
|
@ -3,7 +3,16 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
|
||||
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Pool, Regie
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
DraftInvoice,
|
||||
DraftInvoiceLine,
|
||||
InjectedLine,
|
||||
Invoice,
|
||||
InvoiceLine,
|
||||
Pool,
|
||||
Regie,
|
||||
)
|
||||
from tests.utils import login
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -1277,3 +1286,150 @@ def test_set_error_status_line(app, admin_user):
|
|||
app.get(
|
||||
'/manage/invoicing/ajax/campaign/%s/pool/%s/line/%s/reset/' % (campaign.pk, pool.pk, 0), status=404
|
||||
)
|
||||
|
||||
|
||||
def test_non_invoiced_line_list(app, admin_user):
|
||||
regie = Regie.objects.create(label='Regie')
|
||||
|
||||
campaign = Campaign.objects.create(
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
)
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=True,
|
||||
)
|
||||
other_campaign = Campaign.objects.create(
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
)
|
||||
other_pool = Pool.objects.create(
|
||||
campaign=other_campaign,
|
||||
draft=False,
|
||||
)
|
||||
|
||||
# not invoiced
|
||||
InjectedLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
slug='event-2022-09-01',
|
||||
label='Event 2022-09-01',
|
||||
quantity=2,
|
||||
unit_amount=1.5,
|
||||
total_amount=3,
|
||||
user_external_id='user:1',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
)
|
||||
# not invoiced, but linked in a DraftInvoiceLine
|
||||
injected_line2 = InjectedLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
slug='event-2022-09-02',
|
||||
label='Event 2022-09-02',
|
||||
quantity=2,
|
||||
unit_amount=1.5,
|
||||
total_amount=3,
|
||||
user_external_id='user:1',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
)
|
||||
DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 2),
|
||||
slug='event-2022-09-02',
|
||||
label='Event 2022-09-02',
|
||||
quantity=2,
|
||||
unit_amount=1.5,
|
||||
total_amount=3,
|
||||
pool=pool,
|
||||
from_injected_line=injected_line2,
|
||||
)
|
||||
# invoiced, as linked in a non draft pool
|
||||
injected_line3 = InjectedLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
slug='event-2022-09-03',
|
||||
label='Event 2022-09-03',
|
||||
quantity=2,
|
||||
unit_amount=1.5,
|
||||
total_amount=3,
|
||||
user_external_id='user:1',
|
||||
payer_external_id='payer:1',
|
||||
regie=regie,
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 3),
|
||||
slug='event-2022-09-03',
|
||||
label='Event 2022-09-03',
|
||||
quantity=2,
|
||||
unit_amount=1.5,
|
||||
total_amount=3,
|
||||
pool=other_pool,
|
||||
from_injected_line=injected_line3,
|
||||
)
|
||||
|
||||
# non fixed error
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 4),
|
||||
slug='event-2022-09-04',
|
||||
label='Event 2022-09-04',
|
||||
quantity=0,
|
||||
unit_amount=0,
|
||||
total_amount=0,
|
||||
pool=other_pool,
|
||||
status='error',
|
||||
)
|
||||
# fixed or ignored errors
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 5),
|
||||
slug='event-2022-09-05',
|
||||
label='Event 2022-09-05',
|
||||
quantity=0,
|
||||
unit_amount=0,
|
||||
total_amount=0,
|
||||
pool=other_pool,
|
||||
status='error',
|
||||
error_status='fixed',
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 6),
|
||||
slug='event-2022-09-06',
|
||||
label='Event 2022-09-06',
|
||||
quantity=0,
|
||||
unit_amount=0,
|
||||
total_amount=0,
|
||||
pool=other_pool,
|
||||
status='error',
|
||||
error_status='ignored',
|
||||
)
|
||||
# not errors
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 7),
|
||||
slug='event-2022-09-07',
|
||||
label='Event 2022-09-07',
|
||||
quantity=0,
|
||||
unit_amount=0,
|
||||
total_amount=0,
|
||||
pool=other_pool,
|
||||
status='success',
|
||||
)
|
||||
InvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 8),
|
||||
slug='event-2022-09-08',
|
||||
label='Event 2022-09-08',
|
||||
quantity=0,
|
||||
unit_amount=0,
|
||||
total_amount=0,
|
||||
pool=other_pool,
|
||||
status='warning',
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/invoicing/campaigns/non-invoiced-lines/')
|
||||
assert 'event-2022-09-01' in resp
|
||||
assert 'event-2022-09-02' in resp
|
||||
assert 'event-2022-09-03' not in resp
|
||||
assert 'event-2022-09-04' in resp
|
||||
assert 'event-2022-09-05' not in resp
|
||||
assert 'event-2022-09-06' not in resp
|
||||
assert 'event-2022-09-07' not in resp
|
||||
assert 'event-2022-09-08' not in resp
|
||||
|
|
Loading…
Reference in New Issue