Files
OpenNest/OpenNest/Forms/LedgerCodeConflictForm.cs
T
ajandClaude Opus 5.5 8776eb4734 feat(ledger): keep MaterialLedger item codes from being double-booked
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>
2026-10-27 22:43:08 -04:00

109 lines
4.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Data.MaterialLedger;
namespace OpenNest.Forms;
/// <summary>
/// Lists the nest's item codes that are no longer free for it and asks what to do on this
/// save or post: go on after acknowledging, remove the codes, or cancel. Like pre-post
/// verification, going on needs the acknowledgement each time; nothing remembers it.
/// </summary>
public sealed class LedgerCodeConflictForm : Form
{
private readonly CheckBox acknowledgeBox;
private readonly Button proceedButton;
private readonly Button removeButton;
public LedgerCodeConflictForm(string action, IReadOnlyList<LedgerCodeConflict> conflicts)
{
ArgumentNullException.ThrowIfNull(conflicts);
Text = "Ledger Stock Changed";
StartPosition = FormStartPosition.CenterParent;
ClientSize = new Size(560, 340);
MinimumSize = new Size(440, 300);
MinimizeBox = false;
MaximizeBox = false;
ShowInTaskbar = false;
FormBorderStyle = FormBorderStyle.Sizable;
var codes = conflicts.Select(c => c.ItemCode).Distinct(StringComparer.Ordinal).Count();
var headline = new Label
{
Text = (codes == 1 ? "1 item code on this nest is" : $"{codes} item codes on this nest are")
+ " no longer free for it. Another job may cut the same sheet.\n"
+ "Removing the codes keeps the layout; MaterialLedger then matches those sheets by size.",
Dock = DockStyle.Top,
AutoSize = true,
MaximumSize = new Size(536, 0),
Padding = new Padding(0, 0, 0, 8),
};
var list = new ListView
{
Name = "conflictList",
Dock = DockStyle.Fill,
View = View.Details,
FullRowSelect = true,
HeaderStyle = ColumnHeaderStyle.Nonclickable,
};
list.Columns.Add("Plate", 50);
list.Columns.Add("Item code", 120);
list.Columns.Add("Why", 340);
foreach (var conflict in conflicts)
list.Items.Add(new ListViewItem(new[] { conflict.PlateNumber.ToString(), conflict.ItemCode, conflict.Reason }));
acknowledgeBox = new CheckBox
{
Name = "acknowledgeBox",
Text = $"{action} with these codes although the sheets may be taken",
Dock = DockStyle.Bottom,
AutoSize = true,
Padding = new Padding(0, 6, 0, 0),
};
acknowledgeBox.CheckedChanged += (_, _) => proceedButton.Enabled = acknowledgeBox.Checked;
var buttons = new FlowLayoutPanel
{
Dock = DockStyle.Bottom,
AutoSize = true,
FlowDirection = FlowDirection.RightToLeft,
Padding = new Padding(0, 6, 0, 0),
};
var cancel = new Button { Name = "cancelButton", Text = "Cancel", AutoSize = true, DialogResult = DialogResult.Cancel };
proceedButton = new Button { Name = "proceedButton", Text = $"{action} Anyway", AutoSize = true, Enabled = false };
removeButton = new Button { Name = "removeButton", Text = $"Remove Codes and {action}", AutoSize = true };
proceedButton.Click += (_, _) => Finish(acknowledgeBox.Checked ? LedgerCodeRecheck.Choice.Proceed : LedgerCodeRecheck.Choice.Cancel);
removeButton.Click += (_, _) => Finish(LedgerCodeRecheck.Choice.RemoveCodes);
buttons.Controls.Add(cancel);
buttons.Controls.Add(proceedButton);
buttons.Controls.Add(removeButton);
var body = new Panel { Dock = DockStyle.Fill, Padding = new Padding(12) };
body.Controls.Add(list);
body.Controls.Add(headline);
body.Controls.Add(acknowledgeBox);
body.Controls.Add(buttons);
Controls.Add(body);
AcceptButton = removeButton;
CancelButton = cancel;
}
/// <summary>What the programmer chose; Cancel until a button is clicked.</summary>
internal LedgerCodeRecheck.Choice Choice { get; private set; } = LedgerCodeRecheck.Choice.Cancel;
private void Finish(LedgerCodeRecheck.Choice choice)
{
// Check again here; an enabled button alone is not consent enforcement.
if (choice == LedgerCodeRecheck.Choice.Cancel)
return;
Choice = choice;
DialogResult = DialogResult.OK;
Close();
}
}