From 691713b69ed434eea81e701ecaa4b0f407e332fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Thu, 26 Jan 2023 16:31:31 +0100 Subject: [PATCH] invoicing: list non invoiced lines (#73741) --- .../invoicing/manager_campaign_list.html | 1 + .../manager_non_invoiced_line_list.html | 90 ++++++++++ .../lingo/invoicing/manager_pool_journal.html | 2 +- lingo/invoicing/urls.py | 5 + lingo/invoicing/views.py | 55 +++++- lingo/manager/static/css/style.scss | 2 +- tests/invoicing/manager/test_campaign.py | 158 +++++++++++++++++- 7 files changed, 309 insertions(+), 4 deletions(-) create mode 100644 lingo/invoicing/templates/lingo/invoicing/manager_non_invoiced_line_list.html diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html index b7e3b17..8bfea07 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html @@ -9,6 +9,7 @@ {% block appbar %}

{% trans "Campaigns" %}

+ {% trans 'Non invoiced lines' %} {% trans 'New campaign' %} {% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_non_invoiced_line_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_non_invoiced_line_list.html new file mode 100644 index 0000000..6a2c00b --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_non_invoiced_line_list.html @@ -0,0 +1,90 @@ +{% extends "lingo/invoicing/manager_campaign_list.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% trans "Non invoiced lines" %} +{% endblock %} + +{% block appbar %} +

{% trans "Non invoiced lines" %}

+{% endblock %} + +{% block content %} +
+ + + + + + + + + + + + + + + {% for line in object_list %} + + + + + + + + + + + {% if line.status != 'injected' %} + + + + {% endif %} + {% endfor %} + +
{% trans "Event" %}{% trans "Quantity" %}{% trans "Unit amount" %}{% trans "Total amount" %}{% trans "User" %}{% trans "Payer" %}{% trans "Status" %}
+ {{ line.event_date|date:"d/m/Y" }} - {{ line.label }} +
+ {% if line.status == 'error'%} + ({{ line.slug }}) + {% else %} + ({{ line.slug }}) + {% endif %} +
{{ line.quantity }}{{ line.unit_amount }}{{ line.total_amount }}{{ line.user_name }} ({{ line.user_external_id }}){{ line.payer_external_id }} + {% spaceless %} + {% if line.status == 'error'%} + {% trans "Error" %} + {% else %} + {% trans "Injected" %} + {% endif %} + {% endspaceless %} + {% if line.status == 'error'%} + ({{ line.error_display }}) + {% endif %} + {% if line.status != 'injected' %}{% trans "see details" %}{% endif %}
+ {% trans "Pricing data:" %} +
{{ line.pricing_data|pprint }}
+ {% trans "Event:" %} +
{{ line.event|pprint }}
+
+ {% include "gadjo/pagination.html" %} +
+ +{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html b/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html index 921bb5e..8a840b3 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html @@ -39,7 +39,7 @@ }); - +
diff --git a/lingo/invoicing/urls.py b/lingo/invoicing/urls.py index e82cf19..e2bac62 100644 --- a/lingo/invoicing/urls.py +++ b/lingo/invoicing/urls.py @@ -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', + ), ] diff --git a/lingo/invoicing/views.py b/lingo/invoicing/views.py index 995c375..3472e61 100644 --- a/lingo/invoicing/views.py +++ b/lingo/invoicing/views.py @@ -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() diff --git a/lingo/manager/static/css/style.scss b/lingo/manager/static/css/style.scss index 2adc26f..25e7865 100644 --- a/lingo/manager/static/css/style.scss +++ b/lingo/manager/static/css/style.scss @@ -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; diff --git a/tests/invoicing/manager/test_campaign.py b/tests/invoicing/manager/test_campaign.py index 36f0579..17bb2ed 100644 --- a/tests/invoicing/manager/test_campaign.py +++ b/tests/invoicing/manager/test_campaign.py @@ -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 -- 2.39.2
{% trans "PK" %}