mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 01:25:44 -05:00
A release can now cover several sets, each with its own kit count, so parts
from different kits share sheets. It can also merge into an existing draft
nest instead of always creating one. Schema v9 replaces the single-set
release tables: releases, their set lines, their nests (created or merged,
with the nest revision before and after) and the drawing quantities each set
added to each nest, so a kit stays traceable once kits share a nest. The v8
migration turns each existing release into one set line with created nests.
Undo runs on the server in one transaction: created nests are soft-deleted
and merged nests restored to their pre-merge revision, but only while each
still holds what the release wrote. Otherwise nothing changes and the
changed nests are listed, so the operator can skip them. A nest restored to
that revision counts as unchanged, which lets releases sharing a nest be
undone newest first.
The old /api/sets/{id}/releases and /api/nests/{id}/release routes are
replaced by /api/releases; no tagged desktop used them. The desktop still
releases one set at a time until the release dialog is reworked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
83 lines
3.7 KiB
C#
83 lines
3.7 KiB
C#
using System.Globalization;
|
||
using OpenNest;
|
||
|
||
namespace OpenNest.Data;
|
||
|
||
/// <summary>Operator-facing wording for set releases, shared by the release views and Nest Info.</summary>
|
||
public static class SetReleaseText
|
||
{
|
||
/// <summary>
|
||
/// Where a nest came from, such as "Released from set 'Kit A' revision 3 ×2 by J. Smith",
|
||
/// or "Merged from sets 'Kit A' revision 3 ×2 and 'Kit B' revision 1 ×1 ..." for a release
|
||
/// that added to <paramref name="nestId"/> rather than creating it.
|
||
/// </summary>
|
||
public static string Origin(SetReleaseRecord release, Guid nestId = default)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(release);
|
||
var merged = release.Nests?.FirstOrDefault(n => n.NestId == nestId)?.Kind == SetReleaseNestKind.Merged;
|
||
var sets = release.Sets ?? Array.Empty<SetReleaseLine>();
|
||
var text = $"{(merged ? "Merged" : "Released")} from {(sets.Count == 1 ? "set" : "sets")} {Sets(sets)}";
|
||
if (release.ReleasedAt != default)
|
||
text += $" on {release.ReleasedAt.ToLocalTime().ToString("d", CultureInfo.CurrentCulture)}";
|
||
if (!string.IsNullOrWhiteSpace(release.ReleasedBy))
|
||
text += $" by {release.ReleasedBy}";
|
||
return release.IsUndone ? $"{text} (undone)" : text;
|
||
}
|
||
|
||
/// <summary>The sets of a release, such as "'Kit A' revision 3 ×2 and 'Kit B' revision 1 ×1".</summary>
|
||
public static string Sets(IReadOnlyList<SetReleaseLine> sets)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(sets);
|
||
var names = sets.Select(s => $"'{s.SetName}' revision {s.SetRevision} ×{s.Quantity}").ToList();
|
||
return names.Count <= 1
|
||
? string.Concat(names)
|
||
: string.Join(", ", names.Take(names.Count - 1)) + " and " + names[^1];
|
||
}
|
||
|
||
/// <summary>
|
||
/// How far the release's nests have got, such as "2 of 3 cut" or "1 of 2 cut, 1 deleted",
|
||
/// or "Undone". Deleted nests are left out of the count.
|
||
/// </summary>
|
||
public static string Progress(SetReleaseRecord release)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(release);
|
||
if (release.IsUndone)
|
||
return "Undone";
|
||
var nests = release.Nests ?? Array.Empty<SetReleaseNest>();
|
||
var deleted = nests.Count(IsDeleted);
|
||
var active = nests.Count - deleted;
|
||
if (active == 0)
|
||
return nests.Count == 1 ? "Its nest was deleted" : "All nests deleted";
|
||
|
||
var cut = nests.Count(n => !IsDeleted(n) && n.Nest?.Status == NestStatus.HasBeenCut);
|
||
var text = $"{cut} of {active} cut";
|
||
return deleted == 0 ? text : $"{text}, {deleted} deleted";
|
||
}
|
||
|
||
/// <summary>The state of one nest of a release: its status name, or "Deleted".</summary>
|
||
public static string NestState(SetReleaseNest nest)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(nest);
|
||
if (IsDeleted(nest))
|
||
return "Deleted";
|
||
return nest.Nest is { } record ? NestStatuses.DisplayName(record.Status) : "";
|
||
}
|
||
|
||
/// <summary>The material group a release nest was made for, such as "Carbon Steel A36 0.25 in laser".</summary>
|
||
public static string Group(SetReleaseNest nest)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(nest);
|
||
var parts = new[]
|
||
{
|
||
nest.MaterialName,
|
||
nest.MaterialGrade,
|
||
$"{nest.Thickness.ToString("0.####", CultureInfo.CurrentCulture)} {UnitsHelper.GetShortString(nest.Units)}",
|
||
nest.CutType.ToString().ToLowerInvariant(),
|
||
};
|
||
return string.Join(" ", parts.Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => p.Trim()));
|
||
}
|
||
|
||
// A nest the server no longer joins is as gone as a deleted one.
|
||
private static bool IsDeleted(SetReleaseNest nest) => nest.Nest is null || nest.Nest.Deleted;
|
||
}
|