fix(server): report a nest's units with its sheet sizes

GET /api/nests/{id}/sheets returns sizes as stored, in the nest's units,
but did not say which. MaterialLedger compares them with inches, so a
millimeter nest's sheets never matched inventory. The response now carries
units (inches or millimeters), read the way NestReader reads them: absent
or unknown values are inches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-25 17:41:29 -04:00
1 parent be01124a0f
commit 548522775f
4 files changed
+60 -2

No files matched your search

+3
View File
@@ -10,6 +10,9 @@ public sealed class NestSheets
public int Revision { get; init; }
/// <summary>The nest's units, which every size in <see cref="Sheets"/> is stored in.</summary>
public Units Units { get; init; }
/// <summary>One entry per distinct sheet size, ordered by width, then length.</summary>
public IReadOnlyList<NestSheetSize> Sheets { get; init; } = Array.Empty<NestSheetSize>();
+47
View File
@@ -236,6 +236,53 @@ public sealed class NestSheetsTests
Assert.All(sheets.Plates.Skip(1), p => Assert.Null(p.ExpectedRemnant));
}
[Fact]
public async Task Units_AreTheNestsAndSizesStayAsStored()
{
using var factory = new ServerFactory();
using var client = factory.CreateClient();
var nest = NestApiTests.SyntheticNest();
nest.Units = Units.Millimeters;
var id = await Create(client, nest);
var json = await client.GetStringAsync($"/api/nests/{id}/sheets");
using var document = JsonDocument.Parse(json);
Assert.Equal("millimeters", document.RootElement.GetProperty("units").GetString());
var sheets = JsonSerializer.Deserialize<NestSheets>(json, Options)!;
Assert.Equal(Units.Millimeters, sheets.Units);
Assert.Equal([(20.0, 30.0, 1)], Sizes(sheets));
}
[Fact]
public async Task InchNest_ReportsInches()
{
using var factory = new ServerFactory();
using var client = factory.CreateClient();
var nest = NestApiTests.SyntheticNest();
nest.Units = Units.Inches;
var sheets = await Sheets(client, nest);
Assert.Equal(Units.Inches, sheets.Units);
}
// NestReader keeps the default units when the field is absent or unrecognized.
[Theory]
[InlineData("")]
[InlineData("\"units\":\"furlongs\",")]
public async Task AbsentOrUnknownUnits_AreInches(string unitsField)
{
using var factory = new ServerFactory();
using var client = factory.CreateClient();
var archive = TestNests.Zip("nest.json", "{" + unitsField + "\"drawings\":[],\"plates\":[]}");
var id = await Create(client, archive);
var sheets = (await client.GetFromJsonAsync<NestSheets>($"/api/nests/{id}/sheets", Options))!;
Assert.Equal(Units.Inches, sheets.Units);
}
[Fact]
public async Task UnknownNest_Returns404()
{
+5
View File
@@ -106,10 +106,14 @@ internal static class NestArchive
})
.ToList();
// NestReader: absent or unknown units keep the nest default, inches.
var units = Enum.TryParse<Units>(nest.Units, ignoreCase: true, out var parsed) ? parsed : Units.Inches;
sheets = new NestSheets
{
NestId = record.Id,
Revision = record.Revision,
Units = units,
Sheets = sizes,
Parts = parts,
Plates = platesOut,
@@ -178,6 +182,7 @@ internal static class NestArchive
private sealed class NestJson
{
public string? Units { get; set; }
public double Thickness { get; set; }
public MaterialJson? Material { get; set; } = new();
public PlateDefaultsJson? PlateDefaults { get; set; }
+5 -2
View File
@@ -126,8 +126,11 @@ Restore appends a new snapshot, never rewrites its source or intervening history
`/sheets` lists what a nest needs cut, for inventory holds (MaterialLedger). It
skips the trailing empty new-plate slot, as `plateCount` does. Sheets group by
size with `width` the shorter side, sizes as stored (unrounded), `quantity` the
sum of those plates' quantities, ordered by width then length. `parts` has one
size with `width` the shorter side, sizes as stored (unrounded, in the nest's
`units`: `inches` or `millimeters`, inches when the archive has none or an
unknown value, as `NestReader` reads it), `quantity` the sum of those plates'
quantities, ordered by width then length. Callers that work in one unit, such
as MaterialLedger's inches, must convert using `units`. `parts` has one
entry per drawing, ordered by name: `required` is the drawing's required
quantity, `nested` its parts on each plate times that plate's quantity; parts
whose drawing is missing are skipped.