# 사내 장비/자재 대여 관리 시스템 FS (기능명세서, Functional Specification)

- 문서 버전: v0.1 (초안)
- 작성일: 2026-07-18
- 기반 문서: 사내장비대여관리_PRD.md
- 개발 플랫폼: Google AI Studio
- 데이터베이스: Firebase Firestore

> 💡 **용어 해설**
> - **FS (Functional Specification)**: PRD(무엇을 만들지)를 바탕으로, 화면마다 어떤 버튼/입력창이 있고 어떻게 동작하는지 실제 개발 가능한 수준까지 구체적으로 적은 문서
> - **Firestore**: 데이터를 폴더(컬렉션) 안에 문서(document) 형태로 저장하는 구글의 클라우드 데이터베이스

---

## 1. 시스템 구성 개요

| 항목 | 내용 |
|---|---|
| 프론트엔드 | Google AI Studio 기반 웹앱 (반응형) |
| 인증 | Firebase Authentication (이메일/비밀번호 + Google 소셜로그인) |
| 데이터베이스 | Firebase Firestore |
| 알림 | Firestore 기반 인앱 알림 (별도 푸시 서버 없음) |

---

## 2. Firestore 데이터베이스 설계

> 💡 **용어 해설**
> - **컬렉션(Collection)**: 엑셀의 "시트"와 비슷한 개념, 같은 종류의 데이터 묶음
> - **문서(Document)**: 엑셀의 "한 행(row)"과 비슷한 개념, 실제 데이터 1건

### 2.1 users (회원)

| 필드명 | 타입 | 설명 |
|---|---|---|
| uid | string | Firebase 인증 고유ID (문서ID) |
| name | string | 이름 |
| email | string | 이메일 (로그인 ID) |
| phone | string | 연락처 |
| department | string | 부서명 (departments 컬렉션 참조) |
| role | string | "member" \| "admin" |
| createdAt | timestamp | 가입일시 |

### 2.2 departments (부서)

| 필드명 | 타입 | 설명 |
|---|---|---|
| id | string | 문서ID |
| name | string | 부서명 |
| createdAt | timestamp | 등록일시 |

- 초기값: 생산지원팀, 양조1팀, 양조2팀, 품질보증팀, 설비기술팀
- 관리자가 추가/수정/삭제 가능

### 2.3 categories (대분류/중분류) — 고정 참조용

| 필드명 | 타입 | 설명 |
|---|---|---|
| id | string | 문서ID |
| largeCategory | string | 대분류 (예: IT기기) |
| mediumCategory | string | 중분류 (예: 노트북) |

- 초기 설정 후 고정 (관리자 수정 UI 없음, 데이터 변경 시 개발자가 직접 처리)

### 2.4 equipment (장비/자재)

| 필드명 | 타입 | 설명 |
|---|---|---|
| id | string | 고유번호 (문서ID, 예: 노트북-01) |
| name | string | 장비명 |
| largeCategory | string | 대분류 |
| mediumCategory | string | 중분류 |
| smallCategory | string | 소분류 |
| location | string | 보관 장소 |
| status | string | "available"(가능) \| "rented"(대여중) |
| maxRentalDays | number | 최대대여일 |
| createdAt | timestamp | 등록일시 |
| updatedAt | timestamp | 수정일시 |

### 2.5 rentals (대여 신청)

| 필드명 | 타입 | 설명 |
|---|---|---|
| id | string | 문서ID (자동생성) |
| userId | string | 신청자 uid |
| userName | string | 신청자 이름 (조회 편의용 중복저장) |
| department | string | 신청자 부서 (통계용 중복저장) |
| equipmentId | string | 장비 고유번호 |
| equipmentName | string | 장비명 (중복저장) |
| largeCategory | string | 대분류 (통계용 중복저장) |
| mediumCategory | string | 중분류 (통계용 중복저장) |
| startDate | timestamp | 대여 시작일 |
| endDate | timestamp | 대여 종료일 |
| note | string | 대여 목적/메모 (선택입력, null 가능) |
| status | string | "pending"(대기) \| "approved"(승인) \| "rejected"(반려) \| "return_pending"(반납대기) \| "returned"(반납완료) |
| appliedAt | timestamp | 신청일시 |
| approvedAt | timestamp \| null | 승인일시 |
| returnedAt | timestamp \| null | 반납완료 처리일시 |
| reminderSentAt | timestamp \| null | 자동 독촉 발송일시 (1회 발송 여부 체크용) |

> 💡 **용어 해설: 중복저장(비정규화)**
> Firestore는 여러 컬렉션을 엮어서 조회하는 게 느리고 복잡하므로, 자주 같이 쓰는 정보(신청자 이름, 부서명 등)를 신청 문서 안에 한 번 더 저장해두는 방식. 통계·목록 조회 속도를 크게 높여줌.

### 2.6 notifications (알림)

| 필드명 | 타입 | 설명 |
|---|---|---|
| id | string | 문서ID |
| userId | string | 수신자 uid |
| type | string | "approved" \| "rejected" \| "reminder" |
| message | string | 알림 문구 |
| relatedRentalId | string | 관련 신청 문서ID |
| isRead | boolean | 읽음 여부 |
| createdAt | timestamp | 생성일시 |

---

## 3. 화면 명세 — 일반회원

### 3.1 로그인/회원가입 화면

**구성요소**
- 이메일, 비밀번호 입력창 / 로그인 버튼
- "Google로 로그인" 버튼
- "회원가입" 링크

**회원가입 절차**
1. 이메일, 비밀번호, 이름, 연락처, 부서(드롭다운 선택 필수) 입력
2. 가입 버튼 클릭 → 입력한 이메일로 인증코드 발송
3. 인증코드 입력 → 확인 시 Firebase Authentication 계정 생성 + users 문서 생성 (role: "member")
4. 가입 즉시 로그인 처리 (별도 관리자 승인 대기 없음)

**로직/조건**
- 이메일 중복 시 가입 불가 안내
- 비밀번호 분실 시 "비밀번호 재설정" 클릭 → 이메일로 재설정 링크 발송

### 3.2 장비 목록/신청 화면

**구성요소**
- 필터: 대분류 드롭다운, 중분류 드롭다운, 품목명 검색창, 대여가능여부(가능/대여중) 토글
- 장비 카드/리스트: 장비명, 분류, 장소, 상태 표시
- 각 장비 카드에 "장바구니 담기" 버튼
- 우측 상단 장바구니 아이콘 (담긴 품목 수 표시)

**로직/조건**
- equipment 컬렉션 조회 시 필터 조건에 맞게 where 절 적용
- status가 "rented"인 장비도 목록에는 노출하되, 신청 시점의 실제 가능 여부는 캘린더에서 재확인 (기간제 예약이므로 status만으로 판단하지 않음)

> 💡 **참고**: status "가능/대여중"은 현재 시점 기준 단순 표시용이며, 실제 예약 가능 여부는 rentals의 승인 건 기간과 겹치는지로 판단합니다.

### 3.3 장바구니 화면

**구성요소**
- 담은 품목 리스트 (품목별 카드)
- 각 품목 카드 내: 대여기간 캘린더 선택 UI, 최대대여일 안내 문구
- 캘린더에는 해당 품목의 기존 승인/대기 건 기간이 비활성화(선택불가)로 표시
- 대여 목적/메모 입력창 (선택, 품목별 또는 신청 전체 공통 — 품목별 개별 입력)
- "전체 신청" 버튼

**로직/조건**
1. 품목별 캘린더에서 시작일~종료일 선택
2. 선택 기간이 (종료일 - 시작일) > maxRentalDays 이면 신청 불가 안내
3. 선택 기간이 기존 pending/approved 건과 겹치면 선택 불가 (선착순)
4. "전체 신청" 클릭 시 장바구니 내 품목 수만큼 rentals 문서를 각각 생성 (status: "pending")
5. 신청 완료 후 장바구니 비움

### 3.4 나의 신청현황 화면

**구성요소**
- 신청 목록 (품목명, 대여기간, 상태, 신청일)
- 상태별 필터 (전체/대기/승인/반려/반납대기/반납완료)
- "대기" 상태 건에 한해 "취소" 버튼 노출

**로직/조건**
- rentals 컬렉션에서 userId == 본인 uid 조건으로 조회
- 취소 클릭 시 status를 별도 값으로 변경하지 않고 문서 삭제 또는 "cancelled" 상태로 전환 (권장: 이력 보존을 위해 "cancelled" 상태 추가 고려 — PRD 미정 항목, 개발 시 확정 필요)

### 3.5 알림함 화면

**구성요소**
- 알림 리스트 (최신순), 읽음/안읽음 구분 표시
- 알림 클릭 시 관련 신청현황으로 이동

**로직/조건**
- notifications 컬렉션에서 userId == 본인 uid 조회
- 클릭 시 isRead: true로 업데이트

### 3.6 마이페이지

**구성요소**
- 이름, 이메일(수정불가), 연락처, 부서(드롭다운, 수정가능) 표시/수정
- "저장" 버튼
- 비밀번호 변경 링크

**로직/조건**
- 부서 변경 시 departments 컬렉션의 현재 목록을 드롭다운으로 노출
- 저장 시 users 문서 업데이트

---

## 4. 화면 명세 — 관리자

### 4.1 회원 관리 화면

**구성요소**
- 회원 리스트 (이름, 이메일, 부서, 연락처, 권한, 가입일)
- 부서/권한 필터
- 각 행에 "관리자로 승격" 버튼

**로직/조건**
- users 컬렉션 전체 조회
- 승격 클릭 시 확인 팝업 → role: "admin"으로 업데이트

### 4.2 부서 관리 화면

**구성요소**
- 부서 리스트, "부서 추가" 버튼, 각 행에 수정/삭제 버튼

**로직/조건**
- departments 컬렉션 CRUD
- 삭제 시 해당 부서 소속 회원이 있으면 경고 문구 노출 (삭제는 진행 가능, 회원 부서값은 유지되나 드롭다운에서만 제외)

### 4.3 장비 목록 관리 화면

**구성요소**
- 장비 리스트 (전체), 검색/필터
- "신규 등록" 버튼 (수동 입력 폼: 고유번호, 장비명, 대분류/중분류 드롭다운, 소분류, 장소, 최대대여일)
- "엑셀 업로드" 버튼 → 템플릿 다운로드 링크 + 파일 업로드
- 각 행에 수정/삭제 버튼

**엑셀 업로드 로직**
1. "템플릿 다운로드" 클릭 → 정해진 컬럼(고유번호, 장비명, 대분류, 중분류, 소분류, 장소, 최대대여일) 양식의 xlsx 파일 다운로드
2. 사용자가 작성 후 업로드
3. 시스템이 파일 파싱 → 고유번호 기준으로 기존 데이터면 수정(update), 신규면 등록(create) 판단
4. 업로드 전 미리보기 화면에서 신규/수정 건수 확인 후 "적용" 버튼으로 최종 반영

### 4.4 신청 관리 화면

**구성요소**
- 전체 신청 리스트 (신청자, 부서, 품목, 기간, 상태, 신청일)
- 상태/부서/기간 필터
- 각 행에 상태 변경 액션: 승인/반려/반납완료 처리/취소/기간변경

**로직/조건**
- 승인 클릭 → status: "approved", approvedAt 기록 → 신청자에게 "approved" 알림 생성
- 반려 클릭 → status: "rejected" → 신청자에게 "rejected" 알림 생성 (사유 입력 없음)
- endDate 경과 + status "approved" → 배치(스케줄러)가 자동으로 status: "return_pending"으로 전환
- "반납완료" 클릭 → status: "returned", returnedAt 기록, equipment.status를 "available"로 갱신
- 관리자는 모든 상태에서 취소/변경 가능

### 4.5 통계 대시보드 화면

**구성요소**
- 기간 필터 (주/월/분기/년)
- 부서별 대여건수 표 + 막대그래프
- 부서별 반납지연건수 표
- 대분류/중분류/품목별 대여빈도 표 + 그래프
- "엑셀 다운로드" 버튼

**로직/조건**
- rentals 컬렉션을 기간 조건(appliedAt 또는 startDate 기준)으로 집계
- 반납지연건수 = status "return_pending" 이거나, returnedAt > endDate 인 "returned" 건수
- 엑셀 다운로드는 현재 필터 조건의 집계 데이터를 xlsx로 export

### 4.6 알림 발송(수동 독촉) 화면

**구성요소**
- 반납지연 중인 신청 리스트 (신청자, 품목, 반납예정일, 경과일, 최근 독촉일)
- 각 행에 "독촉 발송" 버튼

**로직/조건**
- status "return_pending" 건 대상
- 자동 발송: endDate 경과 시점에 스케줄러가 1회 자동으로 notifications 생성 (reminderSentAt 기록)
- 수동 발송: 관리자가 버튼 클릭 시마다 notifications 추가 생성 (횟수 제한 없음)

---

## 5. 공통 비즈니스 로직

### 5.1 대여 가능 여부 판단 (선착순 처리)
1. 사용자가 특정 품목 + 기간을 캘린더에서 선택 시도
2. 해당 equipmentId에 대해 status가 "pending" 또는 "approved"인 기존 rentals 중 기간이 겹치는 건이 있는지 확인
3. 겹치는 건이 있으면 해당 날짜를 캘린더에서 비활성화(선택 불가) 처리
4. 겹치지 않으면 선택 가능 → 신청 시점에 한 번 더 서버측 재검증 (동시 신청 방지)

### 5.2 자동 상태 전환 (반납대기)
- 매일 정해진 시각에 스케줄러 실행
- status "approved"이고 endDate < 오늘 인 rentals → status: "return_pending" 전환
- 동시에 reminderSentAt이 비어있으면 자동 독촉 알림 1건 생성 + reminderSentAt 기록

### 5.3 알림 생성 트리거

| 트리거 | 알림 type |
|---|---|
| 관리자 승인 처리 | approved |
| 관리자 반려 처리 | rejected |
| 반납예정일 경과(자동, 1회) | reminder |
| 관리자 수동 독촉 발송 | reminder |

---

## 6. 권한(접근 제어) 요약

| 기능                            | 일반회원 | 관리자 |
|---------------------------------|----------|--------|
| 장비 목록 조회/신청             | O        | O      |
| 나의 신청현황 조회/취소(대기건) | O        | O      |
| 회원 관리/승격                  | X        | O      |
| 부서 관리                       | X        | O      |
| 장비 등록/수정/엑셀업로드       | X        | O      |
| 신청 승인/반려/전체취소변경     | X        | O      |
| 통계 대시보드                   | X        | O      |
| 수동 독촉 발송                  | X        | O      |

---

## 7. 미확정/추가 논의 필요 항목

1. "대기" 상태 신청을 사용자가 취소할 때, 문서를 삭제할지 "cancelled" 상태로 남길지 결정 필요 (이력 보존 vs 단순화)
2. 스케줄러(자동 상태전환, 자동 독촉) 실행 방식 — Google AI Studio 환경에서의 구현 방법 확인 필요 (Cloud Functions 스케줄 트리거 등)
3. 엑셀 업로드 시 오류 데이터(형식 불일치 등) 처리 방식 상세 정의 필요

---

