"""Identity field dictionary — the read-only "字段管理" catalog. A directly-viewable inventory of every identity attribute (user + dept), modelled on Authing's 字段管理. It is **read-only and truthful**: the field list is iterated straight off the SQLAlchemy table columns (so it can never drift from the real schema), and the per-field properties reflect ACTUAL code behaviour rather than a config that pretends to control rendering: * ``user_editable`` is derived live from ``MyProfilePatchRequest`` — the exact set of fields the self-service ``PATCH /auth/me/profile`` accepts (today: ``preferred_language`` only). If a future PR widens that body, this column updates automatically; no second copy to keep in sync. * ``source`` / ``console_visible`` / ``self_visible`` / ``label_key`` are curated in :data:`_USER_FIELD_META` for the meaningful fields; columns without an entry fall back to sensible defaults so a newly-added column still shows up (as ``hris`` / console-visible) instead of vanishing. This is documentation/governance visibility, not a control surface — see the 2026-06-09 decision (read-only dictionary over configurable field governance: the field set is stable for a 500-person single-org, and toggles that don't rewire the render layer would be fake). """ from __future__ import annotations from typing import Any from sqlalchemy import ( Boolean, Date, DateTime, Integer, Numeric, ) from sqlalchemy.types import JSON from app.auth.schemas import MyProfilePatchRequest from app.core.sql_types import GUID from app.identity.person_models import IdentityOrgNode, IdentityPerson # Self-service editable set = the live PATCH /auth/me/profile body fields. # Derived (not hand-listed) so it can't drift from the endpoint. _SELF_EDITABLE: frozenset[str] = frozenset(MyProfilePatchRequest.model_fields) # Columns we never surface in the dictionary: auth secrets + pure # soft-delete plumbing. Their existence isn't a secret, but listing them # adds noise / a wrong impression they're user-facing. _EXCLUDE: frozenset[str] = frozenset({"hashed_password", "deleted_at"}) # source ∈ hris | hr_managed | lifecycle | sso | system (same vocabulary # as the golden-record provenance block, §2.3.1). # Tuple: (source, console_visible, self_visible, label_key) _USER_FIELD_META: dict[str, tuple[str, bool, bool, str]] = { "id": ("system", True, True, "identityFields.user.id"), "work_email": ("hris", True, True, "identityFields.user.work_email"), "display_name": ("hris", True, True, "identityFields.user.display_name"), "first_name": ("hris", True, False, "identityFields.user.first_name"), "last_name": ("hris", True, False, "identityFields.user.last_name"), "name_pinyin": ("hris", True, False, "identityFields.user.name_pinyin"), "name_pinyin_abbr": ("hris", False, False, "identityFields.user.name_pinyin_abbr"), "employee_no": ("hris", True, True, "identityFields.user.employee_no"), "avatar_url": ("sso", True, True, "identityFields.user.avatar_url"), "employment_status": ("lifecycle", True, True, "identityFields.user.employment_status"), "is_china_employee": ("hris", False, False, "identityFields.user.is_china_employee"), "job_title": ("hris", True, True, "identityFields.user.job_title"), "manager_person_id": ("hris", True, True, "identityFields.user.manager_person_id"), "hrbp_person_id": ("hris", False, False, "identityFields.user.hrbp_person_id"), "work_location_code": ("hris", False, False, "identityFields.user.work_location_code"), "work_location_name": ("hris", True, True, "identityFields.user.work_location_name"), "country": ("hris", True, True, "identityFields.user.country"), "city": ("hris", True, True, "identityFields.user.city"), "company_code": ("hris", False, False, "identityFields.user.company_code"), "company_name": ("hris", True, True, "identityFields.user.company_name"), "cost_center": ("hris", True, False, "identityFields.user.cost_center"), "mobile_phone": ("hris", True, True, "identityFields.user.mobile_phone"), "personal_email": ("hris", True, True, "identityFields.user.personal_email"), "preferred_language": ("sso", True, True, "identityFields.user.preferred_language"), "email_aliases": ("sso", False, False, "identityFields.user.email_aliases"), "hire_date": ("hris", True, True, "identityFields.user.hire_date"), "termination_date": ("hris", True, False, "identityFields.user.termination_date"), "birthday": ("hr_managed", True, True, "identityFields.user.birthday"), "work_start_date": ("hris", True, True, "identityFields.user.work_start_date"), "seniority_date": ("hris", True, False, "identityFields.user.seniority_date"), "probation_end_date": ("hris", True, False, "identityFields.user.probation_end_date"), "fte": ("hris", True, False, "identityFields.user.fte"), "annual_leave_carry_legal_days": ("hr_managed", True, True, "identityFields.user.annual_leave_carry_legal_days"), "annual_leave_carry_welfare_days": ("hr_managed", True, True, "identityFields.user.annual_leave_carry_welfare_days"), "annual_leave_used_legal_days": ("hr_managed", True, True, "identityFields.user.annual_leave_used_legal_days"), "annual_leave_used_welfare_days": ("hr_managed", True, True, "identityFields.user.annual_leave_used_welfare_days"), "last_login_at": ("sso", True, False, "identityFields.user.last_login_at"), "hris_record_state": ("lifecycle", True, False, "identityFields.user.hris_record_state"), "hris_purged_at": ("lifecycle", False, False, "identityFields.user.hris_purged_at"), "hris_purged_reason": ("lifecycle", False, False, "identityFields.user.hris_purged_reason"), "it_info_sent_at": ("lifecycle", False, False, "identityFields.user.it_info_sent_at"), "it_info_sent_channels": ("lifecycle", False, False, "identityFields.user.it_info_sent_channels"), "force_logout_at": ("lifecycle", False, False, "identityFields.user.force_logout_at"), "source_system": ("system", True, False, "identityFields.user.source_system"), "hris_instance_id": ("system", False, False, "identityFields.user.hris_instance_id"), "hris_person_id": ("system", True, False, "identityFields.user.hris_person_id"), "source_updated_at": ("system", False, False, "identityFields.user.source_updated_at"), "last_synced_at": ("system", True, False, "identityFields.user.last_synced_at"), "created_at": ("system", True, False, "identityFields.user.created_at"), "updated_at": ("system", False, False, "identityFields.user.updated_at"), } _DEPT_FIELD_META: dict[str, tuple[str, bool, str]] = { # (source, console_visible, label_key) — depts have no self-service view. "id": ("system", False, "identityFields.dept.id"), "org_node_source_id": ("hris", True, "identityFields.dept.org_node_source_id"), "org_node_type": ("hris", True, "identityFields.dept.org_node_type"), "org_node_code": ("hris", True, "identityFields.dept.org_node_code"), "org_node_name": ("hris", True, "identityFields.dept.org_node_name"), "parent_org_node_source_id": ("hris", True, "identityFields.dept.parent_org_node_source_id"), "org_path": ("hris", True, "identityFields.dept.org_path"), "org_level": ("hris", True, "identityFields.dept.org_level"), "node_status": ("lifecycle", True, "identityFields.dept.node_status"), "source_system": ("system", False, "identityFields.dept.source_system"), "source_updated_at": ("system", False, "identityFields.dept.source_updated_at"), "last_synced_at": ("system", True, "identityFields.dept.last_synced_at"), "created_at": ("system", False, "identityFields.dept.created_at"), "updated_at": ("system", False, "identityFields.dept.updated_at"), } def _friendly_type(column) -> str: t = column.type if isinstance(t, GUID): return "uuid" if isinstance(t, Boolean): return "boolean" if isinstance(t, Date): return "date" if isinstance(t, DateTime): return "datetime" if isinstance(t, (Numeric, Integer)): return "number" if isinstance(t, JSON): return "json" return "string" def build_field_catalog() -> dict[str, Any]: """Assemble the read-only user + department field dictionaries by iterating the real table columns (completeness guaranteed) and enriching from the curated metadata above.""" user_fields: list[dict[str, Any]] = [] for col in IdentityPerson.__table__.columns: if col.name in _EXCLUDE: continue source, console_v, self_v, label = _USER_FIELD_META.get( col.name, ("hris", True, False, f"identityFields.user.{col.name}"), ) user_fields.append({ "key": col.name, "label_key": label, "type": _friendly_type(col), "source": source, "console_visible": console_v, "self_visible": self_v, "user_editable": col.name in _SELF_EDITABLE, }) dept_fields: list[dict[str, Any]] = [] for col in IdentityOrgNode.__table__.columns: if col.name in _EXCLUDE: continue source, console_v, label = _DEPT_FIELD_META.get( col.name, ("hris", True, f"identityFields.dept.{col.name}"), ) dept_fields.append({ "key": col.name, "label_key": label, "type": _friendly_type(col), "source": source, "console_visible": console_v, }) return {"user_fields": user_fields, "department_fields": dept_fields}