diff --git a/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs b/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs
index 39ba65c..abd1abc 100644
--- a/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs
+++ b/NEW/src/JdeScoping.ExcelIO/ExcelExportService.cs
@@ -1,234 +1,213 @@
-using ClosedXML.Excel;
-using JdeScoping.Core.Interfaces;
-using JdeScoping.Core.Models.SearchResults;
-using JdeScoping.ExcelIO.Options;
-using JdeScoping.ExcelIO.Formatting;
-using JdeScoping.ExcelIO.Generators;
-using JdeScoping.ExcelIO.Mapping;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-
-// Use Core's SearchModel for the public interface
-using CoreSearchModel = JdeScoping.Core.Models.SearchResults.SearchModel;
-// Use ExcelIO's SearchModel which contains criteria filter properties for CriteriaSheetGenerator
-using ExcelSearchModel = JdeScoping.ExcelIO.Models.Reporting.SearchModel;
-
-namespace JdeScoping.ExcelIO;
-
-///
-/// Service for generating Excel export files from search results.
-///
-public class ExcelExportService : IExcelExportService
-{
- private readonly ILogger _logger;
- private readonly IOptions _options;
- private readonly CriteriaSheetGenerator _criteriaGenerator;
- private readonly FluentTableWriter _tableWriter;
- private readonly ExcelMapRegistry _registry;
-
- ///
- /// Initializes a new instance of the ExcelExportService class.
- ///
- /// Logger instance.
- /// Excel export options.
- /// Criteria sheet generator.
- /// Fluent table writer.
- /// Excel map registry.
- public ExcelExportService(
- ILogger logger,
- IOptions options,
- CriteriaSheetGenerator criteriaGenerator,
- FluentTableWriter tableWriter,
- ExcelMapRegistry registry)
- {
- _logger = logger;
- _options = options;
- _criteriaGenerator = criteriaGenerator;
- _tableWriter = tableWriter;
- _registry = registry;
- }
-
- ///
- public async Task GenerateAsync(CoreSearchModel search, CancellationToken cancellationToken = default)
- {
- ArgumentNullException.ThrowIfNull(search);
-
- // Map Core SearchModel to ExcelIO SearchModel for internal processing
- var excelModel = MapToExcelModel(search);
- return await GenerateInternalAsync(excelModel, cancellationToken);
- }
-
- ///
- /// Maps Core SearchModel to ExcelIO SearchModel for Excel generation.
- ///
- private static ExcelSearchModel MapToExcelModel(CoreSearchModel source)
- {
- return new ExcelSearchModel
- {
- Id = source.Id,
- UserName = source.UserName,
- Name = source.Name,
- SubmitDt = source.SubmitDt,
- StartDt = source.StartDt,
- EndDt = source.EndDt,
- ExtractMisData = source.ExtractMisData,
- Results = source.Results,
- MisResults = source.MisResults,
- MisNonMatchResults = source.MisNonMatchResults
- };
- }
-
- ///
- /// Internal method that generates an Excel file from the ExcelIO search model.
- ///
- /// ExcelIO search model with criteria and results.
- /// Cancellation token.
- /// Excel file as byte array.
- private async Task GenerateInternalAsync(ExcelSearchModel search, CancellationToken cancellationToken = default)
- {
- using var scope = _logger.BeginScope(new Dictionary
- {
- ["SearchId"] = search.Id,
- ["SearchName"] = search.Name
- });
-
- _logger.LogInformation("Starting Excel export generation");
-
- // ClosedXML operations are synchronous, wrap in Task.Run for non-blocking
- return await Task.Run(() =>
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- using var workbook = new XLWorkbook();
-
- // 1. Always generate Search Criteria sheet (first tab)
- _logger.LogDebug("Generating Search Criteria sheet");
- _criteriaGenerator.Generate(workbook, search);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- // 2. Always generate Search Results sheet (second tab)
- _logger.LogDebug("Generating Search Results sheet");
- GenerateResultsSheet(workbook, search.Results);
-
- cancellationToken.ThrowIfCancellationRequested();
-
- // 3. Conditionally generate MIS Info sheet
- if (search.ExtractMisData && search.MisResults != null && search.MisResults.Count > 0)
- {
- _logger.LogDebug("Generating MIS Info sheet with {Count} records", search.MisResults.Count);
- GenerateMisInfoSheet(workbook, search.MisResults);
- }
- else if (search.ExtractMisData)
- {
- _logger.LogWarning("ExtractMisData is true but MisResults is null or empty");
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- // 4. Conditionally generate Investigation sheet
- if (search.ExtractMisData && search.MisNonMatchResults != null && search.MisNonMatchResults.Count > 0)
- {
- _logger.LogDebug("Generating Investigation sheet with {Count} records", search.MisNonMatchResults.Count);
- GenerateInvestigationSheet(workbook, search.MisNonMatchResults);
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- // Save to byte array
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- var result = stream.ToArray();
-
- _logger.LogInformation("Excel export generation completed. Size: {Size} bytes", result.Length);
-
- // Optional: write debug copy to disk
- if (_options.Value.DebugWriteToFile)
- {
- WriteDebugCopy(search.Id, result);
- }
-
- return result;
-
- }, cancellationToken);
- }
-
- private void GenerateResultsSheet(XLWorkbook workbook, List results)
- {
- var map = _registry.GetMap();
- var tabName = map.TabName ?? "Search Results";
-
- var worksheet = workbook.Worksheets.Add(tabName);
- var table = _tableWriter.WriteTable(worksheet, 1, 1, results);
-
- if (table != null)
- {
- // Apply protection with editable extension area
- var lastRow = table.RangeAddress.LastAddress.RowNumber;
- var lastCol = table.RangeAddress.LastAddress.ColumnNumber;
-
- WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol);
- WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
- }
- }
-
- private void GenerateMisInfoSheet(XLWorkbook workbook, List misResults)
- {
- var map = _registry.GetMap();
- var tabName = map.TabName ?? "MIS Info";
-
- var worksheet = workbook.Worksheets.Add(tabName);
- var table = _tableWriter.WriteTable(worksheet, 1, 1, misResults);
-
- if (table != null)
- {
- // Apply protection with editable extension area
- var lastRow = table.RangeAddress.LastAddress.RowNumber;
- var lastCol = table.RangeAddress.LastAddress.ColumnNumber;
-
- WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol);
- WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
- }
- }
-
- private void GenerateInvestigationSheet(XLWorkbook workbook, List misNonMatchResults)
- {
- var map = _registry.GetMap();
- var tabName = map.TabName ?? "Investigation";
-
- var worksheet = workbook.Worksheets.Add(tabName);
- var table = _tableWriter.WriteTable(worksheet, 1, 1, misNonMatchResults);
-
- if (table != null)
- {
- // Apply protection with editable extension area
- var lastRow = table.RangeAddress.LastAddress.RowNumber;
- var lastCol = table.RangeAddress.LastAddress.ColumnNumber;
-
- WorksheetProtector.UnlockExtensionArea(worksheet, lastRow, lastCol);
- WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
- }
- }
-
- private void WriteDebugCopy(int searchId, byte[] result)
- {
- try
- {
- var directory = _options.Value.DebugOutputDirectory;
- if (!Directory.Exists(directory))
- {
- Directory.CreateDirectory(directory);
- }
-
- var debugPath = Path.Combine(
- directory,
- $"Search_{searchId}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx");
- File.WriteAllBytes(debugPath, result);
- _logger.LogDebug("Debug copy written to {Path}", debugPath);
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "Failed to write debug copy");
- }
- }
-}
+using JdeScoping.Core.Interfaces;
+using JdeScoping.Core.Models.SearchResults;
+using JdeScoping.ExcelIO.Options;
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Generators;
+using JdeScoping.ExcelIO.Mapping;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+
+using CoreSearchModel = JdeScoping.Core.Models.SearchResults.SearchModel;
+using ExcelSearchModel = JdeScoping.ExcelIO.Models.Reporting.SearchModel;
+
+namespace JdeScoping.ExcelIO;
+
+///
+/// Service for generating Excel export files from search results.
+///
+public class ExcelExportService : IExcelExportService
+{
+ private readonly ILogger _logger;
+ private readonly IOptions _options;
+ private readonly CriteriaSheetGenerator _criteriaGenerator;
+ private readonly FluentTableWriter _tableWriter;
+ private readonly ExcelMapRegistry _registry;
+
+ ///
+ /// Initializes a new instance of the ExcelExportService class.
+ ///
+ /// Logger instance.
+ /// Excel export options.
+ /// Criteria sheet generator.
+ /// Fluent table writer.
+ /// Excel map registry.
+ public ExcelExportService(
+ ILogger logger,
+ IOptions options,
+ CriteriaSheetGenerator criteriaGenerator,
+ FluentTableWriter tableWriter,
+ ExcelMapRegistry registry)
+ {
+ _logger = logger;
+ _options = options;
+ _criteriaGenerator = criteriaGenerator;
+ _tableWriter = tableWriter;
+ _registry = registry;
+ }
+
+ ///
+ public async Task GenerateAsync(CoreSearchModel search, CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(search);
+
+ var excelModel = MapToExcelModel(search);
+ return await GenerateInternalAsync(excelModel, cancellationToken);
+ }
+
+ ///
+ /// Maps Core SearchModel to ExcelIO SearchModel for Excel generation.
+ ///
+ private static ExcelSearchModel MapToExcelModel(CoreSearchModel source)
+ {
+ return new ExcelSearchModel
+ {
+ Id = source.Id,
+ UserName = source.UserName,
+ Name = source.Name,
+ SubmitDt = source.SubmitDt,
+ StartDt = source.StartDt,
+ EndDt = source.EndDt,
+ ExtractMisData = source.ExtractMisData,
+ Results = source.Results,
+ MisResults = source.MisResults,
+ MisNonMatchResults = source.MisNonMatchResults
+ };
+ }
+
+ ///
+ /// Internal method that generates an Excel file from the ExcelIO search model.
+ ///
+ /// ExcelIO search model with criteria and results.
+ /// Cancellation token.
+ /// Excel file as byte array.
+ private async Task GenerateInternalAsync(ExcelSearchModel search, CancellationToken cancellationToken = default)
+ {
+ using var scope = _logger.BeginScope(new Dictionary
+ {
+ ["SearchId"] = search.Id,
+ ["SearchName"] = search.Name
+ });
+
+ _logger.LogInformation("Starting Excel export generation");
+
+ // NPOI operations are synchronous, wrap in Task.Run for non-blocking.
+ return await Task.Run(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using IWorkbook workbook = new XSSFWorkbook();
+
+ _logger.LogDebug("Generating Search Criteria sheet");
+ _criteriaGenerator.Generate(workbook, search);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ _logger.LogDebug("Generating Search Results sheet");
+ GenerateResultsSheet(workbook, search.Results);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (search.ExtractMisData && search.MisResults != null && search.MisResults.Count > 0)
+ {
+ _logger.LogDebug("Generating MIS Info sheet with {Count} records", search.MisResults.Count);
+ GenerateMisInfoSheet(workbook, search.MisResults);
+ }
+ else if (search.ExtractMisData)
+ {
+ _logger.LogWarning("ExtractMisData is true but MisResults is null or empty");
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (search.ExtractMisData && search.MisNonMatchResults != null && search.MisNonMatchResults.Count > 0)
+ {
+ _logger.LogDebug("Generating Investigation sheet with {Count} records", search.MisNonMatchResults.Count);
+ GenerateInvestigationSheet(workbook, search.MisNonMatchResults);
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ var result = stream.ToArray();
+
+ _logger.LogInformation("Excel export generation completed. Size: {Size} bytes", result.Length);
+
+ if (_options.Value.DebugWriteToFile)
+ {
+ WriteDebugCopy(search.Id, result);
+ }
+
+ return result;
+ }, cancellationToken);
+ }
+
+ private void GenerateResultsSheet(IWorkbook workbook, List results)
+ {
+ var map = _registry.GetMap();
+ var tabName = map.TabName ?? "Search Results";
+
+ var worksheet = workbook.CreateSheet(tabName);
+ var table = _tableWriter.WriteTable(worksheet, 1, 1, results);
+
+ if (table != null)
+ {
+ WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol);
+ WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
+ }
+ }
+
+ private void GenerateMisInfoSheet(IWorkbook workbook, List misResults)
+ {
+ var map = _registry.GetMap();
+ var tabName = map.TabName ?? "MIS Info";
+
+ var worksheet = workbook.CreateSheet(tabName);
+ var table = _tableWriter.WriteTable(worksheet, 1, 1, misResults);
+
+ if (table != null)
+ {
+ WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol);
+ WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
+ }
+ }
+
+ private void GenerateInvestigationSheet(IWorkbook workbook, List misNonMatchResults)
+ {
+ var map = _registry.GetMap();
+ var tabName = map.TabName ?? "Investigation";
+
+ var worksheet = workbook.CreateSheet(tabName);
+ var table = _tableWriter.WriteTable(worksheet, 1, 1, misNonMatchResults);
+
+ if (table != null)
+ {
+ WorksheetProtector.UnlockExtensionArea(worksheet, table.LastRow, table.LastCol);
+ WorksheetProtector.ApplyProtection(worksheet, _options.Value.DataSheetPassword);
+ }
+ }
+
+ private void WriteDebugCopy(int searchId, byte[] result)
+ {
+ try
+ {
+ var directory = _options.Value.DebugOutputDirectory;
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ var debugPath = Path.Combine(
+ directory,
+ $"Search_{searchId}_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx");
+ File.WriteAllBytes(debugPath, result);
+ _logger.LogDebug("Debug copy written to {Path}", debugPath);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to write debug copy");
+ }
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs b/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs
index 31b5a42..06b046d 100644
--- a/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Formatting/HeaderFormatter.cs
@@ -1,49 +1,90 @@
-using ClosedXML.Excel;
-
-namespace JdeScoping.ExcelIO.Formatting;
-
-///
-/// Header cell formatting utilities.
-///
-public static class HeaderFormatter
-{
- ///
- /// Applies header formatting to a cell.
- ///
- /// The cell to format.
- /// Optional text to set in the cell.
- public static void ApplyHeaderFormat(IXLCell cell, string? text = null)
- {
- cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
- cell.Style.Font.Bold = true;
- cell.Style.Fill.BackgroundColor = XLColor.Gainsboro;
-
- if (!string.IsNullOrEmpty(text))
- {
- cell.Value = text;
- }
- }
-
- ///
- /// Applies header formatting to a range.
- ///
- /// The range to format.
- /// Optional text to set in the first cell.
- /// Whether to merge the range.
- public static void ApplyHeaderFormat(IXLRange range, string? text = null, bool merge = false)
- {
- range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
- range.Style.Font.Bold = true;
- range.Style.Fill.BackgroundColor = XLColor.Gainsboro;
-
- if (merge)
- {
- range.Merge();
- }
-
- if (!string.IsNullOrEmpty(text))
- {
- range.FirstCell().Value = text;
- }
- }
-}
+using NPOI.SS.UserModel;
+using NPOI.SS.Util;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Formatting;
+
+///
+/// Header cell formatting utilities.
+///
+public static class HeaderFormatter
+{
+ private static readonly byte[] GainsboroRgb = [0xDC, 0xDC, 0xDC];
+
+ ///
+ /// Applies header formatting to a cell.
+ ///
+ /// The cell to format.
+ /// Optional text to set in the cell.
+ public static void ApplyHeaderFormat(ICell cell, string? text = null)
+ {
+ var workbook = cell.Sheet.Workbook;
+ var style = workbook.CreateCellStyle();
+ style.Alignment = HorizontalAlignment.Center;
+ style.FillPattern = FillPattern.SolidForeground;
+
+ if (style is XSSFCellStyle xssfStyle)
+ {
+ var color = new XSSFColor();
+ color.SetRgb(GainsboroRgb);
+ xssfStyle.SetFillForegroundColor(color);
+ }
+ else
+ {
+ style.FillForegroundColor = IndexedColors.Grey25Percent.Index;
+ }
+
+ var font = workbook.CreateFont();
+ font.IsBold = true;
+ style.SetFont(font);
+
+ cell.CellStyle = style;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ cell.SetCellValue(text);
+ }
+ }
+
+ ///
+ /// Applies header formatting to a range.
+ ///
+ /// The worksheet containing the range.
+ /// First row number (1-based).
+ /// First column number (1-based).
+ /// Last row number (1-based).
+ /// Last column number (1-based).
+ /// Optional text to set in the first cell.
+ /// Whether to merge the range.
+ public static void ApplyHeaderFormat(
+ ISheet sheet,
+ int firstRow,
+ int firstCol,
+ int lastRow,
+ int lastCol,
+ string? text = null,
+ bool merge = false)
+ {
+ for (var row = firstRow; row <= lastRow; row++)
+ {
+ var npoiRow = sheet.GetRow(row - 1) ?? sheet.CreateRow(row - 1);
+ for (var col = firstCol; col <= lastCol; col++)
+ {
+ var cell = npoiRow.GetCell(col - 1) ?? npoiRow.CreateCell(col - 1);
+ ApplyHeaderFormat(cell);
+ }
+ }
+
+ if (merge)
+ {
+ sheet.AddMergedRegion(new CellRangeAddress(firstRow - 1, lastRow - 1, firstCol - 1, lastCol - 1));
+ }
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ var row = sheet.GetRow(firstRow - 1) ?? sheet.CreateRow(firstRow - 1);
+ var cell = row.GetCell(firstCol - 1) ?? row.CreateCell(firstCol - 1);
+ cell.SetCellValue(text);
+ }
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs b/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs
index f517151..206e344 100644
--- a/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Formatting/WorksheetProtector.cs
@@ -1,62 +1,81 @@
-using ClosedXML.Excel;
-
-namespace JdeScoping.ExcelIO.Formatting;
-
-///
-/// Worksheet protection utilities.
-///
-public static class WorksheetProtector
-{
- ///
- /// Applies standard protection to a worksheet with the given password.
- ///
- /// The worksheet to protect.
- /// The protection password.
- public static void ApplyProtection(IXLWorksheet worksheet, string password)
- {
- var protection = worksheet.Protect(password);
-
- // Allow these operations
- protection.AllowElement(XLSheetProtectionElements.DeleteColumns);
- protection.AllowElement(XLSheetProtectionElements.DeleteRows);
- protection.AllowElement(XLSheetProtectionElements.AutoFilter);
- protection.AllowElement(XLSheetProtectionElements.FormatCells);
- protection.AllowElement(XLSheetProtectionElements.FormatColumns);
- protection.AllowElement(XLSheetProtectionElements.FormatRows);
- protection.AllowElement(XLSheetProtectionElements.SelectLockedCells);
- protection.AllowElement(XLSheetProtectionElements.SelectUnlockedCells);
- protection.AllowElement(XLSheetProtectionElements.EditObjects);
- protection.AllowElement(XLSheetProtectionElements.Sort);
- }
-
- ///
- /// Applies criteria sheet protection (simpler, just password).
- ///
- /// The worksheet to protect.
- /// The protection password.
- public static void ApplyCriteriaProtection(IXLWorksheet worksheet, string password)
- {
- worksheet.Protect(password);
- }
-
- ///
- /// Unlocks a range for user editing beyond the data area.
- ///
- /// The worksheet containing the range.
- /// The last row of data.
- /// The last column of data.
- /// Number of rows to unlock beyond data (default 1000).
- /// Number of columns to unlock beyond data (default 1000).
- public static void UnlockExtensionArea(
- IXLWorksheet worksheet,
- int lastDataRow,
- int lastDataCol,
- int extensionRows = 1000,
- int extensionCols = 1000)
- {
- var extensionRange = worksheet.Range(
- 1, lastDataCol + 1,
- lastDataRow + extensionRows, lastDataCol + extensionCols);
- extensionRange.Style.Protection.Locked = false;
- }
-}
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Formatting;
+
+///
+/// Worksheet protection utilities.
+///
+public static class WorksheetProtector
+{
+ ///
+ /// Applies standard protection to a worksheet with the given password.
+ ///
+ /// The worksheet to protect.
+ /// The protection password.
+ public static void ApplyProtection(ISheet worksheet, string password)
+ {
+ worksheet.ProtectSheet(password);
+
+ if (worksheet is not XSSFSheet xssfSheet)
+ {
+ return;
+ }
+
+ // Set to false to allow these operations while sheet protection is enabled.
+ xssfSheet.LockDeleteColumns(false);
+ xssfSheet.LockDeleteRows(false);
+ xssfSheet.LockAutoFilter(false);
+ xssfSheet.LockFormatCells(false);
+ xssfSheet.LockFormatColumns(false);
+ xssfSheet.LockFormatRows(false);
+ xssfSheet.LockSelectLockedCells(false);
+ xssfSheet.LockSelectUnlockedCells(false);
+ xssfSheet.LockObjects(false);
+ xssfSheet.LockSort(false);
+ xssfSheet.EnableLocking();
+ }
+
+ ///
+ /// Applies criteria sheet protection (simpler, just password).
+ ///
+ /// The worksheet to protect.
+ /// The protection password.
+ public static void ApplyCriteriaProtection(ISheet worksheet, string password)
+ {
+ worksheet.ProtectSheet(password);
+ }
+
+ ///
+ /// Unlocks a range for user editing beyond the data area.
+ ///
+ /// The worksheet containing the range.
+ /// The last row of data.
+ /// The last column of data.
+ /// Number of rows to unlock beyond data (default 1000).
+ /// Number of columns to unlock beyond data (default 1000).
+ public static void UnlockExtensionArea(
+ ISheet worksheet,
+ int lastDataRow,
+ int lastDataCol,
+ int extensionRows = 1000,
+ int extensionCols = 1000)
+ {
+ var workbook = worksheet.Workbook;
+ var unlockedStyle = workbook.CreateCellStyle();
+ unlockedStyle.IsLocked = false;
+
+ // Apply unlocked default style to extension columns (NPOI uses 0-based columns).
+ var startCol = lastDataCol;
+ var endCol = lastDataCol + extensionCols - 1;
+ for (var col = startCol; col <= endCol; col++)
+ {
+ worksheet.SetDefaultColumnStyle(col, unlockedStyle);
+ }
+
+ // Touch at least one extension cell to ensure style materializes in workbook XML.
+ var row = worksheet.GetRow(0) ?? worksheet.CreateRow(0);
+ var cell = row.GetCell(startCol) ?? row.CreateCell(startCol);
+ cell.CellStyle = unlockedStyle;
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs b/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs
index 9d8ad5d..7cdc030 100644
--- a/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs
@@ -1,193 +1,134 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Options;
-using JdeScoping.ExcelIO.Formatting;
-using JdeScoping.ExcelIO.Models.Reporting;
-using Microsoft.Extensions.Options;
-
-namespace JdeScoping.ExcelIO.Generators;
-
-///
-/// Generates the Search Criteria sheet for Excel export.
-///
-public class CriteriaSheetGenerator
-{
- private readonly IOptions _options;
- private readonly FluentTableWriter _tableWriter;
-
- ///
- /// Initializes a new instance of the CriteriaSheetGenerator class.
- ///
- /// Excel export options.
- /// Fluent table writer.
- public CriteriaSheetGenerator(
- IOptions options,
- FluentTableWriter tableWriter)
- {
- _options = options;
- _tableWriter = tableWriter;
- }
-
- ///
- /// Generates the Search Criteria sheet.
- ///
- /// The workbook to add the sheet to.
- /// The search model with criteria.
- public void Generate(XLWorkbook workbook, SearchModel search)
- {
- var worksheet = workbook.Worksheets.Add("Search Criteria");
- var row = 1;
-
- // Write name and user
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(row, 1), "Search Name");
- worksheet.Cell(row, 2).Value = search.Name;
-
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "User Name");
- worksheet.Cell(row, 2).Value = search.UserName;
-
- // Skip row
- row++;
-
- // Write timestamps
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Submit timestamp");
- worksheet.Cell(row, 2).Value = FormatTimestamp(search.SubmitDt);
-
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Start timestamp");
- worksheet.Cell(row, 2).Value = FormatTimestamp(search.StartDt);
-
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(++row, 1), "Completed timestamp");
- worksheet.Cell(row, 2).Value = FormatTimestamp(search.EndDt);
-
- // Skip row
- row++;
-
- // Write timespan filter table
- var timespanData = new List
- {
- new() { MinimumDt = search.MinimumDt, MaximumDt = search.MaximumDt }
- };
- var timespanTable = _tableWriter.WriteTable(worksheet, ++row, 1, timespanData);
- if (timespanTable != null)
- {
- row = timespanTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write work order filter table
- var workOrderTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkOrderFilter);
- if (workOrderTable != null)
- {
- row = workOrderTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write item number filter table
- var itemNumberTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemNumberFilter);
- if (itemNumberTable != null)
- {
- row = itemNumberTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write profit center filter table
- var profitCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.ProfitCenterFilter);
- if (profitCenterTable != null)
- {
- row = profitCenterTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write work center filter table
- var workCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkCenterFilter);
- if (workCenterTable != null)
- {
- row = workCenterTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write component lot filter table
- var componentLotTable = _tableWriter.WriteTable(worksheet, row, 1, search.ComponentLotFilter);
- if (componentLotTable != null)
- {
- row = componentLotTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write operator filter table
- var operatorTable = _tableWriter.WriteTable(worksheet, row, 1, search.OperatorFilter);
- if (operatorTable != null)
- {
- row = operatorTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write item/operation/MIS filter table
- var itemOpMisTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemOperationMisFilter);
- if (itemOpMisTable != null)
- {
- row = itemOpMisTable.RangeAddress.LastAddress.RowNumber + 3;
- }
- else
- {
- row += 4;
- }
-
- // Write extract MIS data option
- var headerRange = worksheet.Range(row, 1, row, 2);
- HeaderFormatter.ApplyHeaderFormat(headerRange, "Extract MIS data?", merge: true);
- worksheet.Cell(++row, 1).Value = search.ExtractMisData ? "YES" : "NO";
-
- // Auto-fit columns with 15% padding
- for (var column = 1; column <= 4; column++)
- {
- worksheet.Column(column).AdjustToContents();
- worksheet.Column(column).Width *= ExcelFormats.CriteriaPaddingFactor;
- }
-
- // Apply protection
- WorksheetProtector.ApplyCriteriaProtection(worksheet, _options.Value.CriteriaSheetPassword);
- }
-
- private string FormatTimestamp(DateTime? dateTime)
- {
- if (!dateTime.HasValue)
- {
- return string.Empty;
- }
-
- var options = _options.Value;
- var targetTimezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId);
- var dt = dateTime.Value;
-
- // Convert to target timezone based on the source DateTime's Kind
- var localTime = dt.Kind switch
- {
- DateTimeKind.Utc => TimeZoneInfo.ConvertTimeFromUtc(dt, targetTimezone),
- DateTimeKind.Local => TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Local, targetTimezone),
- // Unspecified - database values are stored in target timezone, no conversion needed
- _ => dt
- };
-
- return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}";
- }
-}
+using JdeScoping.ExcelIO.Options;
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Models.Reporting;
+using Microsoft.Extensions.Options;
+using NPOI.SS.UserModel;
+
+namespace JdeScoping.ExcelIO.Generators;
+
+///
+/// Generates the Search Criteria sheet for Excel export.
+///
+public class CriteriaSheetGenerator
+{
+ private const int ExcelMaxColumnWidth = 255 * 256;
+
+ private readonly IOptions _options;
+ private readonly FluentTableWriter _tableWriter;
+
+ ///
+ /// Initializes a new instance of the CriteriaSheetGenerator class.
+ ///
+ /// Excel export options.
+ /// Fluent table writer.
+ public CriteriaSheetGenerator(
+ IOptions options,
+ FluentTableWriter tableWriter)
+ {
+ _options = options;
+ _tableWriter = tableWriter;
+ }
+
+ ///
+ /// Generates the Search Criteria sheet.
+ ///
+ /// The workbook to add the sheet to.
+ /// The search model with criteria.
+ public void Generate(IWorkbook workbook, SearchModel search)
+ {
+ var worksheet = workbook.CreateSheet("Search Criteria");
+ var row = 1;
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, row, 1), "Search Name");
+ GetOrCreateCell(worksheet, row, 2).SetCellValue(search.Name);
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "User Name");
+ GetOrCreateCell(worksheet, row, 2).SetCellValue(search.UserName);
+
+ row++;
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Submit timestamp");
+ GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.SubmitDt));
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Start timestamp");
+ GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.StartDt));
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, ++row, 1), "Completed timestamp");
+ GetOrCreateCell(worksheet, row, 2).SetCellValue(FormatTimestamp(search.EndDt));
+
+ row++;
+
+ var timespanData = new List
+ {
+ new() { MinimumDt = search.MinimumDt, MaximumDt = search.MaximumDt }
+ };
+
+ var timespanTable = _tableWriter.WriteTable(worksheet, ++row, 1, timespanData);
+ row = GetNextTableStartRow(timespanTable, row);
+
+ var workOrderTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkOrderFilter);
+ row = GetNextTableStartRow(workOrderTable, row);
+
+ var itemNumberTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemNumberFilter);
+ row = GetNextTableStartRow(itemNumberTable, row);
+
+ var profitCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.ProfitCenterFilter);
+ row = GetNextTableStartRow(profitCenterTable, row);
+
+ var workCenterTable = _tableWriter.WriteTable(worksheet, row, 1, search.WorkCenterFilter);
+ row = GetNextTableStartRow(workCenterTable, row);
+
+ var componentLotTable = _tableWriter.WriteTable(worksheet, row, 1, search.ComponentLotFilter);
+ row = GetNextTableStartRow(componentLotTable, row);
+
+ var operatorTable = _tableWriter.WriteTable(worksheet, row, 1, search.OperatorFilter);
+ row = GetNextTableStartRow(operatorTable, row);
+
+ var itemOpMisTable = _tableWriter.WriteTable(worksheet, row, 1, search.ItemOperationMisFilter);
+ row = GetNextTableStartRow(itemOpMisTable, row);
+
+ HeaderFormatter.ApplyHeaderFormat(worksheet, row, 1, row, 2, "Extract MIS data?", merge: true);
+ GetOrCreateCell(worksheet, ++row, 1).SetCellValue(search.ExtractMisData ? "YES" : "NO");
+
+ for (var column = 1; column <= 4; column++)
+ {
+ worksheet.AutoSizeColumn(column - 1);
+ var padded = (int)(worksheet.GetColumnWidth(column - 1) * ExcelFormats.CriteriaPaddingFactor);
+ worksheet.SetColumnWidth(column - 1, padded > ExcelMaxColumnWidth ? ExcelMaxColumnWidth : padded);
+ }
+
+ WorksheetProtector.ApplyCriteriaProtection(worksheet, _options.Value.CriteriaSheetPassword);
+ }
+
+ private static int GetNextTableStartRow(TableWriteResult? result, int fallbackRow)
+ {
+ return result != null ? result.LastRow + 3 : fallbackRow + 4;
+ }
+
+ private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based)
+ {
+ var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1);
+ return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1);
+ }
+
+ private string FormatTimestamp(DateTime? dateTime)
+ {
+ if (!dateTime.HasValue)
+ {
+ return string.Empty;
+ }
+
+ var options = _options.Value;
+ var targetTimezone = TimeZoneInfo.FindSystemTimeZoneById(options.TimezoneId);
+ var dt = dateTime.Value;
+
+ var localTime = dt.Kind switch
+ {
+ DateTimeKind.Utc => TimeZoneInfo.ConvertTimeFromUtc(dt, targetTimezone),
+ DateTimeKind.Local => TimeZoneInfo.ConvertTime(dt, TimeZoneInfo.Local, targetTimezone),
+ _ => dt
+ };
+
+ return $"{localTime:MMM dd, yyyy hh:mm:ss tt} {options.TimezoneAbbreviation}";
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs b/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs
index 0250a3b..5f9d6d1 100644
--- a/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Generators/DataEntryTemplateGenerator.cs
@@ -1,82 +1,92 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Formatting;
-using JdeScoping.ExcelIO.Utilities;
-
-namespace JdeScoping.ExcelIO.Generators;
-
-///
-/// Generates data entry templates for bulk upload.
-///
-public class DataEntryTemplateGenerator
-{
- ///
- /// Generates a single-column data entry template.
- ///
- /// The type of data items.
- /// Optional source data to pre-populate.
- /// Header text for the column.
- /// The Excel file as a byte array.
- public byte[] Generate(IEnumerable? sourceData, string headerText)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Data Entry Template");
-
- // Header
- var headerCell = worksheet.Cell(1, 1);
- HeaderFormatter.ApplyHeaderFormat(headerCell, headerText);
- worksheet.Column(1).Width = 45;
-
- // Data (if provided)
- if (sourceData != null)
- {
- var row = 2;
- foreach (var item in sourceData)
- {
- worksheet.Cell(row++, 1).Value = CellValueConverter.ConvertToXlValue(item);
- }
- }
-
- // All cells as text
- worksheet.Column(1).Style.NumberFormat.Format = ExcelFormats.StdFormat;
-
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-
- ///
- /// Generates a multi-column data entry template.
- ///
- /// Optional source data to pre-populate (array of rows, each row is array of values).
- /// Header texts for each column.
- /// The Excel file as a byte array.
- public byte[] Generate(object[][]? sourceData, string[] headers)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Data Entry Template");
-
- // Headers
- for (var col = 0; col < headers.Length; col++)
- {
- HeaderFormatter.ApplyHeaderFormat(worksheet.Cell(1, col + 1), headers[col]);
- worksheet.Column(col + 1).Width = 65;
- worksheet.Column(col + 1).Style.NumberFormat.Format = ExcelFormats.StdFormat;
- }
-
- // Data
- if (sourceData != null)
- {
- for (var row = 0; row < sourceData.Length; row++)
- {
- for (var col = 0; col < sourceData[row].Length; col++)
- {
- worksheet.Cell(row + 2, col + 1).Value = CellValueConverter.ConvertToXlValue(sourceData[row][col]);
- }
- }
- }
-
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-}
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Utilities;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Generators;
+
+///
+/// Generates data entry templates for bulk upload.
+///
+public class DataEntryTemplateGenerator
+{
+ ///
+ /// Generates a single-column data entry template.
+ ///
+ /// The type of data items.
+ /// Optional source data to pre-populate.
+ /// Header text for the column.
+ /// The Excel file as a byte array.
+ public byte[] Generate(IEnumerable? sourceData, string headerText)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Data Entry Template");
+ var style = workbook.CreateCellStyle();
+ style.DataFormat = workbook.CreateDataFormat().GetFormat(ExcelFormats.StdFormat);
+ worksheet.SetDefaultColumnStyle(0, style);
+
+ var headerCell = GetOrCreateCell(worksheet, 1, 1);
+ HeaderFormatter.ApplyHeaderFormat(headerCell, headerText);
+ worksheet.SetColumnWidth(0, 45 * 256);
+
+ if (sourceData != null)
+ {
+ var row = 2;
+ foreach (var item in sourceData)
+ {
+ var cell = GetOrCreateCell(worksheet, row++, 1);
+ CellValueConverter.SetCellValue(cell, item);
+ }
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ ///
+ /// Generates a multi-column data entry template.
+ ///
+ /// Optional source data to pre-populate (array of rows, each row is array of values).
+ /// Header texts for each column.
+ /// The Excel file as a byte array.
+ public byte[] Generate(object[][]? sourceData, string[] headers)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Data Entry Template");
+
+ var dataFormat = workbook.CreateDataFormat();
+
+ for (var col = 0; col < headers.Length; col++)
+ {
+ var style = workbook.CreateCellStyle();
+ style.DataFormat = dataFormat.GetFormat(ExcelFormats.StdFormat);
+ worksheet.SetDefaultColumnStyle(col, style);
+
+ HeaderFormatter.ApplyHeaderFormat(GetOrCreateCell(worksheet, 1, col + 1), headers[col]);
+ worksheet.SetColumnWidth(col, 65 * 256);
+ }
+
+ if (sourceData != null)
+ {
+ for (var row = 0; row < sourceData.Length; row++)
+ {
+ for (var col = 0; col < sourceData[row].Length; col++)
+ {
+ var cell = GetOrCreateCell(worksheet, row + 2, col + 1);
+ CellValueConverter.SetCellValue(cell, sourceData[row][col]);
+ }
+ }
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based)
+ {
+ var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1);
+ return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1);
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
index 1ef8c3c..c1111e8 100644
--- a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
@@ -1,142 +1,248 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Formatting;
-using JdeScoping.ExcelIO.Mapping;
-using JdeScoping.ExcelIO.Utilities;
-
-namespace JdeScoping.ExcelIO.Generators;
-
-///
-/// Writes Excel tables using fluent mapping configuration.
-///
-public sealed class FluentTableWriter
-{
- private readonly ExcelMapRegistry _registry;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The registry containing Excel column maps for types.
- public FluentTableWriter(ExcelMapRegistry registry)
- {
- _registry = registry;
- }
-
- ///
- /// Writes a table to the worksheet using the registered map for type T.
- ///
- /// The type of objects to write to the table.
- /// The Excel worksheet to write to.
- /// The starting row number (1-based).
- /// The starting column number (1-based).
- /// The data to write to the table.
- /// Optional override for the table name; uses map name if null.
- /// Whether to show a merged header above the table.
- /// Optional text to display in the merged header.
- /// The created Excel table, or null if no columns are configured.
- public IXLTable? WriteTable(
- IXLWorksheet worksheet,
- int startRow,
- int startCol,
- IEnumerable data,
- string? tableNameOverride = null,
- bool showHeader = false,
- string? headerText = null)
- {
- var map = _registry.GetMap();
- var columns = map.Columns;
- var tableName = tableNameOverride ?? map.TableName ?? typeof(T).Name;
- var header = headerText ?? map.TabName ?? string.Empty;
-
- if (columns.Count == 0)
- return null;
-
- var dataList = data.ToList();
- var baseRow = startRow;
-
- // Write merged header if requested
- if (showHeader && !string.IsNullOrEmpty(header))
- {
- var mergedHeaderRange = worksheet.Range(baseRow, startCol, baseRow, startCol + columns.Count - 1);
- HeaderFormatter.ApplyHeaderFormat(mergedHeaderRange, header, merge: true);
- baseRow++;
- }
-
- // Write column headers
- var col = startCol;
- foreach (var column in columns)
- {
- var cell = worksheet.Cell(baseRow, col);
- HeaderFormatter.ApplyHeaderFormat(cell, column.HeaderText);
-
- // Pre-set column formatting
- worksheet.Column(col).Style.Alignment.WrapText = column.WrapText;
- if (!column.AutoWidth)
- {
- worksheet.Column(col).Width = column.Width;
- }
-
- col++;
- }
-
- // Write data rows
- var row = baseRow + 1;
- foreach (var item in dataList)
- {
- col = startCol;
- foreach (var column in columns)
- {
- var value = column.ValueGetter(item!);
- worksheet.Cell(row, col).Value = CellValueConverter.ConvertToXlValue(value);
- col++;
- }
- row++;
- }
-
- // Handle empty data case
- if (dataList.Count == 0)
- {
- row = baseRow + 1;
- }
-
- // Create table range
- var dataRange = worksheet.Range(
- baseRow, startCol,
- baseRow + dataList.Count, startCol + columns.Count - 1);
-
- // Create table
- var table = dataRange.CreateTable(tableName);
- table.Theme = XLTableTheme.TableStyleLight18;
- table.ShowTotalsRow = false;
-
- // Apply column formatting
- col = startCol;
- var tableStartRow = table.RangeAddress.FirstAddress.RowNumber;
- var tableEndRow = table.RangeAddress.LastAddress.RowNumber;
-
- foreach (var column in columns)
- {
- // Apply number format
- worksheet.Range(tableStartRow, col, tableEndRow, col)
- .Style.NumberFormat.Format = column.Format;
-
- // Apply column width
- if (column.WrapText && !column.AutoWidth)
- {
- worksheet.Column(col).Width = column.Width;
- }
- else if (column.AutoWidth)
- {
- worksheet.Column(col).AdjustToContents();
- worksheet.Column(col).Width *= ExcelFormats.DataPaddingFactor;
- }
- else
- {
- worksheet.Column(col).Width = column.Width;
- }
-
- col++;
- }
-
- return table;
- }
-}
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Mapping;
+using JdeScoping.ExcelIO.Utilities;
+using NPOI.SS;
+using NPOI.SS.UserModel;
+using NPOI.SS.Util;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Generators;
+
+///
+/// Result metadata for a written table region.
+///
+public sealed class TableWriteResult
+{
+ public int FirstRow { get; init; }
+ public int LastRow { get; init; }
+ public int FirstCol { get; init; }
+ public int LastCol { get; init; }
+ public XSSFTable? Table { get; init; }
+}
+
+///
+/// Writes Excel tables using fluent mapping configuration.
+///
+public sealed class FluentTableWriter
+{
+ private const int ExcelMaxColumnWidth = 255 * 256;
+
+ private readonly ExcelMapRegistry _registry;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The registry containing Excel column maps for types.
+ public FluentTableWriter(ExcelMapRegistry registry)
+ {
+ _registry = registry;
+ }
+
+ ///
+ /// Writes a table to the worksheet using the registered map for type T.
+ ///
+ /// The type of objects to write to the table.
+ /// The Excel worksheet to write to.
+ /// The starting row number (1-based).
+ /// The starting column number (1-based).
+ /// The data to write to the table.
+ /// Optional override for the table name; uses map name if null.
+ /// Whether to show a merged header above the table.
+ /// Optional text to display in the merged header.
+ /// The created table metadata, or null if no columns are configured.
+ public TableWriteResult? WriteTable(
+ ISheet worksheet,
+ int startRow,
+ int startCol,
+ IEnumerable data,
+ string? tableNameOverride = null,
+ bool showHeader = false,
+ string? headerText = null)
+ {
+ var map = _registry.GetMap();
+ var columns = map.Columns;
+ var tableName = tableNameOverride ?? map.TableName ?? typeof(T).Name;
+ var header = headerText ?? map.TabName ?? string.Empty;
+
+ if (columns.Count == 0)
+ {
+ return null;
+ }
+
+ var dataList = data.ToList();
+ var baseRow = startRow;
+
+ if (showHeader && !string.IsNullOrEmpty(header))
+ {
+ HeaderFormatter.ApplyHeaderFormat(
+ worksheet,
+ baseRow,
+ startCol,
+ baseRow,
+ startCol + columns.Count - 1,
+ header,
+ merge: true);
+ baseRow++;
+ }
+
+ var dataFormat = worksheet.Workbook.CreateDataFormat();
+ var columnStyles = new ICellStyle[columns.Count];
+
+ // Write column headers and set column defaults.
+ for (var i = 0; i < columns.Count; i++)
+ {
+ var col = startCol + i;
+ var columnStyle = worksheet.Workbook.CreateCellStyle();
+ if (!string.IsNullOrEmpty(columns[i].Format))
+ {
+ columnStyle.DataFormat = dataFormat.GetFormat(columns[i].Format);
+ }
+
+ columnStyle.WrapText = columns[i].WrapText;
+ columnStyles[i] = columnStyle;
+
+ var headerRow = GetOrCreateRow(worksheet, baseRow);
+ var headerCell = GetOrCreateCell(headerRow, col);
+ HeaderFormatter.ApplyHeaderFormat(headerCell, columns[i].HeaderText);
+ }
+
+ // Write data rows.
+ var row = baseRow + 1;
+ foreach (var item in dataList)
+ {
+ var npoiRow = GetOrCreateRow(worksheet, row);
+
+ for (var i = 0; i < columns.Count; i++)
+ {
+ var col = startCol + i;
+ var column = columns[i];
+ var value = column.ValueGetter(item!);
+ var cell = GetOrCreateCell(npoiRow, col);
+ CellValueConverter.SetCellValue(cell, value);
+ cell.CellStyle = columnStyles[i];
+ }
+
+ row++;
+ }
+
+ if (dataList.Count == 0)
+ {
+ row = baseRow + 1;
+ }
+
+ var firstRow = baseRow;
+ var lastRow = baseRow + dataList.Count;
+ var firstCol = startCol;
+ var lastCol = startCol + columns.Count - 1;
+
+ var table = CreateTableIfSupported(worksheet, tableName, firstRow, lastRow, firstCol, lastCol);
+
+ // Preserve filter behavior.
+ worksheet.SetAutoFilter(new CellRangeAddress(firstRow - 1, lastRow - 1, firstCol - 1, lastCol - 1));
+
+ // Apply column widths.
+ for (var i = 0; i < columns.Count; i++)
+ {
+ var column = columns[i];
+ var colIndex = startCol + i - 1;
+
+ if (column.WrapText && !column.AutoWidth)
+ {
+ worksheet.SetColumnWidth(colIndex, ToNpoiWidth(column.Width));
+ }
+ else if (column.AutoWidth)
+ {
+ worksheet.AutoSizeColumn(colIndex);
+ var width = worksheet.GetColumnWidth(colIndex);
+ var padded = (int)(width * ExcelFormats.DataPaddingFactor);
+ worksheet.SetColumnWidth(colIndex, ClampWidth(padded));
+ }
+ else
+ {
+ worksheet.SetColumnWidth(colIndex, ToNpoiWidth(column.Width));
+ }
+ }
+
+ return new TableWriteResult
+ {
+ FirstRow = firstRow,
+ LastRow = lastRow,
+ FirstCol = firstCol,
+ LastCol = lastCol,
+ Table = table
+ };
+ }
+
+ private static IRow GetOrCreateRow(ISheet sheet, int rowNumber1Based)
+ {
+ return sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1);
+ }
+
+ private static ICell GetOrCreateCell(IRow row, int colNumber1Based)
+ {
+ return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1);
+ }
+
+ private static XSSFTable? CreateTableIfSupported(
+ ISheet worksheet,
+ string tableName,
+ int firstRow,
+ int lastRow,
+ int firstCol,
+ int lastCol)
+ {
+ if (worksheet is not XSSFSheet xssfSheet)
+ {
+ return null;
+ }
+
+ var safeName = ToSafeTableName(tableName);
+ var area = new AreaReference(
+ new CellReference(firstRow - 1, firstCol - 1),
+ new CellReference(lastRow - 1, lastCol - 1),
+ SpreadsheetVersion.EXCEL2007);
+
+ var table = xssfSheet.CreateTable();
+ table.Name = safeName;
+ table.DisplayName = safeName;
+ table.StyleName = "TableStyleLight18";
+ table.CellReferences = area;
+ table.UpdateReferences();
+ table.UpdateHeaders();
+
+ return table;
+ }
+
+ private static string ToSafeTableName(string tableName)
+ {
+ var chars = tableName.Where(ch => char.IsLetterOrDigit(ch) || ch == '_').ToArray();
+ var value = new string(chars);
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return "Table1";
+ }
+
+ if (!char.IsLetter(value[0]) && value[0] != '_')
+ {
+ value = $"_{value}";
+ }
+
+ return value;
+ }
+
+ private static int ToNpoiWidth(double widthInChars)
+ {
+ return ClampWidth((int)(widthInChars * 256));
+ }
+
+ private static int ClampWidth(int width)
+ {
+ if (width < 0)
+ {
+ return 0;
+ }
+
+ return width > ExcelMaxColumnWidth ? ExcelMaxColumnWidth : width;
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj b/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj
index a7915c7..3655446 100644
--- a/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj
+++ b/NEW/src/JdeScoping.ExcelIO/JdeScoping.ExcelIO.csproj
@@ -6,10 +6,10 @@
enable
-
-
-
-
+
+
+
+
diff --git a/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs b/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs
index d2e4525..bac0277 100644
--- a/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Parsing/ExcelParserService.cs
@@ -1,136 +1,166 @@
-using ClosedXML.Excel;
-using JdeScoping.Core.Interfaces;
-using JdeScoping.Core.ViewModels;
-using Microsoft.Extensions.Logging;
-
-namespace JdeScoping.ExcelIO.Parsing;
-
-///
-/// Service for parsing Excel files uploaded by users.
-///
-public class ExcelParserService : IExcelParserService
-{
- private readonly ILogger _logger;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger instance.
- public ExcelParserService(ILogger logger)
- {
- _logger = logger;
- }
-
- ///
- public List ParseWorkOrders(Stream fileStream)
- {
- using var workbook = new XLWorkbook(fileStream);
- var worksheet = workbook.Worksheet(1);
-
- var workOrderNumbers = new List();
- var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1;
-
- for (var row = 2; row <= lastRow; row++)
- {
- var cellValue = worksheet.Cell(row, 1).GetString()?.Trim();
- if (long.TryParse(cellValue, out var woNumber))
- {
- workOrderNumbers.Add(woNumber);
- }
- }
-
- return workOrderNumbers;
- }
-
- ///
- public List ParseItems(Stream fileStream)
- {
- using var workbook = new XLWorkbook(fileStream);
- var worksheet = workbook.Worksheet(1);
-
- var itemNumbers = new List();
- var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1;
-
- for (var row = 2; row <= lastRow; row++)
- {
- var cellValue = worksheet.Cell(row, 1).GetString()?.Trim();
- if (!string.IsNullOrEmpty(cellValue))
- {
- itemNumbers.Add(cellValue);
- }
- }
-
- return itemNumbers;
- }
-
- ///
- public List ParseComponentLots(Stream fileStream)
- {
- using var workbook = new XLWorkbook(fileStream);
- var worksheet = workbook.Worksheet(1);
-
- var lotViewModels = new List();
- var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1;
-
- for (var row = 2; row <= lastRow; row++)
- {
- var lotNumber = worksheet.Cell(row, 1).GetString()?.Trim() ?? string.Empty;
- var itemNumber = worksheet.Cell(row, 2).GetString()?.Trim() ?? string.Empty;
-
- if (!string.IsNullOrEmpty(lotNumber))
- {
- lotViewModels.Add(new LotViewModel
- {
- LotNumber = lotNumber,
- ItemNumber = itemNumber
- });
- }
- }
-
- return lotViewModels;
- }
-
- ///
- public List ParsePartOperations(Stream fileStream)
- {
- using var workbook = new XLWorkbook(fileStream);
- var worksheet = workbook.Worksheet(1);
-
- var partOperations = new List();
- var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 1;
-
- for (var row = 2; row <= lastRow; row++)
- {
- try
- {
- var itemNumber = worksheet.Cell(row, 1).GetString()?.Trim() ?? string.Empty;
- var operationNumber = worksheet.Cell(row, 2).GetString()?.Trim() ?? string.Empty;
- var misNumber = worksheet.Cell(row, 3).GetString()?.Trim() ?? string.Empty;
- var misRevision = worksheet.Cell(row, 4).GetString()?.Trim() ?? string.Empty;
-
- // Remove decimal places from operation number
- if (!string.IsNullOrEmpty(operationNumber) && operationNumber.Contains('.'))
- {
- operationNumber = operationNumber[..operationNumber.IndexOf('.')];
- }
-
- if (!string.IsNullOrEmpty(itemNumber) && !string.IsNullOrEmpty(operationNumber))
- {
- partOperations.Add(new PartOperationViewModel
- {
- ItemNumber = itemNumber,
- OperationNumber = operationNumber,
- MisNumber = misNumber,
- MisRevision = misRevision
- });
- }
- }
- catch (Exception ex)
- {
- _logger.LogWarning(ex, "Failed to parse row {RowNumber} in part operations sheet", row);
- }
- }
-
- return partOperations;
- }
-}
+using JdeScoping.Core.Interfaces;
+using JdeScoping.Core.ViewModels;
+using Microsoft.Extensions.Logging;
+using NPOI.SS.UserModel;
+
+namespace JdeScoping.ExcelIO.Parsing;
+
+///
+/// Service for parsing Excel files uploaded by users.
+///
+public class ExcelParserService : IExcelParserService
+{
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger instance.
+ public ExcelParserService(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ ///
+ public List ParseWorkOrders(Stream fileStream)
+ {
+ using var workbook = WorkbookFactory.Create(ResetStream(fileStream));
+ var worksheet = workbook.GetSheetAt(0);
+ var formatter = new DataFormatter();
+
+ var workOrderNumbers = new List();
+ var lastRow = worksheet.LastRowNum + 1;
+
+ for (var row = 2; row <= lastRow; row++)
+ {
+ var cellValue = GetCellText(worksheet, row, 1, formatter);
+ if (long.TryParse(cellValue, out var woNumber))
+ {
+ workOrderNumbers.Add(woNumber);
+ }
+ }
+
+ return workOrderNumbers;
+ }
+
+ ///
+ public List ParseItems(Stream fileStream)
+ {
+ using var workbook = WorkbookFactory.Create(ResetStream(fileStream));
+ var worksheet = workbook.GetSheetAt(0);
+ var formatter = new DataFormatter();
+
+ var itemNumbers = new List();
+ var lastRow = worksheet.LastRowNum + 1;
+
+ for (var row = 2; row <= lastRow; row++)
+ {
+ var cellValue = GetCellText(worksheet, row, 1, formatter);
+ if (!string.IsNullOrEmpty(cellValue))
+ {
+ itemNumbers.Add(cellValue);
+ }
+ }
+
+ return itemNumbers;
+ }
+
+ ///
+ public List ParseComponentLots(Stream fileStream)
+ {
+ using var workbook = WorkbookFactory.Create(ResetStream(fileStream));
+ var worksheet = workbook.GetSheetAt(0);
+ var formatter = new DataFormatter();
+
+ var lotViewModels = new List();
+ var lastRow = worksheet.LastRowNum + 1;
+
+ for (var row = 2; row <= lastRow; row++)
+ {
+ var lotNumber = GetCellText(worksheet, row, 1, formatter);
+ var itemNumber = GetCellText(worksheet, row, 2, formatter);
+
+ if (!string.IsNullOrEmpty(lotNumber))
+ {
+ lotViewModels.Add(new LotViewModel
+ {
+ LotNumber = lotNumber,
+ ItemNumber = itemNumber
+ });
+ }
+ }
+
+ return lotViewModels;
+ }
+
+ ///
+ public List ParsePartOperations(Stream fileStream)
+ {
+ using var workbook = WorkbookFactory.Create(ResetStream(fileStream));
+ var worksheet = workbook.GetSheetAt(0);
+ var formatter = new DataFormatter();
+
+ var partOperations = new List();
+ var lastRow = worksheet.LastRowNum + 1;
+
+ for (var row = 2; row <= lastRow; row++)
+ {
+ try
+ {
+ var itemNumber = GetCellText(worksheet, row, 1, formatter);
+ var operationNumber = GetCellText(worksheet, row, 2, formatter);
+ var misNumber = GetCellText(worksheet, row, 3, formatter);
+ var misRevision = GetCellText(worksheet, row, 4, formatter);
+
+ if (!string.IsNullOrEmpty(operationNumber) && operationNumber.Contains('.'))
+ {
+ operationNumber = operationNumber[..operationNumber.IndexOf('.')];
+ }
+
+ if (!string.IsNullOrEmpty(itemNumber) && !string.IsNullOrEmpty(operationNumber))
+ {
+ partOperations.Add(new PartOperationViewModel
+ {
+ ItemNumber = itemNumber,
+ OperationNumber = operationNumber,
+ MisNumber = misNumber,
+ MisRevision = misRevision
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Failed to parse row {RowNumber} in part operations sheet", row);
+ }
+ }
+
+ return partOperations;
+ }
+
+ private static string GetCellText(ISheet sheet, int rowNumber1Based, int colNumber1Based, DataFormatter formatter)
+ {
+ var row = sheet.GetRow(rowNumber1Based - 1);
+ if (row == null)
+ {
+ return string.Empty;
+ }
+
+ var cell = row.GetCell(colNumber1Based - 1);
+ if (cell == null)
+ {
+ return string.Empty;
+ }
+
+ return formatter.FormatCellValue(cell).Trim();
+ }
+
+ private static Stream ResetStream(Stream stream)
+ {
+ if (stream.CanSeek)
+ {
+ stream.Position = 0;
+ }
+
+ return stream;
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs b/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs
index 8caa0c9..663cc2e 100644
--- a/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Templates/ExcelTemplateService.cs
@@ -1,70 +1,85 @@
-using ClosedXML.Excel;
-using JdeScoping.Core.Interfaces;
-
-namespace JdeScoping.ExcelIO.Templates;
-
-///
-/// Service for generating Excel template files for data entry.
-///
-public class ExcelTemplateService : IExcelTemplateService
-{
- ///
- public byte[] GenerateSingleColumn(IEnumerable data, string headerText)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Template");
-
- // Write header
- worksheet.Cell(1, 1).Value = headerText;
- worksheet.Cell(1, 1).Style.Font.Bold = true;
-
- // Write data
- var row = 2;
- foreach (var item in data)
- {
- worksheet.Cell(row, 1).Value = item?.ToString() ?? string.Empty;
- row++;
- }
-
- // Auto-fit column width
- worksheet.Column(1).AdjustToContents();
-
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-
- ///
- public byte[] GenerateMultiColumn(object?[][] data, string[] headers)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Template");
-
- // Write headers
- for (var col = 0; col < headers.Length; col++)
- {
- worksheet.Cell(1, col + 1).Value = headers[col];
- worksheet.Cell(1, col + 1).Style.Font.Bold = true;
- }
-
- // Write data
- for (var row = 0; row < data.Length; row++)
- {
- for (var col = 0; col < data[row].Length; col++)
- {
- var value = data[row][col];
- if (value != null)
- {
- worksheet.Cell(row + 2, col + 1).Value = value.ToString();
- }
- }
- }
-
- // Auto-fit column widths
- worksheet.Columns().AdjustToContents();
-
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-}
+using JdeScoping.Core.Interfaces;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Templates;
+
+///
+/// Service for generating Excel template files for data entry.
+///
+public class ExcelTemplateService : IExcelTemplateService
+{
+ ///
+ public byte[] GenerateSingleColumn(IEnumerable data, string headerText)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Template");
+
+ var headerCell = GetOrCreateCell(worksheet, 1, 1);
+ headerCell.SetCellValue(headerText);
+
+ var boldStyle = workbook.CreateCellStyle();
+ var boldFont = workbook.CreateFont();
+ boldFont.IsBold = true;
+ boldStyle.SetFont(boldFont);
+ headerCell.CellStyle = boldStyle;
+
+ var row = 2;
+ foreach (var item in data)
+ {
+ GetOrCreateCell(worksheet, row++, 1).SetCellValue(item?.ToString() ?? string.Empty);
+ }
+
+ worksheet.AutoSizeColumn(0);
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ ///
+ public byte[] GenerateMultiColumn(object?[][] data, string[] headers)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Template");
+
+ var boldStyle = workbook.CreateCellStyle();
+ var boldFont = workbook.CreateFont();
+ boldFont.IsBold = true;
+ boldStyle.SetFont(boldFont);
+
+ for (var col = 0; col < headers.Length; col++)
+ {
+ var headerCell = GetOrCreateCell(worksheet, 1, col + 1);
+ headerCell.SetCellValue(headers[col]);
+ headerCell.CellStyle = boldStyle;
+ }
+
+ for (var row = 0; row < data.Length; row++)
+ {
+ for (var col = 0; col < data[row].Length; col++)
+ {
+ var value = data[row][col];
+ if (value != null)
+ {
+ GetOrCreateCell(worksheet, row + 2, col + 1).SetCellValue(value.ToString());
+ }
+ }
+ }
+
+ for (var col = 0; col < headers.Length; col++)
+ {
+ worksheet.AutoSizeColumn(col);
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ private static ICell GetOrCreateCell(ISheet sheet, int rowNumber1Based, int colNumber1Based)
+ {
+ var row = sheet.GetRow(rowNumber1Based - 1) ?? sheet.CreateRow(rowNumber1Based - 1);
+ return row.GetCell(colNumber1Based - 1) ?? row.CreateCell(colNumber1Based - 1);
+ }
+}
diff --git a/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs b/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs
index 4c17b0b..bbf0d92 100644
--- a/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Utilities/CellValueConverter.cs
@@ -1,31 +1,51 @@
-using ClosedXML.Excel;
-
-namespace JdeScoping.ExcelIO.Utilities;
-
-///
-/// Utility class for converting .NET objects to ClosedXML cell values.
-///
-public static class CellValueConverter
-{
- ///
- /// Converts a .NET object to an XLCellValue for use in ClosedXML worksheets.
- ///
- /// The value to convert.
- /// An XLCellValue suitable for setting as a cell value.
- public static XLCellValue ConvertToXlValue(object? value)
- {
- return value switch
- {
- null => Blank.Value,
- string s => s,
- int i => i,
- long l => l,
- decimal d => d,
- double dbl => dbl,
- float f => f,
- DateTime dt => dt,
- bool b => b,
- _ => value.ToString() ?? string.Empty
- };
- }
-}
+using NPOI.SS.UserModel;
+
+namespace JdeScoping.ExcelIO.Utilities;
+
+///
+/// Utility class for converting .NET objects to NPOI cell values.
+///
+public static class CellValueConverter
+{
+ ///
+ /// Sets an NPOI cell value from a .NET object.
+ ///
+ /// The target cell.
+ /// The value to write.
+ public static void SetCellValue(ICell cell, object? value)
+ {
+ switch (value)
+ {
+ case null:
+ cell.SetCellType(CellType.Blank);
+ break;
+ case string s:
+ cell.SetCellValue(s);
+ break;
+ case int i:
+ cell.SetCellValue(i);
+ break;
+ case long l:
+ cell.SetCellValue(l);
+ break;
+ case decimal d:
+ cell.SetCellValue((double)d);
+ break;
+ case double dbl:
+ cell.SetCellValue(dbl);
+ break;
+ case float f:
+ cell.SetCellValue(f);
+ break;
+ case DateTime dt:
+ cell.SetCellValue(dt);
+ break;
+ case bool b:
+ cell.SetCellValue(b);
+ break;
+ default:
+ cell.SetCellValue(value.ToString() ?? string.Empty);
+ break;
+ }
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs
index 91080e2..e368f98 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/CriteriaSheetGeneratorTests.cs
@@ -1,431 +1,427 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Options;
-using JdeScoping.ExcelIO.Generators;
-using JdeScoping.ExcelIO.Mapping;
-using JdeScoping.ExcelIO.Mapping.Maps;
-using JdeScoping.ExcelIO.Models.Reporting;
-using Microsoft.Extensions.Options;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests;
-
-public class CriteriaSheetGeneratorTests
-{
- private readonly CriteriaSheetGenerator _generator;
- private readonly IOptions _options;
-
- public CriteriaSheetGeneratorTests()
- {
- _options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
- {
- CriteriaSheetPassword = "TestPassword"
- });
-
- var registry = CreateTestRegistry();
- var tableWriter = new FluentTableWriter(registry);
- _generator = new CriteriaSheetGenerator(_options, tableWriter);
- }
-
- private static ExcelMapRegistry CreateTestRegistry()
- {
- var registry = new ExcelMapRegistry();
- registry.Register(new TimespanFilterMap());
- registry.Register(new WorkOrderFilterEntryMap());
- registry.Register(new ItemNumberFilterEntryMap());
- registry.Register(new ProfitCenterFilterEntryMap());
- registry.Register(new WorkCenterFilterEntryMap());
- registry.Register(new OperatorFilterEntryMap());
- registry.Register(new ComponentLotFilterEntryMap());
- registry.Register(new ItemOperationMisFilterEntryMap());
- return registry;
- }
-
- [Fact]
- public void Generate_CreatesSearchCriteriaSheet()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
-
- _generator.Generate(workbook, search);
-
- workbook.Worksheets.TryGetWorksheet("Search Criteria", out var worksheet).ShouldBeTrue();
- worksheet.ShouldNotBeNull();
- }
-
- [Fact]
- public void Generate_ContainsSearchName()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.Name = "Test Search Name";
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- worksheet.Cell(1, 1).Value.GetText().ShouldBe("Search Name");
- worksheet.Cell(1, 2).Value.GetText().ShouldBe("Test Search Name");
- }
-
- [Fact]
- public void Generate_ContainsUserName()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.UserName = "testuser";
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- worksheet.Cell(2, 1).Value.GetText().ShouldBe("User Name");
- worksheet.Cell(2, 2).Value.GetText().ShouldBe("testuser");
- }
-
- [Fact]
- public void Generate_ContainsTimestamps()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- var submitDt = new DateTime(2024, 1, 15, 10, 30, 0);
- var startDt = new DateTime(2024, 1, 15, 10, 31, 0);
- var endDt = new DateTime(2024, 1, 15, 10, 35, 0);
- search.SubmitDt = submitDt;
- search.StartDt = startDt;
- search.EndDt = endDt;
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- worksheet.Cell(4, 1).Value.GetText().ShouldBe("Submit timestamp");
- worksheet.Cell(4, 2).Value.GetText().ShouldContain("Jan 15, 2024");
-
- worksheet.Cell(5, 1).Value.GetText().ShouldBe("Start timestamp");
- worksheet.Cell(5, 2).Value.GetText().ShouldContain("Jan 15, 2024");
-
- worksheet.Cell(6, 1).Value.GetText().ShouldBe("Completed timestamp");
- worksheet.Cell(6, 2).Value.GetText().ShouldContain("Jan 15, 2024");
- }
-
- [Fact]
- public void Generate_ContainsTimespanFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.MinimumDt = new DateTime(2024, 1, 1);
- search.MaximumDt = new DateTime(2024, 12, 31);
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Timespan_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsWorkOrderFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Work_Order_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsItemNumberFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Item_Number_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsProfitCenterFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ProfitCenterFilter.Add(new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Profit_Center_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsWorkCenterFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.WorkCenterFilter.Add(new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Work_Center_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsOperatorFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.OperatorFilter.Add(new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Operator_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsComponentLotFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ComponentLotFilter.Add(new ComponentLotFilterEntry { LotNumber = "LOT001" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Component_Lot_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsItemOperationMisFilterTable()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ItemOperationMisFilter.Add(new ItemOperationMisFilterEntry
- {
- ItemNumber = "ITEM-001",
- OperationNumber = "10",
- MisNumber = "MIS-001"
- });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
- tables.Any(t => t.Name == "Item_Operation_MIS_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_ContainsExtractMisDataIndicator_WhenTrue()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ExtractMisData = true;
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- // Find the "Extract MIS data?" header and check for YES
- var cells = worksheet.CellsUsed();
- var extractMisCell = cells.FirstOrDefault(c => c.Value.ToString() == "YES" || c.Value.ToString() == "NO");
- extractMisCell.ShouldNotBeNull();
- extractMisCell.Value.GetText().ShouldBe("YES");
- }
-
- [Fact]
- public void Generate_ContainsExtractMisDataIndicator_WhenFalse()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.ExtractMisData = false;
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- // Find the "Extract MIS data?" header and check for NO
- var cells = worksheet.CellsUsed();
- var extractMisCell = cells.FirstOrDefault(c => c.Value.ToString() == "YES" || c.Value.ToString() == "NO");
- extractMisCell.ShouldNotBeNull();
- extractMisCell.Value.GetText().ShouldBe("NO");
- }
-
- [Fact]
- public void Generate_AppliesHeaderFormatting()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- // Search Name header should be bold with Gainsboro background
- var headerCell = worksheet.Cell(1, 1);
- headerCell.Style.Font.Bold.ShouldBeTrue();
- headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro);
- }
-
- [Fact]
- public void Generate_AppliesProtection()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- worksheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_TablesHaveLight18Style()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var table = worksheet.Tables.First(t => t.Name == "Work_Order_Filter");
- table.Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- }
-
- [Fact]
- public void Generate_FilterTables_Have2BlankRowSpacing()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
- search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" });
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var woTable = worksheet.Tables.First(t => t.Name == "Work_Order_Filter");
- var itemTable = worksheet.Tables.First(t => t.Name == "Item_Number_Filter");
-
- // There should be 2 blank rows between tables. With the header row of the next table, that's a gap of 3
- // Looking at CriteriaSheetGenerator: row = table.RangeAddress.LastAddress.RowNumber + 3
- // This means the next table starts 3 rows after the last row, leaving 2 blank rows in between
- var gap = itemTable.RangeAddress.FirstAddress.RowNumber - woTable.RangeAddress.LastAddress.RowNumber;
- // Gap includes header row of next table, so: 2 blank rows + 1 header = gap of 3
- // But with table header (Timespan_Filter has ShowHeader=true), add 1 more
- gap.ShouldBeGreaterThanOrEqualTo(3);
- }
-
- [Fact]
- public void Generate_NullTimestamps_ShowEmptyValue()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.SubmitDt = null;
- search.StartDt = null;
- search.EndDt = null;
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- worksheet.Cell(4, 2).Value.ToString().ShouldBe(string.Empty);
- worksheet.Cell(5, 2).Value.ToString().ShouldBe(string.Empty);
- worksheet.Cell(6, 2).Value.ToString().ShouldBe(string.Empty);
- }
-
- [Fact]
- public void Generate_ColumnsAreAutoFitWithPadding()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.Name = "A Very Long Search Name That Needs Extra Width";
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- // Columns should have been adjusted - verify they have non-default width
- worksheet.Column(1).Width.ShouldBeGreaterThan(0);
- worksheet.Column(2).Width.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Generate_MultipleFiltersWithData_CreatesAllTables()
- {
- using var workbook = new XLWorkbook();
- var search = CreateFullSearchModel();
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var tables = worksheet.Tables;
-
- // Should have 8 filter tables
- tables.Count().ShouldBe(8);
- tables.Any(t => t.Name == "Timespan_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Work_Order_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Item_Number_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Profit_Center_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Work_Center_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Component_Lot_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Operator_Filter").ShouldBeTrue();
- tables.Any(t => t.Name == "Item_Operation_MIS_Filter").ShouldBeTrue();
- }
-
- [Fact]
- public void Generate_TimestampFormat_IncludesESTSuffix()
- {
- using var workbook = new XLWorkbook();
- var search = CreateMinimalSearchModel();
- search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 45);
-
- _generator.Generate(workbook, search);
-
- var worksheet = workbook.Worksheet("Search Criteria");
- var timestampValue = worksheet.Cell(4, 2).Value.GetText();
- timestampValue.ShouldContain("EST");
- timestampValue.ShouldContain("10:30:45");
- }
-
- private static SearchModel CreateMinimalSearchModel()
- {
- return new SearchModel
- {
- Id = 1,
- Name = "Test Search",
- UserName = "testuser",
- SubmitDt = DateTime.Now.AddHours(-1),
- StartDt = DateTime.Now.AddMinutes(-30),
- EndDt = DateTime.Now,
- ExtractMisData = false
- };
- }
-
- private static SearchModel CreateFullSearchModel()
- {
- return new SearchModel
- {
- Id = 1,
- Name = "Full Search",
- UserName = "testuser",
- SubmitDt = DateTime.Now.AddHours(-1),
- StartDt = DateTime.Now.AddMinutes(-30),
- EndDt = DateTime.Now,
- MinimumDt = new DateTime(2024, 1, 1),
- MaximumDt = new DateTime(2024, 12, 31),
- ExtractMisData = true,
- WorkOrderFilter = [new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }],
- ItemNumberFilter = [new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }],
- ProfitCenterFilter = [new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }],
- WorkCenterFilter = [new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }],
- OperatorFilter = [new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }],
- ComponentLotFilter = [new ComponentLotFilterEntry { LotNumber = "LOT001" }],
- ItemOperationMisFilter = [new ItemOperationMisFilterEntry { ItemNumber = "ITEM-001", OperationNumber = "10", MisNumber = "MIS-001" }]
- };
- }
-}
+using JdeScoping.ExcelIO.Options;
+using JdeScoping.ExcelIO.Generators;
+using JdeScoping.ExcelIO.Mapping;
+using JdeScoping.ExcelIO.Mapping.Maps;
+using JdeScoping.ExcelIO.Models.Reporting;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using Microsoft.Extensions.Options;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests;
+
+public class CriteriaSheetGeneratorTests
+{
+ private readonly CriteriaSheetGenerator _generator;
+
+ public CriteriaSheetGeneratorTests()
+ {
+ var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
+ {
+ CriteriaSheetPassword = "TestPassword"
+ });
+
+ var registry = CreateTestRegistry();
+ var tableWriter = new FluentTableWriter(registry);
+ _generator = new CriteriaSheetGenerator(options, tableWriter);
+ }
+
+ private static ExcelMapRegistry CreateTestRegistry()
+ {
+ var registry = new ExcelMapRegistry();
+ registry.Register(new TimespanFilterMap());
+ registry.Register(new WorkOrderFilterEntryMap());
+ registry.Register(new ItemNumberFilterEntryMap());
+ registry.Register(new ProfitCenterFilterEntryMap());
+ registry.Register(new WorkCenterFilterEntryMap());
+ registry.Register(new OperatorFilterEntryMap());
+ registry.Register(new ComponentLotFilterEntryMap());
+ registry.Register(new ItemOperationMisFilterEntryMap());
+ return registry;
+ }
+
+ [Fact]
+ public void Generate_CreatesSearchCriteriaSheet()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+
+ _generator.Generate(workbook, search);
+
+ workbook.GetSheet("Search Criteria").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void Generate_ContainsSearchName()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.Name = "Test Search Name";
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Search Name");
+ ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Test Search Name");
+ }
+
+ [Fact]
+ public void Generate_ContainsUserName()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.UserName = "testuser";
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("User Name");
+ ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe("testuser");
+ }
+
+ [Fact]
+ public void Generate_ContainsTimestamps()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 0);
+ search.StartDt = new DateTime(2024, 1, 15, 10, 31, 0);
+ search.EndDt = new DateTime(2024, 1, 15, 10, 35, 0);
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.GetCellText(worksheet, 4, 1).ShouldBe("Submit timestamp");
+ ExcelTestHelpers.GetCellText(worksheet, 4, 2).ShouldContain("Jan 15, 2024");
+ ExcelTestHelpers.GetCellText(worksheet, 5, 1).ShouldBe("Start timestamp");
+ ExcelTestHelpers.GetCellText(worksheet, 5, 2).ShouldContain("Jan 15, 2024");
+ ExcelTestHelpers.GetCellText(worksheet, 6, 1).ShouldBe("Completed timestamp");
+ ExcelTestHelpers.GetCellText(worksheet, 6, 2).ShouldContain("Jan 15, 2024");
+ }
+
+ [Fact]
+ public void Generate_ContainsTimespanFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.MinimumDt = new DateTime(2024, 1, 1);
+ search.MaximumDt = new DateTime(2024, 12, 31);
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Timespan_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsWorkOrderFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Work_Order_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsItemNumberFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Item_Number_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsProfitCenterFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ProfitCenterFilter.Add(new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Profit_Center_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsWorkCenterFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.WorkCenterFilter.Add(new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Work_Center_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsOperatorFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.OperatorFilter.Add(new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Operator_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsComponentLotFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ComponentLotFilter.Add(new ComponentLotFilterEntry { LotNumber = "LOT001" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Component_Lot_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsItemOperationMisFilterTable()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ItemOperationMisFilter.Add(new ItemOperationMisFilterEntry
+ {
+ ItemNumber = "ITEM-001",
+ OperationNumber = "10",
+ MisNumber = "MIS-001"
+ });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.TableExists(worksheet, "Item_Operation_MIS_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_ContainsExtractMisDataIndicator_WhenTrue()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ExtractMisData = true;
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var yesNo = FindYesNo(worksheet);
+ yesNo.ShouldBe("YES");
+ }
+
+ [Fact]
+ public void Generate_ContainsExtractMisDataIndicator_WhenFalse()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.ExtractMisData = false;
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var yesNo = FindYesNo(worksheet);
+ yesNo.ShouldBe("NO");
+ }
+
+ [Fact]
+ public void Generate_AppliesHeaderFormatting()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var headerCell = ExcelTestHelpers.GetCell(worksheet, 1, 1)!;
+ (headerCell.CellStyle.FontIndex >= 0 &&
+ workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue();
+ ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]);
+ }
+
+ [Fact]
+ public void Generate_AppliesProtection()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.IsSheetProtected(worksheet).ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_TablesHaveLight18Style()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var table = ExcelTestHelpers.GetTableByName(worksheet, "Work_Order_Filter");
+ table.StyleName.ShouldBe("TableStyleLight18");
+ }
+
+ [Fact]
+ public void Generate_FilterTables_Have2BlankRowSpacing()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.WorkOrderFilter.Add(new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" });
+ search.ItemNumberFilter.Add(new ItemNumberFilterEntry { ItemNumber = "ITEM-001" });
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var woTable = ExcelTestHelpers.GetTableByName(worksheet, "Work_Order_Filter");
+ var itemTable = ExcelTestHelpers.GetTableByName(worksheet, "Item_Number_Filter");
+
+ var gap = itemTable.StartCellReference.Row - woTable.EndCellReference.Row;
+ gap.ShouldBeGreaterThanOrEqualTo(3);
+ }
+
+ [Fact]
+ public void Generate_NullTimestamps_ShowEmptyValue()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.SubmitDt = null;
+ search.StartDt = null;
+ search.EndDt = null;
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.GetCellText(worksheet, 4, 2).ShouldBe(string.Empty);
+ ExcelTestHelpers.GetCellText(worksheet, 5, 2).ShouldBe(string.Empty);
+ ExcelTestHelpers.GetCellText(worksheet, 6, 2).ShouldBe(string.Empty);
+ }
+
+ [Fact]
+ public void Generate_ColumnsAreAutoFitWithPadding()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.Name = "A Very Long Search Name That Needs Extra Width";
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBeGreaterThan(0);
+ ExcelTestHelpers.GetColumnWidthChars(worksheet, 2).ShouldBeGreaterThan(0);
+ }
+
+ [Fact]
+ public void Generate_MultipleFiltersWithData_CreatesAllTables()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateFullSearchModel();
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var tableCount = ((XSSFSheet)worksheet).GetTables().Count;
+
+ tableCount.ShouldBe(8);
+ ExcelTestHelpers.TableExists(worksheet, "Timespan_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Work_Order_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Item_Number_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Profit_Center_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Work_Center_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Component_Lot_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Operator_Filter").ShouldBeTrue();
+ ExcelTestHelpers.TableExists(worksheet, "Item_Operation_MIS_Filter").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Generate_TimestampFormat_IncludesESTSuffix()
+ {
+ using var workbook = new XSSFWorkbook();
+ var search = CreateMinimalSearchModel();
+ search.SubmitDt = new DateTime(2024, 1, 15, 10, 30, 45);
+
+ _generator.Generate(workbook, search);
+
+ var worksheet = workbook.GetSheet("Search Criteria");
+ var timestampValue = ExcelTestHelpers.GetCellText(worksheet, 4, 2);
+ timestampValue.ShouldContain("EST");
+ timestampValue.ShouldContain("10:30:45");
+ }
+
+ private static string FindYesNo(NPOI.SS.UserModel.ISheet worksheet)
+ {
+ for (var row = 0; row <= worksheet.LastRowNum; row++)
+ {
+ var npoiRow = worksheet.GetRow(row);
+ if (npoiRow == null)
+ {
+ continue;
+ }
+
+ for (var col = 0; col < npoiRow.LastCellNum; col++)
+ {
+ var text = ExcelTestHelpers.GetCellText(worksheet, row + 1, col + 1);
+ if (text == "YES" || text == "NO")
+ {
+ return text;
+ }
+ }
+ }
+
+ return string.Empty;
+ }
+
+ private static SearchModel CreateMinimalSearchModel()
+ {
+ return new SearchModel
+ {
+ Id = 1,
+ Name = "Test Search",
+ UserName = "testuser",
+ SubmitDt = DateTime.Now.AddHours(-1),
+ StartDt = DateTime.Now.AddMinutes(-30),
+ EndDt = DateTime.Now,
+ ExtractMisData = false
+ };
+ }
+
+ private static SearchModel CreateFullSearchModel()
+ {
+ return new SearchModel
+ {
+ Id = 1,
+ Name = "Full Search",
+ UserName = "testuser",
+ SubmitDt = DateTime.Now.AddHours(-1),
+ StartDt = DateTime.Now.AddMinutes(-30),
+ EndDt = DateTime.Now,
+ MinimumDt = new DateTime(2024, 1, 1),
+ MaximumDt = new DateTime(2024, 12, 31),
+ ExtractMisData = true,
+ WorkOrderFilter = [new WorkOrderFilterEntry { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }],
+ ItemNumberFilter = [new ItemNumberFilterEntry { ItemNumber = "ITEM-001" }],
+ ProfitCenterFilter = [new ProfitCenterFilterEntry { Code = "PC01", Description = "Profit Center 1" }],
+ WorkCenterFilter = [new WorkCenterFilterEntry { Code = "WC01", Description = "Work Center 1" }],
+ OperatorFilter = [new OperatorFilterEntry { UserId = "OP01", FullName = "Operator 1" }],
+ ComponentLotFilter = [new ComponentLotFilterEntry { LotNumber = "LOT001" }],
+ ItemOperationMisFilter = [new ItemOperationMisFilterEntry { ItemNumber = "ITEM-001", OperationNumber = "10", MisNumber = "MIS-001" }]
+ };
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs
index d714573..13f9d69 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/DataEntryTemplateGeneratorTests.cs
@@ -1,145 +1,137 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Generators;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests;
-
-public class DataEntryTemplateGeneratorTests
-{
- private readonly DataEntryTemplateGenerator _generator = new();
-
- [Fact]
- public void Generate_SingleColumn_ReturnsValidExcel()
- {
- var result = _generator.Generate(null, "Test Header");
-
- result.ShouldNotBeNull();
- result.Length.ShouldBeGreaterThan(0);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- workbook.Worksheets.Count.ShouldBe(1);
- }
-
- [Fact]
- public void Generate_SingleColumn_HasCorrectHeader()
- {
- var result = _generator.Generate(null, "Item Numbers");
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Cell(1, 1).Value.GetText().ShouldBe("Item Numbers");
- }
-
- [Fact]
- public void Generate_SingleColumn_HasHeaderFormatting()
- {
- var result = _generator.Generate(null, "Test Header");
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
- var headerCell = worksheet.Cell(1, 1);
-
- headerCell.Style.Font.Bold.ShouldBeTrue();
- headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro);
- }
-
- [Fact]
- public void Generate_SingleColumn_WithData_PopulatesRows()
- {
- var data = new List { "Item1", "Item2", "Item3" };
-
- var result = _generator.Generate(data, "Items");
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Cell(2, 1).Value.GetText().ShouldBe("Item1");
- worksheet.Cell(3, 1).Value.GetText().ShouldBe("Item2");
- worksheet.Cell(4, 1).Value.GetText().ShouldBe("Item3");
- }
-
- [Fact]
- public void Generate_SingleColumn_SetsTextFormat()
- {
- var result = _generator.Generate(null, "Test");
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Column(1).Style.NumberFormat.Format.ShouldBe("@");
- }
-
- [Fact]
- public void Generate_MultiColumn_ReturnsValidExcel()
- {
- var headers = new[] { "Column A", "Column B", "Column C" };
-
- var result = _generator.Generate(null, headers);
-
- result.ShouldNotBeNull();
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Cell(1, 1).Value.GetText().ShouldBe("Column A");
- worksheet.Cell(1, 2).Value.GetText().ShouldBe("Column B");
- worksheet.Cell(1, 3).Value.GetText().ShouldBe("Column C");
- }
-
- [Fact]
- public void Generate_MultiColumn_WithData_PopulatesRows()
- {
- var headers = new[] { "Name", "Value" };
- var data = new[]
- {
- new object[] { "Row1", 100 },
- new object[] { "Row2", 200 }
- };
-
- var result = _generator.Generate(data, headers);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Cell(2, 1).Value.GetText().ShouldBe("Row1");
- worksheet.Cell(2, 2).Value.GetNumber().ShouldBe(100);
- worksheet.Cell(3, 1).Value.GetText().ShouldBe("Row2");
- worksheet.Cell(3, 2).Value.GetNumber().ShouldBe(200);
- }
-
- [Fact]
- public void Generate_MultiColumn_SetsColumnWidth()
- {
- var headers = new[] { "Column A", "Column B" };
-
- var result = _generator.Generate(null, headers);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Column(1).Width.ShouldBe(65);
- worksheet.Column(2).Width.ShouldBe(65);
- }
-
- [Fact]
- public void Generate_SingleColumn_SetsColumnWidth()
- {
- var result = _generator.Generate(null, "Test");
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheets.First();
-
- worksheet.Column(1).Width.ShouldBe(45);
- }
-}
+using JdeScoping.ExcelIO.Generators;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests;
+
+public class DataEntryTemplateGeneratorTests
+{
+ private readonly DataEntryTemplateGenerator _generator = new();
+
+ [Fact]
+ public void Generate_SingleColumn_ReturnsValidExcel()
+ {
+ var result = _generator.Generate(null, "Test Header");
+
+ result.ShouldNotBeNull();
+ result.Length.ShouldBeGreaterThan(0);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ workbook.NumberOfSheets.ShouldBe(1);
+ }
+
+ [Fact]
+ public void Generate_SingleColumn_HasCorrectHeader()
+ {
+ var result = _generator.Generate(null, "Item Numbers");
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Item Numbers");
+ }
+
+ [Fact]
+ public void Generate_SingleColumn_HasHeaderFormatting()
+ {
+ var result = _generator.Generate(null, "Test Header");
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+ var headerCell = ExcelTestHelpers.GetCell(worksheet, 1, 1)!;
+
+ (headerCell.CellStyle.FontIndex >= 0 &&
+ workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue();
+ ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]);
+ }
+
+ [Fact]
+ public void Generate_SingleColumn_WithData_PopulatesRows()
+ {
+ var data = new List { "Item1", "Item2", "Item3" };
+
+ var result = _generator.Generate(data, "Items");
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("Item1");
+ ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("Item2");
+ ExcelTestHelpers.GetCellText(worksheet, 4, 1).ShouldBe("Item3");
+ }
+
+ [Fact]
+ public void Generate_SingleColumn_SetsTextFormat()
+ {
+ var result = _generator.Generate(null, "Test");
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ worksheet.GetColumnStyle(0).GetDataFormatString().ShouldBe("@");
+ }
+
+ [Fact]
+ public void Generate_MultiColumn_ReturnsValidExcel()
+ {
+ var headers = new[] { "Column A", "Column B", "Column C" };
+
+ var result = _generator.Generate(null, headers);
+
+ result.ShouldNotBeNull();
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Column A");
+ ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Column B");
+ ExcelTestHelpers.GetCellText(worksheet, 1, 3).ShouldBe("Column C");
+ }
+
+ [Fact]
+ public void Generate_MultiColumn_WithData_PopulatesRows()
+ {
+ var headers = new[] { "Name", "Value" };
+ var data = new[]
+ {
+ new object[] { "Row1", 100 },
+ new object[] { "Row2", 200 }
+ };
+
+ var result = _generator.Generate(data, headers);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("Row1");
+ ExcelTestHelpers.GetCellNumber(worksheet, 2, 2).ShouldBe(100);
+ ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("Row2");
+ ExcelTestHelpers.GetCellNumber(worksheet, 3, 2).ShouldBe(200);
+ }
+
+ [Fact]
+ public void Generate_MultiColumn_SetsColumnWidth()
+ {
+ var headers = new[] { "Column A", "Column B" };
+
+ var result = _generator.Generate(null, headers);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBe(65);
+ ExcelTestHelpers.GetColumnWidthChars(worksheet, 2).ShouldBe(65);
+ }
+
+ [Fact]
+ public void Generate_SingleColumn_SetsColumnWidth()
+ {
+ var result = _generator.Generate(null, "Test");
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetColumnWidthChars(worksheet, 1).ShouldBe(45);
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs
index a0e0821..d86f0fa 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/ExcelExportServiceTests.cs
@@ -1,249 +1,228 @@
-using ClosedXML.Excel;
-using JdeScoping.Core.Models.SearchResults;
-using JdeScoping.ExcelIO.Options;
-using JdeScoping.ExcelIO.Generators;
-using JdeScoping.ExcelIO.Mapping;
-using JdeScoping.ExcelIO.Mapping.Maps;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using NSubstitute;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests;
-
-public class ExcelExportServiceTests
-{
- private readonly ExcelExportService _service;
- private readonly ILogger _logger;
- private readonly IOptions _options;
-
- public ExcelExportServiceTests()
- {
- _logger = Substitute.For>();
- _options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
- {
- CriteriaSheetPassword = "TestCriteriaPass",
- DataSheetPassword = "TestDataPass"
- });
-
- var registry = CreateTestRegistry();
- var tableWriter = new FluentTableWriter(registry);
- var criteriaGenerator = new CriteriaSheetGenerator(_options, tableWriter);
-
- _service = new ExcelExportService(_logger, _options, criteriaGenerator, tableWriter, registry);
- }
-
- private static ExcelMapRegistry CreateTestRegistry()
- {
- var registry = new ExcelMapRegistry();
-
- // Search result maps
- registry.Register(new SearchResultMap());
- registry.Register(new MisSearchResultMap());
- registry.Register(new MisNonMatchSearchResultMap());
-
- // Filter entry maps
- registry.Register(new TimespanFilterMap());
- registry.Register(new WorkOrderFilterEntryMap());
- registry.Register(new ItemNumberFilterEntryMap());
- registry.Register(new ProfitCenterFilterEntryMap());
- registry.Register(new WorkCenterFilterEntryMap());
- registry.Register(new OperatorFilterEntryMap());
- registry.Register(new ComponentLotFilterEntryMap());
- registry.Register(new ItemOperationMisFilterEntryMap());
-
- return registry;
- }
-
- [Fact]
- public async Task GenerateAsync_ReturnsValidExcelBytes()
- {
- var search = CreateMinimalSearchModel();
-
- var result = await _service.GenerateAsync(search);
-
- result.ShouldNotBeNull();
- result.Length.ShouldBeGreaterThan(0);
-
- // Verify it's a valid Excel file
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- workbook.Worksheets.Count.ShouldBeGreaterThanOrEqualTo(2);
- }
-
- [Fact]
- public async Task GenerateAsync_CreatesSearchCriteriaSheet()
- {
- var search = CreateMinimalSearchModel();
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
-
- workbook.Worksheets.TryGetWorksheet("Search Criteria", out var criteriaSheet).ShouldBeTrue();
- criteriaSheet.ShouldNotBeNull();
- }
-
- [Fact]
- public async Task GenerateAsync_CreatesSearchResultsSheet()
- {
- var search = CreateMinimalSearchModel();
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
-
- workbook.Worksheets.TryGetWorksheet("Search Results", out var resultsSheet).ShouldBeTrue();
- resultsSheet.ShouldNotBeNull();
- }
-
- [Fact]
- public async Task GenerateAsync_WithMisData_CreatesMisInfoSheet()
- {
- var search = CreateSearchModelWithMisData();
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
-
- workbook.Worksheets.Count.ShouldBe(4); // Criteria, Results, MIS Info, Investigation
- workbook.Worksheets.TryGetWorksheet("MIS Info", out var misSheet).ShouldBeTrue();
- misSheet.ShouldNotBeNull();
- }
-
- [Fact]
- public async Task GenerateAsync_WithMisData_CreatesInvestigationSheet()
- {
- var search = CreateSearchModelWithMisData();
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
-
- workbook.Worksheets.TryGetWorksheet("Investigation", out var investigationSheet).ShouldBeTrue();
- investigationSheet.ShouldNotBeNull();
- }
-
- [Fact]
- public async Task GenerateAsync_WithoutMisData_DoesNotCreateMisSheets()
- {
- var search = CreateMinimalSearchModel();
- search.ExtractMisData = false;
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
-
- workbook.Worksheets.Count.ShouldBe(2); // Only Criteria and Results
- }
-
- [Fact]
- public async Task GenerateAsync_CancellationRequested_ThrowsOperationCanceled()
- {
- var search = CreateMinimalSearchModel();
- var cts = new CancellationTokenSource();
- cts.Cancel();
-
- await Should.ThrowAsync(
- () => _service.GenerateAsync(search, cts.Token));
- }
-
- [Fact]
- public async Task GenerateAsync_CriteriaSheet_ContainsSearchName()
- {
- var search = CreateMinimalSearchModel();
- search.Name = "Test Search Name";
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var criteriaSheet = workbook.Worksheet("Search Criteria");
-
- criteriaSheet.Cell(1, 2).Value.GetText().ShouldBe("Test Search Name");
- }
-
- [Fact]
- public async Task GenerateAsync_CriteriaSheet_ContainsUserName()
- {
- var search = CreateMinimalSearchModel();
- search.UserName = "testuser";
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var criteriaSheet = workbook.Worksheet("Search Criteria");
-
- criteriaSheet.Cell(2, 2).Value.GetText().ShouldBe("testuser");
- }
-
- [Fact]
- public async Task GenerateAsync_ResultsSheet_ContainsResultData()
- {
- var search = CreateMinimalSearchModel();
- search.Results.Add(new SearchResult
- {
- WorkOrderNumber = 12345,
- ItemNumber = "ITEM-001",
- LotNumber = "LOT-001",
- Flagged = true
- });
-
- var result = await _service.GenerateAsync(search);
-
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var resultsSheet = workbook.Worksheet("Search Results");
-
- // Check header row
- resultsSheet.Cell(1, 1).Value.GetText().ShouldBe("Work Order Number");
-
- // Check data row
- resultsSheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345);
- }
-
- private static SearchModel CreateMinimalSearchModel()
- {
- return new SearchModel
- {
- Id = 1,
- Name = "Test Search",
- UserName = "testuser",
- SubmitDt = DateTime.Now.AddHours(-1),
- StartDt = DateTime.Now.AddMinutes(-30),
- EndDt = DateTime.Now,
- ExtractMisData = false,
- Results = []
- };
- }
-
- private static SearchModel CreateSearchModelWithMisData()
- {
- return new SearchModel
- {
- Id = 1,
- Name = "Test Search with MIS",
- UserName = "testuser",
- SubmitDt = DateTime.Now.AddHours(-1),
- StartDt = DateTime.Now.AddMinutes(-30),
- EndDt = DateTime.Now,
- ExtractMisData = true,
- Results = [
- new SearchResult { WorkOrderNumber = 12345, Flagged = true }
- ],
- MisResults = [
- new MisSearchResult { ItemNumber = "ITEM-001", MisNumber = "MIS-001" }
- ],
- MisNonMatchResults = [
- new MisNonMatchSearchResult { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }
- ]
- };
- }
-}
+using JdeScoping.Core.Models.SearchResults;
+using JdeScoping.ExcelIO.Options;
+using JdeScoping.ExcelIO.Generators;
+using JdeScoping.ExcelIO.Mapping;
+using JdeScoping.ExcelIO.Mapping.Maps;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests;
+
+public class ExcelExportServiceTests
+{
+ private readonly ExcelExportService _service;
+
+ public ExcelExportServiceTests()
+ {
+ var logger = Substitute.For>();
+ var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
+ {
+ CriteriaSheetPassword = "TestCriteriaPass",
+ DataSheetPassword = "TestDataPass"
+ });
+
+ var registry = CreateTestRegistry();
+ var tableWriter = new FluentTableWriter(registry);
+ var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter);
+
+ _service = new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry);
+ }
+
+ private static ExcelMapRegistry CreateTestRegistry()
+ {
+ var registry = new ExcelMapRegistry();
+
+ registry.Register(new SearchResultMap());
+ registry.Register(new MisSearchResultMap());
+ registry.Register(new MisNonMatchSearchResultMap());
+
+ registry.Register(new TimespanFilterMap());
+ registry.Register(new WorkOrderFilterEntryMap());
+ registry.Register(new ItemNumberFilterEntryMap());
+ registry.Register(new ProfitCenterFilterEntryMap());
+ registry.Register(new WorkCenterFilterEntryMap());
+ registry.Register(new OperatorFilterEntryMap());
+ registry.Register(new ComponentLotFilterEntryMap());
+ registry.Register(new ItemOperationMisFilterEntryMap());
+
+ return registry;
+ }
+
+ [Fact]
+ public async Task GenerateAsync_ReturnsValidExcelBytes()
+ {
+ var search = CreateMinimalSearchModel();
+
+ var result = await _service.GenerateAsync(search);
+
+ result.ShouldNotBeNull();
+ result.Length.ShouldBeGreaterThan(0);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ workbook.NumberOfSheets.ShouldBeGreaterThanOrEqualTo(2);
+ }
+
+ [Fact]
+ public async Task GenerateAsync_CreatesSearchCriteriaSheet()
+ {
+ var search = CreateMinimalSearchModel();
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+
+ workbook.GetSheet("Search Criteria").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public async Task GenerateAsync_CreatesSearchResultsSheet()
+ {
+ var search = CreateMinimalSearchModel();
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+
+ workbook.GetSheet("Search Results").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public async Task GenerateAsync_WithMisData_CreatesMisInfoSheet()
+ {
+ var search = CreateSearchModelWithMisData();
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+
+ workbook.NumberOfSheets.ShouldBe(4);
+ workbook.GetSheet("MIS Info").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public async Task GenerateAsync_WithMisData_CreatesInvestigationSheet()
+ {
+ var search = CreateSearchModelWithMisData();
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+
+ workbook.GetSheet("Investigation").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public async Task GenerateAsync_WithoutMisData_DoesNotCreateMisSheets()
+ {
+ var search = CreateMinimalSearchModel();
+ search.ExtractMisData = false;
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+
+ workbook.NumberOfSheets.ShouldBe(2);
+ }
+
+ [Fact]
+ public async Task GenerateAsync_CancellationRequested_ThrowsOperationCanceled()
+ {
+ var search = CreateMinimalSearchModel();
+ var cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ await Should.ThrowAsync(
+ () => _service.GenerateAsync(search, cts.Token));
+ }
+
+ [Fact]
+ public async Task GenerateAsync_CriteriaSheet_ContainsSearchName()
+ {
+ var search = CreateMinimalSearchModel();
+ search.Name = "Test Search Name";
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var criteriaSheet = workbook.GetSheet("Search Criteria");
+
+ ExcelTestHelpers.GetCellText(criteriaSheet, 1, 2).ShouldBe("Test Search Name");
+ }
+
+ [Fact]
+ public async Task GenerateAsync_CriteriaSheet_ContainsUserName()
+ {
+ var search = CreateMinimalSearchModel();
+ search.UserName = "testuser";
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var criteriaSheet = workbook.GetSheet("Search Criteria");
+
+ ExcelTestHelpers.GetCellText(criteriaSheet, 2, 2).ShouldBe("testuser");
+ }
+
+ [Fact]
+ public async Task GenerateAsync_ResultsSheet_ContainsResultData()
+ {
+ var search = CreateMinimalSearchModel();
+ search.Results.Add(new SearchResult
+ {
+ WorkOrderNumber = 12345,
+ ItemNumber = "ITEM-001",
+ LotNumber = "LOT-001",
+ Flagged = true
+ });
+
+ var result = await _service.GenerateAsync(search);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var resultsSheet = workbook.GetSheet("Search Results");
+
+ ExcelTestHelpers.GetCellText(resultsSheet, 1, 1).ShouldBe("Work Order Number");
+ ExcelTestHelpers.GetCellNumber(resultsSheet, 2, 1).ShouldBe(12345);
+ }
+
+ private static SearchModel CreateMinimalSearchModel()
+ {
+ return new SearchModel
+ {
+ Id = 1,
+ Name = "Test Search",
+ UserName = "testuser",
+ SubmitDt = DateTime.Now.AddHours(-1),
+ StartDt = DateTime.Now.AddMinutes(-30),
+ EndDt = DateTime.Now,
+ ExtractMisData = false,
+ Results = []
+ };
+ }
+
+ private static SearchModel CreateSearchModelWithMisData()
+ {
+ return new SearchModel
+ {
+ Id = 1,
+ Name = "Test Search with MIS",
+ UserName = "testuser",
+ SubmitDt = DateTime.Now.AddHours(-1),
+ StartDt = DateTime.Now.AddMinutes(-30),
+ EndDt = DateTime.Now,
+ ExtractMisData = true,
+ Results = [
+ new SearchResult { WorkOrderNumber = 12345, Flagged = true }
+ ],
+ MisResults = [
+ new MisSearchResult { ItemNumber = "ITEM-001", MisNumber = "MIS-001" }
+ ],
+ MisNonMatchResults = [
+ new MisNonMatchSearchResult { WorkOrderNumber = 12345, ItemNumber = "ITEM-001" }
+ ]
+ };
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs
index 6da7382..a1b3e8a 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/ExcelTestHelpers.cs
@@ -1,18 +1,139 @@
-using ClosedXML.Excel;
-
-namespace JdeScoping.ExcelIO.Tests.Fixtures;
-
-public static class ExcelTestHelpers
-{
- public static List GetHeadersFromSheet(IXLWorksheet sheet)
- {
- var headers = new List();
- var col = 1;
- while (!sheet.Cell(1, col).IsEmpty())
- {
- headers.Add(sheet.Cell(1, col).Value.GetText());
- col++;
- }
- return headers;
- }
-}
+using NPOI.SS.UserModel;
+using NPOI.SS.Util;
+using NPOI.XSSF.UserModel;
+
+namespace JdeScoping.ExcelIO.Tests.Fixtures;
+
+public static class ExcelTestHelpers
+{
+ private static readonly DataFormatter Formatter = new();
+
+ public static List GetHeadersFromSheet(ISheet sheet)
+ {
+ var headers = new List();
+ var headerRow = sheet.GetRow(0);
+ if (headerRow == null)
+ {
+ return headers;
+ }
+
+ var col = 0;
+ while (true)
+ {
+ var cell = headerRow.GetCell(col);
+ var text = cell == null ? string.Empty : Formatter.FormatCellValue(cell);
+ if (string.IsNullOrEmpty(text))
+ {
+ break;
+ }
+
+ headers.Add(text);
+ col++;
+ }
+
+ return headers;
+ }
+
+ public static string GetCellText(ISheet sheet, int row1Based, int col1Based)
+ {
+ var cell = GetCell(sheet, row1Based, col1Based);
+ return cell == null ? string.Empty : Formatter.FormatCellValue(cell);
+ }
+
+ public static double GetCellNumber(ISheet sheet, int row1Based, int col1Based)
+ {
+ var cell = GetCell(sheet, row1Based, col1Based);
+ if (cell == null)
+ {
+ return 0;
+ }
+
+ if (cell.CellType == CellType.Numeric)
+ {
+ return cell.NumericCellValue;
+ }
+
+ var text = Formatter.FormatCellValue(cell);
+ return double.TryParse(text, out var value) ? value : 0;
+ }
+
+ public static ICell? GetCell(ISheet sheet, int row1Based, int col1Based)
+ {
+ return sheet.GetRow(row1Based - 1)?.GetCell(col1Based - 1);
+ }
+
+ public static XSSFSheet GetXssfSheet(IWorkbook workbook, string sheetName)
+ {
+ return (XSSFSheet)workbook.GetSheet(sheetName)!;
+ }
+
+ public static XSSFWorkbook OpenWorkbook(byte[] bytes)
+ {
+ return new XSSFWorkbook(new MemoryStream(bytes));
+ }
+
+ public static double GetColumnWidthChars(ISheet sheet, int col1Based)
+ {
+ return sheet.GetColumnWidth(col1Based - 1) / 256d;
+ }
+
+ public static bool IsSheetProtected(ISheet sheet)
+ {
+ return sheet is XSSFSheet xssf && xssf.IsSheetLocked;
+ }
+
+ public static byte[]? GetFillForegroundRgb(ICell cell)
+ {
+ if (cell.CellStyle is XSSFCellStyle xssfStyle)
+ {
+ return xssfStyle.FillForegroundXSSFColor?.RGB;
+ }
+
+ return null;
+ }
+
+ public static bool IsMerged(ISheet sheet, int firstRow1Based, int lastRow1Based, int firstCol1Based, int lastCol1Based)
+ {
+ for (var i = 0; i < sheet.NumMergedRegions; i++)
+ {
+ var region = sheet.GetMergedRegion(i);
+ if (region.FirstRow == firstRow1Based - 1 &&
+ region.LastRow == lastRow1Based - 1 &&
+ region.FirstColumn == firstCol1Based - 1 &&
+ region.LastColumn == lastCol1Based - 1)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static XSSFTable GetFirstTable(ISheet sheet)
+ {
+ return ((XSSFSheet)sheet).GetTables().First();
+ }
+
+ public static XSSFTable GetTableByName(ISheet sheet, string name)
+ {
+ return ((XSSFSheet)sheet).GetTables().First(t => t.Name == name || t.DisplayName == name);
+ }
+
+ public static int GetTableRowCount(XSSFTable table)
+ {
+ // header row + data rows
+ return table.RowCount;
+ }
+
+ public static bool TableExists(ISheet sheet, string tableName)
+ {
+ return ((XSSFSheet)sheet).GetTables().Any(t => t.Name == tableName || t.DisplayName == tableName);
+ }
+
+ public static bool RegionIntersectsCell(CellRangeAddress region, int row1Based, int col1Based)
+ {
+ var row = row1Based - 1;
+ var col = col1Based - 1;
+ return region.IsInRange(row, col);
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs
index 4bae88f..69e3ce8 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Fixtures/WorkbookFixtureBase.cs
@@ -1,68 +1,67 @@
-using ClosedXML.Excel;
-using JdeScoping.Core.Models.SearchResults;
-using JdeScoping.ExcelIO.Generators;
-using JdeScoping.ExcelIO.Mapping;
-using JdeScoping.ExcelIO.Mapping.Maps;
-using JdeScoping.ExcelIO.Options;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using NSubstitute;
-
-namespace JdeScoping.ExcelIO.Tests.Fixtures;
-
-public abstract class WorkbookFixtureBase : IDisposable
-{
- public XLWorkbook Workbook { get; }
- public SearchModel SearchModel { get; }
-
- protected abstract SearchModel CreateSearchModel();
-
- protected WorkbookFixtureBase()
- {
- SearchModel = CreateSearchModel();
- var service = CreateExportService();
- var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult();
- Workbook = new XLWorkbook(new MemoryStream(bytes));
- }
-
- private static ExcelExportService CreateExportService()
- {
- var logger = Substitute.For>();
- var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
- {
- CriteriaSheetPassword = "TestCriteriaPass",
- DataSheetPassword = "TestDataPass"
- });
-
- var registry = CreateTestRegistry();
- var tableWriter = new FluentTableWriter(registry);
- var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter);
-
- return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry);
- }
-
- private static ExcelMapRegistry CreateTestRegistry()
- {
- var registry = new ExcelMapRegistry();
-
- registry.Register(new SearchResultMap());
- registry.Register(new MisSearchResultMap());
- registry.Register(new MisNonMatchSearchResultMap());
- registry.Register(new TimespanFilterMap());
- registry.Register(new WorkOrderFilterEntryMap());
- registry.Register(new ItemNumberFilterEntryMap());
- registry.Register(new ProfitCenterFilterEntryMap());
- registry.Register(new WorkCenterFilterEntryMap());
- registry.Register(new OperatorFilterEntryMap());
- registry.Register(new ComponentLotFilterEntryMap());
- registry.Register(new ItemOperationMisFilterEntryMap());
-
- return registry;
- }
-
- public void Dispose()
- {
- Workbook.Dispose();
- GC.SuppressFinalize(this);
- }
-}
+using JdeScoping.Core.Models.SearchResults;
+using JdeScoping.ExcelIO.Generators;
+using JdeScoping.ExcelIO.Mapping;
+using JdeScoping.ExcelIO.Mapping.Maps;
+using JdeScoping.ExcelIO.Options;
+using Microsoft.Extensions.Logging;
+using NPOI.XSSF.UserModel;
+using NSubstitute;
+
+namespace JdeScoping.ExcelIO.Tests.Fixtures;
+
+public abstract class WorkbookFixtureBase : IDisposable
+{
+ public XSSFWorkbook Workbook { get; }
+ public SearchModel SearchModel { get; }
+
+ protected abstract SearchModel CreateSearchModel();
+
+ protected WorkbookFixtureBase()
+ {
+ SearchModel = CreateSearchModel();
+ var service = CreateExportService();
+ var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult();
+ Workbook = new XSSFWorkbook(new MemoryStream(bytes));
+ }
+
+ private static ExcelExportService CreateExportService()
+ {
+ var logger = Substitute.For>();
+ var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
+ {
+ CriteriaSheetPassword = "TestCriteriaPass",
+ DataSheetPassword = "TestDataPass"
+ });
+
+ var registry = CreateTestRegistry();
+ var tableWriter = new FluentTableWriter(registry);
+ var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter);
+
+ return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry);
+ }
+
+ private static ExcelMapRegistry CreateTestRegistry()
+ {
+ var registry = new ExcelMapRegistry();
+
+ registry.Register(new SearchResultMap());
+ registry.Register(new MisSearchResultMap());
+ registry.Register(new MisNonMatchSearchResultMap());
+ registry.Register(new TimespanFilterMap());
+ registry.Register(new WorkOrderFilterEntryMap());
+ registry.Register(new ItemNumberFilterEntryMap());
+ registry.Register(new ProfitCenterFilterEntryMap());
+ registry.Register(new WorkCenterFilterEntryMap());
+ registry.Register(new OperatorFilterEntryMap());
+ registry.Register(new ComponentLotFilterEntryMap());
+ registry.Register(new ItemOperationMisFilterEntryMap());
+
+ return registry;
+ }
+
+ public void Dispose()
+ {
+ Workbook.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs
index 6255de1..abd0736 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/HeaderFormatterTests.cs
@@ -1,80 +1,81 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Formatting;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests;
-
-public class HeaderFormatterTests
-{
- [Fact]
- public void ApplyHeaderFormat_Cell_AppliesCorrectStyling()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
- var cell = worksheet.Cell(1, 1);
-
- HeaderFormatter.ApplyHeaderFormat(cell, "Test Header");
-
- cell.Value.GetText().ShouldBe("Test Header");
- cell.Style.Font.Bold.ShouldBeTrue();
- cell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center);
- cell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro);
- }
-
- [Fact]
- public void ApplyHeaderFormat_Cell_WithoutText_AppliesOnlyStyling()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
- var cell = worksheet.Cell(1, 1);
- cell.Value = "Original";
-
- HeaderFormatter.ApplyHeaderFormat(cell);
-
- cell.Value.GetText().ShouldBe("Original");
- cell.Style.Font.Bold.ShouldBeTrue();
- }
-
- [Fact]
- public void ApplyHeaderFormat_Range_AppliesCorrectStyling()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
- var range = worksheet.Range(1, 1, 1, 3);
-
- HeaderFormatter.ApplyHeaderFormat(range, "Header", merge: false);
-
- range.FirstCell().Value.GetText().ShouldBe("Header");
- foreach (var cell in range.Cells())
- {
- cell.Style.Font.Bold.ShouldBeTrue();
- cell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro);
- }
- }
-
- [Fact]
- public void ApplyHeaderFormat_Range_WithMerge_MergesCells()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
- var range = worksheet.Range(1, 1, 1, 3);
-
- HeaderFormatter.ApplyHeaderFormat(range, "Merged Header", merge: true);
-
- range.IsMerged().ShouldBeTrue();
- range.FirstCell().Value.GetText().ShouldBe("Merged Header");
- }
-
- [Fact]
- public void ApplyHeaderFormat_Range_WithoutMerge_DoesNotMergeCells()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
- var range = worksheet.Range(1, 1, 1, 3);
-
- HeaderFormatter.ApplyHeaderFormat(range, "Not Merged", merge: false);
-
- range.IsMerged().ShouldBeFalse();
- }
-}
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests;
+
+public class HeaderFormatterTests
+{
+ [Fact]
+ public void ApplyHeaderFormat_Cell_AppliesCorrectStyling()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Test");
+ var row = worksheet.CreateRow(0);
+ var cell = row.CreateCell(0);
+
+ HeaderFormatter.ApplyHeaderFormat(cell, "Test Header");
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Test Header");
+ cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue();
+ cell.CellStyle.Alignment.ShouldBe(NPOI.SS.UserModel.HorizontalAlignment.Center);
+ ExcelTestHelpers.GetFillForegroundRgb(cell).ShouldBe([0xDC, 0xDC, 0xDC]);
+ }
+
+ [Fact]
+ public void ApplyHeaderFormat_Cell_WithoutText_AppliesOnlyStyling()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Test");
+ var row = worksheet.CreateRow(0);
+ var cell = row.CreateCell(0);
+ cell.SetCellValue("Original");
+
+ HeaderFormatter.ApplyHeaderFormat(cell);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Original");
+ cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void ApplyHeaderFormat_Range_AppliesCorrectStyling()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Test");
+
+ HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Header", merge: false);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Header");
+ for (var col = 1; col <= 3; col++)
+ {
+ var cell = ExcelTestHelpers.GetCell(worksheet, 1, col)!;
+ cell.CellStyle.GetFont(workbook).IsBold.ShouldBeTrue();
+ ExcelTestHelpers.GetFillForegroundRgb(cell).ShouldBe([0xDC, 0xDC, 0xDC]);
+ }
+ }
+
+ [Fact]
+ public void ApplyHeaderFormat_Range_WithMerge_MergesCells()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Test");
+
+ HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Merged Header", merge: true);
+
+ ExcelTestHelpers.IsMerged(worksheet, 1, 1, 1, 3).ShouldBeTrue();
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Merged Header");
+ }
+
+ [Fact]
+ public void ApplyHeaderFormat_Range_WithoutMerge_DoesNotMergeCells()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Test");
+
+ HeaderFormatter.ApplyHeaderFormat(worksheet, 1, 1, 1, 3, "Not Merged", merge: false);
+
+ ExcelTestHelpers.IsMerged(worksheet, 1, 1, 1, 3).ShouldBeFalse();
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs
index 5d0e600..8d8876f 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/InvestigationSheetTests.cs
@@ -1,86 +1,87 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class InvestigationSheetTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
- private readonly IXLWorksheet _sheet;
- private readonly List _headers;
-
- public InvestigationSheetTests(WithMisDataFixture fixture)
- {
- _workbook = fixture.Workbook;
- _sheet = _workbook.Worksheet("Investigation");
- _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
- }
-
- [Fact]
- public void InvestigationSheet_Exists()
- {
- _workbook.Worksheets.TryGetWorksheet("Investigation", out _).ShouldBeTrue();
- }
-
- [Fact]
- public void ColumnCount_Is12()
- {
- _headers.Count.ShouldBe(12);
- }
-
- [Fact]
- public void ColumnHeaders_MatchExpected()
- {
- _headers.ShouldContain("Work Center Code");
- _headers.ShouldContain("Work Order Number");
- _headers.ShouldContain("Work Order Start Date");
- _headers.ShouldContain("Job Step Number");
- _headers.ShouldContain("Function Operation Description");
- _headers.ShouldContain("Job Step End Date");
- _headers.ShouldContain("Function Code");
- _headers.ShouldContain("Was Job Step Added?");
- _headers.ShouldContain("Matched Job Step Number");
- _headers.ShouldContain("Item Number");
- _headers.ShouldContain("Item Description");
- _headers.ShouldContain("Routing Type");
- }
-
- [Fact]
- public void ColumnOrder_MatchesSpec()
- {
- _headers[0].ShouldBe("Work Center Code");
- _headers[1].ShouldBe("Work Order Number");
- _headers[2].ShouldBe("Work Order Start Date");
- _headers[3].ShouldBe("Job Step Number");
- _headers[4].ShouldBe("Function Operation Description");
- _headers[5].ShouldBe("Job Step End Date");
- _headers[6].ShouldBe("Function Code");
- _headers[7].ShouldBe("Was Job Step Added?");
- _headers[8].ShouldBe("Matched Job Step Number");
- _headers[9].ShouldBe("Item Number");
- _headers[10].ShouldBe("Item Description");
- _headers[11].ShouldBe("Routing Type");
- }
-
- [Fact]
- public void TableStyle_IsLight18()
- {
- var table = _sheet.Tables.First();
- table.Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- }
-
- [Fact]
- public void Protection_IsEnabled()
- {
- _sheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void DataRow_ContainsExpectedValues()
- {
- _sheet.Cell(2, 2).Value.GetNumber().ShouldBe(12345);
- _sheet.Cell(2, 10).Value.GetText().ShouldBe("ITEM-001");
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class InvestigationSheetTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+ private readonly ISheet _sheet;
+ private readonly List _headers;
+
+ public InvestigationSheetTests(WithMisDataFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ _sheet = _workbook.GetSheet("Investigation")!;
+ _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
+ }
+
+ [Fact]
+ public void InvestigationSheet_Exists()
+ {
+ _workbook.GetSheet("Investigation").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void ColumnCount_Is12()
+ {
+ _headers.Count.ShouldBe(12);
+ }
+
+ [Fact]
+ public void ColumnHeaders_MatchExpected()
+ {
+ _headers.ShouldContain("Work Center Code");
+ _headers.ShouldContain("Work Order Number");
+ _headers.ShouldContain("Work Order Start Date");
+ _headers.ShouldContain("Job Step Number");
+ _headers.ShouldContain("Function Operation Description");
+ _headers.ShouldContain("Job Step End Date");
+ _headers.ShouldContain("Function Code");
+ _headers.ShouldContain("Was Job Step Added?");
+ _headers.ShouldContain("Matched Job Step Number");
+ _headers.ShouldContain("Item Number");
+ _headers.ShouldContain("Item Description");
+ _headers.ShouldContain("Routing Type");
+ }
+
+ [Fact]
+ public void ColumnOrder_MatchesSpec()
+ {
+ _headers[0].ShouldBe("Work Center Code");
+ _headers[1].ShouldBe("Work Order Number");
+ _headers[2].ShouldBe("Work Order Start Date");
+ _headers[3].ShouldBe("Job Step Number");
+ _headers[4].ShouldBe("Function Operation Description");
+ _headers[5].ShouldBe("Job Step End Date");
+ _headers[6].ShouldBe("Function Code");
+ _headers[7].ShouldBe("Was Job Step Added?");
+ _headers[8].ShouldBe("Matched Job Step Number");
+ _headers[9].ShouldBe("Item Number");
+ _headers[10].ShouldBe("Item Description");
+ _headers[11].ShouldBe("Routing Type");
+ }
+
+ [Fact]
+ public void TableStyle_IsLight18()
+ {
+ var table = ExcelTestHelpers.GetFirstTable(_sheet);
+ table.StyleName.ShouldBe("TableStyleLight18");
+ }
+
+ [Fact]
+ public void Protection_IsEnabled()
+ {
+ ((XSSFSheet)_sheet).IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void DataRow_ContainsExpectedValues()
+ {
+ ExcelTestHelpers.GetCellNumber(_sheet, 2, 2).ShouldBe(12345);
+ ExcelTestHelpers.GetCellText(_sheet, 2, 10).ShouldBe("ITEM-001");
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs
index 7a50670..9c46c7b 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/LargeDataSetTests.cs
@@ -1,24 +1,24 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class LargeDataSetTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
-
- public LargeDataSetTests(LargeDataSetFixture fixture)
- {
- _workbook = fixture.Workbook;
- }
-
- [Fact]
- public void TableRowCount_Is1001()
- {
- var sheet = _workbook.Worksheet("Search Results");
- var table = sheet.Tables.First();
- table.RowCount().ShouldBe(1001); // 1 header + 1000 data rows
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class LargeDataSetTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+
+ public LargeDataSetTests(LargeDataSetFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ }
+
+ [Fact]
+ public void TableRowCount_Is1001()
+ {
+ var sheet = _workbook.GetSheet("Search Results")!;
+ var table = ExcelTestHelpers.GetFirstTable(sheet);
+ ExcelTestHelpers.GetTableRowCount(table).ShouldBe(1001);
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs
index e058160..5b55916 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MinimalSearchTests.cs
@@ -1,48 +1,48 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class MinimalSearchTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
-
- public MinimalSearchTests(MinimalSearchFixture fixture)
- {
- _workbook = fixture.Workbook;
- }
-
- [Fact]
- public void SheetCount_IsTwo()
- {
- _workbook.Worksheets.Count.ShouldBe(2);
- }
-
- [Fact]
- public void SearchCriteriaSheet_Exists()
- {
- _workbook.Worksheets.TryGetWorksheet("Search Criteria", out _).ShouldBeTrue();
- }
-
- [Fact]
- public void SearchResultsSheet_Exists()
- {
- _workbook.Worksheets.TryGetWorksheet("Search Results", out _).ShouldBeTrue();
- }
-
- [Fact]
- public void SearchCriteriaSheet_IsProtected()
- {
- var sheet = _workbook.Worksheet("Search Criteria");
- sheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void SearchResultsSheet_IsProtected()
- {
- var sheet = _workbook.Worksheet("Search Results");
- sheet.Protection.IsProtected.ShouldBeTrue();
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class MinimalSearchTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+
+ public MinimalSearchTests(MinimalSearchFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ }
+
+ [Fact]
+ public void SheetCount_IsTwo()
+ {
+ _workbook.NumberOfSheets.ShouldBe(2);
+ }
+
+ [Fact]
+ public void SearchCriteriaSheet_Exists()
+ {
+ _workbook.GetSheet("Search Criteria").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void SearchResultsSheet_Exists()
+ {
+ _workbook.GetSheet("Search Results").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void SearchCriteriaSheet_IsProtected()
+ {
+ var sheet = (XSSFSheet)_workbook.GetSheet("Search Criteria")!;
+ sheet.IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void SearchResultsSheet_IsProtected()
+ {
+ var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!;
+ sheet.IsSheetLocked.ShouldBeTrue();
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs
index dd9db0d..ba427a3 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/MisInfoSheetTests.cs
@@ -1,130 +1,131 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class MisInfoSheetTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
- private readonly IXLWorksheet _sheet;
- private readonly List _headers;
-
- public MisInfoSheetTests(WithMisDataFixture fixture)
- {
- _workbook = fixture.Workbook;
- _sheet = _workbook.Worksheet("MIS Info");
- _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
- }
-
- [Fact]
- public void SheetCount_IsFour()
- {
- _workbook.Worksheets.Count.ShouldBe(4);
- }
-
- [Fact]
- public void MisInfoSheet_Exists()
- {
- _workbook.Worksheets.TryGetWorksheet("MIS Info", out _).ShouldBeTrue();
- }
-
- [Fact]
- public void ColumnCount_Is19()
- {
- _headers.Count.ShouldBe(19);
- }
-
- [Fact]
- public void ColumnHeaders_MatchExpected()
- {
- _headers.ShouldContain("Item Number");
- _headers.ShouldContain("MIS Job Step Sequence Number");
- _headers.ShouldContain("MIS Number");
- _headers.ShouldContain("MIS Revision");
- _headers.ShouldContain("Item Description");
- _headers.ShouldContain("MIS Release Status");
- _headers.ShouldContain("MIS Release Date");
- _headers.ShouldContain("Branch Code");
- _headers.ShouldContain("Job Step Sequence Number");
- _headers.ShouldContain("Matched Sequence Number");
- _headers.ShouldContain("Matched to F3112Z1?");
- _headers.ShouldContain("Matched to F3003?");
- _headers.ShouldContain("Function Operation Description");
- _headers.ShouldContain("Char Number");
- _headers.ShouldContain("Test Description");
- _headers.ShouldContain("Sampling Type");
- _headers.ShouldContain("Sampling Value");
- _headers.ShouldContain("Tools & Gauges");
- _headers.ShouldContain("Work Instructions");
- }
-
- [Fact]
- public void ColumnOrder_MatchesSpec()
- {
- _headers[0].ShouldBe("Item Number");
- _headers[1].ShouldBe("MIS Job Step Sequence Number");
- _headers[2].ShouldBe("MIS Number");
- _headers[3].ShouldBe("MIS Revision");
- _headers[4].ShouldBe("Item Description");
- _headers[5].ShouldBe("MIS Release Status");
- _headers[6].ShouldBe("MIS Release Date");
- _headers[7].ShouldBe("Branch Code");
- _headers[8].ShouldBe("Job Step Sequence Number");
- _headers[9].ShouldBe("Matched Sequence Number");
- _headers[10].ShouldBe("Matched to F3112Z1?");
- _headers[11].ShouldBe("Matched to F3003?");
- _headers[12].ShouldBe("Function Operation Description");
- _headers[13].ShouldBe("Char Number");
- _headers[14].ShouldBe("Test Description");
- _headers[15].ShouldBe("Sampling Type");
- _headers[16].ShouldBe("Sampling Value");
- _headers[17].ShouldBe("Tools & Gauges");
- _headers[18].ShouldBe("Work Instructions");
- }
-
- [Fact]
- public void TableStyle_IsLight18()
- {
- var table = _sheet.Tables.First();
- table.Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- }
-
- [Fact]
- public void Protection_IsEnabled()
- {
- _sheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void DataRow_ContainsExpectedValues()
- {
- _sheet.Cell(2, 1).Value.GetText().ShouldBe("ITEM-001");
- _sheet.Cell(2, 3).Value.GetText().ShouldBe("MIS-001");
- }
-
- [Fact]
- public void TestDescriptionColumn_IsWrapped()
- {
- var colIndex = _headers.IndexOf("Test Description") + 1;
- _sheet.Column(colIndex).Width.ShouldBe(65);
- _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue();
- }
-
- [Fact]
- public void ToolsGaugesColumn_IsWrapped()
- {
- var colIndex = _headers.IndexOf("Tools & Gauges") + 1;
- _sheet.Column(colIndex).Width.ShouldBe(65);
- _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue();
- }
-
- [Fact]
- public void WorkInstructionsColumn_IsWrapped()
- {
- var colIndex = _headers.IndexOf("Work Instructions") + 1;
- _sheet.Column(colIndex).Width.ShouldBe(65);
- _sheet.Column(colIndex).Style.Alignment.WrapText.ShouldBeTrue();
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class MisInfoSheetTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+ private readonly ISheet _sheet;
+ private readonly List _headers;
+
+ public MisInfoSheetTests(WithMisDataFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ _sheet = _workbook.GetSheet("MIS Info")!;
+ _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
+ }
+
+ [Fact]
+ public void SheetCount_IsFour()
+ {
+ _workbook.NumberOfSheets.ShouldBe(4);
+ }
+
+ [Fact]
+ public void MisInfoSheet_Exists()
+ {
+ _workbook.GetSheet("MIS Info").ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void ColumnCount_Is19()
+ {
+ _headers.Count.ShouldBe(19);
+ }
+
+ [Fact]
+ public void ColumnHeaders_MatchExpected()
+ {
+ _headers.ShouldContain("Item Number");
+ _headers.ShouldContain("MIS Job Step Sequence Number");
+ _headers.ShouldContain("MIS Number");
+ _headers.ShouldContain("MIS Revision");
+ _headers.ShouldContain("Item Description");
+ _headers.ShouldContain("MIS Release Status");
+ _headers.ShouldContain("MIS Release Date");
+ _headers.ShouldContain("Branch Code");
+ _headers.ShouldContain("Job Step Sequence Number");
+ _headers.ShouldContain("Matched Sequence Number");
+ _headers.ShouldContain("Matched to F3112Z1?");
+ _headers.ShouldContain("Matched to F3003?");
+ _headers.ShouldContain("Function Operation Description");
+ _headers.ShouldContain("Char Number");
+ _headers.ShouldContain("Test Description");
+ _headers.ShouldContain("Sampling Type");
+ _headers.ShouldContain("Sampling Value");
+ _headers.ShouldContain("Tools & Gauges");
+ _headers.ShouldContain("Work Instructions");
+ }
+
+ [Fact]
+ public void ColumnOrder_MatchesSpec()
+ {
+ _headers[0].ShouldBe("Item Number");
+ _headers[1].ShouldBe("MIS Job Step Sequence Number");
+ _headers[2].ShouldBe("MIS Number");
+ _headers[3].ShouldBe("MIS Revision");
+ _headers[4].ShouldBe("Item Description");
+ _headers[5].ShouldBe("MIS Release Status");
+ _headers[6].ShouldBe("MIS Release Date");
+ _headers[7].ShouldBe("Branch Code");
+ _headers[8].ShouldBe("Job Step Sequence Number");
+ _headers[9].ShouldBe("Matched Sequence Number");
+ _headers[10].ShouldBe("Matched to F3112Z1?");
+ _headers[11].ShouldBe("Matched to F3003?");
+ _headers[12].ShouldBe("Function Operation Description");
+ _headers[13].ShouldBe("Char Number");
+ _headers[14].ShouldBe("Test Description");
+ _headers[15].ShouldBe("Sampling Type");
+ _headers[16].ShouldBe("Sampling Value");
+ _headers[17].ShouldBe("Tools & Gauges");
+ _headers[18].ShouldBe("Work Instructions");
+ }
+
+ [Fact]
+ public void TableStyle_IsLight18()
+ {
+ var table = ExcelTestHelpers.GetFirstTable(_sheet);
+ table.StyleName.ShouldBe("TableStyleLight18");
+ }
+
+ [Fact]
+ public void Protection_IsEnabled()
+ {
+ ((XSSFSheet)_sheet).IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void DataRow_ContainsExpectedValues()
+ {
+ ExcelTestHelpers.GetCellText(_sheet, 2, 1).ShouldBe("ITEM-001");
+ ExcelTestHelpers.GetCellText(_sheet, 2, 3).ShouldBe("MIS-001");
+ }
+
+ [Fact]
+ public void TestDescriptionColumn_IsWrapped()
+ {
+ var colIndex = _headers.IndexOf("Test Description") + 1;
+ ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65);
+ ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void ToolsGaugesColumn_IsWrapped()
+ {
+ var colIndex = _headers.IndexOf("Tools & Gauges") + 1;
+ ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65);
+ ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void WorkInstructionsColumn_IsWrapped()
+ {
+ var colIndex = _headers.IndexOf("Work Instructions") + 1;
+ ExcelTestHelpers.GetColumnWidthChars(_sheet, colIndex).ShouldBe(65);
+ ExcelTestHelpers.GetCell(_sheet, 2, colIndex)!.CellStyle.WrapText.ShouldBeTrue();
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs
index 90e1384..4f28459 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/ProtectionAndStyleTests.cs
@@ -1,75 +1,77 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class ProtectionAndStyleTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
-
- public ProtectionAndStyleTests(WithMisDataFixture fixture)
- {
- _workbook = fixture.Workbook;
- }
-
- [Fact]
- public void AllDataSheets_AreProtected()
- {
- _workbook.Worksheet("Search Results").Protection.IsProtected.ShouldBeTrue();
- _workbook.Worksheet("MIS Info").Protection.IsProtected.ShouldBeTrue();
- _workbook.Worksheet("Investigation").Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void Protection_AllowsFiltering()
- {
- var sheet = _workbook.Worksheet("Search Results");
- sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue();
- }
-
- [Fact]
- public void Protection_AllowsSorting()
- {
- var sheet = _workbook.Worksheet("Search Results");
- sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue();
- }
-
- [Fact]
- public void Protection_AllowsFormatting()
- {
- var sheet = _workbook.Worksheet("Search Results");
- sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue();
- sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue();
- sheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue();
- }
-
- [Fact]
- public void AllTables_UseLight18Style()
- {
- _workbook.Worksheet("Search Results").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- _workbook.Worksheet("MIS Info").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- _workbook.Worksheet("Investigation").Tables.First().Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- }
-
- [Fact]
- public void HeaderCells_HaveCorrectFormatting()
- {
- var sheet = _workbook.Worksheet("Search Criteria");
- var headerCell = sheet.Cell(1, 1);
- headerCell.Style.Font.Bold.ShouldBeTrue();
- headerCell.Style.Fill.BackgroundColor.ShouldBe(XLColor.Gainsboro);
- headerCell.Style.Alignment.Horizontal.ShouldBe(XLAlignmentHorizontalValues.Center);
- }
-
- [Fact]
- public void CriteriaTimestamp_MatchesLegacyFormat()
- {
- var sheet = _workbook.Worksheet("Search Criteria");
- var timestamp = sheet.Cell(4, 2).Value.GetText();
- timestamp.ShouldContain("Jan 15, 2024");
- timestamp.ShouldContain("02:30:45");
- timestamp.ShouldContain("EST");
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class ProtectionAndStyleTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+
+ public ProtectionAndStyleTests(WithMisDataFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ }
+
+ [Fact]
+ public void AllDataSheets_AreProtected()
+ {
+ ((XSSFSheet)_workbook.GetSheet("Search Results")!).IsSheetLocked.ShouldBeTrue();
+ ((XSSFSheet)_workbook.GetSheet("MIS Info")!).IsSheetLocked.ShouldBeTrue();
+ ((XSSFSheet)_workbook.GetSheet("Investigation")!).IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void Protection_AllowsFiltering()
+ {
+ var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!;
+ sheet.IsAutoFilterLocked.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void Protection_AllowsSorting()
+ {
+ var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!;
+ sheet.IsSortLocked.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void Protection_AllowsFormatting()
+ {
+ var sheet = (XSSFSheet)_workbook.GetSheet("Search Results")!;
+ sheet.IsFormatCellsLocked.ShouldBeFalse();
+ sheet.IsFormatColumnsLocked.ShouldBeFalse();
+ sheet.IsFormatRowsLocked.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void AllTables_UseLight18Style()
+ {
+ ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("Search Results")!).StyleName.ShouldBe("TableStyleLight18");
+ ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("MIS Info")!).StyleName.ShouldBe("TableStyleLight18");
+ ExcelTestHelpers.GetFirstTable(_workbook.GetSheet("Investigation")!).StyleName.ShouldBe("TableStyleLight18");
+ }
+
+ [Fact]
+ public void HeaderCells_HaveCorrectFormatting()
+ {
+ var sheet = _workbook.GetSheet("Search Criteria")!;
+ var headerCell = ExcelTestHelpers.GetCell(sheet, 1, 1)!;
+ (headerCell.CellStyle.FontIndex >= 0 &&
+ _workbook.GetFontAt(headerCell.CellStyle.FontIndex).IsBold).ShouldBeTrue();
+ ExcelTestHelpers.GetFillForegroundRgb(headerCell).ShouldBe([0xDC, 0xDC, 0xDC]);
+ headerCell.CellStyle.Alignment.ShouldBe(HorizontalAlignment.Center);
+ }
+
+ [Fact]
+ public void CriteriaTimestamp_MatchesLegacyFormat()
+ {
+ var sheet = _workbook.GetSheet("Search Criteria")!;
+ var timestamp = ExcelTestHelpers.GetCellText(sheet, 4, 2);
+ timestamp.ShouldContain("Jan 15, 2024");
+ timestamp.ShouldContain("02:30:45");
+ timestamp.ShouldContain("EST");
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs
index 40f4435..36882a0 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Integration/SearchResultsSheetTests.cs
@@ -1,89 +1,90 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Tests.Fixtures;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Integration;
-
-public class SearchResultsSheetTests : IClassFixture
-{
- private readonly XLWorkbook _workbook;
- private readonly IXLWorksheet _sheet;
- private readonly List _headers;
-
- public SearchResultsSheetTests(WithResultsFixture fixture)
- {
- _workbook = fixture.Workbook;
- _sheet = _workbook.Worksheet("Search Results");
- _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
- }
-
- [Fact]
- public void ColumnCount_Is19()
- {
- _headers.Count.ShouldBe(19);
- }
-
- [Fact]
- public void ColumnHeaders_MatchExpected()
- {
- _headers.ShouldContain("Work Order Number");
- _headers.ShouldContain("Work Order Branch Code");
- _headers.ShouldContain("Lot Number");
- _headers.ShouldContain("Item Number");
- _headers.ShouldContain("Planning Family");
- _headers.ShouldContain("Stocking Type");
- _headers.ShouldContain("Order Quantity");
- _headers.ShouldContain("Held Quantity");
- _headers.ShouldContain("Scrapped Quantity");
- _headers.ShouldContain("Shipped Quantity");
- _headers.ShouldContain("Operation Step Branch Code");
- _headers.ShouldContain("Operation Step");
- _headers.ShouldContain("Operation Step Description");
- _headers.ShouldContain("Function Operation Description");
- _headers.ShouldContain("Operation Step Update Timestamp");
- _headers.ShouldContain("Status Code");
- _headers.ShouldContain("Status Description");
- _headers.ShouldContain("Status Update Timestamp");
- _headers.ShouldContain("Inclusion Reason");
- }
-
- [Fact]
- public void ColumnOrder_MatchesSpec()
- {
- _headers[0].ShouldBe("Work Order Number");
- _headers[1].ShouldBe("Work Order Branch Code");
- _headers[2].ShouldBe("Lot Number");
- _headers[3].ShouldBe("Item Number");
- _headers[4].ShouldBe("Planning Family");
- _headers[5].ShouldBe("Stocking Type");
- _headers[6].ShouldBe("Order Quantity");
- _headers[7].ShouldBe("Held Quantity");
- _headers[8].ShouldBe("Scrapped Quantity");
- _headers[9].ShouldBe("Shipped Quantity");
- _headers[10].ShouldBe("Operation Step Branch Code");
- _headers[11].ShouldBe("Operation Step");
- _headers[12].ShouldBe("Operation Step Description");
- _headers[13].ShouldBe("Function Operation Description");
- _headers[14].ShouldBe("Operation Step Update Timestamp");
- _headers[15].ShouldBe("Status Code");
- _headers[16].ShouldBe("Status Description");
- _headers[17].ShouldBe("Status Update Timestamp");
- _headers[18].ShouldBe("Inclusion Reason");
- }
-
- [Fact]
- public void TableStyle_IsLight18()
- {
- var table = _sheet.Tables.First();
- table.Theme.ShouldBe(XLTableTheme.TableStyleLight18);
- }
-
- [Fact]
- public void DataRow_ContainsExpectedValues()
- {
- _sheet.Cell(2, 1).Value.GetNumber().ShouldBe(12345);
- _sheet.Cell(2, 3).Value.GetText().ShouldBe("LOT-001");
- _sheet.Cell(2, 4).Value.GetText().ShouldBe("ITEM-001");
- }
-}
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.SS.UserModel;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Integration;
+
+public class SearchResultsSheetTests : IClassFixture
+{
+ private readonly XSSFWorkbook _workbook;
+ private readonly ISheet _sheet;
+ private readonly List _headers;
+
+ public SearchResultsSheetTests(WithResultsFixture fixture)
+ {
+ _workbook = fixture.Workbook;
+ _sheet = _workbook.GetSheet("Search Results")!;
+ _headers = ExcelTestHelpers.GetHeadersFromSheet(_sheet);
+ }
+
+ [Fact]
+ public void ColumnCount_Is19()
+ {
+ _headers.Count.ShouldBe(19);
+ }
+
+ [Fact]
+ public void ColumnHeaders_MatchExpected()
+ {
+ _headers.ShouldContain("Work Order Number");
+ _headers.ShouldContain("Work Order Branch Code");
+ _headers.ShouldContain("Lot Number");
+ _headers.ShouldContain("Item Number");
+ _headers.ShouldContain("Planning Family");
+ _headers.ShouldContain("Stocking Type");
+ _headers.ShouldContain("Order Quantity");
+ _headers.ShouldContain("Held Quantity");
+ _headers.ShouldContain("Scrapped Quantity");
+ _headers.ShouldContain("Shipped Quantity");
+ _headers.ShouldContain("Operation Step Branch Code");
+ _headers.ShouldContain("Operation Step");
+ _headers.ShouldContain("Operation Step Description");
+ _headers.ShouldContain("Function Operation Description");
+ _headers.ShouldContain("Operation Step Update Timestamp");
+ _headers.ShouldContain("Status Code");
+ _headers.ShouldContain("Status Description");
+ _headers.ShouldContain("Status Update Timestamp");
+ _headers.ShouldContain("Inclusion Reason");
+ }
+
+ [Fact]
+ public void ColumnOrder_MatchesSpec()
+ {
+ _headers[0].ShouldBe("Work Order Number");
+ _headers[1].ShouldBe("Work Order Branch Code");
+ _headers[2].ShouldBe("Lot Number");
+ _headers[3].ShouldBe("Item Number");
+ _headers[4].ShouldBe("Planning Family");
+ _headers[5].ShouldBe("Stocking Type");
+ _headers[6].ShouldBe("Order Quantity");
+ _headers[7].ShouldBe("Held Quantity");
+ _headers[8].ShouldBe("Scrapped Quantity");
+ _headers[9].ShouldBe("Shipped Quantity");
+ _headers[10].ShouldBe("Operation Step Branch Code");
+ _headers[11].ShouldBe("Operation Step");
+ _headers[12].ShouldBe("Operation Step Description");
+ _headers[13].ShouldBe("Function Operation Description");
+ _headers[14].ShouldBe("Operation Step Update Timestamp");
+ _headers[15].ShouldBe("Status Code");
+ _headers[16].ShouldBe("Status Description");
+ _headers[17].ShouldBe("Status Update Timestamp");
+ _headers[18].ShouldBe("Inclusion Reason");
+ }
+
+ [Fact]
+ public void TableStyle_IsLight18()
+ {
+ var table = ExcelTestHelpers.GetFirstTable(_sheet);
+ table.StyleName.ShouldBe("TableStyleLight18");
+ }
+
+ [Fact]
+ public void DataRow_ContainsExpectedValues()
+ {
+ ExcelTestHelpers.GetCellNumber(_sheet, 2, 1).ShouldBe(12345);
+ ExcelTestHelpers.GetCellText(_sheet, 2, 3).ShouldBe("LOT-001");
+ ExcelTestHelpers.GetCellText(_sheet, 2, 4).ShouldBe("ITEM-001");
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj b/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj
index b4a9b8d..3f2df8b 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/JdeScoping.ExcelIO.Tests.csproj
@@ -8,10 +8,11 @@
true
-
-
-
-
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs
index 57e3517..f119d2b 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Parsing/ExcelParserServiceTests.cs
@@ -1,179 +1,171 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Parsing;
-using Microsoft.Extensions.Logging.Abstractions;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Parsing;
-
-public class ExcelParserServiceTests
-{
- private readonly ExcelParserService _service = new(NullLogger.Instance);
-
- [Fact]
- public void ParseWorkOrders_ReturnsWorkOrderNumbers()
- {
- // Arrange
- var excelData = CreateWorkOrderExcel([12345, 67890, 11111]);
-
- // Act
- using var stream = new MemoryStream(excelData);
- var result = _service.ParseWorkOrders(stream);
-
- // Assert
- result.Count.ShouldBe(3);
- result.ShouldContain(12345);
- result.ShouldContain(67890);
- result.ShouldContain(11111);
- }
-
- [Fact]
- public void ParseWorkOrders_SkipsInvalidNumbers()
- {
- // Arrange
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Sheet1");
- worksheet.Cell(1, 1).Value = "Work Order";
- worksheet.Cell(2, 1).Value = "12345";
- worksheet.Cell(3, 1).Value = "not-a-number";
- worksheet.Cell(4, 1).Value = "67890";
-
- using var ms = new MemoryStream();
- workbook.SaveAs(ms);
- ms.Position = 0;
-
- // Act
- var result = _service.ParseWorkOrders(ms);
-
- // Assert
- result.Count.ShouldBe(2);
- }
-
- [Fact]
- public void ParseItems_ReturnsItemNumbers()
- {
- // Arrange
- var excelData = CreateItemExcel(["ITEM-001", "ITEM-002"]);
-
- // Act
- using var stream = new MemoryStream(excelData);
- var result = _service.ParseItems(stream);
-
- // Assert
- result.Count.ShouldBe(2);
- result.ShouldContain("ITEM-001");
- result.ShouldContain("ITEM-002");
- }
-
- [Fact]
- public void ParseComponentLots_ReturnsLotViewModels()
- {
- // Arrange
- var excelData = CreateComponentLotExcel([("LOT001", "ITEM-001"), ("LOT002", "ITEM-002")]);
-
- // Act
- using var stream = new MemoryStream(excelData);
- var result = _service.ParseComponentLots(stream);
-
- // Assert
- result.Count.ShouldBe(2);
- result[0].LotNumber.ShouldBe("LOT001");
- result[0].ItemNumber.ShouldBe("ITEM-001");
- }
-
- [Fact]
- public void ParsePartOperations_ReturnsPartOperations()
- {
- // Arrange
- var excelData = CreatePartOperationExcel([("ITEM-001", "100", "MIS001", "A")]);
-
- // Act
- using var stream = new MemoryStream(excelData);
- var result = _service.ParsePartOperations(stream);
-
- // Assert
- result.Count.ShouldBe(1);
- result[0].ItemNumber.ShouldBe("ITEM-001");
- result[0].OperationNumber.ShouldBe("100");
- result[0].MisNumber.ShouldBe("MIS001");
- result[0].MisRevision.ShouldBe("A");
- }
-
- [Fact]
- public void ParsePartOperations_TruncatesDecimalOperationNumbers()
- {
- // Arrange
- var excelData = CreatePartOperationExcel([("ITEM-001", "100.5", "MIS001", "A")]);
-
- // Act
- using var stream = new MemoryStream(excelData);
- var result = _service.ParsePartOperations(stream);
-
- // Assert
- result[0].OperationNumber.ShouldBe("100");
- }
-
- private static byte[] CreateWorkOrderExcel(long[] workOrderNumbers)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Sheet1");
- worksheet.Cell(1, 1).Value = "Work Order Number";
- for (var i = 0; i < workOrderNumbers.Length; i++)
- {
- worksheet.Cell(i + 2, 1).Value = workOrderNumbers[i];
- }
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-
- private static byte[] CreateItemExcel(string[] itemNumbers)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Sheet1");
- worksheet.Cell(1, 1).Value = "Item Number";
- for (var i = 0; i < itemNumbers.Length; i++)
- {
- worksheet.Cell(i + 2, 1).Value = itemNumbers[i];
- }
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-
- private static byte[] CreateComponentLotExcel((string LotNumber, string ItemNumber)[] lots)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Sheet1");
- worksheet.Cell(1, 1).Value = "Lot Number";
- worksheet.Cell(1, 2).Value = "Item Number";
- for (var i = 0; i < lots.Length; i++)
- {
- worksheet.Cell(i + 2, 1).Value = lots[i].LotNumber;
- worksheet.Cell(i + 2, 2).Value = lots[i].ItemNumber;
- }
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-
- private static byte[] CreatePartOperationExcel((string ItemNumber, string OpNumber, string MisNumber, string MisRevision)[] operations)
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Sheet1");
- worksheet.Cell(1, 1).Value = "Item Number";
- worksheet.Cell(1, 2).Value = "Operation Number";
- worksheet.Cell(1, 3).Value = "MIS Number";
- worksheet.Cell(1, 4).Value = "MIS Revision";
- for (var i = 0; i < operations.Length; i++)
- {
- worksheet.Cell(i + 2, 1).Value = operations[i].ItemNumber;
- worksheet.Cell(i + 2, 2).Value = operations[i].OpNumber;
- worksheet.Cell(i + 2, 3).Value = operations[i].MisNumber;
- worksheet.Cell(i + 2, 4).Value = operations[i].MisRevision;
- }
- using var stream = new MemoryStream();
- workbook.SaveAs(stream);
- return stream.ToArray();
- }
-}
+using JdeScoping.ExcelIO.Parsing;
+using Microsoft.Extensions.Logging.Abstractions;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Parsing;
+
+public class ExcelParserServiceTests
+{
+ private readonly ExcelParserService _service = new(NullLogger.Instance);
+
+ [Fact]
+ public void ParseWorkOrders_ReturnsWorkOrderNumbers()
+ {
+ var excelData = CreateWorkOrderExcel([12345, 67890, 11111]);
+
+ using var stream = new MemoryStream(excelData);
+ var result = _service.ParseWorkOrders(stream);
+
+ result.Count.ShouldBe(3);
+ result.ShouldContain(12345);
+ result.ShouldContain(67890);
+ result.ShouldContain(11111);
+ }
+
+ [Fact]
+ public void ParseWorkOrders_SkipsInvalidNumbers()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Sheet1");
+ worksheet.CreateRow(0).CreateCell(0).SetCellValue("Work Order");
+ worksheet.CreateRow(1).CreateCell(0).SetCellValue("12345");
+ worksheet.CreateRow(2).CreateCell(0).SetCellValue("not-a-number");
+ worksheet.CreateRow(3).CreateCell(0).SetCellValue("67890");
+
+ using var ms = new MemoryStream();
+ workbook.Write(ms, leaveOpen: true);
+ ms.Position = 0;
+
+ var result = _service.ParseWorkOrders(ms);
+
+ result.Count.ShouldBe(2);
+ }
+
+ [Fact]
+ public void ParseItems_ReturnsItemNumbers()
+ {
+ var excelData = CreateItemExcel(["ITEM-001", "ITEM-002"]);
+
+ using var stream = new MemoryStream(excelData);
+ var result = _service.ParseItems(stream);
+
+ result.Count.ShouldBe(2);
+ result.ShouldContain("ITEM-001");
+ result.ShouldContain("ITEM-002");
+ }
+
+ [Fact]
+ public void ParseComponentLots_ReturnsLotViewModels()
+ {
+ var excelData = CreateComponentLotExcel([("LOT001", "ITEM-001"), ("LOT002", "ITEM-002")]);
+
+ using var stream = new MemoryStream(excelData);
+ var result = _service.ParseComponentLots(stream);
+
+ result.Count.ShouldBe(2);
+ result[0].LotNumber.ShouldBe("LOT001");
+ result[0].ItemNumber.ShouldBe("ITEM-001");
+ }
+
+ [Fact]
+ public void ParsePartOperations_ReturnsPartOperations()
+ {
+ var excelData = CreatePartOperationExcel([("ITEM-001", "100", "MIS001", "A")]);
+
+ using var stream = new MemoryStream(excelData);
+ var result = _service.ParsePartOperations(stream);
+
+ result.Count.ShouldBe(1);
+ result[0].ItemNumber.ShouldBe("ITEM-001");
+ result[0].OperationNumber.ShouldBe("100");
+ result[0].MisNumber.ShouldBe("MIS001");
+ result[0].MisRevision.ShouldBe("A");
+ }
+
+ [Fact]
+ public void ParsePartOperations_TruncatesDecimalOperationNumbers()
+ {
+ var excelData = CreatePartOperationExcel([("ITEM-001", "100.5", "MIS001", "A")]);
+
+ using var stream = new MemoryStream(excelData);
+ var result = _service.ParsePartOperations(stream);
+
+ result[0].OperationNumber.ShouldBe("100");
+ }
+
+ private static byte[] CreateWorkOrderExcel(long[] workOrderNumbers)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Sheet1");
+ worksheet.CreateRow(0).CreateCell(0).SetCellValue("Work Order Number");
+ for (var i = 0; i < workOrderNumbers.Length; i++)
+ {
+ worksheet.CreateRow(i + 1).CreateCell(0).SetCellValue(workOrderNumbers[i]);
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ private static byte[] CreateItemExcel(string[] itemNumbers)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Sheet1");
+ worksheet.CreateRow(0).CreateCell(0).SetCellValue("Item Number");
+ for (var i = 0; i < itemNumbers.Length; i++)
+ {
+ worksheet.CreateRow(i + 1).CreateCell(0).SetCellValue(itemNumbers[i]);
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ private static byte[] CreateComponentLotExcel((string LotNumber, string ItemNumber)[] lots)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Sheet1");
+ var header = worksheet.CreateRow(0);
+ header.CreateCell(0).SetCellValue("Lot Number");
+ header.CreateCell(1).SetCellValue("Item Number");
+
+ for (var i = 0; i < lots.Length; i++)
+ {
+ var row = worksheet.CreateRow(i + 1);
+ row.CreateCell(0).SetCellValue(lots[i].LotNumber);
+ row.CreateCell(1).SetCellValue(lots[i].ItemNumber);
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+
+ private static byte[] CreatePartOperationExcel((string ItemNumber, string OpNumber, string MisNumber, string MisRevision)[] operations)
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = workbook.CreateSheet("Sheet1");
+ var header = worksheet.CreateRow(0);
+ header.CreateCell(0).SetCellValue("Item Number");
+ header.CreateCell(1).SetCellValue("Operation Number");
+ header.CreateCell(2).SetCellValue("MIS Number");
+ header.CreateCell(3).SetCellValue("MIS Revision");
+
+ for (var i = 0; i < operations.Length; i++)
+ {
+ var row = worksheet.CreateRow(i + 1);
+ row.CreateCell(0).SetCellValue(operations[i].ItemNumber);
+ row.CreateCell(1).SetCellValue(operations[i].OpNumber);
+ row.CreateCell(2).SetCellValue(operations[i].MisNumber);
+ row.CreateCell(3).SetCellValue(operations[i].MisRevision);
+ }
+
+ using var stream = new MemoryStream();
+ workbook.Write(stream, leaveOpen: true);
+ return stream.ToArray();
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs
index 45578df..cdfe34c 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/Templates/ExcelTemplateServiceTests.cs
@@ -1,96 +1,80 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Templates;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests.Templates;
-
-public class ExcelTemplateServiceTests
-{
- private readonly ExcelTemplateService _service = new();
-
- [Fact]
- public void GenerateSingleColumn_CreatesValidExcel()
- {
- // Arrange
- var data = new[] { 12345L, 67890L };
-
- // Act
- var result = _service.GenerateSingleColumn(data, "Work Order Number");
-
- // Assert
- result.ShouldNotBeNull();
- result.Length.ShouldBeGreaterThan(0);
-
- // Verify content
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheet(1);
-
- worksheet.Cell(1, 1).GetString().ShouldBe("Work Order Number");
- worksheet.Cell(2, 1).GetString().ShouldBe("12345");
- worksheet.Cell(3, 1).GetString().ShouldBe("67890");
- }
-
- [Fact]
- public void GenerateMultiColumn_CreatesValidExcel()
- {
- // Arrange
- var data = new[]
- {
- new object?[] { "ITEM-001", "Description 1" },
- new object?[] { "ITEM-002", "Description 2" }
- };
- var headers = new[] { "Item Number", "Description" };
-
- // Act
- var result = _service.GenerateMultiColumn(data, headers);
-
- // Assert
- result.ShouldNotBeNull();
- result.Length.ShouldBeGreaterThan(0);
-
- // Verify content
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheet(1);
-
- worksheet.Cell(1, 1).GetString().ShouldBe("Item Number");
- worksheet.Cell(1, 2).GetString().ShouldBe("Description");
- worksheet.Cell(2, 1).GetString().ShouldBe("ITEM-001");
- worksheet.Cell(2, 2).GetString().ShouldBe("Description 1");
- }
-
- [Fact]
- public void GenerateSingleColumn_HandlesEmptyData()
- {
- // Act
- var result = _service.GenerateSingleColumn(Array.Empty(), "Header");
-
- // Assert
- result.ShouldNotBeNull();
- result.Length.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void GenerateMultiColumn_HandlesNullValues()
- {
- // Arrange
- var data = new[]
- {
- new object?[] { "ITEM-001", null }
- };
- var headers = new[] { "Item", "Value" };
-
- // Act
- var result = _service.GenerateMultiColumn(data, headers);
-
- // Assert
- result.ShouldNotBeNull();
- using var stream = new MemoryStream(result);
- using var workbook = new XLWorkbook(stream);
- var worksheet = workbook.Worksheet(1);
-
- worksheet.Cell(2, 2).GetString().ShouldBe(string.Empty);
- }
-}
+using JdeScoping.ExcelIO.Templates;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests.Templates;
+
+public class ExcelTemplateServiceTests
+{
+ private readonly ExcelTemplateService _service = new();
+
+ [Fact]
+ public void GenerateSingleColumn_CreatesValidExcel()
+ {
+ var data = new[] { 12345L, 67890L };
+
+ var result = _service.GenerateSingleColumn(data, "Work Order Number");
+
+ result.ShouldNotBeNull();
+ result.Length.ShouldBeGreaterThan(0);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Work Order Number");
+ ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("12345");
+ ExcelTestHelpers.GetCellText(worksheet, 3, 1).ShouldBe("67890");
+ }
+
+ [Fact]
+ public void GenerateMultiColumn_CreatesValidExcel()
+ {
+ var data = new[]
+ {
+ new object?[] { "ITEM-001", "Description 1" },
+ new object?[] { "ITEM-002", "Description 2" }
+ };
+ var headers = new[] { "Item Number", "Description" };
+
+ var result = _service.GenerateMultiColumn(data, headers);
+
+ result.ShouldNotBeNull();
+ result.Length.ShouldBeGreaterThan(0);
+
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 1, 1).ShouldBe("Item Number");
+ ExcelTestHelpers.GetCellText(worksheet, 1, 2).ShouldBe("Description");
+ ExcelTestHelpers.GetCellText(worksheet, 2, 1).ShouldBe("ITEM-001");
+ ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe("Description 1");
+ }
+
+ [Fact]
+ public void GenerateSingleColumn_HandlesEmptyData()
+ {
+ var result = _service.GenerateSingleColumn(Array.Empty(), "Header");
+
+ result.ShouldNotBeNull();
+ result.Length.ShouldBeGreaterThan(0);
+ }
+
+ [Fact]
+ public void GenerateMultiColumn_HandlesNullValues()
+ {
+ var data = new[]
+ {
+ new object?[] { "ITEM-001", null }
+ };
+ var headers = new[] { "Item", "Value" };
+
+ var result = _service.GenerateMultiColumn(data, headers);
+
+ result.ShouldNotBeNull();
+ using var workbook = ExcelTestHelpers.OpenWorkbook(result);
+ var worksheet = workbook.GetSheetAt(0);
+
+ ExcelTestHelpers.GetCellText(worksheet, 2, 2).ShouldBe(string.Empty);
+ }
+}
diff --git a/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs b/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs
index af41d38..b5ba1e0 100644
--- a/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs
+++ b/NEW/tests/JdeScoping.ExcelIO.Tests/WorksheetProtectorTests.cs
@@ -1,80 +1,78 @@
-using ClosedXML.Excel;
-using JdeScoping.ExcelIO.Formatting;
-using Shouldly;
-using Xunit;
-
-namespace JdeScoping.ExcelIO.Tests;
-
-public class WorksheetProtectorTests
-{
- [Fact]
- public void ApplyProtection_ProtectsWorksheet()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
-
- WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
-
- worksheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void ApplyProtection_AllowsSpecifiedOperations()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
-
- WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
-
- // Check that specified operations are allowed
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteColumns).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteRows).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.AutoFilter).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatCells).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatColumns).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.FormatRows).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.SelectLockedCells).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.SelectUnlockedCells).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.EditObjects).ShouldBeTrue();
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.Sort).ShouldBeTrue();
- }
-
- [Fact]
- public void ApplyProtection_AllowsDeleteRows()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
-
- WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
-
- // DeleteRows should be allowed
- worksheet.Protection.AllowedElements.HasFlag(XLSheetProtectionElements.DeleteRows).ShouldBeTrue();
- }
-
- [Fact]
- public void ApplyCriteriaProtection_ProtectsWorksheet()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
-
- WorksheetProtector.ApplyCriteriaProtection(worksheet, "CriteriaPassword");
-
- worksheet.Protection.IsProtected.ShouldBeTrue();
- }
-
- [Fact]
- public void UnlockExtensionArea_UnlocksSpecifiedRange()
- {
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("Test");
-
- // First, set some cells to locked (default)
- worksheet.Range(1, 1, 10, 5).Style.Protection.Locked = true;
-
- WorksheetProtector.UnlockExtensionArea(worksheet, 10, 5, 100, 100);
-
- // Extension area should be unlocked
- var extensionCell = worksheet.Cell(1, 6);
- extensionCell.Style.Protection.Locked.ShouldBeFalse();
- }
-}
+using JdeScoping.ExcelIO.Formatting;
+using JdeScoping.ExcelIO.Tests.Fixtures;
+using NPOI.XSSF.UserModel;
+using Shouldly;
+using Xunit;
+
+namespace JdeScoping.ExcelIO.Tests;
+
+public class WorksheetProtectorTests
+{
+ [Fact]
+ public void ApplyProtection_ProtectsWorksheet()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = (XSSFSheet)workbook.CreateSheet("Test");
+
+ WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
+
+ worksheet.IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void ApplyProtection_AllowsSpecifiedOperations()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = (XSSFSheet)workbook.CreateSheet("Test");
+
+ WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
+
+ worksheet.IsDeleteColumnsLocked.ShouldBeFalse();
+ worksheet.IsDeleteRowsLocked.ShouldBeFalse();
+ worksheet.IsAutoFilterLocked.ShouldBeFalse();
+ worksheet.IsFormatCellsLocked.ShouldBeFalse();
+ worksheet.IsFormatColumnsLocked.ShouldBeFalse();
+ worksheet.IsFormatRowsLocked.ShouldBeFalse();
+ worksheet.IsSelectLockedCellsLocked.ShouldBeFalse();
+ worksheet.IsSelectUnlockedCellsLocked.ShouldBeFalse();
+ worksheet.IsObjectsLocked.ShouldBeFalse();
+ worksheet.IsSortLocked.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void ApplyProtection_AllowsDeleteRows()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = (XSSFSheet)workbook.CreateSheet("Test");
+
+ WorksheetProtector.ApplyProtection(worksheet, "TestPassword");
+
+ worksheet.IsDeleteRowsLocked.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void ApplyCriteriaProtection_ProtectsWorksheet()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = (XSSFSheet)workbook.CreateSheet("Test");
+
+ WorksheetProtector.ApplyCriteriaProtection(worksheet, "CriteriaPassword");
+
+ worksheet.IsSheetLocked.ShouldBeTrue();
+ }
+
+ [Fact]
+ public void UnlockExtensionArea_UnlocksSpecifiedRange()
+ {
+ using var workbook = new XSSFWorkbook();
+ var worksheet = (XSSFSheet)workbook.CreateSheet("Test");
+
+ WorksheetProtector.UnlockExtensionArea(worksheet, 10, 5, 100, 100);
+
+ var extensionStyle = worksheet.GetColumnStyle(5);
+ extensionStyle.IsLocked.ShouldBeFalse();
+
+ var extensionCell = ExcelTestHelpers.GetCell(worksheet, 1, 6)!;
+ extensionCell.CellStyle.IsLocked.ShouldBeFalse();
+ }
+}