Files
sam-docs/sam/docs/features/planning/construction-photos.md
김보곤 4f90c0e869 docs: [planning] 주일기업 기획 메뉴 기술문서 추가
- README.md: 전체 개요, 5개 하위 메뉴 구조, 아키텍처
- construction-photos.md: 공사현장 사진대지 (GCS, 행 구조, 음성입력)
- meeting-minutes.md: 회의록 (STT 화자분리, Gemini AI 요약, 오디오 녹음)
- planning-views.md: 견적/프로젝트/워크플로우 화면 명세
- INDEX.md: 문서 인덱스에 planning 등록
2026-03-06 08:25:20 +09:00

276 lines
8.1 KiB
Markdown

# 공사현장 사진대지
> **작성일**: 2026-03-06
> **상태**: 운영 중
> **라우트**: `/juil/construction-photos`
> **관련**: [README.md](README.md) | [회의록](meeting-minutes.md) | [뷰 화면](planning-views.md)
---
## 1. 개요
건설/시공 현장의 작업 과정을 **작업전/작업중/작업후** 3단계 사진으로 기록하고 관리하는 기능. Google Cloud Storage에 사진을 저장하며, 음성 입력(Web Speech API)으로 현장명과 설명을 입력할 수 있다.
---
## 2. 라우트
```
/juil/construction-photos
├── GET / → index (목록 페이지)
├── GET /list → list (JSON 목록)
├── POST / → store (새 사진대지 등록)
├── POST /log-stt-usage → logSttUsage (STT 시간 기록)
├── GET /{id} → show (상세 조회)
├── PUT /{id} → update (메타데이터 수정)
├── DELETE /{id} → destroy (삭제)
├── POST /{id}/rows → addRow (행 추가)
├── DELETE /{id}/rows/{rowId} → deleteRow (행 삭제)
├── POST /{id}/rows/{rowId}/upload → uploadPhoto (사진 업로드)
├── DELETE /{id}/rows/{rowId}/photo/{type} → deletePhoto (사진 삭제)
└── GET /{id}/rows/{rowId}/download/{type} → downloadPhoto (다운로드)
```
---
## 3. 데이터베이스
### 3.1 construction_site_photos (사진대지)
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `tenant_id` | BIGINT FK | 테넌트 격리 |
| `user_id` | BIGINT FK | 등록자 |
| `site_name` | VARCHAR(200) | 현장명 (필수) |
| `work_date` | DATE | 작업일자 (필수) |
| `description` | TEXT NULL | 설명 |
| `deleted_at` | TIMESTAMP NULL | 소프트 삭제 |
**인덱스**: `tenant_id`, `user_id`, `(tenant_id, work_date)`
### 3.2 construction_site_photo_rows (사진 행)
각 사진대지는 1개 이상의 행을 가지며, 각 행에 3개 타입(before/during/after) 사진 저장.
| 컬럼 | 타입 | 설명 |
|------|------|------|
| `id` | BIGINT PK | |
| `construction_site_photo_id` | BIGINT FK | 부모 (cascade delete) |
| `sort_order` | INT | 정렬 순서 (0부터) |
| `before_photo_path` | VARCHAR(500) NULL | 작업전 GCS 경로 |
| `before_photo_gcs_uri` | VARCHAR(500) NULL | 작업전 GCS URI |
| `before_photo_size` | INT UNSIGNED NULL | 작업전 파일크기 (bytes) |
| `during_photo_path` | VARCHAR(500) NULL | 작업중 GCS 경로 |
| `during_photo_gcs_uri` | VARCHAR(500) NULL | 작업중 GCS URI |
| `during_photo_size` | INT UNSIGNED NULL | 작업중 파일크기 (bytes) |
| `after_photo_path` | VARCHAR(500) NULL | 작업후 GCS 경로 |
| `after_photo_gcs_uri` | VARCHAR(500) NULL | 작업후 GCS URI |
| `after_photo_size` | INT UNSIGNED NULL | 작업후 파일크기 (bytes) |
### 3.3 테이블 관계
```
construction_site_photos
│ 1:N
construction_site_photo_rows (sort_order ASC)
├── before_photo_* (작업전)
├── during_photo_* (작업중)
└── after_photo_* (작업후)
```
---
## 4. API 명세
### 4.1 목록 조회
```
GET /juil/construction-photos/list
```
| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 현장명 검색 |
| `date_from` | date | 시작일 |
| `date_to` | date | 종료일 |
| `per_page` | int | 페이지당 건수 |
### 4.2 생성
```
POST /juil/construction-photos
```
| 필드 | 규칙 | 설명 |
|------|------|------|
| `site_name` | required, max:200 | 현장명 |
| `work_date` | required, date | 작업일자 |
| `description` | nullable, max:2000 | 설명 |
> 생성 시 빈 행 1개 자동 추가
### 4.3 사진 업로드
```
POST /juil/construction-photos/{id}/rows/{rowId}/upload
```
| 필드 | 규칙 | 설명 |
|------|------|------|
| `type` | required, in:before,during,after | 사진 타입 |
| `photo` | required, image, mimes:jpeg,jpg,png,webp, max:10240 | 최대 10MB |
### 4.4 사진 다운로드
```
GET /juil/construction-photos/{id}/rows/{rowId}/download/{type}?inline=1
```
| 파라미터 | 설명 |
|---------|------|
| `inline=1` | 브라우저 표시 (미지정 시 다운로드) |
---
## 5. GCS 저장 구조
### 5.1 경로 패턴
```
construction-site-photos/{tenant_id}/{photo_id}/{row_id}_{timestamp}_{type}.{ext}
```
**예시:**
```
construction-site-photos/1/42/15_1709723456_before.jpg
construction-site-photos/1/42/15_1709723456_during.jpg
construction-site-photos/1/42/15_1709723456_after.png
```
### 5.2 업로드 흐름
```
클라이언트 (Canvas 이미지 압축: 1920px, quality 80%)
FormData (multipart) 전송
컨트롤러: uploadPhoto()
서비스: uploadPhoto()
├── 기존 사진 있으면 GCS에서 삭제
├── GCS에 업로드
├── DB에 path + uri + size 저장
└── AiTokenHelper::saveGcsStorageUsage() 호출
응답: { success, data: Photo with rows }
```
### 5.3 삭제 흐름
```
사진 삭제: GCS 파일 삭제 → DB 필드 null
행 삭제: 행 내 모든 사진 GCS 삭제 → 행 삭제 → sort_order 재정렬
사진대지 삭제: 모든 행의 모든 사진 GCS 삭제 → soft delete
```
---
## 6. 음성 입력 (Web Speech API)
### 6.1 VoiceInputButton 컴포넌트
현장명, 설명 필드에 음성으로 텍스트 입력 가능.
```javascript
// Web Speech Recognition 설정
recognition.lang = 'ko-KR';
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;
```
### 6.2 인식 상태
| 상태 | 표시 | 설명 |
|------|------|------|
| interim (미확정) | 이탤릭 + 회색 | 인식 중간 결과, 2초 후 소실 |
| final (확정) | 일반체 + 진한색 | 확정 텍스트, 영구 저장 |
### 6.3 사용량 추적
```
STT 사용 종료 시:
duration = Math.max(1, (Date.now() - startTime) / 1000)
POST /juil/construction-photos/log-stt-usage
body: { duration_seconds }
AiTokenHelper::saveSttUsage('공사현장사진대지-음성입력', seconds)
```
---
## 7. UI 구성 (React)
### 7.1 사진 타입별 색상
| 타입 | 라벨 | 배경색 | 뱃지색 |
|------|------|--------|--------|
| `before` | 작업전 | `bg-blue-50` | `bg-blue-100 text-blue-800` |
| `during` | 작업중 | `bg-yellow-50` | `bg-yellow-100 text-yellow-800` |
| `after` | 작업후 | `bg-green-50` | `bg-green-100 text-green-800` |
### 7.2 행 관리
- **행 추가**: sort_order 자동 계산 (마지막 + 1)
- **행 삭제**: 최소 1개 행 유지 필수
- **행별 사진**: 각 행에 3개 타입 사진 독립 업로드/삭제
---
## 8. 모델 메서드
### 8.1 ConstructionSitePhoto
```php
user() # BelongsTo User (등록자)
rows() # HasMany Row (sort_order ASC)
getPhotoCount(): int # 전체 사진 개수 (모든 행의 사진 합계)
```
### 8.2 ConstructionSitePhotoRow
```php
constructionSitePhoto() # BelongsTo 부모
hasPhoto(string $type): bool # 특정 타입 사진 존재 여부
getPhotoCount(): int # 이 행의 사진 개수 (0~3)
```
### 8.3 ConstructionSitePhotoService
```php
getList(array $filters) # 검색/필터 목록 (페이지네이션)
create(array $data) # 생성 + 빈 행 1개 자동 추가
update(ConstructionSitePhoto, array $data) # 메타데이터만 수정
delete(ConstructionSitePhoto) # GCS 전체 삭제 → soft delete
uploadPhoto(Row, UploadedFile, string $type) # GCS 업로드 + DB 기록
deletePhotoByType(Row, string $type) # 특정 타입 GCS 삭제
addRow(ConstructionSitePhoto) # 행 추가 (sort_order 자동)
deleteRow(Row) # 행 내 GCS 삭제 → 행 삭제 → 재정렬
```
---
## 관련 문서
- [README.md](README.md) — 기획 메뉴 전체 개요
- [회의록 작성](meeting-minutes.md) — STT/AI 통합 회의 기록
- [견적/프로젝트/워크플로우](planning-views.md) — 화면 명세
---
**최종 업데이트**: 2026-03-06