Files
checklist/backend/app/models.py
ronalds fdad7b10ad v1.0.64 Backend / v1.0.58 Frontend - Renombrar cliente a N° de Pedido
Backend (1.0.64):
- Renombrado campo client_name a order_number en modelo Inspection
- Actualizado InspectionBase schema con nuevo campo order_number
- Comentario descriptivo: "Número de pedido asociado a la inspección"

Frontend (1.0.58):
- Renombrado client_name a order_number en toda la aplicación
- Actualizado label: "Nombre del Cliente" → "Nº de Pedido"
- Actualizado placeholder: "Juan Pérez" → "PED-12345"
- Actualizado título sección: "Información del Cliente" → "Información del Pedido"
- Actualizado filtro de búsqueda para incluir número de pedido
- Actualizado texto de búsqueda: "cliente" → "Nº pedido"

Database:
- Script de migración: rename_client_name_to_order_number.sql
- Comando: ALTER TABLE inspections RENAME COLUMN client_name TO order_number

Nota: Ejecutar migración SQL antes de usar esta versión
2025-11-27 01:49:42 -03:00

252 lines
11 KiB
Python

from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, JSON, Float
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, index=True, nullable=False)
email = Column(String(100), unique=True, index=True)
password_hash = Column(String(255), nullable=False)
role = Column(String(20), nullable=False) # admin, mechanic, asesor
full_name = Column(String(100))
employee_code = Column(String(50)) # Nro Operario - código de otro sistema
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
checklists_created = relationship("Checklist", back_populates="creator")
inspections = relationship("Inspection", back_populates="mechanic")
api_tokens = relationship("APIToken", back_populates="user", cascade="all, delete-orphan")
class APIToken(Base):
__tablename__ = "api_tokens"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
token = Column(String(100), unique=True, index=True, nullable=False)
description = Column(String(200))
is_active = Column(Boolean, default=True)
last_used_at = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationship
user = relationship("User", back_populates="api_tokens")
class Checklist(Base):
__tablename__ = "checklists"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(200), nullable=False)
description = Column(Text)
ai_mode = Column(String(20), default="off") # off, assisted, copilot
scoring_enabled = Column(Boolean, default=True)
max_score = Column(Integer, default=0)
logo_url = Column(String(500))
is_active = Column(Boolean, default=True)
created_by = Column(Integer, ForeignKey("users.id"))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships
creator = relationship("User", back_populates="checklists_created")
questions = relationship("Question", back_populates="checklist", cascade="all, delete-orphan")
inspections = relationship("Inspection", back_populates="checklist")
permissions = relationship("ChecklistPermission", back_populates="checklist", cascade="all, delete-orphan")
class Question(Base):
__tablename__ = "questions"
id = Column(Integer, primary_key=True, index=True)
checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False)
section = Column(String(100)) # Sistema eléctrico, Frenos, etc
text = Column(Text, nullable=False)
type = Column(String(30), nullable=False) # boolean, single_choice, multiple_choice, scale, text, number, date, time
points = Column(Integer, default=1)
options = Column(JSON) # Configuración flexible según tipo de pregunta
order = Column(Integer, default=0)
allow_photos = Column(Boolean, default=True)
max_photos = Column(Integer, default=3)
requires_comment_on_fail = Column(Boolean, default=False)
send_notification = Column(Boolean, default=False)
# Conditional logic - Subpreguntas anidadas hasta 5 niveles
parent_question_id = Column(Integer, ForeignKey("questions.id"), nullable=True)
show_if_answer = Column(String(50), nullable=True) # Valor que dispara esta pregunta
depth_level = Column(Integer, default=0) # 0=principal, 1-5=subpreguntas anidadas
# AI Analysis
ai_prompt = Column(Text, nullable=True) # Prompt personalizado para análisis de IA de esta pregunta
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
checklist = relationship("Checklist", back_populates="questions")
answers = relationship("Answer", back_populates="question")
subquestions = relationship("Question", backref="parent", remote_side=[id])
class Inspection(Base):
__tablename__ = "inspections"
id = Column(Integer, primary_key=True, index=True)
checklist_id = Column(Integer, ForeignKey("checklists.id"), nullable=False)
mechanic_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Datos de la OR
or_number = Column(String(50))
work_order_number = Column(String(50))
# Datos del vehículo
vehicle_plate = Column(String(20), nullable=False, index=True)
vehicle_brand = Column(String(50))
vehicle_model = Column(String(100))
vehicle_km = Column(Integer)
order_number = Column(String(200)) # Nº de Pedido
# Datos del mecánico
mechanic_employee_code = Column(String(50)) # Código de operario del mecánico
# Scoring
score = Column(Integer, default=0)
max_score = Column(Integer, default=0)
percentage = Column(Float, default=0.0)
flagged_items_count = Column(Integer, default=0)
# Estado
status = Column(String(20), default="draft") # draft, completed, inactive
is_active = Column(Boolean, default=True)
# Firma
signature_data = Column(Text) # Base64 de la firma
signed_at = Column(DateTime(timezone=True))
# Timestamps
started_at = Column(DateTime(timezone=True), server_default=func.now())
completed_at = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
pdf_url = Column(String(500)) # URL del PDF en S3
# Relationships
checklist = relationship("Checklist", back_populates="inspections")
mechanic = relationship("User", back_populates="inspections")
answers = relationship("Answer", back_populates="inspection", cascade="all, delete-orphan")
class Answer(Base):
__tablename__ = "answers"
id = Column(Integer, primary_key=True, index=True)
inspection_id = Column(Integer, ForeignKey("inspections.id"), nullable=False)
question_id = Column(Integer, ForeignKey("questions.id"), nullable=False)
answer_value = Column(Text) # La respuesta del mecánico
status = Column(String(20), default="ok") # ok, warning, critical, info
points_earned = Column(Integer, default=0)
comment = Column(Text) # Comentarios adicionales
ai_analysis = Column(JSON) # Análisis de IA si aplica
is_flagged = Column(Boolean, default=False) # Si requiere atención
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Relationships
inspection = relationship("Inspection", back_populates="answers")
question = relationship("Question", back_populates="answers")
media_files = relationship("MediaFile", back_populates="answer", cascade="all, delete-orphan")
class MediaFile(Base):
__tablename__ = "media_files"
id = Column(Integer, primary_key=True, index=True)
answer_id = Column(Integer, ForeignKey("answers.id"), nullable=False)
file_path = Column(String(500), nullable=False)
file_type = Column(String(20), default="image") # image, video
caption = Column(Text)
order = Column(Integer, default=0)
uploaded_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
answer = relationship("Answer", back_populates="media_files")
class AIConfiguration(Base):
__tablename__ = "ai_configurations"
id = Column(Integer, primary_key=True, index=True)
provider = Column(String(50), nullable=False) # openai, gemini
api_key = Column(Text, nullable=False)
model_name = Column(String(100), nullable=True)
logo_url = Column(Text, nullable=True) # URL del logo configurable
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
class ChecklistPermission(Base):
"""Tabla intermedia para permisos de checklist por mecánico"""
__tablename__ = "checklist_permissions"
id = Column(Integer, primary_key=True, index=True)
checklist_id = Column(Integer, ForeignKey("checklists.id", ondelete="CASCADE"), nullable=False)
mechanic_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
checklist = relationship("Checklist", back_populates="permissions")
mechanic = relationship("User")
class QuestionAuditLog(Base):
"""Registro de auditoría para cambios en preguntas de checklists"""
__tablename__ = "question_audit_log"
id = Column(Integer, primary_key=True, index=True)
question_id = Column(Integer, ForeignKey("questions.id", ondelete="CASCADE"), nullable=False)
checklist_id = Column(Integer, ForeignKey("checklists.id", ondelete="CASCADE"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
action = Column(String(50), nullable=False) # created, updated, deleted
field_name = Column(String(100), nullable=True) # Campo modificado
old_value = Column(Text, nullable=True) # Valor anterior
new_value = Column(Text, nullable=True) # Valor nuevo
comment = Column(Text, nullable=True) # Comentario del cambio
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
question = relationship("Question")
checklist = relationship("Checklist")
user = relationship("User")
class InspectionAuditLog(Base):
"""Registro de auditoría para cambios en inspecciones y respuestas"""
__tablename__ = "inspection_audit_log"
id = Column(Integer, primary_key=True, index=True)
inspection_id = Column(Integer, ForeignKey("inspections.id", ondelete="CASCADE"), nullable=False)
answer_id = Column(Integer, ForeignKey("answers.id", ondelete="CASCADE"), nullable=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
action = Column(String(50), nullable=False) # created, updated, deleted, status_changed
entity_type = Column(String(50), nullable=False) # inspection, answer
field_name = Column(String(100), nullable=True) # Campo modificado
old_value = Column(Text, nullable=True) # Valor anterior
new_value = Column(Text, nullable=True) # Valor nuevo
comment = Column(Text, nullable=True) # Comentario del cambio
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Relationships
inspection = relationship("Inspection")
answer = relationship("Answer")
user = relationship("User")