From 706476d4216d3010460fd0b76095634998a8b744 Mon Sep 17 00:00:00 2001 From: dxc Date: Wed, 11 Feb 2026 10:12:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=98=AF=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=86=85=E5=AE=B9=E5=B1=95=E7=A4=BA=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E6=95=B0=E5=92=8C=E5=8F=AF=E7=94=A8=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inventory-backend/app/models/base.py | 12 ++- .../app/models/inbound/service.py | 38 ++++++++-- .../app/services/inbound/base_service.py | 74 ++++++++++++++++++- inventory-web/src/views/material/list.vue | 27 ++++++- 4 files changed, 135 insertions(+), 16 deletions(-) diff --git a/inventory-backend/app/models/base.py b/inventory-backend/app/models/base.py index 3ad07e8..20ea4fb 100644 --- a/inventory-backend/app/models/base.py +++ b/inventory-backend/app/models/base.py @@ -2,6 +2,7 @@ from app.extensions import db import json + class MaterialBase(db.Model): """ 基础信息表模型 @@ -32,19 +33,24 @@ class MaterialBase(db.Model): # 关联关系区域 # ============================================================ - # 1. 关联采购库存 (StockBuy) - 修改 back_populates 为 'base' + # 1. 关联采购库存 (StockBuy) stock_buys = db.relationship('StockBuy', back_populates='base', lazy='dynamic') - # 2. 关联半成品库存 (StockSemi) - 修改 back_populates 为 'base' + # 2. 关联半成品库存 (StockSemi) stock_semis = db.relationship('StockSemi', back_populates='base', lazy='dynamic') - # 3. 关联成品库存 (StockProduct) - 修改 back_populates 为 'base' + # 3. 关联成品库存 (StockProduct) stock_products = db.relationship('StockProduct', back_populates='base', lazy='dynamic') + # 4. 关联服务库存 (StockService) - [新增] + # 假设您的服务库存模型类名为 StockService,且有 base_id 外键 + stock_services = db.relationship('StockService', back_populates='base', lazy='dynamic') + def to_dict(self): """ 序列化方法 """ + # 辅助解析函数:将数据库存储的 JSON 字符串转为 List def parse_list(json_str): if not json_str: diff --git a/inventory-backend/app/models/inbound/service.py b/inventory-backend/app/models/inbound/service.py index 37b06df..5271ad4 100644 --- a/inventory-backend/app/models/inbound/service.py +++ b/inventory-backend/app/models/inbound/service.py @@ -1,4 +1,5 @@ -from app import db +# inventory-backend/app/models/inbound/service.py +from app.extensions import db from datetime import datetime @@ -10,24 +11,43 @@ class StockService(db.Model): __tablename__ = 'stock_service' id = db.Column(db.Integer, primary_key=True, autoincrement=True) + # 关联基础物料信息 + # 注意:这里使用了 db.ForeignKey 指向 material_base 表的 id base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False) + # 系统生成的SKU,格式 SRV-YYYYMMDD-XXXX sku = db.Column(db.String(64), unique=True, nullable=False) + # 售价 sale_price = db.Column(db.Numeric(10, 2), nullable=False) + # 服务商名称 provider_name = db.Column(db.String(255), nullable=False, default='') + # 服务详情/简介 description = db.Column(db.Text, default='') + + # ========================================================================== + # 【新增】库存数量字段 + # 上一轮的 Service 代码中尝试累加这两个字段,如果模型里没有,程序会报错 + # ========================================================================== + actual_quantity = db.Column(db.Integer, default=0, nullable=False, comment='库存数量') + available_quantity = db.Column(db.Integer, default=0, nullable=False, comment='可用数量') + # 创建时间与更新时间 created_at = db.Column(db.DateTime, default=datetime.now, nullable=False) updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False) + # 软删除标志 is_deleted = db.Column(db.Boolean, default=False, nullable=False) - # 关系(可选) - material_base = db.relationship('MaterialBase', backref='service_stocks', lazy='joined') + # ========================================================================== + # 【修复】关系映射 + # 1. 属性名必须叫 'base',因为 MaterialBase 里定义了 back_populates='base' + # 2. back_populates 指向 MaterialBase 里的属性名 'stock_services' + # ========================================================================== + base = db.relationship('MaterialBase', back_populates='stock_services', lazy='joined') def to_dict(self): """转为字典,用于 API 响应""" @@ -38,9 +58,13 @@ class StockService(db.Model): 'sale_price': float(self.sale_price) if self.sale_price is not None else 0, 'provider_name': self.provider_name, 'description': self.description, + 'actual_quantity': self.actual_quantity, # 返回库存数 + 'available_quantity': self.available_quantity, # 返回可用数 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None, 'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None, - 'material_name': self.material_base.name if self.material_base else None, - 'spec_model': self.material_base.spec_model if self.material_base else None, - 'unit': self.material_base.unit if self.material_base else None, - } + + # 注意:这里通过 self.base 访问关联对象,而不是 self.material_base + 'material_name': self.base.name if self.base else None, + 'spec_model': self.base.spec_model if self.base else None, + 'unit': self.base.unit if self.base else None, + } \ No newline at end of file diff --git a/inventory-backend/app/services/inbound/base_service.py b/inventory-backend/app/services/inbound/base_service.py index bed12f2..787d5dc 100644 --- a/inventory-backend/app/services/inbound/base_service.py +++ b/inventory-backend/app/services/inbound/base_service.py @@ -4,6 +4,9 @@ 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_ import traceback import json @@ -51,10 +54,46 @@ class MaterialBaseService: traceback.print_exc() return [] + @staticmethod + 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,这里做兼容查找 + 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) + except: + pass + + return total_inv, total_avail + @staticmethod def get_list(page, limit, filters=None): """ 获取基础信息列表 (带分页和筛选) + 并聚合库存总数和可用总数 """ try: query = MaterialBase.query @@ -84,10 +123,36 @@ class MaterialBaseService: # 按 ID 倒序排列 pagination = query.order_by(MaterialBase.id.desc()).paginate(page=page, per_page=limit, error_out=False) - items = [item.to_dict() for item in pagination.items] - return {"total": pagination.total, "items": items} + 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 + + items_list.append(item_dict) + + return {"total": pagination.total, "items": items_list} except Exception as e: + traceback.print_exc() print(f"查询基础信息列表失败: {e}") return {"total": 0, "items": []} @@ -175,6 +240,11 @@ 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/views/material/list.vue b/inventory-web/src/views/material/list.vue index bb5ba45..34df2ff 100644 --- a/inventory-web/src/views/material/list.vue +++ b/inventory-web/src/views/material/list.vue @@ -87,7 +87,8 @@ - + + @@ -121,8 +122,21 @@ - - + + + + + + + @@ -376,6 +390,9 @@ interface MaterialBaseVO { generalImage: string[]; isEnabled: number; statusLoading?: boolean; + // 新增字段 + inventoryCount?: number; + availableCount?: number; } interface QueryParams { @@ -413,7 +430,9 @@ const columns = reactive({ type: { visible: true }, spec: { visible: true }, unit: { visible: true }, - visibilityLevel: { visible: true }, + // visibilityLevel: { visible: false }, // 不再使用 + inventory: { visible: true }, // 新增 + available: { visible: true }, // 新增 files: { visible: true }, isEnabled: { visible: true } });