from sqlalchemy import Table, Column, Integer, String, Boolean, ForeignKey, DateTime, Text, Enum, JSON from sqlalchemy.orm import relationship from database import Base import datetime import enum # Junction Tables project_specialties = Table( 'project_specialties', Base.metadata, Column('project_id', Integer, ForeignKey('projects.id'), primary_key=True), Column('specialty_id', Integer, ForeignKey('specialties.id'), primary_key=True) ) project_contractors = Table( 'project_contractors', Base.metadata, Column('project_id', Integer, ForeignKey('projects.id'), primary_key=True), Column('contractor_id', Integer, ForeignKey('contractors.id'), primary_key=True) ) class UserRole(str, enum.Enum): ADMIN = "admin" DIRECTOR = "director" SUPERVISOR = "supervisor" COORDINATOR = "coordinator" CONTRACTOR = "contractor" CLIENT = "client" class ActivityType(str, enum.Enum): INSPECTION = "inspection" MEETING = "meeting" VIRTUAL_MEETING = "virtual_meeting" COORDINATION = "coordination" TEST = "test" OTHER = "other" class NCLevel(str, enum.Enum): CRITICAL = "critical" MAJOR = "major" MINOR = "minor" class NCType(str, enum.Enum): HUMAN_ERROR = "Errores humanos" PROCESS_FAILURE = "Fallas en los procesos" DESIGN_ISSUE = "Problemas de diseño" UNCONTROLLED_CHANGE = "Cambios no controlados" COMMUNICATION_FAILURE = "Falta de comunicación" class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) full_name = Column(String) role = Column(Enum(UserRole), default=UserRole.SUPERVISOR) is_active = Column(Boolean, default=True) class Project(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True, nullable=False) code = Column(String, unique=True, index=True) location = Column(String) start_date = Column(DateTime) end_date = Column(DateTime) status = Column(String, default="active") parent_id = Column(Integer, ForeignKey("projects.id"), nullable=True) # Relationships parent = relationship("Project", remote_side=[id], back_populates="subprojects") subprojects = relationship("Project", back_populates="parent") activities = relationship("Activity", back_populates="project") specialties = relationship("Specialty", secondary=project_specialties) contractors = relationship("Contractor", secondary=project_contractors) class Specialty(Base): __tablename__ = "specialties" id = Column(Integer, primary_key=True, index=True) name = Column(String, unique=True, nullable=False) # Civil, Mecánica, Eléctrica, SSOMA class Contractor(Base): __tablename__ = "contractors" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) ruc = Column(String, unique=True) contact_name = Column(String) email = Column(String, nullable=True) phone = Column(String, nullable=True) address = Column(String, nullable=True) is_active = Column(Boolean, default=True) specialty_id = Column(Integer, ForeignKey("specialties.id"), nullable=True) parent_id = Column(Integer, ForeignKey("contractors.id"), nullable=True) specialty = relationship("Specialty") parent = relationship("Contractor", remote_side=[id], back_populates="subcontractors") subcontractors = relationship("Contractor", back_populates="parent") class Activity(Base): __tablename__ = "activities" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) specialty_id = Column(Integer, ForeignKey("specialties.id")) contractor_id = Column(Integer, ForeignKey("contractors.id"), nullable=True) user_id = Column(Integer, ForeignKey("users.id")) # Reporter date = Column(DateTime, default=datetime.datetime.utcnow) end_date = Column(DateTime, nullable=True) type = Column(Enum(ActivityType), default=ActivityType.INSPECTION) area = Column(String) # Frente de obra / Linea description = Column(Text) observations = Column(Text) audio_transcription = Column(Text, nullable=True) status = Column(String, default="completed") project = relationship("Project", back_populates="activities") specialty = relationship("Specialty") contractor = relationship("Contractor") reporter = relationship("User") non_conformities = relationship("NonConformity", back_populates="activity") evidences = relationship("Evidence", back_populates="activity") class NonConformity(Base): __tablename__ = "non_conformities" id = Column(Integer, primary_key=True, index=True) activity_id = Column(Integer, ForeignKey("activities.id")) level = Column(Enum(NCLevel), default=NCLevel.MINOR) description = Column(Text, nullable=False) status = Column(String, default="open") # open, closed # New Fields due_date = Column(DateTime, nullable=True) responsible_person = Column(String, nullable=True) # Legend/Name responsible_email = Column(String, nullable=True) # For guest access access_hash = Column(String, unique=True, index=True, nullable=True) contractor_id = Column(Integer, ForeignKey("contractors.id"), nullable=True) action_checklist = Column(JSON, nullable=True) # List of dicts {text: str, completed: bool} nc_type = Column(Enum(NCType), nullable=True) impact_description = Column(Text, nullable=True) closure_description = Column(Text, nullable=True) guest_actions = Column(Text, nullable=True) # Field for guest to describe actions taken parent_id = Column(Integer, ForeignKey("non_conformities.id"), nullable=True) activity = relationship("Activity", back_populates="non_conformities") contractor = relationship("Contractor") evidences = relationship("Evidence", back_populates="non_conformity") parent = relationship("NonConformity", remote_side=[id], back_populates="child_ncs") child_ncs = relationship("NonConformity", back_populates="parent") class Evidence(Base): __tablename__ = "evidences" id = Column(Integer, primary_key=True, index=True) activity_id = Column(Integer, ForeignKey("activities.id"), nullable=True) non_conformity_id = Column(Integer, ForeignKey("non_conformities.id"), nullable=True) file_path = Column(String, nullable=False) media_type = Column(String) # image, video, document description = Column(String) captured_at = Column(DateTime, default=datetime.datetime.utcnow) # Transcription fields for audio transcription = Column(Text, nullable=True) transcription_status = Column(String, default="none") # none, pending, processing, completed, error activity = relationship("Activity", back_populates="evidences") non_conformity = relationship("NonConformity", back_populates="evidences")