| @@ -0,0 +1,16 @@ | |||||
| { | |||||
| "permissions": { | |||||
| "allow": [ | |||||
| "Bash(dotnet add package:*)", | |||||
| "Bash(dotnet add:*)", | |||||
| "Bash(dotnet remove:*)", | |||||
| "Read(//c/Users/User/Downloads/**)", | |||||
| "Bash(dotnet clean:*)", | |||||
| "Bash(dotnet build)", | |||||
| "Bash(dir:*)", | |||||
| "Bash(dotnet list:*)" | |||||
| ], | |||||
| "deny": [], | |||||
| "ask": [] | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,119 @@ | |||||
| using System.Net; | |||||
| using System.Text; | |||||
| using System.Text.Json; | |||||
| using System.Text.Json.Serialization; | |||||
| using System.Web; | |||||
| using LaneFlowReport.Options; | |||||
| using Microsoft.Extensions.Options; | |||||
| namespace LaneFlowReport.Auth { | |||||
| public class ParkingAuthProvider { | |||||
| private readonly IOptions<OauthOption> _oauthOption; | |||||
| private readonly IHttpClientFactory _httpClientFactory; | |||||
| public ParkingAuthProvider(IOptions<OauthOption> oauthOption, IHttpClientFactory httpClientFactory) { | |||||
| _oauthOption = oauthOption; | |||||
| _httpClientFactory = httpClientFactory; | |||||
| } | |||||
| public const string Name = "Parking"; | |||||
| public async Task<ExternalUserInfoModel> GetUserInfo(string accessCode) { | |||||
| var providerInfo = new { | |||||
| _oauthOption.Value.ClientUrl, | |||||
| _oauthOption.Value.ClientSecret | |||||
| }; | |||||
| var mode = accessCode.Split(";")?[0]; | |||||
| var key = accessCode.Split(";")?[1]; | |||||
| string url; | |||||
| object param; | |||||
| if (mode == "line") { | |||||
| param = new { | |||||
| accessToken = providerInfo.ClientSecret, | |||||
| loginProvider = mode, | |||||
| providerKey = key, | |||||
| roleName = "", | |||||
| }; | |||||
| url = $"{providerInfo.ClientUrl}ExternalRoleOAuth"; | |||||
| } | |||||
| else { | |||||
| param = new { | |||||
| userId = key, | |||||
| accessToken = providerInfo.ClientSecret | |||||
| }; | |||||
| url = $"{providerInfo.ClientUrl}ExternalOAuth"; | |||||
| } | |||||
| var httpClient = _httpClientFactory.CreateClient(); | |||||
| var query = GetQueryString(param); | |||||
| var content = new StringContent(query, Encoding.UTF8, "application/x-www-form-urlencoded"); | |||||
| var response = await httpClient.PostAsync(url, content); | |||||
| if (response.StatusCode != HttpStatusCode.OK) { | |||||
| throw new Exception("Parking Login Error"); | |||||
| } | |||||
| var responseString = await response.Content.ReadAsStringAsync(); | |||||
| var result = JsonSerializer.Deserialize<AjaxResponse<ExternalUserInfoModel>>(responseString); | |||||
| if (result?.Result == null) { | |||||
| throw new Exception("User not exist"); | |||||
| } | |||||
| return new ExternalUserInfoModel { | |||||
| FullName = result.Result.FullName, | |||||
| EmailAddress = result.Result.EmailAddress, | |||||
| PhotoUrl = result.Result.PhotoUrl, | |||||
| UserName = result.Result.UserName | |||||
| }; | |||||
| } | |||||
| private string GetQueryString(object obj) { | |||||
| var properties = from p in obj.GetType().GetProperties() | |||||
| where p.GetValue(obj, null) != null | |||||
| select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString()); | |||||
| return String.Join("&", properties.ToArray()); | |||||
| } | |||||
| } | |||||
| public class ExternalUserInfoModel | |||||
| { | |||||
| [JsonPropertyName("tenantId")] | |||||
| public int TenantId { get; set; } | |||||
| [JsonPropertyName("id")] | |||||
| public int Id { get; set; } | |||||
| [JsonPropertyName("userName")] | |||||
| public string UserName { get; set; } | |||||
| [JsonPropertyName("fullName")] | |||||
| public string FullName { get; set; } | |||||
| [JsonPropertyName("photoUrl")] | |||||
| public string PhotoUrl { get; set; } | |||||
| [JsonPropertyName("emailAddress")] | |||||
| public string EmailAddress { get; set; } | |||||
| } | |||||
| public class AjaxResponse<T> | |||||
| { | |||||
| [JsonPropertyName("result")] | |||||
| public T Result { get; set; } | |||||
| [JsonPropertyName("targetUrl")] | |||||
| public object TargetUrl { get; set; } | |||||
| [JsonPropertyName("success")] | |||||
| public bool Success { get; set; } | |||||
| [JsonPropertyName("error")] | |||||
| public object Error { get; set; } | |||||
| [JsonPropertyName("unAuthorizedRequest")] | |||||
| public bool UnAuthorizedRequest { get; set; } | |||||
| [JsonPropertyName("__abp")] | |||||
| public bool Abp { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,133 @@ | |||||
| using System.Diagnostics; | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| using CouponReport.Models; | |||||
| using Microsoft.AspNetCore.Authentication.Cookies; | |||||
| using Microsoft.AspNetCore.Authentication.OAuth; | |||||
| using Microsoft.AspNetCore.Authentication; | |||||
| using Microsoft.Extensions.Options; | |||||
| using System.Security.Claims; | |||||
| using System.Text.Json; | |||||
| using System.Text; | |||||
| using LaneFlowReport.Options; | |||||
| using LaneFlowReport.Models; | |||||
| namespace CouponReport.Controllers; | |||||
| public class HomeController : Controller | |||||
| { | |||||
| private readonly ILogger<HomeController> _logger; | |||||
| private readonly IHttpClientFactory _httpClientFactory; | |||||
| private readonly IOptions<OauthOption> _oauthOption; | |||||
| public HomeController(ILogger<HomeController> logger, IHttpClientFactory httpClientFactory, IOptions<OauthOption> oauthOption) | |||||
| { | |||||
| _logger = logger; | |||||
| _httpClientFactory = httpClientFactory; | |||||
| _oauthOption = oauthOption; | |||||
| } | |||||
| public IActionResult Index() | |||||
| { | |||||
| ViewBag.OauthUrl = _oauthOption.Value.ParkingOAuthUrl; | |||||
| return View(new LoginViewModel()); | |||||
| } | |||||
| [HttpPost] | |||||
| public async Task<IActionResult> Index(LoginViewModel model) | |||||
| { | |||||
| ViewBag.OauthUrl = _oauthOption.Value.ParkingOAuthUrl; | |||||
| if (ModelState.IsValid) | |||||
| { | |||||
| if (await ValidateCredentialsAsync(model)) | |||||
| { | |||||
| var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] | |||||
| { | |||||
| new Claim(ClaimTypes.Name, model.Username), | |||||
| new Claim(ClaimTypes.Role, "Report"), | |||||
| }, "Cookies")); | |||||
| var authProperties = new AuthenticationProperties | |||||
| { | |||||
| IsPersistent = false | |||||
| }; | |||||
| await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, | |||||
| new ClaimsPrincipal(claimsPrincipal), authProperties); | |||||
| return RedirectToAction("Index", "Report", new { }); | |||||
| } | |||||
| else | |||||
| { | |||||
| ModelState.AddModelError(string.Empty, "登入失敗!"); | |||||
| model.Password = ""; | |||||
| } | |||||
| } | |||||
| return View(model); | |||||
| } | |||||
| //驗證帳號 | |||||
| private async Task<bool> ValidateCredentialsAsync(LoginViewModel model) | |||||
| { | |||||
| var client = _httpClientFactory.CreateClient(); | |||||
| var endPoint = _oauthOption.Value.ParkingLoginUrl; | |||||
| var data = new | |||||
| { | |||||
| userNameOrEmailAddress = model.Username, | |||||
| password = model.Password, | |||||
| tenancyName = "Altob" | |||||
| }; | |||||
| var json = JsonSerializer.Serialize(data); | |||||
| var content = new StringContent(json, Encoding.UTF8, "application/json"); | |||||
| try | |||||
| { | |||||
| var response = await client.PostAsync(endPoint, content); | |||||
| var responseString = await response.Content.ReadAsStringAsync(); | |||||
| _logger.LogInformation($"API Response: {responseString}"); | |||||
| if (response.IsSuccessStatusCode) | |||||
| { | |||||
| //var result = JsonSerializer.Deserialize<AuthenticationResult>(responseString); | |||||
| //if (result != null && result.StatusCode == 200 && result.Msg == "成功") | |||||
| //{ | |||||
| return true; | |||||
| //} | |||||
| } | |||||
| else | |||||
| { | |||||
| return false; | |||||
| } | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| _logger.LogError(ex, "驗證有誤"); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| //登出 | |||||
| [HttpPost] | |||||
| [ValidateAntiForgeryToken] | |||||
| public async Task<IActionResult> Logout() | |||||
| { | |||||
| await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); | |||||
| HttpContext.Session.Clear(); | |||||
| return RedirectToAction("Index", "Home"); | |||||
| } | |||||
| [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] | |||||
| public IActionResult Error() | |||||
| { | |||||
| return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); | |||||
| } | |||||
| public class AuthenticationResult | |||||
| { | |||||
| public string Msg { get; set; } | |||||
| public int StatusCode { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,34 @@ | |||||
| using System.Security.Claims; | |||||
| using LaneFlowReport.Auth; | |||||
| using Microsoft.AspNetCore.Authentication; | |||||
| using Microsoft.AspNetCore.Authentication.Cookies; | |||||
| using Microsoft.AspNetCore.Mvc; | |||||
| namespace LaneFlowReport.Controllers { | |||||
| public class LoginController: Controller { | |||||
| private readonly ParkingAuthProvider _parkingAuthProvider; | |||||
| public LoginController(ParkingAuthProvider parkingAuthProvider) { | |||||
| _parkingAuthProvider = parkingAuthProvider; | |||||
| } | |||||
| [HttpGet] | |||||
| public async Task<IActionResult> LoginCallback(string mode, string providerKey) { | |||||
| var accessCode = $"{mode};{providerKey}"; | |||||
| var result = await _parkingAuthProvider.GetUserInfo(accessCode); | |||||
| var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new[] { | |||||
| new Claim(ClaimTypes.Name, result.UserName), | |||||
| new Claim(ClaimTypes.Role, "Report"), | |||||
| }, "Cookies")); | |||||
| var authProperties = new AuthenticationProperties { | |||||
| IsPersistent = true | |||||
| }; | |||||
| await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, | |||||
| new ClaimsPrincipal(claimsPrincipal), authProperties); | |||||
| HttpContext.User = claimsPrincipal; | |||||
| ViewBag.Status = "authing..."; | |||||
| ViewBag.RedirectUrl = Url.Action("Index", "Report", null, HttpContext.Request.Scheme); | |||||
| return View(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,454 @@ | |||||
| 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); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||||
| <PropertyGroup> | |||||
| <TargetFramework>net8.0</TargetFramework> | |||||
| <Nullable>enable</Nullable> | |||||
| <ImplicitUsings>enable</ImplicitUsings> | |||||
| </PropertyGroup> | |||||
| <ItemGroup> | |||||
| <PackageReference Include="ClosedXML" Version="0.105.0" /> | |||||
| <PackageReference Include="itext.bouncy-castle-adapter" Version="9.3.0" /> | |||||
| <PackageReference Include="itext.font-asian" Version="9.3.0" /> | |||||
| <PackageReference Include="itext7" Version="9.3.0" /> | |||||
| <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.9" /> | |||||
| <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9"> | |||||
| <PrivateAssets>all</PrivateAssets> | |||||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||||
| </PackageReference> | |||||
| </ItemGroup> | |||||
| <ItemGroup> | |||||
| <Folder Include="Models\ParkingEyes\" /> | |||||
| </ItemGroup> | |||||
| </Project> | |||||
| @@ -0,0 +1,25 @@ | |||||
| | |||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||||
| # Visual Studio Version 17 | |||||
| VisualStudioVersion = 17.13.35825.156 d17.13 | |||||
| MinimumVisualStudioVersion = 10.0.40219.1 | |||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CouponReport", "CouponReport.csproj", "{C4EA05A3-EBF0-4401-A77E-FF50A46FE707}" | |||||
| EndProject | |||||
| Global | |||||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||||
| Debug|Any CPU = Debug|Any CPU | |||||
| Release|Any CPU = Release|Any CPU | |||||
| EndGlobalSection | |||||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||||
| {C4EA05A3-EBF0-4401-A77E-FF50A46FE707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||||
| {C4EA05A3-EBF0-4401-A77E-FF50A46FE707}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||||
| {C4EA05A3-EBF0-4401-A77E-FF50A46FE707}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||||
| {C4EA05A3-EBF0-4401-A77E-FF50A46FE707}.Release|Any CPU.Build.0 = Release|Any CPU | |||||
| EndGlobalSection | |||||
| GlobalSection(SolutionProperties) = preSolution | |||||
| HideSolutionNode = FALSE | |||||
| EndGlobalSection | |||||
| GlobalSection(ExtensibilityGlobals) = postSolution | |||||
| SolutionGuid = {B6CF056C-51AA-4280-AAAC-695509DD0EFC} | |||||
| EndGlobalSection | |||||
| EndGlobal | |||||
| @@ -0,0 +1,39 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class CouponConsume | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public string? SiteId { get; set; } | |||||
| public DateTime LogTime { get; set; } | |||||
| public string? DeviceId { get; set; } | |||||
| public decimal TotalAmount { get; set; } | |||||
| public decimal DiscountAmount { get; set; } | |||||
| public bool UploadStatus { get; set; } | |||||
| public int UploadCount { get; set; } | |||||
| public string? SerialNo { get; set; } | |||||
| public string? ExternalSystemKey { get; set; } | |||||
| public DateTime EnterTime { get; set; } | |||||
| public string? PlateNo { get; set; } | |||||
| public Guid? ActivityId { get; set; } | |||||
| public string? RateCode { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class CouponDetail | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public string? Seq { get; set; } | |||||
| public string? TransactionId { get; set; } | |||||
| public string? SerialNo { get; set; } | |||||
| public string? ExternalSystemKey { get; set; } | |||||
| public string? Type { get; set; } | |||||
| public string? Status { get; set; } | |||||
| public string? SiteId { get; set; } | |||||
| public DateTime LogTime { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,135 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using Microsoft.EntityFrameworkCore; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class CouponMiddlewareContext : DbContext | |||||
| { | |||||
| public CouponMiddlewareContext() | |||||
| { | |||||
| } | |||||
| public CouponMiddlewareContext(DbContextOptions<CouponMiddlewareContext> options) | |||||
| : base(options) | |||||
| { | |||||
| } | |||||
| public virtual DbSet<CouponConsume> CouponConsumes { get; set; } | |||||
| public virtual DbSet<CouponDetail> CouponDetails { get; set; } | |||||
| public virtual DbSet<CouponSetting> CouponSettings { get; set; } | |||||
| public virtual DbSet<HospitalQrCode> HospitalQrCodes { get; set; } | |||||
| public virtual DbSet<Log> Logs { get; set; } | |||||
| public virtual DbSet<Member> Members { get; set; } | |||||
| public virtual DbSet<MemberConsume> MemberConsumes { get; set; } | |||||
| protected override void OnModelCreating(ModelBuilder modelBuilder) | |||||
| { | |||||
| modelBuilder.Entity<CouponConsume>(entity => | |||||
| { | |||||
| entity.ToTable("CouponConsume"); | |||||
| entity.Property(e => e.DeviceId) | |||||
| .HasMaxLength(20) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.DiscountAmount).HasColumnType("decimal(18, 2)"); | |||||
| entity.Property(e => e.ExternalSystemKey).HasMaxLength(500); | |||||
| entity.Property(e => e.PlateNo).HasMaxLength(20); | |||||
| entity.Property(e => e.SerialNo) | |||||
| .HasMaxLength(50) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.SiteId) | |||||
| .HasMaxLength(50) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.TotalAmount).HasColumnType("decimal(18, 2)"); | |||||
| }); | |||||
| modelBuilder.Entity<CouponDetail>(entity => | |||||
| { | |||||
| entity.ToTable("CouponDetail"); | |||||
| entity.Property(e => e.ExternalSystemKey).HasMaxLength(500); | |||||
| entity.Property(e => e.Seq).HasMaxLength(3); | |||||
| entity.Property(e => e.SerialNo).HasMaxLength(50); | |||||
| entity.Property(e => e.Status) | |||||
| .HasMaxLength(10) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.TransactionId).HasMaxLength(20); | |||||
| entity.Property(e => e.Type) | |||||
| .HasMaxLength(15) | |||||
| .IsUnicode(false); | |||||
| }); | |||||
| modelBuilder.Entity<CouponSetting>(entity => | |||||
| { | |||||
| entity.ToTable("CouponSetting"); | |||||
| entity.Property(e => e.Name).HasMaxLength(20); | |||||
| entity.Property(e => e.Url).HasMaxLength(500); | |||||
| }); | |||||
| modelBuilder.Entity<HospitalQrCode>(entity => | |||||
| { | |||||
| entity.ToTable("HospitalQrCode"); | |||||
| entity.Property(e => e.ActivityName).HasMaxLength(50); | |||||
| entity.Property(e => e.QrCode).HasMaxLength(100); | |||||
| }); | |||||
| modelBuilder.Entity<Log>(entity => | |||||
| { | |||||
| entity.ToTable("Log"); | |||||
| entity.Property(e => e.DeviceId) | |||||
| .HasMaxLength(30) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.DiscountAmount).HasColumnType("decimal(18, 2)"); | |||||
| entity.Property(e => e.ExternalSystemKey).HasMaxLength(500); | |||||
| entity.Property(e => e.LogType) | |||||
| .HasMaxLength(10) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.PlateNo).HasMaxLength(20); | |||||
| entity.Property(e => e.SerialNo) | |||||
| .HasMaxLength(50) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.SiteId) | |||||
| .HasMaxLength(10) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.Status) | |||||
| .HasMaxLength(10) | |||||
| .IsUnicode(false); | |||||
| entity.Property(e => e.TotalAmount).HasColumnType("decimal(18, 2)"); | |||||
| entity.Property(e => e.Type) | |||||
| .HasMaxLength(10) | |||||
| .IsUnicode(false); | |||||
| }); | |||||
| modelBuilder.Entity<Member>(entity => | |||||
| { | |||||
| entity.ToTable("Member"); | |||||
| entity.Property(e => e.MemberId).HasMaxLength(50); | |||||
| }); | |||||
| modelBuilder.Entity<MemberConsume>(entity => | |||||
| { | |||||
| entity.ToTable("MemberConsume"); | |||||
| entity.Property(e => e.MemberId).HasMaxLength(50); | |||||
| entity.Property(e => e.PlateNo).HasMaxLength(20); | |||||
| entity.Property(e => e.SerialNo) | |||||
| .HasMaxLength(50) | |||||
| .IsUnicode(false); | |||||
| }); | |||||
| OnModelCreatingPartial(modelBuilder); | |||||
| } | |||||
| partial void OnModelCreatingPartial(ModelBuilder modelBuilder); | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public class CouponReportViewModel | |||||
| { | |||||
| public DateTime? StartDate { get; set; } | |||||
| public DateTime? EndDate { get; set; } | |||||
| public List<CouponReportItem> ReportItems { get; set; } = new List<CouponReportItem>(); | |||||
| } | |||||
| public class CouponReportItem | |||||
| { | |||||
| public int RowNumber { get; set; } | |||||
| public string? TenantCode { get; set; } | |||||
| public string? CarNumber { get; set; } | |||||
| public DateTime? InvoiceDate { get; set; } | |||||
| public string? InvoiceNumber { get; set; } | |||||
| public decimal? InvoiceAmount { get; set; } | |||||
| public string? DiscountUnit { get; set; } | |||||
| public decimal? DiscountAmount { get; set; } | |||||
| public DateTime? DiscountTime { get; set; } | |||||
| public DateTime? EnterTime { get; set; } | |||||
| public DateTime? ExitTime { get; set; } | |||||
| public decimal? ParkingAmount { get; set; } | |||||
| public decimal? ClaimAmount { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class CouponSetting | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public Guid ActivityId { get; set; } | |||||
| public string? Url { get; set; } | |||||
| public string? Name { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class HospitalQrCode | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public DateTime StartTime { get; set; } | |||||
| public DateTime EndTime { get; set; } | |||||
| public string? QrCode { get; set; } | |||||
| public string? ActivityName { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,41 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class Log | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public string? SiteId { get; set; } | |||||
| public DateTime LogTime { get; set; } | |||||
| public string? LogInfo { get; set; } | |||||
| public string? LogType { get; set; } | |||||
| public string? Type { get; set; } | |||||
| public string? Status { get; set; } | |||||
| public string? DeviceId { get; set; } | |||||
| public decimal? TotalAmount { get; set; } | |||||
| public decimal? DiscountAmount { get; set; } | |||||
| public string? ExternalSystemKey { get; set; } | |||||
| public string? SerialNo { get; set; } | |||||
| public DateTime? EnterTime { get; set; } | |||||
| public string? PlateNo { get; set; } | |||||
| public string? RateCode { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class Member | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public string? MemberId { get; set; } | |||||
| public DateTime StartDate { get; set; } | |||||
| public DateTime EndDate { get; set; } | |||||
| public int? Quantity { get; set; } | |||||
| public bool IsDeleted { get; set; } | |||||
| public int? RemainingCoupon { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.CouponMiddleware; | |||||
| public partial class MemberConsume | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public DateTime CreateDateTimeUtc { get; set; } | |||||
| public DateTime ModifyDateTimeUtc { get; set; } | |||||
| public string? MemberId { get; set; } | |||||
| public string? PlateNo { get; set; } | |||||
| public DateTime ConsumeTime { get; set; } | |||||
| public string? SerialNo { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| namespace LaneFlowReport.Models; | |||||
| public class ErrorViewModel | |||||
| { | |||||
| public string? RequestId { get; set; } | |||||
| public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| using System.ComponentModel.DataAnnotations; | |||||
| namespace LaneFlowReport.Models; | |||||
| public class LoginViewModel | |||||
| { | |||||
| [Required] | |||||
| public string Username { get; set; } | |||||
| [Required] | |||||
| public string Password { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class AggregatedCounter | |||||
| { | |||||
| public string Key { get; set; } = null!; | |||||
| public long Value { get; set; } | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class AlertLpr | |||||
| { | |||||
| /// <summary> | |||||
| /// 車牌序號 | |||||
| /// </summary> | |||||
| public string LprId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 通知編號 | |||||
| /// </summary> | |||||
| public string? AlertId { get; set; } | |||||
| /// <summary> | |||||
| /// 車牌號碼,以","分開 | |||||
| /// </summary> | |||||
| public string? LprNo { get; set; } | |||||
| /// <summary> | |||||
| /// 運算欄位,無逗號的車牌 | |||||
| /// </summary> | |||||
| public string? LprNo2 { get; set; } | |||||
| public virtual AlertSetting? Alert { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class AlertSetting | |||||
| { | |||||
| /// <summary> | |||||
| /// 通知編號 | |||||
| /// </summary> | |||||
| public string AlertId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 類型 1,依照車格 2 依照車號 | |||||
| /// </summary> | |||||
| public int? GroupBy { get; set; } | |||||
| /// <summary> | |||||
| /// 名稱 | |||||
| /// </summary> | |||||
| public string? AlertName { get; set; } | |||||
| /// <summary> | |||||
| /// 啟動 | |||||
| /// </summary> | |||||
| public string? Enbale { get; set; } | |||||
| /// <summary> | |||||
| /// 進場警示 | |||||
| /// </summary> | |||||
| public string? Inbound { get; set; } | |||||
| /// <summary> | |||||
| /// 出場警示 | |||||
| /// </summary> | |||||
| public string? Outbound { get; set; } | |||||
| /// <summary> | |||||
| /// 通知訊息 | |||||
| /// </summary> | |||||
| public string? Message { get; set; } | |||||
| public virtual ICollection<AlertLpr> AlertLprs { get; set; } = new List<AlertLpr>(); | |||||
| public virtual ICollection<ParkingLot> Lots { get; set; } = new List<ParkingLot>(); | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Camera | |||||
| { | |||||
| /// <summary> | |||||
| /// 攝影機序號 | |||||
| /// </summary> | |||||
| public string CameraId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// IP | |||||
| /// </summary> | |||||
| public string Ip { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 種類 | |||||
| /// </summary> | |||||
| public int? CameraTypeId { get; set; } | |||||
| /// <summary> | |||||
| /// 啟用否 | |||||
| /// </summary> | |||||
| public string? Enable { get; set; } | |||||
| /// <summary> | |||||
| /// AI的IP,暫時為背景寫入值 | |||||
| /// </summary> | |||||
| public string AiLocation { get; set; } = null!; | |||||
| public virtual TypeDef? CameraType { get; set; } | |||||
| public virtual ICollection<ParkingLot> ParkingLots { get; set; } = new List<ParkingLot>(); | |||||
| } | |||||
| @@ -0,0 +1,81 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class CarEnter | |||||
| { | |||||
| public string StationId { get; set; } = null!; | |||||
| public string CarType { get; set; } = null!; | |||||
| public string CarNo { get; set; } = null!; | |||||
| public string? CarNoSimple { get; set; } | |||||
| public string? GridNumber { get; set; } | |||||
| public string? Etag { get; set; } | |||||
| public string? LaneNo { get; set; } | |||||
| public int ErrorCode { get; set; } | |||||
| public string? SourceDeviceId { get; set; } | |||||
| public int EnterType { get; set; } | |||||
| public int IsStationInside { get; set; } | |||||
| public int IsGridInside { get; set; } | |||||
| public DateTime EnterDateTime { get; set; } | |||||
| public DateTime? ValuationDateTime { get; set; } | |||||
| public DateTime? DepartureDateTime { get; set; } | |||||
| public DateTime? LimitedTimeDeparture { get; set; } | |||||
| public string? EnterPhotoPath { get; set; } | |||||
| public string? EnterPhotoUrlPath { get; set; } | |||||
| public int? Pay { get; set; } | |||||
| public string? PayType { get; set; } | |||||
| public DateTime? PayDateTime { get; set; } | |||||
| public string? PayDeviceId { get; set; } | |||||
| public DateTime? CreationDate { get; set; } | |||||
| public DateTime? UpdateDate { get; set; } | |||||
| public string SerialNo { get; set; } = null!; | |||||
| public string? Remark { get; set; } | |||||
| public int CarIdentity { get; set; } | |||||
| public string? DepartureLaneNo { get; set; } | |||||
| public string? DepartureSourceDeviceId { get; set; } | |||||
| public string? DeparturePhotoPath { get; set; } | |||||
| public string? DeparturePhotoUrlPath { get; set; } | |||||
| /// <summary> | |||||
| /// 資料處理的方式(0:一般, 1:程式處理) | |||||
| /// </summary> | |||||
| public int DataHandleType { get; set; } | |||||
| /// <summary> | |||||
| /// 從雲端建立資料 | |||||
| /// </summary> | |||||
| public int CreateFromCloud { get; set; } | |||||
| public DateTime? OriginalDepartureDateTime { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class CarIdentityCounter | |||||
| { | |||||
| public string StationId { get; set; } = null!; | |||||
| public string CounterId { get; set; } = null!; | |||||
| public string CounterType { get; set; } = null!; | |||||
| public string? CounterName { get; set; } | |||||
| /// <summary> | |||||
| /// 顯示類別 | |||||
| /// </summary> | |||||
| public int DisplayType { get; set; } | |||||
| public decimal TotalQty { get; set; } | |||||
| public decimal UsedQty { get; set; } | |||||
| public decimal ReservedQty { get; set; } | |||||
| public string? AdamIp { get; set; } | |||||
| public int? DoPort { get; set; } | |||||
| public bool SetAsFull { get; set; } | |||||
| public bool Enable { get; set; } | |||||
| /// <summary> | |||||
| /// 預先設定要等排程處理的Flag | |||||
| /// </summary> | |||||
| public bool ScheduleSet { get; set; } | |||||
| public string? Setting888 { get; set; } | |||||
| public int? Port { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ChargingBill | |||||
| { | |||||
| public DateTime CreationDate { get; set; } | |||||
| public DateTime? UpdateDate { get; set; } | |||||
| /// <summary> | |||||
| /// 帳單編號 | |||||
| /// </summary> | |||||
| public Guid BillingNo { get; set; } | |||||
| /// <summary> | |||||
| /// 金額 | |||||
| /// </summary> | |||||
| public decimal TotalAmount { get; set; } | |||||
| /// <summary> | |||||
| /// 中控進場序號 | |||||
| /// </summary> | |||||
| public string? CenterSerialNo { get; set; } | |||||
| public string CarNo { get; set; } = null!; | |||||
| public DateTime ValuationDateTime { get; set; } | |||||
| public DateTime LimitedTimeDeparture { get; set; } | |||||
| /// <summary> | |||||
| /// 付款時間 | |||||
| /// </summary> | |||||
| public DateTime? PaidDateTime { get; set; } | |||||
| /// <summary> | |||||
| /// 繳費裝置 | |||||
| /// </summary> | |||||
| public string? PayDeviceId { get; set; } | |||||
| public virtual ICollection<ChargingBillDetail> ChargingBillDetails { get; set; } = new List<ChargingBillDetail>(); | |||||
| public virtual ICollection<ChargingBillSession> ChargingBillSessions { get; set; } = new List<ChargingBillSession>(); | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ChargingBillDetail | |||||
| { | |||||
| public DateTime CreationDate { get; set; } | |||||
| /// <summary> | |||||
| /// 帳單編號 | |||||
| /// </summary> | |||||
| public Guid BillingNo { get; set; } | |||||
| /// <summary> | |||||
| /// 行號 | |||||
| /// </summary> | |||||
| public int LineNo { get; set; } | |||||
| /// <summary> | |||||
| /// 車格號 | |||||
| /// </summary> | |||||
| public string LotNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 收費類型 | |||||
| /// </summary> | |||||
| public string? FeeType { get; set; } | |||||
| /// <summary> | |||||
| /// 小計 | |||||
| /// </summary> | |||||
| public decimal DetailAmount { get; set; } | |||||
| /// <summary> | |||||
| /// 敘述 | |||||
| /// </summary> | |||||
| public string? ChargingDescription { get; set; } | |||||
| /// <summary> | |||||
| /// 瓦數總和 | |||||
| /// </summary> | |||||
| public int SumKwh { get; set; } | |||||
| /// <summary> | |||||
| /// 秒數總和 | |||||
| /// </summary> | |||||
| public int SumSec { get; set; } | |||||
| public virtual ChargingBill BillingNoNavigation { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ChargingBillSession | |||||
| { | |||||
| public DateTime CreationDate { get; set; } | |||||
| /// <summary> | |||||
| /// 帳單編號 | |||||
| /// </summary> | |||||
| public Guid BillingNo { get; set; } | |||||
| /// <summary> | |||||
| /// 充電資訊ID | |||||
| /// </summary> | |||||
| public Guid SessionId { get; set; } | |||||
| public virtual ChargingBill BillingNoNavigation { get; set; } = null!; | |||||
| public virtual ChargingSession Session { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,31 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ChargingPile | |||||
| { | |||||
| public DateTime? CreationDate { get; set; } | |||||
| public DateTime? UpdateDate { get; set; } | |||||
| /// <summary> | |||||
| /// 車格號 | |||||
| /// </summary> | |||||
| public string LotNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 充電樁序號 | |||||
| /// </summary> | |||||
| public string ChargingPileId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 槍號 | |||||
| /// </summary> | |||||
| public string SubPort { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 啟用 | |||||
| /// </summary> | |||||
| public bool Enable { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ChargingSession | |||||
| { | |||||
| public DateTime CreationDate { get; set; } | |||||
| /// <summary> | |||||
| /// 充電資訊ID | |||||
| /// </summary> | |||||
| public Guid Id { get; set; } | |||||
| /// <summary> | |||||
| /// 外部充電資訊Id | |||||
| /// </summary> | |||||
| public string ExternalSessionId { get; set; } = null!; | |||||
| public string? ExternalSerialNo { get; set; } | |||||
| /// <summary> | |||||
| /// 進場序號 | |||||
| /// </summary> | |||||
| public string? SerialNo { get; set; } | |||||
| /// <summary> | |||||
| /// 車格號 | |||||
| /// </summary> | |||||
| public string LotNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 起始時間 | |||||
| /// </summary> | |||||
| public DateTime StartTime { get; set; } | |||||
| /// <summary> | |||||
| /// 結束時間 | |||||
| /// </summary> | |||||
| public DateTime EndTime { get; set; } | |||||
| /// <summary> | |||||
| /// 充電秒數 | |||||
| /// </summary> | |||||
| public int? ChargeSec { get; set; } | |||||
| /// <summary> | |||||
| /// 充電瓦數 | |||||
| /// </summary> | |||||
| public int? ChargeKwh { get; set; } | |||||
| /// <summary> | |||||
| /// 充電狀態 | |||||
| /// </summary> | |||||
| public string ChargeStatus { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 是否有充電行為 | |||||
| /// </summary> | |||||
| public bool IsChargeAction { get; set; } | |||||
| public virtual ICollection<ChargingBillSession> ChargingBillSessions { get; set; } = new List<ChargingBillSession>(); | |||||
| } | |||||
| @@ -0,0 +1,56 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class CommonQueue | |||||
| { | |||||
| public long Id { get; set; } | |||||
| /// <summary> | |||||
| /// 進場:In, 出場: Out | |||||
| /// </summary> | |||||
| public string Type { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// InQueue, Processing, Fail | |||||
| /// </summary> | |||||
| public string Status { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 處理時間 | |||||
| /// </summary> | |||||
| public DateTime? ProcessingTime { get; set; } | |||||
| /// <summary> | |||||
| /// 進場序號 | |||||
| /// </summary> | |||||
| public string SerialNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 下次執行時間 | |||||
| /// </summary> | |||||
| public DateTime NextProcessTime { get; set; } | |||||
| public DateTime? CreationTime { get; set; } | |||||
| /// <summary> | |||||
| /// 時間發生時間 | |||||
| /// </summary> | |||||
| public DateTime EventTime { get; set; } | |||||
| /// <summary> | |||||
| /// 失敗次數 | |||||
| /// </summary> | |||||
| public int ErrorCount { get; set; } | |||||
| /// <summary> | |||||
| /// 客制欄位 | |||||
| /// </summary> | |||||
| public string? ExtendField { get; set; } | |||||
| /// <summary> | |||||
| /// 距離可刪除的時間,null 表示不可刪除 | |||||
| /// </summary> | |||||
| public DateTime? ReadyToDeleteTime { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Counter | |||||
| { | |||||
| public string Key { get; set; } = null!; | |||||
| public int Value { get; set; } | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Division | |||||
| { | |||||
| /// <summary> | |||||
| /// 區域編號 | |||||
| /// </summary> | |||||
| public string DivisionId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 名稱 | |||||
| /// </summary> | |||||
| public string DivisionName { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 區域類別 | |||||
| /// </summary> | |||||
| public int? DivisionType { get; set; } | |||||
| public string? Enable { get; set; } | |||||
| public virtual ICollection<DivisionDisplay> DivisionDisplays { get; set; } = new List<DivisionDisplay>(); | |||||
| public virtual TypeDef? DivisionTypeNavigation { get; set; } | |||||
| public virtual ICollection<ParkingLot> Lots { get; set; } = new List<ParkingLot>(); | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class DivisionDisplay | |||||
| { | |||||
| /// <summary> | |||||
| /// 區域序號 | |||||
| /// </summary> | |||||
| public string DivisionId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 顯示器編號 | |||||
| /// </summary> | |||||
| public string DisplayId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// ADAM IP | |||||
| /// </summary> | |||||
| public string AdamIp { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// Do Port | |||||
| /// </summary> | |||||
| public int? DoPort { get; set; } | |||||
| /// <summary> | |||||
| /// 顯示類別 | |||||
| /// </summary> | |||||
| public int? DisplayType { get; set; } | |||||
| /// <summary> | |||||
| /// 啟用 | |||||
| /// </summary> | |||||
| public string? Enable { get; set; } | |||||
| /// <summary> | |||||
| /// 強制滿位 | |||||
| /// </summary> | |||||
| public string? SetAsFull { get; set; } | |||||
| /// <summary> | |||||
| /// 保留車格 | |||||
| /// </summary> | |||||
| public int? ReserveQty { get; set; } | |||||
| /// <summary> | |||||
| /// 警報秒數 | |||||
| /// </summary> | |||||
| public int? AlarmSec { get; set; } | |||||
| /// <summary> | |||||
| /// 預先設定要等PE處理的Flag | |||||
| /// </summary> | |||||
| public int ScheduleSet { get; set; } | |||||
| public int? Port { get; set; } | |||||
| public virtual Division Division { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,66 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class EventLog | |||||
| { | |||||
| /// <summary> | |||||
| /// 事件編號 | |||||
| /// </summary> | |||||
| public string EventId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 事件類別 | |||||
| /// </summary> | |||||
| public int EventCode { get; set; } | |||||
| /// <summary> | |||||
| /// 車格號 | |||||
| /// </summary> | |||||
| public string? LotId { get; set; } | |||||
| /// <summary> | |||||
| /// 車牌號碼 | |||||
| /// </summary> | |||||
| public string? LprNo { get; set; } | |||||
| /// <summary> | |||||
| /// 裝置編號 | |||||
| /// </summary> | |||||
| public string? DeviceId { get; set; } | |||||
| /// <summary> | |||||
| /// 資訊 | |||||
| /// </summary> | |||||
| public string Info { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 事件時間(可能調整) | |||||
| /// </summary> | |||||
| public DateTime? EventDate { get; set; } | |||||
| /// <summary> | |||||
| /// 建立日期 | |||||
| /// </summary> | |||||
| public DateTime CreateDate { get; set; } | |||||
| /// <summary> | |||||
| /// 雲端同步日期 | |||||
| /// </summary> | |||||
| public DateTime? CloudSyncTime { get; set; } | |||||
| /// <summary> | |||||
| /// 歸連事件編號,通常應用在出場事件 | |||||
| /// </summary> | |||||
| public string? RelatedEventId { get; set; } | |||||
| /// <summary> | |||||
| /// 異常事件解除日期 | |||||
| /// </summary> | |||||
| public DateTime? UnlockDate { get; set; } | |||||
| public virtual TypeDef EventCodeNavigation { get; set; } = null!; | |||||
| public virtual ParkingLot? Lot { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Hash | |||||
| { | |||||
| public string Key { get; set; } = null!; | |||||
| public string Field { get; set; } = null!; | |||||
| public string? Value { get; set; } | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,25 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Job | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public long? StateId { get; set; } | |||||
| public string? StateName { get; set; } | |||||
| public string InvocationData { get; set; } = null!; | |||||
| public string Arguments { get; set; } = null!; | |||||
| public DateTime CreatedAt { get; set; } | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| public virtual ICollection<JobParameter> JobParameters { get; set; } = new List<JobParameter>(); | |||||
| public virtual ICollection<State> States { get; set; } = new List<State>(); | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class JobParameter | |||||
| { | |||||
| public long JobId { get; set; } | |||||
| public string Name { get; set; } = null!; | |||||
| public string? Value { get; set; } | |||||
| public virtual Job Job { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class JobQueue | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public long JobId { get; set; } | |||||
| public string Queue { get; set; } = null!; | |||||
| public DateTime? FetchedAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class List | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public string Key { get; set; } = null!; | |||||
| public string? Value { get; set; } | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class MergedCarEnter | |||||
| { | |||||
| public string IncomingSerialNo { get; set; } = null!; | |||||
| public string SerialNo { get; set; } = null!; | |||||
| public DateTime CreationDate { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ParkingColumn | |||||
| { | |||||
| public string CameraId { get; set; } = null!; | |||||
| public int State { get; set; } | |||||
| public DateTime PassTime { get; set; } | |||||
| public string? ParkingNo { get; set; } | |||||
| public string? DeviceId { get; set; } | |||||
| /// <summary> | |||||
| /// 最後一次心跳包時間 | |||||
| /// </summary> | |||||
| public DateTime? LastHeartBeat { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,60 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ParkingLot | |||||
| { | |||||
| /// <summary> | |||||
| /// 車格序號 | |||||
| /// </summary> | |||||
| public string LotId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 車格編號 | |||||
| /// </summary> | |||||
| public string? LotNo { get; set; } | |||||
| /// <summary> | |||||
| /// 別名 | |||||
| /// </summary> | |||||
| public string? AliasName { get; set; } | |||||
| /// <summary> | |||||
| /// 啟用否 | |||||
| /// </summary> | |||||
| public string? Enable { get; set; } | |||||
| /// <summary> | |||||
| /// 隸屬攝影機 | |||||
| /// </summary> | |||||
| public string CameraId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 上次事件 | |||||
| /// </summary> | |||||
| public string? LastEvent { get; set; } | |||||
| /// <summary> | |||||
| /// 車格排序 | |||||
| /// </summary> | |||||
| public decimal SortOrder { get; set; } | |||||
| /// <summary> | |||||
| /// 是否為充電車格 | |||||
| /// </summary> | |||||
| public bool IsChargePole { get; set; } | |||||
| /// <summary> | |||||
| /// 是否是後拍 | |||||
| /// </summary> | |||||
| public bool IsBackShot { get; set; } | |||||
| public virtual Camera Camera { get; set; } = null!; | |||||
| public virtual ICollection<EventLog> EventLogs { get; set; } = new List<EventLog>(); | |||||
| public virtual ICollection<AlertSetting> Alerts { get; set; } = new List<AlertSetting>(); | |||||
| public virtual ICollection<Division> Divisions { get; set; } = new List<Division>(); | |||||
| } | |||||
| @@ -0,0 +1,19 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class ParkingStationExtendSetting | |||||
| { | |||||
| public int Id { get; set; } | |||||
| public string Type { get; set; } = null!; | |||||
| public string SiteId { get; set; } = null!; | |||||
| public string Value { get; set; } = null!; | |||||
| public bool IsActive { get; set; } | |||||
| public string? Remark { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,27 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class PaymentDevice | |||||
| { | |||||
| /// <summary> | |||||
| /// 裝置序號 | |||||
| /// </summary> | |||||
| public string DeviceId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// IP位置 | |||||
| /// </summary> | |||||
| public string Ip { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 類別, APS=1 | |||||
| /// </summary> | |||||
| public int DeviceType { get; set; } | |||||
| /// <summary> | |||||
| /// 啟用否 | |||||
| /// </summary> | |||||
| public string Enable { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,33 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class PendingCarRecord | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public string StationId { get; set; } = null!; | |||||
| public string SpotNo { get; set; } = null!; | |||||
| public string? LprNo { get; set; } | |||||
| public DateTime IoTime { get; set; } | |||||
| public string? PicUrl { get; set; } | |||||
| public string? Remark { get; set; } | |||||
| public int CreateFromCloud { get; set; } | |||||
| public DateTime CreationDate { get; set; } | |||||
| public int IsProcessed { get; set; } | |||||
| public DateTime? ProcessedTime { get; set; } | |||||
| public string Iotype { get; set; } = null!; | |||||
| public string GuidNum { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class RoadsideQueue | |||||
| { | |||||
| public long Id { get; set; } | |||||
| /// <summary> | |||||
| /// 開單:OpenTicket, 加簽: AddSign, 結單: CloseTicket, 改單:ChangeTicket, 檢查結單了但未離場: CheckLeave, 繳費通加: PaidNotify | |||||
| /// </summary> | |||||
| public string Type { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// InQueue, Processing, Fail | |||||
| /// </summary> | |||||
| public string Status { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 處理時間 | |||||
| /// </summary> | |||||
| public DateTime? ProcessingTime { get; set; } | |||||
| /// <summary> | |||||
| /// 進場序號 | |||||
| /// </summary> | |||||
| public string SerialNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 路邊單號 | |||||
| /// </summary> | |||||
| public string? TicketNo { get; set; } | |||||
| /// <summary> | |||||
| /// 下次執行時間 | |||||
| /// </summary> | |||||
| public DateTime NextProcessTime { get; set; } | |||||
| public DateTime? CreationTime { get; set; } | |||||
| /// <summary> | |||||
| /// 時間發生時間 | |||||
| /// </summary> | |||||
| public DateTime EventTime { get; set; } | |||||
| /// <summary> | |||||
| /// 失敗次數 | |||||
| /// </summary> | |||||
| public int ErrorCount { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,55 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class RoadsideSignRecord | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public long RoadsideTicketId { get; set; } | |||||
| /// <summary> | |||||
| /// 進場序號 | |||||
| /// </summary> | |||||
| public string SerialNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 路邊單號 | |||||
| /// </summary> | |||||
| public string RoadsideTicketNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 加簽開始時間 | |||||
| /// </summary> | |||||
| public DateTime StartTime { get; set; } | |||||
| /// <summary> | |||||
| /// 加簽分鐘數 | |||||
| /// </summary> | |||||
| public int Minutes { get; set; } | |||||
| /// <summary> | |||||
| /// 路邊的應收金額 | |||||
| /// </summary> | |||||
| public decimal? Receivable { get; set; } | |||||
| /// <summary> | |||||
| /// 繳費機的應收金額 | |||||
| /// </summary> | |||||
| public decimal? DeviceReceivable { get; set; } | |||||
| /// <summary> | |||||
| /// 開單照片實體路徑 | |||||
| /// </summary> | |||||
| public string? SignPhotoPath { get; set; } | |||||
| public DateTime CreateTime { get; set; } | |||||
| /// <summary> | |||||
| /// 簽單是否被刪除(取消) | |||||
| /// </summary> | |||||
| public bool IsDeleted { get; set; } | |||||
| public virtual RoadsideTicket RoadsideTicket { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,133 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class RoadsideTicket | |||||
| { | |||||
| public long Id { get; set; } | |||||
| /// <summary> | |||||
| /// 進場序號 | |||||
| /// </summary> | |||||
| public string SerialNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 路邊單號 | |||||
| /// </summary> | |||||
| public string RoadsideTicketNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 進格時間 | |||||
| /// </summary> | |||||
| public DateTime EnterTime { get; set; } | |||||
| /// <summary> | |||||
| /// 計價身份別 | |||||
| /// </summary> | |||||
| public int CarIdentity { get; set; } | |||||
| /// <summary> | |||||
| /// 開單時間 | |||||
| /// </summary> | |||||
| public DateTime? OpenTime { get; set; } | |||||
| /// <summary> | |||||
| /// 結單時間 | |||||
| /// </summary> | |||||
| public DateTime? CloseTime { get; set; } | |||||
| /// <summary> | |||||
| /// 最新加簽時間 | |||||
| /// </summary> | |||||
| public DateTime? LastestSignTime { get; set; } | |||||
| /// <summary> | |||||
| /// 下次加簽時間 | |||||
| /// </summary> | |||||
| public DateTime? NextSignTime { get; set; } | |||||
| /// <summary> | |||||
| /// 路邊的應收金額 | |||||
| /// </summary> | |||||
| public decimal? Receivable { get; set; } | |||||
| /// <summary> | |||||
| /// 繳費機的應收金額 | |||||
| /// </summary> | |||||
| public decimal? DeviceReceivable { get; set; } | |||||
| /// <summary> | |||||
| /// 開單照片實體路徑 | |||||
| /// </summary> | |||||
| public string? OpenPhotoPath { get; set; } | |||||
| /// <summary> | |||||
| /// 結單照片實體路徑 | |||||
| /// </summary> | |||||
| public string? ClosePhotoPath { get; set; } | |||||
| /// <summary> | |||||
| /// 狀態: Open, Close | |||||
| /// </summary> | |||||
| public string Status { get; set; } = null!; | |||||
| public DateTime CreateTime { get; set; } | |||||
| /// <summary> | |||||
| /// 是否為公務車 | |||||
| /// </summary> | |||||
| public bool IsFree { get; set; } | |||||
| /// <summary> | |||||
| /// 是否自動扣繳 | |||||
| /// </summary> | |||||
| public bool IsAutoPay { get; set; } | |||||
| /// <summary> | |||||
| /// 場站代碼 | |||||
| /// </summary> | |||||
| public string SiteId { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 車號 | |||||
| /// </summary> | |||||
| public string CarNo { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 車種 | |||||
| /// </summary> | |||||
| public string? CarType { get; set; } | |||||
| /// <summary> | |||||
| /// 繳費時間 | |||||
| /// </summary> | |||||
| public DateTime? PaidTime { get; set; } | |||||
| /// <summary> | |||||
| /// 繳費期限 | |||||
| /// </summary> | |||||
| public DateTime? PaidDueDate { get; set; } | |||||
| /// <summary> | |||||
| /// 卡片號碼 | |||||
| /// </summary> | |||||
| public string? CardNumber { get; set; } | |||||
| /// <summary> | |||||
| /// 卡片序號 | |||||
| /// </summary> | |||||
| public string? CardSn { get; set; } | |||||
| /// <summary> | |||||
| /// 上次查詢時間 | |||||
| /// </summary> | |||||
| public DateTime? LastQueryTime { get; set; } | |||||
| /// <summary> | |||||
| /// 傳給路邊的付款時間 | |||||
| /// </summary> | |||||
| public DateTime? SendToRoadsidePaidTime { get; set; } | |||||
| public virtual ICollection<RoadsideSignRecord> RoadsideSignRecords { get; set; } = new List<RoadsideSignRecord>(); | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Schema | |||||
| { | |||||
| public int Version { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,13 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Server | |||||
| { | |||||
| public string Id { get; set; } = null!; | |||||
| public string? Data { get; set; } | |||||
| public DateTime LastHeartbeat { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class Set | |||||
| { | |||||
| public string Key { get; set; } = null!; | |||||
| public double Score { get; set; } | |||||
| public string Value { get; set; } = null!; | |||||
| public DateTime? ExpireAt { get; set; } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class State | |||||
| { | |||||
| public long Id { get; set; } | |||||
| public long JobId { get; set; } | |||||
| public string Name { get; set; } = null!; | |||||
| public string? Reason { get; set; } | |||||
| public DateTime CreatedAt { get; set; } | |||||
| public string? Data { get; set; } | |||||
| public virtual Job Job { get; set; } = null!; | |||||
| } | |||||
| @@ -0,0 +1,28 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| namespace CouponReport.Models.Parkingeyes; | |||||
| public partial class TypeDef | |||||
| { | |||||
| /// <summary> | |||||
| /// 類別編號 | |||||
| /// </summary> | |||||
| public int TypeId { get; set; } | |||||
| /// <summary> | |||||
| /// 類別名稱 | |||||
| /// </summary> | |||||
| public string TypeName { get; set; } = null!; | |||||
| /// <summary> | |||||
| /// 主要類別 | |||||
| /// </summary> | |||||
| public int CategoryId { get; set; } | |||||
| public virtual ICollection<Camera> Cameras { get; set; } = new List<Camera>(); | |||||
| public virtual ICollection<Division> Divisions { get; set; } = new List<Division>(); | |||||
| public virtual ICollection<EventLog> EventLogs { get; set; } = new List<EventLog>(); | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| namespace LaneFlowReport.Options { | |||||
| public class OauthOption { | |||||
| public string ParkingLoginUrl { get; set; } | |||||
| public string ParkingOAuthUrl { get; set; } | |||||
| public string ClientUrl { get; set; } | |||||
| public string ClientSecret { get; set; } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| using CouponReport.Models.CouponMiddleware; | |||||
| using CouponReport.Models.Parkingeyes; | |||||
| using CouponReport.Service; | |||||
| using LaneFlowReport.Auth; | |||||
| using LaneFlowReport.Options; | |||||
| using Microsoft.AspNetCore.Authentication.Cookies; | |||||
| using Microsoft.EntityFrameworkCore; | |||||
| var builder = WebApplication.CreateBuilder(args); | |||||
| builder.Services.AddHttpClient(); | |||||
| builder.Services.AddScoped<ReportService>(); | |||||
| builder.Services.AddSingleton<ParkingAuthProvider>(); | |||||
| builder.Services.Configure<OauthOption>(builder.Configuration.GetSection("Authentication:Parking")); | |||||
| builder.Services.AddDbContext<CouponMiddlewareContext>(options => | |||||
| options.UseSqlServer(builder.Configuration.GetConnectionString("myConnection"))); | |||||
| builder.Services.AddDbContext<ParkingEyesContext>(options => | |||||
| options.UseSqlServer(builder.Configuration.GetConnectionString("ParkingEyesConnection"))); | |||||
| builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) | |||||
| .AddCookie(options => | |||||
| { | |||||
| options.LoginPath = "/Home/Index"; | |||||
| options.AccessDeniedPath = "/Home/Index"; | |||||
| options.Cookie.HttpOnly = true; | |||||
| options.Cookie.IsEssential = true; | |||||
| options.Cookie.SameSite = SameSiteMode.Strict; | |||||
| options.SlidingExpiration = true; | |||||
| options.Cookie.Expiration = null; | |||||
| }); | |||||
| // Add services to the container. | |||||
| builder.Services.AddControllersWithViews(); | |||||
| builder.Services.AddSession(options => | |||||
| { | |||||
| options.Cookie.HttpOnly = true; | |||||
| options.Cookie.IsEssential = true; | |||||
| }); | |||||
| var app = builder.Build(); | |||||
| // Configure the HTTP request pipeline. | |||||
| if (!app.Environment.IsDevelopment()) | |||||
| { | |||||
| app.UseExceptionHandler("/Home/Error"); | |||||
| app.UseHsts(); | |||||
| } | |||||
| app.UseHttpsRedirection(); | |||||
| app.UseStaticFiles(); | |||||
| app.UseRouting(); | |||||
| app.UseSession(); | |||||
| app.UseAuthentication(); | |||||
| app.UseAuthorization(); | |||||
| app.MapControllerRoute( | |||||
| name: "default", | |||||
| pattern: "{controller=Home}/{action=Index}/{id?}"); | |||||
| app.Run(); | |||||
| @@ -0,0 +1,38 @@ | |||||
| { | |||||
| "$schema": "http://json.schemastore.org/launchsettings.json", | |||||
| "iisSettings": { | |||||
| "windowsAuthentication": false, | |||||
| "anonymousAuthentication": true, | |||||
| "iisExpress": { | |||||
| "applicationUrl": "http://localhost:58782", | |||||
| "sslPort": 44300 | |||||
| } | |||||
| }, | |||||
| "profiles": { | |||||
| "http": { | |||||
| "commandName": "Project", | |||||
| "dotnetRunMessages": true, | |||||
| "launchBrowser": true, | |||||
| "applicationUrl": "http://localhost:5249", | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| }, | |||||
| "https": { | |||||
| "commandName": "Project", | |||||
| "dotnetRunMessages": true, | |||||
| "launchBrowser": true, | |||||
| "applicationUrl": "https://localhost:7055;http://localhost:5249", | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| }, | |||||
| "IIS Express": { | |||||
| "commandName": "IISExpress", | |||||
| "launchBrowser": true, | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,76 @@ | |||||
| using System.Globalization; | |||||
| using System.Security.Cryptography; | |||||
| using System.Text; | |||||
| namespace CouponReport.Service | |||||
| { | |||||
| public class ReportService | |||||
| { | |||||
| public string GetTenantCode(string externalSystemKey) | |||||
| { | |||||
| var result = DESDecode(externalSystemKey); | |||||
| var tenantNo = result.Split("|")[7]; | |||||
| return tenantNo.Substring(0, 8); | |||||
| } | |||||
| public string GetInvoiceNo(string externalSystemKey) | |||||
| { | |||||
| var result = DESDecode(externalSystemKey); | |||||
| return result.Split("|")[3]; | |||||
| } | |||||
| public decimal GetInvoiceMoney(string externalSystemKey) | |||||
| { | |||||
| var result = DESDecode(externalSystemKey); | |||||
| return decimal.Parse(result.Split("|")[4]); | |||||
| } | |||||
| public DateTime GetInvoiceDateTime(string externalSystemKey) | |||||
| { | |||||
| var result = DESDecode(externalSystemKey); | |||||
| return DateTime.ParseExact(result.Split("|")[5], "yyyyMMddHHmmss", CultureInfo.InvariantCulture); | |||||
| } | |||||
| private string DESDecode(string externalSystemKey) | |||||
| { | |||||
| if (string.IsNullOrWhiteSpace(externalSystemKey)) | |||||
| { | |||||
| return string.Empty; | |||||
| } | |||||
| var key = "RAdWIMPs"; | |||||
| var cipherHex = externalSystemKey.StartsWith("YLC", StringComparison.OrdinalIgnoreCase) | |||||
| ? externalSystemKey.Substring(3) | |||||
| : externalSystemKey; | |||||
| if (cipherHex.Length % 2 != 0) | |||||
| { | |||||
| throw new ArgumentException("Invalid coupon cipher text.", nameof(externalSystemKey)); | |||||
| } | |||||
| var cipherBytes = new byte[cipherHex.Length / 2]; | |||||
| for (var i = 0; i < cipherBytes.Length; i++) | |||||
| { | |||||
| cipherBytes[i] = Convert.ToByte(cipherHex.Substring(i * 2, 2), 16); | |||||
| } | |||||
| using var desProvider = new DESCryptoServiceProvider | |||||
| { | |||||
| Key = Encoding.ASCII.GetBytes(key), | |||||
| IV = Encoding.ASCII.GetBytes(key), | |||||
| Mode = CipherMode.CBC, | |||||
| Padding = PaddingMode.PKCS7 | |||||
| }; | |||||
| using var memoryStream = new MemoryStream(); | |||||
| using (var cryptoStream = new CryptoStream(memoryStream, desProvider.CreateDecryptor(), CryptoStreamMode.Write)) | |||||
| { | |||||
| cryptoStream.Write(cipherBytes, 0, cipherBytes.Length); | |||||
| cryptoStream.FlushFinalBlock(); | |||||
| } | |||||
| return Encoding.UTF8.GetString(memoryStream.ToArray()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,29 @@ | |||||
| @model LaneFlowReport.Models.LoginViewModel | |||||
| <div class="container text-center mt-5"> | |||||
| <h1 class="display-4">@ViewBag.Title</h1> | |||||
| </div> | |||||
| <form method="post" class="mt3"> | |||||
| <div class="form-group"> | |||||
| <label for="Username">使用者名稱:</label> | |||||
| <input type="text" id="Username" name="Username" class="form-control small" placeholder="請輸入帳號" value="@Model.Username" required /> | |||||
| </div> | |||||
| <div class="form-group"> | |||||
| <label for="Password">密碼:</label> | |||||
| <input type="password" id="Password" name="Password" class="form-control small" required /> | |||||
| </div> | |||||
| <div class="text-center mt-2"> | |||||
| <button type="submit" class="btn btn-primary mx-1">登入</button> | |||||
| <button id="loginBtn" type="button" class="btn btn-secondary">Line</button> | |||||
| </div> | |||||
| @if (ViewData.ModelState.ErrorCount > 0) | |||||
| { | |||||
| <div style="color:red;">@Html.ValidationSummary()</div> | |||||
| } | |||||
| </form> | |||||
| <script> | |||||
| document.getElementById("loginBtn").addEventListener("click", function () { | |||||
| const currentUrl = `${location.origin}/Login/LoginCallback`; | |||||
| location.href = '@ViewBag.OauthUrl' + `?redirect_uri=${currentUrl}`; | |||||
| }); | |||||
| </script> | |||||
| @@ -0,0 +1,20 @@ | |||||
| @page | |||||
| <!DOCTYPE html> | |||||
| <html> | |||||
| <head> | |||||
| <title>@ViewBag.Status</title> | |||||
| </head> | |||||
| <body> | |||||
| <div> | |||||
| <h1>@ViewBag.Status</h1> | |||||
| </div> | |||||
| </body> | |||||
| </html> | |||||
| <script> | |||||
| setTimeout(function () { | |||||
| window.location.href = '@ViewBag.RedirectUrl'; | |||||
| }, 1000); | |||||
| </script> | |||||
| @@ -0,0 +1,7 @@ | |||||
| using Microsoft.AspNetCore.Mvc.RazorPages; | |||||
| namespace LaneFlowReport.Views.Login { | |||||
| public class LoginCallback: PageModel { | |||||
| public void OnGet() {} | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,132 @@ | |||||
| @using CouponReport.Models.CouponMiddleware | |||||
| @model CouponReportViewModel | |||||
| @{ | |||||
| ViewData["Title"] = "折扣報表"; | |||||
| } | |||||
| <h2>折扣報表</h2> | |||||
| <div class="card mb-4"> | |||||
| <div class="card-body"> | |||||
| <form method="post" asp-action="Index"> | |||||
| <div class="row"> | |||||
| <div class="col-md-4"> | |||||
| <div class="form-group"> | |||||
| <label for="startDate">開始日期</label> | |||||
| <input type="date" class="form-control" id="startDate" name="startDate" | |||||
| value="@Model.StartDate?.ToString("yyyy-MM-dd")" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4"> | |||||
| <div class="form-group"> | |||||
| <label for="endDate">結束日期</label> | |||||
| <input type="date" class="form-control" id="endDate" name="endDate" | |||||
| value="@Model.EndDate?.ToString("yyyy-MM-dd")" /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4"> | |||||
| <label> </label> | |||||
| <div> | |||||
| <button type="submit" class="btn btn-primary">查詢</button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| @if (Model.ReportItems != null && Model.ReportItems.Any()) | |||||
| { | |||||
| <div class="mb-3"> | |||||
| <a href="@Url.Action("ExportToExcel", new { startDate = Model.StartDate, endDate = Model.EndDate })" | |||||
| class="btn btn-success"> | |||||
| <i class="bi bi-file-excel"></i> 匯出 Excel | |||||
| </a> | |||||
| <a href="@Url.Action("ExportToPdf", new { startDate = Model.StartDate, endDate = Model.EndDate })" | |||||
| class="btn btn-danger"> | |||||
| <i class="bi bi-file-pdf"></i> 匯出 PDF | |||||
| </a> | |||||
| </div> | |||||
| <style> | |||||
| .group-row-even { | |||||
| background-color: #f8f9fa; | |||||
| } | |||||
| .group-row-odd { | |||||
| background-color: #ffffff; | |||||
| } | |||||
| </style> | |||||
| <div class="table-responsive"> | |||||
| <table class="table table-bordered"> | |||||
| <thead> | |||||
| <tr> | |||||
| <th></th> | |||||
| <th>店別(統編)</th> | |||||
| <th>車號</th> | |||||
| <th>發票日期</th> | |||||
| <th>發票號碼</th> | |||||
| <th>發票金額</th> | |||||
| <th>折扣單位</th> | |||||
| <th>折扣金額</th> | |||||
| <th>折扣時間</th> | |||||
| <th>入場時間</th> | |||||
| <th>出場時間</th> | |||||
| <th>停車金額</th> | |||||
| <th>請款金額</th> | |||||
| </tr> | |||||
| </thead> | |||||
| <tbody> | |||||
| @{ | |||||
| int currentRowNumber = -1; | |||||
| int rowSpanCount = 0; | |||||
| int groupIndex = 0; | |||||
| } | |||||
| @foreach (var item in Model.ReportItems) | |||||
| { | |||||
| bool isNewGroup = item.RowNumber != currentRowNumber; | |||||
| if (isNewGroup) | |||||
| { | |||||
| // 計算這個編號有多少筆資料 | |||||
| rowSpanCount = Model.ReportItems.Count(x => x.RowNumber == item.RowNumber); | |||||
| currentRowNumber = item.RowNumber; | |||||
| groupIndex++; | |||||
| } | |||||
| var rowClass = (groupIndex % 2 == 0) ? "group-row-even" : "group-row-odd"; | |||||
| <tr class="@rowClass"> | |||||
| @if (isNewGroup) | |||||
| { | |||||
| <td rowspan="@rowSpanCount" class="align-middle">@item.RowNumber</td> | |||||
| } | |||||
| <td>@item.TenantCode</td> | |||||
| @if (isNewGroup) | |||||
| { | |||||
| <td rowspan="@rowSpanCount" class="align-middle">@item.CarNumber</td> | |||||
| } | |||||
| <td>@item.InvoiceDate?.ToString("yyyy-MM-dd HH:mm:ss")</td> | |||||
| <td>@item.InvoiceNumber</td> | |||||
| <td class="text-end">@item.InvoiceAmount?.ToString("N0")</td> | |||||
| @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> | |||||
| } | |||||
| </tr> | |||||
| } | |||||
| </tbody> | |||||
| </table> | |||||
| </div> | |||||
| <div class="mt-3"> | |||||
| <p>總筆數: <strong>@Model.ReportItems.Count</strong></p> | |||||
| </div> | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| @{ | |||||
| ViewData["Title"] = "Error"; | |||||
| } | |||||
| <h1 class="text-danger">Error.</h1> | |||||
| <h2 class="text-danger">An error occurred while processing your request.</h2> | |||||
| @if (Model.ShowRequestId) | |||||
| { | |||||
| <p> | |||||
| <strong>Request ID:</strong> <code>@Model.RequestId</code> | |||||
| </p> | |||||
| } | |||||
| <h3>Development Mode</h3> | |||||
| <p> | |||||
| Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred. | |||||
| </p> | |||||
| <p> | |||||
| <strong>The Development environment shouldn't be enabled for deployed applications.</strong> | |||||
| It can result in displaying sensitive information from exceptions to end users. | |||||
| For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> | |||||
| and restarting the app. | |||||
| </p> | |||||
| @@ -0,0 +1,35 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="utf-8" /> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
| <title>CouponReport</title> | |||||
| <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> | |||||
| <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> | |||||
| <link rel="stylesheet" href="~/CouponReport.styles.css" asp-append-version="true" /> | |||||
| </head> | |||||
| <body> | |||||
| <header> | |||||
| <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> | |||||
| <div class="container-fluid"> | |||||
| <a class="navbar-brand" asp-area="" asp-controller="Report" asp-action="Index">折扣報表系統</a> | |||||
| </div> | |||||
| </nav> | |||||
| </header> | |||||
| <div class="container-fluid px-4"> | |||||
| <main role="main" class="pb-3"> | |||||
| @RenderBody() | |||||
| </main> | |||||
| </div> | |||||
| <footer class="border-top footer text-muted"> | |||||
| <div class="container-fluid px-4"> | |||||
| © 2025 - 折扣報表系統 | |||||
| </div> | |||||
| </footer> | |||||
| <script src="~/lib/jquery/dist/jquery.min.js"></script> | |||||
| <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> | |||||
| <script src="~/js/site.js" asp-append-version="true"></script> | |||||
| @await RenderSectionAsync("Scripts", required: false) | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,48 @@ | |||||
| /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification | |||||
| for details on configuring this project to bundle and minify static web assets. */ | |||||
| a.navbar-brand { | |||||
| white-space: normal; | |||||
| text-align: center; | |||||
| word-break: break-all; | |||||
| } | |||||
| a { | |||||
| color: #0077cc; | |||||
| } | |||||
| .btn-primary { | |||||
| color: #fff; | |||||
| background-color: #1b6ec2; | |||||
| border-color: #1861ac; | |||||
| } | |||||
| .nav-pills .nav-link.active, .nav-pills .show > .nav-link { | |||||
| color: #fff; | |||||
| background-color: #1b6ec2; | |||||
| border-color: #1861ac; | |||||
| } | |||||
| .border-top { | |||||
| border-top: 1px solid #e5e5e5; | |||||
| } | |||||
| .border-bottom { | |||||
| border-bottom: 1px solid #e5e5e5; | |||||
| } | |||||
| .box-shadow { | |||||
| box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); | |||||
| } | |||||
| button.accept-policy { | |||||
| font-size: 1rem; | |||||
| line-height: inherit; | |||||
| } | |||||
| .footer { | |||||
| position: absolute; | |||||
| bottom: 0; | |||||
| width: 100%; | |||||
| white-space: nowrap; | |||||
| line-height: 60px; | |||||
| } | |||||
| @@ -0,0 +1,2 @@ | |||||
| <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script> | |||||
| <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> | |||||
| @@ -0,0 +1,3 @@ | |||||
| @using CouponReport | |||||
| @using CouponReport.Models | |||||
| @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers | |||||
| @@ -0,0 +1,3 @@ | |||||
| @{ | |||||
| Layout = "_Layout"; | |||||
| } | |||||
| @@ -0,0 +1,12 @@ | |||||
| { | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Information", | |||||
| "Microsoft.AspNetCore": "Warning" | |||||
| } | |||||
| }, | |||||
| "ConnectionStrings": { | |||||
| "myConnection": "Server=MSI\\SQLEXPRESS;Database=CouponMiddleware;User ID=carin;Password=Altob80682490;TrustServerCertificate=true;MultipleActiveResultSets=true;", | |||||
| "ParkingEyesConnection": "Server=MSI\\SQLEXPRESS; Database=ParkingEyes; uid=carin; pwd=Altob80682490; TrustServerCertificate=true; MultipleActiveResultSets=true" | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,17 @@ | |||||
| { | |||||
| "Logging": { | |||||
| "LogLevel": { | |||||
| "Default": "Information", | |||||
| "Microsoft.AspNetCore": "Warning" | |||||
| } | |||||
| }, | |||||
| "AllowedHosts": "*", | |||||
| "Authentication": { | |||||
| "Parking": { | |||||
| "ParkingLoginUrl": "https://serviceapi.altob.com.tw/api/TokenAuth/LoginValidate", | |||||
| "ParkingOAuthUrl": "https://serviceot.altob.com.tw/account/oauthLogin", | |||||
| "ClientUrl": "https://serviceapiot.altob.com.tw/api/TokenAuth/", | |||||
| "ClientSecret": "54b7da5a83e14b00" | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| html { | |||||
| font-size: 14px; | |||||
| } | |||||
| @media (min-width: 768px) { | |||||
| html { | |||||
| font-size: 16px; | |||||
| } | |||||
| } | |||||
| .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { | |||||
| box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; | |||||
| } | |||||
| html { | |||||
| position: relative; | |||||
| min-height: 100%; | |||||
| } | |||||
| body { | |||||
| margin-bottom: 60px; | |||||
| } | |||||
| @@ -0,0 +1,4 @@ | |||||
| // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification | |||||
| // for details on configuring this project to bundle and minify static web assets. | |||||
| // Write your JavaScript code. | |||||
| @@ -0,0 +1,22 @@ | |||||
| The MIT License (MIT) | |||||
| Copyright (c) 2011-2021 Twitter, Inc. | |||||
| Copyright (c) 2011-2021 The Bootstrap Authors | |||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | |||||
| of this software and associated documentation files (the "Software"), to deal | |||||
| in the Software without restriction, including without limitation the rights | |||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||||
| copies of the Software, and to permit persons to whom the Software is | |||||
| furnished to do so, subject to the following conditions: | |||||
| The above copyright notice and this permission notice shall be included in | |||||
| all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||||
| THE SOFTWARE. | |||||
| @@ -0,0 +1,427 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2021 The Bootstrap Authors | |||||
| * Copyright 2011-2021 Twitter, Inc. | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
| */ | |||||
| *, | |||||
| *::before, | |||||
| *::after { | |||||
| box-sizing: border-box; | |||||
| } | |||||
| @media (prefers-reduced-motion: no-preference) { | |||||
| :root { | |||||
| scroll-behavior: smooth; | |||||
| } | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| font-family: var(--bs-body-font-family); | |||||
| font-size: var(--bs-body-font-size); | |||||
| font-weight: var(--bs-body-font-weight); | |||||
| line-height: var(--bs-body-line-height); | |||||
| color: var(--bs-body-color); | |||||
| text-align: var(--bs-body-text-align); | |||||
| background-color: var(--bs-body-bg); | |||||
| -webkit-text-size-adjust: 100%; | |||||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
| } | |||||
| hr { | |||||
| margin: 1rem 0; | |||||
| color: inherit; | |||||
| background-color: currentColor; | |||||
| border: 0; | |||||
| opacity: 0.25; | |||||
| } | |||||
| hr:not([size]) { | |||||
| height: 1px; | |||||
| } | |||||
| h6, h5, h4, h3, h2, h1 { | |||||
| margin-top: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-weight: 500; | |||||
| line-height: 1.2; | |||||
| } | |||||
| h1 { | |||||
| font-size: calc(1.375rem + 1.5vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h1 { | |||||
| font-size: 2.5rem; | |||||
| } | |||||
| } | |||||
| h2 { | |||||
| font-size: calc(1.325rem + 0.9vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h2 { | |||||
| font-size: 2rem; | |||||
| } | |||||
| } | |||||
| h3 { | |||||
| font-size: calc(1.3rem + 0.6vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h3 { | |||||
| font-size: 1.75rem; | |||||
| } | |||||
| } | |||||
| h4 { | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h4 { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| h5 { | |||||
| font-size: 1.25rem; | |||||
| } | |||||
| h6 { | |||||
| font-size: 1rem; | |||||
| } | |||||
| p { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| abbr[title], | |||||
| abbr[data-bs-original-title] { | |||||
| -webkit-text-decoration: underline dotted; | |||||
| text-decoration: underline dotted; | |||||
| cursor: help; | |||||
| -webkit-text-decoration-skip-ink: none; | |||||
| text-decoration-skip-ink: none; | |||||
| } | |||||
| address { | |||||
| margin-bottom: 1rem; | |||||
| font-style: normal; | |||||
| line-height: inherit; | |||||
| } | |||||
| ol, | |||||
| ul { | |||||
| padding-left: 2rem; | |||||
| } | |||||
| ol, | |||||
| ul, | |||||
| dl { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| ol ol, | |||||
| ul ul, | |||||
| ol ul, | |||||
| ul ol { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| dt { | |||||
| font-weight: 700; | |||||
| } | |||||
| dd { | |||||
| margin-bottom: 0.5rem; | |||||
| margin-left: 0; | |||||
| } | |||||
| blockquote { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| b, | |||||
| strong { | |||||
| font-weight: bolder; | |||||
| } | |||||
| small { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| mark { | |||||
| padding: 0.2em; | |||||
| background-color: #fcf8e3; | |||||
| } | |||||
| sub, | |||||
| sup { | |||||
| position: relative; | |||||
| font-size: 0.75em; | |||||
| line-height: 0; | |||||
| vertical-align: baseline; | |||||
| } | |||||
| sub { | |||||
| bottom: -0.25em; | |||||
| } | |||||
| sup { | |||||
| top: -0.5em; | |||||
| } | |||||
| a { | |||||
| color: #0d6efd; | |||||
| text-decoration: underline; | |||||
| } | |||||
| a:hover { | |||||
| color: #0a58ca; | |||||
| } | |||||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||||
| color: inherit; | |||||
| text-decoration: none; | |||||
| } | |||||
| pre, | |||||
| code, | |||||
| kbd, | |||||
| samp { | |||||
| font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||||
| font-size: 1em; | |||||
| direction: ltr /* rtl:ignore */; | |||||
| unicode-bidi: bidi-override; | |||||
| } | |||||
| pre { | |||||
| display: block; | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| overflow: auto; | |||||
| font-size: 0.875em; | |||||
| } | |||||
| pre code { | |||||
| font-size: inherit; | |||||
| color: inherit; | |||||
| word-break: normal; | |||||
| } | |||||
| code { | |||||
| font-size: 0.875em; | |||||
| color: #d63384; | |||||
| word-wrap: break-word; | |||||
| } | |||||
| a > code { | |||||
| color: inherit; | |||||
| } | |||||
| kbd { | |||||
| padding: 0.2rem 0.4rem; | |||||
| font-size: 0.875em; | |||||
| color: #fff; | |||||
| background-color: #212529; | |||||
| border-radius: 0.2rem; | |||||
| } | |||||
| kbd kbd { | |||||
| padding: 0; | |||||
| font-size: 1em; | |||||
| font-weight: 700; | |||||
| } | |||||
| figure { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| img, | |||||
| svg { | |||||
| vertical-align: middle; | |||||
| } | |||||
| table { | |||||
| caption-side: bottom; | |||||
| border-collapse: collapse; | |||||
| } | |||||
| caption { | |||||
| padding-top: 0.5rem; | |||||
| padding-bottom: 0.5rem; | |||||
| color: #6c757d; | |||||
| text-align: left; | |||||
| } | |||||
| th { | |||||
| text-align: inherit; | |||||
| text-align: -webkit-match-parent; | |||||
| } | |||||
| thead, | |||||
| tbody, | |||||
| tfoot, | |||||
| tr, | |||||
| td, | |||||
| th { | |||||
| border-color: inherit; | |||||
| border-style: solid; | |||||
| border-width: 0; | |||||
| } | |||||
| label { | |||||
| display: inline-block; | |||||
| } | |||||
| button { | |||||
| border-radius: 0; | |||||
| } | |||||
| button:focus:not(:focus-visible) { | |||||
| outline: 0; | |||||
| } | |||||
| input, | |||||
| button, | |||||
| select, | |||||
| optgroup, | |||||
| textarea { | |||||
| margin: 0; | |||||
| font-family: inherit; | |||||
| font-size: inherit; | |||||
| line-height: inherit; | |||||
| } | |||||
| button, | |||||
| select { | |||||
| text-transform: none; | |||||
| } | |||||
| [role=button] { | |||||
| cursor: pointer; | |||||
| } | |||||
| select { | |||||
| word-wrap: normal; | |||||
| } | |||||
| select:disabled { | |||||
| opacity: 1; | |||||
| } | |||||
| [list]::-webkit-calendar-picker-indicator { | |||||
| display: none; | |||||
| } | |||||
| button, | |||||
| [type=button], | |||||
| [type=reset], | |||||
| [type=submit] { | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| button:not(:disabled), | |||||
| [type=button]:not(:disabled), | |||||
| [type=reset]:not(:disabled), | |||||
| [type=submit]:not(:disabled) { | |||||
| cursor: pointer; | |||||
| } | |||||
| ::-moz-focus-inner { | |||||
| padding: 0; | |||||
| border-style: none; | |||||
| } | |||||
| textarea { | |||||
| resize: vertical; | |||||
| } | |||||
| fieldset { | |||||
| min-width: 0; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| border: 0; | |||||
| } | |||||
| legend { | |||||
| float: left; | |||||
| width: 100%; | |||||
| padding: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| line-height: inherit; | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| legend { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| legend + * { | |||||
| clear: left; | |||||
| } | |||||
| ::-webkit-datetime-edit-fields-wrapper, | |||||
| ::-webkit-datetime-edit-text, | |||||
| ::-webkit-datetime-edit-minute, | |||||
| ::-webkit-datetime-edit-hour-field, | |||||
| ::-webkit-datetime-edit-day-field, | |||||
| ::-webkit-datetime-edit-month-field, | |||||
| ::-webkit-datetime-edit-year-field { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-inner-spin-button { | |||||
| height: auto; | |||||
| } | |||||
| [type=search] { | |||||
| outline-offset: -2px; | |||||
| -webkit-appearance: textfield; | |||||
| } | |||||
| /* rtl:raw: | |||||
| [type="tel"], | |||||
| [type="url"], | |||||
| [type="email"], | |||||
| [type="number"] { | |||||
| direction: ltr; | |||||
| } | |||||
| */ | |||||
| ::-webkit-search-decoration { | |||||
| -webkit-appearance: none; | |||||
| } | |||||
| ::-webkit-color-swatch-wrapper { | |||||
| padding: 0; | |||||
| } | |||||
| ::file-selector-button { | |||||
| font: inherit; | |||||
| } | |||||
| ::-webkit-file-upload-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| output { | |||||
| display: inline-block; | |||||
| } | |||||
| iframe { | |||||
| border: 0; | |||||
| } | |||||
| summary { | |||||
| display: list-item; | |||||
| cursor: pointer; | |||||
| } | |||||
| progress { | |||||
| vertical-align: baseline; | |||||
| } | |||||
| [hidden] { | |||||
| display: none !important; | |||||
| } | |||||
| /*# sourceMappingURL=bootstrap-reboot.css.map */ | |||||
| @@ -0,0 +1,8 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2021 The Bootstrap Authors | |||||
| * Copyright 2011-2021 Twitter, Inc. | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
| */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} | |||||
| /*# sourceMappingURL=bootstrap-reboot.min.css.map */ | |||||
| @@ -0,0 +1,424 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2021 The Bootstrap Authors | |||||
| * Copyright 2011-2021 Twitter, Inc. | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
| */ | |||||
| *, | |||||
| *::before, | |||||
| *::after { | |||||
| box-sizing: border-box; | |||||
| } | |||||
| @media (prefers-reduced-motion: no-preference) { | |||||
| :root { | |||||
| scroll-behavior: smooth; | |||||
| } | |||||
| } | |||||
| body { | |||||
| margin: 0; | |||||
| font-family: var(--bs-body-font-family); | |||||
| font-size: var(--bs-body-font-size); | |||||
| font-weight: var(--bs-body-font-weight); | |||||
| line-height: var(--bs-body-line-height); | |||||
| color: var(--bs-body-color); | |||||
| text-align: var(--bs-body-text-align); | |||||
| background-color: var(--bs-body-bg); | |||||
| -webkit-text-size-adjust: 100%; | |||||
| -webkit-tap-highlight-color: rgba(0, 0, 0, 0); | |||||
| } | |||||
| hr { | |||||
| margin: 1rem 0; | |||||
| color: inherit; | |||||
| background-color: currentColor; | |||||
| border: 0; | |||||
| opacity: 0.25; | |||||
| } | |||||
| hr:not([size]) { | |||||
| height: 1px; | |||||
| } | |||||
| h6, h5, h4, h3, h2, h1 { | |||||
| margin-top: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-weight: 500; | |||||
| line-height: 1.2; | |||||
| } | |||||
| h1 { | |||||
| font-size: calc(1.375rem + 1.5vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h1 { | |||||
| font-size: 2.5rem; | |||||
| } | |||||
| } | |||||
| h2 { | |||||
| font-size: calc(1.325rem + 0.9vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h2 { | |||||
| font-size: 2rem; | |||||
| } | |||||
| } | |||||
| h3 { | |||||
| font-size: calc(1.3rem + 0.6vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h3 { | |||||
| font-size: 1.75rem; | |||||
| } | |||||
| } | |||||
| h4 { | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| h4 { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| h5 { | |||||
| font-size: 1.25rem; | |||||
| } | |||||
| h6 { | |||||
| font-size: 1rem; | |||||
| } | |||||
| p { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| abbr[title], | |||||
| abbr[data-bs-original-title] { | |||||
| -webkit-text-decoration: underline dotted; | |||||
| text-decoration: underline dotted; | |||||
| cursor: help; | |||||
| -webkit-text-decoration-skip-ink: none; | |||||
| text-decoration-skip-ink: none; | |||||
| } | |||||
| address { | |||||
| margin-bottom: 1rem; | |||||
| font-style: normal; | |||||
| line-height: inherit; | |||||
| } | |||||
| ol, | |||||
| ul { | |||||
| padding-right: 2rem; | |||||
| } | |||||
| ol, | |||||
| ul, | |||||
| dl { | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| } | |||||
| ol ol, | |||||
| ul ul, | |||||
| ol ul, | |||||
| ul ol { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| dt { | |||||
| font-weight: 700; | |||||
| } | |||||
| dd { | |||||
| margin-bottom: 0.5rem; | |||||
| margin-right: 0; | |||||
| } | |||||
| blockquote { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| b, | |||||
| strong { | |||||
| font-weight: bolder; | |||||
| } | |||||
| small { | |||||
| font-size: 0.875em; | |||||
| } | |||||
| mark { | |||||
| padding: 0.2em; | |||||
| background-color: #fcf8e3; | |||||
| } | |||||
| sub, | |||||
| sup { | |||||
| position: relative; | |||||
| font-size: 0.75em; | |||||
| line-height: 0; | |||||
| vertical-align: baseline; | |||||
| } | |||||
| sub { | |||||
| bottom: -0.25em; | |||||
| } | |||||
| sup { | |||||
| top: -0.5em; | |||||
| } | |||||
| a { | |||||
| color: #0d6efd; | |||||
| text-decoration: underline; | |||||
| } | |||||
| a:hover { | |||||
| color: #0a58ca; | |||||
| } | |||||
| a:not([href]):not([class]), a:not([href]):not([class]):hover { | |||||
| color: inherit; | |||||
| text-decoration: none; | |||||
| } | |||||
| pre, | |||||
| code, | |||||
| kbd, | |||||
| samp { | |||||
| font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; | |||||
| font-size: 1em; | |||||
| direction: ltr ; | |||||
| unicode-bidi: bidi-override; | |||||
| } | |||||
| pre { | |||||
| display: block; | |||||
| margin-top: 0; | |||||
| margin-bottom: 1rem; | |||||
| overflow: auto; | |||||
| font-size: 0.875em; | |||||
| } | |||||
| pre code { | |||||
| font-size: inherit; | |||||
| color: inherit; | |||||
| word-break: normal; | |||||
| } | |||||
| code { | |||||
| font-size: 0.875em; | |||||
| color: #d63384; | |||||
| word-wrap: break-word; | |||||
| } | |||||
| a > code { | |||||
| color: inherit; | |||||
| } | |||||
| kbd { | |||||
| padding: 0.2rem 0.4rem; | |||||
| font-size: 0.875em; | |||||
| color: #fff; | |||||
| background-color: #212529; | |||||
| border-radius: 0.2rem; | |||||
| } | |||||
| kbd kbd { | |||||
| padding: 0; | |||||
| font-size: 1em; | |||||
| font-weight: 700; | |||||
| } | |||||
| figure { | |||||
| margin: 0 0 1rem; | |||||
| } | |||||
| img, | |||||
| svg { | |||||
| vertical-align: middle; | |||||
| } | |||||
| table { | |||||
| caption-side: bottom; | |||||
| border-collapse: collapse; | |||||
| } | |||||
| caption { | |||||
| padding-top: 0.5rem; | |||||
| padding-bottom: 0.5rem; | |||||
| color: #6c757d; | |||||
| text-align: right; | |||||
| } | |||||
| th { | |||||
| text-align: inherit; | |||||
| text-align: -webkit-match-parent; | |||||
| } | |||||
| thead, | |||||
| tbody, | |||||
| tfoot, | |||||
| tr, | |||||
| td, | |||||
| th { | |||||
| border-color: inherit; | |||||
| border-style: solid; | |||||
| border-width: 0; | |||||
| } | |||||
| label { | |||||
| display: inline-block; | |||||
| } | |||||
| button { | |||||
| border-radius: 0; | |||||
| } | |||||
| button:focus:not(:focus-visible) { | |||||
| outline: 0; | |||||
| } | |||||
| input, | |||||
| button, | |||||
| select, | |||||
| optgroup, | |||||
| textarea { | |||||
| margin: 0; | |||||
| font-family: inherit; | |||||
| font-size: inherit; | |||||
| line-height: inherit; | |||||
| } | |||||
| button, | |||||
| select { | |||||
| text-transform: none; | |||||
| } | |||||
| [role=button] { | |||||
| cursor: pointer; | |||||
| } | |||||
| select { | |||||
| word-wrap: normal; | |||||
| } | |||||
| select:disabled { | |||||
| opacity: 1; | |||||
| } | |||||
| [list]::-webkit-calendar-picker-indicator { | |||||
| display: none; | |||||
| } | |||||
| button, | |||||
| [type=button], | |||||
| [type=reset], | |||||
| [type=submit] { | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| button:not(:disabled), | |||||
| [type=button]:not(:disabled), | |||||
| [type=reset]:not(:disabled), | |||||
| [type=submit]:not(:disabled) { | |||||
| cursor: pointer; | |||||
| } | |||||
| ::-moz-focus-inner { | |||||
| padding: 0; | |||||
| border-style: none; | |||||
| } | |||||
| textarea { | |||||
| resize: vertical; | |||||
| } | |||||
| fieldset { | |||||
| min-width: 0; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| border: 0; | |||||
| } | |||||
| legend { | |||||
| float: right; | |||||
| width: 100%; | |||||
| padding: 0; | |||||
| margin-bottom: 0.5rem; | |||||
| font-size: calc(1.275rem + 0.3vw); | |||||
| line-height: inherit; | |||||
| } | |||||
| @media (min-width: 1200px) { | |||||
| legend { | |||||
| font-size: 1.5rem; | |||||
| } | |||||
| } | |||||
| legend + * { | |||||
| clear: right; | |||||
| } | |||||
| ::-webkit-datetime-edit-fields-wrapper, | |||||
| ::-webkit-datetime-edit-text, | |||||
| ::-webkit-datetime-edit-minute, | |||||
| ::-webkit-datetime-edit-hour-field, | |||||
| ::-webkit-datetime-edit-day-field, | |||||
| ::-webkit-datetime-edit-month-field, | |||||
| ::-webkit-datetime-edit-year-field { | |||||
| padding: 0; | |||||
| } | |||||
| ::-webkit-inner-spin-button { | |||||
| height: auto; | |||||
| } | |||||
| [type=search] { | |||||
| outline-offset: -2px; | |||||
| -webkit-appearance: textfield; | |||||
| } | |||||
| [type="tel"], | |||||
| [type="url"], | |||||
| [type="email"], | |||||
| [type="number"] { | |||||
| direction: ltr; | |||||
| } | |||||
| ::-webkit-search-decoration { | |||||
| -webkit-appearance: none; | |||||
| } | |||||
| ::-webkit-color-swatch-wrapper { | |||||
| padding: 0; | |||||
| } | |||||
| ::file-selector-button { | |||||
| font: inherit; | |||||
| } | |||||
| ::-webkit-file-upload-button { | |||||
| font: inherit; | |||||
| -webkit-appearance: button; | |||||
| } | |||||
| output { | |||||
| display: inline-block; | |||||
| } | |||||
| iframe { | |||||
| border: 0; | |||||
| } | |||||
| summary { | |||||
| display: list-item; | |||||
| cursor: pointer; | |||||
| } | |||||
| progress { | |||||
| vertical-align: baseline; | |||||
| } | |||||
| [hidden] { | |||||
| display: none !important; | |||||
| } | |||||
| /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ | |||||
| @@ -0,0 +1,8 @@ | |||||
| /*! | |||||
| * Bootstrap Reboot v5.1.0 (https://getbootstrap.com/) | |||||
| * Copyright 2011-2021 The Bootstrap Authors | |||||
| * Copyright 2011-2021 Twitter, Inc. | |||||
| * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) | |||||
| * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
| */*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} | |||||
| /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */ | |||||