969 lines
51 KiB
Python
969 lines
51 KiB
Python
import json
|
|
import re
|
|
|
|
from django.contrib import messages
|
|
from django.db import IntegrityError
|
|
from django.http import JsonResponse
|
|
from django.shortcuts import redirect, render
|
|
from django.utils.translation import get_language, gettext as _
|
|
|
|
from .forms import OffboardingRequestForm, OnboardingRequestForm
|
|
from .form_builder import (
|
|
DEFAULT_FIELD_ORDER,
|
|
FORM_PRESETS,
|
|
LOCKED_FIELD_RULES,
|
|
LOCKED_SECTION_RULES,
|
|
ONBOARDING_DEFAULT_PAGE,
|
|
apply_form_preset,
|
|
build_custom_field_key,
|
|
ensure_form_conditional_rule_configs,
|
|
ensure_form_field_configs,
|
|
ensure_form_section_configs,
|
|
get_custom_section_configs,
|
|
get_default_page_map,
|
|
get_section_definitions,
|
|
get_section_order,
|
|
)
|
|
from .models import (
|
|
FormConditionalRuleConfig,
|
|
FormCustomFieldConfig,
|
|
FormCustomSectionConfig,
|
|
FormFieldConfig,
|
|
FormOption,
|
|
)
|
|
from .roles import ROLE_PLATFORM_OWNER, get_user_role_key
|
|
|
|
|
|
def form_builder_page_impl(
|
|
request,
|
|
*,
|
|
audit_fn,
|
|
translate_choice_list,
|
|
form_field_labels_fn,
|
|
field_rule_summary_fn,
|
|
conditional_rule_summary_fn,
|
|
onboarding_groups,
|
|
conditional_rule_operator_choices,
|
|
):
|
|
_audit = audit_fn
|
|
_translate_choice_list = translate_choice_list
|
|
_form_field_labels = form_field_labels_fn
|
|
_field_rule_summary = field_rule_summary_fn
|
|
_conditional_rule_summary = conditional_rule_summary_fn
|
|
ONBOARDING_GROUPS = onboarding_groups
|
|
CONDITIONAL_RULE_OPERATOR_CHOICES = conditional_rule_operator_choices
|
|
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()
|
|
active_module = (request.GET.get('module') or '').strip()
|
|
active_structure_section = (request.GET.get('structure_section') or '').strip()
|
|
active_field_rules_section = ((request.POST.get('field_rules_section') if request.method == 'POST' else '') or request.GET.get('field_rules_section') or '').strip()
|
|
active_field_texts_section = ((request.POST.get('field_texts_section') if request.method == 'POST' else '') or request.GET.get('field_texts_section') or '').strip()
|
|
active_custom_fields_section = ((request.POST.get('custom_fields_section') if request.method == 'POST' else '') or request.GET.get('custom_fields_section') or '').strip()
|
|
active_section_rules_section = ((request.POST.get('section_rules_section') if request.method == 'POST' else '') or request.GET.get('section_rules_section') or '').strip()
|
|
active_conditional_target = ((request.POST.get('conditional_target') if request.method == 'POST' else '') or request.GET.get('conditional_target') or '').strip()
|
|
if form_type not in DEFAULT_FIELD_ORDER:
|
|
form_type = 'onboarding'
|
|
option_category = request.GET.get('option_category', 'department')
|
|
option_categories = [c[0] for c in FormOption.CATEGORY_CHOICES]
|
|
if option_category not in option_categories:
|
|
option_category = option_categories[0]
|
|
|
|
valid_modules = {
|
|
'structure',
|
|
'section-rules',
|
|
'field-rules',
|
|
'conditional-rules',
|
|
'options',
|
|
'field-texts',
|
|
'custom-sections',
|
|
'custom-fields',
|
|
'preview',
|
|
}
|
|
|
|
if not active_module:
|
|
if active_panel == 'builder-structure':
|
|
active_module = 'structure'
|
|
elif active_panel == 'builder-rules':
|
|
active_module = active_rules_panel or 'section-rules'
|
|
elif active_panel == 'builder-content':
|
|
active_module = active_subpanel or 'options'
|
|
else:
|
|
active_module = 'structure'
|
|
if active_module not in valid_modules:
|
|
active_module = 'structure'
|
|
if form_type != 'onboarding' and active_module == 'custom-sections':
|
|
active_module = 'options'
|
|
if form_type != 'onboarding' and active_module == 'conditional-rules':
|
|
active_module = 'field-rules'
|
|
|
|
if request.method == 'POST':
|
|
delete_option_id = request.POST.get('delete_option_id', '').strip()
|
|
delete_custom_field_id = request.POST.get('delete_custom_field_id', '').strip()
|
|
delete_custom_section_id = request.POST.get('delete_custom_section_id', '').strip()
|
|
if delete_option_id:
|
|
option = FormOption.objects.filter(id=delete_option_id).first()
|
|
if not option:
|
|
messages.error(request, _('Option nicht gefunden.'))
|
|
else:
|
|
option_category = option.category
|
|
deleted_label = option.label
|
|
deleted_id = option.id
|
|
option.delete()
|
|
_audit(request, 'form_option_deleted', target_type='form_option', target_id=deleted_id, target_label=deleted_label)
|
|
messages.success(request, _('Option wurde gelöscht.'))
|
|
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=options")
|
|
if delete_custom_field_id:
|
|
custom_field = FormCustomFieldConfig.objects.filter(id=delete_custom_field_id, form_type=form_type).first()
|
|
if not custom_field:
|
|
messages.error(request, _('Benutzerdefiniertes Feld nicht gefunden.'))
|
|
else:
|
|
deleted_label = custom_field.label
|
|
deleted_id = custom_field.id
|
|
custom_field.delete()
|
|
_audit(request, 'form_custom_field_deleted', target_type='form_custom_field', target_id=deleted_id, target_label=deleted_label)
|
|
messages.success(request, _('Benutzerdefiniertes Feld wurde gelöscht.'))
|
|
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields")
|
|
if delete_custom_section_id:
|
|
custom_section = FormCustomSectionConfig.objects.filter(id=delete_custom_section_id, form_type=form_type).first()
|
|
if not custom_section:
|
|
messages.error(request, _('Benutzerdefinierter Abschnitt nicht gefunden.'))
|
|
else:
|
|
deleted_label = custom_section.title
|
|
deleted_id = custom_section.id
|
|
section_key = custom_section.section_key
|
|
custom_fields = list(FormCustomFieldConfig.objects.filter(form_type=form_type, section_key=section_key))
|
|
deleted_field_count = len(custom_fields)
|
|
if custom_fields:
|
|
field_keys = [item.field_key for item in custom_fields]
|
|
FormConditionalRuleConfig.objects.filter(
|
|
form_type=form_type,
|
|
target_key__in=[f'custom__{field_key}' for field_key in field_keys],
|
|
).delete()
|
|
FormCustomFieldConfig.objects.filter(id__in=[item.id for item in custom_fields]).delete()
|
|
custom_section.delete()
|
|
_audit(
|
|
request,
|
|
'form_custom_section_deleted',
|
|
target_type='form_custom_section',
|
|
target_id=deleted_id,
|
|
target_label=deleted_label,
|
|
details={'section_key': section_key, 'deleted_field_count': deleted_field_count},
|
|
)
|
|
messages.success(request, _('Benutzerdefinierter Abschnitt wurde gelöscht.'))
|
|
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-sections")
|
|
|
|
action = request.POST.get('builder_action', '')
|
|
if action == 'add_option':
|
|
category = request.POST.get('category', '').strip()
|
|
label = request.POST.get('label', '').strip()
|
|
label_en = request.POST.get('label_en', '').strip()
|
|
value = request.POST.get('value', '').strip()
|
|
if category not in option_categories:
|
|
messages.error(request, _('Ungültige Kategorie.'))
|
|
elif not label:
|
|
messages.error(request, _('Bitte einen Namen für die Option angeben.'))
|
|
else:
|
|
next_sort = (
|
|
FormOption.objects.filter(category=category).order_by('-sort_order').values_list('sort_order', flat=True).first()
|
|
)
|
|
FormOption.objects.create(
|
|
# Global form option catalog entry
|
|
category=category,
|
|
label=label,
|
|
label_en=label_en,
|
|
value=value or label,
|
|
sort_order=(next_sort + 1) if next_sort is not None else 0,
|
|
is_active=True,
|
|
)
|
|
_audit(
|
|
request,
|
|
'form_option_added',
|
|
target_type='form_option',
|
|
target_label=label,
|
|
details={'category': category, 'label_en': label_en, 'value': value or label},
|
|
)
|
|
messages.success(request, _('Option wurde hinzugefügt.'))
|
|
option_category = category
|
|
|
|
elif action == 'save_options':
|
|
option_ids = request.POST.getlist('option_ids')
|
|
for pos, raw_id in enumerate(option_ids):
|
|
option = FormOption.objects.filter(id=raw_id).first()
|
|
if not option:
|
|
continue
|
|
next_label = request.POST.get(f'label_{option.id}', '').strip() or option.label
|
|
option.label = next_label
|
|
option.label_en = request.POST.get(f'label_en_{option.id}', '').strip()
|
|
option.value = request.POST.get(f'value_{option.id}', '').strip() or next_label
|
|
option.is_active = request.POST.get(f'active_{option.id}') == 'on'
|
|
option.sort_order = pos
|
|
try:
|
|
option.save(update_fields=['label', 'label_en', 'value', 'is_active', 'sort_order'])
|
|
except IntegrityError:
|
|
messages.error(request, _('Doppelte Bezeichnung in Kategorie: %(label)s') % {'label': next_label})
|
|
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option.category}&module=options")
|
|
option_category = option.category
|
|
_audit(request, 'form_options_saved', target_type='form_option', target_label=option_category, details={'count': len(option_ids)})
|
|
messages.success(request, _('Optionen wurden gespeichert.'))
|
|
|
|
elif action == 'save_field_texts':
|
|
field_ids = request.POST.getlist('field_ids')
|
|
for raw_id in field_ids:
|
|
cfg = FormFieldConfig.objects.filter(id=raw_id, form_type=form_type).first()
|
|
if not cfg:
|
|
continue
|
|
cfg.label_override = (request.POST.get(f'label_override_{cfg.id}') or '').strip()
|
|
cfg.label_override_en = (request.POST.get(f'label_override_en_{cfg.id}') or '').strip()
|
|
cfg.help_text_override = (request.POST.get(f'help_text_override_{cfg.id}') or '').strip()
|
|
cfg.help_text_override_en = (request.POST.get(f'help_text_override_en_{cfg.id}') or '').strip()
|
|
cfg.save(update_fields=['label_override', 'label_override_en', 'help_text_override', 'help_text_override_en'])
|
|
_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()
|
|
section_key = (request.POST.get('custom_section_key') or '').strip()
|
|
field_type = (request.POST.get('custom_field_type') or '').strip()
|
|
sort_order_raw = (request.POST.get('custom_sort_order') or '').strip()
|
|
help_text = (request.POST.get('custom_help_text') or '').strip()
|
|
help_text_en = (request.POST.get('custom_help_text_en') or '').strip()
|
|
select_options = (request.POST.get('custom_select_options') or '').strip()
|
|
select_options_en = (request.POST.get('custom_select_options_en') or '').strip()
|
|
section_choices = {key for key in get_section_order(form_type)}
|
|
field_type_choices = {key for key, _ in FormCustomFieldConfig.FIELD_TYPE_CHOICES}
|
|
if not label:
|
|
messages.error(request, _('Bitte eine Bezeichnung für das benutzerdefinierte Feld angeben.'))
|
|
elif section_key not in section_choices:
|
|
messages.error(request, _('Ungültiger Abschnitt für das benutzerdefinierte Feld.'))
|
|
elif field_type not in field_type_choices:
|
|
messages.error(request, _('Ungültiger Feldtyp.'))
|
|
elif field_type == FormCustomFieldConfig.FIELD_TYPE_SELECT and not select_options:
|
|
messages.error(request, _('Auswahlfelder benötigen mindestens eine Option.'))
|
|
else:
|
|
field_key_base = build_custom_field_key(label)
|
|
field_key = field_key_base
|
|
suffix = 2
|
|
while FormCustomFieldConfig.objects.filter(form_type=form_type, field_key=field_key).exists():
|
|
field_key = f'{field_key_base}_{suffix}'
|
|
suffix += 1
|
|
try:
|
|
sort_order = int(sort_order_raw or 0)
|
|
except ValueError:
|
|
sort_order = 0
|
|
FormCustomFieldConfig.objects.create(
|
|
form_type=form_type,
|
|
field_key=field_key,
|
|
section_key=section_key,
|
|
sort_order=max(0, sort_order),
|
|
field_type=field_type,
|
|
is_active=True,
|
|
is_required=request.POST.get('custom_is_required') == 'on',
|
|
label=label,
|
|
label_en=label_en,
|
|
help_text=help_text,
|
|
help_text_en=help_text_en,
|
|
select_options=select_options,
|
|
select_options_en=select_options_en,
|
|
)
|
|
_audit(request, 'form_custom_field_added', target_type='form_custom_field', target_label=label, details={'form_type': form_type, 'field_type': field_type, 'section_key': section_key})
|
|
messages.success(request, _('Benutzerdefiniertes Feld wurde hinzugefügt.'))
|
|
|
|
elif action == 'save_custom_fields':
|
|
custom_ids = request.POST.getlist('custom_field_ids')
|
|
updated = 0
|
|
section_choices = {key for key in get_section_order(form_type)}
|
|
field_type_choices = {key for key, _ in FormCustomFieldConfig.FIELD_TYPE_CHOICES}
|
|
for raw_id in custom_ids:
|
|
cfg = FormCustomFieldConfig.objects.filter(id=raw_id, form_type=form_type).first()
|
|
if not cfg:
|
|
continue
|
|
field_type = (request.POST.get(f'custom_field_type_{cfg.id}') or '').strip()
|
|
section_key = (request.POST.get(f'custom_section_key_{cfg.id}') or '').strip()
|
|
try:
|
|
sort_order = int((request.POST.get(f'custom_sort_order_{cfg.id}') or '').strip() or cfg.sort_order)
|
|
except ValueError:
|
|
sort_order = cfg.sort_order
|
|
cfg.label = (request.POST.get(f'custom_label_{cfg.id}') or '').strip() or cfg.label
|
|
cfg.label_en = (request.POST.get(f'custom_label_en_{cfg.id}') or '').strip()
|
|
cfg.help_text = (request.POST.get(f'custom_help_text_{cfg.id}') or '').strip()
|
|
cfg.help_text_en = (request.POST.get(f'custom_help_text_en_{cfg.id}') or '').strip()
|
|
cfg.is_required = request.POST.get(f'custom_is_required_{cfg.id}') == 'on'
|
|
cfg.is_active = request.POST.get(f'custom_is_active_{cfg.id}') == 'on'
|
|
if field_type in field_type_choices:
|
|
cfg.field_type = field_type
|
|
if section_key in section_choices:
|
|
cfg.section_key = section_key
|
|
cfg.sort_order = max(0, sort_order)
|
|
cfg.select_options = (request.POST.get(f'custom_select_options_{cfg.id}') or '').strip()
|
|
cfg.select_options_en = (request.POST.get(f'custom_select_options_en_{cfg.id}') or '').strip()
|
|
if cfg.field_type == FormCustomFieldConfig.FIELD_TYPE_SELECT and not cfg.select_options:
|
|
messages.error(request, _('Auswahlfeld "%(label)s" benötigt mindestens eine Option.') % {'label': cfg.label})
|
|
return redirect(f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}&module=custom-fields")
|
|
cfg.save()
|
|
updated += 1
|
|
_audit(request, 'form_custom_fields_saved', target_type='form_custom_field', target_label=form_type, details={'count': updated})
|
|
messages.success(request, _('Benutzerdefinierte Felder wurden gespeichert.'))
|
|
|
|
elif action == 'save_field_rules':
|
|
field_ids = request.POST.getlist('field_rule_ids')
|
|
locked_fields = LOCKED_FIELD_RULES.get(form_type, set())
|
|
updated = 0
|
|
for raw_id in field_ids:
|
|
cfg = FormFieldConfig.objects.filter(id=raw_id, form_type=form_type).first()
|
|
if not cfg:
|
|
continue
|
|
if cfg.field_name in locked_fields and not can_override_locked_builder_rules:
|
|
cfg.is_visible = True
|
|
cfg.is_required = None
|
|
else:
|
|
cfg.is_visible = request.POST.get(f'is_visible_{cfg.id}') == 'on'
|
|
required_mode = (request.POST.get(f'is_required_{cfg.id}') or '').strip()
|
|
cfg.is_required = True if required_mode == 'required' else False if required_mode == 'optional' else None
|
|
cfg.save(update_fields=['is_visible', 'is_required'])
|
|
updated += 1
|
|
_audit(request, 'form_field_rules_saved', target_type='form_config', target_label=form_type, details={'count': updated})
|
|
messages.success(request, _('Feldregeln wurden gespeichert.'))
|
|
|
|
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 and not can_override_locked_builder_rules:
|
|
if not cfg.is_visible:
|
|
cfg.is_visible = True
|
|
cfg.save(update_fields=['is_visible'])
|
|
continue
|
|
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.'))
|
|
|
|
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):
|
|
active_module = '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'}:
|
|
active_module = 'options'
|
|
elif action == 'save_field_texts':
|
|
active_module = 'field-texts'
|
|
elif action in {'add_custom_field', 'save_custom_fields'}:
|
|
active_module = 'custom-fields'
|
|
elif action in {'add_custom_section', 'save_custom_sections'}:
|
|
active_module = 'custom-sections'
|
|
elif action in {'save_field_rules', 'save_section_rules', 'save_conditional_rules'}:
|
|
active_module = 'section-rules'
|
|
if action == 'save_section_rules':
|
|
active_module = 'section-rules'
|
|
elif action == 'save_field_rules':
|
|
active_module = 'field-rules'
|
|
elif action == 'save_conditional_rules':
|
|
active_module = 'conditional-rules'
|
|
redirect_target = f"/admin-tools/form-builder/?form_type={form_type}&option_category={option_category}"
|
|
if active_module:
|
|
redirect_target += f"&module={active_module}"
|
|
if active_structure_section:
|
|
redirect_target += f"&structure_section={active_structure_section}"
|
|
if active_section_rules_section and active_module == 'section-rules':
|
|
redirect_target += f"§ion_rules_section={active_section_rules_section}"
|
|
if active_field_rules_section and active_module == 'field-rules':
|
|
redirect_target += f"&field_rules_section={active_field_rules_section}"
|
|
if active_conditional_target and active_module == 'conditional-rules':
|
|
redirect_target += f"&conditional_target={active_conditional_target}"
|
|
if active_field_texts_section and active_module == 'field-texts':
|
|
redirect_target += f"&field_texts_section={active_field_texts_section}"
|
|
if active_custom_fields_section and active_module == 'custom-fields':
|
|
redirect_target += f"&custom_fields_section={active_custom_fields_section}"
|
|
return redirect(redirect_target)
|
|
|
|
default_names = list(DEFAULT_FIELD_ORDER.get(form_type, []))
|
|
existing_names = list(
|
|
OnboardingRequestForm.base_fields.keys()
|
|
if form_type == 'onboarding'
|
|
else OffboardingRequestForm.base_fields.keys()
|
|
)
|
|
|
|
for name in existing_names:
|
|
if name not in default_names:
|
|
default_names.append(name)
|
|
|
|
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_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(
|
|
FormFieldConfig.objects.filter(form_type=form_type).order_by('sort_order', 'field_name')
|
|
)
|
|
labels = _form_field_labels(form_type)
|
|
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': section_labels.get(key, key),
|
|
'items': [],
|
|
}
|
|
for key in section_order
|
|
]
|
|
column_by_key = {c['key']: c for c in columns}
|
|
fallback = 'abschluss'
|
|
for cfg in configs:
|
|
page_key = cfg.page_key or ONBOARDING_DEFAULT_PAGE.get(cfg.field_name, fallback)
|
|
if page_key not in column_by_key:
|
|
page_key = fallback
|
|
column_by_key[page_key]['items'].append(
|
|
{
|
|
'field_name': cfg.field_name,
|
|
'label': cfg.translated_label_override(language_code) or labels.get(cfg.field_name, cfg.field_name),
|
|
'label_de': cfg.label_override or labels.get(cfg.field_name, cfg.field_name),
|
|
'label_en': cfg.label_override_en,
|
|
'is_visible': cfg.is_visible,
|
|
'is_required': cfg.is_required,
|
|
'locked': cfg.field_name in locked,
|
|
'page_key': page_key,
|
|
'is_custom': False,
|
|
'sort_order': cfg.sort_order,
|
|
}
|
|
)
|
|
for cfg in custom_field_configs:
|
|
page_key = cfg.section_key or fallback
|
|
if page_key not in column_by_key:
|
|
page_key = fallback
|
|
column_by_key[page_key]['items'].append(
|
|
{
|
|
'field_name': f'custom__{cfg.field_key}',
|
|
'label': cfg.translated_label(language_code),
|
|
'label_de': cfg.label,
|
|
'label_en': cfg.label_en,
|
|
'is_visible': cfg.is_active,
|
|
'is_required': cfg.is_required,
|
|
'locked': False,
|
|
'page_key': page_key,
|
|
'is_custom': True,
|
|
'sort_order': cfg.sort_order,
|
|
}
|
|
)
|
|
for column in columns:
|
|
column['items'].sort(key=lambda item: (item.get('sort_order', 9999), item['field_name']))
|
|
else:
|
|
columns = [
|
|
{
|
|
'key': key,
|
|
'title': section_labels.get(key, key),
|
|
'items': [],
|
|
}
|
|
for key in section_order
|
|
]
|
|
column_by_key = {c['key']: c for c in columns}
|
|
fallback = section_order[-1] if section_order else 'all'
|
|
for cfg in configs:
|
|
page_key = cfg.page_key or default_page_map.get(cfg.field_name, fallback)
|
|
if page_key not in column_by_key:
|
|
page_key = fallback
|
|
column_by_key[page_key]['items'].append(
|
|
{
|
|
'field_name': cfg.field_name,
|
|
'label': cfg.translated_label_override(language_code) or labels.get(cfg.field_name, cfg.field_name),
|
|
'label_de': cfg.label_override or labels.get(cfg.field_name, cfg.field_name),
|
|
'label_en': cfg.label_override_en,
|
|
'is_visible': cfg.is_visible,
|
|
'is_required': cfg.is_required,
|
|
'locked': cfg.field_name in locked,
|
|
'page_key': page_key,
|
|
'is_custom': False,
|
|
'sort_order': cfg.sort_order,
|
|
}
|
|
)
|
|
for cfg in custom_field_configs:
|
|
page_key = cfg.section_key or fallback
|
|
if page_key not in column_by_key:
|
|
page_key = fallback
|
|
column_by_key[page_key]['items'].append(
|
|
{
|
|
'field_name': f'custom__{cfg.field_key}',
|
|
'label': cfg.translated_label(language_code),
|
|
'label_de': cfg.label,
|
|
'label_en': cfg.label_en,
|
|
'is_visible': cfg.is_active,
|
|
'is_required': cfg.is_required,
|
|
'locked': False,
|
|
'page_key': page_key,
|
|
'is_custom': True,
|
|
'sort_order': cfg.sort_order,
|
|
}
|
|
)
|
|
for column in columns:
|
|
column['items'].sort(key=lambda item: (item.get('sort_order', 9999), item['field_name']))
|
|
|
|
section_rule_items = []
|
|
if section_order:
|
|
if active_structure_section not in section_order:
|
|
active_structure_section = section_order[0]
|
|
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
|
|
raw_title = str(section_labels.get(key, key))
|
|
display_title = re.sub(r'^\d+\.\s*', '', raw_title) if not is_custom else raw_title
|
|
section_rule_items.append(
|
|
{
|
|
'key': key,
|
|
'title': raw_title,
|
|
'display_title': display_title,
|
|
'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]),
|
|
}
|
|
)
|
|
section_rule_keys = [item['key'] for item in section_rule_items]
|
|
if section_rule_keys and active_section_rules_section not in section_rule_keys:
|
|
active_section_rules_section = section_rule_keys[0]
|
|
|
|
field_rule_items = []
|
|
for cfg in configs:
|
|
page_key = cfg.page_key or default_page_map.get(cfg.field_name, section_order[-1] if section_order else '')
|
|
field_rule_items.append(
|
|
{
|
|
'id': cfg.id,
|
|
'field_name': cfg.field_name,
|
|
'label': cfg.translated_label_override(language_code) or labels.get(cfg.field_name, cfg.field_name),
|
|
'page_key': page_key,
|
|
'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 and not can_override_locked_builder_rules,
|
|
'summary': _field_rule_summary(
|
|
is_visible=cfg.is_visible,
|
|
is_required=cfg.is_required,
|
|
locked=cfg.field_name in locked and not can_override_locked_builder_rules,
|
|
),
|
|
}
|
|
)
|
|
|
|
custom_field_groups = []
|
|
if section_order:
|
|
grouped_custom = {key: [] for key in section_order}
|
|
for cfg in custom_field_configs:
|
|
grouped_custom.setdefault(cfg.section_key, []).append(cfg)
|
|
for key in section_order:
|
|
custom_field_groups.append(
|
|
{
|
|
'key': key,
|
|
'title': section_labels.get(key, key),
|
|
'items': grouped_custom.get(key, []),
|
|
}
|
|
)
|
|
custom_section_field_counts: dict[str, int] = {}
|
|
for cfg in custom_field_configs:
|
|
custom_section_field_counts[cfg.section_key] = custom_section_field_counts.get(cfg.section_key, 0) + 1
|
|
for cfg in custom_section_configs:
|
|
cfg.custom_field_count = custom_section_field_counts.get(cfg.section_key, 0)
|
|
|
|
field_rule_groups = []
|
|
if section_order:
|
|
grouped_rules = {key: [] for key in section_order}
|
|
for item in field_rule_items:
|
|
grouped_rules.setdefault(item['page_key'], []).append(item)
|
|
for key in section_order:
|
|
field_rule_groups.append(
|
|
{
|
|
'key': key,
|
|
'title': section_labels.get(key, key),
|
|
'items': grouped_rules.get(key, []),
|
|
}
|
|
)
|
|
field_rule_group_keys = [group['key'] for group in field_rule_groups]
|
|
if field_rule_group_keys and active_field_rules_section not in field_rule_group_keys:
|
|
active_field_rules_section = field_rule_group_keys[0]
|
|
|
|
field_text_groups = []
|
|
if section_order:
|
|
grouped_texts = {key: [] for key in section_order}
|
|
for cfg in configs:
|
|
page_key = cfg.page_key or default_page_map.get(cfg.field_name, section_order[-1] if section_order else '')
|
|
grouped_texts.setdefault(page_key, []).append(cfg)
|
|
for key in section_order:
|
|
field_text_groups.append(
|
|
{
|
|
'key': key,
|
|
'title': section_labels.get(key, key),
|
|
'items': grouped_texts.get(key, []),
|
|
}
|
|
)
|
|
field_text_group_keys = [group['key'] for group in field_text_groups]
|
|
if field_text_group_keys and active_field_texts_section not in field_text_group_keys:
|
|
active_field_texts_section = field_text_group_keys[0]
|
|
custom_field_group_keys = [group['key'] for group in custom_field_groups]
|
|
if custom_field_group_keys and active_custom_fields_section not in custom_field_group_keys:
|
|
active_custom_fields_section = custom_field_group_keys[0]
|
|
|
|
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)))
|
|
for cfg in custom_field_configs:
|
|
conditional_field_choices.append((f'custom__{cfg.field_key}', cfg.translated_label(language_code)))
|
|
conditional_field_label_map = {value: label for value, label in conditional_field_choices}
|
|
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'),
|
|
}
|
|
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.'),
|
|
}
|
|
for target_key, cfg in conditional_rule_configs.items():
|
|
clauses = list(cfg.clauses or [])
|
|
while len(clauses) < 2:
|
|
clauses.append({'field': '', 'operator': 'equals', 'value': ''})
|
|
if target_key.startswith('custom__'):
|
|
custom_field_key = target_key.replace('custom__', '', 1)
|
|
custom_field = next((item for item in custom_field_configs if item.field_key == custom_field_key), None)
|
|
target_title = custom_field.translated_label(language_code) if custom_field else target_key
|
|
target_description = _('Steuert die Sichtbarkeit dieses benutzerdefinierten Feldes.')
|
|
target_fields = [target_title]
|
|
else:
|
|
target_title = conditional_target_titles.get(target_key, target_key)
|
|
target_description = conditional_target_descriptions.get(target_key, '')
|
|
target_fields = [labels.get(name, name) for name in ONBOARDING_GROUPS.get(target_key, [])]
|
|
conditional_rule_items.append(
|
|
{
|
|
'target_key': target_key,
|
|
'title': target_title,
|
|
'description': target_description,
|
|
'is_active': cfg.is_active,
|
|
'clauses': clauses[:2],
|
|
'summary': _conditional_rule_summary(clauses[:2], conditional_field_label_map),
|
|
'field_choices': conditional_field_choices,
|
|
'operator_choices': CONDITIONAL_RULE_OPERATOR_CHOICES,
|
|
'target_fields': target_fields,
|
|
}
|
|
)
|
|
conditional_rule_keys = [item['target_key'] for item in conditional_rule_items]
|
|
if conditional_rule_keys and active_conditional_target not in conditional_rule_keys:
|
|
active_conditional_target = conditional_rule_keys[0]
|
|
|
|
preview_sections = []
|
|
if section_order:
|
|
field_rule_group_map = {group['key']: group['items'] for group in field_rule_groups}
|
|
for key in section_order:
|
|
section_cfg = section_configs.get(key)
|
|
section_locked = key in locked_sections
|
|
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']
|
|
]
|
|
visible_items.extend(
|
|
[
|
|
{
|
|
'label': cfg.translated_label(language_code),
|
|
'locked': False,
|
|
}
|
|
for cfg in custom_field_configs
|
|
if cfg.section_key == key and cfg.is_active
|
|
]
|
|
)
|
|
if section_visible:
|
|
preview_sections.append(
|
|
{
|
|
'key': key,
|
|
'title': section_labels.get(key, key),
|
|
'items': visible_items,
|
|
}
|
|
)
|
|
|
|
locked_field_count = len([item for item in field_rule_items if item['locked']])
|
|
hidden_field_count = len([item for item in field_rule_items if not item['is_visible']])
|
|
configurable_field_count = len(field_rule_items) - locked_field_count
|
|
hidden_section_count = len([item for item in section_rule_items if not item['is_visible']]) if section_rule_items else 0
|
|
builder_summary = {
|
|
'locked_field_count': locked_field_count,
|
|
'configurable_field_count': configurable_field_count,
|
|
'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]),
|
|
}
|
|
module_labels = {
|
|
'structure': _('Struktur & Reihenfolge'),
|
|
'section-rules': _('Abschnitte'),
|
|
'field-rules': _('Feldregeln'),
|
|
'conditional-rules': _('Bedingte Logik'),
|
|
'options': _('Optionen'),
|
|
'field-texts': _('Feldtexte'),
|
|
'custom-sections': _('Eigene Abschnitte'),
|
|
'custom-fields': _('Eigene Felder'),
|
|
'preview': _('Vorschau'),
|
|
}
|
|
option_category_labels = dict(_translate_choice_list(FormOption.CATEGORY_CHOICES))
|
|
form_type_labels = {
|
|
'onboarding': _('Onboarding'),
|
|
'offboarding': _('Offboarding'),
|
|
}
|
|
active_focus_label = ''
|
|
if active_module == 'structure' and active_structure_section:
|
|
active_focus_label = section_labels.get(active_structure_section, active_structure_section)
|
|
elif active_module == 'section-rules' and section_rule_items:
|
|
active_focus_label = _('Alle Abschnitte')
|
|
elif active_module == 'field-rules' and active_field_rules_section:
|
|
active_focus_label = section_labels.get(active_field_rules_section, active_field_rules_section)
|
|
elif active_module == 'conditional-rules' and active_conditional_target:
|
|
active_focus_label = next((item['title'] for item in conditional_rule_items if item['target_key'] == active_conditional_target), active_conditional_target)
|
|
elif active_module == 'options':
|
|
active_focus_label = option_category_labels.get(option_category, option_category)
|
|
elif active_module == 'field-texts' and active_field_texts_section:
|
|
active_focus_label = section_labels.get(active_field_texts_section, active_field_texts_section)
|
|
elif active_module == 'custom-sections':
|
|
active_focus_label = _('Onboarding')
|
|
elif active_module == 'custom-fields' and active_custom_fields_section:
|
|
active_focus_label = section_labels.get(active_custom_fields_section, active_custom_fields_section)
|
|
elif active_module == 'preview':
|
|
active_focus_label = _('Live-Vorschau')
|
|
|
|
return render(
|
|
request,
|
|
'workflows/form_builder.html',
|
|
{
|
|
'form_type': form_type,
|
|
'columns': columns,
|
|
'form_types': [('onboarding', _('Onboarding')), ('offboarding', _('Offboarding'))],
|
|
'option_categories': _translate_choice_list(FormOption.CATEGORY_CHOICES),
|
|
'selected_option_category': option_category,
|
|
'option_items': FormOption.objects.filter(category=option_category).order_by('sort_order', 'label'),
|
|
'field_text_items': configs,
|
|
'field_rule_items': field_rule_items,
|
|
'field_rule_groups': field_rule_groups,
|
|
'field_text_groups': field_text_groups,
|
|
'preview_sections': preview_sections,
|
|
'section_rule_items': section_rule_items,
|
|
'builder_summary': builder_summary,
|
|
'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,
|
|
'active_module': active_module,
|
|
'active_form_type_label': form_type_labels.get(form_type, form_type),
|
|
'active_module_label': module_labels.get(active_module, active_module),
|
|
'active_focus_label': active_focus_label,
|
|
'active_structure_section': active_structure_section,
|
|
'active_field_rules_section': active_field_rules_section,
|
|
'active_field_texts_section': active_field_texts_section,
|
|
'active_custom_fields_section': active_custom_fields_section,
|
|
'active_section_rules_section': active_section_rules_section,
|
|
'active_conditional_target': active_conditional_target,
|
|
'available_presets': FORM_PRESETS.get(form_type, {}),
|
|
'can_override_locked_builder_rules': can_override_locked_builder_rules,
|
|
},
|
|
)
|
|
|
|
|
|
def form_builder_save_order_impl(request, *, audit_fn):
|
|
try:
|
|
payload = json.loads(request.body.decode('utf-8'))
|
|
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
return JsonResponse({'ok': False, 'error': _('Ungültige JSON-Daten.')}, status=400)
|
|
|
|
form_type = payload.get('form_type')
|
|
if form_type not in DEFAULT_FIELD_ORDER:
|
|
return JsonResponse({'ok': False, 'error': _('Ungültiger Formulartyp.')}, status=400)
|
|
default_page_map = get_default_page_map(form_type)
|
|
|
|
columns = payload.get('columns')
|
|
if not isinstance(columns, dict):
|
|
return JsonResponse({'ok': False, 'error': _('Spalten-Daten fehlen.')}, status=400)
|
|
|
|
configs = list(FormFieldConfig.objects.filter(form_type=form_type).order_by('sort_order', 'field_name'))
|
|
custom_configs = list(FormCustomFieldConfig.objects.filter(form_type=form_type).order_by('sort_order', 'field_key'))
|
|
allowed_names = {cfg.field_name for cfg in configs} | {f'custom__{cfg.field_key}' for cfg in custom_configs}
|
|
seen = set()
|
|
|
|
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}
|
|
sort_order = 0
|
|
|
|
for column_key in allowed_columns:
|
|
names = columns.get(column_key, [])
|
|
if not isinstance(names, list):
|
|
return JsonResponse({'ok': False, 'error': _('Ungültige Spalte: %(column)s') % {'column': column_key}}, status=400)
|
|
|
|
for name in names:
|
|
if not isinstance(name, str):
|
|
continue
|
|
if name not in allowed_names or name in seen:
|
|
continue
|
|
seen.add(name)
|
|
if name in name_to_cfg:
|
|
cfg = name_to_cfg[name]
|
|
cfg.sort_order = sort_order
|
|
cfg.page_key = column_key
|
|
else:
|
|
cfg = custom_name_to_cfg[name]
|
|
cfg.sort_order = sort_order
|
|
cfg.section_key = column_key
|
|
sort_order += 1
|
|
|
|
missing = [cfg.field_name for cfg in configs if cfg.field_name not in seen]
|
|
for name in missing:
|
|
cfg = name_to_cfg[name]
|
|
cfg.sort_order = sort_order
|
|
sort_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
|
|
cfg.section_key = cfg.section_key or fallback_section
|
|
|
|
FormFieldConfig.objects.bulk_update(configs, ['sort_order', 'page_key'])
|
|
if custom_configs:
|
|
FormCustomFieldConfig.objects.bulk_update(custom_configs, ['sort_order', 'section_key'])
|
|
saved_count = len(configs) + len(custom_configs)
|
|
audit_fn(request, 'form_layout_saved', target_type='form_config', target_label=form_type, details={'saved_count': saved_count})
|
|
return JsonResponse({'ok': True, 'saved_count': saved_count})
|