Facturation: verrouillage auto des pointages (#75556) #42
|
@ -39,14 +39,15 @@ def get_chrono_service():
|
|||
return list(settings.KNOWN_SERVICES.get('chrono').values())[0]
|
||||
|
||||
|
||||
def get_chrono_json(path, params=None, log_errors=True):
|
||||
def chrono_json(path, params=None, json_params=None, log_errors=True, method='get'):
|
||||
chrono_site = get_chrono_service()
|
||||
if chrono_site is None:
|
||||
return
|
||||
try:
|
||||
response = requests.get(
|
||||
response = getattr(requests, method)(
|
||||
path,
|
||||
params=params or {},
|
||||
json=json_params or {},
|
||||
remote_service=chrono_site,
|
||||
without_user=True,
|
||||
headers={'accept': 'application/json'},
|
||||
|
@ -65,7 +66,7 @@ def get_chrono_json(path, params=None, log_errors=True):
|
|||
|
||||
|
||||
def collect_agenda_data():
|
||||
result = get_chrono_json('api/agenda/')
|
||||
result = chrono_json('api/agenda/')
|
||||
if result is None:
|
||||
return
|
||||
if result.get('data') is None:
|
||||
|
@ -123,7 +124,7 @@ def get_event(event_slug):
|
|||
def get_events(event_slugs, error_message=None, error_message_with_details=None):
|
||||
error_message = error_message or _('Unable to get events details')
|
||||
error_message_with_details = error_message_with_details or _('Unable to get events details (%s)')
|
||||
result = get_chrono_json('api/agendas/events/', params={'slots': event_slugs})
|
||||
result = chrono_json('api/agendas/events/', params={'slots': event_slugs})
|
||||
if not result:
|
||||
raise ChronoError(error_message)
|
||||
if result.get('err'):
|
||||
|
@ -144,7 +145,7 @@ def get_subscriptions(agenda_slug, user_external_id=None, date_start=None, date_
|
|||
params['date_end'] = date_end
|
||||
if params:
|
||||
url += '?%s' % '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
|
||||
result = get_chrono_json(url)
|
||||
result = chrono_json(url)
|
||||
if not result:
|
||||
raise ChronoError(_('Unable to get subscription details'))
|
||||
if result.get('err'):
|
||||
|
@ -155,7 +156,7 @@ def get_subscriptions(agenda_slug, user_external_id=None, date_start=None, date_
|
|||
|
||||
|
||||
def get_check_status(agenda_slugs, user_external_id, date_start, date_end):
|
||||
result = get_chrono_json(
|
||||
result = chrono_json(
|
||||
'api/agendas/events/check-status/?user_external_id=%s&agendas=%s&date_start=%s&date_end=%s'
|
||||
% (
|
||||
user_external_id,
|
||||
|
@ -171,3 +172,37 @@ def get_check_status(agenda_slugs, user_external_id, date_start, date_end):
|
|||
if 'data' not in result:
|
||||
raise ChronoError(_('Unable to get check status'))
|
||||
return result['data']
|
||||
|
||||
|
||||
def lock_events_check(agenda_slugs, date_start, date_end):
|
||||
result = chrono_json(
|
||||
'/api/agendas/events/check-lock/',
|
||||
json_params={
|
||||
'check_locked': True,
|
||||
'agendas': ','.join(agenda_slugs),
|
||||
'date_start': date_start.isoformat(),
|
||||
'date_end': date_end.isoformat(),
|
||||
},
|
||||
method='post',
|
||||
)
|
||||
if not result:
|
||||
raise ChronoError(_('Unable to lock events check'))
|
||||
if result.get('err'):
|
||||
raise ChronoError(_('Unable to lock events check (%s)') % result['err_desc'])
|
||||
|
||||
|
||||
def unlock_events_check(agenda_slugs, date_start, date_end):
|
||||
result = chrono_json(
|
||||
'/api/agendas/events/check-lock/',
|
||||
json_params={
|
||||
'check_locked': False,
|
||||
'agendas': ','.join(agenda_slugs),
|
||||
'date_start': date_start.isoformat(),
|
||||
'date_end': date_end.isoformat(),
|
||||
},
|
||||
method='post',
|
||||
)
|
||||
if not result:
|
||||
raise ChronoError(_('Unable to unlock events check'))
|
||||
if result.get('err'):
|
||||
raise ChronoError(_('Unable to unlock events check (%s)') % result['err_desc'])
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('invoicing', '0021_campaign_agendas'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='campaign',
|
||||
name='invalid',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -27,7 +27,7 @@ from django.utils.text import slugify
|
|||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from lingo.agendas.chrono import ChronoError
|
||||
from lingo.agendas.chrono import ChronoError, lock_events_check
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.utils.misc import generate_slug
|
||||
|
||||
|
@ -143,6 +143,7 @@ class Campaign(models.Model):
|
|||
max_length=10,
|
||||
)
|
||||
agendas = models.ManyToManyField(Agenda, related_name='campaigns')
|
||||
invalid = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return _('%(label)s (%(start)s - %(end)s)') % {
|
||||
|
@ -151,8 +152,20 @@ class Campaign(models.Model):
|
|||
'end': date_format(self.date_end, 'd/m/Y'),
|
||||
}
|
||||
|
||||
def mark_as_valid(self):
|
||||
self.invalid = False
|
||||
self.save()
|
||||
|
||||
def mark_as_invalid(self):
|
||||
self.invalid = True
|
||||
self.save()
|
||||
|
||||
def generate(self, spool=True):
|
||||
pool = self.pool_set.create(draft=True)
|
||||
try:
|
||||
pool.init()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if spool and 'uwsgi' in sys.modules:
|
||||
from lingo.invoicing.spooler import generate_invoices
|
||||
|
@ -189,6 +202,30 @@ class Pool(models.Model):
|
|||
def is_last(self):
|
||||
return not self.campaign.pool_set.filter(created_at__gt=self.created_at).exists()
|
||||
|
||||
def init(self):
|
||||
from lingo.invoicing import utils
|
||||
|
||||
try:
|
||||
agendas = utils.get_agendas(pool=self)
|
||||
if agendas:
|
||||
lock_events_check(
|
||||
agenda_slugs=[a.slug for a in agendas],
|
||||
date_start=self.campaign.date_start,
|
||||
date_end=self.campaign.date_end,
|
||||
)
|
||||
except ChronoError as e:
|
||||
self.status = 'failed'
|
||||
self.exception = e.msg
|
||||
self.completed_at = now()
|
||||
self.save()
|
||||
raise
|
||||
except Exception:
|
||||
self.status = 'failed'
|
||||
self.exception = traceback.format_exc()
|
||||
self.completed_at = now()
|
||||
self.save()
|
||||
raise
|
||||
|
||||
def generate_invoices(self):
|
||||
from lingo.invoicing import utils
|
||||
|
||||
|
|
|
@ -70,6 +70,9 @@
|
|||
{% 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' regie_pk=regie.pk pk=object.pk %}">{% trans 'Start a pool' %}</a>
|
||||
{% if not object.invalid %}
|
||||
pmarillonnet marked this conversation as resolved
|
||||
<a class="pk-button" rel="popup" href="{% url 'lingo-manager-invoicing-campaign-unlock-check' regie_pk=regie.pk pk=object.pk %}">{% trans 'Unlock check' %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "lingo/invoicing/manager_campaign_detail.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{% url 'lingo-manager-invoicing-campaign-unlock-check' regie.pk object.pk %}">{% trans "Unlock check" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Unlock check" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% trans "Are you sure you want to unlock check?" %}</p>
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Unlock check" %}</button>
|
||||
<a class="cancel" href="{% url 'lingo-manager-invoicing-campaign-detail' regie.pk object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -18,13 +18,19 @@
|
|||
{% if pool.draft and pool.status != 'registered' and pool.status != 'running' %}
|
||||
<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 %}
|
||||
{% if not object.invalid and pool.draft and pool.status == 'completed' and pool.is_last %}
|
||||
<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 %}
|
||||
|
||||
{% block content %}
|
||||
{% if pool.status == 'failed' %}
|
||||
<div class="pk-error">
|
||||
<p>{% trans "Error while running pool." %}</p>
|
||||
{% if pool.exception %}<pre>{{ pool.exception }}</pre>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="section">
|
||||
<div>
|
||||
<form class="invoice-filters">
|
||||
|
|
|
@ -63,6 +63,11 @@ urlpatterns = [
|
|||
views.campaign_delete,
|
||||
name='lingo-manager-invoicing-campaign-delete',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/campaign/<int:pk>/unlock-check/',
|
||||
views.campaign_unlock_check,
|
||||
name='lingo-manager-invoicing-campaign-unlock-check',
|
||||
),
|
||||
path(
|
||||
'regie/<int:regie_pk>/campaign/<int:pk>/pool/add/',
|
||||
views.pool_add,
|
||||
|
|
|
@ -37,6 +37,7 @@ from django.views.generic import (
|
|||
UpdateView,
|
||||
)
|
||||
|
||||
from lingo.agendas.chrono import ChronoError, unlock_events_check
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.forms import (
|
||||
CampaignForm,
|
||||
|
@ -284,6 +285,8 @@ class CampaignDetailView(DetailView):
|
|||
).order_by('-created_at')
|
||||
kwargs['has_running_pool'] = any(p.status in ['registered', 'running'] for p in kwargs['pools'])
|
||||
kwargs['has_real_pool'] = any(not p.draft for p in kwargs['pools'])
|
||||
if self.object.invalid:
|
||||
messages.warning(self.request, _('The last pool is invalid, please start a new pool.'))
|
||||
pmarillonnet marked this conversation as resolved
lguerin
commented
ici le warning ici le warning
pmarillonnet
commented
Nickel. Nickel.
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
|
@ -350,6 +353,47 @@ class CampaignDeleteView(DeleteView):
|
|||
campaign_delete = CampaignDeleteView.as_view()
|
||||
|
||||
|
||||
class CampaignUnlockCheckView(FormView):
|
||||
template_name = 'lingo/invoicing/manager_campaign_unlock_check.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.filter(regie=self.regie, invalid=False)
|
||||
.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.mark_as_invalid()
|
||||
agendas = [a.slug for a in self.object.agendas.all()]
|
||||
if agendas:
|
||||
try:
|
||||
unlock_events_check(
|
||||
agenda_slugs=agendas,
|
||||
date_start=self.object.date_start,
|
||||
date_end=self.object.date_end,
|
||||
)
|
||||
except ChronoError as e:
|
||||
messages.error(self.request, _('Fail to unlock events check: %s') % e)
|
||||
|
||||
return redirect(
|
||||
'%s#open:pools'
|
||||
% reverse('lingo-manager-invoicing-campaign-detail', args=[self.regie.pk, self.object.pk])
|
||||
)
|
||||
|
||||
|
||||
campaign_unlock_check = CampaignUnlockCheckView.as_view()
|
||||
|
||||
|
||||
class PoolDetailView(ListView):
|
||||
template_name = 'lingo/invoicing/manager_pool_detail.html'
|
||||
paginate_by = 100
|
||||
|
@ -454,6 +498,7 @@ class PoolAddView(FormView):
|
|||
return super().get_context_data(**kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object.mark_as_valid()
|
||||
self.object.generate()
|
||||
return redirect(
|
||||
'%s#open:pools'
|
||||
|
@ -473,6 +518,7 @@ class PoolPromoteView(FormView):
|
|||
Pool,
|
||||
campaign__id=kwargs['pk'],
|
||||
campaign__regie=self.regie,
|
||||
campaign__invalid=False,
|
||||
pk=kwargs['pool_pk'],
|
||||
draft=True,
|
||||
status='completed',
|
||||
|
@ -521,6 +567,8 @@ class PoolDeleteView(DeleteView):
|
|||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
if self.object.is_last:
|
||||
pmarillonnet marked this conversation as resolved
pmarillonnet
commented
Pareil ici niveau interface, est-ce qu’il y a l’info qui remonte sur le fait que la campagne est invalide parce que le dernier pool a été supprimé ? Pareil ici niveau interface, est-ce qu’il y a l’info qui remonte sur le fait que la campagne est invalide parce que le dernier pool a été supprimé ?
lguerin
commented
oui, un warning :) oui, un warning :)
|
||||
self.campaign.mark_as_invalid()
|
||||
DraftInvoiceLine.objects.filter(pool=self.object).delete()
|
||||
DraftInvoice.objects.filter(pool=self.object).delete()
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
|
|
@ -13,7 +13,9 @@ from lingo.agendas.chrono import (
|
|||
get_event,
|
||||
get_events,
|
||||
get_subscriptions,
|
||||
lock_events_check,
|
||||
refresh_agendas,
|
||||
unlock_events_check,
|
||||
)
|
||||
from lingo.agendas.models import Agenda
|
||||
|
||||
|
@ -458,3 +460,147 @@ def test_get_check_status():
|
|||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
) == ['foo', 'bar']
|
||||
|
||||
|
||||
def test_lock_events_check_no_service(settings):
|
||||
settings.KNOWN_SERVICES = {}
|
||||
with pytest.raises(ChronoError) as e:
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to lock events check'
|
||||
|
||||
settings.KNOWN_SERVICES = {'other': []}
|
||||
with pytest.raises(ChronoError) as e:
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to lock events check'
|
||||
|
||||
|
||||
def test_lock_events_check_status():
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
requests_post.side_effect = ConnectionError()
|
||||
with pytest.raises(ChronoError) as e:
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to lock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 500
|
||||
requests_post.return_value = mock_resp
|
||||
with pytest.raises(ChronoError) as e:
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to lock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 404
|
||||
requests_post.return_value = mock_resp
|
||||
with pytest.raises(ChronoError) as e:
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to lock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
requests_post.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'}))
|
||||
lock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert requests_post.call_args_list[0][0] == ('/api/agendas/events/check-lock/',)
|
||||
assert requests_post.call_args_list[0][1]['json'] == {
|
||||
'check_locked': True,
|
||||
'agendas': 'foo,bar',
|
||||
'date_start': '2022-09-01',
|
||||
'date_end': '2022-10-01',
|
||||
}
|
||||
assert requests_post.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org'
|
||||
|
||||
|
||||
def test_unlock_events_check_no_service(settings):
|
||||
settings.KNOWN_SERVICES = {}
|
||||
with pytest.raises(ChronoError) as e:
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to unlock events check'
|
||||
|
||||
settings.KNOWN_SERVICES = {'other': []}
|
||||
with pytest.raises(ChronoError) as e:
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to unlock events check'
|
||||
|
||||
|
||||
def test_unlock_events_check_status():
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
requests_post.side_effect = ConnectionError()
|
||||
with pytest.raises(ChronoError) as e:
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to unlock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 500
|
||||
requests_post.return_value = mock_resp
|
||||
with pytest.raises(ChronoError) as e:
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to unlock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
mock_resp = Response()
|
||||
mock_resp.status_code = 404
|
||||
requests_post.return_value = mock_resp
|
||||
with pytest.raises(ChronoError) as e:
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert str(e.value) == 'Unable to unlock events check'
|
||||
|
||||
with mock.patch('requests.Session.post') as requests_post:
|
||||
requests_post.return_value = MockedRequestResponse(content=json.dumps({'foo': 'bar'}))
|
||||
unlock_events_check(
|
||||
agenda_slugs=['foo', 'bar'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
assert requests_post.call_args_list[0][0] == ('/api/agendas/events/check-lock/',)
|
||||
assert requests_post.call_args_list[0][1]['json'] == {
|
||||
'check_locked': False,
|
||||
'agendas': 'foo,bar',
|
||||
'date_start': '2022-09-01',
|
||||
'date_end': '2022-10-01',
|
||||
}
|
||||
assert requests_post.call_args_list[0][1]['remote_service']['url'] == 'http://chrono.example.org'
|
||||
|
|
|
@ -4,6 +4,7 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
|
||||
from lingo.agendas.chrono import ChronoError
|
||||
from lingo.agendas.models import Agenda
|
||||
from lingo.invoicing.models import (
|
||||
Campaign,
|
||||
|
@ -216,6 +217,7 @@ def test_detail_campaign(app, admin_user):
|
|||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool1.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool2.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk) not in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk) not in resp
|
||||
|
||||
pool2.status = 'running'
|
||||
pool2.save()
|
||||
|
@ -225,6 +227,7 @@ def test_detail_campaign(app, admin_user):
|
|||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool1.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool2.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk) not in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk) not in resp
|
||||
|
||||
pool2.status = 'failed'
|
||||
pool2.save()
|
||||
|
@ -234,10 +237,12 @@ def test_detail_campaign(app, admin_user):
|
|||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool1.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool2.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk) in resp
|
||||
|
||||
pool3 = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=False,
|
||||
status='completed',
|
||||
)
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/' % (regie.pk, campaign.pk))
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/edit/' % (regie.pk, campaign.pk) not in resp
|
||||
|
@ -246,6 +251,22 @@ def test_detail_campaign(app, admin_user):
|
|||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool2.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool3.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk) not in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk) not in resp
|
||||
assert 'The last pool is invalid, please start a new pool.' not in resp
|
||||
|
||||
pool3.draft = True
|
||||
pool3.save()
|
||||
campaign.invalid = True
|
||||
campaign.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/' % (regie.pk, campaign.pk))
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/edit/' % (regie.pk, campaign.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/delete/' % (regie.pk, campaign.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool1.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool2.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool3.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk) in resp
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk) not in resp
|
||||
assert 'The last pool is invalid, please start a new pool.' in resp
|
||||
|
||||
line = DraftInvoiceLine.objects.create(
|
||||
event_date=datetime.date(2022, 9, 1),
|
||||
|
@ -538,6 +559,7 @@ def test_add_pool(app, admin_user):
|
|||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
date_issue=datetime.date(2022, 10, 31),
|
||||
invalid=True,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
|
@ -548,6 +570,8 @@ def test_add_pool(app, admin_user):
|
|||
'/manage/invoicing/regie/%s/campaign/%s/#open:pools' % (regie.pk, campaign.pk)
|
||||
)
|
||||
assert mock_generate.call_args_list == [mock.call(campaign)]
|
||||
campaign.refresh_from_db()
|
||||
assert campaign.invalid is False
|
||||
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
|
@ -576,6 +600,93 @@ def test_add_pool(app, admin_user):
|
|||
app.get('/manage/invoicing/regie/%s/campaign/%s/pool/add/' % (regie.pk, campaign.pk), status=404)
|
||||
|
||||
|
||||
@mock.patch('lingo.invoicing.views.unlock_events_check')
|
||||
def test_unlock_check(mock_unlock, app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
agenda = Agenda.objects.create(label='Foo bar', regie=regie)
|
||||
agenda2 = Agenda.objects.create(label='Foo bar 2', regie=regie)
|
||||
Agenda.objects.create(label='Foo bar 3', regie=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),
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
|
||||
# no agendas
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk))
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/#open:pools' % (regie.pk, campaign.pk)
|
||||
)
|
||||
assert mock_unlock.call_args_list == []
|
||||
campaign.refresh_from_db()
|
||||
assert campaign.invalid is True
|
||||
|
||||
# with agendas
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
campaign.agendas.add(agenda, agenda2)
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk))
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/#open:pools' % (regie.pk, campaign.pk)
|
||||
)
|
||||
assert mock_unlock.call_args_list == [
|
||||
mock.call(
|
||||
agenda_slugs=['foo-bar', 'foo-bar-2'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
]
|
||||
campaign.refresh_from_db()
|
||||
assert campaign.invalid is True
|
||||
|
||||
# ChronoError
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
mock_unlock.side_effect = ChronoError('foo baz')
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk))
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'Fail to unlock events check: foo baz' in resp
|
||||
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
pool = Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=True,
|
||||
status='failed',
|
||||
)
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk))
|
||||
|
||||
pool.status = 'completed'
|
||||
pool.save()
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk))
|
||||
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (0, campaign.pk), status=404)
|
||||
|
||||
pool.status = 'registered'
|
||||
pool.save()
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk), status=404)
|
||||
|
||||
pool.status = 'running'
|
||||
pool.save()
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk), status=404)
|
||||
|
||||
pool.status = 'completed'
|
||||
pool.draft = False
|
||||
pool.save()
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk), status=404)
|
||||
|
||||
pool.draft = True
|
||||
pool.save()
|
||||
campaign.invalid = True
|
||||
campaign.save()
|
||||
app.get('/manage/invoicing/regie/%s/campaign/%s/unlock-check/' % (regie.pk, campaign.pk), status=404)
|
||||
|
||||
|
||||
def test_promote_pool(app, admin_user):
|
||||
regie = Regie.objects.create(label='Foo')
|
||||
campaign = Campaign.objects.create(
|
||||
|
@ -634,6 +745,15 @@ def test_promote_pool(app, admin_user):
|
|||
|
||||
pool.draft = True
|
||||
pool.save()
|
||||
campaign.invalid = True
|
||||
campaign.save()
|
||||
app.get(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/promote/' % (regie.pk, campaign.pk, pool.pk),
|
||||
status=404,
|
||||
)
|
||||
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/promote/' % (regie.pk, campaign.pk, pool.pk)
|
||||
)
|
||||
|
@ -728,6 +848,17 @@ def test_detail_pool(app, admin_user):
|
|||
|
||||
pool.status = 'completed'
|
||||
pool.save()
|
||||
campaign.invalid = True
|
||||
campaign.save()
|
||||
resp = app.get('/manage/invoicing/regie/%s/campaign/%s/pool/%s/' % (regie.pk, campaign.pk, pool.pk))
|
||||
assert '/manage/invoicing/regie/%s/campaign/%s/pool/%s/delete/' % (regie.pk, campaign.pk, pool.pk) in resp
|
||||
assert (
|
||||
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/promote/' % (regie.pk, campaign.pk, pool.pk)
|
||||
not in resp
|
||||
)
|
||||
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
Pool.objects.create(
|
||||
campaign=pool.campaign,
|
||||
)
|
||||
|
@ -1752,6 +1883,25 @@ def test_delete_pool(app, admin_user):
|
|||
assert resp.location.endswith(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/#open:pools' % (regie.pk, campaign.pk)
|
||||
)
|
||||
campaign.refresh_from_db()
|
||||
assert campaign.invalid is True
|
||||
|
||||
pool.save()
|
||||
Pool.objects.create(
|
||||
campaign=campaign,
|
||||
draft=True,
|
||||
status='completed',
|
||||
)
|
||||
campaign.invalid = False
|
||||
campaign.save()
|
||||
resp = app.get(
|
||||
'/manage/invoicing/regie/%s/campaign/%s/pool/%s/delete/' % (regie.pk, campaign.pk, pool.pk)
|
||||
)
|
||||
resp.form.submit()
|
||||
assert Pool.objects.count() == 1
|
||||
campaign.refresh_from_db()
|
||||
# pool is not the last, don't invalidate the campaign
|
||||
assert campaign.invalid is False
|
||||
|
||||
pool.draft = True
|
||||
pool.save()
|
||||
|
|
|
@ -1838,11 +1838,12 @@ def test_generate_invoices_from_lines():
|
|||
assert list(invoice2.lines.order_by('pk')) == [line4]
|
||||
|
||||
|
||||
@mock.patch('lingo.invoicing.models.lock_events_check')
|
||||
@mock.patch('lingo.invoicing.utils.get_agendas')
|
||||
@mock.patch('lingo.invoicing.utils.get_users_from_subscriptions')
|
||||
@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):
|
||||
def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas, mock_lock):
|
||||
regie = Regie.objects.create(label='Regie')
|
||||
agenda1 = Agenda.objects.create(label='Agenda 1')
|
||||
agenda2 = Agenda.objects.create(label='Agenda 2')
|
||||
|
@ -1863,7 +1864,14 @@ def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas):
|
|||
pool = Pool.objects.latest('pk')
|
||||
assert pool.campaign == campaign
|
||||
assert pool.draft is True
|
||||
assert mock_agendas.call_args_list == [mock.call(pool=pool)]
|
||||
assert mock_lock.call_args_list == [
|
||||
mock.call(
|
||||
agenda_slugs=['agenda-1', 'agenda-2'],
|
||||
date_start=datetime.date(2022, 9, 1),
|
||||
date_end=datetime.date(2022, 10, 1),
|
||||
)
|
||||
]
|
||||
assert mock_agendas.call_args_list == [mock.call(pool=pool), mock.call(pool=pool)]
|
||||
assert mock_users.call_args_list == [
|
||||
mock.call(
|
||||
agendas=[agenda1, agenda2],
|
||||
|
@ -1885,11 +1893,12 @@ def test_generate_invoices(mock_generate, mock_lines, mock_users, mock_agendas):
|
|||
]
|
||||
|
||||
|
||||
@mock.patch('lingo.invoicing.models.lock_events_check')
|
||||
@mock.patch('lingo.invoicing.utils.get_agendas')
|
||||
@mock.patch('lingo.invoicing.utils.get_users_from_subscriptions')
|
||||
@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):
|
||||
def test_generate_invoices_errors(mock_generate, mock_lines, mock_users, mock_agendas, mock_lock):
|
||||
regie = Regie.objects.create(label='Regie')
|
||||
agenda1 = Agenda.objects.create(label='Agenda 1')
|
||||
agenda2 = Agenda.objects.create(label='Agenda 2')
|
||||
|
@ -1901,6 +1910,15 @@ def test_generate_invoices_errors(mock_generate, mock_lines, mock_users, mock_ag
|
|||
date_issue=datetime.date(2022, 10, 31),
|
||||
)
|
||||
|
||||
mock_lock.side_effect = ChronoError('foo baz')
|
||||
mock_agendas.return_value = [agenda1, agenda2]
|
||||
mock_users.return_value = ['foo', 'bar']
|
||||
campaign.generate()
|
||||
pool = Pool.objects.latest('pk')
|
||||
assert pool.status == 'failed'
|
||||
assert pool.exception == 'foo baz'
|
||||
|
||||
mock_lock.side_effect = None
|
||||
mock_agendas.return_value = [agenda1, agenda2]
|
||||
mock_users.side_effect = ChronoError('foo bar')
|
||||
campaign.generate()
|
||||
|
|
Loading…
Reference in New Issue
Question d’UI, peut-être juste ici indiquer que le déverrouillage n’est pas possible car la campagne est invalide ? Je sens déjà venir les tickets clients “Le bouton de déverrouillage a disparu !!” :)
On a déjà un warning en haut de page qui indique qu'il faut relancer un pool, je pense que ça suffit, non ?
Ah oui ok j’avais oublié cela, merci.