MSI\User 1 месяц назад
Родитель
Сommit
aa9d3b3329
5 измененных файлов: 183 добавлений и 90 удалений
  1. +6
    -0
      Controllers/HomeController.cs
  2. +144
    -77
      Controllers/ReportController.cs
  3. +3
    -1
      Program.cs
  4. +18
    -11
      Views/Report/Index.cshtml
  5. +12
    -1
      Views/Shared/_Layout.cshtml

+ 6
- 0
Controllers/HomeController.cs Просмотреть файл

@@ -27,6 +27,12 @@ public class HomeController : Controller
} }
public IActionResult Index() public IActionResult Index()
{ {
//如果已經登入,直接導向報表頁面
if (User.Identity?.IsAuthenticated == true)
{
return RedirectToAction("Index", "Report");
}

ViewBag.OauthUrl = _oauthOption.Value.ParkingOAuthUrl; ViewBag.OauthUrl = _oauthOption.Value.ParkingOAuthUrl;
return View(new LoginViewModel()); return View(new LoginViewModel());
} }


+ 144
- 77
Controllers/ReportController.cs Просмотреть файл

@@ -52,11 +52,11 @@ public class ReportController : Controller


private async Task<IActionResult> LoadReportData(DateTime? startDate, DateTime? 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();
// 建立包含日期篩選的查詢
var query = _couponContext.Logs.AsQueryable()
.Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));


if (startDate.HasValue)
if (startDate.HasValue)
{ {
query = query.Where(x => x.LogTime >= startDate.Value); query = query.Where(x => x.LogTime >= startDate.Value);
} }
@@ -66,21 +66,30 @@ public class ReportController : Controller
query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1)); query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
} }


var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();

var evaluateLogsQuery = await _couponContext.Logs
.Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
.ToListAsync();

// 建立 SerialNo 到 Evaluate Log 的映射
var evaluateLogDict = evaluateLogsQuery
.GroupBy(x => x.SerialNo)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());

//var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
//var carEnter = _mySqlContext.Cario.Where(x => serialNo.Contains(x.SerialNo)).ToList();


// 建立 SerialNo 到編號的映射 // 建立 SerialNo 到編號的映射
var serialNoToRowNumber = new Dictionary<string, int>(); var serialNoToRowNumber = new Dictionary<string, int>();
int currentRowNumber = 1; int currentRowNumber = 1;


// 建立 SerialNo 到 CarEnter 的映射,方便快速查找 // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());
//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 =>
var reportItems = consumeLogs.Select(log =>
{ {
// 如果這個 SerialNo 還沒出現過,給它一個新編號 // 如果這個 SerialNo 還沒出現過,給它一個新編號
if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo)) if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
@@ -88,17 +97,22 @@ public class ReportController : Controller
serialNoToRowNumber[log.SerialNo] = currentRowNumber++; serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
} }


// 從 CarEnter 取得出場時間
// 從 evaluateLogDict 取得出場時間
DateTime? exitTime = null; DateTime? exitTime = null;
if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
{
exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
}

var tenantCode = string.Empty; var tenantCode = string.Empty;
DateTime? invoiceDate = null; DateTime? invoiceDate = null;
var invoiceNo = string.Empty; var invoiceNo = string.Empty;
var invoiceAmount = 0m; var invoiceAmount = 0m;


if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
{
exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
}
//if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
// {
// exitTime = carEnterDict[log.SerialNo]?.OutTime;
// }


invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey); invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey); invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
@@ -113,7 +127,7 @@ public class ReportController : Controller
InvoiceDate = invoiceDate, InvoiceDate = invoiceDate,
InvoiceNumber = invoiceNo, InvoiceNumber = invoiceNo,
InvoiceAmount = invoiceAmount, InvoiceAmount = invoiceAmount,
DiscountUnit = "新台幣",
DiscountUnit = "金額",
DiscountAmount = log.DiscountAmount, DiscountAmount = log.DiscountAmount,
DiscountTime = log.LogTime, DiscountTime = log.LogTime,
EnterTime = log.EnterTime, EnterTime = log.EnterTime,
@@ -121,7 +135,13 @@ public class ReportController : Controller
ParkingAmount = log.TotalAmount, ParkingAmount = log.TotalAmount,
ClaimAmount = log.DiscountAmount ClaimAmount = log.DiscountAmount
}; };
}).ToList()
}).OrderBy(x => x.RowNumber).ThenBy(x => x.DiscountTime).ToList();

var viewModel = new CouponReportViewModel
{
StartDate = startDate,
EndDate = endDate,
ReportItems = reportItems
}; };


return View(viewModel); return View(viewModel);
@@ -129,9 +149,8 @@ public class ReportController : Controller


public async Task<IActionResult> ExportToExcel(DateTime? startDate, DateTime? endDate) 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();
var query = _couponContext.Logs.AsQueryable()
.Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));


if (startDate.HasValue) if (startDate.HasValue)
{ {
@@ -143,23 +162,29 @@ public class ReportController : Controller
query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1)); query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
} }


var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();

var evaluateLogsQuery = await _couponContext.Logs
.Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
.ToListAsync();

// 建立 SerialNo 到 Evaluate Log 的映射
var evaluateLogDict = evaluateLogsQuery
.GroupBy(x => x.SerialNo)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());


// 建立 SerialNo 到編號的映射 // 建立 SerialNo 到編號的映射
var serialNoToRowNumber = new Dictionary<string, int>(); var serialNoToRowNumber = new Dictionary<string, int>();
int currentRowNumber = 1; 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, var reportItems = new List<(int rowNumber, string tenantCode, string carNumber, DateTime? invoiceDate,
string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount, string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount,
DateTime discountTime, DateTime? enterTime, DateTime? exitTime, DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
decimal? parkingAmount, decimal? claimAmount)>(); decimal? parkingAmount, decimal? claimAmount)>();


foreach (var log in logs)
foreach (var log in consumeLogs)
{ {
if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo)) if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
{ {
@@ -167,9 +192,9 @@ public class ReportController : Controller
} }


DateTime? exitTime = null; DateTime? exitTime = null;
if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
{ {
exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
} }


var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey); var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
@@ -180,37 +205,47 @@ public class ReportController : Controller
int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0; int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;


reportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount, reportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
"新台幣", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
"金額", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
log.TotalAmount, log.DiscountAmount)); log.TotalAmount, log.DiscountAmount));
} }


// 按 RowNumber 排序,確保相同編號的資料連續,避免合併儲存格錯誤
reportItems = reportItems.OrderBy(x => x.rowNumber).ThenBy(x => x.discountTime).ToList();

using var workbook = new XLWorkbook(); using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("折扣報表"); var worksheet = workbook.Worksheets.Add("折扣報表");


// 匯出資訊(合併儲存格以避免影響欄寬)
worksheet.Range(1, 1, 1, 13).Merge();
worksheet.Cell(1, 1).Value = $"匯出時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}";

worksheet.Range(2, 1, 2, 13).Merge();
worksheet.Cell(2, 1).Value = $"篩選條件:{startDate?.ToString("yyyy-MM-dd")} ~ {endDate?.ToString("yyyy-MM-dd")}";

// 標題列 // 標題列
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 = "請款金額";
worksheet.Cell(4, 1).Value = "編號";
worksheet.Cell(4, 2).Value = "店別(統編)";
worksheet.Cell(4, 3).Value = "車號";
worksheet.Cell(4, 4).Value = "發票日期";
worksheet.Cell(4, 5).Value = "發票號碼";
worksheet.Cell(4, 6).Value = "發票金額";
worksheet.Cell(4, 7).Value = "折扣單位";
worksheet.Cell(4, 8).Value = "折扣金額";
worksheet.Cell(4, 9).Value = "折扣時間";
worksheet.Cell(4, 10).Value = "入場時間";
worksheet.Cell(4, 11).Value = "出場時間";
worksheet.Cell(4, 12).Value = "停車金額";
worksheet.Cell(4, 13).Value = "請款金額";


// 設定標題列樣式 // 設定標題列樣式
var headerRange = worksheet.Range(1, 1, 1, 13);
var headerRange = worksheet.Range(4, 1, 4, 13);
headerRange.Style.Font.Bold = true; headerRange.Style.Font.Bold = true;
headerRange.Style.Fill.BackgroundColor = XLColor.LightGray; headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;


// 填入資料並記錄合併範圍 // 填入資料並記錄合併範圍
int row = 2;
int row = 5;
var mergedCells = new Dictionary<int, (int startRow, int endRow)>(); var mergedCells = new Dictionary<int, (int startRow, int endRow)>();


foreach (var item in reportItems) foreach (var item in reportItems)
@@ -219,16 +254,16 @@ public class ReportController : Controller
worksheet.Cell(row, 1).Value = item.rowNumber; worksheet.Cell(row, 1).Value = item.rowNumber;
worksheet.Cell(row, 2).Value = item.tenantCode; worksheet.Cell(row, 2).Value = item.tenantCode;
worksheet.Cell(row, 3).Value = item.carNumber; worksheet.Cell(row, 3).Value = item.carNumber;
worksheet.Cell(row, 4).Value = item.invoiceDate?.ToString("yyyy-MM-dd HH:mm:ss");
worksheet.Cell(row, 4).Value = item.invoiceDate?.ToString("yyyy-MM-dd");
worksheet.Cell(row, 5).Value = item.invoiceNo; worksheet.Cell(row, 5).Value = item.invoiceNo;
worksheet.Cell(row, 6).Value = item.invoiceAmount;
worksheet.Cell(row, 6).Value = $"NT$ {item.invoiceAmount:N0}";
worksheet.Cell(row, 7).Value = item.discountUnit; worksheet.Cell(row, 7).Value = item.discountUnit;
worksheet.Cell(row, 8).Value = item.discountAmount;
worksheet.Cell(row, 8).Value = $"NT$ {item.discountAmount:N0}";
worksheet.Cell(row, 9).Value = item.discountTime.ToString("yyyy-MM-dd HH:mm:ss"); 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, 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, 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;
worksheet.Cell(row, 12).Value = $"NT$ {item.parkingAmount:N0}";
worksheet.Cell(row, 13).Value = $"NT$ {item.claimAmount:N0}";


// 記錄需要合併的儲存格範圍(按 rowNumber 分組) // 記錄需要合併的儲存格範圍(按 rowNumber 分組)
if (item.rowNumber > 0) if (item.rowNumber > 0)
@@ -274,7 +309,7 @@ public class ReportController : Controller
} }


// 將所有資料儲存格置中 // 將所有資料儲存格置中
var dataRange = worksheet.Range(2, 1, row - 1, 13);
var dataRange = worksheet.Range(5, 1, row - 1, 13);
dataRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center; dataRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center; dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;


@@ -288,9 +323,8 @@ public class ReportController : Controller


public async Task<IActionResult> ExportToPdf(DateTime? startDate, DateTime? endDate) 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();
var query = _couponContext.Logs.AsQueryable()
.Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));


if (startDate.HasValue) if (startDate.HasValue)
{ {
@@ -302,23 +336,29 @@ public class ReportController : Controller
query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1)); query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
} }


var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();

var evaluateLogsQuery = await _couponContext.Logs
.Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
.ToListAsync();

// 建立 SerialNo 到 Evaluate Log 的映射
var evaluateLogDict = evaluateLogsQuery
.GroupBy(x => x.SerialNo)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());


// 建立 SerialNo 到編號的映射 // 建立 SerialNo 到編號的映射
var serialNoToRowNumber = new Dictionary<string, int>(); var serialNoToRowNumber = new Dictionary<string, int>();
int currentRowNumber = 1; int currentRowNumber = 1;


// 建立 SerialNo 到 CarEnter 的映射,方便快速查找
var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());
// 建立臨時資料列表以便排序
var tempReportItems = 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)>();


// 建立報表資料並分組
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)
foreach (var log in consumeLogs)
{ {
if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo)) if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
{ {
@@ -326,9 +366,9 @@ public class ReportController : Controller
} }


DateTime? exitTime = null; DateTime? exitTime = null;
if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
{ {
exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
} }


var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey); var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
@@ -338,15 +378,31 @@ public class ReportController : Controller


int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0; int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;


tempReportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
"金額", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
log.TotalAmount, log.DiscountAmount));
}

// 按 RowNumber 排序,確保相同編號的資料連續,避免合併儲存格錯誤
tempReportItems = tempReportItems.OrderBy(x => x.rowNumber).ThenBy(x => x.discountTime).ToList();

// 建立報表資料並分組
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 item in tempReportItems)
{
// 記錄群組資料 // 記錄群組資料
if (!groupData.ContainsKey(rowNumber))
if (!groupData.ContainsKey(item.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)>();
groupData[item.rowNumber] = (item.rowNumber, item.carNumber, item.discountUnit, item.discountAmount, item.discountTime,
item.enterTime, item.exitTime, item.parkingAmount, item.claimAmount);
reportGroups[item.rowNumber] = new List<(string, DateTime?, string, decimal)>();
} }


reportGroups[rowNumber].Add((tenantCode, invoiceDate, invoiceNo, invoiceAmount));
reportGroups[item.rowNumber].Add((item.tenantCode, item.invoiceDate, item.invoiceNo, item.invoiceAmount));
} }


var stream = new MemoryStream(); var stream = new MemoryStream();
@@ -380,6 +436,17 @@ public class ReportController : Controller
.SetFontSize(18); .SetFontSize(18);
document.Add(title); document.Add(title);


// 匯出資訊
var exportInfo = new Paragraph($"匯出時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}")
.SetFontSize(10)
.SetMarginTop(10);
document.Add(exportInfo);

var filterInfo = new Paragraph($"篩選條件:{startDate?.ToString("yyyy-MM-dd")} ~ {endDate?.ToString("yyyy-MM-dd")}")
.SetFontSize(10)
.SetMarginBottom(10);
document.Add(filterInfo);

// 建立表格 // 建立表格
var table = new Table(13).UseAllAvailableWidth(); var table = new Table(13).UseAllAvailableWidth();
table.SetFontSize(8); table.SetFontSize(8);
@@ -428,18 +495,18 @@ public class ReportController : Controller
// 每一行:發票日期、發票號碼、發票金額 // 每一行:發票日期、發票號碼、發票金額
table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceDate?.ToString("yyyy-MM-dd") ?? "")).SetTextAlignment(TextAlignment.CENTER)); 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.invoiceNo ?? "")).SetTextAlignment(TextAlignment.CENTER));
table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceAmount.ToString("N0"))).SetTextAlignment(TextAlignment.CENTER));
table.AddCell(new Cell().Add(new Paragraph($"NT$ {invoice.invoiceAmount:N0}")).SetTextAlignment(TextAlignment.CENTER));


if (isFirstRow) 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.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($"NT$ {group.discountAmount: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.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.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.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));
table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph($"NT$ {group.parkingAmount:N0}")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph($"NT$ {group.claimAmount:N0}")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
} }
} }
} }


+ 3
- 1
Program.cs Просмотреть файл

@@ -24,11 +24,12 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
{ {
options.LoginPath = "/Home/Index"; options.LoginPath = "/Home/Index";
options.AccessDeniedPath = "/Home/Index"; options.AccessDeniedPath = "/Home/Index";
options.Cookie.Name = "CouponReport.Auth"; // 設定 Cookie 名稱
options.Cookie.HttpOnly = true; options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true; options.Cookie.IsEssential = true;
options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SameSite = SameSiteMode.Strict;
options.SlidingExpiration = true; options.SlidingExpiration = true;
options.Cookie.Expiration = null;
options.ExpireTimeSpan = TimeSpan.FromMinutes(15); // 設定15分鐘後過期
}); });


// Add services to the container. // Add services to the container.
@@ -37,6 +38,7 @@ builder.Services.AddSession(options =>
{ {
options.Cookie.HttpOnly = true; options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true; options.Cookie.IsEssential = true;
options.IdleTimeout = TimeSpan.FromMinutes(15); // Session 15分鐘後過期
}); });


var app = builder.Build(); var app = builder.Build();


+ 18
- 11
Views/Report/Index.cshtml Просмотреть файл

@@ -56,6 +56,13 @@
.group-row-odd { .group-row-odd {
background-color: #ffffff; background-color: #ffffff;
} }
.table th, .table td {
text-align: center;
vertical-align: middle;
}
.text-right {
text-align: right !important;
}
</style> </style>


<div class="table-responsive"> <div class="table-responsive">
@@ -100,25 +107,25 @@
<tr class="@rowClass"> <tr class="@rowClass">
@if (isNewGroup) @if (isNewGroup)
{ {
<td rowspan="@rowSpanCount" class="align-middle">@item.RowNumber</td>
<td rowspan="@rowSpanCount">@item.RowNumber</td>
} }
<td>@item.TenantCode</td> <td>@item.TenantCode</td>
@if (isNewGroup) @if (isNewGroup)
{ {
<td rowspan="@rowSpanCount" class="align-middle">@item.CarNumber</td>
<td rowspan="@rowSpanCount">@item.CarNumber</td>
} }
<td>@item.InvoiceDate?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td>@item.InvoiceDate?.ToString("yyyy-MM-dd")</td>
<td>@item.InvoiceNumber</td> <td>@item.InvoiceNumber</td>
<td class="text-end">@item.InvoiceAmount?.ToString("N0")</td>
<td class="text-right">NT$ @item.InvoiceAmount?.ToString("N0")</td>
@if (isNewGroup) @if (isNewGroup)
{ {
<td rowspan="@rowSpanCount" class="align-middle">@item.DiscountUnit</td>
<td rowspan="@rowSpanCount" class="align-middle text-end">@item.DiscountAmount?.ToString("N0")</td>
<td rowspan="@rowSpanCount" class="align-middle">@item.DiscountTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount" class="align-middle">@item.EnterTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount" class="align-middle">@item.ExitTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount" class="align-middle text-end">@item.ParkingAmount?.ToString("N0")</td>
<td rowspan="@rowSpanCount" class="align-middle text-end">@item.ClaimAmount?.ToString("N0")</td>
<td rowspan="@rowSpanCount">@item.DiscountUnit</td>
<td rowspan="@rowSpanCount" class="text-right">NT$ @item.DiscountAmount?.ToString("N0")</td>
<td rowspan="@rowSpanCount">@item.DiscountTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount">@item.EnterTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount">@item.ExitTime?.ToString("yyyy-MM-dd HH:mm:ss")</td>
<td rowspan="@rowSpanCount" class="text-right">NT$ @item.ParkingAmount?.ToString("N0")</td>
<td rowspan="@rowSpanCount" class="text-right">NT$ @item.ClaimAmount?.ToString("N0")</td>
} }
</tr> </tr>
} }


+ 12
- 1
Views/Shared/_Layout.cshtml Просмотреть файл

@@ -12,7 +12,18 @@
<header> <header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Report" asp-action="Index">折扣報表系統</a>
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">首頁</a>
@if (User.Identity?.IsAuthenticated == true)
{
<div class="ms-auto">
<form method="post" asp-controller="Home" asp-action="Logout" style="display: inline;">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-outline-secondary">
<i class="bi bi-box-arrow-right"></i> 登出
</button>
</form>
</div>
}
</div> </div>
</nav> </nav>
</header> </header>


Загрузка…
Отмена
Сохранить