# 미완료 할일 → 오늘로 이동(지연일자 컬럼)

- 날짜: 2026-09-28 (KST)
- 대상: `auto_schedule_app`, `day_plan_app` (쌍둥이 앱 동일 반영)
- 웹(modam_doc app_schedule)은 별도 문서 `20260928_0537_웹_app_schedule_미완료할일_이동_작업안내.md` 참고

## 문제
27일에 만든 할일 "애플 펜슬 충전기/아답터 구입"이 완료되지 않은 채 28일이 되자 27일과 28일 두 칸에 모두 보였다.
- 데이터가 복제된 것은 아니다. 같은 행 1건이 두 규칙으로 두 칸에 그려진 것이다.
  - 규칙 1: 할일은 생성일 칸에 표시한다.
  - 규칙 2: 기한이 지난 미완료 할일은 오늘 칸에 "지연"으로 표시한다.

## 개념 (최종)
- **생성일(`created_at`)은 만들 때 한 번 정해지고 자동으로 바뀌지 않는다.** 사용자가 편집기에서 직접 바꾸는 경우만 예외다.
- **미완료 할일은 그 날 끝내지 못하면 오늘로 "이동"한다.** 이동한 날은 새 컬럼 `delay_date`(지연일자)에 저장한다.
- **완료일자**는 기존 `done_date` 컬럼을 쓴다.
- **한 할일은 한 칸에만 표시한다.** 조회 날짜와 비교하는 우선순위는 다음과 같다.
  1. 완료했으면 완료일자
  2. 지연 중(미완료이면서 지연일자 있음)이면 지연일자
  3. 그 외에는 생성일(없으면 기한)
- 예:
  | 상태 | 27일 칸 | 28일 칸 | 29일 칸 |
  |---|---|---|---|
  | 27일 생성, 28일 현재 미완료(지연일자=28) | 안 나옴 | **지연** | - |
  | 사용자가 편집기에서 생성일을 28일(오늘)로 변경 | 안 나옴 | **오늘** (지연일자 비움) | - |
  | 28일 지연 중 29일 완료(완료일자=29) | 안 나옴 | 안 나옴 | **완료** |
- 메모, 첨부, 기한, 별표, 미리알림 등 모든 옵션은 같은 행이므로 그대로 승계된다. 데이터 삭제는 없다.
- "지연" 표시 조건: 지연일자가 있거나 기한이 지난 미완료 할일.

## 반복 할일 (A: 분리)
- **미완료 회차의 날이 지나고 다음 회차 전**: 지연일자를 오늘로 둔다.
  - 기준일(회차 날) 칸 전개에서는 빠지고 오늘 칸에 "지연"으로 보인다.
  - 생성일, 회차 메모, 첨부는 그대로다.
- **다음 회차가 왔는데 아직 미완료**: 분리한다.
  - 그 회차 행은 반복을 떼고 **개별 지연 할일**로 남는다. 그 회차 메모는 `note`로, 첨부는 비반복 첨부로 옮긴다.
  - 가장 최근 회차 날짜로 **새 반복 행**을 만든다. 생성일은 새 행을 만들 때 한 번 정해진다. 미리 붙여 둔 이후 회차 첨부는 새 행으로 옮긴다.
  - 새 회차 날도 이미 지났으면 새 행도 바로 지연(지연일자 = 오늘)이 된다.
  - 앱을 오래 열지 않아 사이 회차가 여러 개 지난 경우에는 끝내지 못한 회차 1개(지연)와 최근 회차 1개만 남는다. 사이 회차는 건너뛴다.
- **완료 회차**: 완료 기록(비반복)으로 보존하고 다음 회차 행을 만든다.
  - 완료일자는 **실제로 완료한 날**을 유지한다. 전에는 회차 날로 덮어썼다.
  - 다음 회차가 이미 지났으면 곧바로 위의 이동 판단을 한다.
- 이전 롤포워드는 미완료 반복 할일의 생성일을 최근 회차로 바꿨는데, 이 동작을 없앴다.

## 구현
- DB(`lib/db/app_db.dart`)
  - `tb_todo.delay_date TEXT`를 추가하고 DB 버전을 14에서 15로 올렸다(`onCreate`와 `onUpgrade`).
  - `_rollForwardImpl`: 비반복 미완료 할일 중 놓인 날(지연일자, 없으면 생성일)이 오늘 이전인 것은 `delay_date = 오늘`로 갱신한다. 반복 할일은 `_carryRepeatTodo`로 지연 또는 분리한다.
    - 달력 탭, 목록 탭, 저장 후에 실행된다(날짜 칸을 선택해 다시 불러올 때 포함).
  - `listByView('today')`: `delay_date = 오늘` 조건을 추가했다. 비반복은 `생성일 = 오늘`이다.
  - `listByView('overdue')`: 기한 경과 또는 지연일자가 있는 미완료 할일이다.
  - (auto) `applyMergedSnapshot`: 서버 행에 `delay_date`가 없으면 로컬 값을 유지한다.
    - 값이 빠져도 다음 불러오기 때 다시 계산되므로 스스로 복구된다.
- 모델(`lib/models/todo.dart`): `delayDate` 필드, `isDelayed`, `todoDay()`(완료일 → 지연일 → 생성일)
- 반복 전개(`lib/logic/recurrence.dart` `occurrencesIn`): 지연 중인 반복 할일은 기준일 칸 전개에서 뺀다.
- 달력(`lib/screens/calendar_tab.dart`)
  - 월 칸과 날짜 선택 목록이 `todoDay()` 기준이다.
  - 지연 중인 반복 할일은 오늘 칸에 표시한다(숨김 반복 포함).
  - 오늘 목록의 "지연" 표시는 `isDelayed` 또는 기한 경과 기준이다.
- 목록(`lib/screens/list_tab.dart`): "지연" 표시가 `isDelayed` 또는 기한 경과 기준이다.
- 편집기(`lib/widgets/todo_edit_sheet.dart`): 저장할 때 미완료 할일의 생성일이 오늘 이전이면 `delayDate = 오늘`, 아니면 비운다. 사용자가 생성일을 오늘로 바꾸면 "오늘"이 된다.

## 검증
- 두 앱 모두 `flutter analyze`: No issues found
- 두 앱 모두 `flutter test`: 전부 통과
  - `test/todo_carry_test.dart`가 확인하는 것:
    - 지연 중이면 지연일 칸에만 있고 생성일·기한·메모는 유지
    - 지연 중 완료하면 완료일 칸
    - 일정은 대상이 아님
    - 지연 반복 할일은 기준일 전개에서 빠지고 다음 회차는 유지
- DB 갱신과 분리는 기기에서 확인이 필요하다. 테스트 환경에 SQLite가 없다.
