import re from sqlalchemy.orm import Session from app.models import Learner, LearnerNameHistory class LearnerIdentityConflict(ValueError): pass def normalize_name(value: str) -> str: return " ".join(value.strip().split()) def normalize_phone(value: str) -> str: phone = re.sub(r"[\s-]", "", value.strip()) if phone.startswith("+86"): phone = phone[3:] elif phone.startswith("0086"): phone = phone[4:] if not re.fullmatch(r"1\d{10}", phone): raise ValueError("手机号格式错误,请填写11位中国大陆手机号") return phone def resolve_learner( db: Session, name: str, phone: str, *, source: str, create_if_missing: bool = True, ) -> tuple[Learner | None, bool]: normalized_name = normalize_name(name) normalized_phone = normalize_phone(phone) if not normalized_name: raise ValueError("姓名不能为空") learner = db.query(Learner).filter(Learner.phone == normalized_phone, Learner.status != "deleted").first() if learner: if normalize_name(learner.current_name) != normalized_name: raise LearnerIdentityConflict( f"手机号 {normalized_phone} 已属于学员“{learner.current_name}”,与本次姓名“{normalized_name}”不一致" ) if learner.status != "active": raise LearnerIdentityConflict(f"学员“{normalized_name}”当前已停用,请先在学员管理中启用") return learner, False if not create_if_missing: return None, False learner = Learner(phone=normalized_phone, current_name=normalized_name) db.add(learner) db.flush() db.add(LearnerNameHistory(learner_id=learner.id, name=normalized_name, source=source)) return learner, True