feat: 권한 탭 분리 + 부서 인원 표시 + 다수 시스템 개선
- tkuser: 권한 관리를 별도 탭으로 분리, 부서 클릭 시 소속 인원 목록 표시 - system1: 모바일 UI 개선, nginx 권한 보정, 신고 카테고리 타입 마이그레이션 - system2: 신고 상세/보고서 개선, 내 보고서 페이지 추가 - system3: 이슈 뷰/수신함/관리함 개선 - gateway: 포털 라우팅 수정 - user-management API: 부서별 권한 벌크 설정 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,7 @@ class User(Base):
|
||||
# Relationships
|
||||
issues = relationship("Issue", back_populates="reporter", foreign_keys="Issue.reporter_id")
|
||||
reviewed_issues = relationship("Issue", foreign_keys="Issue.reviewed_by_id")
|
||||
daily_works = relationship("DailyWork", back_populates="created_by")
|
||||
page_permissions = relationship("UserPagePermission", back_populates="user", foreign_keys="UserPagePermission.user_id")
|
||||
|
||||
class UserPagePermission(Base):
|
||||
@@ -104,6 +105,7 @@ class Issue(Base):
|
||||
status = Column(Enum(IssueStatus), default=IssueStatus.new)
|
||||
reporter_id = Column(Integer, ForeignKey("sso_users.user_id"))
|
||||
project_id = Column(Integer) # FK 제거 — projects는 tkuser에서 관리
|
||||
location_info = Column(String(200)) # 공장/작업장 위치 정보 (System 2 연동)
|
||||
report_date = Column(DateTime, default=get_kst_now)
|
||||
work_hours = Column(Float, default=0)
|
||||
detail_notes = Column(Text)
|
||||
@@ -182,6 +184,37 @@ class Project(Base):
|
||||
primaryjoin="Project.id == Issue.project_id",
|
||||
foreign_keys="[Issue.project_id]")
|
||||
|
||||
class DailyWork(Base):
|
||||
__tablename__ = "qc_daily_works"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, nullable=False, index=True)
|
||||
worker_count = Column(Integer, nullable=False)
|
||||
regular_hours = Column(Float, nullable=False)
|
||||
overtime_workers = Column(Integer, default=0)
|
||||
overtime_hours = Column(Float, default=0)
|
||||
overtime_total = Column(Float, default=0)
|
||||
total_hours = Column(Float, nullable=False)
|
||||
created_by_id = Column(Integer, ForeignKey("sso_users.user_id"))
|
||||
created_at = Column(DateTime, default=get_kst_now)
|
||||
|
||||
# Relationships
|
||||
created_by = relationship("User", back_populates="daily_works")
|
||||
|
||||
class ProjectDailyWork(Base):
|
||||
__tablename__ = "qc_project_daily_works"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, nullable=False, index=True)
|
||||
project_id = Column(Integer, ForeignKey("projects.project_id"), nullable=False)
|
||||
hours = Column(Float, nullable=False)
|
||||
created_by_id = Column(Integer, ForeignKey("sso_users.user_id"))
|
||||
created_at = Column(DateTime, default=get_kst_now)
|
||||
|
||||
# Relationships
|
||||
project = relationship("Project")
|
||||
created_by = relationship("User")
|
||||
|
||||
class DeletionLog(Base):
|
||||
__tablename__ = "qc_deletion_logs"
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ class IssueBase(BaseModel):
|
||||
project_id: int
|
||||
|
||||
class IssueCreate(IssueBase):
|
||||
location_info: Optional[str] = None # 공장/작업장 위치 정보
|
||||
photo: Optional[str] = None # Base64 encoded image
|
||||
photo2: Optional[str] = None
|
||||
photo3: Optional[str] = None
|
||||
@@ -112,6 +113,7 @@ class IssueUpdate(BaseModel):
|
||||
|
||||
class Issue(IssueBase):
|
||||
id: int
|
||||
location_info: Optional[str] = None
|
||||
photo_path: Optional[str] = None
|
||||
photo_path2: Optional[str] = None
|
||||
photo_path3: Optional[str] = None
|
||||
@@ -259,6 +261,7 @@ class InboxIssue(BaseModel):
|
||||
id: int
|
||||
category: IssueCategory
|
||||
description: str
|
||||
location_info: Optional[str] = None
|
||||
photo_path: Optional[str] = None
|
||||
photo_path2: Optional[str] = None
|
||||
project_id: Optional[int] = None
|
||||
@@ -300,6 +303,50 @@ class Project(ProjectBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# DailyWork schemas
|
||||
class DailyWorkBase(BaseModel):
|
||||
date: datetime
|
||||
worker_count: int = Field(gt=0)
|
||||
overtime_workers: Optional[int] = 0
|
||||
overtime_hours: Optional[float] = 0
|
||||
|
||||
class DailyWorkCreate(DailyWorkBase):
|
||||
pass
|
||||
|
||||
class DailyWorkUpdate(BaseModel):
|
||||
worker_count: Optional[int] = Field(None, gt=0)
|
||||
overtime_workers: Optional[int] = None
|
||||
overtime_hours: Optional[float] = None
|
||||
|
||||
class DailyWork(DailyWorkBase):
|
||||
id: int
|
||||
regular_hours: float
|
||||
overtime_total: float
|
||||
total_hours: float
|
||||
created_by_id: int
|
||||
created_by: User
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class ProjectDailyWorkBase(BaseModel):
|
||||
date: datetime
|
||||
project_id: int
|
||||
hours: float
|
||||
|
||||
class ProjectDailyWorkCreate(ProjectDailyWorkBase):
|
||||
pass
|
||||
|
||||
class ProjectDailyWork(ProjectDailyWorkBase):
|
||||
id: int
|
||||
created_by_id: int
|
||||
created_at: datetime
|
||||
project: Project
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Report schemas
|
||||
class ReportRequest(BaseModel):
|
||||
start_date: datetime
|
||||
|
||||
@@ -35,6 +35,7 @@ async def create_issue(
|
||||
db_issue = Issue(
|
||||
category=issue.category,
|
||||
description=issue.description,
|
||||
location_info=issue.location_info,
|
||||
photo_path=photo_paths.get('photo_path'),
|
||||
photo_path2=photo_paths.get('photo_path2'),
|
||||
photo_path3=photo_paths.get('photo_path3'),
|
||||
|
||||
@@ -564,6 +564,8 @@ function createIssueCard(issue, isCompleted) {
|
||||
|
||||
<p class="text-gray-800 mb-2 line-clamp-2">${issue.description}</p>
|
||||
|
||||
${issue.location_info ? `<div class="flex items-center text-sm text-gray-600 mb-2"><i class="fas fa-map-marker-alt mr-1 text-red-500"></i>${issue.location_info}</div>` : ''}
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4 text-sm text-gray-500">
|
||||
<span><i class="fas fa-user mr-1"></i>${issue.reporter?.full_name || issue.reporter?.username || '알 수 없음'}</span>
|
||||
|
||||
@@ -292,6 +292,10 @@ function displayIssues() {
|
||||
<i class="fas fa-tag mr-2 text-green-500"></i>
|
||||
<span>${getCategoryText(issue.category || issue.final_category)}</span>
|
||||
</div>
|
||||
${issue.location_info ? `<div class="flex items-center text-gray-600">
|
||||
<i class="fas fa-map-marker-alt mr-2 text-red-500"></i>
|
||||
<span>${issue.location_info}</span>
|
||||
</div>` : ''}
|
||||
<div class="flex items-center text-gray-600">
|
||||
<i class="fas fa-camera mr-2 text-purple-500"></i>
|
||||
<span class="${photoCount > 0 ? 'text-purple-600 font-medium' : ''}">${photoInfo}</span>
|
||||
|
||||
@@ -419,6 +419,13 @@ function createInProgressRow(issue, project) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${issue.location_info ? `<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">발생 위치</label>
|
||||
<div class="p-3 bg-gray-50 rounded-lg text-gray-800">
|
||||
<i class="fas fa-map-marker-alt text-red-500 mr-1"></i>${issue.location_info}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">업로드 사진</label>
|
||||
${(() => {
|
||||
|
||||
Reference in New Issue
Block a user