Skip to content

Commit

Permalink
Merge pull request #22 from moshthepitt/issue-19-uncounted-days
Browse files Browse the repository at this point in the history
Add support for free days
  • Loading branch information
moshthepitt authored Dec 8, 2018
2 parents e16e21a + 2febd02 commit 24f0e0b
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 34 deletions.
2 changes: 1 addition & 1 deletion small_small_hr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
Main init file for small_small_hr
"""
VERSION = (0, 1, 2)
VERSION = (0, 1, 3)
__version__ = '.'.join(str(v) for v in VERSION)
# pylint: disable=invalid-name
default_app_config = 'small_small_hr.apps.SmallSmallHrConfig' # noqa
42 changes: 38 additions & 4 deletions small_small_hr/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,22 @@
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.db.models import Q
from django.utils.translation import ugettext as _

import pytz
from crispy_forms.bootstrap import Field, FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit
from phonenumber_field.phonenumber import PhoneNumber
from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.phonenumber import PhoneNumber

from small_small_hr.emails import (leave_application_email,
overtime_application_email)
from small_small_hr.models import (TWOPLACES, Leave, OverTime, Role,
StaffDocument, StaffProfile, AnnualLeave)
from small_small_hr.models import (TWOPLACES, AnnualLeave, FreeDay, Leave,
OverTime, Role, StaffDocument, StaffProfile)


class AnnualLeaveForm(forms.ModelForm):
Expand Down Expand Up @@ -96,6 +96,40 @@ def __init__(self, *args, **kwargs):
)


class FreeDayForm(forms.ModelForm):
"""
Form used when managing FreeDay objects
"""

class Meta: # pylint: disable=too-few-public-methods
"""
Class meta options
"""
model = FreeDay
fields = [
'name',
'date'
]

def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_method = 'post'
self.helper.render_required_fields = True
self.helper.form_show_labels = True
self.helper.html5_required = True
self.helper.form_id = 'freeday-form'
self.helper.layout = Layout(
Field('name',),
Field('date',),
FormActions(
Submit('submitBtn', _('Submit'), css_class='btn-primary'),
)
)


class OverTimeForm(forms.ModelForm):
"""
Form used when managing OverTime objects
Expand Down
77 changes: 77 additions & 0 deletions small_small_hr/migrations/0006_auto_20181209_0108.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Generated by Django 2.1.3 on 2018-12-08 22:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('small_small_hr', '0005_auto_20181115_2112'),
]

operations = [
migrations.CreateModel(
name='FreeDay',
fields=[
('id',
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID')),
('name', models.CharField(max_length=255,
verbose_name='Name')),
('date', models.DateField(unique=True, verbose_name='Date')),
],
options={
'verbose_name': 'Free Day',
'verbose_name_plural': 'Free Days',
'ordering': ['-date'],
},
),
migrations.AlterModelOptions(
name='annualleave',
options={
'ordering': ['-year', 'leave_type', 'staff'],
'verbose_name': 'Annual Leave',
'verbose_name_plural': 'Annual Leave'
},
),
migrations.AlterModelOptions(
name='leave',
options={
'ordering': ['staff', '-start'],
'verbose_name': 'Leave',
'verbose_name_plural': 'Leave'
},
),
migrations.AlterModelOptions(
name='overtime',
options={
'ordering': ['staff', '-date', 'start'],
'verbose_name': 'Overtime',
'verbose_name_plural': 'Overtime'
},
),
migrations.AlterModelOptions(
name='staffdocument',
options={
'ordering': ['staff', 'name', '-created'],
'verbose_name': 'Staff Document',
'verbose_name_plural': 'Staff Documents'
},
),
migrations.AlterField(
model_name='annualleave',
name='year',
field=models.PositiveIntegerField(
choices=[(2017, 2017), (2018, 2018), (2019, 2019), (2020,
2020),
(2021, 2021), (2022, 2022), (2023, 2023), (2024,
2024),
(2025, 2025), (2026, 2026), (2027, 2027)],
db_index=True,
default=2018,
verbose_name='Year'),
),
]
32 changes: 26 additions & 6 deletions small_small_hr/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class Meta: # pylint: disable=too-few-public-methods
abstract = False
verbose_name = _('Staff Document')
verbose_name_plural = _('Staff Documents')
ordering = ['staff', 'name', 'created']
ordering = ['staff', 'name', '-created']

def __str__(self):
# pylint: disable=no-member
Expand Down Expand Up @@ -282,7 +282,7 @@ class Meta: # pylint: disable=too-few-public-methods
abstract = False
verbose_name = _('Leave')
verbose_name_plural = _('Leave')
ordering = ['staff', 'start']
ordering = ['staff', '-start']

def __str__(self):
# pylint: disable=no-member
Expand All @@ -305,7 +305,7 @@ class Meta: # pylint: disable=too-few-public-methods
abstract = False
verbose_name = _('Overtime')
verbose_name_plural = _('Overtime')
ordering = ['staff', 'date', 'start']
ordering = ['staff', '-date', 'start']

def __str__(self):
name = self.staff.get_name() # pylint: disable=no-member
Expand All @@ -328,7 +328,7 @@ class AnnualLeave(TimeStampedModel, models.Model):
Each staff member can only have one record per leave_type per year
"""
YEAR_CHOICES = [
(r, r) for r in range(2017, datetime.today().year + 5)
(r, r) for r in range(2017, datetime.today().year + 10)
]

year = models.PositiveIntegerField(
Expand All @@ -351,7 +351,7 @@ class Meta: # pylint: disable=too-few-public-methods
"""
verbose_name = _('Annual Leave')
verbose_name_plural = _('Annual Leave')
ordering = ['year', 'leave_type', 'staff']
ordering = ['-year', 'leave_type', 'staff']
unique_together = (('year', 'staff', 'leave_type'),)

def __str__(self):
Expand Down Expand Up @@ -401,6 +401,22 @@ def get_available_leave_days(self, month: int = 12):
return Decimal(earned + starting_balance - taken)


class FreeDay(models.Model):
"""Model definition for FreeDay."""
name = models.CharField(_("Name"), max_length=255)
date = models.DateField(_('Date'), unique=True)

class Meta:
"""Meta definition for FreeDay."""
ordering = ['-date']
verbose_name = _('Free Day')
verbose_name_plural = _('Free Days')

def __str__(self):
"""Unicode representation of FreeDay."""
return f"{self.date.year} - {self.name}"


def get_days(start: object, end: object):
"""
Yield the days between two datetime objects
Expand All @@ -424,6 +440,9 @@ def get_taken_leave_days(
taking into account weekends and weekend policy
"""
count = Decimal(0)
free_days = FreeDay.objects.filter(
date__year__gte=start_year, date__year__lte=end_year
).values_list('date', flat=True)
queryset = Leave.objects.filter(
staff=staffprofile,
status=status,
Expand All @@ -432,7 +451,8 @@ def get_taken_leave_days(
for leave_obj in queryset:
days = get_days(start=leave_obj.start, end=leave_obj.end)
for day in days:
if day.year >= start_year and day.year <= end_year:
if day.year >= start_year and day.year <= end_year and\
day not in free_days:
day_value = settings.SSHR_DAY_LEAVE_VALUES[day.isoweekday()]
count = count + Decimal(day_value)
return count
9 changes: 9 additions & 0 deletions small_small_hr/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
}
SSHR_ALLOW_OVERSUBSCRIBE = True # allow taking more leave days one has
SSHR_DEFAULT_TIME = 7 # default time of the day for leave
SSHR_FREE_DAYS = [
{'day': 1, 'month': 1}, # New year
{'day': 1, 'month': 5}, # labour day
{'day': 1, 'month': 6}, # Madaraka day
{'day': 20, 'month': 10}, # Mashujaa day
{'day': 12, 'month': 12}, # Jamhuri day
{'day': 25, 'month': 12}, # Christmas
{'day': 26, 'month': 12}, # Boxing day
] # these are days that are not counted when getting taken leave days
29 changes: 28 additions & 1 deletion small_small_hr/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""
Utils module for small small hr
"""
from datetime import date

from django.conf import settings
from django.utils import timezone

from small_small_hr.models import AnnualLeave, Leave
from small_small_hr.models import AnnualLeave, FreeDay, Leave


def get_carry_over(staffprofile: object, year: int, leave_type: str):
Expand Down Expand Up @@ -53,3 +56,27 @@ def create_annual_leave(staffprofile: object, year: int, leave_type: str):
annual_leave.save()

return annual_leave


def create_free_days(
start_year: int = timezone.now().year, number_of_years: int = 11):
"""
Create FreeDay records
:param start_year: the year from which to start creating free days
:param number_of_years: number of years to create free days objects
"""
default_days = settings.SSHR_FREE_DAYS
years = (start_year + _ for _ in range(number_of_years))
for year in years:
for default_day in default_days:
the_date = date(
year=year,
month=default_day['month'],
day=default_day['day'],
)
free_day = FreeDay(
name=the_date.strftime("%A %d %B %Y"),
date=the_date,
)
free_day.save()
32 changes: 30 additions & 2 deletions tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Module to test small_small_hr models
"""
import os
from datetime import datetime, timedelta
from datetime import datetime, timedelta, date
from unittest.mock import patch

import pytz
Expand All @@ -17,7 +17,7 @@
RoleForm, StaffDocumentForm,
StaffProfileAdminCreateForm,
StaffProfileAdminForm, StaffProfileUserForm,
UserStaffDocumentForm)
UserStaffDocumentForm, FreeDayForm)
from small_small_hr.models import (Leave, OverTime, StaffProfile,
get_taken_leave_days)
from small_small_hr.serializers import StaffProfileSerializer
Expand Down Expand Up @@ -101,6 +101,34 @@ def test_role_form(self):
self.assertEqual('Accountant', role.name)
self.assertEqual('Keep accounts', role.description)

def test_freeday_form(self):
"""
Test FreeDayForm
"""
request = self.factory.get('/')
request.session = {}
request.user = AnonymousUser()

data = {
'name': 'Mosh Day',
'date': '1/1/2017'
}

form = FreeDayForm(data=data)
self.assertTrue(form.is_valid())
free_day = form.save()
self.assertEqual('Mosh Day', free_day.name)
self.assertEqual(date(2017, 1, 1), free_day.date)

# has to be unique
form2 = FreeDayForm(data=data)
self.assertFalse(form2.is_valid())
self.assertEqual(1, len(form2.errors.keys()))
self.assertEqual(
'Free Day with this Date already exists.',
form2.errors['date'][0]
)

@patch('small_small_hr.forms.overtime_application_email')
def test_overtime_form_apply(self, mock):
"""
Expand Down
Loading

0 comments on commit 24f0e0b

Please sign in to comment.