diff --git a/.gitignore b/.gitignore index faf74fd..5fe0831 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,10 @@ __pycache__/ *.pyo .env db.sqlite3 +*.sqlite3 media/ staticfiles/ venv/ .venv/ +.vscode/ +.idea/ diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index f1d119a..2dd5d10 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0.6 on 2026-06-27 17:19 +# Generated by Django 6.0.6 on 2026-06-28 10:19 import django.db.models.deletion from django.conf import settings @@ -46,12 +46,15 @@ class Migration(migrations.Migration): ('projects_inventory_shopping_add', models.BooleanField(default=False, help_text='Benutzer darf Einkaufspositionen erstellen.', verbose_name='Einkauf hinzufügen')), ('projects_inventory_shopping_edit', models.BooleanField(default=False, help_text='Benutzer darf Einkaufspositionen ändern.', verbose_name='Einkauf bearbeiten')), ('projects_inventory_shopping_delete', models.BooleanField(default=False, help_text='Benutzer darf Einkaufspositionen löschen.', verbose_name='Einkauf löschen')), + ('projects_inventory_shopping_to_expense', models.BooleanField(default=False, help_text='Benutzer darf Einkäufe als erledigt markieren.', verbose_name='Einkäufe durchführen')), ('inventory_view', models.BooleanField(default=False, help_text='Benutzer darf Inventar einsehen.', verbose_name='Inventar anzeigen')), ('inventory_add', models.BooleanField(default=False, help_text='Benutzer darf Inventareinträge erstellen.', verbose_name='Inventar hinzufügen')), ('inventory_edit', models.BooleanField(default=False, help_text='Benutzer darf Inventareinträge ändern.', verbose_name='Inventar bearbeiten')), ('inventory_delete', models.BooleanField(default=False, help_text='Benutzer darf Inventareinträge löschen.', verbose_name='Inventar löschen')), ('invoices_view', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen verwalten.', verbose_name='Rechnungsbereich anzeigen')), + ('invoices_info', models.BooleanField(default=False, help_text='Benutzer darf Statistiken sehen.', verbose_name='Statistiken ansehen')), ('invoices_customers_view', models.BooleanField(default=False, help_text='Benutzer darf Kunden sehen.', verbose_name='Kunden anzeigen')), + ('invoices_customers_info', models.BooleanField(default=False, help_text='Benutzer darf Kundenstatistiken sehen.', verbose_name='Kundenstatistiken ansehen')), ('invoices_customers_detail_view', models.BooleanField(default=False, help_text='Benutzer darf Details des Kunden sehen.', verbose_name='Kundendetails anzeigen')), ('invoices_customers_add', models.BooleanField(default=False, help_text='Benutzer darf Kunden anlegen.', verbose_name='Kunden erstellen')), ('invoices_customers_edit', models.BooleanField(default=False, help_text='Benutzer darf Kunden ändern.', verbose_name='Kunden bearbeiten')), @@ -62,6 +65,7 @@ class Migration(migrations.Migration): ('invoices_items_edit', models.BooleanField(default=False, help_text='Benutzer darf Artikel ändern.', verbose_name='Artikel bearbeiten')), ('invoices_items_delete', models.BooleanField(default=False, help_text='Benutzer darf Artikel löschen.', verbose_name='Artikel löschen')), ('invoices_invoices_view', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen sehen.', verbose_name='Rechnungen anzeigen')), + ('invoices_invoices_info', models.BooleanField(default=False, help_text='Benutzer darf Rechnungsstatistiken sehen.', verbose_name='Rechnungsstatistiken ansehen')), ('invoices_invoices_project_add', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen aus Projekten erstellen.', verbose_name='Projekt-Rechnung erstellen')), ('invoices_invoices_customer_add', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen für Kunden erstellen.', verbose_name='Kunden-Rechnung erstellen')), ('invoices_invoices_invoices_view', models.BooleanField(default=False, help_text='Benutzer darf Rechnungsdetails sehen.', verbose_name='Rechnungsdetails anzeigen')), @@ -72,24 +76,23 @@ class Migration(migrations.Migration): ('invoices_invoices_invoices_cancel', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen stornieren.', verbose_name='Rechnung stornieren')), ('invoices_invoices_invoices_paid', models.BooleanField(default=False, help_text='Benutzer darf Rechnungsstatus ändern.', verbose_name='Rechnung auf bezahlt setzen')), ('invoices_invoices_invoices_delete', models.BooleanField(default=False, help_text='Benutzer darf Rechnungen löschen.', verbose_name='Rechnungen löschen')), - ('invoices_invoices_invoices_overdue', models.BooleanField(default=False, help_text='Benutzer darf überfällige Rechnungen einsehen.', verbose_name='Überfällige Rechnungen anzeigen')), ('invoices_offers_view', models.BooleanField(default=False, help_text='Benutzer darf Angebote sehen.', verbose_name='Angebote anzeigen')), + ('invoices_offers_info', models.BooleanField(default=False, help_text='Benutzer darf Angebotsstatistiken sehen.', verbose_name='Angebotsstatistiken ansehen')), ('invoices_offers_add', models.BooleanField(default=False, help_text='Benutzer darf Angebote für Kunden erstellen.', verbose_name='Angebot erstellen')), ('invoices_offers_offers_view', models.BooleanField(default=False, help_text='Benutzer darf Angebotsdetails sehen.', verbose_name='Angebotsdetails anzeigen')), ('invoices_offers_offers_item_add', models.BooleanField(default=False, help_text='Benutzer darf Positionen hinzufügen.', verbose_name='Angebotsposition hinzufügen')), ('invoices_offers_offers_item_delete', models.BooleanField(default=False, help_text='Benutzer darf Positionen löschen.', verbose_name='Angebotsposition löschen')), ('invoices_offers_offers_preview', models.BooleanField(default=False, help_text='Benutzer darf Vorschauen öffnen.', verbose_name='Angebotsvorschau anzeigen')), ('invoices_offers_offers_download', models.BooleanField(default=False, help_text='Benutzer darf Angebote herunterladen.', verbose_name='Angebot herunterladen')), - ('invoices_offers_offers_status', models.BooleanField(default=False, help_text='Benutzer darf Angebotsstatus ändern.', verbose_name='Angebotsstatus bearbeiten')), ('invoices_offers_offers_delete', models.BooleanField(default=False, help_text='Benutzer darf Angebote löschen.', verbose_name='Angebote löschen')), ('invoices_reminders_view', models.BooleanField(default=False, help_text='Benutzer darf Mahnungen einsehen.', verbose_name='Mahnungen anzeigen')), + ('invoices_reminders_info', models.BooleanField(default=False, help_text='Benutzer darf Mahnungsstatistiken sehen.', verbose_name='Mahnungsstatistiken ansehen')), ('invoices_reminders_add', models.BooleanField(default=False, help_text='Benutzer darf Mahnungen erstellen.', verbose_name='Mahnungen hinzufügen')), ('invoices_reminders_reminders_view', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen einsehen.', verbose_name='Erinnerungen anzeigen')), ('invoices_reminders_reminders_cancel', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen stornieren.', verbose_name='Erinnerungen stornieren')), ('invoices_reminders_reminders_delete', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen löschen.', verbose_name='Erinnerungen löschen')), ('invoices_reminders_reminders_download', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen herunterladen.', verbose_name='Erinnerungen herunterladen')), - ('invoices_reminders_reminders_paid', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen als bezahlt markieren.', verbose_name='Erinnerungen als bezahlt markieren')), - ('invoices_reminders_reminders_expire', models.BooleanField(default=False, help_text='Benutzer darf Zahlungserinnerungen als abgelaufen markieren.', verbose_name='Erinnerungen ablaufen lassen')), + ('invoices_payments_view', models.BooleanField(default=False, help_text='Benutzer darf Zahlungen einsehen.', verbose_name='Zahlungen anzeigen')), ('users_view', models.BooleanField(default=False, help_text='Benutzer darf Mitarbeiter sehen.', verbose_name='Benutzer anzeigen')), ('users_add', models.BooleanField(default=False, help_text='Benutzer darf neue Mitarbeiter anlegen.', verbose_name='Benutzer erstellen')), ('users_edit', models.BooleanField(default=False, help_text='Benutzer darf Mitarbeiter bearbeiten.', verbose_name='Benutzer bearbeiten')), diff --git a/accounts/models.py b/accounts/models.py index 20e42e9..c29aa9d 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -42,7 +42,7 @@ class Role(models.Model): projects_inventory_shopping_add = models.BooleanField(default=False, verbose_name="Einkauf hinzufügen", help_text="Benutzer darf Einkaufspositionen erstellen.") projects_inventory_shopping_edit = models.BooleanField(default=False, verbose_name="Einkauf bearbeiten", help_text="Benutzer darf Einkaufspositionen ändern.") projects_inventory_shopping_delete = models.BooleanField(default=False, verbose_name="Einkauf löschen", help_text="Benutzer darf Einkaufspositionen löschen.") - #projects_inventory_shopping_buy = models.BooleanField(default=False, verbose_name="Einkäufe durchführen", help_text="Benutzer darf Einkäufe als erledigt markieren.") + projects_inventory_shopping_to_expense = models.BooleanField(default=False, verbose_name="Einkäufe durchführen", help_text="Benutzer darf Einkäufe als erledigt markieren.") # ===================== @@ -58,8 +58,10 @@ class Role(models.Model): # Rechnungen # ===================== invoices_view = models.BooleanField(default=False, verbose_name="Rechnungsbereich anzeigen", help_text="Benutzer darf Rechnungen verwalten.") - + invoices_info = models.BooleanField(default=False, verbose_name="Statistiken ansehen", help_text="Benutzer darf Statistiken sehen.") + invoices_customers_view = models.BooleanField(default=False, verbose_name="Kunden anzeigen", help_text="Benutzer darf Kunden sehen.") + invoices_customers_info = models.BooleanField(default=False, verbose_name="Kundenstatistiken ansehen", help_text="Benutzer darf Kundenstatistiken sehen.") invoices_customers_detail_view = models.BooleanField(default=False, verbose_name="Kundendetails anzeigen", help_text="Benutzer darf Details des Kunden sehen.") invoices_customers_add = models.BooleanField(default=False, verbose_name="Kunden erstellen", help_text="Benutzer darf Kunden anlegen.") invoices_customers_edit = models.BooleanField(default=False, verbose_name="Kunden bearbeiten", help_text="Benutzer darf Kunden ändern.") @@ -72,6 +74,7 @@ class Role(models.Model): invoices_items_delete = models.BooleanField(default=False, verbose_name="Artikel löschen", help_text="Benutzer darf Artikel löschen.") invoices_invoices_view = models.BooleanField(default=False, verbose_name="Rechnungen anzeigen", help_text="Benutzer darf Rechnungen sehen.") + invoices_invoices_info = models.BooleanField(default=False, verbose_name="Rechnungsstatistiken ansehen", help_text="Benutzer darf Rechnungsstatistiken sehen.") invoices_invoices_project_add = models.BooleanField(default=False, verbose_name="Projekt-Rechnung erstellen", help_text="Benutzer darf Rechnungen aus Projekten erstellen.") invoices_invoices_customer_add = models.BooleanField(default=False, verbose_name="Kunden-Rechnung erstellen", help_text="Benutzer darf Rechnungen für Kunden erstellen.") invoices_invoices_invoices_view = models.BooleanField(default=False, verbose_name="Rechnungsdetails anzeigen", help_text="Benutzer darf Rechnungsdetails sehen.") @@ -82,27 +85,26 @@ class Role(models.Model): invoices_invoices_invoices_cancel = models.BooleanField(default=False, verbose_name="Rechnung stornieren", help_text="Benutzer darf Rechnungen stornieren.") invoices_invoices_invoices_paid = models.BooleanField(default=False, verbose_name="Rechnung auf bezahlt setzen", help_text="Benutzer darf Rechnungsstatus ändern.") invoices_invoices_invoices_delete = models.BooleanField(default=False, verbose_name="Rechnungen löschen", help_text="Benutzer darf Rechnungen löschen.") - invoices_invoices_invoices_overdue = models.BooleanField(default=False, verbose_name="Überfällige Rechnungen anzeigen", help_text="Benutzer darf überfällige Rechnungen einsehen.") invoices_offers_view = models.BooleanField(default=False, verbose_name="Angebote anzeigen", help_text="Benutzer darf Angebote sehen.") + invoices_offers_info = models.BooleanField(default=False, verbose_name="Angebotsstatistiken ansehen", help_text="Benutzer darf Angebotsstatistiken sehen.") invoices_offers_add = models.BooleanField(default=False, verbose_name="Angebot erstellen", help_text="Benutzer darf Angebote für Kunden erstellen.") invoices_offers_offers_view = models.BooleanField(default=False, verbose_name="Angebotsdetails anzeigen", help_text="Benutzer darf Angebotsdetails sehen.") invoices_offers_offers_item_add = models.BooleanField(default=False, verbose_name="Angebotsposition hinzufügen", help_text="Benutzer darf Positionen hinzufügen.") invoices_offers_offers_item_delete = models.BooleanField(default=False, verbose_name="Angebotsposition löschen", help_text="Benutzer darf Positionen löschen.") invoices_offers_offers_preview = models.BooleanField(default=False, verbose_name="Angebotsvorschau anzeigen", help_text="Benutzer darf Vorschauen öffnen.") invoices_offers_offers_download = models.BooleanField(default=False, verbose_name="Angebot herunterladen", help_text="Benutzer darf Angebote herunterladen.") - invoices_offers_offers_status = models.BooleanField(default=False, verbose_name="Angebotsstatus bearbeiten", help_text="Benutzer darf Angebotsstatus ändern.") invoices_offers_offers_delete = models.BooleanField(default=False, verbose_name="Angebote löschen", help_text="Benutzer darf Angebote löschen.") invoices_reminders_view = models.BooleanField(default=False, verbose_name="Mahnungen anzeigen", help_text="Benutzer darf Mahnungen einsehen.") + invoices_reminders_info = models.BooleanField(default=False, verbose_name="Mahnungsstatistiken ansehen", help_text="Benutzer darf Mahnungsstatistiken sehen.") invoices_reminders_add = models.BooleanField(default=False, verbose_name="Mahnungen hinzufügen", help_text="Benutzer darf Mahnungen erstellen.") invoices_reminders_reminders_view = models.BooleanField(default=False, verbose_name="Erinnerungen anzeigen", help_text="Benutzer darf Zahlungserinnerungen einsehen.") invoices_reminders_reminders_cancel = models.BooleanField(default=False, verbose_name="Erinnerungen stornieren", help_text="Benutzer darf Zahlungserinnerungen stornieren.") invoices_reminders_reminders_delete = models.BooleanField(default=False, verbose_name="Erinnerungen löschen", help_text="Benutzer darf Zahlungserinnerungen löschen.") invoices_reminders_reminders_download = models.BooleanField(default=False, verbose_name="Erinnerungen herunterladen", help_text="Benutzer darf Zahlungserinnerungen herunterladen.") - invoices_reminders_reminders_paid = models.BooleanField(default=False, verbose_name="Erinnerungen als bezahlt markieren", help_text="Benutzer darf Zahlungserinnerungen als bezahlt markieren.") - invoices_reminders_reminders_expire = models.BooleanField(default=False, verbose_name="Erinnerungen ablaufen lassen", help_text="Benutzer darf Zahlungserinnerungen als abgelaufen markieren.") + invoices_payments_view = models.BooleanField(default=False, verbose_name="Zahlungen anzeigen", help_text="Benutzer darf Zahlungen einsehen.") # ===================== # Benutzerverwaltung # ===================== diff --git a/accounts/templates/accounts/login.html b/accounts/templates/accounts/login.html index d8b6619..8cca113 100644 --- a/accounts/templates/accounts/login.html +++ b/accounts/templates/accounts/login.html @@ -1,60 +1,60 @@ {% extends "base.html" %} -{% block title %} -Login -{% endblock %} +{% block title %}Login{% endblock %} -{% block header_title %} -
- {% if company_settings.logo %} - - - {% elif company_settings.companyname %} - {{ company_settings.companyname }} - {% else %} - Projektverwaltung - {% endif %} -
-{% endblock %} - -{% block header_description %} -{% endblock %} +{% block header %}{% endblock %} {% block content %} +
+
-
+ +
+ {% if company_settings.logo %} + + {% elif company_settings.companyname %} + {{ company_settings.companyname }} + {% else %} + Projektverwaltung + {% endif %} +
-
+ +
-

- Anmelden -

+

Anmelden

{% csrf_token %} {% for field in form %} -
- - - {% if field.errors %} -

{{ field.errors|join:", " }}

- {% endif %} -
+
+ + + {% if field.errors %} +

{{ field.errors|join:", " }}

+ {% endif %} +
{% endfor %} @@ -62,7 +62,6 @@ Login
-
{% endblock %} \ No newline at end of file diff --git a/accounts/templates/accounts/overview.html b/accounts/templates/accounts/overview.html index f9b58fc..e81f19a 100644 --- a/accounts/templates/accounts/overview.html +++ b/accounts/templates/accounts/overview.html @@ -18,7 +18,7 @@ Mitarbeiter, Rollen und Berechtigungen verwalten
-

Aktionen

+

Aktionen

Mitarbeiter oder Rollen hinzufügen

@@ -35,7 +35,7 @@ Mitarbeiter, Rollen und Berechtigungen verwalten
-

+

Benutzer

@@ -59,7 +59,7 @@ Mitarbeiter, Rollen und Berechtigungen verwalten
-

+

Archivierte Benutzer

@@ -79,7 +79,7 @@ Mitarbeiter, Rollen und Berechtigungen verwalten
-

+

Rollen

diff --git a/accounts/templates/accounts/tables/user/archive_edit_form.html b/accounts/templates/accounts/tables/user/archive_edit_form.html index a780962..c58effb 100644 --- a/accounts/templates/accounts/tables/user/archive_edit_form.html +++ b/accounts/templates/accounts/tables/user/archive_edit_form.html @@ -91,7 +91,7 @@ type="text" disabled value="{{ item.last_login|date:'d.m.Y H:i' }}" - class="border rounded px-3 py-2 w-full bg-slate-100"> + class="border rounded px-3 py-2 w-full bg-slate-100 dark:bg-slate-700">
diff --git a/accounts/templates/accounts/tables/user/edit_form.html b/accounts/templates/accounts/tables/user/edit_form.html index 1352f10..59a08b1 100644 --- a/accounts/templates/accounts/tables/user/edit_form.html +++ b/accounts/templates/accounts/tables/user/edit_form.html @@ -86,12 +86,11 @@ - + class="border rounded px-3 py-2 w-full bg-slate-100 dark:bg-slate-700">
diff --git a/config/settings.py b/config/settings.py index 2a721be..79946b1 100644 --- a/config/settings.py +++ b/config/settings.py @@ -22,6 +22,7 @@ INSTALLED_APPS = [ 'time_tracking', 'tailwind', 'theme', + 'django_apscheduler', "django_tables2", "django_filters", 'django.contrib.admin', @@ -83,6 +84,9 @@ USE_I18N = True USE_TZ = True STATIC_URL = 'static/' +STATICFILES_DIRS = [ + BASE_DIR / "static", +] STATIC_ROOT = BASE_DIR / 'staticfiles' TAILWIND_APP_NAME = 'theme' diff --git a/core/context_processors.py b/core/context_processors.py index bf9f1bd..5dcd2de 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -1,17 +1,23 @@ -from projects.models import ProjectMembership +from projects.models import ProjectMembership, Project from .models import CompanySetting def project_switcher(request): if not request.user.is_authenticated: - return {"user_projects": []} + return {"user_projects": [], "archived_count": 0} memberships = ProjectMembership.objects.select_related("project").filter( user=request.user, project__active=True ) + archived_count = ProjectMembership.objects.filter( + user=request.user, + project__active=False + ).count() + return { - "user_projects": [m.project for m in memberships] + "user_projects": [m.project for m in memberships], + "archived_count": archived_count, } diff --git a/core/forms/base.py b/core/forms/base.py index 193a9e7..4ab8661 100644 --- a/core/forms/base.py +++ b/core/forms/base.py @@ -4,10 +4,13 @@ from django import forms class BaseForm(forms.ModelForm): default_class = ( - "w-full px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 " - "bg-slate-50 dark:bg-slate-700 text-slate-900 dark:text-white " - "placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 " - "focus:border-transparent transition" + "w-full px-4 py-2 rounded-lg border mt-1 " + "border-slate-300 dark:border-slate-600 " + "bg-slate-50 dark:bg-slate-700 " + "text-slate-900 dark:text-white " + "placeholder-slate-400 focus:outline-none " + "focus:border-transparent transition " + "[--tw-ring-color:var(--company-color)] focus:ring-2 " ) def apply_style(self): diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 967f750..5794c96 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0.6 on 2026-06-27 17:19 +# Generated by Django 6.0.6 on 2026-06-28 10:19 from django.db import migrations, models @@ -18,6 +18,7 @@ class Migration(migrations.Migration): ('companyname', models.CharField(max_length=255, verbose_name='Firmenname')), ('name', models.CharField(max_length=255, verbose_name='Geschäftsführer')), ('logo', models.ImageField(blank=True, null=True, upload_to='company/', verbose_name='Logo')), + ('favicon', models.ImageField(blank=True, null=True, upload_to='company/', verbose_name='Favicon')), ('slogan', models.CharField(blank=True, max_length=255, verbose_name='Slogan')), ('color', models.CharField(default='#0f172a', max_length=20, verbose_name='Farbe')), ('street', models.CharField(max_length=255, verbose_name='Straße')), diff --git a/core/models.py b/core/models.py index 3b1f118..c0a35dc 100644 --- a/core/models.py +++ b/core/models.py @@ -5,6 +5,7 @@ class CompanySetting(models.Model): companyname = models.CharField(max_length=255,verbose_name="Firmenname") name = models.CharField(max_length=255,verbose_name="Geschäftsführer") logo = models.ImageField(upload_to="company/",blank=True,null=True,verbose_name="Logo") + favicon = models.ImageField(upload_to="company/",blank=True,null=True,verbose_name="Favicon") slogan = models.CharField(max_length=255,blank=True,verbose_name="Slogan") color = models.CharField(max_length=20,default="#0f172a",verbose_name="Farbe") street = models.CharField(max_length=255,verbose_name="Straße") diff --git a/core/templates/base.html b/core/templates/base.html index 97cfb1a..4a4e977 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -18,6 +18,11 @@ {% tailwind_css %} + {% if company_settings.favicon %} + + {% else %} + + {% endif %}