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:
Hyungi Ahn
2026-02-23 14:12:57 +09:00
parent bf4000c4ae
commit 3cc29c03a8
37 changed files with 1751 additions and 233 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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'),

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
${(() => {