Files
ClawdDotNet/src/ClawdDotNet.Core/Tasks/TaskBoard.cs
T
RichardandClaude Opus 5.5 29d953a5eb Brett je Einheit, Stufe 3: Aufgaben sehen, wer sie sehen soll (#684)
- TaskBoard: das Brett einer Aufgabe wird nicht gespeichert, es folgt aus
  der Organisation - Einheit des Zustaendigen, sonst des Anlegers. Offene
  Aufgaben ziehen damit beim Umzug mit. Sichtbar: das eigene Brett, fuer eine
  Leitung alle darunter, und was man selbst angelegt hat (nach oben
  Gereichtes bleibt im Blick).
- TaskItem.CreatedBy, in der Datei als created_by und in der DB. Ein Import
  aus einer Datei ohne das Feld behaelt, was die DB weiss.
- Taskboard-Tool: task_list, task_find und die Dublettenpruefung sehen nur
  die eigenen Bretter - eine aehnliche Buchhaltungsaufgabe haelt keine
  Marketingaufgabe auf und bekommt keinen Kommentar von dort. task_update und
  task_comment antworten auf eine fremde Aufgabe wie auf eine unbekannte.
- TaskboardService.ListAsync mit Sichtfilter: erst filtern, dann kappen.
- WebUI: jede Aufgabe nennt ihr Brett, ein Filter waehlt eines aus.
- Konzept (Abschnitt 3) und Einrichtungshandbuch nachgetragen.

Ohne Einheiten aendert sich nichts - ein Brett fuer alle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:03:39 +02:00

79 lines
3.3 KiB
C#

using ClawdDotNet.Core.Organisation;
namespace ClawdDotNet.Core.Tasks;
/// <summary>
/// #684: One board per unit. With 50 agents one board for everything is worthless as an
/// overview — the test instance had 159 tasks with seven agents — and a bookkeeping task
/// could slow down the creation of a marketing task through the duplicate check.
///
/// <para><b>The board of a task is not stored, it follows from the organisation:</b> the unit
/// of the agent it is assigned to, or — for a task for a human or a fresh run — the unit of
/// the agent that created it. So open tasks move with an agent that moves (Organisation-
/// Konzept, section 3), and nothing has to be migrated. A task that belongs to no unit is on
/// the board of the top level.</para>
///
/// <para><b>Who sees which board:</b> an agent its own unit's; a lead also the boards of every
/// unit below the one it leads; everyone the tasks they created themselves, so a task handed
/// up to the lead stays in view of whoever handed it up. A task across units is a delegation
/// to the other unit's lead — the address book decides that, not this.</para>
///
/// <para><b>Without units nothing changes:</b> there is one board, and everyone sees it.</para>
/// </summary>
public static class TaskBoard
{
/// <summary>The unit whose board the task is on; <c>null</c> for the top level.</summary>
public static string? UnitOf(TaskItem task, OrgChart org)
{
var assigned = TaskAssignee.AgentId(task.Assignee);
if (assigned.Length > 0 && org.Agent(assigned) is not null)
return org.UnitOf(assigned)?.Id;
if (!string.IsNullOrWhiteSpace(task.CreatedBy) && org.Agent(task.CreatedBy) is not null)
return org.UnitOf(task.CreatedBy)?.Id;
return null;
}
/// <summary>
/// What an agent may see — <c>null</c> for "everything": without units, without an
/// organisation (outside the engine), or for an agent the organisation does not know.
/// </summary>
public static Func<TaskItem, bool>? VisibleTo(OrgChart? org, string agentId)
{
if (org is not { HasUnits: true } || org.Agent(agentId) is null)
return null;
var boards = BoardsOf(org, agentId);
var topLevel = org.UnitOf(agentId) is null;
return task =>
string.Equals(task.CreatedBy, agentId, StringComparison.OrdinalIgnoreCase)
|| (UnitOf(task, org) is { } unit ? boards.Contains(unit) : topLevel);
}
/// <summary>The units whose boards an agent sees: its own, and all below the unit it leads.</summary>
public static IReadOnlySet<string> BoardsOf(OrgChart org, string agentId)
{
var boards = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (org.UnitOf(agentId) is { } own)
boards.Add(own.Id);
if (org.LedBy(agentId) is { } led)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var pending = new Stack<string>();
pending.Push(led.Id);
while (pending.TryPop(out var unitId))
{
if (!seen.Add(unitId))
continue; // a broken tree must not hang this
boards.Add(unitId);
foreach (var child in org.Children(unitId))
pending.Push(child.Id);
}
}
return boards;
}
}