Files
workdock-platform/backend/workflows/models.py
2026-03-24 13:06:39 +01:00

444 lines
21 KiB
Python

from django.db import models
from django.utils.translation import get_language
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 OnboardingRequest(models.Model):
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)
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):
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)')
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):
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)
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)