mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 01:31:06 -04:00
feat(server): keep a nest's units on its record
A record's thickness is in the nest's units, but nothing on the record said which, so a list of nests could not tell 9.525 mm from 9.525 inches without reading each archive. Records now carry units, read from the uploaded archive like the counts (absent or unknown values are inches) and kept by a metadata-only save. Schema v12 adds the column and reads every stored archive, current and historical, so existing millimeter nests report it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
610b9dafcb
commit
2d8d877482
12 files changed
+185
-20
No files matched your search
@@ -34,6 +34,12 @@ public sealed class NestRecord
|
||||
|
||||
public double Thickness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The nest's units, which <see cref="Thickness"/> is in. The server reads them from the
|
||||
/// archive like the counts; inches when the archive has none or the server predates them.
|
||||
/// </summary>
|
||||
public Units Units { get; set; }
|
||||
|
||||
/// <summary>quote | toBeCut | hasBeenCut (camelCase on the wire, case-insensitive readers).</summary>
|
||||
public NestStatus Status { get; set; } = NestStatus.Quote;
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ public static class NestRecordFactory
|
||||
Material = nest.Material?.Name ?? "",
|
||||
MaterialGrade = nest.Material?.Grade ?? "",
|
||||
Thickness = nest.Thickness,
|
||||
Units = nest.Units,
|
||||
Status = nest.Status,
|
||||
// The trailing empty plate is the editor's new-plate slot, not a plate of the nest.
|
||||
PlateCount = PlateDisplayNumbering.DisplayedPlateCount(nest.Plates),
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class DrawingLibraryDatabaseTests
|
||||
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
||||
Assert.Empty(database.QueryDrawings(new DrawingLibraryQuery { IncludeArchived = true }).Items);
|
||||
Assert.Equal(before, NestDigest(factory.DatabasePath));
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
}
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
@@ -43,7 +43,7 @@ public sealed class DrawingLibraryDatabaseTests
|
||||
|
||||
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
||||
{
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(before, NestDigest(factory.DatabasePath));
|
||||
// Two distinct blobs across four revisions: the bracket's notes edit and the
|
||||
// plate both reuse the bracket's first package.
|
||||
@@ -80,7 +80,7 @@ public sealed class DrawingLibraryDatabaseTests
|
||||
{
|
||||
using var factory = new ServerFactory();
|
||||
using (new NestDatabase(factory.DatabasePath, pooling: false)) { }
|
||||
Execute(factory.DatabasePath, "PRAGMA user_version=12");
|
||||
Execute(factory.DatabasePath, "PRAGMA user_version=13");
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => new NestDatabase(factory.DatabasePath, pooling: false));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class DrawingSetDatabaseTests
|
||||
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
||||
Assert.Empty(database.QueryDrawingSets(new DrawingSetQuery { IncludeArchived = true }).Items);
|
||||
Assert.Equal(before, Digest(factory.DatabasePath));
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
}
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
@@ -47,7 +47,7 @@ public sealed class DrawingSetDatabaseTests
|
||||
Assert.Equal(before, Digest(factory.DatabasePath));
|
||||
Execute(factory.DatabasePath, "DROP TABLE ix_drawing_set_members_drawing");
|
||||
using var database = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class MaterialListTests
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Empty(upgraded.ListMaterials());
|
||||
Assert.Equal((" Stainless ", "304"), (upgraded.Get(nest)!.Material, upgraded.Get(nest)!.MaterialGrade));
|
||||
Assert.Equal(drawing.MaterialName, upgraded.GetDrawing(drawing.Id)!.MaterialName);
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class NestMaterialGradeTests
|
||||
Assert.Equal("Stainless", record.Material);
|
||||
Assert.Equal("", record.MaterialGrade);
|
||||
Assert.Equal("", Assert.Single(upgraded.History(id, 0, 10)!.Items).MaterialGrade);
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
}
|
||||
|
||||
private static NestRecord Record(string name, string material, string grade) => new()
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using OpenNest.Data;
|
||||
|
||||
namespace OpenNest.Server.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Nest records carry the archive's units, which their thickness is in, so a list of nests can
|
||||
/// tell a millimeter nest from an inch one without reading each archive.
|
||||
/// </summary>
|
||||
public sealed class NestUnitsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Upload_TakesUnitsFromTheArchive_NotTheClientsClaim()
|
||||
{
|
||||
using var factory = new ServerFactory();
|
||||
using var client = factory.CreateClient();
|
||||
var nest = NestApiTests.SyntheticNest();
|
||||
nest.Units = Units.Millimeters;
|
||||
nest.Thickness = 6.35;
|
||||
|
||||
using var created = await client.PostAsync("/api/nests",
|
||||
NestRevisionTests.Body(new NestRecord { Name = "Metric", Units = Units.Inches }, NestApiTests.Archive(nest)));
|
||||
var json = await created.Content.ReadAsStringAsync();
|
||||
var id = JsonDocument.Parse(json).RootElement.GetProperty("id").GetGuid();
|
||||
|
||||
Assert.Equal("millimeters", JsonDocument.Parse(json).RootElement.GetProperty("units").GetString());
|
||||
using var listed = JsonDocument.Parse(await client.GetStringAsync("/api/nests/query"));
|
||||
var item = Assert.Single(listed.RootElement.GetProperty("items").EnumerateArray());
|
||||
Assert.Equal("millimeters", item.GetProperty("units").GetString());
|
||||
Assert.Equal(6.35, item.GetProperty("thickness").GetDouble());
|
||||
using var record = JsonDocument.Parse(await client.GetStringAsync($"/api/nests/{id}"));
|
||||
Assert.Equal("millimeters", record.RootElement.GetProperty("units").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MetadataOnlySave_KeepsTheArchivesUnits()
|
||||
{
|
||||
using var factory = new ServerFactory();
|
||||
using var database = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
var id = Guid.NewGuid();
|
||||
var created = database.Insert(id, Record("Metric", Units.Millimeters), [1]);
|
||||
|
||||
var updated = database.UpdateMetadata(id, Record("Metric renamed", Units.Inches), created.Revision)!;
|
||||
|
||||
Assert.Equal(Units.Millimeters, updated.Units);
|
||||
Assert.Equal([Units.Millimeters, Units.Millimeters], database.History(id, 0, 10)!.Items.Select(r => r.Units));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version11Database_ReadsEachStoredArchivesUnits()
|
||||
{
|
||||
using var factory = new ServerFactory();
|
||||
var metric = Guid.NewGuid();
|
||||
var inch = Guid.NewGuid();
|
||||
var unreadable = Guid.NewGuid();
|
||||
var metricNest = NestApiTests.SyntheticNest();
|
||||
metricNest.Units = Units.Millimeters;
|
||||
using (var database = new NestDatabase(factory.DatabasePath, pooling: false))
|
||||
{
|
||||
var created = database.Insert(metric, Record("Metric", Units.Inches), NestApiTests.Archive(metricNest));
|
||||
database.UpdateMetadata(metric, Record("Metric renamed", Units.Inches), created.Revision);
|
||||
database.Insert(inch, Record("Inch", Units.Inches), NestApiTests.Archive(NestApiTests.SyntheticNest()));
|
||||
database.Insert(unreadable, Record("Broken", Units.Inches), [1]);
|
||||
}
|
||||
Execute(factory.DatabasePath, """
|
||||
ALTER TABLE nests DROP COLUMN units;
|
||||
ALTER TABLE nest_revisions DROP COLUMN units;
|
||||
PRAGMA user_version=11;
|
||||
""");
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
|
||||
Assert.Equal(Units.Millimeters, upgraded.Get(metric)!.Units);
|
||||
Assert.Equal([Units.Millimeters, Units.Millimeters], upgraded.History(metric, 0, 10)!.Items.Select(r => r.Units));
|
||||
Assert.Equal(Units.Inches, upgraded.Get(inch)!.Units);
|
||||
Assert.Equal(Units.Inches, upgraded.Get(unreadable)!.Units);
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
}
|
||||
|
||||
private static NestRecord Record(string name, Units units) => new()
|
||||
{
|
||||
Name = name,
|
||||
Material = "Steel",
|
||||
Thickness = 0.25,
|
||||
Units = units,
|
||||
DateCreated = new DateTime(2026, 1, 1),
|
||||
DateModified = new DateTime(2026, 1, 2),
|
||||
};
|
||||
|
||||
private static void Execute(string path, string sql)
|
||||
{
|
||||
using var connection = new SqliteConnection($"Data Source={path};Pooling=False");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = sql;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private static object? Scalar(string path, string sql)
|
||||
{
|
||||
using var connection = new SqliteConnection($"Data Source={path};Pooling=False");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = sql;
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ public sealed class SetReleaseDrawingTests
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
var kept = upgraded.GetRelease(releaseId)!;
|
||||
Assert.Empty(kept.Drawings);
|
||||
Assert.Equal(((Guid?)kit.Id, drawing, 2), (kept.Nests[0].Items.Single().SetId, kept.Nests[0].Items.Single().DrawingId, kept.Nests[0].Items.Single().Quantity));
|
||||
|
||||
@@ -294,7 +294,7 @@ public sealed class SetReleaseTests
|
||||
|
||||
using var upgraded = new NestDatabase(factory.DatabasePath, pooling: false);
|
||||
|
||||
Assert.Equal(11L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(12L, Scalar(factory.DatabasePath, "PRAGMA user_version"));
|
||||
Assert.Equal(0L, Scalar(factory.DatabasePath, "SELECT COUNT(*) FROM sqlite_master WHERE name LIKE 'set_release%' OR name LIKE 'ix_set_release%'"));
|
||||
var release = upgraded.GetRelease(releaseId)!;
|
||||
Assert.Equal(("Legacy notes", "Legacy operator", false), (release.Notes, release.ReleasedBy, release.IsUndone));
|
||||
|
||||
@@ -20,7 +20,7 @@ internal static class NestArchive
|
||||
private static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web);
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites the record's plate count, part count, material and thickness with the
|
||||
/// Overwrites the record's plate count, part count, material, thickness and units with the
|
||||
/// archive's own. Returns an error when the bytes are not a readable nest archive.
|
||||
/// </summary>
|
||||
public static string? Apply(NestRecord record, byte[] file)
|
||||
@@ -33,6 +33,7 @@ internal static class NestArchive
|
||||
// falls back to the plate defaults. Thickness falls back when not positive.
|
||||
record.Material = (nest.Material ?? defaults.Material)?.Name ?? "";
|
||||
record.Thickness = nest.Thickness > 0 ? nest.Thickness : defaults.Thickness;
|
||||
record.Units = UnitsOf(nest);
|
||||
|
||||
// NestReader drops parts whose drawing is missing. Cut-off parts are never written as
|
||||
// parts, so every remaining part counts.
|
||||
@@ -106,8 +107,7 @@ 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;
|
||||
var units = UnitsOf(nest);
|
||||
|
||||
sheets = new NestSheets
|
||||
{
|
||||
@@ -121,6 +121,13 @@ internal static class NestArchive
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>The archive's units, for backfilling records stored before they were kept; inches when unreadable.</summary>
|
||||
public static Units ReadUnits(byte[] file) => Read(file, out var nest) is null ? UnitsOf(nest) : Units.Inches;
|
||||
|
||||
// NestReader: absent or unknown units keep the nest default, inches.
|
||||
private static Units UnitsOf(NestJson nest) =>
|
||||
Enum.TryParse<Units>(nest.Units, ignoreCase: true, out var parsed) ? parsed : Units.Inches;
|
||||
|
||||
private static string? Read(byte[] file, out NestJson nest)
|
||||
{
|
||||
nest = null!;
|
||||
|
||||
@@ -154,10 +154,10 @@ public sealed partial class NestDatabase : IDisposable
|
||||
command.Transaction = transaction;
|
||||
command.CommandText = """
|
||||
INSERT INTO nests (id, name, customer, dateCreated, dateModified, material,
|
||||
materialGrade, thickness, status, plateCount, partCount, comments, madeBy,
|
||||
materialGrade, thickness, units, status, plateCount, partCount, comments, madeBy,
|
||||
fileSize, savedAt, file)
|
||||
VALUES ($id, $name, $customer, $dateCreated, $dateModified, $material,
|
||||
$materialGrade, $thickness, $status, $plateCount, $partCount, $comments,
|
||||
$materialGrade, $thickness, $units, $status, $plateCount, $partCount, $comments,
|
||||
$madeBy, $fileSize, $savedAt, $file)
|
||||
""";
|
||||
AddRecordParameters(command, id, record, file, updateFile: true);
|
||||
@@ -186,9 +186,10 @@ public sealed partial class NestDatabase : IDisposable
|
||||
if (current.Revision != expectedRevision) throw new RevisionConflictException(current);
|
||||
if (file is null)
|
||||
{
|
||||
// The counts describe the archive, which a metadata-only save leaves unchanged.
|
||||
// The counts and units describe the archive, which a metadata-only save leaves unchanged.
|
||||
record.PlateCount = current.PlateCount;
|
||||
record.PartCount = current.PartCount;
|
||||
record.Units = current.Units;
|
||||
}
|
||||
file ??= GetFile(id)!;
|
||||
if (SameMetadata(current, record) && GetFile(id)!.AsSpan().SequenceEqual(file)) return current;
|
||||
@@ -210,7 +211,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
UPDATE nests SET
|
||||
name = $name, customer = $customer, dateCreated = $dateCreated,
|
||||
dateModified = $dateModified, material = $material,
|
||||
materialGrade = $materialGrade, thickness = $thickness, status = $status, plateCount = $plateCount,
|
||||
materialGrade = $materialGrade, thickness = $thickness, units = $units, status = $status, plateCount = $plateCount,
|
||||
partCount = $partCount, comments = $comments, madeBy = $madeBy,
|
||||
savedAt = $savedAt, revision = $revision, restoredFromRevision=$source{fileClause}
|
||||
WHERE id = $id
|
||||
@@ -320,7 +321,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
private static bool SameMetadata(NestRecord a, NestRecord b) =>
|
||||
a.Name == b.Name && a.Customer == b.Customer && a.DateCreated == b.DateCreated &&
|
||||
a.DateModified == b.DateModified && a.Material == b.Material && a.MaterialGrade == b.MaterialGrade &&
|
||||
a.Thickness == b.Thickness && a.Status == b.Status && a.PlateCount == b.PlateCount && a.PartCount == b.PartCount &&
|
||||
a.Thickness == b.Thickness && a.Units == b.Units && a.Status == b.Status && a.PlateCount == b.PlateCount && a.PartCount == b.PartCount &&
|
||||
a.Comments == b.Comments && a.MadeBy == b.MadeBy;
|
||||
|
||||
private void Snapshot(Guid id, SqliteTransaction transaction)
|
||||
@@ -341,7 +342,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
command.Transaction = transaction;
|
||||
command.CommandText = "PRAGMA user_version";
|
||||
var version = (long)command.ExecuteScalar()!;
|
||||
if (version > 11) throw new InvalidOperationException("Database schema is newer than this server.");
|
||||
if (version > 12) throw new InvalidOperationException("Database schema is newer than this server.");
|
||||
if (version == 0)
|
||||
{
|
||||
command.CommandText = """
|
||||
@@ -426,10 +427,47 @@ public sealed partial class NestDatabase : IDisposable
|
||||
command.CommandText = ReleaseUpgradeToV11;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
if (version < 12)
|
||||
AddUnits(command);
|
||||
transaction.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
// v12: records keep the archive's units, which their thickness is in. Every stored archive,
|
||||
// current or historical, is read once so millimeter nests are not reported as inches.
|
||||
private static void AddUnits(SqliteCommand command)
|
||||
{
|
||||
foreach (var table in new[] { "nests", "nest_revisions" })
|
||||
{
|
||||
command.CommandText = $"SELECT COUNT(*) FROM pragma_table_info('{table}') WHERE name='units'";
|
||||
if ((long)command.ExecuteScalar()! == 0)
|
||||
{
|
||||
command.CommandText = $"ALTER TABLE {table} ADD COLUMN units TEXT NOT NULL DEFAULT 'Inches'";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
var millimeters = new List<long>();
|
||||
command.CommandText = $"SELECT rowid, file FROM {table}";
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
if (NestArchive.ReadUnits((byte[])reader.GetValue(1)) == Units.Millimeters)
|
||||
millimeters.Add(reader.GetInt64(0));
|
||||
}
|
||||
}
|
||||
command.CommandText = $"UPDATE {table} SET units='Millimeters' WHERE rowid=$rowid";
|
||||
var rowid = command.Parameters.Add("$rowid", SqliteType.Integer);
|
||||
foreach (var id in millimeters)
|
||||
{
|
||||
rowid.Value = id;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
command.Parameters.Clear();
|
||||
}
|
||||
command.CommandText = "PRAGMA user_version=12";
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
/// <summary>Checks the live connection without exposing storage error details.</summary>
|
||||
public bool IsHealthy()
|
||||
{
|
||||
@@ -464,7 +502,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
private const string RecordColumns = """
|
||||
id, name, customer, dateCreated, dateModified, material, thickness,
|
||||
status, plateCount, partCount, comments, madeBy, fileSize, savedAt, revision, deleted, restoredFromRevision,
|
||||
materialGrade
|
||||
materialGrade, units
|
||||
""";
|
||||
|
||||
// Case-insensitive (ASCII) substring over the text columns and the status's stored
|
||||
@@ -557,6 +595,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
command.Parameters.AddWithValue("$material", record.Material);
|
||||
command.Parameters.AddWithValue("$materialGrade", record.MaterialGrade);
|
||||
command.Parameters.AddWithValue("$thickness", record.Thickness);
|
||||
command.Parameters.AddWithValue("$units", record.Units.ToString());
|
||||
command.Parameters.AddWithValue("$status", record.Status.ToString());
|
||||
command.Parameters.AddWithValue("$plateCount", record.PlateCount);
|
||||
command.Parameters.AddWithValue("$partCount", record.PartCount);
|
||||
@@ -594,6 +633,7 @@ public sealed partial class NestDatabase : IDisposable
|
||||
Deleted = reader.GetInt32(first + 15) != 0,
|
||||
RestoredFromRevision = reader.IsDBNull(first + 16) ? null : reader.GetInt32(first + 16),
|
||||
MaterialGrade = reader.GetString(first + 17),
|
||||
Units = Enum.TryParse<Units>(reader.GetString(first + 18), true, out var units) ? units : Units.Inches,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ entry, never programs or geometry, and takes the archive-derived fields below fr
|
||||
| `material` | string | Material name (type), e.g. `Stainless`. Read from the uploaded archive (nest material, else plate defaults' on an explicit null); a metadata-only PUT may change it, and the desktop applies the record's value when opening the nest. |
|
||||
| `materialGrade` | string | Optional; default blank. Older servers drop it. A blank grade on a record saved before schema v8 is treated as unrecorded: opening keeps the archive's grade while the material name matches. |
|
||||
| `thickness` | number | Read from the uploaded archive (nest thickness, else plate defaults' when not positive); editable like `material`. |
|
||||
| `units` | `"inches"` \| `"millimeters"` | The nest's units, which `thickness` is in. Server-computed from the uploaded archive (absent or unknown values are inches, as `NestReader` reads them); ignored on upload and kept by a metadata-only PUT. Schema v12 read every stored archive, current and historical, to fill it. Older servers omit it, which readers take as inches. |
|
||||
| `status` | `"draft"` \| `"quote"` \| `"toBeCut"` \| `"hasBeenCut"` | Default `quote`. |
|
||||
| `plateCount` / `partCount` | integer | Server-computed from the uploaded archive (non-cutoff parts only; a trailing plate with no parts or cut-offs is not counted); ignored on upload and kept by a metadata-only PUT. |
|
||||
| `comments` | string | |
|
||||
@@ -247,7 +248,8 @@ only the requested page of metadata (never archive bytes):
|
||||
can merge into): `status` (`draft`, `quote`, `toBeCut` or `hasBeenCut`),
|
||||
`material` and `materialGrade` (compared by `SharedListNames.Key`, so case and
|
||||
spacing are ignored; a present but blank `materialGrade` asks for nests with no
|
||||
grade) and `thickness` (a number, zero or greater, matched at four decimals).
|
||||
grade) and `thickness` (a number, zero or greater, matched at four decimals in
|
||||
each nest's own units).
|
||||
The client sends them only when set, so plain browsing still works against
|
||||
servers without them.
|
||||
- `offset` (default 0, at least 0) and `limit` (default 100, 1 to 500).
|
||||
|
||||
Reference in new issue
Block a user