snapshot: preserve bilingual email template phase

This commit is contained in:
Md Bayazid Bostame
2026-03-24 12:37:35 +01:00
parent 4d3c7bdf6e
commit ec00ae8b2e
9 changed files with 275 additions and 18 deletions

View File

@@ -42,7 +42,12 @@ Notes:
- Form Builder option labels
- Form Builder field label/help-text overrides
- Intro Builder checklist item labels
- Most email template business text and several generated PDF text blocks are still not fully bilingual yet.
- Admin-configured email templates are now bilingual for:
- notification template subjects and bodies in DE/EN
- notification rule custom subjects and bodies in DE/EN
- welcome email subject and body in DE/EN
- request language capture on onboarding/offboarding to choose the correct email language
- Several generated PDF business text blocks are still not fully bilingual yet.
- CI now validates that translation catalogs compile successfully on push and pull request.
## Current implemented scope

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.1.5 on 2026-03-24 11:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('workflows', '0029_formfieldconfig_help_text_override_en_and_more'),
]
operations = [
migrations.AddField(
model_name='notificationrule',
name='custom_body_en',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='notificationrule',
name='custom_subject_en',
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name='notificationtemplate',
name='body_template_en',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='notificationtemplate',
name='subject_template_en',
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name='offboardingrequest',
name='preferred_language',
field=models.CharField(blank=True, default='de', max_length=10),
),
migrations.AddField(
model_name='onboardingrequest',
name='preferred_language',
field=models.CharField(blank=True, default='de', max_length=10),
),
]

View File

@@ -80,6 +80,7 @@ class OnboardingRequest(models.Model):
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')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
@@ -181,7 +182,9 @@ class NotificationTemplate(models.Model):
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)
@@ -191,6 +194,18 @@ class NotificationTemplate(models.Model):
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 = [
@@ -216,7 +231,9 @@ class NotificationRule(models.Model):
)
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)
@@ -227,6 +244,18 @@ class NotificationRule(models.Model):
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 = [
@@ -393,6 +422,7 @@ class OffboardingRequest(models.Model):
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')
generated_pdf_path = models.CharField(max_length=500, blank=True)
created_at = models.DateTimeField(auto_now_add=True)

View File

@@ -32,6 +32,7 @@ from .forms import (
DEFAULT_NOTIFICATION_TEMPLATES = {
'onboarding_it': {
'subject': '[Onboarding] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Onboarding] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Neue Onboarding-Anfrage für {{ FULL_NAME }}.\n'
'Abteilung: {{ DEPARTMENT }}\n'
@@ -39,9 +40,17 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Angefordert von: {{ REQUESTED_BY }}\n'
'Bitte IT-Setup vorbereiten.'
),
'body_en': (
'New onboarding request for {{ FULL_NAME }}.\n'
'Department: {{ DEPARTMENT }}\n'
'Contract start: {{ CONTRACT_START }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
'Please prepare the IT setup.'
),
},
'onboarding_general_info': {
'subject': '[Info Onboarding] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Onboarding Info] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Hallo,\n\n'
'{{ FULL_NAME }} wird onboarded.\n'
@@ -49,9 +58,17 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Vertragsbeginn: {{ CONTRACT_START }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'Hello,\n\n'
'{{ FULL_NAME }} is being onboarded.\n'
'Department: {{ DEPARTMENT }}\n'
'Contract start: {{ CONTRACT_START }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'onboarding_business_card': {
'subject': '[Visitenkarte] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Business Card] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Hallo,\n\n'
'bitte Visitenkarten erstellen:\n'
@@ -61,9 +78,19 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Telefon: {{ BUSINESS_CARD_PHONE }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'Hello,\n\n'
'please create business cards:\n'
'Name: {{ BUSINESS_CARD_NAME }}\n'
'Title: {{ BUSINESS_CARD_TITLE }}\n'
'Email: {{ BUSINESS_CARD_EMAIL }}\n'
'Phone: {{ BUSINESS_CARD_PHONE }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'onboarding_hr_works': {
'subject': '[HR Works] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[HR Works] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Hello Stefanie,\n\n'
'Es ist wieder soweit. Zuwachs!\n\n'
@@ -77,9 +104,23 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Vielen Dank und schöne Grüße,\n'
'Die IT.'
),
'body_en': (
'Hello Stefanie,\n\n'
'we have a new team member joining.\n\n'
'Could you please create an HR Works account with the following details:\n\n'
'Name: {{ VORNAME }} {{ NACHNAME }}\n'
'Department: {{ DEPARTMENT }}\n'
'Contract start: {{ CONTRACT_START }}\n'
'Email address: {{ EMAIL }}\n\n'
'{% if PDF_LINK %}You will find the employee PDF here in about 2 minutes: {{ PDF_LINK }}\n\n{% endif %}'
'If you need any other information, please contact it@tub.co.\n\n'
'Thank you and best regards,\n'
'IT'
),
},
'onboarding_key': {
'subject': '[Schlüssel] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Key] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Hallo,\n\n'
'bitte Schlüssel vorbereiten für:\n'
@@ -88,9 +129,18 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Vertragsbeginn: {{ CONTRACT_START }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'Hello,\n\n'
'please prepare keys for:\n'
'Name: {{ FULL_NAME }}\n'
'Department: {{ DEPARTMENT }}\n'
'Contract start: {{ CONTRACT_START }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'onboarding_reference': {
'subject': '[Referenz Onboarding] {{ FULL_NAME }} | Ihre Anfrage',
'subject_en': '[Onboarding Reference] {{ FULL_NAME }} | Your Request',
'body': (
'Diese E-Mail dient als Referenz für Ihre Onboarding-Anfrage.\n'
'Name: {{ FULL_NAME }}\n'
@@ -98,9 +148,17 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Vertragsbeginn: {{ CONTRACT_START }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'This email is your reference copy for the onboarding request.\n'
'Name: {{ FULL_NAME }}\n'
'Department: {{ DEPARTMENT }}\n'
'Contract start: {{ CONTRACT_START }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'onboarding_welcome': {
'subject': 'Willkommen bei TUB/CO, {{ VORNAME }}',
'subject_en': 'Welcome to TUB/CO, {{ VORNAME }}',
'body': (
'Hallo {{ FULL_NAME }},\n\n'
'herzlich willkommen bei TUB/CO.\n'
@@ -111,9 +169,20 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Viele Grüße\n'
'TUB/CO IT'
),
'body_en': (
'Hello {{ FULL_NAME }},\n\n'
'welcome to TUB/CO.\n'
'We are very happy that you will join our {{ DEPARTMENT }} team starting on {{ CONTRACT_START }}.\n\n'
'Your work email address is: {{ EMAIL }}.\n'
'You will find your onboarding documents attached as a PDF.\n\n'
'If you have any questions, feel free to contact us anytime.\n\n'
'Best regards,\n'
'TUB/CO IT'
),
},
'offboarding_it': {
'subject': '[Offboarding] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Offboarding] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Neue Offboarding-Anfrage für {{ FULL_NAME }}.\n'
'Abteilung: {{ DEPARTMENT }}\n'
@@ -121,25 +190,45 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Angefordert von: {{ REQUESTED_BY }}\n'
'Bitte IT-Offboarding durchführen.'
),
'body_en': (
'New offboarding request for {{ FULL_NAME }}.\n'
'Department: {{ DEPARTMENT }}\n'
'Last working day: {{ LAST_WORKING_DAY }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
'Please complete the IT offboarding.'
),
},
'offboarding_general_info': {
'subject': '[Info Offboarding] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[Offboarding Info] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Neue Offboarding-Anfrage für {{ FULL_NAME }}.\n'
'Abteilung: {{ DEPARTMENT }}\n'
'Letzter Arbeitstag: {{ LAST_WORKING_DAY }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'New offboarding request for {{ FULL_NAME }}.\n'
'Department: {{ DEPARTMENT }}\n'
'Last working day: {{ LAST_WORKING_DAY }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'offboarding_hr_works_disable': {
'subject': '[HR Works Deaktivierung] {{ FULL_NAME }} | Anfrage von {{ REQUESTED_BY }}',
'subject_en': '[HR Works Disable] {{ FULL_NAME }} | Requested by {{ REQUESTED_BY }}',
'body': (
'Bitte HR Works Zugriff deaktivieren für {{ FULL_NAME }} ({{ EMAIL }}) zum {{ LAST_WORKING_DAY }}.\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'Please disable HR Works access for {{ FULL_NAME }} ({{ EMAIL }}) effective {{ LAST_WORKING_DAY }}.\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
'offboarding_reference': {
'subject': '[Referenz Offboarding] {{ FULL_NAME }} | Ihre Anfrage',
'subject_en': '[Offboarding Reference] {{ FULL_NAME }} | Your Request',
'body': (
'Diese E-Mail dient als Referenz für Ihre Offboarding-Anfrage.\n'
'Name: {{ FULL_NAME }}\n'
@@ -147,6 +236,13 @@ DEFAULT_NOTIFICATION_TEMPLATES = {
'Letzter Arbeitstag: {{ LAST_WORKING_DAY }}\n'
'Angefordert von: {{ REQUESTED_BY }}\n'
),
'body_en': (
'This email is your reference copy for the offboarding request.\n'
'Name: {{ FULL_NAME }}\n'
'Department: {{ DEPARTMENT }}\n'
'Last working day: {{ LAST_WORKING_DAY }}\n'
'Requested by: {{ REQUESTED_BY }}\n'
),
},
}
@@ -419,15 +515,16 @@ def _send_workflow_email(
)
def _render_notification_template(template_key: str, context: dict) -> tuple[str, str]:
def _render_notification_template(template_key: str, context: dict, language_code: str | None = None) -> tuple[str, str]:
lang = (language_code or 'de').split('-')[0]
db_template = NotificationTemplate.objects.filter(key=template_key, is_active=True).first()
if db_template:
subject_template = db_template.subject_template
body_template = db_template.body_template
subject_template = db_template.translated_subject_template(lang)
body_template = db_template.translated_body_template(lang)
else:
fallback = DEFAULT_NOTIFICATION_TEMPLATES[template_key]
subject_template = fallback['subject']
body_template = fallback['body']
subject_template = fallback.get(f'subject_{lang}', '') or fallback['subject']
body_template = fallback.get(f'body_{lang}', '') or fallback['body']
subject = Template(subject_template).render(context).strip()
body = Template(body_template).render(context).strip()
@@ -475,6 +572,7 @@ def _apply_notification_rules(
context: dict,
pdf_path: Path | None = None,
) -> None:
language_code = (getattr(request_obj, 'preferred_language', '') or 'de').split('-')[0]
rules = NotificationRule.objects.filter(event_type=event_type, is_active=True).order_by('sort_order', 'id')
for rule in rules:
if not _rule_matches(rule, request_obj):
@@ -494,11 +592,12 @@ def _apply_notification_rules(
context=context,
to=recipients,
attachments=attachments,
language_code=language_code,
)
continue
subject = (rule.custom_subject or '').strip()
body = (rule.custom_body or '').strip()
subject = rule.translated_custom_subject(language_code)
body = rule.translated_custom_body(language_code)
if not subject and not body:
continue
@@ -547,8 +646,9 @@ def _send_templated_email(
context: dict,
attachments: list[Path] | None = None,
from_email: str | None = None,
language_code: str | None = None,
) -> None:
subject, body = _render_notification_template(template_key, context)
subject, body = _render_notification_template(template_key, context, language_code=language_code)
_send_workflow_email(subject=subject, body=body, to=to, attachments=attachments, from_email=from_email)

View File

@@ -126,6 +126,8 @@ docker compose exec -T web django-admin compilemessages</code></pre>
<li>Do not use custom ad hoc <code>.mo</code> compilation anymore.</li>
<li>Phase 1 bilingual support covers fixed UI.</li>
<li>Phase 2 covers builder-driven labels and checklist items with explicit German and English fields.</li>
<li>Email phase covers <code>NotificationTemplate</code>, <code>NotificationRule</code>, and the welcome-email settings UI with explicit DE/EN subject/body fields.</li>
<li>Request records now persist <code>preferred_language</code> so workflow emails can render in the submitter's active UI language with German fallback.</li>
</ul>
<h2 id="pdf">7) PDF Pipeline</h2>
@@ -150,6 +152,8 @@ docker compose exec -T web django-admin compilemessages</code></pre>
<ul>
<li>Notification defaults are defined in <code>DEFAULT_NOTIFICATION_TEMPLATES</code> in <code>tasks.py</code>.</li>
<li>Admin-configured overrides live in <code>NotificationTemplate</code> and <code>NotificationRule</code>.</li>
<li>Both models now support explicit DE/EN content fields. Prefer filling both languages in the frontend integrations UI instead of embedding mixed-language copy into a single template.</li>
<li>Welcome-email configuration under Admin Apps has the same DE/EN split and follows the same runtime language selection.</li>
<li>Mail sending uses Celery tasks and supports test mode redirection.</li>
<li>MailHog is the local verification path when test mode is active.</li>
</ul>

View File

@@ -195,13 +195,21 @@
<p class="template-title">{{ tpl.get_key_display }} ({{ tpl.key }})</p>
<div class="grid">
<div>
<label for="subject_{{ tpl.key }}">Subject</label>
<label for="subject_{{ tpl.key }}">Subject (DE)</label>
<input id="subject_{{ tpl.key }}" name="subject_{{ tpl.key }}" value="{{ tpl.subject_template }}" />
</div>
<div>
<label for="body_{{ tpl.key }}">Body</label>
<label for="body_{{ tpl.key }}">Body (DE)</label>
<textarea id="body_{{ tpl.key }}" name="body_{{ tpl.key }}">{{ tpl.body_template }}</textarea>
</div>
<div>
<label for="subject_en_{{ tpl.key }}">Subject (EN)</label>
<input id="subject_en_{{ tpl.key }}" name="subject_en_{{ tpl.key }}" value="{{ tpl.subject_template_en }}" />
</div>
<div>
<label for="body_en_{{ tpl.key }}">Body (EN)</label>
<textarea id="body_en_{{ tpl.key }}" name="body_en_{{ tpl.key }}">{{ tpl.body_template_en }}</textarea>
</div>
</div>
</div>
{% endfor %}
@@ -269,6 +277,14 @@
<label for="custom_body_{{ rule.id }}">Custom Body (optional)</label>
<textarea id="custom_body_{{ rule.id }}" name="custom_body_{{ rule.id }}">{{ rule.custom_body }}</textarea>
</div>
<div>
<label for="custom_subject_en_{{ rule.id }}">Custom Subject (EN, optional)</label>
<input id="custom_subject_en_{{ rule.id }}" name="custom_subject_en_{{ rule.id }}" value="{{ rule.custom_subject_en }}" />
</div>
<div>
<label for="custom_body_en_{{ rule.id }}">Custom Body (EN, optional)</label>
<textarea id="custom_body_en_{{ rule.id }}" name="custom_body_en_{{ rule.id }}">{{ rule.custom_body_en }}</textarea>
</div>
</div>
<div class="check-row">
<label><input type="checkbox" name="active_{{ rule.id }}" {% if rule.is_active %}checked{% endif %} /> Aktiv</label>
@@ -332,6 +348,14 @@
<label for="new_custom_body">Custom Body (optional)</label>
<textarea id="new_custom_body" name="new_custom_body"></textarea>
</div>
<div>
<label for="new_custom_subject_en">Custom Subject (EN, optional)</label>
<input id="new_custom_subject_en" name="new_custom_subject_en" />
</div>
<div>
<label for="new_custom_body_en">Custom Body (EN, optional)</label>
<textarea id="new_custom_body_en" name="new_custom_body_en"></textarea>
</div>
</div>
<div class="check-row">
<label><input type="checkbox" name="new_include_pdf" /> PDF anhängen</label>

View File

@@ -166,8 +166,10 @@
<li><strong>Current scope:</strong> the core user interface supports German and English switching for the main fixed UI pages.</li>
<li><strong>Covered now:</strong> login, home, requests dashboard, onboarding form shell, offboarding form shell, and common status/messages in views.</li>
<li><strong>Phase 2 added:</strong> dynamic Form Builder option labels, field label/help-text overrides, and intro-builder checklist item labels now support German and English values.</li>
<li><strong>Email phase added:</strong> admin-managed notification template subject/body content, notification rule custom subject/body content, and welcome email subject/body content now support explicit DE/EN values.</li>
<li><strong>Runtime selection:</strong> onboarding and offboarding requests store the UI language at submission time, and notification delivery uses that language with German fallback.</li>
<li><strong>Editing path:</strong> these DE/EN values are maintained directly in the frontend builder pages, not only in Django admin.</li>
<li><strong>Not fully bilingual yet:</strong> admin-configured email templates and several generated PDF/business text blocks still remain primarily single-language.</li>
<li><strong>Not fully bilingual yet:</strong> several generated PDF/business text blocks still remain primarily single-language.</li>
<li><strong>Implementation:</strong> Django i18n with locale middleware, translation catalogs, and a DE/EN language switch in the main UI.</li>
</ul>

View File

@@ -68,13 +68,21 @@
<input id="welcome_sender_email" name="welcome_sender_email" type="email" value="{{ workflow_config.welcome_sender_email }}" placeholder="Leer = System-Absender" />
</div>
<div>
<label for="welcome_subject">Welcome Subject</label>
<label for="welcome_subject">Welcome Subject (DE)</label>
<input id="welcome_subject" name="welcome_subject" value="{{ welcome_subject_value }}" />
</div>
<div>
<label for="welcome_body">Welcome Text</label>
<label for="welcome_body">Welcome Text (DE)</label>
<textarea id="welcome_body" name="welcome_body">{{ welcome_body_value }}</textarea>
</div>
<div>
<label for="welcome_subject_en">Welcome Subject (EN)</label>
<input id="welcome_subject_en" name="welcome_subject_en" value="{{ welcome_subject_value_en }}" />
</div>
<div>
<label for="welcome_body_en">Welcome Text (EN)</label>
<textarea id="welcome_body_en" name="welcome_body_en">{{ welcome_body_value_en }}</textarea>
</div>
</div>
<div class="check-row">
<label><input type="checkbox" name="welcome_include_pdf" {% if workflow_config.welcome_include_pdf %}checked{% endif %} /> Onboarding-PDF anhängen</label>

View File

@@ -393,7 +393,8 @@ def onboarding_create(request):
if form.is_valid():
obj = form.save()
obj.onboarded_by_name = _display_user_name(request.user)
obj.save(update_fields=['onboarded_by_name'])
obj.preferred_language = (get_language() or 'de').split('-')[0]
obj.save(update_fields=['onboarded_by_name', 'preferred_language'])
process_onboarding_request.delay(obj.id)
return redirect(f"/onboarding/new/?saved=1&id={obj.id}")
else:
@@ -565,6 +566,7 @@ def offboarding_create(request):
else:
obj.requested_by_email = settings.DEFAULT_FROM_EMAIL
obj.requested_by_name = _display_user_name(request.user)
obj.preferred_language = (get_language() or 'de').split('-')[0]
obj.save()
process_offboarding_request.delay(obj.id)
return redirect(f"/offboarding/new/?saved=1&id={obj.id}")
@@ -895,8 +897,12 @@ def welcome_emails_page(request):
default_welcome = DEFAULT_NOTIFICATION_TEMPLATES.get('onboarding_welcome', {})
default_subject = (default_welcome.get('subject') or 'Willkommen bei TUB/CO, {{ FULL_NAME }}').strip()
default_body = (default_welcome.get('body') or 'Hallo {{ FULL_NAME }}, willkommen bei TUB/CO.').strip()
default_subject_en = (default_welcome.get('subject_en') or 'Welcome to TUB/CO, {{ FULL_NAME }}').strip()
default_body_en = (default_welcome.get('body_en') or 'Hello {{ FULL_NAME }}, welcome to TUB/CO.').strip()
subject_value = (welcome_template.subject_template if welcome_template else '').strip() or default_subject
body_value = (welcome_template.body_template if welcome_template else '').strip() or default_body
subject_value_en = (welcome_template.subject_template_en if welcome_template else '').strip() or default_subject_en
body_value_en = (welcome_template.body_template_en if welcome_template else '').strip() or default_body_en
return render(
request,
'workflows/welcome_emails.html',
@@ -906,6 +912,8 @@ def welcome_emails_page(request):
'welcome_template': welcome_template,
'welcome_subject_value': subject_value,
'welcome_body_value': body_value,
'welcome_subject_value_en': subject_value_en,
'welcome_body_value_en': body_value_en,
'welcome_keywords': ['{{ FULL_NAME }}', '{{ VORNAME }}', '{{ NACHNAME }}', '{{ DEPARTMENT }}', '{{ CONTRACT_START }}', '{{ EMAIL }}', '{{ REQUESTED_BY }}'],
},
)
@@ -950,17 +958,25 @@ def save_welcome_email_settings(request):
subject = request.POST.get('welcome_subject')
body = request.POST.get('welcome_body')
if subject is not None or body is not None:
subject_en = request.POST.get('welcome_subject_en')
body_en = request.POST.get('welcome_body_en')
if subject is not None or body is not None or subject_en is not None or body_en is not None:
default_welcome = DEFAULT_NOTIFICATION_TEMPLATES.get('onboarding_welcome', {})
default_subject = (default_welcome.get('subject') or 'Willkommen bei TUB/CO, {{ FULL_NAME }}').strip()
default_body = (default_welcome.get('body') or 'Hallo {{ FULL_NAME }}, willkommen bei TUB/CO.').strip()
default_subject_en = (default_welcome.get('subject_en') or 'Welcome to TUB/CO, {{ FULL_NAME }}').strip()
default_body_en = (default_welcome.get('body_en') or 'Hello {{ FULL_NAME }}, welcome to TUB/CO.').strip()
subject_clean = (subject or '').strip() or default_subject
body_clean = (body or '').strip() or default_body
subject_clean_en = (subject_en or '').strip() or default_subject_en
body_clean_en = (body_en or '').strip() or default_body_en
template, _ = NotificationTemplate.objects.get_or_create(
key='onboarding_welcome',
defaults={
'subject_template': subject_clean,
'body_template': body_clean,
'subject_template_en': subject_clean_en,
'body_template_en': body_clean_en,
'is_active': True,
},
)
@@ -971,6 +987,12 @@ def save_welcome_email_settings(request):
if template.body_template != body_clean:
template.body_template = body_clean
changes.append('body_template')
if template.subject_template_en != subject_clean_en:
template.subject_template_en = subject_clean_en
changes.append('subject_template_en')
if template.body_template_en != body_clean_en:
template.body_template_en = body_clean_en
changes.append('body_template_en')
if not template.is_active:
template.is_active = True
changes.append('is_active')
@@ -1392,17 +1414,23 @@ def save_email_routing_settings(request):
for key in known_keys:
subject = request.POST.get(f'subject_{key}')
body = request.POST.get(f'body_{key}')
if subject is None and body is None:
subject_en = request.POST.get(f'subject_en_{key}')
body_en = request.POST.get(f'body_en_{key}')
if subject is None and body is None and subject_en is None and body_en is None:
continue
subject = (subject or '').strip()
body = (body or '').strip()
if not subject and not body:
subject_en = (subject_en or '').strip()
body_en = (body_en or '').strip()
if not subject and not body and not subject_en and not body_en:
continue
obj, _ = NotificationTemplate.objects.get_or_create(
key=key,
defaults={
'subject_template': subject or f'[{key}]',
'body_template': body or '-',
'subject_template_en': subject_en,
'body_template_en': body_en,
'is_active': True,
},
)
@@ -1413,6 +1441,15 @@ def save_email_routing_settings(request):
if body and obj.body_template != body:
obj.body_template = body
changed.append('body_template')
if obj.subject_template_en != subject_en:
obj.subject_template_en = subject_en
changed.append('subject_template_en')
if obj.body_template_en != body_en:
obj.body_template_en = body_en
changed.append('body_template_en')
if not obj.is_active:
obj.is_active = True
changed.append('is_active')
if changed:
obj.save(update_fields=changed)
@@ -1446,6 +1483,8 @@ def save_notification_rules(request):
rule.template_key = request.POST.get(f'template_key_{rule.id}', '').strip()
rule.custom_subject = request.POST.get(f'custom_subject_{rule.id}', '').strip()
rule.custom_body = request.POST.get(f'custom_body_{rule.id}', '').strip()
rule.custom_subject_en = request.POST.get(f'custom_subject_en_{rule.id}', '').strip()
rule.custom_body_en = request.POST.get(f'custom_body_en_{rule.id}', '').strip()
rule.include_pdf_attachment = request.POST.get(f'include_pdf_{rule.id}') == 'on'
rule.is_active = request.POST.get(f'active_{rule.id}') == 'on'
rule.sort_order = position
@@ -1470,6 +1509,8 @@ def save_notification_rules(request):
template_key=request.POST.get('new_template_key', '').strip(),
custom_subject=request.POST.get('new_custom_subject', '').strip(),
custom_body=request.POST.get('new_custom_body', '').strip(),
custom_subject_en=request.POST.get('new_custom_subject_en', '').strip(),
custom_body_en=request.POST.get('new_custom_body_en', '').strip(),
include_pdf_attachment=request.POST.get('new_include_pdf') == 'on',
is_active=True,
sort_order=NotificationRule.objects.filter(event_type=new_event).count() + 1,