344 lines
10 KiB
Python
344 lines
10 KiB
Python
"""协议当事人三角色客户同步:新建、补证、撞库合并改挂。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from types import SimpleNamespace
|
|
from typing import Any, Dict, Optional
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.crud import customer as customer_crud
|
|
from app.models.models import BizCustomer, BizProtocolPartyCustomer
|
|
from app.models.research_donation import BizResearchDonationParty
|
|
|
|
ROLE_MOTHER = "mother"
|
|
ROLE_FATHER = "father"
|
|
ROLE_NEWBORN = "newborn"
|
|
|
|
_ROLE_SPECS = (
|
|
{
|
|
"role": ROLE_MOTHER,
|
|
"out_key": "customer_id",
|
|
"id_keys": ("customer_id", "customerId"),
|
|
"name_keys": ("mother_name", "motherName"),
|
|
"phone_keys": ("mother_phone", "motherPhone"),
|
|
"id_card_keys": ("mother_id_card", "motherIdCard"),
|
|
"birth_ym_keys": ("mother_birth_ym", "motherBirthYm"),
|
|
"ethnic_keys": ("mother_ethnic", "motherEthnic"),
|
|
"email_keys": ("mother_email", "motherEmail"),
|
|
"address_keys": ("mother_address", "motherAddress"),
|
|
"sex_keys": (),
|
|
"default_sex": "1",
|
|
},
|
|
{
|
|
"role": ROLE_FATHER,
|
|
"out_key": "father_customer_id",
|
|
"id_keys": ("father_customer_id", "fatherCustomerId"),
|
|
"name_keys": ("father_name", "fatherName"),
|
|
"phone_keys": ("father_phone", "fatherPhone"),
|
|
"id_card_keys": ("father_id_card", "fatherIdCard"),
|
|
"birth_ym_keys": (),
|
|
"ethnic_keys": (),
|
|
"email_keys": (),
|
|
"address_keys": (),
|
|
"sex_keys": (),
|
|
"default_sex": "0",
|
|
},
|
|
{
|
|
"role": ROLE_NEWBORN,
|
|
"out_key": "newborn_customer_id",
|
|
"id_keys": ("newborn_customer_id", "newbornCustomerId"),
|
|
"name_keys": ("newborn_name", "newbornName"),
|
|
"phone_keys": ("newborn_phone", "newbornPhone"),
|
|
"id_card_keys": ("newborn_id_card", "newbornIdCard"),
|
|
"birth_ym_keys": (),
|
|
"ethnic_keys": (),
|
|
"email_keys": (),
|
|
"address_keys": (),
|
|
"sex_keys": ("newborn_sex", "newbornSex"),
|
|
"default_sex": "2",
|
|
},
|
|
)
|
|
|
|
_PROFILE_ATTRS = (
|
|
"customer_name",
|
|
"phone_no",
|
|
"id_card_no",
|
|
"sex",
|
|
"age",
|
|
"birth_ym",
|
|
"ethnic",
|
|
"email",
|
|
"address",
|
|
)
|
|
|
|
|
|
def _is_empty(val: Any) -> bool:
|
|
if val is None:
|
|
return True
|
|
if isinstance(val, str) and not val.strip():
|
|
return True
|
|
return False
|
|
|
|
|
|
def _pick(party: Dict[str, Any], keys: tuple) -> str:
|
|
for key in keys:
|
|
if key not in party or party[key] is None:
|
|
continue
|
|
return str(party[key]).strip()
|
|
return ""
|
|
|
|
|
|
def _union_tags(left: Any, right: Any) -> str:
|
|
seen = []
|
|
for raw in (left, right):
|
|
if _is_empty(raw):
|
|
continue
|
|
for part in str(raw).split(","):
|
|
tag = part.strip()
|
|
if tag and tag not in seen:
|
|
seen.append(tag)
|
|
return ",".join(seen)
|
|
|
|
|
|
def _age_from_id_card(id_card: str) -> Optional[int]:
|
|
s = (id_card or "").strip()
|
|
if len(s) != 18:
|
|
return None
|
|
try:
|
|
birth = date(int(s[6:10]), int(s[10:12]), int(s[12:14]))
|
|
except ValueError:
|
|
return None
|
|
today = date.today()
|
|
age = today.year - birth.year - ((today.month, today.day) < (birth.month, birth.day))
|
|
if age < 0 or age > 150:
|
|
return None
|
|
return age
|
|
|
|
|
|
def apply_role_profile_overwrite(target: Any, profile: Any) -> None:
|
|
"""协议非空字段覆盖客户主档;空串/None 跳过,不碰 tags/remark。"""
|
|
for attr in _PROFILE_ATTRS:
|
|
val = getattr(profile, attr, None)
|
|
if _is_empty(val):
|
|
continue
|
|
if attr == "sex":
|
|
setattr(target, attr, str(val).strip()[:1])
|
|
elif attr == "birth_ym":
|
|
setattr(target, attr, str(val).strip()[:6])
|
|
elif attr == "age":
|
|
setattr(target, attr, val)
|
|
else:
|
|
setattr(target, attr, str(val).strip())
|
|
|
|
|
|
def merge_customer_fields(target: Any, source: Any) -> None:
|
|
"""目标为空才补;性别在空或未知(2)时补;标签逗号并集;姓名保留目标。"""
|
|
if _is_empty(getattr(target, "phone_no", None)):
|
|
src_phone = getattr(source, "phone_no", None)
|
|
if not _is_empty(src_phone):
|
|
target.phone_no = src_phone
|
|
if _is_empty(getattr(target, "age", None)):
|
|
src_age = getattr(source, "age", None)
|
|
if not _is_empty(src_age):
|
|
target.age = src_age
|
|
target_sex = getattr(target, "sex", None)
|
|
if _is_empty(target_sex) or str(target_sex).strip() == "2":
|
|
src_sex = getattr(source, "sex", None)
|
|
if not _is_empty(src_sex):
|
|
target.sex = src_sex
|
|
if _is_empty(getattr(target, "remark", None)):
|
|
src_remark = getattr(source, "remark", None)
|
|
if not _is_empty(src_remark):
|
|
target.remark = src_remark
|
|
for attr in ("birth_ym", "ethnic", "email", "address"):
|
|
if _is_empty(getattr(target, attr, None)):
|
|
src_val = getattr(source, attr, None)
|
|
if not _is_empty(src_val):
|
|
setattr(target, attr, src_val)
|
|
target.customer_tags = _union_tags(
|
|
getattr(target, "customer_tags", None),
|
|
getattr(source, "customer_tags", None),
|
|
)
|
|
|
|
|
|
def find_customer_by_id_card(db: Session, id_card: str, tenant_no: str) -> Optional[BizCustomer]:
|
|
ic = (id_card or "").strip()
|
|
if not ic:
|
|
return None
|
|
return (
|
|
db.query(BizCustomer)
|
|
.filter(
|
|
BizCustomer.id_card_no == ic,
|
|
BizCustomer.tenant_no == tenant_no,
|
|
BizCustomer.del_flag == 0,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
|
|
def get_customer(db: Session, customer_id: str, tenant_no: Optional[str] = None) -> Optional[BizCustomer]:
|
|
cid = (customer_id or "").strip()
|
|
if not cid:
|
|
return None
|
|
return customer_crud.get_customer(db, cid, tenant_no=tenant_no)
|
|
|
|
|
|
def repoint_customer_refs(db: Session, from_id: str, to_id: str, tenant_no: str) -> None:
|
|
if not from_id or not to_id or from_id == to_id:
|
|
return
|
|
for model in (BizProtocolPartyCustomer, BizResearchDonationParty):
|
|
for col_name in ("customer_id", "father_customer_id", "newborn_customer_id"):
|
|
col = getattr(model, col_name)
|
|
db.query(model).filter(
|
|
col == from_id,
|
|
model.del_flag == 0,
|
|
model.tenant_no == tenant_no,
|
|
).update({col_name: to_id}, synchronize_session=False)
|
|
|
|
|
|
def _build_role_profile(party: Dict[str, Any], spec: Dict[str, Any]) -> SimpleNamespace:
|
|
name = _pick(party, spec["name_keys"])
|
|
phone = _pick(party, spec["phone_keys"])
|
|
id_card = _pick(party, spec["id_card_keys"])
|
|
sex = _pick(party, spec["sex_keys"]) or spec["default_sex"]
|
|
birth_ym = _pick(party, spec.get("birth_ym_keys") or ())
|
|
ethnic = _pick(party, spec.get("ethnic_keys") or ())
|
|
email = _pick(party, spec.get("email_keys") or ())
|
|
address = _pick(party, spec.get("address_keys") or ())
|
|
return SimpleNamespace(
|
|
customer_name=name or None,
|
|
phone_no=phone or None,
|
|
id_card_no=id_card or None,
|
|
sex=sex or None,
|
|
age=_age_from_id_card(id_card),
|
|
birth_ym=birth_ym or None,
|
|
ethnic=ethnic or None,
|
|
email=email or None,
|
|
address=address or None,
|
|
)
|
|
|
|
|
|
def _create_role_customer(
|
|
db: Session,
|
|
*,
|
|
profile: SimpleNamespace,
|
|
tenant_no: str,
|
|
user_id: str,
|
|
commit: bool = True,
|
|
create_dept_id: str = "",
|
|
) -> str:
|
|
obj = customer_crud.create_customer(
|
|
db,
|
|
customer_name=profile.customer_name,
|
|
sex=profile.sex or "2",
|
|
id_card_no=profile.id_card_no or None,
|
|
age=profile.age,
|
|
birth_ym=profile.birth_ym,
|
|
ethnic=profile.ethnic,
|
|
email=profile.email,
|
|
address=profile.address,
|
|
phone_no=profile.phone_no or None,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
commit=commit,
|
|
create_dept_id=create_dept_id,
|
|
)
|
|
return obj.id
|
|
|
|
|
|
def _merge_and_repoint(
|
|
db: Session,
|
|
*,
|
|
from_id: str,
|
|
to_customer: Any,
|
|
tenant_no: str,
|
|
profile: Optional[SimpleNamespace] = None,
|
|
commit: bool = True,
|
|
) -> str:
|
|
source = get_customer(db, from_id, tenant_no=tenant_no)
|
|
if source is not None:
|
|
merge_customer_fields(to_customer, source)
|
|
db.add(to_customer)
|
|
if profile is not None:
|
|
apply_role_profile_overwrite(to_customer, profile)
|
|
db.add(to_customer)
|
|
repoint_customer_refs(db, from_id, to_customer.id, tenant_no)
|
|
customer_crud.soft_delete_customer(
|
|
db, from_id, tenant_no=tenant_no, commit=commit
|
|
)
|
|
return to_customer.id
|
|
|
|
|
|
def _sync_one_role(
|
|
db: Session,
|
|
party: Dict[str, Any],
|
|
spec: Dict[str, Any],
|
|
tenant_no: str,
|
|
user_id: str,
|
|
commit: bool = True,
|
|
create_dept_id: str = "",
|
|
) -> str:
|
|
profile = _build_role_profile(party, spec)
|
|
if _is_empty(profile.customer_name):
|
|
return ""
|
|
id_card = profile.id_card_no or ""
|
|
linked_id = _pick(party, spec["id_keys"])
|
|
|
|
found = find_customer_by_id_card(db, id_card, tenant_no) if id_card else None
|
|
|
|
if found is not None:
|
|
if linked_id and linked_id != found.id:
|
|
return _merge_and_repoint(
|
|
db,
|
|
from_id=linked_id,
|
|
to_customer=found,
|
|
tenant_no=tenant_no,
|
|
profile=profile,
|
|
commit=commit,
|
|
)
|
|
apply_role_profile_overwrite(found, profile)
|
|
db.add(found)
|
|
return found.id
|
|
|
|
if linked_id:
|
|
linked = get_customer(db, linked_id, tenant_no=tenant_no)
|
|
if linked is not None:
|
|
apply_role_profile_overwrite(linked, profile)
|
|
db.add(linked)
|
|
return linked.id
|
|
|
|
return _create_role_customer(
|
|
db,
|
|
profile=profile,
|
|
tenant_no=tenant_no,
|
|
user_id=user_id,
|
|
commit=commit,
|
|
create_dept_id=create_dept_id,
|
|
)
|
|
|
|
|
|
def sync_party_customers(
|
|
db: Session,
|
|
party_dict: Dict[str, Any],
|
|
tenant_no: str,
|
|
user_id: str,
|
|
*,
|
|
commit: bool = True,
|
|
create_dept_id: str = "",
|
|
) -> Dict[str, Any]:
|
|
out = dict(party_dict or {})
|
|
for spec in _ROLE_SPECS:
|
|
role_id = _sync_one_role(
|
|
db,
|
|
out,
|
|
spec,
|
|
tenant_no,
|
|
user_id,
|
|
commit=commit,
|
|
create_dept_id=create_dept_id,
|
|
)
|
|
for key in spec["id_keys"]:
|
|
out[key] = role_id
|
|
return out
|