PSTW_CentralizeSystem/Areas/OTcalculate/Services/OvertimeExcelService.cs
2025-05-13 17:13:30 +08:00

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;
}
}
}