"""customer_relation 客户-投顾关系仓储:数据权限根(仅本人名下客户)+ 客户列表 + AUM 聚合。""" from __future__ import annotations from decimal import Decimal from sqlalchemy import func, or_, select from model.customer_relation import CustomerRelation from model.fin_customer_profile import FinCustomerProfile from model.fin_holdings import FinHoldings from model.sys_user import SysUser from repositories.base import BaseRepository # 持仓中状态:仅统计「持有中」市值,与 service/holdings.py 口径一致(DDL 注释,未入 common_const) _HOLDING_STATUS = "持有中" class CustomerRelationRepo(BaseRepository): model = CustomerRelation async def get_by_customer_advisor( self, customer_id: int, advisor_id: int ) -> CustomerRelation | None: """取指定客户与投顾的关系(数据权限判断用)。""" return await self.db.scalar( select(CustomerRelation).where( CustomerRelation.customer_id == customer_id, CustomerRelation.advisor_id == advisor_id, ) ) async def get_current_by_customer(self, customer_id: int) -> CustomerRelation | None: """取客户当前有效关系(历史重分配可能多行,取最近一条,按 id 倒序)。""" return await self.db.scalar( select(CustomerRelation) .where(CustomerRelation.customer_id == customer_id) .order_by(CustomerRelation.id.desc()) .limit(1) ) async def list_by_status(self, status: str) -> list[CustomerRelation]: """按状态取全部关系(定时调度遍历 signed 客户用,跨投顾)。""" return list( ( await self.db.scalars( select(CustomerRelation).where(CustomerRelation.status == status) ) ).all() ) async def list_by_advisor( self, advisor_id: int, status: str | None = None ) -> list[CustomerRelation]: """名下客户关系列表(status=None 不过滤)。""" stmt = select(CustomerRelation).where(CustomerRelation.advisor_id == advisor_id) if status: stmt = stmt.where(CustomerRelation.status == status) return list((await self.db.scalars(stmt)).all()) async def list_customer_rows( self, *, advisor_id: int, status: str | None = None, keyword: str | None = None, limit: int = 100, offset: int = 0, ) -> list[tuple[CustomerRelation, SysUser, FinCustomerProfile | None]]: """客户列表:关系 + 客户账号 + 画像(左连),返回三元组供 service 拼装响应。""" conds = [CustomerRelation.advisor_id == advisor_id] if status: conds.append(CustomerRelation.status == status) if keyword: like = f"%{keyword}%" conds.append(or_(SysUser.real_name.like(like), SysUser.phone.like(like))) stmt = ( select(CustomerRelation, SysUser, FinCustomerProfile) .join(SysUser, SysUser.id == CustomerRelation.customer_id) .outerjoin( FinCustomerProfile, FinCustomerProfile.customer_id == CustomerRelation.customer_id, ) .where(*conds) .order_by(CustomerRelation.id.desc()) .limit(limit) .offset(offset) ) return list((await self.db.execute(stmt)).all()) async def count_customer_rows( self, *, advisor_id: int, status: str | None = None, keyword: str | None = None, ) -> int: conds = [CustomerRelation.advisor_id == advisor_id] if status: conds.append(CustomerRelation.status == status) if keyword: like = f"%{keyword}%" conds.append(or_(SysUser.real_name.like(like), SysUser.phone.like(like))) stmt = ( select(func.count()) .select_from(CustomerRelation) .join(SysUser, SysUser.id == CustomerRelation.customer_id) .where(*conds) ) return (await self.db.scalar(stmt)) or 0 async def risk_level_distribution( self, advisor_id: int ) -> list[tuple[str | None, int]]: """名下客户风险等级分布(C1-C5 各档人数),供驾驶舱客户分层。""" stmt = ( select(FinCustomerProfile.risk_level, func.count()) .select_from(CustomerRelation) .join( FinCustomerProfile, FinCustomerProfile.customer_id == CustomerRelation.customer_id, ) .where(CustomerRelation.advisor_id == advisor_id) .group_by(FinCustomerProfile.risk_level) ) return list((await self.db.execute(stmt)).all()) async def sum_holdings_value( self, advisor_id: int, relation_status: str | None = None ) -> Decimal: """AUM:名下客户「持有中」持仓当前市值之和(relation_status 过滤,如 signed)。""" stmt = ( select(func.coalesce(func.sum(FinHoldings.current_value), 0)) .select_from(CustomerRelation) .join(FinHoldings, FinHoldings.customer_id == CustomerRelation.customer_id) .where( CustomerRelation.advisor_id == advisor_id, FinHoldings.status == _HOLDING_STATUS, ) ) if relation_status: stmt = stmt.where(CustomerRelation.status == relation_status) return await self.db.scalar(stmt)