from sqlalchemy import Column, String, Boolean, DateTime, Integer, Numeric, ForeignKey from sqlalchemy.sql import func from sqlalchemy.orm import relationship from app.core.database.db_session import Base class ServiceCatalog(Base): __tablename__ = "service_catalog" service_id = Column(String(26), primary_key=True) name = Column(String(255), nullable=False) description = Column(String(1000), nullable=True) base_price = Column(Numeric(10, 2), nullable=False) estimated_duration_minutes = Column(Integer, nullable=False) workflow_type = Column(String(50), default="REPAIR_QUOTE") # FIXED_SERVICE, INSPECTION_REQUIRED, REPAIR_QUOTE active = Column(Boolean, default=True) from sqlalchemy import Date class ServiceJob(Base): __tablename__ = "service_jobs" job_id = Column(String(26), primary_key=True) job_no = Column(String(50), unique=True, nullable=False) customer_id = Column(String(26), nullable=False, index=True) device_id = Column(String(26), ForeignKey("customer_devices.device_id"), nullable=False) source = Column(String(20), default="ONLINE") # ONLINE, WALK_IN service_id = Column(String(26), ForeignKey("service_catalog.service_id"), nullable=True) # Nullable for Cashify-style jobs status = Column(String(30), default="BOOKING_PENDING", index=True) custom_service_name = Column(String(255), nullable=True) # Granular selection IDs device_type = Column(String(50), nullable=True) brand_id = Column(String(26), ForeignKey("brands.brand_id"), nullable=True) series_id = Column(String(26), ForeignKey("device_series.series_id"), nullable=True) model_id = Column(String(26), ForeignKey("device_models.model_id"), nullable=True) service_type_id = Column(String(26), nullable=True) repair_service_id = Column(String(26), nullable=True) repair_variant_id = Column(String(26), nullable=True) # Parity Price & Config Snapshots currency = Column(String(3), default="INR", nullable=False) service_name_snapshot = Column(String(255), nullable=True) variant_name_snapshot = Column(String(255), nullable=True) base_price_snapshot = Column(Numeric(10, 2), nullable=True) duration_snapshot = Column(Integer, nullable=True) warranty_snapshot = Column(Integer, nullable=True) inspection_fee_snapshot = Column(Numeric(10, 2), nullable=True) # Queue & Priority Attributes queue_number = Column(String(50), nullable=True) queue_date = Column(Date, nullable=True) priority = Column(String(20), default="NORMAL") created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) device = relationship("CustomerDevice") service = relationship("ServiceCatalog") class ServiceAppointment(Base): __tablename__ = "service_appointments" appointment_id = Column(String(26), primary_key=True) service_job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, index=True) scheduled_start = Column(DateTime, nullable=False) scheduled_end = Column(DateTime, nullable=False) status = Column(String(20), default="HELD", index=True) # HELD, CONFIRMED, CANCELLED, EXPIRED, COMPLETED hold_expires_at = Column(DateTime, nullable=True) created_at = Column(DateTime, server_default=func.now()) confirmed_at = Column(DateTime, nullable=True) cancelled_at = Column(DateTime, nullable=True) job = relationship("ServiceJob") class ServiceJobAssignment(Base): __tablename__ = "service_job_assignments" assignment_id = Column(String(26), primary_key=True) service_job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, index=True) appointment_id = Column(String(26), ForeignKey("service_appointments.appointment_id"), nullable=False) technician_id = Column(String(26), nullable=False, index=True) assigned_at = Column(DateTime, server_default=func.now()) assigned_by = Column(String(26), nullable=True) unassigned_at = Column(DateTime, nullable=True) reason = Column(String(255), nullable=True) class ServiceJobIntake(Base): __tablename__ = "service_job_intakes" intake_id = Column(String(26), primary_key=True) service_job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, unique=True) received_by = Column(String(26), nullable=False) received_at = Column(DateTime, server_default=func.now()) power_status = Column(String(50), nullable=True) screen_condition = Column(String(100), nullable=True) body_condition = Column(String(100), nullable=True) back_condition = Column(String(100), nullable=True) camera_condition = Column(String(100), nullable=True) accessories = Column(String(255), nullable=True) customer_notes = Column(String(1000), nullable=True) technician_notes = Column(String(1000), nullable=True) class ServiceJobInspection(Base): __tablename__ = "service_job_inspections" inspection_id = Column(String(26), primary_key=True) service_job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, index=True) technician_id = Column(String(26), nullable=False, index=True) result = Column(String(50), nullable=False) # CUSTOMER_REPORT_CONFIRMED, CUSTOMER_REPORT_PARTIALLY_CONFIRMED, CUSTOMER_REPORT_NOT_CONFIRMED, ADDITIONAL_DAMAGE_FOUND customer_report = Column(String(1000), nullable=True) confirmed_damage = Column(String(1000), nullable=True) additional_damage = Column(String(1000), nullable=True) notes = Column(String(1000), nullable=True) created_at = Column(DateTime, server_default=func.now()) class ServiceJobMedia(Base): __tablename__ = "service_job_media" media_id = Column(String(26), primary_key=True) service_job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, index=True) category = Column(String(50), nullable=False) # INTAKE_PHOTO, INTAKE_VIDEO, BEFORE_REPAIR, AFTER_REPAIR file_id = Column(String(100), nullable=False) created_at = Column(DateTime, server_default=func.now()) class ServiceJobEvent(Base): __tablename__ = "service_job_events" event_id = Column(String(26), primary_key=True) job_id = Column(String(26), ForeignKey("service_jobs.job_id"), nullable=False, index=True) event_type = Column(String(50), nullable=False) performed_by = Column(String(26), nullable=False) timestamp = Column(DateTime, server_default=func.now()) notes = Column(String(500), nullable=True)