diff --git a/CHANGELOG.md b/CHANGELOG.md
index b571aebe84110cc57230d90c9f2d69d691fac54e..2dc8e229104aee482fd7bba92d1c47979d611d77 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -76,4 +76,11 @@ Weitere Verbesserungen der vorangegeangen Versionen dieses Releases.
   + Einfachere Einbindung von React für REBUS
 
 ## 1.1.2 "Aebli"
-* Bugfix in der PWA (zuerst Netzwerk, dann Cache)
\ No newline at end of file
+* Bugfix in der PWA (zuerst Netzwerk, dann Cache)
+
+## 1.1.3 "Aebli"
+* Bugfix in REBUS (Submit wieder erlaubt) (#335)
+* Klassenlehrkräfte werden im Plan angezeigt (#334)
+
+## 1.1.4 "Aebli"
+* Bugfix in der PWA (Offline-Fallback-Page wird nicht mehr bei teils aktiver Netzwerkverbindung fälschlicherweise angezeigt)
\ No newline at end of file
diff --git a/INSTALL.md b/INSTALL.md
index 64d0f5baf2e338d30adb42f025ece35dc18c5e7a..05e7f01bb908b0de69941f43a75cd935c2a443bd 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -33,7 +33,6 @@ GRANT SELECT ON Untis.* TO 'www-data'@'localhost';
 ### UNTIS-Beispieldaten importieren
 Zum Testen kann die Datei `untiskath.sql` vom Forum in die Datenbank `Untis` importiert werden.
 
-
 ### SchoolApps clonen
 ```
 git clone git@github.com:Katharineum/school-apps.git
@@ -49,7 +48,6 @@ pip install -r requirements.txt
 
 - `example_secure_settings.py` zu `secure_settings.py` kopieren und anpassen (hier müssen auch die passenden DB-Zugangsdaten eingetragen werden)
 
-
 ### Migrations durchführen/auflösen
 Leider kommt es bei einer Erstinstallation von SchoolApps immer noch zu Problemen mit den Migrations. Sollte es Schwierigkeiten geben, @hansegucker kontaktieren.
 
diff --git a/README.md b/README.md
index d95f8391b9751c61248844e996ebab6516e7e3ed..731e35747099e550067dcbffad69d490de7b8d3b 100755
--- a/README.md
+++ b/README.md
@@ -2,6 +2,6 @@
 
 SchoolApps ist ein modulares System zum Bereitstellen von speziellen Anwendungen für den Schulalltag. Besonderer Fokus liegt dabei auf dem Stundenplansystem.
 
-© 2018 by [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank-Poetzsch-Heffter](mailto:p-h@katharineum.de)
+© 2018 by [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank Poetzsch-Heffter](mailto:p-h@katharineum.de)
 
-© 2019 by [Hangzhi Yu](mailto:yuha@katharineum.de), [Julian Leucker](mailto:leuckeju@katharineum.de), [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank-Poetzsch-Heffter](mailto:p-h@katharineum.de)
\ No newline at end of file
+© 2019 by [Hangzhi Yu](mailto:yuha@katharineum.de), [Julian Leucker](mailto:leuckeju@katharineum.de), [Jonathan Weth](mailto:wethjo@katharineum.de), [Frank Poetzsch-Heffter](mailto:p-h@katharineum.de)
diff --git a/react/src/rebus.js b/react/src/rebus.js
index a0fd50b4bf9a7aa02d3a0886de069bc0aa15ba4f..5799a15cbc881d4580a2c441bde3ceff19e0071c 100755
--- a/react/src/rebus.js
+++ b/react/src/rebus.js
@@ -222,9 +222,7 @@ function getCategoryOfOption(option) {
 
 function getOption(option) {
     for (const category of BASIC_OPTIONS) {
-        // console.log(category);
         for (const opt of category.options) {
-            // console.log(opt);
             if (opt.id === option) {
                 return opt;
             }
@@ -388,7 +386,7 @@ class REBUSDynSelect extends React.Component {
                     <Input label={"Art des Problems"} icon={"bug_report"} show={sO === "printerIssue"}>
                         <Select onChange={this._onSetB}
                                 values={["Papierstau", "Toner leer", "Papier leer", "Drucker bekommt keine Daten"]}
-                                defaultValue={"Anderes Problem"} show={sO === "subMonitorIssue"}/>
+                                defaultValue={"Anderes Problem"} show={sO === "printerIssue"}/>
                     </Input>
 
                     {/* Section B – Substitution Monitor Issue */}
@@ -432,7 +430,7 @@ class REBUSDynSelect extends React.Component {
                     <Input label={"Ort des Druckers"} icon={"location_on"}
                            show={sO === "printerIssue" && step === 2}>
                         <Select onChange={this._onSetC} values={LOCATIONS}
-                                defaultValue={"Anderer Raum"} show={sO === "presentationDeviceIssue"}/>
+                                defaultValue={"Anderer Raum"} show={sO === "printerIssue"}/>
                     </Input>
 
                     {/* Section C – WLAN Issue */}
diff --git a/requirements.txt b/requirements.txt
index 95162f578fab706905d1cf1eb69c600d9ee99046..e760b444ea85230fa371ede00451160a5bbd353e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,6 @@
 requests
 mysqlclient
-django
+django<3.0
 django-auth-ldap
 django-dbsettings
 django-pdb
diff --git a/schoolapps/aub/forms.py b/schoolapps/aub/forms.py
index 245e2619bd5b7d2afa9cec7906fd1c61fd0ea9cd..1b59bbffc20e41edab82c12146d5e75e7763c9fa 100755
--- a/schoolapps/aub/forms.py
+++ b/schoolapps/aub/forms.py
@@ -4,6 +4,7 @@ from django.utils import timezone
 from datetime import datetime
 from material import Layout, Row, Fieldset
 from aub.models import Aub
+# from tinymce.widgets import TinyMCE
 
 
 class ApplyForAUBForm(forms.ModelForm):
@@ -17,6 +18,7 @@ class ApplyForAUBForm(forms.ModelForm):
     to_date = forms.DateField(label='Datum', input_formats=['%d.%m.%Y'])
     to_lesson = forms.ChoiceField(label='Stunde', choices=lessons, required=False, widget=forms.Select(attrs={'onchange': 'setTime(this)'}))
     to_time = forms.TimeField(label='Zeit', input_formats=['%H:%M'], initial=initial_to_time)
+    # description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.', widget=TinyMCE(attrs={'cols': 80, 'rows': 30}))
     description = forms.CharField(label='Bitte begründen Sie Ihren Antrag.')
 
     layout = Layout(Fieldset('Von',
@@ -33,6 +35,9 @@ class ApplyForAUBForm(forms.ModelForm):
         model = Aub
         fields = ('id', 'from_date', 'from_time', 'to_date', 'to_time', 'description')
 
+    class Media:
+        js = ('/media/tinymce/jscripts/tiny_mce/tiny_mce.js', '',)
+
     def clean(self):
         cleaned_data = super().clean()
 
diff --git a/schoolapps/aub/models.py b/schoolapps/aub/models.py
index e9193afc05c98914f97f43ede0cfe91fdc08af1a..7366bd652640e2b1b84de11b692cd4549a126cff 100755
--- a/schoolapps/aub/models.py
+++ b/schoolapps/aub/models.py
@@ -45,7 +45,6 @@ class Aub(models.Model):
     created_at = models.DateTimeField(default=timezone.now, verbose_name="Erstellungszeitpunkt")
 
     def getStatus(self):
-        print(self.status, self.created_by, self.id)
         return status_list[self.status]
 
     def __str__(self):
diff --git a/schoolapps/aub/templates/aub/index.html b/schoolapps/aub/templates/aub/index.html
index 59defc97477ddb85664572ee5463a189c971d488..ce9d67f5818fa788249e524cf97fa8d164471b0e 100755
--- a/schoolapps/aub/templates/aub/index.html
+++ b/schoolapps/aub/templates/aub/index.html
@@ -33,7 +33,7 @@
                         <div class="col s12 m4">
                             <p>
                             {% if aub.status == 0 %}
-                                <form action="{% url 'aub_edit' aub.id %}" class="right">
+                                <form action="{% url 'aub_edit' aub.id %}" class="left">
                                     {% csrf_token %}
                                     <button type="submit" name="edit" class="waves-effect waves-light btn-flat btn-flat-medium" title="Bearbeiten">
                                         <i class="material-icons center green-text">create</i>
@@ -41,7 +41,7 @@
                                 </form>
                             {% endif %}
                             {% if aub.status == 0 or aub.status == 1 %}
-                                <form action="" method="POST" class="right">
+                                <form action="" method="POST" class="left">
                                     {% csrf_token %}
                                     <input type="hidden" value="{{ aub.id }}" name="aub-id">
                                     <button type="submit" onclick="return confirm('Wollen Sie den Antrag wirklich löschen?')" name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium" title="Löschen">
diff --git a/schoolapps/aub/views.py b/schoolapps/aub/views.py
index ba1a759e7cd7b711d3da9ee0a2058375d98c798e..578c8d89c0dd4cc9c9fe8e808f05e4c243198321 100755
--- a/schoolapps/aub/views.py
+++ b/schoolapps/aub/views.py
@@ -196,7 +196,10 @@ def check2(request):
 def archive(request):
     order_crit = '-from_date'
     if 'created_by' in request.GET:
-        item = int(request.GET['created_by'])
+        try:
+            item = int(request.GET['created_by'])
+        except ValueError:
+            item = ''
         aub_list = Aub.objects.filter((Q(status__exact=2) | Q(status__exact=3)) & Q(created_by=item)).order_by(order_crit)
     else:
         aub_list = Aub.objects.filter(Q(status__exact=2) | Q(status__exact=3)).order_by(order_crit)
diff --git a/schoolapps/dashboard/migrations/0002_cache.py b/schoolapps/dashboard/migrations/0002_cache.py
index 55ee00afe0e0c57fac5e0e62773131ec36a52309..1d146b35c7961c326f2b7de0bcf33ea6bdb552a5 100644
--- a/schoolapps/dashboard/migrations/0002_cache.py
+++ b/schoolapps/dashboard/migrations/0002_cache.py
@@ -13,8 +13,8 @@ class Migration(migrations.Migration):
             name='Cache',
             fields=[
                 ('id',
-                 models.CharField(max_length=200, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
-                ('name', models.CharField(max_length=200, verbose_name='Name')),
+                 models.CharField(max_length=150, primary_key=True, serialize=False, unique=True, verbose_name='ID')),
+                ('name', models.CharField(max_length=150, verbose_name='Name')),
                 ('expiration_time', models.IntegerField(default=20, verbose_name='Ablaufzeit')),
             ],
             options={
diff --git a/schoolapps/dashboard/models.py b/schoolapps/dashboard/models.py
index 3aaf75ae4c872092683b26809d6cbbc0155da8b1..8a26ce363f72ef0867614a389fc1c7b1062ee16f 100755
--- a/schoolapps/dashboard/models.py
+++ b/schoolapps/dashboard/models.py
@@ -11,7 +11,7 @@ from mailer import send_mail_with_template
 class Activity(models.Model):
     user = models.ForeignKey(to=User, on_delete=models.CASCADE)
 
-    title = models.CharField(max_length=200)
+    title = models.CharField(max_length=150)
     description = models.TextField(max_length=500)
 
     app = models.CharField(max_length=100)
@@ -24,7 +24,7 @@ class Activity(models.Model):
 
 class Notification(models.Model):
     user = models.ForeignKey(to=User, on_delete=models.CASCADE, related_name="notifications")
-    title = models.CharField(max_length=200)
+    title = models.CharField(max_length=150)
     description = models.TextField(max_length=500)
     link = models.URLField(blank=True)
 
@@ -48,8 +48,8 @@ def register_notification(user, title, description, app="SchoolApps", link=""):
 
 
 class Cache(models.Model):
-    id = models.CharField(max_length=200, unique=True, primary_key=True, verbose_name="ID")
-    name = models.CharField(max_length=200, verbose_name="Name")
+    id = models.CharField(max_length=150, unique=True, primary_key=True, verbose_name="ID")
+    name = models.CharField(max_length=150, verbose_name="Name")
     expiration_time = models.IntegerField(default=20, verbose_name="Ablaufzeit")
     last_time_updated = models.DateTimeField(blank=True, null=True,
                                              verbose_name="Letzter Aktualisierungszeitpunkt des Caches")
diff --git a/schoolapps/fibu/README.md b/schoolapps/fibu/README.md
new file mode 100755
index 0000000000000000000000000000000000000000..bca3b98cf3f604d0009e59c57ce83a6fd698f3c8
--- /dev/null
+++ b/schoolapps/fibu/README.md
@@ -0,0 +1,30 @@
+# SchoolApps: FiBu (Finanzbuchhaltung)
+## Workflow
+
+1.  Berechtigungen setzen
+    + Anwender: request_booking
+    + Buchhalter: manage_booking
+    + Manager: manage_costcenter, manage_account
+
+2.  Manager richtet Kostenstellen (z.B. Schulträger-konsumtiv, 
+Schulträger-investiv, Elternverein, Fundraising, ...) sowie 
+Buchungskonten (z.B. Klassenraum-Festwert, Geschäftszimmer, 
+Fachschaft Mathematik, ...) ein und setzt dabei Budgets fest, 
+die in der Summe den Haushaltsplan ergeben.
+
+3. Anwender stellt Anträge für Ausgaben.
+
+4. Manager bewilligt Anträge unter Zuordnung zum passenden 
+Buchungskonto oder lehnt sie ab.
+
+5. Anwender gibt für bewilligte Anträge Bestellung auf und 
+ändert den Status der Buchung entsprechend.
+
+6. Anwender reicht Rechnung im Geschäftszimmer ein und ändert 
+den Status der Buchung entsprechend.
+
+7. Buchhalter vervollständigt die Buchung, weist die Rechnung 
+an und ändert den Status auf bezahlt.
+
+8. Buchhalter und Manager kontrollieren durch Aufruf von Berichten 
+den Haushalt.
\ No newline at end of file
diff --git a/schoolapps/fibu/__init__.py b/schoolapps/fibu/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/schoolapps/fibu/admin.py b/schoolapps/fibu/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f16fa184564f6a7a31fcaec3458e6e16b26c43c
--- /dev/null
+++ b/schoolapps/fibu/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+from .models import CostCenter, Account, Booking
+
+admin.site.register(CostCenter)
+admin.site.register(Account)
+admin.site.register(Booking)
diff --git a/schoolapps/fibu/apps.py b/schoolapps/fibu/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..f98393e1c0883b1ad219582feb398b0381e45263
--- /dev/null
+++ b/schoolapps/fibu/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class FibuConfig(AppConfig):
+    name = 'fibu'
diff --git a/schoolapps/fibu/decorators.py b/schoolapps/fibu/decorators.py
new file mode 100644
index 0000000000000000000000000000000000000000..63210079b3262886d437ca6cd785810f6ac9c7df
--- /dev/null
+++ b/schoolapps/fibu/decorators.py
@@ -0,0 +1,24 @@
+from django.contrib.auth.decorators import user_passes_test
+from django.contrib.auth import REDIRECT_FIELD_NAME
+
+from .models import Booking
+
+
+# prevent to show booking details from foreign users
+def check_own_booking_verification(user):
+    return Booking.objects.all().filter(created_by=user)
+
+
+def check_own_booking(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
+    """
+    Decorator for views that checks that the user only gets his own bookings, redirecting
+    to the dashboard if necessary.
+    """
+    actual_decorator = user_passes_test(
+        check_own_booking_verification,
+        login_url=login_url,
+        redirect_field_name=redirect_field_name
+    )
+    if function:
+        return actual_decorator(function)
+    return actual_decorator
diff --git a/schoolapps/fibu/filters.py b/schoolapps/fibu/filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6ca80862169f0d0fdd037163f23b0ffdfaedd34
--- /dev/null
+++ b/schoolapps/fibu/filters.py
@@ -0,0 +1,25 @@
+from django.contrib.auth.models import User
+import django_filters
+from .models import Booking
+from django.db.utils import ProgrammingError
+
+
+def get_fibu_users():
+    """ Find all users who requests a boooking """
+    try:
+        fibu_users = Booking.objects.values_list('contact')
+        users = list(User.objects.filter(id__in=fibu_users))
+        # user_ids = [(str(user.id),user.username) for user in users]
+        user_ids = [(str(user.id), user.last_name + ', ' + user.first_name) for user in users]
+        user_ids_sorted = sorted(user_ids, key=lambda user: user[1])
+        return user_ids_sorted
+    except ProgrammingError:
+        return []
+
+
+class BookingFilter(django_filters.FilterSet):
+    contact = django_filters.ChoiceFilter(label='Von', choices=get_fibu_users())
+
+    class Meta:
+        model = Booking
+        fields = ['contact', ]
diff --git a/schoolapps/fibu/forms.py b/schoolapps/fibu/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a4854092ee7068770b0275eee4f3b3ab6887471
--- /dev/null
+++ b/schoolapps/fibu/forms.py
@@ -0,0 +1,62 @@
+from django import forms
+from django.utils import timezone
+from material import Layout, Row, Fieldset
+
+from .models import Booking, CostCenter, Account
+
+
+class SimpleBookingForm(forms.ModelForm):
+    description = forms.CharField(label='Beschreibung – Was soll angeschafft werden?')
+    planned_amount = forms.IntegerField(
+        label='Erwarteter Betrag – Welcher Betrag ist erforderlich?', help_text="in Euro, ohne Komma")
+    justification = forms.CharField(label='Begründung – Begründe ggf. deinen Antrag.', required=False)
+
+    layout = Layout(Row('description', 'planned_amount'), Row('justification'))
+
+    class Meta:
+        model = Booking
+        fields = ['id', 'description', 'planned_amount', 'justification']
+
+
+class CheckBookingForm(forms.ModelForm):
+    account = forms.ModelChoiceField(Account.objects.filter().order_by('cost_center', 'name'))
+
+    class Meta:
+        model = Booking
+        fields = ['account', ]
+
+
+class CompleteBookingForm(forms.ModelForm):
+    accounts = Account.objects.filter().order_by('cost_center', 'name')
+    account = forms.ModelChoiceField(queryset=accounts)
+    submission_date = forms.DateField(label='Bearbeitungsdatum', initial=timezone.now())
+
+    layout = Layout(Fieldset("Allgemeines",
+                             Row('description', 'justification'),
+                             Row("contact", "planned_amount"),
+                             Row('account', 'status')
+                             ),
+                    Fieldset('Details',
+                             Row('firma', 'invoice_number', 'amount'),
+                             Row('invoice_date', 'maturity', 'submission_date', 'booking_date'),
+                             Row('payout_number', 'upload')
+                             )
+                    )
+
+    class Meta:
+        model = Booking
+        fields = ['id', 'description', 'planned_amount', 'justification', 'account', 'contact', 'invoice_date',
+                  'invoice_number', 'firma', 'amount', 'submission_date', 'payout_number', 'booking_date',
+                  'maturity', 'upload', 'status']
+
+
+class CostCenterForm(forms.ModelForm):
+    class Meta:
+        model = CostCenter
+        fields = ['id', 'name', 'year']
+
+
+class AccountForm(forms.ModelForm):
+    class Meta:
+        model = Account
+        fields = ['id', 'name', 'cost_center', 'income', 'budget']
diff --git a/schoolapps/fibu/migrations/0001_initial.py b/schoolapps/fibu/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..acff540c447b1c52a06949b1e1a9b78d20edded1
--- /dev/null
+++ b/schoolapps/fibu/migrations/0001_initial.py
@@ -0,0 +1,72 @@
+# Generated by Django 2.2.8 on 2019-12-27 06:08
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Account',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(default='', max_length=20)),
+                ('income', models.BooleanField(default=False)),
+                ('budget', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
+                ('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
+                ('rest', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
+            ],
+            options={
+                'permissions': [('manage_account', 'Can manage account')],
+            },
+        ),
+        migrations.CreateModel(
+            name='CostCenter',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=30)),
+                ('year', models.IntegerField(choices=[(2020, '2020'), (2021, '2021'), (2022, '2022'), (2023, '2023')],
+                                             default=2019, verbose_name='Jahr')),
+            ],
+            options={
+                'permissions': [('manage_costcenter', 'Can manage costcenter')],
+            },
+        ),
+        migrations.CreateModel(
+            name='Booking',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('invoice_date', models.DateField(default='2000-12-31')),
+                ('invoice_number', models.CharField(default='0', max_length=20)),
+                ('firma', models.CharField(default='', max_length=30)),
+                ('description', models.CharField(max_length=50)),
+                ('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=9)),
+                ('planned_amount', models.IntegerField()),
+                ('submission_date', models.DateField(default='2000-12-31')),
+                ('justification', models.CharField(blank=True, max_length=2000, null=True)),
+                ('payout_number', models.IntegerField(default=0)),
+                ('booking_date', models.DateField(default='2000-12-31')),
+                ('maturity', models.DateField(default='2000-12-31')),
+                ('upload', models.FileField(blank=True, default=None, null=True, upload_to='uploads/fibu/%Y/')),
+                ('status', models.IntegerField(choices=[(0, 'beantragt'), (1, 'bewilligt'), (2, 'abgelehnt'), (3, 'bestellt'), (4, 'eingereicht'), (5, 'bezahlt')], default=0, verbose_name='Status')),
+                ('account', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='fibu.Account')),
+                ('contact', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bookings', to=settings.AUTH_USER_MODEL, verbose_name='Erstellt von')),
+            ],
+            options={
+                'permissions': [('manage_booking', 'Can manage bookings'), ('request_booking', 'Can request a booking')],
+            },
+        ),
+        migrations.AddField(
+            model_name='account',
+            name='costcenter',
+            field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, to='fibu.CostCenter'),
+        ),
+    ]
diff --git a/schoolapps/fibu/migrations/0002_auto_20191228_1357.py b/schoolapps/fibu/migrations/0002_auto_20191228_1357.py
new file mode 100644
index 0000000000000000000000000000000000000000..50c6c27ac779af2362fd1a5ca6372f0ea76f7694
--- /dev/null
+++ b/schoolapps/fibu/migrations/0002_auto_20191228_1357.py
@@ -0,0 +1,90 @@
+# Generated by Django 2.2.6 on 2019-12-28 12:57
+
+import datetime
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('fibu', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='account',
+            options={'permissions': [('manage_account', 'Can manage account')], 'verbose_name': 'Buchungskonto',
+                     'verbose_name_plural': 'Buchungskonten'},
+        ),
+        migrations.AlterModelOptions(
+            name='booking',
+            options={
+                'permissions': [('manage_booking', 'Can manage bookings'), ('request_booking', 'Can request a booking'),
+                                ('check_booking', 'Can check bookings')], 'verbose_name': 'Buchung',
+                'verbose_name_plural': 'Buchungen'},
+        ),
+        migrations.AlterModelOptions(
+            name='costcenter',
+            options={'permissions': [('manage_costcenter', 'Can manage costcenter')], 'verbose_name': 'Kostenstelle',
+                     'verbose_name_plural': 'Kostenstellen'},
+        ),
+        migrations.AlterField(
+            model_name='account',
+            name='budget',
+            field=models.DecimalField(decimal_places=2, default=0.0, max_digits=9, verbose_name='Budget'),
+        ),
+        migrations.AlterField(
+            model_name='account',
+            name='costcenter',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fibu.CostCenter',
+                                    verbose_name='Kostenstelle'),
+        ),
+        migrations.AlterField(
+            model_name='account',
+            name='income',
+            field=models.BooleanField(default=False, verbose_name='Einnahmekonto'),
+        ),
+        migrations.AlterField(
+            model_name='account',
+            name='name',
+            field=models.CharField(max_length=20, verbose_name='Buchungskonto'),
+        ),
+        migrations.AlterField(
+            model_name='booking',
+            name='booking_date',
+            field=models.DateField(default=datetime.date.today),
+        ),
+        migrations.AlterField(
+            model_name='booking',
+            name='invoice_date',
+            field=models.DateField(default=datetime.date.today),
+        ),
+        migrations.AlterField(
+            model_name='booking',
+            name='maturity',
+            field=models.DateField(default=datetime.date.today),
+        ),
+        migrations.AlterField(
+            model_name='booking',
+            name='status',
+            field=models.IntegerField(
+                choices=[(0, 'beantragt'), (1, 'abgelehnt'), (2, 'bewilligt'), (3, 'bestellt'), (4, 'eingereicht'),
+                         (5, 'bezahlt')], default=0, verbose_name='Status'),
+        ),
+        migrations.AlterField(
+            model_name='booking',
+            name='submission_date',
+            field=models.DateField(default=datetime.date.today),
+        ),
+        migrations.AlterField(
+            model_name='costcenter',
+            name='name',
+            field=models.CharField(max_length=30, verbose_name='Kostenstelle'),
+        ),
+        migrations.AlterField(
+            model_name='costcenter',
+            name='year',
+            field=models.IntegerField(choices=[(2019, '2019'), (2020, '2020'), (2021, '2021'), (2022, '2022')],
+                                      default=2019, verbose_name='Jahr'),
+        ),
+    ]
diff --git a/schoolapps/fibu/migrations/0003_auto_20191228_1553.py b/schoolapps/fibu/migrations/0003_auto_20191228_1553.py
new file mode 100644
index 0000000000000000000000000000000000000000..6674b3d95d25eb5451050e7c10fefb65149ead28
--- /dev/null
+++ b/schoolapps/fibu/migrations/0003_auto_20191228_1553.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.8 on 2019-12-28 14:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('fibu', '0002_auto_20191228_1357'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='account',
+            name='budget',
+            field=models.IntegerField(default=0, verbose_name='Budget'),
+        ),
+    ]
diff --git a/schoolapps/fibu/migrations/__init__.py b/schoolapps/fibu/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/schoolapps/fibu/models.py b/schoolapps/fibu/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8611ca69c7738929db51b8cd97a02da7f8c5cb5e
--- /dev/null
+++ b/schoolapps/fibu/models.py
@@ -0,0 +1,108 @@
+from django.utils import timezone
+from django.db import models
+from django.contrib.auth.models import User
+from datetime import date
+
+current_year = timezone.now().year
+YEARS = [(x, str(x)) for x in range(current_year, current_year + 4)]
+
+
+class Status:
+    def __init__(self, name, style_class):
+        self.name = name
+        self.style_class = style_class
+
+    def __str__(self):
+        return self.name
+
+
+status_list = [
+    Status(name='beantragt', style_class='red'),
+    Status(name='abgelehnt', style_class='black'),
+    Status(name='bewilligt', style_class='orange'),
+    Status(name='bestellt', style_class='yellow darken-1'),
+    Status(name='eingereicht', style_class='blue'),
+    Status(name='bezahlt', style_class='green'),
+]
+
+status_choices = [(x, val.name) for x, val in enumerate(status_list)]
+
+
+class CostCenter(models.Model):
+    # Kostenstellen z.B. Schulträger-konsumtiv, Schulträger-investiv, Elternverein, ...
+    name = models.CharField(max_length=30, blank=False, verbose_name="Kostenstelle")
+    year = models.IntegerField(default=timezone.now().year, choices=YEARS, blank=False, verbose_name="Jahr")
+
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        verbose_name = "Kostenstelle"
+        verbose_name_plural = "Kostenstellen"
+        permissions = [
+            ('manage_costcenter', 'Can manage costcenter'),
+        ]
+
+
+class Account(models.Model):
+    # Buchungskonten, z.B. Fachschaften, Sekretariat, Schulleiter, Kopieren, Tafelnutzung
+    name = models.CharField(max_length=20, blank=False, verbose_name="Buchungskonto")
+    cost_center = models.ForeignKey(to=CostCenter, on_delete=models.CASCADE, blank=False, verbose_name="Kostenstelle")
+    income = models.BooleanField(default=False,
+                                 verbose_name="Einnahmekonto")  # True, wenn es sich um ein Einnahmekonto handelt
+    budget = models.IntegerField(default=0, verbose_name="Budget")
+    saldo = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
+    rest = models.DecimalField(max_digits=9, decimal_places=2, default=0.00)
+
+    def __str__(self):
+        return "{}: {}".format(self.cost_center, self.name)
+
+    class Meta:
+        verbose_name = "Buchungskonto"
+        verbose_name_plural = "Buchungskonten"
+        permissions = [
+            ('manage_account', 'Can manage account'),
+        ]
+
+
+class Booking(models.Model):
+    # General information
+    account = models.ForeignKey(to=Account, on_delete=models.SET_NULL, blank=True, null=True,
+                                verbose_name="Buchungskonto")
+    contact = models.ForeignKey(to=User, related_name='bookings', on_delete=models.SET_NULL
+                                , verbose_name="Erstellt von", blank=True, null=True)
+    description = models.CharField(max_length=50, verbose_name="Beschreibung")
+    justification = models.CharField(max_length=2000, blank=True, null=True, verbose_name="Begründung")
+    planned_amount = models.IntegerField(verbose_name="Erwarteter Betrag", help_text="ganze Euro")
+
+    # Details
+    invoice_date = models.DateField(blank=True, null=True, verbose_name="Rechnungsdatum")
+    invoice_number = models.CharField(max_length=20, blank=True, null=True, verbose_name="Rechnungsnummer")
+    firma = models.CharField(max_length=30, blank=True, null=True, verbose_name="Firma")
+    amount = models.DecimalField(max_digits=9, decimal_places=2, default=0.00, verbose_name="Betrag")
+    payout_number = models.IntegerField(blank=True, null=True, verbose_name="Auszahlungsnummer")
+
+    submission_date = models.DateField(blank=True, null=True, verbose_name="Bearbeitungsdatum")
+    booking_date = models.DateField(default=date.today, verbose_name="Buchungsdatum")
+    maturity = models.DateField(blank=True, null=True, verbose_name="Fälligkeit")
+
+    upload = models.FileField(upload_to='uploads/fibu/%Y/', default=None, blank=True, null=True,
+                              verbose_name="Scan der Rechnung")
+
+    # Meta information
+    status = models.IntegerField(default=0, choices=status_choices, verbose_name="Status")
+
+    def get_status(self):
+        return status_list[self.status]
+
+    def __str__(self):
+        return "{} ({})".format(self.description, self.account)
+
+    class Meta:
+        verbose_name = "Buchung"
+        verbose_name_plural = "Buchungen"
+        permissions = [
+            ('manage_booking', 'Can manage bookings'),
+            ('request_booking', 'Can request a booking'),
+            ('check_booking', 'Can check bookings'),
+        ]
diff --git a/schoolapps/fibu/templates/fibu/account/edit.html b/schoolapps/fibu/templates/fibu/account/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..185d5f80fdc5ac53d3c5c824c21717a7f7578e6c
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/account/edit.html
@@ -0,0 +1,23 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Buchungskonto bearbeiten</h4>
+
+    <form method="POST" action="">
+        {% csrf_token %}
+        {% form form=form %}
+        {% endform %}
+
+        <span class="right">
+            <button type="submit" class="waves-effect waves-light btn green">
+                <i class="material-icons left">save</i> Änderungen übernehmen
+            </button>
+            <a href="{% url 'fibu_accounts' %}" class="waves-effect waves-light btn red">
+                <i class="material-icons left">cancel</i> Abbrechen
+            </a>
+        </span>
+    </form>
+
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/account/index.html b/schoolapps/fibu/templates/fibu/account/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..b12b41f7025d30f4b8f2971cafc268f095351e7d
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/account/index.html
@@ -0,0 +1,76 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <a class="waves-effect waves-light btn green modal-trigger right" href="#new-modal">
+        <i class="material-icons left">add</i> Buchungskonto anlegen
+    </a>
+
+    <h4>Buchungskonten</h4>
+
+    {% if form.errors %}
+        <script>
+            var onFinish = function () {
+                $("#new-modal").modal("open");
+            };
+        </script>
+    {% endif %}
+
+    <form method="POST">
+        <div id="new-modal" class="modal">
+            <div class="modal-content">
+                <h5>Neues Buchungskonto anlegen</h5>
+                {% csrf_token %}
+                {% form form=form %}
+                {% endform %}
+            </div>
+            <div class="modal-footer">
+                <button type="submit" class="waves-effect waves-light btn green">
+                    <i class="material-icons left">save</i> Buchungskonto anlegen
+                </button>
+            </div>
+        </div>
+    </form>
+
+    <table>
+        <thead>
+        <tr>
+            <th>Buchungskonto</th>
+            <th>Kostenstelle</th>
+            <th class="right-align">erwartete Einnahmen</th>
+            <th class="right-align">erwartete Ausgaben</th>
+            <th>Aktionen</th>
+        </tr>
+        </thead>
+        <tbody>
+        {% for account in accounts %}
+            <tr>
+                <td>{{ account.name }}</td>
+                <td>{{ account.cost_center }}</td>
+                {% if account.income %}
+                    <td class="green-text right-align">{{ account.budget }} €</td>
+                    <td></td>
+                {% else %}
+                    <td></td>
+                    <td class="red-text right-align">{{ account.budget }} €</td>
+                {% endif %}
+                <td class="right-align">
+                    <a href="{% url 'fibu_accounts_edit' account.id %}"
+                       class="waves-effect waves-light btn-flat btn-flat-medium left" title="Bearbeiten">
+                        <i class="material-icons center green-text">create</i>
+                    </a>
+                    <form action="" method="POST" class="left">
+                        {% csrf_token %}
+                        <input type="hidden" value="{{ account.id }}" name="id">
+                        <button type="submit" onclick="return confirm('Buchungskonto wirklich löschen?')"
+                                name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium" title="Löschen">
+                            <i class="material-icons center red-text">cancel</i>
+                        </button>
+                    </form>
+                </td>
+            </tr>
+        {% endfor %}
+        </tbody>
+    </table>
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/booking/book.html b/schoolapps/fibu/templates/fibu/booking/book.html
new file mode 100644
index 0000000000000000000000000000000000000000..0f44c48c719451b596ccf484d9ef90ba84a70b25
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/booking/book.html
@@ -0,0 +1,23 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Buchung bearbeiten</h4>
+
+    <form method="POST">
+        {% csrf_token %}
+        {% form form=form %}
+        {% endform %}
+
+        <span class="right">
+            <button type="submit" class="waves-effect waves-light btn green">
+                <i class="material-icons left">save</i> Änderungen übernehmen
+            </button>
+            <a href="{% url 'fibu_bookings' %}" class="waves-effect waves-light btn red">
+                <i class="material-icons left">cancel</i> Abbrechen
+            </a>
+        </span>
+    </form>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/booking/check.html b/schoolapps/fibu/templates/fibu/booking/check.html
new file mode 100755
index 0000000000000000000000000000000000000000..9dad97f53214556cb9152fc5093440dc5c13caf5
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/booking/check.html
@@ -0,0 +1,44 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Anträge prüfen</h4>
+    {% if not filter.qs %}
+        <span  class="flow-text center-align">
+            Keine offenen Anträge vorhanden
+        </span>
+    {% else %}
+        <div class="collection-item row">
+            <div class="col s2"><strong>Antragsteller</strong></div>
+            <div class="col s3"><strong>Anschaffungswunsch</strong></div>
+            <div class="col s2 right-align"><strong>Geplante Kosten</strong></div>
+            <div class="col s3"><strong>Buchungskonto</strong></div>
+            <div class="col s2"><strong>Aktionen</strong></div>
+        </div>
+
+        {% for booking in filter.qs %}
+            <div class="collection-item row">
+                <form method="POST">
+                    {% csrf_token %}
+                    <input type="hidden" value="{{ booking.id }}" name="booking-id">
+                    <div class="col s2">{{ booking.contact.get_full_name }}</div>
+                    <div class="col s3">{{ booking.description }}</div>
+                    <div class="col s2 right-align">{{ booking.planned_amount }} €</div>
+                    <div class="col s3">{{ form.account }}</div>
+                    <div class="col s2">
+                        <button type="submit" name="allow"
+                                class="waves-effect waves-light btn-flat btn-flat-medium" title="Annehmen">
+                            <i class="material-icons center green-text">check_circle</i>
+                        </button>
+                        <button type="submit" name="deny"
+                                class="waves-effect waves-light btn-flat btn-flat-medium" title="Ablehnen">
+                            <i class="material-icons center red-text">not_interested</i>
+                        </button>
+                    </div>
+                </form>
+            </div>
+        {% endfor %}
+   {% endif %}
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/booking/edit.html b/schoolapps/fibu/templates/fibu/booking/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..4fabb87a6fc09dbb9c0358efd787a0c611337c1f
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/booking/edit.html
@@ -0,0 +1,24 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Antrag bearbeiten</h4>
+
+    <form method="POST" action="">
+        {% csrf_token %}
+        {% form form=form %}
+            {% part form.planned_amount prefix %}<i class="material-icons prefix">euro_symbol</i>{% endpart %}
+        {% endform %}
+
+        <div class="right">
+            <button type="submit" class="waves-effect waves-light btn green">
+                <i class="material-icons left">save</i> Änderungen übernehmen
+            </button>
+            <a href="{% url 'fibu_index' %}" class="waves-effect waves-light btn red">
+                <i class="material-icons left">cancel</i> Abbrechen
+            </a>
+        </div>
+    </form>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/booking/index.html b/schoolapps/fibu/templates/fibu/booking/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..3ad4fd6ebf9f6ae4ae26f842a3d5d285aa7e1381
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/booking/index.html
@@ -0,0 +1,68 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <div class="right">
+        {% if is_archive %}
+            <a href="{% url 'fibu_bookings' %}" class="waves-effect waves-light btn grey">Zur aktuellen Übersicht</a>
+        {% else %}
+            <a href="{% url 'fibu_bookings_archive' "archive" %}" class="waves-effect waves-light btn grey">Zum Archiv
+            </a>
+        {% endif %}
+
+        <a href="{% url 'fibu_bookings_new' %}" class="waves-effect waves-light btn green">Neue Buchung anlegen</a>&nbsp;
+    </div>
+
+    <h4>{% if is_archive %}Buchungsarchiv{% else %}Aktuelle Buchungen{% endif %}</h4>
+
+    <table>
+        <thead>
+        <tr>
+            <th>Artikelbeschreibung</th>
+            <th class="right-align">erwarteter Betrag</th>
+            <th class="right-align">Rechnungsbetrag</th>
+            <th>Buchungskonto</th>
+            <th>Antragsteller</th>
+            <th>Status</th>
+            <th>Aktionen</th>
+        </tr>
+        </thead>
+        {% for booking in bookings %}
+            <tr>
+                <td>
+                    <a href="{% url "fibu_bookings_edit" booking.id %}">{{ booking.description }}</a>
+                </td>
+                <td class="right-align">{{ booking.planned_amount }} €</td>
+                <td class="right-align">{{ booking.amount }} €</td>
+                <td>{{ booking.account|default:"" }}</td>
+                <td>{{ booking.contact.get_full_name }}</td>
+                <td>
+                    <span class="badge new center-align {{ booking.get_status.style_class }}">
+                        {{ booking.get_status.name }}
+                    </span>
+                </td>
+                <td>
+                    <span class="left">
+                        <a class="waves-effect waves-light btn-flat btn-flat-medium green-text" title="Bearbeiten"
+                           href="{% url "fibu_bookings_edit" booking.id %}">
+                            <i class="material-icons">edit</i>
+                        </a>
+                    </span>
+                    {% if booking.status < 2 %}
+                        <form method="POST" class="left">
+                            {% csrf_token %}
+                            <input type="hidden" value="{{ booking.id }}" name="booking-id">
+                            <button type="submit"
+                                    onclick="return confirm('Wirklich löschen?')"
+                                    name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium red-text"
+                                    title="Löschen">
+                                <i class="material-icons center">cancel</i>
+                            </button>
+                        </form>
+                    {% endif %}
+                </td>
+            </tr>
+        {% endfor %}
+    </table>
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/booking/new.html b/schoolapps/fibu/templates/fibu/booking/new.html
new file mode 100644
index 0000000000000000000000000000000000000000..d79cdd2595c1950ee2ec505e0dd8923be165fbdb
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/booking/new.html
@@ -0,0 +1,23 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Neue Buchung</h4>
+
+    <form method="POST">
+        {% csrf_token %}
+        {% form form=form %}
+        {% endform %}
+
+        <span class="right">
+            <button type="submit" class="waves-effect waves-light btn green">
+                <i class="material-icons left">save</i> Buchung anlegen
+            </button>
+            <a href="{% url 'fibu_bookings' %}" class="waves-effect waves-light btn red">
+                <i class="material-icons left">cancel</i> Abbrechen
+            </a>
+        </span>
+    </form>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/cost_center/edit.html b/schoolapps/fibu/templates/fibu/cost_center/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..e251f5a3333100c1860bea9c12ce16cb92131f45
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/cost_center/edit.html
@@ -0,0 +1,23 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Kostenstelle bearbeiten</h4>
+
+    <form method="POST">
+        {% csrf_token %}
+        {% form form=form %}
+        {% endform %}
+
+        <span class="right">
+            <button type="submit" class="waves-effect waves-light btn green">
+                <i class="material-icons left">save</i> Änderungen übernehmen
+            </button>
+            <a href="{% url 'fibu_cost_centers' %}" class="waves-effect waves-light btn red">
+                <i class="material-icons left">cancel</i> Abbrechen
+            </a>
+        </span>
+    </form>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/cost_center/index.html b/schoolapps/fibu/templates/fibu/cost_center/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..87d6b9af09f482dcdf2a5e49de7b341d42076f77
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/cost_center/index.html
@@ -0,0 +1,72 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <a class="waves-effect waves-light btn green modal-trigger right" href="#new-modal">
+        <i class="material-icons left">add</i> Kostenstelle anlegen
+    </a>
+
+    <h4>Kostenstellen</h4>
+
+    {% if form.errors %}
+        <script>
+            var onFinish = function () {
+                $("#new-modal").modal("open");
+            };
+        </script>
+    {% endif %}
+
+    <form method="POST">
+        <div id="new-modal" class="modal">
+            <div class="modal-content">
+                <h5>Neue Kostenstelle anlegen</h5>
+                {% csrf_token %}
+                {% form form=form %}
+                {% endform %}
+            </div>
+            <div class="modal-footer">
+                <button type="submit" class="waves-effect waves-light btn green">
+                    <i class="material-icons left">save</i> Kostenstelle anlegen
+                </button>
+            </div>
+        </div>
+    </form>
+
+    <table>
+        <thead>
+        <tr>
+            <th>Kostenstelle</th>
+            <th>Jahr</th>
+            <th>Aktionen</th>
+        </tr>
+        </thead>
+        {% for cost_center in cost_centers %}
+            <tr>
+                <td>{{ cost_center.name }}</td>
+                <td>{{ cost_center.year }}</td>
+                <td>
+                    <form action="{% url 'fibu_cost_centers_edit' cost_center.id %}" class="left">
+                        {% csrf_token %}
+                        <input type="hidden" value="{{ cost_center.id }}" name="id">
+                        <button type="submit" name="edit"
+                                class="waves-effect waves-light btn-flat btn-flat-medium" title="Bearbeiten">
+                            <i class="material-icons center green-text">create</i>
+                        </button>
+                    </form>
+                    <form action="" method="POST" class="left">
+                        {% csrf_token %}
+                        <input type="hidden" value="{{ cost_center.id }}" name="id">
+                        <button type="submit"
+                                onclick="return confirm('Wollen Sie die Kostenstelle wirklich löschen?')"
+                                name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium"
+                                title="Löschen">
+                            <i class="material-icons center red-text">cancel</i>
+                        </button>
+                    </form>
+                </td>
+            </tr>
+        {% endfor %}
+    </table>
+</main>
+
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/index.html b/schoolapps/fibu/templates/fibu/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..3eca9a5d3ba7980a266d30c79d0f0661f0141d15
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/index.html
@@ -0,0 +1,108 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <a class="waves-effect waves-light btn green modal-trigger right" href="#new-modal">
+        <i class="material-icons left">add</i> Antrag stellen
+    </a>
+    <h4>Meine Anträge</h4>
+
+
+    {% if form.errors %}
+        <script>
+            var onFinish = function () {
+                $("#new-modal").modal("open");
+            };
+        </script>
+    {% endif %}
+
+    <form method="POST">
+        <div id="new-modal" class="modal">
+            <div class="modal-content">
+                <h5>Neuen Antrag stellen</h5>
+                {% csrf_token %}
+                {% form form=form %}
+                    {% part form.planned_amount prefix %}<i class="material-icons prefix">euro_symbol</i>
+                    {% endpart %}
+                {% endform %}
+            </div>
+            <div class="modal-footer">
+                <button type="submit" class="waves-effect waves-light btn green">
+                    <i class="material-icons left">send</i> Antrag stellen
+                </button>
+            </div>
+        </div>
+    </form>
+
+<div class="collection-item row">
+            <div class="col s5"><strong>Beschreibung</strong></div>
+            <div class="col s2 right-align"><strong>erwarteter Betrag</strong></div>
+            <div class="col s2 right-align"><strong>Rechnungsbetrag</strong></div>
+            <div class="col s1 center-align"><strong>Status</strong></div>
+            <div class="col s2 center-align"><strong>Aktionen</strong></div>
+        </div>
+    <div class="collection">
+        {% for booking in bookings %}
+            <div class="collection-item row">
+                <div class="col s5">{{ booking.description }}</div>
+                <div class="col s2 right-align">{{ booking.planned_amount }} €</div>
+                <div class="col s2 right-align">{{ booking.amount }} €</div>
+                <div class="col s1">
+                    <span class="badge new {{ booking.get_status.style_class }}">{{ booking.get_status.name }}</span>
+                </div>
+                <div class="col s2">
+                    {# Delete #}
+                    {% if booking.status < 3 %}
+                        <form action="" method="POST" class="right">
+                            {% csrf_token %}
+                            <input type="hidden" value="{{ booking.id }}" name="booking-id">
+                            <button type="submit"
+                                    onclick="return confirm('Wollen Sie den Antrag wirklich zurücknehmen?')"
+                                    name="cancel" class="waves-effect waves-light btn-flat btn-flat-medium"
+                                    title="Antrag zurücknehmen">
+                                <i class="material-icons center red-text">cancel</i>
+                            </button>
+                        </form>
+                    {% endif %}
+
+                    {# Edit #}
+                    {% if booking.status == 0 %}
+                        <form action="{% url 'fibu_bookings_user_edit' booking.id %}" class="right">
+                            {% csrf_token %}
+                            <input type="hidden" value="{{ booking.id }}" name="booking-id">
+                            <button type="submit" name="edit"
+                                    class="waves-effect waves-light btn-flat btn-flat-medium right"
+                                    title="Bearbeiten">
+                                <i class="material-icons center green-text">create</i>
+                            </button>
+                        </form>
+                    {% endif %}
+
+                    <form action="" method="POST" class="right">
+                        {% csrf_token %}
+                        <input type="hidden" value="{{ booking.id }}" name="booking-id">
+                        <input type="hidden" value="{{ booking.status }}" name="booking-status">
+
+
+                        {% if booking.status == 2 %}
+                            <button type="submit" name="ordered"
+                                    class="waves-effect waves-light btn-flat btn-flat-medium left"
+                                    title="Status auf 'bestellt' ändern">
+                                <i class="material-icons center green-text">shopping_cart</i>
+                            </button>
+
+
+                        {% elif booking.status == 3 %}
+                            <button type="submit" name="submit-invoice"
+                                    class="waves-effect waves-light btn-flat btn-flat-medium left"
+                                    title="Status auf 'Rechnung eingereicht' ändern">
+                                <i class="material-icons center green-text">description</i>
+                            </button>
+                        {% endif %}
+                    </form>
+                </div>
+            </div>
+        {% endfor %}
+    </div>
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/reports/expenses.html b/schoolapps/fibu/templates/fibu/reports/expenses.html
new file mode 100755
index 0000000000000000000000000000000000000000..c35fb8ec1d032c9ad8f1943005f1b0f02d1eda9b
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/reports/expenses.html
@@ -0,0 +1,36 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Schlussrechnung</h4>
+
+    <div class="collection">
+        {% for cost_center, accounts in cost_center_accounts.items %}
+            <h5 class="collection-item">{{ cost_center }}</h5>
+
+            <div class="collection">
+                <div class="row">
+                    <span class="col s5 m5 white-text grey">Buchungskonto</span>
+                    <span class="col s2 m2 white-text grey right-align">Budget</span>
+                    <span class="col s1 m1 white-text grey right-align">Einnahmen</span>
+                    <span class="col s1 m1 white-text grey right-align">Ausgaben</span>
+                    <span class="col s1 m1 white-text grey right-align">Reste</span>
+                </div>
+                {% for account in accounts %}
+                    <div class="collection-item row">
+                        <span class="col s5 m5">{{ account.name }}</span>
+                        <span class="col s2 m2 right-align">{{ account.budget }} €</span>
+                        <span class="col s1 m1 right-align">
+                            {% if account.income %}{{ account.saldo }} €{% endif %}
+                        </span>
+                        <span class="col s1 m1 right-align">
+                            {% if not account.income %}{{ account.saldo }} €{% endif %}
+                        </span>
+                        <span class="col s1 m1 right-align">{{ account.rest }} €</span>
+                    </div>
+                {% endfor %}
+            </div>
+        {% endfor %}
+    </div>
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/templates/fibu/reports/index.html b/schoolapps/fibu/templates/fibu/reports/index.html
new file mode 100755
index 0000000000000000000000000000000000000000..5399308743095cfc65dfe4b93de840618b9a16e4
--- /dev/null
+++ b/schoolapps/fibu/templates/fibu/reports/index.html
@@ -0,0 +1,11 @@
+{% include 'partials/header.html' %}
+{% load material_form %}
+
+<main>
+    <h4>Berichte</h4>
+
+    <div class="collection">
+        <a href="{% url "fibu_reports_expenses" %}" class="collection-item">Ausgaben</a>
+    </div>
+</main>
+{% include 'partials/footer.html' %}
diff --git a/schoolapps/fibu/urls.py b/schoolapps/fibu/urls.py
new file mode 100755
index 0000000000000000000000000000000000000000..42d8911ac49f9eca2867a53769ce15dc159d033b
--- /dev/null
+++ b/schoolapps/fibu/urls.py
@@ -0,0 +1,19 @@
+from django.urls import path
+from . import views
+
+
+urlpatterns = [
+    path('', views.index, name='fibu_index'),
+    path('<int:pk>/', views.user_edit, name='fibu_bookings_user_edit'),
+    path('bookings/check/', views.check, name='fibu_bookings_check'),
+    path('bookings/new/', views.new_booking, name='fibu_bookings_new'),
+    path('bookings/', views.booking, name='fibu_bookings'),
+    path('bookings/<str:is_archive>/', views.booking, name='fibu_bookings_archive'),
+    path('bookings/<int:pk>/edit/', views.book, name='fibu_bookings_edit'),
+    path('costcenters/', views.cost_centers, name='fibu_cost_centers'),
+    path('costcenters/edit/<int:pk>/', views.cost_center_edit, name='fibu_cost_centers_edit'),
+    path('accounts/', views.account, name='fibu_accounts'),
+    path('accounts/<int:pk>/', views.account_edit, name='fibu_accounts_edit'),
+    path('reports/', views.reports, name='fibu_reports'),
+    path('reports/expenses/', views.expenses, name='fibu_reports_expenses'),
+]
diff --git a/schoolapps/fibu/views.py b/schoolapps/fibu/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea3c3afcc38ce7b70949c2d8e5fdfde15ae7b514
--- /dev/null
+++ b/schoolapps/fibu/views.py
@@ -0,0 +1,266 @@
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required, permission_required
+from django.db.models import Sum
+from django.urls import reverse
+from django.shortcuts import render, redirect, get_object_or_404
+from .models import Booking, CostCenter, Account
+from .filters import BookingFilter
+from .forms import SimpleBookingForm, CheckBookingForm, CompleteBookingForm, CostCenterForm, AccountForm
+
+
+@login_required
+@permission_required('fibu.request_booking')
+def index(request):
+    if request.method == 'POST':
+        if 'booking-id' in request.POST:
+            booking_id = request.POST['booking-id']
+            booking = get_object_or_404(Booking, pk=booking_id)
+
+            if 'cancel' in request.POST:
+                booking.delete()
+                print('Eintrag gelöscht')
+                return redirect('fibu_index')
+
+            elif 'ordered' in request.POST:
+                Booking.objects.filter(id=booking_id).update(status=3)
+                return redirect('fibu_index')
+
+            elif 'submit-invoice' in request.POST:
+                Booking.objects.filter(id=booking_id).update(status=4)
+                return redirect('fibu_index')
+
+            form = SimpleBookingForm(instance=booking)
+        else:
+            form = SimpleBookingForm(request.POST)
+    else:
+        form = SimpleBookingForm()
+
+    if form.is_valid():
+        description = form.cleaned_data['description']
+        planned_amount = form.cleaned_data['planned_amount']
+        justification = form.cleaned_data['justification']
+        booking = Booking(description=description, planned_amount=planned_amount, contact=request.user,
+                          justification=justification)
+        booking.save()
+
+        messages.success(request, "Der Antrag wurde erfolgreich übermittelt.")
+
+        return redirect('fibu_index')
+
+    bookings = Booking.objects.filter(contact=request.user).order_by('status')
+
+    context = {'bookings': bookings, 'form': form}
+    return render(request, 'fibu/index.html', context)
+
+
+@login_required
+@permission_required('fibu.request_booking')
+def user_edit(request, pk):
+    booking = get_object_or_404(Booking, pk=pk)
+    form = SimpleBookingForm(instance=booking)
+
+    if request.method == 'POST':
+        form = SimpleBookingForm(request.POST, instance=booking)
+
+        if form.is_valid():
+            form.save()
+
+            messages.success(request, "Die Änderungen am Antrag wurden erfolgreich übernommen.")
+            return redirect(reverse('fibu_index'))
+
+    context = {'form': form}
+    return render(request, 'fibu/booking/edit.html', context)
+
+
+@login_required
+@permission_required('fibu.check_booking')
+def check(request):
+    if request.method == 'POST':
+        if 'booking-id' in request.POST:
+            booking_id = request.POST['booking-id']
+            if 'allow' in request.POST:
+                if "account" in request.POST:
+                    account = request.POST['account']
+                    Booking.objects.filter(id=booking_id).update(status=2, account=account)
+                    messages.success(request, "Der Antrag wurde angenommen.")
+                else:
+                    messages.error(request, "Bitte wähle ein Buchungskonto aus, um den Antrag anzunehmen.")
+            elif 'deny' in request.POST:
+                Booking.objects.filter(id=booking_id).update(status=1)
+                messages.success(request, "Der Antrag wurde abgelehnt.")
+
+    booking_list = Booking.objects.filter(status=0).order_by('submission_date')
+    bookings = BookingFilter(request.GET, queryset=booking_list)
+    form = CheckBookingForm()
+    return render(request, 'fibu/booking/check.html', {'filter': bookings, 'form': form})
+
+
+@login_required
+@permission_required('fibu.manage_booking')
+def booking(request, is_archive=""):
+    is_archive = is_archive == "archive"
+    if is_archive:
+        bookings = Booking.objects.filter(status=5).order_by('-status')
+    else:
+        bookings = Booking.objects.filter(status__lt=5).order_by('-status')
+    context = {'bookings': bookings, 'is_archive': is_archive}
+    return render(request, 'fibu/booking/index.html', context)
+
+
+@login_required
+@permission_required('fibu.manage_booking')
+def book(request, pk):
+    booking = get_object_or_404(Booking, pk=pk)
+    form = CompleteBookingForm(instance=booking)
+    template = 'fibu/booking/book.html'
+    if request.method == 'POST':
+        form = CompleteBookingForm(request.POST, request.FILES, instance=booking)
+        if form.is_valid():
+            form.save()
+            messages.success(request, "Die Änderungen an der Buchung wurden erfolgreich übernommen.")
+            return redirect(reverse('fibu_bookings'))
+    context = {'form': form}
+    return render(request, template, context)
+
+
+@login_required
+@permission_required('fibu.manage_booking')
+def new_booking(request):
+    form = CompleteBookingForm()
+    template = 'fibu/booking/new.html'
+    if request.method == 'POST':
+        form = CompleteBookingForm(request.POST, request.FILES)
+        if form.is_valid():
+            form.save()
+            messages.success(request, "Die Buchung wurde erfolgreich angelegt.")
+
+            return redirect(reverse('fibu_bookings'))
+    context = {'form': form}
+    return render(request, template, context)
+
+
+@login_required
+@permission_required('fibu.manage_costcenter')
+def cost_centers(request):
+    form = CostCenterForm()
+
+    if request.method == 'POST':
+        if 'id' in request.POST and 'cancel' in request.POST:
+            cost_center_id = request.POST['id']
+            cost_center = CostCenter.objects.get(id=cost_center_id)
+            cost_center.delete()
+
+            messages.success(request, "Die Kostenstelle wurde erfolgreich gelöscht.")
+
+            return redirect('fibu_cost_centers')
+        else:
+            form = CostCenterForm(request.POST)
+
+    if form.is_valid():
+        form.save()
+        messages.success(request, "Die Kostenstelle wurde erfolgreich angelegt.")
+        return redirect('fibu_cost_centers')
+
+    cost_centers = CostCenter.objects.filter()
+
+    context = {'cost_centers': cost_centers, 'form': form}
+    return render(request, 'fibu/cost_center/index.html', context)
+
+
+@login_required
+@permission_required('fibu.manage_costcenter')
+def cost_center_edit(request, pk):
+    cost_center = get_object_or_404(CostCenter, pk=pk)
+    form = CostCenterForm(instance=cost_center)
+
+    if request.method == 'POST':
+        form = CostCenterForm(request.POST, instance=cost_center)
+
+        if form.is_valid():
+            form.save()
+
+            messages.success(request, "Die Änderungen an der Kostenstelle wurden erfolgreich übernommen.")
+
+            return redirect(reverse('fibu_cost_centers'))
+
+    context = {'form': form}
+    return render(request, 'fibu/cost_center/edit.html', context)
+
+
+@login_required
+@permission_required('fibu.manage_account')
+def account(request):
+    form = AccountForm()
+
+    if request.method == 'POST':
+        if 'account-id' in request.POST and 'cancel' in request.POST:
+            account_id = request.POST['id']
+            account = Account.objects.get(id=account_id)
+            account.delete()
+
+            messages.success(request, "Das Buchungskonto wurde erfolgreich gelöscht")
+            return redirect('account')
+        else:
+            form = AccountForm(request.POST)
+
+    if form.is_valid():
+        form.save()
+
+        messages.success(request, "Das Buchungskonto wurde erfolgreich angelegt.")
+        return redirect('account')
+
+    accounts = Account.objects.filter().order_by('cost_center', '-income', 'name')
+    context = {'accounts': accounts, 'form': form}
+    return render(request, 'fibu/account/index.html', context)
+
+
+@login_required
+@permission_required('fibu.manage_account')
+def account_edit(request, pk):
+    account = get_object_or_404(Account, pk=pk)
+    form = AccountForm(instance=account)
+
+    if request.method == 'POST':
+        form = AccountForm(request.POST, instance=account)
+
+        if form.is_valid():
+            form.save()
+
+            messages.success(request, "Die Änderungen am Buchungskonto wurden erfolgreich übernommen.")
+            return redirect(reverse('account'))
+
+    context = {'form': form}
+    return render(request, 'fibu/account/edit.html', context)
+
+
+@login_required
+@permission_required('fibu.manage_booking')
+def reports(request):
+    return render(request, 'fibu/reports/index.html')
+
+
+@login_required
+@permission_required('fibu.manage_booking')
+def expenses(request):
+    cost_centers = CostCenter.objects.filter()
+    cost_center_accounts = {}
+    account_rests = {}
+    for cost_center in cost_centers:
+        accounts = Account.objects.filter(cost_center=cost_center)
+        # update saldo
+        for account in accounts:
+            saldo = Booking.objects.filter(account=account).aggregate(Sum('amount'))
+            saldo = saldo['amount__sum']
+            try:
+                rest = account.budget - saldo
+            except:
+                rest = account.budget
+            try:
+                Account.objects.filter(id=account.id).update(saldo=saldo, rest=rest)
+            except:
+                Account.objects.filter(id=account.id).update(saldo=0, rest=account.budget)
+
+        cost_center_accounts[cost_center.name] = list(
+            Account.objects.filter(cost_center=cost_center).order_by('-income'))
+    context = {'cost_center_accounts': cost_center_accounts, 'account_rests': account_rests}
+    return render(request, 'fibu/reports/expenses.html', context)
diff --git a/schoolapps/meta.py b/schoolapps/meta.py
index 17d6f3a65229465c39108f62d4241abf443076a4..9af2f86c59f640d67970a38470a11f3f616d5fbe 100644
--- a/schoolapps/meta.py
+++ b/schoolapps/meta.py
@@ -10,7 +10,7 @@ with open(copyright_path, "r") as f:
 
 COPYRIGHT_SHORT = "© 2018–2019 Mitglieder der Computer-AG, Katharineum zu Lübeck"
 
-VERSION = '1.1.2 "Aebli"'
+VERSION = '1.1.4 "Aebli"'
 
 LICENSE_APACHE_2 = "Apache 2.0 License"
 LICENSE_BSD = "2-Clause BSD License"
diff --git a/schoolapps/schoolapps/settings.py b/schoolapps/schoolapps/settings.py
index 5239dc663f357b6cd3550a7833320ac967c16022..d6a2219b1b0f2cf7cc374e2db8a14020cb85bda5 100755
--- a/schoolapps/schoolapps/settings.py
+++ b/schoolapps/schoolapps/settings.py
@@ -34,6 +34,7 @@ INSTALLED_APPS = [
     'dashboard.apps.DashboardConfig',
     "debug.apps.DebugConfig",
     'aub.apps.AubConfig',
+    'fibu.apps.FibuConfig',
     'untisconnect.apps.UntisconnectConfig',
     'timetable.apps.TimetableConfig',
     'menu.apps.MenuConfig',
diff --git a/schoolapps/schoolapps/urls.py b/schoolapps/schoolapps/urls.py
index 2e24ef70e17da0a8ab6183c2319ad83b4cd1db07..ba42b84d22a00a0aefe57eb87e08ac9daf6fba37 100755
--- a/schoolapps/schoolapps/urls.py
+++ b/schoolapps/schoolapps/urls.py
@@ -45,6 +45,11 @@ urlpatterns = [
     #######
     path('aub/', include('aub.urls')),
 
+    ########
+    # FIBU #
+    ########
+    path('fibu/', include('fibu.urls')),
+
     #############
     # TIMETABLE #
     #############
diff --git a/schoolapps/static/common/helper.js b/schoolapps/static/common/helper.js
index 235e9e61f6c87828a7e7e182cd8c98ced9d81a87..d552d9a659aa5d479f32486800a98d61e50a6972 100644
--- a/schoolapps/static/common/helper.js
+++ b/schoolapps/static/common/helper.js
@@ -96,10 +96,17 @@ $(document).ready(function () {
     // Initialize FABs [MAT]
     $('.fixed-action-btn').floatingActionButton();
 
+    // Initialize Modals [MAT]
+    $('.modal').modal();
+
     // Initialize delete button
     $(".delete-button").click(function (e) {
         if (!confirm("Wirklich löschen?")) {
             e.preventDefault();
         }
-    })
+    });
+
+    if (typeof onFinish !== 'undefined') {
+        onFinish();
+    }
 });
\ No newline at end of file
diff --git a/schoolapps/static/common/serviceworker.js b/schoolapps/static/common/serviceworker.js
index 25c0ca0d31ec83b7e1339728608f3c09191ede4c..babd95c4dea5ab66a59b5b0c7eccdba48bed8524 100644
--- a/schoolapps/static/common/serviceworker.js
+++ b/schoolapps/static/common/serviceworker.js
@@ -1,22 +1,19 @@
-//This is the SchoolApps service worker with Advanced caching
+//This is the SchoolApps service worker
 
 const CACHE = "schoolapps-cache";
 
 const precacheFiles = [
-    '/',
-    '/faq',
+    '',
+    '/faq/',
 ];
 
 const offlineFallbackPage = '/offline';
 
-const cacheFirstPaths = [
-    '/faq',
-];
-
 const avoidCachingPaths = [
     '/admin',
     '/settings',
     '/support',
+    '/tools',
     '/faq/ask',
     '/aub/apply_for',
     '/aub/check1',
@@ -45,14 +42,14 @@ function comparePaths(requestUrl, pathsArray) {
 }
 
 self.addEventListener("install", function (event) {
-  console.log("[SchoolApps PWA] Install Event processing");
+  console.log("[SchoolApps PWA] Install Event processing.");
 
-  console.log("[SchoolApps PWA] Skip waiting on install");
+  console.log("[SchoolApps PWA] Skipping waiting on install.");
   self.skipWaiting();
 
   event.waitUntil(
     caches.open(CACHE).then(function (cache) {
-      console.log("[SchoolApps PWA] Caching pages during install");
+      console.log("[SchoolApps PWA] Caching pages during install.");
 
       return cache.addAll(precacheFiles).then(function () {
         return cache.add(offlineFallbackPage);
@@ -63,84 +60,43 @@ self.addEventListener("install", function (event) {
 
 // Allow sw to control of current page
 self.addEventListener("activate", function (event) {
-  console.log("[SchoolApps PWA] Claiming clients for current page");
+  console.log("[SchoolApps PWA] Claiming clients for current page.");
   event.waitUntil(self.clients.claim());
 });
 
 // If any fetch fails, it will look for the request in the cache and serve it from there first
 self.addEventListener("fetch", function (event) {
   if (event.request.method !== "GET") return;
-
-  if (comparePaths(event.request.url, cacheFirstPaths)) {
-    cacheFirstFetch(event);
-  } else {
-    networkFirstFetch(event);
-  }
+  networkFirstFetch(event);
 });
 
-function cacheFirstFetch(event) {
-  event.respondWith(
-    fromCache(event.request).then(
-      function (response) {
-        // The response was found in the cache so we respond with it and update the entry
-
-        // This is where we call the server to get the newest version of the
-        // file to use the next time we show view
-        event.waitUntil(
-          fetch(event.request).then(function (response) {
-            return updateCache(event.request, response);
-          })
-        );
-
-        return response;
-      },
-      function () {
-        // The response was not found in the cache so we look for it on the server
-        return fetch(event.request)
-          .then(function (response) {
-            // If request was successful, add or update it in the cache
-            event.waitUntil(updateCache(event.request, response.clone()));
-
-            return response;
-          })
-          .catch(function (error) {
-            // The following validates that the request was for a navigation to a new document
-            if (event.request.destination !== "document" || event.request.mode !== "navigate") {
-              return;
-            }
-
-            console.log("[SchoolApps PWA] Network request failed and no cache." + error);
-            // Use the precached offline page as fallback
-            return caches.match(offlineFallbackPage)
-          });
-      }
-    )
-  );
-}
-
 function networkFirstFetch(event) {
   event.respondWith(
     fetch(event.request)
       .then(function (response) {
         // If request was successful, add or update it in the cache
+        console.log("[SchoolApps PWA] Network request successful.");
         event.waitUntil(updateCache(event.request, response.clone()));
         return response;
       })
       .catch(function (error) {
         console.log("[SchoolApps PWA] Network request failed. Serving content from cache: " + error);
-        return fromCache(event.request);
+        return fromCache(event);
       })
   );
 }
 
-function fromCache(request) {
+function fromCache(event) {
   // Check to see if you have it in the cache
   // Return response
   // If not in the cache, then return offline fallback page
   return caches.open(CACHE).then(function (cache) {
-    return cache.match(request).then(function (matching) {
+    return cache.match(event.request)
+    .then(function (matching) {
       if (!matching || matching.status === 404) {
-        return caches.match(offlineFallbackPage);
+        console.log("[SchoolApps PWA] Cache request failed. Serving offline fallback page.");
+        // Use the precached offline page as fallback
+        return caches.match(offlineFallbackPage)
       }
 
       return matching;
diff --git a/schoolapps/static/common/style.css b/schoolapps/static/common/style.css
index e94e7d303657fe61f2f7abbd23e0b8fd46803736..c418d66e7b209a29b46122e9e8d9afae5e5ea911 100644
--- a/schoolapps/static/common/style.css
+++ b/schoolapps/static/common/style.css
@@ -249,7 +249,7 @@ table.substitutions td, table.substitutions th {
 }
 
 .lesson-card a, .substitutions a {
-    color: black;
+    color: inherit;
 }
 
 /*.timetable-time {*/
@@ -312,10 +312,11 @@ table.substitutions td, table.substitutions th {
 
 /* Table*/
 
-table.striped > tbody > tr:nth-child(odd) {
+table.striped > tbody > tr:nth-child(odd), table tr.striped {
     background-color: rgba(208, 208, 208, 0.5);
 }
 
+
 /*+++++++*/
 /* Print */
 /*+++++++*/
@@ -402,7 +403,7 @@ table.striped > tbody > tr:nth-child(odd) {
     border-color: #b71c1c;
 }
 
-.alert.primary > p, .alert.primary > div {
+.alert.primary > p, .alert.primary > div, .alert.info > p, .alert.info > div {
     background-color: #ececec;
     border-color: #da1f3d;
 }
diff --git a/schoolapps/static/common/workbox-sw.js b/schoolapps/static/common/workbox-sw.js
deleted file mode 100644
index 61b3289a81a12f15841f55e57ace9063bafa3e51..0000000000000000000000000000000000000000
--- a/schoolapps/static/common/workbox-sw.js
+++ /dev/null
@@ -1,2 +0,0 @@
-!function(){"use strict";try{self["workbox:sw:4.3.1"]&&_()}catch(t){}const t="https://storage.googleapis.com/workbox-cdn/releases/4.3.1",e={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(t,s){if(t[s])return t[s];const o=e[s];return o&&t.loadModule(`workbox-${o}`),t[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(e){if(this.t.modulePathCb)return this.t.modulePathCb(e,this.t.debug);let s=[t];const o=`${e}.${this.s}.js`,r=this.t.modulePathPrefix;return r&&""===(s=r.split("/"))[s.length-1]&&s.splice(s.length-1,1),s.push(o),s.join("/")}}}();
-//# sourceMappingURL=workbox-sw.js.map
diff --git a/schoolapps/static/css/paper.css b/schoolapps/static/css/paper.css
new file mode 100644
index 0000000000000000000000000000000000000000..9e5b66db1a7f87dca6123920a4b03bb54e0270a6
--- /dev/null
+++ b/schoolapps/static/css/paper.css
@@ -0,0 +1,134 @@
+/* MIT © Tsutomu Kawamura */
+/* Edited by SchoolApps Team */
+
+@page {
+    margin: 0
+}
+
+body {
+    margin: 0
+}
+
+.sheet {
+    margin: 0;
+    overflow: hidden;
+    position: relative;
+    box-sizing: border-box;
+    page-break-after: always;
+}
+
+/** Paper sizes **/
+body.A3 .sheet {
+    width: 297mm;
+    height: 419mm
+}
+
+body.A3.landscape .sheet {
+    width: 420mm;
+    height: 296mm
+}
+
+body.A4 .sheet {
+    width: 210mm;
+    height: 296mm
+}
+
+body.A4.landscape .sheet {
+    width: 297mm;
+    height: 209mm
+}
+
+body.A5 .sheet {
+    width: 148mm;
+    height: 209mm
+}
+
+body.A5.landscape .sheet {
+    width: 210mm;
+    height: 147mm
+}
+
+body.letter .sheet {
+    width: 216mm;
+    height: 279mm
+}
+
+body.letter.landscape .sheet {
+    width: 280mm;
+    height: 215mm
+}
+
+body.legal .sheet {
+    width: 216mm;
+    height: 356mm
+}
+
+body.legal.landscape .sheet {
+    width: 357mm;
+    height: 215mm
+}
+
+.sheet.infinitive {
+    height: auto !important;
+}
+
+/** Padding area **/
+.sheet.padding-10mm {
+    padding: 10mm
+}
+
+.sheet.padding-15mm {
+    padding: 15mm
+}
+
+.sheet.padding-20mm {
+    padding: 20mm
+}
+
+.sheet.padding-25mm {
+    padding: 25mm
+}
+
+/** For screen preview **/
+@media screen {
+    body {
+        background: #e0e0e0
+    }
+
+    .sheet {
+        background: white;
+        box-shadow: 0 .5mm 2mm rgba(0, 0, 0, .3);
+        margin: 5mm auto;
+    }
+}
+
+/** Fix for Chrome issue #273306 **/
+@media print {
+    body.A3.landscape {
+        width: 420mm
+    }
+
+    body.A3, body.A4.landscape {
+        width: 297mm
+    }
+
+    body.A4, body.A5.landscape {
+        width: 210mm
+    }
+
+    body.A5 {
+        width: 148mm
+    }
+
+    body.letter, body.legal {
+        width: 216mm
+    }
+
+    body.letter.landscape {
+        width: 280mm
+    }
+
+    body.legal.landscape {
+        width: 357mm
+    }
+}
diff --git a/schoolapps/static/js/react/prop-types.development.js b/schoolapps/static/js/react/prop-types.development.js
new file mode 100644
index 0000000000000000000000000000000000000000..fba1dfd762c08a9987fc18cff24d9b61f6dc6f1a
--- /dev/null
+++ b/schoolapps/static/js/react/prop-types.development.js
@@ -0,0 +1,917 @@
+(function (f) {
+    if (typeof exports === "object" && typeof module !== "undefined") {
+        module.exports = f()
+    } else if (typeof define === "function" && define.amd) {
+        define([], f)
+    } else {
+        var g;
+        if (typeof window !== "undefined") {
+            g = window
+        } else if (typeof global !== "undefined") {
+            g = global
+        } else if (typeof self !== "undefined") {
+            g = self
+        } else {
+            g = this
+        }
+        g.PropTypes = f()
+    }
+})(function () {
+    var define, module, exports;
+    return (function e(t, n, r) {
+        function s(o, u) {
+            if (!n[o]) {
+                if (!t[o]) {
+                    var a = typeof require == "function" && require;
+                    if (!u && a) return a(o, !0);
+                    if (i) return i(o, !0);
+                    var f = new Error("Cannot find module '" + o + "'");
+                    throw f.code = "MODULE_NOT_FOUND", f
+                }
+                var l = n[o] = {exports: {}};
+                t[o][0].call(l.exports, function (e) {
+                    var n = t[o][1][e];
+                    return s(n ? n : e)
+                }, l, l.exports, e, t, n, r)
+            }
+            return n[o].exports
+        }
+
+        var i = typeof require == "function" && require;
+        for (var o = 0; o < r.length; o++) s(r[o]);
+        return s
+    })({
+        1: [function (require, module, exports) {
+            /**
+             * Copyright (c) 2013-present, Facebook, Inc.
+             *
+             * This source code is licensed under the MIT license found in the
+             * LICENSE file in the root directory of this source tree.
+             */
+
+            'use strict';
+
+            var printWarning = function () {
+            };
+
+            if ("development" !== 'production') {
+                var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
+                var loggedTypeFailures = {};
+
+                printWarning = function (text) {
+                    var message = 'Warning: ' + text;
+                    if (typeof console !== 'undefined') {
+                        console.error(message);
+                    }
+                    try {
+                        // --- Welcome to debugging React ---
+                        // This error was thrown as a convenience so that you can use this stack
+                        // to find the callsite that caused this warning to fire.
+                        throw new Error(message);
+                    } catch (x) {
+                    }
+                };
+            }
+
+            /**
+             * Assert that the values match with the type specs.
+             * Error messages are memorized and will only be shown once.
+             *
+             * @param {object} typeSpecs Map of name to a ReactPropType
+             * @param {object} values Runtime values that need to be type-checked
+             * @param {string} location e.g. "prop", "context", "child context"
+             * @param {string} componentName Name of the component for error messages.
+             * @param {?Function} getStack Returns the component stack.
+             * @private
+             */
+            function checkPropTypes(typeSpecs, values, location, componentName, getStack) {
+                if ("development" !== 'production') {
+                    for (var typeSpecName in typeSpecs) {
+                        if (typeSpecs.hasOwnProperty(typeSpecName)) {
+                            var error;
+                            // Prop type validation may throw. In case they do, we don't want to
+                            // fail the render phase where it didn't fail before. So we log it.
+                            // After these have been cleaned up, we'll let them throw.
+                            try {
+                                // This is intentionally an invariant that gets caught. It's the same
+                                // behavior as without this statement except with a better message.
+                                if (typeof typeSpecs[typeSpecName] !== 'function') {
+                                    var err = Error(
+                                        (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +
+                                        'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.'
+                                    );
+                                    err.name = 'Invariant Violation';
+                                    throw err;
+                                }
+                                error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
+                            } catch (ex) {
+                                error = ex;
+                            }
+                            if (error && !(error instanceof Error)) {
+                                printWarning(
+                                    (componentName || 'React class') + ': type specification of ' +
+                                    location + ' `' + typeSpecName + '` is invalid; the type checker ' +
+                                    'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +
+                                    'You may have forgotten to pass an argument to the type checker ' +
+                                    'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
+                                    'shape all require an argument).'
+                                )
+
+                            }
+                            if (error instanceof Error && !(error.message in loggedTypeFailures)) {
+                                // Only monitor this failure once because there tends to be a lot of the
+                                // same error.
+                                loggedTypeFailures[error.message] = true;
+
+                                var stack = getStack ? getStack() : '';
+
+                                printWarning(
+                                    'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+
+            module.exports = checkPropTypes;
+
+        }, {"./lib/ReactPropTypesSecret": 5}],
+        2: [function (require, module, exports) {
+            /**
+             * Copyright (c) 2013-present, Facebook, Inc.
+             *
+             * This source code is licensed under the MIT license found in the
+             * LICENSE file in the root directory of this source tree.
+             */
+
+            'use strict';
+
+            var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
+
+            function emptyFunction() {
+            }
+
+            module.exports = function () {
+                function shim(props, propName, componentName, location, propFullName, secret) {
+                    if (secret === ReactPropTypesSecret) {
+                        // It is still safe when called from React.
+                        return;
+                    }
+                    var err = new Error(
+                        'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +
+                        'Use PropTypes.checkPropTypes() to call them. ' +
+                        'Read more at http://fb.me/use-check-prop-types'
+                    );
+                    err.name = 'Invariant Violation';
+                    throw err;
+                };
+                shim.isRequired = shim;
+
+                function getShim() {
+                    return shim;
+                };
+                // Important!
+                // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`.
+                var ReactPropTypes = {
+                    array: shim,
+                    bool: shim,
+                    func: shim,
+                    number: shim,
+                    object: shim,
+                    string: shim,
+                    symbol: shim,
+
+                    any: shim,
+                    arrayOf: getShim,
+                    element: shim,
+                    instanceOf: getShim,
+                    node: shim,
+                    objectOf: getShim,
+                    oneOf: getShim,
+                    oneOfType: getShim,
+                    shape: getShim,
+                    exact: getShim
+                };
+
+                ReactPropTypes.checkPropTypes = emptyFunction;
+                ReactPropTypes.PropTypes = ReactPropTypes;
+
+                return ReactPropTypes;
+            };
+
+        }, {"./lib/ReactPropTypesSecret": 5}],
+        3: [function (require, module, exports) {
+            /**
+             * Copyright (c) 2013-present, Facebook, Inc.
+             *
+             * This source code is licensed under the MIT license found in the
+             * LICENSE file in the root directory of this source tree.
+             */
+
+            'use strict';
+
+            var assign = require('object-assign');
+
+            var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
+            var checkPropTypes = require('./checkPropTypes');
+
+            var printWarning = function () {
+            };
+
+            if ("development" !== 'production') {
+                printWarning = function (text) {
+                    var message = 'Warning: ' + text;
+                    if (typeof console !== 'undefined') {
+                        console.error(message);
+                    }
+                    try {
+                        // --- Welcome to debugging React ---
+                        // This error was thrown as a convenience so that you can use this stack
+                        // to find the callsite that caused this warning to fire.
+                        throw new Error(message);
+                    } catch (x) {
+                    }
+                };
+            }
+
+            function emptyFunctionThatReturnsNull() {
+                return null;
+            }
+
+            module.exports = function (isValidElement, throwOnDirectAccess) {
+                /* global Symbol */
+                var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
+                var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.
+
+                /**
+                 * Returns the iterator method function contained on the iterable object.
+                 *
+                 * Be sure to invoke the function with the iterable as context:
+                 *
+                 *     var iteratorFn = getIteratorFn(myIterable);
+                 *     if (iteratorFn) {
+                 *       var iterator = iteratorFn.call(myIterable);
+                 *       ...
+                 *     }
+                 *
+                 * @param {?object} maybeIterable
+                 * @return {?function}
+                 */
+                function getIteratorFn(maybeIterable) {
+                    var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);
+                    if (typeof iteratorFn === 'function') {
+                        return iteratorFn;
+                    }
+                }
+
+                /**
+                 * Collection of methods that allow declaration and validation of props that are
+                 * supplied to React components. Example usage:
+                 *
+                 *   var Props = require('ReactPropTypes');
+                 *   var MyArticle = React.createClass({
+                 *     propTypes: {
+                 *       // An optional string prop named "description".
+                 *       description: Props.string,
+                 *
+                 *       // A required enum prop named "category".
+                 *       category: Props.oneOf(['News','Photos']).isRequired,
+                 *
+                 *       // A prop named "dialog" that requires an instance of Dialog.
+                 *       dialog: Props.instanceOf(Dialog).isRequired
+                 *     },
+                 *     render: function() { ... }
+                 *   });
+                 *
+                 * A more formal specification of how these methods are used:
+                 *
+                 *   type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)
+                 *   decl := ReactPropTypes.{type}(.isRequired)?
+                 *
+                 * Each and every declaration produces a function with the same signature. This
+                 * allows the creation of custom validation functions. For example:
+                 *
+                 *  var MyLink = React.createClass({
+                 *    propTypes: {
+                 *      // An optional string or URI prop named "href".
+                 *      href: function(props, propName, componentName) {
+                 *        var propValue = props[propName];
+                 *        if (propValue != null && typeof propValue !== 'string' &&
+                 *            !(propValue instanceof URI)) {
+                 *          return new Error(
+                 *            'Expected a string or an URI for ' + propName + ' in ' +
+                 *            componentName
+                 *          );
+                 *        }
+                 *      }
+                 *    },
+                 *    render: function() {...}
+                 *  });
+                 *
+                 * @internal
+                 */
+
+                var ANONYMOUS = '<<anonymous>>';
+
+                // Important!
+                // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.
+                var ReactPropTypes = {
+                    array: createPrimitiveTypeChecker('array'),
+                    bool: createPrimitiveTypeChecker('boolean'),
+                    func: createPrimitiveTypeChecker('function'),
+                    number: createPrimitiveTypeChecker('number'),
+                    object: createPrimitiveTypeChecker('object'),
+                    string: createPrimitiveTypeChecker('string'),
+                    symbol: createPrimitiveTypeChecker('symbol'),
+
+                    any: createAnyTypeChecker(),
+                    arrayOf: createArrayOfTypeChecker,
+                    element: createElementTypeChecker(),
+                    instanceOf: createInstanceTypeChecker,
+                    node: createNodeChecker(),
+                    objectOf: createObjectOfTypeChecker,
+                    oneOf: createEnumTypeChecker,
+                    oneOfType: createUnionTypeChecker,
+                    shape: createShapeTypeChecker,
+                    exact: createStrictShapeTypeChecker,
+                };
+
+                /**
+                 * inlined Object.is polyfill to avoid requiring consumers ship their own
+                 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
+                 */
+
+                /*eslint-disable no-self-compare*/
+                function is(x, y) {
+                    // SameValue algorithm
+                    if (x === y) {
+                        // Steps 1-5, 7-10
+                        // Steps 6.b-6.e: +0 != -0
+                        return x !== 0 || 1 / x === 1 / y;
+                    } else {
+                        // Step 6.a: NaN == NaN
+                        return x !== x && y !== y;
+                    }
+                }
+
+                /*eslint-enable no-self-compare*/
+
+                /**
+                 * We use an Error-like object for backward compatibility as people may call
+                 * PropTypes directly and inspect their output. However, we don't use real
+                 * Errors anymore. We don't inspect their stack anyway, and creating them
+                 * is prohibitively expensive if they are created too often, such as what
+                 * happens in oneOfType() for any type before the one that matched.
+                 */
+                function PropTypeError(message) {
+                    this.message = message;
+                    this.stack = '';
+                }
+
+                // Make `instanceof Error` still work for returned errors.
+                PropTypeError.prototype = Error.prototype;
+
+                function createChainableTypeChecker(validate) {
+                    if ("development" !== 'production') {
+                        var manualPropTypeCallCache = {};
+                        var manualPropTypeWarningCount = 0;
+                    }
+
+                    function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {
+                        componentName = componentName || ANONYMOUS;
+                        propFullName = propFullName || propName;
+
+                        if (secret !== ReactPropTypesSecret) {
+                            if (throwOnDirectAccess) {
+                                // New behavior only for users of `prop-types` package
+                                var err = new Error(
+                                    'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +
+                                    'Use `PropTypes.checkPropTypes()` to call them. ' +
+                                    'Read more at http://fb.me/use-check-prop-types'
+                                );
+                                err.name = 'Invariant Violation';
+                                throw err;
+                            } else if ("development" !== 'production' && typeof console !== 'undefined') {
+                                // Old behavior for people using React.PropTypes
+                                var cacheKey = componentName + ':' + propName;
+                                if (
+                                    !manualPropTypeCallCache[cacheKey] &&
+                                    // Avoid spamming the console because they are often not actionable except for lib authors
+                                    manualPropTypeWarningCount < 3
+                                ) {
+                                    printWarning(
+                                        'You are manually calling a React.PropTypes validation ' +
+                                        'function for the `' + propFullName + '` prop on `' + componentName + '`. This is deprecated ' +
+                                        'and will throw in the standalone `prop-types` package. ' +
+                                        'You may be seeing this warning due to a third-party PropTypes ' +
+                                        'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.'
+                                    );
+                                    manualPropTypeCallCache[cacheKey] = true;
+                                    manualPropTypeWarningCount++;
+                                }
+                            }
+                        }
+                        if (props[propName] == null) {
+                            if (isRequired) {
+                                if (props[propName] === null) {
+                                    return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
+                                }
+                                return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
+                            }
+                            return null;
+                        } else {
+                            return validate(props, propName, componentName, location, propFullName);
+                        }
+                    }
+
+                    var chainedCheckType = checkType.bind(null, false);
+                    chainedCheckType.isRequired = checkType.bind(null, true);
+
+                    return chainedCheckType;
+                }
+
+                function createPrimitiveTypeChecker(expectedType) {
+                    function validate(props, propName, componentName, location, propFullName, secret) {
+                        var propValue = props[propName];
+                        var propType = getPropType(propValue);
+                        if (propType !== expectedType) {
+                            // `propValue` being instance of, say, date/regexp, pass the 'object'
+                            // check, but we can offer a more precise error message here rather than
+                            // 'of type `object`'.
+                            var preciseType = getPreciseType(propValue);
+
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'));
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createAnyTypeChecker() {
+                    return createChainableTypeChecker(emptyFunctionThatReturnsNull);
+                }
+
+                function createArrayOfTypeChecker(typeChecker) {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        if (typeof typeChecker !== 'function') {
+                            return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');
+                        }
+                        var propValue = props[propName];
+                        if (!Array.isArray(propValue)) {
+                            var propType = getPropType(propValue);
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
+                        }
+                        for (var i = 0; i < propValue.length; i++) {
+                            var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
+                            if (error instanceof Error) {
+                                return error;
+                            }
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createElementTypeChecker() {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        var propValue = props[propName];
+                        if (!isValidElement(propValue)) {
+                            var propType = getPropType(propValue);
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createInstanceTypeChecker(expectedClass) {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        if (!(props[propName] instanceof expectedClass)) {
+                            var expectedClassName = expectedClass.name || ANONYMOUS;
+                            var actualClassName = getClassName(props[propName]);
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createEnumTypeChecker(expectedValues) {
+                    if (!Array.isArray(expectedValues)) {
+                        "development" !== 'production' ? printWarning('Invalid argument supplied to oneOf, expected an instance of array.') : void 0;
+                        return emptyFunctionThatReturnsNull;
+                    }
+
+                    function validate(props, propName, componentName, location, propFullName) {
+                        var propValue = props[propName];
+                        for (var i = 0; i < expectedValues.length; i++) {
+                            if (is(propValue, expectedValues[i])) {
+                                return null;
+                            }
+                        }
+
+                        var valuesString = JSON.stringify(expectedValues);
+                        return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createObjectOfTypeChecker(typeChecker) {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        if (typeof typeChecker !== 'function') {
+                            return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');
+                        }
+                        var propValue = props[propName];
+                        var propType = getPropType(propValue);
+                        if (propType !== 'object') {
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
+                        }
+                        for (var key in propValue) {
+                            if (propValue.hasOwnProperty(key)) {
+                                var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
+                                if (error instanceof Error) {
+                                    return error;
+                                }
+                            }
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createUnionTypeChecker(arrayOfTypeCheckers) {
+                    if (!Array.isArray(arrayOfTypeCheckers)) {
+                        "development" !== 'production' ? printWarning('Invalid argument supplied to oneOfType, expected an instance of array.') : void 0;
+                        return emptyFunctionThatReturnsNull;
+                    }
+
+                    for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
+                        var checker = arrayOfTypeCheckers[i];
+                        if (typeof checker !== 'function') {
+                            printWarning(
+                                'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +
+                                'received ' + getPostfixForTypeWarning(checker) + ' at index ' + i + '.'
+                            );
+                            return emptyFunctionThatReturnsNull;
+                        }
+                    }
+
+                    function validate(props, propName, componentName, location, propFullName) {
+                        for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
+                            var checker = arrayOfTypeCheckers[i];
+                            if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) {
+                                return null;
+                            }
+                        }
+
+                        return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.'));
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createNodeChecker() {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        if (!isNode(props[propName])) {
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createShapeTypeChecker(shapeTypes) {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        var propValue = props[propName];
+                        var propType = getPropType(propValue);
+                        if (propType !== 'object') {
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
+                        }
+                        for (var key in shapeTypes) {
+                            var checker = shapeTypes[key];
+                            if (!checker) {
+                                continue;
+                            }
+                            var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
+                            if (error) {
+                                return error;
+                            }
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function createStrictShapeTypeChecker(shapeTypes) {
+                    function validate(props, propName, componentName, location, propFullName) {
+                        var propValue = props[propName];
+                        var propType = getPropType(propValue);
+                        if (propType !== 'object') {
+                            return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
+                        }
+                        // We need to check all keys in case some are required but missing from
+                        // props.
+                        var allKeys = assign({}, props[propName], shapeTypes);
+                        for (var key in allKeys) {
+                            var checker = shapeTypes[key];
+                            if (!checker) {
+                                return new PropTypeError(
+                                    'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
+                                    '\nBad object: ' + JSON.stringify(props[propName], null, '  ') +
+                                    '\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, '  ')
+                                );
+                            }
+                            var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
+                            if (error) {
+                                return error;
+                            }
+                        }
+                        return null;
+                    }
+
+                    return createChainableTypeChecker(validate);
+                }
+
+                function isNode(propValue) {
+                    switch (typeof propValue) {
+                        case 'number':
+                        case 'string':
+                        case 'undefined':
+                            return true;
+                        case 'boolean':
+                            return !propValue;
+                        case 'object':
+                            if (Array.isArray(propValue)) {
+                                return propValue.every(isNode);
+                            }
+                            if (propValue === null || isValidElement(propValue)) {
+                                return true;
+                            }
+
+                            var iteratorFn = getIteratorFn(propValue);
+                            if (iteratorFn) {
+                                var iterator = iteratorFn.call(propValue);
+                                var step;
+                                if (iteratorFn !== propValue.entries) {
+                                    while (!(step = iterator.next()).done) {
+                                        if (!isNode(step.value)) {
+                                            return false;
+                                        }
+                                    }
+                                } else {
+                                    // Iterator will provide entry [k,v] tuples rather than values.
+                                    while (!(step = iterator.next()).done) {
+                                        var entry = step.value;
+                                        if (entry) {
+                                            if (!isNode(entry[1])) {
+                                                return false;
+                                            }
+                                        }
+                                    }
+                                }
+                            } else {
+                                return false;
+                            }
+
+                            return true;
+                        default:
+                            return false;
+                    }
+                }
+
+                function isSymbol(propType, propValue) {
+                    // Native Symbol.
+                    if (propType === 'symbol') {
+                        return true;
+                    }
+
+                    // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'
+                    if (propValue['@@toStringTag'] === 'Symbol') {
+                        return true;
+                    }
+
+                    // Fallback for non-spec compliant Symbols which are polyfilled.
+                    if (typeof Symbol === 'function' && propValue instanceof Symbol) {
+                        return true;
+                    }
+
+                    return false;
+                }
+
+                // Equivalent of `typeof` but with special handling for array and regexp.
+                function getPropType(propValue) {
+                    var propType = typeof propValue;
+                    if (Array.isArray(propValue)) {
+                        return 'array';
+                    }
+                    if (propValue instanceof RegExp) {
+                        // Old webkits (at least until Android 4.0) return 'function' rather than
+                        // 'object' for typeof a RegExp. We'll normalize this here so that /bla/
+                        // passes PropTypes.object.
+                        return 'object';
+                    }
+                    if (isSymbol(propType, propValue)) {
+                        return 'symbol';
+                    }
+                    return propType;
+                }
+
+                // This handles more types than `getPropType`. Only used for error messages.
+                // See `createPrimitiveTypeChecker`.
+                function getPreciseType(propValue) {
+                    if (typeof propValue === 'undefined' || propValue === null) {
+                        return '' + propValue;
+                    }
+                    var propType = getPropType(propValue);
+                    if (propType === 'object') {
+                        if (propValue instanceof Date) {
+                            return 'date';
+                        } else if (propValue instanceof RegExp) {
+                            return 'regexp';
+                        }
+                    }
+                    return propType;
+                }
+
+                // Returns a string that is postfixed to a warning about an invalid type.
+                // For example, "undefined" or "of type array"
+                function getPostfixForTypeWarning(value) {
+                    var type = getPreciseType(value);
+                    switch (type) {
+                        case 'array':
+                        case 'object':
+                            return 'an ' + type;
+                        case 'boolean':
+                        case 'date':
+                        case 'regexp':
+                            return 'a ' + type;
+                        default:
+                            return type;
+                    }
+                }
+
+                // Returns class name of the object, if any.
+                function getClassName(propValue) {
+                    if (!propValue.constructor || !propValue.constructor.name) {
+                        return ANONYMOUS;
+                    }
+                    return propValue.constructor.name;
+                }
+
+                ReactPropTypes.checkPropTypes = checkPropTypes;
+                ReactPropTypes.PropTypes = ReactPropTypes;
+
+                return ReactPropTypes;
+            };
+
+        }, {"./checkPropTypes": 1, "./lib/ReactPropTypesSecret": 5, "object-assign": 6}],
+        4: [function (require, module, exports) {
+            /**
+             * Copyright (c) 2013-present, Facebook, Inc.
+             *
+             * This source code is licensed under the MIT license found in the
+             * LICENSE file in the root directory of this source tree.
+             */
+
+            if ("development" !== 'production') {
+                var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' &&
+                    Symbol.for &&
+                    Symbol.for('react.element')) ||
+                    0xeac7;
+
+                var isValidElement = function (object) {
+                    return typeof object === 'object' &&
+                        object !== null &&
+                        object.$$typeof === REACT_ELEMENT_TYPE;
+                };
+
+                // By explicitly using `prop-types` you are opting into new development behavior.
+                // http://fb.me/prop-types-in-prod
+                var throwOnDirectAccess = true;
+                module.exports = require('./factoryWithTypeCheckers')(isValidElement, throwOnDirectAccess);
+            } else {
+                // By explicitly using `prop-types` you are opting into new production behavior.
+                // http://fb.me/prop-types-in-prod
+                module.exports = require('./factoryWithThrowingShims')();
+            }
+
+        }, {"./factoryWithThrowingShims": 2, "./factoryWithTypeCheckers": 3}],
+        5: [function (require, module, exports) {
+            /**
+             * Copyright (c) 2013-present, Facebook, Inc.
+             *
+             * This source code is licensed under the MIT license found in the
+             * LICENSE file in the root directory of this source tree.
+             */
+
+            'use strict';
+
+            var ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';
+
+            module.exports = ReactPropTypesSecret;
+
+        }, {}],
+        6: [function (require, module, exports) {
+            /*
+            object-assign
+            (c) Sindre Sorhus
+            @license MIT
+            */
+
+            'use strict';
+            /* eslint-disable no-unused-vars */
+            var getOwnPropertySymbols = Object.getOwnPropertySymbols;
+            var hasOwnProperty = Object.prototype.hasOwnProperty;
+            var propIsEnumerable = Object.prototype.propertyIsEnumerable;
+
+            function toObject(val) {
+                if (val === null || val === undefined) {
+                    throw new TypeError('Object.assign cannot be called with null or undefined');
+                }
+
+                return Object(val);
+            }
+
+            function shouldUseNative() {
+                try {
+                    if (!Object.assign) {
+                        return false;
+                    }
+
+                    // Detect buggy property enumeration order in older V8 versions.
+
+                    // https://bugs.chromium.org/p/v8/issues/detail?id=4118
+                    var test1 = new String('abc');  // eslint-disable-line no-new-wrappers
+                    test1[5] = 'de';
+                    if (Object.getOwnPropertyNames(test1)[0] === '5') {
+                        return false;
+                    }
+
+                    // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+                    var test2 = {};
+                    for (var i = 0; i < 10; i++) {
+                        test2['_' + String.fromCharCode(i)] = i;
+                    }
+                    var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
+                        return test2[n];
+                    });
+                    if (order2.join('') !== '0123456789') {
+                        return false;
+                    }
+
+                    // https://bugs.chromium.org/p/v8/issues/detail?id=3056
+                    var test3 = {};
+                    'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
+                        test3[letter] = letter;
+                    });
+                    if (Object.keys(Object.assign({}, test3)).join('') !==
+                        'abcdefghijklmnopqrst') {
+                        return false;
+                    }
+
+                    return true;
+                } catch (err) {
+                    // We don't expect any of the above to throw, but better to be safe.
+                    return false;
+                }
+            }
+
+            module.exports = shouldUseNative() ? Object.assign : function (target, source) {
+                var from;
+                var to = toObject(target);
+                var symbols;
+
+                for (var s = 1; s < arguments.length; s++) {
+                    from = Object(arguments[s]);
+
+                    for (var key in from) {
+                        if (hasOwnProperty.call(from, key)) {
+                            to[key] = from[key];
+                        }
+                    }
+
+                    if (getOwnPropertySymbols) {
+                        symbols = getOwnPropertySymbols(from);
+                        for (var i = 0; i < symbols.length; i++) {
+                            if (propIsEnumerable.call(from, symbols[i])) {
+                                to[symbols[i]] = from[symbols[i]];
+                            }
+                        }
+                    }
+                }
+
+                return to;
+            };
+
+        }, {}]
+    }, {}, [4])(4)
+});
\ No newline at end of file
diff --git a/schoolapps/static/js/react/prop-types.production.min.js b/schoolapps/static/js/react/prop-types.production.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..128d0111a92844041d9066d971ee0d46d2546f8b
--- /dev/null
+++ b/schoolapps/static/js/react/prop-types.production.min.js
@@ -0,0 +1 @@
+!function(f){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=f();else if("function"==typeof define&&define.amd)define([],f);else{var g;g="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,g.PropTypes=f()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module){"use strict";function emptyFunction(){}var ReactPropTypesSecret=require(3);module.exports=function(){function e(e,r,t,o,n,p){if(p!==ReactPropTypesSecret){var c=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw c.name="Invariant Violation",c}}function r(){return e}e.isRequired=e;var t={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:r,element:e,instanceOf:r,node:e,objectOf:r,oneOf:r,oneOfType:r,shape:r,exact:r};return t.checkPropTypes=emptyFunction,t.PropTypes=t,t}},{3:3}],2:[function(require,module){module.exports=require(1)()},{1:1}],3:[function(require,module){"use strict";var ReactPropTypesSecret="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";module.exports=ReactPropTypesSecret},{}]},{},[2])(2)});
\ No newline at end of file
diff --git a/schoolapps/static/js/rebus.js b/schoolapps/static/js/rebus.js
index d07b96a864c131e7804e5eef5afbf1046ec9a211..467e1a71a1c965cc1cb04f9ad72e094527e030cc 100755
--- a/schoolapps/static/js/rebus.js
+++ b/schoolapps/static/js/rebus.js
@@ -243,8 +243,6 @@ function getOption(option) {
     try {
         for (var _iterator3 = BASIC_OPTIONS[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
             var category = _step3.value;
-
-            // console.log(category);
             var _iteratorNormalCompletion4 = true;
             var _didIteratorError4 = false;
             var _iteratorError4 = undefined;
@@ -253,7 +251,6 @@ function getOption(option) {
                 for (var _iterator4 = category.options[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
                     var opt = _step4.value;
 
-                    // console.log(opt);
                     if (opt.id === option) {
                         return opt;
                     }
diff --git a/schoolapps/support/templates/support/rebus.html b/schoolapps/support/templates/support/rebus.html
index c9ad6405d2a36b7abb979698c88c39811791211f..cf21afca5ce750b778e8d33294d772e6f4c3ec57 100644
--- a/schoolapps/support/templates/support/rebus.html
+++ b/schoolapps/support/templates/support/rebus.html
@@ -30,7 +30,8 @@
             <div class="input-field col s12 support-input-mobile">
                 <i class="material-icons prefix">mode_edit</i>
                 <textarea id="{{ form.long_description.id_for_label }}" name="{{ form.long_description.html_name }}"
-                          class="materialize-textarea"></textarea>
+                          class="materialize-textarea">
+                </textarea>
 
                 <label for="{{ form.long_description.id_for_label }}">Bitte beschreibe dein Problem
                     <strong>genauer</strong> (optional)
diff --git a/schoolapps/templates/partials/header.html b/schoolapps/templates/partials/header.html
index 83d1fa36dfbef936af0ab8f91ccea244f3682c64..a0c320fd86156d421695a267b7582ebd839b7063 100755
--- a/schoolapps/templates/partials/header.html
+++ b/schoolapps/templates/partials/header.html
@@ -120,7 +120,8 @@
             <img src="{% static 'common/logo.png' %}">
         </div>
         <div class="col s6 right-align">
-            <a href="/"><strong>SchoolApps</strong></a><br>
+            <a href="/"><strong>SchoolApps</strong></a>
+            <br>
             Katharineum zu Lübeck
         </div>
     </div>
@@ -147,19 +148,13 @@
             <ul class="collapsible collapsible-accordion">
 
             {% if perms.aub.apply_for_aub or perms.aub.check1_aub or perms.aub.check2_aub %}
-                <li class="bold">
-                    <a class="collapsible-header waves-effect waves-primary"><i class="material-icons">assignment</i>
-                        Lehrerfunktionen
+                <li class="bold url-aub_index url-aub_details url-ab_edit url-aub_apply_for urlaub_applied_for">
+                    <a class="collapsible-header waves-effect waves-primary" href="{% url 'aub_index' %}"><i
+                            class="material-icons">business_center</i>
+                        Unterrichtsbefreiungen
                     </a>
                     <div class="collapsible-body">
                         <ul>
-                            {% if perms.aub.apply_for_aub %}
-                                <li class="url-aub_index url-aub_details url-ab_edit url-aub_apply_for urlaub_applied_for">
-                                    <a href="{% url 'aub_index' %}"><i class="material-icons">business_center</i>
-                                        Unterrichtsbefreiungen
-                                    </a>
-                                </li>
-                            {% endif %}
                             {% if perms.aub.check1_aub %}
                                 <li class="url-aub_check1">
                                     <a href="{% url 'aub_check1' %}"><i class="material-icons">done</i> Anträge
@@ -169,7 +164,8 @@
                             {% endif %}
                             {% if perms.aub.check2_aub %}
                                 <li class="url-aub_check2">
-                                    <a href="{% url 'aub_check2' %}"><i class="material-icons">done_all</i> Anträge
+                                    <a href="{% url 'aub_check2' %}"><i class="material-icons">done_all</i>
+                                        Anträge
                                         genehmigen 2
                                     </a>
                                 </li>
@@ -189,6 +185,49 @@
                     <div class="divider"></div>
                 </li>
             {% endif %}
+            {% if perms.fibu.request_booking  or perms.fibu.manage_booking or perms.fibu.manage_costcenter or perms.fibu.manage.account %}
+                <li class="bold url-fibu_index url-fibu_bookings_user_edit">
+                    <a class="collapsible-header waves-effect waves-primary" href="{% url 'fibu_index' %}"><i
+                            class="material-icons">euro_symbol</i>
+                        Finanzen
+                    </a>
+                    <div class="collapsible-body">
+                        <ul>
+                            {% if perms.fibu.check_booking %}
+                                <li class="url-fibu_bookings_check">
+                                    <a href="{% url 'fibu_bookings_check' %}"><i class="material-icons">done_all</i>Anträge
+                                        prüfen
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_booking %}
+                                <li class="url-fibu_bookings url-fibu_bookings_edit url-fibu_bookings_new">
+                                    <a href="{% url 'fibu_bookings' %}"><i class="material-icons">receipt</i>Buchungen
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_costcenter %}
+                                <li class="url-fibu_cost_centers url-fibu_cost_centers_edit">
+                                    <a href="{% url 'fibu_cost_centers' %}"><i class="material-icons">done</i>Kostenstellen
+                                    </a>
+                                </li>
+                                <li class="url-fibu_accounts url-fibu_accounts_edit">
+                                    <a href="{% url 'fibu_accounts' %}"><i class="material-icons">done</i>Buchungskonten
+                                    </a>
+                                </li>
+                            {% endif %}
+                            {% if perms.fibu.manage_booking %}
+                                <li class="url-fibu_reports url-fibu_reports_expenses">
+                                    <a href="{% url 'fibu_reports' %}"><i class="material-icons">list</i>Berichte</a>
+                                </li>
+                            {% endif %}
+                        </ul>
+                    </div>
+                </li>
+                <li>
+                    <div class="divider"></div>
+                </li>
+            {% endif %}
 
             {% if perms.timetable.show_plan %}
                 <li class="bold">
@@ -235,11 +274,11 @@
                         </ul>
                     </div>
                 </li>
-            {% endif %}
 
-            <li>
-                <div class="divider"></div>
-            </li>
+                <li>
+                    <div class="divider"></div>
+                </li>
+            {% endif %}
 
             <li>
                 <a href="{% url 'menu_show_current' %}" target="_blank">
@@ -247,7 +286,6 @@
                 </a>
             </li>
 
-
             {% if perms.menu.add_menu %}
                 <li class="url-menu_index url-menu_upload url-menu_index_msg">
                     <a href="{% url 'menu_index' %}">
@@ -359,6 +397,27 @@
 {#    </p>#}
 {#</header>#}
 
+{% if messages %}
+    <header>
+        {% for message in messages %}
+            <div class="alert {% if message.tags %}{{ message.tags }}{% else %}info{% endif %}">
+                <p>
+                    {% if message.tags == "success" %}
+                        <i class="material-icons left">check_circle</i>
+                    {% elif  message.tags == "info" %}
+                        <i class="material-icons left">info</i>
+                    {% elif  message.tags == "warning" %}
+                        <i class="material-icons left">warning</i>
+                    {% elif  message.tags == "error" %}
+                        <i class="material-icons left">error</i>
+                    {% endif %}
+                    {{ message }}
+                </p>
+            </div>
+        {% endfor %}
+    </header>
+{% endif %}
+
 {% if user.is_authenticated %}
     <div class="fixed-action-btn">
         <a class="btn-floating btn-large green">
diff --git a/schoolapps/templates/partials/paper/footer.html b/schoolapps/templates/partials/paper/footer.html
new file mode 100755
index 0000000000000000000000000000000000000000..1dc3a88b8281509e4eb8d18212b4513d32189845
--- /dev/null
+++ b/schoolapps/templates/partials/paper/footer.html
@@ -0,0 +1,32 @@
+{% load static %}
+<footer>
+    <div class="left">
+        Katharineum zu Lübeck
+    </div>
+
+    <div class="right">
+        Umsetzung: {{ COPYRIGHT_SHORT }}
+    </div>
+</footer>
+</div>
+</td>
+</tr>
+</tbody>
+<tfoot>
+<tr class="no-border">
+    <td>
+        <div class="footer-space">&nbsp;</div>
+    </td>
+</tr>
+</tfoot>
+</table>
+</main>
+
+<!---------------->
+<!-- JavaScript (jquery v. 3.4.1.slim)-->
+<!---------------->
+<script src="{% static 'common/manup.min.js' %}"></script>
+<script src="{% static 'js/materialize.min.js' %}"></script>
+<script src="{% static 'common/helper.js' %}"></script>
+</body>
+</html>
diff --git a/schoolapps/templates/partials/paper/header.html b/schoolapps/templates/partials/paper/header.html
new file mode 100755
index 0000000000000000000000000000000000000000..4b92397ddc86886771e811b1ec34ad72e2c776e2
--- /dev/null
+++ b/schoolapps/templates/partials/paper/header.html
@@ -0,0 +1,146 @@
+{% load static %}
+{% load pwa %}
+{% load url_name %}
+
+<!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">
+    <meta name="description" content="Selbst programmierte Anwendungen für den Schullaltag am Katharineum zu Lübeck">
+    <title>SchoolApps – Katharineum zu Lübeck</title>
+
+    <!--------->
+    <!-- CSS -->
+    <!--------->
+    <link href="{% static 'css/materialdesignicons-webfont/material-icons.css' %}" rel="stylesheet">
+    <link rel="stylesheet" type="text/css" href="{% static 'css/materialize.min.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'css/paper.css' %}">
+    <link rel="stylesheet" type="text/css" href="{% static 'common/style.css' %}">
+    <script src="{% static 'js/jquery/jquery-3.3.1.min.js' %}"></script>
+
+
+    <style>
+        body {
+            font-family: Cabin, sans-serif;
+        }
+
+        @page {
+            size: A4;
+            padding: 30mm;
+        }
+
+        header {
+            display: block;
+            width: 190mm;
+        }
+
+
+        #print-header {
+            display: block !important;
+            border-bottom: 1px solid;
+            margin-bottom: 0;
+            height: 22mm;
+            background: white;
+        }
+
+        header, main, footer {
+            margin: 0;
+        }
+
+        #print-header .col.right-align {
+            padding: 15px;
+        }
+
+        .sheet {
+            padding: 10mm;
+        }
+
+
+        .header-space, .footer-space {
+            height: 0;
+        }
+
+        .print-layout-table td {
+            padding: 0;
+        }
+
+        .print-layout-table .no-border {
+            border: 0;
+        }
+
+
+        footer {
+            margin-top: 5mm;
+            text-align: center;
+            width: 190mm;
+
+        }
+
+        header .row, header .col {
+            padding: 0 !important;
+            margin: 0 !important;
+        }
+
+        @media print {
+            .header-space {
+                height: 32mm;
+            }
+
+            .footer-space {
+                height: 20mm
+            }
+
+            header, footer {
+                height: 22mm;
+            }
+
+            header {
+                position: fixed;
+                top: 10mm;
+            }
+
+            footer {
+                position: fixed;
+                bottom: 0;
+            }
+
+            @page {
+                @bottom-center {
+                    content: "Seite " counter(page) " von " counter(pages);
+                }
+            }
+        }
+    </style>
+</head>
+<body class="A4">
+
+
+<div style="margin-top: -10mm;"></div>
+<main class="sheet infinitive">
+    <table class="print-layout-table">
+        <thead>
+        <tr class="no-border">
+            <td>
+                <div class="header-space">&nbsp;</div>
+            </td>
+        </tr>
+        </thead>
+        <tbody>
+        <tr class="no-border">
+            <td>
+                <div class="content">
+                    <header>
+                        <div id="print-header" class="row">
+                            <div class="col s6 logo">
+                                <img src="{% static 'common/logo.png' %}">
+                            </div>
+                            <div class="col s6 right-align">
+                                <h5>SchoolApps</h5>
+                                {% now "j. F Y H:i" %}
+                            </div>
+                        </div>
+                    </header>
+
+
diff --git a/schoolapps/timetable/templates/timetable/hintsinsubprint.html b/schoolapps/timetable/templates/timetable/hintsinsubprint.html
new file mode 100644
index 0000000000000000000000000000000000000000..d39760fac1dd3843c4f286c252c8088193443c91
--- /dev/null
+++ b/schoolapps/timetable/templates/timetable/hintsinsubprint.html
@@ -0,0 +1,16 @@
+{% load martortags %}
+{% if c.hints %}
+    {% for hint in c.hints %}
+        <div class="alert primary">
+            <div>
+                <strong>
+                    {{ hint.classes_formatted }}{% if hint.teachers and hint.classes.all %}, Lehrkräfte{% endif %}:
+                </strong>
+
+                <i class="material-icons left">announcement</i>
+
+                {{ hint.text|safe_markdown }}
+            </div>
+        </div>
+    {% endfor %}
+{% endif %}
diff --git a/schoolapps/timetable/templates/timetable/plan.html b/schoolapps/timetable/templates/timetable/plan.html
index 9874ea7b4aa8e2484a6a8c9b505af70a16d5562b..07a8aeef0fc284bfe37ebff502863a262e5cc49f 100755
--- a/schoolapps/timetable/templates/timetable/plan.html
+++ b/schoolapps/timetable/templates/timetable/plan.html
@@ -43,6 +43,19 @@
             <h3>
                 Stundenplan <i>{{ el }}</i>
             </h3>
+
+            {# Show class teacher and deputy class teacher #}
+            {% if type == 2 and el.teachers %}
+                <h5>Klassenlehrkräfte:
+                    {% for teacher in el.teachers %}
+
+                        <span data-position="bottom" class="tooltipped"
+                              data-tooltip="{{ teacher }}">
+                             <a href="{% url "timetable_smart_plan" "teacher" teacher.id %}">
+                                {{ teacher.shortcode }}</a></span>{% if not forloop.last %},{% endif %}
+                    {% endfor %}
+                </h5>
+            {% endif %}
         </div>
         {# Show print button only if not on mobile #}
         <div class="col s4 m6 l4 xl3 right align-right no-print">
diff --git a/schoolapps/timetable/templates/timetable/substitutionprint.html b/schoolapps/timetable/templates/timetable/substitutionprint.html
new file mode 100755
index 0000000000000000000000000000000000000000..39363d68a5b46e52b00d3099716362fb8aa9784e
--- /dev/null
+++ b/schoolapps/timetable/templates/timetable/substitutionprint.html
@@ -0,0 +1,110 @@
+{% load common %}
+{% include 'partials/paper/header.html' %}
+
+<script type="text/javascript">
+    var dest = "/timetable/substitutions/";
+</script>
+
+<style>
+    table.substitutions td, table.substitutions th {
+        padding: 0 2px;
+    }
+
+    span.badge.new {
+        font-size: 0.9rem;
+        line-height: 20px;
+        height: 20px;
+        margin: 2px;
+        letter-spacing: 0.3pt;
+    }
+</style>
+
+{% for c in days %}
+    <h4>Vertretungen {{ c.date|date:"l, j. F Y" }}</h4>
+
+
+    {% include "timetable/hintsinsubprint.html" %}
+
+    <div style="margin-bottom: 20px">
+        {% if c.header_info.is_box_needed %}
+            {% for row in c.header_info.rows %}
+                <div class="row no-margin">
+                    <div class="col s3 no-padding">
+                        <strong>{{ row.0 }}</strong>
+                    </div>
+                    <div class="col s9 no-padding">
+                        {{ row.1 }}
+                    </div>
+                </div>
+            {% endfor %}
+        {% endif %}
+    </div>
+
+    <table class="substitutions">
+        <thead>
+        <tr>
+            <th><i class="material-icons">people</i></th>
+            <th><i class="material-icons">access_time</i></th>
+            <th>Lehrer</th>
+            <th>Fach</th>
+            <th>Raum</th>
+            <th>Hinweis</th>
+            <th></th>
+        </tr>
+        </thead>
+        <tbody>
+        {% if not c.sub_table %}
+            <td colspan="7">
+                <p class="flow-text center">
+                    Keine Vertretungen vorhanden
+                </p>
+            </td>
+        {% endif %}
+
+        {% set color_background = 1 %}
+        {% set last_classes = "" %}
+
+        {% for sub in c.sub_table %}
+
+            {#  Color groups of classes in grey/white #}
+            {% if last_classes != sub.classes %}
+                {% if color_background %}{% set color_background = 0 %}
+                    {% else %}{% set color_background = 1 %}
+                {% endif %}
+            {% endif %}
+            {% set last_classes = sub.classes %}
+
+
+            <tr class="{{ sub.color }}-text {% if color_background %}striped{% endif %}">
+                <td>
+                    {{ sub.classes }}
+                </td>
+                <td>
+                    <strong>
+                        {{ sub.lesson }}
+                    </strong>
+                </td>
+                <td>
+                    {% include "timetable/subs/teacher.html" %}
+                </td>
+                <td>
+                    {% include "timetable/subs/subject.html" %}
+                </td>
+                <td>
+                    {% include "timetable/subs/room.html" %}
+                </td>
+                <td>
+                    {% if sub.badge %}
+                        <span class="badge new green">{{ sub.badge }}</span>
+                    {% endif %}
+                    <em>{{ sub.text|default:"" }}</em>
+                </td>
+            </tr>
+        {% endfor %}
+        </tbody>
+    </table>
+
+{% endfor %}
+
+
+{% include 'partials/paper/footer.html' %}
diff --git a/schoolapps/timetable/urls.py b/schoolapps/timetable/urls.py
index 1a8f979a2854425a642fe53b460ef7db7dd60d99..610ffb897ded39e7aa399ba6f326d15b3fe51881 100755
--- a/schoolapps/timetable/urls.py
+++ b/schoolapps/timetable/urls.py
@@ -23,12 +23,17 @@ try:
         path('substitutions/', views.substitutions, name='timetable_substitutions'),
         path('substitutions/<int:year>/<int:month>/<int:day>/', views.substitutions,
              name='timetable_substitutions_date'),
+        path('substitutions/<int:year>/<int:month>/<int:day>/print/', views.substitutions_print,
+             name='timetable_substitutions_date_print'),
+        path('substitutions/print/', views.substitutions_print,
+             name='timetable_substitutions_print'),
         path('aktuell.pdf', views.sub_pdf, name="timetable_substitutions_pdf"),
         path('<str:plan_date>-aktuell.pdf', views.sub_pdf, name="timetable_substitutions_pdf_date")
     ]
 
-except (Terms.DoesNotExist, Schoolyear.DoesNotExist, ProgrammingError, OperationalError):
+except (Terms.DoesNotExist, Schoolyear.DoesNotExist, ProgrammingError, OperationalError) as e:
     from . import fallback_view
+    print(e)
 
     urlpatterns = [
         path('hints', fallback_view.fallback, name="timetable_hints"),
@@ -46,5 +51,10 @@ except (Terms.DoesNotExist, Schoolyear.DoesNotExist, ProgrammingError, Operation
         path('substitutions/', fallback_view.fallback, name='timetable_substitutions'),
         path('substitutions/<int:year>/<int:month>/<int:day>/', fallback_view.fallback,
              name='timetable_substitutions_date'),
-        path('aktuell.pdf', fallback_view.fallback, name="timetable_substitutions_pdf")
+        path('substitutions/<int:year>/<int:month>/<int:day>/<str:print_view>/', fallback_view.fallback,
+             name='timetable_substitutions_date_print'),
+        path('substitutions/<str:print_view>/', fallback_view.fallback,
+             name='timetable_substitutions_print'),
+        path('aktuell.pdf', fallback_view.fallback, name="timetable_substitutions_pdf"),
+        path('<str:plan_date>-aktuell.pdf', fallback_view.fallback, name="timetable_substitutions_pdf_date")
     ]
diff --git a/schoolapps/timetable/views.py b/schoolapps/timetable/views.py
index c716d63dc3f0ed51e93e273ffc3c5d501b6d0c57..16d7ef2d35a7e1fdca49e601813cd3f05dcfdd86 100755
--- a/schoolapps/timetable/views.py
+++ b/schoolapps/timetable/views.py
@@ -328,19 +328,7 @@ def sub_pdf(request, plan_date=None):
     return FileResponse(file, content_type="application/pdf")
 
 
-@login_required
-@permission_required("timetable.show_plan")
-@cache_page(SUBS_VIEW_CACHE.expiration_time)
-def substitutions(request, year=None, month=None, day=None):
-    """Show substitutions in a classic view"""
-
-    date, time = find_out_what_is_today(year, month, day)
-
-    # Get next weekday if it is a weekend
-    next_weekday = get_next_weekday_with_time(date, time)
-    if next_weekday != date:
-        return redirect("timetable_substitutions_date", next_weekday.year, next_weekday.month, next_weekday.day)
-
+def get_subs_context(request, date):
     # Get subs and generate table
     events = get_all_events_by_date(date)
     subs = get_substitutions_by_date(date)
@@ -354,7 +342,7 @@ def substitutions(request, year=None, month=None, day=None):
     header_info = get_header_information(subs, date, events)
     hints = list(get_all_hints_by_time_period(date, date))
 
-    context = {
+    return {
         "subs": subs,
         "sub_table": sub_table,
         "date": date,
@@ -363,7 +351,51 @@ def substitutions(request, year=None, month=None, day=None):
         "hints": hints,
     }
 
-    return render(request, 'timetable/substitution.html', context)
+
+@login_required
+@permission_required("timetable.show_plan")
+@cache_page(SUBS_VIEW_CACHE.expiration_time)
+def substitutions(request, year=None, month=None, day=None):
+    """Show substitutions in a classic view"""
+
+    date, time = find_out_what_is_today(year, month, day)
+
+    # Get next weekday if it is a weekend
+    next_weekday = get_next_weekday_with_time(date, time)
+    if next_weekday != date:
+        return redirect("timetable_substitutions_date", next_weekday.year, next_weekday.month, next_weekday.day)
+
+    context = get_subs_context(request, date)
+
+    template_name = 'timetable/substitution.html'
+
+    return render(request, template_name, context)
+
+
+@login_required
+@permission_required("timetable.show_plan")
+@cache_page(SUBS_VIEW_CACHE.expiration_time)
+def substitutions_print(request, year=None, month=None, day=None):
+    """Show substitutions in a classic view"""
+
+    date, time = find_out_what_is_today(year, month, day)
+
+    # Get next weekday if it is a weekend
+    next_weekday = get_next_weekday_with_time(date, time)
+    if next_weekday != date:
+        return redirect("timetable_substitutions_date_print", next_weekday.year, next_weekday.month, next_weekday.day)
+
+    second_date = get_next_weekday(date + datetime.timedelta(days=1))
+    context1 = get_subs_context(request, date)
+    context2 = get_subs_context(request, second_date)
+
+    context = {
+        "days": [context1, context2]
+    }
+
+    template_name = 'timetable/substitutionprint.html'
+
+    return render(request, template_name, context)
 
 
 ###################
diff --git a/schoolapps/untisconnect/api.py b/schoolapps/untisconnect/api.py
index 313067603011e3d4a493980e98c36c0e60e4c686..767e5b599a9eb51ecf26c683228de34fa714dd7c 100755
--- a/schoolapps/untisconnect/api.py
+++ b/schoolapps/untisconnect/api.py
@@ -1,6 +1,7 @@
 from django.conf import settings
 
-from untisconnect.api_helper import get_term_by_ids, run_using, untis_date_to_date, date_to_untis_date
+from untisconnect.api_helper import get_term_by_ids, run_using, untis_date_to_date, date_to_untis_date, \
+    untis_split_first
 from . import models
 from timetable.settings import untis_settings
 
@@ -8,8 +9,6 @@ TYPE_TEACHER = 0
 TYPE_ROOM = 1
 TYPE_CLASS = 2
 
-from datetime import date
-
 
 def run_all(obj, filter_term=True):
     return run_default_filter(run_using(obj).all(), filter_term=filter_term)
@@ -118,6 +117,7 @@ class Class(object):
         self.text1 = None
         self.text2 = None
         self.room = None
+        self.teachers = []
 
     def __str__(self):
         if self.filled:
@@ -138,6 +138,9 @@ class Class(object):
         self.name = db_obj.name
         self.text1 = db_obj.longname
         self.text2 = db_obj.text
+        teacher_ids = untis_split_first(db_obj.teacherids, int)
+        self.teachers = [get_teacher_by_id(t_id) for t_id in teacher_ids]
+        print(self.teachers)
         # print(db_obj.room_id)
         if db_obj.room_id != 0:
             #   print("RAUM")