# 개발 일지 - 교시 마감 시스템 구현
**날짜**: 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
## ✨ 결론
교시 마감 시스템이 성공적으로 구현되었습니다. 모든 요구사항이 충족되었으며, 기존 시스템과의 호환성을 유지하면서 새로운 기능이 추가되었습니다. 사용자는 이제 교시별로 마감 처리를 할 수 있으며, 마감된 교시에는 더 이상 대기자가 등록되지 않습니다.