[CLAUDE] Office: P11-E AttendanceReport+Excel+OtPolicy + P11-F MaTicket codegen (Wave 1)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m10s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m10s
P11-F: MaTicket gen-on-create qua WorkflowAppCodeGen (IT/2026/NNN Serializable atomic, kanban no-workflow). P11-E: GetAttendanceReportQuery monthly aggregate (day-type weekday/weekend/holiday OT x OtPolicy multiplier in-memory) + AttendanceReportExcelExporter (ClosedXML) + 2 endpoint Admin-only + fe-admin AttendanceReportPage. Migration-free. +5 test (186->191). reviewer PASS (gotcha #44 role-string verified, 0 blocker). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -38,6 +38,7 @@ public static class DependencyInjection
|
||||
services.AddScoped<IPurchaseEvaluationCodeGenerator, PurchaseEvaluationCodeGenerator>();
|
||||
services.AddScoped<IEmployeeCodeGenerator, EmployeeCodeGenerator>();
|
||||
services.AddScoped<IContractExcelExporter, ContractExcelExporter>();
|
||||
services.AddScoped<IAttendanceReportExcelExporter, AttendanceReportExcelExporter>();
|
||||
services.AddScoped<INotificationService, NotificationService>();
|
||||
services.AddScoped<IChangelogService, ChangelogService>();
|
||||
services.AddSingleton<IFileStorage, LocalFileStorage>();
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
using ClosedXML.Excel;
|
||||
using SolutionErp.Application.Forms.Services;
|
||||
using SolutionErp.Application.Office;
|
||||
using SolutionErp.Application.Reports.Services;
|
||||
|
||||
namespace SolutionErp.Infrastructure.Reports;
|
||||
|
||||
// Phase 11 P11-E — mirror ContractExcelExporter (ClosedXML XLWorkbook → MemoryStream → RenderResult).
|
||||
// Input = AttendanceReportDto (controller query rồi pass — KHÔNG đụng DB).
|
||||
public class AttendanceReportExcelExporter : IAttendanceReportExcelExporter
|
||||
{
|
||||
public RenderResult Export(AttendanceReportDto report)
|
||||
{
|
||||
using var wb = new XLWorkbook();
|
||||
var ws = wb.Worksheets.Add("ChamCong");
|
||||
|
||||
// Title row
|
||||
var headers = new[]
|
||||
{
|
||||
"STT", "Họ tên", "Phòng ban", "Ngày công", "Tổng giờ làm",
|
||||
"OT thường", "OT cuối tuần", "OT lễ", "OT quy đổi"
|
||||
};
|
||||
var titleRange = ws.Range(1, 1, 1, headers.Length);
|
||||
titleRange.Merge();
|
||||
titleRange.Value = $"BÁO CÁO CHẤM CÔNG THÁNG {report.Month}/{report.Year}";
|
||||
titleRange.Style.Font.Bold = true;
|
||||
titleRange.Style.Font.FontSize = 14;
|
||||
titleRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
|
||||
// Header row
|
||||
const int headerRowIdx = 2;
|
||||
for (int i = 0; i < headers.Length; i++)
|
||||
ws.Cell(headerRowIdx, i + 1).Value = headers[i];
|
||||
|
||||
var headerRange = ws.Range(headerRowIdx, 1, headerRowIdx, headers.Length);
|
||||
headerRange.Style.Font.Bold = true;
|
||||
headerRange.Style.Fill.BackgroundColor = XLColor.LightBlue;
|
||||
headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
||||
|
||||
// Data rows
|
||||
const int firstDataRow = headerRowIdx + 1;
|
||||
for (int i = 0; i < report.Rows.Count; i++)
|
||||
{
|
||||
var r = report.Rows[i];
|
||||
var rowIdx = firstDataRow + i;
|
||||
ws.Cell(rowIdx, 1).Value = i + 1;
|
||||
ws.Cell(rowIdx, 2).Value = r.FullName;
|
||||
ws.Cell(rowIdx, 3).Value = r.DepartmentName ?? "—";
|
||||
ws.Cell(rowIdx, 4).Value = r.DaysPresent;
|
||||
ws.Cell(rowIdx, 5).Value = r.TotalWorkHours;
|
||||
ws.Cell(rowIdx, 5).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(rowIdx, 6).Value = r.OtWeekday;
|
||||
ws.Cell(rowIdx, 6).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(rowIdx, 7).Value = r.OtWeekend;
|
||||
ws.Cell(rowIdx, 7).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(rowIdx, 8).Value = r.OtHoliday;
|
||||
ws.Cell(rowIdx, 8).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(rowIdx, 9).Value = r.OtWeighted;
|
||||
ws.Cell(rowIdx, 9).Style.NumberFormat.Format = "#,##0.0#";
|
||||
}
|
||||
|
||||
// Footer total row
|
||||
if (report.Rows.Count > 0)
|
||||
{
|
||||
var sumRow = firstDataRow + report.Rows.Count;
|
||||
ws.Cell(sumRow, 4).Value = "TỔNG:";
|
||||
ws.Cell(sumRow, 4).Style.Font.Bold = true;
|
||||
ws.Cell(sumRow, 4).Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Right;
|
||||
ws.Cell(sumRow, 5).Value = report.GrandTotalWorkHours;
|
||||
ws.Cell(sumRow, 5).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(sumRow, 5).Style.Font.Bold = true;
|
||||
ws.Cell(sumRow, 9).Value = report.GrandTotalOtWeighted;
|
||||
ws.Cell(sumRow, 9).Style.NumberFormat.Format = "#,##0.0#";
|
||||
ws.Cell(sumRow, 9).Style.Font.Bold = true;
|
||||
}
|
||||
|
||||
ws.Columns().AdjustToContents();
|
||||
ws.SheetView.FreezeRows(headerRowIdx);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
wb.SaveAs(ms);
|
||||
return new RenderResult(
|
||||
ms.ToArray(),
|
||||
$"BaoCao-ChamCong-{report.Year}-{report.Month:D2}.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user