diff --git a/backend/app/repositories/reminder_repository.py b/backend/app/repositories/reminder_repository.py index c3c5d3f..29c2802 100644 --- a/backend/app/repositories/reminder_repository.py +++ b/backend/app/repositories/reminder_repository.py @@ -22,30 +22,37 @@ class ReminderRepository: 被 ReminderService(提醒服务)调用。 """ - def list_reminders(self, session: Session, filters: dict) -> list[SystemReminder]: - """查询系统提醒列表 + def list_reminders(self, session: Session, filters: dict) -> tuple[list[SystemReminder], int]: + """查询系统提醒列表(SQL 分页) 根据提醒类型、状态、接收人等条件过滤提醒记录, - 按 id 倒序返回。 + 按 id 倒序返回当前页数据及总条数。 参数: session: 数据库会话 - filters: 过滤条件字典,支持 reminder_type / status / receiver_user_id + filters: 过滤条件字典,支持 reminder_type / status / receiver_user_id / page_no / page_size 返回: - 符合条件的系统提醒列表 + (当前页系统提醒列表, 总条数) 元组 """ - stmt = select(SystemReminder) + base = select(SystemReminder) if filters.get("reminder_type"): - stmt = stmt.where(SystemReminder.reminder_type == filters["reminder_type"]) + base = base.where(SystemReminder.reminder_type == filters["reminder_type"]) if filters.get("status"): - stmt = stmt.where(SystemReminder.status == filters["status"]) + base = base.where(SystemReminder.status == filters["status"]) if filters.get("receiver_user_id") is not None: - stmt = stmt.where(SystemReminder.receiver_user_id == filters["receiver_user_id"]) + base = base.where(SystemReminder.receiver_user_id == filters["receiver_user_id"]) - stmt = stmt.order_by(SystemReminder.id.desc()) - return list(session.execute(stmt).scalars()) + count_stmt = select(func.count()).select_from(base.subquery()) + total = session.execute(count_stmt).scalar() or 0 + + page_no = filters.get("page_no", 1) + page_size = filters.get("page_size", 20) + offset = max(page_no - 1, 0) * page_size + stmt = base.order_by(SystemReminder.id.desc()).offset(offset).limit(page_size) + rows = list(session.execute(stmt).scalars()) + return rows, total def get_reminder(self, session: Session, reminder_id: int) -> SystemReminder | None: """根据 ID 查询单条系统提醒 @@ -364,29 +371,63 @@ class ReminderRepository: .order_by(Customer.id.asc()) ).scalars() ) + if not customers: + return [] + + customer_ids = [c.id for c in customers] + + # 批量查累计合同金额(替代逐个 N+1) + amount_rows = session.execute( + select( + SalesOrder.customer_id, + func.coalesce(func.sum(SalesOrder.contract_amount), 0).label("total_amount"), + ) + .where( + SalesOrder.deleted == 0, + SalesOrder.customer_id.in_(customer_ids), + SalesOrder.order_status != "canceled", + ) + .group_by(SalesOrder.customer_id) + ).all() + amount_map = {row.customer_id: float(row.total_amount) for row in amount_rows} + + # 批量查每个客户最近一笔订单(窗口函数,替代逐个 N+1) + from sqlalchemy import over + latest_order_sq = ( + select( + SalesOrder.customer_id, + SalesOrder.id.label("order_id"), + SalesOrder.created_at, + over(func.row_number(), partition_by=SalesOrder.customer_id, + order_by=(SalesOrder.created_at.desc(), SalesOrder.id.desc())).label("rn"), + ) + .where( + SalesOrder.deleted == 0, + SalesOrder.customer_id.in_(customer_ids), + SalesOrder.order_status != "canceled", + ) + .subquery() + ) + latest_rows = session.execute( + select(latest_order_sq).where(latest_order_sq.c.rn == 1) + ).all() + latest_order_map = {row.customer_id: row.order_id for row in latest_rows} + + # 组装结果,用已查到的 order_id 批量加载 SalesOrder 对象 + candidate_order_ids = [oid for oid in latest_order_map.values() if oid is not None] + if candidate_order_ids: + order_objs = session.execute( + select(SalesOrder).where(SalesOrder.id.in_(candidate_order_ids)) + ).scalars().all() + order_obj_map = {o.id: o for o in order_objs} + else: + order_obj_map = {} results: list[tuple[Customer, SalesOrder | None, float]] = [] for customer in customers: - total_amount_stmt = ( - select(func.coalesce(func.sum(SalesOrder.contract_amount), 0)) - .where( - SalesOrder.deleted == 0, - SalesOrder.customer_id == customer.id, - SalesOrder.order_status != "canceled", - ) - ) - total_amount = float(session.execute(total_amount_stmt).scalar() or 0) - - latest_order_stmt = ( - select(SalesOrder) - .where( - SalesOrder.deleted == 0, - SalesOrder.customer_id == customer.id, - SalesOrder.order_status != "canceled", - ) - .order_by(SalesOrder.created_at.desc(), SalesOrder.id.desc()) - ) - latest_order = session.execute(latest_order_stmt).scalars().first() + total_amount = amount_map.get(customer.id, 0.0) + latest_order_id = latest_order_map.get(customer.id) + latest_order = order_obj_map.get(latest_order_id) if latest_order_id else None if latest_order is None or (latest_order.created_at and latest_order.created_at <= inactive_before): results.append((customer, latest_order, total_amount)) return results diff --git a/backend/app/services/reminder_service.py b/backend/app/services/reminder_service.py index 0573d55..122b9c0 100644 --- a/backend/app/services/reminder_service.py +++ b/backend/app/services/reminder_service.py @@ -55,16 +55,14 @@ class ReminderService: if cached is not None: return cached try: - reminders = self.repository.list_reminders(session, filters) + reminders, total = self.repository.list_reminders(session, filters) page_no = filters.get("page_no", 1) page_size = filters.get("page_size", 20) - start = max(page_no - 1, 0) * page_size - page_list = reminders[start : start + page_size] result = { - "total": len(reminders), + "total": total, "page_no": page_no, "page_size": page_size, - "list": [self._map_reminder(item) for item in page_list], + "list": [self._map_reminder(item) for item in reminders], } cache_set(cache_key, result, ttl=30) return result