snapshot: preserve company config foundation and staff dashboard access
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from django.conf import settings
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .emailing import send_system_email
|
from .emailing import send_system_email
|
||||||
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
||||||
|
|
||||||
|
|
||||||
@admin.register(EmployeeProfile)
|
@admin.register(EmployeeProfile)
|
||||||
@@ -25,6 +25,11 @@ class PortalBrandingAdmin(admin.ModelAdmin):
|
|||||||
list_display = ('name', 'portal_title', 'company_name', 'company_domain', 'support_email', 'default_language', 'updated_at')
|
list_display = ('name', 'portal_title', 'company_name', 'company_domain', 'support_email', 'default_language', 'updated_at')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(PortalCompanyConfig)
|
||||||
|
class PortalCompanyConfigAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'legal_company_name', 'website_url', 'hr_contact_email', 'it_contact_email', 'updated_at')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(PortalAppConfig)
|
@admin.register(PortalAppConfig)
|
||||||
class PortalAppConfigAdmin(admin.ModelAdmin):
|
class PortalAppConfigAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ APP_DEFINITIONS: tuple[AppDefinition, ...] = (
|
|||||||
accent='APP',
|
accent='APP',
|
||||||
tags=(_('Suche'), _('Status'), _('PDF Zugriff')),
|
tags=(_('Suche'), _('Status'), _('PDF Zugriff')),
|
||||||
),
|
),
|
||||||
|
AppDefinition(
|
||||||
|
key='company_config',
|
||||||
|
section=PortalAppConfig.SECTION_PLATFORM,
|
||||||
|
route_name='portal_company_config_page',
|
||||||
|
title=_('Company Config'),
|
||||||
|
description=_('Rechtliche Firmendaten, Kontaktpunkte und öffentliche Unternehmenslinks pflegen.'),
|
||||||
|
action_label=_('Öffnen'),
|
||||||
|
capability='manage_company_config',
|
||||||
|
),
|
||||||
AppDefinition(
|
AppDefinition(
|
||||||
key='branding',
|
key='branding',
|
||||||
section=PortalAppConfig.SECTION_PLATFORM,
|
section=PortalAppConfig.SECTION_PLATFORM,
|
||||||
@@ -185,6 +194,12 @@ DEFAULT_ROLE_VISIBILITY = {
|
|||||||
ROLE_IT_STAFF: False,
|
ROLE_IT_STAFF: False,
|
||||||
ROLE_STAFF: False,
|
ROLE_STAFF: False,
|
||||||
},
|
},
|
||||||
|
'company_config': {
|
||||||
|
ROLE_SUPER_ADMIN: False,
|
||||||
|
ROLE_ADMIN: False,
|
||||||
|
ROLE_IT_STAFF: False,
|
||||||
|
ROLE_STAFF: False,
|
||||||
|
},
|
||||||
'app_registry': {
|
'app_registry': {
|
||||||
ROLE_SUPER_ADMIN: False,
|
ROLE_SUPER_ADMIN: False,
|
||||||
ROLE_ADMIN: False,
|
ROLE_ADMIN: False,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from django.conf import settings
|
|||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.translation import get_language
|
from django.utils.translation import get_language
|
||||||
|
|
||||||
from .models import PortalBranding
|
from .models import PortalBranding, PortalCompanyConfig
|
||||||
|
|
||||||
|
|
||||||
def get_portal_branding() -> PortalBranding:
|
def get_portal_branding() -> PortalBranding:
|
||||||
@@ -32,6 +32,26 @@ def get_portal_branding() -> PortalBranding:
|
|||||||
return branding
|
return branding
|
||||||
|
|
||||||
|
|
||||||
|
def get_portal_company_config() -> PortalCompanyConfig:
|
||||||
|
company_config, _ = PortalCompanyConfig.objects.get_or_create(
|
||||||
|
name='Default',
|
||||||
|
defaults={
|
||||||
|
'legal_company_name': 'TUBCO GmbH',
|
||||||
|
'country': 'Deutschland',
|
||||||
|
'website_url': '',
|
||||||
|
'imprint_url': '',
|
||||||
|
'privacy_url': '',
|
||||||
|
'hr_contact_email': '',
|
||||||
|
'it_contact_email': '',
|
||||||
|
'operations_contact_email': '',
|
||||||
|
'phone_number': '',
|
||||||
|
'vat_id': '',
|
||||||
|
'registration_number': '',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return company_config
|
||||||
|
|
||||||
|
|
||||||
def get_company_email_domain() -> str:
|
def get_company_email_domain() -> str:
|
||||||
branding = get_portal_branding()
|
branding = get_portal_branding()
|
||||||
domain = (branding.company_domain or '').strip().lower().lstrip('@')
|
domain = (branding.company_domain or '').strip().lower().lstrip('@')
|
||||||
@@ -72,6 +92,7 @@ def get_portal_letterhead_path() -> Path:
|
|||||||
|
|
||||||
def get_branding_context() -> dict[str, object]:
|
def get_branding_context() -> dict[str, object]:
|
||||||
branding = get_portal_branding()
|
branding = get_portal_branding()
|
||||||
|
company_config = get_portal_company_config()
|
||||||
lang = (get_language() or branding.default_language or 'de').split('-')[0]
|
lang = (get_language() or branding.default_language or 'de').split('-')[0]
|
||||||
footer_text = (branding.footer_text_en or '').strip() if lang == 'en' else ''
|
footer_text = (branding.footer_text_en or '').strip() if lang == 'en' else ''
|
||||||
legal_notice = (branding.legal_notice_en or '').strip() if lang == 'en' else ''
|
legal_notice = (branding.legal_notice_en or '').strip() if lang == 'en' else ''
|
||||||
@@ -97,6 +118,21 @@ def get_branding_context() -> dict[str, object]:
|
|||||||
'portal_has_custom_logo': bool(branding.logo_image),
|
'portal_has_custom_logo': bool(branding.logo_image),
|
||||||
'portal_has_custom_letterhead': bool(branding.pdf_letterhead),
|
'portal_has_custom_letterhead': bool(branding.pdf_letterhead),
|
||||||
'portal_has_custom_favicon': bool(branding.favicon_image),
|
'portal_has_custom_favicon': bool(branding.favicon_image),
|
||||||
|
'portal_company_config': company_config,
|
||||||
|
'portal_company_legal_name': company_config.legal_company_name or branding.company_name,
|
||||||
|
'portal_company_street': company_config.street_address,
|
||||||
|
'portal_company_postal_code': company_config.postal_code,
|
||||||
|
'portal_company_city': company_config.city,
|
||||||
|
'portal_company_country': company_config.country,
|
||||||
|
'portal_company_website_url': company_config.website_url,
|
||||||
|
'portal_company_imprint_url': company_config.imprint_url,
|
||||||
|
'portal_company_privacy_url': company_config.privacy_url,
|
||||||
|
'portal_company_hr_contact_email': company_config.hr_contact_email,
|
||||||
|
'portal_company_it_contact_email': company_config.it_contact_email,
|
||||||
|
'portal_company_operations_contact_email': company_config.operations_contact_email,
|
||||||
|
'portal_company_phone_number': company_config.phone_number,
|
||||||
|
'portal_company_vat_id': company_config.vat_id,
|
||||||
|
'portal_company_registration_number': company_config.registration_number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.utils.translation import get_language, gettext as _, gettext_lazy
|
|||||||
|
|
||||||
from .branding import get_company_email_domain
|
from .branding import get_company_email_domain
|
||||||
from .form_builder import apply_form_field_config
|
from .form_builder import apply_form_field_config
|
||||||
from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, PortalBranding, WorkflowConfig
|
from .models import EmployeeProfile, FormOption, OffboardingRequest, OnboardingRequest, PortalBranding, PortalCompanyConfig, WorkflowConfig
|
||||||
from .roles import ROLE_ADMIN, ROLE_GROUP_NAMES, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, assign_user_role
|
from .roles import ROLE_ADMIN, ROLE_GROUP_NAMES, ROLE_IT_STAFF, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_STAFF, ROLE_SUPER_ADMIN, assign_user_role
|
||||||
|
|
||||||
|
|
||||||
@@ -244,6 +244,43 @@ class PortalBrandingForm(forms.ModelForm):
|
|||||||
return favicon
|
return favicon
|
||||||
|
|
||||||
|
|
||||||
|
class PortalCompanyConfigForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PortalCompanyConfig
|
||||||
|
fields = [
|
||||||
|
'legal_company_name',
|
||||||
|
'street_address',
|
||||||
|
'postal_code',
|
||||||
|
'city',
|
||||||
|
'country',
|
||||||
|
'website_url',
|
||||||
|
'imprint_url',
|
||||||
|
'privacy_url',
|
||||||
|
'hr_contact_email',
|
||||||
|
'it_contact_email',
|
||||||
|
'operations_contact_email',
|
||||||
|
'phone_number',
|
||||||
|
'vat_id',
|
||||||
|
'registration_number',
|
||||||
|
]
|
||||||
|
labels = {
|
||||||
|
'legal_company_name': gettext_lazy('Rechtlicher Firmenname'),
|
||||||
|
'street_address': gettext_lazy('Straße und Hausnummer'),
|
||||||
|
'postal_code': gettext_lazy('Postleitzahl'),
|
||||||
|
'city': gettext_lazy('Stadt'),
|
||||||
|
'country': gettext_lazy('Land'),
|
||||||
|
'website_url': gettext_lazy('Website'),
|
||||||
|
'imprint_url': gettext_lazy('Impressum-URL'),
|
||||||
|
'privacy_url': gettext_lazy('Datenschutz-URL'),
|
||||||
|
'hr_contact_email': gettext_lazy('HR-Kontakt'),
|
||||||
|
'it_contact_email': gettext_lazy('IT-Kontakt'),
|
||||||
|
'operations_contact_email': gettext_lazy('Operations-Kontakt'),
|
||||||
|
'phone_number': gettext_lazy('Zentrale Telefonnummer'),
|
||||||
|
'vat_id': gettext_lazy('USt-IdNr.'),
|
||||||
|
'registration_number': gettext_lazy('Register- oder Handelsnummer'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class OnboardingRequestForm(forms.ModelForm):
|
class OnboardingRequestForm(forms.ModelForm):
|
||||||
first_name = forms.CharField(label='Vorname', required=False)
|
first_name = forms.CharField(label='Vorname', required=False)
|
||||||
last_name = forms.CharField(label='Nachname', required=False)
|
last_name = forms.CharField(label='Nachname', required=False)
|
||||||
|
|||||||
39
backend/workflows/migrations/0042_portalcompanyconfig.py
Normal file
39
backend/workflows/migrations/0042_portalcompanyconfig.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Generated by Django 5.1.5 on 2026-03-26 12:48
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('workflows', '0041_portalappconfig_visible_to_admin_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PortalCompanyConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(default='Default', max_length=80, unique=True)),
|
||||||
|
('legal_company_name', models.CharField(blank=True, default='', max_length=255)),
|
||||||
|
('street_address', models.CharField(blank=True, default='', max_length=255)),
|
||||||
|
('postal_code', models.CharField(blank=True, default='', max_length=50)),
|
||||||
|
('city', models.CharField(blank=True, default='', max_length=120)),
|
||||||
|
('country', models.CharField(blank=True, default='Deutschland', max_length=120)),
|
||||||
|
('website_url', models.URLField(blank=True, default='')),
|
||||||
|
('imprint_url', models.URLField(blank=True, default='')),
|
||||||
|
('privacy_url', models.URLField(blank=True, default='')),
|
||||||
|
('hr_contact_email', models.EmailField(blank=True, default='', max_length=254)),
|
||||||
|
('it_contact_email', models.EmailField(blank=True, default='', max_length=254)),
|
||||||
|
('operations_contact_email', models.EmailField(blank=True, default='', max_length=254)),
|
||||||
|
('phone_number', models.CharField(blank=True, default='', max_length=80)),
|
||||||
|
('vat_id', models.CharField(blank=True, default='', max_length=80)),
|
||||||
|
('registration_number', models.CharField(blank=True, default='', max_length=120)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Portal Company Config',
|
||||||
|
'verbose_name_plural': 'Portal Company Config',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -72,6 +72,32 @@ class PortalBranding(models.Model):
|
|||||||
return self.portal_title or self.company_name or self.name
|
return self.portal_title or self.company_name or self.name
|
||||||
|
|
||||||
|
|
||||||
|
class PortalCompanyConfig(models.Model):
|
||||||
|
name = models.CharField(max_length=80, default='Default', unique=True)
|
||||||
|
legal_company_name = models.CharField(max_length=255, blank=True, default='')
|
||||||
|
street_address = models.CharField(max_length=255, blank=True, default='')
|
||||||
|
postal_code = models.CharField(max_length=50, blank=True, default='')
|
||||||
|
city = models.CharField(max_length=120, blank=True, default='')
|
||||||
|
country = models.CharField(max_length=120, blank=True, default='Deutschland')
|
||||||
|
website_url = models.URLField(blank=True, default='')
|
||||||
|
imprint_url = models.URLField(blank=True, default='')
|
||||||
|
privacy_url = models.URLField(blank=True, default='')
|
||||||
|
hr_contact_email = models.EmailField(blank=True, default='')
|
||||||
|
it_contact_email = models.EmailField(blank=True, default='')
|
||||||
|
operations_contact_email = models.EmailField(blank=True, default='')
|
||||||
|
phone_number = models.CharField(max_length=80, blank=True, default='')
|
||||||
|
vat_id = models.CharField(max_length=80, blank=True, default='')
|
||||||
|
registration_number = models.CharField(max_length=120, blank=True, default='')
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Portal Company Config'
|
||||||
|
verbose_name_plural = 'Portal Company Config'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.legal_company_name or self.name
|
||||||
|
|
||||||
|
|
||||||
class PortalAppConfig(models.Model):
|
class PortalAppConfig(models.Model):
|
||||||
SECTION_APP = 'app'
|
SECTION_APP = 'app'
|
||||||
SECTION_PLATFORM = 'platform'
|
SECTION_PLATFORM = 'platform'
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ ROLE_LABELS = {
|
|||||||
CAPABILITIES = {
|
CAPABILITIES = {
|
||||||
'manage_users': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN},
|
'manage_users': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN},
|
||||||
'manage_product_branding': {ROLE_PLATFORM_OWNER},
|
'manage_product_branding': {ROLE_PLATFORM_OWNER},
|
||||||
|
'manage_company_config': {ROLE_PLATFORM_OWNER},
|
||||||
'manage_app_registry': {ROLE_PLATFORM_OWNER},
|
'manage_app_registry': {ROLE_PLATFORM_OWNER},
|
||||||
'access_requests_dashboard': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
'access_requests_dashboard': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF, ROLE_STAFF},
|
||||||
|
'view_request_timeline': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
||||||
'run_intro_session': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
'run_intro_session': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
||||||
'generate_intro_pdfs': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
'generate_intro_pdfs': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
||||||
'retry_requests': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
'retry_requests': {ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, ROLE_ADMIN, ROLE_IT_STAFF},
|
||||||
@@ -124,9 +126,11 @@ def template_role_context(user) -> dict[str, object]:
|
|||||||
'role_key': role_key,
|
'role_key': role_key,
|
||||||
'role_label': str(ROLE_LABELS[role_key]),
|
'role_label': str(ROLE_LABELS[role_key]),
|
||||||
'can_manage_product_branding': user_has_capability(user, 'manage_product_branding'),
|
'can_manage_product_branding': user_has_capability(user, 'manage_product_branding'),
|
||||||
|
'can_manage_company_config': user_has_capability(user, 'manage_company_config'),
|
||||||
'can_manage_app_registry': user_has_capability(user, 'manage_app_registry'),
|
'can_manage_app_registry': user_has_capability(user, 'manage_app_registry'),
|
||||||
'can_manage_users': user_has_capability(user, 'manage_users'),
|
'can_manage_users': user_has_capability(user, 'manage_users'),
|
||||||
'can_access_requests_dashboard': user_has_capability(user, 'access_requests_dashboard'),
|
'can_access_requests_dashboard': user_has_capability(user, 'access_requests_dashboard'),
|
||||||
|
'can_view_request_timeline': user_has_capability(user, 'view_request_timeline'),
|
||||||
'can_run_intro_session': user_has_capability(user, 'run_intro_session'),
|
'can_run_intro_session': user_has_capability(user, 'run_intro_session'),
|
||||||
'can_generate_intro_pdfs': user_has_capability(user, 'generate_intro_pdfs'),
|
'can_generate_intro_pdfs': user_has_capability(user, 'generate_intro_pdfs'),
|
||||||
'can_retry_requests': user_has_capability(user, 'retry_requests'),
|
'can_retry_requests': user_has_capability(user, 'retry_requests'),
|
||||||
|
|||||||
@@ -141,6 +141,29 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-site-footer-links {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-site-footer-links a {
|
||||||
|
color: #1f3a5f;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
color var(--motion-fast) var(--motion-ease),
|
||||||
|
transform var(--motion-fast) var(--motion-ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-site-footer-links a:hover {
|
||||||
|
color: var(--app-brand-blue);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.app-header,
|
.app-header,
|
||||||
.app-header-in-shell {
|
.app-header-in-shell {
|
||||||
|
|||||||
@@ -197,7 +197,7 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_scripts %}
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const searchInput = document.getElementById('app-registry-search');
|
const searchInput = document.getElementById('app-registry-search');
|
||||||
|
|||||||
@@ -22,6 +22,13 @@
|
|||||||
<div class="app-site-footer">
|
<div class="app-site-footer">
|
||||||
{% if portal_footer_text %}<div class="app-site-footer-main">{{ portal_footer_text }}</div>{% endif %}
|
{% if portal_footer_text %}<div class="app-site-footer-main">{{ portal_footer_text }}</div>{% endif %}
|
||||||
{% if portal_legal_notice %}<div class="app-site-footer-legal">{{ portal_legal_notice }}</div>{% endif %}
|
{% if portal_legal_notice %}<div class="app-site-footer-legal">{{ portal_legal_notice }}</div>{% endif %}
|
||||||
|
{% if portal_company_imprint_url or portal_company_privacy_url or portal_company_website_url %}
|
||||||
|
<div class="app-site-footer-links">
|
||||||
|
{% if portal_company_website_url %}<a href="{{ portal_company_website_url }}" target="_blank" rel="noopener">{% trans "Website" %}</a>{% endif %}
|
||||||
|
{% if portal_company_imprint_url %}<a href="{{ portal_company_imprint_url }}" target="_blank" rel="noopener">{% trans "Impressum" %}</a>{% endif %}
|
||||||
|
{% if portal_company_privacy_url %}<a href="{{ portal_company_privacy_url }}" target="_blank" rel="noopener">{% trans "Datenschutz" %}</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="confirm-modal" id="app-confirm-modal" hidden aria-hidden="true">
|
<div class="confirm-modal" id="app-confirm-modal" hidden aria-hidden="true">
|
||||||
|
|||||||
120
backend/workflows/templates/workflows/company_config.html
Normal file
120
backend/workflows/templates/workflows/company_config.html
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
{% extends 'workflows/base_shell.html' %}
|
||||||
|
{% load static i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Company Config" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'workflows/css/admin_tools.css' %}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block shell_body %}
|
||||||
|
{% include 'workflows/includes/app_header.html' with header_show_home=1 header_show_lang=1 header_inside_shell=1 %}
|
||||||
|
<h1>{% trans "Company Config" %}</h1>
|
||||||
|
<p class="sub">{% trans "Strukturierte Firmendaten, Kontaktpunkte und öffentliche Unternehmenslinks zentral pflegen." %}</p>
|
||||||
|
|
||||||
|
{% include 'workflows/includes/messages.html' %}
|
||||||
|
|
||||||
|
<section class="branding-sections">
|
||||||
|
<form method="post" action="{% url 'save_portal_company_config' %}" class="stack-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<section class="branding-block">
|
||||||
|
<div class="branding-block-head">
|
||||||
|
<h2>{% trans "Firmenprofil" %}</h2>
|
||||||
|
<p>{% trans "Rechtlicher Name und zentrale Stammdaten der Firma." %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.legal_company_name.id_for_label }}">{{ form.legal_company_name.label }}</label>
|
||||||
|
{{ form.legal_company_name }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.phone_number.id_for_label }}">{{ form.phone_number.label }}</label>
|
||||||
|
{{ form.phone_number }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.website_url.id_for_label }}">{{ form.website_url.label }}</label>
|
||||||
|
{{ form.website_url }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.country.id_for_label }}">{{ form.country.label }}</label>
|
||||||
|
{{ form.country }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="branding-block">
|
||||||
|
<div class="branding-block-head">
|
||||||
|
<h2>{% trans "Adresse & Register" %}</h2>
|
||||||
|
<p>{% trans "Anschrift sowie optionale Register- und Steuerangaben." %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="field field-full">
|
||||||
|
<label for="{{ form.street_address.id_for_label }}">{{ form.street_address.label }}</label>
|
||||||
|
{{ form.street_address }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.postal_code.id_for_label }}">{{ form.postal_code.label }}</label>
|
||||||
|
{{ form.postal_code }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.city.id_for_label }}">{{ form.city.label }}</label>
|
||||||
|
{{ form.city }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.registration_number.id_for_label }}">{{ form.registration_number.label }}</label>
|
||||||
|
{{ form.registration_number }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.vat_id.id_for_label }}">{{ form.vat_id.label }}</label>
|
||||||
|
{{ form.vat_id }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="branding-block">
|
||||||
|
<div class="branding-block-head">
|
||||||
|
<h2>{% trans "Kontaktpunkte" %}</h2>
|
||||||
|
<p>{% trans "Zentrale Ansprechpartner für HR, IT und Operations." %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.hr_contact_email.id_for_label }}">{{ form.hr_contact_email.label }}</label>
|
||||||
|
{{ form.hr_contact_email }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.it_contact_email.id_for_label }}">{{ form.it_contact_email.label }}</label>
|
||||||
|
{{ form.it_contact_email }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.operations_contact_email.id_for_label }}">{{ form.operations_contact_email.label }}</label>
|
||||||
|
{{ form.operations_contact_email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="branding-block">
|
||||||
|
<div class="branding-block-head">
|
||||||
|
<h2>{% trans "Recht & Öffentlichkeit" %}</h2>
|
||||||
|
<p>{% trans "Öffentliche Links für Website, Impressum und Datenschutz." %}</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.imprint_url.id_for_label }}">{{ form.imprint_url.label }}</label>
|
||||||
|
{{ form.imprint_url }}
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="{{ form.privacy_url.id_for_label }}">{{ form.privacy_url.label }}</label>
|
||||||
|
{{ form.privacy_url }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="hint">{% trans "Diese Links können später im Portal-Footer oder in öffentlichen Seiten verwendet werden." %}</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="toolbar" style="margin-top:1rem;">
|
||||||
|
<div class="hint">{% trans "Diese Ebene ist bewusst von Branding getrennt: Hier geht es um strukturierte Firmendaten, nicht um visuelle Gestaltung." %}</div>
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Firmenkonfiguration speichern" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
<th>{% trans "E-Mail" %}</th>
|
<th>{% trans "E-Mail" %}</th>
|
||||||
<th>{% trans "Dokument" %}</th>
|
<th>{% trans "Dokument" %}</th>
|
||||||
{% if can_run_intro_session or can_generate_intro_pdfs %}<th>{% trans "Einweisung" %}</th>{% endif %}
|
{% if can_run_intro_session or can_generate_intro_pdfs %}<th>{% trans "Einweisung" %}</th>{% endif %}
|
||||||
{% if can_retry_requests or can_delete_requests or can_access_requests_dashboard %}<th>{% trans "Aktion" %}</th>{% endif %}
|
{% if can_view_request_timeline or can_retry_requests or can_delete_requests %}<th>{% trans "Aktion" %}</th>{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -271,9 +271,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_retry_requests or can_delete_requests or can_access_requests_dashboard %}
|
{% if can_view_request_timeline or can_retry_requests or can_delete_requests %}
|
||||||
<td class="actions-cell">
|
<td class="actions-cell">
|
||||||
|
{% if can_view_request_timeline %}
|
||||||
<a class="btn btn-secondary" href="/requests/timeline/{{ row.kind_slug }}/{{ row.id }}/">{% trans "Timeline" %}</a>
|
<a class="btn btn-secondary" href="/requests/timeline/{{ row.kind_slug }}/{{ row.id }}/">{% trans "Timeline" %}</a>
|
||||||
|
{% endif %}
|
||||||
{% if can_retry_requests and row.status_key == 'failed' %}
|
{% if can_retry_requests and row.status_key == 'failed' %}
|
||||||
<form method="post" action="/requests/retry/{{ row.kind_slug }}/{{ row.id }}/" class="inline-delete" data-confirm="{% trans 'Eintrag erneut verarbeiten?' %}">
|
<form method="post" action="/requests/retry/{{ row.kind_slug }}/{{ row.id }}/" class="inline-delete" data-confirm="{% trans 'Eintrag erneut verarbeiten?' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ urlpatterns = [
|
|||||||
path('admin-tools/handbook/', views.handbook_page, name='handbook_page'),
|
path('admin-tools/handbook/', views.handbook_page, name='handbook_page'),
|
||||||
path('admin-tools/branding/', views.portal_branding_page, name='portal_branding_page'),
|
path('admin-tools/branding/', views.portal_branding_page, name='portal_branding_page'),
|
||||||
path('admin-tools/branding/save/', views.save_portal_branding, name='save_portal_branding'),
|
path('admin-tools/branding/save/', views.save_portal_branding, name='save_portal_branding'),
|
||||||
|
path('admin-tools/company/', views.portal_company_config_page, name='portal_company_config_page'),
|
||||||
|
path('admin-tools/company/save/', views.save_portal_company_config, name='save_portal_company_config'),
|
||||||
path('admin-tools/apps/', views.portal_app_registry_page, name='portal_app_registry_page'),
|
path('admin-tools/apps/', views.portal_app_registry_page, name='portal_app_registry_page'),
|
||||||
path('admin-tools/apps/save/', views.save_portal_app_registry, name='save_portal_app_registry'),
|
path('admin-tools/apps/save/', views.save_portal_app_registry, name='save_portal_app_registry'),
|
||||||
path('admin-tools/users/', views.user_management_page, name='user_management_page'),
|
path('admin-tools/users/', views.user_management_page, name='user_management_page'),
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from django.urls import reverse
|
|||||||
from .app_registry import build_portal_app_sections, get_portal_app_registry_rows
|
from .app_registry import build_portal_app_sections, get_portal_app_registry_rows
|
||||||
from .backup_ops import create_backup_bundle, delete_backup_bundle, list_backup_bundles, verify_backup_bundle
|
from .backup_ops import create_backup_bundle, delete_backup_bundle, list_backup_bundles, verify_backup_bundle
|
||||||
from .branding import get_branding_email_copy, get_company_email_domain, get_default_notification_templates
|
from .branding import get_branding_email_copy, get_company_email_domain, get_default_notification_templates
|
||||||
from .forms import OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, UserManagementCreateForm
|
from .forms import OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, PortalCompanyConfigForm, UserManagementCreateForm
|
||||||
from .form_builder import (
|
from .form_builder import (
|
||||||
DEFAULT_FIELD_ORDER,
|
DEFAULT_FIELD_ORDER,
|
||||||
LOCKED_FIELD_RULES,
|
LOCKED_FIELD_RULES,
|
||||||
@@ -36,7 +36,7 @@ from .form_builder import (
|
|||||||
ONBOARDING_PAGE_ORDER,
|
ONBOARDING_PAGE_ORDER,
|
||||||
ensure_form_field_configs,
|
ensure_form_field_configs,
|
||||||
)
|
)
|
||||||
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
from .models import AdminAuditLog, EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, ScheduledWelcomeEmail, SystemEmailConfig, WorkflowConfig
|
||||||
from .emailing import send_system_email
|
from .emailing import send_system_email
|
||||||
from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability
|
from .roles import ROLE_GROUP_NAMES, ROLE_LABELS, ROLE_PLATFORM_OWNER, ROLE_SUPER_ADMIN, assign_user_role, get_user_role_key, get_user_role_label, user_has_capability
|
||||||
from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud
|
from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud
|
||||||
@@ -587,6 +587,64 @@ def save_portal_branding(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@_require_capability('manage_company_config')
|
||||||
|
def portal_company_config_page(request):
|
||||||
|
company_config, created = PortalCompanyConfig.objects.get_or_create(name='Default')
|
||||||
|
form = PortalCompanyConfigForm(instance=company_config)
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'workflows/company_config.html',
|
||||||
|
{
|
||||||
|
'form': form,
|
||||||
|
'company_config': company_config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@_require_capability('manage_company_config')
|
||||||
|
@require_POST
|
||||||
|
def save_portal_company_config(request):
|
||||||
|
company_config, created = PortalCompanyConfig.objects.get_or_create(name='Default')
|
||||||
|
form = PortalCompanyConfigForm(request.POST, instance=company_config)
|
||||||
|
if not form.is_valid():
|
||||||
|
messages.error(request, _('Firmenkonfiguration konnte nicht gespeichert werden. Bitte prüfen Sie die Eingaben.'))
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'workflows/company_config.html',
|
||||||
|
{
|
||||||
|
'form': form,
|
||||||
|
'company_config': company_config,
|
||||||
|
},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
company_config = form.save()
|
||||||
|
_audit(
|
||||||
|
request,
|
||||||
|
'portal_company_config_saved',
|
||||||
|
target_type='portal_company_config',
|
||||||
|
target_id=company_config.id,
|
||||||
|
target_label=company_config.legal_company_name or 'Default',
|
||||||
|
details={
|
||||||
|
'website_url': company_config.website_url,
|
||||||
|
'imprint_url': company_config.imprint_url,
|
||||||
|
'privacy_url': company_config.privacy_url,
|
||||||
|
'hr_contact_email': company_config.hr_contact_email,
|
||||||
|
'it_contact_email': company_config.it_contact_email,
|
||||||
|
'operations_contact_email': company_config.operations_contact_email,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
messages.success(request, _('Firmenkonfiguration wurde gespeichert.'))
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'workflows/company_config.html',
|
||||||
|
{
|
||||||
|
'form': PortalCompanyConfigForm(instance=company_config),
|
||||||
|
'company_config': company_config,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@_require_capability('manage_users')
|
@_require_capability('manage_users')
|
||||||
@require_POST
|
@require_POST
|
||||||
def create_user_from_admin(request):
|
def create_user_from_admin(request):
|
||||||
@@ -838,7 +896,7 @@ def delete_backup_from_admin(request, backup_name: str):
|
|||||||
return redirect('backup_recovery_page')
|
return redirect('backup_recovery_page')
|
||||||
|
|
||||||
|
|
||||||
@_require_capability('access_requests_dashboard')
|
@_require_capability('view_request_timeline')
|
||||||
def request_timeline_page(request, kind: str, request_id: int):
|
def request_timeline_page(request, kind: str, request_id: int):
|
||||||
if kind == 'onboarding':
|
if kind == 'onboarding':
|
||||||
obj = get_object_or_404(OnboardingRequest, id=request_id)
|
obj = get_object_or_404(OnboardingRequest, id=request_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user