mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 09:01:07 -04:00
Auto Nest on MaterialLedger stock now records which inventory item each
physical sheet uses, so ML can hold exactly those items instead of matching
by size.
- Core: Plate.StockItemCodes (entry i = sheet i of Quantity; empty = uncoded).
Core invalidates it: a resize clears it (turning the sheet keeps it), a lower
Quantity truncates it, a nest material/grade/thickness change clears every
plate's, ClearAll clears it and new plates never copy it.
- Desktop Auto Nest: after a kept proposal is committed, LedgerStockAssignment
deals each ledger row's codes to its plates in Nest.Plates order (bundles
repeat their code; clamp-removal variants use their base row). Rejected,
discarded or cancelled proposals never reach it. NestPipelineCommit.PairWithStock
pairs committed plates with their solved stock; the engine stays ledger-free.
- Plate list tooltip: "Stock: CODE (location)"; locations are display-only.
- nest.json: optional plate stockItemCodes, omitted when empty, read leniently.
- /api/nests/{id}/sheets: new plates[] { index, width, length, quantity,
stockItemCodes }; sheets[] unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
2.7 KiB
C#
80 lines
2.7 KiB
C#
using System.Text.Json.Nodes;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Shapes;
|
|
using static OpenNest.IO.Tests.NestArchiveJson;
|
|
|
|
namespace OpenNest.IO.Tests;
|
|
|
|
/// <summary>
|
|
/// A plate's stock item codes survive the .nest round trip as an optional
|
|
/// <c>stockItemCodes</c> array; files without it load with no codes.
|
|
/// </summary>
|
|
public class StockItemCodesPersistenceTests
|
|
{
|
|
private static Nest NestWithPlate(int quantity, params string[] codes)
|
|
{
|
|
var nest = new Nest("coded") { Material = new Material("Mild Steel", "A36"), Thickness = 0.25 };
|
|
var drawing = new RectangleShape { Name = "SYN", Length = 4, Width = 2 }.GetDrawing();
|
|
nest.Drawings.Add(drawing);
|
|
var plate = new Plate(30, 52.5) { Quantity = quantity };
|
|
plate.Parts.Add(new Part(drawing, new Vector(1, 1)));
|
|
nest.Plates.Add(plate);
|
|
plate.StockItemCodes = codes;
|
|
return nest;
|
|
}
|
|
|
|
private static byte[] Write(Nest nest)
|
|
{
|
|
using var stream = new MemoryStream();
|
|
Assert.True(new NestWriter(nest).Write(stream));
|
|
return stream.ToArray();
|
|
}
|
|
|
|
[Fact]
|
|
public void CodesRoundTripInSheetOrder()
|
|
{
|
|
var (reopened, _) = RoundTrip(Write(NestWithPlate(3, "SHEET-456", "SHEET-456")));
|
|
|
|
var plate = Assert.Single(reopened.Plates);
|
|
Assert.Equal(3, plate.Quantity);
|
|
Assert.Equal(["SHEET-456", "SHEET-456"], plate.StockItemCodes);
|
|
Assert.Equal("Mild Steel", reopened.Material.Name);
|
|
}
|
|
|
|
[Fact]
|
|
public void UncodedPlate_WritesNoKey()
|
|
{
|
|
var json = NestJson(Write(NestWithPlate(1)));
|
|
|
|
Assert.False(json["plates"]![0]!.AsObject().ContainsKey("stockItemCodes"));
|
|
}
|
|
|
|
[Fact]
|
|
public void OldFileWithoutTheKey_LoadsWithNoCodes()
|
|
{
|
|
var archive = Write(NestWithPlate(1, "DROP-1182"));
|
|
var json = NestJson(archive);
|
|
Assert.True(json["plates"]![0]!.AsObject().Remove("stockItemCodes"));
|
|
|
|
var (reopened, _) = RoundTrip(ReplaceNestJson(archive, json));
|
|
|
|
Assert.Empty(Assert.Single(reopened.Plates).StockItemCodes);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("\"DROP-1\"", new string[0])]
|
|
[InlineData("[\"DROP-1\", 7, \"DROP-2\"]", new[] { "DROP-1", "DROP-2" })]
|
|
[InlineData("[\"DROP-1\", \"\", \"DROP-2\"]", new[] { "DROP-1" })]
|
|
[InlineData("[\"A\", \"B\", \"C\", \"D\"]", new[] { "A", "B", "C" })]
|
|
public void MalformedCodes_ReadLeniently(string value, string[] expected)
|
|
{
|
|
var archive = Write(NestWithPlate(3, "X"));
|
|
var json = NestJson(archive);
|
|
json["plates"]![0]!["stockItemCodes"] = JsonNode.Parse(value);
|
|
|
|
var (reopened, _) = RoundTrip(ReplaceNestJson(archive, json));
|
|
|
|
Assert.Equal(expected, Assert.Single(reopened.Plates).StockItemCodes);
|
|
}
|
|
}
|