# -*- coding: utf-8 -*- """ 人员匹配服务 —— 根据申请信息在 FDW 外部表中查找候选匹配。 职责: - find_candidates():根据 site_id + phone(+ employee_number)在助教表和员工表中查找匹配 查询通过业务库的 fdw_etl Schema 访问 ETL 库的 RLS 视图。 查询前需 SET LOCAL app.current_site_id 以启用门店隔离。 FDW 外部表可能不存在(测试库等场景),需优雅降级返回空列表。 """ from __future__ import annotations import logging from app.database import get_connection logger = logging.getLogger(__name__) async def find_candidates( site_id: int | None, phone: str, employee_number: str | None = None, ) -> list[dict]: """ 在助教表和员工表中查找匹配候选。 查询逻辑: 1. 若 site_id 为 None,跳过匹配,返回空列表 2. 设置 app.current_site_id 进行 RLS 隔离 3. fdw_etl.v_dim_assistant: WHERE mobile = phone 4. fdw_etl.v_dim_staff JOIN fdw_etl.v_dim_staff_ex: WHERE mobile = phone OR job_num = employee_number 5. 合并结果返回统一候选列表 参数: site_id: 门店 ID(None 时跳过匹配) phone: 手机号 employee_number: 员工编号(可选,用于 job_num 匹配) 返回: [{"source_type": "assistant"|"staff", "id": int, "name": str, "mobile": str|None, "job_num": str|None}] """ # site_id 为空时直接返回空列表(需求 5.6) if site_id is None: return [] candidates: list[dict] = [] conn = get_connection() try: conn.autocommit = False with conn.cursor() as cur: # 设置 RLS 隔离:FDW 会透传 session 变量到远端 ETL 库 cur.execute( "SET LOCAL app.current_site_id = %s", (str(site_id),) ) # 1. 查询助教匹配 candidates.extend(_query_assistants(cur, phone)) # 2. 查询员工匹配 candidates.extend(_query_staff(cur, phone, employee_number)) conn.commit() except Exception: logger.warning( "FDW 人员匹配查询失败 (site_id=%s, phone=%s),返回空列表", site_id, phone, exc_info=True, ) try: conn.rollback() except Exception: pass return [] finally: conn.close() return candidates def _query_assistants(cur, phone: str) -> list[dict]: """查询 fdw_etl.v_dim_assistant 中按 mobile 匹配的助教记录。""" try: cur.execute( """ SELECT assistant_id, real_name, mobile FROM fdw_etl.v_dim_assistant WHERE mobile = %s AND scd2_is_current = TRUE """, (phone,), ) rows = cur.fetchall() except Exception: logger.warning( "查询 fdw_etl.v_dim_assistant 失败,跳过助教匹配", exc_info=True, ) return [] return [ { "source_type": "assistant", "id": row[0], "name": row[1] or "", "mobile": row[2], "job_num": None, } for row in rows ] def _query_staff( cur, phone: str, employee_number: str | None ) -> list[dict]: """ 查询 fdw_etl.v_dim_staff JOIN fdw_etl.v_dim_staff_ex 按 mobile 或 job_num 匹配的员工记录。 """ try: # 构建 WHERE 条件:mobile = phone,或 job_num = employee_number if employee_number: cur.execute( """ SELECT s.staff_id, s.staff_name, s.mobile, ex.job_num FROM fdw_etl.v_dim_staff s LEFT JOIN fdw_etl.v_dim_staff_ex ex ON s.staff_id = ex.staff_id AND ex.scd2_is_current = TRUE WHERE s.scd2_is_current = TRUE AND (s.mobile = %s OR ex.job_num = %s) """, (phone, employee_number), ) else: cur.execute( """ SELECT s.staff_id, s.staff_name, s.mobile, ex.job_num FROM fdw_etl.v_dim_staff s LEFT JOIN fdw_etl.v_dim_staff_ex ex ON s.staff_id = ex.staff_id AND ex.scd2_is_current = TRUE WHERE s.scd2_is_current = TRUE AND s.mobile = %s """, (phone,), ) rows = cur.fetchall() except Exception: logger.warning( "查询 fdw_etl.v_dim_staff 失败,跳过员工匹配", exc_info=True, ) return [] return [ { "source_type": "staff", "id": row[0], "name": row[1] or "", "mobile": row[2], "job_num": row[3], } for row in rows ]