From 7e2fa8db8e1ee76fdf9b3c874094e7c976f640b7 Mon Sep 17 00:00:00 2001 From: dxc Date: Tue, 24 Feb 2026 15:13:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E7=A1=80=E4=BF=A1=E6=81=AF=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=89=80=E5=B1=9E=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=EF=BC=8C=E5=90=8C=E6=97=B6=E4=BF=AE=E6=AD=A3=E7=B1=BB?= =?UTF-8?q?=E5=88=AB=E6=8E=92=E5=BA=8F=E4=BB=A5=E5=8F=8A=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=97=B6=E5=80=99=E7=B1=BB=E5=88=AB=E9=80=89=E6=8B=A9=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inventory-backend/app/models/base.py | 8 +- .../app/services/inbound/base_service.py | 73 +++--- inventory-web/src/App.vue | 2 +- inventory-web/src/views/material/list.vue | 229 ++++++++++++++---- 4 files changed, 214 insertions(+), 98 deletions(-) diff --git a/inventory-backend/app/models/base.py b/inventory-backend/app/models/base.py index 20ea4fb..21e5210 100644 --- a/inventory-backend/app/models/base.py +++ b/inventory-backend/app/models/base.py @@ -12,6 +12,9 @@ class MaterialBase(db.Model): # 1. 基础字段 id = db.Column(db.Integer, primary_key=True) + # [修改] 所属公司,去除了 default='IRIS' + company_name = db.Column(db.String(255), comment='所属公司') + name = db.Column(db.String(255), nullable=False, comment='名称') common_name = db.Column(db.String(255), comment='俗名') category = db.Column(db.String(100), comment='类别') @@ -42,8 +45,7 @@ class MaterialBase(db.Model): # 3. 关联成品库存 (StockProduct) stock_products = db.relationship('StockProduct', back_populates='base', lazy='dynamic') - # 4. 关联服务库存 (StockService) - [新增] - # 假设您的服务库存模型类名为 StockService,且有 base_id 外键 + # 4. 关联服务库存 (StockService) stock_services = db.relationship('StockService', back_populates='base', lazy='dynamic') def to_dict(self): @@ -65,6 +67,7 @@ class MaterialBase(db.Model): return { 'id': self.id, + 'companyName': self.company_name, 'name': self.name, 'commonName': self.common_name, 'category': self.category, @@ -72,7 +75,6 @@ class MaterialBase(db.Model): 'spec': self.spec_model, 'unit': self.unit, 'visibilityLevel': self.visibility_level, - # 修改:解析为列表返回 'generalManual': parse_list(self.manual_link), 'generalImage': parse_list(self.product_image), 'isEnabled': 1 if self.is_enabled else 0, diff --git a/inventory-backend/app/services/inbound/base_service.py b/inventory-backend/app/services/inbound/base_service.py index 12538b5..eecc8e2 100644 --- a/inventory-backend/app/services/inbound/base_service.py +++ b/inventory-backend/app/services/inbound/base_service.py @@ -4,7 +4,6 @@ from app.extensions import db from app.models.base import MaterialBase from app.models.inbound.buy import StockBuy from app.models.inbound.semi import StockSemi -# 假设您有 StockProduct 和 StockService 的模型定义 # from app.models.inbound.product import StockProduct # from app.models.inbound.service import StockService from sqlalchemy import or_ @@ -33,7 +32,9 @@ class MaterialBaseService: or_( MaterialBase.name.ilike(f'%{keyword}%'), MaterialBase.common_name.ilike(f'%{keyword}%'), - MaterialBase.spec_model.ilike(f'%{keyword}%') + MaterialBase.spec_model.ilike(f'%{keyword}%'), + # 支持搜索公司名 + MaterialBase.company_name.ilike(f'%{keyword}%') ) ).limit(20) @@ -41,6 +42,7 @@ class MaterialBaseService: for item in query.all(): results.append({ 'id': item.id, + 'companyName': item.company_name, 'name': item.name, 'commonName': item.common_name, 'spec': item.spec_model, @@ -58,29 +60,21 @@ class MaterialBaseService: def _get_stock_counts(stock_query): """ 辅助函数:安全计算库存列表的总数量 - 修复逻辑:优先查找 'stock_quantity' (Buy/Semi/Product表中实际使用的字段) """ total_inv = 0 total_avail = 0 - # 如果 stock_query 是动态加载的查询对象 (AppenderQuery),需要迭代它 - # 如果是列表,直接迭代 try: items = list(stock_query) # 触发查询 except: items = [] for x in items: - # 1. 获取库存数 - # 【修复点】根据你提供的 Service 代码,Buy/Semi/Product 均使用 stock_quantity - # Service 使用 actual_quantity,这里做兼容查找 + # 1. 获取库存数 (兼容不同字段名) q = getattr(x, 'stock_quantity', getattr(x, 'actual_quantity', getattr(x, 'quantity', 0))) - # 2. 获取可用数 - # 这里的字段名通常都是 available_quantity a = getattr(x, 'available_quantity', q) - # 累加 (转 float 防止 None 或 Decimal 计算报错) try: total_inv += float(q if q is not None else 0) total_avail += float(a if a is not None else 0) @@ -93,13 +87,12 @@ class MaterialBaseService: def get_list(page, limit, filters=None): """ 获取基础信息列表 (带分页和筛选) - 并聚合库存总数和可用总数 """ try: query = MaterialBase.query if filters: - # 1. 关键词模糊搜索 (名称 或 俗名 或 规格型号) + # 1. 关键词模糊搜索 if filters.get('keyword'): kw = f"%{filters['keyword']}%" query = query.filter(or_( @@ -109,6 +102,10 @@ class MaterialBaseService: )) # 2. 精确筛选 + # 公司筛选 + if filters.get('company'): + query = query.filter_by(company_name=filters['company']) + if filters.get('category'): query = query.filter_by(category=filters['category']) @@ -116,7 +113,6 @@ class MaterialBaseService: query = query.filter_by(material_type=filters['type']) if filters.get('isEnabled') is not None: - # 前端传 1/0,转为 Boolean is_active = bool(int(filters['isEnabled'])) query = query.filter_by(is_enabled=is_active) @@ -125,25 +121,14 @@ class MaterialBaseService: items_list = [] for item in pagination.items: - # 获取基础字典 item_dict = item.to_dict() - # [调用修复后的辅助函数] - - # 1. 采购库存 (StockBuy) + # 聚合库存 buy_inv, buy_avail = MaterialBaseService._get_stock_counts(item.stock_buys) - - # 2. 半成品库存 (StockSemi) semi_inv, semi_avail = MaterialBaseService._get_stock_counts(item.stock_semis) - - # 3. 成品库存 (StockProduct) prod_inv, prod_avail = MaterialBaseService._get_stock_counts(item.stock_products) - - # 4. 服务库存 (StockService) - # 使用 getattr 防止关联不存在时报错 serv_inv, serv_avail = MaterialBaseService._get_stock_counts(getattr(item, 'stock_services', [])) - # 合并总数 item_dict['inventoryCount'] = buy_inv + semi_inv + prod_inv + serv_inv item_dict['availableCount'] = buy_avail + semi_avail + prod_avail + serv_avail @@ -159,37 +144,45 @@ class MaterialBaseService: @staticmethod def get_distinct_options(): """ - 获取所有已存在的类别和类型 (去重) - 用于前端下拉筛选 + 获取所有已存在的类别、类型、公司 (去重且排序) """ try: - # 查询所有不为空的类别并去重 + # 1. 类别 (获取后在内存或前端做层级处理,这里先按字母序返回扁平列表) categories = db.session.query(MaterialBase.category) \ .filter(MaterialBase.category != None, MaterialBase.category != '') \ .distinct().all() - # 查询所有不为空的类型并去重 + # 对类别进行排序 + sorted_categories = sorted([c[0] for c in categories]) + + # 2. 类型 types = db.session.query(MaterialBase.material_type) \ .filter(MaterialBase.material_type != None, MaterialBase.material_type != '') \ .distinct().all() + sorted_types = sorted([t[0] for t in types]) + + # 3. 公司 + companies = db.session.query(MaterialBase.company_name) \ + .filter(MaterialBase.company_name != None, MaterialBase.company_name != '') \ + .distinct().all() + sorted_companies = sorted([c[0] for c in companies]) return { - "categories": [c[0] for c in categories], - "types": [t[0] for t in types] + "categories": sorted_categories, + "types": sorted_types, + "companies": sorted_companies } except Exception as e: traceback.print_exc() - return {"categories": [], "types": []} + return {"categories": [], "types": [], "companies": []} @staticmethod def create_material(data): """新增基础信息""" try: - # 0. 基础校验 if not data.get('name') or not data.get('spec'): raise ValueError("名称和规格型号不能为空") - # 1. 查重 exist = MaterialBase.query.filter_by( name=data['name'], spec_model=data['spec'] @@ -197,8 +190,9 @@ class MaterialBaseService: if exist: raise ValueError(f"已存在相同名称和规格的数据 (ID: {exist.id})") - # 2. 创建对象 (列表转JSON字符串) new_material = MaterialBase( + # [修改] 移除了 'IRIS' 默认值 + company_name=data.get('companyName'), name=data['name'], common_name=data.get('commonName'), spec_model=data['spec'], @@ -206,7 +200,6 @@ class MaterialBaseService: material_type=data.get('type'), unit=data.get('unit'), visibility_level=data.get('visibilityLevel'), - # 修改:将列表 dumps 为字符串 manual_link=json.dumps(data.get('generalManual', [])), product_image=json.dumps(data.get('generalImage', [])), is_enabled=True if data.get('isEnabled', 1) == 1 else False @@ -229,6 +222,7 @@ class MaterialBaseService: raise ValueError("数据不存在") # 更新字段 + if 'companyName' in data: material.company_name = data['companyName'] if 'name' in data: material.name = data['name'] if 'commonName' in data: material.common_name = data['commonName'] if 'spec' in data: material.spec_model = data['spec'] @@ -237,7 +231,6 @@ class MaterialBaseService: if 'unit' in data: material.unit = data['unit'] if 'visibilityLevel' in data: material.visibility_level = data['visibilityLevel'] - # 修改:将列表 dumps 为字符串 if 'generalManual' in data: material.manual_link = json.dumps(data['generalManual']) if 'generalImage' in data: @@ -266,10 +259,6 @@ class MaterialBaseService: buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count() semi_usage_count = StockSemi.query.filter_by(base_id=m_id).count() - # 如果需要检查成品和服务,可以解开注释 - # from app.models.inbound.product import StockProduct - # prod_usage_count = StockProduct.query.filter_by(base_id=m_id).count() - total_usage = buy_usage_count + semi_usage_count if total_usage > 0: diff --git a/inventory-web/src/App.vue b/inventory-web/src/App.vue index bf657bd..52c6f31 100644 --- a/inventory-web/src/App.vue +++ b/inventory-web/src/App.vue @@ -82,7 +82,7 @@ const handleLogout = () => { diff --git a/inventory-web/src/views/material/list.vue b/inventory-web/src/views/material/list.vue index dae6349..025fddb 100644 --- a/inventory-web/src/views/material/list.vue +++ b/inventory-web/src/views/material/list.vue @@ -11,6 +11,18 @@ @input="handleInputSearch" /> + + + + + @@ -105,6 +118,13 @@ style="width: 100%; margin-top: 15px" > + + + + + @@ -114,7 +134,7 @@ - + @@ -196,19 +216,6 @@ -
- -
- - + + + +
+ +
/
+ +
+
+ * 必须构成4层结构 +
+
+
+ + + - - - + + + + + + + (0低-9高) + + - - - (0为最低,9为最高) - -