This commit is contained in:
Marsway 2026-03-25 16:20:05 +08:00
parent 92a3a1bed7
commit c291a02a89
2 changed files with 90 additions and 8 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from urllib.parse import quote
from typing import Any from typing import Any
import httpx import httpx
@ -164,3 +165,32 @@ class SeeyonClient(BaseClient):
json=body, json=body,
headers={"Content-Type": "application/json"}, headers={"Content-Type": "application/json"},
) )
def get_org_members_by_code(self, *, code: str, pageNo: int = 0, pageSize: int = 20) -> list[dict[str, Any]]:
"""
按人员编码查询 OA 人员信息
GET /seeyon/rest/orgMembers/code/{code}?pageNo={pageNo}&pageSize={pageSize}
"""
c = str(code or "").strip()
if not c:
raise ValueError("code is required")
if pageNo < 0:
pageNo = 0
if pageSize <= 0:
pageSize = 20
path = f"/seeyon/rest/orgMembers/code/{quote(c, safe='')}"
resp = self.request("GET", path, params={"pageNo": pageNo, "pageSize": pageSize})
payload = resp.json() if resp.content else {}
if isinstance(payload, list):
return [x for x in payload if isinstance(x, dict)]
if isinstance(payload, dict):
data = payload.get("data")
if isinstance(data, list):
return [x for x in data if isinstance(x, dict)]
if isinstance(data, dict):
items = data.get("content")
if isinstance(items, list):
return [x for x in items if isinstance(x, dict)]
return [data]
return [payload]
return []

View File

@ -40,6 +40,12 @@ def _cell_value(cell: Any) -> str:
return str(cell or "").strip() return str(cell or "").strip()
def _cell_show_value(cell: Any) -> str:
if isinstance(cell, dict):
return str(cell.get("showValue") or "").strip()
return ""
def _date_only(s: Any) -> str: def _date_only(s: Any) -> str:
v = str(s or "").strip() v = str(s or "").strip()
if not v: if not v:
@ -148,6 +154,20 @@ def _extract_staff_code(staff_profile: dict[str, Any]) -> str:
).strip() ).strip()
def _pick_best_member_by_code(members: list[dict[str, Any]]) -> dict[str, Any] | None:
if not members:
return None
# 优先:可用且在职
for m in members:
if bool(m.get("isValid", True)) and int(m.get("state", 1) or 1) == 1:
return m
# 次优enabled
for m in members:
if bool(m.get("enabled", True)):
return m
return members[0]
def _extract_oa_row_id_and_fields(row: dict[str, Any]) -> tuple[int | None, dict[str, Any]]: def _extract_oa_row_id_and_fields(row: dict[str, Any]) -> tuple[int | None, dict[str, Any]]:
""" """
兼容不同 OA export 返回结构提取 兼容不同 OA export 返回结构提取
@ -391,12 +411,28 @@ class SyncEhrToOaFormJob(BaseJob):
code = _extract_staff_code(profile) code = _extract_staff_code(profile)
if code: if code:
user_id_to_staff_code[int(uid)] = code user_id_to_staff_code[int(uid)] = code
# 3.4) 将 HRBP/汇报人工号转换为 OA 人员IDmember 字段要求)
need_member_codes = list({c for c in user_id_to_staff_code.values() if str(c or "").strip()})
code_to_member: dict[str, dict[str, str]] = {}
for code in need_member_codes:
try:
members = seeyon.get_org_members_by_code(code=code, pageNo=0, pageSize=20)
best = _pick_best_member_by_code(members)
if not best:
continue
member_id = str(best.get("id") or "").strip()
member_name = str(best.get("name") or best.get("loginName") or "").strip()
if member_id:
code_to_member[code] = {"id": member_id, "name": member_name}
except Exception as e: # noqa: BLE001
logger.warning("OA 人员查询失败code=%s err=%r", code, e)
logger.info( logger.info(
"人员工号反查完成staff_profiles=%s reportings=%s hrbp_ids=%s resolved_staff_codes=%s", "人员工号反查完成staff_profiles=%s reportings=%s hrbp_ids=%s resolved_staff_codes=%s resolved_member_ids=%s",
len(staff_profile_by_user_id), len(staff_profile_by_user_id),
len(reporting_user_ids), len(reporting_user_ids),
len(hrbp_user_ids), len(hrbp_user_ids),
len(user_id_to_staff_code), len(user_id_to_staff_code),
len(code_to_member),
) )
if verbose_trace: if verbose_trace:
for job_no in list(ehr_by_job_no.keys()): for job_no in list(ehr_by_job_no.keys()):
@ -427,13 +463,19 @@ class SyncEhrToOaFormJob(BaseJob):
leave_date = _date_only(rec.get("lastWorkDate")) leave_date = _date_only(rec.get("lastWorkDate"))
id_number = str(emp.get("iDNumber") or "") id_number = str(emp.get("iDNumber") or "")
hrbp_uid = _to_int_safe(rec.get(_EHR_HRBP_ID_KEY) or _custom_prop_value(rec.get("customProperties"), _EHR_HRBP_ID_KEY)) hrbp_uid = _to_int_safe(rec.get(_EHR_HRBP_ID_KEY) or _custom_prop_value(rec.get("customProperties"), _EHR_HRBP_ID_KEY))
hrbp = str(user_id_to_staff_code.get(hrbp_uid) or "") hrbp_code = str(user_id_to_staff_code.get(hrbp_uid) or "")
hrbp_member = code_to_member.get(hrbp_code, {})
hrbp = str(hrbp_member.get("id") or "")
hrbp_show = str(hrbp_member.get("name") or hrbp_code)
manager_uid = _extract_reporting_user_id(staff_profile_by_user_id.get(user_id, {})) manager_uid = _extract_reporting_user_id(staff_profile_by_user_id.get(user_id, {}))
manager = str(user_id_to_staff_code.get(manager_uid) or "") manager_code = str(user_id_to_staff_code.get(manager_uid) or "")
manager_member = code_to_member.get(manager_code, {})
manager = str(manager_member.get("id") or "")
manager_show = str(manager_member.get("name") or manager_code)
is_leaving = "" if _date_only(rec.get("lastWorkDate")) else "" is_leaving = "" if _date_only(rec.get("lastWorkDate")) else ""
domain_account = _custom_prop_value(emp.get("customProperties"), domain_custom_key) or str(emp.get("_Name") or "") domain_account = _custom_prop_value(emp.get("customProperties"), domain_custom_key) or str(emp.get("_Name") or "")
logger.info( logger.info(
"EHR 字段预览job_no=%s company=%s name=%s rd_attr=%s place=%s entry_date=%s leave_date=%s id_number=%s hrbp=%s manager=%s is_leaving=%s domain_account=%s", "EHR 字段预览job_no=%s company=%s name=%s rd_attr=%s place=%s entry_date=%s leave_date=%s id_number=%s hrbp_id=%s hrbp_show=%s manager_id=%s manager_show=%s is_leaving=%s domain_account=%s",
job_no, job_no,
company, company,
name, name,
@ -443,7 +485,9 @@ class SyncEhrToOaFormJob(BaseJob):
leave_date, leave_date,
id_number, id_number,
hrbp, hrbp,
hrbp_show,
manager, manager,
manager_show,
is_leaving, is_leaving,
domain_account, domain_account,
) )
@ -652,9 +696,15 @@ class SyncEhrToOaFormJob(BaseJob):
leave_date = _date_only(rec.get("lastWorkDate")) leave_date = _date_only(rec.get("lastWorkDate"))
id_number = str(emp.get("iDNumber") or "") id_number = str(emp.get("iDNumber") or "")
hrbp_uid = _to_int_safe(rec.get(_EHR_HRBP_ID_KEY) or _custom_prop_value(rec.get("customProperties"), _EHR_HRBP_ID_KEY)) hrbp_uid = _to_int_safe(rec.get(_EHR_HRBP_ID_KEY) or _custom_prop_value(rec.get("customProperties"), _EHR_HRBP_ID_KEY))
hrbp = str(user_id_to_staff_code.get(hrbp_uid) or "") hrbp_code = str(user_id_to_staff_code.get(hrbp_uid) or "")
hrbp_member = code_to_member.get(hrbp_code, {})
hrbp = str(hrbp_member.get("id") or "")
hrbp_show = str(hrbp_member.get("name") or hrbp_code)
manager_uid = _extract_reporting_user_id(staff_profile_by_user_id.get(user_id, {})) manager_uid = _extract_reporting_user_id(staff_profile_by_user_id.get(user_id, {}))
manager = str(user_id_to_staff_code.get(manager_uid) or "") manager_code = str(user_id_to_staff_code.get(manager_uid) or "")
manager_member = code_to_member.get(manager_code, {})
manager = str(manager_member.get("id") or "")
manager_show = str(manager_member.get("name") or manager_code)
is_leaving = "" if _date_only(rec.get("lastWorkDate")) else "" is_leaving = "" if _date_only(rec.get("lastWorkDate")) else ""
domain_account = _custom_prop_value(emp.get("customProperties"), domain_custom_key) or str(emp.get("_Name") or "") domain_account = _custom_prop_value(emp.get("customProperties"), domain_custom_key) or str(emp.get("_Name") or "")
@ -668,6 +718,8 @@ class SyncEhrToOaFormJob(BaseJob):
id_number = _prefer_non_empty(id_number, _cell_value(existing_field_map.get(display_to_code["身份证号"]))) id_number = _prefer_non_empty(id_number, _cell_value(existing_field_map.get(display_to_code["身份证号"])))
hrbp = _prefer_non_empty(hrbp, _cell_value(existing_field_map.get(display_to_code["HRBP"]))) hrbp = _prefer_non_empty(hrbp, _cell_value(existing_field_map.get(display_to_code["HRBP"])))
manager = _prefer_non_empty(manager, _cell_value(existing_field_map.get(display_to_code["汇报人"]))) manager = _prefer_non_empty(manager, _cell_value(existing_field_map.get(display_to_code["汇报人"])))
hrbp_show = _prefer_non_empty(hrbp_show, _cell_show_value(existing_field_map.get(display_to_code["HRBP"])))
manager_show = _prefer_non_empty(manager_show, _cell_show_value(existing_field_map.get(display_to_code["汇报人"])))
is_leaving = _prefer_non_empty(is_leaving, _cell_value(existing_field_map.get(display_to_code["在离职"]))) is_leaving = _prefer_non_empty(is_leaving, _cell_value(existing_field_map.get(display_to_code["在离职"])))
domain_account = _prefer_non_empty(domain_account, _cell_value(existing_field_map.get(display_to_code["域账号"]))) domain_account = _prefer_non_empty(domain_account, _cell_value(existing_field_map.get(display_to_code["域账号"])))
@ -679,8 +731,8 @@ class SyncEhrToOaFormJob(BaseJob):
{"name": display_to_code["入职日期"], "value": entry_date, "showValue": entry_date}, {"name": display_to_code["入职日期"], "value": entry_date, "showValue": entry_date},
{"name": display_to_code["离职日期"], "value": leave_date, "showValue": leave_date}, {"name": display_to_code["离职日期"], "value": leave_date, "showValue": leave_date},
{"name": display_to_code["身份证号"], "value": id_number, "showValue": id_number}, {"name": display_to_code["身份证号"], "value": id_number, "showValue": id_number},
{"name": display_to_code["HRBP"], "value": hrbp, "showValue": hrbp}, {"name": display_to_code["HRBP"], "value": hrbp, "showValue": hrbp_show},
{"name": display_to_code["汇报人"], "value": manager, "showValue": manager}, {"name": display_to_code["汇报人"], "value": manager, "showValue": manager_show},
{"name": display_to_code["在离职"], "value": is_leaving, "showValue": is_leaving}, {"name": display_to_code["在离职"], "value": is_leaving, "showValue": is_leaving},
{"name": display_to_code["域账号"], "value": domain_account, "showValue": domain_account}, {"name": display_to_code["域账号"], "value": domain_account, "showValue": domain_account},
] ]