eo_conges: add new app to report worker holidays (#72676)

This commit is contained in:
Valentin Deniaud 2023-07-26 12:10:39 +02:00
parent 0bc94be4ca
commit 7f25841bce
10 changed files with 688 additions and 0 deletions

1
debian/control vendored
View File

@ -16,6 +16,7 @@ Depends: ${misc:Depends}, ${python3:Depends},
python3-gadjo,
python3-django-mellon,
weasyprint,
python3-caldav,
Description: Logiciel de compta/facturation interne
Package: barbacompta

View File

@ -21,9 +21,12 @@ from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.contrib.auth.models import Group
from django.http import HttpResponseRedirect
from django.urls import path
from django.utils.http import urlencode
from django.views.decorators.cache import never_cache
from .eo_conges.views import workers_monthly_report, workers_monthly_report_redirect
User = get_user_model()
@ -46,6 +49,17 @@ class EOGestionAdminSite(admin.AdminSite):
return HttpResponseRedirect(url)
return super().logout(request, extra_context=extra_context)
def get_urls(self):
urls = [
path('conges/', self.admin_view(workers_monthly_report_redirect)),
path(
'conges/<int:year>/<int:month>/',
self.admin_view(workers_monthly_report),
name='conges-workers-monthly-report',
),
]
return urls + super().get_urls()
site = EOGestionAdminSite()
site.enable_nav_sidebar = False

View File

View File

@ -0,0 +1,3 @@
.worker span {
display: block;
}

View File

@ -0,0 +1,33 @@
{% extends "admin/base.html" %}
{% load static %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "css/conges.css" %}"/>
{% endblock %}
{% block content %}
<a href="{{ view.get_previous_month_url }}">Mois précédent</span></a> -
<a href="{{ view.get_next_month_url }}">Mois suivant</span></a>
{% if fatal_error %}
<p>{{ fatal_error }}</p>
{% else %}
{% if errors %}
<h2>Attention, des erreurs ont été rencontrées :</h2>
{% for error in errors %}
<p>{{ error }}</p>
{% endfor %}
{% endif %}
<h2>Rapport :</h2>
{% for worker in workers %}
<p class='worker'>
<span>{{ worker.display_name }} (matricule {{ worker.number }})</span>
<span>Tickets restaurant : 19 tickets de 8 euros</span>
{% if worker.is_parisian %}<span>Carte transport Paris 50%</span>{% endif %}
<span class='holidays'>Congés : {% firstof worker.holidays|join:', ' '0' %}.</span>
</p>
{% endfor %}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,271 @@
import dataclasses
import datetime
import caldav
from django.conf import settings
from django.http import Http404
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.text import slugify
from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.dates import MonthMixin, YearMixin
from lxml.etree import XMLSyntaxError
class ReportError(Exception):
pass
@dataclasses.dataclass
class Holiday:
start: datetime.date
end: datetime.date
half: bool = False
def __str__(self):
if self.half:
return '0,5j le %s' % date_format(self.start, 'd/m')
elif self.start == self.end:
return 'le %s' % date_format(self.start, 'd/m')
elif (self.end - self.start).days == 1:
return 'les %s et %s' % (date_format(self.start, 'd'), date_format(self.end, 'd/m'))
return 'du %s au %s' % (date_format(self.start, 'd'), date_format(self.end, 'd/m'))
class WorkersMonthlyReportRedirect(RedirectView):
pattern_name = 'admin:conges-workers-monthly-report'
def get_redirect_url(self, *args, **kwargs):
today = datetime.date.today()
kwargs['year'] = today.year
kwargs['month'] = today.month
return super().get_redirect_url(*args, **kwargs)
workers_monthly_report_redirect = WorkersMonthlyReportRedirect.as_view()
class WorkersMonthlyReport(MonthMixin, YearMixin, TemplateView):
template_name = 'eo_conges/workers_monthly_report.html'
def dispatch(self, *args, **kwargs):
try:
self.date = datetime.date(self.get_year(), self.get_month(), 1)
except ValueError:
raise Http404()
self.last_date = (self.date + datetime.timedelta(days=40)).replace(day=1) - datetime.timedelta(days=1)
return super().dispatch(*args, **kwargs)
def get_previous_month_url(self):
previous_month = self.date - datetime.timedelta(days=1)
return reverse(
'admin:conges-workers-monthly-report',
kwargs={'year': previous_month.year, 'month': previous_month.month},
)
def get_next_month_url(self):
next_month = self.last_date + datetime.timedelta(days=1)
return reverse(
'admin:conges-workers-monthly-report', kwargs={'year': next_month.year, 'month': next_month.month}
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['errors'] = self.errors = []
try:
context['workers'] = self.build_workers_data()
except ReportError as e:
context['fatal_error'] = e
return context
def get_events(self):
config = settings.WORKERS_CALENDAR_CONFIG
client = caldav.DAVClient(config['url'], username=config['username'], password=config['password'])
try:
calendar = client.principal().calendar(name=config['calendar'])
except caldav.error.DAVError as e:
raise ReportError('Erreur lors de la récupération du calendrier : %s' % e)
try:
events = calendar.search(event=True, start=self.date, end=self.last_date)
except caldav.error.DAVError as e:
raise ReportError('Erreur lors de la récupération du calendrier : %s' % e)
except XMLSyntaxError:
try:
events = calendar.search(event=True, start=self.date, end=self.last_date)
except XMLSyntaxError:
raise ReportError('Erreur lors de la récupération du calendrier : %s' % e)
return [x.vobject_instance.vevent for x in events]
def build_workers_data(self):
events = self.get_events()
holidays_events = [x for x in events if self.is_holiday(x)]
day_off_events = [x for x in events if self.is_day_off(x)]
workers_data = [
self.get_data_for_worker(worker, day_off_events, holidays_events)
for worker in settings.WORKERS_CALENDAR_CONFIG['workers']
]
workers_data = [x for x in workers_data if not x['ignore']]
events_no_match = [
x.summary.value for x in holidays_events + day_off_events if not hasattr(x, 'match')
]
if events_no_match:
self.errors.append(
'Aucun·e coopérateurice trouvé·e pour les évènements : %s.' % ', '.join(events_no_match)
)
return sorted(workers_data, key=lambda x: x['number'])
def is_day_off(self, event):
if not hasattr(event, 'categories'):
return False
return bool(settings.WORKERS_CALENDAR_CONFIG['day_off_category'] in event.categories.value)
def is_holiday(self, event):
if not hasattr(event, 'categories'):
return False
return bool(settings.WORKERS_CALENDAR_CONFIG['holiday_category'] in event.categories.value)
def get_data_for_worker(self, worker, day_off_events, holidays_events):
name = worker['display_name']
first_name, last_name = name.split()
worker_possible_names = [
worker['username'], # jdupont
first_name, # Jean
first_name + last_name[0], # JeanD
first_name[0] + last_name[0], # JD
]
worker_possible_names.extend(worker.get('aliases', [])) # ponpon
worker_possible_names = [slugify(x) for x in worker_possible_names]
errors = []
day_off = None
days_off = [x for x in day_off_events if self.is_event_for_worker(x, worker_possible_names)]
if len(days_off) > 1:
errors.append(
'%s a plusieurs jours off : %s.' % (name, ', '.join(x.summary.value for x in days_off))
)
elif not days_off:
errors.append("%s n'a pas de jour off." % name)
else:
day_off = days_off[0].getrruleset()[0].weekday()
def get_date_end(event):
if hasattr(event, 'rrule'):
return event.getrruleset()[-1].date() + datetime.timedelta(days=1)
return event.dtend.value
holidays = [
(x.dtstart.value, get_date_end(x))
for x in holidays_events
if self.is_event_for_worker(x, worker_possible_names)
]
holidays = self.normalize_holidays(holidays, day_off)
ignore = worker.get('ignore', False)
if not ignore:
self.errors.extend(errors)
return {
'number': worker['number'],
'ignore': ignore,
'is_parisian': worker.get('is_parisian', False),
'display_name': name,
'holidays': holidays,
}
def is_event_for_worker(self, event, worker_possible_names):
splitted_summary = event.summary.value.split()
worker_match = any(slugify(x) in worker_possible_names for x in splitted_summary)
if worker_match:
if hasattr(event, 'match'):
self.errors.append(
'L\'évènement %s correspond à plusieurs coopérateurices : %s et %s.'
% (event.summary.value, worker_possible_names[0], event.match)
)
event.__dict__['match'] = worker_possible_names[0]
return worker_match
def normalize_holidays(self, worker_holidays, day_off=None):
def make_holiday(start, end):
if type(start) != type(end):
start = start.date() if hasattr(start, 'date') else start
end = end.date() if hasattr(end, 'date') else end
half = False
if isinstance(start, datetime.datetime):
if start.date() == end.date():
if start.time() > datetime.time(12, 00):
half = True
elif end.time() <= datetime.time(15, 00):
half = True
start = start.date()
end = end.date()
else:
end -= datetime.timedelta(days=1)
start = max(start, self.date)
end = min(end, self.last_date)
return Holiday(start, end, half)
def split_holidays_on_day_off(holiday):
start, end = holiday.start, holiday.end
if start == end:
if start.weekday() != day_off:
yield holiday
return
if start.weekday() == day_off:
start += datetime.timedelta(days=1)
interval_start = start
for i in range((end - start).days + 1):
interval_end = start + datetime.timedelta(days=i)
if interval_end.weekday() == day_off:
yield Holiday(interval_start, interval_end - datetime.timedelta(days=1))
interval_start = interval_end + datetime.timedelta(days=1)
if interval_start <= interval_end:
yield Holiday(interval_start, interval_end)
def strip_weekend(holiday):
if holiday.start.weekday() in (5, 6):
holiday.start += datetime.timedelta(days=7 - holiday.start.weekday())
if holiday.end.weekday() in (5, 6):
holiday.end -= datetime.timedelta(days=holiday.end.weekday() - 4)
if holiday.start <= holiday.end:
return holiday
worker_holidays = [make_holiday(*dates) for dates in worker_holidays]
if day_off is not None:
worker_holidays = [
sub_holiday
for holiday in worker_holidays
for sub_holiday in split_holidays_on_day_off(holiday)
]
worker_holidays = [
holiday_no_weekend
for holiday in worker_holidays
if (holiday_no_weekend := strip_weekend(holiday))
]
return worker_holidays
workers_monthly_report = WorkersMonthlyReport.as_view()

View File

@ -168,6 +168,7 @@ INSTALLED_APPS = (
"django.contrib.admin",
"eo_gestion.eo_facture",
"eo_gestion.eo_banque",
'eo_gestion.eo_conges',
'eo_gestion.chorus',
'taggit',
'adminsortable2',
@ -187,6 +188,25 @@ REQUESTS_TIMEOUT = 32
TAGGIT_CASE_INSENSITIVE = True
WORKERS_CALENDAR_CONFIG = {
'url': 'https://calendar.entrouvert.org/groupdav.php/',
'username': 'user',
'password': 'pass',
'calendar': 'Calendrier Groupe Entrouvert',
'day_off_category': 'Journées off',
'holiday_category': 'Congés',
'workers': [
# {
# 'username': 'lmichel',
# 'display_name': 'Louise Michel',
# 'number': 42,
# 'aliases': ['enjolras'],
# 'is_parisian': True,
# 'ignore': False,
# },
],
}
local_settings_file = os.environ.get("BARBACOMPTA_SETTINGS_FILE", "local_settings.py")
if os.path.exists(local_settings_file):
with open(local_settings_file) as fd:

View File

@ -26,6 +26,7 @@ install_requires = [
'gadjo',
'django-mellon',
'python-dateutil',
'caldav',
]

337
tests/test_conges.py Normal file
View File

@ -0,0 +1,337 @@
import datetime
from unittest import mock
import pytest
import vobject
from django.utils.html import escape
from eo_gestion.eo_conges.views import ReportError
from .utils import login
@pytest.fixture
def events():
with mock.patch('caldav.davclient.DAVClient.principal') as mocked_principal:
mocked_principal.side_effet = FakePrincipal()
yield
def get_event(summary, start, end, category='Congés'):
event = vobject.newFromBehavior('vevent')
event = event.transformToNative()
event.add('summary').value = summary
event.add('dtstart').value = start
event.add('dtend').value = end
event.add('categories').value = [category]
return event
def test_conges_redirect(app, settings):
login(app)
resp = app.get('/conges/')
assert resp.location == '/conges/2019/1/'
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
events = mocked_get_events.return_value = []
login(app)
resp = app.get('/conges/2023/01/')
assert [x.text for x in resp.pyquery('.worker span')] == [
'Jean Dupont (matricule 42)',
'Tickets restaurant : 19 tickets de 8 euros',
'Congés : 0.',
]
assert '/conges/2022/12/' in resp.text
assert '/conges/2023/2/' in resp.text
events.append(
get_event('Jean', datetime.datetime(2023, 1, 2, 8, 00), datetime.datetime(2023, 1, 2, 18, 00))
)
resp = app.get('/conges/2023/01/')
assert [x.text for x in resp.pyquery('.worker span')] == [
'Jean Dupont (matricule 42)',
'Tickets restaurant : 19 tickets de 8 euros',
'Congés : le 02/01.',
]
events += [
# one day, using dates
get_event('Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5)),
# two days range, using mixed date/datetimes
get_event('Jean', datetime.datetime(2023, 1, 10, 8, 00), datetime.datetime(2023, 1, 11)),
# two days range, using dates
get_event('Jean', datetime.date(2023, 1, 12), datetime.date(2023, 1, 14)),
# 4 days range, using dates
get_event('Jean', datetime.date(2023, 1, 16), datetime.date(2023, 1, 20)),
# only morning
get_event('Jean', datetime.datetime(2023, 1, 23, 8, 00), datetime.datetime(2023, 1, 23, 14, 00)),
# only afternoon
get_event('Jean', datetime.datetime(2023, 1, 24, 14, 00), datetime.datetime(2023, 1, 24, 18, 00)),
]
# 6 days range, using rrule
event = get_event('Jean', datetime.date(2023, 1, 25), datetime.date(2023, 1, 26))
event.add('rrule').value = 'FREQ=DAILY;UNTIL=20230131'
events.append(event)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == (
'Congés : le 02/01, le 04/01, les 10 et 11/01, les 12 et 13/01, du 16 au 19/01, 0,5j le 23/01, '
'0,5j le 24/01, du 25 au 31/01.'
)
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_fatal_errors(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.side_effect = ReportError('Error xxx')
login(app)
resp = app.get('/conges/2023/01/')
assert 'Error xxx' in resp.text
app.get('/conges/2023/42/', status=404)
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_multiple_match_error(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
{'username': 'jdurand', 'display_name': 'Jean Durand', 'number': 43},
]
mocked_get_events.return_value = [
get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 10)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert (
escape("L'évènement Jean correspond à plusieurs coopérateurices : jdurand et jdupont.") in resp.text
)
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_no_match_error(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.return_value = [
get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7)),
get_event('xxx', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9)),
get_event('yyy', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert escape("Aucun·e coopérateurice trouvé·e pour les évènements : xxx, yyy") in resp.text
assert 'le 06/01' in resp.text
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_day_off_error(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.return_value = []
login(app)
resp = app.get('/conges/2023/01/')
assert escape("Jean Dupont n'a pas de jour off.") in resp.text
ev1 = get_event('Jean off', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7), category='Journées off')
ev1.add('rrule').value = 'FREQ=WEEKLY;BYDAY=MO'
ev2 = get_event(
'Jean off 2', datetime.date(2023, 1, 8), datetime.date(2023, 1, 9), category='Journées off'
)
ev2.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
mocked_get_events.return_value = [ev1, ev2]
resp = app.get('/conges/2023/01/')
assert 'Jean Dupont a plusieurs jours off : Jean off, Jean off 2.' in resp.text
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_is_parisian(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.return_value = []
login(app)
resp = app.get('/conges/2023/01/')
assert 'Carte transport Paris 50%' not in resp.text
settings.WORKERS_CALENDAR_CONFIG['workers'][0]['is_parisian'] = True
resp = app.get('/conges/2023/01/')
assert 'Carte transport Paris 50%' in resp.text
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_ignored_workers(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
{'username': 'lmichel', 'display_name': 'Louise Michel', 'number': 0, 'ignore': True},
]
mocked_get_events.return_value = [
get_event('congés jdupont', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
get_event('congés lmichel', datetime.date(2023, 1, 3), datetime.date(2023, 1, 4)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert 'Jean' in resp.text
assert 'Louise' not in resp.text
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_worker_possible_names(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
{'username': 'jmartin', 'display_name': 'Jean Martin', 'number': 43, 'aliases': ['Jeannot']},
]
mocked_get_events.return_value = [
get_event('congés jdupont', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
get_event('Jeannot en vacances', datetime.date(2023, 1, 3), datetime.date(2023, 1, 4)),
get_event('congés JD', datetime.date(2023, 1, 5), datetime.date(2023, 1, 6)),
get_event('JeanM', datetime.date(2023, 1, 6), datetime.date(2023, 1, 7)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert [x.text for x in resp.pyquery('.worker span.holidays')] == [
'Congés : le 02/01, le 05/01.',
'Congés : le 03/01, le 06/01.',
]
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_strip_weekend(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.return_value = [
# starts friday, ends monday
get_event('Jean', datetime.date(2023, 1, 6), datetime.date(2023, 1, 10)),
# start saturday, ends sunday next week
get_event('Jean', datetime.date(2023, 1, 14), datetime.date(2023, 1, 23)),
# two days of weekend
get_event('Jean', datetime.date(2023, 1, 28), datetime.date(2023, 1, 30)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 06 au 09/01, du 16 au 20/01.'
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_month_span(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
mocked_get_events.return_value = [
# starts in december
get_event('Jean', datetime.date(2022, 12, 25), datetime.date(2023, 1, 5)),
# ends in february
get_event('Jean', datetime.date(2023, 1, 30), datetime.date(2023, 2, 10)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 04/01, les 30 et 31/01.'
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_day_off(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
holidays = get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 7))
events = mocked_get_events.return_value = [holidays]
login(app)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 06/01.'
# off on wednesday, holidays split in half
event = get_event('Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5), category='Journées off')
event.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
events.append(event)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : les 02 et 03/01, les 05 et 06/01.'
# off on friday, holidays end one day sooner
event.rrule.value = 'FREQ=WEEKLY;BYDAY=FR'
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 02 au 05/01.'
# off on monday, holidays start one day later
event.rrule.value = 'FREQ=WEEKLY;BYDAY=MO'
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : du 03 au 06/01.'
# long holidays get split multiple times
holidays.dtend.value = datetime.date(2023, 1, 21)
resp = app.get('/conges/2023/01/')
assert (
resp.pyquery('.worker span.holidays').text()
== 'Congés : du 03 au 06/01, du 10 au 13/01, du 17 au 20/01.'
)
# split on wednesday
event.rrule.value = 'FREQ=WEEKLY;BYDAY=WE'
resp = app.get('/conges/2023/01/')
assert (
resp.pyquery('.worker span.holidays').text()
== 'Congés : les 02 et 03/01, du 05 au 10/01, du 12 au 17/01, les 19 et 20/01.'
)
@mock.patch('eo_gestion.eo_conges.views.WorkersMonthlyReport.get_events')
def test_conges_split_on_day_off_single_day(mocked_get_events, app, settings):
settings.WORKERS_CALENDAR_CONFIG['workers'] = [
{'username': 'jdupont', 'display_name': 'Jean Dupont', 'number': 42},
]
# off on wednesday
off_event = get_event(
'Jean', datetime.date(2023, 1, 4), datetime.date(2023, 1, 5), category='Journées off'
)
off_event.add('rrule').value = 'FREQ=WEEKLY;BYDAY=WE'
mocked_get_events.return_value = [
off_event,
# holidays on monday
get_event('Jean', datetime.date(2023, 1, 2), datetime.date(2023, 1, 3)),
]
login(app)
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : le 02/01.'
# off on monday
off_event.rrule.value = 'FREQ=WEEKLY;BYDAY=MO'
resp = app.get('/conges/2023/01/')
assert resp.pyquery('.worker span.holidays').text() == 'Congés : 0.'

8
tests/utils.py Normal file
View File

@ -0,0 +1,8 @@
def login(app, username='admin', password='admin'):
login_page = app.get('/login/')
login_form = login_page.forms[0]
login_form['username'] = username
login_form['password'] = password
resp = login_form.submit()
assert resp.status_int == 302
return app