diff --git a/inventory-backend/app/api/v1/inbound/buy.py b/inventory-backend/app/api/v1/inbound/buy.py index 590ac49..7ccbd51 100644 --- a/inventory-backend/app/api/v1/inbound/buy.py +++ b/inventory-backend/app/api/v1/inbound/buy.py @@ -1,4 +1,3 @@ -# app/api/v1/inbound/buy.py from flask import Blueprint, request, jsonify from app.services.inbound.buy_service import BuyInboundService import traceback @@ -7,14 +6,10 @@ inbound_buy_bp = Blueprint('stock_buy', __name__) # ------------------------------------------------------------------ -# 0. 基础物料搜索 (已修改:支持 page 参数) +# 0. 基础物料搜索 # ------------------------------------------------------------------ @inbound_buy_bp.route('/search-base', methods=['GET']) def search_base(): - """ - 供前端下拉框远程搜索使用,支持分页 - Query Param: keyword (名称或规格), page (默认1) - """ try: keyword = request.args.get('keyword', '') page = request.args.get('page', 1, type=int) @@ -35,7 +30,7 @@ def search_base(): # ------------------------------------------------------------------ -# 1. 获取列表 +# 1. 获取列表 (修改:接收 category 和 material_type) # ------------------------------------------------------------------ @inbound_buy_bp.route('/list', methods=['GET']) def get_list(): @@ -44,11 +39,15 @@ def get_list(): limit = request.args.get('pageSize', 15, type=int) keyword = request.args.get('keyword', '') - # 获取状态列表参数,前端传参格式: statuses=在库,借库 + # 新增筛选参数 + category = request.args.get('category', '') + material_type = request.args.get('material_type', '') + + # 状态参数处理 statuses_str = request.args.get('statuses', '') statuses = statuses_str.split(',') if statuses_str else [] - result = BuyInboundService.get_list(page, limit, keyword, statuses) + result = BuyInboundService.get_list(page, limit, keyword, statuses, category, material_type) return jsonify({"code": 200, "msg": "success", "data": result}) except Exception as e: traceback.print_exc() @@ -103,7 +102,19 @@ def delete_buy(id): # ------------------------------------------------------------------ -# 5. 获取关联的出库历史 (如果有) +# 5. [新增] 获取筛选下拉选项 (修复404的关键) +# ------------------------------------------------------------------ +@inbound_buy_bp.route('/options', methods=['GET']) +def get_options(): + try: + data = BuyInboundService.get_filter_options() + return jsonify({"code": 200, "msg": "success", "data": data}) + except Exception as e: + return jsonify({"code": 500, "msg": str(e)}), 500 + + +# ------------------------------------------------------------------ +# 6. 获取关联的出库历史 (如果有) # ------------------------------------------------------------------ @inbound_buy_bp.route('//history', methods=['GET']) def get_history(id): @@ -112,7 +123,7 @@ def get_history(id): # ------------------------------------------------------------------ -# 6. 供应商建议 +# 7. 供应商建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/suppliers', methods=['GET']) def get_supplier_suggestions(): @@ -124,7 +135,7 @@ def get_supplier_suggestions(): # ------------------------------------------------------------------ -# 7. 采购人建议 (全局) +# 8. 采购人建议 (全局) # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/users', methods=['GET']) def get_user_suggestions(): @@ -134,7 +145,7 @@ def get_user_suggestions(): # ------------------------------------------------------------------ -# 8. 链接建议 +# 9. 链接建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/links', methods=['GET']) def get_link_suggestions(): @@ -147,7 +158,7 @@ def get_link_suggestions(): # ------------------------------------------------------------------ -# 9. [新增] 库位建议 +# 10. 库位建议 # ------------------------------------------------------------------ @inbound_buy_bp.route('/suggestions/locations', methods=['GET']) def get_location_suggestions(): diff --git a/inventory-backend/app/services/inbound/buy_service.py b/inventory-backend/app/services/inbound/buy_service.py index 0790c7d..c975ea5 100644 --- a/inventory-backend/app/services/inbound/buy_service.py +++ b/inventory-backend/app/services/inbound/buy_service.py @@ -10,7 +10,7 @@ import json class BuyInboundService: # ============================================================ - # 0. 辅助:唯一性校验 (保持不变) + # 0. 辅助:唯一性校验 # ============================================================ @staticmethod def _check_unique(base_id, serial_number, batch_number, exclude_id=None): @@ -34,34 +34,24 @@ class BuyInboundService: raise ValueError(f"该物料已存在批号【{batch_number}】,请勿重复录入,可直接在该批次下追加库存。") # ============================================================ - # 1. 基础物料搜索 (修复:逻辑优先级 & 模糊匹配) + # 1. 基础物料搜索 # ============================================================ @staticmethod def search_base_material(keyword, page=1, limit=50): try: - # 1. 基础查询:只查询启用的 query = MaterialBase.query.filter(MaterialBase.is_enabled == True) if keyword: k = keyword.strip() k_str = f'%{k}%' - - # [核心修复] 使用 and_ 确保逻辑优先级正确 - # 生成 SQL 类似: WHERE is_enabled = true AND (name LIKE %k% OR spec LIKE %k% ...) query = query.filter(and_( or_( - MaterialBase.name.ilike(k_str), # ilike 忽略大小写 - MaterialBase.spec_model.ilike(k_str), - MaterialBase.pinyin.ilike(k_str), - # 如果需要支持 ID 搜索,取消下面注释 - # func.cast(MaterialBase.id, String).ilike(k_str) + MaterialBase.name.ilike(k_str), + MaterialBase.spec_model.ilike(k_str) ) )) - # 2. 排序:ID 倒序 query = query.order_by(MaterialBase.id.desc()) - - # 3. 分页 pagination = query.paginate(page=page, per_page=limit, error_out=False) items = [] @@ -90,7 +80,7 @@ class BuyInboundService: return {"items": [], "total": 0, "page": 1, "has_next": False} # ============================================================ - # 2. 新增入库逻辑 (保持不变) + # 2. 新增入库逻辑 # ============================================================ @staticmethod def handle_inbound(data): @@ -157,7 +147,7 @@ class BuyInboundService: raise e # ============================================================ - # 3. 更新入库 (保持不变) + # 3. 更新入库 # ============================================================ @staticmethod def update_inbound(stock_id, data): @@ -197,7 +187,7 @@ class BuyInboundService: raise e # ============================================================ - # 4. 删除 (保持不变) + # 4. 删除 # ============================================================ @staticmethod def delete_inbound(stock_id): @@ -212,21 +202,37 @@ class BuyInboundService: raise e # ============================================================ - # 5. 获取列表 (保持不变) + # 5. 获取列表 # ============================================================ @staticmethod - def get_list(page, limit, keyword=None, statuses=None): + def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None): try: query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id) + + # 1. 通用关键词搜索 if keyword: k_str = f'%{keyword.strip()}%' - conditions = [StockBuy.sku.ilike(k_str), StockBuy.barcode.ilike(k_str), - StockBuy.batch_number.ilike(k_str), StockBuy.serial_number.ilike(k_str), - StockBuy.supplier_name.ilike(k_str), StockBuy.buyer_name.ilike(k_str), - MaterialBase.name.ilike(k_str), MaterialBase.spec_model.ilike(k_str), - MaterialBase.pinyin.ilike(k_str)] + conditions = [ + StockBuy.sku.ilike(k_str), + StockBuy.barcode.ilike(k_str), + StockBuy.batch_number.ilike(k_str), + StockBuy.serial_number.ilike(k_str), + StockBuy.supplier_name.ilike(k_str), + StockBuy.buyer_name.ilike(k_str), + MaterialBase.name.ilike(k_str), # 名称 + MaterialBase.spec_model.ilike(k_str), # 规格 + ] query = query.filter(or_(*conditions)) + # 2. 类别独立搜索 + if category and category.strip(): + query = query.filter(MaterialBase.category == category.strip()) # 下拉框通常是精确匹配 + + # 3. 类型独立搜索 + if material_type and material_type.strip(): + query = query.filter(MaterialBase.material_type == material_type.strip()) # 精确匹配 + + # 4. 状态筛选 if not statuses: statuses = ['在库', '借库'] if '已出库' in statuses: query = query.filter(StockBuy.status.in_(statuses)) @@ -259,7 +265,31 @@ class BuyInboundService: traceback.print_exc() return {"total": 0, "items": []} - # 6-9 建议类接口保持不变 (略以节省篇幅,原样保留即可) + # ============================================================ + # 6. [新增] 获取筛选选项(类别、类型) + # ============================================================ + @staticmethod + def get_filter_options(): + try: + # 获取所有非空的类别 + categories = db.session.query(MaterialBase.category) \ + .filter(MaterialBase.category != None, MaterialBase.category != '') \ + .distinct().all() + + # 获取所有非空的类型 + types = db.session.query(MaterialBase.material_type) \ + .filter(MaterialBase.material_type != None, MaterialBase.material_type != '') \ + .distinct().all() + + return { + "categories": [r[0] for r in categories], + "types": [r[0] for r in types] + } + except Exception: + traceback.print_exc() + return {"categories": [], "types": []} + + # 7-10 建议类接口保持不变 @staticmethod def get_history_suppliers(base_id): return [r[0] for r in db.session.query(StockBuy.supplier_name).filter(StockBuy.base_id == base_id, diff --git a/inventory-web/src/api/inbound/buy.ts b/inventory-web/src/api/inbound/buy.ts index 84ddc62..e7e99d2 100644 --- a/inventory-web/src/api/inbound/buy.ts +++ b/inventory-web/src/api/inbound/buy.ts @@ -35,7 +35,15 @@ export function deleteBuyInbound(id: number) { }) } -// 5. 搜索基础物料 (修改:支持分页参数) +// 5. [新增] 获取筛选下拉选项(类别、类型) +export function getFilterOptions() { + return request({ + url: '/inbound/buy/options', + method: 'get' + }) +} + +// 6. 搜索基础物料 export function searchMaterialBase(keyword: string, page: number = 1) { return request({ url: '/inbound/buy/search-base', @@ -44,7 +52,7 @@ export function searchMaterialBase(keyword: string, page: number = 1) { }) } -// 6. 文件上传 (用于图片/拍照) +// 7. 文件上传 export function uploadFile(data: FormData) { return request({ url: '/common/upload', @@ -54,7 +62,7 @@ export function uploadFile(data: FormData) { }) } -// 7. 文件删除 +// 8. 文件删除 export function deleteFile(filename: string) { return request({ url: `/common/files/${filename}`, @@ -62,7 +70,7 @@ export function deleteFile(filename: string) { }) } -// 8. 供应商建议 +// 9. 供应商建议 export function getSupplierSuggestions(params: any) { return request({ url: '/inbound/buy/suggestions/suppliers', @@ -71,7 +79,7 @@ export function getSupplierSuggestions(params: any) { }) } -// 9. 用户建议 (采购人) +// 10. 用户建议 export function getUserSuggestions(params: any) { return request({ url: '/inbound/buy/suggestions/users', @@ -80,7 +88,7 @@ export function getUserSuggestions(params: any) { }) } -// 10. 链接建议 +// 11. 链接建议 export function getLinkSuggestions(params: any) { return request({ url: '/inbound/buy/suggestions/links', @@ -89,7 +97,7 @@ export function getLinkSuggestions(params: any) { }) } -// 11. 库位建议 +// 12. 库位建议 export function getLocationSuggestions(params: any) { return request({ url: '/inbound/buy/suggestions/locations', diff --git a/inventory-web/src/views/stock/inbound/buy.vue b/inventory-web/src/views/stock/inbound/buy.vue index 7504ab1..f2a0538 100644 --- a/inventory-web/src/views/stock/inbound/buy.vue +++ b/inventory-web/src/views/stock/inbound/buy.vue @@ -1,42 +1,51 @@