裕隆城折扣合作@中興低碳 折扣報表
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

ReportController.cs 23KB

1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
1ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. using CouponReport.Models;
  2. using Microsoft.AspNetCore.Mvc;
  3. using Microsoft.EntityFrameworkCore;
  4. using ClosedXML.Excel;
  5. using iText.Kernel.Pdf;
  6. using iText.Layout;
  7. using iText.Layout.Element;
  8. using iText.Layout.Properties;
  9. using iText.Kernel.Font;
  10. using iText.IO.Font;
  11. using iText.IO.Font.Constants;
  12. using CouponReport.Models.CouponMiddleware;
  13. using CouponReport.Models.Parkingeyes;
  14. using CouponReport.Service;
  15. using Microsoft.AspNetCore.Authorization;
  16. using Microsoft.AspNetCore.Authentication.Cookies;
  17. namespace CouponReport.Controllers;
  18. [Authorize]
  19. public class ReportController : Controller
  20. {
  21. private readonly CouponMiddlewareContext _couponContext;
  22. private readonly ParkingEyesContext _parkingEyesContext;
  23. private readonly ReportService _reportService;
  24. public ReportController(CouponMiddlewareContext context, ParkingEyesContext parkingEyesContext, ReportService reportService)
  25. {
  26. _couponContext = context;
  27. _parkingEyesContext = parkingEyesContext;
  28. _reportService = reportService;
  29. }
  30. public async Task<IActionResult> Index(DateTime? startDate, DateTime? endDate)
  31. {
  32. // 如果沒有提供日期,預設為當月第一天到最後一天
  33. if (!startDate.HasValue && !endDate.HasValue)
  34. {
  35. var today = DateTime.Today;
  36. startDate = new DateTime(today.Year, today.Month, 1);
  37. endDate = startDate.Value.AddMonths(1).AddDays(-1);
  38. }
  39. return await LoadReportData(startDate, endDate);
  40. }
  41. [HttpPost]
  42. public async Task<IActionResult> Index(DateTime? startDate, DateTime? endDate, string action)
  43. {
  44. return await LoadReportData(startDate, endDate);
  45. }
  46. private async Task<IActionResult> LoadReportData(DateTime? startDate, DateTime? endDate)
  47. {
  48. // 建立包含日期篩選的查詢
  49. var query = _couponContext.Logs.AsQueryable()
  50. .Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  51. if (startDate.HasValue)
  52. {
  53. query = query.Where(x => x.LogTime >= startDate.Value);
  54. }
  55. if (endDate.HasValue)
  56. {
  57. query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
  58. }
  59. var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
  60. var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();
  61. var evaluateLogsQuery = await _couponContext.Logs
  62. .Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
  63. .ToListAsync();
  64. // 建立 SerialNo 到 Evaluate Log 的映射
  65. var evaluateLogDict = evaluateLogsQuery
  66. .GroupBy(x => x.SerialNo)
  67. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  68. //var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
  69. //var carEnter = _mySqlContext.Cario.Where(x => serialNo.Contains(x.SerialNo)).ToList();
  70. // 建立 SerialNo 到編號的映射
  71. var serialNoToRowNumber = new Dictionary<string, int>();
  72. int currentRowNumber = 1;
  73. // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
  74. //var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
  75. // .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  76. var reportItems = consumeLogs.Select(log =>
  77. {
  78. // 如果這個 SerialNo 還沒出現過,給它一個新編號
  79. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  80. {
  81. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  82. }
  83. // 從 evaluateLogDict 取得出場時間
  84. DateTime? exitTime = null;
  85. if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
  86. {
  87. exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
  88. }
  89. var tenantCode = string.Empty;
  90. DateTime? invoiceDate = null;
  91. var invoiceNo = string.Empty;
  92. var invoiceAmount = 0m;
  93. //if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
  94. // {
  95. // exitTime = carEnterDict[log.SerialNo]?.OutTime;
  96. // }
  97. invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  98. invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  99. invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  100. tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  101. return new CouponReportItem
  102. {
  103. RowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0,
  104. TenantCode = tenantCode,
  105. CarNumber = log.PlateNo,
  106. InvoiceDate = invoiceDate,
  107. InvoiceNumber = invoiceNo,
  108. InvoiceAmount = invoiceAmount,
  109. DiscountUnit = "金額",
  110. DiscountAmount = log.DiscountAmount,
  111. DiscountTime = log.LogTime,
  112. EnterTime = log.EnterTime,
  113. ExitTime = exitTime,
  114. ParkingAmount = log.TotalAmount,
  115. ClaimAmount = log.DiscountAmount
  116. };
  117. }).OrderBy(x => x.RowNumber).ThenBy(x => x.DiscountTime).ToList();
  118. var viewModel = new CouponReportViewModel
  119. {
  120. StartDate = startDate,
  121. EndDate = endDate,
  122. ReportItems = reportItems
  123. };
  124. return View(viewModel);
  125. }
  126. public async Task<IActionResult> ExportToExcel(DateTime? startDate, DateTime? endDate)
  127. {
  128. var query = _couponContext.Logs.AsQueryable()
  129. .Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  130. if (startDate.HasValue)
  131. {
  132. query = query.Where(x => x.LogTime >= startDate.Value);
  133. }
  134. if (endDate.HasValue)
  135. {
  136. query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
  137. }
  138. var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
  139. var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();
  140. var evaluateLogsQuery = await _couponContext.Logs
  141. .Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
  142. .ToListAsync();
  143. // 建立 SerialNo 到 Evaluate Log 的映射
  144. var evaluateLogDict = evaluateLogsQuery
  145. .GroupBy(x => x.SerialNo)
  146. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  147. // 建立 SerialNo 到編號的映射
  148. var serialNoToRowNumber = new Dictionary<string, int>();
  149. int currentRowNumber = 1;
  150. // 建立報表資料列表
  151. var reportItems = new List<(int rowNumber, string tenantCode, string carNumber, DateTime? invoiceDate,
  152. string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount,
  153. DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
  154. decimal? parkingAmount, decimal? claimAmount)>();
  155. foreach (var log in consumeLogs)
  156. {
  157. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  158. {
  159. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  160. }
  161. DateTime? exitTime = null;
  162. if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
  163. {
  164. exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
  165. }
  166. var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  167. var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  168. var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  169. var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  170. int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
  171. reportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
  172. "金額", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
  173. log.TotalAmount, log.DiscountAmount));
  174. }
  175. // 按 RowNumber 排序,確保相同編號的資料連續,避免合併儲存格錯誤
  176. reportItems = reportItems.OrderBy(x => x.rowNumber).ThenBy(x => x.discountTime).ToList();
  177. using var workbook = new XLWorkbook();
  178. var worksheet = workbook.Worksheets.Add("折扣報表");
  179. // 匯出資訊(合併儲存格以避免影響欄寬)
  180. worksheet.Range(1, 1, 1, 13).Merge();
  181. worksheet.Cell(1, 1).Value = $"匯出時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
  182. worksheet.Range(2, 1, 2, 13).Merge();
  183. worksheet.Cell(2, 1).Value = $"篩選條件:{startDate?.ToString("yyyy-MM-dd")} ~ {endDate?.ToString("yyyy-MM-dd")}";
  184. // 標題列
  185. worksheet.Cell(4, 1).Value = "編號";
  186. worksheet.Cell(4, 2).Value = "店別(統編)";
  187. worksheet.Cell(4, 3).Value = "車號";
  188. worksheet.Cell(4, 4).Value = "發票日期";
  189. worksheet.Cell(4, 5).Value = "發票號碼";
  190. worksheet.Cell(4, 6).Value = "發票金額";
  191. worksheet.Cell(4, 7).Value = "折扣單位";
  192. worksheet.Cell(4, 8).Value = "折扣金額";
  193. worksheet.Cell(4, 9).Value = "折扣時間";
  194. worksheet.Cell(4, 10).Value = "入場時間";
  195. worksheet.Cell(4, 11).Value = "出場時間";
  196. worksheet.Cell(4, 12).Value = "停車金額";
  197. worksheet.Cell(4, 13).Value = "請款金額";
  198. // 設定標題列樣式
  199. var headerRange = worksheet.Range(4, 1, 4, 13);
  200. headerRange.Style.Font.Bold = true;
  201. headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
  202. headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  203. headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  204. // 填入資料並記錄合併範圍
  205. int row = 5;
  206. var mergedCells = new Dictionary<int, (int startRow, int endRow)>();
  207. foreach (var item in reportItems)
  208. {
  209. // 填入資料
  210. worksheet.Cell(row, 1).Value = item.rowNumber;
  211. worksheet.Cell(row, 2).Value = item.tenantCode;
  212. worksheet.Cell(row, 3).Value = item.carNumber;
  213. worksheet.Cell(row, 4).Value = item.invoiceDate?.ToString("yyyy-MM-dd");
  214. worksheet.Cell(row, 5).Value = item.invoiceNo;
  215. worksheet.Cell(row, 6).Value = $"NT$ {item.invoiceAmount:N0}";
  216. worksheet.Cell(row, 7).Value = item.discountUnit;
  217. worksheet.Cell(row, 8).Value = $"NT$ {item.discountAmount:N0}";
  218. worksheet.Cell(row, 9).Value = item.discountTime.ToString("yyyy-MM-dd HH:mm:ss");
  219. worksheet.Cell(row, 10).Value = item.enterTime?.ToString("yyyy-MM-dd HH:mm:ss");
  220. worksheet.Cell(row, 11).Value = item.exitTime?.ToString("yyyy-MM-dd HH:mm:ss");
  221. worksheet.Cell(row, 12).Value = $"NT$ {item.parkingAmount:N0}";
  222. worksheet.Cell(row, 13).Value = $"NT$ {item.claimAmount:N0}";
  223. // 記錄需要合併的儲存格範圍(按 rowNumber 分組)
  224. if (item.rowNumber > 0)
  225. {
  226. if (!mergedCells.ContainsKey(item.rowNumber))
  227. {
  228. mergedCells[item.rowNumber] = (row, row);
  229. }
  230. else
  231. {
  232. mergedCells[item.rowNumber] = (mergedCells[item.rowNumber].startRow, row);
  233. }
  234. }
  235. row++;
  236. }
  237. // 執行合併儲存格
  238. foreach (var (rowNumber, (startRow, endRow)) in mergedCells)
  239. {
  240. if (startRow < endRow)
  241. {
  242. // 合併:編號(1)、車號(3)、折扣單位(7)、折扣金額(8)、折扣時間(9)、入場時間(10)、出場時間(11)、停車金額(12)、請款金額(13)
  243. int[] mergeCols = { 1, 3, 7, 8, 9, 10, 11, 12, 13 };
  244. foreach (int col in mergeCols)
  245. {
  246. var range = worksheet.Range(startRow, col, endRow, col);
  247. range.Merge();
  248. range.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  249. range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  250. }
  251. }
  252. }
  253. // 自動調整欄寬,並設定最小寬度以顯示完整表頭
  254. worksheet.Columns().AdjustToContents();
  255. for (int col = 1; col <= 13; col++)
  256. {
  257. if (worksheet.Column(col).Width < 12)
  258. {
  259. worksheet.Column(col).Width = 12;
  260. }
  261. }
  262. // 將所有資料儲存格置中
  263. var dataRange = worksheet.Range(5, 1, row - 1, 13);
  264. dataRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  265. dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  266. var stream = new MemoryStream();
  267. workbook.SaveAs(stream);
  268. stream.Position = 0;
  269. var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
  270. return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
  271. }
  272. public async Task<IActionResult> ExportToPdf(DateTime? startDate, DateTime? endDate)
  273. {
  274. var query = _couponContext.Logs.AsQueryable()
  275. .Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  276. if (startDate.HasValue)
  277. {
  278. query = query.Where(x => x.LogTime >= startDate.Value);
  279. }
  280. if (endDate.HasValue)
  281. {
  282. query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
  283. }
  284. var consumeLogs = await query.OrderBy(x => x.LogTime).ToListAsync();
  285. var serialNo = consumeLogs.Select(x => x.SerialNo).Distinct().ToList();
  286. var evaluateLogsQuery = await _couponContext.Logs
  287. .Where(x => serialNo.Contains(x.SerialNo) && x.LogType == "Evaluate" && x.LogInfo.Contains("評估成功"))
  288. .ToListAsync();
  289. // 建立 SerialNo 到 Evaluate Log 的映射
  290. var evaluateLogDict = evaluateLogsQuery
  291. .GroupBy(x => x.SerialNo)
  292. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  293. // 建立 SerialNo 到編號的映射
  294. var serialNoToRowNumber = new Dictionary<string, int>();
  295. int currentRowNumber = 1;
  296. // 建立臨時資料列表以便排序
  297. var tempReportItems = new List<(int rowNumber, string tenantCode, string carNumber, DateTime? invoiceDate,
  298. string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount,
  299. DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
  300. decimal? parkingAmount, decimal? claimAmount)>();
  301. foreach (var log in consumeLogs)
  302. {
  303. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  304. {
  305. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  306. }
  307. DateTime? exitTime = null;
  308. if (!string.IsNullOrEmpty(log.SerialNo) && evaluateLogDict.ContainsKey(log.SerialNo))
  309. {
  310. exitTime = evaluateLogDict[log.SerialNo]?.LogTime;
  311. }
  312. var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  313. var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  314. var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  315. var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  316. int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
  317. tempReportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
  318. "金額", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
  319. log.TotalAmount, log.DiscountAmount));
  320. }
  321. // 按 RowNumber 排序,確保相同編號的資料連續,避免合併儲存格錯誤
  322. tempReportItems = tempReportItems.OrderBy(x => x.rowNumber).ThenBy(x => x.discountTime).ToList();
  323. // 建立報表資料並分組
  324. var reportGroups = new Dictionary<int, List<(string tenantCode, DateTime? invoiceDate, string invoiceNo, decimal invoiceAmount)>>();
  325. var groupData = new Dictionary<int, (int rowNumber, string carNumber, string discountUnit, decimal? discountAmount,
  326. DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
  327. decimal? parkingAmount, decimal? claimAmount)>();
  328. foreach (var item in tempReportItems)
  329. {
  330. // 記錄群組資料
  331. if (!groupData.ContainsKey(item.rowNumber))
  332. {
  333. groupData[item.rowNumber] = (item.rowNumber, item.carNumber, item.discountUnit, item.discountAmount, item.discountTime,
  334. item.enterTime, item.exitTime, item.parkingAmount, item.claimAmount);
  335. reportGroups[item.rowNumber] = new List<(string, DateTime?, string, decimal)>();
  336. }
  337. reportGroups[item.rowNumber].Add((item.tenantCode, item.invoiceDate, item.invoiceNo, item.invoiceAmount));
  338. }
  339. var stream = new MemoryStream();
  340. var writer = new PdfWriter(stream);
  341. writer.SetCloseStream(false); // 防止關閉 MemoryStream
  342. var pdf = new PdfDocument(writer);
  343. var document = new Document(pdf, iText.Kernel.Geom.PageSize.A4.Rotate());
  344. // 設定中文字型
  345. var fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "kaiu.ttf");
  346. if (!System.IO.File.Exists(fontPath))
  347. {
  348. fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "msjh.ttc,0");
  349. }
  350. PdfFont font;
  351. try
  352. {
  353. font = PdfFontFactory.CreateFont(fontPath, PdfEncodings.IDENTITY_H);
  354. }
  355. catch
  356. {
  357. font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
  358. }
  359. document.SetFont(font);
  360. // 標題
  361. var title = new Paragraph("折扣報表")
  362. .SetTextAlignment(TextAlignment.CENTER)
  363. .SetFontSize(18);
  364. document.Add(title);
  365. // 匯出資訊
  366. var exportInfo = new Paragraph($"匯出時間:{DateTime.Now:yyyy-MM-dd HH:mm:ss}")
  367. .SetFontSize(10)
  368. .SetMarginTop(10);
  369. document.Add(exportInfo);
  370. var filterInfo = new Paragraph($"篩選條件:{startDate?.ToString("yyyy-MM-dd")} ~ {endDate?.ToString("yyyy-MM-dd")}")
  371. .SetFontSize(10)
  372. .SetMarginBottom(10);
  373. document.Add(filterInfo);
  374. // 建立表格
  375. var table = new Table(13).UseAllAvailableWidth();
  376. table.SetFontSize(8);
  377. // 標題列
  378. table.AddHeaderCell(new Cell().Add(new Paragraph("編號")).SetTextAlignment(TextAlignment.CENTER));
  379. table.AddHeaderCell(new Cell().Add(new Paragraph("店別")).SetTextAlignment(TextAlignment.CENTER));
  380. table.AddHeaderCell(new Cell().Add(new Paragraph("車號")).SetTextAlignment(TextAlignment.CENTER));
  381. table.AddHeaderCell(new Cell().Add(new Paragraph("發票日期")).SetTextAlignment(TextAlignment.CENTER));
  382. table.AddHeaderCell(new Cell().Add(new Paragraph("發票號碼")).SetTextAlignment(TextAlignment.CENTER));
  383. table.AddHeaderCell(new Cell().Add(new Paragraph("發票金額")).SetTextAlignment(TextAlignment.CENTER));
  384. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣單位")).SetTextAlignment(TextAlignment.CENTER));
  385. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣金額")).SetTextAlignment(TextAlignment.CENTER));
  386. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣時間")).SetTextAlignment(TextAlignment.CENTER));
  387. table.AddHeaderCell(new Cell().Add(new Paragraph("入場時間")).SetTextAlignment(TextAlignment.CENTER));
  388. table.AddHeaderCell(new Cell().Add(new Paragraph("出場時間")).SetTextAlignment(TextAlignment.CENTER));
  389. table.AddHeaderCell(new Cell().Add(new Paragraph("停車金額")).SetTextAlignment(TextAlignment.CENTER));
  390. table.AddHeaderCell(new Cell().Add(new Paragraph("請款金額")).SetTextAlignment(TextAlignment.CENTER));
  391. // 資料列(使用合併儲存格)
  392. foreach (var (rowNum, group) in groupData.OrderBy(x => x.Key))
  393. {
  394. var invoices = reportGroups[rowNum];
  395. int rowSpan = invoices.Count;
  396. for (int i = 0; i < invoices.Count; i++)
  397. {
  398. var invoice = invoices[i];
  399. bool isFirstRow = i == 0;
  400. if (isFirstRow)
  401. {
  402. // 第一行:顯示合併的欄位 - 編號
  403. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.rowNumber.ToString())).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  404. }
  405. // 每一行:店別
  406. table.AddCell(new Cell().Add(new Paragraph(invoice.tenantCode ?? "")).SetTextAlignment(TextAlignment.CENTER));
  407. if (isFirstRow)
  408. {
  409. // 車號
  410. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.carNumber ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  411. }
  412. // 每一行:發票日期、發票號碼、發票金額
  413. table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceDate?.ToString("yyyy-MM-dd") ?? "")).SetTextAlignment(TextAlignment.CENTER));
  414. table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceNo ?? "")).SetTextAlignment(TextAlignment.CENTER));
  415. table.AddCell(new Cell().Add(new Paragraph($"NT$ {invoice.invoiceAmount:N0}")).SetTextAlignment(TextAlignment.CENTER));
  416. if (isFirstRow)
  417. {
  418. // 合併欄位:折扣單位、折扣金額、折扣時間、入場時間、出場時間、停車金額、請款金額
  419. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountUnit ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  420. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph($"NT$ {group.discountAmount:N0}")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  421. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountTime.ToString("yyyy-MM-dd HH:mm"))).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  422. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.enterTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  423. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.exitTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  424. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph($"NT$ {group.parkingAmount:N0}")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  425. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph($"NT$ {group.claimAmount:N0}")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  426. }
  427. }
  428. }
  429. document.Add(table);
  430. document.Close();
  431. stream.Position = 0;
  432. var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.pdf";
  433. return File(stream, "application/pdf", fileName);
  434. }
  435. }