wip/71911-invoicing-ui (#71911) #3
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "lingo/invoicing/manager_campaign_list.html" %}
|
||||
lguerin marked this conversation as resolved
|
||||
{% 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
|
@ -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)
|
|
@ -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'))
|
Loading…
Reference in New Issue
Pour ce template et les suivants, il faut ajouter une ligne dans MANIFEST.in genre
recursive-include lingo/invoicing/templates *.html *.txt
oui c'est https://gitea.entrouvert.org/entrouvert/lingo/pulls/5 :)