snapshot: preserve dynamic builder and section ordering work
This commit is contained in:
@@ -45,20 +45,20 @@ from .form_builder import (
|
||||
OFFBOARDING_PAGE_LABELS,
|
||||
OFFBOARDING_PAGE_ORDER,
|
||||
ONBOARDING_DEFAULT_PAGE,
|
||||
ONBOARDING_PAGE_LABELS,
|
||||
ONBOARDING_PAGE_ORDER,
|
||||
build_custom_field_key,
|
||||
custom_field_target_key,
|
||||
ensure_form_field_configs,
|
||||
ensure_form_conditional_rule_configs,
|
||||
ensure_form_section_configs,
|
||||
get_custom_field_configs,
|
||||
get_custom_section_configs,
|
||||
get_default_page_map,
|
||||
get_section_definitions,
|
||||
get_section_labels,
|
||||
get_section_order,
|
||||
apply_form_preset,
|
||||
)
|
||||
from .models import AdminAuditLog, AsyncTaskLog, EmployeeProfile, FormConditionalRuleConfig, FormCustomFieldConfig, 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, FormCustomFieldConfig, FormCustomSectionConfig, 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
|
||||
@@ -106,7 +106,6 @@ ONBOARDING_GROUPS = {
|
||||
'extra-software-box': ['additional_software_multi', 'additional_software'],
|
||||
'extra-access-box': ['additional_access_text'],
|
||||
'successor-box': ['successor_name', 'inherit_phone_number_choice'],
|
||||
'phone-box': ['phone_number_choice'],
|
||||
}
|
||||
|
||||
ONBOARDING_INLINE_CHECKS = {'order_business_cards', 'agreement_confirm'}
|
||||
@@ -119,7 +118,6 @@ ONBOARDING_CHECKBOX_LISTS = {
|
||||
'needed_workspace_groups_multi',
|
||||
'needed_resources_multi',
|
||||
}
|
||||
ONBOARDING_SECTION_ORDER = ['stammdaten', 'vertrag', 'itsetup', 'abschluss']
|
||||
ONBOARDING_SECTION_META = {
|
||||
'stammdaten': {'title': gettext_lazy('Stammdaten'), 'subtitle': gettext_lazy('Person, Rolle, Abteilung')},
|
||||
'vertrag': {'title': gettext_lazy('Vertrag'), 'subtitle': gettext_lazy('Beschäftigung und Termine')},
|
||||
@@ -602,21 +600,24 @@ def _section_for_block(block: dict, field_pages: dict[str, str]) -> str:
|
||||
|
||||
|
||||
def _build_onboarding_sections(blocks: list[dict], field_pages: dict[str, str], visible_section_keys: set[str] | None = None) -> list[dict]:
|
||||
grouped = {key: [] for key in ONBOARDING_SECTION_ORDER}
|
||||
section_defs = get_section_definitions('onboarding')
|
||||
section_order = [item['key'] for item in section_defs]
|
||||
section_titles = {item['key']: item['title'] for item in section_defs}
|
||||
grouped = {key: [] for key in section_order}
|
||||
for block in blocks:
|
||||
section_key = _section_for_block(block, field_pages)
|
||||
if section_key not in grouped:
|
||||
section_key = 'abschluss'
|
||||
grouped[section_key].append(block)
|
||||
visible_keys = visible_section_keys or set(ONBOARDING_SECTION_ORDER)
|
||||
visible_keys = visible_section_keys or set(section_order)
|
||||
return [
|
||||
{
|
||||
'key': key,
|
||||
'title': ONBOARDING_SECTION_META[key]['title'],
|
||||
'subtitle': ONBOARDING_SECTION_META[key]['subtitle'],
|
||||
'title': section_titles.get(key, ONBOARDING_SECTION_META.get(key, {}).get('title', key)),
|
||||
'subtitle': ONBOARDING_SECTION_META.get(key, {}).get('subtitle', ''),
|
||||
'blocks': grouped[key],
|
||||
}
|
||||
for key in ONBOARDING_SECTION_ORDER
|
||||
for key in section_order
|
||||
if key in visible_keys
|
||||
]
|
||||
|
||||
@@ -1930,10 +1931,14 @@ def onboarding_create(request):
|
||||
onboarding_blocks = _build_onboarding_layout(form)
|
||||
field_pages = getattr(form, '_field_page_keys', {})
|
||||
section_configs = ensure_form_section_configs('onboarding')
|
||||
visible_section_keys = {
|
||||
key for key in ONBOARDING_SECTION_ORDER
|
||||
if key in LOCKED_SECTION_RULES.get('onboarding', set()) or section_configs.get(key, None) is None or section_configs[key].is_visible
|
||||
}
|
||||
visible_section_keys = set()
|
||||
for section in get_section_definitions('onboarding'):
|
||||
key = section['key']
|
||||
if section.get('is_custom'):
|
||||
if section.get('is_active', True):
|
||||
visible_section_keys.add(key)
|
||||
elif key in LOCKED_SECTION_RULES.get('onboarding', set()) or section_configs.get(key, None) is None or section_configs[key].is_visible:
|
||||
visible_section_keys.add(key)
|
||||
onboarding_sections = _build_onboarding_sections(onboarding_blocks, field_pages, visible_section_keys=visible_section_keys)
|
||||
onboarding_conditional_rules = _normalized_conditional_rule_payload('onboarding')
|
||||
|
||||
@@ -2162,9 +2167,11 @@ def offboarding_success(request, request_id: int):
|
||||
def form_builder_page(request):
|
||||
language_code = get_language()
|
||||
form_type = request.GET.get('form_type', 'onboarding')
|
||||
can_override_locked_builder_rules = get_user_role_key(request.user) == ROLE_PLATFORM_OWNER
|
||||
anchor = (request.GET.get('anchor') or '').strip()
|
||||
active_panel = (request.GET.get('panel') or '').strip()
|
||||
active_subpanel = (request.GET.get('subpanel') or '').strip()
|
||||
active_rules_panel = (request.GET.get('rules_panel') or '').strip()
|
||||
if form_type not in DEFAULT_FIELD_ORDER:
|
||||
form_type = 'onboarding'
|
||||
option_category = request.GET.get('option_category', 'department')
|
||||
@@ -2267,6 +2274,54 @@ def form_builder_page(request):
|
||||
_audit(request, 'form_field_texts_saved', target_type='form_config', target_label=form_type, details={'count': len(field_ids)})
|
||||
messages.success(request, 'Feldtexte wurden gespeichert.')
|
||||
|
||||
elif action == 'add_custom_section' and form_type == 'onboarding':
|
||||
title = (request.POST.get('custom_section_title') or '').strip()
|
||||
title_en = (request.POST.get('custom_section_title_en') or '').strip()
|
||||
sort_order_raw = (request.POST.get('custom_section_sort_order') or '').strip()
|
||||
if not title:
|
||||
messages.error(request, 'Bitte einen Titel für den benutzerdefinierten Abschnitt angeben.')
|
||||
else:
|
||||
section_key_base = build_custom_field_key(title)
|
||||
section_key = section_key_base
|
||||
suffix = 2
|
||||
while FormCustomSectionConfig.objects.filter(form_type=form_type, section_key=section_key).exists():
|
||||
section_key = f'{section_key_base}_{suffix}'
|
||||
suffix += 1
|
||||
try:
|
||||
sort_order = int(sort_order_raw or 0)
|
||||
except ValueError:
|
||||
sort_order = 0
|
||||
FormCustomSectionConfig.objects.create(
|
||||
form_type=form_type,
|
||||
section_key=section_key,
|
||||
sort_order=max(0, sort_order),
|
||||
title=title,
|
||||
title_en=title_en,
|
||||
is_active=True,
|
||||
)
|
||||
_audit(request, 'form_custom_section_added', target_type='form_custom_section', target_label=title, details={'form_type': form_type, 'section_key': section_key})
|
||||
messages.success(request, 'Benutzerdefinierter Abschnitt wurde hinzugefügt.')
|
||||
|
||||
elif action == 'save_custom_sections' and form_type == 'onboarding':
|
||||
section_ids = request.POST.getlist('custom_section_ids')
|
||||
updated = 0
|
||||
for raw_id in section_ids:
|
||||
cfg = FormCustomSectionConfig.objects.filter(id=raw_id, form_type=form_type).first()
|
||||
if not cfg:
|
||||
continue
|
||||
try:
|
||||
sort_order = int((request.POST.get(f'custom_section_sort_order_{cfg.id}') or '').strip() or cfg.sort_order)
|
||||
except ValueError:
|
||||
sort_order = cfg.sort_order
|
||||
cfg.title = (request.POST.get(f'custom_section_title_{cfg.id}') or '').strip() or cfg.title
|
||||
cfg.title_en = (request.POST.get(f'custom_section_title_en_{cfg.id}') or '').strip()
|
||||
cfg.is_active = request.POST.get(f'custom_section_is_active_{cfg.id}') == 'on'
|
||||
cfg.sort_order = max(0, sort_order)
|
||||
cfg.save(update_fields=['title', 'title_en', 'is_active', 'sort_order'])
|
||||
updated += 1
|
||||
_audit(request, 'form_custom_sections_saved', target_type='form_custom_section', target_label=form_type, details={'count': updated})
|
||||
messages.success(request, 'Benutzerdefinierte Abschnitte wurden gespeichert.')
|
||||
|
||||
elif action == 'add_custom_field':
|
||||
label = (request.POST.get('custom_label') or '').strip()
|
||||
label_en = (request.POST.get('custom_label_en') or '').strip()
|
||||
@@ -2360,7 +2415,7 @@ def form_builder_page(request):
|
||||
cfg = FormFieldConfig.objects.filter(id=raw_id, form_type=form_type).first()
|
||||
if not cfg:
|
||||
continue
|
||||
if cfg.field_name in locked_fields:
|
||||
if cfg.field_name in locked_fields and not can_override_locked_builder_rules:
|
||||
cfg.is_visible = True
|
||||
cfg.is_required = None
|
||||
else:
|
||||
@@ -2375,9 +2430,27 @@ def form_builder_page(request):
|
||||
elif action == 'save_section_rules' and form_type in {'onboarding', 'offboarding'}:
|
||||
section_configs = ensure_form_section_configs(form_type)
|
||||
locked_sections = LOCKED_SECTION_RULES.get(form_type, set())
|
||||
posted_order = request.POST.getlist('section_order')
|
||||
next_sort_order = 0
|
||||
updated = 0
|
||||
for section_key in posted_order:
|
||||
cfg = section_configs.get(section_key)
|
||||
if cfg is not None:
|
||||
if cfg.sort_order != next_sort_order:
|
||||
cfg.sort_order = next_sort_order
|
||||
cfg.save(update_fields=['sort_order'])
|
||||
updated += 1
|
||||
next_sort_order += 1
|
||||
continue
|
||||
if form_type == 'onboarding':
|
||||
custom_cfg = FormCustomSectionConfig.objects.filter(form_type=form_type, section_key=section_key).first()
|
||||
if custom_cfg and custom_cfg.sort_order != next_sort_order:
|
||||
custom_cfg.sort_order = next_sort_order
|
||||
custom_cfg.save(update_fields=['sort_order'])
|
||||
updated += 1
|
||||
next_sort_order += 1
|
||||
for section_key, cfg in section_configs.items():
|
||||
if section_key in locked_sections:
|
||||
if section_key in locked_sections and not can_override_locked_builder_rules:
|
||||
if not cfg.is_visible:
|
||||
cfg.is_visible = True
|
||||
cfg.save(update_fields=['is_visible'])
|
||||
@@ -2385,6 +2458,11 @@ def form_builder_page(request):
|
||||
cfg.is_visible = request.POST.get(f'section_visible_{section_key}') == 'on'
|
||||
cfg.save(update_fields=['is_visible'])
|
||||
updated += 1
|
||||
if form_type == 'onboarding':
|
||||
for cfg in FormCustomSectionConfig.objects.filter(form_type=form_type):
|
||||
cfg.is_active = request.POST.get(f'section_visible_{cfg.section_key}') == 'on'
|
||||
cfg.save(update_fields=['is_active'])
|
||||
updated += 1
|
||||
_audit(request, 'form_section_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
|
||||
messages.success(request, 'Abschnittsregeln wurden gespeichert.')
|
||||
|
||||
@@ -2412,13 +2490,14 @@ def form_builder_page(request):
|
||||
elif action == 'apply_preset':
|
||||
preset_key = (request.POST.get('preset_key') or '').strip()
|
||||
if apply_form_preset(form_type, preset_key):
|
||||
active_panel = 'builder-preview'
|
||||
active_panel = 'builder-content'
|
||||
active_subpanel = 'preview'
|
||||
_audit(request, 'form_preset_applied', target_type='form_config', target_label=form_type, details={'preset': preset_key})
|
||||
messages.success(request, 'Preset wurde angewendet.')
|
||||
else:
|
||||
messages.error(request, 'Preset konnte nicht angewendet werden.')
|
||||
|
||||
if action in {'add_option', 'save_options', 'save_field_texts', 'add_custom_field', 'save_custom_fields'}:
|
||||
if action in {'add_option', 'save_options', 'save_field_texts', 'add_custom_field', 'save_custom_fields', 'add_custom_section', 'save_custom_sections'}:
|
||||
active_panel = 'builder-content'
|
||||
if action in {'add_option', 'save_options'}:
|
||||
active_subpanel = 'options'
|
||||
@@ -2426,13 +2505,23 @@ def form_builder_page(request):
|
||||
active_subpanel = 'field-texts'
|
||||
elif action in {'add_custom_field', 'save_custom_fields'}:
|
||||
active_subpanel = 'custom-fields'
|
||||
elif action in {'add_custom_section', 'save_custom_sections'}:
|
||||
active_subpanel = 'custom-sections'
|
||||
elif action in {'save_field_rules', 'save_section_rules', 'save_conditional_rules'}:
|
||||
active_panel = 'builder-rules'
|
||||
if action == 'save_section_rules':
|
||||
active_rules_panel = 'section-rules'
|
||||
elif action == 'save_field_rules':
|
||||
active_rules_panel = 'field-rules'
|
||||
elif action == 'save_conditional_rules':
|
||||
active_rules_panel = 'conditional-rules'
|
||||
redirect_target = f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}"
|
||||
if active_panel:
|
||||
redirect_target += f"&panel={active_panel}"
|
||||
if active_subpanel:
|
||||
redirect_target += f"&subpanel={active_subpanel}"
|
||||
if active_rules_panel:
|
||||
redirect_target += f"&rules_panel={active_rules_panel}"
|
||||
if anchor == 'builder-content' or active_panel == 'builder-content':
|
||||
redirect_target += "#builder-content"
|
||||
return redirect(redirect_target)
|
||||
@@ -2450,8 +2539,9 @@ 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)
|
||||
section_definitions = get_section_definitions(form_type, include_inactive_custom=True)
|
||||
section_order = [item['key'] for item in section_definitions]
|
||||
section_labels = {item['key']: item['title'] for item in section_definitions}
|
||||
default_page_map = get_default_page_map(form_type)
|
||||
|
||||
configs = list(
|
||||
@@ -2461,15 +2551,16 @@ def form_builder_page(request):
|
||||
locked = LOCKED_FIELD_RULES.get(form_type, set())
|
||||
locked_sections = LOCKED_SECTION_RULES.get(form_type, set())
|
||||
custom_field_configs = list(FormCustomFieldConfig.objects.filter(form_type=form_type).order_by('section_key', 'sort_order', 'field_key'))
|
||||
custom_section_configs = get_custom_section_configs(form_type, include_inactive=True)
|
||||
|
||||
if form_type == 'onboarding':
|
||||
columns = [
|
||||
{
|
||||
'key': key,
|
||||
'title': ONBOARDING_PAGE_LABELS.get(key, key),
|
||||
'title': section_labels.get(key, key),
|
||||
'items': [],
|
||||
}
|
||||
for key in ONBOARDING_PAGE_ORDER
|
||||
for key in section_order
|
||||
]
|
||||
column_by_key = {c['key']: c for c in columns}
|
||||
fallback = 'abschluss'
|
||||
@@ -2564,15 +2655,20 @@ def form_builder_page(request):
|
||||
section_rule_items = []
|
||||
if section_order:
|
||||
fallback_section = section_order[-1] if section_order else ''
|
||||
custom_section_map = {cfg.section_key: cfg for cfg in custom_section_configs}
|
||||
for key in section_order:
|
||||
cfg = section_configs.get(key)
|
||||
custom_cfg = custom_section_map.get(key)
|
||||
is_custom = custom_cfg is not None
|
||||
section_rule_items.append(
|
||||
{
|
||||
'key': key,
|
||||
'title': section_labels.get(key, key),
|
||||
'is_visible': True if not cfg else cfg.is_visible,
|
||||
'locked': key in locked_sections,
|
||||
'field_count': len([c for c in configs if (c.page_key or default_page_map.get(c.field_name, fallback_section)) == key]),
|
||||
'is_visible': bool(custom_cfg.is_active) if is_custom else (True if not cfg else cfg.is_visible),
|
||||
'locked': False if is_custom else (key in locked_sections and not can_override_locked_builder_rules),
|
||||
'is_custom': is_custom,
|
||||
'sort_order': custom_cfg.sort_order if is_custom else (cfg.sort_order if cfg else 0),
|
||||
'field_count': len([c for c in configs if (c.page_key or default_page_map.get(c.field_name, fallback_section)) == key]) + len([c for c in custom_field_configs if c.section_key == key]),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2588,7 +2684,7 @@ def form_builder_page(request):
|
||||
'page_label': section_labels.get(page_key, page_key) if section_order else '',
|
||||
'is_visible': cfg.is_visible,
|
||||
'is_required': cfg.is_required,
|
||||
'locked': cfg.field_name in locked,
|
||||
'locked': cfg.field_name in locked and not can_override_locked_builder_rules,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2659,7 +2755,6 @@ def form_builder_page(request):
|
||||
'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.'),
|
||||
@@ -2669,7 +2764,6 @@ def form_builder_page(request):
|
||||
'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 [])
|
||||
@@ -2704,7 +2798,8 @@ def form_builder_page(request):
|
||||
for key in section_order:
|
||||
section_cfg = section_configs.get(key)
|
||||
section_locked = key in locked_sections
|
||||
section_visible = True if section_locked or not section_cfg else section_cfg.is_visible
|
||||
custom_section = next((cfg for cfg in custom_section_configs if cfg.section_key == key), None)
|
||||
section_visible = bool(custom_section.is_active) if custom_section else (True if section_locked or not section_cfg else section_cfg.is_visible)
|
||||
visible_items = [
|
||||
item for item in field_rule_group_map.get(key, [])
|
||||
if item['locked'] or item['is_visible']
|
||||
@@ -2738,6 +2833,7 @@ def form_builder_page(request):
|
||||
'hidden_field_count': hidden_field_count,
|
||||
'hidden_section_count': hidden_section_count,
|
||||
'custom_field_count': len([cfg for cfg in custom_field_configs if cfg.is_active]),
|
||||
'custom_section_count': len([cfg for cfg in custom_section_configs if cfg.is_active]),
|
||||
}
|
||||
|
||||
return render(
|
||||
@@ -2760,9 +2856,12 @@ def form_builder_page(request):
|
||||
'conditional_rule_items': conditional_rule_items,
|
||||
'custom_field_groups': custom_field_groups,
|
||||
'custom_field_type_choices': _translate_choice_list(FormCustomFieldConfig.FIELD_TYPE_CHOICES),
|
||||
'custom_section_items': custom_section_configs,
|
||||
'active_panel': active_panel,
|
||||
'active_subpanel': active_subpanel,
|
||||
'active_rules_panel': active_rules_panel,
|
||||
'available_presets': FORM_PRESETS.get(form_type, {}),
|
||||
'can_override_locked_builder_rules': can_override_locked_builder_rules,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3213,10 +3312,8 @@ def form_builder_save_order(request):
|
||||
allowed_names = {cfg.field_name for cfg in configs} | {f'custom__{cfg.field_key}' for cfg in custom_configs}
|
||||
seen = set()
|
||||
|
||||
if form_type == 'onboarding':
|
||||
allowed_columns = ONBOARDING_PAGE_ORDER
|
||||
else:
|
||||
allowed_columns = OFFBOARDING_PAGE_ORDER
|
||||
allowed_columns = get_section_order(form_type)
|
||||
fallback_section = allowed_columns[-1] if allowed_columns else ''
|
||||
|
||||
name_to_cfg = {cfg.field_name: cfg for cfg in configs}
|
||||
custom_name_to_cfg = {f'custom__{cfg.field_key}': cfg for cfg in custom_configs}
|
||||
@@ -3248,20 +3345,14 @@ def form_builder_save_order(request):
|
||||
cfg = name_to_cfg[name]
|
||||
cfg.sort_order = sort_order
|
||||
sort_order += 1
|
||||
if form_type == 'onboarding':
|
||||
cfg.page_key = cfg.page_key or ONBOARDING_DEFAULT_PAGE.get(name, 'abschluss')
|
||||
else:
|
||||
cfg.page_key = cfg.page_key or default_page_map.get(name, OFFBOARDING_PAGE_ORDER[-1])
|
||||
cfg.page_key = cfg.page_key or default_page_map.get(name, fallback_section)
|
||||
|
||||
missing_custom = [name for name in custom_name_to_cfg.keys() if name not in seen]
|
||||
for name in missing_custom:
|
||||
cfg = custom_name_to_cfg[name]
|
||||
cfg.sort_order = sort_order
|
||||
sort_order += 1
|
||||
if form_type == 'onboarding':
|
||||
cfg.section_key = cfg.section_key or 'abschluss'
|
||||
else:
|
||||
cfg.section_key = cfg.section_key or OFFBOARDING_PAGE_ORDER[-1]
|
||||
cfg.section_key = cfg.section_key or fallback_section
|
||||
|
||||
FormFieldConfig.objects.bulk_update(configs, ['sort_order', 'page_key'])
|
||||
if custom_configs:
|
||||
|
||||
Reference in New Issue
Block a user