305 lines
13 KiB
C#
305 lines
13 KiB
C#
using ClosedXML.Excel;
|
|
using System.IO;
|
|
using PSTW_CentralSystem.Models;
|
|
using PSTW_CentralSystem.Areas.OTcalculate.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using ClosedXML.Excel.Drawings;
|
|
|
|
namespace PSTW_CentralSystem.Areas.OTcalculate.Services
|
|
{
|
|
public class OvertimeExcelService
|
|
{
|
|
public MemoryStream GenerateOvertimeExcel(
|
|
List<OtRegisterModel> records,
|
|
int departmentId,
|
|
string userFullName,
|
|
string departmentName,
|
|
int userStateId,
|
|
int weekendId,
|
|
List<CalendarModel> publicHolidays,
|
|
bool isAdminUser = false,
|
|
byte[]? logoImage = null
|
|
)
|
|
{
|
|
var workbook = new XLWorkbook();
|
|
var worksheet = workbook.Worksheets.Add("Overtime Records");
|
|
int currentRow = 1;
|
|
int logoBottomRow = 3;
|
|
|
|
if (logoImage != null)
|
|
{
|
|
using (var ms = new MemoryStream(logoImage))
|
|
{
|
|
var picture = worksheet.AddPicture(ms)
|
|
.MoveTo(worksheet.Cell(currentRow, 1))
|
|
.WithPlacement(XLPicturePlacement.FreeFloating);
|
|
picture.Name = "Company Logo";
|
|
picture.Scale(0.3);
|
|
}
|
|
}
|
|
|
|
currentRow = logoBottomRow + 1;
|
|
|
|
int mergeCols = 10; // or set to 'col' if you want to merge all active columns
|
|
|
|
if (!string.IsNullOrEmpty(userFullName))
|
|
{
|
|
var nameCell = worksheet.Range(currentRow, 1, currentRow, mergeCols).Merge();
|
|
nameCell.Value = $"Name: {userFullName}";
|
|
nameCell.Style.Font.Bold = true;
|
|
nameCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
|
|
nameCell.Style.Alignment.WrapText = true;
|
|
currentRow++;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(departmentName))
|
|
{
|
|
var deptCell = worksheet.Range(currentRow, 1, currentRow, mergeCols).Merge();
|
|
deptCell.Value = $"Department: {departmentName}";
|
|
deptCell.Style.Font.Bold = true;
|
|
deptCell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Left;
|
|
deptCell.Style.Alignment.WrapText = true;
|
|
currentRow++;
|
|
}
|
|
|
|
currentRow++;
|
|
|
|
// Header setup
|
|
int headerRow1 = currentRow;
|
|
int headerRow2 = currentRow + 1;
|
|
|
|
worksheet.Cell(headerRow1, 1).Value = "Days";
|
|
worksheet.Cell(headerRow1, 2).Value = "Date";
|
|
worksheet.Range(headerRow1, 1, headerRow2, 1).Merge(); // Days
|
|
worksheet.Range(headerRow1, 2, headerRow2, 2).Merge(); // Date
|
|
|
|
worksheet.Cell(headerRow1, 3).Value = "Office Hours";
|
|
worksheet.Range(headerRow1, 3, headerRow1, 5).Merge();
|
|
|
|
worksheet.Cell(headerRow2, 3).Value = "From";
|
|
worksheet.Cell(headerRow2, 4).Value = "To";
|
|
worksheet.Cell(headerRow2, 5).Value = "Break (min)";
|
|
|
|
worksheet.Cell(headerRow1, 6).Value = "After Office Hours";
|
|
worksheet.Range(headerRow1, 6, headerRow1, 8).Merge();
|
|
|
|
worksheet.Cell(headerRow2, 6).Value = "From";
|
|
worksheet.Cell(headerRow2, 7).Value = "To";
|
|
worksheet.Cell(headerRow2, 8).Value = "Break (min)";
|
|
|
|
worksheet.Cell(headerRow1, 9).Value = "Total OT Hours";
|
|
worksheet.Cell(headerRow1, 10).Value = "Break Hours (min)";
|
|
worksheet.Cell(headerRow1, 11).Value = "Net OT Hours";
|
|
worksheet.Range(headerRow1, 9, headerRow2, 9).Merge();
|
|
worksheet.Range(headerRow1, 10, headerRow2, 10).Merge();
|
|
worksheet.Range(headerRow1, 11, headerRow2, 11).Merge();
|
|
|
|
int col = 12;
|
|
if (departmentId == 2 || isAdminUser)
|
|
{
|
|
worksheet.Cell(headerRow1, col).Value = "Station";
|
|
worksheet.Range(headerRow1, col, headerRow2, col).Merge();
|
|
col++;
|
|
}
|
|
|
|
worksheet.Cell(headerRow1, col).Value = "Description";
|
|
worksheet.Range(headerRow1, col, headerRow2, col).Merge();
|
|
|
|
// Apply styling after header setup
|
|
var headerRange = worksheet.Range(headerRow1, 1, headerRow2, col);
|
|
headerRange.Style.Font.Bold = true;
|
|
headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
|
headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
|
headerRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
|
headerRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
|
|
|
// Background colors for header cells
|
|
worksheet.Range(headerRow1, 1, headerRow2, 2).Style.Fill.BackgroundColor = XLColor.LightGreen;
|
|
worksheet.Range(headerRow1, 3, headerRow2, 5).Style.Fill.BackgroundColor = XLColor.AliceBlue;
|
|
worksheet.Range(headerRow1, 6, headerRow2, 8).Style.Fill.BackgroundColor = XLColor.AliceBlue;
|
|
worksheet.Range(headerRow1, 9, headerRow2, 11).Style.Fill.BackgroundColor = XLColor.Peach;
|
|
if (departmentId == 2 || isAdminUser)
|
|
worksheet.Range(headerRow1, 12, headerRow2, 12).Style.Fill.BackgroundColor = XLColor.LightCyan;
|
|
|
|
worksheet.Cell(headerRow1, col).Style.Fill.BackgroundColor = XLColor.LightBlue;
|
|
|
|
// Update currentRow after headers
|
|
currentRow = headerRow2 + 1;
|
|
|
|
DateTime? previousDate = null;
|
|
|
|
foreach (var r in records)
|
|
{
|
|
int dataCol = 1;
|
|
|
|
bool isSameDateAsPrevious = previousDate == r.OtDate.Date;
|
|
previousDate = r.OtDate.Date;
|
|
|
|
var dayCell = worksheet.Cell(currentRow, dataCol++);
|
|
var dateCell = worksheet.Cell(currentRow, dataCol++);
|
|
|
|
// Check the type of day first
|
|
var dayOfWeek = r.OtDate.DayOfWeek;
|
|
bool isWeekend = (weekendId == 1 && (dayOfWeek == DayOfWeek.Friday || dayOfWeek == DayOfWeek.Saturday)) ||
|
|
(weekendId == 2 && (dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday));
|
|
bool isPublicHoliday = publicHolidays.Any(h => h.HolidayDate.Date == r.OtDate.Date);
|
|
|
|
// Apply color regardless of whether the value is shown
|
|
if (isPublicHoliday)
|
|
{
|
|
dayCell.Style.Fill.BackgroundColor = XLColor.Pink;
|
|
dateCell.Style.Fill.BackgroundColor = XLColor.Pink;
|
|
}
|
|
else if (isWeekend)
|
|
{
|
|
dayCell.Style.Fill.BackgroundColor = XLColor.LightBlue;
|
|
dateCell.Style.Fill.BackgroundColor = XLColor.LightBlue;
|
|
}
|
|
|
|
// Show value only if it's not repeated
|
|
if (!isSameDateAsPrevious)
|
|
{
|
|
dayCell.Value = r.OtDate.ToString("ddd");
|
|
dateCell.Value = r.OtDate.ToString("yyyy-MM-dd");
|
|
}
|
|
else
|
|
{
|
|
dayCell.Value = "";
|
|
dateCell.Value = "";
|
|
}
|
|
|
|
worksheet.Cell(currentRow, dataCol++).Value = FormatTime(r.OfficeFrom);
|
|
worksheet.Cell(currentRow, dataCol++).Value = FormatTime(r.OfficeTo);
|
|
worksheet.Cell(currentRow, dataCol++).Value = r.OfficeBreak;
|
|
|
|
worksheet.Cell(currentRow, dataCol++).Value = FormatTime(r.AfterFrom);
|
|
worksheet.Cell(currentRow, dataCol++).Value = FormatTime(r.AfterTo);
|
|
worksheet.Cell(currentRow, dataCol++).Value = r.AfterBreak;
|
|
|
|
TimeSpan totalOT = CalculateTotalOT(r);
|
|
int totalBreak = (r.OfficeBreak ?? 0) + (r.AfterBreak ?? 0);
|
|
TimeSpan netOT = totalOT - TimeSpan.FromMinutes(totalBreak);
|
|
|
|
var totalOTCell = worksheet.Cell(currentRow, dataCol++);
|
|
totalOTCell.Value = totalOT;
|
|
totalOTCell.Style.NumberFormat.Format = "hh:mm";
|
|
|
|
worksheet.Cell(currentRow, dataCol++).Value = totalBreak;
|
|
|
|
var netOTCell = worksheet.Cell(currentRow, dataCol++);
|
|
netOTCell.Value = netOT;
|
|
netOTCell.Style.NumberFormat.Format = "hh:mm";
|
|
|
|
if (departmentId == 2 || isAdminUser)
|
|
worksheet.Cell(currentRow, dataCol++).Value = r.Stations?.StationName ?? "";
|
|
|
|
worksheet.Cell(currentRow, dataCol++).Value = r.OtDescription ?? "";
|
|
|
|
for (int i = 1; i <= col; i++)
|
|
{
|
|
var cell = worksheet.Cell(currentRow, i);
|
|
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
|
cell.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
|
|
cell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
|
cell.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
|
}
|
|
|
|
currentRow++;
|
|
}
|
|
|
|
if (records.Any())
|
|
{
|
|
int totalRow = currentRow;
|
|
worksheet.Cell(totalRow, 1).Value = "TOTAL";
|
|
worksheet.Cell(totalRow, 1).Style.Font.Bold = true;
|
|
|
|
int colTotalOT = 9; // Column for Total OT
|
|
int colBreakMin = 10; // Column for Break Hours (min)
|
|
int colNetOT = 11; // Column for Net OT
|
|
|
|
var totalOTSumCell = worksheet.Cell(totalRow, colTotalOT);
|
|
totalOTSumCell.FormulaA1 = $"SUM({GetColumnLetter(colTotalOT)}{headerRow1 + 1}:{GetColumnLetter(colTotalOT)}{totalRow - 1})";
|
|
totalOTSumCell.Style.NumberFormat.Format = "hh:mm";
|
|
|
|
var breakMinTotalCell = worksheet.Cell(totalRow, colBreakMin);
|
|
breakMinTotalCell.FormulaA1 = $"SUM({GetColumnLetter(colBreakMin)}{headerRow1 + 1}:{GetColumnLetter(colBreakMin)}{totalRow - 1})/1440";
|
|
breakMinTotalCell.Style.NumberFormat.Format = "hh:mm";
|
|
|
|
var netOTSumCell = worksheet.Cell(totalRow, colNetOT);
|
|
netOTSumCell.FormulaA1 = $"SUM({GetColumnLetter(colNetOT)}{headerRow1 + 1}:{GetColumnLetter(colNetOT)}{totalRow - 1})";
|
|
netOTSumCell.Style.NumberFormat.Format = "hh:mm";
|
|
|
|
for (int i = 1; i <= col; i++)
|
|
{
|
|
var cell = worksheet.Cell(totalRow, i);
|
|
cell.Style.Font.Bold = true;
|
|
cell.Style.Fill.BackgroundColor = XLColor.Yellow;
|
|
cell.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
|
cell.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
|
}
|
|
}
|
|
|
|
// Step 1: Enable wrap text on header range
|
|
headerRange.Style.Alignment.WrapText = true;
|
|
worksheet.Style.Alignment.WrapText = true;
|
|
|
|
// Step 2: Adjust row height based on wrap text
|
|
worksheet.Rows(headerRow1, headerRow2).AdjustToContents();
|
|
|
|
// Step 3 (optional): Manually set column widths to ensure long headers show correctly
|
|
worksheet.Column(1).Width = 8; // Days
|
|
worksheet.Column(2).Width = 13; // Date
|
|
worksheet.Column(3).Width = 12; // Office From
|
|
worksheet.Column(4).Width = 12; // Office To
|
|
worksheet.Column(5).Width = 12; // Office Break
|
|
worksheet.Column(6).Width = 12; // After From
|
|
worksheet.Column(7).Width = 12; // After To
|
|
worksheet.Column(8).Width = 12; // After Break
|
|
worksheet.Column(9).Width = 14; // Total OT Hours
|
|
worksheet.Column(10).Width = 15.5f; // Break Hours
|
|
worksheet.Column(11).Width = 14; // Net OT Hours
|
|
|
|
int colIndex = 12;
|
|
if (departmentId == 2 || isAdminUser)
|
|
{
|
|
worksheet.Column(colIndex++).Width = 20; // Station
|
|
}
|
|
worksheet.Column(colIndex).Width = 35; // Description
|
|
|
|
var stream = new MemoryStream();
|
|
workbook.SaveAs(stream);
|
|
stream.Position = 0;
|
|
return stream;
|
|
}
|
|
|
|
private TimeSpan CalculateTotalOT(OtRegisterModel r)
|
|
{
|
|
TimeSpan office = (r.OfficeTo ?? TimeSpan.Zero) - (r.OfficeFrom ?? TimeSpan.Zero);
|
|
TimeSpan after = (r.AfterTo ?? TimeSpan.Zero) - (r.AfterFrom ?? TimeSpan.Zero);
|
|
if (after < TimeSpan.Zero)
|
|
after += TimeSpan.FromHours(24);
|
|
return office + after;
|
|
}
|
|
|
|
private string FormatTime(TimeSpan? time)
|
|
{
|
|
return time == null || time == TimeSpan.Zero ? "" : time.Value.ToString(@"hh\:mm");
|
|
}
|
|
|
|
private string GetColumnLetter(int columnNumber)
|
|
{
|
|
string columnString = "";
|
|
while (columnNumber > 0)
|
|
{
|
|
int currentLetterNumber = (columnNumber - 1) % 26;
|
|
char currentLetter = (char)(currentLetterNumber + 65);
|
|
columnString = currentLetter + columnString;
|
|
columnNumber = (columnNumber - 1) / 26;
|
|
}
|
|
return columnString;
|
|
}
|
|
}
|
|
}
|