diff --git a/lingo/invoicing/forms.py b/lingo/invoicing/forms.py new file mode 100644 index 0000000..9d12433 --- /dev/null +++ b/lingo/invoicing/forms.py @@ -0,0 +1,54 @@ +# lingo - payment and billing system +# Copyright (C) 2022 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from lingo.invoicing.models import Campaign + + +class CampaignForm(forms.ModelForm): + class Meta: + model = Campaign + fields = ['date_start', 'date_end', 'date_issue'] + widgets = { + 'date_start': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), + 'date_end': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), + 'date_issue': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'), + } + + def clean(self): + cleaned_data = super().clean() + if 'date_start' in cleaned_data and 'date_end' in cleaned_data: + overlapping_qs = Campaign.objects.extra( + where=["(date_start, date_end) OVERLAPS (%s, %s)"], + params=[cleaned_data['date_start'], cleaned_data['date_end']], + ) + if self.instance.pk: + overlapping_qs = overlapping_qs.exclude(pk=self.instance.pk) + if overlapping_qs.exists(): + self.add_error(None, _('Another campaign overlapping this period already exists.')) + + return cleaned_data + + +class PoolForm(forms.Form): + draft = forms.BooleanField(label=_('Run a simulation'), initial=True, required=False) + + def __init__(self, *args, **kwargs): + self.campaign = kwargs.pop('campaign') + super().__init__(*args, **kwargs) diff --git a/lingo/invoicing/models.py b/lingo/invoicing/models.py index 242d6be..131a3ce 100644 --- a/lingo/invoicing/models.py +++ b/lingo/invoicing/models.py @@ -19,6 +19,7 @@ import copy from django.contrib.auth.models import Group from django.contrib.postgres.fields import JSONField from django.db import models +from django.utils.formats import date_format from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ @@ -92,6 +93,12 @@ class Campaign(models.Model): date_end = models.DateField(_('End date')) date_issue = models.DateField(_('Issue date')) + def __str__(self): + return _('From %(start)s to %(end)s') % { + 'start': date_format(self.date_start, 'd/m/Y'), + 'end': date_format(self.date_end, 'd/m/Y'), + } + class Pool(models.Model): campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html new file mode 100644 index 0000000..3f76e5f --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_detail.html @@ -0,0 +1,57 @@ +{% extends "lingo/invoicing/manager_campaign_list.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {{ object }} +{% endblock %} + +{% block appbar %} +

{{ object }}

+ {% if not has_real_pool %} + + {% trans "Delete" %} + {% trans "Edit" %} + + {% endif %} +{% endblock %} + +{% block content %} +
+
+
+ + +
+
+ +
+
    +
  • {% 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' }}
  • +
+
+ + + +
+
+
+{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_form.html b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_form.html new file mode 100644 index 0000000..a8d5f96 --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_form.html @@ -0,0 +1,35 @@ +{% extends "lingo/invoicing/manager_campaign_list.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% if form.instance.pk %} + {{ object }} + {% trans "Edit" %} + {% else %} + {% trans "New campaign" %} + {% endif %} +{% endblock %} + +{% block appbar %} + {% if object.pk %} +

{% trans "Edit campaign" %}

+ {% else %} +

{% trans "New campaign" %}

+ {% endif %} +{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} +
+ + {% if object.pk %} + {% trans 'Cancel' %} + {% else %} + {% trans 'Cancel' %} + {% endif %} +
+
+{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html new file mode 100644 index 0000000..b7e3b17 --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_campaign_list.html @@ -0,0 +1,42 @@ +{% extends "lingo/invoicing/manager_home.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% trans "Campaigns" %} +{% endblock %} + +{% block appbar %} +

{% trans "Campaigns" %}

+ + {% trans 'New campaign' %} + +{% endblock %} + +{% block content %} +
+

{% trans "Manage here invoicing campaigns." %}

+
+ {% if object_list %} +
+

{% trans "Campaigns" %}

+ +
+ {% else %} +
+ {% blocktrans trimmed %} + This site doesn't have any campaign yet. Click on the "New campaign" button in the top + right of the page to add a first one. + {% endblocktrans %} +
+ {% endif %} +{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_home.html b/lingo/invoicing/templates/lingo/invoicing/manager_home.html index b518c5f..1ed09e1 100644 --- a/lingo/invoicing/templates/lingo/invoicing/manager_home.html +++ b/lingo/invoicing/templates/lingo/invoicing/manager_home.html @@ -17,6 +17,10 @@ {% trans "Regies" %}

{% trans "Invoicing regies." %}

+ + {% trans "Campaigns" %} +

{% trans "Manage invoicing campaigns." %}

+
{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html b/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html new file mode 100644 index 0000000..eb955e2 --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_pool_detail.html @@ -0,0 +1,51 @@ +{% extends "lingo/invoicing/manager_campaign_detail.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% trans "Pool" %} +{% endblock %} + +{% block appbar %} +

{{ pool.created_at|date:"DATETIME_FORMAT" }}

+ {% if pool.draft %} + + {% trans "Delete" %} + + {% endif %} +{% endblock %} + +{% block content %} + + + + + + + + + + + + + + + + + {% for line in lines %} + + + + + + + + + + + + + {% endfor %} + +
{% trans "PK" %}{% trans "Invoice PK" %}{% trans "Label" %}{% trans "Event" %}{% trans "Quantity" %}{% trans "Unit amount" %}{% trans "Total amount" %}{% trans "User" %}{% trans "Payer" %}{% trans "Status" %}
{{ line.pk }}{{ line.invoice_id|default:'' }}{{ line.label }}{{ line.event.slug }}{{ line.quantity }}{{ line.unit_amount }}{{ line.total_amount }}{{ line.user_external_id }}{{ line.adult_external_id }}{{ line.get_status_display }}
+{% endblock %} diff --git a/lingo/invoicing/templates/lingo/invoicing/manager_pool_form.html b/lingo/invoicing/templates/lingo/invoicing/manager_pool_form.html new file mode 100644 index 0000000..e590fb1 --- /dev/null +++ b/lingo/invoicing/templates/lingo/invoicing/manager_pool_form.html @@ -0,0 +1,22 @@ +{% extends "lingo/invoicing/manager_campaign_detail.html" %} +{% load i18n %} + +{% block breadcrumb %} + {{ block.super }} + {% trans "Start a pool" %} +{% endblock %} + +{% block appbar %} +

{% trans "New pool" %}

+{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} +
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/lingo/invoicing/urls.py b/lingo/invoicing/urls.py index 526954d..31c19de 100644 --- a/lingo/invoicing/urls.py +++ b/lingo/invoicing/urls.py @@ -43,4 +43,40 @@ urlpatterns = [ ), path('regies/import/', views.regies_import, name='lingo-manager-invoicing-regie-import'), path('regies/export/', views.regies_export, name='lingo-manager-invoicing-regie-export'), + path('campaigns/', views.campaign_list, name='lingo-manager-invoicing-campaign-list'), + path( + 'campaign/add/', + views.campaign_add, + name='lingo-manager-invoicing-campaign-add', + ), + path( + 'campaign//', + views.campaign_detail, + name='lingo-manager-invoicing-campaign-detail', + ), + path( + 'campaign//edit/', + views.campaign_edit, + name='lingo-manager-invoicing-campaign-edit', + ), + path( + 'campaign//delete/', + views.campaign_delete, + name='lingo-manager-invoicing-campaign-delete', + ), + path( + 'campaign//pool/add/', + views.pool_add, + name='lingo-manager-invoicing-pool-add', + ), + path( + 'campaign//pool//', + views.pool_detail, + name='lingo-manager-invoicing-pool-detail', + ), + path( + 'campaign//pool//delete/', + views.pool_delete, + name='lingo-manager-invoicing-pool-delete', + ), ] diff --git a/lingo/invoicing/views.py b/lingo/invoicing/views.py index fa75e66..9e4c8d7 100644 --- a/lingo/invoicing/views.py +++ b/lingo/invoicing/views.py @@ -21,6 +21,7 @@ import json from django.contrib import messages from django.db import transaction from django.http import HttpResponse +from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext @@ -35,7 +36,17 @@ from django.views.generic import ( ) from lingo.agendas.models import Agenda -from lingo.invoicing.models import Regie, RegieImportError +from lingo.invoicing.forms import CampaignForm, PoolForm +from lingo.invoicing.models import ( + Campaign, + DraftInvoice, + DraftInvoiceLine, + InvoiceLine, + Pool, + Regie, + RegieImportError, +) +from lingo.invoicing.utils import generate_invoices from lingo.pricing.forms import ImportForm @@ -175,3 +186,150 @@ class RegiesImportView(FormView): regies_import = RegiesImportView.as_view() + + +class CampaignListView(ListView): + template_name = 'lingo/invoicing/manager_campaign_list.html' + model = Campaign + + +campaign_list = CampaignListView.as_view() + + +class CampaignAddView(CreateView): + template_name = 'lingo/invoicing/manager_campaign_form.html' + model = Campaign + form_class = CampaignForm + + def get_success_url(self): + return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) + + +campaign_add = CampaignAddView.as_view() + + +class CampaignDetailView(DetailView): + template_name = 'lingo/invoicing/manager_campaign_detail.html' + model = Campaign + + def get_context_data(self, **kwargs): + kwargs['pools'] = self.object.pool_set.order_by('created_at') + kwargs['has_real_pool'] = any(not p.draft for p in kwargs['pools']) + return super().get_context_data(**kwargs) + + +campaign_detail = CampaignDetailView.as_view() + + +class CampaignEditView(UpdateView): + template_name = 'lingo/invoicing/manager_campaign_form.html' + model = Campaign + form_class = CampaignForm + + def get_queryset(self): + return super().get_queryset().exclude(pool__draft=False) + + def get_success_url(self): + return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) + + +campaign_edit = CampaignEditView.as_view() + + +class CampaignDeleteView(DeleteView): + template_name = 'lingo/manager_confirm_delete.html' + model = Campaign + + def get_queryset(self): + return super().get_queryset().exclude(pool__draft=False) + + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + DraftInvoiceLine.objects.filter(pool__campaign=self.object).delete() + DraftInvoice.objects.filter(pool__campaign=self.object).delete() + Pool.objects.filter(campaign=self.object).delete() + return super().delete(request, *args, **kwargs) + + def get_success_url(self): + return reverse('lingo-manager-invoicing-campaign-list') + + +campaign_delete = CampaignDeleteView.as_view() + + +class PoolDetailView(DetailView): + template_name = 'lingo/invoicing/manager_pool_detail.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) + + 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 + kwargs['lines'] = line_model.objects.filter(pool=self.object) + return super().get_context_data(**kwargs) + + +pool_detail = PoolDetailView.as_view() + + +class PoolAddView(FormView): + template_name = 'lingo/invoicing/manager_pool_form.html' + form_class = PoolForm + + def dispatch(self, request, *args, **kwargs): + self.object = get_object_or_404(Campaign.objects.exclude(pool__draft=False), pk=kwargs['pk']) + return super().dispatch(request, *args, **kwargs) + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['campaign'] = self.object + return kwargs + + def get_context_data(self, **kwargs): + kwargs['object'] = self.object + return super().get_context_data(**kwargs) + + def form_valid(self, form): + generate_invoices(campaign=self.object, draft=form.cleaned_data['draft']) + return super().form_valid(form) + + def get_success_url(self): + return '%s#open:pools' % reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk]) + + +pool_add = PoolAddView.as_view() + + +class PoolDeleteView(DeleteView): + template_name = 'lingo/manager_confirm_delete.html' + model = Pool + pk_url_kwarg = 'pool_pk' + + def dispatch(self, request, *args, **kwargs): + self.campaign = get_object_or_404(Campaign.objects.exclude(pool__draft=False), pk=kwargs['pk']) + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self): + return self.campaign.pool_set.filter(draft=True) + + def delete(self, request, *args, **kwargs): + self.object = self.get_object() + DraftInvoiceLine.objects.filter(pool=self.object).delete() + DraftInvoice.objects.filter(pool=self.object).delete() + return super().delete(request, *args, **kwargs) + + def get_success_url(self): + return '%s#open:pools' % reverse('lingo-manager-invoicing-campaign-detail', args=[self.campaign.pk]) + + +pool_delete = PoolDeleteView.as_view() diff --git a/tests/invoicing/manager/__init__.py b/tests/invoicing/manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/invoicing/manager/test_all.py b/tests/invoicing/manager/test_all.py new file mode 100644 index 0000000..b432b3c --- /dev/null +++ b/tests/invoicing/manager/test_all.py @@ -0,0 +1,28 @@ +import pytest +from django.urls import reverse + +from tests.utils import login + +pytestmark = pytest.mark.django_db + + +def test_manager_home_show_invoicing(app, admin_user): + app = login(app) + resp = app.get('/manage/') + anchor = resp.pyquery('div#appbar span.actions a[href="%s"]' % reverse('lingo-manager-invoicing-home')) + assert anchor.text() == 'Invoicing' + + +def test_manager_invoicing_home(app, admin_user): + app = login(app) + resp = app.get(reverse('lingo-manager-invoicing-home')) + h2 = resp.pyquery('div#appbar h2') + assert h2.text() == 'Invoicing' + anchor = resp.pyquery( + 'div#lingo-manager-main div a[href="%s"]' % reverse('lingo-manager-invoicing-regie-list') + ) + assert anchor.text().startswith('Regies') + anchor = resp.pyquery( + 'div#lingo-manager-main div a[href="%s"]' % reverse('lingo-manager-invoicing-campaign-list') + ) + assert anchor.text().startswith('Campaigns') diff --git a/tests/invoicing/manager/test_campaign.py b/tests/invoicing/manager/test_campaign.py new file mode 100644 index 0000000..e1e84af --- /dev/null +++ b/tests/invoicing/manager/test_campaign.py @@ -0,0 +1,296 @@ +import datetime +from unittest import mock + +import pytest + +from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Pool, Regie +from tests.utils import login + +pytestmark = pytest.mark.django_db + + +def test_campaign_home(app, admin_user): + app = login(app) + resp = app.get('/manage/invoicing/campaigns/') + h2 = resp.pyquery('div#appbar h2') + assert h2.text() == 'Campaigns' + + +def test_add_campaign(app, admin_user): + app = login(app) + resp = app.get('/manage/') + resp = resp.click('Invoicing') + resp = resp.click(href='/manage/invoicing/campaigns/') + resp = resp.click('New campaign') + resp.form['date_start'] = '2022-09-01' + resp.form['date_end'] = '2022-10-01' + resp.form['date_issue'] = '2022-10-31' + resp = resp.form.submit() + campaign = Campaign.objects.latest('pk') + assert resp.location.endswith('/manage/invoicing/campaign/%s/' % campaign.pk) + assert campaign.date_start == datetime.date(2022, 9, 1) + assert campaign.date_end == datetime.date(2022, 10, 1) + assert campaign.date_issue == datetime.date(2022, 10, 31) + + resp = app.get('/manage/invoicing/campaign/add/') + resp.form['date_start'] = '2022-08-31' + resp.form['date_end'] = '2022-09-02' + resp.form['date_issue'] = '2022-10-31' + resp = resp.form.submit() + assert resp.context['form'].errors['__all__'] == [ + 'Another campaign overlapping this period already exists.' + ] + resp.form['date_end'] = '2022-09-01' + resp = resp.form.submit() + campaign = Campaign.objects.latest('pk') + assert campaign.date_start == datetime.date(2022, 8, 31) + assert campaign.date_end == datetime.date(2022, 9, 1) + assert campaign.date_issue == datetime.date(2022, 10, 31) + + resp = app.get('/manage/invoicing/campaign/add/') + resp.form['date_start'] = '2022-09-30' + resp.form['date_end'] = '2022-10-02' + resp.form['date_issue'] = '2022-10-31' + resp = resp.form.submit() + assert resp.context['form'].errors['__all__'] == [ + 'Another campaign overlapping this period already exists.' + ] + resp.form['date_start'] = '2022-10-01' + resp = resp.form.submit() + campaign = Campaign.objects.latest('pk') + assert campaign.date_start == datetime.date(2022, 10, 1) + assert campaign.date_end == datetime.date(2022, 10, 2) + assert campaign.date_issue == datetime.date(2022, 10, 31) + + +def test_detail_campaign(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), + ) + pool1 = Pool.objects.create( + campaign=campaign, + draft=True, + ) + pool2 = Pool.objects.create( + campaign=campaign, + draft=True, + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaigns/') + resp = resp.click(href='/manage/invoicing/campaign/%s/' % campaign.pk) + assert '/manage/invoicing/campaign/%s/edit/' % campaign.pk in resp + assert '/manage/invoicing/campaign/%s/delete/' % campaign.pk in resp + assert '/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool1.pk) in resp + assert '/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool2.pk) in resp + assert '/manage/invoicing/campaign/%s/pool/add/' % (campaign.pk) in resp + + pool3 = Pool.objects.create( + campaign=campaign, + draft=False, + ) + resp = app.get('/manage/invoicing/campaign/%s/' % campaign.pk) + assert '/manage/invoicing/campaign/%s/edit/' % campaign.pk not in resp + assert '/manage/invoicing/campaign/%s/delete/' % campaign.pk not in resp + assert '/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool1.pk) in resp + assert '/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool2.pk) in resp + assert '/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool3.pk) in resp + assert '/manage/invoicing/campaign/%s/pool/add/' % (campaign.pk) not in resp + + +def test_edit_campaign(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), + ) + Campaign.objects.create( + date_start=datetime.date(2022, 10, 1), + date_end=datetime.date(2022, 11, 1), + date_issue=datetime.date(2022, 11, 30), + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/edit/' % campaign.pk) + resp.form['date_start'] = '2022-09-30' + resp.form['date_end'] = '2022-10-02' + resp.form['date_issue'] = '2022-12-31' + resp = resp.form.submit() + assert resp.context['form'].errors['__all__'] == [ + 'Another campaign overlapping this period already exists.' + ] + resp.form['date_end'] = '2022-10-01' + resp = resp.form.submit() + assert resp.location.endswith('/manage/invoicing/campaign/%s/' % campaign.pk) + campaign.refresh_from_db() + assert campaign.date_start == datetime.date(2022, 9, 30) + assert campaign.date_end == datetime.date(2022, 10, 1) + assert campaign.date_issue == datetime.date(2022, 12, 31) + + resp = app.get('/manage/invoicing/campaign/%s/edit/' % campaign.pk) + resp.form['date_start'] = '2022-10-31' + resp.form['date_end'] = '2022-11-02' + resp.form['date_issue'] = '2022-12-31' + resp = resp.form.submit() + assert resp.context['form'].errors['__all__'] == [ + 'Another campaign overlapping this period already exists.' + ] + resp.form['date_start'] = '2022-11-01' + resp = resp.form.submit() + campaign.refresh_from_db() + assert campaign.date_start == datetime.date(2022, 11, 1) + assert campaign.date_end == datetime.date(2022, 11, 2) + assert campaign.date_issue == datetime.date(2022, 12, 31) + + pool = Pool.objects.create( + campaign=campaign, + draft=True, + ) + app.get('/manage/invoicing/campaign/%s/edit/' % campaign.pk) + pool.draft = False + pool.save() + app.get('/manage/invoicing/campaign/%s/edit/' % campaign.pk, status=404) + + +def test_delete_campaign(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), + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/delete/' % campaign.pk) + resp = resp.form.submit() + assert Campaign.objects.count() == 0 + assert resp.location.endswith('/manage/invoicing/campaigns/') + + campaign.save() + pool = Pool.objects.create( + campaign=campaign, + draft=True, + ) + regie = Regie.objects.create(label='Foo') + invoice = DraftInvoice.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool) + DraftInvoiceLine.objects.create( + invoice=invoice, + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + ) + resp = app.get('/manage/invoicing/campaign/%s/delete/' % campaign.pk) + resp = resp.form.submit() + assert Campaign.objects.count() == 0 + + campaign.save() + pool.draft = False + pool.save() + app.get('/manage/invoicing/campaign/%s/delete/' % campaign.pk, status=404) + + +def test_add_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), + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/pool/add/' % campaign.pk) + assert resp.form['draft'].value == 'on' + with mock.patch('lingo.invoicing.views.generate_invoices') as mock_generate: + resp = resp.form.submit() + assert resp.location.endswith('/manage/invoicing/campaign/%s/#open:pools' % campaign.pk) + assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=True)] + + pool = Pool.objects.create( + campaign=campaign, + draft=True, + ) + resp = app.get('/manage/invoicing/campaign/%s/pool/add/' % campaign.pk) + resp.form['draft'] = False + with mock.patch('lingo.invoicing.views.generate_invoices') as mock_generate: + resp = resp.form.submit() + assert resp.location.endswith('/manage/invoicing/campaign/%s/#open:pools' % campaign.pk) + assert mock_generate.call_args_list == [mock.call(campaign=campaign, draft=False)] + + pool.draft = False + pool.save() + app.get('/manage/invoicing/campaign/%s/pool/add/' % campaign.pk, status=404) + + +def test_detail_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, + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/' % (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/' % (0, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign2.pk, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, 0), status=404) + + pool.draft = False + pool.save() + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/' % (campaign.pk, pool.pk)) + assert '/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk) not in resp + + +def test_delete_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, + ) + regie = Regie.objects.create(label='Foo') + invoice = DraftInvoice.objects.create(date_issue=datetime.date.today(), regie=regie, pool=pool) + DraftInvoiceLine.objects.create( + invoice=invoice, + quantity=1, + unit_amount=1, + total_amount=1, + status='success', + pool=pool, + ) + + app = login(app) + resp = app.get('/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk)) + resp = resp.form.submit() + assert Pool.objects.count() == 0 + assert resp.location.endswith('/manage/invoicing/campaign/%s/#open:pools' % campaign.pk) + + pool.draft = True + pool.save() + app.get('/manage/invoicing/campaign/%s/pool/%s/delete/' % (0, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign2.pk, pool.pk), status=404) + app.get('/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, 0), status=404) + + pool.draft = False + pool.save() + app.get('/manage/invoicing/campaign/%s/pool/%s/delete/' % (campaign.pk, pool.pk), status=404) diff --git a/tests/invoicing/test_manager.py b/tests/invoicing/manager/test_regie.py similarity index 91% rename from tests/invoicing/test_manager.py rename to tests/invoicing/manager/test_regie.py index 09f3d52..6c5acce 100644 --- a/tests/invoicing/test_manager.py +++ b/tests/invoicing/manager/test_regie.py @@ -13,24 +13,6 @@ from tests.utils import login pytestmark = pytest.mark.django_db -def test_manager_home_show_invoicing(app, admin_user): - app = login(app) - resp = app.get('/manage/') - anchor = resp.pyquery('div#appbar span.actions a[href="%s"]' % reverse('lingo-manager-invoicing-home')) - assert anchor.text() == 'Invoicing' - - -def test_manager_invoicing_home(app, admin_user): - app = login(app) - resp = app.get(reverse('lingo-manager-invoicing-home')) - h2 = resp.pyquery('div#appbar h2') - assert h2.text() == 'Invoicing' - anchor = resp.pyquery( - 'div#lingo-manager-main div a[href="%s"]' % reverse('lingo-manager-invoicing-regie-list') - ) - assert anchor.text().startswith('Regies') - - def test_manager_invoicing_regie_list_title(app, admin_user): app = login(app) resp = app.get(reverse('lingo-manager-invoicing-regie-list'))