using ClosedXML.Excel; using WorkTracker.Domain; namespace WorkTracker.Services.Exports; public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExporter { private const int DayHeaderRow = 1; private const int DayStartColumn = 3; private const int TemplateDayCount = 30; private static readonly IReadOnlyDictionary TimesheetRowMap = new Dictionary(StringComparer.Ordinal) { ["office"] = 2, ["home"] = 4, ["overtime"] = 5, ["weekend"] = 6, ["night"] = 7, ["vacation"] = 8, ["permit"] = 9, ["compensatory-rest"] = 10, ["sick"] = 11, ["holiday"] = 12 }; public byte[] Export(MonthlyTimesheetModel timesheet, Stream templateStream) { ArgumentNullException.ThrowIfNull(timesheet); ArgumentNullException.ThrowIfNull(templateStream); if (templateStream.CanSeek) { templateStream.Position = 0; } using var workbook = new XLWorkbook(templateStream); workbook.DefinedNames.DeleteAll(); var worksheet = workbook.Worksheet(1); var dayCount = timesheet.Days.Count; AdjustDayColumns(worksheet, dayCount); ApplyDayHeaders(worksheet, timesheet.Days); ApplyRowValues(worksheet, timesheet.Rows, dayCount); ApplyTotalFormulas(worksheet, dayCount); using var output = new MemoryStream(); workbook.SaveAs(output); return output.ToArray(); } private static void AdjustDayColumns(IXLWorksheet worksheet, int dayCount) { var totalColumn = DayStartColumn + TemplateDayCount; var delta = dayCount - TemplateDayCount; if (delta > 0) { worksheet.Column(totalColumn).InsertColumnsBefore(delta); } else if (delta < 0) { worksheet.Columns(DayStartColumn + dayCount, DayStartColumn + TemplateDayCount - 1).Delete(); } } private static void ApplyDayHeaders(IXLWorksheet worksheet, IReadOnlyList days) { var lastDayColumn = DayStartColumn + days.Count - 1; for (var index = 0; index < days.Count; index++) { var day = days[index]; var column = DayStartColumn + index; var headerCell = worksheet.Cell(DayHeaderRow, column); headerCell.Value = day.Date.Day; ApplyDayStyle(worksheet, headerCell, day, column, lastDayColumn, 1); foreach (var rowNumber in TimesheetRowMap.Values) { ApplyDayStyle(worksheet, worksheet.Cell(rowNumber, column), day, column, lastDayColumn, rowNumber); } } } private static void ApplyRowValues(IXLWorksheet worksheet, IReadOnlyList rows, int dayCount) { foreach (var row in rows) { if (!TimesheetRowMap.TryGetValue(row.Key, out var worksheetRow)) { continue; } for (var dayIndex = 0; dayIndex < dayCount; dayIndex++) { var cell = worksheet.Cell(worksheetRow, DayStartColumn + dayIndex); var value = dayIndex < row.DailyValues.Count ? row.DailyValues[dayIndex] : null; if (value.HasValue && value.Value > 0m) { cell.Value = value.Value; } else { cell.Clear(XLClearOptions.Contents); } } } } private static void ApplyTotalFormulas(IXLWorksheet worksheet, int dayCount) { var lastDayColumn = DayStartColumn + dayCount - 1; var totalColumn = lastDayColumn + 1; var firstDayColumnLetter = XLHelper.GetColumnLetterFromNumber(DayStartColumn); var lastDayColumnLetter = XLHelper.GetColumnLetterFromNumber(lastDayColumn); foreach (var rowNumber in TimesheetRowMap.Values) { worksheet.Cell(rowNumber, totalColumn).FormulaA1 = $"SUM({firstDayColumnLetter}{rowNumber}:{lastDayColumnLetter}{rowNumber})"; } } private static void ApplyDayStyle(IXLWorksheet worksheet, IXLCell targetCell, MonthlyTimesheetDayModel day, int column, int lastDayColumn, int rowNumber) { var baseCell = worksheet.Cell(GetBaseStyleRowAddress(rowNumber), GetBaseStyleColumn(column, lastDayColumn)); var baseStyle = baseCell.Style; if (!ShouldHighlight(day)) { targetCell.Style = baseStyle; return; } var sampleColumn = GetHighlightSampleColumn(day, rowNumber); var sampleCell = worksheet.Cell(GetSampleStyleRow(rowNumber), sampleColumn); var sampleStyle = sampleCell.Style; targetCell.Style = sampleStyle; targetCell.Style.Border.LeftBorder = baseStyle.Border.LeftBorder; targetCell.Style.Border.LeftBorderColor = baseStyle.Border.LeftBorderColor; targetCell.Style.Border.RightBorder = baseStyle.Border.RightBorder; targetCell.Style.Border.RightBorderColor = baseStyle.Border.RightBorderColor; targetCell.Style.Border.TopBorder = baseStyle.Border.TopBorder; targetCell.Style.Border.TopBorderColor = baseStyle.Border.TopBorderColor; targetCell.Style.Border.BottomBorder = baseStyle.Border.BottomBorder; targetCell.Style.Border.BottomBorderColor = baseStyle.Border.BottomBorderColor; } private static bool ShouldHighlight(MonthlyTimesheetDayModel day) { return day.IsWeekend || day.IsHoliday; } private static int GetBaseStyleRowAddress(int rowNumber) { return rowNumber switch { 1 => 1, 2 => 2, 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 => 4, 12 => 12, _ => rowNumber }; } private static int GetSampleStyleRow(int rowNumber) { return rowNumber switch { 1 => 1, 2 => 2, 4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 => 4, 12 => 12, _ => rowNumber }; } private static int GetBaseStyleColumn(int column, int lastDayColumn) { if (column == DayStartColumn) { return DayStartColumn; } if (column == lastDayColumn) { return lastDayColumn == DayStartColumn + TemplateDayCount - 1 ? DayStartColumn + TemplateDayCount - 1 : DayStartColumn + 1; } return DayStartColumn + 1; } private static int GetHighlightSampleColumn(MonthlyTimesheetDayModel day, int rowNumber) { if (rowNumber == 1) { if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday) { return 7; } if (day.Date.DayOfWeek == DayOfWeek.Saturday) { return 6; } return 7; } if (rowNumber == 2) { if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday) { return 7; } if (day.Date.DayOfWeek == DayOfWeek.Saturday) { return 6; } return 7; } return 6; } }