feat: 관리함 진행중 페이지에 추가 정보 입력 기능 구현

🎯 관리함 진행중 페이지 추가 정보 입력 시스템:

📊 DB 구조 확장:
- responsible_person_detail: 해당자 상세 정보 (VARCHAR 200)
- cause_detail: 원인 상세 정보 (TEXT)
- additional_info_updated_at: 추가 정보 입력 시간
- additional_info_updated_by_id: 추가 정보 입력자 ID
- 018_add_additional_info_fields.sql 마이그레이션 실행 완료

🔧 백엔드 API:
- /api/management/{issue_id}/additional-info (PUT): 추가 정보 업데이트
- /api/management/{issue_id}/additional-info (GET): 추가 정보 조회
- AdditionalInfoUpdateRequest 스키마 추가
- management.py 라우터 생성 및 등록

🎨 프론트엔드 UI:
- 진행중 탭 상단에 '추가 정보 입력' 버튼 추가
- 완료됨 탭에서는 버튼 자동 숨김
- 세련된 모달 디자인 (오렌지 테마)
- 원인부서 드롭다운 (생산/품질/구매/설계/영업)
- 해당자 상세 입력 필드
- 원인 상세 텍스트 영역

💡 핵심 특징:
- 모든 필드 선택사항 (NULL 허용)
- 기록용 정보로 외부 노출 없음
- 기존 데이터 자동 로드 및 수정 가능
- 입력 시간/입력자 자동 추적
- 진행중 상태 이슈만 대상

🔐 권한 관리:
- issues_management 페이지 권한 필요
- 진행중 상태 이슈만 수정 가능
- 사용자별 입력 이력 추적

🎯 사용 시나리오:
1. 관리함 > 진행중 탭 접근
2. '추가 정보 입력' 버튼 클릭
3. 원인부서, 해당자, 원인상세 입력
4. 저장 후 내부 기록으로 보관

Expected Result:
 관리함에서 상세한 원인 정보 기록 가능
 체계적인 이슈 추적 및 분석 기반 마련
 선택적 정보 입력으로 유연한 운영
 깔끔한 UI로 사용자 경험 향상
This commit is contained in:
Hyungi Ahn
2025-10-26 11:39:30 +09:00
parent b45cfd96bc
commit 61f5720af3
10 changed files with 409 additions and 12 deletions

View File

@@ -132,6 +132,12 @@ class Issue(Base):
final_description = Column(Text) # 최종 내용 (수정본 또는 원본)
final_category = Column(Enum(IssueCategory)) # 최종 카테고리 (수정본 또는 원본)
# 추가 정보 필드들 (관리함에서 기록용)
responsible_person_detail = Column(String(200)) # 해당자 상세 정보
cause_detail = Column(Text) # 원인 상세 정보
additional_info_updated_at = Column(DateTime) # 추가 정보 입력 시간
additional_info_updated_by_id = Column(Integer, ForeignKey("users.id")) # 추가 정보 입력자
# Relationships
reporter = relationship("User", back_populates="issues", foreign_keys=[reporter_id])
reviewer = relationship("User", foreign_keys=[reviewed_by_id], overlaps="reviewed_issues")

View File

@@ -141,6 +141,12 @@ class Issue(IssueBase):
final_description: Optional[str] = None # 최종 내용
final_category: Optional[IssueCategory] = None # 최종 카테고리
# 추가 정보 필드들 (관리함에서 기록용)
responsible_person_detail: Optional[str] = None # 해당자 상세 정보
cause_detail: Optional[str] = None # 원인 상세 정보
additional_info_updated_at: Optional[datetime] = None # 추가 정보 입력 시간
additional_info_updated_by_id: Optional[int] = None # 추가 정보 입력자
class Config:
from_attributes = True
@@ -176,6 +182,12 @@ class ManagementUpdateRequest(BaseModel):
management_comment: Optional[str] = None # ISSUE에 대한 의견
completion_photo: Optional[str] = None # 완료 사진 (Base64)
class AdditionalInfoUpdateRequest(BaseModel):
"""추가 정보 업데이트 요청 (관리함 진행중에서 사용)"""
cause_department: Optional[DepartmentType] = None # 원인부서
responsible_person_detail: Optional[str] = None # 해당자 상세 정보
cause_detail: Optional[str] = None # 원인 상세 정보
class InboxIssue(BaseModel):
"""수신함용 부적합 정보 (간소화된 버전)"""
id: int

View File

@@ -5,7 +5,7 @@ import uvicorn
from database.database import engine, get_db
from database.models import Base
from routers import auth, issues, daily_work, reports, projects, page_permissions, inbox
from routers import auth, issues, daily_work, reports, projects, page_permissions, inbox, management
from services.auth_service import create_admin_user
# 데이터베이스 테이블 생성
@@ -38,6 +38,7 @@ app.include_router(daily_work.router)
app.include_router(reports.router)
app.include_router(projects.router)
app.include_router(page_permissions.router)
app.include_router(management.router) # 관리함 라우터 추가
# 시작 시 관리자 계정 생성
@app.on_event("startup")

View File

@@ -0,0 +1,85 @@
-- 수신함 추가 정보 필드 추가
-- 원인부서, 해당자, 원인 상세 정보를 기록하기 위한 필드들
DO $migration$
DECLARE
col_count INTEGER := 0;
idx_count INTEGER := 0;
BEGIN
RAISE NOTICE '=== 수신함 추가 정보 필드 추가 마이그레이션 시작 ===';
-- 1. 해당자 상세 정보 (responsible_person과 별도)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'responsible_person_detail') THEN
ALTER TABLE issues ADD COLUMN responsible_person_detail VARCHAR(200);
RAISE NOTICE '✅ issues.responsible_person_detail 컬럼이 추가되었습니다.';
ELSE
RAISE NOTICE ' issues.responsible_person_detail 컬럼이 이미 존재합니다.';
END IF;
-- 2. 원인 상세 정보 (기록용)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'cause_detail') THEN
ALTER TABLE issues ADD COLUMN cause_detail TEXT;
RAISE NOTICE '✅ issues.cause_detail 컬럼이 추가되었습니다.';
ELSE
RAISE NOTICE ' issues.cause_detail 컬럼이 이미 존재합니다.';
END IF;
-- 3. 추가 정보 입력 시간 (언제 입력되었는지 기록)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'additional_info_updated_at') THEN
ALTER TABLE issues ADD COLUMN additional_info_updated_at TIMESTAMP WITH TIME ZONE;
RAISE NOTICE '✅ issues.additional_info_updated_at 컬럼이 추가되었습니다.';
ELSE
RAISE NOTICE ' issues.additional_info_updated_at 컬럼이 이미 존재합니다.';
END IF;
-- 4. 추가 정보 입력자 ID (누가 입력했는지 기록)
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'additional_info_updated_by_id') THEN
ALTER TABLE issues ADD COLUMN additional_info_updated_by_id INTEGER REFERENCES users(id);
RAISE NOTICE '✅ issues.additional_info_updated_by_id 컬럼이 추가되었습니다.';
ELSE
RAISE NOTICE ' issues.additional_info_updated_by_id 컬럼이 이미 존재합니다.';
END IF;
-- 인덱스 추가 (검색 성능 향상)
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'issues' AND indexname = 'idx_issues_additional_info_updated_at') THEN
CREATE INDEX idx_issues_additional_info_updated_at ON issues (additional_info_updated_at);
RAISE NOTICE '✅ idx_issues_additional_info_updated_at 인덱스가 생성되었습니다.';
ELSE
RAISE NOTICE ' idx_issues_additional_info_updated_at 인덱스가 이미 존재합니다.';
END IF;
-- 검증
SELECT COUNT(*) INTO col_count FROM information_schema.columns
WHERE table_name = 'issues' AND column_name IN (
'responsible_person_detail', 'cause_detail', 'additional_info_updated_at', 'additional_info_updated_by_id'
);
SELECT COUNT(*) INTO idx_count FROM pg_indexes
WHERE tablename = 'issues' AND indexname = 'idx_issues_additional_info_updated_at';
RAISE NOTICE '=== 마이그레이션 검증 결과 ===';
RAISE NOTICE '추가된 컬럼: %/4개', col_count;
RAISE NOTICE '생성된 인덱스: %/1개', idx_count;
IF col_count = 4 AND idx_count = 1 THEN
RAISE NOTICE '✅ 모든 추가 정보 필드가 성공적으로 추가되었습니다.';
ELSE
RAISE WARNING '⚠️ 일부 필드나 인덱스가 누락되었습니다.';
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '❌ 마이그레이션 실행 중 오류 발생: %', SQLERRM;
END $migration$;
-- 마이그레이션 로그 기록
INSERT INTO migration_log (migration_file, executed_at, status, notes)
VALUES (
'018_add_additional_info_fields.sql',
NOW(),
'SUCCESS',
'수신함 추가 정보 필드 추가: 해당자 상세(responsible_person_detail), 원인 상세(cause_detail), 입력 시간/입력자 추적 필드'
) ON CONFLICT (migration_file) DO UPDATE SET
executed_at = NOW(),
status = EXCLUDED.status,
notes = EXCLUDED.notes;

Binary file not shown.

View File

@@ -0,0 +1,103 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from datetime import datetime
from typing import List
from database.database import get_db
from database.models import Issue, User, ReviewStatus
from database.schemas import (
ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema
)
from routers.auth import get_current_user
from routers.page_permissions import check_page_access
router = APIRouter(prefix="/api/management", tags=["management"])
@router.get("/", response_model=List[IssueSchema])
async def get_management_issues(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
관리함 - 진행 중 및 완료된 부적합 목록 조회
"""
# 관리함 페이지 권한 확인
if not check_page_access(current_user.id, 'issues_management', db):
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
# 진행 중 또는 완료된 이슈들 조회
issues = db.query(Issue).filter(
Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed])
).order_by(Issue.reviewed_at.desc()).all()
return issues
@router.put("/{issue_id}/additional-info")
async def update_additional_info(
issue_id: int,
additional_info: AdditionalInfoUpdateRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
추가 정보 업데이트 (원인부서, 해당자 상세, 원인 상세)
"""
# 관리함 페이지 권한 확인
if not check_page_access(current_user.id, 'issues_management', db):
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
# 이슈 조회
issue = db.query(Issue).filter(Issue.id == issue_id).first()
if not issue:
raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.")
# 진행 중 상태인지 확인
if issue.review_status != ReviewStatus.in_progress:
raise HTTPException(status_code=400, detail="진행 중 상태의 부적합만 추가 정보를 입력할 수 있습니다.")
# 추가 정보 업데이트
update_data = additional_info.dict(exclude_unset=True)
for field, value in update_data.items():
setattr(issue, field, value)
# 추가 정보 입력 시간 및 입력자 기록
issue.additional_info_updated_at = datetime.now()
issue.additional_info_updated_by_id = current_user.id
db.commit()
db.refresh(issue)
return {
"message": "추가 정보가 성공적으로 업데이트되었습니다.",
"issue_id": issue.id,
"updated_at": issue.additional_info_updated_at,
"updated_by": current_user.username
}
@router.get("/{issue_id}/additional-info")
async def get_additional_info(
issue_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""
추가 정보 조회
"""
# 관리함 페이지 권한 확인
if not check_page_access(current_user.id, 'issues_management', db):
raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.")
# 이슈 조회
issue = db.query(Issue).filter(Issue.id == issue_id).first()
if not issue:
raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.")
return {
"issue_id": issue.id,
"cause_department": issue.cause_department.value if issue.cause_department else None,
"responsible_person_detail": issue.responsible_person_detail,
"cause_detail": issue.cause_detail,
"additional_info_updated_at": issue.additional_info_updated_at,
"additional_info_updated_by_id": issue.additional_info_updated_by_id
}