## 처리시간 지표 - 업무시간 기준(09-18 평일) / 공휴일 제외 24h / 달력 기준 3가지 모드 선택 - 공휴일 DB 관리 (holidays 테이블, 수동 등록·삭제·일괄 추가) - 2026년 공휴일 등록 지원 - 설정 페이지에서 라디오 버튼으로 모드 선택 ## 대시보드 차트 - 월별 평균 처리시간 막대 차트 추가 - 월별 신고 접수 건수 누적 막대 차트 추가 - 월별 → 일별 드릴다운 (막대 클릭 시 해당 월의 일별 차트로 전환) - 일별 막대 클릭 시 처리 완료/신고 접수 상세 내역 모달 - 충전기별 누적 고장 건수 Top 10 수평 막대 차트 추가 ## 신고 목록 - # 컬럼을 DB PK 대신 현재 목록 순서(1, 2, 3…)로 표시 - 엑셀 export 접수번호도 순차번호로 변경 ## 모바일 네비게이션 버그 수정 - 모바일에서 가로 오버플로우 시 nav가 body 넓이로 늘어나 햄버거 버튼이 화면 밖으로 밀리는 문제 수정 - nav를 position:fixed + body padding-top:54px 로 변경 (전체 페이지 적용) - 충전기 관리·신고 목록 페이지 지도 컨테이너에 isolation:isolate 적용 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
209 lines
9.5 KiB
Python
209 lines
9.5 KiB
Python
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())
|
|
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])
|
|
|
|
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"))
|
|
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(20))
|
|
cost_party_manufacturer_id = Column(Integer, ForeignKey("users.id"))
|
|
cost_party_custom = Column(String(100))
|
|
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])
|
|
manufacturer = relationship("User", foreign_keys=[cost_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("users.id"))
|
|
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("User", 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)
|