코드를 쓸 때 따라가는 문서

기능 연결 방식

화면과 서버가 무엇을 주고받는지. 기능 번호와 이어져 있습니다.

34,288자 · 시스템이 만든 그대로입니다

사람이 손댄 곳 3군데
  • · 3단계 ‘추론 확인’에서 두 곳을 고쳤습니다. 인터뷰에 서류 만료 알림을 첫 버전에 넣겠다고 적었는데, 정작 서류 관리 자체는 후속 버전이었습니다. 그대로 두면 없는 데이터를 읽어 알림을 보내는 문서가 되므로, 첫 버전 알림을 시간표 변경 하나로 줄이고 필수 화면 목록에 알림함을 넣었습니다.
  • · 4단계에서 확인할 사용 흐름 세 가지를 다시 묶었습니다. 처음 제안된 묶음은 화면 열한 개를 순서대로 셋으로 나눈 것이라 운영자 화면과 강사 화면이 한 흐름에 섞여 있었습니다. 흐름은 “이 순서로 막힘없이 이어지는가”를 보는 자리이므로 역할별로 다시 나누고 이름도 업체가 실제로 하는 일로 바꿨습니다.
  • · 문서 본문은 손대지 않았습니다. 위 두 가지는 모두 인터뷰 답변과 화면 목록을 고친 것이고, 그 뒤 문서는 고쳐진 답변으로 다시 만들어졌습니다.

방과후 운영 한눈에 API 연동 문서

대상: Next.js 기반 웹 서비스
인증 방식: 로그인 후 발급된 세션 또는 접근 토큰을 요청에 포함한다.
공통 원칙:

  • 모든 운영자 요청은 로그인한 사용자가 해당 위탁업체의 운영자 권한을 가졌는지 확인한다.
  • 모든 강사 요청은 로그인한 계정이 해당 위탁업체의 해당 강사 정보와 연결되었는지 확인한다.
  • 위탁업체 식별값은 로그인 정보에서 확인하며, 화면이 다른 위탁업체의 식별값을 임의로 보내 다른 업체 데이터를 조회·수정할 수 없다. (NFR-001)
  • 날짜는 YYYY-MM-DD, 시각은 HH:mm 형식으로 처리한다. 시간대는 대한민국 표준시를 사용한다. (NFR-003)
  • 원본 서류 파일, 서류 상태, 대체강사, 정산, 민원 데이터는 첫 개발 범위에 포함하지 않는다. (NFR-005)

공통 응답 형식

성공 응답

{
  "data": {}
}

오류 응답

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "입력한 내용을 다시 확인해 주세요.",
    "fields": [
      {
        "name": "schoolName",
        "message": "학교명을 입력해 주세요."
      }
    ]
  }
}

화면에 표시할 주요 오류 코드

오류 코드사용자가 이해할 수 있는 안내
UNAUTHORIZED로그인 시간이 만료되었습니다. 다시 로그인해 주세요.
FORBIDDEN이 정보에 접근할 권한이 없습니다.
NOT_FOUND요청한 정보를 찾을 수 없습니다.
VALIDATION_ERROR빠졌거나 올바르지 않은 입력값이 있습니다. 표시된 항목을 확인해 주세요.
DUPLICATE_TIME_OVERLAP같은 강사가 서로 다른 학교의 겹치는 시간에 배정되어 저장할 수 없습니다.
SAME_SCHOOL_EXCEPTION_REASON_REQUIRED같은 학교 시간 겹침을 저장하려면 예외 사유를 입력해 주세요.
CONCURRENT_UPDATE다른 사용자가 먼저 변경했습니다. 최신 내용을 확인한 뒤 다시 저장해 주세요.
FILE_FORMAT_ERROR지정 엑셀 양식 또는 지원하는 파일 형식인지 확인해 주세요.
INVITATION_INVALID초대 링크가 유효하지 않거나 이미 사용할 수 없습니다.
PUBLISH_BLOCKED해결되지 않은 시간 중복 또는 예외 사유 누락이 있어 공개할 수 없습니다.

1. 계정 시작과 로그인

POST /api/agencies

목적: 대표가 운영자 계정과 위탁업체를 함께 생성한다. (FR-001, UIR-001)
인증: 불필요
HTTP 방식과 경로: POST /api/agencies

요청값

{
  "ownerName": "홍길동",
  "email": "owner@example.com",
  "password": "비밀번호",
  "agencyName": "방과후 운영 한눈에 교육",
  "phone": "010-1234-5678"
}
필수설명
ownerName대표 이름
email로그인에 사용할 이메일
password로그인 비밀번호
agencyName위탁업체 이름
phone아니오위탁업체 연락처

성공 응답

{
  "data": {
    "agencyId": "agency_001",
    "userId": "user_001",
    "role": "operator",
    "redirectPath": "/operator/dashboard"
  }
}

사용자 오류 상황

  • 필수 입력값이 비어 있으면 해당 입력칸 아래에 안내한다.
  • 이미 사용 중인 이메일이면 기존 계정으로 로그인하도록 안내한다.
  • 계정 생성 중 위탁업체 생성에 실패하면 계정 또는 위탁업체가 일부만 남지 않도록 전체 생성을 취소한다. (FR-001, NFR-002)

POST /api/auth/login

목적: 운영자 또는 강사가 로그인하고 역할에 맞는 첫 화면으로 이동한다. (FR-002, UIR-001, UIR-008)
인증: 불필요
HTTP 방식과 경로: POST /api/auth/login

요청값

{
  "email": "instructor@example.com",
  "password": "비밀번호"
}

성공 응답

{
  "data": {
    "userId": "user_020",
    "role": "instructor",
    "agencyId": "agency_001",
    "redirectPath": "/instructor/timetable",
    "invitationPending": false
  }
}

사용자 오류 상황

  • 이메일 또는 비밀번호가 맞지 않으면 “로그인 정보를 확인해 주세요.”라고 표시한다.
  • 계정 존재 여부를 구분해 알려주지 않는다.
  • 강사 초대 연결이 끝나지 않았다면 개인 시간표 대신 초대 연결 화면으로 이동시킨다. (FR-002)

POST /api/auth/logout

목적: 현재 로그인 상태를 종료한다. (FR-002)
인증: 필요
HTTP 방식과 경로: POST /api/auth/logout

요청값

없음

성공 응답

{
  "data": {
    "loggedOut": true
  }
}

사용자 오류 상황

  • 이미 로그인 시간이 끝난 경우에도 로그인 화면으로 이동시킨다.

GET /api/auth/me

목적: 현재 로그인한 사용자, 역할, 연결된 위탁업체 및 강사 연결 상태를 확인한다. 화면 접근 권한을 판단할 때 사용한다. (FR-002, FR-015)
인증: 필요
HTTP 방식과 경로: GET /api/auth/me

요청값

없음

성공 응답

{
  "data": {
    "userId": "user_020",
    "role": "instructor",
    "agencyId": "agency_001",
    "instructorId": "instructor_014",
    "invitationPending": false
  }
}

사용자 오류 상황

  • 로그인하지 않았거나 로그인 시간이 만료된 경우 로그인 화면으로 이동한다.
  • 운영자 화면에 강사가 접근하면 접근을 차단한다. (FR-002, NFR-004)

2. 운영자 기본 화면

GET /api/operator/dashboard

목적: 운영자가 로그인 후 학교·강사 등록, 수업 등록, 전체 시간표 확인으로 바로 이동할 수 있는 시작 정보를 제공한다. (FR-001, FR-003, FR-004, FR-013, UIR-002)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/operator/dashboard

요청값

없음

성공 응답

{
  "data": {
    "agency": {
      "agencyId": "agency_001",
      "agencyName": "방과후 운영 한눈에 교육"
    },
    "summary": {
      "schoolCount": 20,
      "instructorCount": 35,
      "classPlanCount": 120,
      "unresolvedDifferentSchoolOverlapCount": 2,
      "sameSchoolOverlapExceptionCount": 1,
      "unpublishedClassPlanCount": 18
    },
    "quickActions": [
      "학교 등록",
      "강사 등록",
      "엑셀 시간표 업로드",
      "수업 직접 등록",
      "전체 시간표 보기"
    ]
  }
}

사용자 오류 상황

  • 운영자 권한이 없으면 운영자 화면을 열 수 없다고 안내한다.
  • 아직 학교나 강사가 없으면 빈 상태와 함께 등록 버튼을 표시한다. (UIR-002)

3. 학교와 강사 관리

GET /api/schools

목적: 위탁업체가 등록한 학교 목록을 보여준다. (FR-003, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/schools

요청값

필수설명
keyword아니오학교명 검색어
page아니오페이지 번호
pageSize아니오한 화면에 표시할 수

성공 응답

{
  "data": {
    "items": [
      {
        "schoolId": "school_001",
        "schoolName": "새봄초등학교",
        "semesterCount": 2,
        "classPlanCount": 12
      }
    ],
    "page": 1,
    "pageSize": 20,
    "totalCount": 1
  }
}

사용자 오류 상황

  • 검색 결과가 없으면 현재 검색 조건과 “학교 등록” 버튼을 함께 보여준다.

POST /api/schools

목적: 운영자가 새로운 학교를 등록한다. (FR-003, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/schools

요청값

{
  "schoolName": "새봄초등학교"
}

성공 응답

{
  "data": {
    "schoolId": "school_001",
    "schoolName": "새봄초등학교"
  }
}

사용자 오류 상황

  • 학교명이 비어 있으면 저장하지 않는다.
  • 같은 이름의 학교가 있으면 기존 학교인지 확인하도록 경고한다.
  • 같은 이름이라도 운영자가 새 학교 등록을 명확히 선택한 경우에는 별도 학교로 저장할 수 있다. 학교 구분은 이름이 아닌 학교 고유 식별값을 기준으로 한다. (FR-003)

PATCH /api/schools/{schoolId}

목적: 운영자가 학교의 표시 정보를 수정한다. (FR-003, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: PATCH /api/schools/{schoolId}

요청값

{
  "schoolName": "새봄초등학교"
}

성공 응답

{
  "data": {
    "schoolId": "school_001",
    "schoolName": "새봄초등학교",
    "updatedAt": "2026-08-25T10:00:00+09:00"
  }
}

사용자 오류 상황

  • 학교명이 비어 있으면 저장하지 않는다.
  • 학교명을 바꿔도 기존 수업 계획은 같은 학교 고유 식별값에 연결된 상태로 유지한다. (FR-003)

GET /api/instructors

목적: 위탁업체가 등록한 강사 목록과 강사 초대 상태를 조회한다. (FR-004, FR-015, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/instructors

요청값

필수설명
keyword아니오강사명 검색어
invitationStatus아니오not_invited, invited, connected 중 하나
page아니오페이지 번호
pageSize아니오한 화면에 표시할 수

성공 응답

{
  "data": {
    "items": [
      {
        "instructorId": "instructor_014",
        "instructorName": "김미술",
        "invitationStatus": "connected",
        "classPlanCount": 4
      }
    ],
    "page": 1,
    "pageSize": 20,
    "totalCount": 35
  }
}

사용자 오류 상황

  • 같은 이름의 강사가 여러 명이어도 각각 별도 항목으로 보여준다.
  • 이름만으로 강사를 자동으로 하나 선택하지 않는다. (FR-004)

POST /api/instructors

목적: 운영자가 강사 정보를 등록한다. (FR-004, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/instructors

요청값

{
  "instructorName": "김미술",
  "phone": "010-9876-5432",
  "email": "instructor@example.com"
}
필수설명
instructorName강사 표시 이름
phone아니오운영자 확인용 연락처
email아니오강사 초대 시 사용할 이메일

성공 응답

{
  "data": {
    "instructorId": "instructor_014",
    "instructorName": "김미술",
    "invitationStatus": "not_invited"
  }
}

사용자 오류 상황

  • 강사 이름이 비어 있으면 저장하지 않는다.
  • 같은 이름의 강사는 저장할 수 있으나, 이후 엑셀 업로드에서 운영자가 직접 구분해야 한다. (FR-004, FR-008)

PATCH /api/instructors/{instructorId}

목적: 운영자가 강사 표시 정보를 수정한다. (FR-004, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: PATCH /api/instructors/{instructorId}

요청값

{
  "instructorName": "김미술",
  "phone": "010-9876-5432",
  "email": "instructor@example.com"
}

성공 응답

{
  "data": {
    "instructorId": "instructor_014",
    "instructorName": "김미술",
    "updatedAt": "2026-08-25T10:00:00+09:00"
  }
}

사용자 오류 상황

  • 존재하지 않는 강사를 수정하려 하면 목록을 새로고침하도록 안내한다.
  • 다른 위탁업체의 강사를 수정하려는 요청은 차단한다. (FR-004, NFR-001)

4. 학교별 학기와 교시 템플릿

GET /api/schools/{schoolId}/semesters

목적: 선택한 학교에 등록된 학기 목록을 조회한다. (FR-005, UIR-004)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/schools/{schoolId}/semesters

요청값

없음

성공 응답

{
  "data": {
    "items": [
      {
        "semesterId": "semester_2026_2",
        "semesterName": "2026년 2학기",
        "startDate": "2026-08-20",
        "endDate": "2027-02-10"
      }
    ]
  }
}

사용자 오류 상황

  • 해당 학교를 찾을 수 없으면 학교 목록으로 돌아가도록 안내한다.

POST /api/schools/{schoolId}/semesters

목적: 학교별 학기를 등록한다. (FR-005, UIR-004)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/schools/{schoolId}/semesters

요청값

{
  "semesterName": "2026년 2학기",
  "startDate": "2026-08-20",
  "endDate": "2027-02-10"
}

성공 응답

{
  "data": {
    "semesterId": "semester_2026_2",
    "semesterName": "2026년 2학기",
    "startDate": "2026-08-20",
    "endDate": "2027-02-10"
  }
}

사용자 오류 상황

  • 종료일이 시작일보다 빠르면 저장하지 않는다.
  • 학기 이름, 시작일 또는 종료일이 빠지면 해당 항목을 표시한다. (FR-005)

GET /api/semesters/{semesterId}/period-templates

목적: 학교·학기에 적용 중인 교시 템플릿과 요일별 교시표를 조회한다. (FR-005, UIR-004)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/semesters/{semesterId}/period-templates

요청값

없음

성공 응답

{
  "data": {
    "items": [
      {
        "periodTemplateId": "template_001",
        "templateName": "기본 교시표",
        "startDate": "2026-08-20",
        "endDate": "2027-02-10",
        "periodTable": [
          {
            "weekday": "TUE",
            "periodName": "3교시",
            "startTime": "10:40",
            "endTime": "11:20"
          }
        ]
      }
    ]
  }
}

사용자 오류 상황

  • 교시 템플릿이 없으면 수업 직접 등록 전에 교시표를 등록하도록 안내한다.
  • 직접 실제 시작·종료 시각을 입력하는 수업은 교시 템플릿 없이도 등록할 수 있다. (FR-005, FR-009)

POST /api/semesters/{semesterId}/period-templates

목적: 운영자가 기본 교시표 또는 단축수업용 교시 템플릿을 등록한다. (FR-005, UIR-004)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/semesters/{semesterId}/period-templates

요청값

{
  "templateName": "기본 교시표",
  "startDate": "2026-08-20",
  "endDate": "2027-02-10",
  "periodTable": [
    {
      "weekday": "TUE",
      "periodName": "3교시",
      "startTime": "10:40",
      "endTime": "11:20"
    }
  ]
}

성공 응답

{
  "data": {
    "periodTemplateId": "template_001",
    "templateName": "기본 교시표",
    "startDate": "2026-08-20",
    "endDate": "2027-02-10"
  }
}

사용자 오류 상황

  • 종료 시각이 시작 시각과 같거나 빠르면 해당 교시를 저장하지 않는다.
  • 적용 종료일이 적용 시작일보다 빠르면 저장하지 않는다.
  • 같은 학교·학기·요일·적용 기간에 둘 이상의 교시 템플릿이 적용되어 어느 교시표를 써야 하는지 판단할 수 없으면 저장하지 않는다. (FR-005, NFR-003)

PATCH /api/period-templates/{periodTemplateId}

목적: 교시 템플릿과 교시표를 수정하고, 영향받는 수업 수를 확인한다. (FR-005, UIR-004)
인증: 운영자 필요
HTTP 방식과 경로: PATCH /api/period-templates/{periodTemplateId}

요청값

{
  "templateName": "단축수업 교시표",
  "startDate": "2026-10-01",
  "endDate": "2026-10-02",
  "periodTable": [
    {
      "weekday": "TUE",
      "periodName": "3교시",
      "startTime": "10:30",
      "endTime": "11:00"
    }
  ],
  "confirmAffectedClassPlans": true
}

성공 응답

{
  "data": {
    "periodTemplateId": "template_001",
    "affectedClassPlanCount": 6,
    "requiresOverlapRecheck": true
  }
}

사용자 오류 상황

  • 기존 수업 계획에 영향을 주는 변경이면 영향받는 수업 수를 먼저 보여주고 운영자 확인 후 반영한다.
  • 공개된 수업의 실제 시간이 달라지는 경우 시간 중복 검사를 다시 통과해야 한다.
  • 충돌이 생기면 공개 상태를 유지한 채 변경을 완료하지 않고, 충돌 수정 화면으로 이동할 수 있게 한다. (FR-005, FR-010, FR-011)

5. 엑셀 시간표 등록

GET /api/class-plans/import-template

목적: 운영자가 지정 엑셀 시간표 양식을 내려받는다. (FR-006, UIR-005)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/class-plans/import-template

요청값

없음

성공 응답

엑셀 파일을 내려받는다. 파일에는 다음 열과 입력 예시를 포함한다.

열 이름설명
학교명이미 등록된 학교명
학기학교에 등록된 학기
운영 시작일수업 반복 시작일
운영 종료일수업 반복 종료일
요일월~일
과목담당 과목
강사명등록된 강사명
교시선택 입력
실제 시작 시각교시 대신 직접 입력 가능
실제 종료 시각교시 대신 직접 입력 가능

사용자 오류 상황

  • 파일 생성에 실패하면 빈 파일을 제공하지 않는다.
  • “양식을 만들지 못했습니다. 잠시 후 다시 시도해 주세요.”라고 안내한다. (FR-006)

POST /api/class-plans/imports

목적: 엑셀 파일을 실제 시간표에 저장하지 않고 임시로 읽어 미리보기 데이터를 만든다. (FR-007, UIR-006)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans/imports
요청 형식: multipart/form-data

요청값

필수설명
file지정 양식으로 작성한 엑셀 파일

성공 응답

{
  "data": {
    "importId": "import_001",
    "status": "preview_ready",
    "totalRowCount": 120,
    "normalRowCount": 110,
    "warningRowCount": 5,
    "errorRowCount": 5,
    "rows": [
      {
        "rowId": "row_001",
        "rowNumber": 2,
        "status": "error",
        "values": {
          "schoolName": "새봄초등학교",
          "semesterName": "2026년 2학기",
          "weekday": "TUE",
          "periodName": "3교시",
          "startTime": "10:40",
          "endTime": "11:20",
          "subject": "미술",
          "instructorName": "김미술"
        },
        "errors": [
          {
            "field": "instructorName",
            "message": "같은 이름의 강사가 2명입니다. 올바른 강사를 선택해 주세요."
          }
        ],
        "warnings": []
      }
    ]
  }
}

사용자 오류 상황

  • 지원하지 않는 파일 형식이거나 필수 열을 읽을 수 없으면 업로드를 중단한다.
  • 일부 행을 읽지 못하면 행 번호와 원인을 표에 표시한다.
  • 파일 전체를 읽을 수 없으면 기존 수업 계획에는 어떤 데이터도 저장하지 않는다.
  • 업로드한 파일은 검증과 확정에 필요한 기간에만 보관하고, 확정하지 않은 임시 데이터는 삭제 정책을 적용한다. 세부 보관 기간은 확인 필요다. (FR-007, NFR-006)

PATCH /api/class-plans/imports/{importId}/rows/{rowId}

목적: 운영자가 엑셀 미리보기에서 오류 또는 경고가 있는 행을 직접 수정한다. (FR-008, UIR-006)
인증: 운영자 필요
HTTP 방식과 경로: PATCH /api/class-plans/imports/{importId}/rows/{rowId}

요청값

{
  "schoolId": "school_001",
  "semesterId": "semester_2026_2",
  "instructorId": "instructor_014",
  "operatingStartDate": "2026-08-20",
  "operatingEndDate": "2027-02-10",
  "weekday": "TUE",
  "periodName": "3교시",
  "startTime": "10:40",
  "endTime": "11:20",
  "subject": "미술",
  "timeSource": "period_template"
}
필수설명
schoolId화면에서 선택한 학교 고유 식별값
semesterId선택한 학기 고유 식별값
instructorId동명이인 구분 후 선택한 강사 고유 식별값
operatingStartDate운영 기간 시작일
operatingEndDate운영 기간 종료일
weekday반복 요일
periodName조건부교시로 등록할 때 사용
startTime조건부실제 시각 직접 입력 또는 교시 변환 결과
endTime조건부실제 시각 직접 입력 또는 교시 변환 결과
subject과목
timeSourceperiod_template 또는 manual

성공 응답

{
  "data": {
    "rowId": "row_001",
    "status": "normal",
    "errors": [],
    "warnings": []
  }
}

사용자 오류 상황

  • 등록되지 않은 학교명은 오류로 표시하고 등록된 학교를 선택하도록 한다.
  • 동명이인 강사는 자동 연결하지 않고 운영자가 강사를 선택할 때까지 오류로 유지한다.
  • 교시와 실제 시작·종료 시각이 모두 없으면 저장 차단 오류로 처리한다.
  • 종료 시각이 시작 시각보다 늦지 않으면 저장할 수 없다.
  • 교시 템플릿 시간과 직접 입력한 시간이 다르면 어떤 시간을 사용할지 확인하는 경고를 표시한다. (FR-008)

POST /api/class-plans/imports/{importId}/confirm

목적: 미리보기에서 모든 저장 차단 오류를 해결한 엑셀 행을 수업 계획으로 한 번에 확정한다. (FR-008, FR-010, FR-012, UIR-006)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans/imports/{importId}/confirm

요청값

{
  "confirm": true
}

성공 응답

{
  "data": {
    "savedClassPlanCount": 118,
    "temporaryAssignmentCount": 118,
    "sameSchoolOverlapExceptionCount": 1
  }
}

사용자 오류 상황

  • 오류 행이 하나라도 남아 있으면 전체 확정을 막고 오류 행을 다시 보여준다.
  • 확정 시점에 다른 운영자의 저장으로 새 시간 중복이 생기면 전체 확정을 완료하지 않고 충돌한 행을 표시한다.
  • 서로 다른 학교의 시간 중복은 저장할 수 없다.
  • 같은 학교 시간 중복은 예외 사유가 있는 경우에만 저장할 수 있다. (FR-008, FR-010, FR-012, NFR-002)

6. 수업 계획 직접 등록과 수정

GET /api/class-plans

목적: 운영자가 학교별 또는 강사별 수업 계획을 조회한다. (FR-009, FR-013, UIR-005, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/class-plans

요청값

필수설명
semesterId아니오학기 필터
schoolId아니오학교 필터
instructorId아니오강사 필터
weekday아니오요일 필터
publicationStatus아니오temporary, confirmed, published
includeExceptions아니오같은 학교 시간 겹침 예외 포함 여부

성공 응답

{
  "data": {
    "items": [
      {
        "classPlanId": "class_001",
        "school": {
          "schoolId": "school_001",
          "schoolName": "새봄초등학교"
        },
        "semester": {
          "semesterId": "semester_2026_2",
          "semesterName": "2026년 2학기"
        },
        "operatingPeriod": {
          "startDate": "2026-08-20",
          "endDate": "2027-02-10"
        },
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:40",
        "endTime": "11:20",
        "subject": "미술",
        "instructor": {
          "instructorId": "instructor_014",
          "instructorName": "김미술"
        },
        "assignmentStatus": "temporary",
        "sameSchoolOverlapException": null
      }
    ]
  }
}

사용자 오류 상황

  • 필터 결과가 없으면 시간표 전체가 없다는 뜻으로 오해하지 않도록 현재 적용한 필터를 표시한다.
  • 등록된 수업이 하나도 없으면 직접 등록과 엑셀 업로드 버튼을 보여준다. (FR-013, UIR-007)

POST /api/class-plans

목적: 운영자가 화면에서 수업 계획과 담당 강사를 한 건씩 직접 등록한다. (FR-009, FR-010, FR-012, UIR-005)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans

요청값

{
  "schoolId": "school_001",
  "semesterId": "semester_2026_2",
  "operatingStartDate": "2026-08-20",
  "operatingEndDate": "2027-02-10",
  "weekday": "TUE",
  "periodName": "3교시",
  "startTime": "10:40",
  "endTime": "11:20",
  "timeSource": "period_template",
  "subject": "미술",
  "instructorId": "instructor_014",
  "sameSchoolOverlapExceptionReason": null
}

성공 응답

{
  "data": {
    "classPlanId": "class_001",
    "assignmentStatus": "temporary",
    "overlapStatus": "none"
  }
}

사용자 오류 상황

  • 운영 기간이 학기 범위를 벗어나면 확인할 항목을 표시한다.
  • 교시를 선택했지만 실제 시간을 찾을 수 없으면 교시 템플릿 등록 또는 실제 시각 직접 입력을 안내한다.
  • 종료 시각이 시작 시각보다 늦지 않으면 저장하지 않는다.
  • 다른 학교 수업과 시간이 겹치면 저장을 막고 충돌한 두 수업 비교 화면을 연다.
  • 같은 학교 수업과 시간이 겹치면 예외 사유 입력 후에만 저장할 수 있다. (FR-009, FR-010, FR-012)

PATCH /api/class-plans/{classPlanId}

목적: 운영자가 기존 수업 계획의 학교, 운영 기간, 시간, 과목 또는 담당 강사를 수정한다. (FR-009, FR-010, FR-011, FR-012, FR-017, UIR-005, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: PATCH /api/class-plans/{classPlanId}

요청값

{
  "weekday": "TUE",
  "periodName": "4교시",
  "startTime": "11:30",
  "endTime": "12:10",
  "subject": "미술",
  "instructorId": "instructor_018",
  "sameSchoolOverlapExceptionReason": null,
  "expectedUpdatedAt": "2026-08-25T09:55:00+09:00"
}

성공 응답

{
  "data": {
    "classPlanId": "class_001",
    "assignmentStatus": "temporary",
    "publicationImpact": {
      "wasPublished": true,
      "changeNotificationRequired": true
    }
  }
}

사용자 오류 상황

  • 저장 중 새 시간 중복이 발생하면 저장하지 않고 충돌 정보를 반환한다.
  • 이미 공개된 수업의 변경은 다음 공개 처리 시 강사에게 변경 알림을 만들 수 있도록 변경 전후 값을 남긴다.
  • 다른 사용자가 먼저 수정한 경우 최신 내용을 확인한 뒤 다시 저장하도록 안내한다. (FR-009, FR-010, FR-011, FR-017)

7. 시간 중복 검사와 현장 수정

POST /api/class-plans/overlap-check

목적: 수업 계획을 저장하거나 수정하기 전에 동일 강사의 시간 중복 여부를 확인한다. (FR-010, NFR-002, NFR-003)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans/overlap-check

요청값

{
  "classPlanId": "class_001",
  "schoolId": "school_001",
  "instructorId": "instructor_014",
  "operatingStartDate": "2026-08-20",
  "operatingEndDate": "2027-02-10",
  "weekday": "TUE",
  "startTime": "10:40",
  "endTime": "11:20"
}

classPlanId는 새 수업 등록 시 생략할 수 있다.

성공 응답: 중복 없음

{
  "data": {
    "hasOverlap": false,
    "canSave": true
  }
}

성공 응답: 다른 학교 시간 중복

{
  "data": {
    "hasOverlap": true,
    "canSave": false,
    "overlapType": "different_school",
    "conflicts": [
      {
        "classPlanId": "class_099",
        "schoolName": "푸른초등학교",
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:50",
        "endTime": "11:30",
        "subject": "창의미술",
        "instructorName": "김미술"
      }
    ]
  }
}

성공 응답: 같은 학교 시간 중복

{
  "data": {
    "hasOverlap": true,
    "canSave": false,
    "overlapType": "same_school",
    "exceptionAvailable": true,
    "conflicts": [
      {
        "classPlanId": "class_099",
        "schoolName": "새봄초등학교",
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:50",
        "endTime": "11:30",
        "subject": "창의미술",
        "instructorName": "김미술"
      }
    ]
  }
}

사용자 오류 상황

  • 14:00~14:4014:40~15:20은 시간이 이어질 뿐 겹치지 않으므로 중복으로 처리하지 않는다.
  • 운영 기간이 겹치지 않으면 시간이 같아도 중복으로 처리하지 않는다.
  • 이름이 같아도 강사 고유 식별값이 다르면 다른 강사로 처리한다.
  • 이 확인 결과만으로 저장을 허용하지 않으며, 실제 저장 시 서버와 데이터베이스가 다시 검사한다. (FR-010, NFR-002)

POST /api/class-plans/{classPlanId}/resolve-overlap

목적: 서로 다른 학교의 시간 중복이 발견됐을 때, 충돌한 수업을 나란히 보고 한쪽 수업을 수정해 다시 저장한다. (FR-011, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans/{classPlanId}/resolve-overlap

요청값

{
  "targetClassPlanId": "class_001",
  "changes": {
    "instructorId": "instructor_018",
    "periodName": "4교시",
    "startTime": "11:30",
    "endTime": "12:10"
  }
}

성공 응답

{
  "data": {
    "classPlanId": "class_001",
    "saved": true,
    "remainingConflicts": []
  }
}

사용자 오류 상황

  • 변경한 수업이 다른 수업과 새로 겹치면 새 충돌 수업을 보여주고 저장을 막는다.
  • 수정을 취소하면 기존에 저장되지 않은 변경 내용은 버린다.
  • 서로 다른 학교 시간 중복은 사유 입력만으로 강제 저장할 수 없다. (FR-011)

POST /api/class-plans/{classPlanId}/same-school-overlap-exception

목적: 같은 학교 안에서 의도적으로 시간이 겹치는 수업을 예외 사유와 함께 저장한다. (FR-012, FR-019, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/class-plans/{classPlanId}/same-school-overlap-exception

요청값

{
  "conflictingClassPlanId": "class_099",
  "reason": "학교 요청으로 두 반의 인수인계 시간이 10분 겹칩니다."
}

성공 응답

{
  "data": {
    "classPlanId": "class_001",
    "exceptionId": "exception_001",
    "saved": true,
    "reason": "학교 요청으로 두 반의 인수인계 시간이 10분 겹칩니다.",
    "createdAt": "2026-08-25T10:30:00+09:00"
  }
}

사용자 오류 상황

  • 예외 사유가 비어 있으면 저장할 수 없다.
  • 두 수업이 서로 다른 학교에 속하면 예외 저장을 허용하지 않는다.
  • 이후 학교, 운영 기간, 요일 또는 실제 시간이 바뀌면 예외가 여전히 가능한지 다시 검사한다. (FR-012)

8. 전체 시간표, 확정 시간표와 공개

GET /api/timetables

목적: 여러 학교의 수업 배정 상태, 시간 중복, 같은 학교 시간 겹침 예외를 전체 시간표에서 확인한다. (FR-013, FR-019, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/timetables

요청값

필수설명
semesterId아니오학기 필터
schoolId아니오학교 필터
instructorId아니오강사 필터
weekday아니오요일 필터
status아니오배정 또는 공개 상태 필터

성공 응답

{
  "data": {
    "items": [
      {
        "classPlanId": "class_001",
        "schoolName": "새봄초등학교",
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:40",
        "endTime": "11:20",
        "subject": "미술",
        "instructorName": "김미술",
        "assignmentStatus": "temporary",
        "overlapStatus": "same_school_exception",
        "exceptionId": "exception_001"
      }
    ],
    "summary": {
      "unresolvedDifferentSchoolOverlapCount": 0,
      "sameSchoolOverlapExceptionCount": 1
    }
  }
}

사용자 오류 상황

  • 등록된 수업이 없으면 엑셀 업로드 또는 수업 직접 등록으로 이동할 수 있는 빈 화면을 보여준다.
  • 필터 결과가 없으면 적용 중인 조건을 표시하고 필터를 초기화할 수 있게 한다. (FR-013)

POST /api/timetable-publications/validate

목적: 운영자가 시간표를 확정·공개하기 전에 선택한 범위에 해결되지 않은 시간 중복 또는 예외 사유 누락이 있는지 검사한다. (FR-014)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/timetable-publications/validate

요청값

{
  "semesterId": "semester_2026_2",
  "classPlanIds": ["class_001", "class_002"]
}

classPlanIds를 생략하면 선택한 학기의 전체 수업 계획을 검사한다.

성공 응답

{
  "data": {
    "canPublish": true,
    "differentSchoolOverlapCount": 0,
    "sameSchoolExceptionMissingReasonCount": 0,
    "classPlanCount": 120
  }
}

사용자 오류 상황

  • 해결되지 않은 서로 다른 학교 시간 중복이 있으면 충돌한 수업 목록을 반환하고 공개를 막는다.
  • 같은 학교 시간 중복에 예외 사유가 없으면 해당 수업을 반환하고 공개를 막는다. (FR-014)

POST /api/timetable-publications

목적: 충돌 검사를 통과한 수업 계획을 확정 시간표로 만들고 강사에게 공개한다. (FR-014, FR-016, FR-017, UIR-007, UIR-010)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/timetable-publications

요청값

{
  "semesterId": "semester_2026_2",
  "classPlanIds": ["class_001", "class_002"],
  "publish": true
}

성공 응답

{
  "data": {
    "publicationId": "publication_001",
    "semesterId": "semester_2026_2",
    "publishedAt": "2026-08-25T11:00:00+09:00",
    "publishedClassPlanCount": 120,
    "affectedInstructorCount": 35,
    "createdNotificationCount": 35
  }
}

사용자 오류 상황

  • 공개 전 검사에서 중복이 발견되면 확정 시간표와 공개본을 만들지 않는다.
  • 공개 처리 중 일부 수업만 반영되는 오류가 나면 공개본 전체 생성을 취소한다.
  • 공개된 시간표의 수정 내용을 다시 공개하면 영향을 받는 강사에게 변경 알림을 생성한다. (FR-014, FR-017)

9. 강사 초대와 계정 연결

POST /api/instructors/{instructorId}/invitations

목적: 운영자가 강사 계정 연결을 위한 초대 링크를 생성한다. (FR-015, UIR-003)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/instructors/{instructorId}/invitations

요청값

{
  "expiresInDays": 7
}

expiresInDays의 기본값과 최대값은 확인 필요다.

성공 응답

{
  "data": {
    "invitationId": "invite_001",
    "instructorId": "instructor_014",
    "status": "invited",
    "invitationUrl": "https://example.com/invitations/accept?token=...",
    "expiresAt": "2026-09-01T23:59:59+09:00"
  }
}

사용자 오류 상황

  • 이미 다른 계정에 연결된 강사 정보에는 새 초대를 만들지 않는다.
  • 운영자는 생성된 링크를 복사해 원하는 채널로 직접 전달한다.
  • 카카오톡 자동 발송은 첫 개발 범위에 포함하지 않는다. (FR-015)

GET /api/invitations/{token}

목적: 강사가 초대 링크를 열었을 때 초대 상태와 연결할 위탁업체·강사 정보를 확인한다. (FR-015, UIR-008)
인증: 불필요
HTTP 방식과 경로: GET /api/invitations/{token}

요청값

없음

성공 응답

{
  "data": {
    "invitationStatus": "valid",
    "agencyName": "방과후 운영 한눈에 교육",
    "instructorName": "김미술",
    "expiresAt": "2026-09-01T23:59:59+09:00"
  }
}

사용자 오류 상황

  • 유효하지 않거나 만료된 링크면 “초대 링크를 사용할 수 없습니다. 운영자에게 새 초대를 요청해 주세요.”라고 안내한다.
  • 이미 연결을 마친 링크면 로그인 후 개인 시간표로 이동하도록 안내한다. (FR-015)

POST /api/invitations/{token}/accept

목적: 강사가 새 계정을 만들거나 기존 계정으로 로그인해 등록된 강사 정보와 연결한다. (FR-015, FR-002, UIR-008)
인증: 선택. 기존 계정 로그인 상태라면 필요, 신규 계정 생성 시 불필요
HTTP 방식과 경로: POST /api/invitations/{token}/accept

요청값: 신규 계정 생성

{
  "mode": "sign_up",
  "email": "instructor@example.com",
  "password": "비밀번호"
}

요청값: 기존 계정 연결

{
  "mode": "sign_in",
  "email": "instructor@example.com",
  "password": "비밀번호"
}

성공 응답

{
  "data": {
    "instructorId": "instructor_014",
    "agencyId": "agency_001",
    "connectionStatus": "connected",
    "redirectPath": "/instructor/timetable"
  }
}

사용자 오류 상황

  • 이미 다른 계정과 연결된 강사 정보는 중복 연결하지 않는다.
  • 초대 링크와 무관한 다른 강사 정보에 임의로 연결할 수 없다.
  • 로그인 정보가 맞지 않으면 계정 존재 여부를 구분하지 않고 로그인 실패를 안내한다. (FR-015, FR-002)

10. 강사 개인 시간표

GET /api/instructor/timetable

목적: 강사가 자신에게 공개된 개인 시간표를 확인한다. (FR-016, UIR-009, UIR-010)
인증: 강사 필요
HTTP 방식과 경로: GET /api/instructor/timetable

요청값

필수설명
semesterId아니오학기 선택
weekday아니오요일 필터

성공 응답

{
  "data": {
    "instructor": {
      "instructorId": "instructor_014",
      "instructorName": "김미술"
    },
    "items": [
      {
        "classPlanId": "class_001",
        "schoolName": "새봄초등학교",
        "semesterName": "2026년 2학기",
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:40",
        "endTime": "11:20",
        "subject": "미술",
        "publishedAt": "2026-08-25T11:00:00+09:00"
      }
    ]
  }
}

사용자 오류 상황

  • 아직 강사 초대 연결이 끝나지 않은 경우 초대 연결 화면으로 이동한다.
  • 공개된 시간표가 없으면 “운영자가 시간표를 공개하면 이곳에서 확인할 수 있습니다.”라고 보여준다.
  • 다른 강사의 시간표 또는 전체 시간표는 조회할 수 없다. (FR-016, NFR-001)

11. 시간표 공개·변경 알림과 알림함

GET /api/instructor/notifications

목적: 강사가 시간표 공개·변경 알림을 모아 확인한다. (FR-017, FR-018, UIR-011)
인증: 강사 필요
HTTP 방식과 경로: GET /api/instructor/notifications

요청값

필수설명
status아니오unread, read, all
page아니오페이지 번호
pageSize아니오한 화면에 표시할 수

성공 응답

{
  "data": {
    "unreadCount": 2,
    "items": [
      {
        "notificationId": "notification_001",
        "type": "timetable_published",
        "title": "2026년 2학기 시간표가 공개되었습니다.",
        "message": "새봄초등학교 화요일 3교시 미술 수업을 확인해 주세요.",
        "relatedClassPlanId": "class_001",
        "createdAt": "2026-08-25T11:00:00+09:00",
        "readAt": null
      },
      {
        "notificationId": "notification_002",
        "type": "timetable_changed",
        "title": "시간표가 변경되었습니다.",
        "message": "새봄초등학교 화요일 수업 시간이 10:40~11:20에서 11:30~12:10으로 변경되었습니다.",
        "relatedClassPlanId": "class_001",
        "createdAt": "2026-08-26T10:00:00+09:00",
        "readAt": null
      }
    ]
  }
}

사용자 오류 상황

  • 알림이 없으면 “새 알림이 없습니다.”라고 표시한다.
  • 강사는 본인에게 생성된 알림만 볼 수 있다. (FR-018)

PATCH /api/instructor/notifications/{notificationId}

목적: 강사가 알림을 읽음으로 표시한다. (FR-018, UIR-011)
인증: 강사 필요
HTTP 방식과 경로: PATCH /api/instructor/notifications/{notificationId}

요청값

{
  "read": true
}

성공 응답

{
  "data": {
    "notificationId": "notification_001",
    "readAt": "2026-08-25T11:10:00+09:00"
  }
}

사용자 오류 상황

  • 다른 강사의 알림을 읽음 처리하려는 요청은 차단한다.
  • 이미 읽은 알림은 같은 상태로 반환한다. (FR-018)

POST /api/timetable-publications/{publicationId}/notifications

목적: 시간표 공개 또는 공개된 시간표 변경 시 영향을 받는 강사의 변경 알림을 생성한다. 일반 사용자가 직접 실행하는 화면용 요청이 아니라, 운영자가 시간표를 공개할 때 연결해 사용한다. (FR-017, FR-014)
인증: 운영자 필요
HTTP 방식과 경로: POST /api/timetable-publications/{publicationId}/notifications

요청값

{
  "notificationType": "timetable_published"
}

notificationType 값:

  • timetable_published: 처음 공개된 시간표 알림
  • timetable_changed: 이미 공개된 수업의 변경 알림

성공 응답

{
  "data": {
    "publicationId": "publication_001",
    "notificationType": "timetable_published",
    "createdNotificationCount": 35
  }
}

사용자 오류 상황

  • 공개되지 않은 시간표에는 알림을 만들지 않는다.
  • 같은 공개 처리에서 동일 강사에게 같은 알림이 중복 생성되지 않도록 한다.
  • 알림 생성 실패 시 시간표 공개 상태와 알림 생성 상태를 운영자가 확인할 수 있어야 한다. 세부 재시도 방식은 확인 필요다. (FR-017)

12. 공개본과 예외 기록 조회

GET /api/timetable-publications

목적: 운영자가 학기별 시간표 공개 이력과 공개 시각을 조회한다. (FR-019, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/timetable-publications

요청값

필수설명
semesterId아니오학기 필터
page아니오페이지 번호
pageSize아니오한 화면에 표시할 수

성공 응답

{
  "data": {
    "items": [
      {
        "publicationId": "publication_001",
        "semesterName": "2026년 2학기",
        "publishedAt": "2026-08-25T11:00:00+09:00",
        "publishedClassPlanCount": 120,
        "publishedBy": {
          "userId": "user_001",
          "name": "홍길동"
        }
      }
    ]
  }
}

사용자 오류 상황

  • 공개 이력이 없으면 아직 공개하지 않은 시간표임을 명확히 표시한다.
  • 다른 위탁업체의 공개 이력은 조회할 수 없다. (FR-019, NFR-001)

GET /api/timetable-publications/{publicationId}

목적: 운영자가 특정 확정 시간표의 수업 목록과 공개 당시 상태를 확인한다. (FR-019, FR-014)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/timetable-publications/{publicationId}

요청값

없음

성공 응답

{
  "data": {
    "publicationId": "publication_001",
    "semesterName": "2026년 2학기",
    "publishedAt": "2026-08-25T11:00:00+09:00",
    "classPlans": [
      {
        "classPlanId": "class_001",
        "schoolName": "새봄초등학교",
        "weekday": "TUE",
        "periodName": "3교시",
        "startTime": "10:40",
        "endTime": "11:20",
        "subject": "미술",
        "instructorName": "김미술"
      }
    ]
  }
}

사용자 오류 상황

  • 존재하지 않는 공개본이면 공개 이력 목록으로 돌아가도록 안내한다. (FR-019)

GET /api/same-school-overlap-exceptions

목적: 운영자가 같은 학교 시간 겹침 예외 기록, 사유, 처리자와 처리 시각을 조회한다. (FR-019, FR-012, UIR-007)
인증: 운영자 필요
HTTP 방식과 경로: GET /api/same-school-overlap-exceptions

요청값

필수설명
semesterId아니오학기 필터
schoolId아니오학교 필터
instructorId아니오강사 필터

성공 응답

{
  "data": {
    "items": [
      {
        "exceptionId": "exception_001",
        "schoolName": "새봄초등학교",
        "instructorName": "김미술",
        "classPlans": [
          {
            "classPlanId": "class_001",
            "subject": "미술",
            "weekday": "TUE",
            "startTime": "10:40",
            "endTime": "11:20"
          },
          {
            "classPlanId": "class_099",
            "subject": "창의미술",
            "weekday": "TUE",
            "startTime": "10:50",
            "endTime": "11:30"
          }
        ],
        "reason": "학교 요청으로 두 반의 인수인계 시간이 10분 겹칩니다.",
        "handledBy": "홍길동",
        "createdAt": "2026-08-25T10:30:00+09:00"
      }
    ]
  }
}

사용자 오류 상황

  • 조회 결과가 없으면 등록된 같은 학교 시간 겹침 예외가 없다고 표시한다.
  • 서로 다른 학교 수업의 시간 중복 기록은 예외로 조회되지 않으며, 저장 자체가 차단된다. (FR-019, FR-010)

13. 비기능 구현 기준

위탁업체별 데이터 분리 (NFR-001)

  • 학교, 강사, 학기, 교시 템플릿, 수업 계획, 확정 시간표, 시간표 공개, 변경 알림, 예외 기록은 모두 위탁업체 식별값에 연결한다.
  • URL의 식별값만 바꿔 다른 위탁업체 정보를 보거나 수정할 수 없어야 한다.
  • 서버는 요청마다 로그인 사용자와 위탁업체의 연결 관계를 확인한다.

중복 배정의 서버·데이터베이스 차단 (NFR-002)

  • 화면의 사전 검사와 별도로 수업 계획 저장·수정·엑셀 확정·시간표 공개 시점에 다시 중복을 검사한다.
  • 동시에 두 운영자가 수업 계획을 저장해 서로 다른 학교 시간 중복이 생기는 상황도 막아야 한다.
  • 서로 다른 학교의 시간 중복은 예외 사유로 저장할 수 없다.
  • 같은 학교 시간 중복은 예외 사유, 처리 운영자, 처리 시각, 관련 수업 계획을 모두 남긴 경우에만 저장할 수 있다.

시간 처리 일관성 (NFR-003)

  • 실제 시작·종료 시각을 시간 중복의 유일한 판단 기준으로 사용한다.
  • 시간 중복은 아래 조건을 만족할 때 발생한다.
기존 수업 시작 < 새 수업 종료
그리고
새 수업 시작 < 기존 수업 종료
  • 종료 시각과 다음 수업 시작 시각이 같은 경우는 시간 중복이 아니다.
  • 교시명은 화면 표시와 실제 시작·종료 시각 자동 입력을 위해 사용하지만, 중복 판단은 교시명 자체가 아닌 실제 시각으로 한다.
  • 엑셀의 숫자형 시간과 문자열 시간을 같은 HH:mm 형식으로 변환한다.

인증과 전송 보안 (NFR-004)

  • 로그인, 초대 연결, 모든 운영자·강사 데이터 요청은 암호화된 연결을 사용한다.
  • 비밀번호는 원문으로 저장하지 않는다.
  • 로그인 실패 시 이메일 존재 여부를 드러내지 않는다.
  • 초대 링크는 임의 추측이 어려운 값으로 만들고, 만료·사용 완료 상태를 확인한다.

민감정보 최소화 (NFR-005)

  • 성범죄 경력·아동학대 전력 조회 회신서 등 민감한 원본 파일을 저장하지 않는다.
  • 첫 개발 범위에는 서류 상태와 만료일 데이터도 포함하지 않는다.
  • 강사 연락처와 이메일은 운영자 관리와 초대에 필요한 범위에서만 사용한다.

엑셀 업로드 안전성 (NFR-006)

  • 지정 양식과 지원 파일 형식만 받는다.
  • 파일을 읽는 동안 기존 수업 계획을 변경하지 않는다.
  • 모든 행의 검증이 끝나고 운영자가 확정할 때만 수업 계획으로 저장한다.
  • 읽지 못한 행, 필수값 누락, 등록되지 않은 학교, 동명이인 강사, 시간 오류를 행 번호와 함께 보여준다.

접근성과 오류 표현 (NFR-007)

  • 오류를 색상만으로 표현하지 않고 행 번호, 입력 항목, 짧은 오류 문구를 함께 제공한다.
  • 엑셀 업로드 오류 행은 표에서 바로 수정할 수 있어야 한다.
  • 시간 중복 경고는 충돌한 두 수업의 학교, 요일, 교시, 실제 시간, 과목, 강사를 나란히 보여준다.
  • 강사가 개인 시간표와 알림함을 쉽게 확인할 수 있도록 웹의 작은 화면에서도 주요 정보가 가려지지 않아야 한다.

검증 데이터 규모 (NFR-008)

첫 검증 기준은 강사 약 35명, 학교 약 20곳의 실제 운영 데이터다.

  • 다수 학교와 강사가 있는 엑셀 시간표를 임시 읽기, 행별 검증, 수정, 확정까지 처리할 수 있어야 한다.
  • 전체 시간표에서 학교·강사·요일·학기 기준 필터를 적용해 필요한 수업을 확인할 수 있어야 한다.
  • 정확한 최대 등록 건수와 동시 사용자 기준은 개발 계약 전 확인 필요다.

웹 제공 기준 (NFR-009)

  • 서비스는 Next.js 기반 웹으로 제공한다.
  • 모바일 앱 스토어 등록과 별도 모바일 앱 빌드는 전제하지 않는다.
  • 운영자는 PC 환경에서 엑셀 업로드, 대량 시간표 확인, 충돌 수정을 수행할 수 있어야 한다.
  • 강사는 웹에서 개인 시간표와 알림함을 확인할 수 있어야 한다.
기능 연결 방식 — 방과후·늘봄 강사 배정 관리 서비스 | Prometheon