MSI\User 1 месяц назад
Родитель
Сommit
20e6a765b3
100 измененных файлов: 35913 добавлений и 0 удалений
  1. +16
    -0
      .claude/settings.local.json
  2. +119
    -0
      Auth/ParkingAuthProvider.cs
  3. +133
    -0
      Controllers/HomeController.cs
  4. +34
    -0
      Controllers/LoginController.cs
  5. +454
    -0
      Controllers/ReportController.cs
  6. +25
    -0
      CouponReport.csproj
  7. +25
    -0
      CouponReport.sln
  8. +39
    -0
      Models/CouponMiddleware/CouponConsume.cs
  9. +29
    -0
      Models/CouponMiddleware/CouponDetail.cs
  10. +135
    -0
      Models/CouponMiddleware/CouponMiddlewareContext.cs
  11. +25
    -0
      Models/CouponMiddleware/CouponReportViewModel.cs
  12. +19
    -0
      Models/CouponMiddleware/CouponSetting.cs
  13. +21
    -0
      Models/CouponMiddleware/HospitalQrCode.cs
  14. +41
    -0
      Models/CouponMiddleware/Log.cs
  15. +25
    -0
      Models/CouponMiddleware/Member.cs
  16. +21
    -0
      Models/CouponMiddleware/MemberConsume.cs
  17. +8
    -0
      Models/ErrorViewModel.cs
  18. +11
    -0
      Models/LoginViewModel.cs
  19. +13
    -0
      Models/ParkingEyes/AggregatedCounter.cs
  20. +29
    -0
      Models/ParkingEyes/AlertLpr.cs
  21. +46
    -0
      Models/ParkingEyes/AlertSetting.cs
  22. +36
    -0
      Models/ParkingEyes/Camera.cs
  23. +81
    -0
      Models/ParkingEyes/CarEnter.cs
  24. +43
    -0
      Models/ParkingEyes/CarIdentityCounter.cs
  25. +46
    -0
      Models/ParkingEyes/ChargingBill.cs
  26. +51
    -0
      Models/ParkingEyes/ChargingBillDetail.cs
  27. +23
    -0
      Models/ParkingEyes/ChargingBillSession.cs
  28. +31
    -0
      Models/ParkingEyes/ChargingPile.cs
  29. +63
    -0
      Models/ParkingEyes/ChargingSession.cs
  30. +56
    -0
      Models/ParkingEyes/CommonQueue.cs
  31. +13
    -0
      Models/ParkingEyes/Counter.cs
  32. +30
    -0
      Models/ParkingEyes/Division.cs
  33. +61
    -0
      Models/ParkingEyes/DivisionDisplay.cs
  34. +66
    -0
      Models/ParkingEyes/EventLog.cs
  35. +15
    -0
      Models/ParkingEyes/Hash.cs
  36. +25
    -0
      Models/ParkingEyes/Job.cs
  37. +15
    -0
      Models/ParkingEyes/JobParameter.cs
  38. +15
    -0
      Models/ParkingEyes/JobQueue.cs
  39. +15
    -0
      Models/ParkingEyes/List.cs
  40. +13
    -0
      Models/ParkingEyes/MergedCarEnter.cs
  41. +22
    -0
      Models/ParkingEyes/ParkingColumn.cs
  42. +1050
    -0
      Models/ParkingEyes/ParkingEyesContext.cs
  43. +60
    -0
      Models/ParkingEyes/ParkingLot.cs
  44. +19
    -0
      Models/ParkingEyes/ParkingStationExtendSetting.cs
  45. +27
    -0
      Models/ParkingEyes/PaymentDevice.cs
  46. +33
    -0
      Models/ParkingEyes/PendingCarRecord.cs
  47. +51
    -0
      Models/ParkingEyes/RoadsideQueue.cs
  48. +55
    -0
      Models/ParkingEyes/RoadsideSignRecord.cs
  49. +133
    -0
      Models/ParkingEyes/RoadsideTicket.cs
  50. +9
    -0
      Models/ParkingEyes/Schema.cs
  51. +13
    -0
      Models/ParkingEyes/Server.cs
  52. +15
    -0
      Models/ParkingEyes/Set.cs
  53. +21
    -0
      Models/ParkingEyes/State.cs
  54. +28
    -0
      Models/ParkingEyes/TypeDef.cs
  55. +8
    -0
      Options/OauthOption.cs
  56. +64
    -0
      Program.cs
  57. +38
    -0
      Properties/launchSettings.json
  58. +76
    -0
      Service/ReportService.cs
  59. +29
    -0
      Views/Home/Index.cshtml
  60. +20
    -0
      Views/Login/LoginCallback.cshtml
  61. +7
    -0
      Views/Login/LoginCallback.cshtml.cs
  62. +132
    -0
      Views/Report/Index.cshtml
  63. +24
    -0
      Views/Shared/Error.cshtml
  64. +35
    -0
      Views/Shared/_Layout.cshtml
  65. +48
    -0
      Views/Shared/_Layout.cshtml.css
  66. +2
    -0
      Views/Shared/_ValidationScriptsPartial.cshtml
  67. +3
    -0
      Views/_ViewImports.cshtml
  68. +3
    -0
      Views/_ViewStart.cshtml
  69. +12
    -0
      appsettings.Development.json
  70. +17
    -0
      appsettings.json
  71. +22
    -0
      wwwroot/css/site.css
  72. Двоичные данные
      wwwroot/favicon.ico
  73. +4
    -0
      wwwroot/js/site.js
  74. +22
    -0
      wwwroot/lib/bootstrap/LICENSE
  75. +4997
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
  76. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
  77. +7
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
  78. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
  79. +4996
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
  80. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
  81. +7
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
  82. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
  83. +427
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css
  84. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
  85. +8
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css
  86. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
  87. +424
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css
  88. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map
  89. +8
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css
  90. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map
  91. +4866
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css
  92. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map
  93. +7
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css
  94. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map
  95. +4857
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css
  96. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map
  97. +7
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css
  98. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map
  99. +11221
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap.css
  100. +1
    -0
      wwwroot/lib/bootstrap/dist/css/bootstrap.css.map

+ 16
- 0
.claude/settings.local.json Просмотреть файл

@@ -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": []
}
}

+ 119
- 0
Auth/ParkingAuthProvider.cs Просмотреть файл

@@ -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; }
}

}

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

@@ -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; }
}
}

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

@@ -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();
}
}
}

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

@@ -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);
}
}

+ 25
- 0
CouponReport.csproj Просмотреть файл

@@ -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>

+ 25
- 0
CouponReport.sln Просмотреть файл

@@ -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

+ 39
- 0
Models/CouponMiddleware/CouponConsume.cs Просмотреть файл

@@ -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; }
}

+ 29
- 0
Models/CouponMiddleware/CouponDetail.cs Просмотреть файл

@@ -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; }
}

+ 135
- 0
Models/CouponMiddleware/CouponMiddlewareContext.cs Просмотреть файл

@@ -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);
}

+ 25
- 0
Models/CouponMiddleware/CouponReportViewModel.cs Просмотреть файл

@@ -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; }
}

+ 19
- 0
Models/CouponMiddleware/CouponSetting.cs Просмотреть файл

@@ -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; }
}

+ 21
- 0
Models/CouponMiddleware/HospitalQrCode.cs Просмотреть файл

@@ -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; }
}

+ 41
- 0
Models/CouponMiddleware/Log.cs Просмотреть файл

@@ -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; }
}

+ 25
- 0
Models/CouponMiddleware/Member.cs Просмотреть файл

@@ -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; }
}

+ 21
- 0
Models/CouponMiddleware/MemberConsume.cs Просмотреть файл

@@ -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; }
}

+ 8
- 0
Models/ErrorViewModel.cs Просмотреть файл

@@ -0,0 +1,8 @@
namespace LaneFlowReport.Models;

public class ErrorViewModel
{
public string? RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

+ 11
- 0
Models/LoginViewModel.cs Просмотреть файл

@@ -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; }
}

+ 13
- 0
Models/ParkingEyes/AggregatedCounter.cs Просмотреть файл

@@ -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; }
}

+ 29
- 0
Models/ParkingEyes/AlertLpr.cs Просмотреть файл

@@ -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>
/// 車牌號碼,以&quot;,&quot;分開
/// </summary>
public string? LprNo { get; set; }

/// <summary>
/// 運算欄位,無逗號的車牌
/// </summary>
public string? LprNo2 { get; set; }

public virtual AlertSetting? Alert { get; set; }
}

+ 46
- 0
Models/ParkingEyes/AlertSetting.cs Просмотреть файл

@@ -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>();
}

+ 36
- 0
Models/ParkingEyes/Camera.cs Просмотреть файл

@@ -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>();
}

+ 81
- 0
Models/ParkingEyes/CarEnter.cs Просмотреть файл

@@ -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; }
}

+ 43
- 0
Models/ParkingEyes/CarIdentityCounter.cs Просмотреть файл

@@ -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; }
}

+ 46
- 0
Models/ParkingEyes/ChargingBill.cs Просмотреть файл

@@ -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>();
}

+ 51
- 0
Models/ParkingEyes/ChargingBillDetail.cs Просмотреть файл

@@ -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!;
}

+ 23
- 0
Models/ParkingEyes/ChargingBillSession.cs Просмотреть файл

@@ -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!;
}

+ 31
- 0
Models/ParkingEyes/ChargingPile.cs Просмотреть файл

@@ -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; }
}

+ 63
- 0
Models/ParkingEyes/ChargingSession.cs Просмотреть файл

@@ -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>();
}

+ 56
- 0
Models/ParkingEyes/CommonQueue.cs Просмотреть файл

@@ -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; }
}

+ 13
- 0
Models/ParkingEyes/Counter.cs Просмотреть файл

@@ -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; }
}

+ 30
- 0
Models/ParkingEyes/Division.cs Просмотреть файл

@@ -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>();
}

+ 61
- 0
Models/ParkingEyes/DivisionDisplay.cs Просмотреть файл

@@ -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!;
}

+ 66
- 0
Models/ParkingEyes/EventLog.cs Просмотреть файл

@@ -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; }
}

+ 15
- 0
Models/ParkingEyes/Hash.cs Просмотреть файл

@@ -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; }
}

+ 25
- 0
Models/ParkingEyes/Job.cs Просмотреть файл

@@ -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>();
}

+ 15
- 0
Models/ParkingEyes/JobParameter.cs Просмотреть файл

@@ -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!;
}

+ 15
- 0
Models/ParkingEyes/JobQueue.cs Просмотреть файл

@@ -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; }
}

+ 15
- 0
Models/ParkingEyes/List.cs Просмотреть файл

@@ -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; }
}

+ 13
- 0
Models/ParkingEyes/MergedCarEnter.cs Просмотреть файл

@@ -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; }
}

+ 22
- 0
Models/ParkingEyes/ParkingColumn.cs Просмотреть файл

@@ -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; }
}

+ 1050
- 0
Models/ParkingEyes/ParkingEyesContext.cs
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 60
- 0
Models/ParkingEyes/ParkingLot.cs Просмотреть файл

@@ -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>();
}

+ 19
- 0
Models/ParkingEyes/ParkingStationExtendSetting.cs Просмотреть файл

@@ -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; }
}

+ 27
- 0
Models/ParkingEyes/PaymentDevice.cs Просмотреть файл

@@ -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!;
}

+ 33
- 0
Models/ParkingEyes/PendingCarRecord.cs Просмотреть файл

@@ -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!;
}

+ 51
- 0
Models/ParkingEyes/RoadsideQueue.cs Просмотреть файл

@@ -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; }
}

+ 55
- 0
Models/ParkingEyes/RoadsideSignRecord.cs Просмотреть файл

@@ -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!;
}

+ 133
- 0
Models/ParkingEyes/RoadsideTicket.cs Просмотреть файл

@@ -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>();
}

+ 9
- 0
Models/ParkingEyes/Schema.cs Просмотреть файл

@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;

namespace CouponReport.Models.Parkingeyes;

public partial class Schema
{
public int Version { get; set; }
}

+ 13
- 0
Models/ParkingEyes/Server.cs Просмотреть файл

@@ -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; }
}

+ 15
- 0
Models/ParkingEyes/Set.cs Просмотреть файл

@@ -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; }
}

+ 21
- 0
Models/ParkingEyes/State.cs Просмотреть файл

@@ -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!;
}

+ 28
- 0
Models/ParkingEyes/TypeDef.cs Просмотреть файл

@@ -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>();
}

+ 8
- 0
Options/OauthOption.cs Просмотреть файл

@@ -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; }
}
}

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

@@ -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();

+ 38
- 0
Properties/launchSettings.json Просмотреть файл

@@ -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"
}
}
}
}

+ 76
- 0
Service/ReportService.cs Просмотреть файл

@@ -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());
}
}
}

+ 29
- 0
Views/Home/Index.cshtml Просмотреть файл

@@ -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>

+ 20
- 0
Views/Login/LoginCallback.cshtml Просмотреть файл

@@ -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>

+ 7
- 0
Views/Login/LoginCallback.cshtml.cs Просмотреть файл

@@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace LaneFlowReport.Views.Login {
public class LoginCallback: PageModel {
public void OnGet() {}
}
}

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

@@ -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>&nbsp;</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>
}

+ 24
- 0
Views/Shared/Error.cshtml Просмотреть файл

@@ -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>

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

@@ -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">
&copy; 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>

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

@@ -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;
}

+ 2
- 0
Views/Shared/_ValidationScriptsPartial.cshtml Просмотреть файл

@@ -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>

+ 3
- 0
Views/_ViewImports.cshtml Просмотреть файл

@@ -0,0 +1,3 @@
@using CouponReport
@using CouponReport.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

+ 3
- 0
Views/_ViewStart.cshtml Просмотреть файл

@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

+ 12
- 0
appsettings.Development.json Просмотреть файл

@@ -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"
}
}

+ 17
- 0
appsettings.json Просмотреть файл

@@ -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"
}
}
}

+ 22
- 0
wwwroot/css/site.css Просмотреть файл

@@ -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;
}

Двоичные данные
wwwroot/favicon.ico Просмотреть файл

До После

+ 4
- 0
wwwroot/js/site.js Просмотреть файл

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

+ 22
- 0
wwwroot/lib/bootstrap/LICENSE Просмотреть файл

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

+ 4997
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 7
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 4996
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 7
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 427
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css Просмотреть файл

@@ -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 */

+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 8
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css Просмотреть файл

@@ -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 */

+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 424
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css Просмотреть файл

@@ -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 */

+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 8
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css Просмотреть файл

@@ -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 */

+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 4866
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 7
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 4857
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 7
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 11221
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap.css
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 1
- 0
wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


Некоторые файлы не были показаны из-за большого количества измененных файлов

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