From 36d54f8b2c87c43e5149d841212679893492a7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Fri, 27 Jan 2023 09:10:59 +0100 Subject: [PATCH 1/2] invoicing: filter injected lines on journal (#73742) --- lingo/invoicing/forms.py | 3 +++ tests/invoicing/manager/test_campaign.py | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/lingo/invoicing/forms.py b/lingo/invoicing/forms.py index 91516e6..2dcfd3b 100644 --- a/lingo/invoicing/forms.py +++ b/lingo/invoicing/forms.py @@ -92,6 +92,7 @@ class AbstractLineFilterSet(django_filters.FilterSet): status_choices = [ ('success', _('Success')), + ('success_injected', '%s (%s)' % (_('Success'), _('Injected'))), ('warning', _('Warning')), ('error', _('Error')), ] @@ -106,6 +107,8 @@ class AbstractLineFilterSet(django_filters.FilterSet): def filter_status(self, queryset, name, value): if not value: return queryset + if value == 'success_injected': + return queryset.filter(status='success', from_injected_line__isnull=False) if value == 'error_todo': return queryset.filter(status='error', error_status='') if value == 'error_ignored': diff --git a/tests/invoicing/manager/test_campaign.py b/tests/invoicing/manager/test_campaign.py index 17bb2ed..3fb2f7b 100644 --- a/tests/invoicing/manager/test_campaign.py +++ b/tests/invoicing/manager/test_campaign.py @@ -784,6 +784,7 @@ def test_journal_pool_lines(app, admin_user, draft): invoice_model = DraftInvoice if draft else Invoice line_model = DraftInvoiceLine if draft else InvoiceLine + regie = Regie.objects.create(label='Foo') campaign = Campaign.objects.create( date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1), @@ -871,6 +872,15 @@ def test_journal_pool_lines(app, admin_user, draft): lines[-2].error_status = 'fixed' lines[-2].save() + injected_line = InjectedLine.objects.create( + event_date=datetime.date(2022, 9, 1), + quantity=1, + unit_amount=1, + total_amount=1, + user_external_id='user:2', + payer_external_id='payer:2', + regie=regie, + ) lines.append( line_model.objects.create( event_date=datetime.date(2022, 9, 1), @@ -884,6 +894,7 @@ def test_journal_pool_lines(app, admin_user, draft): event={'event': 'foobar2'}, user_external_id='user:2', payer_external_id='payer:2', + from_injected_line=injected_line, ), ) @@ -1005,7 +1016,10 @@ def test_journal_pool_lines(app, admin_user, draft): "{'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 ( + format_status(resp.pyquery('tr[data-line-id="%s"] td.status' % lines[13].pk).text()) + == 'Success (Injected)' + ) assert ( resp.pyquery('tr[data-details-for-line-id="%s"] td pre' % lines[13].pk).text().strip() == "{'foo': 'bar'} {'event': 'foobar2'}" @@ -1064,6 +1078,11 @@ def test_journal_pool_lines(app, admin_user, draft): 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': 'success_injected'}, + ) + assert len(resp.pyquery('tr td.status')) == 1 resp = app.get( '/manage/invoicing/campaign/%s/pool/%s/journal/' % (campaign.pk, pool.pk), params={'status': 'warning'}, -- 2.39.2 From 6dee6eb4dcc668a90379aba4d777460ec37a96fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= Date: Fri, 27 Jan 2023 09:51:50 +0100 Subject: [PATCH 2/2] invoicing: configure injected lines on campaign (#73742) --- lingo/invoicing/forms.py | 2 +- .../migrations/0015_injected_lines.py | 25 ++++ lingo/invoicing/models.py | 10 ++ .../invoicing/manager_campaign_detail.html | 1 + lingo/invoicing/utils.py | 71 ++++++------ lingo/invoicing/views.py | 1 + tests/invoicing/test_invoice_generation.py | 109 ++++++++++++------ 7 files changed, 147 insertions(+), 72 deletions(-) create mode 100644 lingo/invoicing/migrations/0015_injected_lines.py diff --git a/lingo/invoicing/forms.py b/lingo/invoicing/forms.py index 2dcfd3b..b2f385a 100644 --- a/lingo/invoicing/forms.py +++ b/lingo/invoicing/forms.py @@ -25,7 +25,7 @@ from lingo.invoicing.models import Campaign, DraftInvoiceLine, InvoiceLine, Regi class CampaignForm(forms.ModelForm): class Meta: model = Campaign - fields = ['date_start', 'date_end', 'date_issue'] + fields = ['date_start', 'date_end', 'date_issue', 'injected_lines'] widgets = { 'date_start': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), 'date_end': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), diff --git a/lingo/invoicing/migrations/0015_injected_lines.py b/lingo/invoicing/migrations/0015_injected_lines.py new file mode 100644 index 0000000..cd1e42a --- /dev/null +++ b/lingo/invoicing/migrations/0015_injected_lines.py @@ -0,0 +1,25 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('invoicing', '0014_error_status'), + ] + + operations = [ + migrations.AddField( + model_name='campaign', + name='injected_lines', + field=models.CharField( + choices=[ + ('no', 'No'), + ('period', 'Yes, only for the period'), + ('all', 'Yes, all injected lines before the end of the period'), + ], + default='no', + max_length=10, + verbose_name='Integrate injected lines', + ), + ), + ] diff --git a/lingo/invoicing/models.py b/lingo/invoicing/models.py index ab848cf..d66b2ae 100644 --- a/lingo/invoicing/models.py +++ b/lingo/invoicing/models.py @@ -134,6 +134,16 @@ class Campaign(models.Model): date_start = models.DateField(_('Start date')) date_end = models.DateField(_('End date')) date_issue = models.DateField(_('Issue date')) + injected_lines = models.CharField( + _('Integrate injected lines'), + choices=[ + ('no', _('No')), + ('period', _('Yes, only for the period')), + ('all', _('Yes, all injected lines before the end of the period')), + ], + default='no', + max_length=10, + ) def __str__(self): return _('From %(start)s to %(end)s') % { diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html index bbe2c02..bd5262f 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html @@ -30,6 +30,7 @@
  • {% trans "Start date:" %} {{ object.date_start|date:'d/m/Y' }}
  • {% trans "End date:" %} {{ object.date_end|date:'d/m/Y' }}
  • {% trans "Issue date:" %} {{ object.date_issue|date:'d/m/Y' }}
  • +
  • {% trans "Integrate injected lines:" %} {{ object.get_injected_lines_display }}
  • diff --git a/lingo/invoicing/utils.py b/lingo/invoicing/utils.py index 862bbcd..4b7a1cc 100644 --- a/lingo/invoicing/utils.py +++ b/lingo/invoicing/utils.py @@ -143,42 +143,47 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, user ) ) - # fetch injected lines - injected_lines = ( - InjectedLine.objects.filter( - event_date__gte=pool.campaign.date_start, - event_date__lt=pool.campaign.date_end, - user_external_id=user_external_id, - ) - .exclude( - # exclude already invoiced lines - invoiceline__isnull=False - ) - .exclude( - # exclude lines used in another campaign - pk__in=DraftInvoiceLine.objects.filter(from_injected_line__isnull=False) - .exclude(pool__campaign=pool.campaign) - .values('from_injected_line') - ) - ) - - 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, + if pool.campaign.injected_lines != 'no': + # fetch injected lines + injected_lines = ( + InjectedLine.objects.filter( + event_date__lt=pool.campaign.date_end, user_external_id=user_external_id, - user_name=user_name, - payer_external_id=injected_line.user_external_id, - status='success', - pool=pool, - from_injected_line=injected_line, ) + .exclude( + # exclude already invoiced lines + invoiceline__isnull=False + ) + .exclude( + # exclude lines used in another campaign + pk__in=DraftInvoiceLine.objects.filter(from_injected_line__isnull=False) + .exclude(pool__campaign=pool.campaign) + .values('from_injected_line') + ) + .order_by('event_date') ) + if pool.campaign.injected_lines == 'period': + injected_lines = injected_lines.filter( + event_date__gte=pool.campaign.date_start, + ) + + 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=user_external_id, + user_name=user_name, + payer_external_id=injected_line.user_external_id, + status='success', + pool=pool, + from_injected_line=injected_line, + ) + ) DraftInvoiceLine.objects.bulk_create(lines) diff --git a/lingo/invoicing/views.py b/lingo/invoicing/views.py index 3472e61..ca5e256 100644 --- a/lingo/invoicing/views.py +++ b/lingo/invoicing/views.py @@ -198,6 +198,7 @@ regies_import = RegiesImportView.as_view() class CampaignListView(ListView): template_name = 'lingo/invoicing/manager_campaign_list.html' model = Campaign + ordering = '-date_start' campaign_list = CampaignListView.as_view() diff --git a/tests/invoicing/test_invoice_generation.py b/tests/invoicing/test_invoice_generation.py index 74aa159..e9c6944 100644 --- a/tests/invoicing/test_invoice_generation.py +++ b/tests/invoicing/test_invoice_generation.py @@ -222,9 +222,10 @@ def test_get_invoice_lines_for_user_check_status_error(mock_status): ) +@pytest.mark.parametrize('injected_lines', ['no', 'period', 'all']) @mock.patch('lingo.invoicing.utils.get_check_status') @mock.patch('lingo.pricing.models.AgendaPricing.get_pricing_data_for_event') -def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_status): +def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_status, injected_lines): regie = Regie.objects.create(label='Regie') agenda1 = Agenda.objects.create(label='Agenda 1') agenda2 = Agenda.objects.create(label='Agenda 2') @@ -239,6 +240,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 10, 1), date_issue=datetime.date(2022, 10, 31), + injected_lines=injected_lines, ) old_pool = Pool.objects.create( campaign=campaign, @@ -259,8 +261,8 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s ) # create some injected lines - InjectedLine.objects.create( - event_date=datetime.date(2022, 8, 31), # too soon + injected_line1 = InjectedLine.objects.create( + event_date=datetime.date(2022, 8, 31), # before the period slug='event-2022-08-31', label='Event 2022-08-31', quantity=2, @@ -387,7 +389,12 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s user_name='User1 Name1', pool=pool, ) - assert len(lines) == 2 # injected lines + if injected_lines == 'no': + assert len(lines) == 0 + elif injected_lines == 'period': + assert len(lines) == 2 # injected lines + else: + assert len(lines) == 3 # injected lines assert mock_status.call_args_list == [ mock.call( agenda_slugs=['agenda-1', 'agenda-2'], @@ -511,8 +518,15 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s adult_external_id='user:1', ), ] - assert len(lines) == 6 - line1, line2, line3, line4, line5, line6 = lines + if injected_lines == 'no': + assert len(lines) == 4 + line1, line2, line3, line4 = lines + elif injected_lines == 'period': + assert len(lines) == 6 + line1, line2, line3, line4, line6, line7 = lines + else: + assert len(lines) == 7 + line1, line2, line3, line4, line5, line6, line7 = lines assert isinstance(line1, DraftInvoiceLine) assert line1.invoice is None assert line1.event_date == datetime.date(2022, 9, 1) @@ -597,38 +611,56 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s assert line4.status == 'success' assert line4.pool == pool 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 == {} - assert line5.status == 'success' - assert line5.pool == pool - 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 == {} - assert line6.status == 'success' - assert line6.pool == pool - assert line6.from_injected_line == injected_line4 + if injected_lines != 'no': + if injected_lines == 'all': + assert isinstance(line5, DraftInvoiceLine) + assert line5.invoice is None + assert line5.event_date == injected_line1.event_date + assert line5.slug == 'event-2022-08-31' + assert line5.label == 'Event 2022-08-31' + 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 == {} + assert line5.status == 'success' + assert line5.pool == pool + assert line5.from_injected_line == injected_line1 + assert isinstance(line6, DraftInvoiceLine) + assert line6.invoice is None + assert line6.event_date == injected_line2.event_date + assert line6.slug == 'event-2022-09-01' + assert line6.label == 'Event 2022-09-01' + assert line6.quantity == 2 + assert line6.unit_amount == 1.5 + assert line6.total_amount == 3 + 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 == {} + assert line6.status == 'success' + assert line6.pool == pool + assert line6.from_injected_line == injected_line2 + assert isinstance(line7, DraftInvoiceLine) + assert line7.invoice is None + assert line7.event_date == injected_line4.event_date + assert line7.slug == 'event-2022-09-30' + assert line7.label == 'Event 2022-09-30' + assert line7.quantity == 3 + assert line7.unit_amount == 1.5 + assert line7.total_amount == 4.5 + assert line7.user_external_id == 'user:1' + assert line7.user_name == 'User1 Name1' + assert line7.payer_external_id == 'user:1' + assert line7.event == {} + assert line7.pricing_data == {} + assert line7.status == 'success' + assert line7.pool == pool + assert line7.from_injected_line == injected_line4 @mock.patch('lingo.invoicing.utils.get_check_status') @@ -1016,6 +1048,7 @@ def test_get_all_invoice_lines_queryset(mock_status): date_start=datetime.date(2022, 9, 1), date_end=datetime.date(2022, 11, 1), date_issue=datetime.date(2022, 11, 30), + injected_lines='all', ) pool = Pool.objects.create( campaign=campaign, -- 2.39.2