from sqlalchemy import Column, Integer, String, Text, Boolean, Float, Date, TIMESTAMP, ARRAY, ForeignKey, func from sqlalchemy.orm import relationship from database import Base class ChargerType(Base): __tablename__ = "charger_types" id = Column(Integer, primary_key=True) name = Column(String(100), nullable=False) description = Column(Text) created_at = Column(TIMESTAMP, server_default=func.now()) chargers = relationship("Charger", back_populates="charger_type") errors = relationship("ChargerTypeError", back_populates="charger_type", cascade="all, delete-orphan", order_by="ChargerTypeError.display_order") class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) username = Column(String(50), unique=True, nullable=False) password_hash = Column(String(255), nullable=False) role = Column(String(20), nullable=False) company = Column(String(100)) name = Column(String(50), nullable=False) phone = Column(String(20)) email = Column(String(100)) is_active = Column(Boolean, default=True) is_pending = Column(Boolean, default=False) created_at = Column(TIMESTAMP, server_default=func.now()) class Charger(Base): __tablename__ = "chargers" id = Column(String(50), primary_key=True) charger_type_id = Column(Integer, ForeignKey("charger_types.id")) name = Column(String(100), nullable=False) station_name = Column(String(100), nullable=False) location_detail = Column(Text) cpo_name = Column(String(100)) installed_at = Column(Date) gps_lat = Column(Float) gps_lng = Column(Float) is_active = Column(Boolean, default=True) created_at = Column(TIMESTAMP, server_default=func.now()) charger_type = relationship("ChargerType", back_populates="chargers") reports = relationship("Report", back_populates="charger") class Report(Base): __tablename__ = "reports" id = Column(Integer, primary_key=True) charger_id = Column(String(50), ForeignKey("chargers.id")) issue_types = Column(ARRAY(Text), nullable=False) issue_detail = Column(Text) error_code = Column(String(100)) occurred_at = Column(TIMESTAMP) contact = Column(String(20)) consent = Column(Boolean, default=False) gps_lat = Column(Float) gps_lng = Column(Float) status = Column(String(30), default="pending") ocpp_log = Column(Text) source = Column(String(20), default="qr") # qr | admin reported_by = Column(Integer, ForeignKey("users.id"), nullable=True) reported_at = Column(TIMESTAMP, server_default=func.now()) closure_type = Column(String(30)) # natural|remote_reset|false_alarm|other closure_note = Column(Text) closed_at = Column(TIMESTAMP) closed_by = Column(Integer, ForeignKey("users.id"), nullable=True) re_dispatch_count = Column(Integer, default=0) report_scope = Column(String(20), default="single") # single | station | type | multi scope_charger_count = Column(Integer, default=1) charger_ids = Column(ARRAY(Text), nullable=True) # multi 범위일 때 선택된 충전기 ID 목록 charger = relationship("Charger", back_populates="reports") photos = relationship("ReportPhoto", back_populates="report", cascade="all, delete-orphan") repair_links = relationship("RepairReport", back_populates="report") reporter = relationship("User", foreign_keys=[reported_by]) closer = relationship("User", foreign_keys=[closed_by]) class ReportPhoto(Base): __tablename__ = "report_photos" id = Column(Integer, primary_key=True) report_id = Column(Integer, ForeignKey("reports.id", ondelete="CASCADE")) file_path = Column(String(255), nullable=False) uploaded_at = Column(TIMESTAMP, server_default=func.now()) report = relationship("Report", back_populates="photos") class Repair(Base): __tablename__ = "repairs" id = Column(Integer, primary_key=True) mechanic_id = Column(Integer, ForeignKey("users.id")) repair_types = Column(ARRAY(Text), nullable=False) description = Column(Text, nullable=False) started_at = Column(TIMESTAMP, nullable=False) completed_at = Column(TIMESTAMP) result_status = Column(String(20), default="done") mechanic_lat = Column(Float) mechanic_lng = Column(Float) approved_at = Column(TIMESTAMP) approved_by = Column(Integer, ForeignKey("users.id")) re_dispatch_requested = Column(Boolean, default=False) re_dispatch_requested_at = Column(TIMESTAMP) mechanic = relationship("User", foreign_keys=[mechanic_id]) approver = relationship("User", foreign_keys=[approved_by]) report_links = relationship("RepairReport", back_populates="repair", cascade="all, delete-orphan") photos = relationship("RepairPhoto", back_populates="repair", cascade="all, delete-orphan") cost = relationship("RepairCost", back_populates="repair", uselist=False) class RepairReport(Base): __tablename__ = "repair_reports" repair_id = Column(Integer, ForeignKey("repairs.id", ondelete="CASCADE"), primary_key=True) report_id = Column(Integer, ForeignKey("reports.id", ondelete="CASCADE"), primary_key=True) repair = relationship("Repair", back_populates="report_links") report = relationship("Report", back_populates="repair_links") class RepairPhoto(Base): __tablename__ = "repair_photos" id = Column(Integer, primary_key=True) repair_id = Column(Integer, ForeignKey("repairs.id", ondelete="CASCADE")) photo_type = Column(String(10), default="after") file_path = Column(String(255), nullable=False) uploaded_at = Column(TIMESTAMP, server_default=func.now()) repair = relationship("Repair", back_populates="photos") class RepairCost(Base): __tablename__ = "repair_costs" id = Column(Integer, primary_key=True) repair_id = Column(Integer, ForeignKey("repairs.id", ondelete="CASCADE"), unique=True) root_cause = Column(Text) admin_note = Column(Text) cost_party_type = Column(String(50)) cost_party_manufacturer_id = Column(Integer, ForeignKey("manufacturers.id", ondelete="SET NULL")) cost_party_custom = Column(String(200)) recv_party_type = Column(String(50)) recv_party_manufacturer_id = Column(Integer, ForeignKey("manufacturers.id", ondelete="SET NULL")) recv_party_custom = Column(String(200)) cost_amount = Column(Integer, default=0) cost_status = Column(String(20), default="pending") reviewed_by = Column(Integer, ForeignKey("users.id")) reviewed_at = Column(TIMESTAMP) repair = relationship("Repair", back_populates="cost") reviewer = relationship("User", foreign_keys=[reviewed_by]) cost_manufacturer = relationship("Manufacturer", foreign_keys=[cost_party_manufacturer_id]) recv_manufacturer = relationship("Manufacturer", foreign_keys=[recv_party_manufacturer_id]) class Improvement(Base): __tablename__ = "improvements" id = Column(Integer, primary_key=True) title = Column(String(200), nullable=False) category = Column(String(20), nullable=False) description = Column(Text, nullable=False) priority = Column(String(10), default="normal") part_name = Column(String(100)) status = Column(String(20), default="registered") manufacturer_id = Column(Integer, ForeignKey("manufacturers.id", ondelete="SET NULL")) created_by = Column(Integer, ForeignKey("users.id")) sw_deploy_target = Column(Date) sw_deployed_at = Column(Date) manufacturer_memo = Column(Text) created_at = Column(TIMESTAMP, server_default=func.now()) manufacturer = relationship("Manufacturer", foreign_keys=[manufacturer_id]) creator = relationship("User", foreign_keys=[created_by]) report_links = relationship("ImprovementReport", back_populates="improvement", cascade="all, delete-orphan") attachments = relationship("ImprovementAttachment", back_populates="improvement", cascade="all, delete-orphan") logs = relationship("ImprovementLog", back_populates="improvement", cascade="all, delete-orphan") class ImprovementReport(Base): __tablename__ = "improvement_reports" improvement_id = Column(Integer, ForeignKey("improvements.id", ondelete="CASCADE"), primary_key=True) report_id = Column(Integer, ForeignKey("reports.id", ondelete="CASCADE"), primary_key=True) improvement = relationship("Improvement", back_populates="report_links") report = relationship("Report") class ImprovementAttachment(Base): __tablename__ = "improvement_attachments" id = Column(Integer, primary_key=True) improvement_id = Column(Integer, ForeignKey("improvements.id", ondelete="CASCADE")) file_path = Column(String(255), nullable=False) file_name = Column(String(255)) uploaded_at = Column(TIMESTAMP, server_default=func.now()) improvement = relationship("Improvement", back_populates="attachments") class ImprovementLog(Base): __tablename__ = "improvement_logs" id = Column(Integer, primary_key=True) improvement_id = Column(Integer, ForeignKey("improvements.id", ondelete="CASCADE")) changed_by = Column(Integer, ForeignKey("users.id")) old_status = Column(String(20)) new_status = Column(String(20)) memo = Column(Text) changed_at = Column(TIMESTAMP, server_default=func.now()) improvement = relationship("Improvement", back_populates="logs") changer = relationship("User") class ChargerTypeError(Base): __tablename__ = "charger_type_errors" id = Column(Integer, primary_key=True) charger_type_id = Column(Integer, ForeignKey("charger_types.id", ondelete="CASCADE"), nullable=False) error_code = Column(String(20), nullable=False) error_name = Column(String(100), nullable=False) range_condition = Column(String(200)) description = Column(Text) auto_recovery = Column(Boolean, default=True) display_order = Column(Integer, default=0) charger_type = relationship("ChargerType", back_populates="errors") class Manufacturer(Base): __tablename__ = "manufacturers" id = Column(Integer, primary_key=True) name = Column(String(100), nullable=False) representative_name = Column(String(100)) business_number = Column(String(50)) phone = Column(String(30)) address = Column(Text) is_active = Column(Boolean, default=True) created_at = Column(TIMESTAMP, server_default=func.now()) class SystemSetting(Base): __tablename__ = "system_settings" key = Column(String(100), primary_key=True) value = Column(Text, nullable=False) updated_at = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) class Holiday(Base): __tablename__ = "holidays" holiday_date = Column(Date, primary_key=True) name = Column(String(100), nullable=False)