feat(03-01): implement DailyPlanDao with cross-room join query and completion count

- watchAllTasksWithRoomName: innerJoin tasks+rooms, sorted by nextDueDate asc
- watchCompletionsToday: customSelect with readsFrom for proper stream invalidation
- All 7 DAO unit tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 12:29:24 +01:00
parent 74b3bd5543
commit ad70eb7ff1

View File

@@ -11,14 +11,42 @@ class DailyPlanDao extends DatabaseAccessor<AppDatabase>
DailyPlanDao(super.attachedDatabase); DailyPlanDao(super.attachedDatabase);
/// Watch all tasks joined with room name, sorted by nextDueDate ascending. /// Watch all tasks joined with room name, sorted by nextDueDate ascending.
/// Includes ALL tasks (overdue, today, future) -- filtering is done in the
/// provider layer to avoid multiple queries.
Stream<List<TaskWithRoom>> watchAllTasksWithRoomName() { Stream<List<TaskWithRoom>> watchAllTasksWithRoomName() {
// TODO: implement final query = select(tasks).join([
throw UnimplementedError(); innerJoin(rooms, rooms.id.equalsExp(tasks.roomId)),
]);
query.orderBy([OrderingTerm.asc(tasks.nextDueDate)]);
return query.watch().map((rows) {
return rows.map((row) {
final task = row.readTable(tasks);
final room = row.readTable(rooms);
return TaskWithRoom(
task: task,
roomName: room.name,
roomId: room.id,
);
}).toList();
});
} }
/// Count task completions recorded today. /// Count task completions recorded today.
/// Uses customSelect with readsFrom for proper stream invalidation.
Stream<int> watchCompletionsToday({DateTime? today}) { Stream<int> watchCompletionsToday({DateTime? today}) {
// TODO: implement final now = today ?? DateTime.now();
throw UnimplementedError(); final startOfDay = DateTime(now.year, now.month, now.day);
final endOfDay = startOfDay.add(const Duration(days: 1));
return customSelect(
'SELECT COUNT(*) AS c FROM task_completions '
'WHERE completed_at >= ? AND completed_at < ?',
variables: [
Variable(startOfDay.millisecondsSinceEpoch ~/ 1000),
Variable(endOfDay.millisecondsSinceEpoch ~/ 1000),
],
readsFrom: {taskCompletions},
).watchSingle().map((row) => row.read<int>('c'));
} }
} }