Enhance cut list report formatting and readability

Improves the exported text file format with better organization, visual
hierarchy, and comprehensive summary information.

- Add header with creation timestamp and overall statistics
- Redesign bar sections with clearer labels and formatting
- Replace verbose item format with tabular layout (QTY/Length/Tag)
- Add final summary section with totals and best/worst utilization
- Use Unicode box-drawing characters for visual separation
- Fix namespace references (SawCut.FormatHelper → FormatHelper)
- Add null safety with DefaultIfEmpty for empty bin collections
- Make _bins field readonly for immutability

The new format is more professional and easier to scan visually, making it
simpler for operators to execute the cut list.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
AJ
2025-11-23 18:03:52 -05:00
parent 36fd8df1ac
commit a619353375

View File

@@ -9,7 +9,7 @@ namespace CutList.Forms
{ {
public class BinFileSaver public class BinFileSaver
{ {
private IEnumerable<Bin> _bins; private readonly IEnumerable<Bin> _bins;
private int PaddingWidthOfItemLength { get; set; } private int PaddingWidthOfItemLength { get; set; }
@@ -25,13 +25,22 @@ namespace CutList.Forms
using (var writer = new StreamWriter(file)) using (var writer = new StreamWriter(file))
{ {
writer.AutoFlush = true; writer.AutoFlush = true;
PaddingWidthOfItemLength = _bins.Max(b => b.Items.Max(i => SawCut.FormatHelper.ConvertToMixedFraction(i.Length).Length));
var id = 1;
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) foreach (var bin in _bins)
{ {
WriteBinSummary(writer, bin, id++); WriteBinSummary(writer, bin, id++);
} }
WriteFinalSummary(writer);
} }
if (OpenFileAfterSave) if (OpenFileAfterSave)
@@ -46,38 +55,128 @@ namespace CutList.Forms
{ {
Process.Start("notepad.exe", file); Process.Start("notepad.exe", file);
} }
catch (Exception) catch
{ {
// Notepad is not installed, so just open the file
Process.Start(file); 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) private void WriteBinSummary(StreamWriter writer, Bin bin, int id)
{ {
var totalLength = SawCut.FormatHelper.ConvertToMixedFraction(bin.Length); var stockLength = FormatHelper.ConvertToMixedFraction(bin.Length);
var remainingLength = SawCut.FormatHelper.ConvertToMixedFraction(bin.RemainingLength); 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 utilization = Math.Round(bin.Utilization * 100, 2);
writer.WriteLine($"{id}. Length: {totalLength}, {remainingLength} remaining, {bin.Items.Count} items, {utilization}% utilization"); 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); WriteBinItems(writer, bin);
writer.WriteLine("---------------------------------------------------------------------");
writer.WriteLine();
writer.WriteLine(new string('─', 80));
writer.WriteLine();
} }
private void WriteBinItems(StreamWriter writer, Bin bin) private void WriteBinItems(StreamWriter writer, Bin bin)
{ {
var groups = bin.Items.GroupBy(i => $"{i.Name} {i.Length}"); // 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) foreach (var group in groups)
{ {
var first = group.First(); var first = group.First();
var count = group.Count(); var count = group.Count();
var length = SawCut.FormatHelper.ConvertToMixedFraction(first.Length).PadLeft(PaddingWidthOfItemLength); var length = FormatHelper
var name = first.Name; .ConvertToMixedFraction(first.Length)
var pcsSingularOrPlural = count == 1 ? "pc " : "pcs"; .PadLeft(PaddingWidthOfItemLength);
writer.WriteLine($" {count}{pcsSingularOrPlural} @ {length} LG Tag: {name}"); 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");
}
} }
} }