|
- using CouponReport.Models;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using ClosedXML.Excel;
- using iText.Kernel.Pdf;
- using iText.Layout;
- using iText.Layout.Element;
- using iText.Layout.Properties;
- using iText.Kernel.Font;
- using iText.IO.Font;
- using iText.IO.Font.Constants;
- using CouponReport.Models.CouponMiddleware;
- using CouponReport.Models.Parkingeyes;
- using CouponReport.Service;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Authentication.Cookies;
-
- namespace CouponReport.Controllers;
-
- [Authorize]
- public class ReportController : Controller
- {
- private readonly CouponMiddlewareContext _couponContext;
- private readonly ParkingEyesContext _parkingEyesContext;
- private readonly ReportService _reportService;
-
- public ReportController(CouponMiddlewareContext context, ParkingEyesContext parkingEyesContext, ReportService reportService)
- {
- _couponContext = context;
- _parkingEyesContext = parkingEyesContext;
- _reportService = reportService;
- }
-
- public async Task<IActionResult> Index(DateTime? startDate, DateTime? endDate)
- {
- // 如果沒有提供日期,預設為當月第一天到最後一天
- if (!startDate.HasValue && !endDate.HasValue)
- {
- var today = DateTime.Today;
- startDate = new DateTime(today.Year, today.Month, 1);
- endDate = startDate.Value.AddMonths(1).AddDays(-1);
- }
-
- return await LoadReportData(startDate, endDate);
- }
-
- [HttpPost]
- public async Task<IActionResult> Index(DateTime? startDate, DateTime? endDate, string action)
- {
- return await LoadReportData(startDate, endDate);
- }
-
- private async Task<IActionResult> LoadReportData(DateTime? startDate, DateTime? endDate)
- {
- var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
- var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
- var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
-
- if (startDate.HasValue)
- {
- query = query.Where(x => x.LogTime >= startDate.Value);
- }
-
- if (endDate.HasValue)
- {
- query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
- }
-
- var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
-
- // 建立 SerialNo 到編號的映射
- var serialNoToRowNumber = new Dictionary<string, int>();
- int currentRowNumber = 1;
-
- // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
- var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
- .ToDictionary(g => g.Key, g => g.FirstOrDefault());
-
- var viewModel = new CouponReportViewModel
- {
- StartDate = startDate,
- EndDate = endDate,
- ReportItems = logs.Select(log =>
- {
- // 如果這個 SerialNo 還沒出現過,給它一個新編號
- if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
- {
- serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
- }
-
- // 從 CarEnter 取得出場時間
- DateTime? exitTime = null;
- var tenantCode = string.Empty;
- DateTime? invoiceDate = null;
- var invoiceNo = string.Empty;
- var invoiceAmount = 0m;
-
- if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
- {
- exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
- }
-
- invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
- invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
- invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
- tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
-
- return new CouponReportItem
- {
- RowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0,
- TenantCode = tenantCode,
- CarNumber = log.PlateNo,
- InvoiceDate = invoiceDate,
- InvoiceNumber = invoiceNo,
- InvoiceAmount = invoiceAmount,
- DiscountUnit = "新台幣",
- DiscountAmount = log.DiscountAmount,
- DiscountTime = log.LogTime,
- EnterTime = log.EnterTime,
- ExitTime = exitTime,
- ParkingAmount = log.TotalAmount,
- ClaimAmount = log.DiscountAmount
- };
- }).ToList()
- };
-
- return View(viewModel);
- }
-
- public async Task<IActionResult> ExportToExcel(DateTime? startDate, DateTime? endDate)
- {
- var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
- var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
- var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
-
- if (startDate.HasValue)
- {
- query = query.Where(x => x.LogTime >= startDate.Value);
- }
-
- if (endDate.HasValue)
- {
- query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
- }
-
- var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
-
- // 建立 SerialNo 到編號的映射
- var serialNoToRowNumber = new Dictionary<string, int>();
- int currentRowNumber = 1;
-
- // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
- var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
- .ToDictionary(g => g.Key, g => g.FirstOrDefault());
-
- // 建立報表資料列表
- var reportItems = new List<(int rowNumber, string tenantCode, string carNumber, DateTime? invoiceDate,
- string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount,
- DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
- decimal? parkingAmount, decimal? claimAmount)>();
-
- foreach (var log in logs)
- {
- if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
- {
- serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
- }
-
- DateTime? exitTime = null;
- if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
- {
- exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
- }
-
- var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
- var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
- var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
- var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
-
- int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
-
- reportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
- "新台幣", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
- log.TotalAmount, log.DiscountAmount));
- }
-
- using var workbook = new XLWorkbook();
- var worksheet = workbook.Worksheets.Add("折扣報表");
-
- // 標題列
- worksheet.Cell(1, 1).Value = "編號";
- worksheet.Cell(1, 2).Value = "店別(統編)";
- worksheet.Cell(1, 3).Value = "車號";
- worksheet.Cell(1, 4).Value = "發票日期";
- worksheet.Cell(1, 5).Value = "發票號碼";
- worksheet.Cell(1, 6).Value = "發票金額";
- worksheet.Cell(1, 7).Value = "折扣單位";
- worksheet.Cell(1, 8).Value = "折扣金額";
- worksheet.Cell(1, 9).Value = "折扣時間";
- worksheet.Cell(1, 10).Value = "入場時間";
- worksheet.Cell(1, 11).Value = "出場時間";
- worksheet.Cell(1, 12).Value = "停車金額";
- worksheet.Cell(1, 13).Value = "請款金額";
-
- // 設定標題列樣式
- var headerRange = worksheet.Range(1, 1, 1, 13);
- headerRange.Style.Font.Bold = true;
- headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
- headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
- headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
-
- // 填入資料並記錄合併範圍
- int row = 2;
- var mergedCells = new Dictionary<int, (int startRow, int endRow)>();
-
- foreach (var item in reportItems)
- {
- // 填入資料
- worksheet.Cell(row, 1).Value = item.rowNumber;
- worksheet.Cell(row, 2).Value = item.tenantCode;
- worksheet.Cell(row, 3).Value = item.carNumber;
- worksheet.Cell(row, 4).Value = item.invoiceDate?.ToString("yyyy-MM-dd HH:mm:ss");
- worksheet.Cell(row, 5).Value = item.invoiceNo;
- worksheet.Cell(row, 6).Value = item.invoiceAmount;
- worksheet.Cell(row, 7).Value = item.discountUnit;
- worksheet.Cell(row, 8).Value = item.discountAmount;
- worksheet.Cell(row, 9).Value = item.discountTime.ToString("yyyy-MM-dd HH:mm:ss");
- worksheet.Cell(row, 10).Value = item.enterTime?.ToString("yyyy-MM-dd HH:mm:ss");
- worksheet.Cell(row, 11).Value = item.exitTime?.ToString("yyyy-MM-dd HH:mm:ss");
- worksheet.Cell(row, 12).Value = item.parkingAmount;
- worksheet.Cell(row, 13).Value = item.claimAmount;
-
- // 記錄需要合併的儲存格範圍(按 rowNumber 分組)
- if (item.rowNumber > 0)
- {
- if (!mergedCells.ContainsKey(item.rowNumber))
- {
- mergedCells[item.rowNumber] = (row, row);
- }
- else
- {
- mergedCells[item.rowNumber] = (mergedCells[item.rowNumber].startRow, row);
- }
- }
-
- row++;
- }
-
- // 執行合併儲存格
- foreach (var (rowNumber, (startRow, endRow)) in mergedCells)
- {
- if (startRow < endRow)
- {
- // 合併:編號(1)、車號(3)、折扣單位(7)、折扣金額(8)、折扣時間(9)、入場時間(10)、出場時間(11)、停車金額(12)、請款金額(13)
- int[] mergeCols = { 1, 3, 7, 8, 9, 10, 11, 12, 13 };
- foreach (int col in mergeCols)
- {
- var range = worksheet.Range(startRow, col, endRow, col);
- range.Merge();
- range.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
- range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
- }
- }
- }
-
- // 自動調整欄寬,並設定最小寬度以顯示完整表頭
- worksheet.Columns().AdjustToContents();
- for (int col = 1; col <= 13; col++)
- {
- if (worksheet.Column(col).Width < 12)
- {
- worksheet.Column(col).Width = 12;
- }
- }
-
- // 將所有資料儲存格置中
- var dataRange = worksheet.Range(2, 1, row - 1, 13);
- dataRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
- dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
-
- var stream = new MemoryStream();
- workbook.SaveAs(stream);
- stream.Position = 0;
-
- var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
- return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
- }
-
- public async Task<IActionResult> ExportToPdf(DateTime? startDate, DateTime? endDate)
- {
- var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
- var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
- var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
-
- if (startDate.HasValue)
- {
- query = query.Where(x => x.LogTime >= startDate.Value);
- }
-
- if (endDate.HasValue)
- {
- query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
- }
-
- var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
-
- // 建立 SerialNo 到編號的映射
- var serialNoToRowNumber = new Dictionary<string, int>();
- int currentRowNumber = 1;
-
- // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
- var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
- .ToDictionary(g => g.Key, g => g.FirstOrDefault());
-
- // 建立報表資料並分組
- var reportGroups = new Dictionary<int, List<(string tenantCode, DateTime? invoiceDate, string invoiceNo, decimal invoiceAmount)>>();
- var groupData = new Dictionary<int, (int rowNumber, string carNumber, string discountUnit, decimal? discountAmount,
- DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
- decimal? parkingAmount, decimal? claimAmount)>();
-
- foreach (var log in logs)
- {
- if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
- {
- serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
- }
-
- DateTime? exitTime = null;
- if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
- {
- exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
- }
-
- var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
- var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
- var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
- var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
-
- int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
-
- // 記錄群組資料
- if (!groupData.ContainsKey(rowNumber))
- {
- groupData[rowNumber] = (rowNumber, log.PlateNo, "新台幣", log.DiscountAmount, log.LogTime,
- log.EnterTime, exitTime, log.TotalAmount, log.DiscountAmount);
- reportGroups[rowNumber] = new List<(string, DateTime?, string, decimal)>();
- }
-
- reportGroups[rowNumber].Add((tenantCode, invoiceDate, invoiceNo, invoiceAmount));
- }
-
- var stream = new MemoryStream();
- var writer = new PdfWriter(stream);
- writer.SetCloseStream(false); // 防止關閉 MemoryStream
- var pdf = new PdfDocument(writer);
- var document = new Document(pdf, iText.Kernel.Geom.PageSize.A4.Rotate());
-
- // 設定中文字型
- var fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "kaiu.ttf");
- if (!System.IO.File.Exists(fontPath))
- {
- fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "msjh.ttc,0");
- }
-
- PdfFont font;
- try
- {
- font = PdfFontFactory.CreateFont(fontPath, PdfEncodings.IDENTITY_H);
- }
- catch
- {
- font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
- }
-
- document.SetFont(font);
-
- // 標題
- var title = new Paragraph("折扣報表")
- .SetTextAlignment(TextAlignment.CENTER)
- .SetFontSize(18);
- document.Add(title);
-
- // 建立表格
- var table = new Table(13).UseAllAvailableWidth();
- table.SetFontSize(8);
-
- // 標題列
- table.AddHeaderCell(new Cell().Add(new Paragraph("編號")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("店別")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("車號")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("發票日期")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("發票號碼")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("發票金額")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("折扣單位")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("折扣金額")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("折扣時間")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("入場時間")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("出場時間")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("停車金額")).SetTextAlignment(TextAlignment.CENTER));
- table.AddHeaderCell(new Cell().Add(new Paragraph("請款金額")).SetTextAlignment(TextAlignment.CENTER));
-
- // 資料列(使用合併儲存格)
- foreach (var (rowNum, group) in groupData.OrderBy(x => x.Key))
- {
- var invoices = reportGroups[rowNum];
- int rowSpan = invoices.Count;
-
- for (int i = 0; i < invoices.Count; i++)
- {
- var invoice = invoices[i];
- bool isFirstRow = i == 0;
-
- if (isFirstRow)
- {
- // 第一行:顯示合併的欄位 - 編號
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.rowNumber.ToString())).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- }
-
- // 每一行:店別
- table.AddCell(new Cell().Add(new Paragraph(invoice.tenantCode ?? "")).SetTextAlignment(TextAlignment.CENTER));
-
- if (isFirstRow)
- {
- // 車號
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.carNumber ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- }
-
- // 每一行:發票日期、發票號碼、發票金額
- table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceDate?.ToString("yyyy-MM-dd") ?? "")).SetTextAlignment(TextAlignment.CENTER));
- table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceNo ?? "")).SetTextAlignment(TextAlignment.CENTER));
- table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceAmount.ToString("N0"))).SetTextAlignment(TextAlignment.CENTER));
-
- if (isFirstRow)
- {
- // 合併欄位:折扣單位、折扣金額、折扣時間、入場時間、出場時間、停車金額、請款金額
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountUnit ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountTime.ToString("yyyy-MM-dd HH:mm"))).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.enterTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.exitTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.parkingAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.claimAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
- }
- }
- }
-
- document.Add(table);
- document.Close();
-
- stream.Position = 0;
- var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.pdf";
- return File(stream, "application/pdf", fileName);
- }
- }
|