from datetime import datetime, date from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from extensions import db class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False, index=True) email = db.Column(db.String(120), unique=True, nullable=True) password_hash = db.Column(db.String(256), nullable=False) full_name = db.Column(db.String(200), nullable=False) role = db.Column(db.String(20), nullable=False, default='employee') is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) senior_expert_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) telegram_chat_id = db.Column(db.String(100), nullable=True) profile_pic = db.Column(db.String(300), nullable=True) grade = db.Column(db.String(50), nullable=True) project_start_date = db.Column(db.Date, nullable=True) phone = db.Column(db.String(30), nullable=True) telegram = db.Column(db.String(100), nullable=True) emergency_contact = db.Column(db.String(200), nullable=True) emergency_contact_phone = db.Column(db.String(30), nullable=True) position = db.Column(db.String(100), nullable=True) delegated_admin_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) previous_review_rating = db.Column(db.String(50), nullable=True) senior_expert = db.relationship('User', remote_side=[id], backref='subordinates', foreign_keys=[senior_expert_id]) delegated_admin = db.relationship('User', remote_side=[id], foreign_keys=[delegated_admin_id]) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def has_role(self, *roles): return self.role in roles def _is_admin(self): if self.role in ('admin', 'sm'): return True if self.position == 'руководитель': return True if self.delegated_admin_id is not None: return True return False def can_view_employee(self, employee): if not self.is_authenticated: return False if self._is_admin(): return True if self.role == 'guest': return True if self.id == employee.id: return True if employee.is_active: return True return False def can_edit(self): return self._is_admin() def get_visible_users(self): if self._is_admin(): return User.query.filter_by(is_active=True).all() if self.role == 'guest': return User.query.filter_by(is_active=True).all() if self.role == 'senior_expert': visible = [self] visible.extend(User.query.filter_by(senior_expert_id=self.id, is_active=True).all()) return visible return [self] class KpiDefinition(db.Model): __tablename__ = 'kpi_definitions' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(200), nullable=False) description = db.Column(db.Text, nullable=True) unit = db.Column(db.String(50), nullable=True) category = db.Column(db.String(100), nullable=True) target_value = db.Column(db.Float, nullable=True) is_active = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) class KpiValue(db.Model): __tablename__ = 'kpi_values' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) kpi_id = db.Column(db.Integer, db.ForeignKey('kpi_definitions.id'), nullable=False, index=True) value = db.Column(db.Float, nullable=True) month = db.Column(db.Integer, nullable=False) year = db.Column(db.Integer, nullable=False) uploaded_file_id = db.Column(db.Integer, db.ForeignKey('uploaded_files.id'), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', backref='kpi_values') kpi = db.relationship('KpiDefinition', backref='values') uploaded_file = db.relationship('UploadedFile', backref='kpi_values') class UploadedFile(db.Model): __tablename__ = 'uploaded_files' id = db.Column(db.Integer, primary_key=True) filename = db.Column(db.String(300), nullable=False) original_name = db.Column(db.String(300), nullable=False) file_type = db.Column(db.String(10), nullable=False) file_size = db.Column(db.Integer, default=0) description = db.Column(db.String(500), nullable=True) kpi_name = db.Column(db.String(200), nullable=True) uploaded_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) upload_date = db.Column(db.DateTime, default=datetime.utcnow) uploader = db.relationship('User', backref='uploads') class Notification(db.Model): __tablename__ = 'notifications' __table_args__ = ( db.Index('ix_notif_user_read', 'user_id', 'is_read'), ) id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) message = db.Column(db.Text, nullable=False) type = db.Column(db.String(50), default='info') is_read = db.Column(db.Boolean, default=False) link = db.Column(db.String(500), nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', backref='notifications') class Task(db.Model): __tablename__ = 'tasks' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(300), nullable=False) description = db.Column(db.Text, nullable=True) assigned_to = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) assigned_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) status = db.Column(db.String(20), default='pending') priority = db.Column(db.String(20), default='medium') due_date = db.Column(db.Date, nullable=True) comment = db.Column(db.Text, nullable=True) rework_comment = db.Column(db.Text, nullable=True) completed_at = db.Column(db.DateTime, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) assignee = db.relationship('User', foreign_keys=[assigned_to], backref='assigned_tasks') assigner = db.relationship('User', foreign_keys=[assigned_by], backref='created_tasks') class KpiWeeklyData(db.Model): __tablename__ = 'kpi_weekly_data' __table_args__ = ( db.Index('ix_kwd_week_cat_metric', 'week_start', 'category', 'metric_name'), ) id = db.Column(db.Integer, primary_key=True) category = db.Column(db.String(100), nullable=False) metric_name = db.Column(db.String(200), nullable=False) week_start = db.Column(db.Date, nullable=False, index=True) value = db.Column(db.String(50), nullable=True) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) class EmployeeKpiValue(db.Model): __tablename__ = 'employee_kpi_values' __table_args__ = ( db.Index('ix_ekv_lookup', 'user_id', 'week_start', 'category', 'metric_name'), db.Index('ix_ekv_week', 'week_start'), ) id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) category = db.Column(db.String(100), nullable=False) metric_name = db.Column(db.String(200), nullable=False) week_start = db.Column(db.Date, nullable=False, index=True) value_date = db.Column(db.Date, nullable=True) plan_value = db.Column(db.String(50), nullable=True) fact_value = db.Column(db.String(50), nullable=True) aggregation = db.Column(db.String(10), default='sum') updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user = db.relationship('User', backref='kpi_entries') class EmployeeMapping(db.Model): __tablename__ = 'employee_mappings' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, unique=True, index=True) rebus_login = db.Column(db.String(200), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', backref='rebus_mapping', uselist=False) class Training(db.Model): __tablename__ = 'trainings' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(300), nullable=False) description = db.Column(db.Text, nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) date = db.Column(db.Date, nullable=False, default=date.today) hours = db.Column(db.Float, nullable=True) status = db.Column(db.String(20), default='completed') file_path = db.Column(db.String(300), nullable=True) original_name = db.Column(db.String(300), nullable=True) file_size = db.Column(db.Integer, default=0) uploaded_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) upload_date = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', foreign_keys=[user_id], backref='trainings') uploader = db.relationship('User', foreign_keys=[uploaded_by], backref='uploaded_trainings') class Audit(db.Model): __tablename__ = 'audits' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) file_name = db.Column(db.String(300), nullable=False) original_name = db.Column(db.String(300), nullable=False) audit_type = db.Column(db.String(20), nullable=False, default='audit') file_size = db.Column(db.Integer, default=0) uploaded_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) upload_date = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', foreign_keys=[user_id], backref='audits') uploader = db.relationship('User', foreign_keys=[uploaded_by], backref='uploaded_audits') class ScheduleShift(db.Model): __tablename__ = 'schedule_shifts' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True) date = db.Column(db.Date, nullable=False, index=True) shift_type = db.Column(db.String(20), nullable=False, default='shift') time_from = db.Column(db.String(5), nullable=True) time_to = db.Column(db.String(5), nullable=True) hours = db.Column(db.Float, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) user = db.relationship('User', backref='schedule_shifts') __table_args__ = ( db.UniqueConstraint('user_id', 'date', name='uq_user_date'), )