修改是基础信息内容展示库存数和可用数
This commit is contained in:
@ -2,6 +2,7 @@
|
|||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class MaterialBase(db.Model):
|
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')
|
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')
|
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')
|
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):
|
def to_dict(self):
|
||||||
"""
|
"""
|
||||||
序列化方法
|
序列化方法
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 辅助解析函数:将数据库存储的 JSON 字符串转为 List
|
# 辅助解析函数:将数据库存储的 JSON 字符串转为 List
|
||||||
def parse_list(json_str):
|
def parse_list(json_str):
|
||||||
if not json_str:
|
if not json_str:
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from app import db
|
# inventory-backend/app/models/inbound/service.py
|
||||||
|
from app.extensions import db
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
@ -10,24 +11,43 @@ class StockService(db.Model):
|
|||||||
__tablename__ = 'stock_service'
|
__tablename__ = 'stock_service'
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
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)
|
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False)
|
||||||
|
|
||||||
# 系统生成的SKU,格式 SRV-YYYYMMDD-XXXX
|
# 系统生成的SKU,格式 SRV-YYYYMMDD-XXXX
|
||||||
sku = db.Column(db.String(64), unique=True, nullable=False)
|
sku = db.Column(db.String(64), unique=True, nullable=False)
|
||||||
|
|
||||||
# 售价
|
# 售价
|
||||||
sale_price = db.Column(db.Numeric(10, 2), nullable=False)
|
sale_price = db.Column(db.Numeric(10, 2), nullable=False)
|
||||||
|
|
||||||
# 服务商名称
|
# 服务商名称
|
||||||
provider_name = db.Column(db.String(255), nullable=False, default='')
|
provider_name = db.Column(db.String(255), nullable=False, default='')
|
||||||
|
|
||||||
# 服务详情/简介
|
# 服务详情/简介
|
||||||
description = db.Column(db.Text, 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)
|
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)
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
|
||||||
|
|
||||||
# 软删除标志
|
# 软删除标志
|
||||||
is_deleted = db.Column(db.Boolean, default=False, 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):
|
def to_dict(self):
|
||||||
"""转为字典,用于 API 响应"""
|
"""转为字典,用于 API 响应"""
|
||||||
@ -38,9 +58,13 @@ class StockService(db.Model):
|
|||||||
'sale_price': float(self.sale_price) if self.sale_price is not None else 0,
|
'sale_price': float(self.sale_price) if self.sale_price is not None else 0,
|
||||||
'provider_name': self.provider_name,
|
'provider_name': self.provider_name,
|
||||||
'description': self.description,
|
'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,
|
'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,
|
'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,
|
# 注意:这里通过 self.base 访问关联对象,而不是 self.material_base
|
||||||
'unit': self.material_base.unit if self.material_base else None,
|
'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,
|
||||||
}
|
}
|
||||||
@ -4,6 +4,9 @@ from app.extensions import db
|
|||||||
from app.models.base import MaterialBase
|
from app.models.base import MaterialBase
|
||||||
from app.models.inbound.buy import StockBuy
|
from app.models.inbound.buy import StockBuy
|
||||||
from app.models.inbound.semi import StockSemi
|
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_
|
from sqlalchemy import or_
|
||||||
import traceback
|
import traceback
|
||||||
import json
|
import json
|
||||||
@ -51,10 +54,46 @@ class MaterialBaseService:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return []
|
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
|
@staticmethod
|
||||||
def get_list(page, limit, filters=None):
|
def get_list(page, limit, filters=None):
|
||||||
"""
|
"""
|
||||||
获取基础信息列表 (带分页和筛选)
|
获取基础信息列表 (带分页和筛选)
|
||||||
|
并聚合库存总数和可用总数
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
query = MaterialBase.query
|
query = MaterialBase.query
|
||||||
@ -84,10 +123,36 @@ class MaterialBaseService:
|
|||||||
# 按 ID 倒序排列
|
# 按 ID 倒序排列
|
||||||
pagination = query.order_by(MaterialBase.id.desc()).paginate(page=page, per_page=limit, error_out=False)
|
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]
|
items_list = []
|
||||||
return {"total": pagination.total, "items": items}
|
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:
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
print(f"查询基础信息列表失败: {e}")
|
print(f"查询基础信息列表失败: {e}")
|
||||||
return {"total": 0, "items": []}
|
return {"total": 0, "items": []}
|
||||||
|
|
||||||
@ -175,6 +240,11 @@ class MaterialBaseService:
|
|||||||
|
|
||||||
buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count()
|
buy_usage_count = StockBuy.query.filter_by(base_id=m_id).count()
|
||||||
semi_usage_count = StockSemi.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
|
total_usage = buy_usage_count + semi_usage_count
|
||||||
|
|
||||||
if total_usage > 0:
|
if total_usage > 0:
|
||||||
|
|||||||
@ -87,7 +87,8 @@
|
|||||||
<el-checkbox v-model="columns.type.visible" label="类型" />
|
<el-checkbox v-model="columns.type.visible" label="类型" />
|
||||||
<el-checkbox v-model="columns.spec.visible" label="规格型号" />
|
<el-checkbox v-model="columns.spec.visible" label="规格型号" />
|
||||||
<el-checkbox v-model="columns.unit.visible" label="单位" />
|
<el-checkbox v-model="columns.unit.visible" label="单位" />
|
||||||
<el-checkbox v-model="columns.visibilityLevel.visible" label="可见等级" />
|
<el-checkbox v-model="columns.inventory.visible" label="库存数" />
|
||||||
|
<el-checkbox v-model="columns.available.visible" label="可用数" />
|
||||||
<el-checkbox v-model="columns.files.visible" label="资料" />
|
<el-checkbox v-model="columns.files.visible" label="资料" />
|
||||||
<el-checkbox v-model="columns.isEnabled.visible" label="状态" />
|
<el-checkbox v-model="columns.isEnabled.visible" label="状态" />
|
||||||
</div>
|
</div>
|
||||||
@ -121,8 +122,21 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="columns.spec.visible" prop="spec" label="规格型号" min-width="180" show-overflow-tooltip />
|
<el-table-column v-if="columns.spec.visible" prop="spec" label="规格型号" min-width="180" show-overflow-tooltip />
|
||||||
<el-table-column v-if="columns.unit.visible" prop="unit" label="单位" min-width="80" align="center" />
|
<el-table-column v-if="columns.unit.visible" prop="unit" label="单位" min-width="80" align="center" />
|
||||||
<el-table-column v-if="columns.visibilityLevel.visible" prop="visibilityLevel" label="可见等级" min-width="100" align="center">
|
|
||||||
<template #default="scope">L{{ scope.row.visibilityLevel }}</template>
|
<el-table-column v-if="columns.inventory.visible" prop="inventoryCount" label="库存数" min-width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :style="{ fontWeight: 'bold', color: row.inventoryCount > 0 ? '#67C23A' : '#909399' }">
|
||||||
|
{{ row.inventoryCount }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column v-if="columns.available.visible" prop="availableCount" label="可用数" min-width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :style="{ fontWeight: 'bold', color: row.availableCount > 0 ? '#409EFF' : '#909399' }">
|
||||||
|
{{ row.availableCount }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-if="columns.files.visible" label="资料" min-width="140" align="center">
|
<el-table-column v-if="columns.files.visible" label="资料" min-width="140" align="center">
|
||||||
@ -376,6 +390,9 @@ interface MaterialBaseVO {
|
|||||||
generalImage: string[];
|
generalImage: string[];
|
||||||
isEnabled: number;
|
isEnabled: number;
|
||||||
statusLoading?: boolean;
|
statusLoading?: boolean;
|
||||||
|
// 新增字段
|
||||||
|
inventoryCount?: number;
|
||||||
|
availableCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryParams {
|
interface QueryParams {
|
||||||
@ -413,7 +430,9 @@ const columns = reactive({
|
|||||||
type: { visible: true },
|
type: { visible: true },
|
||||||
spec: { visible: true },
|
spec: { visible: true },
|
||||||
unit: { visible: true },
|
unit: { visible: true },
|
||||||
visibilityLevel: { visible: true },
|
// visibilityLevel: { visible: false }, // 不再使用
|
||||||
|
inventory: { visible: true }, // 新增
|
||||||
|
available: { visible: true }, // 新增
|
||||||
files: { visible: true },
|
files: { visible: true },
|
||||||
isEnabled: { visible: true }
|
isEnabled: { visible: true }
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user