diff --git a/lingo/invoicing/migrations/0009_user_name.py b/lingo/invoicing/migrations/0009_user_name.py new file mode 100644 index 0000000..1a53209 --- /dev/null +++ b/lingo/invoicing/migrations/0009_user_name.py @@ -0,0 +1,23 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('invoicing', '0008_injected_line'), + ] + + operations = [ + migrations.AddField( + model_name='draftinvoiceline', + name='user_name', + field=models.CharField(default='', max_length=250), + preserve_default=False, + ), + migrations.AddField( + model_name='invoiceline', + name='user_name', + field=models.CharField(default='', max_length=250), + preserve_default=False, + ), + ] diff --git a/lingo/invoicing/migrations/0010_event_date.py b/lingo/invoicing/migrations/0010_event_date.py new file mode 100644 index 0000000..9c4efe6 --- /dev/null +++ b/lingo/invoicing/migrations/0010_event_date.py @@ -0,0 +1,25 @@ +import datetime + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('invoicing', '0009_user_name'), + ] + + operations = [ + migrations.AddField( + model_name='draftinvoiceline', + name='event_date', + field=models.DateField(default=datetime.date.today), + preserve_default=False, + ), + migrations.AddField( + model_name='invoiceline', + name='event_date', + field=models.DateField(default=datetime.date.today), + preserve_default=False, + ), + ] diff --git a/lingo/invoicing/models.py b/lingo/invoicing/models.py index 04b131f..e75c5d4 100644 --- a/lingo/invoicing/models.py +++ b/lingo/invoicing/models.py @@ -152,11 +152,9 @@ class Pool(models.Model): # get agendas with pricing corresponding to the period agendas = utils.get_agendas(pool=self) # get subscribed users for each agenda, for the period - user_external_ids = utils.get_users_from_subscriptions(agendas=agendas, pool=self) + users = utils.get_users_from_subscriptions(agendas=agendas, pool=self) # get invoice lines for all subscribed users, for each agenda in the corresponding period - lines = utils.get_all_invoice_lines( - agendas=agendas, user_external_ids=user_external_ids, pool=self - ) + lines = utils.get_all_invoice_lines(agendas=agendas, users=users, pool=self) # and generate invoices utils.generate_invoices_from_lines(agendas=agendas, all_lines=lines, pool=self) except (RegieNotConfigured, ChronoError) as e: @@ -208,6 +206,7 @@ class InjectedLine(models.Model): class AbstractInvoiceLine(models.Model): + event_date = models.DateField() slug = models.SlugField(max_length=250) label = models.CharField(max_length=260) quantity = models.FloatField() @@ -215,6 +214,7 @@ class AbstractInvoiceLine(models.Model): total_amount = models.DecimalField(max_digits=9, decimal_places=2) user_external_id = models.CharField(max_length=250) + user_name = models.CharField(max_length=250) payer_external_id = models.CharField(max_length=250) event = JSONField(default=dict) pricing_data = JSONField(default=dict, encoder=DjangoJSONEncoder) diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html index e7077be..1bb74ca 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html @@ -13,76 +13,28 @@ {% if pool.error_count %}{{ pool.error_count }}{% endif %} {{ pool.created_at|date:"DATETIME_FORMAT" }} - {% if pool.draft and pool.status != 'registered' and pool.status != 'running' %} - + + {% trans "Journal" %} + {% if pool.draft and pool.status != 'registered' and pool.status != 'running' %} {% trans "Delete" %} - - {% endif %} + {% endif %} + {% endblock %} {% block content %} - {% if pool.status == 'failed' %} -
-

{% trans "Error while running pool." %}

- {% if pool.exception %}
{{ pool.exception }}
{% endif %} -
- {% endif %} - - - - - - - - - - - - - - - - - - {% for line in lines %} - - - - - - - - - - - - - - - - - {% endfor %} - -
{% trans "PK" %}{% trans "Invoice PK" %}{% trans "Label" %}{% trans "Slug" %}{% trans "Quantity" %}{% trans "Unit amount" %}{% trans "Total amount" %}{% trans "User" %}{% trans "Payer" %}{% trans "Status" %}
{{ line.pk }}{{ line.invoice_id|default:'' }}{{ line.label }}{{ line.slug }}{{ line.quantity }}{{ line.unit_amount }}{{ line.total_amount }}{{ line.user_external_id }}{{ line.payer_external_id }} - {{ line.get_status_display }} - {% if line.status != 'success' %}({{ line.get_error_display }}){% endif %} - {% if line.from_injected_line_id %}({% trans "Injected" %}){% endif %} - {% trans "see details" %}
-
{{ line.pricing_data|pprint }}
-
- +
+ {% for line in lines %} + {% ifchanged line.invoice_id %} + {% if not forloop.first %}{% endif %} +

+ {% blocktrans with number=line.invoice_id payer=line.invoice.payer amount=line.invoice.total_amount %}Invoice #{{ number }} addressed to {{ payer }}, amount {{ amount }}€{% endblocktrans %} +

+ {% endif %} + {% endfor %} +
{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html b/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html new file mode 100644 index 0000000..173d988 --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_pool_journal.html @@ -0,0 +1,93 @@ +{% extends "lingo/invoicing/manager_pool_detail.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% trans "Journal" %} +{% endblock %} + +{% block appbar %} +

+ {% if pool.success_count %}{{ pool.success_count }}{% endif %} + {% if pool.warning_count %}{{ pool.warning_count }}{% endif %} + {% if pool.error_count %}{{ pool.error_count }}{% endif %} + {{ pool.created_at|date:"DATETIME_FORMAT" }} +

+ {% if pool.draft and pool.status != 'registered' and pool.status != 'running' %} + + {% trans "Delete" %} + + {% endif %} +{% endblock %} + +{% block content %} + {% if pool.status == 'failed' %} +
+

{% trans "Error while running pool." %}

+ {% if pool.exception %}
{{ pool.exception }}
{% endif %} +
+ {% endif %} + + + + + + + + + + + + + + + + + {% for line in lines %} + + + + + + + + + + + + + + + + {% endfor %} + +
{% trans "PK" %}{% trans "Invoice PK" %}{% trans "Event" %}{% trans "Quantity" %}{% trans "Unit amount" %}{% trans "Total amount" %}{% trans "User" %}{% trans "Payer" %}{% trans "Status" %}
{{ line.pk }}{{ line.invoice_id|default:'' }} + {{ line.event_date|date:"d/m/Y" }} - {{ line.label }} +
+ ({{ line.slug }}) +
{{ line.quantity }}{{ line.unit_amount }}{{ line.total_amount }}{{ line.user_name }} ({{ line.user_external_id }}){{ line.payer_external_id }} + {{ line.get_status_display }} + {% if line.status != 'success' %}({{ line.get_error_display }}){% endif %} + {% if line.from_injected_line_id %}({% trans "Injected" %}){% endif %} + {% trans "see details" %}
+ {% trans "Pricing data:" %} +
{{ line.pricing_data|pprint }}
+ {% trans "Event:" %} +
{{ line.event|pprint }}
+
+ +{% endblock %} diff --git a/lingo/invoicing/urls.py b/lingo/invoicing/urls.py index 31c19de..342cbd3 100644 --- a/lingo/invoicing/urls.py +++ b/lingo/invoicing/urls.py @@ -74,6 +74,11 @@ urlpatterns = [ views.pool_detail, name='lingo-manager-invoicing-pool-detail', ), + path( + 'campaign//pool//journal/', + views.pool_journal, + name='lingo-manager-invoicing-pool-journal', + ), path( 'campaign//pool//delete/', views.pool_delete, diff --git a/lingo/invoicing/utils.py b/lingo/invoicing/utils.py index f8ee024..862bbcd 100644 --- a/lingo/invoicing/utils.py +++ b/lingo/invoicing/utils.py @@ -35,7 +35,7 @@ def get_agendas(pool): def get_users_from_subscriptions(agendas, pool): - user_external_ids = set() + users = {} for agenda in agendas: subscriptions = get_subscriptions( agenda_slug=agenda.slug, @@ -43,11 +43,15 @@ def get_users_from_subscriptions(agendas, pool): date_end=pool.campaign.date_end, ) for subscription in subscriptions: - user_external_ids.add(subscription['user_external_id']) - return user_external_ids + user_external_id = subscription['user_external_id'] + if user_external_id in users: + continue + user_name = '%s %s' % (subscription['user_first_name'], subscription['user_last_name']) + users[user_external_id] = user_name.strip()[:250] or user_external_id + return [(user_id, user_name) for user_id, user_name in users.items()] -def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, pool): +def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, user_name, pool): def get_agenda_pricing(agendas_pricings_for_agenda, date_event): # same logic as AgendaPricing.get_agenda_pricing for agenda_pricing in agendas_pricings_for_agenda: @@ -104,12 +108,14 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, pool } lines.append( DraftInvoiceLine( + event_date=event_date, slug=event_slug, label=serialized_event['label'], quantity=0, unit_amount=0, total_amount=0, user_external_id=user_external_id, + user_name=user_name, payer_external_id=user_external_id, # XXX event=serialized_event, pricing_data=pricing_error, @@ -121,12 +127,14 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, pool # XXX log all context ! lines.append( DraftInvoiceLine( + event_date=event_date, slug=event_slug, label=serialized_event['label'], quantity=1, unit_amount=pricing_data['pricing'], total_amount=pricing_data['pricing'], user_external_id=user_external_id, + user_name=user_name, payer_external_id=user_external_id, # XXX event=serialized_event, pricing_data=pricing_data, @@ -157,12 +165,14 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, pool for injected_line in injected_lines: lines.append( DraftInvoiceLine( + event_date=injected_line.event_date, slug=injected_line.slug, label=injected_line.label, quantity=injected_line.quantity, unit_amount=injected_line.unit_amount, total_amount=injected_line.total_amount, - user_external_id=injected_line.user_external_id, + user_external_id=user_external_id, + user_name=user_name, payer_external_id=injected_line.user_external_id, status='success', pool=pool, @@ -175,7 +185,7 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, pool return lines -def get_all_invoice_lines(agendas, user_external_ids, pool): +def get_all_invoice_lines(agendas, users, pool): agendas_pricings = ( AgendaPricing.objects.filter(flat_fee_schedule=False) .extra( @@ -186,12 +196,13 @@ def get_all_invoice_lines(agendas, user_external_ids, pool): ) lines = [] - for user_external_id in user_external_ids: + for user_external_id, user_name in users: # generate lines for each user lines += get_invoice_lines_for_user( agendas=agendas, agendas_pricings=agendas_pricings, user_external_id=user_external_id, + user_name=user_name, pool=pool, ) return lines diff --git a/lingo/invoicing/views.py b/lingo/invoicing/views.py index d2b9660..4cd3efd 100644 --- a/lingo/invoicing/views.py +++ b/lingo/invoicing/views.py @@ -297,6 +297,39 @@ class PoolDetailView(DetailView): model = Pool pk_url_kwarg = 'pool_pk' + def dispatch(self, request, *args, **kwargs): + self.campaign = get_object_or_404(Campaign, pk=kwargs['pk']) + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + return self.campaign.pool_set.all() + + def get_context_data(self, **kwargs): + kwargs['object'] = self.campaign + kwargs['pool'] = self.object + line_model = InvoiceLine + if self.object.draft: + line_model = DraftInvoiceLine + all_lines = line_model.objects.filter(pool=self.object) + 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']) + kwargs['lines'] = ( + all_lines.filter(invoice__isnull=False) + .select_related('invoice') + .order_by('invoice__pk', 'user_external_id', 'pk') + ) + return super().get_context_data(**kwargs) + + +pool_detail = PoolDetailView.as_view() + + +class PoolJournalView(DetailView): + template_name = 'lingo/invoicing/manager_pool_journal.html' + model = Pool + pk_url_kwarg = 'pool_pk' + def dispatch(self, request, *args, **kwargs): self.campaign = get_object_or_404(Campaign, pk=kwargs['pk']) return super().dispatch(request, *args, **kwargs) @@ -317,7 +350,7 @@ class PoolDetailView(DetailView): return super().get_context_data(**kwargs) -pool_detail = PoolDetailView.as_view() +pool_journal = PoolJournalView.as_view() class PoolAddView(FormView): diff --git a/tests/invoicing/manager/test_campaign.py b/tests/invoicing/manager/test_campaign.py index 70ab537..c453d64 100644 --- a/tests/invoicing/manager/test_campaign.py +++ b/tests/invoicing/manager/test_campaign.py @@ -120,6 +120,7 @@ def test_detail_campaign(app, admin_user): assert '/manage/invoicing/campaign/%s/pool/add/' % (campaign.pk) not in resp line = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=1, unit_amount=1, total_amount=1, @@ -149,6 +150,7 @@ def test_detail_campaign(app, admin_user): assert 'tag-error' not in resp line = InvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=1, unit_amount=1, total_amount=1, @@ -264,6 +266,7 @@ def test_delete_campaign(app, admin_user): regie = Regie.objects.create(label='Foo') invoice = DraftInvoice.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool) DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), invoice=invoice, quantity=1, unit_amount=1, @@ -386,6 +389,7 @@ def test_detail_pool(app, admin_user): assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) in resp line = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=1, unit_amount=1, total_amount=1, @@ -417,6 +421,7 @@ def test_detail_pool(app, admin_user): pool.draft = False pool.save() line = InvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=1, unit_amount=1, total_amount=1, @@ -441,7 +446,223 @@ def test_detail_pool(app, admin_user): assert 'tag-error' not in resp -def test_detail_pool_lines(app, admin_user): +def test_detail_pool_invoices(app, admin_user): + 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, + status='completed', + ) + regie = Regie.objects.create(label='Foo') + invoice1 = DraftInvoice.objects.create( + date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:1' + ) + invoice2 = DraftInvoice.objects.create( + date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:2' + ) + + line11 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), + invoice=invoice1, + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + label='Label 11', + user_external_id='user:1', + user_name='User1 Name1', + ) + line12 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 2), + invoice=invoice1, + quantity=1, + unit_amount=2, + total_amount=2, + status='success', + pool=pool, + label='Label 12', + user_external_id='user:2', + user_name='User2 Name2', + ) + line13 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 3), + invoice=invoice1, + quantity=1, + unit_amount=3, + total_amount=3, + status='success', + pool=pool, + label='Label 13', + user_external_id='user:1', + user_name='User1 Name1', + ) + + orphan_line = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), + quantity=1, + unit_amount=42, + total_amount=42, + status='failed', + pool=pool, + label='Label 14', + user_external_id='user:1', + user_name='User1 Name1', + ) + + line21 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), + invoice=invoice2, + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + label='Label 21', + user_external_id='user:1', + user_name='User1 Name1', + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool.pk)) + assert '#%s' % orphan_line.pk not in resp + assert ( + resp.pyquery('h3[data-invoice-id="%s"]' % invoice1.pk).text() + == 'Invoice #%s addressed to payer:1, amount 6.00€' % invoice1.pk + ) + assert len(resp.pyquery('ul[data-invoice-id="%s"] li' % invoice1.pk)) == 3 + assert ( + resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(1)' % invoice1.pk).text() + == '#%s User1 Name1 - 01/09/2022 - Label 11 (1.00)' % line11.pk + ) + assert ( + resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(2)' % invoice1.pk).text() + == '#%s User1 Name1 - 03/09/2022 - Label 13 (3.00)' % line13.pk + ) + assert ( + resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(3)' % invoice1.pk).text() + == '#%s User2 Name2 - 02/09/2022 - Label 12 (2.00)' % line12.pk + ) + assert ( + resp.pyquery('h3[data-invoice-id="%s"]' % invoice2.pk).text() + == 'Invoice #%s addressed to payer:2, amount 1.00€' % invoice2.pk + ) + assert len(resp.pyquery('ul[data-invoice-id="%s"] li' % invoice2.pk)) == 1 + assert ( + resp.pyquery('ul[data-invoice-id="%s"] li:nth-child(1)' % invoice2.pk).text() + == '#%s User1 Name1 - 01/09/2022 - Label 21 (1.00)' % line21.pk + ) + + +def test_journal_pool(app, admin_user): + 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), + ) + campaign2 = Campaign.objects.create( + date_start=datetime.date(2022, 10, 1), + date_end=datetime.date(2022, 11, 1), + date_issue=datetime.date(2022, 11, 30), + ) + pool = Pool.objects.create( + campaign=campaign, + draft=True, + status='completed', + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) in resp + + app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (0, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign2.pk, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, 0), status=404) + + pool.draft = False + pool.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) not in resp + + pool.draft = True + pool.status = 'registered' + pool.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) not in resp + + pool.status = 'running' + pool.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) not in resp + + pool.status = 'failed' + pool.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) in resp + + line = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + ) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '1' in resp + assert 'tag-warning' not in resp + assert 'tag-error' not in resp + line.status = 'error' + line.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert 'tag-success' not in resp + assert 'tag-warning' not in resp + assert '1' in resp + line.status = 'warning' + line.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert 'tag-success' not in resp + assert '1' in resp + assert 'tag-error' not in resp + line.delete() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert 'tag-success' not in resp + assert 'tag-warning' not in resp + assert 'tag-error' not in resp + + pool.draft = False + pool.save() + line = InvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + ) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert '1' in resp + assert 'tag-warning' not in resp + assert 'tag-error' not in resp + line.status = 'error' + line.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert 'tag-success' not in resp + assert 'tag-warning' not in resp + assert '1' in resp + line.status = 'warning' + line.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) + assert 'tag-success' not in resp + assert '1' in resp + assert 'tag-error' not in resp + + +def test_journal_pool_lines(app, admin_user): campaign = Campaign.objects.create( date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1), @@ -457,6 +678,7 @@ def test_detail_pool_lines(app, admin_user): lines = [ DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), invoice=invoice, quantity=1, unit_amount=1, @@ -464,6 +686,7 @@ def test_detail_pool_lines(app, admin_user): status='success', pool=pool, pricing_data={'foo': 'bar'}, + event={'event': 'foobar'}, ) ] errors = [ @@ -493,6 +716,7 @@ def test_detail_pool_lines(app, admin_user): for error, error_details in errors: lines.append( DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), invoice=invoice, quantity=1, unit_amount=1, @@ -503,6 +727,7 @@ def test_detail_pool_lines(app, admin_user): 'error': error, 'error_details': error_details, }, + event={'event': 'foobar'}, ) ) @@ -510,11 +735,11 @@ def test_detail_pool_lines(app, admin_user): return (' '.join([v.strip() for v in value.split('\n')])).strip() app = login(app) - resp = app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool.pk)) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk)) assert format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[0].pk).text()) == 'Success' assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[0].pk).text().strip() - == "{'foo': 'bar'}" + == "{'foo': 'bar'} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[1].pk).text()) @@ -522,7 +747,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[1].pk).text().strip() - == "{'error': 'AgendaPricingNotFound', 'error_details': {}}" + == "{'error': 'AgendaPricingNotFound', 'error_details': {}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[2].pk).text()) @@ -530,7 +755,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[2].pk).text().strip() - == "{'error': 'CriteriaConditionNotFound', 'error_details': {'category': 'cat-foo'}}" + == "{'error': 'CriteriaConditionNotFound', 'error_details': {'category': 'cat-foo'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[3].pk).text()) @@ -538,7 +763,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[3].pk).text().strip() - == "{'error': 'MultipleDefaultCriteriaCondition', 'error_details': {'category': 'cat-foo'}}" + == "{'error': 'MultipleDefaultCriteriaCondition', 'error_details': {'category': 'cat-foo'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[4].pk).text()) @@ -546,7 +771,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[4].pk).text().strip() - == "{'error': 'PricingDataError', 'error_details': {'criterias': {'foo': 'bar', 'qf': 'qf-1'}}}" + == "{'error': 'PricingDataError', 'error_details': {'criterias': {'foo': 'bar', 'qf': 'qf-1'}}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[5].pk).text()) @@ -554,7 +779,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[5].pk).text().strip() - == "{'error': 'PricingDataFormatError', 'error_details': {'pricing': 'foobar', 'wanted': 'decimal'}}" + == "{'error': 'PricingDataFormatError', 'error_details': {'pricing': 'foobar', 'wanted': 'decimal'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[6].pk).text()) @@ -562,7 +787,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[6].pk).text().strip() - == "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}}" + == "{'error': 'PricingUnknownCheckStatusError', 'error_details': {'status': 'unknown'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[7].pk).text()) @@ -570,7 +795,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[7].pk).text().strip() - == "{'error': 'PricingEventNotCheckedError', 'error_details': {}}" + == "{'error': 'PricingEventNotCheckedError', 'error_details': {}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[8].pk).text()) @@ -578,7 +803,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[8].pk).text().strip() - == "{'error': 'PricingBookingNotCheckedError', 'error_details': {}}" + == "{'error': 'PricingBookingNotCheckedError', 'error_details': {}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[9].pk).text()) @@ -586,7 +811,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[9].pk).text().strip() - == "{'error': 'PricingMultipleBookingError', 'error_details': {}}" + == "{'error': 'PricingMultipleBookingError', 'error_details': {}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[10].pk).text()) @@ -594,7 +819,7 @@ def test_detail_pool_lines(app, admin_user): ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[10].pk).text().strip() - == "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}}" + == "{'error': 'PricingBookingCheckTypeError', 'error_details': {'reason': 'not-found'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[11].pk).text()) @@ -602,7 +827,7 @@ def test_detail_pool_lines(app, admin_user): ) assert resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[11].pk).text().strip() == ( "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', " - "'check_type_group': 'foo-bar', 'reason': 'not-configured'}}" + "'check_type_group': 'foo-bar', 'reason': 'not-configured'}} {'event': 'foobar'}" ) assert ( format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[12].pk).text()) @@ -610,7 +835,7 @@ def test_detail_pool_lines(app, admin_user): ) assert resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[12].pk).text().strip() == ( "{'error': 'PricingBookingCheckTypeError', 'error_details': {'check_type': 'foo-reason', " - "'check_type_group': 'foo-bar', 'reason': 'wrong-kind'}}" + "'check_type_group': 'foo-bar', 'reason': 'wrong-kind'}} {'event': 'foobar'}" ) @@ -633,6 +858,7 @@ def test_delete_pool(app, admin_user): regie = Regie.objects.create(label='Foo') invoice = DraftInvoice.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool) DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), invoice=invoice, quantity=1, unit_amount=1, diff --git a/tests/invoicing/test_invoice_generation.py b/tests/invoicing/test_invoice_generation.py index 4426b8f..cf1cf23 100644 --- a/tests/invoicing/test_invoice_generation.py +++ b/tests/invoicing/test_invoice_generation.py @@ -130,12 +130,12 @@ def test_get_users_from_subscriptions(mock_subscriptions): ) # no agendas - assert utils.get_users_from_subscriptions(agendas=[], pool=pool) == set() + assert utils.get_users_from_subscriptions(agendas=[], pool=pool) == [] assert mock_subscriptions.call_args_list == [] # no subscriptions mock_subscriptions.return_value = [] - assert utils.get_users_from_subscriptions(agendas=[agenda1, agenda2], pool=pool) == set() + assert utils.get_users_from_subscriptions(agendas=[agenda1, agenda2], pool=pool) == [] assert mock_subscriptions.call_args_list == [ mock.call( agenda_slug='agenda-1', date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1) @@ -151,16 +151,22 @@ def test_get_users_from_subscriptions(mock_subscriptions): [ { 'user_external_id': 'user:1', + 'user_first_name': 'User1', + 'user_last_name': 'Name1', 'date_start': '2022-08-01', 'date_end': '2022-09-02', }, { 'user_external_id': 'user:1', + 'user_first_name': 'Foo Bar', + 'user_last_name': '', 'date_start': '2022-09-02', 'date_end': '2022-09-03', }, { 'user_external_id': 'user:2', + 'user_first_name': '', + 'user_last_name': '', 'date_start': '2022-09-02', 'date_end': '2022-09-03', }, @@ -168,12 +174,17 @@ def test_get_users_from_subscriptions(mock_subscriptions): [ { 'user_external_id': 'user:1', + 'user_first_name': 'User1 Name1', + 'user_last_name': '', 'date_start': '2022-08-01', 'date_end': '2022-10-01', }, ], ] - assert utils.get_users_from_subscriptions(agendas=[agenda1, agenda2], pool=pool) == {'user:1', 'user:2'} + assert utils.get_users_from_subscriptions(agendas=[agenda1, agenda2], pool=pool) == [ + ('user:1', 'User1 Name1'), + ('user:2', 'user:2'), + ] assert mock_subscriptions.call_args_list == [ mock.call( agenda_slug='agenda-1', date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1) @@ -202,6 +213,7 @@ def test_get_invoice_lines_for_user_check_status_error(mock_status): agendas=[agenda], agendas_pricings=[], user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) @@ -267,6 +279,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s ) # ok, same campaign DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=0, unit_amount=0, total_amount=0, @@ -319,6 +332,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s ) # nok, already invoiced InvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 15), quantity=0, unit_amount=0, total_amount=0, @@ -338,6 +352,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s ) # nok, other campaign DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 16), quantity=0, unit_amount=0, total_amount=0, @@ -351,6 +366,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s agendas=[], agendas_pricings=[agenda_pricing], user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) == [] @@ -364,6 +380,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s agendas=[agenda1, agenda2], agendas_pricings=[agenda_pricing], user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert len(lines) == 2 # injected lines @@ -433,6 +450,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s agendas=[agenda1, agenda2], agendas_pricings=[agenda_pricing], user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert mock_pricing_data_event.call_args_list == [ @@ -493,12 +511,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s line1, line2, line3, line4, line5, line6 = lines assert isinstance(line1, DraftInvoiceLine) assert line1.invoice is None + assert line1.event_date == datetime.date(2022, 9, 1) assert line1.slug == 'agenda-1@event-1' assert line1.label == 'Event 1' assert line1.quantity == 1 assert line1.unit_amount == 1 assert line1.total_amount == 1 assert line1.user_external_id == 'user:1' + assert line1.user_name == 'User1 Name1' assert line1.payer_external_id == 'user:1' assert line1.event == { 'agenda': 'agenda-1', @@ -512,12 +532,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line1.from_injected_line is None assert isinstance(line2, DraftInvoiceLine) assert line2.invoice is None + assert line2.event_date == datetime.date(2022, 9, 2) assert line2.slug == 'agenda-1@event-2' assert line2.label == 'Event 2' assert line2.quantity == 1 assert line2.unit_amount == 2 assert line2.total_amount == 2 assert line2.user_external_id == 'user:1' + assert line2.user_name == 'User1 Name1' assert line2.payer_external_id == 'user:1' assert line2.event == { 'agenda': 'agenda-1', @@ -531,12 +553,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line2.from_injected_line is None assert isinstance(line3, DraftInvoiceLine) assert line3.invoice is None + assert line3.event_date == datetime.date(2022, 9, 1) assert line3.slug == 'agenda-2@eveeent-1' assert line3.label == 'Eveeent 1' assert line3.quantity == 1 assert line3.unit_amount == 3 assert line3.total_amount == 3 assert line3.user_external_id == 'user:1' + assert line3.user_name == 'User1 Name1' assert line3.payer_external_id == 'user:1' assert line3.event == { 'agenda': 'agenda-2', @@ -550,12 +574,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line3.from_injected_line is None assert isinstance(line4, DraftInvoiceLine) assert line4.invoice is None + assert line4.event_date == datetime.date(2022, 9, 2) assert line4.slug == 'agenda-2@eveeent-2' assert line4.label == 'Eveeent 2' assert line4.quantity == 1 assert line4.unit_amount == 4 assert line4.total_amount == 4 assert line4.user_external_id == 'user:1' + assert line4.user_name == 'User1 Name1' assert line4.payer_external_id == 'user:1' assert line4.event == { 'agenda': 'agenda-2', @@ -569,12 +595,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line4.from_injected_line is None assert isinstance(line5, DraftInvoiceLine) assert line5.invoice is None + assert line5.event_date == injected_line2.event_date assert line5.slug == 'event-2022-09-01' assert line5.label == 'Event 2022-09-01' assert line5.quantity == 2 assert line5.unit_amount == 1.5 assert line5.total_amount == 3 assert line5.user_external_id == 'user:1' + assert line5.user_name == 'User1 Name1' assert line5.payer_external_id == 'user:1' assert line5.event == {} assert line5.pricing_data == {} @@ -583,12 +611,14 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line5.from_injected_line == injected_line2 assert isinstance(line6, DraftInvoiceLine) assert line6.invoice is None + assert line6.event_date == injected_line4.event_date assert line6.slug == 'event-2022-09-30' assert line6.label == 'Event 2022-09-30' assert line6.quantity == 3 assert line6.unit_amount == 1.5 assert line6.total_amount == 4.5 assert line6.user_external_id == 'user:1' + assert line6.user_name == 'User1 Name1' assert line6.payer_external_id == 'user:1' assert line6.event == {} assert line6.pricing_data == {} @@ -652,6 +682,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu agendas=[agenda], agendas_pricings=AgendaPricing.objects.all(), user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert len(lines) == 1 @@ -677,6 +708,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu agendas=[agenda], agendas_pricings=AgendaPricing.objects.all(), user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert len(lines) == 1 @@ -701,6 +733,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu agendas=[agenda], agendas_pricings=AgendaPricing.objects.all(), user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert len(lines) == 1 @@ -713,6 +746,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu assert line.unit_amount == 0 assert line.total_amount == 0 assert line.user_external_id == 'user:1' + assert line.user_name == 'User1 Name1' assert line.payer_external_id == 'user:1' assert line.event == { 'agenda': 'agenda', @@ -788,18 +822,21 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data agendas=[agenda], agendas_pricings=[agenda_pricing], user_external_id='user:1', + user_name='User1 Name1', pool=pool, ) assert len(lines) == 3 line1, line2, line3 = lines assert isinstance(line1, DraftInvoiceLine) assert line1.invoice is None + assert line1.event_date == datetime.date(2022, 9, 1) assert line1.slug == 'agenda@event-1' assert line1.label == 'Event 1' assert line1.quantity == 1 assert line1.unit_amount == 1 assert line1.total_amount == 1 assert line1.user_external_id == 'user:1' + assert line1.user_name == 'User1 Name1' assert line1.payer_external_id == 'user:1' assert line1.event == { 'agenda': 'agenda', @@ -812,12 +849,14 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data assert line1.pool == pool assert isinstance(line2, DraftInvoiceLine) assert line2.invoice is None + assert line2.event_date == datetime.date(2022, 9, 2) assert line2.slug == 'agenda@event-2' assert line2.label == 'Event 2' assert line2.quantity == 0 assert line2.unit_amount == 0 assert line2.total_amount == 0 assert line2.user_external_id == 'user:1' + assert line2.user_name == 'User1 Name1' assert line2.payer_external_id == 'user:1' assert line2.event == { 'agenda': 'agenda', @@ -830,12 +869,14 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data assert line2.pool == pool assert isinstance(line3, DraftInvoiceLine) assert line3.invoice is None + assert line3.event_date == datetime.date(2022, 9, 3) assert line3.slug == 'agenda@event-3' assert line3.label == 'Event 3' assert line3.quantity == 1 assert line3.unit_amount == 3 assert line3.total_amount == 3 assert line3.user_external_id == 'user:1' + assert line3.user_name == 'User1 Name1' assert line3.payer_external_id == 'user:1' assert line3.event == { 'agenda': 'agenda', @@ -864,18 +905,21 @@ def test_get_all_invoice_lines(mock_user_lines): ) line1 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=0, unit_amount=0, total_amount=0, pool=pool, ) line2 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=0, unit_amount=0, total_amount=0, pool=pool, ) line3 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=0, unit_amount=0, total_amount=0, @@ -888,7 +932,7 @@ def test_get_all_invoice_lines(mock_user_lines): assert ( utils.get_all_invoice_lines( agendas=[agenda1, agenda2], - user_external_ids=[], + users=[], pool=pool, ) == [] @@ -898,7 +942,7 @@ def test_get_all_invoice_lines(mock_user_lines): # with subscribed users assert utils.get_all_invoice_lines( agendas=[agenda1, agenda2], - user_external_ids=['user:1', 'user:2'], + users=[('user:1', 'User1 Name1'), ('user:2', 'User2 Name2')], pool=pool, ) == [line1, line2, line3] assert mock_user_lines.call_args_list == [ @@ -906,12 +950,14 @@ def test_get_all_invoice_lines(mock_user_lines): agendas=[agenda1, agenda2], agendas_pricings=mock.ANY, user_external_id='user:1', + user_name='User1 Name1', pool=pool, ), mock.call( agendas=[agenda1, agenda2], agendas_pricings=mock.ANY, user_external_id='user:2', + user_name='User2 Name2', pool=pool, ), ] @@ -1018,7 +1064,7 @@ def test_get_all_invoice_lines_queryset(mock_status): with CaptureQueriesContext(connection) as ctx: lines = utils.get_all_invoice_lines( agendas=[agenda1, agenda2], - user_external_ids=['user:1', 'user:2'], + users=[('user:1', 'User1 Name1'), ('user:2', 'User2 Name2')], pool=pool, ) assert lines @@ -1044,6 +1090,7 @@ def test_generate_invoices_from_lines(): ) line_error = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-1'}, quantity=0, unit_amount=0, @@ -1054,6 +1101,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line1 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-1'}, quantity=1, unit_amount=1, @@ -1064,6 +1112,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line2 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-1'}, quantity=1, unit_amount=2, @@ -1074,6 +1123,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line3 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-2'}, quantity=1, unit_amount=3, @@ -1084,6 +1134,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line4 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-2'}, quantity=1, unit_amount=4, @@ -1094,6 +1145,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line5 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-3'}, quantity=1, unit_amount=5, @@ -1104,6 +1156,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) DraftInvoiceLine.objects.create( # not used for generation + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-3'}, quantity=1, unit_amount=5, @@ -1114,6 +1167,7 @@ def test_generate_invoices_from_lines(): pool=pool, ) line6 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), event={'agenda': 'agenda-4'}, # regie not configured quantity=1, unit_amount=6, @@ -1135,6 +1189,7 @@ def test_generate_invoices_from_lines(): regie=regie1, ) line7 = DraftInvoiceLine.objects.create( + event_date=datetime.date(2022, 9, 1), quantity=1, unit_amount=7, total_amount=7, @@ -1232,7 +1287,7 @@ def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas): assert mock_lines.call_args_list == [ mock.call( agendas=[agenda1, agenda2], - user_external_ids=['foo', 'bar'], + users=['foo', 'bar'], pool=pool, ) ] diff --git a/tests/invoicing/test_models.py b/tests/invoicing/test_models.py index 7790a31..eebc827 100644 --- a/tests/invoicing/test_models.py +++ b/tests/invoicing/test_models.py @@ -30,6 +30,7 @@ def test_invoice_total_amount(draft): # line with error status, ignored line = line_model.objects.create( + event_date=datetime.date.today(), invoice=invoice, # with invoice quantity=0, unit_amount=0, @@ -80,6 +81,7 @@ def test_invoice_total_amount(draft): # create line with invoice, status success line2 = line_model.objects.create( + event_date=datetime.date.today(), invoice=invoice, quantity=1, unit_amount=20, @@ -109,6 +111,7 @@ def test_invoice_total_amount(draft): # create line without invoice, status success line3 = line_model.objects.create( + event_date=datetime.date.today(), quantity=1, unit_amount=20, total_amount=20,