"""Bind a SuccessFactors client to the HRIS instance that OWNS a person. Extracted from ``hris_email_writeback`` when a second write-back (the GitHub-ID one) needed the identical routing. It is not a convenience: on this platform ``SuccessFactorsClient.from_config`` returns the PRIMARY (CN, ``api30.sapsf.cn``) tenant only, and 31 of 417 employees live on the EU tenant (``api55.sapsf.eu``). Writing them through the CN client is not an error the API reports — the lookup simply finds no row and the write silently does nothing, which is exactly how every EU hire's corporate email failed to reach SF until 2026-07-02. Any new SF **write** path must route through here rather than ``from_config``; a read that legitimately targets the primary tenant may still call ``from_config`` directly. """ from __future__ import annotations from sqlalchemy.ext.asyncio import AsyncSession from app.identity.person_models import IdentityPerson from app.identity.successfactors_auth import build_auth_from_config from app.identity.successfactors_client import SuccessFactorsClient from app.integrations.compat import list_hris_instances async def sf_client_for_person( db: AsyncSession, person: IdentityPerson, ) -> SuccessFactorsClient: """Return the SF client for ``person``'s owning HRIS instance. Falls back to ``from_config`` (the primary tenant) for legacy rows where ``hris_instance_id`` is NULL, or when the owning instance is not among the enabled integrations — the historical single-instance behaviour, which is right for the ~all-CN population that predates the EU tenant. """ if person.hris_instance_id is not None: for row, cfg in await list_hris_instances(session=db): if str(row.id) != str(person.hris_instance_id): continue base_url = (cfg.get("base_url") or "").strip() if not base_url: break return SuccessFactorsClient( base_url=base_url, auth=build_auth_from_config(cfg), ) return await SuccessFactorsClient.from_config(session=db)