- 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>
79 lines
3.3 KiB
C#
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;
|
|
}
|
|
}
|