First
This commit is contained in:
commit
d30cb95ed1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AdmConfig(AppConfig):
|
||||||
|
name = 'adm'
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
from django import forms
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from users.models import AgencyBills
|
||||||
|
from timemanagement.models import Absence, AbsenceReason, FreeDays, Workday, Breaks
|
||||||
|
from bootstrap_datepicker_plus import DatePickerInput
|
||||||
|
|
||||||
|
class AgencyBillForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = AgencyBills
|
||||||
|
fields = ['agency', 'start']
|
||||||
|
labels = {
|
||||||
|
'agency' : "Agentur",
|
||||||
|
'start' : "Leistungszeitraum Start",
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdmWorkdayForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Workday
|
||||||
|
labels = {
|
||||||
|
"start" : "Start",
|
||||||
|
"end" : "Ende",
|
||||||
|
"target" : "Zielarbeitszeit",
|
||||||
|
"freefield" : "Notiz"
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"start", "end", "target", "freefield"
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
'start': DatePickerInput(options={"format":'DD.MM.YYYY HH:mm', "locale":'de'}),
|
||||||
|
'end': DatePickerInput(options={"format":'DD.MM.YYYY HH:mm', "locale":'de'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
# ADD BREAK FORM
|
||||||
|
class AdmBreakAddForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Breaks
|
||||||
|
labels = {
|
||||||
|
"start" : "Start",
|
||||||
|
"end" : "Ende"
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
"start", "end"
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
'start': DatePickerInput(options={"format":'DD.MM.YYYY HH:mm', "locale":'de'}),
|
||||||
|
'end': DatePickerInput(options={"format":'DD.MM.YYYY HH:mm', "locale":'de'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *arg, **kwargs):
|
||||||
|
super(AdmBreakAddForm, self).__init__(*arg, **kwargs)
|
||||||
|
self.fields['start'].required = True
|
||||||
|
self.fields['end'].required = True
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 3.0 on 2020-10-09 17:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MainStatistic',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('agencys', models.IntegerField(default=0)),
|
||||||
|
('users', models.IntegerField(default=0)),
|
||||||
|
('standards', models.IntegerField(default=0)),
|
||||||
|
('chatmessages', models.IntegerField(default=0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 3.0 on 2020-10-09 17:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='staticdate',
|
||||||
|
field=models.DateField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Generated by Django 3.0 on 2021-01-28 09:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0002_mainstatistic_staticdate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='absenceobjects',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='active_abos',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='agency_activerecover',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='agency_recoverobjects',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='allfiles',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='allfiles_storage',
|
||||||
|
field=models.FloatField(default=0.0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='logins',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='organizerobjects',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='user_active_timemanagement',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 3.0 on 2021-01-28 11:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0003_auto_20210128_0947'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MainSalesMonth',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('salesmonthdate', models.DateField(default=django.utils.timezone.now)),
|
||||||
|
('value', models.FloatField(default=0.0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0 on 2021-02-19 10:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0004_mainsalesmonth'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
name='mra',
|
||||||
|
field=models.FloatField(default=0.0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0 on 2021-02-19 10:25
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0005_mainstatistic_mra'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='mainstatistic',
|
||||||
|
old_name='mra',
|
||||||
|
new_name='mrr',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.0 on 2021-03-26 11:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('adm', '0006_auto_20210219_1025'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AGBLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('entry', models.CharField(default='', max_length=5000)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,28 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
# Create your models here.
|
||||||
|
# MAIN RECOVERDIR PASSWORD AND CONFIG
|
||||||
|
class MainStatistic(models.Model):
|
||||||
|
staticdate = models.DateField(default=timezone.now)
|
||||||
|
agencys = models.IntegerField(default=0)
|
||||||
|
users = models.IntegerField(default=0)
|
||||||
|
standards = models.IntegerField(default=0)
|
||||||
|
chatmessages = models.IntegerField(default=0)
|
||||||
|
active_abos = models.IntegerField(default=0)
|
||||||
|
absenceobjects = models.IntegerField(default=0)
|
||||||
|
user_active_timemanagement = models.IntegerField(default=0)
|
||||||
|
organizerobjects = models.IntegerField(default=0)
|
||||||
|
agency_activerecover = models.IntegerField(default=0)
|
||||||
|
agency_recoverobjects = models.IntegerField(default=0)
|
||||||
|
allfiles = models.IntegerField(default=0)
|
||||||
|
allfiles_storage = models.FloatField(default=0.0)
|
||||||
|
logins = models.IntegerField(default=0)
|
||||||
|
mrr = models.FloatField(default=0.0)
|
||||||
|
|
||||||
|
class MainSalesMonth(models.Model):
|
||||||
|
salesmonthdate = models.DateField(default=timezone.now)
|
||||||
|
value = models.FloatField(default=0.0)
|
||||||
|
|
||||||
|
|
||||||
|
class AGBLog(models.Model):
|
||||||
|
entry = models.CharField(default="", max_length=5000)
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
{% load mathfilters %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load counter_tag %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Rechnung manuell erstellen</h4>
|
||||||
|
<hr>
|
||||||
|
Achtung! Sie sind in Begriff, eine Rechnung manuell zu erstellen! Achten Sie auf die korrekte Agentur sowie Start- und Endtermin! Prüfen Sie vor Anlagen Ihre Eingaben!
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.media}}
|
||||||
|
{{form|crispy}}
|
||||||
|
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="confirmDel" onclick="javascript:toggleEntryOkButton()">
|
||||||
|
<label class="form-check-label" for="confirmDel">
|
||||||
|
Bestätigung, dass die Rechnungsinformationenx korrekt sind.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<a class="btn btn-secondary" href="{% url 'adm-agencys' %}" name="action" disabled="true">Abbrechen</a>
|
||||||
|
<button style="float: right" class="btn btn-primary" type="submit" name="action" disabled="true" id="doaddbill"><b>Rechnung inkl. PDF-Generierung erstellen und an Agentur schicken.</button>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function toggleEntryOkButton(){
|
||||||
|
if($('#confirmDel').prop('checked')){
|
||||||
|
$('#doaddbill').attr('disabled', false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#doaddbill').attr('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
{% load mathfilters %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load counter_tag %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Agentur löschen
|
||||||
|
<span style="float: right">
|
||||||
|
<a style="float: right" class="btn btn-secondary btn-sm ml-2" href="{% url 'adm-agencys' %}"><small><i class="fas fa-chevron-circle-left"></i></small></a>
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
<hr>
|
||||||
|
Sie sind im Begriff, die Agentur <b>{{object.name}}</b> zu löschen! Damit werden alle Daten der Agentur unwiderruflich entfernt. Möchten Sie fortfahren?
|
||||||
|
<hr>
|
||||||
|
Bitte bestätigen!
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="confirmDel" onclick="javascript:toggleDoDelBtn()">
|
||||||
|
<label class="form-check-label" for="confirmDel">
|
||||||
|
Ja, Agentur <b>{{object.name}}</b> wirklich und für immer löschen!
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<a class="btn btn-secondary" href="{% url 'adm-agencys' %}" name="action" disabled="true">Abbrechen</a>
|
||||||
|
<button style="float: right" class="btn btn-primary" type="submit" name="action" disabled="true" id="dodel"><b>Agentur <b>{{object.name}}</b> unwiderruflich löschen</button>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function toggleDoDelBtn(){
|
||||||
|
if($('#confirmDel').prop('checked')){
|
||||||
|
$('#dodel').attr('disabled', false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#dodel').attr('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
{% getAgencyData agency as agdata %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Agentur {{agency.name}}
|
||||||
|
|
||||||
|
<span style="float: right">
|
||||||
|
<a style="float: right" class="btn btn-secondary btn-sm ml-2" href="{% url 'adm-agency-delete' agency.pk %}"><small><i class="fas fa-trash"></i></small></a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</h4>
|
||||||
|
<hr>
|
||||||
|
<h5>Daten der Agentur</h5>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="min-width: 180px">Registriert am</td>
|
||||||
|
<td>{{agency.registerdate|date:"d.m.Y"}}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Nutzer</td>
|
||||||
|
<td>{{agdata.0}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Standards</td>
|
||||||
|
<td>{{agdata.1}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Bezahlstatus</td>
|
||||||
|
<td>{% if agency.paymentstatus == 0 %} Normal {% else %}Kostenlos{% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr>
|
||||||
|
<h5>Rechnungen (letzten drei)</h5>
|
||||||
|
{% if bills|length == 0 %}
|
||||||
|
Es liegen keine Rechnungen vor.
|
||||||
|
{% else %}
|
||||||
|
<table style="width: 70%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Rechnungs-Nr.</td>
|
||||||
|
<td>Datum</td>
|
||||||
|
<td>Status</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bill in bills %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'ag-getbillpdf' bill.pk %}" target="_blank">{{bill.billnumber}}</a></td>
|
||||||
|
<td>{{bill.billdate|date:"d.m.Y"}}</td>
|
||||||
|
<td>
|
||||||
|
{% if bill.billstatus == "open" %} <i class="far fa-times-circle" style="color: red"></i> {% elif bill.billstatus == "paid" %} <i class="far fa-check-circle" style="color: green"></i> {% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<h5>Nutzerübersicht ({{agdata.0}})</h5>
|
||||||
|
<table class="table table-hover" id="agdata" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Vorname</th>
|
||||||
|
<th scope="col">Nachname</th>
|
||||||
|
<th scope="col">Funktion</th>
|
||||||
|
<th scope="col">Letzter Login</th>
|
||||||
|
<th scope="col">Organ. Sichtbar</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ele in users_of_agency %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'adm-user-single' ele.pk %}">{{ele.first_name}}</a></td>
|
||||||
|
<td><a href="{% url 'adm-user-single' ele.pk %}">{{ele.last_name}}</a></td>
|
||||||
|
<td>{{ele.profile.func|default:""}}</td>
|
||||||
|
<td data-sort='{{ele.last_login|date:"U"|default:""}}'>{{ele.last_login|date:"d.m.Y, H:i"|default:""}}</td>
|
||||||
|
<td>{% if ele.profile.visible %} Sichtbar {% else %} Nicht sichtbar {% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#agdata').DataTable({
|
||||||
|
order: [3, 'desc'],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Agenturübersicht</h4>
|
||||||
|
{% loadAboCount as abocount %}
|
||||||
|
<small>Aktive Abos: {{abocount}}</small>
|
||||||
|
<hr>
|
||||||
|
<table class="table table-hover" id="agdata" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Agenturname</th>
|
||||||
|
<th scope="col">Registriert am</th>
|
||||||
|
<th scope="col">Mitarbeiter</th>
|
||||||
|
<th scope="col">Standards</th>
|
||||||
|
<th scope="col">Abo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ele in agencys %}
|
||||||
|
{% getAgencyData ele as agdata %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'adm-agency-single' ele.pk %}">{{ele.name}}</a></td>
|
||||||
|
<td data-sort='{{ele.registerdate|date:"U"}}'>{{ele.registerdate}}</td>
|
||||||
|
<td>{{agdata.0}}</td>
|
||||||
|
<td>{{agdata.1}}</td>
|
||||||
|
<td>
|
||||||
|
{% if ele.paymentplan == 1 %}
|
||||||
|
Aktiv
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#agdata').DataTable({
|
||||||
|
order: [1, 'desc'],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
TASK: Quota-Ansicht hier mit einbauen
|
||||||
|
|
||||||
|
-->
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,724 @@
|
||||||
|
{% load static %}
|
||||||
|
{% load counter_tag %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="author" content="">
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="{% static 'users/img/favicon_neu.ico' %}">
|
||||||
|
<title>Digitale Agentur - Administrativer Bereich</title>
|
||||||
|
|
||||||
|
<!-- Custom fonts for this template-->
|
||||||
|
|
||||||
|
<link rel="canonical" href="https://app.digitale-agentur.com">
|
||||||
|
<!--<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.js" type="text/javascript"></script>-->
|
||||||
|
<script src="{%static 'users/js/jquery.js' %}" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<link href="{%static 'users/vendor/fontawesome-free/css/all.min.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
<!--<link href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i" rel="stylesheet">-->
|
||||||
|
<link href="{%static 'users/css/google_font.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
|
||||||
|
<!--<link href="{%static 'users/css/bootstrap.min.css' %}" rel="stylesheet">-->
|
||||||
|
<!--<link href='https://fonts.googleapis.com/css?family=Roboto&display=swap' rel='stylesheet' type='text/css'>-->
|
||||||
|
<link href="{%static 'users/css/google_swap.css' %}" rel="stylesheet" type="text/css">
|
||||||
|
<!-- include summernote css/js -->
|
||||||
|
<!--<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.15/dist/summernote.min.js"></script>-->
|
||||||
|
|
||||||
|
<!--<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.1/jquery-ui.min.js"></script>-->
|
||||||
|
<script src="{%static 'users/js/jquery_ui_min.js' %}" type="text/javascript"></script>
|
||||||
|
<!-- <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>-->
|
||||||
|
<!-- CROPPER -->
|
||||||
|
<link href="{% static 'users/css/cropper.min.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- DATATABLES -->
|
||||||
|
<!--<link href="https://cdn.datatables.net/1.10.20/css/jquery.dataTables.min.css" type="text/css" rel="stylesheet">-->
|
||||||
|
<link href="{% static 'users/css/jquery_datatables.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
<!--<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" type="text/css" rel="stylesheet">-->
|
||||||
|
<link href="{% static 'users/css/datatables_bs4.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom styles for this template-->
|
||||||
|
<link href="{% static 'users/css/sb-admin-2.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
|
||||||
|
<link href="{% static 'users/css/theme.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
|
||||||
|
|
||||||
|
<script src="{%static 'users/js/bs4_summernote.js' %}" type="text/javascript"></script>
|
||||||
|
<!--<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/summernote@0.8.16/dist/summernote-bs4.js"></script>-->
|
||||||
|
<link href="{% static 'users/css/bs4_summernote.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
<!--<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.16/dist/summernote.css" rel="stylesheet" type="text/css">-->
|
||||||
|
<script type="text/javascript" src="{% static 'summernote/lang/summernote-de-DE.min.js' %}"></script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Page Wrapper -->
|
||||||
|
<div id="wrapper" >
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<ul class=" bg-gray-900 sidebar sidebar-dark accordion fixed-top " style="overflow: all; height: 100vh;"id="accordionSidebar">
|
||||||
|
|
||||||
|
<!-- Sidebar - Brand -->
|
||||||
|
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="{% url 'users-dashboard' %}">
|
||||||
|
<!--<i class="fas fa-laptop"></i>
|
||||||
|
<div class="sidebar-brand-text mx-2" style="">Digitale Agentur</div>-->
|
||||||
|
<img src="{% static 'users/img/logo_neu.png' %}" width="100%">
|
||||||
|
</a>
|
||||||
|
<!-- Divider -->
|
||||||
|
<hr class="sidebar-divider my-0">
|
||||||
|
|
||||||
|
<!-- Nav Item - Dashboard -->
|
||||||
|
{% if active_link == 'adm-statistic' %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{%endif%}
|
||||||
|
<a class="nav-link" href="{% url 'adm-main' %}">
|
||||||
|
<i class="fas fa-chart-bar"></i>
|
||||||
|
<span>Statistik</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Dashboard -->
|
||||||
|
{% if active_link == 'adm-agencys' %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{%endif%}
|
||||||
|
<a class="nav-link" href="{% url 'adm-agencys' %}">
|
||||||
|
<i class="fas fa-user-friends"></i>
|
||||||
|
<span>Agenturen</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% if active_link == 'adm-bills' %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{%endif%}
|
||||||
|
<a class="nav-link" href="{% url 'adm-bills' %}">
|
||||||
|
<i class="fas fa-file-invoice-dollar"></i>
|
||||||
|
<span>Rechnungen</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Nav Item - Dashboard -->
|
||||||
|
{% if active_link == 'adm-users' %}
|
||||||
|
<li class="nav-item active">
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{%endif%}
|
||||||
|
<a class="nav-link" href="{% url 'adm-users' %}">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<span>Benutzer</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<hr class="sidebar-divider my-0">
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'users-dashboard' %}">
|
||||||
|
<i class="fas fa-laptop"></i>
|
||||||
|
<span>Zur Digitalen Agentur</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Sidebar Toggler (Sidebar) -->
|
||||||
|
<!--
|
||||||
|
<div class="text-center d-none d-md-inline">
|
||||||
|
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<!-- End of Sidebar -->
|
||||||
|
|
||||||
|
<!-- Content Wrapper -->
|
||||||
|
<style scoped>
|
||||||
|
.sidebar{
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
height : 100%; or
|
||||||
|
height : 100vh; or
|
||||||
|
height : 450px;
|
||||||
|
}
|
||||||
|
#content-wrapper {
|
||||||
|
margin-left: 212px;
|
||||||
|
margin-top: -72px;
|
||||||
|
}
|
||||||
|
/* MARGIN TOP FOR FIREFOX */
|
||||||
|
@-moz-document url-prefix() {
|
||||||
|
#content-wrapper {
|
||||||
|
margin-left: 212px;
|
||||||
|
margin-top: -158px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MARGIN FOR IOS/SAFARI 10+ and 6+ */
|
||||||
|
@media not all and (min-resolution:.001dpcm) {
|
||||||
|
@media {
|
||||||
|
#content-wrapper {
|
||||||
|
margin-left: 212px;
|
||||||
|
margin-top: -158px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-color-index:0) and(-webkit-min-device-pixel-ratio:0) {
|
||||||
|
@media {
|
||||||
|
#content-wrapper {
|
||||||
|
margin-left: 212px;
|
||||||
|
margin-top: -158px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@media screen and (max-width: 768px) {
|
||||||
|
#content-wrapper {
|
||||||
|
margin-left: 0px !important;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
</style>
|
||||||
|
<div id="content-wrapper">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<!-- Topbar -->
|
||||||
|
<nav id="topnavbarmain" class="navbar navbar-expand navbar-light bg-white topbar fixed-top mb-4 static-top shadow" style="margin-left: 224px;">
|
||||||
|
<!-- Sidebar Toggle (Topbar) -->
|
||||||
|
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3" onclick="javascript:toggleSidebar()">
|
||||||
|
<i class="fa fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
<b>Mitarbeiterbereich</b>
|
||||||
|
<!-- Topbar Search -->
|
||||||
|
<!-- <form class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
|
||||||
|
<div class="input-group">
|
||||||
|
<input list="searchres" placeholder="Agenturweite Suche..." id="search_string" onkeyup="javascript:startSearch(this.value)" class="form-control bg-light border-0 small" >
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<input type="text" onkeyup="javascript:startSearch(this.value)" class="form-control bg-light border-0 small" placeholder="Suche..." aria-label="Suche" aria-describedby="basic-addon2" id="searchfield">
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-primary" type="button" onclick="javascript:clearSF()">
|
||||||
|
<i class="fas fa-times fa-sm"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form> -->
|
||||||
|
<style type="text/css">
|
||||||
|
.dropdown-header {
|
||||||
|
background-color: #5a5c69 !important;
|
||||||
|
border-color: #5a5c69 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Topbar Navbar -->
|
||||||
|
<ul class="navbar-nav ml-auto ">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ALERT_AREA -->
|
||||||
|
<!-- Nav Item - Alerts -->
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
{% getonlinestatuscolor request.user as onlinecolor %}
|
||||||
|
.roundimg_base {
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-box-shadow: 0px 0px 4px 5px {{onlinecolor}};
|
||||||
|
-moz-box-shadow: 0px 0px 4px 5px {{onlinecolor}};
|
||||||
|
box-shadow: 0px 0px 4px 5px {{onlinecolor}};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="topbar-divider d-none d-sm-block"></div>
|
||||||
|
<!-- Nav Item - User Information -->
|
||||||
|
<li class="nav-item dropdown no-arrow">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="mr-2 d-none d-lg-inline text-gray-600 small">{{request.user.first_name}} {{request.user.last_name}}
|
||||||
|
</span>
|
||||||
|
<img id="userbaseprofilepicture" class="img-profile roundimg_base ml-2" src="{{ user.profile.get_photo_url }}">
|
||||||
|
</a>
|
||||||
|
<!-- Dropdown - User Information -->
|
||||||
|
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
|
||||||
|
<a class="dropdown-item" onclick="javascript:changeOnlineStatus(0)" href="#/">
|
||||||
|
<i class="fas fa-circle mr-2" style="color: green"></i>
|
||||||
|
Online
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" onclick="javascript:changeOnlineStatus(1)" href="#/">
|
||||||
|
<i class="fas fa-circle mr-2" style="color: red"></i>
|
||||||
|
Beschäftigt
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" onclick="javascript:changeOnlineStatus(2)" href="#/">
|
||||||
|
<i class="fas fa-circle mr-2" style="color: yellow"></i>
|
||||||
|
Abwesend
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" onclick="javascript:changeOnlineStatus(3)" href="#/">
|
||||||
|
<i class="fas fa-circle mr-2" style="color: grey"></i>
|
||||||
|
Offline anzeigen
|
||||||
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<!--<a class="dropdown-item" onclick="userGoToSettings({{user.pk}})" href="{% url 'orga-single' user.pk %}">-->
|
||||||
|
<a class="dropdown-item" onclick="userGoToSettings()" href="#/">
|
||||||
|
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Einstellungen
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item" onclick="userGoToNotification()" href="#/">
|
||||||
|
<i class="fas fa-bell fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Benachrichtigungen
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item" href="{% url 'users-logout' %}">
|
||||||
|
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||||
|
Abmelden
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- End of Topbar -->
|
||||||
|
<!-- Begin Page Content -->
|
||||||
|
<div class="container-fluid" >
|
||||||
|
|
||||||
|
|
||||||
|
<div id="maincontent" style="min-height: 100%; margin-top: 85px;" >
|
||||||
|
<!-- MESSAGES -->
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show col-6" role="alert" id="message_{{forloop.counter}}">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div style="height: 300px"> </div>
|
||||||
|
</div> <!-- End of Main Content CONTAINER FLUID-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- CHAT BUTTON -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- End of Page Wrapper -->
|
||||||
|
<!--
|
||||||
|
<footer class="sticky-footer bg-white" style="width: 86.2%;position: absolute;
|
||||||
|
bottom: 0; margin-top: 80px; padding-top: 20px; padding-bottom: 20px;
|
||||||
|
">
|
||||||
|
<div class="container my-auto">
|
||||||
|
<div class="copyright text-center my-auto">
|
||||||
|
<span>Copyright © digitale-agentur.com für <b>{{ user.profile.agency.name }}</b></span><br /><small>Version 0.0.5</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
-->
|
||||||
|
<!-- Scroll to Top Button-->
|
||||||
|
<a class="scroll-to-top rounded" href="#page-top">
|
||||||
|
<i class="fas fa-angle-up"></i>
|
||||||
|
</a>
|
||||||
|
<!-- Bootstrap core JavaScript-->
|
||||||
|
<!--<script src="{%static 'users/vendor/jquery/jquery.min.js' %}"></script>-->
|
||||||
|
<script type="text/javascript" src="{%static 'users/vendor/bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||||
|
<!-- Core plugin JavaScript-->
|
||||||
|
<script type="text/javascript" src="{%static 'users/vendor/jquery-easing/jquery.easing.min.js' %}"></script>
|
||||||
|
<!-- DATABLES JS -->
|
||||||
|
<!--<script type="text/javascript" src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>-->
|
||||||
|
<script type="text/javascript" src="{%static 'users/js/jquery_dataTables.min.js' %}"></script>
|
||||||
|
<!--<script type="text/javascript" src="https://cdn.datatables.net/1.10.20/js/dataTables.bootstrap4.min.js"></script>-->
|
||||||
|
<script type="text/javascript" src="{%static 'users/js/bs4_dt.js' %}"></script>
|
||||||
|
|
||||||
|
<!-- Custom scripts for all pages-->
|
||||||
|
<script type="text/javascript" src="{%static 'users/js/sb-admin-2.js' %}"></script>
|
||||||
|
<!-- CUSTOM FONT -->
|
||||||
|
<!--<link href="{% static 'users/css/custom.css' %}" rel="stylesheet">-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- TABLE SORT -->
|
||||||
|
<!--<script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>-->
|
||||||
|
|
||||||
|
<!-- Page level plugins -->
|
||||||
|
<!--<script src="vendor/chart.js/Chart.min.js"></script>-->
|
||||||
|
|
||||||
|
<!-- Page level custom scripts -->
|
||||||
|
<!--<script src="js/demo/chart-area-demo.js"></script>-->
|
||||||
|
<!--<script src="js/demo/chart-pie-demo.js"></script>-->
|
||||||
|
|
||||||
|
|
||||||
|
<link href="{% static 'users/css/custom.css' %}" type="text/css" rel="stylesheet">
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
{% if request.user.profile.showtooltips %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function userGoToSettings(){
|
||||||
|
localStorage.setItem('activeTabSettings', "profil");
|
||||||
|
location.href = "{% url 'dasettings' %}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function userGoToNotification(){
|
||||||
|
localStorage.setItem('activeTabSettings', "notifications");
|
||||||
|
location.href = "{% url 'dasettings' %}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSF(){
|
||||||
|
$("#searchcontent").empty();
|
||||||
|
$("#searchcontent").hide();
|
||||||
|
$("#maincontent").show();
|
||||||
|
$("#search_string").val("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSearch(searchstring){
|
||||||
|
if(searchstring.length > 2){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/dashboard/globalsearch",
|
||||||
|
data:{
|
||||||
|
searchstring: searchstring
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
$("#maincontent").hide();
|
||||||
|
$("#searchcontent").show();
|
||||||
|
$("#searchcontent").html(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$("#searchcontent").empty();
|
||||||
|
$("#searchcontent").hide();
|
||||||
|
$("#maincontent").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
if($( window ).width() < 768)
|
||||||
|
{
|
||||||
|
$("#accordionSidebar").addClass("toggled");
|
||||||
|
$("#content-wrapper").css("margin-left" , "0px");
|
||||||
|
|
||||||
|
$("#topnavbarmain").css("margin-left" , "0px");
|
||||||
|
|
||||||
|
sidebar_hidden = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle the side navigation
|
||||||
|
function toggleSidebar(){
|
||||||
|
|
||||||
|
if(sidebar_hidden == false){
|
||||||
|
$("#accordionSidebar").addClass("toggled");
|
||||||
|
$("#content-wrapper").css("margin-left" , "105px");
|
||||||
|
|
||||||
|
$("#topnavbarmain").css("margin-left" , "105px");
|
||||||
|
|
||||||
|
sidebar_hidden = true;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#accordionSidebar").removeClass("toggled");
|
||||||
|
$("#content-wrapper").css("margin-left" , "0px");
|
||||||
|
$("#topnavbarmain").css("margin-left" , "0px");
|
||||||
|
sidebar_hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$( window ).resize(function() {
|
||||||
|
if($( window ).width() < 750)
|
||||||
|
{
|
||||||
|
$("#accordionSidebar").addClass("toggled");
|
||||||
|
$("#content-wrapper").css("margin-left" , "0px");
|
||||||
|
$("#topnavbarmain").css("margin-left" , "0px");
|
||||||
|
sidebar_hidden = false;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#accordionSidebar").removeClass("toggled");
|
||||||
|
$("#content-wrapper").css("margin-left" , "212px");
|
||||||
|
|
||||||
|
$("#topnavbarmain").css("margin-left" , "224px");
|
||||||
|
sidebar_hidden = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
AJAX CALL FOR NOTIFICATIONS
|
||||||
|
|
||||||
|
*/
|
||||||
|
newunknownotificationscounter = 0;
|
||||||
|
function loadUnsendNotifications(){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/notifications/checknotifications",
|
||||||
|
data : {
|
||||||
|
action : "checknotifications"
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
$("#notification_items").html("");
|
||||||
|
notifications = data['unknownnotification'];
|
||||||
|
var i = 0;
|
||||||
|
for (var key in notifications) {
|
||||||
|
|
||||||
|
$("#notification_items").append('<span><a href="/'+notifications[i]['elelink']+'" id="notifyid_'+notifications[i]['not_id']+'" class="dropdown-item d-flex align-items-center"><div><div class="small text-gray-500">'+notifications[i]['date']+'</div>'+notifications[i]['text']+'</div></a></span>')
|
||||||
|
i = i + 1;
|
||||||
|
newunknownotificationscounter = newunknownotificationscounter + 1;
|
||||||
|
}
|
||||||
|
$("#notificationcounter").html("");
|
||||||
|
|
||||||
|
if(i > 0){$("#notificationcounter").html(i);}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadUnviewnNotifications(){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/notifications/getnotifications",
|
||||||
|
data : {
|
||||||
|
action : "oldnotifications"
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
notifications = data['oldnotifications'];
|
||||||
|
i = 0;
|
||||||
|
$("#notification_items").html("");
|
||||||
|
for (var key in notifications) {
|
||||||
|
if(newunknownotificationscounter <= 5){
|
||||||
|
$("#notification_items").append('<a href="/'+notifications[i]['elelink']+'" id="notifyid_'+notifications[i]['not_id']+'" class="dropdown-item d-flex align-items-center"><div><div class="small text-gray-500">'+notifications[i]['date']+'</div>'+notifications[i]['text']+'</div></a>')
|
||||||
|
i = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeNewNotToViewed(){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/notifications/newnotificationsviewed",
|
||||||
|
data : {
|
||||||
|
action : "newnotificationsviewed"
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
$("#notificationcounter").html("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeNotification(notifyid){
|
||||||
|
//$("#notifyid_" + notifyid).remove()
|
||||||
|
//$("#allnotificationsarea").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(document).on('click', function (e) {
|
||||||
|
|
||||||
|
if(e.target["id"] != 'chatButton'){
|
||||||
|
if ($(e.target).closest("#chat_alluserscontent").length === 0) {
|
||||||
|
$("#chat_alluserscontent").fadeOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- WEBSOCKETS -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
$("#chat_alluserscontent").hide();
|
||||||
|
|
||||||
|
ws_string = 'wss://'
|
||||||
|
if (location.protocol !== 'https:') {
|
||||||
|
ws_string = 'ws://'
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainwebsocket = new WebSocket(ws_string+window.location.host+"/ws/")
|
||||||
|
|
||||||
|
mainwebsocket.onmessage = function(e) {
|
||||||
|
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
|
||||||
|
if(e["data"] != "presence_update")
|
||||||
|
{
|
||||||
|
//HANDLER FOR ALL PUSHNOTIFICATIONS
|
||||||
|
if(e["data"].split("__")[0] == "pushnotification"){
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check for Chat-Message in CHatview or invisible-Browser
|
||||||
|
*/
|
||||||
|
tempsplit = e["data"].split("__");
|
||||||
|
finalsplit = tempsplit[1].split(" ");
|
||||||
|
|
||||||
|
if(finalsplit[0] != "Chat"){
|
||||||
|
var notify = new Notification('Digitale Agentur', {
|
||||||
|
body: e["data"].split("__")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
{% if active_link == "chat" %}
|
||||||
|
chatopen= true;
|
||||||
|
{% else %}
|
||||||
|
chatopen= false;
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
windowvisible = false;
|
||||||
|
//Get Minimized-Window-Status to show up chat message, when window is in chat, but not focused by user
|
||||||
|
|
||||||
|
if(!document.hasFocus()){
|
||||||
|
var notify = new Notification('Digitale Agentur', {
|
||||||
|
body: e["data"].split("__")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(chatopen == false){
|
||||||
|
if($("#dynamicchatwindow").is(":visible")){
|
||||||
|
console.log("user in chat...")
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var notify = new Notification('Digitale Agentur', {
|
||||||
|
body: e["data"].split("__")[1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
loadUnsendNotifications();
|
||||||
|
loadUnviewnNotifications();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
{% if active_link == "chat" %}
|
||||||
|
if(preventUpdatePresLive == false){
|
||||||
|
updatePresenceLive();
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mainwebsocket.onclose = function(e) {
|
||||||
|
console.error('Chat socket closed unexpectedly');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//HEARTBEAT every minute
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
mainwebsocket.send(JSON.stringify("heartbeat"));
|
||||||
|
console.log("heartbeat is alive...");
|
||||||
|
},60000);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function changeOnlineStatus(newstat){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "{% url 'users-updateonlinestat' %}",
|
||||||
|
data : {
|
||||||
|
newstat : newstat
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
if(data["newstat"] == "0"){
|
||||||
|
$("#userbaseprofilepicture").css({
|
||||||
|
'-webkit-box-shadow' : '0px 0px 4px 5px green',
|
||||||
|
'-moz-box-shadow': '0px 0px 4px 5px green',
|
||||||
|
'box-shadow': '0px 0px 4px 5px green'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if(data["newstat"] == "1"){
|
||||||
|
$("#userbaseprofilepicture").css({
|
||||||
|
'-webkit-box-shadow' : '0px 0px 4px 5px red',
|
||||||
|
'-moz-box-shadow': '0px 0px 4px 5px red',
|
||||||
|
'box-shadow': '0px 0px 4px 5px red'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if(data["newstat"] == "2"){
|
||||||
|
$("#userbaseprofilepicture").css({
|
||||||
|
'-webkit-box-shadow' : '0px 0px 4px 5px orange',
|
||||||
|
'-moz-box-shadow': '0px 0px 4px 5px orange',
|
||||||
|
'box-shadow': '0px 0px 4px 5px orange'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if(data["newstat"] == "3"){
|
||||||
|
$("#userbaseprofilepicture").css({
|
||||||
|
'-webkit-box-shadow' : '0px 0px 4px 5px grey',
|
||||||
|
'-moz-box-shadow': '0px 0px 4px 5px grey',
|
||||||
|
'box-shadow': '0px 0px 4px 5px grey'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onerror = function (msg, url, line) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.Notification) {
|
||||||
|
console.log('Browser does not support notifications.');
|
||||||
|
} else {
|
||||||
|
// check if permission is already granted
|
||||||
|
if (Notification.permission === 'granted') {
|
||||||
|
// show notification here
|
||||||
|
} else {
|
||||||
|
// request permission from user
|
||||||
|
Notification.requestPermission().then(function(p) {
|
||||||
|
if(p === 'granted') {
|
||||||
|
// show notification here
|
||||||
|
console.log("User receive notifications.")
|
||||||
|
} else {
|
||||||
|
console.log('User blocked notifications.');
|
||||||
|
}
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$("#chatButton").click(function(){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "{% url 'chat:chtaajax-getloggedusers' %}",
|
||||||
|
data : {
|
||||||
|
action : "getloggedusers"
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
$("#chat_alluserscontent").fadeToggle();
|
||||||
|
$("#chat_alluserscontent").html(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
{% load mathfilters %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load counter_tag %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Rechungsübersicht
|
||||||
|
<span style="float: right;">
|
||||||
|
<a class="btn btn-primary btn-sm" href="{% url 'admbill-add' %}" style="float: right;"><i class="fas fa-plus"></i> Rechnung</a>
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
<hr>
|
||||||
|
<table class="table table-hover" id="agdata" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Agentur</th>
|
||||||
|
<th scope="col">Rechnungsnummer</th>
|
||||||
|
<th scope="col">Rechnungsdatum</th>
|
||||||
|
<th scope="col">Leistung Start</th>
|
||||||
|
<th scope="col">Leistung Ende</th>
|
||||||
|
<th scope="col">Betrag</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ele in bills %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'adm-agency-single' ele.agency.pk %}">{{ele.agency.name}}</a></td>
|
||||||
|
<td><a href="{% url 'ag-getbillpdf' ele.pk %}" target="_blank">{{ele.billnumber}}</a></td>
|
||||||
|
<td data-sort='{{ele.billdate|date:"U"}}'>{{ele.billdate|date:"d.m.Y"}}</td>
|
||||||
|
<td data-sort='{{ele.start|date:"U"}}'>{{ele.start|date:"d.m.Y"}}</td>
|
||||||
|
<td data-sort='{{ele.end|date:"U"}}'>{{ele.end|date:"d.m.Y"}}</td>
|
||||||
|
<td>
|
||||||
|
{% loadBillValue ele as fm %}
|
||||||
|
{% if fm != False %}
|
||||||
|
{{fm|floatformat:2|intcomma}} €
|
||||||
|
{% else %}
|
||||||
|
Fehler bei Rechnungsabfrage (ID {{ele.pk}})
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if ele.billstatus == "open" %} <i class="far fa-times-circle" style="color: red"></i> {% elif ele.billstatus == "paid" %} <i class="far fa-check-circle" style="color: green"></i> {% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#agdata').DataTable({
|
||||||
|
order: [2, 'desc'],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h4>Rechungsübersicht STORNO
|
||||||
|
<span style="float: right;">
|
||||||
|
<a class="btn btn-primary btn-sm" href="{% url 'admbill-add' %}" style="float: right;"><i class="fas fa-plus"></i> Rechnung</a>
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
<hr>
|
||||||
|
<table class="table table-hover" id="ag_storno" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Agentur</th>
|
||||||
|
<th scope="col">Rechnungsnummer</th>
|
||||||
|
<th scope="col">Rechnungsdatum</th>
|
||||||
|
<th scope="col">Leistung Start</th>
|
||||||
|
<th scope="col">Leistung Ende</th>
|
||||||
|
<th scope="col">Betrag</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ele in bills_storno %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'adm-agency-single' ele.agency.pk %}">{{ele.agency.name}}</a></td>
|
||||||
|
<td><a href="{% url 'ag-getbillpdf' ele.pk %}" target="_blank">{{ele.billnumber}}</a></td>
|
||||||
|
<td data-sort='{{ele.billdate|date:"U"}}'>{{ele.billdate|date:"d.m.Y"}}</td>
|
||||||
|
<td data-sort='{{ele.start|date:"U"}}'>{{ele.start|date:"d.m.Y"}}</td>
|
||||||
|
<td data-sort='{{ele.end|date:"U"}}'>{{ele.end|date:"d.m.Y"}}</td>
|
||||||
|
<td>
|
||||||
|
{% loadBillValue ele as fm %}
|
||||||
|
{% if fm != False %}
|
||||||
|
{{fm|floatformat:2|intcomma}} €
|
||||||
|
{% else %}
|
||||||
|
Fehler bei Rechnungsabfrage (ID {{ele.pk}})
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if ele.billstatus == "open" %} <i class="far fa-times-circle" style="color: red"></i> {% elif ele.billstatus == "paid" %} <i class="far fa-check-circle" style="color: green"></i> {% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#ag_storno').DataTable({
|
||||||
|
order: [2, 'desc'],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% if request.user.profile.agency.module_timemanagement %}
|
||||||
|
<div class="content-section col-7">
|
||||||
|
|
||||||
|
<h3>Zum Arbeitstag am {{workday.start|date:"d.m.Y"}} Pause hinzufügen</h3>
|
||||||
|
<hr>
|
||||||
|
<div class="col-6" style="margin-left: -10px;">
|
||||||
|
<form method="POST" class="">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.media}}
|
||||||
|
{{form}}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<a class="btn" href="{% url 'adm-workday-update' workday.pk %}">Abbrechen</a>
|
||||||
|
<button type="submit" class="btn btn-primary">Pause hinzufügen</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Pause des Arbeitstags von {{object.user.get_full_name}} am {{object.workday.start|date:"d.m.Y"}} löschen?</h4>
|
||||||
|
<hr>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Achtung! Das Löschen kann nicht rückgängig gemacht werden. Die Pause von {{object.start|date:"H:i"}} bis {{object.end|date:"H:i"}} wird entfernt!</p>
|
||||||
|
<a class="btn btn-secondary" href="{% url 'adm-workday-update' object.workday.pk %}" name="action">Abbrechen</a>
|
||||||
|
<button style="float: right" class="btn btn-primary" type="submit" name="action">Pause löschen</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load mathfilters %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
{% if statistik|length > 0 %}
|
||||||
|
{% getlaststat as statistik_last %}
|
||||||
|
<h4>Statistik vom {{statistik.0.staticdate|date:"d.m.Y"}} bis {{statistik_last.staticdate|date:"d.m.Y"}}
|
||||||
|
</h4>
|
||||||
|
{% else %}
|
||||||
|
<h4>Statisk noch nicht begonnen </h4>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<div class="chart-container" style="">
|
||||||
|
<canvas id="all_stats"></canvas>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
{% getMRR as finalmrr %}
|
||||||
|
<h5>MRR: {{finalmrr|floatformat:2|intcomma}} €</h5>
|
||||||
|
<hr>
|
||||||
|
<h5>Monatliche Umsätze</h5>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<td style="min-width: 80px;">Monat</td>
|
||||||
|
<td>Umsatz</td>
|
||||||
|
</thead>
|
||||||
|
{% for m in money %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{m.salesmonthdate|date:"m/Y"}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{m.value}} €
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h5>Zahlenübersicht stand jetzt</h5>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="min-width: 150px;">Agenturen</td>
|
||||||
|
<td>{{agencycount}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Nutzer</td>
|
||||||
|
<td>{{usercount}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Standards</td>
|
||||||
|
<td>{{standardcount}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.6/Chart.bundle.min.js"></script>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr>
|
||||||
|
<a href="{% url 'getorders' %}" target="_blank">Bestellungen der Notfallhilfe als CSV herunterladen</a>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
new Chart(document.getElementById("all_stats"),{
|
||||||
|
"type":"line",
|
||||||
|
"data":
|
||||||
|
{
|
||||||
|
"labels":
|
||||||
|
[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
"{{ele.staticdate|date:'d.m.Y'}}",
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"datasets":[
|
||||||
|
{"label":"Agenturen",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.agencys}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#664AD9",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Nutzer",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.users}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#5DF0CB",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Standards",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.standards}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#FA746A",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"MRR",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.mrr|stringformat:".2f"}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#FA746A",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Chatnachrichten",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.chatmessages}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#FFFF99",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Aktive Abos",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.active_abos}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#009900",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Abwesenheiten",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.absenceobjects}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#CC00CC",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Nutzer mit Zeiterfassung",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.user_active_timemanagement}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#FFB266",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Organizerelemente",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.organizerobjects}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#6600CC",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Agenturen mit Notfallhilfenpasswort",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.agency_activerecover}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#FF0000",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Notfallhilfenelemente",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.agency_recoverobjects}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#990000",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Dateien",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.allfiles}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#CCCC00",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Speicherplatzverbrauch",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ ele.allfiles_storage|mul:0.000000000931|stringformat:".10f" }},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#666600",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
{"label":"Gestrige Logins",
|
||||||
|
"data":[
|
||||||
|
{% for ele in statistik %}
|
||||||
|
{{ele.logins}},
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
"fill":false,
|
||||||
|
"borderColor":"#000099",
|
||||||
|
"lineTension":0.1
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options":{}});
|
||||||
|
</script>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
logins = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
<!-- Mitarbeier ist immer der angemeledete!!! -->
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="img-profile" width="100%" src="{{ userdata.profile.get_photo_url }}" >
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<h4>{{userdata.get_full_name}} aus {{userdata.profile.agency.name}}</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td style="min-width: 120px;">Name</td>
|
||||||
|
<td>{{userdata.get_full_name}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>E-Mail</td>
|
||||||
|
<td>{{userdata.email}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Letzter Login</td>
|
||||||
|
<td>{{userdata.last_login|default:"-"}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<nav>
|
||||||
|
<div class="nav nav-tabs" id="usersingleelements" role="tablist">
|
||||||
|
<a class="nav-link active" id="nav-groups-tab" data-bs-toggle="tab" href="#nav-groups" role="tab" aria-controls="nav-groups" aria-selected="true">Gruppen</a>
|
||||||
|
<a class="nav-link" id="nav-contractdata-tab" data-bs-toggle="tab" href="#nav-contractdata" role="tab" aria-controls="nav-contractdata" aria-selected="false">Vertragsdaten</a>
|
||||||
|
<a class="nav-link" id="nav-contract-tab" data-bs-toggle="tab" href="#nav-contract" role="tab" aria-controls="nav-contract" aria-selected="false">Arbeitstage</a>
|
||||||
|
<a class="nav-link" id="nav-absence-tab" data-bs-toggle="tab" href="#nav-absence" role="tab" aria-controls="nav-absence" aria-selected="false">Abwesenheiten</a>
|
||||||
|
<a class="nav-link" id="nav-year-tab" data-bs-toggle="tab" href="#nav-year" role="tab" aria-controls="nav-year" aria-selected="false">Jahresübersicht</a>
|
||||||
|
<a class="nav-link" id="nav-logins-tab" data-bs-toggle="tab" href="#nav-logins" role="tab" aria-controls="nav-logins" aria-selected="false">Logins</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content" id="nav-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="nav-groups" role="tabpanel" aria-labelledby="nav-groups-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Gruppenübersicht</h5>
|
||||||
|
<small>Tabelle zeigt alle Gruppen, in denen der Nutzer ist.</small>
|
||||||
|
<table class="table table-hover" id="users_groups" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Gruppenname</th>
|
||||||
|
<th scope="col">Interner Name</th>
|
||||||
|
<th scope="col">Löschschutz</th>
|
||||||
|
<th scope="col">Administrativ</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for g in userdata.groups.all %}
|
||||||
|
{% getAgencyGroupName g as aggdata %}
|
||||||
|
<tr>
|
||||||
|
<td>{{aggdata.agencygroupname}}</td>
|
||||||
|
<td>{{g.name}}</td>
|
||||||
|
<td>{% if aggdata.savefordel %} Ja {% else %} Nein {% endif %}</td>
|
||||||
|
<td>{% if aggdata.is_admin %} Ja {% else %} Nein {% endif %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#users_groups').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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="nav-contractdata" role="tabpanel" aria-labelledby="nav-contractdata-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Vertragsdaten<small> UserTime-ID {{usertimedata.pk}}</small></h5>
|
||||||
|
Arbeitszeit
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Montag</td>
|
||||||
|
<td>Dienstag</td>
|
||||||
|
<td>Mittwoch</td>
|
||||||
|
<td>Donnerstag</td>
|
||||||
|
<td>Freitag</td>
|
||||||
|
<td>Samstag</td>
|
||||||
|
<td>Sonntag</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{usertimedata.wd_mo}}</td>
|
||||||
|
<td>{{usertimedata.wd_tu}}</td>
|
||||||
|
<td>{{usertimedata.wd_we}}</td>
|
||||||
|
<td>{{usertimedata.wd_th}}</td>
|
||||||
|
<td>{{usertimedata.wd_fr}}</td>
|
||||||
|
<td>{{usertimedata.wd_sa}}</td>
|
||||||
|
<td>{{usertimedata.wd_so}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Urlaub verfällt am</td>
|
||||||
|
<td><b>{{usertimedata.loose_holidedate}}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Einstellungsdatum</td>
|
||||||
|
<td><b>{{usertimedata.startdate|date:"d.m.Y"}}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Zeiterfassung Startwert</td>
|
||||||
|
<td><b>{{usertimedata.startcount}}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Zeiterfassung Nutzen</td>
|
||||||
|
<td><b>{{usertimedata.usetime}}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Zeiterfassung ab dem</td>
|
||||||
|
<td><b>{{usertimedata.usetime_start|date:"d.m.Y"}}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="nav-contract" role="tabpanel" aria-labelledby="nav-contract-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Arbeitstage</h5>
|
||||||
|
<a class="btn btn-sm btn-primary" href="{% url 'adm-workday-add' userdata.pk %}">+ Arbeitstag</a><br />
|
||||||
|
<br />
|
||||||
|
<table class="table table-hover" id="user_workdays" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Start</th>
|
||||||
|
<th scope="col">Ende</th>
|
||||||
|
<th scope="col">Ziel</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for wd in workdays %}
|
||||||
|
<tr>
|
||||||
|
<td>{{wd.pk}}</td>
|
||||||
|
<td>{{wd.start|date:"d.m.Y H:i"}}</td>
|
||||||
|
<td>{{wd.end|date:"d.m.Y H:i"}}</td>
|
||||||
|
<td>{{wd.target}}</td>
|
||||||
|
<td><a class="btn btn-secondary btn-sm " href="{% url 'adm-workday-update' wd.pk %}"><i class="fas fa-pen"></i></a> <a class="btn btn-sm " href="{% url 'adm-workday-delete' wd.pk %}"><i class="fas fa-trash"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#user_workdays').DataTable({
|
||||||
|
columnDefs: [
|
||||||
|
{ 'targets': 1, type: 'date-euro' },
|
||||||
|
{ 'targets': 2, type: 'date-euro' }
|
||||||
|
],
|
||||||
|
order: [
|
||||||
|
[1, 'asc'] // date column
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 20,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="nav-absence" role="tabpanel" aria-labelledby="nav-absence-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Abwesenheiten</h5>
|
||||||
|
<table class="table table-hover" id="user_absences" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Art</th>
|
||||||
|
<th scope="col">Start</th>
|
||||||
|
<th scope="col">Ende</th>
|
||||||
|
<th scope="col">Ganz/Vor/Na</th>
|
||||||
|
<th scope="col">Ganz/Vor/Na</th>
|
||||||
|
<th scope="col">Erstellt am</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Urlaub N</th>
|
||||||
|
<th scope="col">Urlaub R</th>
|
||||||
|
<th scope="col">Urlaub Next N</th>
|
||||||
|
<th scope="col">Urlaub Next Rest</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ab in absences %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ab.pk}}</td>
|
||||||
|
<td>{{ab.reason.name}}</td>
|
||||||
|
<td>{{ab.start|date:"d.m.Y"}}</td>
|
||||||
|
<td>{{ab.end|date:"d.m.Y"}}</td>
|
||||||
|
<td>{% if ab.startday_info == "1" %} Vormittags {% elif ab.startday_info == "2" %} Nachmittags {% else %} Ganzer Tag {% endif %}</td>
|
||||||
|
<td>{% if ab.endday_info == "1" %} Vormittags {% elif ab.endday_info == "2" %} Nachmittags {% else %} Ganzer Tag {% endif %}</td>
|
||||||
|
<td>{{ab.created_date|date:"d.m.Y"}}</td>
|
||||||
|
<td>{% if ab.confirm_status == 0 %} OK {% elif ab.confirm_status == 1 %} Beantragt {% else %} Abgelehnt {% endif %} </td>
|
||||||
|
<td>{{ab.holidays_normal}}</td>
|
||||||
|
<td>{{ab.holidays_rest}}</td>
|
||||||
|
<td>{{ab.holidays_normal_next}}</td>
|
||||||
|
<td>{{ab.holidays_rest_next}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#user_absences').DataTable({
|
||||||
|
columnDefs: [
|
||||||
|
{ 'targets': 1, type: 'date-euro' },
|
||||||
|
{ 'targets': 2, type: 'date-euro' }
|
||||||
|
],
|
||||||
|
order: [
|
||||||
|
[1, 'asc'] // date column
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 10,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="nav-year" role="tabpanel" aria-labelledby="nav-year-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Jahresübersicht</h5>
|
||||||
|
|
||||||
|
<table class="table table-hover" id="user_absence_year" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Jahr</th>
|
||||||
|
<th scope="col">Tage Gesamt</th>
|
||||||
|
<th scope="col">Tage Verbraucht</th>
|
||||||
|
<th scope="col">Rest Vorjahr</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for yi in yearinfo %}
|
||||||
|
<tr>
|
||||||
|
<td>{{forloop.counter}}</td>
|
||||||
|
<td>{{yi.year}}</td>
|
||||||
|
<td>{{yi.days}}</td>
|
||||||
|
<td>{{yi.days_inuse}}</td>
|
||||||
|
<td>{{yi.restdays}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#user_absence_year').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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 10,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="nav-logins" role="tabpanel" aria-labelledby="nav-logins-tab">
|
||||||
|
<br />
|
||||||
|
<h5>Logins (letzten 50)</h5>
|
||||||
|
{% for ele in logdata reversed %}
|
||||||
|
{{forloop.revcounter}}. {{ele|date:"d.m.Y, H:i"}}<br />
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$('#usersingleelements a').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$(this).tab('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load adm_tags %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Nutzerübersicht ({{users|length}})</h4>
|
||||||
|
<hr>
|
||||||
|
<table class="table table-hover" id="all_users" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">E-Mail</th>
|
||||||
|
<th scope="col">Agentur</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for ele in users %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'adm-user-single' ele.pk %}">{{ele.get_full_name}}</a></td>
|
||||||
|
<td>{{ele.email}}</td>
|
||||||
|
<td>{{ele.profile.agency.name}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#all_users').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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 50,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% if request.user.profile.agency.module_timemanagement %}
|
||||||
|
<div class="content-section col-7">
|
||||||
|
|
||||||
|
<h3>Arbeitstag für {{user.get_full_name}} erstellen</h3>
|
||||||
|
<hr>
|
||||||
|
<div class="col-6" style="margin-left: -10px;">
|
||||||
|
<form method="POST" class="">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.media}}
|
||||||
|
{{form|crispy}}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<a class="btn" href="{% url 'adm-user-single' user.pk %}">Abbrechen</a>
|
||||||
|
<button type="submit" class="btn btn-primary">Arbeitstag speichern</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Arbeitstag von {{object.user.get_full_name}} am {{object.start|date:"d.m.Y"}} löschen?</h4>
|
||||||
|
<hr>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Achtung! Das Löschen kann nicht rückgängig gemacht werden. Der komplette Arbeitstag wird entfernt!</p>
|
||||||
|
<a class="btn btn-secondary" href="{% url 'adm-user-single' object.user.pk %}" name="action">Abbrechen</a>
|
||||||
|
<button style="float: right" class="btn btn-primary" type="submit" name="action">Arbeitstag löschen</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
{% extends "adm/adm_base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h4>Arbeitstag von {{object.user.get_full_name}} am {{object.start|date:"d.m.Y"}} aktualisieren</h4>
|
||||||
|
<hr>
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{form.media}}
|
||||||
|
{{form|crispy}}
|
||||||
|
<hr>
|
||||||
|
<h5>Pausen</h5>
|
||||||
|
<a class="btn btn-sm btn-primary" href="{% url 'adm-break-add' object.pk %}">+ Pause</a><br /><br />
|
||||||
|
{% if object.breaks.all|length > 0 %}
|
||||||
|
<table class="table table-hover" id="user_breaks" >
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">Start</th>
|
||||||
|
<th scope="col">Ende</th>
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody >
|
||||||
|
{% for break in object.breaks.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{forloop.counter}}</td>
|
||||||
|
<td>{{break.start|date:"H:i"}}</td>
|
||||||
|
<td>{{break.end|date:"H:i"}}</td>
|
||||||
|
<td><a class="btn btn-secondary btn-sm " href="{% url 'adm-break-delete' break.pk %}"><i class="fas fa-trash"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<p>Arbeitstag hat keine Pausen</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<a class="btn btn-secondary" href="{% url 'adm-user-single' object.user.pk %}" name="action">Abbrechen</a>
|
||||||
|
<button style="float: right" class="btn btn-primary" type="submit" name="action">Speichern</button>
|
||||||
|
</form>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('#user_breaks').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"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"pageLength": 10,
|
||||||
|
"buttons" : {
|
||||||
|
"className" : "btn-danger"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,95 @@
|
||||||
|
from django import template
|
||||||
|
from django.contrib.auth.models import Group, User
|
||||||
|
from users.models import AgencyGroup, Agency, AgencyNetwork, AgencyNetworkPreperation, UserTime, UserYearAbsenceInfo
|
||||||
|
from standards.models import Standards, StandardCommentRate, StandardComments
|
||||||
|
from timemanagement.models import Workday, FreeDays, Absence
|
||||||
|
from message.models import Message
|
||||||
|
import os
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import date
|
||||||
|
import datetime
|
||||||
|
from adm.models import MainStatistic
|
||||||
|
register = template.Library()
|
||||||
|
import requests
|
||||||
|
from django.conf import settings
|
||||||
|
import json
|
||||||
|
'''
|
||||||
|
|
||||||
|
Agenturdaten zurückgeben
|
||||||
|
|
||||||
|
TASK: Hier noch mehr Datenfelder über die Agentur hinzunehmen, bei Bedarf
|
||||||
|
|
||||||
|
'''
|
||||||
|
@register.simple_tag
|
||||||
|
def getAgencyData(ele):
|
||||||
|
data = [0,1]
|
||||||
|
data[0] = len(User.objects.filter(profile__agency=ele))
|
||||||
|
data[1] = len(Standards.objects.filter(agency=ele))
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Gibt den letzten Statisk-Datensatz zurück
|
||||||
|
@register.simple_tag
|
||||||
|
def getlaststat():
|
||||||
|
return MainStatistic.objects.all().order_by("-staticdate")[:1][0]
|
||||||
|
|
||||||
|
# Gibt die AgencyGroup anhand einer Gruppe zurück
|
||||||
|
@register.simple_tag
|
||||||
|
def getAgencyGroupName(group):
|
||||||
|
try:
|
||||||
|
return AgencyGroup.objects.get(group=group)
|
||||||
|
except:
|
||||||
|
return "Nicht gefunden"
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def loadAboCount():
|
||||||
|
return len(Agency.objects.filter(paymentplan=1))
|
||||||
|
|
||||||
|
# Return Bill-Value with Tax
|
||||||
|
@register.simple_tag
|
||||||
|
def loadBillValue(bill):
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + settings.LEX_API,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
lexdata = {
|
||||||
|
"renderType" : "pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data = json.dumps(lexdata)
|
||||||
|
|
||||||
|
returnvalue = False
|
||||||
|
try:
|
||||||
|
r_final = requests.get("https://api.lexoffice.io/v1/invoices/"+bill.lexid, data=json_data, headers=headers)
|
||||||
|
billdata = json.loads(r_final.text)
|
||||||
|
returnvalue = billdata['totalPrice']['totalGrossAmount']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return returnvalue
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Hier wird der aktuelle Umsatz berechnet anhand Abos und Mitarbeiterzahlen
|
||||||
|
|
||||||
|
'''
|
||||||
|
@register.simple_tag
|
||||||
|
def getMRR():
|
||||||
|
# Berechnung das MRR
|
||||||
|
finalmrr = 0
|
||||||
|
allag_withabo = Agency.objects.filter(paymentplan=1)
|
||||||
|
abos = len(allag_withabo) * 21
|
||||||
|
extrausercount = 0
|
||||||
|
for ag in allag_withabo:
|
||||||
|
user_ag = User.objects.filter(profile__agency=ag)
|
||||||
|
if len(user_ag) > 3:
|
||||||
|
extrausercount += len(user_ag) - 3
|
||||||
|
finalmrr = abos + 3*extrausercount
|
||||||
|
|
||||||
|
return finalmrr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
from django.urls import path
|
||||||
|
from .views import *
|
||||||
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
|
||||||
|
'''
|
||||||
|
Permissions definiert in models.py bei USERS und dann hier vor die View geschrieben!
|
||||||
|
'''
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', AdmMain.as_view(), name='adm-main'),
|
||||||
|
path('ag/', AdmAgencys.as_view(), name="adm-agencys"),
|
||||||
|
path('us/', AdmUsers.as_view(), name="adm-users"),
|
||||||
|
path('agsingle/<int:agpk>', AdmAgencySingle.as_view(), name="adm-agency-single"),
|
||||||
|
path('ad/del/<int:pk>', delAgency.as_view(), name='adm-agency-delete'),
|
||||||
|
path('ag/bills/', AdmBills.as_view(), name="adm-bills"),
|
||||||
|
path('usersingle/<int:uspk>', AdmUserSingle.as_view(), name="adm-user-single"),
|
||||||
|
path('cron/<slug:code>', statisticCronJob, name="adm-cron"),
|
||||||
|
path('getorders/', getCSVRDOrders, name="getorders"),
|
||||||
|
path('adm/addbill', AdmAddBill.as_view(), name="admbill-add"),
|
||||||
|
path('wd/<int:pk>/update', AdmWorkdayUpdate.as_view(), name="adm-workday-update"),
|
||||||
|
path('wd/add/<int:uspk>', AdmWorkdayAdd.as_view(), name="adm-workday-add"),
|
||||||
|
path('wd/<int:pk>/delete', AdmWorkdayDelete.as_view(), name="adm-workday-delete"),
|
||||||
|
path('wd/break/<int:pk>/delete', AdmBreakDelete.as_view(), name="adm-break-delete"),
|
||||||
|
path('wd/<int:pk>/break/add', AdmAddBreak.as_view(), name="adm-break-add"),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,679 @@
|
||||||
|
from django.views.generic import CreateView, ListView, UpdateView, DetailView, DeleteView, FormView, TemplateView
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.shortcuts import render, redirect, reverse
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpResponseRedirect,HttpResponse, JsonResponse
|
||||||
|
from .models import MainStatistic, MainSalesMonth
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from chat.models import ChatMessage
|
||||||
|
from users.models import Agency, AgencyBills, RegNotfallhilfe
|
||||||
|
from standards.models import Standards
|
||||||
|
import csv, os
|
||||||
|
from auditlog.models import LogEntry
|
||||||
|
import json
|
||||||
|
from users.models import UserYearAbsenceInfo, UserTime
|
||||||
|
from timemanagement.models import Workday, Absence, Breaks
|
||||||
|
from recoverdir.models import *
|
||||||
|
from .forms import AgencyBillForm, AdmWorkdayForm, AdmBreakAddForm
|
||||||
|
from datetime import date, timedelta, datetime
|
||||||
|
from organizer.models import QuickLinks, AGContacts, AGPassword
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
|
import io as BytesIO
|
||||||
|
import base64
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from dateutil.relativedelta import *
|
||||||
|
import requests
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from cloud.models import DataFile
|
||||||
|
import math
|
||||||
|
import requests
|
||||||
|
'''
|
||||||
|
Prüfung, ob angemeldeter User Mitarbeiterstatus hat. IMMER PER DISPATCH EINBAUEN!
|
||||||
|
'''
|
||||||
|
def checkForStuffUser(request):
|
||||||
|
if request.user.is_staff:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
CSV mit Bestellungen herunterladen
|
||||||
|
|
||||||
|
'''
|
||||||
|
def getCSVRDOrders(request):
|
||||||
|
if(request.method == "GET"):
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
|
||||||
|
response['Content-Disposition'] = 'attachment; charset=UTF-8; filename="DA-Export_NF_Bestellungen_' + str(today.day) + '_'+ str(today.month)+'_'+ str(today.year)+'.csv"'
|
||||||
|
|
||||||
|
writer = csv.writer(response)
|
||||||
|
writer.writerow(['Datum', 'E-Mail', 'Name', 'Personalnummer', 'mitgliedsnummer', 'PLZ', 'Stadt', 'Strasse', 'Rabatt'])
|
||||||
|
orders = RegNotfallhilfe.objects.filter(wassend=False)
|
||||||
|
|
||||||
|
for order in orders:
|
||||||
|
rabatt = "NEIN"
|
||||||
|
if order.rabatt:
|
||||||
|
rabatt = "JA"
|
||||||
|
writer.writerow([str(order.orderdate),str(order.mail),str(order.name),str(order.persnumber),str(order.mitgliedsnummer),str(order.plz), str(order.stadt),str(order.street), rabatt ])
|
||||||
|
order.wassend = True
|
||||||
|
order.save()
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class delAgency(DeleteView):
|
||||||
|
model = Agency
|
||||||
|
success_url = reverse_lazy("adm-agencys")
|
||||||
|
template_name = "adm/adm_admdelconfirm.html"
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
# Alle Abwesenheiten werden entfernt und dann erst die Agentur
|
||||||
|
Absence.objects.filter(agency=self.get_object().pk).all().delete()
|
||||||
|
|
||||||
|
messages.success(self.request, f'Agentur erfolgreich gelöscht!')
|
||||||
|
return super(delAgency, self).delete(*args, **kwargs)
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
'''
|
||||||
|
Hauptansicht Statisik
|
||||||
|
'''
|
||||||
|
class AdmMain(TemplateView):
|
||||||
|
template_name = "adm/adm_main.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context.update({'active_link' : "adm-statistic"})
|
||||||
|
|
||||||
|
context.update({'statistik' : MainStatistic.objects.all().order_by('staticdate') })
|
||||||
|
context.update({
|
||||||
|
"agencycount" : len(Agency.objects.all()),
|
||||||
|
"usercount" : len(User.objects.all().exclude(is_staff=True, is_superuser=True)),
|
||||||
|
"standardcount" : len(Standards.objects.all()),
|
||||||
|
"chatmessagescount" : len(ChatMessage.objects.all()),
|
||||||
|
"money" : MainSalesMonth.objects.all(),
|
||||||
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Gesatmübersicht aller Rechnungen
|
||||||
|
|
||||||
|
'''
|
||||||
|
class AdmBills(TemplateView):
|
||||||
|
|
||||||
|
template_name = "adm/adm_bills.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context.update({'active_link' : "adm-bills"})
|
||||||
|
context.update({'bills' : AgencyBills.objects.all().exclude(billstatus="voided")})
|
||||||
|
context.update({'bills_storno' : AgencyBills.objects.filter(billstatus="voided")})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Erstellen einer neuen Rechnung
|
||||||
|
'''
|
||||||
|
class AdmAddBill(CreateView):
|
||||||
|
template_name = "adm/adm_addbill.html"
|
||||||
|
model = AgencyBills
|
||||||
|
success_url = reverse_lazy('adm-bills')
|
||||||
|
form_class = AgencyBillForm
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
|
||||||
|
agency = form.cleaned_data['agency']
|
||||||
|
|
||||||
|
# USERCOUNT BERECHNEN
|
||||||
|
usercount = len(User.objects.filter(profile__agency=agency))
|
||||||
|
|
||||||
|
if(usercount < 4):
|
||||||
|
usercount = 0
|
||||||
|
else:
|
||||||
|
usercount = usercount - 3
|
||||||
|
|
||||||
|
# HEADERS CURL
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + settings.LEX_API,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
plan = 1
|
||||||
|
|
||||||
|
start_date = form.cleaned_data["start"]
|
||||||
|
start_date_string = start_date.strftime("%d.%m.%Y")
|
||||||
|
end_date = start_date + relativedelta(months=1)
|
||||||
|
end_date = end_date - relativedelta(days=1)
|
||||||
|
end_date_string= end_date.strftime("%d.%m.%Y")
|
||||||
|
|
||||||
|
# Rechnungsdatum passt ja
|
||||||
|
voucher_date_today = date.today().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
monthword = "Monat"
|
||||||
|
|
||||||
|
lexdata = {
|
||||||
|
"voucherDate": voucher_date_today + "T00:00:00.000+00:00",
|
||||||
|
"address" : {
|
||||||
|
"name" : agency.name,
|
||||||
|
"street": agency.street,
|
||||||
|
"zip": agency.plz,
|
||||||
|
"city": agency.city,
|
||||||
|
"countryCode" : "DE"
|
||||||
|
},
|
||||||
|
"totalPrice" : {
|
||||||
|
"currency" : "EUR",
|
||||||
|
},
|
||||||
|
"lineItems" : [
|
||||||
|
{
|
||||||
|
"type" : "custom",
|
||||||
|
"name" : "Digitale Agentur: Grundbetrag für " + str(plan) + " " + monthword,
|
||||||
|
"quantity" : 1,
|
||||||
|
"unitName" : "Stück",
|
||||||
|
"description" : "Zeitraum " + start_date_string + " - " + end_date_string,
|
||||||
|
"unitPrice" :
|
||||||
|
{
|
||||||
|
"currency" : "EUR",
|
||||||
|
"netAmount" : 21.00,
|
||||||
|
"taxRatePercentage" : 19
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "custom",
|
||||||
|
"name" : "Digitale Agentur: Zusätzliche Nutzer",
|
||||||
|
"description" : "Zeitraum " + start_date_string + " - " + end_date_string,
|
||||||
|
"quantity" : usercount,
|
||||||
|
"unitName" : "Stück",
|
||||||
|
"unitPrice" :
|
||||||
|
{
|
||||||
|
"currency" : "EUR",
|
||||||
|
"netAmount" : 3,
|
||||||
|
"taxRatePercentage" : 19
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"taxConditions": {
|
||||||
|
"taxType": "net"
|
||||||
|
},
|
||||||
|
#"paymentConditions": {
|
||||||
|
# "paymentTermLabel": "Bitte zahlen Sie innerhalb von 14 Tagen.",
|
||||||
|
# "paymentTermDuration": 14,
|
||||||
|
#},
|
||||||
|
"shippingConditions": {
|
||||||
|
#"shippingDate": voucher_date_today + "T00:00:00.000+00:00",
|
||||||
|
"shippingType": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_data = json.dumps(lexdata)
|
||||||
|
# WIEDER RAUSNEHMEN
|
||||||
|
# NEUE RECHNUNG ALs ENTWURF
|
||||||
|
|
||||||
|
#r = requests.post("https://api.lexoffice.io/v1/invoices/", data=json_data, headers=headers)
|
||||||
|
# RICHTIGE RECHNUNG
|
||||||
|
r = requests.post("https://api.lexoffice.io/v1/invoices/?finalize=true", data=json_data, headers=headers)
|
||||||
|
|
||||||
|
if(r.status_code == 201):
|
||||||
|
|
||||||
|
response_text = json.loads(r.text)
|
||||||
|
newbill_id = response_text["id"]
|
||||||
|
|
||||||
|
# OrganizationId berechnen, wenn noch nicht gesetzt
|
||||||
|
r = requests.get("https://api.lexoffice.io/v1/invoices/" + response_text["id"], data=json_data, headers=headers)
|
||||||
|
response_text = json.loads(r.text)
|
||||||
|
|
||||||
|
form.instance.agency = agency
|
||||||
|
form.instance.lexid = newbill_id
|
||||||
|
form.instance.agency = agency
|
||||||
|
form.instance.billtype="invoice"
|
||||||
|
form.instance.billnumber = response_text["voucherNumber"]
|
||||||
|
form.instance.billstatus = response_text["voucherStatus"]
|
||||||
|
form.instance.start = start_date
|
||||||
|
form.instance.end = end_date
|
||||||
|
form.instance.plan = plan
|
||||||
|
form.instance.usercount = usercount
|
||||||
|
#newbill = AgencyBills(agency=agency, lexid=newbill_id, billtype="invoice", billnumber=response_text["voucherNumber"], billstatus=response_text["voucherStatus"], start=start_date, end=end_date, plan=plan, usercount=usercount)
|
||||||
|
form.save()
|
||||||
|
|
||||||
|
mail_to_send = ""
|
||||||
|
if(agency.payment_address == None):
|
||||||
|
mail_to_send = agency.agency_email
|
||||||
|
else:
|
||||||
|
mail_to_send = agency.payment_address
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# BCC Mail with Object - NICHT DEN IMPORT VERGESSEN!!!
|
||||||
|
email = EmailMultiAlternatives(
|
||||||
|
'Digitale Agentur | Rechnung ' + str(response_text["voucherNumber"]),
|
||||||
|
'Sehr geehrte Nutzer, hiermit erhalten Sie eine neue Rechnung für die Digitale Agentur. Ihr Team der Digitalen Agentur',
|
||||||
|
'noreply@digitale-agentur.com',
|
||||||
|
[mail_to_send],
|
||||||
|
['info@digitale-agentur.com'],
|
||||||
|
headers={},
|
||||||
|
)
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + settings.LEX_API,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
lexdata = {
|
||||||
|
"renderType" : "pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data = json.dumps(lexdata)
|
||||||
|
|
||||||
|
r_final = requests.get("https://api.lexoffice.io/v1/invoices/"+newbill_id+"/document", data=json_data, headers=headers)
|
||||||
|
json.loads(r_final.text)
|
||||||
|
|
||||||
|
base64String = requests.get("https://api.lexoffice.io/v1/files/"+json.loads(r_final.text)["documentFileId"]+"/", data=json_data, headers=headers)
|
||||||
|
|
||||||
|
content = base64.b64decode(base64String.text)
|
||||||
|
|
||||||
|
msg_html = render_to_string('users/newbill_mail.html', {})
|
||||||
|
email.attach_alternative(msg_html, "text/html")
|
||||||
|
email.attach('Rechnung_' + str(response_text["voucherNumber"]) + '.pdf', content, "application/pdf")
|
||||||
|
email.send()
|
||||||
|
|
||||||
|
return super(AdmAddBill, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context.update({'active_link' : "adm-agencys"})
|
||||||
|
|
||||||
|
#context.update({'agencys' : Agency.objects.all()})
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
Gesamtansicht der Agenturen
|
||||||
|
'''
|
||||||
|
class AdmAgencys(TemplateView):
|
||||||
|
template_name = "adm/adm_agencys.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context.update({'active_link' : "adm-agencys"})
|
||||||
|
|
||||||
|
context.update({'agencys' : Agency.objects.all()})
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class AdmUsers(TemplateView):
|
||||||
|
template_name = "adm/adm_users.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context.update({'active_link' : "adm-users"})
|
||||||
|
|
||||||
|
context.update({'users' : User.objects.all().exclude(is_staff=True, is_superuser=True)})
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
'''
|
||||||
|
Einzelansicht der Agenturen
|
||||||
|
'''
|
||||||
|
class AdmAgencySingle(TemplateView):
|
||||||
|
template_name = "adm/adm_agency_single.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : "adm-agencys"})
|
||||||
|
context.update({'agency' : Agency.objects.get(pk=kwargs['agpk'])})
|
||||||
|
context.update({'bills' : AgencyBills.objects.filter(agency=Agency.objects.get(pk=kwargs['agpk'])).order_by('-billdate')[:3]})
|
||||||
|
context.update({'users_of_agency' : User.objects.filter(profile__agency=Agency.objects.get(pk=kwargs['agpk'])).order_by('-last_name')})
|
||||||
|
return context
|
||||||
|
|
||||||
|
class AdmUserSingle(TemplateView):
|
||||||
|
template_name = "adm/adm_user_single.html"
|
||||||
|
|
||||||
|
def dispatch(self, *args, **kwargs):
|
||||||
|
if(checkForStuffUser(self.request)):
|
||||||
|
return super().dispatch(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
|
||||||
|
return redirect("login")
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
|
userrequested = User.objects.get(pk=kwargs['uspk'])
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : "adm-users"})
|
||||||
|
context.update({'userdata' : userrequested})
|
||||||
|
|
||||||
|
# Loading Logindata
|
||||||
|
logdata = LogEntry.objects.filter(object_pk=kwargs['uspk'])[:50]
|
||||||
|
|
||||||
|
logdata_logins = []
|
||||||
|
|
||||||
|
for ele in reversed(logdata):
|
||||||
|
try:
|
||||||
|
datestring = json.loads(ele.changes)["last_login"][1]
|
||||||
|
|
||||||
|
datestring = datestring.split(".")[0]
|
||||||
|
logdata_logins.append(datetime.strptime(datestring, '%Y-%m-%d %H:%M:%S'))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
context.update({'logdata' : logdata_logins})
|
||||||
|
|
||||||
|
context.update({'workdays' : Workday.objects.filter(user=userrequested)})
|
||||||
|
context.update({'absences' : Absence.objects.filter(user=userrequested)})
|
||||||
|
context.update({'yearinfo' : UserYearAbsenceInfo.objects.filter(user=userrequested)})
|
||||||
|
context.update({'usertimedata' : UserTime.objects.get(user=userrequested)})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# CRONJOB, um die Statistik zu füllen!
|
||||||
|
def statisticCronJob(request, code):
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
if(code == settings.CRONAPIKEY_STATSTIC):
|
||||||
|
print("STATISTIC is running...")
|
||||||
|
today = date.today()
|
||||||
|
'''
|
||||||
|
|
||||||
|
= models.FloatField(default=0.0)
|
||||||
|
logins = models.IntegerField(default=0)
|
||||||
|
'''
|
||||||
|
# AGENCYS
|
||||||
|
agencycount = len(Agency.objects.all())
|
||||||
|
# USERS
|
||||||
|
usercount = len(User.objects.all().exclude(is_staff=True, is_superuser=True))
|
||||||
|
# STANDARDS
|
||||||
|
standardcount = len(Standards.objects.all())
|
||||||
|
# CHATMESSAGES
|
||||||
|
chatmesscount = len(ChatMessage.objects.all())
|
||||||
|
# ABOCOUNT
|
||||||
|
abocount = len(Agency.objects.filter(paymentplan=1))
|
||||||
|
# ABSENCE OBJECTS
|
||||||
|
absenceobjects = len(Absence.objects.all())
|
||||||
|
# USER WITH TIMEMANAGEMENT
|
||||||
|
user_active_timemanagement = len(User.objects.filter(usertime__usetime=True))
|
||||||
|
# ORGANIZEROBJECTS
|
||||||
|
organizerobjects = len(QuickLinks.objects.all()) + len(AGContacts.objects.all()) + len(AGPassword.objects.all())
|
||||||
|
# AGENCY WITH RECOVERPASS
|
||||||
|
agency_activerecover = 0
|
||||||
|
agency_activerecover_all = RecoverDirSetting.objects.all()
|
||||||
|
for re in agency_activerecover_all:
|
||||||
|
if len(re.logpass) > 0:
|
||||||
|
agency_activerecover += 1
|
||||||
|
|
||||||
|
# FILES
|
||||||
|
'''
|
||||||
|
TODO: Hier bitte einmal checken, ob umbenannte Dateien auch gefunden werden können oder nicht
|
||||||
|
'''
|
||||||
|
allfiles = 0
|
||||||
|
try:
|
||||||
|
files_data = DataFile.objects.all()
|
||||||
|
allfiles = len(files_data)
|
||||||
|
# FILE SOTRAGE
|
||||||
|
allfiles_storage = 0.0
|
||||||
|
for f in files_data:
|
||||||
|
allfiles_storage += os.stat(f.file.path).st_size
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# LOGINS YESTERDAY
|
||||||
|
yesterday = today - timedelta(days=1)
|
||||||
|
logins = 0
|
||||||
|
for u in User.objects.all():
|
||||||
|
try:
|
||||||
|
logdata = list(LogEntry.objects.filter(object_pk=u.pk)[:1])[0]
|
||||||
|
datestring = json.loads(logdata.changes)["last_login"][1]
|
||||||
|
datestring = datestring.split(".")[0]
|
||||||
|
logdate = datetime.strptime(datestring, '%Y-%m-%d %H:%M:%S')
|
||||||
|
logdate = logdate.date()
|
||||||
|
if logdate == yesterday:
|
||||||
|
logins += 1
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# RECOVEROBJECTS
|
||||||
|
agency_recoverobjects = len(PersLetter.objects.all()) + len(Handlungsleitfaden.objects.all()) + len(RDContact.objects.all()) + len(RDTrustPerson.objects.all()) + len(Documents.objects.all()) + len(HandlungsleitfadenVF.objects.all()) + len(DepositVollmacht.objects.all()) + len(ErgoVerDir.objects.all()) + len(OnlineBank.objects.all()) + len(StreamingAbo.objects.all()) + len(DigitalAccounts.objects.all()) + len(Personal.objects.all()) + len(RDContract.objects.all()) + len(RDElse.objects.all())
|
||||||
|
|
||||||
|
# COUNT INVOICES WHEN FIRST DAY OF MONTH
|
||||||
|
lastmonth = today - timedelta(days=today.day)
|
||||||
|
monthvalue = 0.0
|
||||||
|
if today.day == 1:
|
||||||
|
for bill in AgencyBills.objects.filter(billdate__month=lastmonth.month):
|
||||||
|
|
||||||
|
newvalue = getLexOfficeBill(bill.lexid)
|
||||||
|
if newvalue != False:
|
||||||
|
monthvalue += newvalue
|
||||||
|
|
||||||
|
nm = MainSalesMonth(value=monthvalue, salesmonthdate=lastmonth)
|
||||||
|
nm.save()
|
||||||
|
|
||||||
|
|
||||||
|
# Monatlicher MRR
|
||||||
|
finalrma = 0
|
||||||
|
allag_withabo = Agency.objects.filter(paymentplan=1)
|
||||||
|
abos = len(allag_withabo) * 21
|
||||||
|
extrausercount = 0
|
||||||
|
for ag in allag_withabo:
|
||||||
|
user_ag = User.objects.filter(profile__agency=ag)
|
||||||
|
if len(user_ag) > 3:
|
||||||
|
extrausercount += len(user_ag) - 3
|
||||||
|
finalmrr = abos + 3*extrausercount
|
||||||
|
|
||||||
|
newMainS = MainStatistic(agencys=agencycount,users=usercount,standards=standardcount,chatmessages=chatmesscount, active_abos=abocount, absenceobjects=absenceobjects, user_active_timemanagement=user_active_timemanagement, organizerobjects=organizerobjects, agency_recoverobjects=agency_recoverobjects, allfiles=allfiles, allfiles_storage=allfiles_storage, logins=logins, mrr=finalmrr)
|
||||||
|
newMainS.save()
|
||||||
|
data.update({"status" : "success"})
|
||||||
|
else:
|
||||||
|
print("API STATISTIC CODE FAILED")
|
||||||
|
data.update({"status" : "failed"})
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
# Return an Tax-Free Value of bill
|
||||||
|
def getLexOfficeBill(billid):
|
||||||
|
headers = {
|
||||||
|
'Authorization': 'Bearer ' + settings.LEX_API,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
lexdata = {
|
||||||
|
"renderType" : "pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data = json.dumps(lexdata)
|
||||||
|
|
||||||
|
returnvalue = False
|
||||||
|
try:
|
||||||
|
r_final = requests.get("https://api.lexoffice.io/v1/invoices/"+billid, data=json_data, headers=headers)
|
||||||
|
billdata = json.loads(r_final.text)
|
||||||
|
returnvalue = billdata['totalPrice']['totalNetAmount']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return returnvalue
|
||||||
|
|
||||||
|
def convert_size(size_bytes):
|
||||||
|
if size_bytes == 0:
|
||||||
|
return "0B"
|
||||||
|
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||||
|
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = round(size_bytes / p, 2)
|
||||||
|
return "%s %s" % (s, size_name[i])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
''' WORKDAY VIEWS
|
||||||
|
|
||||||
|
Hier sind alle Views für Arbeitstage und Pausen (Create, Update, Delete)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
class AdmWorkdayAdd(CreateView):
|
||||||
|
model = Workday
|
||||||
|
template_name = "adm/adm_workday_add.html"
|
||||||
|
form_class = AdmWorkdayForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
wd_user = User.objects.get(pk=self.kwargs['uspk'])
|
||||||
|
wd = Workday(user=wd_user, agency=wd_user.profile.agency, start=form.cleaned_data['start'], end=form.cleaned_data['end'], target=form.cleaned_data["target"], freefield=form.cleaned_data["freefield"])
|
||||||
|
wd.save()
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : 'adm-users'})
|
||||||
|
context.update({'user' : User.objects.get(pk=self.kwargs['uspk'])})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('adm-user-single', kwargs={'uspk': self.kwargs['uspk']})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AdmWorkdayUpdate(UpdateView):
|
||||||
|
model = Workday
|
||||||
|
form_class = AdmWorkdayForm
|
||||||
|
template_name = "adm/adm_workday_update.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : 'adm-users'})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('adm-user-single', kwargs={'uspk': self.get_object().user.pk})
|
||||||
|
|
||||||
|
|
||||||
|
class AdmWorkdayDelete(DeleteView):
|
||||||
|
model = Workday
|
||||||
|
template_name = "adm/adm_workday_delete.html"
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('adm-user-single', kwargs={'uspk': self.get_object().user.pk})
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : 'adm-users'})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AdmBreakDelete(DeleteView):
|
||||||
|
model = Breaks
|
||||||
|
template_name = "adm/adm_break_delete.html"
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('adm-workday-update', kwargs={'pk': self.get_object().workday.pk})
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : 'adm-users'})
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AdmAddBreak(CreateView):
|
||||||
|
model = Breaks
|
||||||
|
template_name = "adm/adm_break_add.html"
|
||||||
|
form_class = AdmBreakAddForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
wd = Workday.objects.get(pk=self.kwargs['pk'])
|
||||||
|
b = Breaks(user=wd.user, agency=wd.user.profile.agency, workday=wd, start=form.cleaned_data["start"], end=form.cleaned_data["end"])
|
||||||
|
b.save()
|
||||||
|
wd.breaks.add(b)
|
||||||
|
wd.save()
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context.update({'active_link' : 'adm-users'})
|
||||||
|
context.update({'workday' : Workday.objects.get(pk=self.kwargs['pk'])})
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('adm-workday-update', kwargs={'pk': self.kwargs['pk']})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
name = 'api'
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
|
from standards.models import Standards
|
||||||
|
from chat.models import ChatRoom, ChatMessage
|
||||||
|
|
||||||
|
class StandardsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
username = serializers.SerializerMethodField('getCreatedByUser')
|
||||||
|
last_modified_on = serializers.SerializerMethodField('getFormatedLastModified')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Standards
|
||||||
|
fields = ["id", "name", "last_modified_on", "username", "content"]
|
||||||
|
|
||||||
|
def getCreatedByUser(self, standard):
|
||||||
|
return standard.created_standard_by.first_name + " " + standard.created_standard_by.first_name
|
||||||
|
|
||||||
|
def getFormatedLastModified(self, standard):
|
||||||
|
return standard.last_modified_on.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRoomSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ChatRoom
|
||||||
|
fields = ["id", "roomname", "roomname_channel"]
|
||||||
|
|
||||||
|
class ChatMessageSerializer(serializers.ModelSerializer):
|
||||||
|
sendtime = serializers.SerializerMethodField('getFormatedMessageTimeStarted')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ChatMessage
|
||||||
|
fields = ["author", "content", "sendtime"]
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRoomFullSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
chatroom_createddate = serializers.SerializerMethodField('getFormatedChatStarted')
|
||||||
|
#messages = ChatMessageSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ChatRoom
|
||||||
|
fields = ["id", "roomname", "roomname_channel", "chatroom_createddate", "chatmembers", "chatmember_single","messages", "creator"]
|
||||||
|
depth = 2
|
||||||
|
|
||||||
|
def getFormatedChatStarted(self, chatroom):
|
||||||
|
return chatroom.chatroom_createddate.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.urls import path
|
||||||
|
from .views import GetUserId
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'api'
|
||||||
|
urlpatterns = [
|
||||||
|
path('getuserid/', GetUserId.as_view(), name='api-getuserid'),
|
||||||
|
path('getstandards/', views.getStandardList, name='api-getstandards'),
|
||||||
|
path('getsinglestandard/<int:pk>', views.getSingleStandard, name='api-getsinglestandards'),
|
||||||
|
path('logout/', views.logoutByToken, name='api-logout'),
|
||||||
|
path('getchatrooms/', views.getchatrooms, name='api-getchatrooms'),
|
||||||
|
path('getsinglechat/<int:pk>', views.getsinglechat, name='api-getsinglechat'),
|
||||||
|
path('chatnewmessage/', views.savenewchatmessage, name='api-savechatmessage'),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import IsAuthenticated # <-- Here
|
||||||
|
import json
|
||||||
|
from standards.models import Standards
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .serializers import StandardsSerializer, ChatRoomSerializer, ChatRoomFullSerializer
|
||||||
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication
|
||||||
|
from rest_framework.decorators import authentication_classes
|
||||||
|
from chat.models import ChatRoom, ChatMessage
|
||||||
|
from django.http import HttpResponseRedirect,HttpResponse, JsonResponse
|
||||||
|
|
||||||
|
from timemanagement.models import Absence
|
||||||
|
|
||||||
|
class GetUserId(APIView):
|
||||||
|
permission_classes = (IsAuthenticated,) # <-- And here
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
return Response({"userid" : self.request.user.pk})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def getStandardList(request):
|
||||||
|
standards = Standards.objects.filter(agency=request.user.profile.agency)
|
||||||
|
ser = StandardsSerializer(standards, many=True)
|
||||||
|
return Response(ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def getSingleStandard(request, pk):
|
||||||
|
standard = Standards.objects.get(pk=int(pk))
|
||||||
|
ser = StandardsSerializer(standard, many=False)
|
||||||
|
return Response(ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def logoutByToken(request):
|
||||||
|
print(request)
|
||||||
|
request.user.auth_token.delete()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def getchatrooms(request):
|
||||||
|
chatrooms = ChatRoom.objects.filter(creator=request.user) | ChatRoom.objects.filter(chatmember_single=request.user)
|
||||||
|
chatrooms_ser = ChatRoomSerializer(chatrooms, many=True)
|
||||||
|
return Response(chatrooms_ser.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def getsinglechat(request, pk):
|
||||||
|
chatroom = ChatRoom.objects.get(pk=pk)
|
||||||
|
if chatroom.creator == request.user or chatroom.chatmember_single == request.user or (request.user in chatroom.chatmembers.all()):
|
||||||
|
chatroom_ser = ChatRoomFullSerializer(chatroom, many=False)
|
||||||
|
return Response(chatroom_ser.data, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST', ])
|
||||||
|
@permission_classes((IsAuthenticated,))
|
||||||
|
def savenewchatmessage(request):
|
||||||
|
room = ChatRoom.objects.get(pk=request.POST["room"])
|
||||||
|
if(request.user == room.creator or request.user == room.chatmember_single):
|
||||||
|
newmessage = ChatMessage(room=room, author=request.user, content=request.POST["message"])
|
||||||
|
newmessage.save()
|
||||||
|
room.messages.add(newmessage)
|
||||||
|
room.save()
|
||||||
|
return Response(status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import Areas
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(Areas)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AreasConfig(AppConfig):
|
||||||
|
name = 'areas'
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django import forms
|
||||||
|
from django.forms import ModelForm
|
||||||
|
from django.forms.widgets import TextInput
|
||||||
|
from .models import Areas
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
class AreaAddAreaForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Areas
|
||||||
|
labels = {
|
||||||
|
"name" : "Bereichsname",
|
||||||
|
"color" : "Farbe",
|
||||||
|
"desc" : "Beschreibung",
|
||||||
|
"visible": "Im Organigramm sichtbar"
|
||||||
|
|
||||||
|
}
|
||||||
|
fields = ['name', 'color', 'desc']
|
||||||
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 3.0 on 2020-09-25 07:13
|
||||||
|
|
||||||
|
import colorful.fields
|
||||||
|
import datetime
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Areas',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('color', colorful.fields.RGBColorField(blank=True, colors=['#FFB900', '#E74856', '#0078D7', '#0099BC', '#7A7574'], default='#0099BC')),
|
||||||
|
('desc', models.TextField(blank=True, max_length=3000)),
|
||||||
|
('created_area_date', models.DateField(blank=True, default=datetime.date.today)),
|
||||||
|
('visible', models.BooleanField(default=True)),
|
||||||
|
('areaorder', models.IntegerField(default=0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 3.0 on 2020-09-25 07:13
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('areas', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='areas',
|
||||||
|
name='agency',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='users.Agency'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='areas',
|
||||||
|
name='created_area_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='areas',
|
||||||
|
name='usersfield',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='users_in_area', to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 3.0 on 2020-12-04 09:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0031_auto_20201204_0927'),
|
||||||
|
('areas', '0002_auto_20200925_0713'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='areas',
|
||||||
|
name='agency',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Agency'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,39 @@
|
||||||
|
from django.db import models
|
||||||
|
from users.models import Agency
|
||||||
|
from django.urls import reverse
|
||||||
|
from colorful.fields import RGBColorField
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Model Areas
|
||||||
|
|
||||||
|
Verwaltet alle gespeicherten Bereiche für die Agentur. Wird eine neue erstellt,
|
||||||
|
wird dieser die Agency zugewiesen. Das Farb-Feld ist für später, damit im
|
||||||
|
Ogranigramm eine Farbe für den jeweiligen Bereich festgelegt wird.
|
||||||
|
|
||||||
|
users speichert alle primary-Keys der User, welche diesem Bereich zugeordnet sind!
|
||||||
|
|
||||||
|
'''
|
||||||
|
class Areas(models.Model):
|
||||||
|
|
||||||
|
# Wenn die Area gelöscht wird, wird NICHT die Agency gelöscht
|
||||||
|
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=200, blank=False)
|
||||||
|
color = RGBColorField(colors=['#FFB900', '#E74856', '#0078D7', '#0099BC', '#7A7574'], default='#0099BC', blank=True)
|
||||||
|
desc = models.TextField(max_length=3000, blank=True)
|
||||||
|
usersfield = models.ManyToManyField(User, blank=True, related_name='users_in_area')
|
||||||
|
created_area_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
||||||
|
created_area_date = models.DateField(default=datetime.date.today, blank=True)
|
||||||
|
visible = models.BooleanField(default=True)
|
||||||
|
areaorder = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
# Hier Path für Templates des Models mit Parametern
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('areas-update', kwargs={'pk':self.pk})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "users/base.html" %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-section">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media-body">
|
||||||
|
<h2 class="account-heading">Bereich {{ object.name }} löschen?</h2>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Für das Speichern der Bilder enctype -->
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Alle unter diesem Bereich erstellten Aufgaben und Standards werden gelöscht!</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-danger">Bereich löschen</button>
|
||||||
|
<a href="{% url 'areas-management' %}" class="btn btn-success">Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{% extends "users/base.html" %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-section col-6">
|
||||||
|
<h3>Neuen Bereich anlegen</h3>
|
||||||
|
<hr>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<div class="form-group mb-2 mb-3">
|
||||||
|
<span>Farbe</span><input type="color" id="color-picker" name="areacolor " />
|
||||||
|
</div>
|
||||||
|
<p>Nachdem Erstellen eines Bereichs können Mitarbeiter zugewiesen werden.</p>
|
||||||
|
<hr>
|
||||||
|
<button type="submit" class="btn btn-success" href="{% url 'areas-addarea' %} ">Bereich anlegen</button>
|
||||||
|
<a class="btn" href="{% url 'areas-management' %} ">Abbrechen</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css"/> <!-- 'classic' theme -->
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.es5.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#div_id_color").hide();
|
||||||
|
})
|
||||||
|
|
||||||
|
const pickr1 = new Pickr({
|
||||||
|
el: '#color-picker',
|
||||||
|
default: "#000000",
|
||||||
|
components: {
|
||||||
|
preview: false,
|
||||||
|
hue: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pickr1.on('changestop', function(){
|
||||||
|
var col = pickr1.getColor().toHEXA().toString();
|
||||||
|
pickr1.setColor(col);
|
||||||
|
$("#id_color").val(col);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
{% extends "users/base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||||
|
<div class="content-section col-12">
|
||||||
|
<h3>Bereichsverwaltung</h3>
|
||||||
|
<hr>
|
||||||
|
<p>
|
||||||
|
Bereiche unterteilen die Agentur in verschiedene Verantwortungsbereiche.
|
||||||
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="content-section col-4">
|
||||||
|
<a class="btn btn-primary" href="{% url 'areas-addarea' %} ">Bereich anlegen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<input class="form-control" id="tableSearch" size="50" type="text" placeholder="Suche in Tabelle...">
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover" id="areas_maintable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Erstellt von</th>
|
||||||
|
<th scope="col">Erstellt am</th>
|
||||||
|
<th scope="col">Farbe</th>
|
||||||
|
<th scope="col"> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tableresults">
|
||||||
|
{% for item in areas_of_agency %}
|
||||||
|
<tr id="{{ item.pk }}">
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>{{ item.created_area_by.first_name }} {{ item.created_area_by.last_name }}</td>
|
||||||
|
<td>{{ item.created_area_date }}</td>
|
||||||
|
<td><div style="width: 60px; height: 20px; background-color: {{ item.color }}"></div></td>
|
||||||
|
<td>
|
||||||
|
<div class="dropdown no-arrow">
|
||||||
|
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" aria-labelledby="dropdownMenuLink">
|
||||||
|
<div class="dropdown-header">Bereichsinfo</div>
|
||||||
|
<a class="dropdown-item" href="{% url 'areas-manage' item.pk %}">Bearbeiten</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<a class="dropdown-item text-danger" href="{% url 'areas-delete' item.pk %}" >Löschen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
/*$('#areas_maintable').DataTable();*/
|
||||||
|
$("#tableSearch").on("keyup", function() {
|
||||||
|
var value = $(this).val().toLowerCase();
|
||||||
|
$("#tableresults tr").filter(function() {
|
||||||
|
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Update the sort-list by drag'n'drop
|
||||||
|
*/
|
||||||
|
$( "tbody" ).sortable({
|
||||||
|
update: function( event, ui ) {
|
||||||
|
datatoserver = [];
|
||||||
|
var rows = $( "tbody" ).sortable( "widget" )[0]['rows'];
|
||||||
|
for(i = 0; i < rows.length; i++){
|
||||||
|
datatoserver.push({"id" : rows[i]['id'], "neworder" : i});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/areas/updateorder",
|
||||||
|
data:{
|
||||||
|
action: "newareaorder",
|
||||||
|
finalod : JSON.stringify(datatoserver)
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
{% extends "users/base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="content-section col-6" onmouseup="javascript:checkValue()">
|
||||||
|
<h3>Bereich aktualisieren</h3>
|
||||||
|
<hr>
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
<div class="form-group mb-2 mb-3">
|
||||||
|
<span>Farbe</span><input type="color" id="color-picker" name="areacolor " />
|
||||||
|
</div>
|
||||||
|
<h6>Mitarbeiter hinzufügen</h6>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<input class="form-control" list="usersfree" name="searchusers" id="searchusers" type="text" onkeyup="javascript:checkValue()" onchange="javascript:checkValue()" >
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" id="addusertoareabtn" onclick="javascript:addUserToArea()" class="btn btn-success" disabled>Mitarbeiter hinzufügen</button>
|
||||||
|
<button type="button" onclick="javascript:clearSearchfield()" class="btn btn-secondary" ><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
<datalist id="usersfree">
|
||||||
|
{% for us in possible_users %}
|
||||||
|
<option id="{{us.pk}}" value="{{us.first_name}} {{us.last_name}}"></option>
|
||||||
|
{% endfor %}
|
||||||
|
</datalist>
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h6>Zugewiesene Mitarbeiter</h6>
|
||||||
|
<div id="added_users_button">
|
||||||
|
{% if added_users|length > 0 %}
|
||||||
|
<p id="no_user_in_area" style="display: none">Noch kein Mitarbeiter zugewiesen.</p>
|
||||||
|
{% for us in added_users %}
|
||||||
|
<span id="span_btn_{{us.pk}}" class="badge badge-pill badge-primary mr-2 mt-2"><a class="btn btn-primary" onclick="javascript:removeUserFromArea('{{ us.pk }}')">{{ us.first_name }} {{ us.last_name }} <i class="fas fa-times"></i></a >
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p id="no_user_in_area">Diesem Bereich ist noch kein Mitarbeiter zugewiesen.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-success">Bereich aktualisieren</button>
|
||||||
|
<a class="btn" href="{% url 'areas-management' %} ">Abbrechen</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css"/> <!-- 'classic' theme -->
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.es5.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#div_id_color").hide();
|
||||||
|
})
|
||||||
|
|
||||||
|
const pickr1 = new Pickr({
|
||||||
|
el: '#color-picker',
|
||||||
|
default: "{{object.color}}",
|
||||||
|
components: {
|
||||||
|
preview: false,
|
||||||
|
hue: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
pickr1.on('changestop', function(){
|
||||||
|
var col = pickr1.getColor().toHEXA().toString();
|
||||||
|
pickr1.setColor(col);
|
||||||
|
$("#id_color").val(col);
|
||||||
|
});
|
||||||
|
|
||||||
|
var ua = window.navigator.userAgent;
|
||||||
|
var isIE = /MSIE|Trident/.test(ua);
|
||||||
|
if ( isIE ) {
|
||||||
|
//IE specific code goes here
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
checkValue();
|
||||||
|
},250);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var tempid = null;
|
||||||
|
var tempcounter = 0;
|
||||||
|
function addUserToArea(){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/areas/areaajax",
|
||||||
|
data:{
|
||||||
|
userid: tempid,
|
||||||
|
action : 'adduser',
|
||||||
|
objectid : {{objectid}}
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
clearSearchfield();
|
||||||
|
//Add User-Button
|
||||||
|
$("#added_users_button").append('<span id="span_btn_'+data['userid']+'" class="badge badge-pill badge-primary mr-2 mt-2"><a class="btn btn-primary" onclick="javascript:removeUserFromArea('+data['userid']+')">'+data['username_clean']+' <i class="fas fa-times"></i></a ></span>');
|
||||||
|
|
||||||
|
$("#usersfree").empty();
|
||||||
|
for (var i in data['remaining_users']) {
|
||||||
|
id = data['remaining_users'][i]['id'];
|
||||||
|
name = data['remaining_users'][i]['first_name'] + " " + data['remaining_users'][i]['last_name'];
|
||||||
|
$("#usersfree").append('<option id="'+id+'" value="'+name+'"></option>');
|
||||||
|
}
|
||||||
|
if(data['remaining_users_counter'] == 0){
|
||||||
|
$("#no_user_in_area").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#no_user_in_area").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove individual User from area, appened to the datalist!
|
||||||
|
function removeUserFromArea(user_id){
|
||||||
|
$.ajax(
|
||||||
|
{
|
||||||
|
type: "GET",
|
||||||
|
url: "/areas/areaajax",
|
||||||
|
data:{
|
||||||
|
userid: user_id,
|
||||||
|
action : 'remuser',
|
||||||
|
objectid : {{objectid}}
|
||||||
|
},
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
//Remove User-Button
|
||||||
|
$("#span_btn_"+data['userid']).remove();
|
||||||
|
//Rebuilding the Datalist
|
||||||
|
$("#usersfree").empty();
|
||||||
|
for (var i in data['remaining_users']) {
|
||||||
|
id = data['remaining_users'][i]['id'];
|
||||||
|
name = data['remaining_users'][i]['first_name'] + " " + data['remaining_users'][i]['last_name'];
|
||||||
|
$("#usersfree").append('<option id="'+id+'" value="'+name+'"></option>');
|
||||||
|
}
|
||||||
|
if(data['remaining_users_counter'] == 0){
|
||||||
|
$("#no_user_in_area").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#no_user_in_area").hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Clearing searchfield and set AddUser to false
|
||||||
|
function clearSearchfield(){
|
||||||
|
$("#searchusers").val("");
|
||||||
|
$("#addusertoareabtn").prop('disabled', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Check for valid input on inputfield
|
||||||
|
function checkValue(){
|
||||||
|
var g = $('#searchusers').val();
|
||||||
|
var id = $('#usersfree').find('option[value="' + g + '"]').attr('id');
|
||||||
|
if(id != undefined && id.length > 0){
|
||||||
|
tempid = id;
|
||||||
|
$("#addusertoareabtn").prop('disabled', false);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
tempid = null;
|
||||||
|
$("#addusertoareabtn").prop("disabled", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django.urls import path
|
||||||
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
'''
|
||||||
|
|
||||||
|
'' - Startseite nach erfolgreichem LOGIN
|
||||||
|
logout - Logoutseite nach LOGOUT
|
||||||
|
|
||||||
|
Permissions definiert in models.py bei USERS und dann hier vor die View geschrieben!
|
||||||
|
'''
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('areaajax/', views.area_addareas_ajax, name="area-ajaxview"),
|
||||||
|
path('updateorder/', views.area_neworder, name="area-ajaxorder")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.views.generic import CreateView, ListView, UpdateView, DetailView, DeleteView, View
|
||||||
|
from .models import Areas
|
||||||
|
from django.contrib import messages
|
||||||
|
from .forms import AreaAddAreaForm
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.http import HttpResponse, JsonResponse
|
||||||
|
import json
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from users.priomodel import Prio
|
||||||
|
from tasks.models import Tasks
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def area_addareas_ajax(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
# ADD USER TO MANY-TO-MANY USERSFIELD
|
||||||
|
if request.GET['action'] == 'adduser':
|
||||||
|
area = Areas.objects.get(pk=request.GET['objectid'])
|
||||||
|
|
||||||
|
if(area.agency == request.user.profile.agency):
|
||||||
|
area.usersfield.add(User.objects.get(pk=request.GET['userid']))
|
||||||
|
area.save()
|
||||||
|
# REMOVE USER TO MANY-TO-MANY USERSFIELD
|
||||||
|
elif request.GET['action'] == 'remuser':
|
||||||
|
area = Areas.objects.get(pk=request.GET['objectid'])
|
||||||
|
if(area.agency == request.user.profile.agency):
|
||||||
|
area.usersfield.remove(User.objects.get(pk=request.GET['userid']))
|
||||||
|
area.save()
|
||||||
|
userid = request.GET['userid']
|
||||||
|
workinguser = User.objects.get(pk=userid)
|
||||||
|
username_clean = workinguser.first_name + " " + workinguser.last_name
|
||||||
|
|
||||||
|
# Getting Remaining-Users
|
||||||
|
area = Areas.objects.get(pk=request.GET['objectid'])
|
||||||
|
if(area.agency == request.user.profile.agency):
|
||||||
|
added_users = area.usersfield.all()
|
||||||
|
possible_users = User.objects.filter(profile__agency__pk=request.user.profile.agency.pk).exclude(pk__in=added_users)
|
||||||
|
possible_users_js = list(possible_users.values())
|
||||||
|
# Cleaned out, that only data is neede will send to the side (first/last-name and id)
|
||||||
|
final_possible_users = {}
|
||||||
|
for ele in possible_users_js:
|
||||||
|
final_possible_users.update({'first_name':ele['first_name'],'last_name':ele['last_name'],'id':ele['id']})
|
||||||
|
# Counter for remaining users to show/hide "Keine Mitarbeiter"-Div
|
||||||
|
remaining_users_counter = len(added_users)
|
||||||
|
return JsonResponse({'userid' : userid, 'username_clean' : username_clean, 'remaining_users':possible_users_js, 'remaining_users_counter' : final_possible_users})
|
||||||
|
else:
|
||||||
|
return HttpResponse("Request method is not a GET")
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Update the Area-Order by drag and drop. Save per ID and order in table, example:
|
||||||
|
ID ORDER
|
||||||
|
0 --> 0
|
||||||
|
4 --> 1
|
||||||
|
2 --> 2
|
||||||
|
5 --> 3
|
||||||
|
|
||||||
|
Triggered by ajax in areas_management.html
|
||||||
|
Save all areas after drag n drop elements in table
|
||||||
|
|
||||||
|
'''
|
||||||
|
@login_required
|
||||||
|
def area_neworder(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
if request.GET['action'] == 'newareaorder':
|
||||||
|
neworderdata = json.loads(request.GET['finalod'])
|
||||||
|
for ele in neworderdata:
|
||||||
|
area = Areas.objects.get(pk=ele['id'])
|
||||||
|
if(area.agency == request.user.profile.agency):
|
||||||
|
area.areaorder = ele['neworder']
|
||||||
|
area.save()
|
||||||
|
return HttpResponse("UPDATED")
|
||||||
|
elif request.GET['action'] == 'newtaskorder':
|
||||||
|
tempuser = User.objects.get(pk=request.GET['userid'])
|
||||||
|
neworderdata = json.loads(request.GET['finalod'])
|
||||||
|
for ele in neworderdata:
|
||||||
|
print(request.GET['userid'])
|
||||||
|
print(ele)
|
||||||
|
prio = Prio.objects.filter(user__pk=request.GET['userid'], task__pk=ele['id'])
|
||||||
|
# PRIO FOUND
|
||||||
|
if(len(prio) > 0):
|
||||||
|
task = Tasks.objects.get(pk=ele['id'])
|
||||||
|
if(task.agency == request.user.profile.agency):
|
||||||
|
prio[0].prio = ele['neworder']
|
||||||
|
prio[0].save()
|
||||||
|
# NO PRIO FOUND - CREATE, SET NEW ORDER AND SAVE
|
||||||
|
else:
|
||||||
|
task = Tasks.objects.get(pk=ele['id'])
|
||||||
|
if(task.agency == request.user.profile.agency):
|
||||||
|
newprio = Prio(user=tempuser, task=Tasks.objects.get(pk=ele['id']), prio=ele['neworder']).save()
|
||||||
|
|
||||||
|
return HttpResponse("UPDATED")
|
||||||
|
else:
|
||||||
|
return HttpResponse("Request method is not a GET")
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ChatConfig(AppConfig):
|
||||||
|
name = 'chat'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue