Zielkette, Stufe 1: jede Aufgabe kann sagen, wofuer sie da ist (#714)
- TaskItem.ParentId, in der Datei als parent, in der DB als ParentId. Die
Elternaufgabe ist der Zweck ("wofuer"), blocked_by die Voraussetzung
("wann").
- GoalChain: Eltern bis zu acht Stufen, Zyklen brechen den Gang ab und
werden beim Setzen abgelehnt. Oben steht das Ziel der Einheit - der
obersten Aufgabe, die zu einer Einheit gehoert (ein Ziel, das ein Mensch
auf der obersten Ebene setzt, gehoert keiner), sonst der naechsten Einheit
darueber mit Ziel.
- Der Lauf einer Aufgabe bekommt den Absatz "Why this task exists" und die
Bitte, zu melden, wenn die Aufgabe ihrem Zweck nicht mehr dient.
- Taskboard-Tool: parent bei task_create/task_update, geprueft auf Existenz,
Sichtbarkeit (#684) und Zyklen; task_list zeigt "Serves".
- WebUI: Feld im Aufgabendialog, Anzeige auf der Karte; die Verwaltung
prueft wie das Tool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b75b68c786
commit
b22272345e
@@ -20,7 +20,8 @@ public sealed record TaskInput(
|
||||
string? WhenValue = null,
|
||||
string? TimeZone = null,
|
||||
string Acceptance = "",
|
||||
int Priority = 3);
|
||||
int Priority = 3,
|
||||
string ParentId = "");
|
||||
|
||||
/// <summary>
|
||||
/// Das Taskboard fuer die Oberflaechen (#594): anlegen, aendern, pausieren, sofort ausfuehren,
|
||||
@@ -85,6 +86,8 @@ public sealed class TaskAdministration(
|
||||
|
||||
if (Pruefe(input, out var assignee, out var when) is { } fehler)
|
||||
return AdminResult.Failure(fehler);
|
||||
if (await CheckParentAsync(null, input.ParentId, ct) is { } parentProblem)
|
||||
return AdminResult.Failure(parentProblem);
|
||||
|
||||
var created = await board.CreateAsync(new TaskItem
|
||||
{
|
||||
@@ -95,7 +98,8 @@ public sealed class TaskAdministration(
|
||||
Assignee = assignee,
|
||||
When = when,
|
||||
Status = TaskItemStatus.Todo,
|
||||
Type = TaskItemType.Work
|
||||
Type = TaskItemType.Work,
|
||||
ParentId = input.ParentId.Trim()
|
||||
}, ct);
|
||||
|
||||
return AdminResult.Success(MitHinweis($"Aufgabe '{created.Title}' angelegt ({created.Id}).", created));
|
||||
@@ -108,6 +112,8 @@ public sealed class TaskAdministration(
|
||||
|
||||
if (Pruefe(input, out var assignee, out var when) is { } fehler)
|
||||
return AdminResult.Failure(fehler);
|
||||
if (await CheckParentAsync(id, input.ParentId, ct) is { } parentProblem)
|
||||
return AdminResult.Failure(parentProblem);
|
||||
|
||||
var updated = await board.UpdateAsync(id, current => current with
|
||||
{
|
||||
@@ -116,7 +122,8 @@ public sealed class TaskAdministration(
|
||||
Acceptance = input.Acceptance.Trim(),
|
||||
Priority = Math.Clamp(input.Priority, 1, 5),
|
||||
Assignee = assignee,
|
||||
When = when
|
||||
When = when,
|
||||
ParentId = input.ParentId.Trim()
|
||||
}, ct);
|
||||
|
||||
return updated is null
|
||||
@@ -124,6 +131,19 @@ public sealed class TaskAdministration(
|
||||
: AdminResult.Success(MitHinweis($"Aufgabe '{updated.Title}' gespeichert.", updated));
|
||||
}
|
||||
|
||||
/// <summary>#714: The parent must exist and must not lie below the task itself.</summary>
|
||||
private async Task<string?> CheckParentAsync(string? taskId, string parentId, CancellationToken ct)
|
||||
{
|
||||
parentId = parentId.Trim();
|
||||
if (parentId.Length == 0 || board is null)
|
||||
return null;
|
||||
if (await board.GetAsync(parentId, ct) is null)
|
||||
return $"There is no task '{parentId}' to serve as parent.";
|
||||
if (taskId is not null && await GoalChain.WouldCycleAsync(taskId, parentId, board.GetAsync, ct))
|
||||
return $"'{parentId}' lies below '{taskId}' (or is the task itself) — a task cannot serve itself.";
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Pausieren heisst <c>backlog</c>, Aktivieren <c>todo</c> - dieselben Zustaende, die der Scanner kennt.</summary>
|
||||
public async Task<AdminResult> SetEnabledAsync(string id, bool enabled, CancellationToken ct)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user