From 0928492ae4b539fea614e62314e3ce1e69337707 Mon Sep 17 00:00:00 2001 From: Marsway Date: Wed, 4 Mar 2026 14:39:41 +0800 Subject: [PATCH] fixing --- extensions/sync_ehr_to_oa/job.py | 113 +++++++++++++++++++------------ 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/extensions/sync_ehr_to_oa/job.py b/extensions/sync_ehr_to_oa/job.py index 3d5fff1..716142b 100644 --- a/extensions/sync_ehr_to_oa/job.py +++ b/extensions/sync_ehr_to_oa/job.py @@ -79,23 +79,6 @@ def _normalize_job_no(v: Any) -> str: return s.upper() -def _to_int_candidate(v: Any) -> int | None: - if v is None: - return None - if isinstance(v, dict): - v = _cell_value(v) - s = str(v).strip() - if not s: - return None - try: - # 兼容 "123.0" - if "." in s and s.endswith(".0"): - return int(float(s)) - return int(s) - except Exception: - return None - - def _extract_oa_row_id_and_fields(row: dict[str, Any]) -> tuple[int | None, dict[str, Any]]: """ 兼容不同 OA export 返回结构,提取: @@ -111,20 +94,14 @@ def _extract_oa_row_id_and_fields(row: dict[str, Any]) -> tuple[int | None, dict for k, v in master.items(): if isinstance(k, str) and k.startswith("field"): field_map[k] = v - # 常见主键位置优先尝试 - for candidate in ( - row.get("id"), - row.get("masterDataId"), - master.get("id"), - master.get("ID"), - master.get("recordId"), - master.get("dataId"), - master.get("_id"), - ): - rid = _to_int_candidate(candidate) - if rid is not None: - row_id = rid + for candidate in (row.get("id"), row.get("masterDataId"), master.get("id")): + if candidate is None: + continue + try: + row_id = int(str(candidate)) break + except Exception: + continue # 结构 B:masterTable.record.fields = [{name,value,showValue}, ...] master_table = row.get("masterTable") @@ -140,7 +117,12 @@ def _extract_oa_row_id_and_fields(row: dict[str, Any]) -> tuple[int | None, dict if name: field_map[name] = fld if row_id is None: - row_id = _to_int_candidate(record.get("id")) + rid = record.get("id") + if rid is not None: + try: + row_id = int(str(rid)) + except Exception: + pass # 结构 C:行级 fields 列表 row_fields = row.get("fields") @@ -226,6 +208,12 @@ class SyncEhrToOaFormJob(BaseJob): verbose_trace = _to_bool_or_none(params.get("verbose_trace")) if verbose_trace is None: verbose_trace = True + preview_ehr_data = _to_bool_or_none(params.get("preview_ehr_data")) + if preview_ehr_data is None: + preview_ehr_data = True + preview_limit = int(params.get("preview_limit") or 20) + if preview_limit <= 0: + preview_limit = 20 seeyon = SeeyonClient(base_url=oa_base_url, rest_user=rest_user, rest_password=rest_password, loginName=login_name) ehr = SyncEhrToOaApi(secret_params={"app_key": app_key, "app_secret": app_secret}) @@ -277,6 +265,51 @@ class SyncEhrToOaFormJob(BaseJob): 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)) + if preview_ehr_data: + logger.info("EHR 字段预览开始:limit=%s", preview_limit) + count = 0 + for job_no, item in ehr_by_job_no.items(): + emp = item.get("employeeInfo") or {} + rec = item.get("recordInfo") or {} + if not isinstance(emp, dict): + emp = {} + if not isinstance(rec, dict): + rec = {} + + org_oid = str(rec.get("oIdOrganization") or rec.get("oIdDepartment") or "").strip() + org = org_by_oid.get(org_oid, {}) + company = str((org or {}).get("name") or "") + name = str(emp.get("name") or "") + rd_attr = _custom_prop_value(rec.get("customProperties"), rd_attr_custom_key) or _custom_prop_value( + emp.get("customProperties"), rd_attr_custom_key + ) + place = str(rec.get("place") or "") + entry_date = _date_only(rec.get("entryDate")) + leave_date = _date_only(rec.get("lastWorkDate")) or "2099-12-31" + id_number = str(emp.get("iDNumber") or "") + hrbp = str((org or {}).get("hRBP") or "") + manager = str(rec.get("pOIdEmpAdmin") or "") + 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 "") + 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", + job_no, + company, + name, + rd_attr, + place, + entry_date, + leave_date, + id_number, + hrbp, + manager, + is_leaving, + domain_account, + ) + count += 1 + if count >= preview_limit: + break + logger.info("EHR 字段预览结束:printed=%s", count) # 4) 导出 OA 表单,建立字段映射 + 工号到记录ID映射 exp_resp = seeyon.export_cap4_form_soap( @@ -369,8 +402,6 @@ class SyncEhrToOaFormJob(BaseJob): oa_id_by_job_no: dict[str, int] = {} oa_id_by_job_no_norm: dict[str, int] = {} row_parse_miss = 0 - row_id_fallback_count = 0 - row_id_fallback_samples: list[str] = [] for row in rows: if not isinstance(row, dict): continue @@ -388,30 +419,24 @@ class SyncEhrToOaFormJob(BaseJob): continue if row_id is None: - # 某些 OA export 不返回主键 id;此时依赖 uniqueFiled(工号) 进行更新匹配。 - row_id = 0 - row_id_fallback_count += 1 - if len(row_id_fallback_samples) < 20: - row_id_fallback_samples.append(job_no) - if verbose_trace and row_id_fallback_count <= 20: + row_parse_miss += 1 + if verbose_trace and row_parse_miss <= 20: logger.info( - "OA 行解析未取到记录ID,使用回退ID=0:job_no=%s row_keys=%s", + "OA 行解析未取到记录ID:job_no=%s row_keys=%s", job_no, list(row.keys())[:20], ) + continue oa_id_by_job_no[job_no] = row_id job_no_norm = _normalize_job_no(job_no) if job_no_norm: oa_id_by_job_no_norm[job_no_norm] = row_id logger.info( - "OA 工号索引完成:indexed_job_numbers=%s indexed_job_numbers_norm=%s parse_miss=%s id_fallback=%s", + "OA 工号索引完成:indexed_job_numbers=%s indexed_job_numbers_norm=%s parse_miss=%s", len(oa_id_by_job_no), len(oa_id_by_job_no_norm), row_parse_miss, - row_id_fallback_count, ) - if row_id_fallback_samples: - logger.info("OA 记录ID回退样本(前20):%s", row_id_fallback_samples) if verbose_trace: for job_no, row_id in list(oa_id_by_job_no.items()): logger.info("OA 工号索引明细:raw=%s norm=%s row_id=%s", job_no, _normalize_job_no(job_no), row_id)