from django.utils.translation import gettext as _, gettext_lazy from .form_builder import ( LOCKED_SECTION_RULES, OFFBOARDING_PAGE_ORDER, ensure_form_conditional_rule_configs, get_section_definitions, ) ONBOARDING_GROUPS = { 'business-card-box': ['business_card_name', 'business_card_title', 'business_card_email', 'business_card_phone'], 'employment-end-box': ['employment_end_date'], 'group-mailboxes-box': ['group_mailboxes'], 'extra-hardware-box': ['additional_hardware_multi', 'additional_hardware_other'], 'extra-software-box': ['additional_software_multi', 'additional_software'], 'extra-access-box': ['additional_access_text'], 'successor-box': ['successor_name', 'inherit_phone_number_choice'], } ONBOARDING_INLINE_CHECKS = {'order_business_cards', 'agreement_confirm'} ONBOARDING_CHECKBOX_LISTS = { 'needed_devices_multi', 'additional_hardware_multi', 'needed_software_multi', 'additional_software_multi', 'needed_accesses_multi', 'needed_workspace_groups_multi', 'needed_resources_multi', } CONDITIONAL_RULE_OPERATOR_CHOICES = [ ('checked', _('ist aktiviert')), ('equals', _('ist gleich')), ('not_equals', _('ist nicht gleich')), ] 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')}, 'itsetup': {'title': gettext_lazy('IT-Setup'), 'subtitle': gettext_lazy('Geräte, Software und Zugänge')}, 'abschluss': {'title': gettext_lazy('Abschluss'), 'subtitle': gettext_lazy('Notizen und Freigabe')}, } OFFBOARDING_SECTION_META = { 'mitarbeitende': {'title': gettext_lazy('Mitarbeitende'), 'subtitle': gettext_lazy('Person, Rolle und Bereich')}, 'austritt': {'title': gettext_lazy('Austritt'), 'subtitle': gettext_lazy('Letzter Arbeitstag')}, 'abschluss': {'title': gettext_lazy('Abschluss'), 'subtitle': gettext_lazy('Hinweise und Abschlussnotizen')}, } def field_rule_summary(*, is_visible: bool, is_required, locked: bool) -> str: if locked: return str(_('Fixes Kernfeld, immer sichtbar.')) if not is_visible: return str(_('Ausgeblendet, erscheint nicht im Formular.')) if is_required is True: return str(_('Sichtbar und als Pflichtfeld markiert.')) if is_required is False: return str(_('Sichtbar und optional.')) return str(_('Sichtbar mit Standardverhalten.')) def conditional_clause_sentence(clause: dict, field_label_map: dict[str, str]) -> str: field_name = (clause.get('field') or '').strip() operator = (clause.get('operator') or '').strip() value = clause.get('value') if not field_name or not operator: return '' field_label = field_label_map.get(field_name, field_name) if operator == 'checked': return _('%(field)s ist aktiviert') % {'field': field_label} if operator == 'equals': if value not in (None, ''): return _('%(field)s ist gleich %(value)s') % {'field': field_label, 'value': value} return _('%(field)s ist gleich') % {'field': field_label} if operator == 'not_equals': if value not in (None, ''): return _('%(field)s ist nicht gleich %(value)s') % {'field': field_label, 'value': value} return _('%(field)s ist nicht gleich') % {'field': field_label} return _('%(field)s erfüllt die Bedingung') % {'field': field_label} def conditional_rule_summary(clauses: list[dict], field_label_map: dict[str, str]) -> str: active_clauses = [clause for clause in clauses if clause.get('field') and clause.get('operator')] if not active_clauses: return str(_('Immer sichtbar.')) parts = [str(conditional_clause_sentence(clause, field_label_map)) for clause in active_clauses] return str(_('Sichtbar, wenn %(conditions)s.') % {'conditions': ' und '.join(parts)}) 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 active_conditional_target_keys(form_type: str) -> set[str]: return set(normalized_conditional_rule_payload(form_type).keys()) def translate_choice_list(choices): return [(value, str(label)) for value, label in choices] def build_onboarding_layout(form) -> list[dict]: ordered_names = list(form.fields.keys()) group_by_field = {} for group_id, group_fields in ONBOARDING_GROUPS.items(): for name in group_fields: group_by_field[name] = group_id conditional_target_keys = active_conditional_target_keys('onboarding') rendered_groups = set() consumed = set() blocks = [] for field_name in ordered_names: if field_name in consumed: continue group_id = group_by_field.get(field_name) if group_id: if group_id in rendered_groups: continue group_fields = [form[name] for name in ONBOARDING_GROUPS[group_id] if name in form.fields] if not group_fields: continue blocks.append( { 'kind': 'group', 'id': group_id, 'hidden_default': group_id in conditional_target_keys, 'fields': group_fields, } ) rendered_groups.add(group_id) consumed.update([f.name for f in group_fields]) continue if field_name.startswith('custom__') and field_name in conditional_target_keys: blocks.append( { 'kind': 'group', 'id': field_name, 'hidden_default': True, 'fields': [form[field_name]], } ) consumed.add(field_name) continue blocks.append({'kind': 'field', 'field': form[field_name]}) consumed.add(field_name) return blocks def section_for_block(block: dict, field_pages: dict[str, str]) -> str: if block['kind'] == 'field': return field_pages.get(block['field'].name, 'abschluss') fields = block.get('fields') or [] if not fields: return 'abschluss' return field_pages.get(fields[0].name, 'abschluss') def build_onboarding_sections(blocks: list[dict], field_pages: dict[str, str], visible_section_keys: set[str] | None = None) -> list[dict]: 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(section_order) sections = [] custom_section_keys = {item['key'] for item in section_defs if item.get('is_custom')} for key in section_order: if key not in visible_keys: continue blocks_for_section = grouped[key] has_custom_checkbox_fields = False for block in blocks_for_section: candidate_fields = [block['field']] if block['kind'] == 'field' else (block.get('fields') or []) for bound_field in candidate_fields: widget_type = getattr(getattr(bound_field.field, 'widget', None), 'input_type', '') if bound_field.name.startswith('custom__') and widget_type == 'checkbox': has_custom_checkbox_fields = True break if has_custom_checkbox_fields: break sections.append( { 'key': key, 'title': section_titles.get(key, ONBOARDING_SECTION_META.get(key, {}).get('title', key)), 'subtitle': ONBOARDING_SECTION_META.get(key, {}).get('subtitle', ''), 'blocks': blocks_for_section, 'is_custom': key in custom_section_keys, 'has_custom_checkbox_fields': has_custom_checkbox_fields, } ) return sections def build_offboarding_sections(form, visible_section_keys: set[str] | None = None) -> list[dict]: field_pages = getattr(form, '_field_page_keys', {}) grouped = {key: [] for key in OFFBOARDING_PAGE_ORDER} for field_name in form.fields.keys(): section_key = field_pages.get(field_name, 'abschluss') if section_key not in grouped: section_key = 'abschluss' grouped[section_key].append(form[field_name]) visible_keys = visible_section_keys or set(OFFBOARDING_PAGE_ORDER) return [ { 'key': key, 'title': OFFBOARDING_SECTION_META[key]['title'], 'subtitle': OFFBOARDING_SECTION_META[key]['subtitle'], 'fields': grouped[key], } for key in OFFBOARDING_PAGE_ORDER if key in visible_keys and grouped[key] ]