# 개발 일지 - 교시 마감 시스템 구현 **날짜**: 2025-11-28 **개발자**: Claude (AI Assistant) **작업 시간**: 약 2시간 ## 📋 작업 개요 일괄 출석 기능을 교시 마감 시스템으로 전환하여, 마감된 교시에는 더 이상 대기자를 등록할 수 없도록 구현했습니다. ## 🎯 요구사항 사용자가 요청한 기능: 1. **교시별 마감 기능**: 일괄 출석 대신 교시 마감으로 동작 변경 2. **대기자 등록 차단**: 마감된 교시에는 더 이상 대기자 등록 불가 3. **대기자 리스트 유지**: 마감 후에도 대기자 리스트는 그대로 표시 (비활성화 상태) 4. **시각적 표시**: - 탭 색상을 빨강으로 변경 - 인원수 옆에 "마감" 텍스트 표시 5. **왼쪽 화살표 비활성화**: 마감된 교시 오른쪽의 대기자는 왼쪽 화살표 비활성화 ## 🔨 구현 내용 ### 1. 데이터베이스 변경 #### 새로운 테이블 추가: `class_closure` ```python class ClassClosure(Base): """교시 마감 정보""" __tablename__ = "class_closure" id = Column(Integer, primary_key=True, index=True) business_date = Column(Date, nullable=False, index=True) class_id = Column(Integer, ForeignKey("class_info.id"), nullable=False) closed_at = Column(DateTime, default=datetime.now) created_at = Column(DateTime, default=datetime.now) ``` **파일**: `models.py` (98-106번 줄) #### 마이그레이션 스크립트 - **파일**: `migrate_add_class_closure.py` - **실행 결과**: ✅ 마이그레이션 성공 ### 2. 백엔드 API 수정 #### 2.1 교시 마감 엔드포인트 (`/api/board/batch-attendance`) **파일**: `routers/waiting_board.py` (394-450번 줄) **변경 전**: 모든 대기자를 attended 상태로 변경 **변경 후**: ClassClosure 레코드 생성, 대기자 상태는 waiting 유지 ```python @router.post("/batch-attendance") async def batch_attendance(batch: BatchAttendance, db: Session = Depends(get_db)): """교시 마감 처리""" # 이미 마감된 교시인지 확인 existing_closure = db.query(ClassClosure).filter(...).first() if existing_closure: raise HTTPException(status_code=400, detail="이미 마감된 교시입니다.") # 교시 마감 레코드 생성 closure = ClassClosure( business_date=today, class_id=batch.class_id, closed_at=datetime.now() ) db.add(closure) db.commit() # SSE 브로드캐스트 await sse_manager.broadcast( store_id="default", event_type="class_closed", # 이벤트 타입 변경 data={...} ) ``` #### 2.2 다음 마감 대상 조회 (`/api/board/next-batch-class`) **파일**: `routers/waiting_board.py` (452-492번 줄) 마감된 교시를 제외한 첫 번째 대기자 있는 교시를 반환하도록 수정 ```python # 이미 마감된 교시 ID 목록 closed_class_ids = db.query(ClassClosure.class_id).filter( ClassClosure.business_date == today ).all() closed_class_ids = set(c.class_id for c in closed_class_ids) for cls in classes: if cls.id in closed_class_ids: continue # 마감된 교시는 건너뜀 # ... ``` #### 2.3 마감된 교시 목록 조회 (`/api/board/closed-classes`) **파일**: `routers/waiting_board.py` (494-507번 줄) 새로운 엔드포인트 추가 - 오늘 마감된 교시 ID 목록 반환 ```python @router.get("/closed-classes") async def get_closed_classes(db: Session = Depends(get_db)): """오늘 마감된 교시 목록 조회""" today = date.today() closed_classes = db.query(ClassClosure).filter( ClassClosure.business_date == today ).all() return {"closed_class_ids": [c.class_id for c in closed_classes]} ``` #### 2.4 대기자 등록 차단 **파일**: `routers/waiting.py` (27-95번 줄) `get_available_class()` 함수 수정 - 마감된 교시를 배치 대상에서 제외 ```python def get_available_class(db: Session, business_date: date): """배치 가능한 클래스 찾기 - 마감된 교시 제외""" # 마감된 교시 ID 목록 조회 closed_class_ids = db.query(ClassClosure.class_id).filter( ClassClosure.business_date == business_date ).all() closed_class_ids = set(c.class_id for c in closed_class_ids) # 마감되지 않은 교시만 필터링 available_classes = [c for c in classes if c.id not in closed_class_ids] if not available_classes: raise HTTPException( status_code=400, detail="모든 교시가 마감되었습니다. 대기 접수를 받을 수 없습니다." ) # ... ``` ### 3. 프론트엔드 UI 수정 #### 3.1 CSS 스타일 변경 **파일**: `templates/manage.html` (74-136번 줄) **변경 전**: 완료된 교시는 회색 (`#95a5a6`) **변경 후**: 마감된 교시는 빨강 (`#e74c3c`) ```css /* 마감된 교시 탭 스타일 - 빨강 */ .class-tab.closed { background: #e74c3c; color: #fff; border-color: #c0392b; opacity: 0.9; } /* 마감된 교시의 리스트 스타일 - 비활성화 상태로 표시 */ .waiting-table.closed { background: #f5f5f5; opacity: 0.8; } .waiting-table.closed::before { content: '🔒 마감된 교시입니다'; display: block; padding: 15px; background: #e74c3c; color: #fff; text-align: center; font-weight: 600; font-size: 14px; } ``` #### 3.2 JavaScript 로직 수정 **파일**: `templates/manage.html` **주요 변경 사항**: 1. **변수명 변경**: `completedClasses` → `closedClasses` 2. **초기 로드 시 마감된 교시 조회** (774-816번 줄): ```javascript async function loadClasses() { // 마감된 교시 목록 조회 const closedResponse = await fetch('/api/board/closed-classes'); const closedData = await closedResponse.json(); closedClasses = new Set(closedData.closed_class_ids); // ... } ``` 3. **탭 렌더링 수정** (813-833번 줄): ```javascript function renderClassTabs() { classes.forEach(cls => { const isClosed = closedClasses.has(cls.id); tab.className = isClosed ? 'class-tab closed' : 'class-tab'; // 마감된 교시는 인원수와 "마감" 배지 표시 tab.innerHTML = ` ${cls.class_name} ${cls.current_count || 0}명 ${isClosed ? '마감' : ''} `; }); } ``` 4. **SSE 이벤트 핸들러 수정** (506-515번 줄): ```javascript case 'class_closed': // 이벤트 타입 변경 closedClasses.add(message.data.class_id); updateClassCounts(); loadBatchInfo(); if (currentClassId === message.data.class_id) { updateWaitingOrder(); } break; ``` 5. **왼쪽 화살표 비활성화** (967-1019번 줄): ```javascript // 왼쪽에 마감된 교시가 있는지 확인 let hasClosedClassOnLeft = false; if (hasPrevClass) { const prevClass = classes[classIndex - 1]; hasClosedClassOnLeft = closedClasses.has(prevClass.id); } // 왼쪽 화살표 비활성화 조건 const leftArrowDisabled = !hasPrevClass || hasClosedClassOnLeft; div.innerHTML = ` ... ... `; ``` 6. **마감 확인 다이얼로그** (1292-1319번 줄): ```javascript async function batchAttendance() { if (!confirm(`${batchClass.class_name}을(를) 마감하시겠습니까?\n마감 후 해당 교시에는 더 이상 대기자를 등록할 수 없습니다.`)) return; // ... } ``` 7. **대기자 리스트 표시 로직** (688-722번 줄): ```javascript async function updateWaitingOrder() { // 마감된 교시도 대기 목록 표시 (비활성화 상태로) const isClosed = closedClasses.has(currentClassId); const status = 'waiting'; // 마감된 교시도 waiting 상태 유지 const response = await fetch(`/api/waiting/list?status=${status}&class_id=${currentClassId}`); // ... } ``` ## ✅ 테스트 항목 ### 기능 테스트 - [x] 교시 마감 버튼 동작 - [x] 마감된 교시 탭 빨강색 표시 - [x] "마감" 배지 표시 - [x] 마감된 교시의 대기자 리스트 비활성화 표시 - [x] 마감된 교시에 대기자 등록 차단 - [x] 왼쪽 화살표 비활성화 (마감된 교시 우측) - [x] 이미 마감된 교시 중복 마감 방지 - [x] SSE 실시간 업데이트 - [x] 페이지 새로고침 시 마감 상태 유지 ### UI/UX 테스트 - [x] 마감 확인 다이얼로그 표시 - [x] 대기자 리스트 그대로 유지 (비활성화 상태) - [x] 드래그 앤 드롭 비활성화 (마감된 교시) - [x] 모든 버튼 비활성화 (마감된 교시) ## 📊 변경 파일 목록 ### 신규 파일 1. `migrate_add_class_closure.py` - 마이그레이션 스크립트 ### 수정 파일 1. `models.py` - ClassClosure 모델 추가 2. `routers/waiting_board.py` - 교시 마감 API 구현 3. `routers/waiting.py` - 대기자 등록 차단 로직 4. `templates/manage.html` - UI 및 JavaScript 로직 전면 수정 ### 영향받는 파일 (수정 없음) - `schemas.py` - BatchAttendance 스키마 재사용 - `sse_manager.py` - SSE 이벤트 타입만 변경 - `database.py` - 변경 없음 ## 🐛 발견된 문제 및 해결 ### 문제 1: 모듈 import 오류 **증상**: `ModuleNotFoundError: No module named 'fastapi'` **원인**: 가상환경 활성화 없이 서버 실행 **해결**: `source venv/bin/activate` 후 실행 ### 문제 2: 없음 초기 설계가 명확했고, 요구사항이 구체적이어서 추가 문제 없이 구현 완료 ## 📝 코드 품질 ### 장점 - ✅ 기존 코드 구조 유지하며 최소한의 변경 - ✅ 명확한 변수명과 함수명 사용 - ✅ 일관된 코딩 스타일 유지 - ✅ 충분한 주석과 docstring 작성 - ✅ 에러 핸들링 적절히 구현 ### 개선 가능한 부분 - ⚠️ 마감 취소 기능 미구현 (향후 필요시 추가) - ⚠️ 마감 이력 조회 기능 미구현 - ⚠️ 마감 시간 설정 기능 미구현 (현재는 즉시 마감만 가능) ## 🎓 배운 점 1. **상태 관리의 중요성**: - 프론트엔드에서 `closedClasses` Set을 사용하여 효율적으로 마감 상태 추적 - 초기 로드 시 서버에서 마감 상태 동기화 2. **점진적 기능 전환**: - 기존 일괄 출석 기능을 완전히 대체하지 않고, 엔드포인트는 유지하며 동작만 변경 - UI 텍스트만 변경하여 사용자 혼란 최소화 3. **실시간 동기화**: - SSE 이벤트 타입만 변경하여 실시간 업데이트 유지 - 여러 화면 간 상태 동기화 보장 ## 🚀 향후 개선 방향 1. **마감 취소 기능**: 실수로 마감한 경우 취소 가능하도록 2. **마감 이력**: 언제 누가 마감했는지 이력 추적 3. **자동 마감**: 특정 시간에 자동으로 교시 마감 4. **마감 알림**: 마감 전 관리자에게 알림 발송 5. **마감 통계**: 일별/월별 마감 현황 통계 ## 📌 참고사항 - **데이터베이스**: SQLite 사용 - **실시간 통신**: Server-Sent Events (SSE) 사용 - **프론트엔드**: Vanilla JavaScript (프레임워크 없음) - **백엔드**: FastAPI + SQLAlchemy ## ✨ 결론 교시 마감 시스템이 성공적으로 구현되었습니다. 모든 요구사항이 충족되었으며, 기존 시스템과의 호환성을 유지하면서 새로운 기능이 추가되었습니다. 사용자는 이제 교시별로 마감 처리를 할 수 있으며, 마감된 교시에는 더 이상 대기자가 등록되지 않습니다.