From 853ffcf5d02f1b7a4b297a0efb9f6d2593288e33 Mon Sep 17 00:00:00 2001 From: Marsway Date: Thu, 30 Apr 2026 18:18:05 +0800 Subject: [PATCH] update --- extensions/sync_ehr_to_ad/README.md | 2 +- extensions/sync_ehr_to_ad/job.py | 112 +++++++--------------------- 2 files changed, 27 insertions(+), 87 deletions(-) diff --git a/extensions/sync_ehr_to_ad/README.md b/extensions/sync_ehr_to_ad/README.md index e8ada69..f9c60a4 100644 --- a/extensions/sync_ehr_to_ad/README.md +++ b/extensions/sync_ehr_to_ad/README.md @@ -65,7 +65,7 @@ - `ldap_verify_tls`: 是否校验证书,默认 `true`。 - `proxy_alias_domain`: 生成 `smtp:@domain` 别名时使用的域名。 - `department_code_ad_attribute`: 部门编码写入的 AD 属性,默认 `departmentNumber`。 -- `street_address_key`: 具体地址字段编码,默认 `extgzddxx_606508_618643707`,写入 AD 的 `streetAddress`。 +- `street_address_key`: 具体地址字段编码,默认 `WorkSpacevalue`,写入 AD 的 `streetAddress`;取不到时会兜底尝试 `extgzddxx_606508_618643707`。 - `default_company`: 固定公司名;不传时尝试取 EHR 根组织名称。 - `location_mappings`: 工作地点到 AD 国家、省、市字段的映射。 diff --git a/extensions/sync_ehr_to_ad/job.py b/extensions/sync_ehr_to_ad/job.py index 21b0e10..b51d709 100644 --- a/extensions/sync_ehr_to_ad/job.py +++ b/extensions/sync_ehr_to_ad/job.py @@ -13,7 +13,8 @@ logger = logging.getLogger("connecthub.extensions.sync_ehr_to_ad") _EHR_AD_ACCOUNT_KEY = "extADAccountName_606508_511687157" _EHR_WORK_LOCATION_TEXT_KEY = "extgzddxx1_606508_892394263Text" -_EHR_STREET_ADDRESS_KEY = "extgzddxx_606508_618643707" +_EHR_STREET_ADDRESS_KEY = "WorkSpacevalue" +_EHR_STREET_ADDRESS_FALLBACK_KEYS = ("extgzddxx_606508_618643707",) def _to_bool_or_none(v: Any) -> bool | None: @@ -38,17 +39,12 @@ def _to_int_safe(v: Any) -> int: def _scalar_value(raw: Any) -> str: if isinstance(raw, dict): - val = None - for value_key in ("value", "showValue", "text", "Text", "displayValue", "DisplayValue"): - val = raw.get(value_key) - if val is not None and str(val).strip() != "": - break - if isinstance(val, (dict, list, tuple, set)): - return _scalar_value(val) + val = raw.get("value") + if val is None or str(val).strip() == "": + val = raw.get("showValue") return str(val or "").strip() if isinstance(raw, (list, tuple, set)): - values = [_scalar_value(x) for x in raw] - return "\n".join([x for x in values if x]) + return "\n".join([str(x).strip() for x in raw if x is not None and str(x).strip() != ""]) return str(raw or "").strip() @@ -75,76 +71,6 @@ def _translate_value(node: dict[str, Any], key: str | None) -> str: return "" -def _candidate_keys(key: str | None) -> list[str]: - if not key: - return [] - candidates = [key, f"{key}Text"] - if key.endswith("Text"): - candidates.insert(0, key) - candidates.append(key[:-4]) - out: list[str] = [] - seen: set[str] = set() - for candidate in candidates: - s = str(candidate or "").strip() - if s and s not in seen: - seen.add(s) - out.append(s) - return out - - -def _deep_field_value(node: Any, key: str | None) -> str: - candidates = set(_candidate_keys(key)) - if not candidates: - return "" - if isinstance(node, dict): - for candidate in candidates: - if candidate in node: - s = _scalar_value(node.get(candidate)) - if s: - return s - for value in node.values(): - s = _deep_field_value(value, key) - if s: - return s - elif isinstance(node, list): - for value in node: - s = _deep_field_value(value, key) - if s: - return s - return "" - - -def _collect_matching_keys(node: Any, keyword: str, *, limit: int = 30) -> list[str]: - matches: list[str] = [] - seen: set[str] = set() - needle = str(keyword or "").lower() - if not needle: - return matches - - def walk(current: Any) -> None: - if len(matches) >= limit: - return - if isinstance(current, dict): - for k, v in current.items(): - key = str(k) - if needle in key.lower() and key not in seen: - seen.add(key) - matches.append(key) - if len(matches) >= limit: - return - walk(v) - if len(matches) >= limit: - return - elif isinstance(current, list): - for v in current: - walk(v) - if len(matches) >= limit: - return - - walk(node) - return matches - - def _field_value(item: dict[str, Any], key: str) -> str: emp = item.get("employeeInfo") or {} rec = item.get("recordInfo") or {} @@ -160,9 +86,18 @@ def _field_value(item: dict[str, Any], key: str) -> str: s = _custom_prop_value(node.get("customProperties"), key) if s: return s - s = _deep_field_value(node, key) - if s: - return s + for child in node.values(): + if not isinstance(child, dict): + continue + s = _scalar_value(child.get(key)) + if s: + return s + s = _translate_value(child, key) + if s: + return s + s = _custom_prop_value(child.get("customProperties"), key) + if s: + return s return "" @@ -620,14 +555,19 @@ class SyncEhrToAdUserJob(BaseJob): office = _field_translate_or_value(item, "Place") workplace_text = _field_translate_or_value(item, work_location_text_key) street_address = _field_value(item, street_address_key) + if not street_address: + for fallback_key in _EHR_STREET_ADDRESS_FALLBACK_KEYS: + if fallback_key == street_address_key: + continue + street_address = _field_value(item, fallback_key) + if street_address: + break if verbose_trace: - address_candidate_keys = _collect_matching_keys(item, "extgzddxx") if not street_address else [] logger.info( - "AD 地址字段解析:sam=%s street_address_key=%s streetAddress=%r candidate_keys=%s", + "AD 地址字段解析:sam=%s street_address_key=%s streetAddress=%r", sam, street_address_key, street_address, - address_candidate_keys, ) company = default_company or _root_org_name(org, org_by_oid) location_attrs = _location_from_workplace(workplace_text or office, location_mappings)