diff --git a/extensions/sync_ehr_leaves_to_oa/job.py b/extensions/sync_ehr_leaves_to_oa/job.py index 2339798..257d486 100644 --- a/extensions/sync_ehr_leaves_to_oa/job.py +++ b/extensions/sync_ehr_leaves_to_oa/job.py @@ -189,6 +189,7 @@ class SyncEhrLeavesToOaMonthJob(BaseJob): max_pages_per_day = int(params.get("max_pages_per_day") or 500) if max_pages_per_day <= 0: max_pages_per_day = 500 + debug_trace = str(params.get("debug_trace") or "").strip().lower() in ("1", "true", "yes", "y", "on") approved_statuses_param = params.get("approved_statuses") if approved_statuses_param is None: @@ -332,6 +333,15 @@ class SyncEhrLeavesToOaMonthJob(BaseJob): # 姓名按工号+日期保留一个非空值(用于 OA 展示) if staff_name and key not in name_map: name_map[key] = staff_name + if debug_trace and len(agg) <= 20: + logger.info( + "EHR 请假汇总样本:staff_id=%s job_no=%s date=%s day_value=%s approve_status=%s", + sid, + job_no, + leave_date, + _decimal_to_str(day_value), + approve_status, + ) existing_row_map_by_export: dict[tuple[str, str], dict[str, str]] = {} for row in rows: @@ -357,6 +367,16 @@ class SyncEhrLeavesToOaMonthJob(BaseJob): month_end.isoformat(), len(existing_row_map_by_export), ) + if debug_trace: + for (k_job, k_date), rv in list(existing_row_map_by_export.items())[:20]: + logger.info( + "OA export 索引样本:job_no=%s date=%s row_id=%s leave_days=%s name=%s", + k_job, + k_date, + rv.get("id"), + rv.get("leave_days"), + rv.get("name"), + ) existing_row_map_by_sql = ehr.get_oa_rows_by_job_and_date( table_name=_OA_SQLSERVER_TABLE, @@ -471,6 +491,53 @@ class SyncEhrLeavesToOaMonthJob(BaseJob): for k, v in fd.items(): if str(k) not in failed_data: failed_data[str(k)] = str(v) + logger.info( + "OA batch-update chunk done: index=%s size=%s success=%s failed=%s message=%s", + i // batch_size + 1, + len(chunk), + chunk_success, + chunk_failed, + str(rj.get("message") or ""), + ) + if debug_trace and isinstance(fd, dict) and fd: + logger.warning( + "OA batch-update failedData sample: chunk=%s sample=%s", + i // batch_size + 1, + list(fd.items())[:20], + ) + + # 写入后复核:重新 export,核对本次 key 实际存在数量 + verify_resp = seeyon.export_cap4_form_soap( + templateCode=oa_template_code, + senderLoginName=sender_login_name, + rightId=oa_right_id, + ) + verify_raw = verify_resp.text or "" + verify_payload = json.loads(verify_raw) if verify_raw else {} + verify_form = ((verify_payload.get("data") or {}).get("data") or {}) if isinstance(verify_payload, dict) else {} + verify_rows = verify_form.get("data") or [] + if not isinstance(verify_rows, list): + verify_rows = [] + verify_key_set: set[tuple[str, str]] = set() + for row in verify_rows: + if not isinstance(row, dict): + continue + _, field_map = _extract_oa_row_id_and_fields(row) + job_no = _cell_value(field_map.get(field_job_no)) + leave_date = _cell_show_value(field_map.get(field_leave_date)) or _date_only(_cell_value(field_map.get(field_leave_date))) + if job_no and leave_date: + verify_key_set.add((job_no, leave_date)) + aggregated_keys = set(agg.keys()) + matched_after_write = len(aggregated_keys & verify_key_set) + missing_after_write = sorted(list(aggregated_keys - verify_key_set))[:50] + logger.info( + "OA 写入后复核:aggregated_keys=%s matched_after_write=%s missing_after_write=%s", + len(aggregated_keys), + matched_after_write, + len(aggregated_keys - verify_key_set), + ) + if debug_trace and missing_after_write: + logger.warning("OA 写入后缺失样本(job_no,date)=%s", missing_after_write[:20]) logger.info( "EHR 请假月度同步完成:month=%s~%s vacations=%s aggregated=%s to_update=%s to_insert=%s skipped_unchanged=%s success=%s failed=%s", @@ -499,6 +566,9 @@ class SyncEhrLeavesToOaMonthJob(BaseJob): "skipped_not_approved": skipped_not_approved, "skipped_non_positive": skipped_non_positive, "failed_data": dict(list(failed_data.items())[:100]), + "matched_after_write": matched_after_write, + "missing_after_write": len(aggregated_keys - verify_key_set), + "missing_after_write_sample": missing_after_write, } finally: ehr.close()