Files
OpenNest/OpenNest.Data/DrawingSetQuery.cs
T
ajandClaude Opus 5.5 be057bf337 feat(server): store versioned drawing sets
A drawing set is a named list of library drawings with quantities (a kit,
an assembly, a recurring order) that will later be released into one nest
per material. This is the server half: sets get the same revision,
If-Match and archive contract as /api/drawings so the client and tests
follow a known shape.

Schema v7 adds drawing_sets, drawing_set_revisions and
drawing_set_members. Members reference drawings by id and mean each
drawing's current revision, so reads join the current drawing record:
the client can group members by material without one request per drawing,
and the joined record is ignored on save so a read record can be sent
back unchanged. Every change keeps its own member list, and
/api/drawings/{id}/sets answers "used in" from current revisions only.

The drawing query's enum, integer and history-paging parsing is now shared
so both APIs refuse bad input identically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:51:56 -04:00

49 lines
1.9 KiB
C#

namespace OpenNest.Data;
/// <summary>
/// One bounded page request for browsing drawing sets (<c>GET /api/sets/query</c>).
/// The server filters and pages in SQL; the client and server share these bounds.
/// </summary>
public sealed class DrawingSetQuery
{
public const int DefaultLimit = 100;
public const int MaxLimit = 500;
public const int MaxSearchLength = 200;
/// <summary>
/// Case-insensitive (ASCII) substring of name, customer or notes. Blank means no filter.
/// </summary>
public string Search { get; init; } = "";
/// <summary>Exact customer, compared by <see cref="SharedListNames.Key"/>. Blank means any.</summary>
public string Customer { get; init; } = "";
public bool IncludeArchived { get; init; }
/// <summary>Column to order by; ties are broken by id in the same direction.</summary>
public DrawingSetSortField Sort { get; init; } = DrawingSetSortField.SavedAt;
/// <summary>Largest/newest/Z first when true (the default, newest saved first).</summary>
public bool Descending { get; init; } = true;
public int Offset { get; init; }
public int Limit { get; init; } = DefaultLimit;
/// <summary>The search text as matched: trimmed, never null.</summary>
public string NormalizedSearch => (Search ?? "").Trim();
/// <summary>The first violated bound, or null when the query may be sent.</summary>
public string? GetValidationError()
{
if (!Enum.IsDefined(Sort))
return "sort must be a supported column.";
if (Offset < 0)
return "offset must be zero or greater.";
if (Limit < 1 || Limit > MaxLimit)
return $"limit must be between 1 and {MaxLimit}.";
return DrawingLibraryRules.TextError("search", NormalizedSearch, MaxSearchLength)
?? DrawingLibraryRules.TextError("customer", Customer ?? "", DrawingLibraryRules.MaxTextLength);
}
}