Projet

Général

Profil

0002-manager-timesheet-generation-61070.patch

Lauréline Guérin, 18 février 2022 15:32

Télécharger (33,3 ko)

Voir les différences:

Subject: [PATCH 2/5] manager: timesheet generation (#61070)

 chrono/manager/forms.py                       | 122 ++++-
 chrono/manager/static/css/style.scss          |  22 +
 .../chrono/manager_agenda_open_events.html    |   4 +
 .../manager_events_agenda_day_view.html       |   4 +
 .../manager_events_agenda_month_view.html     |   7 +-
 .../chrono/manager_events_timesheet.html      |  54 ++
 chrono/manager/templatetags/__init__.py       |   0
 chrono/manager/templatetags/chrono.py         |  31 ++
 chrono/manager/urls.py                        |   5 +
 chrono/manager/views.py                       |  23 +
 tests/manager/test_all.py                     |   6 +-
 tests/manager/test_event.py                   | 486 ++++++++++++++++++
 12 files changed, 757 insertions(+), 7 deletions(-)
 create mode 100644 chrono/manager/templates/chrono/manager_events_timesheet.html
 create mode 100644 chrono/manager/templatetags/__init__.py
 create mode 100644 chrono/manager/templatetags/chrono.py
chrono/manager/forms.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17

  
18
import copy
18 19
import csv
19 20
import datetime
21
from collections import defaultdict
22
from operator import itemgetter
20 23

  
21 24
import django_filters
25
from dateutil.relativedelta import relativedelta
22 26
from django import forms
23 27
from django.conf import settings
24 28
from django.contrib.auth.models import Group
......
27 31
from django.forms import ValidationError
28 32
from django.utils.encoding import force_text
29 33
from django.utils.six import StringIO
30
from django.utils.timezone import make_aware, now
34
from django.utils.timezone import localtime, make_aware, now
31 35
from django.utils.translation import ugettext_lazy as _
32 36

  
33 37
from chrono.agendas.models import (
......
357 361
            ]
358 362

  
359 363

  
364
class EventsTimesheetForm(forms.Form):
365
    date_start = forms.DateField(
366
        label=_('Start date'),
367
        widget=forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
368
    )
369
    date_end = forms.DateField(
370
        label=_('End date'),
371
        widget=forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
372
    )
373

  
374
    def __init__(self, *args, **kwargs):
375
        self.agenda = kwargs.pop('agenda')
376
        super().__init__(*args, **kwargs)
377

  
378
    def get_slots(self):
379
        min_start = make_aware(
380
            datetime.datetime.combine(self.cleaned_data['date_start'], datetime.time(0, 0))
381
        )
382
        max_start = make_aware(datetime.datetime.combine(self.cleaned_data['date_end'], datetime.time(0, 0)))
383
        max_start = max_start + datetime.timedelta(days=1)
384

  
385
        # fetch all events in this range
386
        events_qs = (
387
            self.agenda.event_set.filter(
388
                recurrence_days__isnull=True,
389
                start_datetime__gte=min_start,
390
                start_datetime__lt=max_start,
391
            )
392
            .select_related('primary_event')
393
            .order_by('start_datetime', 'label')
394
        )
395
        all_events = self.agenda.add_event_recurrences(events_qs, min_start, max_start)
396
        dates = set()
397
        events = []
398
        dates_per_event_id = defaultdict(list)
399
        for event in all_events:
400
            date = localtime(event.start_datetime).date()
401
            dates.add(date)
402
            real_event = event.primary_event or event
403
            if real_event not in events:
404
                events.append(real_event)
405
            dates_per_event_id[real_event.pk].append(date)
406
        dates = sorted(dates)
407

  
408
        event_slots = []
409
        for event in events:
410
            event_slots.append(
411
                {'event': event, 'dates': {date: False for date in dates_per_event_id[event.pk]}}
412
            )
413

  
414
        users = {}
415
        subscriptions = self.agenda.subscriptions.filter(date_start__lt=max_start, date_end__gt=min_start)
416
        for subscription in subscriptions:
417
            if subscription.user_external_id in users:
418
                continue
419
            users[subscription.user_external_id] = {
420
                'user_id': subscription.user_external_id,
421
                'user_first_name': subscription.user_first_name,
422
                'user_last_name': subscription.user_last_name,
423
                'events': copy.deepcopy(event_slots),
424
            }
425

  
426
        booking_qs_kwargs = {}
427
        if not self.agenda.subscriptions.exists():
428
            booking_qs_kwargs = {'cancellation_datetime__isnull': True}
429
        booked_qs = (
430
            Booking.objects.filter(
431
                event__in=all_events,
432
                in_waiting_list=False,
433
                primary_booking__isnull=True,
434
                **booking_qs_kwargs,
435
            )
436
            .exclude(user_external_id='')
437
            .select_related('event')
438
            .order_by('event__start_datetime')
439
        )
440
        for booking in booked_qs:
441
            user_id = booking.user_external_id
442
            if user_id not in users:
443
                users[user_id] = {
444
                    'user_id': user_id,
445
                    'user_first_name': booking.user_first_name,
446
                    'user_last_name': booking.user_last_name,
447
                    'events': copy.deepcopy(event_slots),
448
                }
449
            if booking.cancellation_datetime is not None:
450
                continue
451
            # mark the slot as booked
452
            date = localtime(booking.event.start_datetime).date()
453
            for event in users[user_id]['events']:
454
                if event['event'].pk != (booking.event.primary_event_id or booking.event_id):
455
                    continue
456
                if date in event['dates']:
457
                    event['dates'][date] = True
458
                break
459

  
460
        users = sorted(users.values(), key=itemgetter('user_last_name', 'user_first_name', 'user_id'))
461

  
462
        return {
463
            'dates': dates,
464
            'events': events,
465
            'users': users,
466
        }
467

  
468
    def clean(self):
469
        cleaned_data = super().clean()
470

  
471
        if 'date_start' in cleaned_data and 'date_end' in cleaned_data:
472
            if cleaned_data['date_end'] < cleaned_data['date_start']:
473
                self.add_error('date_end', _('End date must be greater than start date.'))
474
            elif (cleaned_data['date_start'] + relativedelta(months=3)) < cleaned_data['date_end']:
475
                self.add_error('date_end', _('Please select an interval of no more than 3 months.'))
476

  
477
        return cleaned_data
478

  
479

  
360 480
class AgendaResourceForm(forms.Form):
361 481
    resource = forms.ModelChoiceField(label=_('Resource'), queryset=Resource.objects.none())
362 482

  
chrono/manager/static/css/style.scss
481 481
div.ui-dialog div.widget .datetime input {
482 482
	width: auto;
483 483
}
484

  
485
table.timesheet {
486
	width: auto;
487
	th {
488
		padding: 0.5em 0.5ex;
489
		background: none;
490
		border: 1px solid #f3f3f3;
491
		&.date {
492
			width: 20px;
493
			text-align: center;
494
		}
495
	}
496
	td {
497
		padding: 0.5em 0.5ex;
498
		border: 1px solid #f3f3f3;
499
		&.date {
500
			text-align: center;
501
			padding: 0px;
502
			max-width: 2em;
503
		}
504
	}
505
}
chrono/manager/templates/chrono/manager_agenda_open_events.html
9 9
{% endblock %}
10 10

  
11 11
{% block actions %}
12
<a class="extra-actions-menu-opener"></a>
13
<ul class="extra-actions-menu">
14
  <li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
15
</ul>
12 16
{{ block.super }}
13 17
<a href="{% url 'chrono-manager-agenda-month-redirect-view' pk=agenda.pk %}">{% trans 'Month view' %}</a>
14 18
{% endblock %}
chrono/manager/templates/chrono/manager_events_agenda_day_view.html
2 2
{% load i18n %}
3 3

  
4 4
{% block actions %}
5
<a class="extra-actions-menu-opener"></a>
6
<ul class="extra-actions-menu">
7
  <li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
8
</ul>
5 9
{{ block.super }}
6 10
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
7 11
<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>
chrono/manager/templates/chrono/manager_events_agenda_month_view.html
3 3

  
4 4
{% block actions %}
5 5
<a class="extra-actions-menu-opener"></a>
6
{{ block.super }}
7
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
8
<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>
9 6
<ul class="extra-actions-menu">
10 7
  <li><a href="{% url 'chrono-manager-event-cancellation-report-list' pk=agenda.pk %}">{% trans 'Cancellation error reports' %}</a></li>
8
  <li><a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans 'Timesheet' %}</a></li>
11 9
</ul>
10
{{ block.super }}
11
<a href="{% url 'chrono-manager-agenda-open-events-view' pk=agenda.pk %}">{% trans 'Open events' %}</a>
12
<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>
12 13
{% endblock %}
13 14

  
14 15
{% block content %}
chrono/manager/templates/chrono/manager_events_timesheet.html
1
{% extends "chrono/manager_agenda_view.html" %}
2
{% load i18n chrono %}
3

  
4
{% block breadcrumb %}
5
{{ block.super }}
6
<a href="{% url 'chrono-manager-events-timesheet' pk=agenda.pk %}">{% trans "Timesheet" %}</a>
7
{% endblock %}
8

  
9
{% block appbar_actions %}{% endblock %}
10

  
11
{% block content %}
12
<div class="section">
13
  <h3>{% trans "Timesheet configuration" %}</h3>
14
  <div>
15
    <form>
16
      {{ form.as_p }}
17
      <button class="submit-button">{% trans "See timesheet" %}</button>
18
    </form>
19

  
20
    {% if request.GET and form.is_valid %}
21
    <h4>{% blocktrans with start=form.cleaned_data.date_start end=form.cleaned_data.date_end %}Timesheet from {{ start }} to {{ end }}{% endblocktrans %}</h4>
22

  
23
    {% with slots=form.get_slots %}
24
    {% with events_num=slots.events|length %}
25
    <table class="main timesheet">
26
      <thead>
27
        <tr>
28
          <th>{% trans "First name" %}</th>
29
          <th>{% trans "Last name" %}</th>
30
          {% if events_num > 1 %}<th>{% trans "Activity" %}</th>{% endif %}
31
          {% for date in slots.dates %}<th class="date">{{ date|date:"D d/m" }}</th>{% endfor %}
32
        </tr>
33
      </thead>
34
      <tbody>
35
        {% for user in slots.users %}{% for event in user.events %}
36
        <tr>
37
          {% if forloop.first %}
38
          <td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_first_name }}</td>
39
          <td {% if events_num > 1 %}rowspan="{{ events_num }}"{% endif %}>{{ user.user_last_name }}</td>
40
          {% endif %}
41
          {% if events_num > 1 %}<td>{{ event.event }}</td>{% endif %}
42
          {% for date in slots.dates %}
43
          {% with booked=event.dates|get:date %}<td class="date">{% if booked is True %}☐{% elif booked is None %}-{% endif %}</td>{% endwith %}
44
          {% endfor %}
45
        </tr>
46
        {% endfor %}{% endfor %}
47
      </tbody>
48
    </table>
49
    {% endwith %}
50
    {% endwith %}
51
    {% endif %}
52
  </div>
53
</div>
54
{% endblock %}
chrono/manager/templatetags/chrono.py
1
# chrono - agendas system
2
# Copyright (C) 2022  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17

  
18
from django import template
19

  
20
register = template.Library()
21

  
22

  
23
@register.filter(name='get')
24
def get(obj, key):
25
    try:
26
        return obj.get(key)
27
    except AttributeError:
28
        try:
29
            return obj[key]
30
        except (IndexError, KeyError, TypeError):
31
            return None
chrono/manager/urls.py
191 191
        views.agenda_reminder_preview,
192 192
        name='chrono-manager-agenda-reminder-preview',
193 193
    ),
194
    url(
195
        r'^agendas/(?P<pk>\d+)/events/timesheet$',
196
        views.events_timesheet,
197
        name='chrono-manager-events-timesheet',
198
    ),
194 199
    url(
195 200
        r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/$',
196 201
        views.event_view,
chrono/manager/views.py
97 97
    DeskForm,
98 98
    EventCancelForm,
99 99
    EventForm,
100
    EventsTimesheetForm,
100 101
    ExceptionsImportForm,
101 102
    ImportEventsForm,
102 103
    MeetingTypeForm,
......
1953 1954
agenda_reminder_preview = AgendaReminderPreviewView.as_view()
1954 1955

  
1955 1956

  
1957
class EventsTimesheetView(ViewableAgendaMixin, DetailView):
1958
    model = Agenda
1959
    template_name = 'chrono/manager_events_timesheet.html'
1960

  
1961
    def set_agenda(self, **kwargs):
1962
        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'), kind='events')
1963

  
1964
    def get_object(self, **kwargs):
1965
        return self.agenda
1966

  
1967
    def get_context_data(self, **kwargs):
1968
        context = super().get_context_data(**kwargs)
1969
        form = EventsTimesheetForm(agenda=self.agenda, data=self.request.GET or None)
1970
        if self.request.GET:
1971
            form.is_valid()
1972
        context['form'] = form
1973
        return context
1974

  
1975

  
1976
events_timesheet = EventsTimesheetView.as_view()
1977

  
1978

  
1956 1979
class EventDetailView(ViewableAgendaMixin, DetailView):
1957 1980
    model = Event
1958 1981
    pk_url_kwarg = 'event_pk'
tests/manager/test_all.py
1448 1448
    )
1449 1449
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 11))
1450 1450
    assert len(resp.pyquery.find('.event-info')) == 4
1451
    assert 'abc' in resp.pyquery.find('li')[4].text_content()
1452
    assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[5].text_content()
1453
    assert 'xyz' in resp.pyquery.find('li')[6].text_content()
1451
    assert 'abc' in resp.pyquery.find('li')[5].text_content()
1452
    assert 'Exception: 11/10/2020' in resp.pyquery.find('li')[6].text_content()
1453
    assert 'xyz' in resp.pyquery.find('li')[7].text_content()
1454 1454

  
1455 1455
    # 12/2020 has 5 Wednesday
1456 1456
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2020, 12))
tests/manager/test_event.py
2007 2007
    user_bookings = resp.pyquery.find('td.booking-username.waiting')
2008 2008
    assert len(user_bookings) == 1
2009 2009
    assert user_bookings[0].text == 'Jane Doe (2 places)'
2010

  
2011

  
2012
def test_events_timesheet_wrong_kind(app, admin_user):
2013
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2014

  
2015
    app = login(app)
2016
    app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404)
2017
    agenda.kind = 'virtual'
2018
    agenda.save()
2019
    app.get('/manage/agendas/%s/events/timesheet' % agenda.id, status=404)
2020

  
2021

  
2022
def test_events_timesheet_form(app, admin_user):
2023
    agenda = Agenda.objects.create(label='Events', kind='events')
2024

  
2025
    login(app)
2026
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2027
    resp.form['date_start'] = '2022-01-01'
2028
    resp.form['date_end'] = '2021-12-31'
2029
    resp = resp.form.submit()
2030
    assert resp.context['form'].errors['date_end'] == ['End date must be greater than start date.']
2031

  
2032
    resp.form['date_end'] = '2022-04-02'
2033
    resp = resp.form.submit()
2034
    assert resp.context['form'].errors['date_end'] == ['Please select an interval of no more than 3 months.']
2035

  
2036
    resp.form['date_end'] = '2022-04-01'
2037
    resp = resp.form.submit()
2038
    assert resp.context['form'].errors == {}
2039

  
2040

  
2041
@pytest.mark.freeze_time('2022-02-15')
2042
def test_events_timesheet_slots(app, admin_user):
2043
    start, end = (
2044
        now() - datetime.timedelta(days=15),
2045
        now() + datetime.timedelta(days=14),
2046
    )  # 2022-02-31, 2022-03-01
2047
    agenda = Agenda.objects.create(label='Events', kind='events')
2048
    Event.objects.create(label='event 1', start_datetime=start, places=10, agenda=agenda)
2049
    event2 = Event.objects.create(
2050
        label='event 2', start_datetime=start + datetime.timedelta(days=1), places=10, agenda=agenda
2051
    )
2052
    event3 = Event.objects.create(label='event 3', start_datetime=now(), places=10, agenda=agenda)
2053
    Event.objects.create(
2054
        label='event cancelled',
2055
        start_datetime=now() + datetime.timedelta(days=4),
2056
        places=10,
2057
        agenda=agenda,
2058
        cancelled=True,
2059
    )
2060
    event4 = Event.objects.create(
2061
        label='event 4', start_datetime=end - datetime.timedelta(days=1), places=10, agenda=agenda
2062
    )
2063
    Event.objects.create(label='event 5', start_datetime=end, places=10, agenda=agenda)
2064
    Subscription.objects.create(
2065
        agenda=agenda,
2066
        user_external_id='user:1',
2067
        user_first_name='Subscription',
2068
        user_last_name='42',
2069
        date_start=start,
2070
        date_end=end + datetime.timedelta(days=1),
2071
    )
2072
    recurring_event1 = Event.objects.create(
2073
        label='recurring 1',
2074
        start_datetime=start,
2075
        places=10,
2076
        agenda=agenda,
2077
        recurrence_days=[0, 1],
2078
        recurrence_end_date=end,
2079
    )
2080
    recurring_event1.create_all_recurrences()
2081
    recurring_event2 = Event.objects.create(
2082
        label='recurring 2',
2083
        start_datetime=start,
2084
        places=10,
2085
        agenda=agenda,
2086
        recurrence_days=[1, 2],
2087
        recurrence_end_date=end,
2088
    )
2089
    recurring_event2.create_all_recurrences()
2090

  
2091
    login(app)
2092
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2093
    resp.form['date_start'] = '2022-02-01'
2094
    resp.form['date_end'] = '2022-02-28'
2095
    with CaptureQueriesContext(connection) as ctx:
2096
        resp = resp.form.submit()
2097
        assert len(ctx.captured_queries) == 9
2098

  
2099
    slots = resp.context['form'].get_slots()
2100
    assert slots['dates'] == [
2101
        datetime.date(2022, 2, 1),
2102
        datetime.date(2022, 2, 2),
2103
        datetime.date(2022, 2, 7),
2104
        datetime.date(2022, 2, 8),
2105
        datetime.date(2022, 2, 9),
2106
        datetime.date(2022, 2, 14),
2107
        datetime.date(2022, 2, 15),
2108
        datetime.date(2022, 2, 16),
2109
        datetime.date(2022, 2, 21),
2110
        datetime.date(2022, 2, 22),
2111
        datetime.date(2022, 2, 23),
2112
        datetime.date(2022, 2, 28),
2113
    ]
2114
    assert slots['events'] == [
2115
        event2,
2116
        recurring_event1,
2117
        recurring_event2,
2118
        event3,
2119
        event4,
2120
    ]
2121
    assert slots['users'] == [
2122
        {
2123
            'user_id': 'user:1',
2124
            'user_first_name': 'Subscription',
2125
            'user_last_name': '42',
2126
            'events': [
2127
                {
2128
                    'event': event2,
2129
                    'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 1)},
2130
                },
2131
                {
2132
                    'event': recurring_event1,
2133
                    'dates': {date: False for date in slots['dates'] if date.weekday() in [0, 1]},
2134
                },
2135
                {
2136
                    'event': recurring_event2,
2137
                    'dates': {date: False for date in slots['dates'] if date.weekday() in [1, 2]},
2138
                },
2139
                {
2140
                    'event': event3,
2141
                    'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 15)},
2142
                },
2143
                {
2144
                    'event': event4,
2145
                    'dates': {date: False for date in slots['dates'] if date == datetime.date(2022, 2, 28)},
2146
                },
2147
            ],
2148
        },
2149
    ]
2150

  
2151

  
2152
@pytest.mark.freeze_time('2022-02-15')
2153
def test_events_timesheet_subscription_limits(app, admin_user):
2154
    agenda = Agenda.objects.create(label='Events', kind='events')
2155
    event1 = Event.objects.create(
2156
        start_datetime=make_aware(datetime.datetime(2022, 2, 1, 17, 0)), places=10, agenda=agenda
2157
    )
2158
    event2 = Event.objects.create(
2159
        start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
2160
    )
2161
    event3 = Event.objects.create(
2162
        start_datetime=make_aware(datetime.datetime(2022, 2, 28, 17, 0)), places=10, agenda=agenda
2163
    )
2164

  
2165
    dates = [
2166
        ('2022-01-31', '2022-02-01'),
2167
        ('2022-02-01', '2022-02-02'),
2168
        ('2022-02-01', '2022-02-15'),
2169
        ('2022-02-01', '2022-02-16'),
2170
        ('2022-02-15', '2022-02-28'),
2171
        ('2022-02-15', '2022-03-01'),
2172
        ('2022-02-16', '2022-03-01'),
2173
        ('2022-02-01', '2022-03-01'),
2174
        ('2022-02-28', '2022-03-01'),
2175
        ('2022-03-01', '2022-03-02'),
2176
    ]
2177

  
2178
    for start, end in dates:
2179
        Subscription.objects.create(
2180
            agenda=agenda,
2181
            user_external_id='user:%s-%s' % (start, end),
2182
            user_first_name='Subscription',
2183
            user_last_name='%s - %s' % (start, end),
2184
            date_start=datetime.datetime.strptime(start, '%Y-%m-%d'),
2185
            date_end=datetime.datetime.strptime(end, '%Y-%m-%d'),
2186
        )
2187

  
2188
    login(app)
2189
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2190
    resp.form['date_start'] = '2022-02-01'
2191
    resp.form['date_end'] = '2022-02-28'
2192
    resp = resp.form.submit()
2193

  
2194
    slots = resp.context['form'].get_slots()
2195
    assert slots['dates'] == [
2196
        datetime.date(2022, 2, 1),
2197
        datetime.date(2022, 2, 15),
2198
        datetime.date(2022, 2, 28),
2199
    ]
2200

  
2201
    assert slots['events'] == [
2202
        event1,
2203
        event2,
2204
        event3,
2205
    ]
2206
    assert len(slots['users']) == 8
2207
    assert slots['users'][0]['user_id'] == 'user:2022-02-01-2022-02-02'
2208
    assert slots['users'][1]['user_id'] == 'user:2022-02-01-2022-02-15'
2209
    assert slots['users'][2]['user_id'] == 'user:2022-02-01-2022-02-16'
2210
    assert slots['users'][3]['user_id'] == 'user:2022-02-01-2022-03-01'
2211
    assert slots['users'][4]['user_id'] == 'user:2022-02-15-2022-02-28'
2212
    assert slots['users'][5]['user_id'] == 'user:2022-02-15-2022-03-01'
2213
    assert slots['users'][6]['user_id'] == 'user:2022-02-16-2022-03-01'
2214
    assert slots['users'][7]['user_id'] == 'user:2022-02-28-2022-03-01'
2215

  
2216

  
2217
def test_events_timesheet_users(app, admin_user):
2218
    agenda = Agenda.objects.create(label='Events', kind='events')
2219
    event = Event.objects.create(
2220
        start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
2221
    )
2222

  
2223
    booking1 = Booking.objects.create(
2224
        event=event, user_external_id='user:1', user_first_name='User', user_last_name='42'
2225
    )
2226
    Booking.objects.create(
2227
        event=event, user_external_id='user:2', user_first_name='User', user_last_name='01'
2228
    )
2229
    Booking.objects.create(
2230
        event=event, user_external_id='user:3', user_first_name='User', user_last_name='17'
2231
    )
2232
    Booking.objects.create(
2233
        event=event, user_external_id='user:4', user_first_name='User', user_last_name='35'
2234
    )
2235
    Booking.objects.create(
2236
        event=event, user_external_id='user:5', user_first_name='User', user_last_name='05'
2237
    )
2238
    booking6 = Booking.objects.create(
2239
        event=event, user_external_id='user:6', user_first_name='User', user_last_name='12 Cancelled'
2240
    )
2241
    booking6.cancel()
2242
    Booking.objects.create(
2243
        event=event,
2244
        user_external_id='user:7',
2245
        user_first_name='User',
2246
        user_last_name='Waiting',
2247
        in_waiting_list=True,
2248
    )
2249
    booking8 = Booking.objects.create(
2250
        event=event,
2251
        user_external_id='user:8',
2252
        user_first_name='User',
2253
        user_last_name='Waiting and Cancelled',
2254
        in_waiting_list=True,
2255
    )
2256
    booking8.cancel()
2257
    Booking.objects.create(
2258
        event=event,
2259
        user_external_id='user:1',
2260
        user_first_name='User',
2261
        user_last_name='Secondary',
2262
        primary_booking=booking1,
2263
    )
2264

  
2265
    login(app)
2266
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2267
    resp.form['date_start'] = '2022-02-01'
2268
    resp.form['date_end'] = '2022-02-28'
2269
    resp = resp.form.submit()
2270
    slots = resp.context['form'].get_slots()
2271
    assert [u['user_id'] for u in slots['users']] == [
2272
        'user:2',
2273
        'user:5',
2274
        'user:3',
2275
        'user:4',
2276
        'user:1',
2277
    ]
2278

  
2279
    start = datetime.date(2022, 2, 1)
2280
    end = datetime.date(2022, 3, 1)
2281
    Subscription.objects.create(
2282
        agenda=agenda,
2283
        user_external_id='user:1',
2284
        user_first_name='Subscription',
2285
        user_last_name='42',
2286
        date_start=start,
2287
        date_end=end,
2288
    )
2289
    Subscription.objects.create(
2290
        agenda=agenda,
2291
        user_external_id='user:9',
2292
        user_first_name='Subscription',
2293
        user_last_name='43',
2294
        date_start=start,
2295
        date_end=end,
2296
    )
2297
    Subscription.objects.create(
2298
        agenda=agenda,
2299
        user_external_id='user:10',
2300
        user_first_name='Subscription',
2301
        user_last_name='14',
2302
        date_start=start,
2303
        date_end=end,
2304
    )
2305
    Subscription.objects.create(
2306
        agenda=agenda,
2307
        user_external_id='user:7',
2308
        user_first_name='Subscription',
2309
        user_last_name='Waiting',
2310
        date_start=start,
2311
        date_end=end,
2312
    )
2313
    Subscription.objects.create(
2314
        agenda=agenda,
2315
        user_external_id='user:42',
2316
        user_first_name='Subscription',
2317
        user_last_name='Too soon',
2318
        date_start=start - datetime.timedelta(days=1),
2319
        date_end=start,
2320
    )
2321
    Subscription.objects.create(
2322
        agenda=agenda,
2323
        user_external_id='user:43',
2324
        user_first_name='Subscription',
2325
        user_last_name='Too late',
2326
        date_start=end + datetime.timedelta(days=1),
2327
        date_end=end + datetime.timedelta(days=2),
2328
    )
2329

  
2330
    resp = resp.form.submit()
2331
    slots = resp.context['form'].get_slots()
2332
    assert [u['user_id'] for u in slots['users']] == [
2333
        'user:2',
2334
        'user:5',
2335
        'user:6',
2336
        'user:10',
2337
        'user:3',
2338
        'user:4',
2339
        'user:1',
2340
        'user:9',
2341
        'user:7',
2342
    ]
2343

  
2344

  
2345
def test_events_timesheet_user_ids(app, admin_user):
2346
    agenda = Agenda.objects.create(label='Events', kind='events')
2347
    event = Event.objects.create(
2348
        start_datetime=make_aware(datetime.datetime(2022, 2, 15, 17, 0)), places=10, agenda=agenda
2349
    )
2350
    booking = Booking.objects.create(event=event, user_first_name='User', user_last_name='42')
2351

  
2352
    login(app)
2353
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2354
    resp.form['date_start'] = '2022-02-01'
2355
    resp.form['date_end'] = '2022-02-28'
2356
    resp = resp.form.submit()
2357
    slots = resp.context['form'].get_slots()
2358
    # no user_id found
2359
    assert [u['user_id'] for u in slots['users']] == []
2360
    assert [u['user_first_name'] for u in slots['users']] == []
2361
    assert [u['user_last_name'] for u in slots['users']] == []
2362

  
2363
    booking.user_external_id = 'user:1'
2364
    booking.save()
2365

  
2366
    resp = resp.form.submit()
2367
    slots = resp.context['form'].get_slots()
2368
    assert [u['user_id'] for u in slots['users']] == [
2369
        'user:1',
2370
    ]
2371
    assert [u['user_first_name'] for u in slots['users']] == ['User']
2372
    assert [u['user_last_name'] for u in slots['users']] == ['42']
2373

  
2374
    Subscription.objects.create(
2375
        agenda=agenda,
2376
        user_external_id='user:1',
2377
        user_first_name='Subscription',
2378
        user_last_name='41',
2379
        date_start=datetime.date(2022, 2, 1),
2380
        date_end=datetime.date(2022, 3, 1),
2381
    )
2382

  
2383
    resp = resp.form.submit()
2384
    slots = resp.context['form'].get_slots()
2385
    assert [u['user_id'] for u in slots['users']] == [
2386
        'user:1',
2387
    ]
2388
    assert [u['user_first_name'] for u in slots['users']] == [
2389
        'Subscription',
2390
    ]
2391
    assert [u['user_last_name'] for u in slots['users']] == [
2392
        '41',
2393
    ]
2394

  
2395

  
2396
@pytest.mark.freeze_time('2022-02-01')
2397
def test_events_timesheet_booked(app, admin_user):
2398
    agenda = Agenda.objects.create(label='Events', kind='events')
2399
    event_date = make_aware(datetime.datetime(2022, 2, 15, 17, 0))
2400
    event1 = Event.objects.create(label='event 1', start_datetime=event_date, places=10, agenda=agenda)
2401
    event2 = Event.objects.create(label='event 2', start_datetime=event_date, places=10, agenda=agenda)
2402
    event3 = Event.objects.create(label='event 3', start_datetime=event_date, places=10, agenda=agenda)
2403
    recurring_event1 = Event.objects.create(
2404
        label='recurring 1',
2405
        start_datetime=event_date,
2406
        places=10,
2407
        agenda=agenda,
2408
        recurrence_days=[1],
2409
        recurrence_end_date=event_date + datetime.timedelta(days=1),
2410
    )
2411
    recurring_event1.create_all_recurrences()
2412
    recurring_event1_occurence = recurring_event1.recurrences.first()
2413
    recurring_event2 = Event.objects.create(
2414
        label='recurring 2',
2415
        start_datetime=event_date,
2416
        places=10,
2417
        agenda=agenda,
2418
        recurrence_days=[1],
2419
        recurrence_end_date=event_date + datetime.timedelta(days=1),
2420
    )
2421
    recurring_event2.create_all_recurrences()
2422
    recurring_event2_occurence = recurring_event2.recurrences.first()
2423

  
2424
    Subscription.objects.create(
2425
        agenda=agenda,
2426
        user_external_id='user:1',
2427
        user_first_name='Subscription',
2428
        user_last_name='42',
2429
        date_start=datetime.date(2022, 2, 1),
2430
        date_end=datetime.date(2022, 3, 1),
2431
    )
2432
    Booking.objects.create(
2433
        event=event1,
2434
        user_external_id='user:1',
2435
        user_first_name='User',
2436
        user_last_name='42',
2437
    )
2438
    Booking.objects.create(
2439
        event=event2,
2440
        user_external_id='user:1',
2441
        user_first_name='User',
2442
        user_last_name='42',
2443
        cancellation_datetime=now(),
2444
    )
2445
    Booking.objects.create(
2446
        event=recurring_event1_occurence,
2447
        user_external_id='user:1',
2448
        user_first_name='User',
2449
        user_last_name='42',
2450
    )
2451
    Booking.objects.create(
2452
        event=recurring_event2_occurence,
2453
        user_external_id='user:1',
2454
        user_first_name='User',
2455
        user_last_name='42',
2456
        cancellation_datetime=now(),
2457
    )
2458

  
2459
    login(app)
2460
    resp = app.get('/manage/agendas/%s/events/timesheet' % agenda.pk)
2461
    resp.form['date_start'] = '2022-02-01'
2462
    resp.form['date_end'] = '2022-02-28'
2463
    resp = resp.form.submit()
2464
    slots = resp.context['form'].get_slots()
2465

  
2466
    assert slots['events'] == [
2467
        event1,
2468
        event2,
2469
        event3,
2470
        recurring_event1,
2471
        recurring_event2,
2472
    ]
2473
    assert len(slots['users']) == 1
2474
    assert slots['users'][0]['events'] == [
2475
        {
2476
            'event': event1,
2477
            'dates': {datetime.date(2022, 2, 15): True},
2478
        },
2479
        {
2480
            'event': event2,
2481
            'dates': {datetime.date(2022, 2, 15): False},
2482
        },
2483
        {
2484
            'event': event3,
2485
            'dates': {datetime.date(2022, 2, 15): False},
2486
        },
2487
        {
2488
            'event': recurring_event1,
2489
            'dates': {datetime.date(2022, 2, 15): True},
2490
        },
2491
        {
2492
            'event': recurring_event2,
2493
            'dates': {datetime.date(2022, 2, 15): False},
2494
        },
2495
    ]
2010
-