wip/71911-invoicing-ui (#71911) #3

Merged
lguerin merged 2 commits from wip/71911-invoicing-ui into main 2022-12-09 18:32:26 +01:00
14 changed files with 791 additions and 19 deletions

54
lingo/invoicing/forms.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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)

View File

@ -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)

View File

@ -0,0 +1,57 @@
{% extends "lingo/invoicing/manager_campaign_list.html" %}
lguerin marked this conversation as resolved
Review

Pour ce template et les suivants, il faut ajouter une ligne dans MANIFEST.in genre

recursive-include lingo/invoicing/templates *.html *.txt

Pour ce template et les suivants, il faut ajouter une ligne dans MANIFEST.in genre recursive-include lingo/invoicing/templates *.html *.txt
Review
oui c'est https://gitea.entrouvert.org/entrouvert/lingo/pulls/5 :)
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-campaign-detail' object.pk %}">{{ object }}</a>
{% endblock %}
{% block appbar %}
<h2>{{ object }}</h2>
{% if not has_real_pool %}
<span class="actions">
<a href="{% url 'lingo-manager-invoicing-campaign-delete' pk=object.pk %}" rel="popup">{% trans "Delete" %}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-edit' pk=object.pk %}" rel="popup">{% trans "Edit" %}</a>
</span>
{% endif %}
{% endblock %}
{% block content %}
<div class="section">
<div class="pk-tabs">
<div class="pk-tabs--tab-list" role="tablist">
<button aria-controls="panel-settings" aria-selected="true" id="tab-settings" role="tab" tabindex="0">{% trans "Settings" %}</button>
<button aria-controls="panel-pools" aria-selected="false" id="tab-pools" role="tab" tabindex="-1">{% trans "Pools" %}</button>
</div>
<div class="pk-tabs--container">
<div aria-labelledby="tab-settings" id="panel-settings" role="tabpanel" tabindex="0">
<ul>
<li>{% trans "Start date:" %} {{ object.date_start|date:'d/m/Y' }}</li>
<li>{% trans "End date:" %} {{ object.date_end|date:'d/m/Y' }}</li>
<li>{% trans "Issue date:" %} {{ object.date_issue|date:'d/m/Y' }}</li>
</ul>
</div>
<div aria-labelledby="tab-pools" hidden="" id="panel-pools" role="tabpanel" tabindex="0">
<ul class="objects-list single-links">
{% for pool in pools %}
<li>
<a href="{% url 'lingo-manager-invoicing-pool-detail' pk=object.pk pool_pk=pool.pk %}">
{{ pool.created_at|date:'DATETIME_FORMAT' }}
{% if pool.draft %}<span class="badge">{% trans "draft" %}</span>{% endif %}
</a>
</li>
{% endfor %}
</ul>
{% if not has_real_pool %}
<div class="panel--buttons">
<a class="pk-button" rel="popup" href="{% url 'lingo-manager-invoicing-pool-add' pk=object.pk %}">{% trans 'Start a pool' %}</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "lingo/invoicing/manager_campaign_list.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
{% if form.instance.pk %}
<a href="{% url 'lingo-manager-invoicing-campaign-detail' object.pk %}">{{ object }}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-edit' object.pk %}">{% trans "Edit" %}</a>
{% else %}
<a href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans "New campaign" %}</a>
{% endif %}
{% endblock %}
{% block appbar %}
{% if object.pk %}
<h2>{% trans "Edit campaign" %}</h2>
{% else %}
<h2>{% trans "New campaign" %}</h2>
{% endif %}
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button class="submit-button">{% trans "Save" %}</button>
{% if object.pk %}
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-detail' object.pk %}">{% trans 'Cancel' %}</a>
{% else %}
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-list' %}">{% trans 'Cancel' %}</a>
{% endif %}
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends "lingo/invoicing/manager_home.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-campaign-list' %}">{% trans "Campaigns" %}</a>
{% endblock %}
{% block appbar %}
<h2>{% trans "Campaigns" %}</h2>
<span class="actions">
<a rel="popup" href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans 'New campaign' %}</a>
</span>
{% endblock %}
{% block content %}
<div class="pk-information">
<p>{% trans "Manage here invoicing campaigns." %}</p>
</div>
{% if object_list %}
<div>
<h3>{% trans "Campaigns" %}</h3>
<ul class="objects-list single-links">
{% for object in object_list %}
<li>
<a href="{% url 'lingo-manager-invoicing-campaign-detail' pk=object.pk %}">
{{ object }}
<span class="extra-info"> [{% trans "issue date:" %} {{ object.date_issue|date:'d/m/Y' }}]</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="big-msg-info">
{% 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 %}
</div>
{% endif %}
{% endblock %}

View File

@ -17,6 +17,10 @@
{% trans "Regies" %}
<p>{% trans "Invoicing regies." %}</p>
</a>
<a class="button button-paragraph" href="{% url 'lingo-manager-invoicing-campaign-list' %}">
{% trans "Campaigns" %}
<p>{% trans "Manage invoicing campaigns." %}</p>
</a>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,51 @@
{% extends "lingo/invoicing/manager_campaign_detail.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-pool-detail' pk=object.pk pool_pk=pool.pk %}">{% trans "Pool" %}</a>
{% endblock %}
{% block appbar %}
<h2>{{ pool.created_at|date:"DATETIME_FORMAT" }}</h2>
{% if pool.draft %}
<span class="actions">
<a href="{% url 'lingo-manager-invoicing-pool-delete' pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Delete" %}</a>
</span>
{% endif %}
{% endblock %}
{% block content %}
<table class="main">
<thead>
<tr>
<th>{% trans "PK" %}</th>
<th>{% trans "Invoice PK" %}</th>
<th>{% trans "Label" %}</th>
<th>{% trans "Event" %}</th>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Unit amount" %}</th>
<th>{% trans "Total amount" %}</th>
<th>{% trans "User" %}</th>
<th>{% trans "Payer" %}</th>
<th>{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for line in lines %}
<tr>
<td>{{ line.pk }}</td>
<td>{{ line.invoice_id|default:'' }}</td>
<td>{{ line.label }}</td>
<td>{{ line.event.slug }}</td>
<td>{{ line.quantity }}</td>
<td>{{ line.unit_amount }}</td>
<td>{{ line.total_amount }}</td>
<td>{{ line.user_external_id }}</td>
<td>{{ line.adult_external_id }}</td>
<td>{{ line.get_status_display }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "lingo/invoicing/manager_campaign_detail.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans "Start a pool" %}</a>
{% endblock %}
{% block appbar %}
<h2>{% trans "New pool" %}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button class="submit-button">{% trans "Run" %}</button>
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-detail' object.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -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/<int:pk>/',
views.campaign_detail,
name='lingo-manager-invoicing-campaign-detail',
),
path(
'campaign/<int:pk>/edit/',
views.campaign_edit,
name='lingo-manager-invoicing-campaign-edit',
),
path(
'campaign/<int:pk>/delete/',
views.campaign_delete,
name='lingo-manager-invoicing-campaign-delete',
),
path(
'campaign/<int:pk>/pool/add/',
views.pool_add,
name='lingo-manager-invoicing-pool-add',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/',
views.pool_detail,
name='lingo-manager-invoicing-pool-detail',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/delete/',
views.pool_delete,
name='lingo-manager-invoicing-pool-delete',
),
]

View File

@ -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()

View File

View File

@ -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')

View File

@ -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)

View File

@ -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'))