Files
KCGL/inventory-backend/app/models/base.py
yueli 760f78e016 feat: 添加参考价格列 + 修复公司筛选跨域问题 + CLIP模型持久化
## 新增功能
- material_base 表新增 reference_price 列(NUMERIC(10,2))
- 基础信息 list.vue / buyOdoo.vue 页面增加「参考价格」列展示和编辑
- 新增 material_list:referencePrice 权限元素,支持按角色控制可见性

## Bug 修复
- 入库三页面(buy/product/semi)公司筛选:非超管用户 company=ALL 不再传给旧版后端
- 基础信息两页面(list/buyOdoo):buyOdoo 增加 v-if=isSuperAdmin 与 list.vue 行为统一
- company=ALL 默认值在各页面 getList/fetchData 中自动过滤,兼容旧版后端

## 运维优化
- docker-compose.prod.yml 增加 models_prod 卷挂载,CLIP模型持久化免重复上传
- deploy_code.sh 增加 models_prod 目录检查与模型文件存在性告警
2026-07-13 17:30:52 +08:00

138 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app/models/base.py
from app.extensions import db
from pgvector.sqlalchemy import Vector
import json
class MaterialBase(db.Model):
"""
基础信息表模型
对应数据库表: material_base
"""
__tablename__ = 'material_base'
# 1. 基础字段
id = db.Column(db.Integer, primary_key=True)
company_name = db.Column(db.String(255), comment='所属公司')
name = db.Column(db.String(255), nullable=False, index=True, comment='名称') # ★ 模糊搜索/精确定位高频列
common_name = db.Column(db.String(255), comment='俗名')
category = db.Column(db.String(100), index=True, comment='类别') # ★ 分类统计/过滤高频列
material_type = db.Column(db.String(100), index=True, comment='类型') # ★ 类型分组/过滤高频列
spec_model = db.Column(db.String(255), index=True, comment='规格型号') # ★ 模糊搜索/精确匹配高频列
unit = db.Column(db.String(50), comment='计量单位')
# 可见等级
visibility_level = db.Column(db.Integer, default=0, comment='信息可见等级')
# 链接与图片 (现在存储 JSON 字符串)
manual_link = db.Column(db.Text, comment='通用说明书')
product_image = db.Column(db.Text, comment='通用产品图')
# 备注字段(纯文本,与文件 URL 分离存储)
product_image_remark = db.Column(db.Text, default='', comment='产品图备注')
manual_link_remark = db.Column(db.Text, default='', comment='说明书备注')
# 启用状态
is_enabled = db.Column(db.Boolean, default=True, comment='是否启用')
# 强制质检标记(采购入库时必须上传检测报告)
is_inspection_required = db.Column(db.Boolean, default=False, comment='是否强制要求质检')
# 参考价格
reference_price = db.Column(db.Numeric(10, 2), nullable=True, comment='参考价格')
# CLIP 视觉向量(用于以图搜图)
img_embedding = db.Column(Vector(512), nullable=True)
# ============================================================
# 关联关系区域
# ============================================================
# 1. 关联采购库存 (StockBuy)
stock_buys = db.relationship('StockBuy', back_populates='base', lazy='dynamic')
# 2. 关联半成品库存 (StockSemi)
stock_semis = db.relationship('StockSemi', back_populates='base', lazy='dynamic')
# 3. 关联成品库存 (StockProduct)
stock_products = db.relationship('StockProduct', back_populates='base', lazy='dynamic')
# 4. 关联服务库存 (StockService)
stock_services = db.relationship('StockService', back_populates='base', lazy='dynamic')
# 5. 关联预警设置 (MaterialWarningSetting)
warning_settings = db.relationship('MaterialWarningSetting', back_populates='material', lazy='dynamic', cascade='all, delete-orphan')
def to_dict(self):
"""
序列化方法
"""
# 辅助解析函数:将数据库存储的 JSON 字符串转为 List
def parse_list(json_str):
if not json_str:
return []
try:
# 兼容旧数据:如果不是 JSON 格式(比如是单个 URL),则包装成 list
if not json_str.startswith('['):
return [json_str]
return json.loads(json_str)
except:
return []
return {
'id': self.id,
'companyName': self.company_name,
'name': self.name,
'commonName': self.common_name,
'category': self.category,
'type': self.material_type,
'spec': self.spec_model,
'unit': self.unit,
'visibilityLevel': self.visibility_level,
'generalManual': parse_list(self.manual_link),
'generalImage': parse_list(self.product_image),
'productImageRemark': self.product_image_remark or '',
'manualLinkRemark': self.manual_link_remark or '',
# 【核心修改】:直接返回布尔值,不再转成 1 或 0
'isEnabled': bool(self.is_enabled),
# 强制质检标记
'isInspectionRequired': bool(self.is_inspection_required),
# 参考价格
'referencePrice': float(self.reference_price) if self.reference_price is not None else None,
}
class MaterialWarningSetting(db.Model):
"""
物料预警设置表模型
对应数据库表: material_warning_settings
"""
__tablename__ = 'material_warning_settings'
id = db.Column(db.Integer, primary_key=True)
base_id = db.Column(db.Integer, db.ForeignKey('material_base.id'), nullable=False, comment='物料基础信息ID')
is_enabled = db.Column(db.Boolean, default=False, comment='是否启用预警')
yellow_threshold = db.Column(db.Numeric(10, 2), nullable=True, comment='黄色预警阈值')
red_threshold = db.Column(db.Numeric(10, 2), nullable=True, comment='红色预警阈值')
yellow_emails = db.Column(db.String(500), nullable=True, comment='黄色预警通知邮箱')
red_emails = db.Column(db.String(500), nullable=True, comment='红色预警通知邮箱')
is_ordered = db.Column(db.Boolean, default=False, comment='是否已处理采购')
last_notified_at = db.Column(db.DateTime, nullable=True, comment='上次邮件通知时间')
# 关联关系
material = db.relationship('MaterialBase', back_populates='warning_settings')
def to_dict(self):
return {
'id': self.id,
'baseId': self.base_id,
'isEnabled': bool(self.is_enabled),
'yellowThreshold': float(self.yellow_threshold) if self.yellow_threshold is not None else None,
'redThreshold': float(self.red_threshold) if self.red_threshold is not None else None,
'yellowEmails': self.yellow_emails or '',
'redEmails': self.red_emails or '',
'isOrdered': bool(self.is_ordered),
'lastNotifiedAt': self.last_notified_at.strftime('%Y-%m-%d %H:%M:%S') if self.last_notified_at else None
}