Files
waiting-system/docs/개발일지_교시마감시스템.md
Jun-dev f699a29a85 Add waiting system application files
- Add main application files (main.py, models.py, schemas.py, etc.)
- Add routers for all features (waiting, attendance, members, etc.)
- Add HTML templates for admin and user interfaces
- Add migration scripts and utility files
- Add Docker configuration
- Add documentation files
- Add .gitignore to exclude database and cache files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 00:29:39 +09:00

12 KiB

개발 일지 - 교시 마감 시스템 구현

날짜: 2025-11-28 개발자: Claude (AI Assistant) 작업 시간: 약 2시간

📋 작업 개요

일괄 출석 기능을 교시 마감 시스템으로 전환하여, 마감된 교시에는 더 이상 대기자를 등록할 수 없도록 구현했습니다.

🎯 요구사항

사용자가 요청한 기능:

  1. 교시별 마감 기능: 일괄 출석 대신 교시 마감으로 동작 변경
  2. 대기자 등록 차단: 마감된 교시에는 더 이상 대기자 등록 불가
  3. 대기자 리스트 유지: 마감 후에도 대기자 리스트는 그대로 표시 (비활성화 상태)
  4. 시각적 표시:
    • 탭 색상을 빨강으로 변경
    • 인원수 옆에 "마감" 텍스트 표시
  5. 왼쪽 화살표 비활성화: 마감된 교시 오른쪽의 대기자는 왼쪽 화살표 비활성화

🔨 구현 내용

1. 데이터베이스 변경

새로운 테이블 추가: class_closure

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 유지

@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번 줄)

마감된 교시를 제외한 첫 번째 대기자 있는 교시를 반환하도록 수정

# 이미 마감된 교시 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 목록 반환

@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() 함수 수정 - 마감된 교시를 배치 대상에서 제외

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)

/* 마감된 교시 탭 스타일 - 빨강 */
.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. 변수명 변경: completedClassesclosedClasses
  2. 초기 로드 시 마감된 교시 조회 (774-816번 줄):
async function loadClasses() {
    // 마감된 교시 목록 조회
    const closedResponse = await fetch('/api/board/closed-classes');
    const closedData = await closedResponse.json();
    closedClasses = new Set(closedData.closed_class_ids);
    // ...
}
  1. 탭 렌더링 수정 (813-833번 줄):
function renderClassTabs() {
    classes.forEach(cls => {
        const isClosed = closedClasses.has(cls.id);
        tab.className = isClosed ? 'class-tab closed' : 'class-tab';

        // 마감된 교시는 인원수와 "마감" 배지 표시
        tab.innerHTML = `
            ${cls.class_name}
            <span class="count">${cls.current_count || 0}명</span>
            ${isClosed ? '<span class="badge-closed">마감</span>' : ''}
        `;
    });
}
  1. SSE 이벤트 핸들러 수정 (506-515번 줄):
case 'class_closed':  // 이벤트 타입 변경
    closedClasses.add(message.data.class_id);
    updateClassCounts();
    loadBatchInfo();
    if (currentClassId === message.data.class_id) {
        updateWaitingOrder();
    }
    break;
  1. 왼쪽 화살표 비활성화 (967-1019번 줄):
// 왼쪽에 마감된 교시가 있는지 확인
let hasClosedClassOnLeft = false;
if (hasPrevClass) {
    const prevClass = classes[classIndex - 1];
    hasClosedClassOnLeft = closedClasses.has(prevClass.id);
}

// 왼쪽 화살표 비활성화 조건
const leftArrowDisabled = !hasPrevClass || hasClosedClassOnLeft;

div.innerHTML = `
    ...
    <button class="btn-icon btn-left"
            ${leftArrowDisabled ? 'disabled' : ''}
            onclick="moveToClass(${item.id}, ${classIndex - 1})"
            title="이전 교시로 이동">
    </button>
    ...
`;
  1. 마감 확인 다이얼로그 (1292-1319번 줄):
async function batchAttendance() {
    if (!confirm(`${batchClass.class_name}을(를) 마감하시겠습니까?\n마감 후 해당 교시에는 더 이상 대기자를 등록할 수 없습니다.`)) return;
    // ...
}
  1. 대기자 리스트 표시 로직 (688-722번 줄):
async function updateWaitingOrder() {
    // 마감된 교시도 대기 목록 표시 (비활성화 상태로)
    const isClosed = closedClasses.has(currentClassId);
    const status = 'waiting'; // 마감된 교시도 waiting 상태 유지
    const response = await fetch(`/api/waiting/list?status=${status}&class_id=${currentClassId}`);
    // ...
}

테스트 항목

기능 테스트

  • 교시 마감 버튼 동작
  • 마감된 교시 탭 빨강색 표시
  • "마감" 배지 표시
  • 마감된 교시의 대기자 리스트 비활성화 표시
  • 마감된 교시에 대기자 등록 차단
  • 왼쪽 화살표 비활성화 (마감된 교시 우측)
  • 이미 마감된 교시 중복 마감 방지
  • SSE 실시간 업데이트
  • 페이지 새로고침 시 마감 상태 유지

UI/UX 테스트

  • 마감 확인 다이얼로그 표시
  • 대기자 리스트 그대로 유지 (비활성화 상태)
  • 드래그 앤 드롭 비활성화 (마감된 교시)
  • 모든 버튼 비활성화 (마감된 교시)

📊 변경 파일 목록

신규 파일

  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

결론

교시 마감 시스템이 성공적으로 구현되었습니다. 모든 요구사항이 충족되었으며, 기존 시스템과의 호환성을 유지하면서 새로운 기능이 추가되었습니다. 사용자는 이제 교시별로 마감 처리를 할 수 있으며, 마감된 교시에는 더 이상 대기자가 등록되지 않습니다.