refactor(repos): 归属留痕共享数据访问,人员在职校验收口单点
- ownership_log_repository:客户/业务两侧逐行同构的 list/count/add 收口(owner_type 与引用列参数化),双键倒序与被删人员兜底口径 单点维护;两侧 repository 变薄包装,service 层零改动 - PERSONNEL_STATUS_ACTIVE 单点导出于 models/personnel(对照 BUSINESS_STATUS_* 先例);业务/客户两侧的「存在且在职」校验收口 于 personnel_service.ensure_active——此前两份常量+校验仅靠注释 维持同步,在职判定调整时会两入口口径分叉;校验放 service 层遵守 「repositories 不抛业务异常」红线 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
409e26614f
commit
60f6af6494
7 files changed
+119
-85
No files matched your search
@@ -21,6 +21,7 @@ from app.models import (
|
||||
Shipment,
|
||||
)
|
||||
from app.models.customer import OWNER_TYPE_BUSINESS
|
||||
from app.repositories import ownership_log_repository
|
||||
|
||||
|
||||
def get_by_id(session: Session, business_id: int, *, for_update: bool = False) -> Business | None:
|
||||
@@ -229,41 +230,28 @@ def count_status_logs(session: Session, business_id: int) -> int:
|
||||
|
||||
|
||||
def add_ownership_log(session: Session, log: OwnershipChangeLog) -> None:
|
||||
"""追加归属变更留痕(不 commit)。"""
|
||||
session.add(log)
|
||||
session.flush()
|
||||
"""追加归属变更留痕(共享实现,不 commit)。"""
|
||||
ownership_log_repository.add(session, log)
|
||||
|
||||
|
||||
def list_ownership_logs(
|
||||
session: Session, business_id: int, *, offset: int, limit: int
|
||||
) -> list[Row]:
|
||||
"""某业务的归属变更留痕(join 人员出姓名;人员删除后为 None,留痕固化 ID 兜底)。"""
|
||||
old_p = aliased(Personnel)
|
||||
new_p = aliased(Personnel)
|
||||
return list(
|
||||
session.execute(
|
||||
select(OwnershipChangeLog, old_p.personnel_name, new_p.personnel_name)
|
||||
.outerjoin(old_p, old_p.id == OwnershipChangeLog.old_owner_personnel_id)
|
||||
.outerjoin(new_p, new_p.id == OwnershipChangeLog.new_owner_personnel_id)
|
||||
.where(
|
||||
OwnershipChangeLog.owner_type == OWNER_TYPE_BUSINESS,
|
||||
OwnershipChangeLog.business_id == business_id,
|
||||
)
|
||||
.order_by(OwnershipChangeLog.create_time.desc(), OwnershipChangeLog.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
).all()
|
||||
"""某业务的归属变更留痕(共享实现,owner_type/business_id 参数化)。"""
|
||||
return ownership_log_repository.list_by_ref(
|
||||
session,
|
||||
owner_type=OWNER_TYPE_BUSINESS,
|
||||
ref_column=OwnershipChangeLog.business_id,
|
||||
ref_id=business_id,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
def count_ownership_logs(session: Session, business_id: int) -> int:
|
||||
return int(
|
||||
session.execute(
|
||||
select(func.count())
|
||||
.select_from(OwnershipChangeLog)
|
||||
.where(
|
||||
OwnershipChangeLog.owner_type == OWNER_TYPE_BUSINESS,
|
||||
OwnershipChangeLog.business_id == business_id,
|
||||
)
|
||||
).scalar_one()
|
||||
return ownership_log_repository.count_by_ref(
|
||||
session,
|
||||
owner_type=OWNER_TYPE_BUSINESS,
|
||||
ref_column=OwnershipChangeLog.business_id,
|
||||
ref_id=business_id,
|
||||
)
|
||||
@@ -4,17 +4,13 @@
|
||||
"""
|
||||
|
||||
from sqlalchemy import ColumnElement, Row, Select, func, or_, select
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import Customer, OwnershipChangeLog, Personnel
|
||||
from app.models.customer import OWNER_TYPE_CUSTOMER
|
||||
from app.repositories import ownership_log_repository
|
||||
from app.repositories.query_utils import fuzzy
|
||||
|
||||
# 归属留痕表对人员 ID 无外键固化:新旧人员姓名靠对 personnel 的两次外联带出,
|
||||
# 人员被删除后姓名为 null,行仍完整可读
|
||||
_old_owner = aliased(Personnel)
|
||||
_new_owner = aliased(Personnel)
|
||||
|
||||
# ==================== 读取 ====================
|
||||
|
||||
|
||||
@@ -102,33 +98,25 @@ def list_ownership_logs(
|
||||
offset: int,
|
||||
limit: int,
|
||||
) -> list[Row]:
|
||||
"""查某客户的归属变更留痕,行内容为 (留痕, 旧归属人名, 新归属人名)。
|
||||
|
||||
按 (create_time, id) 双键倒序:create_time 是秒级精度,同一秒内多条
|
||||
记录排序不确定,id 兜底保证翻页稳定(与操作日志同一理由)。
|
||||
"""
|
||||
stmt = (
|
||||
select(OwnershipChangeLog, _old_owner.personnel_name, _new_owner.personnel_name)
|
||||
.outerjoin(_old_owner, OwnershipChangeLog.old_owner_personnel_id == _old_owner.id)
|
||||
.outerjoin(_new_owner, OwnershipChangeLog.new_owner_personnel_id == _new_owner.id)
|
||||
.where(
|
||||
OwnershipChangeLog.owner_type == OWNER_TYPE_CUSTOMER,
|
||||
OwnershipChangeLog.customer_id == customer_id,
|
||||
)
|
||||
.order_by(OwnershipChangeLog.create_time.desc(), OwnershipChangeLog.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
"""查某客户的归属变更留痕(共享实现,owner_type/customer_id 参数化)。"""
|
||||
return ownership_log_repository.list_by_ref(
|
||||
session,
|
||||
owner_type=OWNER_TYPE_CUSTOMER,
|
||||
ref_column=OwnershipChangeLog.customer_id,
|
||||
ref_id=customer_id,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
return list(session.execute(stmt).all())
|
||||
|
||||
|
||||
def count_ownership_logs(session: Session, *, customer_id: int) -> int:
|
||||
"""统计某客户的归属留痕总数,供分页使用。"""
|
||||
stmt = select(func.count(OwnershipChangeLog.id)).where(
|
||||
OwnershipChangeLog.owner_type == OWNER_TYPE_CUSTOMER,
|
||||
OwnershipChangeLog.customer_id == customer_id,
|
||||
return ownership_log_repository.count_by_ref(
|
||||
session,
|
||||
owner_type=OWNER_TYPE_CUSTOMER,
|
||||
ref_column=OwnershipChangeLog.customer_id,
|
||||
ref_id=customer_id,
|
||||
)
|
||||
return int(session.execute(stmt).scalar_one())
|
||||
|
||||
|
||||
def _base_select() -> Select:
|
||||
@@ -186,7 +174,5 @@ def delete(session: Session, customer: Customer) -> None:
|
||||
|
||||
|
||||
def add_ownership_log(session: Session, log: OwnershipChangeLog) -> OwnershipChangeLog:
|
||||
"""写入一条归属变更留痕。只 flush 不 commit,与主表归属更新同事务提交。"""
|
||||
session.add(log)
|
||||
session.flush()
|
||||
return log
|
||||
"""写入一条归属变更留痕(共享实现)。只 flush 不 commit,与主表更新同事务。"""
|
||||
return ownership_log_repository.add(session, log)
|
||||
@@ -0,0 +1,58 @@
|
||||
"""归属变更留痕的共享数据访问(客户/业务两维度共用)。
|
||||
|
||||
owner_type 与引用列由调用方参数化 —— 此前客户、业务两侧各持一套逐行
|
||||
同构的 list/count/add,排序口径与 join 兜底的调整须同步两处;收口后
|
||||
「被删人员留痕仍可读」「翻页稳定双键倒序」等口径单点维护。
|
||||
"""
|
||||
|
||||
from sqlalchemy import Row, func, select
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
|
||||
from app.models import OwnershipChangeLog, Personnel
|
||||
|
||||
|
||||
def list_by_ref(
|
||||
session: Session,
|
||||
*,
|
||||
owner_type: int,
|
||||
ref_column,
|
||||
ref_id: int,
|
||||
offset: int,
|
||||
limit: int,
|
||||
) -> list[Row]:
|
||||
"""查某引用对象的归属留痕,行内容为 (留痕, 旧归属人名, 新归属人名)。
|
||||
|
||||
人员删除后 join 出 None,留痕固化 ID 兜底可读。按 (create_time, id)
|
||||
双键倒序:create_time 是秒级精度,同一秒内多条排序不确定,id 兜底
|
||||
保证翻页稳定(与操作日志同一理由)。
|
||||
"""
|
||||
old_person = aliased(Personnel)
|
||||
new_person = aliased(Personnel)
|
||||
stmt = (
|
||||
select(OwnershipChangeLog, old_person.personnel_name, new_person.personnel_name)
|
||||
.outerjoin(old_person, OwnershipChangeLog.old_owner_personnel_id == old_person.id)
|
||||
.outerjoin(new_person, OwnershipChangeLog.new_owner_personnel_id == new_person.id)
|
||||
.where(OwnershipChangeLog.owner_type == owner_type, ref_column == ref_id)
|
||||
.order_by(OwnershipChangeLog.create_time.desc(), OwnershipChangeLog.id.desc())
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
)
|
||||
return list(session.execute(stmt).all())
|
||||
|
||||
|
||||
def count_by_ref(
|
||||
session: Session, *, owner_type: int, ref_column, ref_id: int
|
||||
) -> int:
|
||||
"""统计某引用对象的归属留痕总数,供分页使用。"""
|
||||
stmt = select(func.count(OwnershipChangeLog.id)).where(
|
||||
OwnershipChangeLog.owner_type == owner_type,
|
||||
ref_column == ref_id,
|
||||
)
|
||||
return int(session.execute(stmt).scalar_one())
|
||||
|
||||
|
||||
def add(session: Session, log: OwnershipChangeLog) -> OwnershipChangeLog:
|
||||
"""写入一条归属变更留痕。只 flush 不 commit,与主表归属更新同事务提交。"""
|
||||
session.add(log)
|
||||
session.flush()
|
||||
return log
|
||||
Reference in new issue
Block a user