Files
OpenNest/OpenNest.IO/Bom/BomImportGroups.cs
T
aj 81d3253580 feat(bom): validate edited part quantities
Prepare BOM rows for quantity editing:

- BomQuantity.TryParse accepts only a whole number of at least 1
  (surrounding spaces allowed; no sign, decimal, exponent, separator or
  overflow).
- BomPartRow keeps the BOM's own quantity (BomQty), refuses invalid
  typed quantities in TrySetQuantity, and raises PropertyChanged for
  Material, Thickness and Qty plus the status they affect, so a bound
  grid can refresh.
- A blank BOM quantity becomes 1, as it was imported before, but is now
  visible in the row and counted in the summary ("had no BOM quantity
  (1 used)"). A BOM quantity below 1 is no longer imported as 0: the
  row reads "Needs quantity" until the operator enters one.

BomImportFlowTests covers BOM items to rows to an edited quantity to the
group total and the created nest's required quantity. Mutation reds:
a zero floor, signed or decimal input, ignoring the quantity in the
status, not assuming 1 for blanks, setting unparsed text and dropping
the status notification each fail the Bom tests.
2026-10-05 17:56:35 -04:00

62 lines
1.9 KiB
C#

using System.Collections.Generic;
using System.Linq;
namespace OpenNest.IO.Bom;
/// <summary>
/// The rows of one material and thickness; each group becomes one nest.
/// </summary>
public sealed class BomImportGroup
{
public BomImportGroup(string material, double thickness, IReadOnlyList<BomPartRow> parts)
{
Material = material;
Thickness = thickness;
Parts = parts;
}
/// <summary>The material as the group's first row spells it.</summary>
public string Material { get; }
public double Thickness { get; }
public IReadOnlyList<BomPartRow> Parts { get; }
/// <summary>Sum of the rows' quantities.</summary>
public int TotalQty => Parts.Sum(p => p.Qty ?? 0);
public string Key => BomImportGroups.Key(Material, Thickness);
}
/// <summary>
/// Groups importable rows by material and thickness. The Groups tab and
/// Create Nests both use this, so they always agree.
/// </summary>
public static class BomImportGroups
{
/// <summary>
/// Returns the groups of <see cref="BomRowStatus.Ready"/> rows. Material
/// is compared case-insensitively. Groups are ordered by material, then
/// thickness.
/// </summary>
public static List<BomImportGroup> Build(IEnumerable<BomPartRow> rows)
{
return rows.Where(p => p.Status == BomRowStatus.Ready)
.GroupBy(p => new
{
Material = p.Material.ToUpperInvariant(),
Thickness = p.Thickness.Value,
})
.OrderBy(g => g.First().Material)
.ThenBy(g => g.Key.Thickness)
.Select(g => new BomImportGroup(g.First().Material, g.Key.Thickness, g.ToList()))
.ToList();
}
/// <summary>
/// The key that identifies a group's plate settings across regrouping.
/// </summary>
public static string Key(string material, double thickness) =>
$"{material?.ToUpperInvariant()}|{thickness}";
}