# 할일·일정·기록 파일첨부

- 날짜: 2026-09-28 (KST)
- 대상: `auto_schedule_app`, `day_plan_app` (쌍둥이 앱 동일 반영)

## 요약
편집기의 **메모 입력란 위**에 "첨부파일" 영역을 추가했다.
할일 · 일정 · 기록 편집기와 완료된 할일/지난 일정 상세에 모두 들어간다.

- `[+ 파일 첨부]`: 파일을 고르면 앱 전용 폴더에 복사한다. 여러 개를 한 번에 고를 수 있다.
- 목록 항목을 누르면 폰의 기본 앱(갤러리, PDF 뷰어, Office, 한글 등)으로 연다.
- `✕`를 누르면 확인 후 첨부에서 뺀다.
- 추가와 빼기는 **저장/추가 버튼을 눌러야 반영**된다. 저장 없이 닫으면 새로 고른 파일은 지워지고, 기존 첨부는 그대로 남는다.

## 허용 형식 · 제한
| 분류 | 확장자 |
|---|---|
| 이미지 | jpg, jpeg, png, gif, webp, bmp, heic, heif |
| 텍스트 | txt, csv, md, log |
| PDF | pdf |
| MS Office | doc, docx, xls, xlsx, ppt, pptx |
| 한글 | hwp, hwpx |

- **동영상·음성 등 그 외 형식은 불가**하다. 선택하면 "첨부할 수 없는 형식" 안내를 띄운다.
- 파일당 **20MB 이하**다. 크기를 먼저 확인하고, 복사 중에도 20MB를 넘으면 중단하고 삭제한다.
- 선택기는 전체 파일로 열고, 앱에서 확장자를 검사한다.
  - 이유: Android 시스템 선택기는 MIME이 등록되지 않은 확장자(hwp/hwpx)를 필터에서 빼 버려서 확장자 필터를 쓰면 한글 파일을 고를 수 없다.

## 저장 구조
- 새 테이블 `tb_attachment(id, todo_uid, occ_day, file_name, file_path, file_size, created_at)`와 인덱스 `ix_attach_uid`를 추가했다.
  - DB 버전은 12에서 14로 올렸다. 13에서 올라오는 경우는 `occ_day` 컬럼만 추가한다.
- 파일 실체는 앱 문서 폴더의 `attachments/<uuid>.<확장자>`에 둔다. DB에는 상대경로를 저장한다.
- `tb_todo`에 컬럼을 추가하지 않았다. 서버 동기화 데이터와 서버 스키마에 영향을 주지 않기 위해서다.
- 항목을 삭제(소프트 삭제)해도 첨부는 지우지 않는다(완료 항목 보존 원칙).

## 첨부는 반복되지 않는다 (회차별 독립)
- 첨부 키는 **(항목 `sync_uid`, 회차 날짜 `occ_day`)**이다.
  - 반복 항목의 `occ_day`는 그 회차 날짜다. 회차 메모와 같은 기준인 `Todo.memoDay`를 쓴다.
  - 반복이 아닌 항목은 `NULL`이다.
  - 계산 함수는 `attachOccDay(Todo)`다.
- 예: 28일에 할일 A를 매주 반복으로 만들고 `aaa.txt`를 첨부하면 28일 회차에만 보인다. 다른 회차(10/5 등)는 비어 있다.
- 편집기는 **연 회차의 첨부만** 보여 준다.
  - 저장할 때 키가 바뀌면(반복을 켜거나 끄거나 기준일을 바꾼 경우) 보던 첨부를 새 키로 옮긴다(`rekeyAttachments`).
- 반복 회차가 실제 행으로 갈라질 때는 첨부를 **복사하지 않고 그 회차 것만 옮긴다**(`_moveAttachments`).
  - 반복 회차 완료로 완료 기록이 생기면(`completeRepeatOccurrence`), 그 날짜 첨부를 완료 기록으로 옮긴다.
  - 롤포워드에서 완료된 회차는 비반복 완료 기록이 되고, 그 날짜 첨부를 그대로 가진다. 미리 붙여 둔 이후 회차 첨부는 다음 회차 행으로 옮긴다.
  - **미완료 회차는 지연으로 이동하고 첨부는 그 회차에 그대로 남는다.** 생성일(회차 날)이 바뀌지 않으므로 첨부 키도 그대로다.
    - 다음 회차가 왔는데 미완료이면 그 회차는 개별 지연 할일로 분리된다. 그 회차 첨부는 분리된 할일의 첨부(`occ_day` NULL)가 된다.
    - 새 회차에는 첨부가 없다(미리 붙여 둔 이후 회차 첨부만 새 반복 행으로 옮김).
    - 자세한 규칙은 `20260928_0537_미완료할일_오늘로_이동_지연표시.md` 참고.
  - 반복 일정을 삭제해 과거 회차가 개별 일정이 되면(`detachPastRepeatEvent`), 각 날짜 첨부가 해당 개별 일정으로 옮겨진다.
  - 반복 할일 중단(`stopRepeatTodo`)으로 완료 행의 반복이 풀리면, 그 회차 첨부가 비반복 첨부가 된다.
- 첨부를 빼면 DB 행을 삭제한다. 같은 파일을 쓰는 다른 행이 없을 때만 실제 파일을 지운다.

## 서버 동기화 (auto)
- 첨부는 **폰 로컬 전용**이며 modam_doc 서버와 동기화하지 않는다. 웹 app_schedule에 첨부 기능이 없기 때문이다.

## 변경 파일
- 신규(두 앱 동일): `lib/models/attachment.dart`, `lib/services/attachments.dart`, `lib/widgets/attachment_section.dart`, `test/attachment_ext_test.dart`
- 수정(두 앱 동일): `lib/widgets/todo_edit_sheet.dart`
  - 첨부 영역을 메모 위 4곳에 추가했다.
  - 저장할 때 연 회차 키로 첨부를 반영한다(`_save`, `_saveContentNote`). 반복 롤포워드 전에 반영한다.
  - 닫힐 때(`dispose`) 저장하지 않은 새 파일을 정리한다.
- 수정(각 앱): `lib/db/app_db.dart`
  - 테이블 생성과 마이그레이션, 첨부 CRUD를 추가했다.
  - 회차가 행으로 갈라지는 지점과 롤포워드, 반복 중단에서 회차 첨부를 이동한다.
- `pubspec.yaml`: `file_picker ^13.1.0`, `open_filex ^4.7.0`, `path_provider ^2.1.6`
- `android/app/src/main/AndroidManifest.xml`
  - open_filex가 자동으로 넣는 `READ_EXTERNAL_STORAGE`와 `READ_MEDIA_IMAGES/VIDEO/AUDIO` 권한을 `tools:node="remove"`로 제거했다.
  - 이유: 첨부는 앱 내부 폴더에서 FileProvider로 열기 때문에 이 권한이 필요 없다. 또한 Play 정책 심사 대응이다.

## 검증
- 두 앱 모두 `flutter analyze`: No issues found
- 두 앱 모두 `flutter test`: 기존 테스트, 확장자 허용/차단 테스트, 회차 키(`attachOccDay`) 테스트 전부 통과
  - 회차 키 테스트: 반복 아님은 null, 매주·매일 반복 할일과 반복 일정은 회차 날짜별로 다른 키
- 두 앱의 `lib/`를 비교했다. 공통 파일은 동일하고, 나머지 차이는 기존 앱 고유 부분과 작업 전부터 있던 차이뿐이다.
