diff --git a/small_small_hr/forms.py b/small_small_hr/forms.py index a8d7343..778c49a 100644 --- a/small_small_hr/forms.py +++ b/small_small_hr/forms.py @@ -4,6 +4,7 @@ from datetime import datetime, time from django import forms +from django.db.models import Q from django.conf import settings from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -102,11 +103,29 @@ def clean(self): cleaned_data = super().clean() end = cleaned_data.get('end') start = cleaned_data.get('start') + date = cleaned_data.get('date') + staff = cleaned_data.get('staff') # end must be later than start if end <= start: self.add_error('end', _("end must be greater than start")) + # must not overlap within the same date + # pylint: disable=no-member + overlap_qs = OverTime.objects.filter( + date=date, staff=staff, status=OverTime.APPROVED).filter( + Q(start__gte=start) | Q(end__lte=end)) + + if self.instance is not None: + overlap_qs = overlap_qs.exclude(id=self.instance.id) + + if overlap_qs.exists(): + msg = _('you cannot have overlapping overtime hours on the ' + 'same day') + self.add_error('start', msg) + self.add_error('end', msg) + self.add_error('date', msg) + class ApplyOverTimeForm(OverTimeForm): """ @@ -253,6 +272,20 @@ def clean(self): self.add_error('start', msg) self.add_error('end', msg) + # must not overlap + # pylint: disable=no-member + overlap_qs = Leave.objects.filter( + staff=staff, status=Leave.APPROVED, leave_type=leave_type).filter( + Q(start__gte=start) | Q(end__lte=end)) + + if self.instance is not None: + overlap_qs = overlap_qs.exclude(id=self.instance.id) + + if overlap_qs.exists(): + msg = _('you cannot have overlapping leave days') + self.add_error('start', msg) + self.add_error('end', msg) + class ApplyLeaveForm(LeaveForm): """ diff --git a/tests/test_forms.py b/tests/test_forms.py index 117e85b..d815ad8 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -91,6 +91,51 @@ def test_overtime_form_apply(self): self.assertEqual(OverTime.PENDING, overtime.status) self.assertEqual('', overtime.comments) + def test_overtime_form_apply_no_overlap(self): + """ + Test no overlaps on OverTime + """ + user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') + staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + + request = self.factory.get('/') + request.session = {} + request.user = AnonymousUser() + + # 6 hours of overtime + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 5, 6, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + mommy.make( + 'small_small_hr.OverTime', start=start.time(), end=end.time(), + status=OverTime.APPROVED, date=start.date, staff=staffprofile) + + data = { + 'staff': staffprofile.id, + 'date': start.date(), + 'start': start.time(), + 'end': end.time(), + 'reason': 'Extra work', + } + + form = ApplyOverTimeForm(data=data) + self.assertFalse(form.is_valid()) + self.assertEqual(3, len(form.errors.keys())) + self.assertEqual( + 'you cannot have overlapping overtime hours on the same day', + form.errors['start'][0] + ) + self.assertEqual( + 'you cannot have overlapping overtime hours on the same day', + form.errors['date'][0] + ) + self.assertEqual( + 'you cannot have overlapping overtime hours on the same day', + form.errors['end'][0] + ) + def test_overtime_form_process(self): """ Test OverTimeForm @@ -208,6 +253,53 @@ def test_leaveform_apply(self): self.assertEqual(Leave.PENDING, leave.status) self.assertEqual('', leave.comments) + def test_leaveform_no_overlap(self): + """ + Test LeaveForm no overlap + """ + user = mommy.make('auth.User', first_name='Bob', last_name='Ndoe') + staffprofile = mommy.make('small_small_hr.StaffProfile', user=user) + staffprofile.leave_days = 21 + staffprofile.sick_days = 10 + staffprofile.save() + + request = self.factory.get('/') + request.session = {} + request.user = AnonymousUser() + + # 6 days of leave + start = datetime( + 2017, 6, 5, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + end = datetime( + 2017, 6, 10, 0, 0, 0, tzinfo=pytz.timezone(settings.TIME_ZONE)) + + mommy.make('small_small_hr.AnnualLeave', staff=staffprofile, year=2017, + leave_type=Leave.REGULAR, carried_over_days=12) + + mommy.make('small_small_hr.Leave', leave_type=Leave.REGULAR, + start=start, end=end, status=Leave.APPROVED, + staff=staffprofile) + + data = { + 'staff': staffprofile.id, + 'leave_type': Leave.REGULAR, + 'start': start, + 'end': end, + 'reason': 'Need a break', + } + + form = ApplyLeaveForm(data=data) + self.assertFalse(form.is_valid()) + self.assertEqual(2, len(form.errors.keys())) + self.assertEqual( + 'you cannot have overlapping leave days', + form.errors['start'][0] + ) + self.assertEqual( + 'you cannot have overlapping leave days', + form.errors['end'][0] + ) + def test_leaveform_admin(self): """ Test LeaveForm apply for leave