from datetime import datetime import enum from sqlalchemy import ( BIGINT, INTEGER, JSON, Column, DateTime, Enum, ForeignKey, Index, String, Text, func, ) from app import db class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(50), unique=True, nullable=False) password = db.Column(db.String(255), nullable=False) status = db.Column(db.Integer, default=1, comment="用户状态:1-正常,0-禁用") created_at = db.Column(db.DateTime, default=datetime.now, comment="创建时间") updated_at = db.Column( db.DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间" ) email = db.Column(db.String(255), nullable=True, comment="邮箱") avatar = db.Column(db.String(255), nullable=True, comment="头像(通常存储图片URL)") region = db.Column(db.String(255), nullable=True, comment="国家地区") phone = db.Column(db.String(255), nullable=True, comment="电话") brief = db.Column(db.String(255), nullable=True, comment="个人简介") third_party_account = db.Column( db.String(255), nullable=True, comment="第三方账号(如微信、QQ等)" ) position = db.Column(db.String(255), nullable=True, comment="职务") department = db.Column(db.String(255), nullable=True, comment="部门") label = db.Column( db.String(255), nullable=True, comment="个人标签(多个标签可逗号分隔)" ) # 自引用外键 created_by = db.Column(db.Integer, db.ForeignKey("users.id")) updated_by = db.Column(db.Integer, db.ForeignKey("users.id")) # 关系定义 creator = db.relationship( "User", foreign_keys=[created_by], remote_side=[id], backref="created_users" ) updater = db.relationship( "User", foreign_keys=[updated_by], remote_side=[id], backref="updated_users" ) # 关联关系 roles = db.relationship( "Role", secondary="user_roles", back_populates="users", foreign_keys="[UserRole.user_id, UserRole.role_id]", ) # 明确指定外键) created_roles = db.relationship( "Role", backref="creator", foreign_keys="Role.created_by" ) updated_roles = db.relationship( "Role", backref="updater", foreign_keys="Role.updated_by" ) def __repr__(self): return f"" class Role(db.Model): __tablename__ = "roles" id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(50), unique=True, nullable=False, comment="角色名称") description = db.Column(db.String(255), comment="角色描述") status = db.Column(db.Integer, default=1, comment="状态:1-启用,0-禁用") created_at = db.Column(db.DateTime, default=datetime.now) updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) # 外键字段 created_by = db.Column(db.Integer, db.ForeignKey("users.id")) updated_by = db.Column(db.Integer, db.ForeignKey("users.id")) # 关系定义 users = db.relationship( "User", secondary="user_roles", back_populates="roles", foreign_keys="[UserRole.role_id, UserRole.user_id]", ) def __repr__(self): return f"" class UserRole(db.Model): __tablename__ = "user_roles" user_id = db.Column(db.Integer, db.ForeignKey("users.id"), primary_key=True) role_id = db.Column(db.Integer, db.ForeignKey("roles.id"), primary_key=True) created_at = db.Column(db.DateTime, default=datetime.now) created_by = db.Column(db.Integer, db.ForeignKey("users.id")) # 关系定义 user = db.relationship("User", foreign_keys=[user_id], backref="role_assignments") role = db.relationship("Role", foreign_keys=[role_id], backref="user_assignments") creator_rel = db.relationship( "User", foreign_keys=[created_by], backref="created_assignments" ) def __repr__(self): return f"" class AccidList(db.Model): """事故工单列表模型类(对应 accid_list 表)""" __tablename__ = "accid_list" # 对应 MySQL 表名 __table_args__ = ( # 修正:使用 db.Index(和 OperationHistory 模型一致),移除注释避免兼容问题 db.Index("idx_ticket_id", "ticket_id"), db.Index("idx_creator", "creator"), db.Index("idx_create_time", "create_time"), db.Index("idx_carline", "carline"), db.Index("idx_affects_versions", "affects_versions"), db.Index("idx_components", "components"), db.Index("idx_labels", "labels"), db.Index("idx_fix_versions", "fix_versions"), db.Index("idx_accident_level", "accident_level"), db.Index("idx_assignee_name", "assignee_name"), # 表级属性(参考 Fst 模型的 collation 写法) { "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", "mysql_engine": "InnoDB", "mysql_row_format": "DYNAMIC", }, ) # 核心修正:所有字段使用 db. 前缀(和 User 模型一致) id = db.Column( db.Integer, primary_key=True, autoincrement=True, comment="主键ID,自增" ) ticket_id = db.Column(db.String(50), nullable=False, comment="工单ID") model = db.Column(db.String(100), nullable=True, default=None, comment="型号") vin = db.Column(db.String(50), nullable=False, comment="车辆识别码(VIN)") creator = db.Column( db.String(50), nullable=False, default="", comment="创建人(用户名,如ZHANMEN)" ) create_time = db.Column(db.DateTime, nullable=False, comment="工单新建时间") occur_datetime = db.Column(db.DateTime, nullable=False, comment="问题发生时间") case_description = db.Column(db.Text, nullable=False, comment="Case描述") fo_solution = db.Column(db.Text, nullable=False, comment="Fo解答") category = db.Column(db.String(100), nullable=False, default="", comment="分类") keywords = db.Column( db.String(200), nullable=False, default="", comment="关键词(多个用逗号分隔)" ) handle_status = db.Column( db.String(20), nullable=True, default="已完成", comment="处理状态:已完成/未完成", ) # 修正:时间字段默认值用 db.func.current_timestamp()(和 BagFile 模型一致) create_at = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), comment="记录插入数据库时间", ) update_at = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="记录最后更新时间", ) bmbs_name = db.Column( db.String(50), nullable=True, default="", comment="BMBS联系人" ) # 修正:Enum 默认值错误(原 default='' 改为 'P1'),使用 db.Enum accident_level = db.Column( db.Enum("P1", "P2", "P3"), nullable=True, default="P1", comment="事故评级" ) rdca_reporter = db.Column( db.String(50), nullable=True, default="", comment="RDCA报告人" ) carline = db.Column( db.String(100), nullable=True, default=None, comment="车系(关联字典表dict_code=carline)", ) software_version = db.Column( db.String(50), nullable=True, default="", comment="软件版本" ) labels = db.Column( db.String(100), nullable=True, default=None, comment="事故类型标签(关联字典表dict_code=labels)", ) attachment_url = db.Column( db.String(255), nullable=True, default="", comment="附件地址(多个用逗号分隔)" ) assignee_name = db.Column( db.String(50), nullable=True, default="", comment="经办人(关联字典表/用户表)" ) affects_versions = db.Column( db.String(100), nullable=True, default=None, comment="受影响的版本(关联字典表dict_code=affects_versions)", ) components = db.Column( db.String(100), nullable=True, default=None, comment="零部件/组件(关联字典表dict_code=components)", ) fix_versions = db.Column( db.String(100), nullable=True, default=None, comment="修复方案对应的版本(关联字典表dict_code=fix_versions)", ) subtasks = db.Column( db.String(200), nullable=False, default="", comment="子任务(多个用逗号分隔)" ) linked_work_items = db.Column( db.String(200), nullable=False, default="", comment="关联的工作项(格式:类型:ID,类型:ID)", ) descriptions = db.Column( db.String(255), nullable=False, default="", comment="补充描述(区别于case_description)", ) mviz_link = db.Column( db.String(255), nullable=False, default="", comment="mviz的链接" ) system_function = db.Column( db.String(255), nullable=False, default="", comment="system_function字段" ) image_ids = db.Column( db.String(255), nullable=False, default="", comment="关联图片ID串,逗号分隔(如:1,2,3)", ) def __repr__(self): return f"" def to_dict(self): """转为字典,适配接口返回(参考 DictType 的 to_dict 方法)""" return { "id": self.id, "ticket_id": self.ticket_id, "model": self.model, "vin": self.vin, "creator": self.creator, "create_time": ( self.create_time.strftime("%Y-%m-%d %H:%M:%S") if self.create_time else None ), "occur_datetime": ( self.occur_datetime.strftime("%Y-%m-%d %H:%M:%S") if self.occur_datetime else None ), "case_description": self.case_description, "fo_solution": self.fo_solution, "category": self.category, "keywords": self.keywords, "handle_status": self.handle_status, "create_at": ( self.create_at.strftime("%Y-%m-%d %H:%M:%S") if self.create_at else None ), "update_at": ( self.update_at.strftime("%Y-%m-%d %H:%M:%S") if self.update_at else None ), "bmbs_name": self.bmbs_name, "accident_level": self.accident_level, "rdca_reporter": self.rdca_reporter, "carline": self.carline, "software_version": self.software_version, "labels": self.labels, "attachment_url": self.attachment_url, "assignee_name": self.assignee_name, "affects_versions": self.affects_versions, "components": self.components, "fix_versions": self.fix_versions, "subtasks": self.subtasks, "linked_work_items": self.linked_work_items, "descriptions": self.descriptions, "mviz_link": self.mviz_link, "system_function":self.system_function, "image_ids":self.image_ids } class DictType(db.Model): """字典类型表模型类(对应 dict_type 表)""" __tablename__ = "dict_type" # 匹配 MySQL 表名 __table_args__ = ( # 修正:使用 db.UniqueConstraint(参考 RuleVersions 模型) db.UniqueConstraint( "dict_code", name="uk_dict_code", comment="字典编码唯一(对应唯一字段)" ), # 表级属性(对齐项目规范) { "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", "mysql_engine": "InnoDB", "mysql_row_format": "Dynamic", "comment": "字典类型表(管理需要下拉的字段)", }, ) # 修正:所有字段使用 db. 前缀,类型改为 db.Integer(和 User 模型一致) id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment="主键ID") dict_code = db.Column( db.String(50), nullable=False, comment="字典编码(对应accid_list的字段名,如carline、labels)", ) dict_name = db.Column( db.String(50), nullable=False, comment="字典名称(字段中文名称,如车系、事故类型)", ) remark = db.Column( db.String(200), nullable=True, default="", comment="备注(如“事故工单-车系下拉选项”)", ) status = db.Column( db.Integer, nullable=False, default=1, comment="状态:1-启用,0-禁用" ) create_time = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), comment="创建时间" ) update_time = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间", ) def __repr__(self): return f"" def to_dict(self): """将模型对象转为字典,便于接口返回数据""" return { "id": self.id, "dict_code": self.dict_code, "dict_name": self.dict_name, "remark": self.remark, "status": self.status, "create_time": ( self.create_time.strftime("%Y-%m-%d %H:%M:%S") if self.create_time else None ), "update_time": ( self.update_time.strftime("%Y-%m-%d %H:%M:%S") if self.update_time else None ), } class DictItem(db.Model): """字典项表模型类(对应 dict_item 表)""" __tablename__ = "dict_item" # 严格匹配 MySQL 表名 __table_args__ = ( # 修正:使用 db.Index(对齐 OperationHistory 模型) db.Index("idx_dict_id", "dict_id"), db.Index("idx_item_value", "item_value"), # 表级属性(对齐项目规范) { "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", "mysql_engine": "InnoDB", "mysql_row_format": "Dynamic", "comment": "字典项表(管理每个字段的具体下拉选项)", }, ) # 修正:所有字段使用 db. 前缀,类型改为 db.Integer(和 User 模型一致) id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment="主键ID") # 修正:外键使用 db.ForeignKey(参考 BagFile 模型) dict_id = db.Column( db.Integer, db.ForeignKey("dict_type.id"), nullable=False, comment="关联字典类型表的ID", ) item_value = db.Column( db.String(100), nullable=False, comment="选项值(存储到accid_list的实际值,如“大众帕萨特”“P1”)", ) item_label = db.Column( db.String(100), nullable=False, comment="选项标签(前端显示的文本,如“大众帕萨特(车系)”)", ) sort = db.Column( db.Integer, nullable=True, default=0, comment="排序号(前端下拉框显示顺序,数字越小越靠前)", ) status = db.Column( db.Integer, nullable=False, default=1, comment="状态:1-启用,0-禁用(禁用后前端不显示)", ) remark = db.Column( db.String(200), nullable=True, default="", comment="备注(如“2025款帕萨特专属”)", ) create_time = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), comment="创建时间" ) update_time = db.Column( db.DateTime, nullable=True, default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间", ) # 修正:关系使用 db.relationship + db.backref(参考 User 模型) dict_type = db.relationship( "DictType", backref=db.backref("dict_items", lazy="joined"), lazy="joined" ) def __repr__(self): return f"" def to_dict(self): """将模型对象转为字典,适配接口返回数据""" return { "id": self.id, "dict_id": self.dict_id, "item_value": self.item_value, "item_label": self.item_label, "sort": self.sort, "status": self.status, "remark": self.remark, "create_time": ( self.create_time.strftime("%Y-%m-%d %H:%M:%S") if self.create_time else None ), "update_time": ( self.update_time.strftime("%Y-%m-%d %H:%M:%S") if self.update_time else None ), # 关联返回字典类型信息(可选,提升接口易用性) "dict_code": self.dict_type.dict_code if self.dict_type else None, "dict_name": self.dict_type.dict_name if self.dict_type else None, } class FileUpload(db.Model): """通用文件上传元信息表模型类(对应 file_uploads 表)""" __tablename__ = "file_uploads" # 对应 MySQL 表名 __table_args__ = ( # 索引定义(与建表语句完全一致) db.Index("idx_business_type_id", "business_type", "business_id"), db.Index("idx_create_time", "create_time"), db.Index("idx_status", "status"), # 表级属性(与 AccidList 模型格式一致) { "mysql_charset": "utf8mb4", "mysql_collate": "utf8mb4_unicode_ci", "mysql_engine": "InnoDB", "mysql_row_format": "DYNAMIC", }, ) # 核心修正:全用 db.Integer,通过 info 指定 MySQL 底层类型(无参数报错) id = db.Column( db.Integer, # 模型层用 int 兼容 primary_key=True, autoincrement=True, info={'mysql_type': 'BIGINT UNSIGNED'}, # 数据库底层仍是 BIGINT UNSIGNED comment="主键(自增数值ID)", ) file_name = db.Column( db.String(255), nullable=False, comment="原始文件名(如:事故图片1.jpg)" ) file_alias = db.Column( db.String(255), nullable=False, comment="存储别名(避免重名,如:20260210_153950_89782.jpg)", ) file_path = db.Column( db.String(512), nullable=False, comment="存储路径/访问URL(本地:/uploads/202602/xxx.jpg;OSS:https://xxx.oss-cn-beijing.aliyuncs.com/xxx.jpg)", ) file_size = db.Column( db.Integer, # 模型层用 int 兼容 nullable=False, info={'mysql_type': 'BIGINT UNSIGNED'}, # 数据库底层 BIGINT UNSIGNED comment="文件大小(字节)" ) file_type = db.Column( db.String(50), nullable=False, comment="文件MIME类型(如:image/jpeg、image/png)", ) business_type = db.Column( db.String(50), nullable=True, default=None, comment="关联业务类型(如:case=案例、user=用户头像)", ) business_id = db.Column( db.Integer, # 模型层用 int 兼容 nullable=True, default=None, info={'mysql_type': 'BIGINT UNSIGNED'}, # 数据库底层 BIGINT UNSIGNED comment="关联业务主键(案例ID/用户ID等,初始为NULL)", ) create_time = db.Column( db.DateTime, nullable=False, default=func.current_timestamp(), # 对应 DEFAULT CURRENT_TIMESTAMP comment="上传时间", ) create_by = db.Column( db.String(50), nullable=True, default=None, comment="上传人(用户ID/用户名)" ) status = db.Column( db.Integer, # 模型层用 int 兼容 nullable=False, default=1, info={'mysql_type': 'TINYINT(3) UNSIGNED'}, # 数据库底层 TINYINT(3) UNSIGNED comment="状态:1=有效,0=逻辑删除", ) def __repr__(self): """模型字符串表示,便于调试""" return f"" def to_dict(self): """转为字典,适配接口返回(与 AccidList 模型风格一致)""" return { "id": self.id, "file_name": self.file_name, "file_alias": self.file_alias, "file_path": self.file_path, "file_size": self.file_size, "file_type": self.file_type, "business_type": self.business_type, "business_id": self.business_id, "create_time": ( self.create_time.strftime("%Y-%m-%d %H:%M:%S") if self.create_time else None ), "create_by": self.create_by, "status": self.status, "status_text": "有效" if self.status == 1 else "已删除", }