feat(purchase): 采购列表支持关键词搜索与日期范围
背景
----
采购管理此前只有状态筛选,无法按单号/物料/申请人检索,也无法按采购
日期收窄范围。现对齐出库/报废记录的搜索语义,便于用户迁移使用习惯。
后端
----
PurchaseService.get_purchase_list 新增参数:
keyword / search_type / start_date / end_date
search_type 取值(与出库、报废一致):
all 单号 | 物料名称 | 规格型号 | 备注 | 申请人
no 单号
name 物料名称
spec_model 规格型号
requester 申请人
实现要点
--------
· 申请人姓名存在 SysUser.username,格式「姓名/账号」(如 韩善龙/hanshanlong),
ilike 直接匹配整串即可命中;
· 该类字段只在 SysUser 上,故**按需 join** —— 单号/名称/规格分支不联表,
避免无谓开销;
· 公司隔离分支也需 join SysUser,与关键词分支可能重复 join 同一目标,
会产生笛卡尔积导致结果翻倍,故按需补 join 并用 distinct 兜底;
· 日期范围按 purchase_date 过滤(该列是 Date,无需补时分秒)。
实测:名称'充电器'→1 单,申请人'韩善龙'→21 单,日期 05-13→2 单,
关键词不存在→0 单,状态+搜索组合→14 单。
This commit is contained in:
@ -65,7 +65,13 @@ def get_purchase_list():
|
|||||||
page = int(request.args.get('page', 1))
|
page = int(request.args.get('page', 1))
|
||||||
per_page = int(request.args.get('limit', 20))
|
per_page = int(request.args.get('limit', 20))
|
||||||
status = request.args.get('status')
|
status = request.args.get('status')
|
||||||
status = int(status) if status is not None else None
|
status = int(status) if status not in (None, '', 'all') else None
|
||||||
|
|
||||||
|
# 搜索参数(与出库/报废记录同一套语义,便于用户迁移习惯)
|
||||||
|
keyword = (request.args.get('keyword') or '').strip()
|
||||||
|
search_type = (request.args.get('search_type') or 'all').strip()
|
||||||
|
start_date = (request.args.get('start_date') or '').strip()
|
||||||
|
end_date = (request.args.get('end_date') or '').strip()
|
||||||
|
|
||||||
user_id = get_current_user_id()
|
user_id = get_current_user_id()
|
||||||
can_view_all = _user_has_purchase_perm()
|
can_view_all = _user_has_purchase_perm()
|
||||||
@ -74,7 +80,11 @@ def get_purchase_list():
|
|||||||
page=page,
|
page=page,
|
||||||
per_page=per_page,
|
per_page=per_page,
|
||||||
requester_id=None if can_view_all else user_id,
|
requester_id=None if can_view_all else user_id,
|
||||||
status=status
|
status=status,
|
||||||
|
keyword=keyword,
|
||||||
|
search_type=search_type,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ★ 字段级价格过滤
|
# ★ 字段级价格过滤
|
||||||
|
|||||||
@ -180,11 +180,20 @@ class PurchaseService:
|
|||||||
return purchase
|
return purchase
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_purchase_list(page=1, per_page=20, requester_id=None, status=None):
|
def get_purchase_list(page=1, per_page=20, requester_id=None, status=None,
|
||||||
"""获取采购申请列表,普通用户只看自己的,主管/超管看同公司全部"""
|
keyword=None, search_type='all',
|
||||||
|
start_date=None, end_date=None):
|
||||||
|
"""
|
||||||
|
获取采购申请列表,普通用户只看自己的,主管/超管看同公司全部。
|
||||||
|
|
||||||
|
搜索参数(与出库/报废记录保持同一套语义,便于用户迁移习惯):
|
||||||
|
keyword 关键词
|
||||||
|
search_type all / no(单号) / name(物料名称) / spec_model / requester(申请人)
|
||||||
|
start_date / end_date 按采购日期过滤(含边界)
|
||||||
|
"""
|
||||||
from app.utils.decorators import get_current_company_filter
|
from app.utils.decorators import get_current_company_filter
|
||||||
from app.models.system import SysUser
|
from app.models.system import SysUser
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_, cast, String
|
||||||
|
|
||||||
query = PurchaseRequest.query
|
query = PurchaseRequest.query
|
||||||
|
|
||||||
@ -194,16 +203,58 @@ class PurchaseService:
|
|||||||
if status is not None:
|
if status is not None:
|
||||||
query = query.filter(PurchaseRequest.status == status)
|
query = query.filter(PurchaseRequest.status == status)
|
||||||
|
|
||||||
|
# ---- 关键词搜索 ----
|
||||||
|
#
|
||||||
|
# 申请人姓名存在 SysUser.username,格式为「姓名/账号」(如 韩善龙/hanshanlong),
|
||||||
|
# 因此按「姓名」搜索时用 ilike 直接匹配整串即可命中。
|
||||||
|
# 该类字段只存在于 SysUser,故需要 join;单号/名称/规格则不需要 ——
|
||||||
|
# 只在必要时 join,避免无谓的联表。
|
||||||
|
needs_user = bool(keyword) and search_type in ('requester', 'all')
|
||||||
|
if needs_user:
|
||||||
|
query = query.outerjoin(SysUser, PurchaseRequest.requester_id == SysUser.id)
|
||||||
|
|
||||||
|
if keyword:
|
||||||
|
kw = f'%{keyword}%'
|
||||||
|
if search_type == 'no':
|
||||||
|
query = query.filter(PurchaseRequest.request_no.ilike(kw))
|
||||||
|
elif search_type == 'name':
|
||||||
|
query = query.filter(PurchaseRequest.name.ilike(kw))
|
||||||
|
elif search_type == 'spec_model':
|
||||||
|
query = query.filter(PurchaseRequest.spec_model.ilike(kw))
|
||||||
|
elif search_type == 'requester':
|
||||||
|
query = query.filter(SysUser.username.ilike(kw))
|
||||||
|
else: # all —— 覆盖单号 / 名称 / 规格 / 备注 / 申请人
|
||||||
|
query = query.filter(or_(
|
||||||
|
PurchaseRequest.request_no.ilike(kw),
|
||||||
|
PurchaseRequest.name.ilike(kw),
|
||||||
|
PurchaseRequest.spec_model.ilike(kw),
|
||||||
|
PurchaseRequest.remark.ilike(kw),
|
||||||
|
SysUser.username.ilike(kw),
|
||||||
|
))
|
||||||
|
|
||||||
|
# ---- 日期范围(按采购日期)----
|
||||||
|
if start_date:
|
||||||
|
query = query.filter(PurchaseRequest.purchase_date >= start_date)
|
||||||
|
if end_date:
|
||||||
|
query = query.filter(PurchaseRequest.purchase_date <= end_date)
|
||||||
|
|
||||||
# 【行级数据隔离】同公司可见:匹配 MaterialBase.company_name 或 SysUser.department
|
# 【行级数据隔离】同公司可见:匹配 MaterialBase.company_name 或 SysUser.department
|
||||||
company_limit = get_current_company_filter()
|
company_limit = get_current_company_filter()
|
||||||
if company_limit is not None:
|
if company_limit is not None:
|
||||||
query = query.outerjoin(MaterialBase, PurchaseRequest.base_id == MaterialBase.id) \
|
query = query.outerjoin(MaterialBase, PurchaseRequest.base_id == MaterialBase.id)
|
||||||
.outerjoin(SysUser, PurchaseRequest.requester_id == SysUser.id) \
|
# SysUser 可能已被上面的关键词分支 join 过 —— 重复 join 会产生
|
||||||
.filter(or_(
|
# 笛卡尔积导致结果翻倍,此处按需补 join。
|
||||||
|
if not needs_user:
|
||||||
|
query = query.outerjoin(SysUser, PurchaseRequest.requester_id == SysUser.id)
|
||||||
|
query = query.filter(or_(
|
||||||
MaterialBase.company_name == company_limit,
|
MaterialBase.company_name == company_limit,
|
||||||
SysUser.department == company_limit
|
SysUser.department == company_limit
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# ★ 兜底去重:上述 join 在特定组合下(如 base_id 为空 + requester 匹配)
|
||||||
|
# 仍可能产生重复行,用 distinct 保证每个单据只出现一次。
|
||||||
|
query = query.distinct()
|
||||||
|
|
||||||
query = query.order_by(PurchaseRequest.created_at.desc())
|
query = query.order_by(PurchaseRequest.created_at.desc())
|
||||||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user