namespace OpenNest.Data.MaterialLedger;
///
/// Takes item codes already on other open nests out of ledger stock rows, so two nests are
/// not planned on the same sheet before ML holds exist for either. Pure: rows are rebuilt,
/// never changed.
///
public static class LedgerCodeFilter
{
///
/// without : every copy of a code
/// leaves the row's and
/// , its quantity drops by the copies removed, and
/// a row left with none is dropped. Row ids and order are kept.
/// counts the sheets and drops taken out.
///
public static IReadOnlyList Exclude(IEnumerable rows,
IEnumerable codesInUse, out int removed)
{
ArgumentNullException.ThrowIfNull(rows);
ArgumentNullException.ThrowIfNull(codesInUse);
var exclude = codesInUse as IReadOnlySet ?? codesInUse.ToHashSet(StringComparer.Ordinal);
return Rebuild(rows, exclude.Contains, out removed);
}
///
/// with only of each code taken out, in
/// row and ledger order: a bundle whose one code repeats per sheet loses as many sheets
/// as were taken, not the whole bundle. Otherwise as
/// .
///
public static IReadOnlyList ExcludeCopies(IEnumerable rows,
IReadOnlyDictionary copies, out int removed)
{
ArgumentNullException.ThrowIfNull(rows);
ArgumentNullException.ThrowIfNull(copies);
var left = new Dictionary(copies, StringComparer.Ordinal);
return Rebuild(rows, code =>
{
if (!left.TryGetValue(code, out var count) || count <= 0)
return false;
left[code] = count - 1;
return true;
}, out removed);
}
// takeOut is asked once per copy, in row order and the ledger order within a row.
private static IReadOnlyList Rebuild(IEnumerable rows,
Func takeOut, out int removed)
{
removed = 0;
var kept = new List();
foreach (var row in rows)
{
if (row == null)
continue;
var codes = row.ItemCodes.Where(code => !takeOut(code)).ToList();
var dropped = row.ItemCodes.Count - codes.Count;
if (dropped == 0)
{
kept.Add(row);
continue;
}
removed += dropped;
if (codes.Count == 0)
continue;
var left = codes.ToHashSet(StringComparer.Ordinal);
var itemLocations = row.ItemLocations
.Where(pair => left.Contains(pair.Key) || !row.ItemCodes.Contains(pair.Key))
.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal);
// A location stays listed while an item left in the row is there, or when the row
// never said which item it held.
var locations = row.Locations
.Where(location => itemLocations.ContainsValue(location)
|| !row.ItemLocations.Values.Contains(location, StringComparer.Ordinal))
.ToList();
kept.Add(row with
{
Quantity = codes.Count,
ItemCodes = codes,
Locations = locations,
ItemLocations = itemLocations,
});
}
return kept;
}
///
/// with taken out of its rows and
/// the sheets removed counted in .
///
public static LedgerStockMapping Exclude(LedgerStockMapping mapping, IEnumerable codesInUse)
{
ArgumentNullException.ThrowIfNull(mapping);
var rows = Exclude(mapping.Rows, codesInUse, out var removed);
return mapping with { Rows = rows, OnOtherNests = mapping.OnOtherNests + removed };
}
}