[일괄다운로드 PR1] BulkReportDownload 모델 + 마이그레이션

ID: 2d6027ef-f080-454c-b637-0d8fb0e249c3

높음 리뷰

## 설명
일괄 리포트 다운로드 비동기 처리를 위한 DB 레이어.

### 모델: BulkReportDownload
- `workspace_id` (references, not null, index)
- `user_id` (references, not null) — 요청한 관리자
- `status` (string, default: "pending") — pending/processing/completed/failed
- `member_count` (integer) — 선택된 멤버 수
- `processed_count` (integer, default: 0) — 진행 상황
- `error_message` (text, nullable)
- `member_ids` (text[]) — 선택된 workspace_member IDs
- `has_one_attached :file` (ZIP)

### TDD
1. validation: workspace, user 필수
2. status enum: pending → processing → completed/failed
3. scope: expired (created_at < 7.days.ago)
4. association: belongs_to workspace/user, has_one_attached file

### 참고 패턴
- Active Storage: `Coaching::Recording`, `Study::Assignment` 등 4개 모델에서 사용 중

## 완료 기준
- [ ] 마이그레이션 정상 실행
- [ ] 모델 validation, scope, enum 테스트 통과
- [ ] fixture 생성

첨부 이미지

이미지 추가 (Ctrl+V로 붙여넣기 또는 클릭)

JPEG, PNG, GIF, WebP / 최대 10MB

담당자: 팀리드
생성일: 2026년 03월 30일 00:47

활동 로그

  • 팀리드 일괄다운로드 비동기 처리 전체 구현 완료

    2026년 03월 30일 01:43:37

    상세 보기

    ## 구현 완료된 PR (5개)

    ### PR1: BulkReportDownload 모델 + 마이그레이션
    - `db/migrate/20260330012810_create_bulk_report_downloads.rb`
    - `app/models/bulk_report_download.rb` (enum status, scope expired/active, has_one_attached :file)
    - `test/models/bulk_report_download_test.rb` (17 tests)
    - `test/fixtures/bulk_report_downloads.yml` (5 fixtures)

    ### PR2: BulkReportGenerateJob + CleanupJob
    - `app/jobs/bulk_report_generate_job.rb` (PDF→ZIP→Active Storage, 순차 처리, GC 관리)
    - `app/jobs/bulk_report_cleanup_job.rb` (7일 만료 삭제)
    - `config/recurring.yml` 에 매일 2am cleanup 등록
    - 테스트 9 runs

    ### PR3: BulkReportMailer
    - `app/mailers/bulk_report_mailer.rb` (completed/failed)
    - `app/views/bulk_report_mailer/completed.html.erb`, `failed.html.erb`
    - i18n 4개 언어 (ko, en, zh, vi)
    - 테스트 6 runs

    ### PR4: Controller + Routes 비동기 전환
    - `handle_bulk_download` 동기→비동기 전환 (Job enqueue)
    - `bulk_download_status` (JSON 폴링), `bulk_download_file` (파일 다운로드)
    - 중복 요청 방지
    - 테스트 10 runs

    ### PR5: View + Stimulus 폴링 배너
    - `bulk_download_status_controller.js` (3초 폴링, 상태별 UI)
    - `_bulk_download_banner.html.erb` (progress bar, download button)
    - i18n 4개 언어

    ## 전체 테스트
    - **42 runs, 90 assertions, 0 failures, 0 errors**

  • 팀리드 상태 변경: 진행 중 → 리뷰

    2026년 03월 30일 01:30:05

  • 팀리드 작업 시작 - 일괄다운로드 PR1 (모델 + 마이그레이션)

    2026년 03월 30일 01:26:11

    상세 보기

    ## 작업 계획
    - 목표: BulkReportDownload 모델 + 마이그레이션 + 테스트 + fixture
    - 접근 방식: TDD (테스트 먼저 → 구현)

    ## 전체 일괄다운로드 진행 순서
    1. **PR1 (현재)**: 모델 + 마이그레이션
    2. PR2 + PR3 (PR1 완료 후 병렬): Job + Mailer
    3. PR4 (PR2 완료 후): Controller + Routes
    4. PR5 (PR4 완료 후): View + Stimulus

  • 팀리드 티켓 클레임 완료

    2026년 03월 30일 01:26:07

  • 팀리드 기능 계획 수립 - 워크스페이스 리포트 일괄 다운로드 비동기 ��리

    2026년 03월 30일 00:48:17

    상세 보기

    ## 기능 개요
    - 일괄 리포트 ��운로드 시 동기 PDF 생성으로 인한 HTTP 타임아웃 해결
    - 백그라운드 Job으로 전환 + 폴링 방식 상태 표시 + 이메일 알림

    ## 분석 결과
    - 영향 범위: members_controller, PdfGenerator, routes, members views, i18n
    - 기존 패턴: AiInsightGenerateJob(폴링), GiftMailer(���메일), Active Storage(4개 모델)
    - Turbo Stream broadcast 미사용 → 폴링 방식 채택

    ## 생성된 티켓 (5개)
    - [PR1] BulkReportDownload 모델 + 마이그레이��� (high)
    - [PR2] BulkReportGenerateJob + CleanupJob (high)
    - [PR3] BulkReportMailer (medium)
    - [PR4] Controller + Routes 비동기 전환 (high)
    - [PR5] View + Stimulus 폴링 배너 UI (high)

    ## 리스크
    - Grover 메모리 → 순차 처리 + 주기적 GC
    - 디스크 누적 → 7일 만료 CleanupJob (매일 2am)
    - 중복 요청 → pending/processing 존재 시 거부

    ## 기술적 결정
    - ActionCable 인프라 없음 → Turbo Stream broadcast 대신 Stimulus 폴링 (3초)
    - Active Storage local disk 사용 (프로덕션 전환 시 S3 검토)

  • 팀리드 상태 변경: 할 일 → 백로그

    2026년 03월 30일 00:48:04