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

455 行
20KB

  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. var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  49. var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
  50. var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
  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 logs = await query.OrderBy(x => x.LogTime).ToListAsync();
  60. // 建立 SerialNo 到編號的映射
  61. var serialNoToRowNumber = new Dictionary<string, int>();
  62. int currentRowNumber = 1;
  63. // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
  64. var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
  65. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  66. var viewModel = new CouponReportViewModel
  67. {
  68. StartDate = startDate,
  69. EndDate = endDate,
  70. ReportItems = logs.Select(log =>
  71. {
  72. // 如果這個 SerialNo 還沒出現過,給它一個新編號
  73. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  74. {
  75. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  76. }
  77. // 從 CarEnter 取得出場時間
  78. DateTime? exitTime = null;
  79. var tenantCode = string.Empty;
  80. DateTime? invoiceDate = null;
  81. var invoiceNo = string.Empty;
  82. var invoiceAmount = 0m;
  83. if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
  84. {
  85. exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
  86. }
  87. invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  88. invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  89. invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  90. tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  91. return new CouponReportItem
  92. {
  93. RowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0,
  94. TenantCode = tenantCode,
  95. CarNumber = log.PlateNo,
  96. InvoiceDate = invoiceDate,
  97. InvoiceNumber = invoiceNo,
  98. InvoiceAmount = invoiceAmount,
  99. DiscountUnit = "新台幣",
  100. DiscountAmount = log.DiscountAmount,
  101. DiscountTime = log.LogTime,
  102. EnterTime = log.EnterTime,
  103. ExitTime = exitTime,
  104. ParkingAmount = log.TotalAmount,
  105. ClaimAmount = log.DiscountAmount
  106. };
  107. }).ToList()
  108. };
  109. return View(viewModel);
  110. }
  111. public async Task<IActionResult> ExportToExcel(DateTime? startDate, DateTime? endDate)
  112. {
  113. var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  114. var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
  115. var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
  116. if (startDate.HasValue)
  117. {
  118. query = query.Where(x => x.LogTime >= startDate.Value);
  119. }
  120. if (endDate.HasValue)
  121. {
  122. query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
  123. }
  124. var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
  125. // 建立 SerialNo 到編號的映射
  126. var serialNoToRowNumber = new Dictionary<string, int>();
  127. int currentRowNumber = 1;
  128. // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
  129. var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
  130. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  131. // 建立報表資料列表
  132. var reportItems = new List<(int rowNumber, string tenantCode, string carNumber, DateTime? invoiceDate,
  133. string invoiceNo, decimal invoiceAmount, string discountUnit, decimal? discountAmount,
  134. DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
  135. decimal? parkingAmount, decimal? claimAmount)>();
  136. foreach (var log in logs)
  137. {
  138. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  139. {
  140. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  141. }
  142. DateTime? exitTime = null;
  143. if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
  144. {
  145. exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
  146. }
  147. var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  148. var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  149. var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  150. var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  151. int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
  152. reportItems.Add((rowNumber, tenantCode, log.PlateNo, invoiceDate, invoiceNo, invoiceAmount,
  153. "新台幣", log.DiscountAmount, log.LogTime, log.EnterTime, exitTime,
  154. log.TotalAmount, log.DiscountAmount));
  155. }
  156. using var workbook = new XLWorkbook();
  157. var worksheet = workbook.Worksheets.Add("折扣報表");
  158. // 標題列
  159. worksheet.Cell(1, 1).Value = "編號";
  160. worksheet.Cell(1, 2).Value = "店別(統編)";
  161. worksheet.Cell(1, 3).Value = "車號";
  162. worksheet.Cell(1, 4).Value = "發票日期";
  163. worksheet.Cell(1, 5).Value = "發票號碼";
  164. worksheet.Cell(1, 6).Value = "發票金額";
  165. worksheet.Cell(1, 7).Value = "折扣單位";
  166. worksheet.Cell(1, 8).Value = "折扣金額";
  167. worksheet.Cell(1, 9).Value = "折扣時間";
  168. worksheet.Cell(1, 10).Value = "入場時間";
  169. worksheet.Cell(1, 11).Value = "出場時間";
  170. worksheet.Cell(1, 12).Value = "停車金額";
  171. worksheet.Cell(1, 13).Value = "請款金額";
  172. // 設定標題列樣式
  173. var headerRange = worksheet.Range(1, 1, 1, 13);
  174. headerRange.Style.Font.Bold = true;
  175. headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
  176. headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  177. headerRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  178. // 填入資料並記錄合併範圍
  179. int row = 2;
  180. var mergedCells = new Dictionary<int, (int startRow, int endRow)>();
  181. foreach (var item in reportItems)
  182. {
  183. // 填入資料
  184. worksheet.Cell(row, 1).Value = item.rowNumber;
  185. worksheet.Cell(row, 2).Value = item.tenantCode;
  186. worksheet.Cell(row, 3).Value = item.carNumber;
  187. worksheet.Cell(row, 4).Value = item.invoiceDate?.ToString("yyyy-MM-dd HH:mm:ss");
  188. worksheet.Cell(row, 5).Value = item.invoiceNo;
  189. worksheet.Cell(row, 6).Value = item.invoiceAmount;
  190. worksheet.Cell(row, 7).Value = item.discountUnit;
  191. worksheet.Cell(row, 8).Value = item.discountAmount;
  192. worksheet.Cell(row, 9).Value = item.discountTime.ToString("yyyy-MM-dd HH:mm:ss");
  193. worksheet.Cell(row, 10).Value = item.enterTime?.ToString("yyyy-MM-dd HH:mm:ss");
  194. worksheet.Cell(row, 11).Value = item.exitTime?.ToString("yyyy-MM-dd HH:mm:ss");
  195. worksheet.Cell(row, 12).Value = item.parkingAmount;
  196. worksheet.Cell(row, 13).Value = item.claimAmount;
  197. // 記錄需要合併的儲存格範圍(按 rowNumber 分組)
  198. if (item.rowNumber > 0)
  199. {
  200. if (!mergedCells.ContainsKey(item.rowNumber))
  201. {
  202. mergedCells[item.rowNumber] = (row, row);
  203. }
  204. else
  205. {
  206. mergedCells[item.rowNumber] = (mergedCells[item.rowNumber].startRow, row);
  207. }
  208. }
  209. row++;
  210. }
  211. // 執行合併儲存格
  212. foreach (var (rowNumber, (startRow, endRow)) in mergedCells)
  213. {
  214. if (startRow < endRow)
  215. {
  216. // 合併:編號(1)、車號(3)、折扣單位(7)、折扣金額(8)、折扣時間(9)、入場時間(10)、出場時間(11)、停車金額(12)、請款金額(13)
  217. int[] mergeCols = { 1, 3, 7, 8, 9, 10, 11, 12, 13 };
  218. foreach (int col in mergeCols)
  219. {
  220. var range = worksheet.Range(startRow, col, endRow, col);
  221. range.Merge();
  222. range.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  223. range.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  224. }
  225. }
  226. }
  227. // 自動調整欄寬,並設定最小寬度以顯示完整表頭
  228. worksheet.Columns().AdjustToContents();
  229. for (int col = 1; col <= 13; col++)
  230. {
  231. if (worksheet.Column(col).Width < 12)
  232. {
  233. worksheet.Column(col).Width = 12;
  234. }
  235. }
  236. // 將所有資料儲存格置中
  237. var dataRange = worksheet.Range(2, 1, row - 1, 13);
  238. dataRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
  239. dataRange.Style.Alignment.Vertical = XLAlignmentVerticalValues.Center;
  240. var stream = new MemoryStream();
  241. workbook.SaveAs(stream);
  242. stream.Position = 0;
  243. var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
  244. return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
  245. }
  246. public async Task<IActionResult> ExportToPdf(DateTime? startDate, DateTime? endDate)
  247. {
  248. var query = _couponContext.Logs.AsQueryable().Where(x => x.LogType == "Consume" && x.LogInfo.Contains("耗用成功"));
  249. var serialNo = query.GroupBy(x => x.SerialNo).Select(g => g.Key).ToList();
  250. var carEnter = _parkingEyesContext.CarEnters.Where(x => serialNo.Contains(x.SerialNo)).ToList();
  251. if (startDate.HasValue)
  252. {
  253. query = query.Where(x => x.LogTime >= startDate.Value);
  254. }
  255. if (endDate.HasValue)
  256. {
  257. query = query.Where(x => x.LogTime <= endDate.Value.AddDays(1).AddSeconds(-1));
  258. }
  259. var logs = await query.OrderBy(x => x.LogTime).ToListAsync();
  260. // 建立 SerialNo 到編號的映射
  261. var serialNoToRowNumber = new Dictionary<string, int>();
  262. int currentRowNumber = 1;
  263. // 建立 SerialNo 到 CarEnter 的映射,方便快速查找
  264. var carEnterDict = carEnter.GroupBy(x => x.SerialNo)
  265. .ToDictionary(g => g.Key, g => g.FirstOrDefault());
  266. // 建立報表資料並分組
  267. var reportGroups = new Dictionary<int, List<(string tenantCode, DateTime? invoiceDate, string invoiceNo, decimal invoiceAmount)>>();
  268. var groupData = new Dictionary<int, (int rowNumber, string carNumber, string discountUnit, decimal? discountAmount,
  269. DateTime discountTime, DateTime? enterTime, DateTime? exitTime,
  270. decimal? parkingAmount, decimal? claimAmount)>();
  271. foreach (var log in logs)
  272. {
  273. if (!string.IsNullOrEmpty(log.SerialNo) && !serialNoToRowNumber.ContainsKey(log.SerialNo))
  274. {
  275. serialNoToRowNumber[log.SerialNo] = currentRowNumber++;
  276. }
  277. DateTime? exitTime = null;
  278. if (!string.IsNullOrEmpty(log.SerialNo) && carEnterDict.ContainsKey(log.SerialNo))
  279. {
  280. exitTime = carEnterDict[log.SerialNo]?.DepartureDateTime;
  281. }
  282. var invoiceDate = _reportService.GetInvoiceDateTime(log.ExternalSystemKey);
  283. var invoiceNo = _reportService.GetInvoiceNo(log.ExternalSystemKey);
  284. var invoiceAmount = _reportService.GetInvoiceMoney(log.ExternalSystemKey);
  285. var tenantCode = _reportService.GetTenantCode(log.ExternalSystemKey);
  286. int rowNumber = !string.IsNullOrEmpty(log.SerialNo) ? serialNoToRowNumber[log.SerialNo] : 0;
  287. // 記錄群組資料
  288. if (!groupData.ContainsKey(rowNumber))
  289. {
  290. groupData[rowNumber] = (rowNumber, log.PlateNo, "新台幣", log.DiscountAmount, log.LogTime,
  291. log.EnterTime, exitTime, log.TotalAmount, log.DiscountAmount);
  292. reportGroups[rowNumber] = new List<(string, DateTime?, string, decimal)>();
  293. }
  294. reportGroups[rowNumber].Add((tenantCode, invoiceDate, invoiceNo, invoiceAmount));
  295. }
  296. var stream = new MemoryStream();
  297. var writer = new PdfWriter(stream);
  298. writer.SetCloseStream(false); // 防止關閉 MemoryStream
  299. var pdf = new PdfDocument(writer);
  300. var document = new Document(pdf, iText.Kernel.Geom.PageSize.A4.Rotate());
  301. // 設定中文字型
  302. var fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "kaiu.ttf");
  303. if (!System.IO.File.Exists(fontPath))
  304. {
  305. fontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "msjh.ttc,0");
  306. }
  307. PdfFont font;
  308. try
  309. {
  310. font = PdfFontFactory.CreateFont(fontPath, PdfEncodings.IDENTITY_H);
  311. }
  312. catch
  313. {
  314. font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
  315. }
  316. document.SetFont(font);
  317. // 標題
  318. var title = new Paragraph("折扣報表")
  319. .SetTextAlignment(TextAlignment.CENTER)
  320. .SetFontSize(18);
  321. document.Add(title);
  322. // 建立表格
  323. var table = new Table(13).UseAllAvailableWidth();
  324. table.SetFontSize(8);
  325. // 標題列
  326. table.AddHeaderCell(new Cell().Add(new Paragraph("編號")).SetTextAlignment(TextAlignment.CENTER));
  327. table.AddHeaderCell(new Cell().Add(new Paragraph("店別")).SetTextAlignment(TextAlignment.CENTER));
  328. table.AddHeaderCell(new Cell().Add(new Paragraph("車號")).SetTextAlignment(TextAlignment.CENTER));
  329. table.AddHeaderCell(new Cell().Add(new Paragraph("發票日期")).SetTextAlignment(TextAlignment.CENTER));
  330. table.AddHeaderCell(new Cell().Add(new Paragraph("發票號碼")).SetTextAlignment(TextAlignment.CENTER));
  331. table.AddHeaderCell(new Cell().Add(new Paragraph("發票金額")).SetTextAlignment(TextAlignment.CENTER));
  332. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣單位")).SetTextAlignment(TextAlignment.CENTER));
  333. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣金額")).SetTextAlignment(TextAlignment.CENTER));
  334. table.AddHeaderCell(new Cell().Add(new Paragraph("折扣時間")).SetTextAlignment(TextAlignment.CENTER));
  335. table.AddHeaderCell(new Cell().Add(new Paragraph("入場時間")).SetTextAlignment(TextAlignment.CENTER));
  336. table.AddHeaderCell(new Cell().Add(new Paragraph("出場時間")).SetTextAlignment(TextAlignment.CENTER));
  337. table.AddHeaderCell(new Cell().Add(new Paragraph("停車金額")).SetTextAlignment(TextAlignment.CENTER));
  338. table.AddHeaderCell(new Cell().Add(new Paragraph("請款金額")).SetTextAlignment(TextAlignment.CENTER));
  339. // 資料列(使用合併儲存格)
  340. foreach (var (rowNum, group) in groupData.OrderBy(x => x.Key))
  341. {
  342. var invoices = reportGroups[rowNum];
  343. int rowSpan = invoices.Count;
  344. for (int i = 0; i < invoices.Count; i++)
  345. {
  346. var invoice = invoices[i];
  347. bool isFirstRow = i == 0;
  348. if (isFirstRow)
  349. {
  350. // 第一行:顯示合併的欄位 - 編號
  351. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.rowNumber.ToString())).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  352. }
  353. // 每一行:店別
  354. table.AddCell(new Cell().Add(new Paragraph(invoice.tenantCode ?? "")).SetTextAlignment(TextAlignment.CENTER));
  355. if (isFirstRow)
  356. {
  357. // 車號
  358. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.carNumber ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  359. }
  360. // 每一行:發票日期、發票號碼、發票金額
  361. table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceDate?.ToString("yyyy-MM-dd") ?? "")).SetTextAlignment(TextAlignment.CENTER));
  362. table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceNo ?? "")).SetTextAlignment(TextAlignment.CENTER));
  363. table.AddCell(new Cell().Add(new Paragraph(invoice.invoiceAmount.ToString("N0"))).SetTextAlignment(TextAlignment.CENTER));
  364. if (isFirstRow)
  365. {
  366. // 合併欄位:折扣單位、折扣金額、折扣時間、入場時間、出場時間、停車金額、請款金額
  367. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountUnit ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  368. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  369. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.discountTime.ToString("yyyy-MM-dd HH:mm"))).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  370. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.enterTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  371. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.exitTime?.ToString("yyyy-MM-dd HH:mm") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  372. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.parkingAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  373. table.AddCell(new Cell(rowSpan, 1).Add(new Paragraph(group.claimAmount?.ToString("N0") ?? "")).SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.MIDDLE));
  374. }
  375. }
  376. }
  377. document.Add(table);
  378. document.Close();
  379. stream.Position = 0;
  380. var fileName = $"折扣報表_{DateTime.Now:yyyyMMddHHmmss}.pdf";
  381. return File(stream, "application/pdf", fileName);
  382. }
  383. }