裕隆城折扣合作@中興低碳 折扣報表
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

522 строки
23KB

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