snapshot: preserve configurable onboarding conditional logic

This commit is contained in:
Md Bayazid Bostame
2026-03-27 12:54:47 +01:00
parent eb0fb811e4
commit 2e5e941d41
9 changed files with 431 additions and 41 deletions

View File

@@ -37,6 +37,7 @@ from .branding import get_branding_email_copy, get_company_email_domain, get_def
from .forms import AccountAvatarForm, AccountDetailsForm, AccountNotificationPreferencesForm, AccountTOTPDisableForm, AccountTOTPEnableForm, AccountTOTPRegenerateRecoveryCodesForm, AppLoginForm, AppTOTPChallengeForm, OffboardingRequestForm, OnboardingRequestForm, PortalBrandingForm, PortalCompanyConfigForm, PortalTrialConfigForm, UserManagementCreateForm
from .form_builder import (
DEFAULT_FIELD_ORDER,
DEFAULT_CONDITIONAL_RULES,
FORM_PRESETS,
LOCKED_FIELD_RULES,
LOCKED_SECTION_RULES,
@@ -46,13 +47,14 @@ from .form_builder import (
ONBOARDING_PAGE_LABELS,
ONBOARDING_PAGE_ORDER,
ensure_form_field_configs,
ensure_form_conditional_rule_configs,
ensure_form_section_configs,
get_default_page_map,
get_section_labels,
get_section_order,
apply_form_preset,
)
from .models import AdminAuditLog, AsyncTaskLog, EmployeeProfile, FormFieldConfig, FormOption, FormSectionConfig, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, PortalTrialConfig, ScheduledWelcomeEmail, SystemEmailConfig, UserNotification, UserProfile, WorkflowConfig
from .models import AdminAuditLog, AsyncTaskLog, EmployeeProfile, FormConditionalRuleConfig, FormFieldConfig, FormOption, FormSectionConfig, IntroChecklistItem, NotificationRule, NotificationTemplate, OffboardingRequest, OnboardingIntroductionSession, OnboardingRequest, PortalAppConfig, PortalBranding, PortalCompanyConfig, PortalTrialConfig, ScheduledWelcomeEmail, SystemEmailConfig, UserNotification, UserProfile, WorkflowConfig
from .emailing import send_system_email
from .notifications import notify_user
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
@@ -103,15 +105,7 @@ ONBOARDING_GROUPS = {
'phone-box': ['phone_number_choice'],
}
ONBOARDING_HIDDEN_BY_DEFAULT = {
'business-card-box',
'employment-end-box',
'group-mailboxes-box',
'extra-hardware-box',
'extra-software-box',
'extra-access-box',
'successor-box',
}
ONBOARDING_HIDDEN_BY_DEFAULT = set(DEFAULT_CONDITIONAL_RULES.get('onboarding', {}).keys())
ONBOARDING_INLINE_CHECKS = {'order_business_cards', 'agreement_confirm'}
ONBOARDING_CHECKBOX_LISTS = {
@@ -131,6 +125,24 @@ ONBOARDING_SECTION_META = {
'abschluss': {'title': gettext_lazy('Abschluss'), 'subtitle': gettext_lazy('Notizen und Freigabe')},
}
CONDITIONAL_RULE_OPERATOR_CHOICES = [
('checked', _('ist aktiviert')),
('equals', _('ist gleich')),
('not_equals', _('ist nicht gleich')),
]
def _normalized_conditional_rule_payload(form_type: str) -> dict[str, dict]:
configs = ensure_form_conditional_rule_configs(form_type)
payload = {}
for target_key, cfg in configs.items():
if not cfg.is_active:
continue
clauses = [clause for clause in (cfg.clauses or []) if clause.get('field') and clause.get('operator')]
if clauses:
payload[target_key] = {'all': clauses}
return payload
def healthz(request):
db_ok = True
@@ -1825,6 +1837,7 @@ def onboarding_create(request):
if key in LOCKED_SECTION_RULES.get('onboarding', set()) or section_configs.get(key, None) is None or section_configs[key].is_visible
}
onboarding_sections = _build_onboarding_sections(onboarding_blocks, field_pages, visible_section_keys=visible_section_keys)
onboarding_conditional_rules = _normalized_conditional_rule_payload('onboarding')
return render(
request,
@@ -1835,6 +1848,7 @@ def onboarding_create(request):
'onboarding_sections': onboarding_sections,
'onboarding_inline_checks': ONBOARDING_INLINE_CHECKS,
'onboarding_checkbox_lists': ONBOARDING_CHECKBOX_LISTS,
'onboarding_conditional_rules': onboarding_conditional_rules,
'legal_text': legal_text,
'saved': request.GET.get('saved') == '1',
'saved_request_id': request.GET.get('id', ''),
@@ -2179,6 +2193,27 @@ def form_builder_page(request):
_audit(request, 'form_section_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
messages.success(request, 'Abschnittsregeln wurden gespeichert.')
elif action == 'save_conditional_rules' and form_type == 'onboarding':
rule_configs = ensure_form_conditional_rule_configs(form_type)
updated = 0
for target_key, cfg in rule_configs.items():
cfg.is_active = request.POST.get(f'conditional_active_{target_key}') == 'on'
clauses = []
clause_total = 2
for index in range(clause_total):
field_name = (request.POST.get(f'conditional_field_{target_key}_{index}') or '').strip()
operator = (request.POST.get(f'conditional_operator_{target_key}_{index}') or '').strip()
value = (request.POST.get(f'conditional_value_{target_key}_{index}') or '').strip()
if not field_name or not operator:
continue
parsed_value = True if operator == 'checked' else value
clauses.append({'field': field_name, 'operator': operator, 'value': parsed_value})
cfg.clauses = clauses
cfg.save(update_fields=['is_active', 'clauses'])
updated += 1
_audit(request, 'form_conditional_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
messages.success(request, 'Bedingte Logik wurde gespeichert.')
elif action == 'apply_preset':
preset_key = (request.POST.get('preset_key') or '').strip()
if apply_form_preset(form_type, preset_key):
@@ -2194,7 +2229,7 @@ def form_builder_page(request):
active_subpanel = 'options'
elif action == 'save_field_texts':
active_subpanel = 'field-texts'
elif action in {'save_field_rules', 'save_section_rules'}:
elif action in {'save_field_rules', 'save_section_rules', 'save_conditional_rules'}:
active_panel = 'builder-rules'
redirect_target = f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}"
if active_panel:
@@ -2217,6 +2252,7 @@ def form_builder_page(request):
ensure_form_field_configs(form_type, default_names)
section_configs = ensure_form_section_configs(form_type)
conditional_rule_configs = ensure_form_conditional_rule_configs(form_type) if form_type == 'onboarding' else {}
section_order = get_section_order(form_type)
section_labels = get_section_labels(form_type)
default_page_map = get_default_page_map(form_type)
@@ -2343,6 +2379,57 @@ def form_builder_page(request):
}
)
conditional_rule_items = []
if form_type == 'onboarding':
conditional_field_choices = []
for field_name in [
'order_business_cards',
'employment_type',
'group_mailboxes_required_choice',
'additional_hardware_needed_choice',
'additional_software_needed_choice',
'additional_access_needed_choice',
'successor_required_choice',
'inherit_phone_number_choice',
]:
conditional_field_choices.append((field_name, labels.get(field_name, field_name)))
conditional_target_titles = {
'business-card-box': _('Visitenkarten-Details'),
'employment-end-box': _('Vertragsende'),
'group-mailboxes-box': _('Gruppenpostfächer'),
'extra-hardware-box': _('Zusätzliche Hardware'),
'extra-software-box': _('Zusätzliche Software'),
'extra-access-box': _('Zusätzliche Zugänge'),
'successor-box': _('Nachfolge'),
'phone-box': _('Direktwahl'),
}
conditional_target_descriptions = {
'business-card-box': _('Steuert die Detailfelder für Visitenkarten.'),
'employment-end-box': _('Steuert das Enddatum bei befristeter Beschäftigung.'),
'group-mailboxes-box': _('Steuert das Freitextfeld für Gruppenpostfächer.'),
'extra-hardware-box': _('Steuert zusätzliche Hardware-Felder.'),
'extra-software-box': _('Steuert zusätzliche Software-Felder.'),
'extra-access-box': _('Steuert zusätzliche Zugangsangaben.'),
'successor-box': _('Steuert Nachfolge- und Übernahmefelder.'),
'phone-box': _('Steuert die manuelle Direktwahl.'),
}
for target_key, cfg in conditional_rule_configs.items():
clauses = list(cfg.clauses or [])
while len(clauses) < 2:
clauses.append({'field': '', 'operator': 'equals', 'value': ''})
conditional_rule_items.append(
{
'target_key': target_key,
'title': conditional_target_titles.get(target_key, target_key),
'description': conditional_target_descriptions.get(target_key, ''),
'is_active': cfg.is_active,
'clauses': clauses[:2],
'field_choices': conditional_field_choices,
'operator_choices': CONDITIONAL_RULE_OPERATOR_CHOICES,
'target_fields': [labels.get(name, name) for name in ONBOARDING_GROUPS.get(target_key, [])],
}
)
preview_sections = []
if section_order:
field_rule_group_map = {group['key']: group['items'] for group in field_rule_groups}
@@ -2391,6 +2478,7 @@ def form_builder_page(request):
'preview_sections': preview_sections,
'section_rule_items': section_rule_items,
'builder_summary': builder_summary,
'conditional_rule_items': conditional_rule_items,
'active_panel': active_panel,
'active_subpanel': active_subpanel,
'available_presets': FORM_PRESETS.get(form_type, {}),