diff --git a/inventory-backend/app/__init__.py b/inventory-backend/app/__init__.py index b2a08f0..22266b7 100644 --- a/inventory-backend/app/__init__.py +++ b/inventory-backend/app/__init__.py @@ -45,7 +45,7 @@ def create_app(): with app.app_context(): try: # 1. 基础物料 - from app.models.material import MaterialBase + from app.models.base import MaterialBase # 2. 采购入库 from app.models.inbound.buy import StockBuy # 3. 半成品入库 diff --git a/inventory-backend/app/services/inbound/base_service.py b/inventory-backend/app/services/inbound/base_service.py index cb4434c..7b1ce02 100644 --- a/inventory-backend/app/services/inbound/base_service.py +++ b/inventory-backend/app/services/inbound/base_service.py @@ -1,7 +1,7 @@ # 文件路径: app/services/inbound/base_service.py from app.extensions import db -from app.models.material import MaterialBase +from app.models.base import MaterialBase # ============================================================================== # ✅ 正确的引用方式 diff --git a/inventory-backend/app/services/inbound/buy_service.py b/inventory-backend/app/services/inbound/buy_service.py index 0ba0a93..219b12f 100644 --- a/inventory-backend/app/services/inbound/buy_service.py +++ b/inventory-backend/app/services/inbound/buy_service.py @@ -1,6 +1,9 @@ from app.extensions import db from app.models.inbound.buy import StockBuy -from app.models.material import MaterialBase +# ============================================================================== +# ✅ 修复点:基础物料模型实际位于 app/models/base.py +# ============================================================================== +from app.models.base import MaterialBase from datetime import datetime from sqlalchemy import or_, func import traceback @@ -9,16 +12,23 @@ import traceback class BuyInboundService: @staticmethod def search_base_material(keyword): + """ + 搜索基础物料 + 如果 keyword 为空,返回最新的 20 条记录 + """ try: - if not keyword: - return [] - query = MaterialBase.query.filter( - MaterialBase.is_enabled == True, - or_( - MaterialBase.name.ilike(f'%{keyword}%'), - MaterialBase.spec_model.ilike(f'%{keyword}%') + query = MaterialBase.query.filter(MaterialBase.is_enabled == True) + + if keyword: + query = query.filter( + or_( + MaterialBase.name.ilike(f'%{keyword}%'), + MaterialBase.spec_model.ilike(f'%{keyword}%') + ) ) - ).limit(20) + + # 无论是否有关键词,都按 ID 倒序排列,取前 20 条 + query = query.order_by(MaterialBase.id.desc()).limit(20) results = [] for item in query.all(): @@ -50,10 +60,11 @@ class BuyInboundService: in_date_val = datetime.utcnow().date() if data.get('in_date'): try: - if len(str(data['in_date'])) > 10: - in_date_val = datetime.strptime(str(data['in_date'])[:10], '%Y-%m-%d').date() - else: - in_date_val = datetime.strptime(str(data['in_date']), '%Y-%m-%d').date() + # 兼容 YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD + date_str = str(data['in_date']) + if len(date_str) > 10: + date_str = date_str[:10] + in_date_val = datetime.strptime(date_str, '%Y-%m-%d').date() except ValueError: pass @@ -82,8 +93,7 @@ class BuyInboundService: buyer_email=data.get('purchaser_email'), original_link=data.get('source_link'), detail_link=data.get('detail_link'), - arrival_photo=data.get('arrival_photo'), - remark=data.get('remark') + arrival_photo=data.get('arrival_photo') ) db.session.add(new_stock) @@ -98,7 +108,6 @@ class BuyInboundService: def update_inbound(stock_id, data): try: print(f"----- UPDATE DEBUG: ID={stock_id} -----") - print(f"Payload: {data}") stock = StockBuy.query.get(stock_id) if not stock: @@ -115,7 +124,6 @@ class BuyInboundService: 'supplier_name': 'supplier_name', 'detail_link': 'detail_link', 'arrival_photo': 'arrival_photo', - 'remark': 'remark', 'currency': 'currency', 'exchange_rate': 'exchange_rate', 'purchaser': 'buyer_name', @@ -193,16 +201,13 @@ class BuyInboundService: pagination = query.order_by(StockBuy.id.desc()).paginate(page=page, per_page=limit, error_out=False) # --------------------------------------------------------------------- - # 新增逻辑:计算总库存 + # 计算总库存 (聚合) # --------------------------------------------------------------------- - # 2. 提取当前页所有涉及的 base_id current_items = pagination.items base_ids = list(set([item.base_id for item in current_items if item.base_id])) - # 3. 聚合查询:一次性查出这些 base_id 对应的 stock_quantity 和 available_quantity 总和 stock_map = {} if base_ids: - # SELECT base_id, SUM(stock_quantity), SUM(available_quantity) FROM stock_buy WHERE base_id IN (...) GROUP BY base_id aggregates = db.session.query( StockBuy.base_id, func.sum(StockBuy.stock_quantity).label('total_stock'), @@ -214,7 +219,6 @@ class BuyInboundService: 'total_stock': float(agg.total_stock or 0), 'total_avail': float(agg.total_avail or 0) } - # --------------------------------------------------------------------- items = [] for item in current_items: @@ -224,7 +228,6 @@ class BuyInboundService: mat_unit = item.material.unit if item.material else '' mat_type = item.material.material_type if item.material else '' - # 获取该物料的统计数据 stats = stock_map.get(item.base_id, {'total_stock': 0, 'total_avail': 0}) d = { @@ -244,12 +247,10 @@ class BuyInboundService: 'status': item.status, 'inspection_status': item.inspection_status, - # --- 原始批次数据 (用于编辑) --- 'qty_inbound': float(item.in_quantity or 0), 'qty_stock': float(item.stock_quantity or 0), 'qty_available': float(item.available_quantity or 0), - # --- [新增] 聚合统计数据 (用于列表显示) --- 'sum_stock': stats['total_stock'], 'sum_available': stats['total_avail'], @@ -263,8 +264,7 @@ class BuyInboundService: 'purchaser_email': item.buyer_email, 'source_link': item.original_link, 'detail_link': item.detail_link, - 'arrival_photo': item.arrival_photo, - 'remark': item.remark + 'arrival_photo': item.arrival_photo } items.append(d) diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py index 092abb0..9212660 100644 --- a/inventory-backend/app/services/inbound/product_service.py +++ b/inventory-backend/app/services/inbound/product_service.py @@ -1,5 +1,5 @@ from app.extensions import db -from app.models.material import MaterialBase +from app.models.base import MaterialBase # 引用新的 product 路径 from app.models.inbound.product import StockProduct from datetime import datetime diff --git a/inventory-backend/app/services/inbound/semi_service.py b/inventory-backend/app/services/inbound/semi_service.py index 7789a81..3b6421b 100644 --- a/inventory-backend/app/services/inbound/semi_service.py +++ b/inventory-backend/app/services/inbound/semi_service.py @@ -1,5 +1,5 @@ from app.extensions import db -from app.models.material import MaterialBase +from app.models.base import MaterialBase from app.models.inbound.semi import StockSemi from datetime import datetime from sqlalchemy import or_, func diff --git a/inventory-web/src/views/stock/inbound/buy.vue b/inventory-web/src/views/stock/inbound/buy.vue index 938b8c1..639435a 100644 --- a/inventory-web/src/views/stock/inbound/buy.vue +++ b/inventory-web/src/views/stock/inbound/buy.vue @@ -161,6 +161,7 @@ {{ item.name }} {{ item.spec }} 历史 + 系统 @@ -168,7 +169,7 @@ - 点击可查看最近使用的物料,或输入关键词从云端搜索。 + 未输入时展示最新物料;输入关键词进行精确搜索。 @@ -524,9 +525,8 @@ const saveMaterialHistory = (item: any) => { try { const existing = localStorage.getItem(key) let list = existing ? JSON.parse(existing) : [] - // 必须保存完整对象,因为下拉框需要显示 name, spec 等 list = list.filter((i: any) => i.id !== item.id) - list.unshift({ ...item, isHistory: true }) // 标记为历史 + list.unshift({ ...item, isHistory: true }) if (list.length > 10) list = list.slice(0, 10) localStorage.setItem(key, JSON.stringify(list)) } catch (e) {} @@ -563,7 +563,7 @@ const mixedSearch = (queryString: string, tableField: string, storageKey: string // 合并去重 const map = new Map() historyList.forEach(i => map.set(i.value, i)) - tableList.forEach(i => map.set(i.value, i)) // 表格数据优先(虽然value一样没区别) + tableList.forEach(i => map.set(i.value, i)) const allList = Array.from(map.values()) const results = queryString ? allList.filter(createFilter(queryString)) : allList @@ -594,34 +594,39 @@ const querySearchCurrency = (queryString: string, cb: any) => { } // ------------------------------------ -// 物料搜索逻辑 (支持历史回显 + API) +// 物料搜索逻辑 (优化:支持空查询加载默认值) // ------------------------------------ -// 下拉框展开时触发 const handleMaterialDropdownVisible = (visible: boolean) => { if (visible) { - // 只有当列表为空时(即没有进行API搜索时),才填充历史记录 - // 这样不会覆盖用户正在搜的结果 + // 【修改点】: 展开时,如果列表为空,直接调用 API 加载前 20 条数据 + // 同时也会把历史记录合并进去(在 handleSearchMaterial 内部处理或分开展示) + // 这里简单处理:直接调用 handleSearchMaterial('') 让后端返回默认数据 if (materialOptions.value.length === 0) { - materialOptions.value = getMaterialHistory() + handleSearchMaterial('') } } } const handleSearchMaterial = async (query: string) => { - if (query) { - searchLoading.value = true - try { - const res: any = await searchMaterialBase(query) - // 给 API 返回的数据加个标记,区分历史 - const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false })) + searchLoading.value = true + try { + // 即使 query 为空,后端现在也会返回数据 + const res: any = await searchMaterialBase(query) + const apiResults = (res.data || []).map((i: any) => ({ ...i, isHistory: false })) + + // 如果是空搜索,我们可以把本地历史记录插在前面,做个合并 + if (!query) { + const history = getMaterialHistory() + // 简单去重:如果 API 返回的 ID 在历史记录里有,就跳过 API 的那个(优先显示历史标记) + const historyIds = new Set(history.map((h: any) => h.id)) + const filteredApi = apiResults.filter((apiItem: any) => !historyIds.has(apiItem.id)) + materialOptions.value = [...history, ...filteredApi] + } else { materialOptions.value = apiResults - } finally { - searchLoading.value = false } - } else { - // 搜索词清空时,恢复历史记录 - materialOptions.value = getMaterialHistory() + } finally { + searchLoading.value = false } } @@ -629,7 +634,6 @@ const handleSearchMaterial = async (query: string) => { const onMaterialSelected = (val: number) => { const item = materialOptions.value.find(i => i.id === val) if (item) { - // 存入历史 saveMaterialHistory(item) form.material_name = item.name @@ -832,7 +836,7 @@ const submitForm = async () => { ElMessage.success('更新成功') } - // 核心修改:提交成功时,保存供应商等信息到历史记录 + // 保存供应商等信息到历史记录 saveToHistory(HISTORY_KEYS.SUPPLIER, form.supplier_name) saveToHistory(HISTORY_KEYS.PURCHASER, form.purchaser) saveToHistory(HISTORY_KEYS.EMAIL, form.purchaser_email)