""" Patient auto-link service. Given a national ID and/or phone number, try to find an existing Patient — first locally, then via HIS — and return it so the calling record (complaint, inquiry, suggestion) can link to it. Never creates a Patient from typed-only data; only links an existing local patient or creates one from real HIS demographics. This function never raises: on any failure (HIS unreachable, config missing, unexpected error) it returns None, so record creation always proceeds with patient = NULL rather than failing. """ import logging logger = logging.getLogger(__name__) def find_or_link_patient(national_id=None, phone=None, hospital=None): """Return a Patient that matches the given identifiers, or None. Lookup order: 1. Local Patient table — by national_id_hash (exact), then by phone (exact). 2. HIS — if not found locally, query HIS by SSN/MobileNo; if a match is returned, materialize it as a local Patient via HISAdapter and link it. 3. None — if neither local nor HIS yields a match (or HIS is unavailable). Args: national_id (str|None): Patient national ID / Iqama. phone (str|None): Patient phone number. hospital: Optional Hospital instance (used when creating from HIS data). Returns: Patient instance or None. """ national_id = (national_id or "").strip() phone = (phone or "").strip() if not national_id and not phone: return None # 1. Local lookup patient = _lookup_local(national_id, phone) if patient is not None: return patient # 2. HIS lookup (only if integration is configured and reachable) patient = _lookup_his(national_id, phone, hospital) return patient # may be None def _lookup_local(national_id, phone): from apps.core.encryption import compute_national_id_hash from apps.organizations.models import Patient if national_id: nid_hash = compute_national_id_hash(national_id) if nid_hash: patient = Patient.objects.filter(national_id_hash=nid_hash, status="active").first() if patient: return patient if phone: patient = Patient.objects.filter(phone=phone, status="active").first() if patient: return patient return None def _lookup_his(national_id, phone, hospital): """Query HIS and, on a hit, materialize a local Patient from the demographics.""" try: from apps.integrations.models import IntegrationConfig from apps.integrations.services.his_adapter import HISAdapter from apps.integrations.services.his_client import HISClient except Exception: # Integrations app unavailable — not configured. return None config = IntegrationConfig.objects.filter(source_system__in=["his", "other"]).first() if not config: return None try: client = HISClient(config) patients = client.fetch_patient_by_identifier(ssn=national_id or None, mobile_no=phone or None) except Exception as e: logger.info("HIS patient lookup failed (network/config): %s", e) return None if not patients: return None # Use the first HIS match to get-or-create a local Patient. his_patient = patients[0] try: his_hospital = HISAdapter.get_or_create_hospital(his_patient) target_hospital = hospital or his_hospital if target_hospital is None: return None return HISAdapter.get_or_create_patient(his_patient, target_hospital) except Exception as e: logger.warning("HIS patient materialization failed: %s", e) return None