This commit is contained in:
Marsway 2026-05-08 17:35:33 +08:00
parent a4980481eb
commit bc83d4ccfc
2 changed files with 133 additions and 2 deletions

View File

@ -18,6 +18,7 @@
- `displayName``employeeInfo.customProperties.extDDNIC_606508_303466862`
- `department` 写入“EHR 部门编码 + EHR 部门名称”,例如 `V000076 IT`
- `streetAddress` 默认取 `translateProperties.extgzddxx1_606508_892394263Text`
- `st`、`l`、`physicalDeliveryOfficeName` 默认按 `extgzddxx1_606508_892394263` 的原始值做映射。
## public_cfg 示例
@ -68,9 +69,23 @@
- `ldap_verify_tls`: 是否校验证书,默认 `true`
- `proxy_alias_domain`: 生成 `smtp:<sAMAccountName>@domain` 别名时使用的域名。
- `department_code_ad_attribute`: 部门编码写入的 AD 属性,默认 `departmentNumber`
- `work_location_value_key`: 工作地点原始值字段编码,默认 `extgzddxx1_606508_892394263`,用于映射 AD 省、市、办公室。
- `street_address_key`: 具体地址字段编码,默认 `extgzddxx1_606508_892394263Text`,写入 AD 的 `streetAddress`;取不到时会兜底尝试 `WorkSpacevalue``extgzddxx_606508_618643707`
- `default_company`: 固定公司名;不传时尝试取 EHR 根组织名称。
- `location_mappings`: 工作地点到 AD 国家、省、市字段的映射。
- `location_mappings`: 工作地点原始值到 AD 国家、省、市、办公室字段的映射,可覆盖内置映射。
内置工作地点映射:
| EHR 原始值 | AD st | AD l | AD Office |
| --- | --- | --- | --- |
| `qjjdizcpjk` | 上海 | 上海 | 上海张江 |
| `j1lw6k13ya` | 陕西 | 西安 | 西安环普 |
| `uzwlfke8vd` | 广州 | 深圳 | 深圳田厦 |
| `mrtkjjhoxx` | 上海 | 上海 | 上海徐汇 |
| `t21uyq5qvx` | 北京 | 北京 | 北京 |
| `nhu45qlh80` | 四川 | 成都 | 成都 |
| `5sntzs5dlr` | 加拿大 | 加拿大 | 多伦多 |
| `1` | 上海 | 上海 | 上海闵行 |
## 指定用户同步示例

View File

@ -12,6 +12,7 @@ from extensions.sync_ehr_to_ad.api import ActiveDirectoryClient, SyncEhrToAdApi
logger = logging.getLogger("connecthub.extensions.sync_ehr_to_ad")
_EHR_AD_ACCOUNT_KEY = "extADAccountName_606508_511687157"
_EHR_WORK_LOCATION_VALUE_KEY = "extgzddxx1_606508_892394263"
_EHR_WORK_LOCATION_TEXT_KEY = "extgzddxx1_606508_892394263Text"
_EHR_STREET_ADDRESS_KEY = _EHR_WORK_LOCATION_TEXT_KEY
_EHR_STREET_ADDRESS_FALLBACK_KEYS = ("WorkSpacevalue", "extgzddxx_606508_618643707")
@ -102,6 +103,30 @@ def _field_value(item: dict[str, Any], key: str) -> str:
return ""
def _field_raw_value(item: dict[str, Any], key: str) -> str:
emp = item.get("employeeInfo") or {}
rec = item.get("recordInfo") or {}
for node in (emp, rec):
if not isinstance(node, dict):
continue
s = _scalar_value(node.get(key))
if s:
return s
s = _custom_prop_value(node.get("customProperties"), 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 = _custom_prop_value(child.get("customProperties"), key)
if s:
return s
return ""
def _field_translate_or_value(item: dict[str, Any], key: str) -> str:
emp = item.get("employeeInfo") or {}
rec = item.get("recordInfo") or {}
@ -266,6 +291,82 @@ def _location_from_workplace(workplace: str, mappings: dict[str, Any] | None = N
return {}
def _location_from_workspace_value(value: str, mappings: dict[str, Any] | None = None) -> dict[str, Any]:
code = str(value or "").strip()
defaults: dict[str, dict[str, Any]] = {
"qjjdizcpjk": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "上海",
"l": "上海",
"office": "上海张江",
},
"j1lw6k13ya": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "陕西",
"l": "西安",
"office": "西安环普",
},
"uyglfkg8vd": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "广州",
"l": "深圳",
"office": "深圳田厦",
},
"mrtkjjhoxx": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "上海",
"l": "上海",
"office": "上海徐汇",
},
"t21uyq5qvx": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "北京",
"l": "北京",
"office": "北京",
},
"nhu45qlh80": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "四川",
"l": "成都",
"office": "成都",
},
"5sntzg5dlr": {
"co": "Canada",
"c": "CA",
"countryCode": 124,
"st": "加拿大",
"l": "加拿大",
"office": "多伦多",
},
"1": {
"co": "China",
"c": "CN",
"countryCode": 156,
"st": "上海",
"l": "上海",
"office": "上海闵行",
},
}
merged = dict(defaults)
if isinstance(mappings, dict):
for k, v in mappings.items():
if isinstance(v, dict):
merged[str(k).strip()] = v
return dict(merged.get(code, {}))
def _org_name(org: dict[str, Any]) -> str:
for key in ("name", "Name", "shortName", "ShortName"):
s = str(org.get(key) or "").strip()
@ -390,6 +491,7 @@ class SyncEhrToAdUserJob(BaseJob):
max_users = int(params.get("max_users") or 0)
connect_timeout_s = int(params.get("ldap_connect_timeout_s") or 10)
domain_account_key = str(params.get("domain_account_custom_key") or "").strip() or _EHR_AD_ACCOUNT_KEY
work_location_value_key = str(params.get("work_location_value_key") or "").strip() or _EHR_WORK_LOCATION_VALUE_KEY
work_location_text_key = str(params.get("work_location_text_key") or "").strip() or _EHR_WORK_LOCATION_TEXT_KEY
street_address_key = str(params.get("street_address_key") or "").strip() or _EHR_STREET_ADDRESS_KEY
proxy_alias_domain = str(params.get("proxy_alias_domain") or "").strip() or None
@ -566,6 +668,7 @@ class SyncEhrToAdUserJob(BaseJob):
job_number = str(rec.get("jobNumber") or rec.get("JobNumber") or "").strip()
mobile = _field_value(item, "MobilePhone") or _extract_mobile_phone(emp)
office = _field_translate_or_value(item, "Place")
workplace_value = _field_raw_value(item, work_location_value_key)
workplace_text = _field_translate_or_value(item, work_location_text_key)
street_address = _field_value(item, street_address_key)
if not street_address:
@ -583,7 +686,20 @@ class SyncEhrToAdUserJob(BaseJob):
street_address,
)
company = default_company or _root_org_name(org, org_by_oid)
location_attrs = _location_from_workplace(workplace_text or office, location_mappings)
location_attrs = _location_from_workspace_value(workplace_value, location_mappings) or _location_from_workplace(
workplace_text or office, location_mappings
)
office = str(location_attrs.get("office") or office or "").strip()
if verbose_trace:
logger.info(
"AD 办公地点映射sam=%s workspace_key=%s workspace_value=%r province=%r city=%r office=%r",
sam,
work_location_value_key,
workplace_value,
location_attrs.get("st"),
location_attrs.get("l"),
office,
)
manager_dn = ""
manager_uid = _to_int_safe(rec.get("pOIdEmpAdmin") or rec.get("POIdEmpAdmin"))