dingdanquanliucheng/backend/app/repositories/report_repository.py
wsb1224 5b8e41b209 feat: 报表导出增加订单明细
- CSV:每个统计周期汇总行后追加该周期的订单明细行
- XLSX:新增"订单明细"sheet,包含订单号、客户、产品、数量、单价、金额、利润、提成、日期
- report_repository 查询增加 order_no、customer_name、product_name 字段

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 14:23:33 +08:00

127 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""报表模块 Repository 层
负责销售绩效报表、业绩统计等报表相关数据的复杂查询操作。
被 ReportService报表服务调用。
"""
from datetime import datetime, timedelta
from sqlalchemy import and_, func, not_, or_, select
from sqlalchemy.orm import Session
from backend.app.models.business import Customer, Product, ProductCategory, SalesOrder, SalesOrderItem
# 电商来源关键词,用于排除电商渠道订单
ECOMMERCE_SOURCE_KEYWORDS = ["电商", "淘宝", "天猫", "京东", "拼多多", "1688", "线上", "抖音", "快手"]
class ReportRepository:
"""报表数据仓储
封装销售绩效等报表的复杂多表联查操作。
被 ReportService报表服务调用。
"""
def list_performance_rows(self, session: Session, filters: dict) -> list:
"""查询销售绩效明细数据
联表查询销售订单、订单明细、产品和产品分类,
返回按订单和明细粒度的绩效数据行。
支持按日期范围、产品分类过滤,以及排除电商渠道订单。
参数:
session: 数据库会话
filters: 过滤条件字典,支持 start_date / end_date / category_id / exclude_ecommerce
返回:
包含 order_id / created_at / order_source / commission_amount /
quantity / sale_price / category_id / category_name 的行列表
"""
stmt = (
select(
SalesOrder.id.label("order_id"),
SalesOrder.order_no.label("order_no"),
SalesOrder.created_at.label("created_at"),
SalesOrder.order_source.label("order_source"),
SalesOrder.commission_amount.label("commission_amount"),
SalesOrder.contract_amount.label("contract_amount"),
SalesOrder.cost_price_total.label("cost_price_total"),
SalesOrder.profit_total.label("profit_total"),
SalesOrder.profit_rate.label("profit_rate"),
SalesOrderItem.quantity.label("quantity"),
SalesOrderItem.sale_price.label("sale_price"),
Product.category_id.label("category_id"),
ProductCategory.category_name.label("category_name"),
Customer.customer_name.label("customer_name"),
Product.product_name.label("product_name"),
)
.join(
SalesOrderItem,
and_(SalesOrderItem.order_id == SalesOrder.id, SalesOrderItem.deleted == 0),
)
.outerjoin(
Customer,
and_(Customer.id == SalesOrder.customer_id, Customer.deleted == 0),
)
.outerjoin(
Product,
and_(Product.id == SalesOrderItem.product_id, Product.deleted == 0),
)
.outerjoin(
ProductCategory,
and_(ProductCategory.id == Product.category_id, ProductCategory.deleted == 0),
)
.where(
SalesOrder.deleted == 0,
SalesOrder.order_status != "canceled",
)
)
if filters.get("start_date"):
stmt = stmt.where(SalesOrder.created_at >= filters["start_date"])
if filters.get("end_date"):
stmt = stmt.where(SalesOrder.created_at < filters["end_date"] + timedelta(days=1))
if filters.get("category_id") is not None:
stmt = stmt.where(Product.category_id == filters["category_id"])
if filters.get("exclude_ecommerce"):
ecommerce_conditions = [
SalesOrder.order_source.like(f"%{kw}%") for kw in ECOMMERCE_SOURCE_KEYWORDS
]
stmt = stmt.where(not_(or_(*ecommerce_conditions)))
if filters.get("salesman_id") is not None:
stmt = stmt.where(SalesOrder.salesman_id == filters["salesman_id"])
if filters.get("order_type"):
stmt = stmt.where(SalesOrder.order_type == filters["order_type"])
stmt = stmt.order_by(SalesOrder.created_at.asc(), SalesOrder.id.asc(), SalesOrderItem.id.asc())
return list(session.execute(stmt).all())
def count_excluded_orders(self, session: Session, filters: dict) -> int:
"""统计被电商排除条件筛掉的订单数量
参数:
session: 数据库会话
filters: 过滤条件字典,需包含 start_date / end_date
返回:
被排除的订单数量
"""
stmt = (
select(func.count(func.distinct(SalesOrder.id)))
.where(
SalesOrder.deleted == 0,
SalesOrder.order_status != "canceled",
)
)
if filters.get("start_date"):
stmt = stmt.where(SalesOrder.created_at >= filters["start_date"])
if filters.get("end_date"):
stmt = stmt.where(SalesOrder.created_at < filters["end_date"] + timedelta(days=1))
if filters.get("salesman_id") is not None:
stmt = stmt.where(SalesOrder.salesman_id == filters["salesman_id"])
ecommerce_conditions = [
SalesOrder.order_source.like(f"%{kw}%") for kw in ECOMMERCE_SOURCE_KEYWORDS
]
stmt = stmt.where(or_(*ecommerce_conditions))
return session.execute(stmt).scalar() or 0