Files
jdescopingtool/OLD/WorkerService/Process/ExcelWriter.cs
T
Joseph Doherty 26ff8d9b4f Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
2026-01-02 07:43:29 -05:00

500 lines
23 KiB
C#
Executable File

using System.Collections.Generic;
using System.Drawing;
using System.IO;
using DataModel.Models;
using OfficeOpenXml;
using OfficeOpenXml.Style;
using OfficeOpenXml.Table;
using WorkerService.Helpers;
using WorkerService.Models.Reporting;
namespace WorkerService.Process
{
/// <summary>
/// Search results report writer
/// </summary>
public class ExcelWriter
{
/// <summary>
/// Standard date format
/// </summary>
private const string DATE_FORMAT = "{0:MM/dd/yyyy}";
/// <summary>
/// Standard timestamp format
/// </summary>
private const string TIMESTAMP_FORMAT = "[$-409]m/d/yy h:mm AM/PM;@";
/// <summary>
/// Override width for cells with wrapped data
/// </summary>
private const double WRAPPED_CELL_WIDTH = 65;
/// <summary>
/// Generates Excel report for the given search criteria and results
/// </summary>
/// <param name="searchModel">Search model to generate report for</param>
/// <returns>Excel report file contents</returns>
public static byte[] Generate(SearchModel searchModel)
{
byte[] data;
using (ExcelPackage package = new ExcelPackage())
{
//Write search criteria tab
WriteCriteria(package, searchModel);
//Write results tab
package.Workbook.LoadTab(searchModel.Results);
//Write MIS tab
if(searchModel.ExtractMisData)
{
package.Workbook.LoadTab(searchModel.MisResults);
}
//Write mismatch tab
if (searchModel.ExtractMisData)
{
package.Workbook.LoadTab(searchModel.MisNonMatchResults);
}
//Save workbook to array
using (MemoryStream memoryStream = new MemoryStream())
{
package.SaveAs(memoryStream);
memoryStream.Position = 0;
data = memoryStream.ToArray();
}
}
return data;
}
/// <summary>
/// Writes search criteria to a tab in the worksheet
/// </summary>
/// <param name="package">Excel package to write to</param>
/// <param name="searchModel">Search model to generate report for</param>
private static void WriteCriteria(ExcelPackage package, SearchModel searchModel)
{
//Create table in workbook to hold criteria
ExcelWorksheet criteriaTab = package.Workbook.Worksheets.Add("Search Criteria");
int row = 1;
//Write name and revision number
ApplyHeaderFormat(criteriaTab.Cells[row, 1], "Search Name");
criteriaTab.Cells[row, 2].Value = searchModel.Name;
ApplyHeaderFormat(criteriaTab.Cells[++row, 1], "User Name");
criteriaTab.Cells[row, 2].Value = searchModel.UserName;
//Skip row
row++;
//Write timestamps
ApplyHeaderFormat(criteriaTab.Cells[++row, 1], "Submit timestamp");
criteriaTab.Cells[row, 2].Value = $"{searchModel.SubmitDT:MMM dd, yyyy hh:mm:ss tt} EST";
ApplyHeaderFormat(criteriaTab.Cells[++row, 1], "Start timestamp");
criteriaTab.Cells[row, 2].Value = $"{searchModel.StartDT:MMM dd, yyyy hh:mm:ss tt} EST";
ApplyHeaderFormat(criteriaTab.Cells[++row, 1], "Completed timestamp");
criteriaTab.Cells[row, 2].Value = $"{searchModel.EndDT:MMM dd, yyyy hh:mm:ss tt} EST";
//Skip row
row++;
/*
* Write min/max times
*/
ExcelTable timespanFilterTable = criteriaTab.Cells[++row, 1].LoadTable(
new List<TimespanFilter>()
{
new TimespanFilter()
{
MinimumDT = searchModel.MinimumDT, MaximumDT = searchModel.MaximumDT
}
}
);
row = timespanFilterTable.Address.End.Row + 3;
/*
* Write lot numbers
*/
ExcelTable workOrderFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.WorkOrderFilter);
row = workOrderFilterTable.Address.End.Row +3;
/*
* Write item numbers
*/
ExcelTable itemNumberFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.ItemNumberFilter);
row = itemNumberFilterTable.Address.End.Row +3;
/*
* Write profit centers
*/
ExcelTable profitCenterFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.ProfitCenterFilter);
row = profitCenterFilterTable.Address.End.Row + 3;
/*
* Write work centers
*/
ExcelTable workCenterFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.WorkCenterFilter);
row = workCenterFilterTable.Address.End.Row + 3;
/*
* Write component lot numbers
*/
ExcelTable componentLotFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.ComponentLotFilter);
row = componentLotFilterTable.Address.End.Row + 3;
/*
* Write operators
*/
ExcelTable operatorFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.OperatorFilter);
row = operatorFilterTable.Address.End.Row + 3;
/*
* Write item/operation/mis
*/
ExcelTable itemOperationMisFilterTable = criteriaTab.Cells[row, 1].LoadTable(searchModel.ItemOperationMisFilter);
row = itemOperationMisFilterTable.Address.End.Row + 3;
/*
* Write extract MIS data option
*/
ApplyHeaderFormat(criteriaTab.Cells[row, 1, row, 2], "Extract MIS data?", true);
criteriaTab.Cells[++row, 1].Value = searchModel.ExtractMisData ? "YES" : "NO";
//Auto-fit columns
for (int column = 1; column <= 4; column++)
{
criteriaTab.Column(column).AutoFit();
criteriaTab.Column(column).Width = criteriaTab.Column(column).Width * 1.15;
}
//Set worksheet/tab as protected
criteriaTab.Protection.SetPassword("JDE_SCOPING_TOOL_PASS");
}
/// <summary>
/// Writes search results to a tab in the worksheet
/// </summary>
/// <param name="package">Excel package to write to</param>
/// <param name="search">Search to extract criteria from</param>
/// <param name="results">Search results to write</param>
private static void WriteResults(ExcelPackage package, Search search, List<SearchResult> results)
{
//Create tab in workbook to hold results
ExcelWorksheet resultsTab = package.Workbook.Worksheets.Add("Search Results");
int row = 1;
int col = 1;
//Write header
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Work Order Number");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Work Order Branch Code");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Lot Number");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Item Number");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Planning Family");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Order Quantity");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Held Quantity");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Scrapped Quantity");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Shipped Quantity");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Operation Step Branch Code");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Operation Step");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Operation Step Description");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Function Operation Description");
resultsTab.Column(col).Style.Numberformat.Format = TIMESTAMP_FORMAT;
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Operation Step Update Timestamp");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Status Code");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Status Description");
resultsTab.Column(col).Style.Numberformat.Format = TIMESTAMP_FORMAT;
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Status Update Timestamp");
ApplyHeaderFormat(resultsTab.Cells[row, col++], "Inclusion Reason");
//Write data
foreach (SearchResult searchResult in results)
{
row++;
col = 1;
resultsTab.Cells[row, col++].Value = searchResult.WorkOrderNumber;
resultsTab.Cells[row, col++].Value = searchResult.WorkOrderBranchCode;
resultsTab.Cells[row, col++].Value = searchResult.LotNumber;
resultsTab.Cells[row, col++].Value = searchResult.ItemNumber;
resultsTab.Cells[row, col++].Value = searchResult.PlanningFamily;
resultsTab.Cells[row, col++].Value = searchResult.OrderQuantity;
resultsTab.Cells[row, col++].Value = searchResult.HeldQuantity;
resultsTab.Cells[row, col++].Value = searchResult.ScrappedQuantity;
resultsTab.Cells[row, col++].Value = searchResult.ShippedQuantity;
resultsTab.Cells[row, col++].Value = searchResult.StepBranchCode;
resultsTab.Cells[row, col++].Value = searchResult.StepNumber;
resultsTab.Cells[row, col++].Value = searchResult.StepDescription;
resultsTab.Cells[row, col++].Value = searchResult.FunctionOperationDescription;
resultsTab.Cells[row, col++].Value = searchResult.StepUpdateDT;
resultsTab.Cells[row, col++].Value = searchResult.StatusCode;
resultsTab.Cells[row, col++].Value = searchResult.StatusDescription;
resultsTab.Cells[row, col++].Value = searchResult.StatusUpdateDT;
resultsTab.Cells[row, col++].Value = searchResult.InclusionReason;
}
//Auto-fit columns
for (int column = 1; column <= resultsTab.Dimension.Columns; column++)
{
resultsTab.Column(column).AutoFit();
resultsTab.Column(column).Width = resultsTab.Column(column).Width * 1.3;
}
//Get protected / unprotected ranges for editing
ExcelRange protectedRange = resultsTab.Cells[1, 1, row, resultsTab.Dimension.Columns];
ExcelRange unprotectedRange = resultsTab.Cells[1, resultsTab.Dimension.Columns + 1, row + 1000, resultsTab.Dimension.Columns + 1000];
//Format as table
ExcelTable table = resultsTab.Tables.Add(protectedRange, "Search_Results");
table.ShowTotal = false;
table.TableStyle = TableStyles.Medium1;
//Write-protect range
resultsTab.Protection.IsProtected = true;
resultsTab.ProtectedRanges.Add("Editable", unprotectedRange);
resultsTab.Protection.AllowDeleteColumns = true;
resultsTab.Protection.AllowDeleteRows = false;
resultsTab.Protection.AllowAutoFilter = true;
resultsTab.Protection.AllowAutoFilter = true;
resultsTab.Protection.AllowFormatCells = true;
resultsTab.Protection.AllowFormatColumns = true;
resultsTab.Protection.AllowFormatRows = true;
resultsTab.Protection.AllowSelectLockedCells = true;
resultsTab.Protection.AllowSelectUnlockedCells = true;
resultsTab.Protection.AllowEditObject = true;
resultsTab.Protection.AllowSort = true;
resultsTab.Protection.SetPassword("JDESCOPINGTOOL");
}
/// <summary>
/// Writes intermediate MIS search results to a tab in the worksheet
/// </summary>
/// <param name="package">Excel package to write to</param>
/// <param name="misInfo">Intermediate MIS search results to write</param>
private static void WriteMisInfo(ExcelPackage package, List<MisSearchResult> misInfo)
{
if (misInfo == null)
{
return;
}
//Create tab in workbook to hold results
ExcelWorksheet misInfoTab = package.Workbook.Worksheets.Add("MIS Info");
int row = 1;
int col = 1;
//Write header
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Item Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Item Description");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "MIS Job Step Sequence Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "MIS Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "MIS Revision");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "MIS Release Status");
misInfoTab.Column(col).Style.Numberformat.Format = TIMESTAMP_FORMAT;
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "MIS Release Date");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Branch Code");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Job Step Sequence Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Matched Sequence Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Matched to F3112Z1?");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Matched to F3003?");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Function Operation Description");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Char Number");
misInfoTab.Column(col).Style.WrapText = true;
misInfoTab.Column(col).Width = 65;
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Test Description");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Sampling Type");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Sampling Value");
misInfoTab.Column(col).Style.WrapText = true;
misInfoTab.Column(col).Width = WRAPPED_CELL_WIDTH;
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Tools & Gauges");
misInfoTab.Column(col).Style.WrapText = true;
misInfoTab.Column(col).Width = WRAPPED_CELL_WIDTH;
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Work Instructions");
//Write data
foreach (MisSearchResult misData in misInfo)
{
row++;
col = 1;
misInfoTab.Cells[row, col++].Value = misData.ItemNumber;
misInfoTab.Cells[row, col++].Value = misData.ItemDescription;
misInfoTab.Cells[row, col++].Value = misData.SequenceNumber;
misInfoTab.Cells[row, col++].Value = misData.MisNumber;
misInfoTab.Cells[row, col++].Value = misData.RevID;
misInfoTab.Cells[row, col++].Value = misData.Status;
misInfoTab.Cells[row, col++].Value = misData.ReleaseDate;
misInfoTab.Cells[row, col++].Value = misData.BranchCode;
misInfoTab.Cells[row, col++].Value = misData.JobStepSequenceNumber;
misInfoTab.Cells[row, col++].Value = misData.MatchedSequenceNumber;
misInfoTab.Cells[row, col++].Value = misData.RoutingMatch;
misInfoTab.Cells[row, col++].Value = misData.MasterMatch;
misInfoTab.Cells[row, col++].Value = misData.FunctionOperationDescription;
misInfoTab.Cells[row, col++].Value = misData.CharNumber;
misInfoTab.Cells[row, col++].Value = misData.TestDescription;
misInfoTab.Cells[row, col++].Value = misData.SamplingType;
misInfoTab.Cells[row, col++].Value = misData.SamplingValue;
misInfoTab.Cells[row, col++].Value = misData.ToolsGauges;
misInfoTab.Cells[row, col++].Value = misData.WorkInstructions;
}
//Auto-fit columns
for (int column = 1; column <= misInfoTab.Dimension.Columns; column++)
{
if (misInfoTab.Column(column).Width == WRAPPED_CELL_WIDTH)
{
continue;
}
misInfoTab.Column(column).AutoFit();
misInfoTab.Column(column).Width = misInfoTab.Column(column).Width * 1.3;
}
//Get protected / unprotected ranges for editing
ExcelRange protectedRange = misInfoTab.Cells[1, 1, row, misInfoTab.Dimension.Columns];
ExcelRange unprotectedRange = misInfoTab.Cells[1, misInfoTab.Dimension.Columns + 1, row + 1000, misInfoTab.Dimension.Columns + 1000];
//Format as table
ExcelTable table = misInfoTab.Tables.Add(protectedRange, "MIS_Info");
table.ShowTotal = false;
table.TableStyle = TableStyles.Medium1;
//Write-protect data
ApplySecurity(misInfoTab);
misInfoTab.ProtectedRanges.Add("Editable", unprotectedRange);
}
/// <summary>
/// Writes master router mismatches to a tab in the worksheet
/// </summary>
/// <param name="package">Excel package to write to</param>
/// <param name="misMatches">Master router mismatches to write</param>
private static void WriteRouterMismatches(ExcelPackage package, List<MisNonMatchSearchResult> misMatches)
{
if (misMatches == null)
{
return;
}
//Create tab in workbook to hold results
ExcelWorksheet misInfoTab = package.Workbook.Worksheets.Add("Investigation");
int row = 1;
int col = 1;
//Write header
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Work Center Code");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Work Order Number");
misInfoTab.Column(col).Style.Numberformat.Format = "m/d/yyyy";
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Work Order Start Date");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Job Step Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Function Operation Description");
misInfoTab.Column(col).Style.Numberformat.Format = "m/d/yyyy";
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Job Step End Date");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Function Code");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Item Number");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Item Description");
ApplyHeaderFormat(misInfoTab.Cells[row, col++], "Routing Type");
//Write data
foreach (MisNonMatchSearchResult misMatch in misMatches)
{
row++;
col = 1;
misInfoTab.Cells[row, col++].Value = misMatch.WorkCenterCode;
misInfoTab.Cells[row, col++].Value = misMatch.WorkOrderNumber;
misInfoTab.Cells[row, col++].Value = misMatch.WorkOrderStartDate;
misInfoTab.Cells[row, col++].Value = misMatch.JobStepNumber;
misInfoTab.Cells[row, col++].Value = misMatch.JobStepDescription;
misInfoTab.Cells[row, col++].Value = misMatch.JobStepEndDate;
misInfoTab.Cells[row, col++].Value = misMatch.FunctionCode;
misInfoTab.Cells[row, col++].Value = misMatch.ItemNumber;
misInfoTab.Cells[row, col++].Value = misMatch.ItemDescription;
misInfoTab.Cells[row, col++].Value = misMatch.RoutingType;
}
//Auto-fit columns
for (int column = 1; column <= misInfoTab.Dimension.Columns; column++)
{
misInfoTab.Column(column).AutoFit();
misInfoTab.Column(column).Width = misInfoTab.Column(column).Width * 1.3;
}
//Get protected / unprotected ranges for editing
ExcelRange protectedRange = misInfoTab.Cells[1, 1, row, misInfoTab.Dimension.Columns];
ExcelRange unprotectedRange = misInfoTab.Cells[1, misInfoTab.Dimension.Columns + 1, row + 1000, misInfoTab.Dimension.Columns + 1000];
//Format as table
ExcelTable table = misInfoTab.Tables.Add(protectedRange, "Investigation");
table.ShowTotal = false;
table.TableStyle = TableStyles.Medium1;
//Write-protect data
ApplySecurity(misInfoTab);
misInfoTab.ProtectedRanges.Add("Editable", unprotectedRange);
}
/// <summary>
/// Applies header formatting to the range of cells
/// </summary>
/// <param name="range">Range of cells to format</param>
/// <param name="text">Text to write to cells</param>
/// <param name="merge">Whether or not to merge the cells</param>
private static void ApplyHeaderFormat(ExcelRange range, string text = null, bool merge = false)
{
range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
range.Style.Font.Bold = true;
range.Style.Fill.PatternType = ExcelFillStyle.Solid;
range.Style.Fill.BackgroundColor.SetColor(Color.Gainsboro);
if (!string.IsNullOrEmpty(text))
{
range.Value = text;
}
range.Merge = merge;
}
/// <summary>
/// Applies write protection security to given worksheet
/// </summary>
/// <param name="worksheet">Worksheet to apply write protection security to</param>
private static void ApplySecurity(ExcelWorksheet worksheet)
{
worksheet.Protection.IsProtected = true;
worksheet.Protection.AllowDeleteColumns = true;
worksheet.Protection.AllowDeleteRows = false;
worksheet.Protection.AllowAutoFilter = true;
worksheet.Protection.AllowFormatCells = true;
worksheet.Protection.AllowFormatColumns = true;
worksheet.Protection.AllowFormatRows = true;
worksheet.Protection.AllowSelectLockedCells = true;
worksheet.Protection.AllowSelectUnlockedCells = true;
worksheet.Protection.AllowEditObject = true;
worksheet.Protection.AllowSort = true;
worksheet.Protection.SetPassword("JDESCOPINGTOOL");
}
}
}