Files
workdock-platform/backend/workflows/pdf_sections.py

312 lines
13 KiB
Python

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