This commit is contained in:
holger.trampe 2021-06-11 12:34:33 +02:00
commit d30cb95ed1
4289 changed files with 378728 additions and 0 deletions

1
README.md Normal file
View File

@ -0,0 +1 @@
digitale agentur - README

0
adm/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
adm/admin.py Normal file
View File

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

5
adm/apps.py Normal file
View File

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

57
adm/forms.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',
),
]

View File

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

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

28
adm/models.py Normal file
View File

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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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">&times;</span>
</button>
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</div>
<div style="height: 300px">&nbsp;</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 &copy; 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>

View File

@ -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>&nbsp;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>&nbsp;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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>&nbsp;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>&nbsp;<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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

Binary file not shown.

Binary file not shown.

View File

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

3
adm/tests.py Normal file
View File

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

26
adm/urls.py Normal file
View File

@ -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"),
]

679
adm/views.py Normal file
View File

@ -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']})

0
api/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
api/admin.py Normal file
View File

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

5
api/apps.py Normal file
View File

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

View File

3
api/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

54
api/serializers.py Normal file
View File

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

3
api/tests.py Normal file
View File

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

14
api/urls.py Normal file
View File

@ -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'),
]

77
api/views.py Normal file
View File

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

0
areas/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

5
areas/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Areas
# Register your models here.
admin.site.register(Areas)

5
areas/apps.py Normal file
View File

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

19
areas/forms.py Normal file
View File

@ -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']

View File

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

View File

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

View File

@ -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'),
),
]

View File

Binary file not shown.

39
areas/models.py Normal file
View File

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

View File

@ -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>&nbsp;
<a href="{% url 'areas-management' %}" class="btn btn-success">Abbrechen</a>
</div>
</form>
</div>
{% endblock content %}

View File

@ -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>&nbsp;
<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 %}

View File

@ -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">&nbsp;</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 %}

View File

@ -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 }}&nbsp;&nbsp;<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>&nbsp;
<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']+'&nbsp;&nbsp;<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 %}

3
areas/tests.py Normal file
View File

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

19
areas/urls.py Normal file
View File

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

95
areas/views.py Normal file
View File

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

0
chat/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
chat/admin.py Normal file
View File

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

5
chat/apps.py Normal file
View File

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