snapshot: preserve dashboard redesign and live protocol workflow state
This commit is contained in:
@@ -25,11 +25,14 @@ from .form_builder import (
|
||||
ONBOARDING_PAGE_ORDER,
|
||||
ensure_form_field_configs,
|
||||
)
|
||||
from .models import EmployeeProfile, FormFieldConfig, FormOption, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingRequest, ScheduledWelcomeEmail, WorkflowConfig
|
||||
from .models import EmployeeProfile, FormFieldConfig, FormOption, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, ScheduledWelcomeEmail, WorkflowConfig
|
||||
from .emailing import send_system_email
|
||||
from .services import get_email_test_redirect, is_email_test_mode, is_nextcloud_enabled, upload_to_nextcloud
|
||||
from .tasks import (
|
||||
DEFAULT_NOTIFICATION_TEMPLATES,
|
||||
_generate_onboarding_intro_pdf,
|
||||
_generate_onboarding_intro_session_pdf,
|
||||
build_intro_sections_for_request,
|
||||
process_offboarding_request,
|
||||
process_onboarding_request,
|
||||
send_scheduled_welcome_email,
|
||||
@@ -272,6 +275,9 @@ def requests_dashboard(request):
|
||||
|
||||
rows = []
|
||||
for obj in onboarding_items:
|
||||
intro_session = OnboardingIntroductionSession.objects.filter(onboarding_request=obj).first()
|
||||
if intro_session and intro_session.exported_pdf_path:
|
||||
intro_session.exported_pdf_url = f"/media/pdfs/{Path(intro_session.exported_pdf_path).name}"
|
||||
rows.append(
|
||||
{
|
||||
'id': obj.id,
|
||||
@@ -281,6 +287,8 @@ def requests_dashboard(request):
|
||||
'work_email': obj.work_email,
|
||||
'created_at': obj.created_at,
|
||||
'pdf_url': f"/media/pdfs/{Path(obj.generated_pdf_path).name}" if obj.generated_pdf_path else None,
|
||||
'intro_pdf_url': f"/media/pdfs/{Path(obj.intro_pdf_path).name}" if obj.intro_pdf_path else None,
|
||||
'intro_session': intro_session,
|
||||
'status': 'PDF erstellt' if obj.generated_pdf_path else 'In Bearbeitung',
|
||||
}
|
||||
)
|
||||
@@ -294,6 +302,8 @@ def requests_dashboard(request):
|
||||
'work_email': obj.work_email,
|
||||
'created_at': obj.created_at,
|
||||
'pdf_url': f"/media/pdfs/{Path(obj.generated_pdf_path).name}" if obj.generated_pdf_path else None,
|
||||
'intro_pdf_url': None,
|
||||
'intro_session': None,
|
||||
'status': 'PDF erstellt' if obj.generated_pdf_path else 'In Bearbeitung',
|
||||
}
|
||||
)
|
||||
@@ -400,6 +410,101 @@ def onboarding_success(request, request_id: int):
|
||||
return render(request, 'workflows/onboarding_success.html', {'obj': obj, 'pdf_url': pdf_url})
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(_is_staff)
|
||||
@require_POST
|
||||
def generate_onboarding_intro_pdf(request, request_id: int):
|
||||
obj = get_object_or_404(OnboardingRequest, id=request_id)
|
||||
pdf_path = _generate_onboarding_intro_pdf(obj)
|
||||
obj.intro_pdf_path = str(pdf_path)
|
||||
obj.save(update_fields=['intro_pdf_path'])
|
||||
messages.success(request, 'Einweisungs- und Übergabeprotokoll wurde erzeugt.')
|
||||
return redirect('requests_dashboard')
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(_is_staff)
|
||||
@require_POST
|
||||
def generate_onboarding_intro_session_pdf(request, request_id: int):
|
||||
onboarding = get_object_or_404(OnboardingRequest, id=request_id)
|
||||
session, _created = OnboardingIntroductionSession.objects.get_or_create(onboarding_request=onboarding)
|
||||
pdf_path = _generate_onboarding_intro_session_pdf(session, admin_signature_name=_display_user_name(request.user))
|
||||
session.exported_pdf_path = str(pdf_path)
|
||||
session.save(update_fields=['exported_pdf_path'])
|
||||
messages.success(request, 'Einweisungsprotokoll aus Live-Status wurde erzeugt.')
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(_is_staff)
|
||||
def onboarding_intro_session_page(request, request_id: int):
|
||||
onboarding = get_object_or_404(OnboardingRequest, id=request_id)
|
||||
session, _created = OnboardingIntroductionSession.objects.get_or_create(onboarding_request=onboarding)
|
||||
sections = build_intro_sections_for_request(onboarding)
|
||||
|
||||
if request.method == 'POST':
|
||||
checked_ids = set(request.POST.getlist('checked_items'))
|
||||
checklist_state = {}
|
||||
for section in sections:
|
||||
for item in section['items']:
|
||||
checklist_state[item['id']] = item['id'] in checked_ids
|
||||
|
||||
action = (request.POST.get('session_action') or 'save').strip()
|
||||
session.checklist_state = checklist_state
|
||||
session.notes = (request.POST.get('notes') or '').strip()
|
||||
if action == 'reset':
|
||||
session.checklist_state = {}
|
||||
session.notes = ''
|
||||
session.status = 'draft'
|
||||
session.completed_at = None
|
||||
session.completed_by_name = ''
|
||||
session.exported_pdf_path = ''
|
||||
session.save(update_fields=['checklist_state', 'notes', 'status', 'completed_at', 'completed_by_name', 'exported_pdf_path'])
|
||||
messages.success(request, 'Einweisung wurde zurückgesetzt.')
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
if action == 'complete':
|
||||
session.status = 'completed'
|
||||
session.completed_at = timezone.now()
|
||||
session.completed_by_name = _display_user_name(request.user)
|
||||
messages.success(request, 'Einweisung wurde als abgeschlossen gespeichert.')
|
||||
else:
|
||||
session.status = 'draft'
|
||||
session.completed_at = None
|
||||
session.completed_by_name = ''
|
||||
messages.success(request, 'Einweisung wurde als Entwurf gespeichert.')
|
||||
session.save()
|
||||
return redirect('onboarding_intro_session_page', request_id=request_id)
|
||||
|
||||
checked_map = session.checklist_state or {}
|
||||
checked_count = 0
|
||||
total_count = 0
|
||||
for section in sections:
|
||||
for item in section['items']:
|
||||
item['checked'] = bool(checked_map.get(item['id']))
|
||||
total_count += 1
|
||||
if item['checked']:
|
||||
checked_count += 1
|
||||
|
||||
salutation = (onboarding.get_gender_display() or '').strip()
|
||||
display_name = f"{salutation} {onboarding.full_name}".strip() if salutation else onboarding.full_name
|
||||
progress_percent = int((checked_count / total_count) * 100) if total_count else 0
|
||||
|
||||
return render(
|
||||
request,
|
||||
'workflows/onboarding_intro_session.html',
|
||||
{
|
||||
'onboarding': onboarding,
|
||||
'session': session,
|
||||
'display_name': display_name,
|
||||
'sections': sections,
|
||||
'checked_count': checked_count,
|
||||
'total_count': total_count,
|
||||
'progress_percent': progress_percent,
|
||||
'session_pdf_url': f"/media/pdfs/{Path(session.exported_pdf_path).name}" if session.exported_pdf_path else None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def offboarding_create(request):
|
||||
@@ -608,6 +713,108 @@ def form_builder_page(request):
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(_is_staff)
|
||||
def intro_builder_page(request):
|
||||
if request.method == 'POST':
|
||||
delete_id = (request.POST.get('delete_item_id') or '').strip()
|
||||
if delete_id:
|
||||
item = IntroChecklistItem.objects.filter(id=delete_id).first()
|
||||
if item:
|
||||
item.delete()
|
||||
messages.success(request, 'Checklistenpunkt wurde gelöscht.')
|
||||
else:
|
||||
messages.error(request, 'Checklistenpunkt nicht gefunden.')
|
||||
return redirect('intro_builder_page')
|
||||
|
||||
action = (request.POST.get('builder_action') or '').strip()
|
||||
if action == 'add_item':
|
||||
section = (request.POST.get('section') or '').strip()
|
||||
label = (request.POST.get('label') or '').strip()
|
||||
if section not in {k for k, _ in IntroChecklistItem.SECTION_CHOICES}:
|
||||
messages.error(request, 'Ungültiger Abschnitt.')
|
||||
return redirect('intro_builder_page')
|
||||
if not label:
|
||||
messages.error(request, 'Bitte eine Bezeichnung für den Checklistenpunkt angeben.')
|
||||
return redirect('intro_builder_page')
|
||||
next_sort = (
|
||||
IntroChecklistItem.objects.filter(section=section).order_by('-sort_order').values_list('sort_order', flat=True).first()
|
||||
)
|
||||
IntroChecklistItem.objects.create(
|
||||
section=section,
|
||||
label=label,
|
||||
sort_order=(next_sort + 1) if next_sort is not None else 0,
|
||||
is_active=True,
|
||||
condition_operator='always',
|
||||
)
|
||||
messages.success(request, 'Checklistenpunkt wurde hinzugefügt.')
|
||||
return redirect('intro_builder_page')
|
||||
|
||||
if action == 'save_items':
|
||||
item_ids = request.POST.getlist('item_ids')
|
||||
valid_sections = {k for k, _ in IntroChecklistItem.SECTION_CHOICES}
|
||||
valid_ops = {k for k, _ in IntroChecklistItem.OPERATOR_CHOICES}
|
||||
for pos, raw_id in enumerate(item_ids):
|
||||
item = IntroChecklistItem.objects.filter(id=raw_id).first()
|
||||
if not item:
|
||||
continue
|
||||
section = (request.POST.get(f'section_{item.id}') or item.section).strip()
|
||||
if section not in valid_sections:
|
||||
section = item.section
|
||||
operator = (request.POST.get(f'operator_{item.id}') or item.condition_operator).strip()
|
||||
if operator not in valid_ops:
|
||||
operator = 'always'
|
||||
item.section = section
|
||||
item.label = (request.POST.get(f'label_{item.id}') or item.label).strip() or item.label
|
||||
item.is_active = request.POST.get(f'active_{item.id}') == 'on'
|
||||
item.condition_field = (request.POST.get(f'field_{item.id}') or '').strip()
|
||||
item.condition_operator = operator
|
||||
item.condition_value = (request.POST.get(f'value_{item.id}') or '').strip()
|
||||
item.sort_order = pos
|
||||
item.save(
|
||||
update_fields=[
|
||||
'section',
|
||||
'label',
|
||||
'is_active',
|
||||
'condition_field',
|
||||
'condition_operator',
|
||||
'condition_value',
|
||||
'sort_order',
|
||||
]
|
||||
)
|
||||
messages.success(request, 'Einweisungs-Checkliste wurde gespeichert.')
|
||||
return redirect('intro_builder_page')
|
||||
|
||||
condition_field_choices = [
|
||||
('', 'Keine Bedingung'),
|
||||
('needed_devices', 'Benötigte Geräte und Gegenstände'),
|
||||
('needed_software', 'Benötigte Software'),
|
||||
('needed_accesses', 'Benötigte Zugänge'),
|
||||
('needed_workspace_groups', 'Benötigte Gruppen im Workspace'),
|
||||
('needed_resources', 'Benötigte Ressourcen'),
|
||||
('additional_hardware', 'Zusätzliche Hardware'),
|
||||
('additional_software', 'Zusätzliche Software'),
|
||||
('additional_access_text', 'Weitere Zugänge (Freitext)'),
|
||||
('group_mailboxes_required', 'Gruppenpostfächer erforderlich'),
|
||||
('order_business_cards', 'Visitenkarten bestellt'),
|
||||
('phone_number', 'Direktwahl vorhanden'),
|
||||
('successor_name', 'Nachfolge vorhanden'),
|
||||
('department', 'Abteilung'),
|
||||
]
|
||||
|
||||
items = list(IntroChecklistItem.objects.all().order_by('section', 'sort_order', 'label'))
|
||||
return render(
|
||||
request,
|
||||
'workflows/intro_builder.html',
|
||||
{
|
||||
'items': items,
|
||||
'section_choices': IntroChecklistItem.SECTION_CHOICES,
|
||||
'operator_choices': IntroChecklistItem.OPERATOR_CHOICES,
|
||||
'condition_field_choices': condition_field_choices,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(_is_staff)
|
||||
def integrations_setup_page(request):
|
||||
|
||||
Reference in New Issue
Block a user