Facturation: ranger les campagnes de facturation dans une régie (#74516) #29

Merged
lguerin merged 5 commits from wip/74516-invoicing-attach-campaign-to-regie into main 2023-03-10 08:52:49 +01:00
25 changed files with 905 additions and 511 deletions

View File

@ -19,7 +19,7 @@ import django_filters
from django import forms
from django.utils.translation import ugettext_lazy as _
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine, Regie
from lingo.invoicing.models import Campaign, DraftInvoice, DraftInvoiceLine, Invoice, InvoiceLine
class CampaignForm(forms.ModelForm):
@ -35,7 +35,7 @@ class CampaignForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if 'date_start' in cleaned_data and 'date_end' in cleaned_data:
overlapping_qs = Campaign.objects.extra(
overlapping_qs = Campaign.objects.filter(regie=self.instance.regie).extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"],
params=[cleaned_data['date_start'], cleaned_data['date_end']],
)
@ -47,15 +47,7 @@ class CampaignForm(forms.ModelForm):
return cleaned_data
def regie_queryset(request):
return Regie.objects.all()
class AbstractInvoiceFilterSet(django_filters.FilterSet):
regie = django_filters.ModelChoiceFilter(
label=_('Regie'),
queryset=regie_queryset,
)
# for Invoice
number = django_filters.CharFilter(
label=_('Invoice number'),
@ -107,11 +99,6 @@ class InvoiceFilterSet(AbstractInvoiceFilterSet):
class AbstractLineFilterSet(django_filters.FilterSet):
regie = django_filters.ModelChoiceFilter(
label=_('Regie'),
queryset=regie_queryset,
field_name='invoice__regie',
)
# for InvoiceLine
invoice_number = django_filters.CharFilter(
label=_('Invoice number'),

View File

@ -18,7 +18,7 @@ import datetime
from django.core.management.base import BaseCommand, CommandError
from lingo.invoicing.models import Campaign
from lingo.invoicing.models import Campaign, Regie
class Command(BaseCommand):
@ -27,6 +27,7 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('date_start')
parser.add_argument('date_end')
parser.add_argument('regie_id')
parser.add_argument('--date-issue')
def handle(self, *args, **options):
@ -38,6 +39,10 @@ class Command(BaseCommand):
date_end = datetime.datetime.fromisoformat(options['date_end']).date()
except ValueError:
raise CommandError('Bad value "%s" for date_end' % options['date_end'])
try:
regie = Regie.objects.get(pk=options['regie_id'])
except Regie.DoesNotExist:
raise CommandError('Bad value "%s" for regie_id' % options['regie_id'])
if options.get('date_issue'):
try:
date_issue = datetime.datetime.fromisoformat(options['date_issue']).date()
@ -47,15 +52,15 @@ class Command(BaseCommand):
date_issue = date_end
try:
campaign = Campaign.objects.get(date_start=date_start, date_end=date_end)
campaign = Campaign.objects.get(date_start=date_start, date_end=date_end, regie=regie)
except Campaign.DoesNotExist:
campaigns = Campaign.objects.extra(
campaigns = Campaign.objects.filter(regie=regie).extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"], params=[date_start, date_end]
)
if campaigns.exists():
raise CommandError('Overlapping campaigns already exist')
campaign = Campaign.objects.create(
date_start=date_start, date_end=date_end, date_issue=date_issue
regie=regie, date_start=date_start, date_end=date_end, date_issue=date_issue
)
campaign.generate(spool=False)

View File

@ -0,0 +1,28 @@
from django.db import migrations
lguerin marked this conversation as resolved
Review

Ok, je comprends la nécessité du nettoyage ici, mais n’y a-t-il pas des instances en recettes où des données ont déjà été créées à des fins de tests, par Stef ou quelqu’un d’autre, et qu’il faudrait sauvegarder dans un coin avant d’exécuter cette migration ?

Ok, je comprends la nécessité du nettoyage ici, mais n’y a-t-il pas des instances en recettes où des données ont déjà été créées à des fins de tests, par Stef ou quelqu’un d’autre, et qu’il faudrait sauvegarder dans un coin avant d’exécuter cette migration ?
Review

non pas de données à conserver, et c'était écrit dans la description du ticket :)

non pas de données à conserver, et c'était écrit dans la description du ticket :)
Review

Arf désolé j’ai loupé ça.

Arf désolé j’ai loupé ça.
def forward(apps, schema_editor):
Campaign = apps.get_model('invoicing', 'Campaign')
Pool = apps.get_model('invoicing', 'Pool')
DraftInvoice = apps.get_model('invoicing', 'DraftInvoice')
DraftInvoiceLine = apps.get_model('invoicing', 'DraftInvoiceLine')
Invoice = apps.get_model('invoicing', 'Invoice')
InvoiceLine = apps.get_model('invoicing', 'InvoiceLine')
InvoiceLine.objects.all().delete()
Invoice.objects.all().delete()
DraftInvoiceLine.objects.all().delete()
DraftInvoice.objects.all().delete()
Pool.objects.all().delete()
Campaign.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
('invoicing', '0015_injected_lines'),
]
operations = [
migrations.RunPython(forward, reverse_code=migrations.RunPython.noop),
]

View File

@ -0,0 +1,17 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('invoicing', '0016_campaign_regie'),
]
operations = [
migrations.AddField(
model_name='campaign',
name='regie',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='invoicing.Regie'),
Review

Ok, mais il faut alors dans la RegieDeleteView attraper les erreurs (django.db.ProtectedError) levées à la tentative de suppression alors qu’il existe encore des Campaign liées. Je ne vois ça nulle part (et la DeleteView django de base héritée ne gère pas cela du tout), je loupe un truc ?
Si c’est effectivement un oubli, sans doute qu’un petit test en plus serait le bienvenu.

Ok, mais il faut alors dans la RegieDeleteView attraper les erreurs (`django.db.ProtectedError`) levées à la tentative de suppression alors qu’il existe encore des Campaign liées. Je ne vois ça nulle part (et la DeleteView django de base héritée ne gère pas cela du tout), je loupe un truc ? Si c’est effectivement un oubli, sans doute qu’un petit test en plus serait le bienvenu.
Review

Justement j'ai choisi de mettre PROTECT pour garantir que rien n'est supprimé si ce n'est pas explicitement géré par le code.
Un delete sauvage d'une régie n'est pas possible, l'orm vérifie les relations et balance une erreur avant de tenter la suppression
(l'orm ne délègue pas la gestion des FK et des relations à la DB, il le gère lui-même et se comporte en fonction du on_delete).

Les données de facturation étant sensibles, je préfère lever une erreur et ne pas laisser passer un delete irréfléchi.
(et une campagne n'a pas vocation à être supprimée, on doit conserver les données de facturation)

Justement j'ai choisi de mettre PROTECT pour garantir que rien n'est supprimé si ce n'est pas explicitement géré par le code. Un delete sauvage d'une régie n'est pas possible, l'orm vérifie les relations et balance une erreur avant de tenter la suppression (l'orm ne délègue pas la gestion des FK et des relations à la DB, il le gère lui-même et se comporte en fonction du on_delete). Les données de facturation étant sensibles, je préfère lever une erreur et ne pas laisser passer un delete irréfléchi. (et une campagne n'a pas vocation à être supprimée, on doit conserver les données de facturation)
Review

Ok, à dérouler le code de Lingo puis de la DeleteView de base Django, j’étais dans l’impression que la RegieDeleteView sur une régie qui est encore liée à des campagnes allait lancer une bête trace python au lieu d’une erreur applicative propre. Je loupe un truc ?

Ok, à dérouler le code de Lingo puis de la DeleteView de base Django, j’étais dans l’impression que la RegieDeleteView sur une régie qui est encore liée à des campagnes allait lancer une bête trace python au lieu d’une erreur applicative propre. Je loupe un truc ?
Review

ha zut il y a une vue de suppression de régie ? bien vu :)
je m'en vais donc interdire la suppression de régie si campagne ou facture liées.

ha zut il y a une vue de suppression de régie ? bien vu :) je m'en vais donc interdire la suppression de régie si campagne ou facture liées.
Review

commit ajouté

commit ajouté
Review

¡Maravilloso!

¡Maravilloso!
),
]

View File

@ -35,11 +35,6 @@ class RegieImportError(Exception):
pass
class RegieNotConfigured(Exception):
def __init__(self, msg):
self.msg = msg
class PoolPromotionError(Exception):
def __init__(self, msg):
self.msg = msg
@ -131,6 +126,7 @@ class Regie(models.Model):
class Campaign(models.Model):
regie = models.ForeignKey(Regie, on_delete=models.PROTECT)
date_start = models.DateField(_('Start date'))
date_end = models.DateField(_('End date'))
date_issue = models.DateField(_('Issue date'))
@ -202,8 +198,8 @@ class Pool(models.Model):
# get invoice lines for all subscribed users, for each agenda in the corresponding period
lines = utils.get_all_invoice_lines(agendas=agendas, users=users, pool=self)
# and generate invoices
utils.generate_invoices_from_lines(agendas=agendas, all_lines=lines, pool=self)
except (RegieNotConfigured, ChronoError) as e:
utils.generate_invoices_from_lines(all_lines=lines, pool=self)
except ChronoError as e:
self.status = 'failed'
self.exception = e.msg
except Exception:

View File

@ -1,17 +1,17 @@
{% extends "lingo/invoicing/manager_campaign_list.html" %}
{% extends "lingo/invoicing/manager_regie_detail.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-campaign-detail' object.pk %}">{{ object }}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-detail' regie.pk object.pk %}">{{ object }}</a>
{% endblock %}
{% block appbar %}
<h2>{{ object }}</h2>
{% if not has_running_pool and 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>
<a href="{% url 'lingo-manager-invoicing-campaign-delete' regie_pk=regie.pk pk=object.pk %}" rel="popup">{% trans "Delete" %}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-edit' regie_pk=regie.pk pk=object.pk %}" rel="popup">{% trans "Edit" %}</a>
</span>
{% endif %}
{% endblock %}
@ -38,7 +38,7 @@
<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 %}">
<a href="{% url 'lingo-manager-invoicing-pool-detail' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">
{% if pool.draft_success_count or pool.success_count %}<span class="tag tag-success">{{ pool.draft_success_count|default:pool.success_count }}</span>{% endif %}
{% if pool.draft_warning_count or pool.warning_count %}<span class="tag tag-warning">{{ pool.draft_warning_count|default:pool.warning_count }}</span>{% endif %}
{% if pool.draft_error_count or pool.error_count %}<span class="tag tag-error">{{ pool.draft_error_count|default:pool.error_count }}</span>{% endif %}
@ -57,7 +57,7 @@
</ul>
{% if not has_running_pool and 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>
<a class="pk-button" rel="popup" href="{% url 'lingo-manager-invoicing-pool-add' regie_pk=regie.pk pk=object.pk %}">{% trans 'Start a pool' %}</a>
</div>
{% endif %}
</div>

View File

@ -1,13 +1,13 @@
{% extends "lingo/invoicing/manager_campaign_list.html" %}
{% extends "lingo/invoicing/manager_regie_detail.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>
<a href="{% url 'lingo-manager-invoicing-campaign-detail' regie.pk object.pk %}">{{ object }}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-edit' regie.pk object.pk %}">{% trans "Edit" %}</a>
{% else %}
<a href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans "New campaign" %}</a>
<a href="{% url 'lingo-manager-invoicing-campaign-add' regie.pk %}">{% trans "New campaign" %}</a>
{% endif %}
{% endblock %}
@ -26,9 +26,9 @@
<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>
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-detail' regie.pk object.pk %}">{% trans 'Cancel' %}</a>
{% else %}
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-list' %}">{% trans 'Cancel' %}</a>
<a class="cancel" href="{% url 'lingo-manager-invoicing-regie-detail' regie.pk %}">{% trans 'Cancel' %}</a>
{% endif %}
</div>
</form>

View File

@ -1,43 +0,0 @@
{% 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 href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' %}">{% trans 'Non invoiced lines' %}</a>
<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,10 +17,6 @@
{% 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

@ -1,4 +1,4 @@
{% url 'lingo-manager-invoicing-pool-journal' pk=object.pk pool_pk=pool.pk as journal_url %}
{% url 'lingo-manager-invoicing-pool-journal' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk as journal_url %}
{% for line in object_list %}
<li class="line" data-invoice-id="{{ line.invoice_id }}">
<a href="{{ journal_url }}?pk={{ line.pk }}">#{{ line.pk }}</a>

View File

@ -30,11 +30,11 @@
{% if line.status == 'error' and not pool.draft %}
<br />
{% if line.error_status %}
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' pk=object.pk pool_pk=pool.pk line_pk=line.pk status='reset' %}">{% trans "reset" %}</a>
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk line_pk=line.pk status='reset' %}">{% trans "reset" %}</a>
{% else %}
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' pk=object.pk pool_pk=pool.pk line_pk=line.pk status='ignore' %}">{% trans "ignore" %}</a>
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk line_pk=line.pk status='ignore' %}">{% trans "ignore" %}</a>
-
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' pk=object.pk pool_pk=pool.pk line_pk=line.pk status='fix' %}">{% trans "mark as fixed" %}</a>
<a class="error-status" href="{% url 'lingo-manager-invoicing-line-set-error-status' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk line_pk=line.pk status='fix' %}">{% trans "mark as fixed" %}</a>
{% endif %}
{% endif %}
{% endif %}

View File

@ -1,9 +1,9 @@
{% extends "lingo/invoicing/manager_campaign_list.html" %}
{% extends "lingo/invoicing/manager_regie_detail.html" %}
{% load i18n %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' %}">{% trans "Non invoiced lines" %}</a>
<a href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' regie_pk=regie.pk %}">{% trans "Non invoiced lines" %}</a>
{% endblock %}
{% block appbar %}
@ -32,7 +32,7 @@
{{ line.event_date|date:"d/m/Y" }} - {{ line.label }}
<br />
{% if line.status == 'error'%}
<a href="{% url 'lingo-manager-invoicing-pool-journal' pk=line.campaign_id pool_pk=line.pool_id %}?pk={{ line.pk }}">({{ line.slug }})</a>
<a href="{% url 'lingo-manager-invoicing-pool-journal' regie_pk=regie.pk pk=line.campaign_id pool_pk=line.pool_id %}?pk={{ line.pk }}">({{ line.slug }})</a>
{% else %}
({{ line.slug }})
{% endif %}

View File

@ -3,7 +3,7 @@
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-campaign-add' %}">{% trans "Start a pool" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-add' regie.pk object.pk %}">{% trans "Start a pool" %}</a>
{% endblock %}
{% block appbar %}
@ -16,7 +16,7 @@
<p>{% trans "Are you sure you want to start a new pool?" %}</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>
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-detail' regie.pk object.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -3,7 +3,7 @@
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-pool-detail' pk=object.pk pool_pk=pool.pk %}">{% trans "Pool" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-detail' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">{% trans "Pool" %}</a>
{% endblock %}
{% block appbar %}
@ -14,12 +14,12 @@
{{ pool.created_at|date:"DATETIME_FORMAT" }}
</h2>
<span class="actions">
<a href="{% url 'lingo-manager-invoicing-pool-journal' pk=object.pk pool_pk=pool.pk %}">{% trans "Journal" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-journal' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">{% trans "Journal" %}</a>
{% if pool.draft and pool.status != 'registered' and pool.status != 'running' %}
<a href="{% url 'lingo-manager-invoicing-pool-delete' pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Delete" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-delete' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Delete" %}</a>
{% endif %}
{% if pool.draft and pool.status == 'completed' and pool.is_last %}
<a href="{% url 'lingo-manager-invoicing-pool-promote' pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Promote" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-promote' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Promote" %}</a>
{% endif %}
</span>
{% endblock %}
@ -45,15 +45,15 @@
</form>
</div>
</div>
{% url 'lingo-manager-invoicing-pool-journal' pk=object.pk pool_pk=pool.pk as journal_url %}
{% url 'lingo-manager-invoicing-pool-journal' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk as journal_url %}
<div>
<ul class="objects-list">
{% for invoice in object_list %}
<li class="invoice untoggled" data-invoice-id="{{ invoice.pk }}" data-invoice-lines-url="{% url 'lingo-manager-invoicing-invoice-line-list' pk=object.pk pool_pk=pool.pk invoice_pk=invoice.pk %}">
<li class="invoice untoggled" data-invoice-id="{{ invoice.pk }}" data-invoice-lines-url="{% url 'lingo-manager-invoicing-invoice-line-list' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk invoice_pk=invoice.pk %}">
{% if pool.draft %}
{% blocktrans with number=invoice.pk payer=invoice.payer amount=invoice.total_amount %}Invoice <a href="{{ journal_url }}?invoice_id={{ number }}">PROFORMA-{{ number }}</a> addressed to <a href="{{ journal_url }}?payer_external_id={{ payer }}">{{ payer }}</a>, amount {{ amount }}€{% endblocktrans %}
{% else %}
{% blocktrans with invoice_number=invoice.formatted_number payer=invoice.payer amount=invoice.total_amount number=invoice.number regie_id=invoice.regie_id %}Invoice <a href="{{ journal_url }}?invoice_number={{ number }}&regie={{ regie_id }}">{{ invoice_number }}</a> addressed to <a href="{{ journal_url }}?payer_external_id={{ payer }}">{{ payer }}</a>, amount {{ amount }}€{% endblocktrans %}
{% blocktrans with invoice_number=invoice.formatted_number payer=invoice.payer amount=invoice.total_amount number=invoice.number %}Invoice <a href="{{ journal_url }}?invoice_number={{ number }}">{{ invoice_number }}</a> addressed to <a href="{{ journal_url }}?payer_external_id={{ payer }}">{{ payer }}</a>, amount {{ amount }}€{% endblocktrans %}
{% endif %}
<span class="togglable"></span>
</li>

View File

@ -3,7 +3,7 @@
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-pool-journal' pk=object.pk pool_pk=pool.pk %}">{% trans "Journal" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-journal' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">{% trans "Journal" %}</a>
{% endblock %}
{% block appbar %}
@ -15,7 +15,7 @@
</h2>
{% if pool.draft and pool.status != 'registered' and pool.status != 'running' %}
<span class="actions">
<a href="{% url 'lingo-manager-invoicing-pool-delete' pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Delete" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-delete' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}" rel="popup">{% trans "Delete" %}</a>
</span>
{% endif %}
{% endblock %}

View File

@ -3,7 +3,7 @@
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-pool-promote' pk=object.pk pool_pk=pool.pk %}">{% trans "Promote" %}</a>
<a href="{% url 'lingo-manager-invoicing-pool-promote' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">{% trans "Promote" %}</a>
{% endblock %}
{% block appbar %}
@ -16,7 +16,7 @@
<p>{% trans "Are you sure you want to promote this pool ? This action is irreversible." %}</p>
<div class="buttons">
<button class="submit-button">{% trans "Promote" %}</button>
<a class="cancel" href="{% url 'lingo-manager-invoicing-pool-detail' pk=object.pk pool_pk=pool.pk %}">{% trans 'Cancel' %}</a>
<a class="cancel" href="{% url 'lingo-manager-invoicing-pool-detail' regie_pk=regie.pk pk=object.pk pool_pk=pool.pk %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -3,14 +3,17 @@
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'lingo-manager-invoicing-regie-detail' regie.pk %}">{{ object }}</a>
<a href="{% url 'lingo-manager-invoicing-regie-detail' regie.pk %}">{{ regie }}</a>
{% endblock %}
{% block appbar %}
{{ block.super }}
<span class="actions">
<a href="{% url 'lingo-manager-invoicing-regie-delete' pk=regie.pk %}" rel="popup">{% trans "Delete" %}</a>
{% if not has_related_objects %}
<a href="{% url 'lingo-manager-invoicing-regie-delete' pk=regie.pk %}" rel="popup">{% trans "Delete" %}</a>
{% endif %}
<a href="{% url 'lingo-manager-invoicing-regie-edit' pk=regie.pk %}" rel="popup">{% trans "Edit" %}</a>
<a href="{% url 'lingo-manager-invoicing-non-invoiced-line-list' regie_pk=regie.pk %}">{% trans 'Non invoiced lines' %}</a>
</span>
{% endblock %}
@ -20,6 +23,7 @@
<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-usage" aria-selected="false" id="tab-usage" role="tab" tabindex="-1">{% trans "Used in agendas" %}</button>
<button aria-controls="panel-campaigns" aria-selected="false" id="tab-campaigns" role="tab" tabindex="-1">{% trans "Campaigns" %}</button>
</div>
<div class="pk-tabs--container">
@ -57,6 +61,22 @@
{% endif %}
</div>
<div aria-labelledby="tab-campaigns" hidden="" id="panel-campaigns" role="tabpanel" tabindex="0">
lguerin marked this conversation as resolved
Review

Dans le gabarit initial que ce bout de gabarit remplace, il y avait un message lorsque cette liste d’objets était vide (“This site doesn't have any campaign yet. […]“). Est-ce volontaire de l’avoir laissé de coté dans cette nouvelle version ?

Dans le gabarit initial que ce bout de gabarit remplace, il y avait un message lorsque cette liste d’objets était vide (“This site doesn't have any campaign yet. […]“). Est-ce volontaire de l’avoir laissé de coté dans cette nouvelle version ?
Review

Dans le template précédent, on avait une liste de campagnes et c'est tout.
Là on est dans un onglet sur la page de détail d'une régie.

Sur la page de détail d'une campagne, dans l'onglet pool il n'y a pas on plus de message lorsque par de pools.

Si tu insistes je peux ajouter ça pour les campagnes et pour les pools (pour que ce soit homogène)

Dans le template précédent, on avait une liste de campagnes et c'est tout. Là on est dans un onglet sur la page de détail d'une régie. Sur la page de détail d'une campagne, dans l'onglet pool il n'y a pas on plus de message lorsque par de pools. Si tu insistes je peux ajouter ça pour les campagnes et pour les pools (pour que ce soit homogène)
Review

Non, je ne connais pas assez le métier pour insister, je signalais juste ce qui me semblait être une incohérence de forme, mais il s’avère que c’est délibéré. Ok pour moi.

Non, je ne connais pas assez le métier pour insister, je signalais juste ce qui me semblait être une incohérence de forme, mais il s’avère que c’est délibéré. Ok pour moi.
<ul class="objects-list single-links">
{% for campaign in campaigns %}
<li>
<a href="{% url 'lingo-manager-invoicing-campaign-detail' regie_pk=regie.pk pk=campaign.pk %}">
{{ campaign }}
<span class="extra-info"> [{% trans "issue date:" %} {{ campaign.date_issue|date:'d/m/Y' }}]</span>
</a>
</li>
{% endfor %}
</ul>
<div class="panel--buttons">
<a rel="popup" class="pk-button" href="{% url 'lingo-manager-invoicing-campaign-add' regie_pk=regie.pk %}">{% trans "New campaign" %}</a>
</div>
</div>
</div>
</div>
</div>

View File

@ -43,64 +43,63 @@ 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/',
'regie/<int:regie_pk>/campaign/add/',
views.campaign_add,
name='lingo-manager-invoicing-campaign-add',
),
path(
'campaign/<int:pk>/',
'regie/<int:regie_pk>/campaign/<int:pk>/',
views.campaign_detail,
name='lingo-manager-invoicing-campaign-detail',
),
path(
'campaign/<int:pk>/edit/',
'regie/<int:regie_pk>/campaign/<int:pk>/edit/',
views.campaign_edit,
name='lingo-manager-invoicing-campaign-edit',
),
path(
'campaign/<int:pk>/delete/',
'regie/<int:regie_pk>/campaign/<int:pk>/delete/',
views.campaign_delete,
name='lingo-manager-invoicing-campaign-delete',
),
path(
'campaign/<int:pk>/pool/add/',
'regie/<int:regie_pk>/campaign/<int:pk>/pool/add/',
views.pool_add,
name='lingo-manager-invoicing-pool-add',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/',
'regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/',
views.pool_detail,
name='lingo-manager-invoicing-pool-detail',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/journal/',
'regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/journal/',
views.pool_journal,
name='lingo-manager-invoicing-pool-journal',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/promote/',
'regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/promote/',
views.pool_promote,
name='lingo-manager-invoicing-pool-promote',
),
path(
'campaign/<int:pk>/pool/<int:pool_pk>/delete/',
'regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/delete/',
views.pool_delete,
name='lingo-manager-invoicing-pool-delete',
),
path(
'ajax/campaign/<int:pk>/pool/<int:pool_pk>/invoice/<int:invoice_pk>/lines/',
'ajax/regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/invoice/<int:invoice_pk>/lines/',
views.invoice_line_list,
name='lingo-manager-invoicing-invoice-line-list',
),
path(
'ajax/campaign/<int:pk>/pool/<int:pool_pk>/line/<int:line_pk>/<slug:status>/',
'ajax/regie/<int:regie_pk>/campaign/<int:pk>/pool/<int:pool_pk>/line/<int:line_pk>/<slug:status>/',
views.line_set_error_status,
name='lingo-manager-invoicing-line-set-error-status',
),
path(
'campaigns/non-invoiced-lines/',
'regie/<int:regie_pk>/non-invoiced-lines/',
views.non_invoiced_line_list,
name='lingo-manager-invoicing-non-invoiced-line-list',
),

View File

@ -22,12 +22,14 @@ from django.utils.translation import ugettext_lazy as _
from lingo.agendas.chrono import get_check_status, get_subscriptions
from lingo.agendas.models import Agenda
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, InjectedLine, RegieNotConfigured
from lingo.invoicing.models import DraftInvoice, DraftInvoiceLine, InjectedLine
from lingo.pricing.models import AgendaPricing, AgendaPricingNotFound, PricingError
def get_agendas(pool):
agendas_pricings = AgendaPricing.objects.filter(flat_fee_schedule=False).extra(
agendas_pricings = AgendaPricing.objects.filter(
flat_fee_schedule=False, agendas__regie=pool.campaign.regie
).extra(
where=["(date_start, date_end) OVERLAPS (%s, %s)"],
params=[pool.campaign.date_start, pool.campaign.date_end],
)
@ -147,6 +149,7 @@ def get_invoice_lines_for_user(agendas, agendas_pricings, user_external_id, user
# fetch injected lines
injected_lines = (
InjectedLine.objects.filter(
regie=pool.campaign.regie,
event_date__lt=pool.campaign.date_end,
user_external_id=user_external_id,
)
@ -213,48 +216,34 @@ def get_all_invoice_lines(agendas, users, pool):
return lines
def generate_invoices_from_lines(agendas, all_lines, pool):
agendas_by_slug = {a.slug: a for a in agendas}
def generate_invoices_from_lines(all_lines, pool):
regie = pool.campaign.regie
# regroup lines by regie, and by payer_external_id (payer)
lines_by_regie = {}
# regroup lines by payer_external_id (payer)
lines = {}
for line in all_lines:
if line.status != 'success':
# ignore lines in error
continue
if line.from_injected_line:
regie = line.from_injected_line.regie
else:
agenda_slug = line.event['agenda']
if agenda_slug not in agendas_by_slug:
# should not happen
continue
regie = agendas_by_slug[agenda_slug].regie
if not regie:
raise RegieNotConfigured(_('Regie not configured on %s') % agenda_slug)
if regie.pk not in lines_by_regie:
lines_by_regie[regie.pk] = {}
regie_subs = lines_by_regie[regie.pk]
if line.payer_external_id not in regie_subs:
regie_subs[line.payer_external_id] = []
regie_subs[line.payer_external_id].append(line)
if line.payer_external_id not in lines:
lines[line.payer_external_id] = []
lines[line.payer_external_id].append(line)
# generate invoices by regie and by payer_external_id (payer)
invoices = []
for regie_id, regie_subs in lines_by_regie.items():
for payer_external_id, adult_lines in regie_subs.items():
invoice = DraftInvoice.objects.create(
label=_('Invoice from %(start)s to %(end)s')
% {
'start': pool.campaign.date_start,
'end': pool.campaign.date_end - datetime.timedelta(days=1),
},
date_issue=pool.campaign.date_issue,
regie_id=regie_id,
payer=payer_external_id,
pool=pool,
)
DraftInvoiceLine.objects.filter(pk__in=[line.pk for line in adult_lines]).update(invoice=invoice)
invoices.append(invoice)
for payer_external_id, adult_lines in lines.items():
invoice = DraftInvoice.objects.create(
label=_('Invoice from %(start)s to %(end)s')
% {
'start': pool.campaign.date_start,
'end': pool.campaign.date_end - datetime.timedelta(days=1),
},
date_issue=pool.campaign.date_issue,
regie=regie,
payer=payer_external_id,
pool=pool,
)
DraftInvoiceLine.objects.filter(pk__in=[line.pk for line in adult_lines]).update(invoice=invoice)
invoices.append(invoice)
return invoices

View File

@ -48,6 +48,7 @@ from lingo.invoicing.forms import (
)
from lingo.invoicing.models import (
Campaign,
Counter,
DraftInvoice,
DraftInvoiceLine,
InjectedLine,
@ -111,6 +112,13 @@ class RegieDetailView(DetailView):
def get_context_data(self, **kwargs):
kwargs['regie'] = self.object
kwargs['agendas'] = Agenda.objects.filter(regie=self.object)
kwargs['campaigns'] = self.object.campaign_set.all().order_by('-date_start')
has_related_objects = False
if kwargs['campaigns']:
has_related_objects = True
elif self.object.injectedline_set.exists():
has_related_objects = True
kwargs['has_related_objects'] = has_related_objects
return super().get_context_data(**kwargs)
@ -133,6 +141,14 @@ class RegieDeleteView(DeleteView):
template_name = 'lingo/manager_confirm_delete.html'
model = Regie
def get_queryset(self):
return super().get_queryset().filter(campaign__isnull=True, injectedline__isnull=True)
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
Counter.objects.filter(regie=self.object).delete()
return super().delete(request, *args, **kwargs)
def get_success_url(self):
return reverse('lingo-manager-invoicing-regie-list')
@ -202,22 +218,26 @@ class RegiesImportView(FormView):
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()
class CampaignAddView(CreateView):
template_name = 'lingo/invoicing/manager_campaign_form.html'
model = Campaign
form_class = CampaignForm
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
return super().get_context_data(**kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = Campaign(regie=self.regie)
return kwargs
def get_success_url(self):
return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk])
return reverse('lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.object.pk])
campaign_add = CampaignAddView.as_view()
@ -227,6 +247,13 @@ class CampaignDetailView(DetailView):
template_name = 'lingo/invoicing/manager_campaign_detail.html'
model = Campaign
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return super().get_queryset().filter(regie=self.regie)
def get_context_data(self, **kwargs):
draft_lines = DraftInvoiceLine.objects.filter(pool=OuterRef('pk')).order_by().values('pool')
count_draft_error = draft_lines.filter(status='error').annotate(count=Count('pool')).values('count')
@ -242,6 +269,7 @@ class CampaignDetailView(DetailView):
)
count_warning = lines.filter(status='warning').annotate(count=Count('pool')).values('count')
count_success = lines.filter(status='success').annotate(count=Count('pool')).values('count')
kwargs['regie'] = self.regie
kwargs['pools'] = self.object.pool_set.annotate(
draft_error_count=Coalesce(Subquery(count_draft_error, output_field=IntegerField()), Value(0)),
draft_warning_count=Coalesce(
@ -267,16 +295,25 @@ class CampaignEditView(UpdateView):
model = Campaign
form_class = CampaignForm
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(regie=self.regie)
.exclude(pool__draft=False)
.exclude(pool__status__in=['registered', 'running'])
)
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
return super().get_context_data(**kwargs)
def get_success_url(self):
return reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk])
return reverse('lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.object.pk])
campaign_edit = CampaignEditView.as_view()
@ -286,10 +323,15 @@ class CampaignDeleteView(DeleteView):
template_name = 'lingo/manager_confirm_delete.html'
model = Campaign
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(regie=self.regie)
.exclude(pool__draft=False)
.exclude(pool__status__in=['registered', 'running'])
)
@ -302,7 +344,7 @@ class CampaignDeleteView(DeleteView):
return super().delete(request, *args, **kwargs)
def get_success_url(self):
return reverse('lingo-manager-invoicing-campaign-list')
return '%s#open:campaigns' % reverse('lingo-manager-invoicing-regie-detail', args=[self.regie.pk])
campaign_delete = CampaignDeleteView.as_view()
@ -313,7 +355,8 @@ class PoolDetailView(ListView):
paginate_by = 100
def dispatch(self, request, *args, **kwargs):
self.campaign = get_object_or_404(Campaign, pk=kwargs['pk'])
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.campaign = get_object_or_404(Campaign, pk=kwargs['pk'], regie=self.regie)
self.object = get_object_or_404(Pool, pk=kwargs['pool_pk'], campaign=self.campaign)
return super().dispatch(request, *args, **kwargs)
@ -331,6 +374,7 @@ class PoolDetailView(ListView):
return self.filterset.qs
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
kwargs['object'] = self.campaign
kwargs['pool'] = self.object
kwargs['filterset'] = self.filterset
@ -356,7 +400,8 @@ class PoolJournalView(ListView):
paginate_by = 100
def dispatch(self, request, *args, **kwargs):
self.campaign = get_object_or_404(Campaign, pk=kwargs['pk'])
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.campaign = get_object_or_404(Campaign, pk=kwargs['pk'], regie=self.regie)
self.object = get_object_or_404(Pool, pk=kwargs['pool_pk'], campaign=self.campaign)
return super().dispatch(request, *args, **kwargs)
@ -377,6 +422,7 @@ class PoolJournalView(ListView):
return self.filterset.qs if data and [v for v in data.values() if v] else all_lines
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
kwargs['object'] = self.campaign
kwargs['pool'] = self.object
kwargs['filterset'] = self.filterset
@ -390,21 +436,26 @@ class PoolAddView(FormView):
template_name = 'lingo/invoicing/manager_pool_add.html'
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.object = get_object_or_404(
Campaign.objects.exclude(pool__draft=False).exclude(pool__status__in=['registered', 'running']),
Campaign.objects.filter(regie=self.regie)
.exclude(pool__draft=False)
.exclude(pool__status__in=['registered', 'running']),
pk=kwargs['pk'],
)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs['form'] = None
kwargs['regie'] = self.regie
kwargs['object'] = self.object
return super().get_context_data(**kwargs)
def post(self, request, *args, **kwargs):
self.object.generate()
return redirect(
'%s#open:pools' % reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.pk])
'%s#open:pools'
% reverse('lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.object.pk])
)
@ -415,9 +466,11 @@ class PoolPromoteView(FormView):
template_name = 'lingo/invoicing/manager_pool_promote.html'
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.object = get_object_or_404(
Pool,
campaign__id=kwargs['pk'],
campaign__regie=self.regie,
pk=kwargs['pool_pk'],
draft=True,
status='completed',
@ -428,6 +481,7 @@ class PoolPromoteView(FormView):
def get_context_data(self, **kwargs):
kwargs['form'] = None
kwargs['regie'] = self.regie
kwargs['object'] = self.object.campaign
kwargs['pool'] = self.object
return super().get_context_data(**kwargs)
@ -436,7 +490,9 @@ class PoolPromoteView(FormView):
self.object.promote()
return redirect(
'%s#open:pools'
% reverse('lingo-manager-invoicing-campaign-detail', args=[self.object.campaign.pk])
% reverse(
'lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.object.campaign.pk]
)
)
@ -449,8 +505,11 @@ class PoolDeleteView(DeleteView):
pk_url_kwarg = 'pool_pk'
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.campaign = get_object_or_404(
Campaign.objects.exclude(pool__draft=False).exclude(pool__status__in=['registered', 'running']),
Campaign.objects.filter(regie=self.regie)
.exclude(pool__draft=False)
.exclude(pool__status__in=['registered', 'running']),
pk=kwargs['pk'],
)
return super().dispatch(request, *args, **kwargs)
@ -465,7 +524,9 @@ class PoolDeleteView(DeleteView):
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])
return '%s#open:pools' % reverse(
'lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.campaign.pk]
)
pool_delete = PoolDeleteView.as_view()
@ -475,7 +536,10 @@ class InvoiceLineListView(ListView):
template_name = 'lingo/invoicing/manager_invoice_lines.html'
def dispatch(self, request, *args, **kwargs):
self.pool = get_object_or_404(Pool, pk=kwargs['pool_pk'], campaign_id=kwargs['pk'])
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
self.pool = get_object_or_404(
Pool, pk=kwargs['pool_pk'], campaign_id=kwargs['pk'], campaign__regie=self.regie
)
invoice_model = Invoice
if self.pool.draft:
invoice_model = DraftInvoice
@ -486,6 +550,7 @@ class InvoiceLineListView(ListView):
return self.invoice.lines.all().order_by('user_external_id', 'event_date', 'pk')
def get_context_data(self, **kwargs):
kwargs['regie'] = self.pool.campaign.regie
kwargs['object'] = self.pool.campaign
kwargs['pool'] = self.pool
return super().get_context_data(**kwargs)
@ -499,14 +564,24 @@ class LineSetErrorStatusView(DetailView):
pk_url_kwarg = 'line_pk'
template_name = 'lingo/invoicing/manager_line_detail_fragment.html'
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(status='error', pool=self.kwargs['pool_pk'], pool__campaign=self.kwargs['pk'])
.filter(
status='error',
pool=self.kwargs['pool_pk'],
pool__campaign=self.kwargs['pk'],
pool__campaign__regie=self.regie,
)
)
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
kwargs['object'] = self.object.pool.campaign
kwargs['pool'] = self.object.pool
kwargs['line'] = self.object
@ -530,7 +605,9 @@ class LineSetErrorStatusView(DetailView):
return self.render_to_response(context)
return redirect(
reverse('lingo-manager-invoicing-pool-journal', args=[kwargs['pk'], kwargs['pool_pk']])
reverse(
'lingo-manager-invoicing-pool-journal', args=[self.regie.pk, kwargs['pk'], kwargs['pool_pk']]
)
)
@ -541,6 +618,10 @@ class NonInvoicedLineListView(ListView):
template_name = 'lingo/invoicing/manager_non_invoiced_line_list.html'
paginate_by = 100
def dispatch(self, request, *args, **kwargs):
self.regie = get_object_or_404(Regie, pk=kwargs['regie_pk'])
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
fields = [
'pk',
@ -558,9 +639,11 @@ class NonInvoicedLineListView(ListView):
'status',
'pool_id',
]
qs1 = InvoiceLine.objects.filter(status='error', error_status='').values(*fields)
qs1 = InvoiceLine.objects.filter(
status='error', error_status='', pool__campaign__regie=self.regie
).values(*fields)
qs2 = (
InjectedLine.objects.filter(invoiceline__isnull=True)
InjectedLine.objects.filter(invoiceline__isnull=True, regie=self.regie)
.annotate(
user_name=Value('', output_field=CharField()),
event=Value({}, output_field=JSONField()),
@ -574,6 +657,7 @@ class NonInvoicedLineListView(ListView):
return qs
def get_context_data(self, **kwargs):
kwargs['regie'] = self.regie
context = super().get_context_data(**kwargs)
pools = Pool.objects.filter(draft=False).in_bulk()
for line in context['object_list']:

View File

@ -22,7 +22,3 @@ def test_manager_invoicing_home(app, admin_user):
'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')

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
import datetime
import json
from urllib.parse import urlparse
@ -7,7 +8,7 @@ from django.urls import reverse
from webtest import Upload
from lingo.agendas.models import Agenda
from lingo.invoicing.models import Regie
from lingo.invoicing.models import Campaign, Counter, InjectedLine, Regie
from tests.utils import login
pytestmark = pytest.mark.django_db
@ -138,6 +139,42 @@ def test_manager_invoicing_regie_delete(app, admin_user):
assert Regie.objects.count() == 0
assert urlparse(response.request.url).path == reverse('lingo-manager-invoicing-regie-list')
# can not delete regie containing campaign
regie.save()
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
)
resp = app.get(reverse('lingo-manager-invoicing-regie-detail', kwargs={'pk': regie.pk}))
assert reverse('lingo-manager-invoicing-regie-delete', kwargs={'pk': regie.pk}) not in resp
resp = app.get(reverse('lingo-manager-invoicing-regie-delete', kwargs={'pk': regie.pk}), status=404)
campaign.delete()
# can not delete regie containing injected line
injected_line = InjectedLine.objects.create(
event_date=datetime.date(2022, 9, 1),
quantity=1,
unit_amount=1,
total_amount=1,
regie=regie,
)
resp = app.get(reverse('lingo-manager-invoicing-regie-detail', kwargs={'pk': regie.pk}))
assert reverse('lingo-manager-invoicing-regie-delete', kwargs={'pk': regie.pk}) not in resp
resp = app.get(reverse('lingo-manager-invoicing-regie-delete', kwargs={'pk': regie.pk}), status=404)
injected_line.delete()
# check counters are deleted
Counter.get_count(regie=regie, name='bar')
assert Counter.objects.count() == 1
resp = app.get(reverse('lingo-manager-invoicing-regie-delete', kwargs={'pk': regie.pk}))
response = resp.form.submit().follow()
assert Regie.objects.count() == 0
assert Counter.objects.count() == 0
def test_manager_invoicing_regie_import_export(app, admin_user, freezer):
freezer.move_to('2020-06-15')

View File

@ -22,7 +22,6 @@ from lingo.invoicing.models import (
Pool,
PoolPromotionError,
Regie,
RegieNotConfigured,
)
from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingError
@ -30,12 +29,16 @@ pytestmark = pytest.mark.django_db
def test_get_agendas():
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
agenda3 = Agenda.objects.create(label='Agenda 3')
Agenda.objects.create(label='Agenda 4')
regie1 = Regie.objects.create(label='Regie1')
regie2 = Regie.objects.create(label='Regie2')
agenda1 = Agenda.objects.create(label='Agenda 1', regie=regie1)
agenda2 = Agenda.objects.create(label='Agenda 2', regie=regie1)
agenda3 = Agenda.objects.create(label='Agenda 3', regie=regie1)
Agenda.objects.create(label='Agenda 4', regie=regie1)
agenda5 = Agenda.objects.create(label='Agenda 5', regie=regie2) # other regie
pricing = Pricing.objects.create(label='Foo bar 1')
campaign = Campaign.objects.create(
regie=regie1,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -72,7 +75,7 @@ def test_get_agendas():
assert list(utils.get_agendas(pool=pool)) == []
# link agendas to agenda pricing
agenda_pricing1.agendas.add(agenda1, agenda2)
agenda_pricing1.agendas.add(agenda1, agenda2, agenda5)
agenda_pricing2.agendas.add(agenda3)
assert list(utils.get_agendas(pool=pool)) == [agenda3]
@ -104,8 +107,10 @@ def test_get_agendas():
@mock.patch('lingo.invoicing.utils.get_subscriptions')
def test_get_users_from_subscriptions_error(mock_subscriptions):
regie = Regie.objects.create(label='Regie')
agenda = Agenda.objects.create(label='Agenda')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -121,9 +126,11 @@ def test_get_users_from_subscriptions_error(mock_subscriptions):
@mock.patch('lingo.invoicing.utils.get_subscriptions')
def test_get_users_from_subscriptions(mock_subscriptions):
regie = Regie.objects.create(label='Regie')
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -201,8 +208,10 @@ def test_get_users_from_subscriptions(mock_subscriptions):
@mock.patch('lingo.invoicing.utils.get_check_status')
def test_get_invoice_lines_for_user_check_status_error(mock_status):
regie = Regie.objects.create(label='Regie')
agenda = Agenda.objects.create(label='Agenda')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -227,6 +236,7 @@ def test_get_invoice_lines_for_user_check_status_error(mock_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, injected_lines):
regie = Regie.objects.create(label='Regie')
other_regie = Regie.objects.create(label='Other Regie')
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
pricing = Pricing.objects.create(label='Foo bar 1')
@ -237,6 +247,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
)
agenda_pricing.agendas.add(agenda1, agenda2)
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -251,6 +262,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
draft=True,
)
other_campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -365,6 +377,18 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
pool=other_pool,
from_injected_line=injected_line7,
)
# nok, other regie
InjectedLine.objects.create(
event_date=datetime.date(2022, 9, 15),
slug='event-2022-09-15',
label='Event 2022-09-15',
quantity=3,
unit_amount=1.5,
total_amount=4.5,
user_external_id='user:1',
payer_external_id='payer:1',
regie=other_regie,
)
# no agendas
assert (
@ -665,6 +689,7 @@ def test_get_invoice_lines_for_user_check_status(mock_pricing_data_event, mock_s
@mock.patch('lingo.invoicing.utils.get_check_status')
def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_status):
regie = Regie.objects.create(label='Regie')
agenda = Agenda.objects.create(label='Agenda')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing1 = AgendaPricing.objects.create(
@ -687,6 +712,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
)
agenda_pricing3.agendas.add(agenda)
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 1, 1),
date_end=datetime.date(2023, 1, 1),
date_issue=datetime.date(2023, 1, 31),
@ -799,6 +825,7 @@ def test_get_invoice_lines_for_user_check_status_agenda_pricing_dates(mock_statu
@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_pricing_error(mock_pricing_data_event, mock_status):
regie = Regie.objects.create(label='Regie')
agenda = Agenda.objects.create(label='Agenda')
pricing = Pricing.objects.create(label='Foo bar')
agenda_pricing = AgendaPricing.objects.create(
@ -808,6 +835,7 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
)
agenda_pricing.agendas.add(agenda)
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -927,10 +955,12 @@ def test_get_invoice_lines_for_user_check_status_pricing_error(mock_pricing_data
@mock.patch('lingo.invoicing.utils.get_invoice_lines_for_user')
def test_get_all_invoice_lines(mock_user_lines):
regie = Regie.objects.create(label='Regie')
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
Agenda.objects.create(label='Agenda 3')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1044,7 +1074,9 @@ def test_get_all_invoice_lines_queryset(mock_status):
)
agenda_pricing22.agendas.add(agenda2)
regie = Regie.objects.create(label='Regie')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 11, 1),
date_issue=datetime.date(2022, 11, 30),
@ -1109,14 +1141,10 @@ def test_get_all_invoice_lines_queryset(mock_status):
def test_generate_invoices_from_lines():
regie1 = Regie.objects.create(label='Regie 1')
regie2 = Regie.objects.create(label='Regie 2')
agenda1 = Agenda.objects.create(label='Agenda 1', regie=regie1)
agenda2 = Agenda.objects.create(label='Agenda 2', regie=regie2)
agenda3 = Agenda.objects.create(label='Agenda 3', regie=regie1)
agenda4 = Agenda.objects.create(label='Agenda 4') # regie not configured
regie = Regie.objects.create(label='Regie')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1203,17 +1231,6 @@ def test_generate_invoices_from_lines():
status='success',
pool=pool,
)
line6 = DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
event={'agenda': 'agenda-4'}, # regie not configured
quantity=1,
unit_amount=6,
total_amount=6,
user_external_id='user:1',
payer_external_id='user:1',
status='success',
pool=pool,
)
injected_line = InjectedLine.objects.create(
event_date=datetime.date(2022, 9, 1),
slug='event-2022-09-01',
@ -1223,9 +1240,9 @@ def test_generate_invoices_from_lines():
total_amount=7,
user_external_id='user:1',
payer_external_id='user:1',
regie=regie1,
regie=regie,
)
line7 = DraftInvoiceLine.objects.create(
line6 = DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
quantity=1,
unit_amount=7,
@ -1238,57 +1255,30 @@ def test_generate_invoices_from_lines():
)
invoices = utils.generate_invoices_from_lines(
agendas=[],
all_lines=[line_error, line1, line2, line3, line4, line5, line6],
pool=pool,
)
assert len(invoices) == 0
with pytest.raises(RegieNotConfigured) as excinfo:
invoices = utils.generate_invoices_from_lines(
agendas=[agenda1, agenda2, agenda3, agenda4],
all_lines=[line_error, line1, line2, line3, line4, line5, line6],
pool=pool,
)
assert '%s' % excinfo.value == 'Regie not configured on agenda-4'
assert len(invoices) == 0
line6.delete()
invoices = utils.generate_invoices_from_lines(
agendas=[agenda1, agenda2, agenda3, agenda4],
all_lines=[line_error, line1, line2, line3, line4, line5, line7],
pool=pool,
)
assert len(invoices) == 3
invoice1, invoice2, invoice3 = invoices
assert len(invoices) == 2
invoice1, invoice2 = invoices
# refresh total_amount field (triggered)
invoice1.refresh_from_db()
invoice2.refresh_from_db()
invoice3.refresh_from_db()
assert isinstance(invoice1, DraftInvoice)
assert invoice1.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice1.total_amount == 15
assert invoice1.total_amount == 18
assert invoice1.date_issue == datetime.date(2022, 10, 31)
assert invoice1.regie == regie1
assert invoice1.regie == regie
assert invoice1.payer == 'user:1'
assert invoice1.pool == pool
assert list(invoice1.lines.order_by('pk')) == [line1, line2, line5, line7]
assert list(invoice1.lines.order_by('pk')) == [line1, line2, line3, line5, line6]
assert isinstance(invoice2, DraftInvoice)
assert invoice2.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice2.total_amount == 3
assert invoice2.total_amount == 4
assert invoice2.date_issue == datetime.date(2022, 10, 31)
assert invoice2.regie == regie2
assert invoice2.payer == 'user:1'
assert invoice2.regie == regie
assert invoice2.payer == 'user:2'
assert invoice2.pool == pool
assert list(invoice2.lines.order_by('pk')) == [line3]
assert isinstance(invoice3, DraftInvoice)
assert invoice3.label == 'Invoice from 2022-09-01 to 2022-09-30'
assert invoice3.total_amount == 4
assert invoice3.date_issue == datetime.date(2022, 10, 31)
assert invoice3.regie == regie2
assert invoice3.payer == 'user:2'
assert invoice3.pool == pool
assert list(invoice3.lines.order_by('pk')) == [line4]
assert list(invoice2.lines.order_by('pk')) == [line4]
@mock.patch('lingo.invoicing.utils.get_agendas')
@ -1296,10 +1286,12 @@ def test_generate_invoices_from_lines():
@mock.patch('lingo.invoicing.utils.get_all_invoice_lines')
@mock.patch('lingo.invoicing.utils.generate_invoices_from_lines')
def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas):
regie = Regie.objects.create(label='Regie')
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
Agenda.objects.create(label='Agenda 3')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1330,7 +1322,6 @@ def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas):
]
assert mock_generate.call_args_list == [
mock.call(
agendas=[agenda1, agenda2],
all_lines=['foo', 'baz'],
pool=pool,
)
@ -1342,10 +1333,12 @@ def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas):
@mock.patch('lingo.invoicing.utils.get_all_invoice_lines')
@mock.patch('lingo.invoicing.utils.generate_invoices_from_lines')
def test_generate_invoices_errors(mock_generate, mock_lines, mock_users, mock_agendas):
regie = Regie.objects.create(label='Regie')
agenda1 = Agenda.objects.create(label='Agenda 1')
agenda2 = Agenda.objects.create(label='Agenda 2')
Agenda.objects.create(label='Agenda 3')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1358,41 +1351,43 @@ def test_generate_invoices_errors(mock_generate, mock_lines, mock_users, mock_ag
assert pool.status == 'failed'
assert pool.exception == 'foo bar'
mock_users.side_effect = None
mock_users.return_value = ['foo', 'bar']
mock_lines.side_effect = RegieNotConfigured('foo baz')
campaign.generate()
pool = Pool.objects.latest('pk')
assert pool.status == 'failed'
assert pool.exception == 'foo baz'
def test_generate_invoices_cmd():
regie = Regie.objects.create(label='Regie')
with mock.patch.object(Campaign, 'generate', autospec=True) as mock_generate:
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices')
assert '%s' % excinfo.value == 'Error: the following arguments are required: date_start, date_end'
assert (
'%s' % excinfo.value
== 'Error: the following arguments are required: date_start, date_end, regie_id'
)
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', 'bad-value', '2022-10-01')
call_command('generate_invoices', 'bad-value', '2022-10-01', regie.pk)
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_start'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-01', 'bad-value')
call_command('generate_invoices', '2022-09-01', 'bad-value', regie.pk)
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_end'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-01', '2022-10-01', '--date-issue=bad-value')
call_command('generate_invoices', '2022-09-01', '2022-10-01', regie.pk, '--date-issue=bad-value')
assert '%s' % excinfo.value == 'Bad value "bad-value" for date_issue'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-01', '2022-10-01', 0)
assert '%s' % excinfo.value == 'Bad value "0" for regie_id'
assert mock_generate.call_args_list == []
assert Campaign.objects.count() == 0
call_command('generate_invoices', '2022-09-01', '2022-10-01')
call_command('generate_invoices', '2022-09-01', '2022-10-01', regie.pk)
assert Campaign.objects.count() == 1
campaign = Campaign.objects.latest('pk')
assert campaign.regie == regie
assert campaign.date_start == datetime.date(2022, 9, 1)
assert campaign.date_end == datetime.date(2022, 10, 1)
assert campaign.date_issue == campaign.date_end
@ -1400,44 +1395,56 @@ def test_generate_invoices_cmd():
mock_generate.reset_mock()
# again
call_command('generate_invoices', '2022-09-01', '2022-10-01')
call_command('generate_invoices', '2022-09-01', '2022-10-01', regie.pk)
assert Campaign.objects.count() == 1
assert mock_generate.call_args_list == [mock.call(campaign, spool=False)]
mock_generate.reset_mock()
# with overlapping
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-08-31', '2022-09-02')
call_command('generate_invoices', '2022-08-31', '2022-09-02', regie.pk)
assert '%s' % excinfo.value == 'Overlapping campaigns already exist'
assert mock_generate.call_args_list == []
with pytest.raises(CommandError) as excinfo:
call_command('generate_invoices', '2022-09-30', '2022-10-02')
call_command('generate_invoices', '2022-09-30', '2022-10-02', regie.pk)
assert '%s' % excinfo.value == 'Overlapping campaigns already exist'
assert mock_generate.call_args_list == []
# no overlapping
call_command('generate_invoices', '2022-08-01', '2022-09-01', '--date-issue=2022-10-31')
call_command('generate_invoices', '2022-08-01', '2022-09-01', regie.pk, '--date-issue=2022-10-31')
assert Campaign.objects.count() == 2
campaign = Campaign.objects.latest('pk')
assert mock_generate.call_args_list == [mock.call(campaign, spool=False)]
assert campaign.regie == regie
assert campaign.date_start == datetime.date(2022, 8, 1)
assert campaign.date_end == datetime.date(2022, 9, 1)
assert campaign.date_issue == datetime.date(2022, 10, 31)
mock_generate.reset_mock()
call_command('generate_invoices', '2022-10-01', '2022-11-01')
call_command('generate_invoices', '2022-10-01', '2022-11-01', regie.pk)
assert Campaign.objects.count() == 3
campaign = Campaign.objects.latest('pk')
assert mock_generate.call_args_list == [mock.call(campaign, spool=False)]
assert campaign.regie == regie
assert campaign.date_start == datetime.date(2022, 10, 1)
assert campaign.date_end == datetime.date(2022, 11, 1)
assert campaign.date_issue == campaign.date_end
regie2 = Regie.objects.create(label='Regie2')
mock_generate.reset_mock()
call_command('generate_invoices', '2022-08-31', '2022-09-02', regie2.pk)
assert Campaign.objects.count() == 4
campaign = Campaign.objects.latest('pk')
assert mock_generate.call_args_list == [mock.call(campaign, spool=False)]
assert campaign.regie == regie2
assert campaign.date_start == datetime.date(2022, 8, 31)
assert campaign.date_end == datetime.date(2022, 9, 2)
assert campaign.date_issue == campaign.date_end
def test_promote_pool():
today = datetime.date.today()
regie1 = Regie.objects.create(label='Regie1')
regie2 = Regie.objects.create(label='Regie2')
regie = Regie.objects.create(label='Regie')
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1453,6 +1460,7 @@ def test_promote_pool():
status='completed',
)
other_campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),
@ -1463,7 +1471,7 @@ def test_promote_pool():
)
invoice1 = DraftInvoice.objects.create(
date_issue=datetime.date.today(), regie=regie1, pool=pool, payer='payer:1'
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:1'
)
line11 = DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1490,7 +1498,7 @@ def test_promote_pool():
total_amount=2,
user_external_id='user:2',
payer_external_id='payer:1',
regie=regie1,
regie=regie,
)
line12 = DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1508,7 +1516,7 @@ def test_promote_pool():
pool=pool,
)
invoice2 = DraftInvoice.objects.create(
date_issue=datetime.date.today(), regie=regie1, pool=pool, payer='payer:2'
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:2'
)
injected_line21 = InjectedLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1519,7 +1527,7 @@ def test_promote_pool():
total_amount=1,
user_external_id='user:2',
payer_external_id='payer:2',
regie=regie1,
regie=regie,
)
line21 = DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1578,11 +1586,11 @@ def test_promote_pool():
)
invoice3 = DraftInvoice.objects.create(
date_issue=datetime.date.today(), regie=regie2, pool=pool, payer='payer:1'
date_issue=datetime.date.today(), regie=regie, pool=pool, payer='payer:1'
)
old_invoice = DraftInvoice.objects.create(
date_issue=datetime.date.today(), regie=regie1, pool=old_pool, payer='payer:1'
date_issue=datetime.date.today(), regie=regie, pool=old_pool, payer='payer:1'
)
DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1607,7 +1615,7 @@ def test_promote_pool():
total_amount=2,
user_external_id='user:2',
payer_external_id='payer:1',
regie=regie1,
regie=regie,
)
DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1626,7 +1634,7 @@ def test_promote_pool():
)
other_invoice = DraftInvoice.objects.create(
date_issue=datetime.date.today(), regie=regie1, pool=other_pool, payer='payer:1'
date_issue=datetime.date.today(), regie=regie, pool=other_pool, payer='payer:1'
)
DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1651,7 +1659,7 @@ def test_promote_pool():
total_amount=2,
user_external_id='user:2',
payer_external_id='payer:1',
regie=regie1,
regie=regie,
)
DraftInvoiceLine.objects.create(
event_date=datetime.date(2022, 9, 1),
@ -1692,8 +1700,7 @@ def test_promote_pool():
assert Invoice.objects.count() == 3
assert InvoiceLine.objects.count() == 6
assert InjectedLine.objects.count() == 4
assert Counter.objects.get(regie=regie1, name=today.strftime('%y')).value == 2
assert Counter.objects.get(regie=regie2, name=today.strftime('%y')).value == 1
assert Counter.objects.get(regie=regie, name=today.strftime('%y')).value == 3
test_counts()
@ -1706,12 +1713,12 @@ def test_promote_pool():
final_invoice1 = Invoice.objects.order_by('pk')[0]
assert final_invoice1.date_issue == invoice1.date_issue
assert final_invoice1.regie == regie1
assert final_invoice1.regie == regie
assert final_invoice1.pool == final_pool
assert final_invoice1.payer == invoice1.payer
assert final_invoice1.total_amount == invoice1.total_amount == 3
assert final_invoice1.number == 1
assert final_invoice1.formatted_number == 'F%02d-%s-0000001' % (regie1.pk, today.strftime('%y-%m'))
assert final_invoice1.formatted_number == 'F%02d-%s-0000001' % (regie.pk, today.strftime('%y-%m'))
final_line11 = InvoiceLine.objects.order_by('pk')[0]
assert final_line11.event_date == line11.event_date
@ -1749,12 +1756,12 @@ def test_promote_pool():
final_invoice2 = Invoice.objects.order_by('pk')[1]
assert final_invoice2.date_issue == invoice2.date_issue
assert final_invoice2.regie == regie1
assert final_invoice2.regie == regie
assert final_invoice2.pool == final_pool
assert final_invoice2.payer == invoice2.payer
assert final_invoice2.total_amount == invoice2.total_amount == 3
assert final_invoice2.number == 2
assert final_invoice2.formatted_number == 'F%02d-%s-0000002' % (regie1.pk, today.strftime('%y-%m'))
assert final_invoice2.formatted_number == 'F%02d-%s-0000002' % (regie.pk, today.strftime('%y-%m'))
final_line21 = InvoiceLine.objects.order_by('pk')[2]
assert final_line21.event_date == line21.event_date
@ -1824,12 +1831,12 @@ def test_promote_pool():
final_invoice3 = Invoice.objects.order_by('pk')[2]
assert final_invoice3.date_issue == invoice3.date_issue
assert final_invoice3.regie == regie2
assert final_invoice3.regie == regie
assert final_invoice3.pool == final_pool
assert final_invoice3.payer == invoice3.payer
assert final_invoice3.total_amount == invoice3.total_amount == 0
assert final_invoice3.number == 1
assert final_invoice3.formatted_number == 'F%02d-%s-0000001' % (regie2.pk, today.strftime('%y-%m'))
assert final_invoice3.number == 3
assert final_invoice3.formatted_number == 'F%02d-%s-0000003' % (regie.pk, today.strftime('%y-%m'))
with pytest.raises(PoolPromotionError) as excinfo:
old_pool.promote()

View File

@ -23,6 +23,7 @@ def test_invoice_total_amount(draft):
line_model = DraftInvoiceLine if draft else InvoiceLine
campaign = Campaign.objects.create(
regie=regie,
date_start=datetime.date(2022, 9, 1),
date_end=datetime.date(2022, 10, 1),
date_issue=datetime.date(2022, 10, 31),