Files
OpenNest/OpenNest.Data/MaterialLedger/LedgerStock.cs
T
ajandClaude Opus 5.5 ea1decc097 feat(autonest): load sheet and drop stock from MaterialLedger
Auto Nest's Stock Options gets a Load from ledger button that reads the nest
material's free sheets and drops from MaterialLedger's API-key-protected
GET /api/stock/opennest. Ledger rows are request-local: the engine sees them as
ordinary NestPlateStock rows with ledger:{n} ids and finite quantities, and they
are never written to Nest.PlateOptions.

- OpenNest.Data/MaterialLedger: settings file, read-only client (X-Api-Key,
  5 s timeout, closed LedgerStockResult set) and LedgerStockMapper (bundles and
  sheets one row each, same-size drops grouped with ordered item codes, unknown
  shapes and too-small items skipped, inch/mm conversion).
- Options: MaterialLedger URL and API key (DPAPI, CurrentUser) with Test.
- Auto Nest grid: Use, Qty and Source columns for every row; PlateOption gains
  request-local Quantity and Id, which NestStockBuilder passes to the engine.
  Ledger rows score by area; hand-typed costs stay saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 17:40:44 -04:00

68 lines
2.5 KiB
C#

namespace OpenNest.Data.MaterialLedger;
/// <summary>
/// The body of MaterialLedger's <c>GET /api/stock/opennest</c>: the <c>/api/stock</c> shape plus
/// <see cref="Mapped"/>, false when ML has no approved mapping for the OpenNest material.
/// </summary>
public sealed record LedgerStockResponse(
bool Mapped,
IReadOnlyList<LedgerStockSpec>? Specs,
IReadOnlyList<LedgerStockItem>? Items);
public sealed record LedgerStockSpec(long MaterialSpecId, string DisplayName, decimal ThicknessIn, string? GaugeLabel);
/// <summary>
/// Mirrors ML <c>StockApi.StockItemDto</c>. Sizes are inches with <see cref="WidthIn"/> the short
/// side; <see cref="Quantity"/> is free after active holds. <see cref="Kind"/> and
/// <see cref="Shape"/> stay strings so a new ML enum value still deserializes.
/// </summary>
public sealed record LedgerStockItem(
string ItemCode,
long MaterialSpecId,
string Kind,
decimal WidthIn,
decimal LengthIn,
int Quantity,
string Shape,
string LocationCode,
string LocationName,
string? ParentLocationCode,
decimal? CostPerUnit,
DateTime LastObservedAtUtc);
/// <summary>ML <c>InventoryItemKind</c> names.</summary>
public static class LedgerItemKinds
{
public const string FullSheetBundle = "FullSheetBundle";
public const string FullSheet = "FullSheet";
public const string Drop = "Drop";
}
/// <summary>ML <c>ShapeClassification</c> names.</summary>
public static class LedgerItemShapes
{
public const string Rectangular = "Rectangular";
public const string IrregularConservativeRectangle = "IrregularConservativeRectangle";
public const string Unknown = "Unknown";
}
/// <summary>What a ledger stock request came to. Expected failures are results, never exceptions.</summary>
public abstract record LedgerStockResult
{
private LedgerStockResult() { }
/// <summary>No ledger URL is set on this PC.</summary>
public sealed record NotConfigured : LedgerStockResult;
/// <summary>The ledger did not answer usefully: network error, timeout, bad status or body.</summary>
public sealed record Unreachable(string Reason) : LedgerStockResult;
/// <summary>The ledger rejected the API key (401/403).</summary>
public sealed record Unauthorized : LedgerStockResult;
/// <summary>The ledger has no approved mapping for this material, grade and thickness.</summary>
public sealed record Unmapped : LedgerStockResult;
public sealed record Ok(IReadOnlyList<LedgerStockSpec> Specs, IReadOnlyList<LedgerStockItem> Items) : LedgerStockResult;
}