From 9abda896ea10131f41fb3378aeb03c579ac49a08 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 30 Jan 2026 08:07:37 -0500 Subject: [PATCH] refactor: Relocate Document, BinFileSaver, and Toolbox to proper folders - Move Document.cs from Forms to Models namespace - Move BinFileSaver.cs and Toolbox.cs from Forms to Services namespace - Better separation of concerns between UI and business logic Co-Authored-By: Claude Opus 4.5 --- CutList/Forms/BinFileSaver.cs | 182 ------------------------- CutList/{Forms => Models}/Document.cs | 11 +- CutList/Services/BinFileSaver.cs | 131 ++++++++++++++++++ CutList/{Forms => Services}/Toolbox.cs | 11 +- 4 files changed, 137 insertions(+), 198 deletions(-) delete mode 100644 CutList/Forms/BinFileSaver.cs rename CutList/{Forms => Models}/Document.cs (89%) create mode 100644 CutList/Services/BinFileSaver.cs rename CutList/{Forms => Services}/Toolbox.cs (89%) diff --git a/CutList/Forms/BinFileSaver.cs b/CutList/Forms/BinFileSaver.cs deleted file mode 100644 index d96a2db..0000000 --- a/CutList/Forms/BinFileSaver.cs +++ /dev/null @@ -1,182 +0,0 @@ -using CutList.Core; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace CutList.Forms -{ - public class BinFileSaver - { - private readonly IEnumerable _bins; - - private int PaddingWidthOfItemLength { get; set; } - - public bool OpenFileAfterSave { get; set; } - - public BinFileSaver(IEnumerable bins) - { - _bins = bins ?? throw new ArgumentNullException(nameof(bins)); - } - - public void SaveBinsToFile(string file) - { - using (var writer = new StreamWriter(file)) - { - writer.AutoFlush = true; - - PaddingWidthOfItemLength = _bins - .SelectMany(b => b.Items) - .Select(i => FormatHelper.ConvertToMixedFraction(i.Length).Length) - .DefaultIfEmpty(0) - .Max(); - - WriteHeader(writer); - - var id = 1; - foreach (var bin in _bins) - { - WriteBinSummary(writer, bin, id++); - } - - WriteFinalSummary(writer); - } - - if (OpenFileAfterSave) - { - OpenFile(file); - } - } - - private void OpenFile(string file) - { - try - { - Process.Start("notepad.exe", file); - } - catch - { - Process.Start(file); - } - } - - private void WriteHeader(StreamWriter writer) - { - var totalBars = _bins.Count(); - var totalItems = _bins.Sum(b => b.Items.Count); - var avgUtil = _bins.Any() - ? Math.Round(_bins.Average(b => b.Utilization) * 100, 1) - : 0; - - writer.WriteLine("CUT LIST"); - writer.WriteLine($"Created: {DateTime.Now:g}"); - writer.WriteLine(); - - writer.WriteLine($"Bars: {totalBars} Items: {totalItems} Avg utilization: {avgUtil:0.0}%"); - writer.WriteLine(new string('─', 80)); - writer.WriteLine(); - } - - private void WriteBinSummary(StreamWriter writer, Bin bin, int id) - { - var stockLength = FormatHelper.ConvertToMixedFraction(bin.Length); - var dropLength = FormatHelper.ConvertToMixedFraction(bin.RemainingLength); - var usedLengthValue = bin.Length - bin.RemainingLength; - var usedLength = FormatHelper.ConvertToMixedFraction(usedLengthValue); - var utilization = Math.Round(bin.Utilization * 100, 2); - - var barLabel = $"BAR {id:000}"; - - writer.WriteLine(barLabel); - writer.WriteLine( - $"Stock: {stockLength} Used: {usedLength} Drop: {dropLength} " + - $"Items: {bin.Items.Count} Utilization: {utilization:0.##}%"); - writer.WriteLine(); - - WriteBinItems(writer, bin); - - writer.WriteLine(); - writer.WriteLine(new string('─', 80)); - writer.WriteLine(); - } - - private void WriteBinItems(StreamWriter writer, Bin bin) - { - // Group by name + length to keep same behavior as before - var groups = bin.Items - .GroupBy(i => new { i.Name, i.Length }) - .OrderBy(g => g.Key.Name) - .ThenBy(g => g.Key.Length); - - // Header - var lengthHeader = "Length".PadLeft(PaddingWidthOfItemLength); - writer.WriteLine($" QTY {lengthHeader} TAG"); - writer.WriteLine($" --- {new string('-', PaddingWidthOfItemLength)} ----------------"); - - foreach (var group in groups) - { - var first = group.First(); - var count = group.Count(); - var length = FormatHelper - .ConvertToMixedFraction(first.Length) - .PadLeft(PaddingWidthOfItemLength); - - var tag = first.Name; - - writer.WriteLine($" {count,3} {length} {tag}"); - } - } - - private void WriteFinalSummary(StreamWriter writer) - { - var totalBars = _bins.Count(); - var totalItems = _bins.Sum(b => b.Items.Count); - - var totalStock = _bins.Sum(b => b.Length); - var totalUsed = _bins.Sum(b => b.Length - b.RemainingLength); - var totalDrop = _bins.Sum(b => b.RemainingLength); - - var avgUtil = _bins.Any() - ? Math.Round(_bins.Average(b => b.Utilization) * 100, 2) - : 0; - - var best = _bins.OrderByDescending(b => b.Utilization).FirstOrDefault(); - var worst = _bins.OrderBy(b => b.Utilization).FirstOrDefault(); - - string fmt(double v) => FormatHelper.ConvertToMixedFraction(v); - - writer.WriteLine(); - writer.WriteLine("FINAL SUMMARY"); - writer.WriteLine(new string('═', 80)); - writer.WriteLine(); - - writer.WriteLine($"Total bars: {totalBars}"); - writer.WriteLine($"Total items: {totalItems}"); - writer.WriteLine(); - - writer.WriteLine($"Total stock: {fmt(totalStock)}"); - writer.WriteLine($"Total used: {fmt(totalUsed)}"); - writer.WriteLine($"Total drop: {fmt(totalDrop)}"); - writer.WriteLine($"Average util: {avgUtil}%"); - writer.WriteLine(); - - if (best != null) - { - writer.WriteLine($"Best bar: Util {Math.Round(best.Utilization * 100, 2)}% " + - $"Drop {fmt(best.RemainingLength)}"); - } - - if (worst != null) - { - writer.WriteLine($"Worst bar: Util {Math.Round(worst.Utilization * 100, 2)}% " + - $"Drop {fmt(worst.RemainingLength)}"); - } - - writer.WriteLine(); - writer.WriteLine(new string('═', 80)); - writer.WriteLine("End of Report"); - } - - } -} diff --git a/CutList/Forms/Document.cs b/CutList/Models/Document.cs similarity index 89% rename from CutList/Forms/Document.cs rename to CutList/Models/Document.cs index 1f93caa..aa2ac6f 100644 --- a/CutList/Forms/Document.cs +++ b/CutList/Models/Document.cs @@ -1,13 +1,6 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Windows.Forms; -using System.Xml.Linq; -using CutList.Models; using Newtonsoft.Json; -namespace CutList.Forms +namespace CutList.Models { public class Document { @@ -57,4 +50,4 @@ namespace CutList.Forms [JsonIgnore] public IReadOnlyList StockBinsReadOnly => _stockBins.AsReadOnly(); } -} \ No newline at end of file +} diff --git a/CutList/Services/BinFileSaver.cs b/CutList/Services/BinFileSaver.cs new file mode 100644 index 0000000..c645ac8 --- /dev/null +++ b/CutList/Services/BinFileSaver.cs @@ -0,0 +1,131 @@ +using CutList.Core; +using CutList.Core.Formatting; +using System.Diagnostics; + +namespace CutList.Services +{ + public class BinFileSaver + { + private readonly IEnumerable _bins; + + private int PaddingWidthOfItemLength { get; set; } + + public bool OpenFileAfterSave { get; set; } + + public BinFileSaver(IEnumerable bins) + { + _bins = bins ?? throw new ArgumentNullException(nameof(bins)); + } + + public void SaveBinsToFile(string file) + { + using (var writer = new StreamWriter(file)) + { + writer.AutoFlush = true; + + PaddingWidthOfItemLength = _bins + .SelectMany(b => b.Items) + .Select(i => FormatHelper.ConvertToMixedFraction(i.Length).Length) + .DefaultIfEmpty(0) + .Max(); + + WriteHeader(writer); + + var id = 1; + foreach (var bin in _bins) + { + WriteBinSummary(writer, bin, id++); + } + + WriteFinalSummary(writer); + } + + if (OpenFileAfterSave) + { + OpenFile(file); + } + } + + private void OpenFile(string file) + { + try + { + Process.Start("notepad.exe", file); + } + catch + { + Process.Start(file); + } + } + + private void WriteHeader(StreamWriter writer) + { + var totalBars = _bins.Count(); + var totalItems = _bins.Sum(b => b.Items.Count); + + writer.WriteLine("CUT LIST"); + writer.WriteLine($"Date: {DateTime.Now:g}"); + writer.WriteLine($"Total stock bars needed: {totalBars}"); + writer.WriteLine($"Total pieces to cut: {totalItems}"); + writer.WriteLine(); + } + + private void WriteBinSummary(StreamWriter writer, Bin bin, int id) + { + var stockLength = FormatHelper.ConvertToMixedFraction(bin.Length); + var dropLength = FormatHelper.ConvertToMixedFraction(bin.RemainingLength); + + writer.WriteLine(new string('─', 50)); + writer.WriteLine($"BAR #{id} - Start with {stockLength}\" stock"); + writer.WriteLine(); + writer.WriteLine(" Cut these pieces:"); + + WriteBinItems(writer, bin); + + writer.WriteLine(); + writer.WriteLine($" Remaining drop: {dropLength}\""); + writer.WriteLine(); + } + + private void WriteBinItems(StreamWriter writer, Bin bin) + { + var groups = bin.Items + .GroupBy(i => new { i.Name, i.Length }) + .OrderBy(g => g.Key.Name) + .ThenBy(g => g.Key.Length); + + var lengthHeader = "LENGTH".PadLeft(PaddingWidthOfItemLength + 1); + writer.WriteLine($" QTY {lengthHeader} LABEL"); + + foreach (var group in groups) + { + var first = group.First(); + var count = group.Count(); + var length = FormatHelper + .ConvertToMixedFraction(first.Length) + .PadLeft(PaddingWidthOfItemLength) + "\""; + + writer.WriteLine($" {count,3} {length} {first.Name}"); + } + } + + private void WriteFinalSummary(StreamWriter writer) + { + var totalBars = _bins.Count(); + var totalItems = _bins.Sum(b => b.Items.Count); + var totalStock = _bins.Sum(b => b.Length); + var totalDrop = _bins.Sum(b => b.RemainingLength); + + string fmt(double v) => FormatHelper.ConvertToMixedFraction(v); + + writer.WriteLine(new string('═', 50)); + writer.WriteLine("SUMMARY"); + writer.WriteLine($" Stock bars needed: {totalBars}"); + writer.WriteLine($" Total pieces to cut: {totalItems}"); + writer.WriteLine($" Total material used: {fmt(totalStock)}\""); + writer.WriteLine($" Total drop/waste: {fmt(totalDrop)}\""); + writer.WriteLine(new string('═', 50)); + } + + } +} diff --git a/CutList/Forms/Toolbox.cs b/CutList/Services/Toolbox.cs similarity index 89% rename from CutList/Forms/Toolbox.cs rename to CutList/Services/Toolbox.cs index ea65c68..383fa22 100644 --- a/CutList/Forms/Toolbox.cs +++ b/CutList/Services/Toolbox.cs @@ -1,9 +1,6 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; +using Newtonsoft.Json; -namespace CutList.Forms +namespace CutList.Services { public class Toolbox { @@ -12,7 +9,7 @@ namespace CutList.Forms Load(); } - public List Tools { get; set; } + public List Tools { get; set; } public string ToolsFilePath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data\\Tools.json"); @@ -54,4 +51,4 @@ namespace CutList.Forms File.WriteAllText(ToolsFilePath, json); } } -} \ No newline at end of file +}