Files
zkxw/backend/app/services/party_customer_sync.py
T

263 lines
7.9 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"),
"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"),
"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"),
"sex_keys": ("newborn_sex", "newbornSex"),
"default_sex": "2",
},
)
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 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
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 _create_role_customer(
db: Session,
*,
name: str,
sex: str,
id_card: str,
phone: str,
tenant_no: str,
user_id: str,
) -> str:
obj = customer_crud.create_customer(
db,
customer_name=name,
sex=sex,
id_card_no=id_card or None,
age=_age_from_id_card(id_card),
phone_no=phone or None,
tenant_no=tenant_no,
user_id=user_id,
)
return obj.id
def _apply_direct_identity(obj: Any, *, name: str, phone: str, id_card: str) -> None:
obj.customer_name = name
if phone:
obj.phone_no = phone
if id_card:
obj.id_card_no = id_card
age = _age_from_id_card(id_card)
if age is not None and _is_empty(getattr(obj, "age", None)):
obj.age = age
def _merge_and_repoint(
db: Session,
*,
from_id: str,
to_customer: Any,
tenant_no: str,
) -> 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)
repoint_customer_refs(db, from_id, to_customer.id, tenant_no)
customer_crud.soft_delete_customer(db, from_id, tenant_no=tenant_no)
return to_customer.id
def _sync_one_role(
db: Session,
party: Dict[str, Any],
spec: Dict[str, Any],
tenant_no: str,
user_id: str,
) -> str:
name = _pick(party, spec["name_keys"])
if not name:
return ""
phone = _pick(party, spec["phone_keys"])
id_card = _pick(party, spec["id_card_keys"])
linked_id = _pick(party, spec["id_keys"])
sex = _pick(party, spec["sex_keys"]) or spec["default_sex"]
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)
merge_customer_fields(
found,
SimpleNamespace(
phone_no=phone or None,
age=_age_from_id_card(id_card),
sex=sex,
remark=None,
customer_tags=None,
),
)
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_direct_identity(linked, name=name, phone=phone, id_card=id_card)
db.add(linked)
return linked.id
return _create_role_customer(
db,
name=name,
sex=sex,
id_card=id_card,
phone=phone,
tenant_no=tenant_no,
user_id=user_id,
)
def sync_party_customers(db: Session, party_dict: Dict[str, Any], tenant_no: str, user_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)
for key in spec["id_keys"]:
out[key] = role_id
return out