diff --git a/extensions/sync_ehr_to_oa/api.py b/extensions/sync_ehr_to_oa/api.py index 773fbc0..7142067 100644 --- a/extensions/sync_ehr_to_oa/api.py +++ b/extensions/sync_ehr_to_oa/api.py @@ -491,28 +491,55 @@ class SyncEhrToOaApi: for i in range(0, len(clean_ids), chunk_size): chunk = clean_ids[i : i + chunk_size] for uid in chunk: - resp = self._client.request( - "GET", - "/UserFrameworkApiV3/api/v1/staffs/Get", - params={"userId": uid}, - ) - payload = resp.json() if resp.content else {} - # 兼容多种返回结构 - data = payload.get("data", payload) - if isinstance(data, list): - items = [x for x in data if isinstance(x, dict)] - if items: - out[uid] = items[0] - continue - if isinstance(data, dict): - # 可能是单条,也可能是 map - if "userId" in data or "UserId" in data: - out[uid] = data - else: - # map 场景:key=uid - d2 = data.get(str(uid)) - if isinstance(d2, dict): - out[uid] = d2 - # 非 200 业务码场景不硬失败,避免单个用户影响全量 + profile: dict[str, Any] | None = None + for params in ({"userId": str(uid)}, {"userid": str(uid)}): + try: + resp = self._client.request( + "GET", + "/UserFrameworkApiV3/api/v1/staffs/Get", + params=params, + ) + except Exception: + continue + payload = resp.json() if resp.content else {} + profile = self._find_staff_profile_by_uid(payload, uid) + if profile is not None: + break + if profile is not None: + out[uid] = profile logger.info("EHR 员工详情查询完成:input_user_ids=%s matched_profiles=%s", len(clean_ids), len(out)) return out + + @staticmethod + def _find_staff_profile_by_uid(payload: Any, uid: int) -> dict[str, Any] | None: + def _iter_dicts(node: Any): + if isinstance(node, dict): + yield node + for v in node.values(): + yield from _iter_dicts(v) + elif isinstance(node, list): + for it in node: + yield from _iter_dicts(it) + + def _uid_from_dict(d: dict[str, Any]) -> int: + for k in ("userId", "UserId", "userid", "UserID", "id", "Id", "ID"): + if k in d: + try: + return int(str(d.get(k)).strip()) + except Exception: + continue + return 0 + + best: dict[str, Any] | None = None + for d in _iter_dicts(payload): + if not isinstance(d, dict): + continue + duid = _uid_from_dict(d) + if duid != uid: + continue + # 优先返回带工号字段的对象 + if any(k in d for k in ("staffCode", "StaffCode", "code", "Code", "jobNumber", "JobNumber", "employeeNo", "EmployeeNo")): + return d + if best is None: + best = d + return best diff --git a/extensions/sync_ehr_to_oa/job.py b/extensions/sync_ehr_to_oa/job.py index bacc148..54463a1 100644 --- a/extensions/sync_ehr_to_oa/job.py +++ b/extensions/sync_ehr_to_oa/job.py @@ -355,10 +355,12 @@ class SyncEhrToOaFormJob(BaseJob): # 3) 员工按工号归并(同工号保留“最新”记录) ehr_by_job_no: dict[str, dict[str, Any]] = {} ehr_by_job_no_norm: dict[str, dict[str, Any]] = {} + ehr_user_id_to_job_no: dict[int, str] = {} for item in emp_rows: if not isinstance(item, dict): continue record = item.get("recordInfo") or {} + emp_info = item.get("employeeInfo") or {} if not isinstance(record, dict): continue job_no = str(record.get("jobNumber") or "").strip() @@ -370,12 +372,19 @@ class SyncEhrToOaFormJob(BaseJob): if job_no_norm: ex2 = ehr_by_job_no_norm.get(job_no_norm) ehr_by_job_no_norm[job_no_norm] = item if ex2 is None else _choose_better_record(ex2, item) + try: + uid = int((emp_info or {}).get("userID")) + except Exception: + uid = 0 + if uid > 0 and uid not in ehr_user_id_to_job_no: + ehr_user_id_to_job_no[uid] = job_no logger.info( - "EHR 数据准备完成:employee_rows=%s organization_rows=%s distinct_job_numbers=%s distinct_job_numbers_norm=%s", + "EHR 数据准备完成:employee_rows=%s organization_rows=%s distinct_job_numbers=%s distinct_job_numbers_norm=%s userid_jobno_map=%s", len(emp_rows), len(org_rows), len(ehr_by_job_no), len(ehr_by_job_no_norm), + len(ehr_user_id_to_job_no), ) # 3.1) 按员工 UserID 查询合同主体公司(firstParty),作为“所属公司”的优先来源 contract_user_ids: list[int] = [] @@ -424,6 +433,10 @@ class SyncEhrToOaFormJob(BaseJob): code = _extract_staff_code(profile) if code: user_id_to_staff_code[int(uid)] = code + # 兜底:若 staffs/Get 未返回工号,尝试从本次员工数据中反查 + for uid in resolve_user_ids: + if int(uid) not in user_id_to_staff_code and int(uid) in ehr_user_id_to_job_no: + user_id_to_staff_code[int(uid)] = ehr_user_id_to_job_no[int(uid)] # 3.4) 将 HRBP/汇报人工号转换为 OA 人员ID(member 字段要求) 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]] = {} @@ -448,6 +461,9 @@ class SyncEhrToOaFormJob(BaseJob): len(user_id_to_staff_code), len(code_to_member), ) + if verbose_trace: + sample_codes = list(user_id_to_staff_code.items())[:30] + logger.info("人员工号反查样本(uid->code)=%s", sample_codes) if verbose_trace: for job_no in list(ehr_by_job_no.keys()): logger.info("EHR 工号明细:raw=%s norm=%s", job_no, _normalize_job_no(job_no))