Files
OpenNest/OpenNest.Data/SetReleaseText.cs
T
ajandClaude Opus 5.5 74203483c5 feat(data): release several sets in one plan and merge groups into draft nests
The release plan now takes several sets, each with its own kit count. A
drawing listed by more than one set becomes one item with the quantities
summed and each set's share kept, so the release can record what every kit
put into every nest. FindDraftsAsync lists the Draft nests of a group's
material through the new nest query filters.

The session can merge a group into a chosen draft instead of making a new
nest. It downloads the draft at the revision it was chosen at, refuses it
unless it is still a Draft of the group's material, thickness, units and cut
type, adds the items, and removes every plate so the whole demand is nested
again. The save is guarded by that revision, so a draft someone saved
meanwhile fails its group instead of being overwritten. An optional renest
step, run before anything is uploaded, will back "Nest after release"; if it
throws, the nest is still released without a layout.

The release record now carries every set line, merged nests with their
revisions before and after, and each set's drawing quantities.
SetReleaseText.UndoRefusal words a refused undo for the release history.

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

104 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
/// Why an undo was refused, for the operator: the release was undone before, or the nests
/// that changed since it, each with what happened to it, so they can be skipped.
/// </summary>
public static string UndoRefusal(SetReleaseUndoConflict conflict)
{
ArgumentNullException.ThrowIfNull(conflict);
if (conflict.Reason == SetReleaseUndoRefusal.AlreadyUndone)
return "This release was already undone.";
var lines = conflict.ChangedNests.Select(n =>
{
var name = string.IsNullOrWhiteSpace(n.Name) ? n.Id.ToString() : n.Name;
return n.Deleted
? $"- {name}: deleted since the release"
: $"- {name}: saved since the release (now revision {n.Revision}, {NestStatuses.DisplayName(n.Status)})";
});
return "These nests changed after the release, so undoing would overwrite that work:\n"
+ string.Join("\n", lines)
+ "\n\nUndo the rest and leave these as they are, or restore them from their history.";
}
/// <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;
}