update
This commit is contained in:
parent
4a91cd9520
commit
48c4816439
|
|
@ -364,3 +364,103 @@ class SyncEhrToOaApi:
|
||||||
is_with_deleted=is_with_deleted,
|
is_with_deleted=is_with_deleted,
|
||||||
max_pages=max_pages,
|
max_pages=max_pages,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _pick_company_from_contracts(contracts: list[dict[str, Any]]) -> str:
|
||||||
|
if not contracts:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _sort_key(item: dict[str, Any]) -> str:
|
||||||
|
return str(item.get("effectiveDate") or item.get("createdTime") or item.get("modifiedTime") or "")
|
||||||
|
|
||||||
|
sorted_items = sorted([x for x in contracts if isinstance(x, dict)], key=_sort_key, reverse=True)
|
||||||
|
for c in sorted_items:
|
||||||
|
first_party = str(c.get("firstParty") or "").strip()
|
||||||
|
if first_party:
|
||||||
|
return first_party
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_contract_first_party_by_user_ids(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
user_ids: list[int],
|
||||||
|
is_current_effective: bool = True,
|
||||||
|
status: int | None = 1,
|
||||||
|
contract_type: int | None = None,
|
||||||
|
is_with_deleted: bool = False,
|
||||||
|
columns: list[str] | None = None,
|
||||||
|
enable_translate: bool = False,
|
||||||
|
chunk_size: int = 300,
|
||||||
|
) -> dict[int, str]:
|
||||||
|
"""
|
||||||
|
调用合同接口按员工 UserID 集合获取所属公司(firstParty)。
|
||||||
|
接口:POST /TenantBaseExternal/api/v5/Contract/GetByUserIds
|
||||||
|
"""
|
||||||
|
if chunk_size <= 0 or chunk_size > 300:
|
||||||
|
raise ValueError("chunk_size must be in range [1, 300]")
|
||||||
|
if not user_ids:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
clean_ids: list[int] = []
|
||||||
|
seen: set[int] = set()
|
||||||
|
for u in user_ids:
|
||||||
|
try:
|
||||||
|
uid = int(u)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if uid <= 0:
|
||||||
|
continue
|
||||||
|
if uid in seen:
|
||||||
|
continue
|
||||||
|
seen.add(uid)
|
||||||
|
clean_ids.append(uid)
|
||||||
|
if not clean_ids:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
out: dict[int, str] = {}
|
||||||
|
for i in range(0, len(clean_ids), chunk_size):
|
||||||
|
chunk = clean_ids[i : i + chunk_size]
|
||||||
|
body: dict[str, Any] = {
|
||||||
|
"oIds": chunk,
|
||||||
|
"isCurrentEffective": is_current_effective,
|
||||||
|
"isWithDeleted": is_with_deleted,
|
||||||
|
"enableTranslate": enable_translate,
|
||||||
|
}
|
||||||
|
if status is not None:
|
||||||
|
body["status"] = status
|
||||||
|
if contract_type is not None:
|
||||||
|
body["contractType"] = contract_type
|
||||||
|
if columns is not None:
|
||||||
|
body["columns"] = columns
|
||||||
|
|
||||||
|
resp = self._client.request(
|
||||||
|
"POST",
|
||||||
|
"/TenantBaseExternal/api/v5/Contract/GetByUserIds",
|
||||||
|
json=body,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
payload = resp.json() if resp.content else {}
|
||||||
|
code = str(payload.get("code", "") or "")
|
||||||
|
if code != "200":
|
||||||
|
raise RuntimeError(f"EHR Contract.GetByUserIds failed code={code!r} message={payload.get('message')!r}")
|
||||||
|
|
||||||
|
data = payload.get("data") or {}
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise RuntimeError("EHR Contract.GetByUserIds invalid response: data is not an object")
|
||||||
|
|
||||||
|
for k, v in data.items():
|
||||||
|
try:
|
||||||
|
uid = int(str(k))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
contracts = v if isinstance(v, list) else []
|
||||||
|
company = self._pick_company_from_contracts(contracts)
|
||||||
|
if company:
|
||||||
|
out[uid] = company
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"EHR 合同公司查询完成:input_user_ids=%s matched_first_party=%s",
|
||||||
|
len(clean_ids),
|
||||||
|
len(out),
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
|
||||||
|
|
@ -103,23 +103,6 @@ def _prefer_non_empty(new_val: Any, old_val: Any) -> str:
|
||||||
return str(old_val or "").strip()
|
return str(old_val or "").strip()
|
||||||
|
|
||||||
|
|
||||||
def _resolve_org_for_company(record_info: dict[str, Any], org_by_oid: dict[str, dict[str, Any]]) -> tuple[dict[str, Any], str, str]:
|
|
||||||
"""
|
|
||||||
解析“所属公司”使用的组织对象。
|
|
||||||
经验上 oIdOrganization 可能是公司根(导致全员同值),优先尝试 oIdDepartment。
|
|
||||||
返回:(org_obj, source_field, source_oid)
|
|
||||||
"""
|
|
||||||
dept_oid = str(record_info.get("oIdDepartment") or "").strip()
|
|
||||||
org_oid = str(record_info.get("oIdOrganization") or "").strip()
|
|
||||||
for source, oid in (("oIdDepartment", dept_oid), ("oIdOrganization", org_oid)):
|
|
||||||
if not oid:
|
|
||||||
continue
|
|
||||||
org = org_by_oid.get(oid) or {}
|
|
||||||
if isinstance(org, dict) and str(org.get("name") or "").strip():
|
|
||||||
return org, source, oid
|
|
||||||
return {}, "", ""
|
|
||||||
|
|
||||||
|
|
||||||
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 返回结构,提取:
|
||||||
|
|
@ -317,6 +300,30 @@ class SyncEhrToOaFormJob(BaseJob):
|
||||||
len(ehr_by_job_no),
|
len(ehr_by_job_no),
|
||||||
len(ehr_by_job_no_norm),
|
len(ehr_by_job_no_norm),
|
||||||
)
|
)
|
||||||
|
# 3.1) 按员工 UserID 查询合同主体公司(firstParty),作为“所属公司”的优先来源
|
||||||
|
contract_user_ids: list[int] = []
|
||||||
|
for item in ehr_by_job_no.values():
|
||||||
|
emp_info = item.get("employeeInfo") or {}
|
||||||
|
if not isinstance(emp_info, dict):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
uid = int(emp_info.get("userID"))
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if uid > 0:
|
||||||
|
contract_user_ids.append(uid)
|
||||||
|
first_party_by_user_id = ehr.get_contract_first_party_by_user_ids(
|
||||||
|
user_ids=contract_user_ids,
|
||||||
|
is_current_effective=True,
|
||||||
|
status=1,
|
||||||
|
contract_type=None,
|
||||||
|
is_with_deleted=False,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"所属公司来源(合同主体)准备完成:input_user_ids=%s matched=%s",
|
||||||
|
len(contract_user_ids),
|
||||||
|
len(first_party_by_user_id),
|
||||||
|
)
|
||||||
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()):
|
||||||
logger.info("EHR 工号明细:raw=%s norm=%s", job_no, _normalize_job_no(job_no))
|
logger.info("EHR 工号明细:raw=%s norm=%s", job_no, _normalize_job_no(job_no))
|
||||||
|
|
@ -331,8 +338,14 @@ class SyncEhrToOaFormJob(BaseJob):
|
||||||
if not isinstance(rec, dict):
|
if not isinstance(rec, dict):
|
||||||
rec = {}
|
rec = {}
|
||||||
|
|
||||||
org, company_source, company_source_oid = _resolve_org_for_company(rec, org_by_oid)
|
user_id = 0
|
||||||
company = str((org or {}).get("name") or "")
|
try:
|
||||||
|
user_id = int(emp.get("userID"))
|
||||||
|
except Exception:
|
||||||
|
user_id = 0
|
||||||
|
org_oid = str(rec.get("oIdOrganization") or rec.get("oIdDepartment") or "").strip()
|
||||||
|
org = org_by_oid.get(org_oid, {})
|
||||||
|
company = str(first_party_by_user_id.get(user_id) or str((org or {}).get("name") or ""))
|
||||||
name = str(emp.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(
|
rd_attr = _custom_prop_value(rec.get("customProperties"), rd_attr_custom_key) or _custom_prop_value(
|
||||||
emp.get("customProperties"), rd_attr_custom_key
|
emp.get("customProperties"), rd_attr_custom_key
|
||||||
|
|
@ -346,11 +359,9 @@ class SyncEhrToOaFormJob(BaseJob):
|
||||||
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 company_source=%s company_source_oid=%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=%s manager=%s is_leaving=%s domain_account=%s",
|
||||||
job_no,
|
job_no,
|
||||||
company,
|
company,
|
||||||
company_source,
|
|
||||||
company_source_oid,
|
|
||||||
name,
|
name,
|
||||||
rd_attr,
|
rd_attr,
|
||||||
place,
|
place,
|
||||||
|
|
@ -550,10 +561,16 @@ class SyncEhrToOaFormJob(BaseJob):
|
||||||
if not isinstance(rec, dict):
|
if not isinstance(rec, dict):
|
||||||
rec = {}
|
rec = {}
|
||||||
|
|
||||||
org, company_source, company_source_oid = _resolve_org_for_company(rec, org_by_oid)
|
user_id = 0
|
||||||
|
try:
|
||||||
|
user_id = int(emp.get("userID"))
|
||||||
|
except Exception:
|
||||||
|
user_id = 0
|
||||||
|
org_oid = str(rec.get("oIdOrganization") or rec.get("oIdDepartment") or "").strip()
|
||||||
|
org = org_by_oid.get(org_oid, {})
|
||||||
existing_field_map = oa_fields_by_job_no_norm.get(_normalize_job_no(job_no), {})
|
existing_field_map = oa_fields_by_job_no_norm.get(_normalize_job_no(job_no), {})
|
||||||
|
|
||||||
company = str((org or {}).get("name") or "")
|
company = str(first_party_by_user_id.get(user_id) or str((org or {}).get("name") or ""))
|
||||||
name = str(emp.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(
|
rd_attr = _custom_prop_value(rec.get("customProperties"), rd_attr_custom_key) or _custom_prop_value(
|
||||||
emp.get("customProperties"), rd_attr_custom_key
|
emp.get("customProperties"), rd_attr_custom_key
|
||||||
|
|
@ -594,13 +611,7 @@ class SyncEhrToOaFormJob(BaseJob):
|
||||||
{"name": display_to_code["域账号"], "value": domain_account, "showValue": domain_account},
|
{"name": display_to_code["域账号"], "value": domain_account, "showValue": domain_account},
|
||||||
]
|
]
|
||||||
if verbose_trace:
|
if verbose_trace:
|
||||||
logger.info(
|
logger.info("字段映射:job_no=%s row_id=%s", job_no, oa_record_id)
|
||||||
"字段映射:job_no=%s row_id=%s company_source=%s company_source_oid=%s",
|
|
||||||
job_no,
|
|
||||||
oa_record_id,
|
|
||||||
company_source,
|
|
||||||
company_source_oid,
|
|
||||||
)
|
|
||||||
for fld in fields_payload:
|
for fld in fields_payload:
|
||||||
logger.info("字段映射明细:job_no=%s field=%s value=%s", job_no, fld["name"], fld["value"])
|
logger.info("字段映射明细:job_no=%s field=%s value=%s", job_no, fld["name"], fld["value"])
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue