diff --git a/.gitignore b/.gitignore index 8d54bba..9d79368 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ media/agencymain/* media/agencydata/* !media/default.jpg !media/ag_default.jpg +!media/agencymain/default.jpg +!media/agencymain/ag_default.jpg !media/agencymain/linkdefault.png digitaleagentur/__pycache__/* diff --git a/dasettings/forms.py b/dasettings/forms.py index cfcea11..8f1d43a 100644 --- a/dasettings/forms.py +++ b/dasettings/forms.py @@ -1,8 +1,9 @@ from django import forms from django.db import models from django.contrib.auth.models import User -from users.models import AgencyGroup, Agency, Profile, AgencyJob, AgencyNetwork +from users.models import AgencyGroup, Agency, Profile, AgencyJob, AgencyNetwork, UserTime from PIL import Image +from bootstrap_datepicker_plus import DatePickerInput class AgencyOrganigrammForm(forms.ModelForm): class Meta: @@ -48,6 +49,25 @@ class UsersNotificationForm(forms.ModelForm): #fields = ['news_mail', 'news_push', 'user_standard_public_mail', 'user_standard_public_push', 'agency_new_standard_mail', 'agency_new_standard_push', 'add_new_group_mail', 'add_new_group_push', 'add_task_mail', 'add_task_push', 'user_messages_mail', 'user_messages_push'] fields = ['news_mail', 'news_push', 'agency_new_standard_mail', 'agency_new_standard_push', 'add_new_group_mail', 'add_new_group_push', 'add_task_mail', 'add_task_push', 'user_messages_mail', 'user_messages_push'] +# Usertime Form +class UserTimeForm(forms.ModelForm): + class Meta: + model = UserTime + labels = { + "holiday" : "Urlaubstage", + "loose_holidedate" : "Urlaubstage aus Vorjahr verfallen am", + "wd_mo" : "Montag", + "wd_tu" : "Dienstag", + "wd_we" : "Mittwoch", + "wd_th" : "Donnerstag", + "wd_fr" : "Freitag", + } + fields = ["holiday", "loose_holidedate", "wd_mo", "wd_tu", "wd_we", "wd_th", "wd_fr"] + widgets = { + 'loose_holidedate': DatePickerInput(options={"format":'DD.MM.YYYY', "locale":'de'}) + } + + # PERMISSION GROUPS FORM class AgencyGroupPerms(forms.Form): ''' @@ -77,8 +97,9 @@ class AgencyModulsForm(forms.ModelForm): 'module_files' : "Dateien", 'module_organigramm' : "Organigramm", 'module_messages' : "Mitteilungen", + 'module_timemanagement' : "Zeiterfassung", } - fields = ['module_news','module_organizer','module_files','module_organigramm', 'module_messages'] + fields = ['module_news','module_organizer','module_files','module_organigramm', 'module_messages', 'module_timemanagement'] # NEW USER FORM class UserNewUserForm(forms.ModelForm): diff --git a/dasettings/templates/dasettings/groups_content.html b/dasettings/templates/dasettings/groups_content.html index 81e47cd..4f6d88f 100644 --- a/dasettings/templates/dasettings/groups_content.html +++ b/dasettings/templates/dasettings/groups_content.html @@ -40,7 +40,7 @@
{% for perm in perms %} - {% if forloop.counter|divisibleby:7 %} + {% if forloop.counter|divisibleby:8 %}
diff --git a/dasettings/templates/dasettings/moduls_content.html b/dasettings/templates/dasettings/moduls_content.html index c434c2a..d9ca767 100644 --- a/dasettings/templates/dasettings/moduls_content.html +++ b/dasettings/templates/dasettings/moduls_content.html @@ -17,7 +17,7 @@ {{formfield.label_tag}} {{formfield}} - + {% if formfield.name == 'module_organigramm' %}{% endif %} {% endfor %} @@ -26,7 +26,6 @@
- {% for formfield in modulform %}
diff --git a/dasettings/views.py b/dasettings/views.py index cea9182..3213588 100644 --- a/dasettings/views.py +++ b/dasettings/views.py @@ -1,13 +1,14 @@ from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect,HttpResponse, JsonResponse -from .forms import UsersSelfChangeForm, UsersNotificationForm, AgencyGroupPerms, AgencyModulsForm, UserNewUserForm, UserProfileForm, AgencyNetworkForm, AgencyOrganigrammForm +from .forms import UsersSelfChangeForm, UsersNotificationForm, AgencyGroupPerms, AgencyModulsForm, UserNewUserForm, UserProfileForm, AgencyNetworkForm, AgencyOrganigrammForm, UserTimeForm from django.contrib import messages from django.contrib.auth import update_session_auth_hash from django.contrib.auth.forms import PasswordChangeForm from users.usersforms import AgencyUpdateForm from users.models import AgencyJob, AgencyGroup, AgencyNetwork, Agency, AgencyNetworkPreperation from django.contrib.auth.models import User, Group, Permission +from users.models import UserTime import random import string from django.template.loader import render_to_string @@ -501,40 +502,70 @@ def UserProfileUpdate(request, pk, newuser=0): usertochange.profile.image = request.FILES['image'] formtosave = False - formtosave = UserProfileForm(request.POST, instance=usertochange.profile) - - if formtosave.is_valid(): - try: - usertochange.profile.parent = User.objects.get(pk=request.POST['usertoparent'], profile__agency=request.user.profile.agency) - usertochange.save() - except Exception as e: - usertochange.profile.parent = None - usertochange.save() + if(request.POST["form_type"] == "profileform"): + + formtosave = UserProfileForm(request.POST, instance=usertochange.profile) + + if formtosave.is_valid(): + try: + usertochange.profile.parent = User.objects.get(pk=request.POST['usertoparent'], profile__agency=request.user.profile.agency) + usertochange.save() + except Exception as e: + usertochange.profile.parent = None + usertochange.save() + + formtosave.save() + messages.success(request, f'Profil gespeichert!') + return redirect('dasettings') + else: + messages.success(request, f'Fehlerhafte Eingabe!') + context = { + 'active_link' : 'dasettings', + 'user_fullname' : user_fullname, + 'first_name' : usertochange.first_name, + 'last_name' : usertochange.last_name, + 'usertime_form' : UserTimeForm(instance=UserTime.objects.get(user=usertochange)), + 'newuser' : newuser, + 'vieweduser' : usertochange.pk, + 'parentuser' : parentuser, + 'mail' : usertochange.email, + 'imagelink' : usertochange.profile.get_photo_url, + 'profileform' : UserProfileForm(instance=usertochange.profile), + 'usertoparent' : User.objects.filter(profile__agency__pk=usertochange.profile.agency.pk, profile__visible=True) + } + return render(request, 'dasettings/user_usprof.html', context) + elif(request.POST["form_type"] == "contract"): + + formtosave = UserTimeForm(request.POST, instance=UserTime.objects.get(user=usertochange)) + print(formtosave) + if(formtosave.is_valid()): + messages.success(request, f'Vertragsdaten gespeichert!') + formtosave.save() + return redirect('dasettings') + else: + messages.success(request, f'Fehlerhafte Eingabe!') + context = { + 'active_link' : 'dasettings', + 'user_fullname' : user_fullname, + 'first_name' : usertochange.first_name, + 'last_name' : usertochange.last_name, + 'usertime_form' : UserTimeForm(instance=UserTime.objects.get(user=usertochange)), + 'newuser' : newuser, + 'vieweduser' : usertochange.pk, + 'parentuser' : parentuser, + 'mail' : usertochange.email, + 'imagelink' : usertochange.profile.get_photo_url, + 'profileform' : UserProfileForm(instance=usertochange.profile), + 'usertoparent' : User.objects.filter(profile__agency__pk=usertochange.profile.agency.pk, profile__visible=True) + } + return render(request, 'dasettings/user_usprof.html', context) - formtosave.save() - messages.success(request, f'Profil gespeichert!') - return redirect('dasettings') - else: - messages.success(request, f'Fehlerhafte Eingabe!') - context = { - 'active_link' : 'dasettings', - 'user_fullname' : user_fullname, - 'first_name' : usertochange.first_name, - 'last_name' : usertochange.last_name, - 'newuser' : newuser, - 'vieweduser' : usertochange.pk, - 'parentuser' : parentuser, - 'mail' : usertochange.email, - 'imagelink' : usertochange.profile.get_photo_url, - 'profileform' : UserProfileForm(instance=usertochange.profile), - 'usertoparent' : User.objects.filter(profile__agency__pk=usertochange.profile.agency.pk, profile__visible=True) - } - return render(request, 'dasettings/user_usprof.html', context) else: context = { 'active_link' : 'dasettings', 'user_fullname' : user_fullname, + 'usertime_form' : UserTimeForm(instance=UserTime.objects.get(user=usertochange)), 'first_name' : usertochange.first_name, 'last_name' : usertochange.last_name, 'newuser' : newuser, diff --git a/digitaleagentur/__pycache__/settings.cpython-38.pyc b/digitaleagentur/__pycache__/settings.cpython-38.pyc index 73fad64..6c473d8 100644 Binary files a/digitaleagentur/__pycache__/settings.cpython-38.pyc and b/digitaleagentur/__pycache__/settings.cpython-38.pyc differ diff --git a/digitaleagentur/__pycache__/urls.cpython-38.pyc b/digitaleagentur/__pycache__/urls.cpython-38.pyc index be0e15f..c61c031 100644 Binary files a/digitaleagentur/__pycache__/urls.cpython-38.pyc and b/digitaleagentur/__pycache__/urls.cpython-38.pyc differ diff --git a/digitaleagentur/settings.py b/digitaleagentur/settings.py index 80793ab..dc98465 100644 --- a/digitaleagentur/settings.py +++ b/digitaleagentur/settings.py @@ -72,6 +72,7 @@ INSTALLED_APPS = [ 'tasks.apps.TasksConfig', 'organizer.apps.OrganizerConfig', 'standards.apps.StandardsConfig', + 'timemanagement.apps.TimemanagementConfig', 'news.apps.NewsConfig', 'crispy_forms', 'colorful', @@ -83,7 +84,7 @@ INSTALLED_APPS = [ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', - 'django.contrib.staticfiles', + 'django.contrib.staticfiles', 'bootstrap_datepicker_plus', 'django_cleanup', 'django_user_agents', diff --git a/digitaleagentur/urls.py b/digitaleagentur/urls.py index 1ddc000..926a486 100644 --- a/digitaleagentur/urls.py +++ b/digitaleagentur/urls.py @@ -42,7 +42,8 @@ urlpatterns = [ path('register/', registerNewAgency, name='register'), path('register/done', views.registerdone, name='register-done'), path('summernote/', include('django_summernote.urls')), - path('notifications/', include('notificsys.urls'), name="notifications") + path('notifications/', include('notificsys.urls'), name="notifications"), + path('tm/', include('timemanagement.urls'), name="timemanagement") ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/media/agencymain/ag_default.jpg b/media/agencymain/ag_default.jpg new file mode 100644 index 0000000..c8d0904 Binary files /dev/null and b/media/agencymain/ag_default.jpg differ diff --git a/media/default.jpg b/media/default.jpg index c139055..1fb7d8c 100644 Binary files a/media/default.jpg and b/media/default.jpg differ diff --git a/standards/templates/standards/standards_single_agn.html b/standards/templates/standards/standards_single_agn.html index afb7b32..0d6b2ef 100644 --- a/standards/templates/standards/standards_single_agn.html +++ b/standards/templates/standards/standards_single_agn.html @@ -100,12 +100,19 @@

- + {% for comment in comments %} {% getcommentstat_user comment.pk request.user as cstat %}
- Von {{comment.comment_by.first_name}} {{comment.comment_by.last_name}} am {{comment.last_modified_on|date:"d.m.Y H:i"}}
+ + + {{comment.comment_by.first_name}} {{comment.comment_by.last_name}} am {{comment.last_modified_on|date:"d.m.Y H:i"}}
{{comment.content}}
+ + + +
+
+
+ Arbeitsbeginn: 00:00:00
+ Arbeitsende: 00:00:00
+ Pausenzeit: 00:00:00 +
+
+
+ +
+
+Gleitzeitkonto: +00:13 Stunden +
+ + diff --git a/timemanagement/templates/timemanagement/timemanagement_management.html b/timemanagement/templates/timemanagement/timemanagement_management.html new file mode 100644 index 0000000..7ad4256 --- /dev/null +++ b/timemanagement/templates/timemanagement/timemanagement_management.html @@ -0,0 +1,42 @@ +{% extends "users/base.html" %} +{% block content %} +{% if request.user.profile.agency.module_timemanagement %} +
+

Zeiterfassung 

+
+ + + +{% else %} +

Das Modul Zeiterfassung wurde in ihrer Agentur deaktiviert.

+{% endif %} +{% endblock content %} diff --git a/timemanagement/tests.py b/timemanagement/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/timemanagement/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/timemanagement/urls.py b/timemanagement/urls.py new file mode 100644 index 0000000..61a4812 --- /dev/null +++ b/timemanagement/urls.py @@ -0,0 +1,19 @@ +from django.urls import path +from django.contrib.auth.decorators import login_required, permission_required +from .views import TimeManagement, TimeAjax +''' +Permissions definiert in models.py bei USERS und dann hier vor die View geschrieben! +''' + +urlpatterns = [ + path('', TimeManagement, name='tm-management'), + path('ajax/', TimeAjax, name='tm-ajax'), + #path('newsadd/', permission_required('users.modulenews')(views.NewsAdd), name='news-add'), + #path('newsupdate//', permission_required('users.modulenews')(views.NewsUpdate), name='news-update'), + #path('news//delete', permission_required('users.modulenews')(NewsDeleteView.as_view()), name='news-delete'), + + #path('standard//changestat', views.StandardChangePublic, name="standard-status"), + #path('news//single', views.NewsSingle, name="news-single"), + #path('standard//area', views.StandardArea, name="standard-area"), + #path('standard//task', views.StandardTask, name="standard-task") +] diff --git a/timemanagement/views.py b/timemanagement/views.py new file mode 100644 index 0000000..20a139c --- /dev/null +++ b/timemanagement/views.py @@ -0,0 +1,78 @@ +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from .models import Workday, Breaks +from django.utils import timezone + +@login_required +def TimeManagement(request): + context = { + "active_link" : "timemanagement" + } + return render(request, 'timemanagement/timemanagement_management.html', context) + +@login_required +def TimeAjax(request): + data = {} + if request.method == "GET": + if request.GET["action"] == "start_day": + wd = Workday(user=request.user, agency=request.user.profile.agency, start=timezone.now()) + wd.save() + data = { + "success" : True, + "wd_starttime" : wd.start.strftime("%H:%M:%S"), + "wd_starttime_complete" : wd.start + } + elif request.GET["action"] == "end_day": + wd = list(Workday.objects.filter(user=request.user, agency=request.user.profile.agency, end=None))[0] + # END ALL BREAKS + for b in wd.breaks.all(): + if b.end == None: + b.end = timezone.now() + b.save() + wd.end = timezone.now() + wd.save() + + breaksum = 0 + for b in wd.breaks.all(): + if(b.end != None): + breaksum += (b.end - b.start).seconds + + data = { + "success" : True, + "wd_endtime" : wd.end.strftime("%H:%M:%S"), + "actualbreaktime" : breaksum*1000 + } + # START A BREAK + elif request.GET["action"] == "start_break": + wd = list(Workday.objects.filter(user=request.user, agency=request.user.profile.agency, end=None))[0] + newbreak = Breaks(workday=wd, user=request.user, agency=request.user.profile.agency, start=timezone.now()) + newbreak.save() + wd.breaks.add(newbreak) + data = { + "success" : True, + "break_starttime" : newbreak.start, + } + elif request.GET["action"] == "end_break": + wd = list(Workday.objects.filter(user=request.user, agency=request.user.profile.agency, end=None))[0] + toendbreak = list(wd.breaks.filter(end=None))[0] + toendbreak.end = timezone.now() + toendbreak.save() + + wd = list(Workday.objects.filter(user=request.user, agency=request.user.profile.agency, end=None))[0] + breaksum = 0 + for b in wd.breaks.all(): + if(b.end != None): + breaksum += (b.end - b.start).seconds + + data = { + "success" : True, + "actualbreaktime" : breaksum*1000 + } + else: + data = { + "success" : False + } + + return JsonResponse(data) + diff --git a/users/admin.py b/users/admin.py index 5108f47..e008407 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,12 +1,12 @@ from django.contrib import admin -from .models import Profile, Agency, AgencyGroup, AgencyJob, AgencyNetwork, AgencyNetworkPreperation +from .models import Profile, Agency, AgencyGroup, AgencyJob, AgencyNetwork, AgencyNetworkPreperation, UserTime from .priomodel import Prio from standards.models import StandardCommentRate, StandardComments from django.contrib.auth.models import Permission from message.models import Message from cloud.models import DataFile from organizer.models import AGContacts - +from timemanagement.models import Workday, Breaks admin.site.register(StandardComments) admin.site.register(StandardCommentRate) @@ -21,4 +21,7 @@ admin.site.register(AgencyNetwork) admin.site.register(AGContacts) admin.site.register(AgencyNetworkPreperation) admin.site.register(DataFile) +admin.site.register(UserTime) +admin.site.register(Workday) +admin.site.register(Breaks) diff --git a/users/models.py b/users/models.py index 6465175..d18d342 100644 --- a/users/models.py +++ b/users/models.py @@ -88,6 +88,8 @@ class Agency(models.Model): # MONEY balance = models.FloatField(default=0.0, max_length=9, blank=True) nextdebiting = models.DateTimeField(default=timezone.now, blank=True) + monthlyprice = models.FloatField(default=25.0, max_length=9, blank=True) + # MODULEEINSTELLUNGEN FÜR DIE AGENTUR module_news = models.BooleanField(default=True) @@ -95,6 +97,10 @@ class Agency(models.Model): module_files = models.BooleanField(default=True) module_organigramm = models.BooleanField(default=True) module_messages = models.BooleanField(default=True) + + # KOSTENPFLICHTIGE MODULE + module_timemanagement = models.BooleanField(default=False) + module_timemanagement_price = models.FloatField(default=5.0, max_length=9, blank=True) # Steckbrief dynamisch aus Standard dynamicprofile = models.BooleanField(default=True) @@ -158,7 +164,7 @@ class Profile(models.Model): func = models.ForeignKey("AgencyJob", blank=True, null=True, default=None, on_delete=models.SET_NULL) # Wenn dieses Profil gelöscht wird, wird NICHT die Agency geslöscht agency = models.ForeignKey(Agency, on_delete=models.PROTECT) - image = models.ImageField(default='default.jpg', upload_to=picturepath_user, blank=True) + image = models.ImageField(default='agencymain/default.jpg', upload_to=picturepath_user, blank=True) compfunc = models.CharField(max_length=60, blank=True) visible = models.BooleanField(default=True) persnumber = models.CharField(default="", max_length=50, blank=True) @@ -197,16 +203,6 @@ class Profile(models.Model): user_messages_mail = models.BooleanField(default=True) user_messages_push = models.BooleanField(default=True) - # TIME ELEMENTS - wd_mo = models.IntegerField(default=8) - wd_tu = models.IntegerField(default=8) - wd_we = models.IntegerField(default=8) - wd_th = models.IntegerField(default=8) - wd_fr = models.IntegerField(default=8) - holiday = models.IntegerField(default=24) - loose_holidedate = models.DateField(default=datetime.date(datetime.datetime.now().year, 4,30)) - - def __str__(self): return f'{self.user.last_name}' @@ -238,8 +234,19 @@ class Profile(models.Model): return self.image.url else: return "/media/default.jpg" + +class UserTime(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True, default=None) + # TIME ELEMENTS + wd_mo = models.FloatField(default=8.0) + wd_tu = models.FloatField(default=8.0) + wd_we = models.FloatField(default=8.0) + wd_th = models.FloatField(default=8.0) + wd_fr = models.FloatField(default=8.0) + holiday = models.FloatField(default=24.0) + loose_holidedate = models.DateField(default=datetime.date(datetime.datetime.now().year + 1, 4,30)) ''' @@ -270,7 +277,7 @@ class AgencyGroup(models.Model): ('filesmanager', 'Dateien bearbeiten'), ('filedirmanager', 'Ordner bearbeiten'), ('filesviewer', 'Dateien lesen'), - + ('absencemanager', 'Abwesenheiten verwalten') ] # SUBCLASS diff --git a/users/signals.py b/users/signals.py index 05e0614..961f181 100644 --- a/users/signals.py +++ b/users/signals.py @@ -13,6 +13,25 @@ import os from django.conf import settings from django.utils import timezone from standards.models import Standards +from django.contrib.auth.signals import user_logged_in +from timemanagement.models import Workday, Breaks +from datetime import date +import datetime + +# CHECK SOMETHING WHEN USER LOGGED IN +@receiver(signal=user_logged_in, sender=User) +def checkForWorkDays(sender, user, request, **kwargs): + today = date.today() + wd = Workday.objects.filter(user=user, end=None, start__day__lte=today.day) + + for d in wd: + d.end = datetime.datetime(d.start.year, d.start.month, d.start.day, 23, 59, 00) + d.save() + for b in d.breaks.all(): + if(b.end == None): + b.end = datetime.datetime(d.start.year, d.start.month, d.start.day, 23, 59, 00) + b.save() + # Deletes all Notifications added to to delete news diff --git a/users/static/users/img/ag_default.jpg b/users/static/users/img/ag_default.jpg new file mode 100644 index 0000000..c8d0904 Binary files /dev/null and b/users/static/users/img/ag_default.jpg differ diff --git a/users/static/users/img/default.jpg b/users/static/users/img/default.jpg new file mode 100644 index 0000000..1fb7d8c Binary files /dev/null and b/users/static/users/img/default.jpg differ diff --git a/media/agencymain/linkdefault.png b/users/static/users/img/linkdefault.png similarity index 100% rename from media/agencymain/linkdefault.png rename to users/static/users/img/linkdefault.png diff --git a/users/templates/users/base.html b/users/templates/users/base.html index 4ad28ca..7e1eb8c 100644 --- a/users/templates/users/base.html +++ b/users/templates/users/base.html @@ -150,8 +150,6 @@ {%endif%} {% getmesscounter request.user as gs %} - - Mitteilungen @@ -163,8 +161,18 @@ {% endif %} - - + {% if request.user.profile.agency.module_timemanagement %} + {% if active_link == 'timemanagement' %} + + {% endif %} -