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

8.1 KiB

공사현장 사진대지

작성일: 2026-03-06 상태: 운영 중 라우트: /juil/construction-photos 관련: README.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 컴포넌트

현장명, 설명 필드에 음성으로 텍스트 입력 가능.

// 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

user()                  # BelongsTo User (등록자)
rows()                  # HasMany Row (sort_order ASC)
getPhotoCount(): int    # 전체 사진 개수 (모든 행의 사진 합계)

8.2 ConstructionSitePhotoRow

constructionSitePhoto()         # BelongsTo 부모
hasPhoto(string $type): bool    # 특정 타입 사진 존재 여부
getPhotoCount(): int            # 이 행의 사진 개수 (0~3)

8.3 ConstructionSitePhotoService

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 삭제 → 행 삭제 → 재정렬

관련 문서


최종 업데이트: 2026-03-06