mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 13:41:05 -04:00
Auto Nest leaves out ledger item codes already on other open server nests, and save and post re-check a nest's codes before going on. - Server: GET /api/nests/stock-item-codes?exclude= lists the codes on non-deleted Draft, Quote and To Be Cut nests. Codes are stored in nest_stock_codes (schema v14) at every archive write; the upgrade reads each current archive once. - Data: INestRepository.GetStockCodesInUseAsync, LedgerCodesInUse (5 s, failures become an unknown answer), the pure LedgerCodeFilter for ledger rows (reusable by release-time nesting) and LedgerCodeCheck for the re-check. - Desktop: the Auto Nest ledger summary counts "(N on other nests)" or says other nests were not checked. Save, Save As and post open Ledger Stock Changed when codes are taken: proceed after a per-attempt acknowledgement, remove the codes (undoable), or cancel. A ledger that cannot be read skips the check with a status note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
3.4 KiB
C#
79 lines
3.4 KiB
C#
using System.Net.Http;
|
|
|
|
namespace OpenNest.Data.MaterialLedger;
|
|
|
|
/// <summary>
|
|
/// The ledger item codes other open server nests are coded with, or why they could not be
|
|
/// read. ML's free counts drop only once a hold exists, so these codes are what another
|
|
/// Auto Nest run must leave out until then. Reading them never blocks: a failure is a result.
|
|
/// </summary>
|
|
public sealed record LedgerCodesInUse
|
|
{
|
|
/// <summary>The nest server gets this long to answer, so a stock load or save never waits on it.</summary>
|
|
public static readonly TimeSpan RequestTimeout = TimeSpan.FromSeconds(5);
|
|
|
|
private LedgerCodesInUse(IReadOnlyList<NestStockCodeUse> uses, string? unavailable)
|
|
{
|
|
Uses = uses;
|
|
Unavailable = unavailable;
|
|
Codes = uses.Select(use => use.ItemCode).ToHashSet(StringComparer.Ordinal);
|
|
}
|
|
|
|
/// <summary>One entry per code and nest; empty when <see cref="Unavailable"/>.</summary>
|
|
public IReadOnlyList<NestStockCodeUse> Uses { get; }
|
|
|
|
/// <summary>The distinct codes of <see cref="Uses"/>.</summary>
|
|
public IReadOnlySet<string> Codes { get; }
|
|
|
|
/// <summary>Why the codes could not be read; null when they were.</summary>
|
|
public string? Unavailable { get; }
|
|
|
|
public bool IsKnown => Unavailable == null;
|
|
|
|
public static LedgerCodesInUse Known(IEnumerable<NestStockCodeUse> uses) =>
|
|
new((uses ?? throw new ArgumentNullException(nameof(uses))).Where(use => use != null).ToList(), null);
|
|
|
|
public static LedgerCodesInUse Unknown(string reason) =>
|
|
new(Array.Empty<NestStockCodeUse>(), string.IsNullOrWhiteSpace(reason) ? "not available" : reason);
|
|
|
|
/// <summary>The names of the other nests using <paramref name="code"/>, in name order.</summary>
|
|
public IReadOnlyList<string> NestsUsing(string code) =>
|
|
Uses.Where(use => string.Equals(use.ItemCode, code, StringComparison.Ordinal))
|
|
.Select(use => use.NestName)
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(name => name, StringComparer.CurrentCultureIgnoreCase)
|
|
.ToList();
|
|
|
|
/// <summary>
|
|
/// Reads the codes from <paramref name="repository"/>, leaving out the nest
|
|
/// <paramref name="excludeNestId"/> (the one being edited). A null repository is File
|
|
/// mode. Only the caller's own cancellation throws.
|
|
/// </summary>
|
|
public static async Task<LedgerCodesInUse> ReadAsync(INestRepository? repository, Guid excludeNestId,
|
|
CancellationToken cancellationToken, TimeSpan? timeout = null)
|
|
{
|
|
if (repository == null)
|
|
return Unknown("nests are saved as files");
|
|
|
|
using var timeoutSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
timeoutSource.CancelAfter(timeout ?? RequestTimeout);
|
|
try
|
|
{
|
|
return Known(await repository.GetStockCodesInUseAsync(excludeNestId, timeoutSource.Token));
|
|
}
|
|
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
return Unknown("the nest server did not answer");
|
|
}
|
|
catch (NotSupportedException)
|
|
{
|
|
return Unknown("the nest server needs an update");
|
|
}
|
|
catch (Exception ex) when (ex is HttpRequestException or IOException or System.Text.Json.JsonException
|
|
or ArgumentException or InvalidOperationException)
|
|
{
|
|
return Unknown("the nest server could not be reached");
|
|
}
|
|
}
|
|
}
|