fix(claude-do): merge ScheduledFor als UTC speichern — geplante Tasks starten aktu
ClaudeDo-Task: ea2b5950-baff-4631-ad0d-6c156a01267e
This commit is contained in:
@@ -121,7 +121,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
|
|||||||
public bool HasBranch => !string.IsNullOrWhiteSpace(Branch);
|
public bool HasBranch => !string.IsNullOrWhiteSpace(Branch);
|
||||||
public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0;
|
public bool HasDiff => DiffAdditions > 0 || DiffDeletions > 0;
|
||||||
public bool HasSteps => StepsCount > 0;
|
public bool HasSteps => StepsCount > 0;
|
||||||
public bool IsOverdue => ScheduledFor is { } d && d.Date < DateTime.Today && !Done;
|
public bool IsOverdue => ScheduledFor is { } d && d.ToLocalTime().Date < DateTime.Today && !Done;
|
||||||
public bool IsRunning => Status == TaskStatus.Running;
|
public bool IsRunning => Status == TaskStatus.Running;
|
||||||
public bool IsWaitingForReview => Status == TaskStatus.WaitingForReview;
|
public bool IsWaitingForReview => Status == TaskStatus.WaitingForReview;
|
||||||
// Parked = set aside from review: Idle but still holding its Active worktree (vs a plain Idle
|
// Parked = set aside from review: Idle but still holding its Active worktree (vs a plain Idle
|
||||||
|
|||||||
@@ -637,7 +637,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
|||||||
|
|
||||||
if (r.Done && !underOpenPlanningParent)
|
if (r.Done && !underOpenPlanningParent)
|
||||||
completed.Add(r);
|
completed.Add(r);
|
||||||
else if (r.ScheduledFor is { } d && d.Date < today)
|
else if (r.ScheduledFor is { } d && d.ToLocalTime().Date < today)
|
||||||
overdue.Add(r);
|
overdue.Add(r);
|
||||||
else
|
else
|
||||||
open.Add(r);
|
open.Add(r);
|
||||||
@@ -1361,12 +1361,18 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
|||||||
public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
|
public async Task SetScheduledForAsync(TaskRowViewModel row, DateTime? when)
|
||||||
{
|
{
|
||||||
if (row is null) return;
|
if (row is null) return;
|
||||||
|
// ThemedDatePicker builds Unspecified/Local wall-clock values; the DB's UtcConverter only
|
||||||
|
// tags DateTimes as Utc on read, it never converts on write, so this is the one place that
|
||||||
|
// must turn "local wall clock" into a real UTC instant before it's persisted.
|
||||||
|
var whenUtc = when is { } w
|
||||||
|
? (w.Kind == DateTimeKind.Utc ? w : DateTime.SpecifyKind(w, DateTimeKind.Local).ToUniversalTime())
|
||||||
|
: (DateTime?)null;
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
|
var entity = await db.Tasks.FirstOrDefaultAsync(t => t.Id == row.Id);
|
||||||
if (entity is null) return;
|
if (entity is null) return;
|
||||||
entity.ScheduledFor = when;
|
entity.ScheduledFor = whenUtc;
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
row.ScheduledFor = when;
|
row.ScheduledFor = whenUtc;
|
||||||
Regroup();
|
Regroup();
|
||||||
UpdateSubtitle();
|
UpdateSubtitle();
|
||||||
TasksChanged?.Invoke(this, EventArgs.Empty);
|
TasksChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ public partial class TaskRowView : UserControl
|
|||||||
{
|
{
|
||||||
if (DataContext is not TaskRowViewModel row) return;
|
if (DataContext is not TaskRowViewModel row) return;
|
||||||
_pendingScheduleRow = row;
|
_pendingScheduleRow = row;
|
||||||
ScheduleDate.SelectedDate = row.ScheduledFor ?? DateTime.Now.AddHours(1);
|
ScheduleDate.SelectedDate = row.ScheduledFor?.ToLocalTime() ?? DateTime.Now.AddHours(1);
|
||||||
ScheduleAnchor.Flyout?.ShowAt(ScheduleAnchor);
|
ScheduleAnchor.Flyout?.ShowAt(ScheduleAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -346,4 +346,24 @@ public class TasksIslandRegroupTests : IDisposable
|
|||||||
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Items.Select(r => r.Id));
|
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Items.Select(r => r.Id));
|
||||||
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Rows.OfType<TaskRowViewModel>().Select(r => r.Id));
|
Assert.Equal(new[] { "t2", "t3", "t1" }, vm.Rows.OfType<TaskRowViewModel>().Select(r => r.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SetScheduledForAsync_LocalDateTime_PersistsAsUtc()
|
||||||
|
{
|
||||||
|
await SeedTasksAsync(("t1", TaskStatus.Idle, null, 0));
|
||||||
|
|
||||||
|
var vm = BuildViewModel();
|
||||||
|
await LoadAndWaitAsync(vm, UserList("list1", "Default"));
|
||||||
|
var row = vm.Items.First(r => r.Id == "t1");
|
||||||
|
|
||||||
|
// ThemedDatePicker hands SetScheduledForAsync Local/Unspecified wall-clock values.
|
||||||
|
var localWhen = DateTime.SpecifyKind(new DateTime(2026, 8, 26, 15, 0, 0), DateTimeKind.Local);
|
||||||
|
|
||||||
|
await vm.SetScheduledForAsync(row, localWhen);
|
||||||
|
|
||||||
|
await using var db = NewContext();
|
||||||
|
var entity = await db.Tasks.FirstAsync(t => t.Id == "t1");
|
||||||
|
Assert.Equal(DateTimeKind.Utc, entity.ScheduledFor!.Value.Kind);
|
||||||
|
Assert.Equal(localWhen.ToUniversalTime(), entity.ScheduledFor!.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user