Modulcommit Zeiterfassung

This commit is contained in:
holger.trampe 2020-04-26 21:19:18 +02:00
parent f52d1d64e4
commit 226eecb46e
42 changed files with 823 additions and 75 deletions

2
.gitignore vendored
View File

@ -2,6 +2,8 @@ media/agencymain/*
media/agencydata/* media/agencydata/*
!media/default.jpg !media/default.jpg
!media/ag_default.jpg !media/ag_default.jpg
!media/agencymain/default.jpg
!media/agencymain/ag_default.jpg
!media/agencymain/linkdefault.png !media/agencymain/linkdefault.png
digitaleagentur/__pycache__/* digitaleagentur/__pycache__/*

View File

@ -1,8 +1,9 @@
from django import forms from django import forms
from django.db import models from django.db import models
from django.contrib.auth.models import User 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 PIL import Image
from bootstrap_datepicker_plus import DatePickerInput
class AgencyOrganigrammForm(forms.ModelForm): class AgencyOrganigrammForm(forms.ModelForm):
class Meta: 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', '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'] 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 # PERMISSION GROUPS FORM
class AgencyGroupPerms(forms.Form): class AgencyGroupPerms(forms.Form):
''' '''
@ -77,8 +97,9 @@ class AgencyModulsForm(forms.ModelForm):
'module_files' : "Dateien", 'module_files' : "Dateien",
'module_organigramm' : "Organigramm", 'module_organigramm' : "Organigramm",
'module_messages' : "Mitteilungen", '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 # NEW USER FORM
class UserNewUserForm(forms.ModelForm): class UserNewUserForm(forms.ModelForm):

View File

@ -40,7 +40,7 @@
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4">
{% for perm in perms %} {% for perm in perms %}
{% if forloop.counter|divisibleby:7 %} {% if forloop.counter|divisibleby:8 %}
</div> </div>
<div class="col-4"> <div class="col-4">
<div class="custom-control custom-checkbox mb-2 {{perm.name}}"> <div class="custom-control custom-checkbox mb-2 {{perm.name}}">

View File

@ -17,7 +17,7 @@
<tr> <tr>
<td>{{formfield.label_tag}}</td> <td>{{formfield.label_tag}}</td>
<td>{{formfield}}</td> <td>{{formfield}}</td>
<td><button type="button" class="btn btn-sm btn-primary" onclick="javascript:$('#modulesettings_{{formfield.name}}').modal('toggle');"><i class="fas fa-cog"></i></button></td> <td>{% if formfield.name == 'module_organigramm' %}<button type="button" class="btn btn-sm btn-primary" onclick="javascript:$('#modulesettings_{{formfield.name}}').modal('toggle');"><i class="fas fa-cog"></i></button>{% endif %}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -26,7 +26,6 @@
</form> </form>
</div> </div>
{% for formfield in modulform %} {% for formfield in modulform %}
<div class="modal fade" id="modulesettings_{{formfield.name}}" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="" aria-hidden="true"> <div class="modal fade" id="modulesettings_{{formfield.name}}" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="" aria-hidden="true">
<div class="modal-dialog " role="document"> <div class="modal-dialog " role="document">

View File

@ -130,6 +130,7 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<input type="hidden" name="form_type" value="profileform">
{% for field in profileform %} {% for field in profileform %}
{% if forloop.counter|divisibleby:6 %} {% if forloop.counter|divisibleby:6 %}
</div> </div>
@ -141,7 +142,6 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<hr> <hr>
{% if newuser == 1 %} {% if newuser == 1 %}
<button type="submit" class="btn btn-primary" style="float: right">Profilerstellung abschließen</button> <button type="submit" class="btn btn-primary" style="float: right">Profilerstellung abschließen</button>
@ -151,17 +151,48 @@
<a class="btn" href="{% url 'dasettings' %} ">Profilbearbeitung abbrechen</a> <a class="btn" href="{% url 'dasettings' %} ">Profilbearbeitung abbrechen</a>
</form> </form>
</div> </div>
<div class="tab-pane fade" id="contract" role="tabpanel" aria-labelledby="contract-tab"> <div class="tab-pane fade" id="contract" role="tabpanel" aria-labelledby="contract-tab">
<div class="col-9 mt-2" style="margin-left: -10px;">
<form method="POST" enctype="multipart/form-data" name="usertime_basic">
{% csrf_token %}
<input type="hidden" name="form_type" value="contract">
<p>Arbeitszeiten&nbsp;<small><i data-toggle="tooltip" data-placement="top" title="Legen Sie fest, an welchen Tagen dieser Mitarbeiter wie viele Stunden arbeitet." class="far fa-question-circle"></i></small></p>
<div class="table-responsive">
<table class="table">
<tr>
<td>{{usertime_form.wd_mo|as_crispy_field}}</td>
<td>{{usertime_form.wd_tu|as_crispy_field}}</td>
<td>{{usertime_form.wd_we|as_crispy_field}}</td>
<td>{{usertime_form.wd_th|as_crispy_field}}</td>
<td>{{usertime_form.wd_fr|as_crispy_field}}</td>
</tr>
</table>
</div>
<hr style="margin-top: -20px;">
<div class="col-5" >
<p>Urlaub&nbsp;<small><i data-toggle="tooltip" data-placement="top" title="Legen Sie fest, wie viel Urlaub dieser Mitarbeiter im Jahr hat." class="far fa-question-circle"></i></small></p>
{{usertime_form.media}}
{{usertime_form.holiday|as_crispy_field}}
{{usertime_form.loose_holidedate|as_crispy_field}}
</div>
<div class="col-12">
<hr>
<button type="submit" class="btn btn-primary" style="float: right">Vertragsdaten aktualisieren</button>
<a class="btn" href="{% url 'dasettings' %} ">Abbrechen</a>
</div>
</form>
</div>
<br />
<br />
<br />
<br />
<br />
<br />
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,14 @@
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect,HttpResponse, JsonResponse 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 import messages
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from users.usersforms import AgencyUpdateForm from users.usersforms import AgencyUpdateForm
from users.models import AgencyJob, AgencyGroup, AgencyNetwork, Agency, AgencyNetworkPreperation from users.models import AgencyJob, AgencyGroup, AgencyNetwork, Agency, AgencyNetworkPreperation
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from users.models import UserTime
import random import random
import string import string
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -501,6 +502,8 @@ def UserProfileUpdate(request, pk, newuser=0):
usertochange.profile.image = request.FILES['image'] usertochange.profile.image = request.FILES['image']
formtosave = False formtosave = False
if(request.POST["form_type"] == "profileform"):
formtosave = UserProfileForm(request.POST, instance=usertochange.profile) formtosave = UserProfileForm(request.POST, instance=usertochange.profile)
if formtosave.is_valid(): if formtosave.is_valid():
@ -521,6 +524,7 @@ def UserProfileUpdate(request, pk, newuser=0):
'user_fullname' : user_fullname, 'user_fullname' : user_fullname,
'first_name' : usertochange.first_name, 'first_name' : usertochange.first_name,
'last_name' : usertochange.last_name, 'last_name' : usertochange.last_name,
'usertime_form' : UserTimeForm(instance=UserTime.objects.get(user=usertochange)),
'newuser' : newuser, 'newuser' : newuser,
'vieweduser' : usertochange.pk, 'vieweduser' : usertochange.pk,
'parentuser' : parentuser, 'parentuser' : parentuser,
@ -530,11 +534,38 @@ def UserProfileUpdate(request, pk, newuser=0):
'usertoparent' : User.objects.filter(profile__agency__pk=usertochange.profile.agency.pk, profile__visible=True) 'usertoparent' : User.objects.filter(profile__agency__pk=usertochange.profile.agency.pk, profile__visible=True)
} }
return render(request, 'dasettings/user_usprof.html', context) 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)
else: else:
context = { context = {
'active_link' : 'dasettings', 'active_link' : 'dasettings',
'user_fullname' : user_fullname, 'user_fullname' : user_fullname,
'usertime_form' : UserTimeForm(instance=UserTime.objects.get(user=usertochange)),
'first_name' : usertochange.first_name, 'first_name' : usertochange.first_name,
'last_name' : usertochange.last_name, 'last_name' : usertochange.last_name,
'newuser' : newuser, 'newuser' : newuser,

View File

@ -72,6 +72,7 @@ INSTALLED_APPS = [
'tasks.apps.TasksConfig', 'tasks.apps.TasksConfig',
'organizer.apps.OrganizerConfig', 'organizer.apps.OrganizerConfig',
'standards.apps.StandardsConfig', 'standards.apps.StandardsConfig',
'timemanagement.apps.TimemanagementConfig',
'news.apps.NewsConfig', 'news.apps.NewsConfig',
'crispy_forms', 'crispy_forms',
'colorful', 'colorful',

View File

@ -42,7 +42,8 @@ urlpatterns = [
path('register/', registerNewAgency, name='register'), path('register/', registerNewAgency, name='register'),
path('register/done', views.registerdone, name='register-done'), path('register/done', views.registerdone, name='register-done'),
path('summernote/', include('django_summernote.urls')), 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) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -100,12 +100,19 @@
</div> </div>
<hr> <hr>
<div id="commentsection"> <div id="commentsection">
<style type="text/css">
.commentimg {
border-radius: 50%;
width: 8%;
}
</style>
{% for comment in comments %} {% for comment in comments %}
{% getcommentstat_user comment.pk request.user as cstat %} {% getcommentstat_user comment.pk request.user as cstat %}
<div id="comment_{{comment.pk}}"> <div id="comment_{{comment.pk}}">
<small>Von {{comment.comment_by.first_name}} {{comment.comment_by.last_name}} am {{comment.last_modified_on|date:"d.m.Y H:i"}}</small><br /> <small>
<img class="commentimg" src="{{ comment.comment_by.profile.get_photo_url }}">
{{comment.comment_by.first_name}} {{comment.comment_by.last_name}} am {{comment.last_modified_on|date:"d.m.Y H:i"}}</small><br />
{{comment.content}}<br/> {{comment.content}}<br/>
<div class="mt-2"> <div class="mt-2">
<button type="button" id="comment_{{comment.pk}}_rate_down" class="btn btn-sm {% if cstat == 0 %} btn-primary {% elif cstat == 'nostat' %} btn-secondary {% endif %}" {% if comment.comment_by == request.user %} disabled="true" {% endif %} onclick="javascript:rateComment({{comment.pk}}, 0)"> <button type="button" id="comment_{{comment.pk}}_rate_down" class="btn btn-sm {% if cstat == 0 %} btn-primary {% elif cstat == 'nostat' %} btn-secondary {% endif %}" {% if comment.comment_by == request.user %} disabled="true" {% endif %} onclick="javascript:rateComment({{comment.pk}}, 0)">

View File

@ -2,9 +2,12 @@ from django import template
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from users.models import AgencyGroup, Agency, AgencyNetwork, AgencyNetworkPreperation from users.models import AgencyGroup, Agency, AgencyNetwork, AgencyNetworkPreperation
from standards.models import Standards, StandardCommentRate, StandardComments from standards.models import Standards, StandardCommentRate, StandardComments
from timemanagement.models import Workday
from message.models import Message from message.models import Message
import os import os
from django.conf import settings from django.conf import settings
from django.utils import timezone
from datetime import date
register = template.Library() register = template.Library()
@ -261,3 +264,96 @@ def isUserInRep(task, area, user_id):
found = True found = True
return found return found
# REALTIME
# Check for active WorkDay
@register.simple_tag
def getactualworkingday(user):
wd = Workday.objects.filter(user=user, agency=user.profile.agency, end=None)
returnstat = 0
if(len(wd) > 0):
returnstat = list(wd)[0].start
return returnstat
# Return formatted Time-String
@register.simple_tag
def getformatedstarttime(user):
wd = list(Workday.objects.filter(user=user, agency=user.profile.agency, end=None))[0]
return wd.start.strftime("%H:%M:%S")
@register.simple_tag
def getformattetstarttime_last_start(user):
today = date.today()
wd = Workday.objects.filter(user=user, agency=user.profile.agency, start__day=today.day).order_by("start")
if(len(wd) == 0):
return ("00:00:00")
else:
return list(wd)[0].start.strftime("%H:%M:%S")
@register.simple_tag
def getformattetstarttime_last_end(user):
today = date.today()
wd = Workday.objects.filter(user=user, agency=user.profile.agency, end__day=today.day).order_by("end")
if(len(wd) == 0):
return ("00:00:00")
else:
return list(wd)[0].end.strftime("%H:%M:%S")
@register.simple_tag
def getactualbreak(user):
wd = Workday.objects.filter(user=user, agency=user.profile.agency, end=None)
returnstat = 0
# ACTIVE WORKING DAY
if(len(wd) > 0):
wd = list(wd)[0]
# BREAK FOUND
if(len(wd.breaks.all()) > 0):
# Check if all Breaks ended
wdbreak = wd.breaks.filter(end=None)
if(len(wdbreak) > 0):
returnstat = True
else:
returnstat = False
return returnstat
@register.simple_tag
def getactualbreakcounter(user):
wd = Workday.objects.filter(user=user, agency=user.profile.agency, end=None)
returnstat = False
# ACTIVE WORKING DAY
if(len(wd) > 0):
wd = list(wd)[0]
if(len(wd.breaks.all()) > 0):
wdbreak = wd.breaks.filter(end=None)
if(len(wdbreak) > 0):
now = timezone.now()
breakstart = list(wdbreak)[0].start
returnstat = (now - breakstart).seconds * 1000
return returnstat
# GET ALL BREAK AS MILLISECOND-RESULT
@register.simple_tag
def getdailybreaktime(user):
wd = list(Workday.objects.filter(user=user, agency=user.profile.agency, end=None))[0]
breaksum = 0
for b in wd.breaks.all():
if(b.end != None):
breaksum += (b.end - b.start).seconds
return breaksum*1000
@register.simple_tag
def getdailybreaktimetoday(user):
today = date.today()
wd = list(Workday.objects.filter(user=user, agency=user.profile.agency, start__day=today.day).order_by("start"))[0]
breaksum = 0
for b in wd.breaks.all():
if(b.end != None):
breaksum += (b.end - b.start).seconds
return breaksum*1000

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
timemanagement/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
timemanagement/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class TimemanagementConfig(AppConfig):
name = 'timemanagement'

View File

@ -0,0 +1,44 @@
# Generated by Django 3.0.4 on 2020-04-26 15:22
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('users', '0022_auto_20200426_1522'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Breaks',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start', models.DateTimeField(blank=True, default=None, null=True)),
('end', models.DateTimeField(blank=True, default=None, null=True)),
('agency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Agency')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Workday',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start', models.DateTimeField(blank=True, default=None, null=True)),
('end', models.DateTimeField(blank=True, default=None, null=True)),
('agency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Agency')),
('breaks', models.ManyToManyField(blank=True, related_name='breaks_at_day', to='timemanagement.Breaks')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='breaks',
name='workday',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workdayele', to='timemanagement.Workday'),
),
]

View File

19
timemanagement/models.py Normal file
View File

@ -0,0 +1,19 @@
from django.db import models
from django.contrib.auth.models import User
from users.models import Agency
# Create your models here.
class Workday(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
breaks = models.ManyToManyField("Breaks", blank=True, related_name='breaks_at_day')
start = models.DateTimeField(default=None, null=True, blank=True)
end = models.DateTimeField(default=None, null=True, blank=True)
class Breaks(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
workday = models.ForeignKey("Workday", on_delete=models.CASCADE, related_name='workdayele')
start = models.DateTimeField(default=None, null=True, blank=True)
end = models.DateTimeField(default=None, null=True, blank=True)

View File

@ -0,0 +1,264 @@
{% load counter_tag %}
<div class="mt-2 ml-2 text-center">
<div id="activeDay" style="display: none;">
<span id="worktime">
<h3>Heutiger Arbeitstag</h3>
<h2 id="realtimeclock">00:00</h2>
<hr>
</span>
<span id="breaktimeclock" style="display: none">
<h3>Aktuelle Pause</h3>
<h2 id="realtimeclock_break">00:00</h2>
<hr>
</span>
<span>
<button onclick="javascript:endBreak()" type="button" id="end_break" class="btn btn-success" style="display: none;"><i class="fas fa-play"></i></button>
<button onclick="javascript:startBreak()" type="button" id="start_break" class="btn btn-primary"><i class="fas fa-pause"></i></button>
<button onclick="javascript:endDay()" type="button" id="end_workday" class="btn btn-secondary">Arbeitstag beenden</button>
</span>
<hr>
</div>
<div>
Arbeitsbeginn: <span id="starttime">00:00:00</span><br />
Arbeitsende: <span id="endtime">00:00:00</span><br />
Pausenzeit: <span id="breaksum">00:00:00</span>
</div>
<div id="start_workday">
<hr>
<button onclick="javascript:startDay()" type="button" id="" class="btn btn-success">Arbeitstag starten</button>
</div>
<hr>
Gleitzeitkonto: +00:13 Stunden
</div>
<script type="text/javascript">
window.setInterval(function(){
if(starttime_view != false && isbreak == false){
realTimeClock();
}
else if(isbreak == true){
realTimeBreakClock();
}
}, 1000);
function convertMS(ms) {
var d, h, m, s;
s = Math.floor(ms / 1000);
m = Math.floor(s / 60);
s = s % 60;
h = Math.floor(m / 60);
m = m % 60;
d = Math.floor(h / 24);
h = h % 24;
h += d * 24;
if(s < 10){
s = "0" + s;
}
if(m < 10){
m = "0" + m;
}
if(h < 10){
h = "0" + h;
}
return h + ':' + m + ':' + s;
}
{% getactualworkingday request.user as starttime %}
//No day info
{% if starttime == 0 %}
var starttime_view = false;
var startbreaktime_view = false;
var isbreak = false;
var breaktime = 0;
{% getformattetstarttime_last_start request.user as formattetstarttime_last_start %}
{% getformattetstarttime_last_end request.user as formattetstarttime_last_end %}
$("#starttime").html("{{formattetstarttime_last_start}}");
$("#endtime").html("{{formattetstarttime_last_end}}");
$("#breaksum").html("00:00:00");
//day info
{% else %}
{% getdailybreaktime request.user as actbreaktime %}
$("#breaksum").html(convertMS({{actbreaktime}}));
var breaktime = {{actbreaktime}};
{% getformatedstarttime request.user as formattetstarttime %}
{% getactualbreak request.user as breakcounter %}
//BREAK CHECKEN!
{% if breakcounter %}
{% getactualbreakcounter request.user as breaktimer %}
var isbreak = true;
var startbreaktime_view = {{breaktimer}};
var starttime_view = Date.parse("{{starttime}}");
$("#start_workday").hide();
$("#activeDay").show();
$("#worktime").hide();
$("#breaktimeclock").show();
$("#end_break").show();
$("#start_break").hide();
$("#starttime").html("{{formattetstarttime}}");
{% else %}
var isbreak = false;
var startbreaktime_view = 0;
var starttime_view = Date.parse("{{starttime}}");
$("#start_workday").hide();
$("#activeDay").show();
$("#starttime").html("{{formattetstarttime}}");
{% endif %}
{% endif %}
function realTimeClock(start=false)
{
if(start != false){
startDate = Date.parse(start);
starttime_view = startDate;
realTimeClock();
}
else{
now = new Date();
viewtime = now - starttime_view - breaktime;
$("#realtimeclock").html(convertMS(viewtime));
}
}
function realTimeBreakClock(){
breaktime = startbreaktime_view + 1000;
startbreaktime_view = startbreaktime_view + 1000;
$("#realtimeclock_break").html(convertMS(startbreaktime_view));
}
function startBreak(){
isbreak = true;
$("#timemanagement_clock").click(function(e){
e.stopPropagation();
})
$("#breaktimeclock").show();
$("#realtimeclock").hide();
$("#end_break").show();
$("#start_break").hide();
$("#worktime").hide();
$.ajax(
{
type: "GET",
url: "{% url 'tm-ajax' %}",
data:{
action : "start_break",
},
success: function( data )
{
startbreaktime_view = -1000;
realTimeBreakClock();
}
});
}
function endBreak(){
isbreak = false;
$("#timemanagement_clock").click(function(e){
e.stopPropagation();
})
$("#breaktimeclock").hide();
$("#realtimeclock").show();
$("#end_break").hide();
$("#start_break").show();
$("#worktime").show();
$.ajax(
{
type: "GET",
url: "{% url 'tm-ajax' %}",
data:{
action : "end_break",
},
success: function( data )
{
breaktime = data["actualbreaktime"];
$("#breaksum").html(convertMS(data["actualbreaktime"]));
}
});
}
function startDay()
{
$("#timemanagement_clock").click(function(e){
e.stopPropagation();
})
$("#start_workday").hide();
$("#activeDay").show();
$("#breaktimeclock").hide();
$.ajax(
{
type: "GET",
url: "{% url 'tm-ajax' %}",
data:{
action : "start_day",
},
success: function( data )
{
if(data["success"]){
$("#starttime").html(data["wd_starttime"])
$("#endtime").html("00:00:00");
$("#breaksum").html("00:00:00");
}
realTimeClock(data["wd_starttime_complete"]);
}
});
}
function endDay()
{
$("#timemanagement_clock").click(function(e){
e.stopPropagation();
})
$.ajax(
{
type: "GET",
url: "{% url 'tm-ajax' %}",
data:{
action : "end_day",
},
success: function( data )
{
if(data["success"]){
$("#breaktimeclock").hide();
$("#realtimeclock").show();
$("#end_break").hide();
$("#start_break").show();
$("#worktime").show();
$("#endtime").html(data["wd_endtime"]);
$("#activeDay").hide();
$("#start_workday").show();
$("#breaksum").html(convertMS(data["actualbreaktime"]));
starttime_view = false;
isbreak = false;
breaktime = 0;
}
realTimeClock(data["wd_starttime_complete"]);
}
});
}
</script>

View File

@ -0,0 +1,42 @@
{% extends "users/base.html" %}
{% block content %}
{% if request.user.profile.agency.module_timemanagement %}
<div class="content-section col-12">
<h3>Zeiterfassung&nbsp;<small><i data-toggle="tooltip" data-placement="top" title="Bearbeiten Sie hier Ihre Zeiterfassung." class="far fa-question-circle"></i></small></h3>
<hr>
<style>
/* DATATABLES */
.paginate_button {
padding: 0px !important;
border: 0px !important;
}
</style>
<script>
$(document).ready(function(){
/*
$('#activenews').DataTable({
"language": {
"search" : "Suche",
"info": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
"lengthMenu": "Zeige _MENU_ Einträge",
"zeroRecords": "Nichts gefunden",
"infoEmpty": "Keine Einträge",
"paginate": {
"first": "Erste",
"last": "Letzte",
"next": "Nächste",
"previous": "Zurück"
},
},
"buttons" : {
"className" : "btn-danger"
}*/
});
</script>
{% else %}
<h3>Das Modul Zeiterfassung wurde in ihrer Agentur deaktiviert.</h3>
{% endif %}
{% endblock content %}

3
timemanagement/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

19
timemanagement/urls.py Normal file
View File

@ -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/<int:id>/', permission_required('users.modulenews')(views.NewsUpdate), name='news-update'),
#path('news/<int:pk>/delete', permission_required('users.modulenews')(NewsDeleteView.as_view()), name='news-delete'),
#path('standard/<int:pk>/changestat', views.StandardChangePublic, name="standard-status"),
#path('news/<int:pk>/single', views.NewsSingle, name="news-single"),
#path('standard/<int:pk>/area', views.StandardArea, name="standard-area"),
#path('standard/<int:pk>/task', views.StandardTask, name="standard-task")
]

78
timemanagement/views.py Normal file
View File

@ -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)

View File

@ -1,12 +1,12 @@
from django.contrib import admin 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 .priomodel import Prio
from standards.models import StandardCommentRate, StandardComments from standards.models import StandardCommentRate, StandardComments
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from message.models import Message from message.models import Message
from cloud.models import DataFile from cloud.models import DataFile
from organizer.models import AGContacts from organizer.models import AGContacts
from timemanagement.models import Workday, Breaks
admin.site.register(StandardComments) admin.site.register(StandardComments)
admin.site.register(StandardCommentRate) admin.site.register(StandardCommentRate)
@ -21,4 +21,7 @@ admin.site.register(AgencyNetwork)
admin.site.register(AGContacts) admin.site.register(AGContacts)
admin.site.register(AgencyNetworkPreperation) admin.site.register(AgencyNetworkPreperation)
admin.site.register(DataFile) admin.site.register(DataFile)
admin.site.register(UserTime)
admin.site.register(Workday)
admin.site.register(Breaks)

View File

@ -88,6 +88,8 @@ class Agency(models.Model):
# MONEY # MONEY
balance = models.FloatField(default=0.0, max_length=9, blank=True) balance = models.FloatField(default=0.0, max_length=9, blank=True)
nextdebiting = models.DateTimeField(default=timezone.now, 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 # MODULEEINSTELLUNGEN FÜR DIE AGENTUR
module_news = models.BooleanField(default=True) module_news = models.BooleanField(default=True)
@ -96,6 +98,10 @@ class Agency(models.Model):
module_organigramm = models.BooleanField(default=True) module_organigramm = models.BooleanField(default=True)
module_messages = 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 # Steckbrief dynamisch aus Standard
dynamicprofile = models.BooleanField(default=True) 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) 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 # Wenn dieses Profil gelöscht wird, wird NICHT die Agency geslöscht
agency = models.ForeignKey(Agency, on_delete=models.PROTECT) 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) compfunc = models.CharField(max_length=60, blank=True)
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
persnumber = models.CharField(default="", max_length=50, blank=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_mail = models.BooleanField(default=True)
user_messages_push = 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): def __str__(self):
return f'{self.user.last_name}' return f'{self.user.last_name}'
@ -240,6 +236,17 @@ class Profile(models.Model):
return "/media/default.jpg" 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'), ('filesmanager', 'Dateien bearbeiten'),
('filedirmanager', 'Ordner bearbeiten'), ('filedirmanager', 'Ordner bearbeiten'),
('filesviewer', 'Dateien lesen'), ('filesviewer', 'Dateien lesen'),
('absencemanager', 'Abwesenheiten verwalten')
] ]
# SUBCLASS # SUBCLASS

View File

@ -13,6 +13,25 @@ import os
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from standards.models import Standards 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 # Deletes all Notifications added to to delete news

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -150,8 +150,6 @@
{%endif%} {%endif%}
{% getmesscounter request.user as gs %} {% getmesscounter request.user as gs %}
<a class="nav-link " href="{% url 'messages' %}" aria-expanded="true"> <a class="nav-link " href="{% url 'messages' %}" aria-expanded="true">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
<span>Mitteilungen <span>Mitteilungen
@ -163,8 +161,18 @@
</li> </li>
{% endif %} {% endif %}
{% if request.user.profile.agency.module_timemanagement %}
{% if active_link == 'timemanagement' %}
<li class="nav-item active">
{% else%}
<li class="nav-item">
{%endif%}
<a class="nav-link " href="{% url 'tm-management' %}" aria-expanded="true">
<i class="far fa-clock"></i>
<span>Zeiterfassung</span>
</a>
</li>
{% endif %}
<!-- Sidebar Toggler (Sidebar) --> <!-- Sidebar Toggler (Sidebar) -->
<!-- <!--
@ -251,9 +259,38 @@
</div> </div>
</div> </div>
</form> </form>
<style type="text/css">
.dropdown-header {
background-color: #5a5c69 !important;
border-color: #5a5c69 !important;
}
</style>
<!-- Topbar Navbar --> <!-- Topbar Navbar -->
<ul class="navbar-nav ml-auto "> <ul class="navbar-nav ml-auto ">
{% if request.user.profile.agency.module_timemanagement %}
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" onclick="" id="timemanagement_realtime" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="far fa-clock"></i>
</a>
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="alertsDropdown" id="timemanagement_clock" name="timemanagement_clock" aria-role="static">
<h6 class="dropdown-header text-white">
Zeiterfassung
</h6>
<div>
{% block timemanagement_content_realtime %}
{% include "timemanagement/realtime_dropdown.html" %}
{% endblock %}
</div>
<a class="dropdown-item text-center small text-gray-500" href="{% url 'tm-management' %}">Zur Zeiterfassung</a>
</div>
</li>
{% endif %}
<!-- ALERT_AREA --> <!-- ALERT_AREA -->
<!-- Nav Item - Alerts --> <!-- Nav Item - Alerts -->

View File

@ -7,7 +7,7 @@ from django.views.generic import CreateView, ListView, UpdateView, DetailView, D
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.db import models from django.db import models
from .models import Profile, Agency from .models import Profile, Agency, UserTime
from django.core.mail import send_mail from django.core.mail import send_mail
from django.http import HttpResponseRedirect,HttpResponse, JsonResponse from django.http import HttpResponseRedirect,HttpResponse, JsonResponse
from areas.models import Areas from areas.models import Areas
@ -94,6 +94,13 @@ def toUpdate(request):
else: else:
print("default groups existing") print("default groups existing")
# CHECK FOR ALL POSSIBLE RIGHTS ON ADMINGROUP
ag_admingroup = list(AgencyGroup.objects.filter(agency=request.user.profile.agency, is_admin=True))[0]
perms = AgencyGroup._meta.permissions
for p in perms:
tempperm = Permission.objects.get(codename=p[0])
ag_admingroup.group.permissions.add(tempperm)
# INITIAL ROOT DIR # INITIAL ROOT DIR
rootdir = DataDir.objects.filter(is_root=True, agency__pk=request.user.profile.agency.pk) rootdir = DataDir.objects.filter(is_root=True, agency__pk=request.user.profile.agency.pk)
@ -133,6 +140,15 @@ def toUpdate(request):
a.group.permissions.add(Permission.objects.get(codename="moduleorganizer")) a.group.permissions.add(Permission.objects.get(codename="moduleorganizer"))
a.group.permissions.add(Permission.objects.get(codename="agencynetwork")) a.group.permissions.add(Permission.objects.get(codename="agencynetwork"))
# USER TIME MODEL
usersofagency = User.objects.filter(profile__agency=request.user.profile.agency)
for u in usersofagency:
if(len(UserTime.objects.filter(user=u)) == 0):
usertime_new = UserTime(user=u)
usertime_new.save()
''' '''
DASHBOARD-View DASHBOARD-View