api: configure shared custody agenda on creation (#64423)
This commit is contained in:
parent
513980d5b1
commit
3fab3b136c
|
@ -1,11 +1,27 @@
|
|||
import collections
|
||||
import datetime
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import models, transaction
|
||||
from django.db.models import ExpressionWrapper, F
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from chrono.agendas.models import Agenda, Booking, Category, CheckType, Event, EventsType, Subscription
|
||||
from chrono.agendas.models import (
|
||||
Agenda,
|
||||
Booking,
|
||||
Category,
|
||||
CheckType,
|
||||
Event,
|
||||
EventsType,
|
||||
Person,
|
||||
SharedCustodyAgenda,
|
||||
SharedCustodyHolidayRule,
|
||||
SharedCustodyRule,
|
||||
Subscription,
|
||||
TimePeriodExceptionGroup,
|
||||
)
|
||||
|
||||
|
||||
def get_objects_from_slugs(slugs, qs):
|
||||
|
@ -488,16 +504,144 @@ class SubscriptionSerializer(serializers.ModelSerializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class PersonSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Person
|
||||
fields = ['user_external_id', 'first_name', 'last_name']
|
||||
extra_kwargs = {'user_external_id': {'validators': []}}
|
||||
|
||||
|
||||
class SharedCustodyAgendaSerializer(serializers.Serializer):
|
||||
first_guardian_first_name = serializers.CharField(max_length=250)
|
||||
first_guardian_last_name = serializers.CharField(max_length=250)
|
||||
first_guardian_id = serializers.CharField(max_length=250)
|
||||
second_guardian_first_name = serializers.CharField(max_length=250)
|
||||
second_guardian_last_name = serializers.CharField(max_length=250)
|
||||
second_guardian_id = serializers.CharField(max_length=250)
|
||||
period_mirrors = {
|
||||
'even': 'odd',
|
||||
'odd': 'even',
|
||||
'first-half': 'second-half',
|
||||
'second-half': 'first-half',
|
||||
'first-and-third-quarters': 'second-and-fourth-quarters',
|
||||
'second-and-fourth-quarters': 'first-and-third-quarters',
|
||||
}
|
||||
|
||||
guardian_first_name = serializers.CharField(max_length=250)
|
||||
guardian_last_name = serializers.CharField(max_length=250)
|
||||
guardian_id = serializers.CharField(max_length=250)
|
||||
other_guardian_first_name = serializers.CharField(max_length=250)
|
||||
other_guardian_last_name = serializers.CharField(max_length=250)
|
||||
other_guardian_id = serializers.CharField(max_length=250)
|
||||
children = PersonSerializer(many=True)
|
||||
weeks = serializers.ChoiceField(required=False, choices=['', 'even', 'odd'])
|
||||
|
||||
class SharedCustodyChildSerializer(serializers.Serializer):
|
||||
first_name = serializers.CharField(max_length=250)
|
||||
last_name = serializers.CharField(max_length=250)
|
||||
user_external_id = serializers.CharField(max_length=250)
|
||||
settings_url = serializers.SerializerMethodField()
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs['holidays'] = collections.defaultdict(dict)
|
||||
for key, value in self.initial_data.items():
|
||||
if key in attrs or ':' not in key:
|
||||
continue
|
||||
|
||||
holiday_slug, field = key.split(':')
|
||||
if field not in ('periodicity', 'years'):
|
||||
raise ValidationError(
|
||||
_('Unknown parameter for holiday %(holiday)s: %(param)s')
|
||||
% {'holiday': holiday_slug, 'param': field}
|
||||
)
|
||||
attrs['holidays'][holiday_slug][field] = value
|
||||
|
||||
for holiday_slug in attrs['holidays'].copy():
|
||||
try:
|
||||
holiday = TimePeriodExceptionGroup.objects.get(slug=holiday_slug)
|
||||
except TimePeriodExceptionGroup.DoesNotExist:
|
||||
raise ValidationError(_('Unknown holiday: %s') % holiday_slug)
|
||||
|
||||
field_values = attrs['holidays'].pop(holiday_slug)
|
||||
if 'periodicity' not in field_values:
|
||||
raise ValidationError(_('Missing periodicity for holiday: %s') % holiday_slug)
|
||||
|
||||
holidays = holiday.exceptions.annotate(
|
||||
delta=ExpressionWrapper(
|
||||
F('end_datetime') - F('start_datetime'), output_field=models.DurationField()
|
||||
)
|
||||
)
|
||||
is_short_holiday = holidays.filter(delta__lt=datetime.timedelta(days=28)).exists()
|
||||
if 'quarter' in field_values['periodicity'] and is_short_holiday:
|
||||
raise ValidationError(_('Short holidays cannot be cut into quarters.'))
|
||||
|
||||
attrs['holidays'][holiday] = field_values
|
||||
|
||||
return attrs
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
guardian, dummy = Person.objects.get_or_create(
|
||||
user_external_id=validated_data['guardian_id'],
|
||||
defaults={
|
||||
'first_name': validated_data['guardian_first_name'],
|
||||
'last_name': validated_data['guardian_last_name'],
|
||||
},
|
||||
)
|
||||
other_guardian, dummy = Person.objects.get_or_create(
|
||||
user_external_id=validated_data['other_guardian_id'],
|
||||
defaults={
|
||||
'first_name': validated_data['other_guardian_first_name'],
|
||||
'last_name': validated_data['other_guardian_last_name'],
|
||||
},
|
||||
)
|
||||
|
||||
self.agenda = SharedCustodyAgenda.objects.create(
|
||||
first_guardian=guardian, second_guardian=other_guardian
|
||||
)
|
||||
|
||||
children = []
|
||||
children_data = validated_data.pop('children')
|
||||
for child in children_data:
|
||||
children.append(
|
||||
Person.objects.get_or_create(
|
||||
user_external_id=child['user_external_id'],
|
||||
defaults={'first_name': child['first_name'], 'last_name': child['last_name']},
|
||||
)[0]
|
||||
)
|
||||
self.agenda.children.set(children)
|
||||
|
||||
if validated_data.get('weeks'):
|
||||
self.create_custody_rules(guardian, validated_data['weeks'], create_mirror_for=other_guardian)
|
||||
|
||||
for holiday, params in validated_data.get('holidays', {}).items():
|
||||
self.create_holiday_rules(
|
||||
holiday,
|
||||
guardian,
|
||||
create_mirror_for=other_guardian,
|
||||
periodicity=params['periodicity'],
|
||||
years=params.get('years', ''),
|
||||
)
|
||||
|
||||
return self.agenda
|
||||
|
||||
def create_custody_rules(self, guardian, weeks, create_mirror_for=None):
|
||||
SharedCustodyRule.objects.create(
|
||||
agenda=self.agenda, days=list(range(7)), weeks=weeks, guardian=guardian
|
||||
)
|
||||
|
||||
if create_mirror_for:
|
||||
self.create_custody_rules(create_mirror_for, self.period_mirrors[weeks])
|
||||
|
||||
def create_holiday_rules(self, holiday, guardian, years, periodicity, create_mirror_for=None):
|
||||
rule = SharedCustodyHolidayRule.objects.create(
|
||||
agenda=self.agenda, holiday=holiday, guardian=guardian, years=years, periodicity=periodicity
|
||||
)
|
||||
rule.update_or_create_periods()
|
||||
|
||||
if years:
|
||||
rule = SharedCustodyHolidayRule.objects.create(
|
||||
agenda=self.agenda,
|
||||
holiday=holiday,
|
||||
guardian=guardian,
|
||||
years=self.period_mirrors[years],
|
||||
periodicity=self.period_mirrors[periodicity],
|
||||
)
|
||||
rule.update_or_create_periods()
|
||||
|
||||
if create_mirror_for:
|
||||
self.create_holiday_rules(holiday, create_mirror_for, years, self.period_mirrors[periodicity])
|
||||
|
||||
def get_settings_url(self, obj):
|
||||
request = self.context.get('request')
|
||||
return request.build_absolute_uri(obj.get_settings_url())
|
||||
|
|
|
@ -2773,27 +2773,7 @@ class SharedCustodyAgendas(APIView):
|
|||
serializer = self.serializer_class(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors)
|
||||
data = serializer.validated_data
|
||||
|
||||
with transaction.atomic():
|
||||
first_guardian, dummy = Person.objects.get_or_create(
|
||||
user_external_id=data['first_guardian_id'],
|
||||
defaults={
|
||||
'first_name': data['first_guardian_first_name'],
|
||||
'last_name': data['first_guardian_last_name'],
|
||||
},
|
||||
)
|
||||
second_guardian, dummy = Person.objects.get_or_create(
|
||||
user_external_id=data['second_guardian_id'],
|
||||
defaults={
|
||||
'first_name': data['second_guardian_first_name'],
|
||||
'last_name': data['second_guardian_last_name'],
|
||||
},
|
||||
)
|
||||
|
||||
agenda = SharedCustodyAgenda.objects.create(
|
||||
first_guardian=first_guardian, second_guardian=second_guardian
|
||||
)
|
||||
agenda = serializer.save()
|
||||
|
||||
response = {
|
||||
'id': agenda.pk,
|
||||
|
@ -2807,7 +2787,7 @@ shared_custody_agendas = SharedCustodyAgendas.as_view()
|
|||
|
||||
class SharedCustodyAgendaAddChild(APIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
serializer_class = serializers.SharedCustodyChildSerializer
|
||||
serializer_class = serializers.PersonSerializer
|
||||
|
||||
def post(self, request, agenda_pk):
|
||||
agenda = get_object_or_404(SharedCustodyAgenda, pk=agenda_pk)
|
||||
|
|
|
@ -1,29 +1,49 @@
|
|||
import pytest
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from chrono.agendas.models import Person, SharedCustodyAgenda
|
||||
from chrono.agendas.models import Person, SharedCustodyAgenda, UnavailabilityCalendar
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
with open('tests/data/holidays.ics') as f:
|
||||
ICS_HOLIDAYS = f.read()
|
||||
|
||||
|
||||
def test_add_shared_custody_agenda(app, user, settings):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
params = {
|
||||
'first_guardian_first_name': 'John',
|
||||
'first_guardian_last_name': 'Doe',
|
||||
'first_guardian_id': 'xxx',
|
||||
'second_guardian_first_name': 'Jane',
|
||||
'second_guardian_last_name': 'Doe',
|
||||
'second_guardian_id': 'yyy',
|
||||
'guardian_first_name': 'John',
|
||||
'guardian_last_name': 'Doe',
|
||||
'guardian_id': 'xxx',
|
||||
'other_guardian_first_name': 'Jane',
|
||||
'other_guardian_last_name': 'Doe',
|
||||
'other_guardian_id': 'yyy',
|
||||
'children': [
|
||||
{
|
||||
'first_name': 'James',
|
||||
'last_name': 'Doe',
|
||||
'user_external_id': 'zzz',
|
||||
},
|
||||
{
|
||||
'first_name': 'Arthur',
|
||||
'last_name': 'Doe',
|
||||
'user_external_id': '123',
|
||||
},
|
||||
],
|
||||
}
|
||||
resp = app.post_json('/api/shared-custody/', params=params)
|
||||
|
||||
john = Person.objects.get(user_external_id='xxx', first_name='John', last_name='Doe')
|
||||
jane = Person.objects.get(user_external_id='yyy', first_name='Jane', last_name='Doe')
|
||||
first_guardian = Person.objects.get(user_external_id='xxx', first_name='John', last_name='Doe')
|
||||
second_guardian = Person.objects.get(user_external_id='yyy', first_name='Jane', last_name='Doe')
|
||||
first_chidren = Person.objects.get(user_external_id='zzz', first_name='James', last_name='Doe')
|
||||
second_children = Person.objects.get(user_external_id='123', first_name='Arthur', last_name='Doe')
|
||||
|
||||
agenda = SharedCustodyAgenda.objects.get()
|
||||
assert agenda.first_guardian == john
|
||||
assert agenda.second_guardian == jane
|
||||
assert agenda.first_guardian == first_guardian
|
||||
assert agenda.second_guardian == second_guardian
|
||||
assert set(agenda.children.all()) == {first_chidren, second_children}
|
||||
|
||||
assert resp.json['data'] == {
|
||||
'id': agenda.pk,
|
||||
|
@ -31,16 +51,142 @@ def test_add_shared_custody_agenda(app, user, settings):
|
|||
}
|
||||
|
||||
params = {
|
||||
'first_guardian_first_name': 'John',
|
||||
'first_guardian_last_name': 'Doe',
|
||||
'first_guardian_id': 'xxx',
|
||||
'second_guardian_first_name': 'Other',
|
||||
'second_guardian_last_name': 'Other',
|
||||
'second_guardian_id': 'zzz',
|
||||
'guardian_first_name': 'John',
|
||||
'guardian_last_name': 'Doe',
|
||||
'guardian_id': 'xxx',
|
||||
'other_guardian_first_name': 'Other',
|
||||
'other_guardian_last_name': 'Doe',
|
||||
'other_guardian_id': 'other',
|
||||
'children': [
|
||||
{
|
||||
'first_name': 'Bruce',
|
||||
'last_name': 'Doe',
|
||||
'user_external_id': 'bruce',
|
||||
},
|
||||
],
|
||||
}
|
||||
resp = app.post_json('/api/shared-custody/', params=params)
|
||||
assert resp.json['data']['id'] != agenda.pk
|
||||
assert SharedCustodyAgenda.objects.filter(first_guardian=john).count() == 2
|
||||
assert SharedCustodyAgenda.objects.filter(first_guardian=first_guardian).count() == 2
|
||||
|
||||
|
||||
def test_add_shared_custody_agenda_with_rules(app, user, settings):
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# configure holidays
|
||||
unavailability_calendar = UnavailabilityCalendar.objects.create(label='Calendar', slug='chrono-holidays')
|
||||
source = unavailability_calendar.timeperiodexceptionsource_set.create(
|
||||
ics_filename='holidays.ics', ics_file=ContentFile(ICS_HOLIDAYS, name='holidays.ics')
|
||||
)
|
||||
source.refresh_timeperiod_exceptions_from_ics()
|
||||
|
||||
params = {
|
||||
'guardian_first_name': 'John',
|
||||
'guardian_last_name': 'Doe',
|
||||
'guardian_id': 'xxx',
|
||||
'other_guardian_first_name': 'Jane',
|
||||
'other_guardian_last_name': 'Doe',
|
||||
'other_guardian_id': 'yyy',
|
||||
'children': [
|
||||
{
|
||||
'first_name': 'James',
|
||||
'last_name': 'Doe',
|
||||
'user_external_id': 'zzz',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
resp = app.post_json('/api/shared-custody/', params={'weeks': '', **params})
|
||||
agenda = SharedCustodyAgenda.objects.get(pk=resp.json['data']['id'])
|
||||
assert not agenda.is_complete()
|
||||
assert not agenda.rules.exists()
|
||||
|
||||
resp = app.post_json('/api/shared-custody/', params={'weeks': 'even', **params})
|
||||
agenda = SharedCustodyAgenda.objects.get(pk=resp.json['data']['id'])
|
||||
assert agenda.is_complete()
|
||||
assert agenda.rules.filter(guardian__first_name='John', weeks='even').exists()
|
||||
assert agenda.rules.filter(guardian__first_name='Jane', weeks='odd').exists()
|
||||
|
||||
resp = app.post_json('/api/shared-custody/', params={'weeks': 'odd', **params})
|
||||
agenda = SharedCustodyAgenda.objects.get(pk=resp.json['data']['id'])
|
||||
assert agenda.is_complete()
|
||||
assert agenda.rules.filter(guardian__first_name='John', weeks='odd').exists()
|
||||
assert agenda.rules.filter(guardian__first_name='Jane', weeks='even').exists()
|
||||
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/', params={'christmas_holidays:periodicity': 'first-half', **params}
|
||||
)
|
||||
agenda = SharedCustodyAgenda.objects.get(pk=resp.json['data']['id'])
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='John', holiday__slug='christmas_holidays', periodicity='first-half', years=''
|
||||
).exists()
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='Jane', holiday__slug='christmas_holidays', periodicity='second-half', years=''
|
||||
).exists()
|
||||
assert agenda.periods.count() == 12
|
||||
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/',
|
||||
params={
|
||||
'summer_holidays:periodicity': 'first-and-third-quarters',
|
||||
'summer_holidays:years': 'odd',
|
||||
**params,
|
||||
},
|
||||
)
|
||||
agenda = SharedCustodyAgenda.objects.get(pk=resp.json['data']['id'])
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='John',
|
||||
holiday__slug='summer_holidays',
|
||||
periodicity='first-and-third-quarters',
|
||||
years='odd',
|
||||
).exists()
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='John',
|
||||
holiday__slug='summer_holidays',
|
||||
periodicity='second-and-fourth-quarters',
|
||||
years='even',
|
||||
).exists()
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='Jane',
|
||||
holiday__slug='summer_holidays',
|
||||
periodicity='first-and-third-quarters',
|
||||
years='even',
|
||||
).exists()
|
||||
assert agenda.holiday_rules.filter(
|
||||
guardian__first_name='Jane',
|
||||
holiday__slug='summer_holidays',
|
||||
periodicity='second-and-fourth-quarters',
|
||||
years='odd',
|
||||
).exists()
|
||||
assert agenda.periods.count() == 20
|
||||
|
||||
# unknown holiday
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/', params={'unknown:periodicity': 'first-half', **params}, status=400
|
||||
)
|
||||
assert resp.json['errors']['non_field_errors'][0] == 'Unknown holiday: unknown'
|
||||
|
||||
# unknown holiday param
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/', params={'summer_holidays:unknown': 'first-half', **params}, status=400
|
||||
)
|
||||
assert (
|
||||
resp.json['errors']['non_field_errors'][0] == 'Unknown parameter for holiday summer_holidays: unknown'
|
||||
)
|
||||
|
||||
# years without periodicity
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/', params={'summer_holidays:years': 'even', **params}, status=400
|
||||
)
|
||||
assert resp.json['errors']['non_field_errors'][0] == 'Missing periodicity for holiday: summer_holidays'
|
||||
|
||||
# quarters with short holiday
|
||||
resp = app.post_json(
|
||||
'/api/shared-custody/',
|
||||
params={'christmas_holidays:periodicity': 'first-and-third-quarters', **params},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['errors']['non_field_errors'][0] == 'Short holidays cannot be cut into quarters.'
|
||||
|
||||
|
||||
def test_shared_custody_agenda_add_child(app, user, settings):
|
||||
|
|
Loading…
Reference in New Issue