manager: timesheet generation (#61070)
This commit is contained in:
parent
4e71f011b7
commit
0154debfb2
|
@ -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())
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue