Distribution_GL Initialization
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
namespace General_Ledger_Service.Repositories
|
||||
{
|
||||
// Shared tree logic for every self-referencing entity (Account, CostCenter — see
|
||||
// 01_Full_Development_Plan.md Section 4.2). LINQ/EF Core cannot express a recursive CTE, so the
|
||||
// calling repository loads the entity's rows once as TreeRow (a plain id/code/name/parent
|
||||
// projection) and these methods walk that in-memory list. Pure functions: no DbContext, no SQL,
|
||||
// so the same code works on any database provider. This is a shared static helper, not a
|
||||
// generic repository base class (CLAUDE.md Rule 2) — each repository keeps its own public
|
||||
// interface, method signatures, and result DTOs; only the tree-walking is shared.
|
||||
internal static class RecursiveTreeQueries
|
||||
{
|
||||
internal class TreeRow
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public long? ParentId { get; set; }
|
||||
public int Depth { get; set; }
|
||||
}
|
||||
|
||||
// Depth a new child of parentId would sit at: the number of nodes from parentId up to its
|
||||
// root, plus one. A parentId that matches no row yields 1.
|
||||
public static int GetAncestorDepth(IReadOnlyList<TreeRow> rows, long parentId)
|
||||
{
|
||||
var byId = rows.ToDictionary(r => r.Id);
|
||||
var count = 0;
|
||||
var visited = new HashSet<long>();
|
||||
long? current = parentId;
|
||||
|
||||
while (current.HasValue && byId.TryGetValue(current.Value, out var node) && visited.Add(node.Id))
|
||||
{
|
||||
count++;
|
||||
current = node.ParentId;
|
||||
}
|
||||
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
// "Root > ... > Entity" path of names, or null if the entity does not exist.
|
||||
public static string? GetAncestorBreadcrumb(IReadOnlyList<TreeRow> rows, long entityId)
|
||||
{
|
||||
var byId = rows.ToDictionary(r => r.Id);
|
||||
var names = new List<string>();
|
||||
var visited = new HashSet<long>();
|
||||
long? current = entityId;
|
||||
|
||||
while (current.HasValue && byId.TryGetValue(current.Value, out var node) && visited.Add(node.Id))
|
||||
{
|
||||
names.Add(node.Name);
|
||||
current = node.ParentId;
|
||||
}
|
||||
|
||||
if (names.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
names.Reverse();
|
||||
return string.Join(" > ", names);
|
||||
}
|
||||
|
||||
// The entity plus every descendant, depth 1 for the entity itself, ordered by depth then code.
|
||||
public static List<TreeRow> GetDescendantTree(IReadOnlyList<TreeRow> rows, long entityId)
|
||||
{
|
||||
var childrenByParent = rows
|
||||
.Where(r => r.ParentId.HasValue)
|
||||
.ToLookup(r => r.ParentId!.Value);
|
||||
|
||||
var root = rows.FirstOrDefault(r => r.Id == entityId);
|
||||
if (root is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = new List<TreeRow>();
|
||||
var visited = new HashSet<long>();
|
||||
var queue = new Queue<(TreeRow Node, int Depth)>();
|
||||
queue.Enqueue((root, 1));
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var (node, depth) = queue.Dequeue();
|
||||
if (!visited.Add(node.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(new TreeRow
|
||||
{
|
||||
Id = node.Id,
|
||||
Code = node.Code,
|
||||
Name = node.Name,
|
||||
ParentId = node.ParentId,
|
||||
Depth = depth
|
||||
});
|
||||
|
||||
foreach (var child in childrenByParent[node.Id])
|
||||
{
|
||||
queue.Enqueue((child, depth + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return [.. result
|
||||
.OrderBy(r => r.Depth)
|
||||
.ThenBy(r => r.Code, StringComparer.Ordinal)];
|
||||
}
|
||||
|
||||
// Ids of the entity and all of its descendants; empty if the entity does not exist.
|
||||
public static HashSet<long> GetDescendantIds(IReadOnlyList<TreeRow> rows, long entityId) =>
|
||||
[.. GetDescendantTree(rows, entityId).Select(r => r.Id)];
|
||||
|
||||
// True if newParentId is the entity itself or one of its descendants, i.e. making it the
|
||||
// entity's parent would create a loop.
|
||||
public static bool WouldCreateCycle(IReadOnlyList<TreeRow> rows, long entityId, long newParentId) =>
|
||||
GetDescendantIds(rows, entityId).Contains(newParentId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user