from django.conf import settings from django.core.validators import FileExtensionValidator from django.db import models from django.utils.translation import get_language from django.utils.translation import gettext_lazy as _ def _normalized_language_code(value: str | None) -> str: lang = (value or '').strip().split('-')[0].lower() return lang or 'de' class EmployeeProfile(models.Model): full_name = models.CharField(max_length=255) first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=155) department = models.CharField(max_length=255, blank=True) job_title = models.CharField(max_length=255, blank=True) work_email = models.EmailField(unique=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self) -> str: return f"{self.full_name} <{self.work_email}>" class PortalBranding(models.Model): name = models.CharField(max_length=80, default='Default', unique=True) portal_title = models.CharField(max_length=255, default='TUBCO Onboarding & Offboarding Portal') company_name = models.CharField(max_length=255, default='TUBCO') company_domain = models.CharField(max_length=120, blank=True, default='tub.co') support_email = models.EmailField(blank=True, default='info@tub.co') sender_display_name = models.CharField(max_length=255, blank=True, default='TUBCO') login_subtitle = models.CharField(max_length=255, blank=True, default='Bitte melden Sie sich mit Ihrem Benutzerkonto an.') footer_text = models.CharField(max_length=255, blank=True, default='TUBCO Onboarding & Offboarding Portal') footer_text_en = models.CharField(max_length=255, blank=True, default='TUBCO Onboarding & Offboarding Portal') legal_notice = models.TextField(blank=True, default='') legal_notice_en = models.TextField(blank=True, default='') default_language = models.CharField( max_length=10, choices=[('de', 'Deutsch'), ('en', 'English')], default='de', ) logo_image = models.FileField( upload_to='branding/', blank=True, null=True, validators=[FileExtensionValidator(allowed_extensions=['svg', 'png', 'jpg', 'jpeg', 'webp'])], ) pdf_letterhead = models.FileField( upload_to='branding/', blank=True, null=True, validators=[FileExtensionValidator(allowed_extensions=['pdf'])], ) favicon_image = models.FileField( upload_to='branding/', blank=True, null=True, validators=[FileExtensionValidator(allowed_extensions=['ico', 'png', 'svg', 'webp'])], ) primary_color = models.CharField(max_length=20, blank=True, default='#000078') secondary_color = models.CharField(max_length=20, blank=True, default='#c0002b') updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = 'Portal Branding' verbose_name_plural = 'Portal Branding' def __str__(self) -> str: 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 PortalTrialConfig(models.Model): name = models.CharField(max_length=80, default='Default', unique=True) is_trial_mode = models.BooleanField(default=False) trial_started_at = models.DateTimeField(null=True, blank=True) trial_expires_at = models.DateTimeField(null=True, blank=True) restrict_production_integrations = models.BooleanField(default=True) auto_cleanup_enabled = models.BooleanField(default=True) trial_banner_text = models.CharField(max_length=255, blank=True, default='') trial_banner_text_en = models.CharField(max_length=255, blank=True, default='') last_cleanup_at = models.DateTimeField(null=True, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = 'Portal Trial Config' verbose_name_plural = 'Portal Trial Config' def __str__(self) -> str: return self.name class PortalAppConfig(models.Model): SECTION_APP = 'app' SECTION_PLATFORM = 'platform' SECTION_ADMIN = 'admin' SECTION_CHOICES = [ (SECTION_APP, _('Apps')), (SECTION_PLATFORM, _('Platform Apps')), (SECTION_ADMIN, _('Admin Apps')), ] key = models.CharField(max_length=80, unique=True) section = models.CharField(max_length=20, choices=SECTION_CHOICES, default=SECTION_APP) sort_order = models.PositiveIntegerField(default=0) is_enabled = models.BooleanField(default=True) visible_to_super_admin = models.BooleanField(default=True) visible_to_admin = models.BooleanField(default=True) visible_to_it_staff = models.BooleanField(default=False) visible_to_staff = models.BooleanField(default=False) title_override = models.CharField(max_length=255, blank=True) title_override_en = models.CharField(max_length=255, blank=True) description_override = models.TextField(blank=True) description_override_en = models.TextField(blank=True) action_label_override = models.CharField(max_length=255, blank=True) action_label_override_en = models.CharField(max_length=255, blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['section', 'sort_order', 'key'] verbose_name = 'Portal App' verbose_name_plural = 'Portal Apps' def __str__(self) -> str: return self.key def _translated_value(self, field_name: str, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en': english_value = (getattr(self, f'{field_name}_en', '') or '').strip() if english_value: return english_value return (getattr(self, field_name, '') or '').strip() def translated_title_override(self, language_code: str | None = None) -> str: return self._translated_value('title_override', language_code) def translated_description_override(self, language_code: str | None = None) -> str: return self._translated_value('description_override', language_code) def translated_action_label_override(self, language_code: str | None = None) -> str: return self._translated_value('action_label_override', language_code) class AdminAuditLog(models.Model): actor = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name='admin_audit_logs', ) actor_display = models.CharField(max_length=255, blank=True) action = models.CharField(max_length=120) target_type = models.CharField(max_length=80, blank=True) target_id = models.PositiveIntegerField(null=True, blank=True) target_label = models.CharField(max_length=255, blank=True) details = models.JSONField(default=dict, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at', '-id'] verbose_name = 'Admin Audit Log' verbose_name_plural = 'Admin Audit Logs' def __str__(self) -> str: actor = self.actor_display or 'Unbekannt' return f'{self.created_at:%Y-%m-%d %H:%M} | {actor} | {self.action}' class OnboardingRequest(models.Model): STATUS_CHOICES = [ ('submitted', _('Eingereicht')), ('processing', _('In Bearbeitung')), ('completed', _('Abgeschlossen')), ('failed', _('Fehlgeschlagen')), ] full_name = models.CharField(max_length=255, verbose_name='Vorname und Nachname') gender = models.CharField( max_length=20, blank=True, choices=[('herr', _('Herr')), ('frau', _('Frau')), ('divers', _('Divers'))], verbose_name='Anrede', ) job_title = models.CharField(max_length=255, blank=True, verbose_name='Berufsbezeichnung') department = models.CharField(max_length=255, blank=True, verbose_name='Abteilung') work_email = models.EmailField(verbose_name='Gewünschte dienstliche E-Mail-Adresse') contract_start = models.DateField(verbose_name='Vertragsbeginn') employment_type = models.CharField( max_length=20, blank=True, choices=[('befristet', _('befristet')), ('unbefristet', _('unbefristet'))], verbose_name='Beschäftigungsverhältnis', ) employment_end_date = models.DateField(null=True, blank=True, verbose_name='Enddatum (nur bei befristet)') handover_date = models.DateField(null=True, blank=True, verbose_name='Gewünschtes Übergabedatum der Geräte') order_business_cards = models.BooleanField(default=False, verbose_name='Bestellung Visitenkarten') business_card_name = models.CharField(max_length=255, blank=True, verbose_name='Name (Visitenkarte)') business_card_title = models.CharField(max_length=255, blank=True, verbose_name='Titel (Visitenkarte)') business_card_email = models.EmailField(blank=True, verbose_name='E-Mailadresse (Visitenkarte)') business_card_phone = models.CharField(max_length=100, blank=True, verbose_name='Telefonnummer (Visitenkarte)') group_mailboxes_required = models.BooleanField(default=False, verbose_name='Gruppenpostfächer erforderlich?') group_mailboxes = models.TextField(blank=True, verbose_name='Gruppenpostfächer') needed_devices = models.TextField(blank=True, verbose_name='Benötigte Geräte und Gegenstände') needed_software = models.TextField(blank=True, verbose_name='Benötigte Software') needed_accesses = models.TextField(blank=True, verbose_name='Benötigte Zugänge') needed_workspace_groups = models.TextField(blank=True, verbose_name='Benötigte Gruppen im Workspace') additional_software_needed = models.BooleanField(default=False, verbose_name='Wird zusätzliche Software benötigt?') additional_software = models.TextField(blank=True, verbose_name='Zusätzlich gewünschte Software (ohne Garantie)') additional_hardware_needed = models.BooleanField(default=False, verbose_name='Wird zusätzliche Hardware benötigt?') additional_hardware = models.TextField(blank=True, verbose_name='Zusätzliche Hardware') additional_hardware_other = models.TextField(blank=True, verbose_name='Weitere Hardware (Freitext)') additional_access_needed = models.BooleanField(default=False, verbose_name='Werden weitere Zugänge benötigt?') additional_access_text = models.TextField(blank=True, verbose_name='Weitere Zugänge (Freitext)') needed_resources = models.TextField(blank=True, verbose_name='Benötigte Ressourcen') phone_number = models.CharField(max_length=100, blank=True, verbose_name='TUB/CO-Telefon-Direktwahl-Nr. 030 447202 (10-89)') successor_required = models.BooleanField(default=False, verbose_name='Neue Mitarbeitende ist Nachfolge von?') successor_name = models.CharField(max_length=255, blank=True, verbose_name='Name der Vorgängerperson') inherit_phone_number = models.BooleanField(default=False, verbose_name='Telefonnummer von Vorgängerperson übernehmen') additional_notes = models.TextField(blank=True, verbose_name='Raum für zusätzliche Anmerkungen und Wünsche') onboarded_by_email = models.EmailField(blank=True, verbose_name='E-Mail der anfordernden Person') onboarded_by_name = models.CharField(max_length=255, blank=True, verbose_name='Name der anfordernden Person') agreement = models.TextField(blank=True, verbose_name='Vereinbarung') signature_url = models.URLField(blank=True, verbose_name='Unterschrift') signature_image = models.ImageField(upload_to='signatures/', blank=True, null=True, verbose_name='Unterschrift (Bilddatei)') personalized_text = models.TextField( blank=True, verbose_name='Personalisierter Text für PDF', help_text='Optionaler individueller Textblock im Onboarding PDF.', ) generated_pdf_path = models.CharField(max_length=500, blank=True) intro_pdf_path = models.CharField(max_length=500, blank=True) processing_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='submitted') last_error = models.TextField(blank=True) preferred_language = models.CharField(max_length=10, blank=True, default='de', db_default='de') created_at = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: return f"Onboarding #{self.id} - {self.full_name}" def save(self, *args, **kwargs): self.preferred_language = _normalized_language_code(self.preferred_language) super().save(*args, **kwargs) class FormOption(models.Model): CATEGORY_CHOICES = [ ('department', _('Abteilung')), ('device', _('Geräte')), ('software', _('Software')), ('access', _('Zugänge')), ('workspace_group', _('Workspace-Gruppen')), ('resource', _('Ressourcen')), ('phone', _('Telefonnummern')), ] category = models.CharField(max_length=40, choices=CATEGORY_CHOICES) label = models.CharField(max_length=255) label_en = models.CharField(max_length=255, blank=True) value = models.CharField(max_length=255, blank=True) sort_order = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=True) class Meta: ordering = ['category', 'sort_order', 'label'] unique_together = ('category', 'label') def __str__(self) -> str: return f"{self.get_category_display()}: {self.label}" def translated_label(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.label_en.strip(): return self.label_en.strip() return self.label.strip() class FormFieldConfig(models.Model): PAGE_CHOICES = [ ('', _('Automatisch')), ('stammdaten', _('Stammdaten')), ('vertrag', _('Vertrag')), ('itsetup', _('IT-Setup')), ('abschluss', _('Abschluss')), ] FORM_CHOICES = [ ('onboarding', _('Onboarding')), ('offboarding', _('Offboarding')), ] form_type = models.CharField(max_length=20, choices=FORM_CHOICES) field_name = models.CharField(max_length=80) sort_order = models.PositiveIntegerField(default=0) is_visible = models.BooleanField(default=True) is_required = models.BooleanField(null=True, blank=True, default=None) page_key = models.CharField(max_length=20, blank=True, default='', choices=PAGE_CHOICES) label_override = models.CharField(max_length=255, blank=True) label_override_en = models.CharField(max_length=255, blank=True) help_text_override = models.TextField(blank=True) help_text_override_en = models.TextField(blank=True) class Meta: ordering = ['form_type', 'sort_order', 'field_name'] unique_together = ('form_type', 'field_name') verbose_name = 'Formularfeld-Konfiguration' verbose_name_plural = 'Formularfeld-Konfigurationen' def __str__(self) -> str: return f'{self.get_form_type_display()}: {self.field_name}' def translated_label_override(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.label_override_en.strip(): return self.label_override_en.strip() return self.label_override.strip() def translated_help_text_override(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.help_text_override_en.strip(): return self.help_text_override_en.strip() return self.help_text_override.strip() class NotificationTemplate(models.Model): TEMPLATE_CHOICES = [ ('onboarding_it', _('Onboarding: IT')), ('onboarding_general_info', _('Onboarding: Allgemeine Info')), ('onboarding_business_card', _('Onboarding: Visitenkarte')), ('onboarding_hr_works', _('Onboarding: HR Works')), ('onboarding_key', _('Onboarding: Schlüssel')), ('onboarding_reference', _('Onboarding: Referenz Anfordernde Person')), ('onboarding_welcome', _('Onboarding: Welcome E-Mail')), ('offboarding_it', _('Offboarding: IT')), ('offboarding_general_info', _('Offboarding: Allgemeine Info')), ('offboarding_hr_works_disable', _('Offboarding: HR Works Deaktivierung')), ('offboarding_reference', _('Offboarding: Referenz Anfordernde Person')), ] key = models.CharField(max_length=60, choices=TEMPLATE_CHOICES, unique=True) subject_template = models.CharField(max_length=255) subject_template_en = models.CharField(max_length=255, blank=True) body_template = models.TextField() body_template_en = models.TextField(blank=True) is_active = models.BooleanField(default=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['key'] def __str__(self) -> str: return self.get_key_display() def translated_subject_template(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.subject_template_en.strip(): return self.subject_template_en.strip() return self.subject_template.strip() def translated_body_template(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.body_template_en.strip(): return self.body_template_en.strip() return self.body_template.strip() class NotificationRule(models.Model): EVENT_CHOICES = [ ('onboarding', _('Onboarding')), ('offboarding', _('Offboarding')), ] OPERATOR_CHOICES = [ ('always', _('Immer')), ('contains', _('Enthält')), ('equals', _('Ist gleich')), ('is_true', _('Ist aktiv/Ja')), ('is_false', _('Ist inaktiv/Nein')), ] name = models.CharField(max_length=120) is_active = models.BooleanField(default=True) event_type = models.CharField(max_length=20, choices=EVENT_CHOICES) field_name = models.CharField(max_length=80, blank=True) operator = models.CharField(max_length=20, choices=OPERATOR_CHOICES, default='always') expected_value = models.CharField(max_length=255, blank=True) recipients = models.TextField( help_text='Mehrere E-Mail-Adressen mit Komma, Semikolon oder Zeilenumbruch trennen.' ) template_key = models.CharField(max_length=60, blank=True) custom_subject = models.CharField(max_length=255, blank=True) custom_subject_en = models.CharField(max_length=255, blank=True) custom_body = models.TextField(blank=True) custom_body_en = models.TextField(blank=True) include_pdf_attachment = models.BooleanField(default=False) sort_order = models.PositiveIntegerField(default=0) class Meta: ordering = ['event_type', 'sort_order', 'id'] def __str__(self) -> str: state = 'aktiv' if self.is_active else 'inaktiv' return f'{self.get_event_type_display()} | {self.name} ({state})' def translated_custom_subject(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.custom_subject_en.strip(): return self.custom_subject_en.strip() return self.custom_subject.strip() def translated_custom_body(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.custom_body_en.strip(): return self.custom_body_en.strip() return self.custom_body.strip() class ScheduledWelcomeEmail(models.Model): STATUS_CHOICES = [ ('scheduled', _('Geplant')), ('paused', _('Pausiert')), ('cancelled', _('Abgebrochen')), ('sent', _('Gesendet')), ('failed', _('Fehlgeschlagen')), ] onboarding_request = models.OneToOneField(OnboardingRequest, on_delete=models.CASCADE) recipient_email = models.EmailField() send_at = models.DateTimeField() status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='scheduled') celery_task_id = models.CharField(max_length=100, blank=True) sent_at = models.DateTimeField(null=True, blank=True) last_error = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-send_at', '-id'] def __str__(self) -> str: return f'Welcome #{self.id} | {self.recipient_email} | {self.status}' class IntroChecklistItem(models.Model): SECTION_CHOICES = [ ('workplace', _('Geräte und Arbeitsplatz')), ('accounts', _('Konten und Berechtigungen')), ('software', _('Software und Tools')), ('process', _('Prozesse und Hinweise')), ] OPERATOR_CHOICES = [ ('always', _('Immer anzeigen')), ('contains', _('Enthält')), ('equals', _('Ist gleich')), ('is_true', _('Ist Ja / aktiv')), ('is_false', _('Ist Nein / inaktiv')), ] section = models.CharField(max_length=30, choices=SECTION_CHOICES) label = models.CharField(max_length=255) label_en = models.CharField(max_length=255, blank=True) sort_order = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=True) condition_field = models.CharField(max_length=80, blank=True) condition_operator = models.CharField(max_length=20, choices=OPERATOR_CHOICES, default='always') condition_value = models.CharField(max_length=255, blank=True) class Meta: ordering = ['section', 'sort_order', 'label'] def __str__(self) -> str: return f'{self.get_section_display()}: {self.label}' def translated_label(self, language_code: str | None = None) -> str: lang = (language_code or get_language() or 'de').split('-')[0] if lang == 'en' and self.label_en.strip(): return self.label_en.strip() return self.label.strip() class OnboardingIntroductionSession(models.Model): STATUS_CHOICES = [ ('draft', _('Entwurf')), ('completed', _('Abgeschlossen')), ] onboarding_request = models.OneToOneField(OnboardingRequest, on_delete=models.CASCADE) checklist_state = models.JSONField(default=dict, blank=True) notes = models.TextField(blank=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft') completed_at = models.DateTimeField(null=True, blank=True) completed_by_name = models.CharField(max_length=255, blank=True) exported_pdf_path = models.CharField(max_length=500, blank=True) updated_at = models.DateTimeField(auto_now=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: return f'Einweisung #{self.id} | {self.onboarding_request.full_name} | {self.status}' class WorkflowConfig(models.Model): REMOTE_BACKUP_TARGET_CHOICES = [ ('nextcloud', _('Nextcloud')), ('s3', _('S3')), ('nfs', _('NFS')), ] name = models.CharField(max_length=120, default='Default', unique=True) it_onboarding_email = models.EmailField(blank=True) general_info_email = models.EmailField(blank=True) business_card_email = models.EmailField(blank=True) hr_works_email = models.EmailField(blank=True) key_notification_email = models.EmailField(blank=True) nextcloud_enabled_override = models.BooleanField( null=True, blank=True, default=None, verbose_name='Nextcloud Upload aktiviert (Override)', help_text='Leer = ENV-Wert nutzen, Ja = erzwingen aktiv, Nein = erzwingen inaktiv', ) email_test_mode_override = models.BooleanField( null=True, blank=True, default=None, verbose_name='E-Mail Testmodus aktiv (Override)', help_text='Leer = ENV-Wert nutzen, Ja = Testmodus erzwingen, Nein = Produktionsmodus erzwingen', ) nextcloud_base_url_override = models.CharField(max_length=500, blank=True, verbose_name='Nextcloud Base URL (Override)') nextcloud_username_override = models.CharField(max_length=255, blank=True, verbose_name='Nextcloud Benutzername (Override)') nextcloud_password_override = models.CharField(max_length=255, blank=True, verbose_name='Nextcloud Passwort (Override)') nextcloud_directory_override = models.CharField(max_length=255, blank=True, verbose_name='Nextcloud Verzeichnis (Override)') sync_interval_seconds = models.PositiveIntegerField(default=60, verbose_name='Sync-Intervall (Sekunden)') device_handover_lead_days = models.PositiveIntegerField(default=5, verbose_name='Vorlauf Geräteübergabe (Tage)') remote_backup_enabled = models.BooleanField(default=False, verbose_name='Remote Backup aktiviert') remote_backup_target_type = models.CharField( max_length=20, choices=REMOTE_BACKUP_TARGET_CHOICES, default='nextcloud', verbose_name='Remote Backup Zieltyp', ) remote_backup_nextcloud_directory = models.CharField(max_length=255, blank=True, verbose_name='Nextcloud Backup-Verzeichnis') remote_backup_s3_bucket = models.CharField(max_length=255, blank=True, verbose_name='S3 Bucket (optional)') remote_backup_nfs_path = models.CharField(max_length=255, blank=True, verbose_name='NFS Pfad (optional)') welcome_email_delay_days = models.PositiveIntegerField(default=5, verbose_name='Welcome E-Mail Verzögerung (Tage)') welcome_sender_email = models.EmailField(blank=True, verbose_name='Welcome E-Mail Absender') welcome_include_pdf = models.BooleanField(default=True, verbose_name='Welcome E-Mail mit PDF-Anhang') imap_server = models.CharField(max_length=255, blank=True, verbose_name='IMAP Server') mailbox = models.CharField(max_length=120, blank=True, default='INBOX', verbose_name='Mailbox') smtp_server = models.CharField(max_length=255, blank=True, verbose_name='SMTP Server') smtp_port = models.PositiveIntegerField(default=465, verbose_name='SMTP Port') email_account = models.EmailField(blank=True, verbose_name='E-Mail Konto') email_password = models.CharField(max_length=255, blank=True, verbose_name='E-Mail Passwort') smtp_use_ssl = models.BooleanField(default=True, verbose_name='SMTP SSL nutzen') smtp_use_tls = models.BooleanField(default=False, verbose_name='SMTP TLS nutzen') legal_text = models.TextField( blank=True, default='Eine Ausrüstungsvereinbarung erlaubt es einem Mitarbeitenden, die Ausrüstung des Unternehmens im Außendienst oder zu Hause zu nutzen und mitzunehmen.', ) def __str__(self) -> str: return self.name class SystemEmailConfig(models.Model): name = models.CharField(max_length=120, default='Default SMTP', unique=True) is_active = models.BooleanField(default=False) host = models.CharField(max_length=255, blank=True) port = models.PositiveIntegerField(default=587) username = models.CharField(max_length=255, blank=True) password = models.CharField(max_length=255, blank=True) use_tls = models.BooleanField(default=True) use_ssl = models.BooleanField(default=False) from_email = models.EmailField(blank=True) updated_at = models.DateTimeField(auto_now=True) class Meta: verbose_name = 'System SMTP Konfiguration' verbose_name_plural = 'System SMTP Konfigurationen' def __str__(self) -> str: state = 'aktiv' if self.is_active else 'inaktiv' return f'{self.name} ({state})' class OffboardingRequest(models.Model): STATUS_CHOICES = OnboardingRequest.STATUS_CHOICES employee_profile = models.ForeignKey(EmployeeProfile, null=True, blank=True, on_delete=models.SET_NULL) full_name = models.CharField(max_length=255, verbose_name='Vorname und Nachname') work_email = models.EmailField(verbose_name='Dienstliche E-Mail-Adresse') department = models.CharField(max_length=255, blank=True, verbose_name='Abteilung') job_title = models.CharField(max_length=255, blank=True, verbose_name='Berufsbezeichnung') last_working_day = models.DateField(verbose_name='Letzter Arbeitstag') offboarding_reason = models.TextField(blank=True, verbose_name='Grund') notes = models.TextField(blank=True, verbose_name='Notizen') signature = models.CharField(max_length=255, blank=True, verbose_name='Unterschrift (Name)') requested_by_email = models.EmailField(verbose_name='E-Mail der anfordernden Person') requested_by_name = models.CharField(max_length=255, blank=True, verbose_name='Name der anfordernden Person') preferred_language = models.CharField(max_length=10, blank=True, default='de', db_default='de') generated_pdf_path = models.CharField(max_length=500, blank=True) processing_status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='submitted') last_error = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: return f"Offboarding #{self.id} - {self.full_name}" def save(self, *args, **kwargs): self.preferred_language = _normalized_language_code(self.preferred_language) super().save(*args, **kwargs)