manager: timesheet generation (#61070)

This commit is contained in:
Lauréline Guérin 2022-02-15 17:16:31 +01:00
parent 4e71f011b7
commit 0154debfb2
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
12 changed files with 757 additions and 7 deletions

View File

@ -15,10 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import copy
import csv
import datetime
from collections import defaultdict
from operator import itemgetter
import django_filters
from dateutil.relativedelta import relativedelta
from django import forms
from django.conf import settings
from django.contrib.auth.models import Group
@ -27,7 +31,7 @@ from django.db import transaction
from django.forms import ValidationError
from django.utils.encoding import force_text
from django.utils.six import StringIO
from django.utils.timezone import make_aware, now
from django.utils.timezone import localtime, make_aware, now
from django.utils.translation import ugettext_lazy as _
from chrono.agendas.models import (
@ -357,6 +361,122 @@ class BookingAbsenceReasonForm(forms.Form):
]
class EventsTimesheetForm(forms.Form):
date_start = forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
)
date_end = forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
)
def __init__(self, *args, **kwargs):
self.agenda = kwargs.pop('agenda')
super().__init__(*args, **kwargs)
def get_slots(self):
min_start = make_aware(
datetime.datetime.combine(self.cleaned_data['date_start'], datetime.time(0, 0))
)
max_start = make_aware(datetime.datetime.combine(self.cleaned_data['date_end'], datetime.time(0, 0)))
max_start = max_start + datetime.timedelta(days=1)
# fetch all events in this range
events_qs = (
self.agenda.event_set.filter(
recurrence_days__isnull=True,
start_datetime__gte=min_start,
start_datetime__lt=max_start,
)
.select_related('primary_event')
.order_by('start_datetime', 'label')
)
all_events = self.agenda.add_event_recurrences(events_qs, min_start, max_start)
dates = set()
events = []
dates_per_event_id = defaultdict(list)
for event in all_events:
date = localtime(event.start_datetime).date()
dates.add(date)
real_event = event.primary_event or event
if real_event not in events:
events.append(real_event)
dates_per_event_id[real_event.pk].append(date)
dates = sorted(dates)
event_slots = []
for event in events:
event_slots.append(
{'event': event, 'dates': {date: False for date in dates_per_event_id[event.pk]}}
)
users = {}
subscriptions = self.agenda.subscriptions.filter(date_start__lt=max_start, date_end__gt=min_start)
for subscription in subscriptions:
if subscription.user_external_id in users:
continue
users[subscription.user_external_id] = {
'user_id': subscription.user_external_id,
'user_first_name': subscription.user_first_name,
'user_last_name': subscription.user_last_name,
'events': copy.deepcopy(event_slots),
}
booking_qs_kwargs = {}
if not self.agenda.subscriptions.exists():
booking_qs_kwargs = {'cancellation_datetime__isnull': True}
booked_qs = (
Booking.objects.filter(
event__in=all_events,
in_waiting_list=False,
primary_booking__isnull=True,
**booking_qs_kwargs,
)
.exclude(user_external_id='')
.select_related('event')
.order_by('event__start_datetime')
)
for booking in booked_qs:
user_id = booking.user_external_id
if user_id not in users:
users[user_id] = {
'user_id': user_id,
'user_first_name': booking.user_first_name,
'user_last_name': booking.user_last_name,
'events': copy.deepcopy(event_slots),
}
if booking.cancellation_datetime is not None:
continue
# mark the slot as booked
date = localtime(booking.event.start_datetime).date()
for event in users[user_id]['events']:
if event['event'].pk != (booking.event.primary_event_id or booking.event_id):
continue
if date in event['dates']:
event['dates'][date] = True
break
users = sorted(users.values(), key=itemgetter('user_last_name', 'user_first_name', 'user_id'))
return {
'dates': dates,
'events': events,
'users': users,
}
def clean(self):
cleaned_data = super().clean()
if 'date_start' in cleaned_data and 'date_end' in cleaned_data:
if cleaned_data['date_end'] < cleaned_data['date_start']:
self.add_error('date_end', _('End date must be greater than start date.'))
elif (cleaned_data['date_start'] + relativedelta(months=3)) < cleaned_data['date_end']:
self.add_error('date_end', _('Please select an interval of no more than 3 months.'))
return cleaned_data
class AgendaResourceForm(forms.Form):
resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())

View File

@ -481,3 +481,25 @@ form div.widget[id^=id_recurrence] {
div.ui-dialog div.widget .datetime input {
width: auto;
}
table.timesheet {
width: auto;
th {
padding: 0.5em 0.5ex;
background: none;
border: 1px solid #f3f3f3;
&.date {
width: 20px;
text-align: center;
}
}
td {
padding: 0.5em 0.5ex;
border: 1px solid #f3f3f3;
&.date {
text-align: center;
padding: 0px;
max-width: 2em;
}
}
}

View File

@ -9,6 +9,10 @@
{% endblock %}
{% block actions %}
<a class="extra-actions-menu-opener"></a>
<ul class="extra-actions-menu">
<li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
</ul>
{{ block.super }}
<a href="{% url 'chrono-manager-agenda-month-redirect-view' pk=agenda.pk %}">{% trans 'Month view' %}</a>
{% endblock %}

View File

@ -2,6 +2,10 @@
{% load i18n %}
{% block actions %}
<a class="extra-actions-menu-opener"></a>
<ul class="extra-actions-menu">
<li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
</ul>
{{ block.super }}
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
<a href="{% url 'chrono-manager-agenda-month-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"n" %}">{% trans 'Month view' %}</a>

View File

@ -3,12 +3,13 @@
{% block actions %}
<a class="extra-actions-menu-opener"></a>
<ul class="extra-actions-menu">
<li><a href="{% url 'chrono-manager-event-cancellation-report-list' pk=agenda.pk %}">{% trans 'Cancellation error reports' %}</a></li>
<li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
</ul>
{{ block.super }}
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
<a href="{% url 'chrono-manager-agenda-day-view' pk=agenda.pk year=view.date|date:"Y" month=view.date|date:"m" day=view.date|date:"d" %}">{% trans 'Day view' %}</a>
<ul class="extra-actions-menu">
<li><a href="{% url 'chrono-manager-event-cancellation-report-list' pk=agenda.pk %}">{% trans 'Cancellation error reports' %}</a></li>
</ul>
{% endblock %}
{% block content %}

View File

@ -0,0 +1,54 @@
{% extends "chrono/manager_agenda_view.html" %}
{% load i18n chrono %}
{% block breadcrumb %}
{{ block.super }}
<a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans "Timesheet" %}</a>
{% endblock %}
{% block appbar_actions %}{% endblock %}
{% block content %}
<div class="section">
<h3>{% trans "Timesheet configuration" %}</h3>
<div>
<form>
{{ form.as_p }}
<button class="submit-button">{% trans "See timesheet" %}</button>
</form>
{% if request.GET and form.is_valid %}
<h4>{% blocktrans with start=form.cleaned_data.date_start end=form.cleaned_data.date_end %}Timesheet from {{ start }} to {{ end }}{% endblocktrans %}</h4>
{% with slots=form.get_slots %}
{% with events_num=slots.events|length %}
<table class="main timesheet">
<thead>
<tr>
<th>{% trans "First name" %}</th>
<th>{% trans "Last name" %}</th>
{% if events_num > 1 %}<th>{% trans "Activity" %}</th>{% endif %}
{% for date in slots.dates %}<th class="date">{{ date|date:"D d/m" }}</th>{% endfor %}
</tr>
</thead>
<tbody>
{% for user in slots.users %}{% for event in user.events %}
<tr>
{% if forloop.first %}
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }}</td>
<td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }}</td>
{% endif %}
{% if events_num > 1 %}<td>{{ event.event }}</td>{% endif %}
{% for date in slots.dates %}
{% with booked=event.dates|get:date %}<td class="date">{% if booked is True %}☐{% elif booked is None %}-{% endif %}</td>{% endwith %}
{% endfor %}
</tr>
{% endfor %}{% endfor %}
</tbody>
</table>
{% endwith %}
{% endwith %}
{% endif %}
</div>
</div>
{% endblock %}

View File

View File

@ -0,0 +1,31 @@
# chrono - agendas system
# Copyright (C) 2022 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django import template
register = template.Library()
@register.filter(name='get')
def get(obj, key):
try:
return obj.get(key)
except AttributeError:
try:
return obj[key]
except (IndexError, KeyError, TypeError):
return None

View File

@ -191,6 +191,11 @@ urlpatterns = [
views.agenda_reminder_preview,
name='chrono-manager-agenda-reminder-preview',
),
url(
r'^agendas/(?P<pk>\d+)/events/timesheet$',
views.events_timesheet,
name='chrono-manager-events-timesheet',
),
url(
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/$',
views.event_view,

View File

@ -97,6 +97,7 @@ from .forms import (
DeskForm,
EventCancelForm,
EventForm,
EventsTimesheetForm,
ExceptionsImportForm,
ImportEventsForm,
MeetingTypeForm,
@ -1953,6 +1954,28 @@ class AgendaReminderPreviewView(ManagedAgendaMixin, TemplateView):
agenda_reminder_preview = AgendaReminderPreviewView.as_view()
class EventsTimesheetView(ViewableAgendaMixin, DetailView):
model = Agenda
template_name = 'chrono/manager_events_timesheet.html'
def set_agenda(self, **kwargs):
self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
def get_object(self, **kwargs):
return self.agenda
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = EventsTimesheetForm(agenda=self.agenda, data=self.request.GET or None)
if self.request.GET:
form.is_valid()
context['form'] = form
return context
events_timesheet = EventsTimesheetView.as_view()
class EventDetailView(ViewableAgendaMixin, DetailView):
model = Event
pk_url_kwarg = 'event_pk'

View File

@ -1448,9 +1448,9 @@ def test_agenda_events_month_view(app, admin_user):
)
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
assert len(resp.pyquery.find('.event-info')) == 4
assert 'abc' in resp.pyquery.find('li')[4].text_content()
assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[5].text_content()
assert 'xyz' in resp.pyquery.find('li')[6].text_content()
assert 'abc' in resp.pyquery.find('li')[5].text_content()
assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[6].text_content()
assert 'xyz' in resp.pyquery.find('li')[7].text_content()
# 12/2020 has 5 Wednesday
resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))

View File

@ -2007,3 +2007,489 @@ def test_event_check_primary_booking(app, admin_user):
user_bookings = resp.pyquery.find('td.booking-username.waiting')
assert len(user_bookings) == 1
assert user_bookings[0].text == 'Jane Doe (2 places)'
def test_events_timesheet_wrong_kind(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
app = login(app)
app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404)
agenda.kind = 'virtual'
agenda.save()
app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404)
def test_events_timesheet_form(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-01-01'
resp.form['date_end'] = '2021-12-31'
resp = resp.form.submit()
assert resp.context['form'].errors['date_end'] == ['End date must be greater than start date.']
resp.form['date_end'] = '2022-04-02'
resp = resp.form.submit()
assert resp.context['form'].errors['date_end'] == ['Please select an interval of no more than 3 months.']
resp.form['date_end'] = '2022-04-01'
resp = resp.form.submit()
assert resp.context['form'].errors == {}
@pytest.mark.freeze_time('2022-02-15')
def test_events_timesheet_slots(app, admin_user):
start, end = (
now() - datetime.timedelta(days=15),
now() + datetime.timedelta(days=14),
) # 2022-02-31, 2022-03-01
agenda = Agenda.objects.create(label='Events', kind='events')
Event.objects.create(label='event 1', start_datetime=start, places=10, agenda=agenda)
event2 = Event.objects.create(
label='event 2', start_datetime=start + datetime.timedelta(days=1), places=10, agenda=agenda
)
event3 = Event.objects.create(label='event 3', start_datetime=now(), places=10, agenda=agenda)
Event.objects.create(
label='event cancelled',
start_datetime=now() + datetime.timedelta(days=4),
places=10,
agenda=agenda,
cancelled=True,
)
event4 = Event.objects.create(
label='event 4', start_datetime=end - datetime.timedelta(days=1), places=10, agenda=agenda
)
Event.objects.create(label='event 5', start_datetime=end, places=10, agenda=agenda)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
date_start=start,
date_end=end + datetime.timedelta(days=1),
)
recurring_event1 = Event.objects.create(
label='recurring 1',
start_datetime=start,
places=10,
agenda=agenda,
recurrence_days=[0, 1],
recurrence_end_date=end,
)
recurring_event1.create_all_recurrences()
recurring_event2 = Event.objects.create(
label='recurring 2',
start_datetime=start,
places=10,
agenda=agenda,
recurrence_days=[1, 2],
recurrence_end_date=end,
)
recurring_event2.create_all_recurrences()
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-02-01'
resp.form['date_end'] = '2022-02-28'
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit()
assert len(ctx.captured_queries) == 9
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 2),
datetime.date(2022, 2, 7),
datetime.date(2022, 2, 8),
datetime.date(2022, 2, 9),
datetime.date(2022, 2, 14),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 16),
datetime.date(2022, 2, 21),
datetime.date(2022, 2, 22),
datetime.date(2022, 2, 23),
datetime.date(2022, 2, 28),
]
assert slots['events'] == [
event2,
recurring_event1,
recurring_event2,
event3,
event4,
]
assert slots['users'] == [
{
'user_id': 'user:1',
'user_first_name': 'Subscription',
'user_last_name': '42',
'events': [
{
'event': event2,
'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 1)},
},
{
'event': recurring_event1,
'dates': {date: False for date in slots['dates'] if date.weekday() in [0, 1]},
},
{
'event': recurring_event2,
'dates': {date: False for date in slots['dates'] if date.weekday() in [1, 2]},
},
{
'event': event3,
'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 15)},
},
{
'event': event4,
'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 28)},
},
],
},
]
@pytest.mark.freeze_time('2022-02-15')
def test_events_timesheet_subscription_limits(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
event1 = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 1, 17, 0)), places=10, agenda=agenda
)
event2 = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
)
event3 = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 28, 17, 0)), places=10, agenda=agenda
)
dates = [
('2022-01-31', '2022-02-01'),
('2022-02-01', '2022-02-02'),
('2022-02-01', '2022-02-15'),
('2022-02-01', '2022-02-16'),
('2022-02-15', '2022-02-28'),
('2022-02-15', '2022-03-01'),
('2022-02-16', '2022-03-01'),
('2022-02-01', '2022-03-01'),
('2022-02-28', '2022-03-01'),
('2022-03-01', '2022-03-02'),
]
for start, end in dates:
Subscription.objects.create(
agenda=agenda,
user_external_id='user:%s-%s' % (start, end),
user_first_name='Subscription',
user_last_name='%s - %s' % (start, end),
date_start=datetime.datetime.strptime(start, '%Y-%m-%d'),
date_end=datetime.datetime.strptime(end, '%Y-%m-%d'),
)
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-02-01'
resp.form['date_end'] = '2022-02-28'
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['dates'] == [
datetime.date(2022, 2, 1),
datetime.date(2022, 2, 15),
datetime.date(2022, 2, 28),
]
assert slots['events'] == [
event1,
event2,
event3,
]
assert len(slots['users']) == 8
assert slots['users'][0]['user_id'] == 'user:2022-02-01-2022-02-02'
assert slots['users'][1]['user_id'] == 'user:2022-02-01-2022-02-15'
assert slots['users'][2]['user_id'] == 'user:2022-02-01-2022-02-16'
assert slots['users'][3]['user_id'] == 'user:2022-02-01-2022-03-01'
assert slots['users'][4]['user_id'] == 'user:2022-02-15-2022-02-28'
assert slots['users'][5]['user_id'] == 'user:2022-02-15-2022-03-01'
assert slots['users'][6]['user_id'] == 'user:2022-02-16-2022-03-01'
assert slots['users'][7]['user_id'] == 'user:2022-02-28-2022-03-01'
def test_events_timesheet_users(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
)
booking1 = Booking.objects.create(
event=event, user_external_id='user:1', user_first_name='User', user_last_name='42'
)
Booking.objects.create(
event=event, user_external_id='user:2', user_first_name='User', user_last_name='01'
)
Booking.objects.create(
event=event, user_external_id='user:3', user_first_name='User', user_last_name='17'
)
Booking.objects.create(
event=event, user_external_id='user:4', user_first_name='User', user_last_name='35'
)
Booking.objects.create(
event=event, user_external_id='user:5', user_first_name='User', user_last_name='05'
)
booking6 = Booking.objects.create(
event=event, user_external_id='user:6', user_first_name='User', user_last_name='12 Cancelled'
)
booking6.cancel()
Booking.objects.create(
event=event,
user_external_id='user:7',
user_first_name='User',
user_last_name='Waiting',
in_waiting_list=True,
)
booking8 = Booking.objects.create(
event=event,
user_external_id='user:8',
user_first_name='User',
user_last_name='Waiting and Cancelled',
in_waiting_list=True,
)
booking8.cancel()
Booking.objects.create(
event=event,
user_external_id='user:1',
user_first_name='User',
user_last_name='Secondary',
primary_booking=booking1,
)
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-02-01'
resp.form['date_end'] = '2022-02-28'
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert [u['user_id'] for u in slots['users']] == [
'user:2',
'user:5',
'user:3',
'user:4',
'user:1',
]
start = datetime.date(2022, 2, 1)
end = datetime.date(2022, 3, 1)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
date_start=start,
date_end=end,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:9',
user_first_name='Subscription',
user_last_name='43',
date_start=start,
date_end=end,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:10',
user_first_name='Subscription',
user_last_name='14',
date_start=start,
date_end=end,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:7',
user_first_name='Subscription',
user_last_name='Waiting',
date_start=start,
date_end=end,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:42',
user_first_name='Subscription',
user_last_name='Too soon',
date_start=start - datetime.timedelta(days=1),
date_end=start,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='user:43',
user_first_name='Subscription',
user_last_name='Too late',
date_start=end + datetime.timedelta(days=1),
date_end=end + datetime.timedelta(days=2),
)
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert [u['user_id'] for u in slots['users']] == [
'user:2',
'user:5',
'user:6',
'user:10',
'user:3',
'user:4',
'user:1',
'user:9',
'user:7',
]
def test_events_timesheet_user_ids(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
)
booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-02-01'
resp.form['date_end'] = '2022-02-28'
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
# no user_id found
assert [u['user_id'] for u in slots['users']] == []
assert [u['user_first_name'] for u in slots['users']] == []
assert [u['user_last_name'] for u in slots['users']] == []
booking.user_external_id = 'user:1'
booking.save()
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert [u['user_id'] for u in slots['users']] == [
'user:1',
]
assert [u['user_first_name'] for u in slots['users']] == ['User']
assert [u['user_last_name'] for u in slots['users']] == ['42']
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='41',
date_start=datetime.date(2022, 2, 1),
date_end=datetime.date(2022, 3, 1),
)
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert [u['user_id'] for u in slots['users']] == [
'user:1',
]
assert [u['user_first_name'] for u in slots['users']] == [
'Subscription',
]
assert [u['user_last_name'] for u in slots['users']] == [
'41',
]
@pytest.mark.freeze_time('2022-02-01')
def test_events_timesheet_booked(app, admin_user):
agenda = Agenda.objects.create(label='Events', kind='events')
event_date = make_aware(datetime.datetime(2022, 2, 15, 17, 0))
event1 = Event.objects.create(label='event 1', start_datetime=event_date, places=10, agenda=agenda)
event2 = Event.objects.create(label='event 2', start_datetime=event_date, places=10, agenda=agenda)
event3 = Event.objects.create(label='event 3', start_datetime=event_date, places=10, agenda=agenda)
recurring_event1 = Event.objects.create(
label='recurring 1',
start_datetime=event_date,
places=10,
agenda=agenda,
recurrence_days=[1],
recurrence_end_date=event_date + datetime.timedelta(days=1),
)
recurring_event1.create_all_recurrences()
recurring_event1_occurence = recurring_event1.recurrences.first()
recurring_event2 = Event.objects.create(
label='recurring 2',
start_datetime=event_date,
places=10,
agenda=agenda,
recurrence_days=[1],
recurrence_end_date=event_date + datetime.timedelta(days=1),
)
recurring_event2.create_all_recurrences()
recurring_event2_occurence = recurring_event2.recurrences.first()
Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
date_start=datetime.date(2022, 2, 1),
date_end=datetime.date(2022, 3, 1),
)
Booking.objects.create(
event=event1,
user_external_id='user:1',
user_first_name='User',
user_last_name='42',
)
Booking.objects.create(
event=event2,
user_external_id='user:1',
user_first_name='User',
user_last_name='42',
cancellation_datetime=now(),
)
Booking.objects.create(
event=recurring_event1_occurence,
user_external_id='user:1',
user_first_name='User',
user_last_name='42',
)
Booking.objects.create(
event=recurring_event2_occurence,
user_external_id='user:1',
user_first_name='User',
user_last_name='42',
cancellation_datetime=now(),
)
login(app)
resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
resp.form['date_start'] = '2022-02-01'
resp.form['date_end'] = '2022-02-28'
resp = resp.form.submit()
slots = resp.context['form'].get_slots()
assert slots['events'] == [
event1,
event2,
event3,
recurring_event1,
recurring_event2,
]
assert len(slots['users']) == 1
assert slots['users'][0]['events'] == [
{
'event': event1,
'dates': {datetime.date(2022, 2, 15): True},
},
{
'event': event2,
'dates': {datetime.date(2022, 2, 15): False},
},
{
'event': event3,
'dates': {datetime.date(2022, 2, 15): False},
},
{
'event': recurring_event1,
'dates': {datetime.date(2022, 2, 15): True},
},
{
'event': recurring_event2,
'dates': {datetime.date(2022, 2, 15): False},
},
]