from __future__ import annotations from collections import OrderedDict from datetime import date, datetime from django.utils import formats from django.utils.translation import override from .form_builder import ( LOCKED_FIELD_RULES, LOCKED_SECTION_RULES, get_custom_field_configs, ensure_form_field_configs, ensure_form_section_configs, get_default_page_map, get_section_labels, get_section_order, ) from .forms import OffboardingRequestForm, OnboardingRequestForm from .models import FormCustomFieldConfig PDF_SECTION_TITLES = { "onboarding": { "stammdaten": "Stammdaten", "vertrag": "Vertrag", "itsetup": "IT-Setup", "abschluss": "Abschluss", }, "offboarding": { "mitarbeitende": "Mitarbeitende", "austritt": "Austritt", "abschluss": "Abschluss", }, } PDF_FIELD_LABELS = { "onboarding": { "full_name": {"de": "Name", "en": "Name"}, }, "offboarding": { "full_name": {"de": "Name", "en": "Name"}, }, } PDF_EXCLUDED_FIELDS = { "onboarding": { "first_name", "last_name", "onboarded_by_email", "agreement_confirm", "signature_url", "signature_image", }, "offboarding": set(), } PDF_BOOLEAN_CONTROL_FIELDS = { "onboarding": { "order_business_cards", "group_mailboxes_required_choice", "additional_hardware_needed_choice", "additional_software_needed_choice", "additional_access_needed_choice", "successor_required_choice", "inherit_phone_number_choice", }, "offboarding": set(), } def _normalized_lang(language_code: str | None) -> str: return (language_code or "de").split("-")[0].lower() or "de" def _yes_no_text(language_code: str | None) -> tuple[str, str]: lang = _normalized_lang(language_code) if lang == "en": return "Yes", "No" return "Ja", "Nein" def _not_available_text(language_code: str | None) -> str: return "Not provided" if _normalized_lang(language_code) == "en" else "Keine Angabe" def _split_name(full_name: str) -> tuple[str, str]: parts = (full_name or "").split() if not parts: return "", "" return parts[0], " ".join(parts[1:]) def _split_multiline(value: str) -> list[str]: return [line.strip() for line in (value or "").splitlines() if line.strip()] def _format_date(value, language_code: str | None) -> str: if not value: return "" if isinstance(value, datetime): with override(_normalized_lang(language_code)): return formats.date_format(value, "DATETIME_FORMAT", use_l10n=True) if isinstance(value, date): with override(_normalized_lang(language_code)): return formats.date_format(value, "DATE_FORMAT", use_l10n=True) return str(value) def _coerce_text(value) -> str: if value is None: return "" if isinstance(value, (date, datetime)): return str(value) return str(value).strip() def _field_value_from_request(form_type: str, request_obj, field_name: str, language_code: str | None): yes_text, no_text = _yes_no_text(language_code) first_name, last_name = _split_name(getattr(request_obj, "full_name", "")) onboarding_map = { "first_name": first_name, "last_name": last_name, "full_name": getattr(request_obj, "full_name", ""), "gender": getattr(request_obj, "get_gender_display", lambda: "")() or getattr(request_obj, "gender", ""), "job_title": getattr(request_obj, "job_title", ""), "department": getattr(request_obj, "department", ""), "work_email": getattr(request_obj, "work_email", ""), "order_business_cards": yes_text if getattr(request_obj, "order_business_cards", False) else no_text, "business_card_name": getattr(request_obj, "business_card_name", ""), "business_card_title": getattr(request_obj, "business_card_title", ""), "business_card_email": getattr(request_obj, "business_card_email", ""), "business_card_phone": getattr(request_obj, "business_card_phone", ""), "contract_start": _format_date(getattr(request_obj, "contract_start", None), language_code), "employment_type": getattr(request_obj, "get_employment_type_display", lambda: "")() or getattr(request_obj, "employment_type", ""), "employment_end_date": _format_date(getattr(request_obj, "employment_end_date", None), language_code), "handover_date": _format_date(getattr(request_obj, "handover_date", None), language_code), "group_mailboxes_required_choice": yes_text if getattr(request_obj, "group_mailboxes_required", False) else no_text, "group_mailboxes": _split_multiline(getattr(request_obj, "group_mailboxes", "")), "needed_devices_multi": _split_multiline(getattr(request_obj, "needed_devices", "")), "additional_hardware_needed_choice": yes_text if getattr(request_obj, "additional_hardware_needed", False) else no_text, "additional_hardware_multi": _split_multiline(getattr(request_obj, "additional_hardware", "")), "additional_hardware_other": getattr(request_obj, "additional_hardware_other", ""), "needed_software_multi": _split_multiline(getattr(request_obj, "needed_software", "")), "additional_software_needed_choice": yes_text if getattr(request_obj, "additional_software_needed", False) else no_text, "additional_software_multi": _split_multiline(getattr(request_obj, "additional_software", "")), "additional_software": getattr(request_obj, "additional_software", ""), "needed_accesses_multi": _split_multiline(getattr(request_obj, "needed_accesses", "")), "additional_access_needed_choice": yes_text if getattr(request_obj, "additional_access_needed", False) else no_text, "additional_access_text": _split_multiline(getattr(request_obj, "additional_access_text", "")), "needed_workspace_groups_multi": _split_multiline(getattr(request_obj, "needed_workspace_groups", "")), "needed_resources_multi": _split_multiline(getattr(request_obj, "needed_resources", "")), "successor_required_choice": yes_text if getattr(request_obj, "successor_required", False) else no_text, "successor_name": getattr(request_obj, "successor_name", ""), "inherit_phone_number_choice": yes_text if getattr(request_obj, "inherit_phone_number", False) else no_text, "phone_number_choice": getattr(request_obj, "phone_number", ""), "additional_notes": getattr(request_obj, "additional_notes", ""), "signature_url": getattr(request_obj, "signature_url", ""), "signature_image": getattr(getattr(request_obj, "signature_image", None), "name", "") or "", "onboarded_by_email": getattr(request_obj, "onboarded_by_email", ""), "agreement_confirm": yes_text if _coerce_text(getattr(request_obj, "agreement", "")) else no_text, } offboarding_map = { "full_name": getattr(request_obj, "full_name", ""), "work_email": getattr(request_obj, "work_email", ""), "department": getattr(request_obj, "department", ""), "job_title": getattr(request_obj, "job_title", ""), "last_working_day": _format_date(getattr(request_obj, "last_working_day", None), language_code), "notes": getattr(request_obj, "notes", ""), } value_map = onboarding_map if form_type == "onboarding" else offboarding_map return value_map.get(field_name, "") def _field_kind(value) -> str: if isinstance(value, list): return "list" return "text" def _is_empty_value(value) -> bool: if isinstance(value, list): return len(value) == 0 return _coerce_text(value) == "" def _field_meta(form_type: str, field_name: str, language_code: str | None) -> tuple[str, str]: custom_label = PDF_FIELD_LABELS.get(form_type, {}).get(field_name, {}) if custom_label: return custom_label.get(_normalized_lang(language_code), field_name), "" form_class = OnboardingRequestForm if form_type == "onboarding" else OffboardingRequestForm base_field = form_class.base_fields.get(field_name) if not base_field: return field_name, "" with override(_normalized_lang(language_code)): label = str(base_field.label or field_name) help_text = str(base_field.help_text or "").strip() return label, help_text def build_pdf_sections(form_type: str, request_obj, language_code: str | None = None) -> list[dict]: language_code = _normalized_lang(language_code or getattr(request_obj, "preferred_language", None)) default_page_map = get_default_page_map(form_type) section_order = get_section_order(form_type) section_labels = get_section_labels(form_type) field_names = list(default_page_map.keys()) configs = ensure_form_field_configs(form_type, field_names) section_configs = ensure_form_section_configs(form_type) locked_fields = LOCKED_FIELD_RULES.get(form_type, set()) locked_sections = LOCKED_SECTION_RULES.get(form_type, set()) sections: OrderedDict[str, dict] = OrderedDict() for key in section_order: section_cfg = section_configs.get(key) is_visible = True if key not in locked_sections and section_cfg is not None: is_visible = bool(section_cfg.is_visible) if not is_visible: continue sections[key] = { "key": key, "title": PDF_SECTION_TITLES.get(form_type, {}).get(key, section_labels.get(key, key)), "fields": [], } ordered_configs = sorted( configs.values(), key=lambda cfg: (cfg.sort_order, cfg.field_name), ) for cfg in ordered_configs: field_name = cfg.field_name section_key = cfg.page_key or default_page_map.get(field_name, "") if section_key not in sections: continue if field_name in PDF_EXCLUDED_FIELDS.get(form_type, set()): continue if field_name not in locked_fields and not cfg.is_visible: continue base_label, base_help_text = _field_meta(form_type, field_name, language_code) label = cfg.translated_label_override(language_code) or base_label help_text = cfg.translated_help_text_override(language_code) or base_help_text raw_value = _field_value_from_request(form_type, request_obj, field_name, language_code) sections[section_key]["fields"].append( { "name": field_name, "label": label, "help_text": help_text, "kind": _field_kind(raw_value), "value": raw_value, "is_empty": _is_empty_value(raw_value), "is_locked": field_name in locked_fields, } ) custom_values = getattr(request_obj, 'custom_field_values', {}) or {} for cfg in get_custom_field_configs(form_type): if cfg.section_key not in sections: continue raw_value = custom_values.get(cfg.field_key) if cfg.field_type == FormCustomFieldConfig.FIELD_TYPE_CHECKBOX: raw_value = _yes_no_text(language_code)[0] if raw_value else '' sections[cfg.section_key]["fields"].append( { "name": f"custom__{cfg.field_key}", "label": cfg.translated_label(language_code), "help_text": cfg.translated_help_text(language_code), "kind": _field_kind(raw_value), "value": raw_value, "is_empty": _is_empty_value(raw_value), "is_locked": False, } ) not_available = _not_available_text(language_code) result = [] for section in sections.values(): visible_fields = [] for field in section["fields"]: if ( field["name"] in PDF_BOOLEAN_CONTROL_FIELDS.get(form_type, set()) and _coerce_text(field["value"]).lower() in {"nein", "no"} ): continue display_value = field["value"] if field["kind"] == "list" else (_coerce_text(field["value"]) or not_available) visible_fields.append( { **field, "display_value": display_value, } ) render_fields = [field for field in visible_fields if not field["is_empty"]] scalar_fields = [field for field in render_fields if field["kind"] != "list"] list_fields = [field for field in render_fields if field["kind"] == "list"] scalar_rows = [scalar_fields[index:index + 2] for index in range(0, len(scalar_fields), 2)] for row in scalar_rows: if len(row) < 2: row.append(None) result.append( { "key": section["key"], "title": section["title"], "fields": visible_fields, "render_fields": render_fields, "scalar_fields": scalar_fields, "list_fields": list_fields, "scalar_rows": scalar_rows, "has_content": bool(render_fields), } ) return result