Facturation: avoir un flag sur Event pour noter que l'événement est verrouillé (#75416) #56

Merged
lguerin merged 2 commits from wip/75416-event-lock-check into main 2023-04-03 16:27:27 +02:00
6 changed files with 358 additions and 1 deletions
Showing only changes of commit a1302e3389 - Show all commits

View File

@ -0,0 +1,15 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agendas', '0149_booking_extra_user_block'),
]
operations = [
migrations.AddField(
model_name='event',
name='check_locked',
field=models.BooleanField(default=False),
),
]

View File

@ -1521,6 +1521,7 @@ class Event(models.Model):
cancelled = models.BooleanField(default=False)
cancellation_scheduled = models.BooleanField(default=False)
checked = models.BooleanField(default=False)
check_locked = models.BooleanField(default=False)
meeting_type = models.ForeignKey(MeetingType, null=True, on_delete=models.CASCADE)
desk = models.ForeignKey('Desk', null=True, on_delete=models.CASCADE)
resources = models.ManyToManyField('Resource')

View File

@ -157,6 +157,18 @@ class MultipleAgendasEventsCheckStatusSerializer(AgendaSlugsMixin, DateRangeMixi
return get_objects_from_slugs(value, qs=self.get_agenda_qs())
class MultipleAgendasEventsCheckLockSerializer(AgendaSlugsMixin, DateRangeMixin, serializers.Serializer):
check_locked = serializers.BooleanField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in ['agendas', 'date_start', 'date_end', 'check_locked']:
self.fields[field].required = True
def validate_agendas(self, value):
return get_objects_from_slugs(value, qs=self.get_agenda_qs())
class RecurringFillslotsSerializer(MultipleAgendasEventsFillSlotsSerializer):
include_booked_events_detail = serializers.BooleanField(default=False)
check_overlaps = CommaSeparatedStringField(
@ -437,8 +449,10 @@ class EventSerializer(serializers.ModelSerializer):
'url',
'primary_event',
'agenda',
'checked',
'check_locked',
]
read_only_fields = ['slug']
read_only_fields = ['slug', 'checked', 'check_locked']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -38,6 +38,11 @@ urlpatterns = [
views.agendas_events_check_status,
name='api-agendas-events-check-status',
),
path(
'agendas/events/check-lock/',
views.agendas_events_check_lock,
name='api-agendas-events-check-lock',
),
re_path(r'^agenda/(?P<agenda_identifier>[\w-]+)/$', views.agenda),
re_path(
r'^agenda/(?P<agenda_identifier>[\w-]+)/datetimes/$', views.datetimes, name='api-agenda-datetimes'

View File

@ -502,6 +502,7 @@ def get_event_detail(
'url': event.url,
'duration': event.duration,
'checked': event.checked,
'check_locked': event.check_locked,
}
for key, value in event.get_custom_fields().items():
details['custom_field_%s' % key] = value
@ -2296,6 +2297,35 @@ class MultipleAgendasEventsCheckStatus(APIView):
agendas_events_check_status = MultipleAgendasEventsCheckStatus.as_view()
class MultipleAgendasEventsCheckLock(APIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.MultipleAgendasEventsCheckLockSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if not serializer.is_valid():
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors, err=1)
agendas = serializer.validated_data['agendas']
date_start = serializer.validated_data['date_start']
date_end = serializer.validated_data['date_end']
check_locked = serializer.validated_data['check_locked']
events = Event.objects.filter(
agenda__in=agendas,
recurrence_days__isnull=True,
cancelled=False,
start_datetime__gte=date_start,
start_datetime__lt=date_end,
)
events.update(check_locked=check_locked)
return Response({'err': 0})
agendas_events_check_lock = MultipleAgendasEventsCheckLock.as_view()
class SubscriptionFilter(filters.FilterSet):
date_start = filters.DateFilter(method='do_nothing')
date_end = filters.DateFilter(method='do_nothing')

View File

@ -54,6 +54,7 @@ def test_status(app, user):
'disabled': False,
'duration': None,
'checked': False,
'check_locked': False,
'api': {
'bookings_url': 'http://testserver/api/agenda/foo-bar/bookings/event-slug/',
'fillslot_url': 'http://testserver/api/agenda/foo-bar/fillslot/event-slug/',
@ -92,6 +93,7 @@ def test_status(app, user):
'disabled': False,
'duration': None,
'checked': False,
'check_locked': False,
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
@ -880,6 +882,7 @@ def test_events(app, user):
places=10,
agenda=agenda2,
checked=True,
check_locked=True,
)
# cancelled event, not returned
Event.objects.create(
@ -939,6 +942,8 @@ def test_events(app, user):
assert resp.json['data'] == [
{
'agenda': 'bar',
'check_locked': True,
'checked': True,
'description': None,
'duration': None,
'label': 'Event Label',
@ -956,6 +961,8 @@ def test_events(app, user):
},
{
'agenda': 'foo',
'check_locked': False,
'checked': False,
'description': None,
'duration': None,
'label': 'Recurring Event Label',
@ -973,6 +980,8 @@ def test_events(app, user):
},
{
'agenda': 'foo',
'check_locked': False,
'checked': False,
'description': None,
'duration': None,
'label': 'Recurring Event Label',
@ -1301,6 +1310,7 @@ def test_events_check_status_events(app, user):
places=10,
agenda=agenda,
checked=True,
check_locked=True,
)
# not checked event
notchecked_event = Event.objects.create(
@ -1366,6 +1376,8 @@ def test_events_check_status_events(app, user):
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': None,
'check_locked': False,
'checked': False,
'custom_field_bool': None,
'custom_field_text': '',
'custom_field_textarea': '',
@ -1390,6 +1402,8 @@ def test_events_check_status_events(app, user):
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': None,
'check_locked': True,
'checked': True,
'custom_field_bool': None,
'custom_field_text': '',
'custom_field_textarea': '',
@ -1429,6 +1443,8 @@ def test_events_check_status_events(app, user):
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': recurring_event.slug,
'check_locked': False,
'checked': True,
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
@ -1595,3 +1611,279 @@ def test_events_check_status_subscription_filter(app, user, freezer, event_date,
}
resp = app.get(url, params=params)
assert len(resp.json['data']) == int(expected)
def test_events_check_lock_params(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
# missing check_locked
lguerin marked this conversation as resolved Outdated

c'est plutôt missing check_locked, non ?

c'est plutôt missing check_locked, non ?

yes

yes
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={'agendas': 'foo', 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['check_locked'] == ['This field is required.']
# missing agendas
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={'check_locked': True, 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['This field is required.']
# unknown agenda
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={
'check_locked': True,
'agendas': 'foo, bar',

Je comprends que c'est parce qu'on partage du code (AgendaSlugsMixin) avec des bouts appelés depuis w.c.s. mais je note que peut-être là-dedans un jour on pourrait imaginer une évolution pour également accepter une liste de chaines.

Je comprends que c'est parce qu'on partage du code (AgendaSlugsMixin) avec des bouts appelés depuis w.c.s. mais je note que peut-être là-dedans un jour on pourrait imaginer une évolution pour également accepter une liste de chaines.
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar, foo']
Agenda.objects.create(label='Foo')
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={
'check_locked': True,
'agendas': 'foo, bar',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
# wrong kind
wrong_agenda = Agenda.objects.create(label='Bar')
for kind in ['meetings', 'virtual']:
wrong_agenda.kind = kind
wrong_agenda.save()
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={
'check_locked': True,
'agendas': 'foo, bar',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
# missing date_start
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={'check_locked': True, 'agendas': 'foo', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_start'] == ['This field is required.']
# missing date_end
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={'check_locked': True, 'agendas': 'foo', 'date_start': '2022-05-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_end'] == ['This field is required.']
# bad date format
resp = app.post_json(
'/api/agendas/events/check-lock/',
params={'check_locked': True, 'agendas': 'foo', 'date_start': 'wrong', 'date_end': 'wrong'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert 'wrong format' in resp.json['errors']['date_start'][0]
assert 'wrong format' in resp.json['errors']['date_end'][0]
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_lock(app, user):
agenda = Agenda.objects.create(label='Foo')
event = Event.objects.create(
slug='event-slug',
label='Event Label',
start_datetime=now(),
places=10,
agenda=agenda,
checked=True,
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-lock/'
params = {
'check_locked': True,
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
resp = app.post_json(url, params=params)
assert resp.json['err'] == 0
event.refresh_from_db()
assert event.check_locked is True
params['check_locked'] = False
resp = app.post_json(url, params=params)
assert resp.json['err'] == 0
event.refresh_from_db()
assert event.check_locked is False
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_lock_events(app, user):
events_type = EventsType.objects.create(
label='Foo',
)
agenda = Agenda.objects.create(label='Foo', events_type=events_type)
start_datetime = now()
# recurring event
recurring_event = Event.objects.create(
slug='recurring-event-slug',
label='Recurring Event Label',
start_datetime=start_datetime,
recurrence_days=[start_datetime.weekday()],
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
places=10,
agenda=agenda,
)
recurring_event.create_all_recurrences()
first_event = recurring_event.recurrences.get()
event = Event.objects.create(
slug='event-slug',
label='Event Label',
start_datetime=start_datetime - datetime.timedelta(days=1),
places=10,
agenda=agenda,
)
# cancelled event, not updated
cancelled_event = Event.objects.create(
slug='cancelled',
label='Cancelled',
start_datetime=start_datetime,
places=10,
agenda=agenda,
cancelled=True,
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-lock/'
params = {
'check_locked': True,
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
resp = app.post_json(url, params=params)
assert resp.json['err'] == 0
recurring_event.refresh_from_db()
assert recurring_event.check_locked is False
first_event.refresh_from_db()
assert first_event.check_locked is True
event.refresh_from_db()
assert event.check_locked is True
cancelled_event.refresh_from_db()
assert cancelled_event.check_locked is False
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_lock_agendas_filter(app, user):
agenda1 = Agenda.objects.create(label='Foo')
agenda2 = Agenda.objects.create(label='Foo 2')
event1 = Event.objects.create(
slug='event-1',
label='Event 1',
start_datetime=now(),
places=10,
agenda=agenda1,
)
event2 = Event.objects.create(
slug='event-2',
label='Event 2',
start_datetime=now(),
places=10,
agenda=agenda2,
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-lock/'
params = {
'check_locked': True,
'agendas': 'foo, foo-2',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
app.post_json(url, params=params)
event1.refresh_from_db()
assert event1.check_locked is True
event2.refresh_from_db()
assert event2.check_locked is True
params['agendas'] = 'foo'
params['check_locked'] = False
app.post_json(url, params=params)
event1.refresh_from_db()
assert event1.check_locked is False
event2.refresh_from_db()
assert event2.check_locked is True
params['agendas'] = 'foo-2'
app.post_json(url, params=params)
event1.refresh_from_db()
assert event1.check_locked is False
event2.refresh_from_db()
assert event2.check_locked is False
@pytest.mark.parametrize(
'event_date, expected',
[
# just before first day
((2022, 4, 30, 12, 0), False),
# first day
((2022, 5, 1, 12, 0), True),
# last day
((2022, 5, 31, 12, 0), True),
# just after last day
((2022, 6, 1, 12, 0), False),
],
)
def test_events_check_lock_date_filter(app, user, event_date, expected):
agenda = Agenda.objects.create(label='Foo')
event = Event.objects.create(
slug='event',
label='Event',
start_datetime=make_aware(datetime.datetime(*event_date)),
places=10,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-lock/'
params = {
'check_locked': True,
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
app.post_json(url, params=params)
event.refresh_from_db()
assert event.check_locked == expected